From 059667b41a6df69e4d211d2de823d53b8eb96d44 Mon Sep 17 00:00:00 2001 From: Chris Troutner Date: Tue, 28 Jan 2020 20:54:43 -0800 Subject: [PATCH] Revert "Revert "Blockchain refactor [WIP]"" --- src/app.js | 4 + src/routes/v3/full-node/blockchain.js | 16 +- src/routes/v3/full-node/blockchain2.js | 133 +++ src/routes/v3/ninsight.js | 32 + src/routes/v3/route-utils.js | 37 +- src/routes/v3/route-utils2.js | 163 +++ test/v3/blockchain.js | 6 +- test/v3/blockchain2.js | 1271 ++++++++++++++++++++++++ 8 files changed, 1649 insertions(+), 13 deletions(-) create mode 100644 src/routes/v3/full-node/blockchain2.js create mode 100644 src/routes/v3/ninsight.js create mode 100644 src/routes/v3/route-utils2.js create mode 100644 test/v3/blockchain2.js diff --git a/src/app.js b/src/app.js index 54661d7..5e4dc52 100644 --- a/src/app.js +++ b/src/app.js @@ -31,6 +31,7 @@ const utilV3 = require("./routes/v3/util") const slpV3 = require("./routes/v3/slp") const xpubV3 = require("./routes/v3/xpub") const blockbookV3 = require("./routes/v3/blockbook") +const Ninsight = require("./routes/v3/ninsight") require("dotenv").config() @@ -93,6 +94,9 @@ app.use(`/${v3prefix}/` + `slp`, slpV3.router) app.use(`/${v3prefix}/` + `xpub`, xpubV3.router) app.use(`/${v3prefix}/` + `blockbook`, blockbookV3.router) +const ninsight = new Ninsight() +app.use(`/${v3prefix}/` + `ninsight`, ninsight.router) + // catch 404 and forward to error handler app.use((req, res, next) => { const err = { diff --git a/src/routes/v3/full-node/blockchain.js b/src/routes/v3/full-node/blockchain.js index 6056ce7..201244a 100644 --- a/src/routes/v3/full-node/blockchain.js +++ b/src/routes/v3/full-node/blockchain.js @@ -56,18 +56,14 @@ function root(req, res, next) { */ async function getBestBlockHash(req, res, next) { try { - const { - BitboxHTTP, - username, - password, - requestConfig - } = routeUtils.setEnvVars() + // Axios options + const options = routeUtils.getAxiosOptions() + options.data.id = "getbestblockhash" + options.data.method = "getbestblockhash" + options.data.params = [] - requestConfig.data.id = "getbestblockhash" - requestConfig.data.method = "getbestblockhash" - requestConfig.data.params = [] + const response = await axios.request(options) - const response = await BitboxHTTP(requestConfig) return res.json(response.data.result) } catch (err) { // Attempt to decode the error message. diff --git a/src/routes/v3/full-node/blockchain2.js b/src/routes/v3/full-node/blockchain2.js new file mode 100644 index 0000000..a7c103f --- /dev/null +++ b/src/routes/v3/full-node/blockchain2.js @@ -0,0 +1,133 @@ +/* + 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/src/routes/v3/ninsight.js b/src/routes/v3/ninsight.js new file mode 100644 index 0000000..97ee56c --- /dev/null +++ b/src/routes/v3/ninsight.js @@ -0,0 +1,32 @@ +/* + A library for interacting with the Bitcoin.com ninsight (not Insight) indexer. +*/ + +"use strict" + +const express = require("express") +const axios = require("axios") +const routeUtils = require("./route-utils") +const wlogger = require("../../util/winston-logging") + +const router = express.Router() + +const BCHJS = require("@chris.troutner/bch-js") +const bchjs = new BCHJS() + +let _this + +class Ninsight { + constructor() { + _this = this + + this.router = router + this.router.get("/", this.root) + } + + root(req, res, next) { + return res.json({ status: "ninsight" }) + } +} + +module.exports = Ninsight diff --git a/src/routes/v3/route-utils.js b/src/routes/v3/route-utils.js index 38652fc..4f4f580 100644 --- a/src/routes/v3/route-utils.js +++ b/src/routes/v3/route-utils.js @@ -17,7 +17,8 @@ module.exports = { validateNetwork, // Prevents a common user error setEnvVars, // Allows RPC variables to be set dynamically based on changing env vars. decodeError, // Extract and interpret error messages. - validateArraySize // Ensure the passed array meets rate limiting requirements. + validateArraySize, // Ensure the passed array meets rate limiting requirements. + getAxiosOptions } // This function expects the Request Express.js object and an array as input. @@ -74,7 +75,8 @@ function validateNetwork(addr) { // Dynamically set these based on env vars. Allows unit testing. function setEnvVars() { const BitboxHTTP = axios.create({ - baseURL: process.env.RPC_BASEURL + baseURL: process.env.RPC_BASEURL, + timeout: 15000 }) const username = process.env.RPC_USERNAME const password = process.env.RPC_PASSWORD @@ -93,6 +95,22 @@ function setEnvVars() { return { BitboxHTTP, username, password, requestConfig } } +// Axios options used when calling axios.post() to talk with a full node. +function getAxiosOptions() { + return { + method: "post", + baseURL: process.env.RPC_BASEURL, + timeout: 15000, + auth: { + username: process.env.RPC_USERNAME, + password: process.env.RPC_PASSWORD + }, + data: { + jsonrpc: "1.0" + } + } +} + // Error messages returned by a full node can be burried pretty deep inside the // error object returned by Axios. This function attempts to extract and interpret // error messages. @@ -113,6 +131,9 @@ function decodeError(err) { if (err.response && err.response.data) return { msg: err.response.data, status: err.response.status } + // console.log(`err.message: ${err.message}`) + // console.log(`err: `, err) + // Attempt to detect a network connection error. if (err.message && err.message.indexOf("ENOTFOUND") > -1) { return { @@ -140,6 +161,18 @@ function decodeError(err) { } } + // Axios timeout (aborted) error, or service is down (connection refused). + if ( + err.code && + (err.code === "ECONNABORTED" || err.code === "ECONNREFUSED") + ) { + return { + msg: + "Network error: Could not communicate with full node or other external service.", + status: 503 + } + } + return { msg: false, status: 500 } } catch (err) { wlogger.error(`unhandled error in route-utils.js/decodeError(): `, err) diff --git a/src/routes/v3/route-utils2.js b/src/routes/v3/route-utils2.js new file mode 100644 index 0000000..f7315d3 --- /dev/null +++ b/src/routes/v3/route-utils2.js @@ -0,0 +1,163 @@ +/* + A private library of utility functions used by several different routes. +*/ + +"use strict" + +const axios = require("axios") +const wlogger = require("../../util/winston-logging") + +const util = require("util") +util.inspect.defaultOptions = { depth: 1 } + +const BCHJS = require("@chris.troutner/bch-js") +const bchjs = new BCHJS() + +let _this + +class RouteUtils { + constructor() { + _this = this + + this.bchjs = bchjs + this.axios = axios + } + + // This function expects the Request Express.js object and an array as input. + // The array is then validated against freemium and pro-tier rate limiting + // requirements. A boolean is returned to indicate if the array size if valid + // or not. + validateArraySize(req, array) { + const FREEMIUM_INPUT_SIZE = 20 + const PRO_INPUT_SIZE = 20 + + if (req.locals && req.locals.proLimit) { + if (array.length <= PRO_INPUT_SIZE) return true + } else if (array.length <= FREEMIUM_INPUT_SIZE) { + return true + } + + return false + } + + // Axios options used when calling axios.post() to talk with a full node. + getAxiosOptions() { + return { + method: "post", + baseURL: process.env.RPC_BASEURL, + timeout: 15000, + auth: { + username: process.env.RPC_USERNAME, + password: process.env.RPC_PASSWORD + }, + data: { + jsonrpc: "1.0" + } + } + } + + // Returns true if user-provided cash address matches the correct network, + // mainnet or testnet. If NETWORK env var is not defined, it returns false. + // This prevent a common user-error issue that is easy to make: passing a + // testnet address into rest.bitcoin.com or passing a mainnet address into + // trest.bitcoin.com. + validateNetwork(addr) { + try { + const network = process.env.NETWORK + + // Return false if NETWORK is not defined. + if (!network || network === "") { + console.log(`Warning: NETWORK environment variable is not defined!`) + return false + } + + // Convert the user-provided address to a cashaddress, for easy detection + // of the intended network. + const cashAddr = this.bchjs.Address.toCashAddress(addr) + + // Return true if the network and address both match testnet + const addrIsTest = this.bchjs.Address.isTestnetAddress(cashAddr) + if (network === "testnet" && addrIsTest) return true + + // Return true if the network and address both match mainnet + const addrIsMain = this.bchjs.Address.isMainnetAddress(cashAddr) + if (network === "mainnet" && addrIsMain) return true + + return false + } catch (err) { + logger.error(`Error in validateNetwork()`) + return false + } + } + + // Error messages returned by a full node can be burried pretty deep inside the + // error object returned by Axios. This function attempts to extract and interpret + // error messages. + // Returns an object. If successful, obj.msg is a string. + // If there is a failure, obj.msg is false. + decodeError(err) { + try { + // Attempt to extract the full node error message. + if ( + err.response && + err.response.data && + err.response.data.error && + err.response.data.error.message + ) + return { msg: err.response.data.error.message, status: 400 } + + // Attempt to extract the Insight error message + if (err.response && err.response.data) + return { msg: err.response.data, status: err.response.status } + + // console.log(`err.message: ${err.message}`) + // console.log(`err: `, err) + + // Attempt to detect a network connection error. + if (err.message && err.message.indexOf("ENOTFOUND") > -1) { + return { + msg: + "Network error: Could not communicate with full node or other external service.", + status: 503 + } + } + + // Different kind of network error + if (err.message && err.message.indexOf("ENETUNREACH") > -1) { + return { + msg: + "Network error: Could not communicate with full node or other external service.", + status: 503 + } + } + + // Different kind of network error + if (err.message && err.message.indexOf("EAI_AGAIN") > -1) { + return { + msg: + "Network error: Could not communicate with full node or other external service.", + status: 503 + } + } + + // Axios timeout (aborted) error, or service is down (connection refused). + if ( + err.code && + (err.code === "ECONNABORTED" || err.code === "ECONNREFUSED") + ) { + return { + msg: + "Network error: Could not communicate with full node or other external service.", + status: 503 + } + } + + return { msg: false, status: 500 } + } catch (err) { + wlogger.error(`unhandled error in route-utils.js/decodeError(): `, err) + return { msg: false, status: 500 } + } + } +} + +module.exports = RouteUtils diff --git a/test/v3/blockchain.js b/test/v3/blockchain.js index b0daad6..96bd801 100644 --- a/test/v3/blockchain.js +++ b/test/v3/blockchain.js @@ -115,6 +115,10 @@ describe("#BlockchainRouter", () => { ) }) + // it("return proper error connection is refused", async () => { + // + // }) + it("should GET /getBestBlockHash", async () => { // Mock the RPC call for unit tests. if (process.env.TEST === "unit") { @@ -124,7 +128,7 @@ describe("#BlockchainRouter", () => { } const result = await getBestBlockHash(req, res) - //console.log(`result: ${util.inspect(result)}`) + // console.log(`result: ${util.inspect(result)}`) assert.isString(result) assert.equal(result.length, 64, "Hash string is fixed length") diff --git a/test/v3/blockchain2.js b/test/v3/blockchain2.js new file mode 100644 index 0000000..debaa2d --- /dev/null +++ b/test/v3/blockchain2.js @@ -0,0 +1,1271 @@ +/* + 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) + // }) + // }) +})