From f095913538a56fc32eddd1ae84d10c27fa8afe13 Mon Sep 17 00:00:00 2001 From: nymkappa <1612910616@pm.me> Date: Mon, 8 Jan 2024 11:48:55 +0100 Subject: [PATCH 1/4] [bitcoin core] add internal routes to bitcoin core rpc --- .../src/api/bitcoin/bitcoin-core.routes.ts | 221 ++++++++++++++++++ backend/src/index.ts | 2 + backend/src/rpc-api/commands.ts | 1 + 3 files changed, 224 insertions(+) create mode 100644 backend/src/api/bitcoin/bitcoin-core.routes.ts diff --git a/backend/src/api/bitcoin/bitcoin-core.routes.ts b/backend/src/api/bitcoin/bitcoin-core.routes.ts new file mode 100644 index 000000000..dbdcced1f --- /dev/null +++ b/backend/src/api/bitcoin/bitcoin-core.routes.ts @@ -0,0 +1,221 @@ +import { Application, NextFunction, Request, Response } from 'express'; +import logger from '../../logger'; +import bitcoinClient from './bitcoin-client'; + +/** + * Define a set of routes used by the accelerator server + * Those routes are not designed to be public + */ +class BitcoinBackendRoutes { + private static tag = 'BitcoinBackendRoutes'; + + public initRoutes(app: Application) { + app + .get('/api/internal/bitcoinCore/' + 'getMempoolEntry', this.disableCache, this.$getMempoolEntry) + .post('/api/internal/bitcoinCore/' + 'decodeRawTransaction', this.disableCache, this.$decodeRawTransaction) + .get('/api/internal/bitcoinCore/' + 'getRawTransaction', this.disableCache, this.$getRawTransaction) + .post('/api/internal/bitcoinCore/' + 'sendRawTransaction', this.disableCache, this.$sendRawTransaction) + .post('/api/internal/bitcoinCore/' + 'testMempoolAccept', this.disableCache, this.$testMempoolAccept) + .get('/api/internal/bitcoinCore/' + 'getMempoolAncestors', this.disableCache, this.$getMempoolAncestors) + .get('/api/internal/bitcoinCore/' + 'getBlock', this.disableCache, this.$getBlock) + .get('/api/internal/bitcoinCore/' + 'getBlockHash', this.disableCache, this.$getBlockHash) + .get('/api/internal/bitcoinCore/' + 'getBlockCount', this.disableCache, this.$getBlockCount) + ; + } + + /** + * Disable caching for bitcoin core routes + * + * @param req + * @param res + * @param next + */ + private disableCache(req: Request, res: Response, next: NextFunction): void { + res.setHeader('Pragma', 'no-cache'); + res.setHeader('Cache-control', 'private, no-store, no-cache, must-revalidate, proxy-revalidate, max-age=0'); + res.setHeader('expires', -1); + next(); + } + + /** + * Exeption handler to return proper details to the accelerator server + * + * @param e + * @param fnName + * @param res + */ + private static handleException(e: any, fnName: string, res: Response): void { + if (typeof(e.code) === 'number') { + res.status(400).send(JSON.stringify(e, ['code', 'message'])); + } else { + const err = `exception in ${fnName}. ${e}. Details: ${JSON.stringify(e, ['code', 'message'])}`; + logger.err(err, BitcoinBackendRoutes.tag); + res.status(500).send(err); + } + } + + private async $getMempoolEntry(req: Request, res: Response): Promise { + const txid = req.query.txid; + try { + if (typeof(txid) !== 'string' || txid.length !== 64) { + res.status(400).send(`invalid param txid ${txid}. must be a string of 64 char`); + return; + } + const mempoolEntry = await bitcoinClient.getMempoolEntry(txid); + if (!mempoolEntry) { + res.status(404).send(`no mempool entry found for txid ${txid}`); + return; + } + res.status(200).send(mempoolEntry); + } catch (e: any) { + BitcoinBackendRoutes.handleException(e, 'getMempoolEntry', res); + } + } + + private async $decodeRawTransaction(req: Request, res: Response): Promise { + const rawTx = req.body.rawTx; + try { + if (typeof(rawTx) !== 'string') { + res.status(400).send(`invalid param rawTx ${rawTx}. must be a string`); + return; + } + const decodedTx = await bitcoinClient.decodeRawTransaction(rawTx); + if (!decodedTx) { + res.status(400).send(`unable to decode rawTx ${rawTx}`); + return; + } + res.status(200).send(decodedTx); + } catch (e: any) { + BitcoinBackendRoutes.handleException(e, 'decodeRawTransaction', res); + } + } + + private async $getRawTransaction(req: Request, res: Response): Promise { + const txid = req.query.txid; + try { + if (typeof(txid) !== 'string' || txid.length !== 64) { + res.status(400).send(`invalid param txid ${txid}. must be a string of 64 char`); + return; + } + const decodedTx = await bitcoinClient.getRawTransaction(txid); + if (!decodedTx) { + res.status(400).send(`unable to get raw transaction for txid ${txid}`); + return; + } + res.status(200).send(decodedTx); + } catch (e: any) { + BitcoinBackendRoutes.handleException(e, 'decodeRawTransaction', res); + } + } + + private async $sendRawTransaction(req: Request, res: Response): Promise { + const rawTx = req.body.rawTx; + try { + if (typeof(rawTx) !== 'string') { + res.status(400).send(`invalid param rawTx ${rawTx}. must be a string`); + return; + } + const txHex = await bitcoinClient.sendRawTransaction(rawTx); + if (!txHex) { + res.status(400).send(`unable to send rawTx ${rawTx}`); + return; + } + res.status(200).send(txHex); + } catch (e: any) { + BitcoinBackendRoutes.handleException(e, 'sendRawTransaction', res); + } + } + + private async $testMempoolAccept(req: Request, res: Response): Promise { + const rawTx = req.body.rawTx; + try { + if (typeof(rawTx) !== 'string') { + res.status(400).send(`invalid param rawTx ${rawTx}. must be a string`); + return; + } + const txHex = await bitcoinClient.testMempoolAccept([rawTx]); + if (typeof(txHex) !== 'object' || txHex.length === 0) { + res.status(400).send(`testmempoolaccept failed for raw tx ${rawTx}, got an empty result`); + return; + } + res.status(200).send(txHex); + } catch (e: any) { + BitcoinBackendRoutes.handleException(e, 'testMempoolAccept', res); + } + } + + private async $getMempoolAncestors(req: Request, res: Response): Promise { + const txid = req.query.txid; + try { + if (typeof(txid) !== 'string' || txid.length !== 64) { + res.status(400).send(`invalid param txid ${txid}. must be a string of 64 char`); + return; + } + const decodedTx = await bitcoinClient.getMempoolAncestors(txid); + if (!decodedTx) { + res.status(400).send(`unable to get mempool ancestors for txid ${txid}`); + return; + } + res.status(200).send(decodedTx); + } catch (e: any) { + BitcoinBackendRoutes.handleException(e, 'getMempoolAncestors', res); + } + } + + private async $getBlock(req: Request, res: Response): Promise { + const blockHash = req.query.hash; + try { + if (typeof(blockHash) !== 'string' || blockHash.length !== 64) { + res.status(400).send(`invalid param blockHash ${blockHash}. must be a string of 64 char`); + return; + } + const block = await bitcoinClient.getBlock(blockHash); + if (!block) { + res.status(400).send(`unable to get block for block hash ${blockHash}`); + return; + } + res.status(200).send(block); + } catch (e: any) { + BitcoinBackendRoutes.handleException(e, 'getBlock', res); + } + } + + private async $getBlockHash(req: Request, res: Response): Promise { + const blockHeight = req.query.height; + try { + if (typeof(blockHeight) !== 'string') { + res.status(400).send(`invalid param blockHeight ${blockHeight}, must be a string representing an integer`); + return; + } + const blockHeightNumber = parseInt(blockHeight, 10); + if (!blockHeightNumber) { + res.status(400).send(`invalid param blockHeight ${blockHeight}. must be a valid integer`); + return; + } + + const block = await bitcoinClient.getBlockHash(blockHeightNumber); + if (!block) { + res.status(400).send(`unable to get block hash for block height ${blockHeightNumber}`); + return; + } + res.status(200).send(block); + } catch (e: any) { + BitcoinBackendRoutes.handleException(e, 'getBlockHash', res); + } + } + + private async $getBlockCount(req: Request, res: Response): Promise { + try { + const count = await bitcoinClient.getBlockCount(); + if (!count) { + res.status(400).send(`unable to get block count`); + return; + } + res.status(200).send(`${count}`); + } catch (e: any) { + BitcoinBackendRoutes.handleException(e, 'getBlockCount', res); + } + } +} + +export default new BitcoinBackendRoutes \ No newline at end of file diff --git a/backend/src/index.ts b/backend/src/index.ts index 44fe87e3a..a7b2ad4df 100644 --- a/backend/src/index.ts +++ b/backend/src/index.ts @@ -44,6 +44,7 @@ import v8 from 'v8'; import { formatBytes, getBytesUnit } from './utils/format'; import redisCache from './api/redis-cache'; import accelerationApi from './api/services/acceleration'; +import bitcoinCoreRoutes from './api/bitcoin/bitcoin-core.routes'; class Server { private wss: WebSocket.Server | undefined; @@ -282,6 +283,7 @@ class Server { setUpHttpApiRoutes(): void { bitcoinRoutes.initRoutes(this.app); + bitcoinCoreRoutes.initRoutes(this.app); pricesRoutes.initRoutes(this.app); if (config.STATISTICS.ENABLED && config.DATABASE.ENABLED && config.MEMPOOL.ENABLED) { statisticsRoutes.initRoutes(this.app); diff --git a/backend/src/rpc-api/commands.ts b/backend/src/rpc-api/commands.ts index 78f5e12f4..ecfb2ed7c 100644 --- a/backend/src/rpc-api/commands.ts +++ b/backend/src/rpc-api/commands.ts @@ -91,4 +91,5 @@ module.exports = { walletPassphraseChange: 'walletpassphrasechange', getTxoutSetinfo: 'gettxoutsetinfo', getIndexInfo: 'getindexinfo', + testMempoolAccept: 'testmempoolaccept', }; From b38bbb95138f689de373823db3e45bbdbfa4fb3e Mon Sep 17 00:00:00 2001 From: nymkappa <1612910616@pm.me> Date: Tue, 9 Jan 2024 11:47:59 +0100 Subject: [PATCH 2/4] [bitcoin core] add missing verbose params to bitcoin core internal routes --- .../src/api/bitcoin/bitcoin-core.routes.ts | 51 +++++++++++++++---- 1 file changed, 42 insertions(+), 9 deletions(-) diff --git a/backend/src/api/bitcoin/bitcoin-core.routes.ts b/backend/src/api/bitcoin/bitcoin-core.routes.ts index dbdcced1f..d0aa9092a 100644 --- a/backend/src/api/bitcoin/bitcoin-core.routes.ts +++ b/backend/src/api/bitcoin/bitcoin-core.routes.ts @@ -92,12 +92,23 @@ class BitcoinBackendRoutes { private async $getRawTransaction(req: Request, res: Response): Promise { const txid = req.query.txid; + const verbose = req.query.verbose; try { if (typeof(txid) !== 'string' || txid.length !== 64) { res.status(400).send(`invalid param txid ${txid}. must be a string of 64 char`); return; } - const decodedTx = await bitcoinClient.getRawTransaction(txid); + if (typeof(verbose) !== 'string') { + res.status(400).send(`invalid param verbose ${verbose}. must be a string representing an integer`); + return; + } + const verboseNumber = parseInt(verbose, 10); + if (typeof(verboseNumber) !== 'number') { + res.status(400).send(`invalid param verbose ${verbose}. must be a valid integer`); + return; + } + + const decodedTx = await bitcoinClient.getRawTransaction(txid, verboseNumber); if (!decodedTx) { res.status(400).send(`unable to get raw transaction for txid ${txid}`); return; @@ -127,15 +138,15 @@ class BitcoinBackendRoutes { } private async $testMempoolAccept(req: Request, res: Response): Promise { - const rawTx = req.body.rawTx; + const rawTxs = req.body.rawTxs; try { - if (typeof(rawTx) !== 'string') { - res.status(400).send(`invalid param rawTx ${rawTx}. must be a string`); + if (typeof(rawTxs) !== 'object') { + res.status(400).send(`invalid param rawTxs ${JSON.stringify(rawTxs)}. must be an array of string`); return; } - const txHex = await bitcoinClient.testMempoolAccept([rawTx]); + const txHex = await bitcoinClient.testMempoolAccept(rawTxs); if (typeof(txHex) !== 'object' || txHex.length === 0) { - res.status(400).send(`testmempoolaccept failed for raw tx ${rawTx}, got an empty result`); + res.status(400).send(`testmempoolaccept failed for raw txs ${JSON.stringify(rawTxs)}, got an empty result`); return; } res.status(200).send(txHex); @@ -146,12 +157,23 @@ class BitcoinBackendRoutes { private async $getMempoolAncestors(req: Request, res: Response): Promise { const txid = req.query.txid; + const verbose = req.query.verbose; try { if (typeof(txid) !== 'string' || txid.length !== 64) { res.status(400).send(`invalid param txid ${txid}. must be a string of 64 char`); return; } - const decodedTx = await bitcoinClient.getMempoolAncestors(txid); + if (typeof(verbose) !== 'string') { + res.status(400).send(`invalid param verbose ${verbose}. must be a string representing an integer`); + return; + } + const verboseNumber = parseInt(verbose, 10); + if (typeof(verboseNumber) !== 'number') { + res.status(400).send(`invalid param verbose ${verbose}. must be a valid integer`); + return; + } + + const decodedTx = await bitcoinClient.getMempoolAncestors(txid, verboseNumber); if (!decodedTx) { res.status(400).send(`unable to get mempool ancestors for txid ${txid}`); return; @@ -164,12 +186,23 @@ class BitcoinBackendRoutes { private async $getBlock(req: Request, res: Response): Promise { const blockHash = req.query.hash; + const verbosity = req.query.verbosity; try { if (typeof(blockHash) !== 'string' || blockHash.length !== 64) { res.status(400).send(`invalid param blockHash ${blockHash}. must be a string of 64 char`); return; } - const block = await bitcoinClient.getBlock(blockHash); + if (typeof(verbosity) !== 'string') { + res.status(400).send(`invalid param verbosity ${verbosity}. must be a string representing an integer`); + return; + } + const verbosityNumber = parseInt(verbosity, 10); + if (typeof(verbosityNumber) !== 'number') { + res.status(400).send(`invalid param verbosity ${verbosity}. must be a valid integer`); + return; + } + + const block = await bitcoinClient.getBlock(blockHash, verbosityNumber); if (!block) { res.status(400).send(`unable to get block for block hash ${blockHash}`); return; @@ -188,7 +221,7 @@ class BitcoinBackendRoutes { return; } const blockHeightNumber = parseInt(blockHeight, 10); - if (!blockHeightNumber) { + if (typeof(blockHeightNumber) !== 'number') { res.status(400).send(`invalid param blockHeight ${blockHeight}. must be a valid integer`); return; } From f161f7e8e271e92ce29cf2b10e60a7e44172b6ef Mon Sep 17 00:00:00 2001 From: nymkappa <1612910616@pm.me> Date: Fri, 12 Jan 2024 16:38:11 +0100 Subject: [PATCH 3/4] [bitcoin core] fix getMempoolAncestors verbose param --- backend/src/api/bitcoin/bitcoin-core.routes.ts | 17 ++++++----------- 1 file changed, 6 insertions(+), 11 deletions(-) diff --git a/backend/src/api/bitcoin/bitcoin-core.routes.ts b/backend/src/api/bitcoin/bitcoin-core.routes.ts index d0aa9092a..edc32d0fe 100644 --- a/backend/src/api/bitcoin/bitcoin-core.routes.ts +++ b/backend/src/api/bitcoin/bitcoin-core.routes.ts @@ -163,22 +163,17 @@ class BitcoinBackendRoutes { res.status(400).send(`invalid param txid ${txid}. must be a string of 64 char`); return; } - if (typeof(verbose) !== 'string') { - res.status(400).send(`invalid param verbose ${verbose}. must be a string representing an integer`); + if (typeof(verbose) !== 'string' || (verbose !== 'true' && verbose !== 'false')) { + res.status(400).send(`invalid param verbose ${verbose}. must be a string ('true' | 'false')`); return; } - const verboseNumber = parseInt(verbose, 10); - if (typeof(verboseNumber) !== 'number') { - res.status(400).send(`invalid param verbose ${verbose}. must be a valid integer`); - return; - } - - const decodedTx = await bitcoinClient.getMempoolAncestors(txid, verboseNumber); - if (!decodedTx) { + + const ancestors = await bitcoinClient.getMempoolAncestors(txid, verbose === 'true' ? true : false); + if (!ancestors) { res.status(400).send(`unable to get mempool ancestors for txid ${txid}`); return; } - res.status(200).send(decodedTx); + res.status(200).send(ancestors); } catch (e: any) { BitcoinBackendRoutes.handleException(e, 'getMempoolAncestors', res); } From ed9826b2d890a4e5dc58f8cf20f7435212d88bb4 Mon Sep 17 00:00:00 2001 From: nymkappa <1612910616@pm.me> Date: Mon, 15 Jan 2024 16:12:58 +0100 Subject: [PATCH 4/4] [bitcoin core] internal core api from camel case to kebab case --- backend/src/api/bitcoin/bitcoin-core.routes.ts | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/backend/src/api/bitcoin/bitcoin-core.routes.ts b/backend/src/api/bitcoin/bitcoin-core.routes.ts index edc32d0fe..7933dc17b 100644 --- a/backend/src/api/bitcoin/bitcoin-core.routes.ts +++ b/backend/src/api/bitcoin/bitcoin-core.routes.ts @@ -11,15 +11,15 @@ class BitcoinBackendRoutes { public initRoutes(app: Application) { app - .get('/api/internal/bitcoinCore/' + 'getMempoolEntry', this.disableCache, this.$getMempoolEntry) - .post('/api/internal/bitcoinCore/' + 'decodeRawTransaction', this.disableCache, this.$decodeRawTransaction) - .get('/api/internal/bitcoinCore/' + 'getRawTransaction', this.disableCache, this.$getRawTransaction) - .post('/api/internal/bitcoinCore/' + 'sendRawTransaction', this.disableCache, this.$sendRawTransaction) - .post('/api/internal/bitcoinCore/' + 'testMempoolAccept', this.disableCache, this.$testMempoolAccept) - .get('/api/internal/bitcoinCore/' + 'getMempoolAncestors', this.disableCache, this.$getMempoolAncestors) - .get('/api/internal/bitcoinCore/' + 'getBlock', this.disableCache, this.$getBlock) - .get('/api/internal/bitcoinCore/' + 'getBlockHash', this.disableCache, this.$getBlockHash) - .get('/api/internal/bitcoinCore/' + 'getBlockCount', this.disableCache, this.$getBlockCount) + .get('/api/internal/bitcoin-core/' + 'get-mempool-entry', this.disableCache, this.$getMempoolEntry) + .post('/api/internal/bitcoin-core/' + 'decode-raw-transaction', this.disableCache, this.$decodeRawTransaction) + .get('/api/internal/bitcoin-core/' + 'get-raw-transaction', this.disableCache, this.$getRawTransaction) + .post('/api/internal/bitcoin-core/' + 'send-raw-transaction', this.disableCache, this.$sendRawTransaction) + .post('/api/internal/bitcoin-core/' + 'test-mempool-accept', this.disableCache, this.$testMempoolAccept) + .get('/api/internal/bitcoin-core/' + 'get-mempool-ancestors', this.disableCache, this.$getMempoolAncestors) + .get('/api/internal/bitcoin-core/' + 'get-block', this.disableCache, this.$getBlock) + .get('/api/internal/bitcoin-core/' + 'get-block-hash', this.disableCache, this.$getBlockHash) + .get('/api/internal/bitcoin-core/' + 'get-block-count', this.disableCache, this.$getBlockCount) ; }