diff --git a/src/app.js b/src/app.js index 82014dc..3e7fad5 100644 --- a/src/app.js +++ b/src/app.js @@ -24,7 +24,7 @@ const jwtAuth = require("./middleware/jwt-auth") // v3 const healthCheckV3 = require("./routes/v3/health-check") -const blockchainV3 = require("./routes/v3/full-node/blockchain") +const BlockchainV3 = require("./routes/v3/full-node/blockchain") const controlV3 = require("./routes/v3/full-node/control") const miningV3 = require("./routes/v3/full-node/mining") const networkV3 = require("./routes/v3/full-node/network") @@ -37,6 +37,9 @@ const Ninsight = require("./routes/v3/ninsight") require("dotenv").config() +// Instantiate route libraries. +const blockchainV3 = new BlockchainV3() + const app = express() app.locals.env = process.env diff --git a/src/routes/v3/full-node/blockchain.js b/src/routes/v3/full-node/blockchain.js index 201244a..8c34ff6 100644 --- a/src/routes/v3/full-node/blockchain.js +++ b/src/routes/v3/full-node/blockchain.js @@ -1,1152 +1,885 @@ /* - TODO - - Add blockhash functionality back into getTxOutProof + A library for interacting with the Full Node */ "use strict" const express = require("express") const router = express.Router() -const axios = require("axios") -const routeUtils = require("../route-utils") +const axios = require("axios") const wlogger = require("../../../util/winston-logging") +const RouteUtils = require("../route-utils2") +const routeUtils = new RouteUtils() + // Used to convert error messages to strings, to safely pass to users. const util = require("util") util.inspect.defaultOptions = { depth: 1 } -// Define routes. -router.get("/", root) -router.get("/getBestBlockHash", getBestBlockHash) -// Dev Note: getBlock/:hash ommited because its the same as block/detailsByHash -//router.get("/getBlock/:hash", getBlock) -router.get("/getBlockchainInfo", getBlockchainInfo) -router.get("/getBlockCount", getBlockCount) -router.get("/getBlockHeader/:hash", getBlockHeaderSingle) -router.post("/getBlockHeader", getBlockHeaderBulk) -router.get("/getChainTips", getChainTips) -router.get("/getDifficulty", getDifficulty) -router.get("/getMempoolEntry/:txid", getMempoolEntrySingle) -router.post("/getMempoolEntry", getMempoolEntryBulk) -router.get("/getMempoolAncestors/:txid", getMempoolAncestorsSingle) -router.get("/getMempoolInfo", getMempoolInfo) -router.get("/getRawMempool", getRawMempool) -router.get("/getTxOut/:txid/:n", getTxOut) -router.get("/getTxOutProof/:txid", getTxOutProofSingle) -router.post("/getTxOutProof", getTxOutProofBulk) -router.get("/verifyTxOutProof/:proof", verifyTxOutProofSingle) -router.post("/verifyTxOutProof", verifyTxOutProofBulk) +const BCHJS = require("@chris.troutner/bch-js") +const bchjs = new BCHJS() -function root(req, res, next) { - return res.json({ status: "blockchain" }) -} +let _this -/** - * @api {get} /blockchain/getBestBlockHash Get best block hash - * @apiName GetBestBlockHash - * @apiGroup Blockchain - * @apiDescription Returns the hash of the best (tip) block in the longest - * block chain. - * - * @apiExample Example usage: - * curl -X GET "https://mainnet.bchjs.cash/v3/blockchain/getBestBlockHash" -H "accept: application/json" - * - * @apiSuccess {String} bestBlockHash 000000000000000002bc884334336d99c9a9c616670a9244c6a8c1fc35aa91a1 - */ -async function getBestBlockHash(req, res, next) { - try { - // Axios options - const options = routeUtils.getAxiosOptions() - options.data.id = "getbestblockhash" - options.data.method = "getbestblockhash" - options.data.params = [] +class Blockchain { + constructor() { + _this = this - const response = await axios.request(options) + this.bchjs = bchjs + this.axios = axios + this.routeUtils = routeUtils - return res.json(response.data.result) - } catch (err) { - // Attempt to decode the error message. - const { msg, status } = routeUtils.decodeError(err) - if (msg) { - res.status(status) - return res.json({ error: msg }) - } - - // Write out error to error log. - //logger.error(`Error in rawtransactions/decodeRawTransaction: `, err) - wlogger.error(`Error in blockchain.ts/getBestBlockHash().`, err) - - res.status(500) - return res.json({ error: util.inspect(err) }) - } -} - -/** - * @api {get} /blockchain/getBlockchainInfo Get blockchain info - * @apiName GetBlockchainInfo - * @apiGroup Blockchain - * @apiDescription Returns an object containing various state info regarding blockchain processing. - * - * @apiExample Example usage: - * curl -X GET "https://mainnet.bchjs.cash/v3/blockchain/getBlockchainInfo" -H "accept: application/json" - * - * @apiSuccess {Object} object Object containing data - * @apiSuccess {String} object.chain "main" - * @apiSuccess {Number} object.blocks 561838 - * @apiSuccess {Number} object.headers 561838 - * @apiSuccess {String} object.bestblockhash "000000000000000002307dd38cd01c7308b8febfcdf5772cf087b5bb023d55bc" - * @apiSuccess {Number} object.difficulty 246585566638.1496 - * @apiSuccess {String} object.mediantime 1545402693 - * @apiSuccess {Number} object.verificationprogress 0.999998831622689 - * @apiSuccess {Boolean} object.chainwork "000000000000000000000000000000000000000000d8c09a8ab7262080266b3e" - * @apiSuccess {Number} object.pruned false - * @apiSuccess {Array} object.softforks Array of objects - * @apiSuccess {String} object.softforks.id "bip34" - * @apiSuccess {String} object.softforks.version 2 - * @apiSuccess {Object} object.softforks.reject - * @apiSuccess {String} object.softforks.reject.status true - */ -async function getBlockchainInfo(req, res, next) { - try { - const { - BitboxHTTP, - username, - password, - requestConfig - } = routeUtils.setEnvVars() - - requestConfig.data.id = "getblockchaininfo" - requestConfig.data.method = "getblockchaininfo" - requestConfig.data.params = [] - - const response = await BitboxHTTP(requestConfig) - - return res.json(response.data.result) - } catch (err) { - // Attempt to decode the error message. - const { msg, status } = routeUtils.decodeError(err) - if (msg) { - res.status(status) - return res.json({ error: msg }) - } - - // Write out error to error log. - //logger.error(`Error in rawtransactions/decodeRawTransaction: `, err) - wlogger.error(`Error in blockchain.ts/getBlockchainInfo().`, err) - - res.status(500) - return res.json({ error: util.inspect(err) }) - } -} - -/** - * @api {get} /blockchain/getBlockCount Get Block Count - * @apiName GetBlockCount - * @apiGroup Blockchain - * @apiDescription Returns the number of blocks in the longest blockchain. - * - * @apiExample Example usage: - * curl -X GET "https://mainnet.bchjs.cash/v3/blockchain/getBlockCount" -H "accept: application/json" - * - * @apiSuccess {Number} bestBlockCount 587665 - */ -async function getBlockCount(req, res, next) { - try { - const { - BitboxHTTP, - username, - password, - requestConfig - } = routeUtils.setEnvVars() - - requestConfig.data.id = "getblockcount" - requestConfig.data.method = "getblockcount" - requestConfig.data.params = [] - - const response = await BitboxHTTP(requestConfig) - return res.json(response.data.result) - } catch (err) { - // Attempt to decode the error message. - const { msg, status } = routeUtils.decodeError(err) - if (msg) { - res.status(status) - return res.json({ error: msg }) - } - - // Write out error to error log. - //logger.error(`Error in rawtransactions/decodeRawTransaction: `, err) - wlogger.error(`Error in blockchain.ts/getBlockCount().`, err) - - res.status(500) - return res.json({ error: util.inspect(err) }) - } -} - -/** - * @api {get} /blockchain/getBlockHeader/:hash Get single block header - * @apiName GetSingleBlockHeader - * @apiGroup Blockchain - * @apiDescription If verbose is false (default), returns a string that is - * serialized, hex-encoded data for blockheader 'hash'. If verbose is true, - * returns an Object with information about blockheader hash. - * - * @apiExample Example usage: - * curl -X GET "https://mainnet.bchjs.cash/v3/blockchain/getBlockHeader/000000000000000005e14d3f9fdfb70745308706615cfa9edca4f4558332b201?verbose=true" -H "accept: application/json" - * - * @apiParam {String} hash block hash - * @apiParam {Boolean} verbose Return verbose data - * - * @apiSuccess {Object} object Object containing data - * @apiSuccess {String} object.hash "000000000000000005e14d3f9fdfb70745308706615cfa9edca4f4558332b201" - * @apiSuccess {Number} object.confirmations 61839 - * @apiSuccess {Number} object.height 500000 - * @apiSuccess {Number} object.version 536870912 - * @apiSuccess {String} object.versionHex "20000000" - * @apiSuccess {String} object.merkleroot "4af279645e1b337e655ae3286fc2ca09f58eb01efa6ab27adedd1e9e6ec19091" - * @apiSuccess {Number} object.time 1509343584 - * @apiSuccess {Number} object.mediantime 1509336533 - * @apiSuccess {Number} object.nonce 3604508752 - * @apiSuccess {String} object.bits "1809b91a" - * @apiSuccess {Number} object.difficulty 113081236211.4533 - * @apiSuccess {String} object.chainwork "0000000000000000000000000000000000000000007ae48aca46e3b449ad9714" - * @apiSuccess {String} object.previousblockhash "0000000000000000043831d6ebb013716f0580287ee5e5687e27d0ed72e6e523" - * @apiSuccess {String} object.nextblockhash "00000000000000000568f0a96bf4348847bc84e455cbfec389f27311037a20f3" - */ -async function getBlockHeaderSingle(req, res, next) { - try { - let verbose = false - if (req.query.verbose && req.query.verbose.toString() === "true") - verbose = true - - const hash = req.params.hash - if (!hash || hash === "") { - res.status(400) - return res.json({ error: "hash can not be empty" }) - } - - const { - BitboxHTTP, - username, - password, - requestConfig - } = routeUtils.setEnvVars() - - requestConfig.data.id = "getblockheader" - requestConfig.data.method = "getblockheader" - requestConfig.data.params = [hash, verbose] - - const response = await BitboxHTTP(requestConfig) - - return res.json(response.data.result) - } catch (err) { - // Attempt to decode the error message. - const { msg, status } = routeUtils.decodeError(err) - if (msg) { - res.status(status) - return res.json({ error: msg }) - } - - // Write out error to error log. - //logger.error(`Error in rawtransactions/decodeRawTransaction: `, err) - wlogger.error(`Error in blockchain.ts/getBlockHeaderSingle().`, err) - - res.status(500) - return res.json({ error: util.inspect(err) }) - } -} - -/** - * @api {post} /blockchain/getBlockHeader Get multiple block headers - * @apiName GetBulkBlockHeader - * @apiGroup Blockchain - * @apiDescription If verbose is false (default), returns a string that is - * serialized, hex-encoded data for blockheader 'hash'. If verbose is true, - * returns an Object with information about blockheader hash. - * - * @apiExample Example usage: - * curl -X POST "https://rest.bitcoin.com/v3/blockchain/getBlockHeader" -H "accept: application/json" -H "Content-Type: application/json" -d "{\"hashes\":[\"000000000000000005e14d3f9fdfb70745308706615cfa9edca4f4558332b201\",\"00000000000000000568f0a96bf4348847bc84e455cbfec389f27311037a20f3\"],\"verbose\":true}" - * - * @apiParam {String} hash block hash - * @apiParam {Boolean} verbose Return verbose data - * - * @apiSuccess {Array} array array containing objects - * @apiSuccess {String} object.hash "000000000000000005e14d3f9fdfb70745308706615cfa9edca4f4558332b201" - * @apiSuccess {Number} object.confirmations 61839 - * @apiSuccess {Number} object.height 500000 - * @apiSuccess {Number} object.version 536870912 - * @apiSuccess {String} object.versionHex "20000000" - * @apiSuccess {String} object.merkleroot "4af279645e1b337e655ae3286fc2ca09f58eb01efa6ab27adedd1e9e6ec19091" - * @apiSuccess {Number} object.time 1509343584 - * @apiSuccess {Number} object.mediantime 1509336533 - * @apiSuccess {Number} object.nonce 3604508752 - * @apiSuccess {String} object.bits "1809b91a" - * @apiSuccess {Number} object.difficulty 113081236211.4533 - * @apiSuccess {String} object.chainwork "0000000000000000000000000000000000000000007ae48aca46e3b449ad9714" - * @apiSuccess {String} object.previousblockhash "0000000000000000043831d6ebb013716f0580287ee5e5687e27d0ed72e6e523" - * @apiSuccess {String} object.nextblockhash "00000000000000000568f0a96bf4348847bc84e455cbfec389f27311037a20f3" - */ -async function getBlockHeaderBulk(req, res, next) { - try { - const hashes = req.body.hashes - const verbose = req.body.verbose ? req.body.verbose : false - - if (!Array.isArray(hashes)) { - res.status(400) - return res.json({ - error: "hashes needs to be an array. Use GET for single hash." - }) - } - - // Enforce array size rate limits - if (!routeUtils.validateArraySize(req, hashes)) { - res.status(429) // https://github.com/Bitcoin-com/rest.bitcoin.com/issues/330 - return res.json({ - error: `Array too large.` - }) - } - - wlogger.debug( - `Executing blockchain/getBlockHeaderBulk with these hashes: `, - hashes + this.router = router + this.router.get("/", this.root) + this.router.get("/getBestBlockHash", this.getBestBlockHash) + this.router.get("/getBlockchainInfo", this.getBlockchainInfo) + this.router.get("/getBlockCount", this.getBlockCount) + this.router.get("/getBlockHeader/:hash", this.getBlockHeaderSingle) + this.router.post("/getBlockHeader", this.getBlockHeaderBulk) + this.router.get("/getChainTips", this.getChainTips) + this.router.get("/getDifficulty", this.getDifficulty) + this.router.get("/getMempoolEntry/:txid", this.getMempoolEntrySingle) + this.router.post("/getMempoolEntry", this.getMempoolEntryBulk) + this.router.get( + "/getMempoolAncestors/:txid", + this.getMempoolAncestorsSingle ) + this.router.get("/getMempoolInfo", this.getMempoolInfo) + this.router.get("/getRawMempool", this.getRawMempool) + this.router.get("/getTxOut/:txid/:n", this.getTxOut) + this.router.get("/getTxOutProof/:txid", this.getTxOutProofSingle) + this.router.post("/getTxOutProof", this.getTxOutProofBulk) + this.router.get("/verifyTxOutProof/:proof", this.verifyTxOutProofSingle) + this.router.post("/verifyTxOutProof", this.verifyTxOutProofBulk) + } - // Validate each hash in the array. - for (let i = 0; i < hashes.length; i++) { - const hash = hashes[i] + root(req, res, next) { + return res.json({ status: "blockchain" }) + } - if (hash.length !== 64) { + // DRY error handler. + errorHandler(err, res) { + // Attempt to decode the error message. + const { msg, status } = _this.routeUtils.decodeError(err) + if (msg) { + res.status(status) + return res.json({ error: msg }) + } + + res.status(500) + return res.json({ error: util.inspect(err) }) + } + + /** + * @api {get} /blockchain/getBestBlockHash Get best block hash + * @apiName GetBestBlockHash + * @apiGroup Blockchain + * @apiDescription Returns the hash of the best (tip) block in the longest + * block chain. + * + * @apiExample Example usage: + * curl -X GET "https://mainnet.bchjs.cash/v3/blockchain/getBestBlockHash" -H "accept: application/json" + * + * @apiSuccess {String} bestBlockHash 000000000000000002bc884334336d99c9a9c616670a9244c6a8c1fc35aa91a1 + */ + async getBestBlockHash(req, res, next) { + try { + // Axios options + const options = _this.routeUtils.getAxiosOptions() + options.data.id = "getbestblockhash" + options.data.method = "getbestblockhash" + options.data.params = [] + + const response = await _this.axios.request(options) + // console.log(`response.data: ${JSON.stringify(response.data, null, 2)}`) + + return res.json(response.data.result) + } catch (err) { + // Write out error to error log. + wlogger.error("Error in blockchain.ts/getBestBlockHash().", err) + + return _this.errorHandler(err, res) + } + } + + /** + * @api {get} /blockchain/getBlockchainInfo Get blockchain info + * @apiName GetBlockchainInfo + * @apiGroup Blockchain + * @apiDescription Returns an object containing various state info regarding blockchain processing. + * + * @apiExample Example usage: + * curl -X GET "https://mainnet.bchjs.cash/v3/blockchain/getBlockchainInfo" -H "accept: application/json" + * + * @apiSuccess {Object} object Object containing data + * @apiSuccess {String} object.chain "main" + * @apiSuccess {Number} object.blocks 561838 + * @apiSuccess {Number} object.headers 561838 + * @apiSuccess {String} object.bestblockhash "000000000000000002307dd38cd01c7308b8febfcdf5772cf087b5bb023d55bc" + * @apiSuccess {Number} object.difficulty 246585566638.1496 + * @apiSuccess {String} object.mediantime 1545402693 + * @apiSuccess {Number} object.verificationprogress 0.999998831622689 + * @apiSuccess {Boolean} object.chainwork "000000000000000000000000000000000000000000d8c09a8ab7262080266b3e" + * @apiSuccess {Number} object.pruned false + * @apiSuccess {Array} object.softforks Array of objects + * @apiSuccess {String} object.softforks.id "bip34" + * @apiSuccess {String} object.softforks.version 2 + * @apiSuccess {Object} object.softforks.reject + * @apiSuccess {String} object.softforks.reject.status true + */ + async getBlockchainInfo(req, res, next) { + try { + // Axios options + const options = _this.routeUtils.getAxiosOptions() + options.data.id = "getblockchaininfo" + options.data.method = "getblockchaininfo" + options.data.params = [] + + const response = await _this.axios.request(options) + + return res.json(response.data.result) + } catch (err) { + // Write out error to error log. + wlogger.error("Error in blockchain.ts/getBlockchainInfo().", err) + + return _this.errorHandler(err, res) + } + } + + /** + * @api {get} /blockchain/getBlockCount Get Block Count + * @apiName GetBlockCount + * @apiGroup Blockchain + * @apiDescription Returns the number of blocks in the longest blockchain. + * + * @apiExample Example usage: + * curl -X GET "https://mainnet.bchjs.cash/v3/blockchain/getBlockCount" -H "accept: application/json" + * + * @apiSuccess {Number} bestBlockCount 587665 + */ + async getBlockCount(req, res, next) { + try { + // Axios options + const options = _this.routeUtils.getAxiosOptions() + options.data.id = "getblockcount" + options.data.method = "getblockcount" + options.data.params = [] + + const response = await _this.axios.request(options) + + return res.json(response.data.result) + } catch (err) { + // Write out error to error log. + // logger.error(`Error in rawtransactions/decodeRawTransaction: `, err) + wlogger.error("Error in blockchain.ts/getBlockCount().", err) + + return _this.errorHandler(err, res) + } + } + + /** + * @api {get} /blockchain/getBlockHeader/:hash Get single block header + * @apiName GetSingleBlockHeader + * @apiGroup Blockchain + * @apiDescription If verbose is false (default), returns a string that is + * serialized, hex-encoded data for blockheader 'hash'. If verbose is true, + * returns an Object with information about blockheader hash. + * + * @apiExample Example usage: + * curl -X GET "https://mainnet.bchjs.cash/v3/blockchain/getBlockHeader/000000000000000005e14d3f9fdfb70745308706615cfa9edca4f4558332b201?verbose=true" -H "accept: application/json" + * + * @apiParam {String} hash block hash + * @apiParam {Boolean} verbose Return verbose data + * + * @apiSuccess {Object} object Object containing data + * @apiSuccess {String} object.hash "000000000000000005e14d3f9fdfb70745308706615cfa9edca4f4558332b201" + * @apiSuccess {Number} object.confirmations 61839 + * @apiSuccess {Number} object.height 500000 + * @apiSuccess {Number} object.version 536870912 + * @apiSuccess {String} object.versionHex "20000000" + * @apiSuccess {String} object.merkleroot "4af279645e1b337e655ae3286fc2ca09f58eb01efa6ab27adedd1e9e6ec19091" + * @apiSuccess {Number} object.time 1509343584 + * @apiSuccess {Number} object.mediantime 1509336533 + * @apiSuccess {Number} object.nonce 3604508752 + * @apiSuccess {String} object.bits "1809b91a" + * @apiSuccess {Number} object.difficulty 113081236211.4533 + * @apiSuccess {String} object.chainwork "0000000000000000000000000000000000000000007ae48aca46e3b449ad9714" + * @apiSuccess {String} object.previousblockhash "0000000000000000043831d6ebb013716f0580287ee5e5687e27d0ed72e6e523" + * @apiSuccess {String} object.nextblockhash "00000000000000000568f0a96bf4348847bc84e455cbfec389f27311037a20f3" + */ + async getBlockHeaderSingle(req, res, next) { + try { + let verbose = false + if (req.query.verbose && req.query.verbose.toString() === "true") + verbose = true + + const hash = req.params.hash + if (!hash || hash === "") { res.status(400) - return res.json({ error: `This is not a hash: ${hash}` }) + return res.json({ error: "hash can not be empty" }) } + + // Axios options + const options = _this.routeUtils.getAxiosOptions() + options.data.id = "getblockheader" + options.data.method = "getblockheader" + options.data.params = [hash, verbose] + + const response = await _this.axios.request(options) + + return res.json(response.data.result) + } catch (err) { + // Write out error to error log. + // logger.error(`Error in rawtransactions/decodeRawTransaction: `, err) + wlogger.error("Error in blockchain.ts/getBlockHeaderSingle().", err) + + return _this.errorHandler(err, res) } - - const { - BitboxHTTP, - username, - password, - requestConfig - } = routeUtils.setEnvVars() - - // Loop through each hash and creates an array of requests to call in parallel - const promises = hashes.map(async hash => { - requestConfig.data.id = "getblockheader" - requestConfig.data.method = "getblockheader" - requestConfig.data.params = [hash, verbose] - - return await BitboxHTTP(requestConfig) - }) - - const axiosResult = await axios.all(promises) - - // Extract the data component from the axios response. - const result = axiosResult.map(x => x.data.result) - - res.status(200) - return res.json(result) - } catch (err) { - // Attempt to decode the error message. - const { msg, status } = routeUtils.decodeError(err) - if (msg) { - res.status(status) - return res.json({ error: msg }) - } - - // Write out error to error log. - //logger.error(`Error in rawtransactions/decodeRawTransaction: `, err) - wlogger.error(`Error in blockchain.ts/getBlockHeaderBulk().`, err) - - res.status(500) - return res.json({ error: util.inspect(err) }) } -} -/** - * @api {get} /blockchain/getChainTips Get Chain Tips - * @apiName getChainTips - * @apiGroup Blockchain - * @apiDescription Return information about all known tips in the block tree, - * including the main chain as well as orphaned branches. - * - * @apiExample Example usage: - * curl -X GET "https://mainnet.bchjs.cash/v3/blockchain/getChainTips" -H "accept: application/json" - * - */ -async function getChainTips(req, res, next) { - try { - const { - BitboxHTTP, - username, - password, - requestConfig - } = routeUtils.setEnvVars() - - requestConfig.data.id = "getchaintips" - requestConfig.data.method = "getchaintips" - requestConfig.data.params = [] - - const response = await BitboxHTTP(requestConfig) - return res.json(response.data.result) - } catch (err) { - // Attempt to decode the error message. - const { msg, status } = routeUtils.decodeError(err) - if (msg) { - res.status(status) - return res.json({ error: msg }) - } - - // Write out error to error log. - //logger.error(`Error in rawtransactions/decodeRawTransaction: `, err) - wlogger.error(`Error in blockchain.ts/getChainTips().`, err) - - res.status(500) - return res.json({ error: util.inspect(err) }) - } -} - -/** - * @api {get} /blockchain/getDifficulty Get difficulty - * @apiName getDifficulty - * @apiGroup Blockchain - * @apiDescription Get the current difficulty value, used to regulate mining - * power on the network. - * - * @apiExample Example usage: - * curl -X GET "https://mainnet.bchjs.cash/v3/blockchain/getDifficulty" -H "accept: application/json" - * - */ -async function getDifficulty(req, res, next) { - try { - const { - BitboxHTTP, - username, - password, - requestConfig - } = routeUtils.setEnvVars() - - requestConfig.data.id = "getdifficulty" - requestConfig.data.method = "getdifficulty" - requestConfig.data.params = [] - - const response = await BitboxHTTP(requestConfig) - - return res.json(response.data.result) - } catch (err) { - // Attempt to decode the error message. - const { msg, status } = routeUtils.decodeError(err) - if (msg) { - res.status(status) - return res.json({ error: msg }) - } - - // Write out error to error log. - //logger.error(`Error in rawtransactions/decodeRawTransaction: `, err) - wlogger.error(`Error in blockchain.ts/getDifficulty().`, err) - - res.status(500) - return res.json({ error: util.inspect(err) }) - } -} - -/** - * @api {get} /blockchain/getMempoolEntry/:txid Get single mempool entry - * @apiName getMempoolEntry - * @apiGroup Blockchain - * @apiDescription Returns mempool data for given transaction. TXID must be in - * mempool (unconfirmed) - * - * @apiExample Example usage: - * curl -X GET "https://mainnet.bchjs.cash/v3/blockchain/getMempoolEntry/fe28050b93faea61fa88c4c630f0e1f0a1c24d0082dd0e10d369e13212128f33" -H "accept: application/json" - * - */ -async function getMempoolEntrySingle(req, res, next) { - try { - // Validate input parameter - const txid = req.params.txid - if (!txid || txid === "") { - res.status(400) - return res.json({ error: "txid can not be empty" }) - } - - const { - BitboxHTTP, - username, - password, - requestConfig - } = routeUtils.setEnvVars() - - requestConfig.data.id = "getmempoolentry" - requestConfig.data.method = "getmempoolentry" - requestConfig.data.params = [txid] - - const response = await BitboxHTTP(requestConfig) - - return res.json(response.data.result) - } catch (err) { - // Attempt to decode the error message. - const { msg, status } = routeUtils.decodeError(err) - if (msg) { - res.status(status) - return res.json({ error: msg }) - } - - // Write out error to error log. - //logger.error(`Error in rawtransactions/decodeRawTransaction: `, err) - wlogger.error(`Error in blockchain.ts/getMempoolEntrySingle().`, err) - - res.status(500) - return res.json({ error: util.inspect(err) }) - } -} - -/** - * @api {post} /blockchain/getMempoolEntry Get bulk mempool entry - * @apiName getMempoolEntryBulk - * @apiGroup Blockchain - * @apiDescription Returns mempool data for multiple transactions - * - * @apiExample Example usage: - * curl -X POST https://mainnet.bchjs.cash/v3/blockchain/getMempoolEntry -H "Content-Type: application/json" -d "{\"txids\":[\"a5f972572ee1753e2fd2457dd61ce5f40fa2f8a30173d417e49feef7542c96a1\",\"5165dc531aad05d1149bb0f0d9b7bda99c73e2f05e314bcfb5b4bb9ca5e1af5e\"]}" - */ -async function getMempoolEntryBulk(req, res, next) { - try { - const txids = req.body.txids - - if (!Array.isArray(txids)) { - res.status(400) - return res.json({ - error: "txids needs to be an array. Use GET for single txid." - }) - } - - // Enforce array size rate limits - if (!routeUtils.validateArraySize(req, txids)) { - res.status(429) // https://github.com/Bitcoin-com/rest.bitcoin.com/issues/330 - return res.json({ - error: `Array too large.` - }) - } - - wlogger.debug( - `Executing blockchain/getMempoolEntry with these txids: `, - txids - ) - - // Validate each element in the array - for (let i = 0; i < txids.length; i++) { - const txid = txids[i] - - if (txid.length !== 64) { - res.status(400) - return res.json({ error: "This is not a txid" }) - } - } - - const { - BitboxHTTP, - username, - password, - requestConfig - } = routeUtils.setEnvVars() - - // Loop through each txid and creates an array of requests to call in parallel - const promises = txids.map(async txid => { - requestConfig.data.id = "getmempoolentry" - requestConfig.data.method = "getmempoolentry" - requestConfig.data.params = [txid] - - return await BitboxHTTP(requestConfig) - }) - - const axiosResult = await axios.all(promises) - - // Extract the data component from the axios response. - const result = axiosResult.map(x => x.data.result) - - res.status(200) - return res.json(result) - } catch (err) { - // Attempt to decode the error message. - const { msg, status } = routeUtils.decodeError(err) - if (msg) { - res.status(status) - return res.json({ error: msg }) - } - - // Write out error to error log. - //logger.error(`Error in rawtransactions/decodeRawTransaction: `, err) - wlogger.error(`Error in blockchain.ts/getMempoolEntryBulk().`, err) - - res.status(500) - return res.json({ error: util.inspect(err) }) - } -} - -/** - * @api {get} /blockchain/getMempoolAncestors/:txid Get Mempool Ancestors - * @apiName getMempoolAncestors - * @apiGroup Blockchain - * @apiDescription Returns mempool ancestors data for given TXID. It must be in - * mempool (unconfirmed). This call is handy to tell if a UTXO is bumping up - * against the 25 ancestor chain-limit. - * - * @apiExample Example usage: - * curl -X GET "https://mainnet.bchjs.cash/v3/blockchain/getMempoolAncestors/fe28050b93faea61fa88c4c630f0e1f0a1c24d0082dd0e10d369e13212128f33" -H "accept: application/json" - * - */ -async function getMempoolAncestorsSingle(req, res, next) { - try { - // Validate input parameter - const txid = req.params.txid - if (!txid || txid === "") { - res.status(400) - return res.json({ error: "txid can not be empty" }) - } - - let verbose = req.params.verbose - if (verbose === undefined) verbose = false - - const { - BitboxHTTP, - username, - password, - requestConfig - } = routeUtils.setEnvVars() - - requestConfig.data.id = "getmempoolancestors" - requestConfig.data.method = "getmempoolancestors" - requestConfig.data.params = [txid, verbose] - - const response = await BitboxHTTP(requestConfig) - // console.log(`response: ${util.inspect(response)}`) - - return res.json(response.data.result) - } catch (err) { - // Attempt to decode the error message. - const { msg, status } = routeUtils.decodeError(err) - if (msg) { - res.status(status) - return res.json({ error: msg }) - } - - // Write out error to error log. - //logger.error(`Error in rawtransactions/decodeRawTransaction: `, err) - wlogger.error(`Error in blockchain.ts/getMempoolAncestorsSingle().`, err) - - res.status(500) - return res.json({ error: util.inspect(err) }) - } -} - -/** - * @api {get} /blockchain/getMempoolInfo Get mempool info - * @apiName getMempoolInfo - * @apiGroup Blockchain - * @apiDescription Returns details on the active state of the TX memory pool. - * - * @apiExample Example usage: - * curl -X GET https://mainnet.bchjs.cash/v3/getMempoolInfo -H "accept: application/json" - * - */ -async function getMempoolInfo(req, res, next) { - try { - const { - BitboxHTTP, - username, - password, - requestConfig - } = routeUtils.setEnvVars() - - requestConfig.data.id = "getmempoolinfo" - requestConfig.data.method = "getmempoolinfo" - requestConfig.data.params = [] - - const response = await BitboxHTTP(requestConfig) - return res.json(response.data.result) - } catch (err) { - // Attempt to decode the error message. - const { msg, status } = routeUtils.decodeError(err) - if (msg) { - res.status(status) - return res.json({ error: msg }) - } - - // Write out error to error log. - //logger.error(`Error in rawtransactions/decodeRawTransaction: `, err) - wlogger.error(`Error in blockchain.ts/getMempoolInfo().`, err) - - res.status(500) - return res.json({ error: util.inspect(err) }) - } -} - -/** - * @api {get} /blockchain/getRawMempool Get mempool info - * @apiName getMempoolInfo - * @apiGroup Blockchain - * @apiDescription Returns details on the active state of the TX memory pool. - * - * @apiExample Example usage: - * curl -X GET https://mainnet.bchjs.cash/v3/getMempoolInfo -H "accept: application/json" - * - */ -/** - * @api {get} /blockchain/getRawMempool/?verbose= Get raw mempool - * @apiName getRawMempool - * @apiGroup Blockchain - * @apiDescription Returns all transaction ids in memory pool as a json array - * of string transaction ids. - * - * @apiExample Example usage: - * curl -X GET "https://mainnet.bchjs.cash/v3/getRawMempool/?verbose=true" -H "accept: application/json" - * - * @apiParam {Boolean} verbose Return verbose data - * - */ -async function getRawMempool(req, res, next) { - try { - const { - BitboxHTTP, - username, - password, - requestConfig - } = routeUtils.setEnvVars() - - let verbose = false - if (req.query.verbose && req.query.verbose === "true") verbose = true - - requestConfig.data.id = "getrawmempool" - requestConfig.data.method = "getrawmempool" - requestConfig.data.params = [verbose] - - const response = await BitboxHTTP(requestConfig) - - return res.json(response.data.result) - } catch (err) { - // Attempt to decode the error message. - const { msg, status } = routeUtils.decodeError(err) - if (msg) { - res.status(status) - return res.json({ error: msg }) - } - - // Write out error to error log. - //logger.error(`Error in rawtransactions/decodeRawTransaction: `, err) - wlogger.error(`Error in blockchain.ts/getRawMempool().`, err) - - res.status(500) - return res.json({ error: util.inspect(err) }) - } -} - -/** - * @api {get} /blockchain/getTxOut/:txid/:n?mempool= Get Tx Out - * @apiName getTxOut - * @apiGroup Blockchain - * @apiDescription Returns details about an unspent transaction output. - * - * @apiExample Example usage: - * curl -X GET "https://mainnet.bchjs.cash/v3/blockchain/getTxOut/fe28050b93faea61fa88c4c630f0e1f0a1c24d0082dd0e10d369e13212128f33/0?mempool=false" -H "accept: application/json" - * - * @apiParam {String} txid Transaction id (required) - * @apiParam {Number} n Output number (required) - * @apiParam {Boolean} mempool Check mempool or not (optional) - * - */ -// Returns details about an unspent transaction output. -async function getTxOut(req, res, next) { - try { - // Validate input parameter - const txid = req.params.txid - if (!txid || txid === "") { - res.status(400) - return res.json({ error: "txid can not be empty" }) - } - - let n = req.params.n - if (n === undefined || n === "") { - res.status(400) - return res.json({ error: "n can not be empty" }) - } - n = parseInt(n) - - let include_mempool = false - if (req.query.include_mempool && req.query.include_mempool === "true") - include_mempool = true - - const { - BitboxHTTP, - username, - password, - requestConfig - } = routeUtils.setEnvVars() - - requestConfig.data.id = "gettxout" - requestConfig.data.method = "gettxout" - requestConfig.data.params = [txid, n, include_mempool] - - // console.log(`requestConfig: ${JSON.stringify(requestConfig, null, 2)}`) - - const response = await BitboxHTTP(requestConfig) - - return res.json(response.data.result) - } catch (err) { - // Attempt to decode the error message. - const { msg, status } = routeUtils.decodeError(err) - if (msg) { - res.status(status) - return res.json({ error: msg }) - } - - // Write out error to error log. - //logger.error(`Error in rawtransactions/decodeRawTransaction: `, err) - wlogger.error(`Error in blockchain.ts/getTxOut().`, err) - - res.status(500) - return res.json({ error: util.inspect(err) }) - } -} - -/** - * @api {get} /blockchain/getTxOutProofSingle/:txid Get Tx Out Proof - * @apiName getTxOutProofSingle - * @apiGroup Blockchain - * @apiDescription Returns a hex-encoded proof that 'txid' was included in a block. - * - * @apiExample Example usage: - * curl -X GET "https://mainnet.bchjs.cash/v3/blockchain/getTxOutProofSingle/fe28050b93faea61fa88c4c630f0e1f0a1c24d0082dd0e10d369e13212128f33" -H "accept: application/json" - * - * @apiParam {String} txid Transaction id (required) - * - */ -async function getTxOutProofSingle(req, res, next) { - try { - // Validate input parameter - const txid = req.params.txid - if (!txid || txid === "") { - res.status(400) - return res.json({ error: "txid can not be empty" }) - } - - const { - BitboxHTTP, - username, - password, - requestConfig - } = routeUtils.setEnvVars() - - requestConfig.data.id = "gettxoutproof" - requestConfig.data.method = "gettxoutproof" - requestConfig.data.params = [[txid]] - - const response = await BitboxHTTP(requestConfig) - - return res.json(response.data.result) - } catch (err) { - // Attempt to decode the error message. - const { msg, status } = routeUtils.decodeError(err) - if (msg) { - res.status(status) - return res.json({ error: msg }) - } - - // Write out error to error log. - //logger.error(`Error in rawtransactions/decodeRawTransaction: `, err) - wlogger.error(`Error in blockchain.ts/getTxOutProofSingle().`, err) - - res.status(500) - return res.json({ error: util.inspect(err) }) - } -} - -// Returns a hex-encoded proof that 'txid' was included in a block. -async function getTxOutProofBulk(req, res, next) { - try { - const txids = req.body.txids - - // Reject if txids is not an array. - if (!Array.isArray(txids)) { - res.status(400) - return res.json({ - error: "txids needs to be an array. Use GET for single txid." - }) - } - - // Enforce array size rate limits - if (!routeUtils.validateArraySize(req, txids)) { - res.status(429) // https://github.com/Bitcoin-com/rest.bitcoin.com/issues/330 - return res.json({ - error: `Array too large.` - }) - } - - const { - BitboxHTTP, - username, - password, - requestConfig - } = routeUtils.setEnvVars() - - // Validate each element in the array. - for (let i = 0; i < txids.length; i++) { - const txid = txids[i] - - if (txid.length !== 64) { + /** + * @api {post} /blockchain/getBlockHeader Get multiple block headers + * @apiName GetBulkBlockHeader + * @apiGroup Blockchain + * @apiDescription If verbose is false (default), returns a string that is + * serialized, hex-encoded data for blockheader 'hash'. If verbose is true, + * returns an Object with information about blockheader hash. + * + * @apiExample Example usage: + * curl -X POST "https://rest.bitcoin.com/v3/blockchain/getBlockHeader" -H "accept: application/json" -H "Content-Type: application/json" -d "{\"hashes\":[\"000000000000000005e14d3f9fdfb70745308706615cfa9edca4f4558332b201\",\"00000000000000000568f0a96bf4348847bc84e455cbfec389f27311037a20f3\"],\"verbose\":true}" + * + * @apiParam {String} hash block hash + * @apiParam {Boolean} verbose Return verbose data + * + * @apiSuccess {Array} array array containing objects + * @apiSuccess {String} object.hash "000000000000000005e14d3f9fdfb70745308706615cfa9edca4f4558332b201" + * @apiSuccess {Number} object.confirmations 61839 + * @apiSuccess {Number} object.height 500000 + * @apiSuccess {Number} object.version 536870912 + * @apiSuccess {String} object.versionHex "20000000" + * @apiSuccess {String} object.merkleroot "4af279645e1b337e655ae3286fc2ca09f58eb01efa6ab27adedd1e9e6ec19091" + * @apiSuccess {Number} object.time 1509343584 + * @apiSuccess {Number} object.mediantime 1509336533 + * @apiSuccess {Number} object.nonce 3604508752 + * @apiSuccess {String} object.bits "1809b91a" + * @apiSuccess {Number} object.difficulty 113081236211.4533 + * @apiSuccess {String} object.chainwork "0000000000000000000000000000000000000000007ae48aca46e3b449ad9714" + * @apiSuccess {String} object.previousblockhash "0000000000000000043831d6ebb013716f0580287ee5e5687e27d0ed72e6e523" + * @apiSuccess {String} object.nextblockhash "00000000000000000568f0a96bf4348847bc84e455cbfec389f27311037a20f3" + */ + async getBlockHeaderBulk(req, res, next) { + try { + const hashes = req.body.hashes + const verbose = req.body.verbose ? req.body.verbose : false + + if (!Array.isArray(hashes)) { res.status(400) return res.json({ - error: `Invalid txid. Double check your txid is valid: ${txid}` + error: "hashes needs to be an array. Use GET for single hash." }) } - } - wlogger.debug( - `Executing blockchain/getTxOutProof with these txids: `, - txids - ) + // Enforce array size rate limits + if (!routeUtils.validateArraySize(req, hashes)) { + res.status(429) // https://github.com/Bitcoin-com/rest.bitcoin.com/issues/330 + return res.json({ + error: "Array too large." + }) + } - // Loop through each txid and creates an array of requests to call in parallel - const promises = txids.map(async txid => { - requestConfig.data.id = "gettxoutproof" - requestConfig.data.method = "gettxoutproof" - requestConfig.data.params = [[txid]] + wlogger.debug( + "Executing blockchain/getBlockHeaderBulk with these hashes: ", + hashes + ) - return await BitboxHTTP(requestConfig) - }) + // Validate each hash in the array. + for (let i = 0; i < hashes.length; i++) { + const hash = hashes[i] - // Wait for all parallel promisses to resolve. - const axiosResult = await axios.all(promises) + if (hash.length !== 64) { + res.status(400) + return res.json({ error: `This is not a hash: ${hash}` }) + } + } - // Extract the data component from the axios response. - const result = axiosResult.map(x => x.data.result) + // Axios options + const options = _this.routeUtils.getAxiosOptions() - res.status(200) - return res.json(result) - } catch (err) { - // Attempt to decode the error message. - const { msg, status } = routeUtils.decodeError(err) - if (msg) { - res.status(status) - return res.json({ error: msg }) - } + // Loop through each hash and creates an array of requests to call in parallel + const promises = hashes.map(async hash => { + options.data.id = "getblockheader" + options.data.method = "getblockheader" + options.data.params = [hash, verbose] - // Write out error to error log. - //logger.error(`Error in rawtransactions/decodeRawTransaction: `, err) - wlogger.error(`Error in blockchain.ts/getTxOutProofBulk().`, err) - - res.status(500) - return res.json({ error: util.inspect(err) }) - } -} - -/* -// -// router.get('/preciousBlock/:hash', async (req, res, next) => { -// BitboxHTTP({ -// method: 'post', -// auth: { -// username: username, -// password: password -// }, -// data: { -// jsonrpc: "1.0", -// id:"preciousblock", -// method: "preciousblock", -// params: [ -// req.params.hash -// ] -// } -// }) -// .then((response) => { -// res.json(JSON.stringify(response.data.result)); -// }) -// .catch((error) => { -// res.send(error.response.data.error.message); -// }); -// }); -// -// router.post('/pruneBlockchain/:height', async (req, res, next) => { -// BitboxHTTP({ -// method: 'post', -// auth: { -// username: username, -// password: password -// }, -// data: { -// jsonrpc: "1.0", -// id:"pruneblockchain", -// method: "pruneblockchain", -// params: [ -// req.params.height -// ] -// } -// }) -// .then((response) => { -// res.json(response.data.result); -// }) -// .catch((error) => { -// res.send(error.response.data.error.message); -// }); -// }); -// -// router.get('/verifyChain', async (req, res, next) => { -// BitboxHTTP({ -// method: 'post', -// auth: { -// username: username, -// password: password -// }, -// data: { -// jsonrpc: "1.0", -// id:"verifychain", -// method: "verifychain" -// } -// }) -// .then((response) => { -// res.json(response.data.result); -// }) -// .catch((error) => { -// res.send(error.response.data.error.message); -// }); -// }); -*/ - -async function verifyTxOutProofSingle(req, res, next) { - try { - // Validate input parameter - const proof = req.params.proof - if (!proof || proof === "") { - res.status(400) - return res.json({ error: "proof can not be empty" }) - } - - const { - BitboxHTTP, - username, - password, - requestConfig - } = routeUtils.setEnvVars() - - requestConfig.data.id = "verifytxoutproof" - requestConfig.data.method = "verifytxoutproof" - requestConfig.data.params = [req.params.proof] - - const response = await BitboxHTTP(requestConfig) - - return res.json(response.data.result) - } catch (err) { - // Attempt to decode the error message. - const { msg, status } = routeUtils.decodeError(err) - if (msg) { - res.status(status) - return res.json({ error: msg }) - } - - // Write out error to error log. - //logger.error(`Error in rawtransactions/decodeRawTransaction: `, err) - wlogger.error(`Error in blockchain.ts/verifyTxOutProofSingle().`, err) - - res.status(500) - return res.json({ error: util.inspect(err) }) - } -} - -async function verifyTxOutProofBulk(req, res, next) { - try { - const proofs = req.body.proofs - - // Reject if proofs is not an array. - if (!Array.isArray(proofs)) { - res.status(400) - return res.json({ - error: "proofs needs to be an array. Use GET for single proof." + return await _this.axios.request(options) }) - } - // Enforce array size rate limits - if (!routeUtils.validateArraySize(req, proofs)) { - res.status(429) // https://github.com/Bitcoin-com/rest.bitcoin.com/issues/330 - return res.json({ - error: `Array too large.` + const axiosResult = await _this.axios.all(promises) + + // Extract the data component from the axios response. + const result = axiosResult.map(x => x.data.result) + + res.status(200) + return res.json(result) + } catch (err) { + // Write out error to error log. + // logger.error(`Error in rawtransactions/decodeRawTransaction: `, err) + wlogger.error("Error in blockchain.ts/getBlockHeaderBulk().", err) + + return _this.errorHandler(err, res) + } + } + + /** + * @api {get} /blockchain/getChainTips Get Chain Tips + * @apiName getChainTips + * @apiGroup Blockchain + * @apiDescription Return information about all known tips in the block tree, + * including the main chain as well as orphaned branches. + * + * @apiExample Example usage: + * curl -X GET "https://mainnet.bchjs.cash/v3/blockchain/getChainTips" -H "accept: application/json" + * + */ + async getChainTips(req, res, next) { + try { + // Axios options + const options = _this.routeUtils.getAxiosOptions() + + options.data.id = "getchaintips" + options.data.method = "getchaintips" + options.data.params = [] + + const response = await _this.axios.request(options) + return res.json(response.data.result) + } catch (err) { + // Write out error to error log. + // logger.error(`Error in rawtransactions/decodeRawTransaction: `, err) + wlogger.error("Error in blockchain.ts/getChainTips().", err) + + return _this.errorHandler(err, res) + } + } + + /** + * @api {get} /blockchain/getDifficulty Get difficulty + * @apiName getDifficulty + * @apiGroup Blockchain + * @apiDescription Get the current difficulty value, used to regulate mining + * power on the network. + * + * @apiExample Example usage: + * curl -X GET "https://mainnet.bchjs.cash/v3/blockchain/getDifficulty" -H "accept: application/json" + * + */ + async getDifficulty(req, res, next) { + try { + // Axios options + const options = _this.routeUtils.getAxiosOptions() + + options.data.id = "getdifficulty" + options.data.method = "getdifficulty" + options.data.params = [] + + const response = await _this.axios.request(options) + + return res.json(response.data.result) + } catch (err) { + // Write out error to error log. + // logger.error(`Error in rawtransactions/decodeRawTransaction: `, err) + wlogger.error("Error in blockchain.ts/getDifficulty().", err) + + return _this.errorHandler(err, res) + } + } + + /** + * @api {get} /blockchain/getMempoolEntry/:txid Get single mempool entry + * @apiName getMempoolEntry + * @apiGroup Blockchain + * @apiDescription Returns mempool data for given transaction. TXID must be in + * mempool (unconfirmed) + * + * @apiExample Example usage: + * curl -X GET "https://mainnet.bchjs.cash/v3/blockchain/getMempoolEntry/fe28050b93faea61fa88c4c630f0e1f0a1c24d0082dd0e10d369e13212128f33" -H "accept: application/json" + * + */ + async getMempoolEntrySingle(req, res, next) { + try { + // Validate input parameter + const txid = req.params.txid + if (!txid || txid === "") { + res.status(400) + return res.json({ error: "txid can not be empty" }) + } + + // Axios options + const options = _this.routeUtils.getAxiosOptions() + + options.data.id = "getmempoolentry" + options.data.method = "getmempoolentry" + options.data.params = [txid] + + const response = await _this.axios.request(options) + + return res.json(response.data.result) + } catch (err) { + // Write out error to error log. + // logger.error(`Error in rawtransactions/decodeRawTransaction: `, err) + wlogger.error("Error in blockchain.ts/getMempoolEntrySingle().", err) + + return _this.errorHandler(err, res) + } + } + + /** + * @api {post} /blockchain/getMempoolEntry Get bulk mempool entry + * @apiName getMempoolEntryBulk + * @apiGroup Blockchain + * @apiDescription Returns mempool data for multiple transactions + * + * @apiExample Example usage: + * curl -X POST https://mainnet.bchjs.cash/v3/blockchain/getMempoolEntry -H "Content-Type: application/json" -d "{\"txids\":[\"a5f972572ee1753e2fd2457dd61ce5f40fa2f8a30173d417e49feef7542c96a1\",\"5165dc531aad05d1149bb0f0d9b7bda99c73e2f05e314bcfb5b4bb9ca5e1af5e\"]}" + */ + async getMempoolEntryBulk(req, res, next) { + try { + const txids = req.body.txids + + if (!Array.isArray(txids)) { + res.status(400) + return res.json({ + error: "txids needs to be an array. Use GET for single txid." + }) + } + + // Enforce array size rate limits + if (!routeUtils.validateArraySize(req, txids)) { + res.status(429) // https://github.com/Bitcoin-com/rest.bitcoin.com/issues/330 + return res.json({ + error: "Array too large." + }) + } + + wlogger.debug( + "Executing blockchain/getMempoolEntry with these txids: ", + txids + ) + + // Validate each element in the array + for (let i = 0; i < txids.length; i++) { + const txid = txids[i] + + if (txid.length !== 64) { + res.status(400) + return res.json({ error: "This is not a txid" }) + } + } + + // Axios options + const options = _this.routeUtils.getAxiosOptions() + + // Loop through each txid and creates an array of requests to call in parallel + const promises = txids.map(async txid => { + options.data.id = "getmempoolentry" + options.data.method = "getmempoolentry" + options.data.params = [txid] + + return await _this.axios.request(options) }) + + const axiosResult = await _this.axios.all(promises) + + // Extract the data component from the axios response. + const result = axiosResult.map(x => x.data.result) + + res.status(200) + return res.json(result) + } catch (err) { + // Write out error to error log. + // logger.error(`Error in rawtransactions/decodeRawTransaction: `, err) + wlogger.error("Error in blockchain.ts/getMempoolEntryBulk().", err) + + return _this.errorHandler(err, res) } + } - const { - BitboxHTTP, - username, - password, - requestConfig - } = routeUtils.setEnvVars() + /** + * @api {get} /blockchain/getMempoolAncestors/:txid Get Mempool Ancestors + * @apiName getMempoolAncestors + * @apiGroup Blockchain + * @apiDescription Returns mempool ancestors data for given TXID. It must be in + * mempool (unconfirmed). This call is handy to tell if a UTXO is bumping up + * against the 25 ancestor chain-limit. + * + * @apiExample Example usage: + * curl -X GET "https://mainnet.bchjs.cash/v3/blockchain/getMempoolAncestors/fe28050b93faea61fa88c4c630f0e1f0a1c24d0082dd0e10d369e13212128f33" -H "accept: application/json" + * + */ + async getMempoolAncestorsSingle(req, res, next) { + try { + // Validate input parameter + const txid = req.params.txid + if (!txid || txid === "") { + res.status(400) + return res.json({ error: "txid can not be empty" }) + } - // Validate each element in the array. - for (let i = 0; i < proofs.length; i++) { - const proof = proofs[i] + let verbose = req.params.verbose + if (verbose === undefined) verbose = false + // Axios options + const options = _this.routeUtils.getAxiosOptions() + + options.data.id = "getmempoolancestors" + options.data.method = "getmempoolancestors" + options.data.params = [txid, verbose] + + const response = await _this.axios.request(options) + // console.log(`response: ${util.inspect(response)}`) + + return res.json(response.data.result) + } catch (err) { + // Write out error to error log. + // logger.error(`Error in rawtransactions/decodeRawTransaction: `, err) + wlogger.error("Error in blockchain.ts/getMempoolAncestorsSingle().", err) + + return _this.errorHandler(err, res) + } + } + + /** + * @api {get} /blockchain/getMempoolInfo Get mempool info + * @apiName getMempoolInfo + * @apiGroup Blockchain + * @apiDescription Returns details on the active state of the TX memory pool. + * + * @apiExample Example usage: + * curl -X GET https://mainnet.bchjs.cash/v3/getMempoolInfo -H "accept: application/json" + * + */ + async getMempoolInfo(req, res, next) { + try { + // Axios options + const options = _this.routeUtils.getAxiosOptions() + + options.data.id = "getmempoolinfo" + options.data.method = "getmempoolinfo" + options.data.params = [] + + const response = await _this.axios.request(options) + return res.json(response.data.result) + } catch (err) { + // Write out error to error log. + // logger.error(`Error in rawtransactions/decodeRawTransaction: `, err) + wlogger.error("Error in blockchain.ts/getMempoolInfo().", err) + + return _this.errorHandler(err, res) + } + } + + /** + * @api {get} /blockchain/getRawMempool Get mempool info + * @apiName getMempoolInfo + * @apiGroup Blockchain + * @apiDescription Returns details on the active state of the TX memory pool. + * + * @apiExample Example usage: + * curl -X GET https://mainnet.bchjs.cash/v3/getMempoolInfo -H "accept: application/json" + * + */ + + /** + * @api {get} /blockchain/getRawMempool/?verbose= Get raw mempool + * @apiName getRawMempool + * @apiGroup Blockchain + * @apiDescription Returns all transaction ids in memory pool as a json array + * of string transaction ids. + * + * @apiExample Example usage: + * curl -X GET "https://mainnet.bchjs.cash/v3/getRawMempool/?verbose=true" -H "accept: application/json" + * + * @apiParam {Boolean} verbose Return verbose data + * + */ + async getRawMempool(req, res, next) { + try { + // Axios options + const options = _this.routeUtils.getAxiosOptions() + + let verbose = false + if (req.query.verbose && req.query.verbose === "true") verbose = true + + options.data.id = "getrawmempool" + options.data.method = "getrawmempool" + options.data.params = [verbose] + + const response = await _this.axios.request(options) + + return res.json(response.data.result) + } catch (err) { + // Write out error to error log. + // logger.error(`Error in rawtransactions/decodeRawTransaction: `, err) + wlogger.error("Error in blockchain.ts/getRawMempool().", err) + + return _this.errorHandler(err, res) + } + } + + /** + * @api {get} /blockchain/getTxOut/:txid/:n?mempool= Get Tx Out + * @apiName getTxOut + * @apiGroup Blockchain + * @apiDescription Returns details about an unspent transaction output. + * + * @apiExample Example usage: + * curl -X GET "https://mainnet.bchjs.cash/v3/blockchain/getTxOut/fe28050b93faea61fa88c4c630f0e1f0a1c24d0082dd0e10d369e13212128f33/0?mempool=false" -H "accept: application/json" + * + * @apiParam {String} txid Transaction id (required) + * @apiParam {Number} n Output number (required) + * @apiParam {Boolean} mempool Check mempool or not (optional) + * + */ + // Returns details about an unspent transaction output. + async getTxOut(req, res, next) { + try { + // Validate input parameter + const txid = req.params.txid + if (!txid || txid === "") { + res.status(400) + return res.json({ error: "txid can not be empty" }) + } + + let n = req.params.n + if (n === undefined || n === "") { + res.status(400) + return res.json({ error: "n can not be empty" }) + } + n = parseInt(n) + + let include_mempool = false + if (req.query.include_mempool && req.query.include_mempool === "true") + include_mempool = true + + // Axios options + const options = _this.routeUtils.getAxiosOptions() + + options.data.id = "gettxout" + options.data.method = "gettxout" + options.data.params = [txid, n, include_mempool] + + // console.log(`requestConfig: ${JSON.stringify(requestConfig, null, 2)}`) + + const response = await _this.axios.request(options) + + return res.json(response.data.result) + } catch (err) { + // Write out error to error log. + // logger.error(`Error in rawtransactions/decodeRawTransaction: `, err) + wlogger.error("Error in blockchain.ts/getTxOut().", err) + + return _this.errorHandler(err, res) + } + } + + /** + * @api {get} /blockchain/getTxOutProofSingle/:txid Get Tx Out Proof + * @apiName getTxOutProofSingle + * @apiGroup Blockchain + * @apiDescription Returns a hex-encoded proof that 'txid' was included in a block. + * + * @apiExample Example usage: + * curl -X GET "https://mainnet.bchjs.cash/v3/blockchain/getTxOutProofSingle/fe28050b93faea61fa88c4c630f0e1f0a1c24d0082dd0e10d369e13212128f33" -H "accept: application/json" + * + * @apiParam {String} txid Transaction id (required) + * + */ + async getTxOutProofSingle(req, res, next) { + try { + // Validate input parameter + const txid = req.params.txid + if (!txid || txid === "") { + res.status(400) + return res.json({ error: "txid can not be empty" }) + } + + // Axios options + const options = _this.routeUtils.getAxiosOptions() + + options.data.id = "gettxoutproof" + options.data.method = "gettxoutproof" + options.data.params = [[txid]] + + const response = await _this.axios.request(options) + + return res.json(response.data.result) + } catch (err) { + // Write out error to error log. + // logger.error(`Error in rawtransactions/decodeRawTransaction: `, err) + wlogger.error("Error in blockchain.ts/getTxOutProofSingle().", err) + + return _this.errorHandler(err, res) + } + } + + // Returns a hex-encoded proof that 'txid' was included in a block. + async getTxOutProofBulk(req, res, next) { + try { + const txids = req.body.txids + + // Reject if txids is not an array. + if (!Array.isArray(txids)) { + res.status(400) + return res.json({ + error: "txids needs to be an array. Use GET for single txid." + }) + } + + // Enforce array size rate limits + if (!routeUtils.validateArraySize(req, txids)) { + res.status(429) // https://github.com/Bitcoin-com/rest.bitcoin.com/issues/330 + return res.json({ + error: "Array too large." + }) + } + + // Axios options + const options = _this.routeUtils.getAxiosOptions() + + // Validate each element in the array. + for (let i = 0; i < txids.length; i++) { + const txid = txids[i] + + if (txid.length !== 64) { + res.status(400) + return res.json({ + error: `Invalid txid. Double check your txid is valid: ${txid}` + }) + } + } + + wlogger.debug( + "Executing blockchain/getTxOutProof with these txids: ", + txids + ) + + // Loop through each txid and creates an array of requests to call in parallel + const promises = txids.map(async txid => { + options.data.id = "gettxoutproof" + options.data.method = "gettxoutproof" + options.data.params = [[txid]] + + return await _this.axios.request(options) + }) + + // Wait for all parallel promisses to resolve. + const axiosResult = await _this.axios.all(promises) + + // Extract the data component from the axios response. + const result = axiosResult.map(x => x.data.result) + + res.status(200) + return res.json(result) + } catch (err) { + // Write out error to error log. + // logger.error(`Error in rawtransactions/decodeRawTransaction: `, err) + wlogger.error("Error in blockchain.ts/getTxOutProofBulk().", err) + + return _this.errorHandler(err, res) + } + } + + async verifyTxOutProofSingle(req, res, next) { + try { + // Validate input parameter + const proof = req.params.proof if (!proof || proof === "") { res.status(400) - return res.json({ error: `proof can not be empty: ${proof}` }) + return res.json({ error: "proof can not be empty" }) } + + // Axios options + const options = _this.routeUtils.getAxiosOptions() + + options.data.id = "verifytxoutproof" + options.data.method = "verifytxoutproof" + options.data.params = [req.params.proof] + + const response = await _this.axios.request(options) + + return res.json(response.data.result) + } catch (err) { + // Write out error to error log. + // logger.error(`Error in rawtransactions/decodeRawTransaction: `, err) + wlogger.error("Error in blockchain.ts/verifyTxOutProofSingle().", err) + + return _this.errorHandler(err, res) } + } - wlogger.debug( - `Executing blockchain/verifyTxOutProof with these proofs: `, - proofs - ) + async verifyTxOutProofBulk(req, res, next) { + try { + const proofs = req.body.proofs - // Loop through each proof and creates an array of requests to call in parallel - const promises = proofs.map(async proof => { - requestConfig.data.id = "verifytxoutproof" - requestConfig.data.method = "verifytxoutproof" - requestConfig.data.params = [proof] + // Reject if proofs is not an array. + if (!Array.isArray(proofs)) { + res.status(400) + return res.json({ + error: "proofs needs to be an array. Use GET for single proof." + }) + } - return await BitboxHTTP(requestConfig) - }) + // Enforce array size rate limits + if (!routeUtils.validateArraySize(req, proofs)) { + res.status(429) // https://github.com/Bitcoin-com/rest.bitcoin.com/issues/330 + return res.json({ + error: "Array too large." + }) + } - // Wait for all parallel promisses to resolve. - const axiosResult = await axios.all(promises) + // Axios options + const options = _this.routeUtils.getAxiosOptions() - // Extract the data component from the axios response. - const result = axiosResult.map(x => x.data.result[0]) + // Validate each element in the array. + for (let i = 0; i < proofs.length; i++) { + const proof = proofs[i] - res.status(200) - return res.json(result) - } catch (err) { - // Attempt to decode the error message. - const { msg, status } = routeUtils.decodeError(err) - if (msg) { - res.status(status) - return res.json({ error: msg }) + if (!proof || proof === "") { + res.status(400) + return res.json({ error: `proof can not be empty: ${proof}` }) + } + } + + wlogger.debug( + "Executing blockchain/verifyTxOutProof with these proofs: ", + proofs + ) + + // Loop through each proof and creates an array of requests to call in parallel + const promises = proofs.map(async proof => { + options.data.id = "verifytxoutproof" + options.data.method = "verifytxoutproof" + options.data.params = [proof] + + return await _this.axios.request(options) + }) + + // Wait for all parallel promisses to resolve. + const axiosResult = await _this.axios.all(promises) + + // Extract the data component from the axios response. + const result = axiosResult.map(x => x.data.result[0]) + + res.status(200) + return res.json(result) + } catch (err) { + // Write out error to error log. + // logger.error(`Error in rawtransactions/decodeRawTransaction: `, err) + wlogger.error("Error in blockchain.ts/verifyTxOutProofBulk().", err) + + return _this.errorHandler(err, res) } - - // Write out error to error log. - //logger.error(`Error in rawtransactions/decodeRawTransaction: `, err) - wlogger.error(`Error in blockchain.ts/verifyTxOutProofBulk().`, err) - - res.status(500) - return res.json({ error: util.inspect(err) }) } } -module.exports = { - router, - testableComponents: { - root, - getBestBlockHash, - //getBlock, - getBlockchainInfo, - getBlockCount, - getBlockHeaderSingle, - getBlockHeaderBulk, - getChainTips, - getDifficulty, - getMempoolInfo, - getRawMempool, - getMempoolEntrySingle, - getMempoolEntryBulk, - getMempoolAncestorsSingle, - getTxOut, - getTxOutProofSingle, - getTxOutProofBulk, - verifyTxOutProofSingle, - verifyTxOutProofBulk - } -} +module.exports = Blockchain diff --git a/src/routes/v3/full-node/blockchain2.js b/src/routes/v3/full-node/blockchain2.js deleted file mode 100644 index a7c103f..0000000 --- a/src/routes/v3/full-node/blockchain2.js +++ /dev/null @@ -1,133 +0,0 @@ -/* - A library for interacting with the Full Node -*/ - -"use strict" - -const express = require("express") -const router = express.Router() - -const axios = require("axios") -const wlogger = require("../../../util/winston-logging") - -const RouteUtils = require("../route-utils2") -const routeUtils = new RouteUtils() - -// Used to convert error messages to strings, to safely pass to users. -const util = require("util") -util.inspect.defaultOptions = { depth: 1 } - -const BCHJS = require("@chris.troutner/bch-js") -const bchjs = new BCHJS() - -let _this - -class Blockchain { - constructor() { - _this = this - - this.bchjs = bchjs - this.axios = axios - this.routeUtils = routeUtils - - this.router = router - this.router.get("/", this.root) - this.router.get("/getBestBlockHash", this.getBestBlockHash) - this.router.get("/getBlockchainInfo", this.getBlockchainInfo) - } - - root(req, res, next) { - return res.json({ status: "blockchain" }) - } - - // DRY error handler. - errorHandler(err, res) { - // Attempt to decode the error message. - const { msg, status } = this.routeUtils.decodeError(err) - if (msg) { - res.status(status) - return res.json({ error: msg }) - } - - res.status(500) - return res.json({ error: util.inspect(err) }) - } - - /** - * @api {get} /blockchain/getBestBlockHash Get best block hash - * @apiName GetBestBlockHash - * @apiGroup Blockchain - * @apiDescription Returns the hash of the best (tip) block in the longest - * block chain. - * - * @apiExample Example usage: - * curl -X GET "https://mainnet.bchjs.cash/v3/blockchain/getBestBlockHash" -H "accept: application/json" - * - * @apiSuccess {String} bestBlockHash 000000000000000002bc884334336d99c9a9c616670a9244c6a8c1fc35aa91a1 - */ - async getBestBlockHash(req, res, next) { - try { - // Axios options - const options = this.routeUtils.getAxiosOptions() - options.data.id = "getbestblockhash" - options.data.method = "getbestblockhash" - options.data.params = [] - - const response = await this.axios.request(options) - // console.log(`response.data: ${JSON.stringify(response.data, null, 2)}`) - - return res.json(response.data.result) - } catch (err) { - // Write out error to error log. - wlogger.error(`Error in blockchain.ts/getBestBlockHash().`, err) - - return this.errorHandler(err, res) - } - } - - /** - * @api {get} /blockchain/getBlockchainInfo Get blockchain info - * @apiName GetBlockchainInfo - * @apiGroup Blockchain - * @apiDescription Returns an object containing various state info regarding blockchain processing. - * - * @apiExample Example usage: - * curl -X GET "https://mainnet.bchjs.cash/v3/blockchain/getBlockchainInfo" -H "accept: application/json" - * - * @apiSuccess {Object} object Object containing data - * @apiSuccess {String} object.chain "main" - * @apiSuccess {Number} object.blocks 561838 - * @apiSuccess {Number} object.headers 561838 - * @apiSuccess {String} object.bestblockhash "000000000000000002307dd38cd01c7308b8febfcdf5772cf087b5bb023d55bc" - * @apiSuccess {Number} object.difficulty 246585566638.1496 - * @apiSuccess {String} object.mediantime 1545402693 - * @apiSuccess {Number} object.verificationprogress 0.999998831622689 - * @apiSuccess {Boolean} object.chainwork "000000000000000000000000000000000000000000d8c09a8ab7262080266b3e" - * @apiSuccess {Number} object.pruned false - * @apiSuccess {Array} object.softforks Array of objects - * @apiSuccess {String} object.softforks.id "bip34" - * @apiSuccess {String} object.softforks.version 2 - * @apiSuccess {Object} object.softforks.reject - * @apiSuccess {String} object.softforks.reject.status true - */ - async getBlockchainInfo(req, res, next) { - try { - // Axios options - const options = this.routeUtils.getAxiosOptions() - options.data.id = "getblockchaininfo" - options.data.method = "getblockchaininfo" - options.data.params = [] - - const response = await this.axios.request(options) - - return res.json(response.data.result) - } catch (err) { - // Write out error to error log. - wlogger.error(`Error in blockchain.ts/getBlockchainInfo().`, err) - - return this.errorHandler(err, res) - } - } -} - -module.exports = Blockchain diff --git a/test/v3/blockchain.js b/test/v3/blockchain.js index 96bd801..8aa718d 100644 --- a/test/v3/blockchain.js +++ b/test/v3/blockchain.js @@ -10,8 +10,10 @@ const chai = require("chai") const assert = chai.assert -const nock = require("nock") // HTTP mocking -const blockchainRoute = require("../../src/routes/v3/full-node/blockchain") +const sinon = require("sinon") + +const Blockchain = require("../../src/routes/v3/full-node/blockchain") +const uut = new Blockchain() const util = require("util") util.inspect.defaultOptions = { depth: 1 } @@ -26,9 +28,10 @@ let originalEnvVars // Used during transition from integration to unit tests. describe("#BlockchainRouter", () => { let req, res + let sandbox // local node will be started in regtest mode on the port 48332 - //before(panda.runLocalNode) + // before(panda.runLocalNode) before(() => { // Save existing environment variables. @@ -57,20 +60,17 @@ describe("#BlockchainRouter", () => { // Explicitly reset the parmas and body. req.params = {} req.body = {} - - // Activate nock if it's inactive. - if (!nock.isActive()) nock.activate() + sandbox = sinon.createSandbox() }) afterEach(() => { - // Clean up HTTP mocks. - nock.cleanAll() // clear interceptor list. - nock.restore() + // Restore Sandbox + sandbox.restore() }) after(() => { // otherwise the panda will run forever - //process.exit() + // process.exit() // Restore any pre-existing environment variables. process.env.BITCOINCOM_BASEURL = originalEnvVars.BITCOINCOM_BASEURL @@ -80,20 +80,14 @@ describe("#BlockchainRouter", () => { }) describe("#root", () => { - // root route handler. - const root = blockchainRoute.testableComponents.root - it("should respond to GET for base route", async () => { - const result = root(req, res) + const result = uut.root(req, res) assert.equal(result.status, "blockchain", "Returns static string") }) }) describe("getBestBlockHash()", () => { - // block route handler. - const getBestBlockHash = blockchainRoute.testableComponents.getBestBlockHash - it("should throw 503 when network issues", async () => { // Save the existing RPC URL. const savedUrl2 = process.env.RPC_BASEURL @@ -101,8 +95,8 @@ describe("#BlockchainRouter", () => { // Manipulate the URL to cause a 500 network error. process.env.RPC_BASEURL = "http://fakeurl/api/" - const result = await getBestBlockHash(req, res) - //console.log(`result: ${util.inspect(result)}`) + const result = await uut.getBestBlockHash(req, res) + // console.log(`result: ${util.inspect(result)}`) // Restore the saved URL. process.env.RPC_BASEURL = savedUrl2 @@ -115,20 +109,46 @@ describe("#BlockchainRouter", () => { ) }) - // it("return proper error connection is refused", async () => { - // - // }) + it("returns proper error when downstream service stalls", async () => { + // Mock the timeout error. + sandbox.stub(uut.axios, "request").throws({ code: "ECONNABORTED" }) + + const result = await uut.getBestBlockHash(req, res) + // console.log(`result: ${JSON.stringify(result, null, 2)}`) + + assert.isAbove(res.statusCode, 499, "HTTP status code 503 expected.") + assert.include( + result.error, + "Could not communicate with full node", + "Error message expected" + ) + }) + + it("returns proper error when downstream service is down", async () => { + // Mock the timeout error. + sandbox.stub(uut.axios, "request").throws({ code: "ECONNREFUSED" }) + + const result = await uut.getBestBlockHash(req, res) + // console.log(`result: ${JSON.stringify(result, null, 2)}`) + + assert.isAbove(res.statusCode, 499, "HTTP status code 503 expected.") + assert.include( + result.error, + "Could not communicate with full node", + "Error message expected" + ) + }) it("should GET /getBestBlockHash", async () => { // Mock the RPC call for unit tests. if (process.env.TEST === "unit") { - nock(`${process.env.RPC_BASEURL}`) - .post(uri => uri.includes("/")) - .reply(200, { result: mockData.mockBlockHash }) + sandbox + .stub(uut.axios, "request") + .resolves({ data: { result: mockData.mockBlockHash } }) } - const result = await getBestBlockHash(req, res) - // console.log(`result: ${util.inspect(result)}`) + const result = await uut.getBestBlockHash(req, res) + // console.log(`result: ${JSON.stringify(result, null, 2)}`) assert.isString(result) assert.equal(result.length, 64, "Hash string is fixed length") @@ -136,10 +156,6 @@ describe("#BlockchainRouter", () => { }) describe("getBlockchainInfo()", () => { - // block route handler. - const getBlockchainInfo = - blockchainRoute.testableComponents.getBlockchainInfo - it("should throw 503 when network issues", async () => { // Save the existing RPC URL. const savedUrl2 = process.env.RPC_BASEURL @@ -147,8 +163,8 @@ describe("#BlockchainRouter", () => { // Manipulate the URL to cause a 500 network error. process.env.RPC_BASEURL = "http://fakeurl/api/" - const result = await getBlockchainInfo(req, res) - //console.log(`result: ${util.inspect(result)}`) + const result = await uut.getBlockchainInfo(req, res) + // console.log(`result: ${util.inspect(result)}`) // Restore the saved URL. process.env.RPC_BASEURL = savedUrl2 @@ -161,16 +177,46 @@ describe("#BlockchainRouter", () => { ) }) + it("returns proper error when downstream service stalls", async () => { + // Mock the timeout error. + sandbox.stub(uut.axios, "request").throws({ code: "ECONNABORTED" }) + + const result = await uut.getBestBlockHash(req, res) + // console.log(`result: ${JSON.stringify(result, null, 2)}`) + + assert.isAbove(res.statusCode, 499, "HTTP status code 503 expected.") + assert.include( + result.error, + "Could not communicate with full node", + "Error message expected" + ) + }) + + it("returns proper error when downstream service is down", async () => { + // Mock the timeout error. + sandbox.stub(uut.axios, "request").throws({ code: "ECONNREFUSED" }) + + const result = await uut.getBestBlockHash(req, res) + // console.log(`result: ${JSON.stringify(result, null, 2)}`) + + assert.isAbove(res.statusCode, 499, "HTTP status code 503 expected.") + assert.include( + result.error, + "Could not communicate with full node", + "Error message expected" + ) + }) + it("should GET /getBlockchainInfo", async () => { // Mock the RPC call for unit tests. if (process.env.TEST === "unit") { - nock(`${process.env.RPC_BASEURL}`) - .post(uri => uri.includes("/")) - .reply(200, { result: mockData.mockBlockchainInfo }) + sandbox + .stub(uut.axios, "request") + .resolves({ data: { result: mockData.mockBlockchainInfo } }) } - const result = await getBlockchainInfo(req, res) - //console.log(`result: ${util.inspect(result)}`) + const result = await uut.getBlockchainInfo(req, res) + // console.log(`result: ${util.inspect(result)}`) assert.hasAnyKeys(result, [ "chain", @@ -189,9 +235,6 @@ describe("#BlockchainRouter", () => { }) describe("getBlockCount()", () => { - // block route handler. - const getBlockCount = blockchainRoute.testableComponents.getBlockCount - it("should throw 503 when network issues", async () => { // Save the existing RPC URL. const savedUrl2 = process.env.RPC_BASEURL @@ -199,8 +242,8 @@ describe("#BlockchainRouter", () => { // Manipulate the URL to cause a 500 network error. process.env.RPC_BASEURL = "http://fakeurl/api/" - const result = await getBlockCount(req, res) - //console.log(`result: ${util.inspect(result)}`) + const result = await uut.getBlockCount(req, res) + // console.log(`result: ${util.inspect(result)}`) // Restore the saved URL. process.env.RPC_BASEURL = savedUrl2 @@ -213,33 +256,58 @@ describe("#BlockchainRouter", () => { ) }) + it("returns proper error when downstream service stalls", async () => { + // Mock the timeout error. + sandbox.stub(uut.axios, "request").throws({ code: "ECONNABORTED" }) + + const result = await uut.getBlockCount(req, res) + // console.log(`result: ${JSON.stringify(result, null, 2)}`) + + assert.isAbove(res.statusCode, 499, "HTTP status code 503 expected.") + assert.include( + result.error, + "Could not communicate with full node", + "Error message expected" + ) + }) + it("returns proper error when downstream service is down", async () => { + // Mock the timeout error. + sandbox.stub(uut.axios, "request").throws({ code: "ECONNREFUSED" }) + + const result = await uut.getBlockCount(req, res) + // console.log(`result: ${JSON.stringify(result, null, 2)}`) + + assert.isAbove(res.statusCode, 499, "HTTP status code 503 expected.") + assert.include( + result.error, + "Could not communicate with full node", + "Error message expected" + ) + }) + it("should GET /getBlockCount", async () => { // Mock the RPC call for unit tests. if (process.env.TEST === "unit") { - nock(`${process.env.RPC_BASEURL}`) - .post(uri => uri.includes("/")) - .reply(200, { result: 126769 }) + sandbox + .stub(uut.axios, "request") + .resolves({ data: { result: 126769 } }) } - const result = await getBlockCount(req, res) - //console.log(`result: ${util.inspect(result)}`) + const result = await uut.getBlockCount(req, res) + // console.log(`result: ${util.inspect(result)}`) assert.isNumber(result) }) }) describe("getBlockHeaderSingle()", async () => { - const getBlockHeader = - blockchainRoute.testableComponents.getBlockHeaderSingle - it("should throw 400 error if hash is missing", async () => { - const result = await getBlockHeader(req, res) - //console.log(`result: ${util.inspect(result)}`) + const result = await uut.getBlockHeaderSingle(req, res) + // console.log(`result: ${util.inspect(result)}`) assert.hasAllKeys(result, ["error"]) assert.include(result.error, "hash can not be empty") }) - it("should throw 503 when network issues", async () => { // Save the existing RPC URL. const savedUrl2 = process.env.RPC_BASEURL @@ -250,8 +318,8 @@ describe("#BlockchainRouter", () => { req.params.hash = "00000000000008c3679777df34f1a09565f98b2400a05b7c8da72525fdca3900" - const result = await getBlockHeader(req, res) - //console.log(`result: ${util.inspect(result)}`) + const result = await uut.getBlockHeaderSingle(req, res) + // console.log(`result: ${util.inspect(result)}`) // Restore the saved URL. process.env.RPC_BASEURL = savedUrl2 @@ -263,22 +331,55 @@ describe("#BlockchainRouter", () => { "Error message expected" ) }) + it("returns proper error when downstream service stalls", async () => { + // Mock the timeout error. + sandbox.stub(uut.axios, "request").throws({ code: "ECONNABORTED" }) + req.params.hash = + "00000000000008c3679777df34f1a09565f98b2400a05b7c8da72525fdca3900" + + const result = await uut.getBlockHeaderSingle(req, res) + // console.log(`result: ${JSON.stringify(result, null, 2)}`) + + assert.isAbove(res.statusCode, 499, "HTTP status code 503 expected.") + assert.include( + result.error, + "Could not communicate with full node", + "Error message expected" + ) + }) + it("returns proper error when downstream service is down", async () => { + // Mock the timeout error. + sandbox.stub(uut.axios, "request").throws({ code: "ECONNREFUSED" }) + + req.params.hash = + "00000000000008c3679777df34f1a09565f98b2400a05b7c8da72525fdca3900" + + const result = await uut.getBlockHeaderSingle(req, res) + // console.log(`result: ${JSON.stringify(result, null, 2)}`) + + assert.isAbove(res.statusCode, 499, "HTTP status code 503 expected.") + assert.include( + result.error, + "Could not communicate with full node", + "Error message expected" + ) + }) it("should GET block header", async () => { // Mock the RPC call for unit tests. if (process.env.TEST === "unit") { - nock(`${process.env.RPC_BASEURL}`) - .post(uri => uri.includes("/")) - .reply(200, { + sandbox.stub(uut.axios, "request").resolves({ + data: { result: "000000202ed2723e7590e2b937f6821a99d6764cb8799bf30f8e300000000000000000001d311c02df9a1e3f57b8dbdcf97ec8dbc3109a26779724c63e560b29ad9ea501e2af955d286403183049e39c" - }) + } + }) } req.params.hash = "000000000000000002fa9d7851b284c53a5831651b04211c266badf2ad2d8ef0" - const result = await getBlockHeader(req, res) + const result = await uut.getBlockHeaderSingle(req, res) // console.log(`result: ${util.inspect(result)}`) assert.isString(result) @@ -291,17 +392,17 @@ describe("#BlockchainRouter", () => { it("should GET verbose block header", async () => { // Mock the RPC call for unit tests. if (process.env.TEST === "unit") { - nock(`${process.env.RPC_BASEURL}`) - .post(uri => uri.includes("/")) - .reply(200, { result: mockData.mockBlockHeader }) + sandbox + .stub(uut.axios, "request") + .resolves({ data: { result: mockData.mockBlockHeader } }) } req.query.verbose = true req.params.hash = "000000000000000002fa9d7851b284c53a5831651b04211c266badf2ad2d8ef0" - const result = await getBlockHeader(req, res) - //console.log(`result: ${util.inspect(result)}`) + const result = await uut.getBlockHeaderSingle(req, res) + // console.log(`result: ${util.inspect(result)}`) assert.hasAllKeys(result, [ "hash", @@ -322,15 +423,13 @@ describe("#BlockchainRouter", () => { }) }) - describe("#getBlockHeaderBulk", () => { - // route handler. - const getBlockHeaderBulk = - blockchainRoute.testableComponents.getBlockHeaderBulk + // + describe("#getBlockHeaderBulk", () => { it("should throw an error for an empty body", async () => { req.body = {} - const result = await getBlockHeaderBulk(req, res) + const result = await uut.getBlockHeaderBulk(req, res) assert.equal(res.statusCode, 400, "HTTP status code 400 expected.") assert.include( @@ -344,7 +443,7 @@ describe("#BlockchainRouter", () => { req.body.hashes = "00000000000008c3679777df34f1a09565f98b2400a05b7c8da72525fdca3900" - const result = await getBlockHeaderBulk(req, res) + const result = await uut.getBlockHeaderBulk(req, res) assert.equal(res.statusCode, 400, "HTTP status code 400 expected.") assert.include( @@ -360,8 +459,8 @@ describe("#BlockchainRouter", () => { req.body.hashes = testArray - const result = await getBlockHeaderBulk(req, res) - //console.log(`result: ${util.inspect(result)}`) + const result = await uut.getBlockHeaderBulk(req, res) + // console.log(`result: ${util.inspect(result)}`) assert.hasAllKeys(result, ["error"]) assert.include(result.error, "Array too large") @@ -370,7 +469,7 @@ describe("#BlockchainRouter", () => { it("should throw a 400 error for an invalid hash", async () => { req.body.hashes = ["badHash"] - await getBlockHeaderBulk(req, res) + await uut.getBlockHeaderBulk(req, res) // console.log(`result: ${util.inspect(result)}`) assert.equal(res.statusCode, 400, "HTTP status code 400 expected.") @@ -387,7 +486,7 @@ describe("#BlockchainRouter", () => { // Switch the Insight URL to something that will error out. process.env.BITCOINCOM_BASEURL = "http://fakeurl/api/" - const result = await getBlockHeaderBulk(req, res) + const result = await uut.getBlockHeaderBulk(req, res) // Restore the saved URL. process.env.BITCOINCOM_BASEURL = savedUrl @@ -399,6 +498,42 @@ describe("#BlockchainRouter", () => { process.env.BITCOINCOM_BASEURL = savedUrl } }) + it("returns proper error when downstream service stalls", async () => { + // Mock the timeout error. + sandbox.stub(uut.axios, "request").throws({ code: "ECONNABORTED" }) + + req.body.hashes = [ + "000000000000000002fa9d7851b284c53a5831651b04211c266badf2ad2d8ef0" + ] + + const result = await uut.getBlockHeaderBulk(req, res) + // console.log(`result: ${JSON.stringify(result, null, 2)}`) + + assert.isAbove(res.statusCode, 499, "HTTP status code 503 expected.") + assert.include( + result.error, + "Could not communicate with full node", + "Error message expected" + ) + }) + it("returns proper error when downstream service is down", async () => { + // Mock the timeout error. + sandbox.stub(uut.axios, "request").throws({ code: "ECONNREFUSED" }) + + req.body.hashes = [ + "000000000000000002fa9d7851b284c53a5831651b04211c266badf2ad2d8ef0" + ] + + const result = await uut.getBlockHeaderBulk(req, res) + // console.log(`result: ${JSON.stringify(result, null, 2)}`) + + assert.isAbove(res.statusCode, 499, "HTTP status code 503 expected.") + assert.include( + result.error, + "Could not communicate with full node", + "Error message expected" + ) + }) it("should get concise block header for a single hash", async () => { req.body.hashes = [ @@ -407,13 +542,13 @@ describe("#BlockchainRouter", () => { // Mock the RPC call for unit tests. if (process.env.TEST === "unit") { - nock(`${process.env.RPC_BASEURL}`) - .post(uri => uri.includes("/")) - .reply(200, { result: mockData.mockBlockHeaderConcise }) + sandbox + .stub(uut.axios, "request") + .resolves({ data: { result: mockData.mockBlockHeaderConcise } }) } // Call the details API. - const result = await getBlockHeaderBulk(req, res) + const result = await uut.getBlockHeaderBulk(req, res) // console.log(`result: ${util.inspect(result)}`) // Assert that required fields exist in the returned object. @@ -434,14 +569,14 @@ describe("#BlockchainRouter", () => { // Mock the RPC call for unit tests. if (process.env.TEST === "unit") { - nock(`${process.env.RPC_BASEURL}`) - .post(uri => uri.includes("/")) - .reply(200, { result: mockData.mockBlockHeader }) + sandbox + .stub(uut.axios, "request") + .resolves({ data: { result: mockData.mockBlockHeader } }) } // Call the details API. - const result = await getBlockHeaderBulk(req, res) - //console.log(`result: ${util.inspect(result)}`) + const result = await uut.getBlockHeaderBulk(req, res) + // console.log(`result: ${util.inspect(result)}`) // Assert that required fields exist in the returned object. assert.isArray(result) @@ -471,25 +606,20 @@ describe("#BlockchainRouter", () => { // Mock the RPC call for unit tests. if (process.env.TEST === "unit") { - nock(`${process.env.RPC_BASEURL}`) - .post(uri => uri.includes("/")) - .times(2) - .reply(200, { result: mockData.mockBlockHeaderConcise }) + sandbox + .stub(uut.axios, "request") + .resolves({ data: { result: mockData.mockBlockHeaderConcise } }) } // Call the details API. - const result = await getBlockHeaderBulk(req, res) - //console.log(`result: ${util.inspect(result)}`) + const result = await uut.getBlockHeaderBulk(req, res) + // console.log(`result: ${util.inspect(result)}`) assert.isArray(result) assert.equal(result.length, 2, "2 outputs for 2 inputs") }) }) - describe("getChainTips()", () => { - // block route handler. - const getChainTips = blockchainRoute.testableComponents.getChainTips - it("should throw 503 when network issues", async () => { // Save the existing RPC URL. const savedUrl2 = process.env.RPC_BASEURL @@ -497,8 +627,8 @@ describe("#BlockchainRouter", () => { // Manipulate the URL to cause a 500 network error. process.env.RPC_BASEURL = "http://fakeurl/api/" - const result = await getChainTips(req, res) - //console.log(`result: ${util.inspect(result)}`) + const result = await uut.getChainTips(req, res) + // console.log(`result: ${util.inspect(result)}`) // Restore the saved URL. process.env.RPC_BASEURL = savedUrl2 @@ -510,27 +640,51 @@ describe("#BlockchainRouter", () => { "Error message expected" ) }) + it("returns proper error when downstream service stalls", async () => { + // Mock the timeout error. + sandbox.stub(uut.axios, "request").throws({ code: "ECONNABORTED" }) + + const result = await uut.getChainTips(req, res) + // console.log(`result: ${JSON.stringify(result, null, 2)}`) + + assert.isAbove(res.statusCode, 499, "HTTP status code 503 expected.") + assert.include( + result.error, + "Could not communicate with full node", + "Error message expected" + ) + }) + it("returns proper error when downstream service is down", async () => { + // Mock the timeout error. + sandbox.stub(uut.axios, "request").throws({ code: "ECONNREFUSED" }) + + const result = await uut.getChainTips(req, res) + // console.log(`result: ${JSON.stringify(result, null, 2)}`) + + assert.isAbove(res.statusCode, 499, "HTTP status code 503 expected.") + assert.include( + result.error, + "Could not communicate with full node", + "Error message expected" + ) + }) it("should GET /getChainTips", async () => { // Mock the RPC call for unit tests. if (process.env.TEST === "unit") { - nock(`${process.env.RPC_BASEURL}`) - .post(uri => uri.includes("/")) - .reply(200, { result: mockData.mockChainTips }) + sandbox + .stub(uut.axios, "request") + .resolves({ data: { result: mockData.mockChainTips } }) } - const result = await getChainTips(req, res) - //console.log(`result: ${util.inspect(result)}`) + const result = await uut.getChainTips(req, res) + // console.log(`result: ${util.inspect(result)}`) assert.isArray(result) assert.hasAnyKeys(result[0], ["height", "hash", "branchlen", "status"]) }) }) - describe("getDifficulty()", () => { - // block route handler. - const getDifficulty = blockchainRoute.testableComponents.getDifficulty - it("should throw 503 when network issues", async () => { // Save the existing RPC URL. const savedUrl2 = process.env.RPC_BASEURL @@ -538,8 +692,8 @@ describe("#BlockchainRouter", () => { // Manipulate the URL to cause a 500 network error. process.env.RPC_BASEURL = "http://fakeurl/api/" - const result = await getDifficulty(req, res) - //console.log(`result: ${util.inspect(result)}`) + const result = await uut.getDifficulty(req, res) + // console.log(`result: ${util.inspect(result)}`) // Restore the saved URL. process.env.RPC_BASEURL = savedUrl2 @@ -551,26 +705,50 @@ describe("#BlockchainRouter", () => { "Error message expected" ) }) + it("returns proper error when downstream service stalls", async () => { + // Mock the timeout error. + sandbox.stub(uut.axios, "request").throws({ code: "ECONNABORTED" }) + + const result = await uut.getDifficulty(req, res) + // console.log(`result: ${JSON.stringify(result, null, 2)}`) + + assert.isAbove(res.statusCode, 499, "HTTP status code 503 expected.") + assert.include( + result.error, + "Could not communicate with full node", + "Error message expected" + ) + }) + it("returns proper error when downstream service is down", async () => { + // Mock the timeout error. + sandbox.stub(uut.axios, "request").throws({ code: "ECONNREFUSED" }) + + const result = await uut.getDifficulty(req, res) + // console.log(`result: ${JSON.stringify(result, null, 2)}`) + + assert.isAbove(res.statusCode, 499, "HTTP status code 503 expected.") + assert.include( + result.error, + "Could not communicate with full node", + "Error message expected" + ) + }) it("should GET /getDifficulty", async () => { // Mock the RPC call for unit tests. if (process.env.TEST === "unit") { - nock(`${process.env.RPC_BASEURL}`) - .post(uri => uri.includes("/")) - .reply(200, { result: 4049809.205246544 }) + sandbox + .stub(uut.axios, "request") + .resolves({ data: { result: 4049809.205246544 } }) } - const result = await getDifficulty(req, res) - //console.log(`result: ${util.inspect(result)}`) + const result = await uut.getDifficulty(req, res) + // console.log(`result: ${util.inspect(result)}`) assert.isNumber(result) }) }) - describe("getMempoolInfo()", () => { - // block route handler. - const getMempoolInfo = blockchainRoute.testableComponents.getMempoolInfo - it("should throw 503 when network issues", async () => { // Save the existing RPC URL. const savedUrl2 = process.env.RPC_BASEURL @@ -578,8 +756,8 @@ describe("#BlockchainRouter", () => { // Manipulate the URL to cause a 500 network error. process.env.RPC_BASEURL = "http://fakeurl/api/" - const result = await getMempoolInfo(req, res) - //console.log(`result: ${util.inspect(result)}`) + const result = await uut.getMempoolInfo(req, res) + // console.log(`result: ${util.inspect(result)}`) // Restore the saved URL. process.env.RPC_BASEURL = savedUrl2 @@ -591,17 +769,45 @@ describe("#BlockchainRouter", () => { "Error message expected" ) }) + it("returns proper error when downstream service stalls", async () => { + // Mock the timeout error. + sandbox.stub(uut.axios, "request").throws({ code: "ECONNABORTED" }) + + const result = await uut.getMempoolInfo(req, res) + // console.log(`result: ${JSON.stringify(result, null, 2)}`) + + assert.isAbove(res.statusCode, 499, "HTTP status code 503 expected.") + assert.include( + result.error, + "Could not communicate with full node", + "Error message expected" + ) + }) + it("returns proper error when downstream service is down", async () => { + // Mock the timeout error. + sandbox.stub(uut.axios, "request").throws({ code: "ECONNREFUSED" }) + + const result = await uut.getMempoolInfo(req, res) + // console.log(`result: ${JSON.stringify(result, null, 2)}`) + + assert.isAbove(res.statusCode, 499, "HTTP status code 503 expected.") + assert.include( + result.error, + "Could not communicate with full node", + "Error message expected" + ) + }) it("should GET /getMempoolInfo", async () => { // Mock the RPC call for unit tests. if (process.env.TEST === "unit") { - nock(`${process.env.RPC_BASEURL}`) - .post(uri => uri.includes("/")) - .reply(200, { result: mockData.mockMempoolInfo }) + sandbox + .stub(uut.axios, "request") + .resolves({ data: { result: mockData.mockMempoolInfo } }) } - const result = await getMempoolInfo(req, res) - //console.log(`result: ${util.inspect(result)}`) + const result = await uut.getMempoolInfo(req, res) + // console.log(`result: ${util.inspect(result)}`) assert.hasAnyKeys(result, [ "result", @@ -612,11 +818,7 @@ describe("#BlockchainRouter", () => { ]) }) }) - describe("getRawMempool()", () => { - // block route handler. - const getRawMempool = blockchainRoute.testableComponents.getRawMempool - it("should throw 503 when network issues", async () => { // Save the existing RPC URL. const savedUrl2 = process.env.RPC_BASEURL @@ -624,8 +826,8 @@ describe("#BlockchainRouter", () => { // Manipulate the URL to cause a 500 network error. process.env.RPC_BASEURL = "http://fakeurl/api/" - const result = await getRawMempool(req, res) - //console.log(`result: ${util.inspect(result)}`) + const result = await uut.getRawMempool(req, res) + // console.log(`result: ${util.inspect(result)}`) // Restore the saved URL. process.env.RPC_BASEURL = savedUrl2 @@ -637,31 +839,54 @@ describe("#BlockchainRouter", () => { "Error message expected" ) }) + it("returns proper error when downstream service stalls", async () => { + // Mock the timeout error. + sandbox.stub(uut.axios, "request").throws({ code: "ECONNABORTED" }) + + const result = await uut.getRawMempool(req, res) + // console.log(`result: ${JSON.stringify(result, null, 2)}`) + + assert.isAbove(res.statusCode, 499, "HTTP status code 503 expected.") + assert.include( + result.error, + "Could not communicate with full node", + "Error message expected" + ) + }) + it("returns proper error when downstream service is down", async () => { + // Mock the timeout error. + sandbox.stub(uut.axios, "request").throws({ code: "ECONNREFUSED" }) + + const result = await uut.getRawMempool(req, res) + // console.log(`result: ${JSON.stringify(result, null, 2)}`) + + assert.isAbove(res.statusCode, 499, "HTTP status code 503 expected.") + assert.include( + result.error, + "Could not communicate with full node", + "Error message expected" + ) + }) it("should GET /getMempoolInfo", async () => { // Mock the RPC call for unit tests. if (process.env.TEST === "unit") { - nock(`${process.env.RPC_BASEURL}`) - .post(uri => uri.includes("/")) - .reply(200, { result: mockData.mockRawMempool }) + sandbox + .stub(uut.axios, "request") + .resolves({ data: { result: mockData.mockRawMempool } }) } - const result = await getRawMempool(req, res) - //console.log(`result: ${util.inspect(result)}`) + const result = await uut.getRawMempool(req, res) + // console.log(`result: ${util.inspect(result)}`) assert.isArray(result) // Not sure what other assertions should be made here. }) }) - - describe("getMempoolEntry()", () => { - // block route handler. - const getMempoolEntry = - blockchainRoute.testableComponents.getMempoolEntrySingle - + describe("getMempoolEntrySingle()", () => { it("should throw 400 if txid is empty", async () => { - const result = await getMempoolEntry(req, res) - //console.log(`result: ${util.inspect(result)}`) + const result = await uut.getMempoolEntrySingle(req, res) + // console.log(`result: ${util.inspect(result)}`) assert.hasAllKeys(result, ["error"]) assert.include(result.error, "txid can not be empty") @@ -674,10 +899,11 @@ describe("#BlockchainRouter", () => { // Manipulate the URL to cause a 500 network error. process.env.RPC_BASEURL = "http://fakeurl/api/" - req.params.txid = `d65881582ff2bff36747d7a0d0e273f10281abc8bd5c15df5d72f8f3fa779cde` + req.params.txid = + "d65881582ff2bff36747d7a0d0e273f10281abc8bd5c15df5d72f8f3fa779cde" - const result = await getMempoolEntry(req, res) - //console.log(`result: ${util.inspect(result)}`) + const result = await uut.getMempoolEntrySingle(req, res) + // console.log(`result: ${util.inspect(result)}`) // Restore the saved URL. process.env.RPC_BASEURL = savedUrl2 @@ -689,35 +915,65 @@ describe("#BlockchainRouter", () => { "Error message expected" ) }) + it("returns proper error when downstream service stalls", async () => { + // Mock the timeout error. + sandbox.stub(uut.axios, "request").throws({ code: "ECONNABORTED" }) + + req.params.txid = + "d65881582ff2bff36747d7a0d0e273f10281abc8bd5c15df5d72f8f3fa779cde" + + const result = await uut.getMempoolEntrySingle(req, res) + // console.log(`result: ${JSON.stringify(result, null, 2)}`) + + assert.isAbove(res.statusCode, 499, "HTTP status code 503 expected.") + assert.include( + result.error, + "Could not communicate with full node", + "Error message expected" + ) + }) + it("returns proper error when downstream service is down", async () => { + // Mock the timeout error. + sandbox.stub(uut.axios, "request").throws({ code: "ECONNREFUSED" }) + + req.params.txid = + "d65881582ff2bff36747d7a0d0e273f10281abc8bd5c15df5d72f8f3fa779cde" + + const result = await uut.getMempoolEntrySingle(req, res) + // console.log(`result: ${JSON.stringify(result, null, 2)}`) + + assert.isAbove(res.statusCode, 499, "HTTP status code 503 expected.") + assert.include( + result.error, + "Could not communicate with full node", + "Error message expected" + ) + }) it("should GET /getMempoolEntry", async () => { // Mock the RPC call for unit tests. if (process.env.TEST === "unit") { - nock(`${process.env.RPC_BASEURL}`) - .post(uri => uri.includes("/")) - .reply(200, { result: { error: "Transaction not in mempool" } }) + sandbox.stub(uut.axios, "request").resolves({ + data: { result: { error: "Transaction not in mempool" } } + }) } - req.params.txid = `d65881582ff2bff36747d7a0d0e273f10281abc8bd5c15df5d72f8f3fa779cde` + req.params.txid = + "d65881582ff2bff36747d7a0d0e273f10281abc8bd5c15df5d72f8f3fa779cde" - const result = await getMempoolEntry(req, res) - //console.log(`result: ${util.inspect(result)}`) + const result = await uut.getMempoolEntrySingle(req, res) + // console.log(`result: ${util.inspect(result)}`) assert.hasAllKeys(result, ["error"]) assert.isString(result.error) assert.equal(result.error, "Transaction not in mempool") }) }) - describe("#getMempoolEntryBulk", () => { - // route handler. - const getMempoolEntryBulk = - blockchainRoute.testableComponents.getMempoolEntryBulk - it("should throw an error for an empty body", async () => { req.body = {} - const result = await getMempoolEntryBulk(req, res) + const result = await uut.getMempoolEntryBulk(req, res) assert.equal(res.statusCode, 400, "HTTP status code 400 expected.") assert.include( @@ -728,9 +984,10 @@ describe("#BlockchainRouter", () => { }) it("should error on non-array single txid", async () => { - req.body.txids = `d65881582ff2bff36747d7a0d0e273f10281abc8bd5c15df5d72f8f3fa779cde` + req.body.txids = + "d65881582ff2bff36747d7a0d0e273f10281abc8bd5c15df5d72f8f3fa779cde" - const result = await getMempoolEntryBulk(req, res) + const result = await uut.getMempoolEntryBulk(req, res) assert.equal(res.statusCode, 400, "HTTP status code 400 expected.") assert.include( @@ -746,13 +1003,48 @@ describe("#BlockchainRouter", () => { req.body.txids = testArray - const result = await getMempoolEntryBulk(req, res) - //console.log(`result: ${util.inspect(result)}`) + const result = await uut.getMempoolEntryBulk(req, res) + // console.log(`result: ${util.inspect(result)}`) assert.hasAllKeys(result, ["error"]) assert.include(result.error, "Array too large") }) + it("returns proper error when downstream service stalls", async () => { + // Mock the timeout error. + sandbox.stub(uut.axios, "request").throws({ code: "ECONNABORTED" }) + req.body.txids = [ + "d65881582ff2bff36747d7a0d0e273f10281abc8bd5c15df5d72f8f3fa779cde" + ] + + const result = await uut.getMempoolEntryBulk(req, res) + // console.log(`result: ${JSON.stringify(result, null, 2)}`) + + assert.isAbove(res.statusCode, 499, "HTTP status code 503 expected.") + assert.include( + result.error, + "Could not communicate with full node", + "Error message expected" + ) + }) + it("returns proper error when downstream service is down", async () => { + // Mock the timeout error. + sandbox.stub(uut.axios, "request").throws({ code: "ECONNREFUSED" }) + + req.body.txids = [ + "d65881582ff2bff36747d7a0d0e273f10281abc8bd5c15df5d72f8f3fa779cde" + ] + + const result = await uut.getMempoolEntryBulk(req, res) + // console.log(`result: ${JSON.stringify(result, null, 2)}`) + + assert.isAbove(res.statusCode, 499, "HTTP status code 503 expected.") + assert.include( + result.error, + "Could not communicate with full node", + "Error message expected" + ) + }) // Only execute on integration tests. if (process.env.TEST !== "unit") { // Dev-note: This test passes because it expects an error. TXIDs do not @@ -760,11 +1052,11 @@ describe("#BlockchainRouter", () => { // integration test. it("should retrieve single mempool entry", async () => { req.body.txids = [ - `d65881582ff2bff36747d7a0d0e273f10281abc8bd5c15df5d72f8f3fa779cde` + "d65881582ff2bff36747d7a0d0e273f10281abc8bd5c15df5d72f8f3fa779cde" ] - const result = await getMempoolEntryBulk(req, res) - //console.log(`result: ${util.inspect(result)}`) + const result = await uut.getMempoolEntryBulk(req, res) + // console.log(`result: ${util.inspect(result)}`) assert.hasAllKeys(result, ["error"]) assert.isString(result.error) @@ -776,12 +1068,12 @@ describe("#BlockchainRouter", () => { // integration test. it("should retrieve multiple mempool entries", async () => { req.body.txids = [ - `d65881582ff2bff36747d7a0d0e273f10281abc8bd5c15df5d72f8f3fa779cde`, - `d65881582ff2bff36747d7a0d0e273f10281abc8bd5c15df5d72f8f3fa779cde` + "d65881582ff2bff36747d7a0d0e273f10281abc8bd5c15df5d72f8f3fa779cde", + "d65881582ff2bff36747d7a0d0e273f10281abc8bd5c15df5d72f8f3fa779cde" ] - const result = await getMempoolEntryBulk(req, res) - //console.log(`result: ${util.inspect(result)}`) + const result = await uut.getMempoolEntryBulk(req, res) + // console.log(`result: ${util.inspect(result)}`) assert.hasAllKeys(result, ["error"]) assert.isString(result.error) @@ -789,15 +1081,10 @@ describe("#BlockchainRouter", () => { }) } }) - describe("getMempoolAncestorsSingle()", () => { - // block route handler. - const getMempoolAncestorsSingle = - blockchainRoute.testableComponents.getMempoolAncestorsSingle - it("should throw 400 if txid is empty", async () => { - const result = await getMempoolAncestorsSingle(req, res) - //console.log(`result: ${util.inspect(result)}`) + const result = await uut.getMempoolAncestorsSingle(req, res) + // console.log(`result: ${util.inspect(result)}`) assert.hasAllKeys(result, ["error"]) assert.include(result.error, "txid can not be empty") @@ -810,10 +1097,11 @@ describe("#BlockchainRouter", () => { // Manipulate the URL to cause a 500 network error. process.env.RPC_BASEURL = "http://fakeurl/api/" - req.params.txid = `d65881582ff2bff36747d7a0d0e273f10281abc8bd5c15df5d72f8f3fa779cde` + req.params.txid = + "d65881582ff2bff36747d7a0d0e273f10281abc8bd5c15df5d72f8f3fa779cde" - const result = await getMempoolAncestorsSingle(req, res) - //console.log(`result: ${util.inspect(result)}`) + const result = await uut.getMempoolAncestorsSingle(req, res) + // console.log(`result: ${util.inspect(result)}`) // Restore the saved URL. process.env.RPC_BASEURL = savedUrl2 @@ -825,37 +1113,67 @@ describe("#BlockchainRouter", () => { "Error message expected" ) }) + it("returns proper error when downstream service stalls", async () => { + // Mock the timeout error. + sandbox.stub(uut.axios, "request").throws({ code: "ECONNABORTED" }) + req.params.txid = + "d65881582ff2bff36747d7a0d0e273f10281abc8bd5c15df5d72f8f3fa779cde" + + const result = await uut.getMempoolAncestorsSingle(req, res) + // console.log(`result: ${JSON.stringify(result, null, 2)}`) + + assert.isAbove(res.statusCode, 499, "HTTP status code 503 expected.") + assert.include( + result.error, + "Could not communicate with full node", + "Error message expected" + ) + }) + it("returns proper error when downstream service is down", async () => { + // Mock the timeout error. + sandbox.stub(uut.axios, "request").throws({ code: "ECONNREFUSED" }) + + req.params.txid = + "d65881582ff2bff36747d7a0d0e273f10281abc8bd5c15df5d72f8f3fa779cde" + + const result = await uut.getMempoolAncestorsSingle(req, res) + // console.log(`result: ${JSON.stringify(result, null, 2)}`) + + assert.isAbove(res.statusCode, 499, "HTTP status code 503 expected.") + assert.include( + result.error, + "Could not communicate with full node", + "Error message expected" + ) + }) it("should GET /getMempoolAncestorsSingle", async () => { - nock(`${process.env.RPC_BASEURL}`) - .post(uri => uri.includes("/")) - .reply(200, { result: mockData.mockAncestors }) + sandbox + .stub(uut.axios, "request") + .resolves({ data: { result: mockData.mockAncestors } }) - req.params.txid = `bb0d349892d351da2767f8c45f6f7949713ff09bd12838d53e76158ddee3ce93` + req.params.txid = + "bb0d349892d351da2767f8c45f6f7949713ff09bd12838d53e76158ddee3ce93" - const result = await getMempoolAncestorsSingle(req, res) + const result = await uut.getMempoolAncestorsSingle(req, res) // console.log(`result: ${util.inspect(result)}`) assert.isArray(result) }) }) - describe("getTxOut()", () => { - // block route handler. - const getTxOut = blockchainRoute.testableComponents.getTxOut - it("should throw 400 if txid is empty", async () => { - const result = await getTxOut(req, res) - //console.log(`result: ${util.inspect(result)}`) + const result = await uut.getTxOut(req, res) + // console.log(`result: ${util.inspect(result)}`) assert.hasAllKeys(result, ["error"]) assert.include(result.error, "txid can not be empty") }) it("should throw 400 if n is empty", async () => { - req.params.txid = `sometxid` - const result = await getTxOut(req, res) - //console.log(`result: ${util.inspect(result)}`) + req.params.txid = "sometxid" + const result = await uut.getTxOut(req, res) + // console.log(`result: ${util.inspect(result)}`) assert.hasAllKeys(result, ["error"]) assert.include(result.error, "n can not be empty") @@ -868,11 +1186,12 @@ describe("#BlockchainRouter", () => { // Manipulate the URL to cause a 500 network error. process.env.RPC_BASEURL = "http://fakeurl/api/" - req.params.txid = `d65881582ff2bff36747d7a0d0e273f10281abc8bd5c15df5d72f8f3fa779cde` + req.params.txid = + "d65881582ff2bff36747d7a0d0e273f10281abc8bd5c15df5d72f8f3fa779cde" req.params.n = 0 - const result = await getTxOut(req, res) - //console.log(`result: ${util.inspect(result)}`) + const result = await uut.getTxOut(req, res) + // console.log(`result: ${util.inspect(result)}`) // Restore the saved URL. process.env.RPC_BASEURL = savedUrl2 @@ -885,20 +1204,59 @@ describe("#BlockchainRouter", () => { ) }) + it("returns proper error when downstream service stalls", async () => { + // Mock the timeout error. + sandbox.stub(uut.axios, "request").throws({ code: "ECONNABORTED" }) + + req.params.txid = + "197dcda59864b1eee05498fd3c52cad787ec56ab7e635503cb39f9ab6f295d5d" + req.params.n = 0 + req.query.include_mempool = "true" + + const result = await uut.getTxOut(req, res) + // console.log(`result: ${JSON.stringify(result, null, 2)}`) + + assert.isAbove(res.statusCode, 499, "HTTP status code 503 expected.") + assert.include( + result.error, + "Could not communicate with full node", + "Error message expected" + ) + }) + it("returns proper error when downstream service is down", async () => { + // Mock the timeout error. + sandbox.stub(uut.axios, "request").throws({ code: "ECONNREFUSED" }) + + req.params.txid = + "197dcda59864b1eee05498fd3c52cad787ec56ab7e635503cb39f9ab6f295d5d" + req.params.n = 0 + req.query.include_mempool = "true" + + const result = await uut.getTxOut(req, res) + // console.log(`result: ${JSON.stringify(result, null, 2)}`) + + assert.isAbove(res.statusCode, 499, "HTTP status code 503 expected.") + assert.include( + result.error, + "Could not communicate with full node", + "Error message expected" + ) + }) // This test can only run for unit tests. See TODO at the top of this file. it("should GET /getTxOut", async () => { // Mock the RPC call for unit tests. if (process.env.TEST === "unit") { - nock(`${process.env.RPC_BASEURL}`) - .post(uri => uri.includes("/")) - .reply(200, { result: mockData.mockTxOut }) + sandbox + .stub(uut.axios, "request") + .resolves({ data: { result: mockData.mockTxOut } }) } - req.params.txid = `197dcda59864b1eee05498fd3c52cad787ec56ab7e635503cb39f9ab6f295d5d` + req.params.txid = + "197dcda59864b1eee05498fd3c52cad787ec56ab7e635503cb39f9ab6f295d5d" req.params.n = 0 req.query.include_mempool = "true" - const result = await getTxOut(req, res) + const result = await uut.getTxOut(req, res) // console.log(`result: ${JSON.stringify(result, null, 2)}`) assert.hasAllKeys(result, [ @@ -918,13 +1276,10 @@ describe("#BlockchainRouter", () => { assert.isArray(result.scriptPubKey.addresses) }) }) - - describe("getTxOutProof()", () => { - const getTxOutProof = blockchainRoute.testableComponents.getTxOutProofSingle - + describe("getTxOutProofSingle()", () => { it("should throw 400 if txid is empty", async () => { - const result = await getTxOutProof(req, res) - //console.log(`result: ${util.inspect(result)}`) + const result = await uut.getTxOutProofSingle(req, res) + // console.log(`result: ${util.inspect(result)}`) assert.hasAllKeys(result, ["error"]) assert.include(result.error, "txid can not be empty") @@ -937,10 +1292,11 @@ describe("#BlockchainRouter", () => { // Manipulate the URL to cause a 500 network error. process.env.RPC_BASEURL = "http://fakeurl/api/" - req.params.txid = `d65881582ff2bff36747d7a0d0e273f10281abc8bd5c15df5d72f8f3fa779cde` + req.params.txid = + "d65881582ff2bff36747d7a0d0e273f10281abc8bd5c15df5d72f8f3fa779cde" - const result = await getTxOutProof(req, res) - //console.log(`result: ${util.inspect(result)}`) + const result = await uut.getTxOutProofSingle(req, res) + // console.log(`result: ${util.inspect(result)}`) // Restore the saved URL. process.env.RPC_BASEURL = savedUrl2 @@ -952,33 +1308,67 @@ describe("#BlockchainRouter", () => { "Error message expected" ) }) + it("returns proper error when downstream service stalls", async () => { + // Mock the timeout error. + sandbox.stub(uut.axios, "request").throws({ code: "ECONNABORTED" }) + + req.params.txid = + "197dcda59864b1eee05498fd3c52cad787ec56ab7e635503cb39f9ab6f295d5d" + req.params.n = 0 + req.query.include_mempool = "true" + + const result = await uut.getTxOutProofSingle(req, res) + // console.log(`result: ${JSON.stringify(result, null, 2)}`) + + assert.isAbove(res.statusCode, 499, "HTTP status code 503 expected.") + assert.include( + result.error, + "Could not communicate with full node", + "Error message expected" + ) + }) + it("returns proper error when downstream service is down", async () => { + // Mock the timeout error. + sandbox.stub(uut.axios, "request").throws({ code: "ECONNREFUSED" }) + + req.params.txid = + "197dcda59864b1eee05498fd3c52cad787ec56ab7e635503cb39f9ab6f295d5d" + req.params.n = 0 + req.query.include_mempool = "true" + + const result = await uut.getTxOutProofSingle(req, res) + // console.log(`result: ${JSON.stringify(result, null, 2)}`) + + assert.isAbove(res.statusCode, 499, "HTTP status code 503 expected.") + assert.include( + result.error, + "Could not communicate with full node", + "Error message expected" + ) + }) it("should GET /getTxOutProof", async () => { // Mock the RPC call for unit tests. if (process.env.TEST === "unit") { - nock(`${process.env.RPC_BASEURL}`) - .post(uri => uri.includes("/")) - .reply(200, { result: mockData.mockTxOutProof }) + sandbox + .stub(uut.axios, "request") + .resolves({ data: { result: mockData.mockTxOutProof } }) } - req.params.txid = `2c7ae9f865f7ce0c33c189b2f83414176903ce4b06ed9f8b7bcf55efbd4a7266` + req.params.txid = + "2c7ae9f865f7ce0c33c189b2f83414176903ce4b06ed9f8b7bcf55efbd4a7266" - const result = await getTxOutProof(req, res) + const result = await uut.getTxOutProofSingle(req, res) // console.log(`result: ${JSON.stringify(result, null, 2)}`) assert.isString(result) }) }) - describe("#getTxOutProofBulk", () => { - // route handler. - const getTxOutProofBulk = - blockchainRoute.testableComponents.getTxOutProofBulk - it("should throw an error for an empty body", async () => { req.body = {} - const result = await getTxOutProofBulk(req, res) + const result = await uut.getTxOutProofBulk(req, res) assert.equal(res.statusCode, 400, "HTTP status code 400 expected.") assert.include( @@ -989,9 +1379,10 @@ describe("#BlockchainRouter", () => { }) it("should error on non-array single txid", async () => { - req.body.txids = `d65881582ff2bff36747d7a0d0e273f10281abc8bd5c15df5d72f8f3fa779cde` + req.body.txids = + "d65881582ff2bff36747d7a0d0e273f10281abc8bd5c15df5d72f8f3fa779cde" - const result = await getTxOutProofBulk(req, res) + const result = await uut.getTxOutProofBulk(req, res) assert.equal(res.statusCode, 400, "HTTP status code 400 expected.") assert.include( @@ -1007,27 +1398,63 @@ describe("#BlockchainRouter", () => { req.body.txids = testArray - const result = await getTxOutProofBulk(req, res) - //console.log(`result: ${util.inspect(result)}`) + const result = await uut.getTxOutProofBulk(req, res) + // console.log(`result: ${util.inspect(result)}`) assert.hasAllKeys(result, ["error"]) assert.include(result.error, "Array too large") }) + it("returns proper error when downstream service stalls", async () => { + // Mock the timeout error. + sandbox.stub(uut.axios, "request").throws({ code: "ECONNABORTED" }) + + req.body.txids = [ + "2c7ae9f865f7ce0c33c189b2f83414176903ce4b06ed9f8b7bcf55efbd4a7266" + ] + + const result = await uut.getTxOutProofBulk(req, res) + // console.log(`result: ${JSON.stringify(result, null, 2)}`) + + assert.isAbove(res.statusCode, 499, "HTTP status code 503 expected.") + assert.include( + result.error, + "Could not communicate with full node", + "Error message expected" + ) + }) + it("returns proper error when downstream service is down", async () => { + // Mock the timeout error. + sandbox.stub(uut.axios, "request").throws({ code: "ECONNREFUSED" }) + + req.body.txids = [ + "2c7ae9f865f7ce0c33c189b2f83414176903ce4b06ed9f8b7bcf55efbd4a7266" + ] + + const result = await uut.getTxOutProofBulk(req, res) + // console.log(`result: ${JSON.stringify(result, null, 2)}`) + + assert.isAbove(res.statusCode, 499, "HTTP status code 503 expected.") + assert.include( + result.error, + "Could not communicate with full node", + "Error message expected" + ) + }) it("should GET proof for single txid", async () => { // Mock the RPC call for unit tests. if (process.env.TEST === "unit") { - nock(`${process.env.RPC_BASEURL}`) - .post(uri => uri.includes("/")) - .reply(200, { result: mockData.mockTxOutProof }) + sandbox + .stub(uut.axios, "request") + .resolves({ data: { result: mockData.mockTxOutProof } }) } req.body.txids = [ - `2c7ae9f865f7ce0c33c189b2f83414176903ce4b06ed9f8b7bcf55efbd4a7266` + "2c7ae9f865f7ce0c33c189b2f83414176903ce4b06ed9f8b7bcf55efbd4a7266" ] - const result = await getTxOutProofBulk(req, res) - //console.log(`result: ${JSON.stringify(result, null, 2)}`) + const result = await uut.getTxOutProofBulk(req, res) + // console.log(`result: ${JSON.stringify(result, null, 2)}`) assert.isArray(result) assert.isString(result[0]) @@ -1036,32 +1463,27 @@ describe("#BlockchainRouter", () => { it("should GET proof for multiple txids", async () => { // Mock the RPC call for unit tests. if (process.env.TEST === "unit") { - nock(`${process.env.RPC_BASEURL}`) - .post(uri => uri.includes("/")) - .times(2) - .reply(200, { result: mockData.mockTxOutProof }) + sandbox + .stub(uut.axios, "request") + .resolves({ data: { result: mockData.mockTxOutProof } }) } req.body.txids = [ - `2c7ae9f865f7ce0c33c189b2f83414176903ce4b06ed9f8b7bcf55efbd4a7266`, - `2c7ae9f865f7ce0c33c189b2f83414176903ce4b06ed9f8b7bcf55efbd4a7266` + "2c7ae9f865f7ce0c33c189b2f83414176903ce4b06ed9f8b7bcf55efbd4a7266", + "2c7ae9f865f7ce0c33c189b2f83414176903ce4b06ed9f8b7bcf55efbd4a7266" ] - const result = await getTxOutProofBulk(req, res) - //console.log(`result: ${JSON.stringify(result, null, 2)}`) + const result = await uut.getTxOutProofBulk(req, res) + // console.log(`result: ${JSON.stringify(result, null, 2)}`) assert.isArray(result) assert.equal(result.length, 2, "Correct length of returned array") }) }) - - describe("verifyTxOutProof()", () => { - const verifyTxOutProof = - blockchainRoute.testableComponents.verifyTxOutProofSingle - + describe("verifyTxOutProofSingle()", () => { it("should throw 400 if proof is empty", async () => { - const result = await verifyTxOutProof(req, res) - //console.log(`result: ${util.inspect(result)}`) + const result = await uut.verifyTxOutProofSingle(req, res) + // console.log(`result: ${util.inspect(result)}`) assert.hasAllKeys(result, ["error"]) assert.include(result.error, "proof can not be empty") @@ -1076,8 +1498,8 @@ describe("#BlockchainRouter", () => { req.params.proof = mockData.mockTxOutProof - const result = await verifyTxOutProof(req, res) - //console.log(`result: ${util.inspect(result)}`) + const result = await uut.verifyTxOutProofSingle(req, res) + // console.log(`result: ${util.inspect(result)}`) // Restore the saved URL. process.env.RPC_BASEURL = savedUrl2 @@ -1089,39 +1511,67 @@ describe("#BlockchainRouter", () => { "Error message expected" ) }) + it("returns proper error when downstream service stalls", async () => { + // Mock the timeout error. + sandbox.stub(uut.axios, "request").throws({ code: "ECONNABORTED" }) + req.params.proof = + "000000202ed2723e7590e2b937f6821a99d6764cb8799bf30f8e300000000000000000001d311c02df9a1e3f57b8dbdcf97ec8dbc3109a26779724c63e560b29ad9ea501e2af955d286403183049e39c1e000000067139f230701a2819a76795564bd2f67ded7eeae68596f368eddb3dd5bc54e59320e896f71d61cfc3ae3d4a90fca08b1aa35ba91256d8939d2cad11e638c0081f66724abdef55cf7b8b9fed064bce0369171434f8b289c1330ccef765f8e97a2c0d794d81aafb535855f7daa6bb51e40f77c6b59d7af7f62d0eb726a4fc4df82353d56fcbda7c7ea6bd935d61af8fb3b295637e6f323b10231135b3f10a034cfb238f635830c0595e52c6c31247cf677b555f7a287076e20cd0e1d3cc9af7260f02b700" + + const result = await uut.verifyTxOutProofSingle(req, res) + // console.log(`result: ${JSON.stringify(result, null, 2)}`) + + assert.isAbove(res.statusCode, 499, "HTTP status code 503 expected.") + assert.include( + result.error, + "Could not communicate with full node", + "Error message expected" + ) + }) + it("returns proper error when downstream service is down", async () => { + // Mock the timeout error. + sandbox.stub(uut.axios, "request").throws({ code: "ECONNREFUSED" }) + + req.params.proof = + "000000202ed2723e7590e2b937f6821a99d6764cb8799bf30f8e300000000000000000001d311c02df9a1e3f57b8dbdcf97ec8dbc3109a26779724c63e560b29ad9ea501e2af955d286403183049e39c1e000000067139f230701a2819a76795564bd2f67ded7eeae68596f368eddb3dd5bc54e59320e896f71d61cfc3ae3d4a90fca08b1aa35ba91256d8939d2cad11e638c0081f66724abdef55cf7b8b9fed064bce0369171434f8b289c1330ccef765f8e97a2c0d794d81aafb535855f7daa6bb51e40f77c6b59d7af7f62d0eb726a4fc4df82353d56fcbda7c7ea6bd935d61af8fb3b295637e6f323b10231135b3f10a034cfb238f635830c0595e52c6c31247cf677b555f7a287076e20cd0e1d3cc9af7260f02b700" + + const result = await uut.verifyTxOutProofSingle(req, res) + // console.log(`result: ${JSON.stringify(result, null, 2)}`) + + assert.isAbove(res.statusCode, 499, "HTTP status code 503 expected.") + assert.include( + result.error, + "Could not communicate with full node", + "Error message expected" + ) + }) it("should GET /verifyTxOutProof", async () => { const expected = "2c7ae9f865f7ce0c33c189b2f83414176903ce4b06ed9f8b7bcf55efbd4a7266" // Mock the RPC call for unit tests. if (process.env.TEST === "unit") { - nock(`${process.env.RPC_BASEURL}`) - .post(uri => uri.includes("/")) - .reply(200, { result: [expected] }) + sandbox + .stub(uut.axios, "request") + .resolves({ data: { result: [expected] } }) } req.params.proof = "000000202ed2723e7590e2b937f6821a99d6764cb8799bf30f8e300000000000000000001d311c02df9a1e3f57b8dbdcf97ec8dbc3109a26779724c63e560b29ad9ea501e2af955d286403183049e39c1e000000067139f230701a2819a76795564bd2f67ded7eeae68596f368eddb3dd5bc54e59320e896f71d61cfc3ae3d4a90fca08b1aa35ba91256d8939d2cad11e638c0081f66724abdef55cf7b8b9fed064bce0369171434f8b289c1330ccef765f8e97a2c0d794d81aafb535855f7daa6bb51e40f77c6b59d7af7f62d0eb726a4fc4df82353d56fcbda7c7ea6bd935d61af8fb3b295637e6f323b10231135b3f10a034cfb238f635830c0595e52c6c31247cf677b555f7a287076e20cd0e1d3cc9af7260f02b700" - const result = await verifyTxOutProof(req, res) - //console.log(`result: ${JSON.stringify(result, null, 2)}`) + const result = await uut.verifyTxOutProofSingle(req, res) + // console.log(`result: ${JSON.stringify(result, null, 2)}`) assert.isArray(result) assert.isString(result[0]) assert.equal(result[0], expected) }) }) - describe("#verifyTxOutProofBulk", () => { - // route handler. - const verifyTxOutProofBulk = - blockchainRoute.testableComponents.verifyTxOutProofBulk - it("should throw an error for an empty body", async () => { req.body = {} - const result = await verifyTxOutProofBulk(req, res) + const result = await uut.verifyTxOutProofBulk(req, res) assert.equal(res.statusCode, 400, "HTTP status code 400 expected.") assert.include( @@ -1134,7 +1584,7 @@ describe("#BlockchainRouter", () => { it("should error on non-array single txid", async () => { req.body.proofs = mockData.mockTxOutProof - const result = await verifyTxOutProofBulk(req, res) + const result = await uut.verifyTxOutProofBulk(req, res) assert.equal(res.statusCode, 400, "HTTP status code 400 expected.") assert.include( @@ -1150,30 +1600,65 @@ describe("#BlockchainRouter", () => { req.body.proofs = testArray - const result = await verifyTxOutProofBulk(req, res) - //console.log(`result: ${util.inspect(result)}`) + const result = await uut.verifyTxOutProofBulk(req, res) + // console.log(`result: ${util.inspect(result)}`) assert.hasAllKeys(result, ["error"]) assert.include(result.error, "Array too large") }) + it("returns proper error when downstream service stalls", async () => { + // Mock the timeout error. + sandbox.stub(uut.axios, "request").throws({ code: "ECONNABORTED" }) + + req.body.proofs = [ + "000000202ed2723e7590e2b937f6821a99d6764cb8799bf30f8e300000000000000000001d311c02df9a1e3f57b8dbdcf97ec8dbc3109a26779724c63e560b29ad9ea501e2af955d286403183049e39c1e000000067139f230701a2819a76795564bd2f67ded7eeae68596f368eddb3dd5bc54e59320e896f71d61cfc3ae3d4a90fca08b1aa35ba91256d8939d2cad11e638c0081f66724abdef55cf7b8b9fed064bce0369171434f8b289c1330ccef765f8e97a2c0d794d81aafb535855f7daa6bb51e40f77c6b59d7af7f62d0eb726a4fc4df82353d56fcbda7c7ea6bd935d61af8fb3b295637e6f323b10231135b3f10a034cfb238f635830c0595e52c6c31247cf677b555f7a287076e20cd0e1d3cc9af7260f02b700" + ] + const result = await uut.verifyTxOutProofBulk(req, res) + // console.log(`result: ${JSON.stringify(result, null, 2)}`) + + assert.isAbove(res.statusCode, 499, "HTTP status code 503 expected.") + assert.include( + result.error, + "Could not communicate with full node", + "Error message expected" + ) + }) + it("returns proper error when downstream service is down", async () => { + // Mock the timeout error. + sandbox.stub(uut.axios, "request").throws({ code: "ECONNREFUSED" }) + + req.body.proofs = [ + "000000202ed2723e7590e2b937f6821a99d6764cb8799bf30f8e300000000000000000001d311c02df9a1e3f57b8dbdcf97ec8dbc3109a26779724c63e560b29ad9ea501e2af955d286403183049e39c1e000000067139f230701a2819a76795564bd2f67ded7eeae68596f368eddb3dd5bc54e59320e896f71d61cfc3ae3d4a90fca08b1aa35ba91256d8939d2cad11e638c0081f66724abdef55cf7b8b9fed064bce0369171434f8b289c1330ccef765f8e97a2c0d794d81aafb535855f7daa6bb51e40f77c6b59d7af7f62d0eb726a4fc4df82353d56fcbda7c7ea6bd935d61af8fb3b295637e6f323b10231135b3f10a034cfb238f635830c0595e52c6c31247cf677b555f7a287076e20cd0e1d3cc9af7260f02b700" + ] + const result = await uut.verifyTxOutProofBulk(req, res) + // console.log(`result: ${JSON.stringify(result, null, 2)}`) + + assert.isAbove(res.statusCode, 499, "HTTP status code 503 expected.") + assert.include( + result.error, + "Could not communicate with full node", + "Error message expected" + ) + }) + it("should get single proof", async () => { const expected = "2c7ae9f865f7ce0c33c189b2f83414176903ce4b06ed9f8b7bcf55efbd4a7266" // Mock the RPC call for unit tests. if (process.env.TEST === "unit") { - nock(`${process.env.RPC_BASEURL}`) - .post(uri => uri.includes("/")) - .reply(200, { result: [expected] }) + sandbox + .stub(uut.axios, "request") + .resolves({ data: { result: [expected] } }) } req.body.proofs = [ "000000202ed2723e7590e2b937f6821a99d6764cb8799bf30f8e300000000000000000001d311c02df9a1e3f57b8dbdcf97ec8dbc3109a26779724c63e560b29ad9ea501e2af955d286403183049e39c1e000000067139f230701a2819a76795564bd2f67ded7eeae68596f368eddb3dd5bc54e59320e896f71d61cfc3ae3d4a90fca08b1aa35ba91256d8939d2cad11e638c0081f66724abdef55cf7b8b9fed064bce0369171434f8b289c1330ccef765f8e97a2c0d794d81aafb535855f7daa6bb51e40f77c6b59d7af7f62d0eb726a4fc4df82353d56fcbda7c7ea6bd935d61af8fb3b295637e6f323b10231135b3f10a034cfb238f635830c0595e52c6c31247cf677b555f7a287076e20cd0e1d3cc9af7260f02b700" ] - const result = await verifyTxOutProofBulk(req, res) - //console.log(`result: ${JSON.stringify(result, null, 2)}`) + const result = await uut.verifyTxOutProofBulk(req, res) + // console.log(`result: ${JSON.stringify(result, null, 2)}`) assert.isArray(result) assert.isString(result[0]) @@ -1186,10 +1671,9 @@ describe("#BlockchainRouter", () => { // Mock the RPC call for unit tests. if (process.env.TEST === "unit") { - nock(`${process.env.RPC_BASEURL}`) - .post(uri => uri.includes("/")) - .times(2) - .reply(200, { result: [expected] }) + sandbox + .stub(uut.axios, "request") + .resolves({ data: { result: [expected] } }) } req.body.proofs = [ @@ -1197,8 +1681,8 @@ describe("#BlockchainRouter", () => { "000000202ed2723e7590e2b937f6821a99d6764cb8799bf30f8e300000000000000000001d311c02df9a1e3f57b8dbdcf97ec8dbc3109a26779724c63e560b29ad9ea501e2af955d286403183049e39c1e000000067139f230701a2819a76795564bd2f67ded7eeae68596f368eddb3dd5bc54e59320e896f71d61cfc3ae3d4a90fca08b1aa35ba91256d8939d2cad11e638c0081f66724abdef55cf7b8b9fed064bce0369171434f8b289c1330ccef765f8e97a2c0d794d81aafb535855f7daa6bb51e40f77c6b59d7af7f62d0eb726a4fc4df82353d56fcbda7c7ea6bd935d61af8fb3b295637e6f323b10231135b3f10a034cfb238f635830c0595e52c6c31247cf677b555f7a287076e20cd0e1d3cc9af7260f02b700" ] - const result = await verifyTxOutProofBulk(req, res) - //console.log(`result: ${JSON.stringify(result, null, 2)}`) + const result = await uut.verifyTxOutProofBulk(req, res) + // console.log(`result: ${JSON.stringify(result, null, 2)}`) assert.isArray(result) assert.isString(result[0]) diff --git a/test/v3/blockchain2.js b/test/v3/blockchain2.js deleted file mode 100644 index debaa2d..0000000 --- a/test/v3/blockchain2.js +++ /dev/null @@ -1,1271 +0,0 @@ -/* - TODO: - -getRawMempool - --Add tests for 'verbose' input values - -getMempoolEntry & getMempoolEntryBulk - --Needs e2e test to create unconfirmed tx, for real-world test. -*/ - -"use strict" - -const chai = require("chai") -const assert = chai.assert -const nock = require("nock") // HTTP mocking -const sinon = require("sinon") -// const blockchainRoute = require("../../src/routes/v3/full-node/blockchain") - -const Blockchain = require("../../src/routes/v3/full-node/blockchain2") -const uut = new Blockchain() - -const util = require("util") -util.inspect.defaultOptions = { depth: 1 } - -if (!process.env.TEST) process.env.TEST = "unit" - -// Mocking data. -const { mockReq, mockRes } = require("./mocks/express-mocks") -const mockData = require("./mocks/blockchain-mock") - -let originalEnvVars // Used during transition from integration to unit tests. - -describe("#BlockchainRouter2", () => { - let req, res - let sandbox - - // local node will be started in regtest mode on the port 48332 - //before(panda.runLocalNode) - - before(() => { - // Save existing environment variables. - originalEnvVars = { - BITCOINCOM_BASEURL: process.env.BITCOINCOM_BASEURL, - RPC_BASEURL: process.env.RPC_BASEURL, - RPC_USERNAME: process.env.RPC_USERNAME, - RPC_PASSWORD: process.env.RPC_PASSWORD - } - - // Set default environment variables for unit tests. - if (process.env.TEST === "unit") { - process.env.BITCOINCOM_BASEURL = "http://fakeurl/api/" - process.env.RPC_BASEURL = "http://fakeurl/api" - process.env.RPC_USERNAME = "fakeusername" - process.env.RPC_PASSWORD = "fakepassword" - } - }) - - // Setup the mocks before each test. - beforeEach(() => { - // Mock the req and res objects used by Express routes. - req = mockReq - res = mockRes - - // Explicitly reset the parmas and body. - req.params = {} - req.body = {} - - // Activate nock if it's inactive. - if (!nock.isActive()) nock.activate() - - sandbox = sinon.createSandbox() - }) - - afterEach(() => { - // Clean up HTTP mocks. - nock.cleanAll() // clear interceptor list. - nock.restore() - - sandbox.restore() - }) - - after(() => { - // otherwise the panda will run forever - //process.exit() - - // Restore any pre-existing environment variables. - process.env.BITCOINCOM_BASEURL = originalEnvVars.BITCOINCOM_BASEURL - process.env.RPC_BASEURL = originalEnvVars.RPC_BASEURL - process.env.RPC_USERNAME = originalEnvVars.RPC_USERNAME - process.env.RPC_PASSWORD = originalEnvVars.RPC_PASSWORD - }) - - describe("#root", () => { - it("should respond to GET for base route", async () => { - const result = uut.root(req, res) - - assert.equal(result.status, "blockchain", "Returns static string") - }) - }) - - describe("getBestBlockHash()", () => { - // block route handler. - // const getBestBlockHash = blockchainRoute.testableComponents.getBestBlockHash - - it("should throw 503 when network issues", async () => { - // Save the existing RPC URL. - const savedUrl2 = process.env.RPC_BASEURL - - // Manipulate the URL to cause a 500 network error. - process.env.RPC_BASEURL = "http://fakeurl/api/" - - const result = await uut.getBestBlockHash(req, res) - // console.log(`result: ${util.inspect(result)}`) - - // Restore the saved URL. - process.env.RPC_BASEURL = savedUrl2 - - assert.isAbove(res.statusCode, 499, "HTTP status code 503 expected.") - assert.include( - result.error, - "Could not communicate with full node", - "Error message expected" - ) - }) - - it("returns proper error when downstream service stalls", async () => { - // Mock the timeout error. - sandbox.stub(uut.axios, "request").throws({ code: "ECONNABORTED" }) - - const result = await uut.getBestBlockHash(req, res) - // console.log(`result: ${JSON.stringify(result, null, 2)}`) - - assert.isAbove(res.statusCode, 499, "HTTP status code 503 expected.") - assert.include( - result.error, - "Could not communicate with full node", - "Error message expected" - ) - }) - - it("returns proper error when downstream service is down", async () => { - // Mock the timeout error. - sandbox.stub(uut.axios, "request").throws({ code: "ECONNREFUSED" }) - - const result = await uut.getBestBlockHash(req, res) - // console.log(`result: ${JSON.stringify(result, null, 2)}`) - - assert.isAbove(res.statusCode, 499, "HTTP status code 503 expected.") - assert.include( - result.error, - "Could not communicate with full node", - "Error message expected" - ) - }) - - it("should GET /getBestBlockHash", async () => { - // Mock the RPC call for unit tests. - if (process.env.TEST === "unit") { - sandbox - .stub(uut.axios, "request") - .resolves({ data: { result: mockData.mockBlockHash } }) - } - - const result = await uut.getBestBlockHash(req, res) - // console.log(`result: ${JSON.stringify(result, null, 2)}`) - - assert.isString(result) - assert.equal(result.length, 64, "Hash string is fixed length") - }) - }) - - describe("getBlockchainInfo()", () => { - // block route handler. - // const getBlockchainInfo = - // blockchainRoute.testableComponents.getBlockchainInfo - - it("should throw 503 when network issues", async () => { - // Save the existing RPC URL. - const savedUrl2 = process.env.RPC_BASEURL - - // Manipulate the URL to cause a 500 network error. - process.env.RPC_BASEURL = "http://fakeurl/api/" - - const result = await uut.getBlockchainInfo(req, res) - //console.log(`result: ${util.inspect(result)}`) - - // Restore the saved URL. - process.env.RPC_BASEURL = savedUrl2 - - assert.isAbove(res.statusCode, 499, "HTTP status code 503 expected.") - assert.include( - result.error, - "Could not communicate with full node", - "Error message expected" - ) - }) - - it("returns proper error when downstream service stalls", async () => { - // Mock the timeout error. - sandbox.stub(uut.axios, "request").throws({ code: "ECONNABORTED" }) - - const result = await uut.getBestBlockHash(req, res) - // console.log(`result: ${JSON.stringify(result, null, 2)}`) - - assert.isAbove(res.statusCode, 499, "HTTP status code 503 expected.") - assert.include( - result.error, - "Could not communicate with full node", - "Error message expected" - ) - }) - - it("returns proper error when downstream service is down", async () => { - // Mock the timeout error. - sandbox.stub(uut.axios, "request").throws({ code: "ECONNREFUSED" }) - - const result = await uut.getBestBlockHash(req, res) - // console.log(`result: ${JSON.stringify(result, null, 2)}`) - - assert.isAbove(res.statusCode, 499, "HTTP status code 503 expected.") - assert.include( - result.error, - "Could not communicate with full node", - "Error message expected" - ) - }) - - it("should GET /getBlockchainInfo", async () => { - // Mock the RPC call for unit tests. - if (process.env.TEST === "unit") { - nock(`${process.env.RPC_BASEURL}`) - .post(uri => uri.includes("/")) - .reply(200, { result: mockData.mockBlockchainInfo }) - } - - const result = await uut.getBlockchainInfo(req, res) - //console.log(`result: ${util.inspect(result)}`) - - assert.hasAnyKeys(result, [ - "chain", - "blocks", - "headers", - "bestblockhash", - "difficulty", - "mediantime", - "verificationprogress", - "chainwork", - "pruned", - "softforks", - "bip9_softforks" - ]) - }) - }) - - // describe("getBlockCount()", () => { - // // block route handler. - // const getBlockCount = blockchainRoute.testableComponents.getBlockCount - // - // it("should throw 503 when network issues", async () => { - // // Save the existing RPC URL. - // const savedUrl2 = process.env.RPC_BASEURL - // - // // Manipulate the URL to cause a 500 network error. - // process.env.RPC_BASEURL = "http://fakeurl/api/" - // - // const result = await getBlockCount(req, res) - // //console.log(`result: ${util.inspect(result)}`) - // - // // Restore the saved URL. - // process.env.RPC_BASEURL = savedUrl2 - // - // assert.isAbove(res.statusCode, 499, "HTTP status code 503 expected.") - // assert.include( - // result.error, - // "Could not communicate with full node", - // "Error message expected" - // ) - // }) - // - // it("should GET /getBlockCount", async () => { - // // Mock the RPC call for unit tests. - // if (process.env.TEST === "unit") { - // nock(`${process.env.RPC_BASEURL}`) - // .post(uri => uri.includes("/")) - // .reply(200, { result: 126769 }) - // } - // - // const result = await getBlockCount(req, res) - // //console.log(`result: ${util.inspect(result)}`) - // - // assert.isNumber(result) - // }) - // }) - // - // describe("getBlockHeaderSingle()", async () => { - // const getBlockHeader = - // blockchainRoute.testableComponents.getBlockHeaderSingle - // - // it("should throw 400 error if hash is missing", async () => { - // const result = await getBlockHeader(req, res) - // //console.log(`result: ${util.inspect(result)}`) - // - // assert.hasAllKeys(result, ["error"]) - // assert.include(result.error, "hash can not be empty") - // }) - // - // it("should throw 503 when network issues", async () => { - // // Save the existing RPC URL. - // const savedUrl2 = process.env.RPC_BASEURL - // - // // Manipulate the URL to cause a 500 network error. - // process.env.RPC_BASEURL = "http://fakeurl/api/" - // - // req.params.hash = - // "00000000000008c3679777df34f1a09565f98b2400a05b7c8da72525fdca3900" - // - // const result = await getBlockHeader(req, res) - // //console.log(`result: ${util.inspect(result)}`) - // - // // Restore the saved URL. - // process.env.RPC_BASEURL = savedUrl2 - // - // assert.isAbove(res.statusCode, 499, "HTTP status code 503 expected.") - // assert.include( - // result.error, - // "Network error: Could not communicate with full node", - // "Error message expected" - // ) - // }) - // - // it("should GET block header", async () => { - // // Mock the RPC call for unit tests. - // if (process.env.TEST === "unit") { - // nock(`${process.env.RPC_BASEURL}`) - // .post(uri => uri.includes("/")) - // .reply(200, { - // result: - // "000000202ed2723e7590e2b937f6821a99d6764cb8799bf30f8e300000000000000000001d311c02df9a1e3f57b8dbdcf97ec8dbc3109a26779724c63e560b29ad9ea501e2af955d286403183049e39c" - // }) - // } - // - // req.params.hash = - // "000000000000000002fa9d7851b284c53a5831651b04211c266badf2ad2d8ef0" - // - // const result = await getBlockHeader(req, res) - // // console.log(`result: ${util.inspect(result)}`) - // - // assert.isString(result) - // assert.equal( - // result, - // "000000202ed2723e7590e2b937f6821a99d6764cb8799bf30f8e300000000000000000001d311c02df9a1e3f57b8dbdcf97ec8dbc3109a26779724c63e560b29ad9ea501e2af955d286403183049e39c" - // ) - // }) - // - // it("should GET verbose block header", async () => { - // // Mock the RPC call for unit tests. - // if (process.env.TEST === "unit") { - // nock(`${process.env.RPC_BASEURL}`) - // .post(uri => uri.includes("/")) - // .reply(200, { result: mockData.mockBlockHeader }) - // } - // - // req.query.verbose = true - // req.params.hash = - // "000000000000000002fa9d7851b284c53a5831651b04211c266badf2ad2d8ef0" - // - // const result = await getBlockHeader(req, res) - // //console.log(`result: ${util.inspect(result)}`) - // - // assert.hasAllKeys(result, [ - // "hash", - // "confirmations", - // "height", - // "version", - // "versionHex", - // "merkleroot", - // "time", - // "mediantime", - // "nonce", - // "bits", - // "difficulty", - // "chainwork", - // "previousblockhash", - // "nextblockhash" - // ]) - // }) - // }) - // - // describe("#getBlockHeaderBulk", () => { - // // route handler. - // const getBlockHeaderBulk = - // blockchainRoute.testableComponents.getBlockHeaderBulk - // - // it("should throw an error for an empty body", async () => { - // req.body = {} - // - // const result = await getBlockHeaderBulk(req, res) - // - // assert.equal(res.statusCode, 400, "HTTP status code 400 expected.") - // assert.include( - // result.error, - // "hashes needs to be an array", - // "Proper error message" - // ) - // }) - // - // it("should error on non-array single hash", async () => { - // req.body.hashes = - // "00000000000008c3679777df34f1a09565f98b2400a05b7c8da72525fdca3900" - // - // const result = await getBlockHeaderBulk(req, res) - // - // assert.equal(res.statusCode, 400, "HTTP status code 400 expected.") - // assert.include( - // result.error, - // "hashes needs to be an array", - // "Proper error message" - // ) - // }) - // - // it("should throw 400 error if addresses array is too large", async () => { - // const testArray = [] - // for (var i = 0; i < 25; i++) testArray.push("") - // - // req.body.hashes = testArray - // - // const result = await getBlockHeaderBulk(req, res) - // //console.log(`result: ${util.inspect(result)}`) - // - // assert.hasAllKeys(result, ["error"]) - // assert.include(result.error, "Array too large") - // }) - // - // it("should throw a 400 error for an invalid hash", async () => { - // req.body.hashes = ["badHash"] - // - // await getBlockHeaderBulk(req, res) - // // console.log(`result: ${util.inspect(result)}`) - // - // assert.equal(res.statusCode, 400, "HTTP status code 400 expected.") - // }) - // - // it("should throw 500 when network issues", async () => { - // const savedUrl = process.env.BITCOINCOM_BASEURL - // - // try { - // req.body.hashes = [ - // "00000000000008c3679777df34f1a09565f98b2400a05b7c8da72525fdca3900" - // ] - // - // // Switch the Insight URL to something that will error out. - // process.env.BITCOINCOM_BASEURL = "http://fakeurl/api/" - // - // const result = await getBlockHeaderBulk(req, res) - // - // // Restore the saved URL. - // process.env.BITCOINCOM_BASEURL = savedUrl - // - // assert.equal(res.statusCode, 500, "HTTP status code 500 expected.") - // assert.include(result.error, "ENOTFOUND", "Error message expected") - // } catch (err) { - // // Restore the saved URL. - // process.env.BITCOINCOM_BASEURL = savedUrl - // } - // }) - // - // it("should get concise block header for a single hash", async () => { - // req.body.hashes = [ - // "000000000000000002fa9d7851b284c53a5831651b04211c266badf2ad2d8ef0" - // ] - // - // // Mock the RPC call for unit tests. - // if (process.env.TEST === "unit") { - // nock(`${process.env.RPC_BASEURL}`) - // .post(uri => uri.includes("/")) - // .reply(200, { result: mockData.mockBlockHeaderConcise }) - // } - // - // // Call the details API. - // const result = await getBlockHeaderBulk(req, res) - // // console.log(`result: ${util.inspect(result)}`) - // - // // Assert that required fields exist in the returned object. - // assert.isArray(result) - // assert.equal( - // result[0], - // "000000202ed2723e7590e2b937f6821a99d6764cb8799bf30f8e300000000000000000001d311c02df9a1e3f57b8dbdcf97ec8dbc3109a26779724c63e560b29ad9ea501e2af955d286403183049e39c" - // ) - // }) - // - // it("should get verbose block header for a single hash", async () => { - // req.body = { - // hashes: [ - // "000000000000000002fa9d7851b284c53a5831651b04211c266badf2ad2d8ef0" - // ], - // verbose: true - // } - // - // // Mock the RPC call for unit tests. - // if (process.env.TEST === "unit") { - // nock(`${process.env.RPC_BASEURL}`) - // .post(uri => uri.includes("/")) - // .reply(200, { result: mockData.mockBlockHeader }) - // } - // - // // Call the details API. - // const result = await getBlockHeaderBulk(req, res) - // //console.log(`result: ${util.inspect(result)}`) - // - // // Assert that required fields exist in the returned object. - // assert.isArray(result) - // assert.hasAllKeys(result[0], [ - // "hash", - // "confirmations", - // "height", - // "version", - // "versionHex", - // "merkleroot", - // "time", - // "mediantime", - // "nonce", - // "bits", - // "difficulty", - // "chainwork", - // "previousblockhash", - // "nextblockhash" - // ]) - // }) - // - // it("should get details for multiple block heights", async () => { - // req.body.hashes = [ - // "000000000000000002fa9d7851b284c53a5831651b04211c266badf2ad2d8ef0", - // "000000000000000002fa9d7851b284c53a5831651b04211c266badf2ad2d8ef0" - // ] - // - // // Mock the RPC call for unit tests. - // if (process.env.TEST === "unit") { - // nock(`${process.env.RPC_BASEURL}`) - // .post(uri => uri.includes("/")) - // .times(2) - // .reply(200, { result: mockData.mockBlockHeaderConcise }) - // } - // - // // Call the details API. - // const result = await getBlockHeaderBulk(req, res) - // //console.log(`result: ${util.inspect(result)}`) - // - // assert.isArray(result) - // assert.equal(result.length, 2, "2 outputs for 2 inputs") - // }) - // }) - // - // describe("getChainTips()", () => { - // // block route handler. - // const getChainTips = blockchainRoute.testableComponents.getChainTips - // - // it("should throw 503 when network issues", async () => { - // // Save the existing RPC URL. - // const savedUrl2 = process.env.RPC_BASEURL - // - // // Manipulate the URL to cause a 500 network error. - // process.env.RPC_BASEURL = "http://fakeurl/api/" - // - // const result = await getChainTips(req, res) - // //console.log(`result: ${util.inspect(result)}`) - // - // // Restore the saved URL. - // process.env.RPC_BASEURL = savedUrl2 - // - // assert.isAbove(res.statusCode, 499, "HTTP status code 503 expected.") - // assert.include( - // result.error, - // "Could not communicate with full node", - // "Error message expected" - // ) - // }) - // - // it("should GET /getChainTips", async () => { - // // Mock the RPC call for unit tests. - // if (process.env.TEST === "unit") { - // nock(`${process.env.RPC_BASEURL}`) - // .post(uri => uri.includes("/")) - // .reply(200, { result: mockData.mockChainTips }) - // } - // - // const result = await getChainTips(req, res) - // //console.log(`result: ${util.inspect(result)}`) - // - // assert.isArray(result) - // assert.hasAnyKeys(result[0], ["height", "hash", "branchlen", "status"]) - // }) - // }) - // - // describe("getDifficulty()", () => { - // // block route handler. - // const getDifficulty = blockchainRoute.testableComponents.getDifficulty - // - // it("should throw 503 when network issues", async () => { - // // Save the existing RPC URL. - // const savedUrl2 = process.env.RPC_BASEURL - // - // // Manipulate the URL to cause a 500 network error. - // process.env.RPC_BASEURL = "http://fakeurl/api/" - // - // const result = await getDifficulty(req, res) - // //console.log(`result: ${util.inspect(result)}`) - // - // // Restore the saved URL. - // process.env.RPC_BASEURL = savedUrl2 - // - // assert.isAbove(res.statusCode, 499, "HTTP status code 503 expected.") - // assert.include( - // result.error, - // "Could not communicate with full node", - // "Error message expected" - // ) - // }) - // - // it("should GET /getDifficulty", async () => { - // // Mock the RPC call for unit tests. - // if (process.env.TEST === "unit") { - // nock(`${process.env.RPC_BASEURL}`) - // .post(uri => uri.includes("/")) - // .reply(200, { result: 4049809.205246544 }) - // } - // - // const result = await getDifficulty(req, res) - // //console.log(`result: ${util.inspect(result)}`) - // - // assert.isNumber(result) - // }) - // }) - // - // describe("getMempoolInfo()", () => { - // // block route handler. - // const getMempoolInfo = blockchainRoute.testableComponents.getMempoolInfo - // - // it("should throw 503 when network issues", async () => { - // // Save the existing RPC URL. - // const savedUrl2 = process.env.RPC_BASEURL - // - // // Manipulate the URL to cause a 500 network error. - // process.env.RPC_BASEURL = "http://fakeurl/api/" - // - // const result = await getMempoolInfo(req, res) - // //console.log(`result: ${util.inspect(result)}`) - // - // // Restore the saved URL. - // process.env.RPC_BASEURL = savedUrl2 - // - // assert.isAbove(res.statusCode, 499, "HTTP status code 503 expected.") - // assert.include( - // result.error, - // "Could not communicate with full node", - // "Error message expected" - // ) - // }) - // - // it("should GET /getMempoolInfo", async () => { - // // Mock the RPC call for unit tests. - // if (process.env.TEST === "unit") { - // nock(`${process.env.RPC_BASEURL}`) - // .post(uri => uri.includes("/")) - // .reply(200, { result: mockData.mockMempoolInfo }) - // } - // - // const result = await getMempoolInfo(req, res) - // //console.log(`result: ${util.inspect(result)}`) - // - // assert.hasAnyKeys(result, [ - // "result", - // "bytes", - // "usage", - // "maxmempool", - // "mempoolminfree" - // ]) - // }) - // }) - // - // describe("getRawMempool()", () => { - // // block route handler. - // const getRawMempool = blockchainRoute.testableComponents.getRawMempool - // - // it("should throw 503 when network issues", async () => { - // // Save the existing RPC URL. - // const savedUrl2 = process.env.RPC_BASEURL - // - // // Manipulate the URL to cause a 500 network error. - // process.env.RPC_BASEURL = "http://fakeurl/api/" - // - // const result = await getRawMempool(req, res) - // //console.log(`result: ${util.inspect(result)}`) - // - // // Restore the saved URL. - // process.env.RPC_BASEURL = savedUrl2 - // - // assert.isAbove(res.statusCode, 499, "HTTP status code 503 expected.") - // assert.include( - // result.error, - // "Could not communicate with full node", - // "Error message expected" - // ) - // }) - // - // it("should GET /getMempoolInfo", async () => { - // // Mock the RPC call for unit tests. - // if (process.env.TEST === "unit") { - // nock(`${process.env.RPC_BASEURL}`) - // .post(uri => uri.includes("/")) - // .reply(200, { result: mockData.mockRawMempool }) - // } - // - // const result = await getRawMempool(req, res) - // //console.log(`result: ${util.inspect(result)}`) - // - // assert.isArray(result) - // // Not sure what other assertions should be made here. - // }) - // }) - // - // describe("getMempoolEntry()", () => { - // // block route handler. - // const getMempoolEntry = - // blockchainRoute.testableComponents.getMempoolEntrySingle - // - // it("should throw 400 if txid is empty", async () => { - // const result = await getMempoolEntry(req, res) - // //console.log(`result: ${util.inspect(result)}`) - // - // assert.hasAllKeys(result, ["error"]) - // assert.include(result.error, "txid can not be empty") - // }) - // - // it("should throw 503 when network issues", async () => { - // // Save the existing RPC URL. - // const savedUrl2 = process.env.RPC_BASEURL - // - // // Manipulate the URL to cause a 500 network error. - // process.env.RPC_BASEURL = "http://fakeurl/api/" - // - // req.params.txid = `d65881582ff2bff36747d7a0d0e273f10281abc8bd5c15df5d72f8f3fa779cde` - // - // const result = await getMempoolEntry(req, res) - // //console.log(`result: ${util.inspect(result)}`) - // - // // Restore the saved URL. - // process.env.RPC_BASEURL = savedUrl2 - // - // assert.isAbove(res.statusCode, 499, "HTTP status code 503 expected.") - // assert.include( - // result.error, - // "Could not communicate with full node", - // "Error message expected" - // ) - // }) - // - // it("should GET /getMempoolEntry", async () => { - // // Mock the RPC call for unit tests. - // if (process.env.TEST === "unit") { - // nock(`${process.env.RPC_BASEURL}`) - // .post(uri => uri.includes("/")) - // .reply(200, { result: { error: "Transaction not in mempool" } }) - // } - // - // req.params.txid = `d65881582ff2bff36747d7a0d0e273f10281abc8bd5c15df5d72f8f3fa779cde` - // - // const result = await getMempoolEntry(req, res) - // //console.log(`result: ${util.inspect(result)}`) - // - // assert.hasAllKeys(result, ["error"]) - // assert.isString(result.error) - // assert.equal(result.error, "Transaction not in mempool") - // }) - // }) - // - // describe("#getMempoolEntryBulk", () => { - // // route handler. - // const getMempoolEntryBulk = - // blockchainRoute.testableComponents.getMempoolEntryBulk - // - // it("should throw an error for an empty body", async () => { - // req.body = {} - // - // const result = await getMempoolEntryBulk(req, res) - // - // assert.equal(res.statusCode, 400, "HTTP status code 400 expected.") - // assert.include( - // result.error, - // "txids needs to be an array", - // "Proper error message" - // ) - // }) - // - // it("should error on non-array single txid", async () => { - // req.body.txids = `d65881582ff2bff36747d7a0d0e273f10281abc8bd5c15df5d72f8f3fa779cde` - // - // const result = await getMempoolEntryBulk(req, res) - // - // assert.equal(res.statusCode, 400, "HTTP status code 400 expected.") - // assert.include( - // result.error, - // "txids needs to be an array", - // "Proper error message" - // ) - // }) - // - // it("should throw 400 error if addresses array is too large", async () => { - // const testArray = [] - // for (var i = 0; i < 25; i++) testArray.push("") - // - // req.body.txids = testArray - // - // const result = await getMempoolEntryBulk(req, res) - // //console.log(`result: ${util.inspect(result)}`) - // - // assert.hasAllKeys(result, ["error"]) - // assert.include(result.error, "Array too large") - // }) - // - // // Only execute on integration tests. - // if (process.env.TEST !== "unit") { - // // Dev-note: This test passes because it expects an error. TXIDs do not - // // stay in the mempool for long, so it does not work well for a unit or - // // integration test. - // it("should retrieve single mempool entry", async () => { - // req.body.txids = [ - // `d65881582ff2bff36747d7a0d0e273f10281abc8bd5c15df5d72f8f3fa779cde` - // ] - // - // const result = await getMempoolEntryBulk(req, res) - // //console.log(`result: ${util.inspect(result)}`) - // - // assert.hasAllKeys(result, ["error"]) - // assert.isString(result.error) - // assert.equal(result.error, "Transaction not in mempool") - // }) - // - // // Dev-note: This test passes because it expects an error. TXIDs do not - // // stay in the mempool for long, so it does not work well for a unit or - // // integration test. - // it("should retrieve multiple mempool entries", async () => { - // req.body.txids = [ - // `d65881582ff2bff36747d7a0d0e273f10281abc8bd5c15df5d72f8f3fa779cde`, - // `d65881582ff2bff36747d7a0d0e273f10281abc8bd5c15df5d72f8f3fa779cde` - // ] - // - // const result = await getMempoolEntryBulk(req, res) - // //console.log(`result: ${util.inspect(result)}`) - // - // assert.hasAllKeys(result, ["error"]) - // assert.isString(result.error) - // assert.equal(result.error, "Transaction not in mempool") - // }) - // } - // }) - // - // describe("getMempoolAncestorsSingle()", () => { - // // block route handler. - // const getMempoolAncestorsSingle = - // blockchainRoute.testableComponents.getMempoolAncestorsSingle - // - // it("should throw 400 if txid is empty", async () => { - // const result = await getMempoolAncestorsSingle(req, res) - // //console.log(`result: ${util.inspect(result)}`) - // - // assert.hasAllKeys(result, ["error"]) - // assert.include(result.error, "txid can not be empty") - // }) - // - // it("should throw 503 when network issues", async () => { - // // Save the existing RPC URL. - // const savedUrl2 = process.env.RPC_BASEURL - // - // // Manipulate the URL to cause a 500 network error. - // process.env.RPC_BASEURL = "http://fakeurl/api/" - // - // req.params.txid = `d65881582ff2bff36747d7a0d0e273f10281abc8bd5c15df5d72f8f3fa779cde` - // - // const result = await getMempoolAncestorsSingle(req, res) - // //console.log(`result: ${util.inspect(result)}`) - // - // // Restore the saved URL. - // process.env.RPC_BASEURL = savedUrl2 - // - // assert.isAbove(res.statusCode, 499, "HTTP status code 503 expected.") - // assert.include( - // result.error, - // "Could not communicate with full node", - // "Error message expected" - // ) - // }) - // - // it("should GET /getMempoolAncestorsSingle", async () => { - // nock(`${process.env.RPC_BASEURL}`) - // .post(uri => uri.includes("/")) - // .reply(200, { result: mockData.mockAncestors }) - // - // req.params.txid = `bb0d349892d351da2767f8c45f6f7949713ff09bd12838d53e76158ddee3ce93` - // - // const result = await getMempoolAncestorsSingle(req, res) - // // console.log(`result: ${util.inspect(result)}`) - // - // assert.isArray(result) - // }) - // }) - // - // describe("getTxOut()", () => { - // // block route handler. - // const getTxOut = blockchainRoute.testableComponents.getTxOut - // - // it("should throw 400 if txid is empty", async () => { - // const result = await getTxOut(req, res) - // //console.log(`result: ${util.inspect(result)}`) - // - // assert.hasAllKeys(result, ["error"]) - // assert.include(result.error, "txid can not be empty") - // }) - // - // it("should throw 400 if n is empty", async () => { - // req.params.txid = `sometxid` - // const result = await getTxOut(req, res) - // //console.log(`result: ${util.inspect(result)}`) - // - // assert.hasAllKeys(result, ["error"]) - // assert.include(result.error, "n can not be empty") - // }) - // - // it("should throw 503 when network issues", async () => { - // // Save the existing RPC URL. - // const savedUrl2 = process.env.RPC_BASEURL - // - // // Manipulate the URL to cause a 500 network error. - // process.env.RPC_BASEURL = "http://fakeurl/api/" - // - // req.params.txid = `d65881582ff2bff36747d7a0d0e273f10281abc8bd5c15df5d72f8f3fa779cde` - // req.params.n = 0 - // - // const result = await getTxOut(req, res) - // //console.log(`result: ${util.inspect(result)}`) - // - // // Restore the saved URL. - // process.env.RPC_BASEURL = savedUrl2 - // - // assert.isAbove(res.statusCode, 499, "HTTP status code 503 expected.") - // assert.include( - // result.error, - // "Could not communicate with full node", - // "Error message expected" - // ) - // }) - // - // // This test can only run for unit tests. See TODO at the top of this file. - // it("should GET /getTxOut", async () => { - // // Mock the RPC call for unit tests. - // if (process.env.TEST === "unit") { - // nock(`${process.env.RPC_BASEURL}`) - // .post(uri => uri.includes("/")) - // .reply(200, { result: mockData.mockTxOut }) - // } - // - // req.params.txid = `197dcda59864b1eee05498fd3c52cad787ec56ab7e635503cb39f9ab6f295d5d` - // req.params.n = 0 - // req.query.include_mempool = "true" - // - // const result = await getTxOut(req, res) - // // console.log(`result: ${JSON.stringify(result, null, 2)}`) - // - // assert.hasAllKeys(result, [ - // "bestblock", - // "confirmations", - // "value", - // "scriptPubKey", - // "coinbase" - // ]) - // assert.hasAllKeys(result.scriptPubKey, [ - // "asm", - // "hex", - // "reqSigs", - // "type", - // "addresses" - // ]) - // assert.isArray(result.scriptPubKey.addresses) - // }) - // }) - // - // describe("getTxOutProof()", () => { - // const getTxOutProof = blockchainRoute.testableComponents.getTxOutProofSingle - // - // it("should throw 400 if txid is empty", async () => { - // const result = await getTxOutProof(req, res) - // //console.log(`result: ${util.inspect(result)}`) - // - // assert.hasAllKeys(result, ["error"]) - // assert.include(result.error, "txid can not be empty") - // }) - // - // it("should throw 503 when network issues", async () => { - // // Save the existing RPC URL. - // const savedUrl2 = process.env.RPC_BASEURL - // - // // Manipulate the URL to cause a 500 network error. - // process.env.RPC_BASEURL = "http://fakeurl/api/" - // - // req.params.txid = `d65881582ff2bff36747d7a0d0e273f10281abc8bd5c15df5d72f8f3fa779cde` - // - // const result = await getTxOutProof(req, res) - // //console.log(`result: ${util.inspect(result)}`) - // - // // Restore the saved URL. - // process.env.RPC_BASEURL = savedUrl2 - // - // assert.isAbove(res.statusCode, 499, "HTTP status code 503 expected.") - // assert.include( - // result.error, - // "Could not communicate with full node", - // "Error message expected" - // ) - // }) - // - // it("should GET /getTxOutProof", async () => { - // // Mock the RPC call for unit tests. - // if (process.env.TEST === "unit") { - // nock(`${process.env.RPC_BASEURL}`) - // .post(uri => uri.includes("/")) - // .reply(200, { result: mockData.mockTxOutProof }) - // } - // - // req.params.txid = `2c7ae9f865f7ce0c33c189b2f83414176903ce4b06ed9f8b7bcf55efbd4a7266` - // - // const result = await getTxOutProof(req, res) - // // console.log(`result: ${JSON.stringify(result, null, 2)}`) - // - // assert.isString(result) - // }) - // }) - // - // describe("#getTxOutProofBulk", () => { - // // route handler. - // const getTxOutProofBulk = - // blockchainRoute.testableComponents.getTxOutProofBulk - // - // it("should throw an error for an empty body", async () => { - // req.body = {} - // - // const result = await getTxOutProofBulk(req, res) - // - // assert.equal(res.statusCode, 400, "HTTP status code 400 expected.") - // assert.include( - // result.error, - // "txids needs to be an array", - // "Proper error message" - // ) - // }) - // - // it("should error on non-array single txid", async () => { - // req.body.txids = `d65881582ff2bff36747d7a0d0e273f10281abc8bd5c15df5d72f8f3fa779cde` - // - // const result = await getTxOutProofBulk(req, res) - // - // assert.equal(res.statusCode, 400, "HTTP status code 400 expected.") - // assert.include( - // result.error, - // "txids needs to be an array", - // "Proper error message" - // ) - // }) - // - // it("should throw 400 error if addresses array is too large", async () => { - // const testArray = [] - // for (var i = 0; i < 25; i++) testArray.push("") - // - // req.body.txids = testArray - // - // const result = await getTxOutProofBulk(req, res) - // //console.log(`result: ${util.inspect(result)}`) - // - // assert.hasAllKeys(result, ["error"]) - // assert.include(result.error, "Array too large") - // }) - // - // it("should GET proof for single txid", async () => { - // // Mock the RPC call for unit tests. - // if (process.env.TEST === "unit") { - // nock(`${process.env.RPC_BASEURL}`) - // .post(uri => uri.includes("/")) - // .reply(200, { result: mockData.mockTxOutProof }) - // } - // - // req.body.txids = [ - // `2c7ae9f865f7ce0c33c189b2f83414176903ce4b06ed9f8b7bcf55efbd4a7266` - // ] - // - // const result = await getTxOutProofBulk(req, res) - // //console.log(`result: ${JSON.stringify(result, null, 2)}`) - // - // assert.isArray(result) - // assert.isString(result[0]) - // }) - // - // it("should GET proof for multiple txids", async () => { - // // Mock the RPC call for unit tests. - // if (process.env.TEST === "unit") { - // nock(`${process.env.RPC_BASEURL}`) - // .post(uri => uri.includes("/")) - // .times(2) - // .reply(200, { result: mockData.mockTxOutProof }) - // } - // - // req.body.txids = [ - // `2c7ae9f865f7ce0c33c189b2f83414176903ce4b06ed9f8b7bcf55efbd4a7266`, - // `2c7ae9f865f7ce0c33c189b2f83414176903ce4b06ed9f8b7bcf55efbd4a7266` - // ] - // - // const result = await getTxOutProofBulk(req, res) - // //console.log(`result: ${JSON.stringify(result, null, 2)}`) - // - // assert.isArray(result) - // assert.equal(result.length, 2, "Correct length of returned array") - // }) - // }) - // - // describe("verifyTxOutProof()", () => { - // const verifyTxOutProof = - // blockchainRoute.testableComponents.verifyTxOutProofSingle - // - // it("should throw 400 if proof is empty", async () => { - // const result = await verifyTxOutProof(req, res) - // //console.log(`result: ${util.inspect(result)}`) - // - // assert.hasAllKeys(result, ["error"]) - // assert.include(result.error, "proof can not be empty") - // }) - // - // it("should throw 503 when network issues", async () => { - // // Save the existing RPC URL. - // const savedUrl2 = process.env.RPC_BASEURL - // - // // Manipulate the URL to cause a 500 network error. - // process.env.RPC_BASEURL = "http://fakeurl/api/" - // - // req.params.proof = mockData.mockTxOutProof - // - // const result = await verifyTxOutProof(req, res) - // //console.log(`result: ${util.inspect(result)}`) - // - // // Restore the saved URL. - // process.env.RPC_BASEURL = savedUrl2 - // - // assert.isAbove(res.statusCode, 499, "HTTP status code 503 expected.") - // assert.include( - // result.error, - // "Could not communicate with full node", - // "Error message expected" - // ) - // }) - // - // it("should GET /verifyTxOutProof", async () => { - // const expected = - // "2c7ae9f865f7ce0c33c189b2f83414176903ce4b06ed9f8b7bcf55efbd4a7266" - // - // // Mock the RPC call for unit tests. - // if (process.env.TEST === "unit") { - // nock(`${process.env.RPC_BASEURL}`) - // .post(uri => uri.includes("/")) - // .reply(200, { result: [expected] }) - // } - // - // req.params.proof = - // "000000202ed2723e7590e2b937f6821a99d6764cb8799bf30f8e300000000000000000001d311c02df9a1e3f57b8dbdcf97ec8dbc3109a26779724c63e560b29ad9ea501e2af955d286403183049e39c1e000000067139f230701a2819a76795564bd2f67ded7eeae68596f368eddb3dd5bc54e59320e896f71d61cfc3ae3d4a90fca08b1aa35ba91256d8939d2cad11e638c0081f66724abdef55cf7b8b9fed064bce0369171434f8b289c1330ccef765f8e97a2c0d794d81aafb535855f7daa6bb51e40f77c6b59d7af7f62d0eb726a4fc4df82353d56fcbda7c7ea6bd935d61af8fb3b295637e6f323b10231135b3f10a034cfb238f635830c0595e52c6c31247cf677b555f7a287076e20cd0e1d3cc9af7260f02b700" - // - // const result = await verifyTxOutProof(req, res) - // //console.log(`result: ${JSON.stringify(result, null, 2)}`) - // - // assert.isArray(result) - // assert.isString(result[0]) - // assert.equal(result[0], expected) - // }) - // }) - // - // describe("#verifyTxOutProofBulk", () => { - // // route handler. - // const verifyTxOutProofBulk = - // blockchainRoute.testableComponents.verifyTxOutProofBulk - // - // it("should throw an error for an empty body", async () => { - // req.body = {} - // - // const result = await verifyTxOutProofBulk(req, res) - // - // assert.equal(res.statusCode, 400, "HTTP status code 400 expected.") - // assert.include( - // result.error, - // "proofs needs to be an array", - // "Proper error message" - // ) - // }) - // - // it("should error on non-array single txid", async () => { - // req.body.proofs = mockData.mockTxOutProof - // - // const result = await verifyTxOutProofBulk(req, res) - // - // assert.equal(res.statusCode, 400, "HTTP status code 400 expected.") - // assert.include( - // result.error, - // "proofs needs to be an array", - // "Proper error message" - // ) - // }) - // - // it("should throw 400 error if addresses array is too large", async () => { - // const testArray = [] - // for (var i = 0; i < 25; i++) testArray.push("") - // - // req.body.proofs = testArray - // - // const result = await verifyTxOutProofBulk(req, res) - // //console.log(`result: ${util.inspect(result)}`) - // - // assert.hasAllKeys(result, ["error"]) - // assert.include(result.error, "Array too large") - // }) - // - // it("should get single proof", async () => { - // const expected = - // "2c7ae9f865f7ce0c33c189b2f83414176903ce4b06ed9f8b7bcf55efbd4a7266" - // - // // Mock the RPC call for unit tests. - // if (process.env.TEST === "unit") { - // nock(`${process.env.RPC_BASEURL}`) - // .post(uri => uri.includes("/")) - // .reply(200, { result: [expected] }) - // } - // - // req.body.proofs = [ - // "000000202ed2723e7590e2b937f6821a99d6764cb8799bf30f8e300000000000000000001d311c02df9a1e3f57b8dbdcf97ec8dbc3109a26779724c63e560b29ad9ea501e2af955d286403183049e39c1e000000067139f230701a2819a76795564bd2f67ded7eeae68596f368eddb3dd5bc54e59320e896f71d61cfc3ae3d4a90fca08b1aa35ba91256d8939d2cad11e638c0081f66724abdef55cf7b8b9fed064bce0369171434f8b289c1330ccef765f8e97a2c0d794d81aafb535855f7daa6bb51e40f77c6b59d7af7f62d0eb726a4fc4df82353d56fcbda7c7ea6bd935d61af8fb3b295637e6f323b10231135b3f10a034cfb238f635830c0595e52c6c31247cf677b555f7a287076e20cd0e1d3cc9af7260f02b700" - // ] - // - // const result = await verifyTxOutProofBulk(req, res) - // //console.log(`result: ${JSON.stringify(result, null, 2)}`) - // - // assert.isArray(result) - // assert.isString(result[0]) - // assert.equal(result[0], expected) - // }) - // - // it("should get multiple proofs", async () => { - // const expected = - // "2c7ae9f865f7ce0c33c189b2f83414176903ce4b06ed9f8b7bcf55efbd4a7266" - // - // // Mock the RPC call for unit tests. - // if (process.env.TEST === "unit") { - // nock(`${process.env.RPC_BASEURL}`) - // .post(uri => uri.includes("/")) - // .times(2) - // .reply(200, { result: [expected] }) - // } - // - // req.body.proofs = [ - // "000000202ed2723e7590e2b937f6821a99d6764cb8799bf30f8e300000000000000000001d311c02df9a1e3f57b8dbdcf97ec8dbc3109a26779724c63e560b29ad9ea501e2af955d286403183049e39c1e000000067139f230701a2819a76795564bd2f67ded7eeae68596f368eddb3dd5bc54e59320e896f71d61cfc3ae3d4a90fca08b1aa35ba91256d8939d2cad11e638c0081f66724abdef55cf7b8b9fed064bce0369171434f8b289c1330ccef765f8e97a2c0d794d81aafb535855f7daa6bb51e40f77c6b59d7af7f62d0eb726a4fc4df82353d56fcbda7c7ea6bd935d61af8fb3b295637e6f323b10231135b3f10a034cfb238f635830c0595e52c6c31247cf677b555f7a287076e20cd0e1d3cc9af7260f02b700", - // "000000202ed2723e7590e2b937f6821a99d6764cb8799bf30f8e300000000000000000001d311c02df9a1e3f57b8dbdcf97ec8dbc3109a26779724c63e560b29ad9ea501e2af955d286403183049e39c1e000000067139f230701a2819a76795564bd2f67ded7eeae68596f368eddb3dd5bc54e59320e896f71d61cfc3ae3d4a90fca08b1aa35ba91256d8939d2cad11e638c0081f66724abdef55cf7b8b9fed064bce0369171434f8b289c1330ccef765f8e97a2c0d794d81aafb535855f7daa6bb51e40f77c6b59d7af7f62d0eb726a4fc4df82353d56fcbda7c7ea6bd935d61af8fb3b295637e6f323b10231135b3f10a034cfb238f635830c0595e52c6c31247cf677b555f7a287076e20cd0e1d3cc9af7260f02b700" - // ] - // - // const result = await verifyTxOutProofBulk(req, res) - // //console.log(`result: ${JSON.stringify(result, null, 2)}`) - // - // assert.isArray(result) - // assert.isString(result[0]) - // assert.equal(result[0], expected) - // assert.equal(result.length, 2) - // }) - // }) -})