diff --git a/package.json b/package.json index 5f8c218..718c6e5 100644 --- a/package.json +++ b/package.json @@ -12,6 +12,7 @@ "dev": "nodemon ./dist/app.js", "test": "npm run test-v3", "test-v3": "export NETWORK=mainnet && nyc --reporter=text mocha --timeout 25000 test/v3/", + "test:temp": "export NETWORK=mainnet && mocha --timeout 25000 test/v3/blockchain.js", "test:integration": "mocha test/v3/integration", "coverage": "nyc report --reporter=text-lcov | coveralls", "coverage:report": "export NETWORK=testnet && nyc --reporter=html mocha test/v2/", diff --git a/src/routes/v3/full-node/blockchain.js b/src/routes/v3/full-node/blockchain.js index cd3d556..9086629 100644 --- a/src/routes/v3/full-node/blockchain.js +++ b/src/routes/v3/full-node/blockchain.js @@ -29,6 +29,7 @@ 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) @@ -585,6 +586,62 @@ async function getMempoolEntryBulk(req, res, next) { } } +/** + * @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/getMempoolEntry/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 @@ -1089,6 +1146,7 @@ module.exports = { getRawMempool, getMempoolEntrySingle, getMempoolEntryBulk, + getMempoolAncestorsSingle, getTxOut, getTxOutProofSingle, getTxOutProofBulk, diff --git a/test/v3/blockchain.js b/test/v3/blockchain.js index 503e08a..b0daad6 100644 --- a/test/v3/blockchain.js +++ b/test/v3/blockchain.js @@ -786,6 +786,56 @@ 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)}`) + + 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 diff --git a/test/v3/mocks/blockchain-mock.js b/test/v3/mocks/blockchain-mock.js index 527c9f7..8971637 100644 --- a/test/v3/mocks/blockchain-mock.js +++ b/test/v3/mocks/blockchain-mock.js @@ -258,6 +258,33 @@ const mockTxOut = { const mockTxOutProof = "000000200798039affceb7a381e15dc62443c286efe7cae852393b656807000000000000cf9a7d6c654fd1c4558229a619a550ff749b373fa0f0027eed68b1abf6175d5c07c50f5cffff001d0452403b34000000070d0765da76980d542b5670d27ccda9e717073270f8921ad017deb7ee83fe6c4c22830a48cc8392197f1742a3d81805bd2aa4eb7469f38c74f674429d682cb87efcff1713fd0c388a50f0d11adf05363397ef79d16cdb786d47c941c2cb89f2588f42aced9c05f4203b4440616a9e4266ec47909b9580a643f402b5d4e82cc3cddd73df05a33abf6af3975d973d3033d10c4168a73d58d01f685a96d5a2519cd0de9c77faf3f8725ddf155cbdc8ab8102f173e2d0a0d74767f3bff22f588158d6d5bc71c079c7659cc864819c43877635007650502eb4060fcc9feec3280a41d602ad0a" +const mockAncestors = [ + "070504de968fba8a23a1362fd7fc0546a420663f2bcc18099d894a41ecf9cce5", + "20af6b7fcd21a3f38bb65d32fe2d66fb1ae554ed9c00eaf1b17d7e81362381a9", + "227e08663a812cf44d91106b09fc8493fe1a2b7c146f30ca09ebd86c9b84b2a5", + "228833be79d4a833165ea199be160efcaeb9325e71e9455f424b27abc9688ba8", + "2de27df02001e7243435cef6d41ea2312b94a5964912eb21466b936852c0b822", + "2ef76ae690d6f6c9559e20358a75a0ce02250b9b8e80e0aa6f782576239f292a", + "3e314678f52d52694726e6da88b94c9f0ea086d2f9af297b01d014b62247839f", + "4773f840d5255f16c0e5effce48316d8b8824a1d3ddd5da275c5fd99e7b3ee42", + "4804b97112da07e2c187516a915dd4cbd4a3814f3c900b8d08eb1d508ab0ac47", + "4ca69f31081a256cb4711315336fe4358d8f04b67792ca5ee17b5f4089a2446a", + "6ac17dce10ae3a3d1d8e6f42546ad9a4bb981e62cba8d48bab03e01b9ea38ae3", + "6ce71182ef341c481a90d8b081a99d9788bdd91a917f9a6594fdfad2c2024a14", + "8413343ac973fb0968ae1809faa8f0ddf18252975481daa683e88b04afa66ccd", + "9430300f3d7ecdabe39671b6fd0e9c36f763f01a202e619c070554ddc5e8713e", + "94f82b5a2cd1647d8168807ccfdebe41931ea1ab789a050d615f08000a63b624", + "9848db6a7d2835002ca7e1097a685d75eb629f44df94007674635b45d0e97199", + "98d809993353df6c4d55fda5e7b0ed2e383601cef1180a9aa9c87e67e8f4dd94", + "9f5513a35ae8fad3dd061865a97d8127c914e3464527641c089993c06d092535", + "a7201ac90abd2aa42112faa809181c45047b6988a2f649a70a1d5bfb95f46884", + "a9f1284710c655357412a062b01efaeb174820613784ccbfe44b33b86a8bdbc4", + "b814f44c9c8ee24a218b9f295a8c1831106b70c15ffe8aeb7534e10d99935fd0", + "e130ccdcc94dc5eaa4ae37fa50e3ee96283b0e157616bc6e28454b64bae34f48", + "e68dad4a7292105cfa84fcaef7f99e5d4f2ece9613ca625d4d2ebf61efa84118", + "fe94caf5da672be3772d2304a6272eb8bc3d3f5cb4a886f39b7981e1485cf74b" +] + module.exports = { mockBlockHash, mockBlockchainInfo, @@ -267,5 +294,6 @@ module.exports = { mockBlockHeaderConcise, mockBlockHeader, mockTxOut, - mockTxOutProof + mockTxOutProof, + mockAncestors }