From 2871d9aad658940aba91da6ecd668c18903f288b Mon Sep 17 00:00:00 2001 From: Chris Troutner Date: Tue, 20 Nov 2018 08:39:38 -0800 Subject: [PATCH 01/14] Finished unit tests for getRawTransactions() --- src/routes/v2/rawtransactions.ts | 2 +- test/v2/mocks/raw-transactions-mocks.js | 60 ++++++++++++++++++++++++- test/v2/raw-transactions.js | 44 ++++++++++++++++-- 3 files changed, 101 insertions(+), 5 deletions(-) diff --git a/src/routes/v2/rawtransactions.ts b/src/routes/v2/rawtransactions.ts index 1d408c50..3904a6ee 100644 --- a/src/routes/v2/rawtransactions.ts +++ b/src/routes/v2/rawtransactions.ts @@ -183,7 +183,7 @@ async function getRawTransaction( ) { try { let verbose = 0 - if (req.body.verbose && req.body.verbose === "true") verbose = 1 + if (req.body.verbose) verbose = 1 const txids = req.body.txids if (!Array.isArray(txids)) { diff --git a/test/v2/mocks/raw-transactions-mocks.js b/test/v2/mocks/raw-transactions-mocks.js index 9cfa520d..28c1b249 100644 --- a/test/v2/mocks/raw-transactions-mocks.js +++ b/test/v2/mocks/raw-transactions-mocks.js @@ -58,7 +58,65 @@ const mockDecodeScript = { p2sh: "bchtest:pzy6dwfy6yf373w0dr05a6flfqksurjhwcl3awhdvm" } +const mockRawTransactionConcise = + "02000000014e6b52500110b1c30315b85805fb274f0f4afceffc1589f889b27709e59e987d000000006a473044022052762770baa71c1a0b9544ad0f1ea343d32c22aa87c5f8397b6852f464c15b1e02201f4390745cb470e21e0e3c14229f39fef55ea0643a4c997f99d9f3501eae09b7412103c346eee77a77a8d3e073dacc0532ca7a5b9747bc06d88bf091cac9f4bc8bb792ffffffff02d1778f950a0000001976a91436d2f27bbd826a86db1e93618ce3de89ef33169388ac80969800000000001976a914152ea3cd65f18cb8fa9146c84ea1a97af8f051de88ac00000000" + +const mockRawTransactionVerbose = { + hex: + "02000000014e6b52500110b1c30315b85805fb274f0f4afceffc1589f889b27709e59e987d000000006a473044022052762770baa71c1a0b9544ad0f1ea343d32c22aa87c5f8397b6852f464c15b1e02201f4390745cb470e21e0e3c14229f39fef55ea0643a4c997f99d9f3501eae09b7412103c346eee77a77a8d3e073dacc0532ca7a5b9747bc06d88bf091cac9f4bc8bb792ffffffff02d1778f950a0000001976a91436d2f27bbd826a86db1e93618ce3de89ef33169388ac80969800000000001976a914152ea3cd65f18cb8fa9146c84ea1a97af8f051de88ac00000000", + txid: "bd320377db7026a3dd5c7ec444596c0ee18fc25c4f34ee944adc03e432ce1971", + hash: "bd320377db7026a3dd5c7ec444596c0ee18fc25c4f34ee944adc03e432ce1971", + size: 225, + version: 2, + locktime: 0, + vin: [ + { + txid: "7d989ee50977b289f88915fceffc4a0f4f27fb0558b81503c3b1100150526b4e", + vout: 0, + scriptSig: { + asm: + "3044022052762770baa71c1a0b9544ad0f1ea343d32c22aa87c5f8397b6852f464c15b1e02201f4390745cb470e21e0e3c14229f39fef55ea0643a4c997f99d9f3501eae09b7[ALL|FORKID] 03c346eee77a77a8d3e073dacc0532ca7a5b9747bc06d88bf091cac9f4bc8bb792", + hex: + "473044022052762770baa71c1a0b9544ad0f1ea343d32c22aa87c5f8397b6852f464c15b1e02201f4390745cb470e21e0e3c14229f39fef55ea0643a4c997f99d9f3501eae09b7412103c346eee77a77a8d3e073dacc0532ca7a5b9747bc06d88bf091cac9f4bc8bb792" + }, + sequence: 4294967295 + } + ], + vout: [ + { + value: 454.58880465, + n: 0, + scriptPubKey: { + asm: + "OP_DUP OP_HASH160 36d2f27bbd826a86db1e93618ce3de89ef331693 OP_EQUALVERIFY OP_CHECKSIG", + hex: "76a91436d2f27bbd826a86db1e93618ce3de89ef33169388ac", + reqSigs: 1, + type: "pubkeyhash", + addresses: ["bchtest:qqmd9unmhkpx4pkmr6fkrr8rm6y77vckjvqe8aey35"] + } + }, + { + value: 0.1, + n: 1, + scriptPubKey: { + asm: + "OP_DUP OP_HASH160 152ea3cd65f18cb8fa9146c84ea1a97af8f051de OP_EQUALVERIFY OP_CHECKSIG", + hex: "76a914152ea3cd65f18cb8fa9146c84ea1a97af8f051de88ac", + reqSigs: 1, + type: "pubkeyhash", + addresses: ["bchtest:qq2jag7dvhccew86j9rvsn4p49a03uz3mcpw3d6aca"] + } + } + ], + blockhash: "000000000000026fa244de975ca89ea08008aa566564ce2e8ebb3144361b601b", + confirmations: 125, + time: 1542646373, + blocktime: 1542646373 +} + module.exports = { mockDecodeRawTransaction, - mockDecodeScript + mockDecodeScript, + mockRawTransactionConcise, + mockRawTransactionVerbose } diff --git a/test/v2/raw-transactions.js b/test/v2/raw-transactions.js index cd99185d..90c637c9 100644 --- a/test/v2/raw-transactions.js +++ b/test/v2/raw-transactions.js @@ -23,7 +23,7 @@ const mockData = require("./mocks/raw-transactions-mocks") // Used for debugging. const util = require("util") -util.inspect.defaultOptions = { depth: 1 } +util.inspect.defaultOptions = { depth: 5 } describe("#Raw-Transactions", () => { let req, res @@ -251,20 +251,58 @@ describe("#Raw-Transactions", () => { assert.include(result.error, "Request failed with status code 500") }) - it("should get non-verbose transaction data", async () => { + it("should get concise transaction data", async () => { // Mock the RPC call for unit tests. if (process.env.TEST === "unit") { nock(`${process.env.RPC_BASEURL}`) .post(``) - .reply(500, { result: "Error: Request failed with status code 500" }) + .reply(200, { result: mockData.mockRawTransactionConcise }) + } + + req.body.txids = [ + "bd320377db7026a3dd5c7ec444596c0ee18fc25c4f34ee944adc03e432ce1971" + ] + + const result = await getRawTransaction(req, res) + //console.log(`result: ${util.inspect(result)}`) + + assert.isArray(result) + assert.isString(result[0]) + }) + + it("should get verbose transaction data", async () => { + // Mock the RPC call for unit tests. + if (process.env.TEST === "unit") { + nock(`${process.env.RPC_BASEURL}`) + .post(``) + .reply(200, { result: mockData.mockRawTransactionVerbose }) } req.body.txids = [ "bd320377db7026a3dd5c7ec444596c0ee18fc25c4f34ee944adc03e432ce1971" ] + req.body.verbose = true const result = await getRawTransaction(req, res) //console.log(`result: ${util.inspect(result)}`) + + assert.isArray(result) + assert.hasAnyKeys(result[0], [ + "hex", + "txid", + "hash", + "size", + "version", + "locktime", + "vin", + "vout", + "blockhash", + "confirmations", + "time", + "blocktime" + ]) + assert.isArray(result[0].vin) + assert.isArray(result[0].vout) }) }) }) From 857a937171131dc3f85bb82c03a396d20799c603 Mon Sep 17 00:00:00 2001 From: Chris Troutner Date: Tue, 20 Nov 2018 10:53:18 -0800 Subject: [PATCH 02/14] Adding unit tests for sendRawTransaction() --- src/routes/v2/rawtransactions.ts | 174 +++++++++++++++---------------- test/v2/raw-transactions.js | 56 +++++++++- 2 files changed, 141 insertions(+), 89 deletions(-) diff --git a/src/routes/v2/rawtransactions.ts b/src/routes/v2/rawtransactions.ts index 3904a6ee..563f08ac 100644 --- a/src/routes/v2/rawtransactions.ts +++ b/src/routes/v2/rawtransactions.ts @@ -83,14 +83,17 @@ router.get( config.rawTransactionsRateLimit2, decodeRawTransaction ) -router.get( - "/decodeScript/:hex", - config.rawTransactionsRateLimit3, - decodeScript -) +router.get("/decodeScript/:hex", config.rawTransactionsRateLimit3, decodeScript) router.post( "/getRawTransaction/:txid", - config.rawTransactionsRateLimit4, getRawTransaction) + config.rawTransactionsRateLimit4, + getRawTransaction +) +router.post( + "/sendRawTransaction/:hex", + config.rawTransactionsRateLimit5, + sendRawTransaction +) function root( req: express.Request, @@ -101,6 +104,7 @@ function root( } // Decode transaction hex into a JSON object. +// GET async function decodeRawTransaction( req: express.Request, res: express.Response, @@ -138,6 +142,7 @@ async function decodeRawTransaction( } // Decode a raw transaction from hex to assembly. +// GET async function decodeScript( req: express.Request, res: express.Response, @@ -165,7 +170,6 @@ async function decodeScript( const response = await BitboxHTTP(requestConfig) return res.json(response.data.result) - } catch (error) { // Write out error to error log. //logger.error(`Error in rawtransactions/decodeScript: `, err) @@ -176,6 +180,7 @@ async function decodeScript( } // Get a JSON object breakdown of transaction details. +// POST async function getRawTransaction( req: express.Request, res: express.Response, @@ -188,11 +193,11 @@ async function getRawTransaction( const txids = req.body.txids if (!Array.isArray(txids)) { res.status(400) - return res.json({error: "txids must be an array"}) + return res.json({ error: "txids must be an array" }) } if (txids.length > 20) { res.status(400) - return res.json({error: "Array too large. Max 20 txids"}) + return res.json({ error: "Array too large. Max 20 txids" }) } const { @@ -208,7 +213,7 @@ async function getRawTransaction( const results = [] // Loop through each txid in the array - for(let i=0; i < txids.length; i++) { + for (let i = 0; i < txids.length; i++) { const txid = txids[i] if (!txid || txid === "") { @@ -223,29 +228,76 @@ async function getRawTransaction( } return res.json(results) - - } catch(err) { + } catch (err) { // Write out error to error log. //logger.error(`Error in rawtransactions/getRawTransaction: `, err) res.status(500) return res.json({ error: util.inspect(err) }) } +} - - /* +// Transmit a raw transaction to the BCH network. +async function sendRawTransaction( + req: express.Request, + res: express.Response, + next: express.NextFunction +) { try { - let txids = JSON.parse(req.params.txid) - if (txids.length > 20) { + // Validation + const hexs = req.body.hex + if (!Array.isArray(hexs)) { + res.status(400) + return res.json({ error: "hex must be an array" }) + } + if (hexs.length > 20) { + res.status(400) + return res.json({ error: "Array too large. Max 20 entries" }) + } + + const { + BitboxHTTP, + username, + password, + requestConfig + } = routeUtils.setEnvVars() + + requestConfig.data.id = "sendrawtransaction" + requestConfig.data.method = "sendrawtransaction" + + const results = [] + + // Loop through each hex in the array + for (let i = 0; i < hexs.length; i++) { + const hex = hexs[i] + + if (!hex || hex === "") { + res.status(400) + return res.json({ error: "Encountered empty hex" }) + } + + requestConfig.data.params = [hex] + + const response = await BitboxHTTP(requestConfig) + results.push(response.data.result) + } + + return res.json(results) + + + /* + https: let transactions = JSON.parse(req.params.hex) + if (transactions.length > 20) { res.json({ - error: "Array too large. Max 20 txids" + error: "Array too large. Max 20 transactions" }) } + const result = [] as any - txids = txids.map((txid: any) => { - requestConfig.data.id = "getrawtransaction" - requestConfig.data.method = "getrawtransaction" - requestConfig.data.params = [txid, verbose] + transactions = transactions.map((transaction: any) => { + requestConfig.data.id = "sendrawtransaction" + requestConfig.data.method = "sendrawtransaction" + requestConfig.data.params = [transaction] BitboxHTTP(requestConfig).catch(error => { try { return { @@ -262,7 +314,7 @@ async function getRawTransaction( } }) }) - axios.all(txids).then( + axios.all(transactions).then( axios.spread((...args) => { for (let i = 0; i < args.length; i++) { let tmp = {} as any @@ -273,9 +325,9 @@ async function getRawTransaction( }) ) } catch (error) { - requestConfig.data.id = "getrawtransaction" - requestConfig.data.method = "getrawtransaction" - requestConfig.data.params = [req.params.txid, verbose] + requestConfig.data.id = "sendrawtransaction" + requestConfig.data.method = "sendrawtransaction" + requestConfig.data.params = [req.params.hex] BitboxHTTP(requestConfig) .then(response => { res.json(response.data.result) @@ -285,70 +337,15 @@ async function getRawTransaction( }) } */ -} - - -router.post( - "/sendRawTransaction/:hex", - config.rawTransactionsRateLimit5, - async ( - req: express.Request, - res: express.Response, - next: express.NextFunction - ) => { - try {https://twitter.com/vinarmani/status/1064504066259210240 - let transactions = JSON.parse(req.params.hex) - if (transactions.length > 20) { - res.json({ - error: "Array too large. Max 20 transactions" - }) - } + } catch (err) { + // Write out error to error log. + //logger.error(`Error in rawtransactions/sendRawTransaction: `, err) - const result = [] as any - transactions = transactions.map((transaction: any) => { - requestConfig.data.id = "sendrawtransaction" - requestConfig.data.method = "sendrawtransaction" - requestConfig.data.params = [transaction] - BitboxHTTP(requestConfig).catch(error => { - try { - return { - data: { - result: error.response.data.error.message - } - } - } catch (ex) { - return { - data: { - result: "unknown error" - } - } - } - }) - }) - axios.all(transactions).then( - axios.spread((...args) => { - for (let i = 0; i < args.length; i++) { - let tmp = {} as any - const parsed = tmp.data.result - result.push(parsed) - } - res.json(result) - }) - ) - } catch (error) { - requestConfig.data.id = "sendrawtransaction" - requestConfig.data.method = "sendrawtransaction" - requestConfig.data.params = [req.params.hex] - BitboxHTTP(requestConfig) - .then(response => { - res.json(response.data.result) - }) - .catch(error => { - res.send(error.response.data.error.message) - }) - } + res.status(500) + return res.json({ error: util.inspect(err) }) } -) + +} router.post( "/change/:rawtx/:prevTxs/:destination/:fee", @@ -513,6 +510,7 @@ module.exports = { root, decodeRawTransaction, decodeScript, - getRawTransaction + getRawTransaction, + sendRawTransaction } } diff --git a/test/v2/raw-transactions.js b/test/v2/raw-transactions.js index 90c637c9..8761eb5a 100644 --- a/test/v2/raw-transactions.js +++ b/test/v2/raw-transactions.js @@ -211,7 +211,7 @@ describe("#Raw-Transactions", () => { assert.include(result.error, "txids must be an array") }) - it("should throw 400 error if txids is too large", async () => { + it("should throw 400 error if txids array is too large", async () => { const testArray = [] for (var i = 0; i < 25; i++) testArray.push("") @@ -305,4 +305,58 @@ describe("#Raw-Transactions", () => { assert.isArray(result[0].vout) }) }) + + describe("sendRawTransaction()", () => { + // block route handler. + const sendRawTransaction = + rawtransactions.testableComponents.sendRawTransaction + + it("should throw 400 error if hexs array is missing", async () => { + const result = await sendRawTransaction(req, res) + //console.log(`result: ${util.inspect(result)}`) + + assert.hasAllKeys(result, ["error"]) + assert.include(result.error, "hex must be an array") + }) + + it("should throw 400 error if hexs array is too large", async () => { + const testArray = [] + for (var i = 0; i < 25; i++) testArray.push("") + + req.body.hex = testArray + + const result = await sendRawTransaction(req, res) + //console.log(`result: ${util.inspect(result)}`) + + assert.hasAllKeys(result, ["error"]) + assert.include(result.error, "Array too large. Max 20 entries") + }) + + it("should throw 400 error if hex array element is empty", async () => { + req.body.hex = [""] + + const result = await sendRawTransaction(req, res) + //console.log(`result: ${util.inspect(result)}`) + + assert.hasAllKeys(result, ["error"]) + assert.include(result.error, "Encountered empty hex") + }) + + it("should throw 500 error if hex is invalid", async () => { + // Mock the RPC call for unit tests. + if (process.env.TEST === "unit") { + nock(`${process.env.RPC_BASEURL}`) + .post(``) + .reply(500, { result: "Error: Request failed with status code 500" }) + } + + req.body.hex = ["abc123"] + + const result = await sendRawTransaction(req, res) + //console.log(`result: ${util.inspect(result)}`) + + assert.hasAllKeys(result, ["error"]) + assert.include(result.error, "Request failed with status code 500") + }) + }) }) From d80e43f34cbeb471e201a82f6f13b7bd4fcf372b Mon Sep 17 00:00:00 2001 From: Chris Troutner Date: Tue, 20 Nov 2018 14:07:37 -0800 Subject: [PATCH 03/14] Added last unit and integration test for sendRawTransaction --- src/routes/v2/rawtransactions.ts | 67 ++++++++++++++++---------------- test/v2/raw-transactions.js | 22 +++++++++++ 2 files changed, 56 insertions(+), 33 deletions(-) diff --git a/src/routes/v2/rawtransactions.ts b/src/routes/v2/rawtransactions.ts index 563f08ac..d65d6718 100644 --- a/src/routes/v2/rawtransactions.ts +++ b/src/routes/v2/rawtransactions.ts @@ -94,6 +94,11 @@ router.post( config.rawTransactionsRateLimit5, sendRawTransaction ) +router.post( + "/change/:rawtx/:prevTxs/:destination/:fee", + config.rawTransactionsRateLimit6, + whChangeOutput +) function root( req: express.Request, @@ -284,7 +289,6 @@ async function sendRawTransaction( return res.json(results) - /* https: let transactions = JSON.parse(req.params.hex) if (transactions.length > 20) { @@ -344,42 +348,38 @@ async function sendRawTransaction( res.status(500) return res.json({ error: util.inspect(err) }) } - } -router.post( - "/change/:rawtx/:prevTxs/:destination/:fee", - config.rawTransactionsRateLimit6, - async ( - req: express.Request, - res: express.Response, - next: express.NextFunction - ) => { +// WH add change output to the transaction. +async function whChangeOutput( + req: express.Request, + res: express.Response, + next: express.NextFunction +) { + try { + const params = [ + req.params.rawtx, + JSON.parse(req.params.prevTxs), + req.params.destination, + parseFloat(req.params.fee) + ] + if (req.query.position) params.push(parseInt(req.query.position)) + + requestConfig.data.id = "whc_createrawtx_change" + requestConfig.data.method = "whc_createrawtx_change" + requestConfig.data.params = params + try { - const params = [ - req.params.rawtx, - JSON.parse(req.params.prevTxs), - req.params.destination, - parseFloat(req.params.fee) - ] - if (req.query.position) params.push(parseInt(req.query.position)) - - requestConfig.data.id = "whc_createrawtx_change" - requestConfig.data.method = "whc_createrawtx_change" - requestConfig.data.params = params - - try { - const response = await BitboxHTTP(requestConfig) - res.json(response.data.result) - } catch (error) { - res.status(500).send(error.response.data.error) - } - } catch (err) { - res.status(500) - res.send(`Error in /change: ${err.message}`) + const response = await BitboxHTTP(requestConfig) + res.json(response.data.result) + } catch (error) { + res.status(500).send(error.response.data.error) } + } catch (err) { + res.status(500) + res.send(`Error in /change: ${err.message}`) } -) +} router.post( "/input/:rawTx/:txid/:n", @@ -511,6 +511,7 @@ module.exports = { decodeRawTransaction, decodeScript, getRawTransaction, - sendRawTransaction + sendRawTransaction, + whChangeOutput } } diff --git a/test/v2/raw-transactions.js b/test/v2/raw-transactions.js index 8761eb5a..63cfa3f2 100644 --- a/test/v2/raw-transactions.js +++ b/test/v2/raw-transactions.js @@ -358,5 +358,27 @@ describe("#Raw-Transactions", () => { assert.hasAllKeys(result, ["error"]) assert.include(result.error, "Request failed with status code 500") }) + + it("should submit hex encoded transaction", async () => { + // Mock the RPC call for unit tests. + if (process.env.TEST === "unit") { + nock(`${process.env.RPC_BASEURL}`) + .post(``) + .reply(200, { + result: + "aef8848396e67532b42008b9d75b5a5a3459a6717740f31f0553b74102b4b118" + }) + } + + req.body.hex = [ + "0200000001189f7cf4303e2e0bcc5af4be323b9b397dd4104ca2de09528eb90a1450b8a999010000006a4730440220212ec2ffce136a30cec1bc86a40b08a2afdeb6f8dbd652d7bcb07b1aad6dfa8c022041f59585273b89d88879a9a531ba3272dc953f48ff57dad955b2dee70e76c0624121030143ffd18f1c4add75c86b2f930d9551d51f7a6bd786314247022b7afc45d231ffffffff0230d39700000000001976a914af64a026e06910c59463b000d18c3d125d7e951a88ac58c20000000000001976a914af64a026e06910c59463b000d18c3d125d7e951a88ac00000000" + ] + + const result = await sendRawTransaction(req, res) + console.log(`result: ${util.inspect(result)}`) + + //assert.hasAllKeys(result, ["error"]) + //assert.include(result.error, "Request failed with status code 500") + }) }) }) From 0ea720e7db57e58207cce7b4f12ee2a0599f30ee Mon Sep 17 00:00:00 2001 From: Chris Troutner Date: Tue, 20 Nov 2018 14:39:23 -0800 Subject: [PATCH 04/14] Finalized integration and unit tests for sendRawTransaction --- src/routes/v2/rawtransactions.ts | 55 ++++++++++++++++++++++++-------- test/v2/raw-transactions.js | 30 +++++++++++++++-- 2 files changed, 68 insertions(+), 17 deletions(-) diff --git a/src/routes/v2/rawtransactions.ts b/src/routes/v2/rawtransactions.ts index d65d6718..ba028d7b 100644 --- a/src/routes/v2/rawtransactions.ts +++ b/src/routes/v2/rawtransactions.ts @@ -95,7 +95,7 @@ router.post( sendRawTransaction ) router.post( - "/change/:rawtx/:prevTxs/:destination/:fee", + "/change/:rawtx/:prevtxs/:destination/:fee", config.rawTransactionsRateLimit6, whChangeOutput ) @@ -357,27 +357,54 @@ async function whChangeOutput( next: express.NextFunction ) { try { - const params = [ - req.params.rawtx, - JSON.parse(req.params.prevTxs), - req.params.destination, - parseFloat(req.params.fee) - ] + // TODO: What kind of validations should go here? + + const rawTx = req.params.rawtx + if (!rawTx || rawTx === "") { + res.status(400) + return res.json({ error: "rawtx can not be empty" }) + } + + let prevTxs + try { + prevTxs = JSON.parse(req.params.prevtxs) + } catch (err) { + res.status(400) + return res.json({ error: "could not parse prevtxs" }) + } + + const destination = req.params.destination + if (!destination || destination === "") { + res.status(400) + return res.json({ error: "destination can not be empty" }) + } + + let fee = req.params.fee + if (!fee || fee === "") { + res.status(400) + return res.json({ error: "fee can not be empty" }) + } + fee = parseFloat(fee) + + const params = [rawTx, prevTxs, destination, fee] if (req.query.position) params.push(parseInt(req.query.position)) + const { + BitboxHTTP, + username, + password, + requestConfig + } = routeUtils.setEnvVars() + requestConfig.data.id = "whc_createrawtx_change" requestConfig.data.method = "whc_createrawtx_change" requestConfig.data.params = params - try { - const response = await BitboxHTTP(requestConfig) - res.json(response.data.result) - } catch (error) { - res.status(500).send(error.response.data.error) - } + const response = await BitboxHTTP(requestConfig) + return res.json(response.data.result) } catch (err) { res.status(500) - res.send(`Error in /change: ${err.message}`) + return res.json({ error: `Error in /change: ${err.message}` }) } } diff --git a/test/v2/raw-transactions.js b/test/v2/raw-transactions.js index 63cfa3f2..085dd74a 100644 --- a/test/v2/raw-transactions.js +++ b/test/v2/raw-transactions.js @@ -360,6 +360,10 @@ describe("#Raw-Transactions", () => { }) it("should submit hex encoded transaction", async () => { + // This is a difficult test to run as transaction hex is invalid after a + // block confirmation. So the unit tests simulates what the output 'should' + // be, but the integration asserts an expected failure. + // Mock the RPC call for unit tests. if (process.env.TEST === "unit") { nock(`${process.env.RPC_BASEURL}`) @@ -375,10 +379,30 @@ describe("#Raw-Transactions", () => { ] const result = await sendRawTransaction(req, res) - console.log(`result: ${util.inspect(result)}`) + //console.log(`result: ${util.inspect(result)}`) + + if (process.env.TEST === "unit") { + assert.isArray(result) + assert.isString(result[0]) + + // Integration test + } else { + assert.hasAllKeys(result, ["error"]) + assert.include(result.error, "Request failed with status code 500") + } + }) + }) + + describe("whChangeOutput()", () => { + const whChangeOutput = rawtransactions.testableComponents.whChangeOutput - //assert.hasAllKeys(result, ["error"]) - //assert.include(result.error, "Request failed with status code 500") + it("should throw 400 error if rawtx is empty", async () => { + req.params.rawtx = "" + const result = await whChangeOutput(req, res) + //console.log(`result: ${util.inspect(result)}`) + + assert.hasAllKeys(result, ["error"]) + assert.include(result.error, "rawtx can not be empty") }) }) }) From 4dc6bc65b4c934f02b4f37137961b784cc55e9e8 Mon Sep 17 00:00:00 2001 From: Chris Troutner Date: Tue, 20 Nov 2018 15:38:51 -0800 Subject: [PATCH 05/14] Refactored WH change tx with integration and unit tests --- src/routes/v2/rawtransactions.ts | 53 ---------------------- test/v2/raw-transactions.js | 76 ++++++++++++++++++++++++++++++++ 2 files changed, 76 insertions(+), 53 deletions(-) diff --git a/src/routes/v2/rawtransactions.ts b/src/routes/v2/rawtransactions.ts index ba028d7b..b1227619 100644 --- a/src/routes/v2/rawtransactions.ts +++ b/src/routes/v2/rawtransactions.ts @@ -288,59 +288,6 @@ async function sendRawTransaction( } return res.json(results) - - /* - https: let transactions = JSON.parse(req.params.hex) - if (transactions.length > 20) { - res.json({ - error: "Array too large. Max 20 transactions" - }) - } - - const result = [] as any - transactions = transactions.map((transaction: any) => { - requestConfig.data.id = "sendrawtransaction" - requestConfig.data.method = "sendrawtransaction" - requestConfig.data.params = [transaction] - BitboxHTTP(requestConfig).catch(error => { - try { - return { - data: { - result: error.response.data.error.message - } - } - } catch (ex) { - return { - data: { - result: "unknown error" - } - } - } - }) - }) - axios.all(transactions).then( - axios.spread((...args) => { - for (let i = 0; i < args.length; i++) { - let tmp = {} as any - const parsed = tmp.data.result - result.push(parsed) - } - res.json(result) - }) - ) - } catch (error) { - requestConfig.data.id = "sendrawtransaction" - requestConfig.data.method = "sendrawtransaction" - requestConfig.data.params = [req.params.hex] - BitboxHTTP(requestConfig) - .then(response => { - res.json(response.data.result) - }) - .catch(error => { - res.send(error.response.data.error.message) - }) - } - */ } catch (err) { // Write out error to error log. //logger.error(`Error in rawtransactions/sendRawTransaction: `, err) diff --git a/test/v2/raw-transactions.js b/test/v2/raw-transactions.js index 085dd74a..6f9ff92d 100644 --- a/test/v2/raw-transactions.js +++ b/test/v2/raw-transactions.js @@ -404,5 +404,81 @@ describe("#Raw-Transactions", () => { assert.hasAllKeys(result, ["error"]) assert.include(result.error, "rawtx can not be empty") }) + + it("should throw 400 error if prevtx is empty", async () => { + req.params.rawtx = "fakeTx" + const result = await whChangeOutput(req, res) + //console.log(`result: ${util.inspect(result)}`) + + assert.hasAllKeys(result, ["error"]) + assert.include(result.error, "could not parse prevtxs") + }) + + it("should throw 400 error if prevtx not parsable JSON", async () => { + req.params.rawtx = "fakeTx" + req.params.prevtxs = "someUnparsableJSON" + const result = await whChangeOutput(req, res) + //console.log(`result: ${util.inspect(result)}`) + + assert.hasAllKeys(result, ["error"]) + assert.include(result.error, "could not parse prevtxs") + }) + + it("should throw 400 error if destination is empty", async () => { + req.params.rawtx = "fakeTx" + req.params.prevtxs = JSON.stringify([{ a: 0 }]) + const result = await whChangeOutput(req, res) + //console.log(`result: ${util.inspect(result)}`) + + assert.hasAllKeys(result, ["error"]) + assert.include(result.error, "destination can not be empty") + }) + + it("should throw 400 error fee is empty", async () => { + req.params.rawtx = "fakeTx" + req.params.prevtxs = JSON.stringify([{ a: 0 }]) + req.params.destination = "bchtest:fakeaddress" + const result = await whChangeOutput(req, res) + //console.log(`result: ${util.inspect(result)}`) + + assert.hasAllKeys(result, ["error"]) + assert.include(result.error, "fee can not be empty") + }) + + it("should generate change tx hex", async () => { + // Mock the RPC call for unit tests. + if (process.env.TEST === "unit") { + nock(`${process.env.RPC_BASEURL}`) + .post(``) + .reply(200, { + result: + "0100000001b15ee60431ef57ec682790dec5a3c0d83a0c360633ea8308fbf6d5fc10a779670400000000ffffffff03efe40000000000001976a9141522a025f2365eebee65cd8a8b8a38180dbcd59588ac5c0d00000000000047512102f3e471222bb57a7d416c82bf81c627bfcd2bdc47f36e763ae69935bba4601ece21021580b888ff56feb27f17f08802ebed26258c23697d6a462d43fc13b565fda2dd52aeaa0a0000000000001976a914946cb2e08075bcbaf157e47bcb67eb2b2339d24288ac00000000" + }) + } + + req.params.rawtx = + "0100000001b15ee60431ef57ec682790dec5a3c0d83a0c360633ea8308fbf6d5fc10a779670400000000ffffffff025c0d00000000000047512102f3e471222bb57a7d416c82bf81c627bfcd2bdc47f36e763ae69935bba4601ece21021580b888ff56feb27f17f08802ebed26258c23697d6a462d43fc13b565fda2dd52aeaa0a0000000000001976a914946cb2e08075bcbaf157e47bcb67eb2b2339d24288ac00000000" + req.params.prevtxs = JSON.stringify([ + { + txid: + "6779a710fcd5f6fb0883ea3306360c3ad8c0a3c5de902768ec57ef3104e65eb1", + vout: 4, + scriptPubKey: "76a9147b25205fd98d462880a3e5b0541235831ae959e588ac", + value: 0.00068257 + } + ]) + req.params.destination = + "bchtest:qq2j9gp97gm9a6lwvhxc4zu28qvqm0x4j5e72v7ejg" + req.params.fee = 0.000035 + + const result = await whChangeOutput(req, res) + //console.log(`result: ${util.inspect(result)}`) + + assert.isString(result) + assert.equal( + result, + "0100000001b15ee60431ef57ec682790dec5a3c0d83a0c360633ea8308fbf6d5fc10a779670400000000ffffffff03efe40000000000001976a9141522a025f2365eebee65cd8a8b8a38180dbcd59588ac5c0d00000000000047512102f3e471222bb57a7d416c82bf81c627bfcd2bdc47f36e763ae69935bba4601ece21021580b888ff56feb27f17f08802ebed26258c23697d6a462d43fc13b565fda2dd52aeaa0a0000000000001976a914946cb2e08075bcbaf157e47bcb67eb2b2339d24288ac00000000" + ) + }) }) }) From 2ee07ba900634b14a6b463f40c135f3b301af3dc Mon Sep 17 00:00:00 2001 From: Chris Troutner Date: Tue, 20 Nov 2018 16:08:51 -0800 Subject: [PATCH 06/14] Refactored whInput, added unit and integration tests --- src/routes/v2/rawtransactions.ts | 63 +++++++++++++++++++++----------- test/v2/raw-transactions.js | 47 ++++++++++++++++++++++++ 2 files changed, 89 insertions(+), 21 deletions(-) diff --git a/src/routes/v2/rawtransactions.ts b/src/routes/v2/rawtransactions.ts index b1227619..ee4b1ac0 100644 --- a/src/routes/v2/rawtransactions.ts +++ b/src/routes/v2/rawtransactions.ts @@ -99,6 +99,7 @@ router.post( config.rawTransactionsRateLimit6, whChangeOutput ) +router.post("/input/:rawTx/:txid/:n", config.rawTransactionsRateLimit7, whInput) function root( req: express.Request, @@ -355,30 +356,49 @@ async function whChangeOutput( } } -router.post( - "/input/:rawTx/:txid/:n", - config.rawTransactionsRateLimit7, - async ( - req: express.Request, - res: express.Response, - next: express.NextFunction - ) => { +// Add a transaction input +async function whInput( + req: express.Request, + res: express.Response, + next: express.NextFunction +) { + try { + const rawtx = req.params.rawtx + + + 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) + + + const { + BitboxHTTP, + username, + password, + requestConfig + } = routeUtils.setEnvVars() + requestConfig.data.id = "whc_createrawtx_input" requestConfig.data.method = "whc_createrawtx_input" - requestConfig.data.params = [ - req.params.rawTx, - req.params.txid, - parseInt(req.params.n) - ] + requestConfig.data.params = [rawtx, txid, n] - try { - const response = await BitboxHTTP(requestConfig) - res.json(response.data.result) - } catch (error) { - res.status(500).send(error.response.data.error) - } + const response = await BitboxHTTP(requestConfig) + + return res.json(response.data.result) + } catch (err) { + res.status(500) + return res.json({ error: `Error in /input: ${err.message}` }) } -) +} router.post( "/opReturn/:rawTx/:payload", @@ -486,6 +506,7 @@ module.exports = { decodeScript, getRawTransaction, sendRawTransaction, - whChangeOutput + whChangeOutput, + whInput } } diff --git a/test/v2/raw-transactions.js b/test/v2/raw-transactions.js index 6f9ff92d..ad9b476e 100644 --- a/test/v2/raw-transactions.js +++ b/test/v2/raw-transactions.js @@ -481,4 +481,51 @@ describe("#Raw-Transactions", () => { ) }) }) + + describe("whInput()", () => { + const whInput = rawtransactions.testableComponents.whInput + + it("should throw 400 error if txid is empty", async () => { + const result = await whInput(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 error if n is empty", async () => { + req.params.txid = "fakeTXID" + + const result = await whInput(req, res) + //console.log(`result: ${util.inspect(result)}`) + + assert.hasAllKeys(result, ["error"]) + assert.include(result.error, "n can not be empty") + }) + + it("should generate tx hex if rawtx is empty", async () => { + // Mock the RPC call for unit tests. + if (process.env.TEST === "unit") { + nock(`${process.env.RPC_BASEURL}`) + .post(``) + .reply(200, { + result: + "0200000001ee42830efcd184b75581f8ad0a34bee5feccf8d39adf86a5ed05df17907206b00000000000ffffffff0000000000" + }) + } + + req.params.txid = + "b006729017df05eda586df9ad3f8ccfee5be340aadf88155b784d1fc0e8342ee" + req.params.n = 0 + + const result = await whInput(req, res) + //console.log(`result: ${util.inspect(result)}`) + + assert.isString(result) + assert.equal( + result, + "0200000001ee42830efcd184b75581f8ad0a34bee5feccf8d39adf86a5ed05df17907206b00000000000ffffffff0000000000" + ) + }) + }) }) From 58a5aa77337110ca7f5cab122658e292ececaaf2 Mon Sep 17 00:00:00 2001 From: Chris Troutner Date: Tue, 20 Nov 2018 16:39:54 -0800 Subject: [PATCH 07/14] Refactored whOpReturn, added unit and integration tests --- src/routes/v2/rawtransactions.ts | 74 +++++++++++++++++++++----------- test/v2/raw-transactions.js | 46 ++++++++++++++++++++ 2 files changed, 94 insertions(+), 26 deletions(-) diff --git a/src/routes/v2/rawtransactions.ts b/src/routes/v2/rawtransactions.ts index ee4b1ac0..9837df54 100644 --- a/src/routes/v2/rawtransactions.ts +++ b/src/routes/v2/rawtransactions.ts @@ -99,7 +99,12 @@ router.post( config.rawTransactionsRateLimit6, whChangeOutput ) -router.post("/input/:rawTx/:txid/:n", config.rawTransactionsRateLimit7, whInput) +router.post("/input/:rawtx/:txid/:n", config.rawTransactionsRateLimit7, whInput) +router.post( + "/opReturn/:rawtx/:payload", + config.rawTransactionsRateLimit8, + whOpReturn +) function root( req: express.Request, @@ -365,21 +370,19 @@ async function whInput( try { const rawtx = req.params.rawtx - const txid = req.params.txid - if(!txid || 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 === "") { + if (n === undefined || n === "") { res.status(400) return res.json({ error: "n can not be empty" }) } n = parseInt(n) - const { BitboxHTTP, username, @@ -396,30 +399,48 @@ async function whInput( return res.json(response.data.result) } catch (err) { res.status(500) - return res.json({ error: `Error in /input: ${err.message}` }) + return res.json({ error: `Error in whInput: ${err.message}` }) } } -router.post( - "/opReturn/:rawTx/:payload", - config.rawTransactionsRateLimit8, - async ( - req: express.Request, - res: express.Response, - next: express.NextFunction - ) => { +// Add an op-return to the transaction. +async function whOpReturn( + req: express.Request, + res: express.Response, + next: express.NextFunction +) { + try { + const rawtx = req.params.rawtx + if (!rawtx || rawtx === "") { + res.status(400) + return res.json({ error: "rawtx can not be empty" }) + } + + const payload = req.params.payload + if (!payload || payload === "") { + res.status(400) + return res.json({ error: "payload can not be empty" }) + } + + const { + BitboxHTTP, + username, + password, + requestConfig + } = routeUtils.setEnvVars() + requestConfig.data.id = "whc_createrawtx_opreturn" requestConfig.data.method = "whc_createrawtx_opreturn" - requestConfig.data.params = [req.params.rawTx, req.params.payload] + requestConfig.data.params = [rawtx, payload] - try { - const response = await BitboxHTTP(requestConfig) - res.json(response.data.result) - } catch (error) { - res.status(500).send(error.response.data.error) - } + const response = await BitboxHTTP(requestConfig) + + return res.json(response.data.result) + } catch (err) { + res.status(500) + return res.json({ error: `Error in whOpReturn: ${err.message}` }) } -) +} router.post( "/reference/:rawTx/:destination", @@ -438,7 +459,7 @@ router.post( try { const response = await BitboxHTTP(requestConfig) - res.json(response.data.result) + return res.json(response.data.result) } catch (error) { res.status(500).send(error.response.data.error) } @@ -464,7 +485,7 @@ router.post( try { const response = await BitboxHTTP(requestConfig) - res.json(response.data.result) + return res.json(response.data.result) } catch (error) { res.status(500).send(error.response.data.error.message) } @@ -491,7 +512,7 @@ router.post( try { const response = await BitboxHTTP(requestConfig) - res.json(response.data.result) + return res.json(response.data.result) } catch (error) { res.status(500).send(error.response.data.error.message) } @@ -507,6 +528,7 @@ module.exports = { getRawTransaction, sendRawTransaction, whChangeOutput, - whInput + whInput, + whOpReturn } } diff --git a/test/v2/raw-transactions.js b/test/v2/raw-transactions.js index ad9b476e..607fbd3e 100644 --- a/test/v2/raw-transactions.js +++ b/test/v2/raw-transactions.js @@ -528,4 +528,50 @@ describe("#Raw-Transactions", () => { ) }) }) + + describe("whOpReturn()", () => { + const whOpReturn = rawtransactions.testableComponents.whOpReturn + + it("should throw 400 error if rawtx is empty", async () => { + const result = await whOpReturn(req, res) + //console.log(`result: ${util.inspect(result)}`) + + assert.hasAllKeys(result, ["error"]) + assert.include(result.error, "rawtx can not be empty") + }) + + it("should throw 400 error if payload is empty", async () => { + req.params.rawtx = "fakeTX" + + const result = await whOpReturn(req, res) + //console.log(`result: ${util.inspect(result)}`) + + assert.hasAllKeys(result, ["error"]) + assert.include(result.error, "payload can not be empty") + }) + + it("should return a transaction payload", async () => { + // Mock the RPC call for unit tests. + if (process.env.TEST === "unit") { + nock(`${process.env.RPC_BASEURL}`) + .post(``) + .reply(200, { + result: + "0100000000010000000000000000166a140877686300000000000000020000000006dac2c000000000" + }) + } + + req.params.rawtx = "01000000000000000000" + req.params.payload = "00000000000000020000000006dac2c0" + + const result = await whOpReturn(req, res) + //console.log(`result: ${util.inspect(result)}`) + + assert.isString(result) + assert.equal( + result, + "0100000000010000000000000000166a140877686300000000000000020000000006dac2c000000000" + ) + }) + }) }) From 2b2943ff275217ef21f4ab953998358b7d138a59 Mon Sep 17 00:00:00 2001 From: Chris Troutner Date: Tue, 20 Nov 2018 16:59:43 -0800 Subject: [PATCH 08/14] Refactored whReference, added unit and integration tests --- package.json | 3 +- src/routes/v2/rawtransactions.ts | 57 ++++++++++++++++-------- test/v2/raw-transactions.js | 74 ++++++++++++++++++++++++++++++++ 3 files changed, 115 insertions(+), 19 deletions(-) diff --git a/package.json b/package.json index 21844a9d..7661af38 100644 --- a/package.json +++ b/package.json @@ -10,8 +10,7 @@ "dev": "nodemon ./dist/app.js", "test": "npm run test-v1", "test-v1": "npm run build && nyc --reporter=text mocha --require babel-core/register --timeout 15000 test/v1/", - "test-v2": "export NETWORK=testnet && npm run build && nyc --reporter=text mocha --require babel-core/register test/v2/", - "test-v2:blockchain": "nyc --reporter=text mocha --require babel-core/register --timeout 15000 test/v2/blockchain", + "test-v2": "export NETWORK=testnet && npm run build && nyc --reporter=text mocha --require babel-core/register --timeout 15000 test/v2/", "test-all": "TEST=integration nyc --reporter=text mocha --require babel-core/register --timeout 15000 test/v1/ test/v2/", "coverage": "nyc report --reporter=text-lcov | coveralls", "coverage:report": "export NETWORK=testnet && npm run build && nyc --reporter=html mocha --require babel-core/register test/v2/" diff --git a/src/routes/v2/rawtransactions.ts b/src/routes/v2/rawtransactions.ts index 9837df54..33674d13 100644 --- a/src/routes/v2/rawtransactions.ts +++ b/src/routes/v2/rawtransactions.ts @@ -105,6 +105,11 @@ router.post( config.rawTransactionsRateLimit8, whOpReturn ) +router.post( + "/reference/:rawtx/:destination", + config.rawTransactionsRateLimit9, + whReference +) function root( req: express.Request, @@ -442,29 +447,46 @@ async function whOpReturn( } } -router.post( - "/reference/:rawTx/:destination", - config.rawTransactionsRateLimit9, - async ( - req: express.Request, - res: express.Response, - next: express.NextFunction - ) => { - const params = [req.params.rawTx, req.params.destination] +async function whReference( + req: express.Request, + res: express.Response, + next: express.NextFunction +) { + try { + const rawtx = req.params.rawtx + if(!rawtx || rawtx === "") { + res.status(400) + return res.json({ error: "rawtx can not be empty" }) + } + + const destination = req.params.destination + if(!destination || destination === "") { + res.status(400) + return res.json({ error: "destination can not be empty"}) + } + + const params = [rawtx, destination] if (req.query.amount) params.push(req.query.amount) + const { + BitboxHTTP, + username, + password, + requestConfig + } = routeUtils.setEnvVars() + requestConfig.data.id = "whc_createrawtx_reference" requestConfig.data.method = "whc_createrawtx_reference" requestConfig.data.params = params - try { - const response = await BitboxHTTP(requestConfig) - return res.json(response.data.result) - } catch (error) { - res.status(500).send(error.response.data.error) - } + const response = await BitboxHTTP(requestConfig) + + return res.json(response.data.result) + } catch (err) { + res.status(500) + return res.json({ error: `Error in whReference: ${err.message}` }) } -) +} router.post( "/decodeTransaction/:rawTx", @@ -529,6 +551,7 @@ module.exports = { sendRawTransaction, whChangeOutput, whInput, - whOpReturn + whOpReturn, + whReference } } diff --git a/test/v2/raw-transactions.js b/test/v2/raw-transactions.js index 607fbd3e..9e575a02 100644 --- a/test/v2/raw-transactions.js +++ b/test/v2/raw-transactions.js @@ -56,6 +56,7 @@ describe("#Raw-Transactions", () => { // Explicitly reset the parmas and body. req.params = {} req.body = {} + req.query = {} // Activate nock if it's inactive. if (!nock.isActive()) nock.activate() @@ -574,4 +575,77 @@ describe("#Raw-Transactions", () => { ) }) }) + + describe("whReference()", () => { + const whReference = rawtransactions.testableComponents.whReference + + it("should throw 400 error if rawtx is empty", async () => { + const result = await whReference(req, res) + //console.log(`result: ${util.inspect(result)}`) + + assert.hasAllKeys(result, ["error"]) + assert.include(result.error, "rawtx can not be empty") + }) + + it("should throw 400 error if destination is empty", async () => { + req.params.rawtx = "faketx" + + const result = await whReference(req, res) + //console.log(`result: ${util.inspect(result)}`) + + assert.hasAllKeys(result, ["error"]) + assert.include(result.error, "destination can not be empty") + }) + + it("should generate reference tx hex with no amount specified", async () => { + const expected = + "0100000001a7a9402ecd77f3c9f745793c9ec805bfa2e14b89877581c734c774864247e6f50400000000ffffffff04aa0a0000000000001976a9146d18edfe073d53f84dd491dae1379f8fb0dfe5d488ac5c0d0000000000004751210252ce4bdd3ce38b4ebbc5a6e1343608230da508ff12d23d85b58c964204c4cef3210294cc195fc096f87d0f813a337ae7e5f961b1c8a18f1f8604a909b3a5121f065b52aeaa0a0000000000001976a914946cb2e08075bcbaf157e47bcb67eb2b2339d24288ac22020000000000001976a9141522a025f2365eebee65cd8a8b8a38180dbcd59588ac00000000" + + // Mock the RPC call for unit tests. + if (process.env.TEST === "unit") { + nock(`${process.env.RPC_BASEURL}`) + .post(``) + .reply(200, { + result: expected + }) + } + + req.params.rawtx = + "0100000001a7a9402ecd77f3c9f745793c9ec805bfa2e14b89877581c734c774864247e6f50400000000ffffffff03aa0a0000000000001976a9146d18edfe073d53f84dd491dae1379f8fb0dfe5d488ac5c0d0000000000004751210252ce4bdd3ce38b4ebbc5a6e1343608230da508ff12d23d85b58c964204c4cef3210294cc195fc096f87d0f813a337ae7e5f961b1c8a18f1f8604a909b3a5121f065b52aeaa0a0000000000001976a914946cb2e08075bcbaf157e47bcb67eb2b2339d24288ac00000000" + req.params.destination = + "bchtest:qq2j9gp97gm9a6lwvhxc4zu28qvqm0x4j5e72v7ejg" + + const result = await whReference(req, res) + //console.log(`result: ${util.inspect(result)}`) + + assert.isString(result) + assert.equal(result, expected) + }) + + it("should generate reference tx hex with amount specified", async () => { + const expected = + "0100000001a7a9402ecd77f3c9f745793c9ec805bfa2e14b89877581c734c774864247e6f50400000000ffffffff04aa0a0000000000001976a9146d18edfe073d53f84dd491dae1379f8fb0dfe5d488ac5c0d0000000000004751210252ce4bdd3ce38b4ebbc5a6e1343608230da508ff12d23d85b58c964204c4cef3210294cc195fc096f87d0f813a337ae7e5f961b1c8a18f1f8604a909b3a5121f065b52aeaa0a0000000000001976a914946cb2e08075bcbaf157e47bcb67eb2b2339d24288ac20a10700000000001976a9141522a025f2365eebee65cd8a8b8a38180dbcd59588ac00000000" + + // Mock the RPC call for unit tests. + if (process.env.TEST === "unit") { + nock(`${process.env.RPC_BASEURL}`) + .post(``) + .reply(200, { + result: expected + }) + } + + req.params.rawtx = + "0100000001a7a9402ecd77f3c9f745793c9ec805bfa2e14b89877581c734c774864247e6f50400000000ffffffff03aa0a0000000000001976a9146d18edfe073d53f84dd491dae1379f8fb0dfe5d488ac5c0d0000000000004751210252ce4bdd3ce38b4ebbc5a6e1343608230da508ff12d23d85b58c964204c4cef3210294cc195fc096f87d0f813a337ae7e5f961b1c8a18f1f8604a909b3a5121f065b52aeaa0a0000000000001976a914946cb2e08075bcbaf157e47bcb67eb2b2339d24288ac00000000" + req.params.destination = + "bchtest:qq2j9gp97gm9a6lwvhxc4zu28qvqm0x4j5e72v7ejg" + req.query.amount = 0.005 + + const result = await whReference(req, res) + //console.log(`result: ${util.inspect(result)}`) + + assert.isString(result) + assert.equal(result, expected) + }) + }) }) From abf070860c666162c3a28efc617156cb80a9d6d2 Mon Sep 17 00:00:00 2001 From: Chris Troutner Date: Wed, 21 Nov 2018 08:56:10 -0800 Subject: [PATCH 09/14] Successfully mocked node error for whDecodeTx --- src/routes/v2/rawtransactions.ts | 63 +++++++++++++++------- src/routes/v2/route-utils.js | 26 ++++++++- test/v2/mocks/raw-transactions-mocks.js | 23 +++++++- test/v2/raw-transactions.js | 70 +++++++++++++++++++++++++ 4 files changed, 161 insertions(+), 21 deletions(-) diff --git a/src/routes/v2/rawtransactions.ts b/src/routes/v2/rawtransactions.ts index 33674d13..2a11e6e2 100644 --- a/src/routes/v2/rawtransactions.ts +++ b/src/routes/v2/rawtransactions.ts @@ -110,6 +110,11 @@ router.post( config.rawTransactionsRateLimit9, whReference ) +router.post( + "/decodeTransaction/:rawtx", + config.rawTransactionsRateLimit10, + whDecodeTx +) function root( req: express.Request, @@ -454,15 +459,15 @@ async function whReference( ) { try { const rawtx = req.params.rawtx - if(!rawtx || rawtx === "") { + if (!rawtx || rawtx === "") { res.status(400) return res.json({ error: "rawtx can not be empty" }) } const destination = req.params.destination - if(!destination || destination === "") { + if (!destination || destination === "") { res.status(400) - return res.json({ error: "destination can not be empty"}) + return res.json({ error: "destination can not be empty" }) } const params = [rawtx, destination] @@ -488,15 +493,27 @@ async function whReference( } } -router.post( - "/decodeTransaction/:rawTx", - config.rawTransactionsRateLimit10, - async ( - req: express.Request, - res: express.Response, - next: express.NextFunction - ) => { - const params = [req.params.rawTx] +async function whDecodeTx( + req: express.Request, + res: express.Response, + next: express.NextFunction +) { + try { + const rawtx = req.params.rawtx + if (!rawtx || rawtx === "") { + res.status(400) + return res.json({ error: "rawtx can not be empty" }) + } + + const { + BitboxHTTP, + username, + password, + requestConfig + } = routeUtils.setEnvVars() + + const params = [rawtx] + if (req.query.prevTxs) params.push(JSON.parse(req.query.prevTxs)) if (req.query.height) params.push(req.query.height) @@ -505,14 +522,21 @@ router.post( requestConfig.data.method = "whc_decodetransaction" requestConfig.data.params = params - try { - const response = await BitboxHTTP(requestConfig) - return res.json(response.data.result) - } catch (error) { - res.status(500).send(error.response.data.error.message) + const response = await BitboxHTTP(requestConfig) + + return res.json(response.data.result) + } catch (err) { + // Return the error message from the node, if it exists. + const msg = routeUtils.getNodeError(err) + if(msg) { + res.status(400) + return res.json({error: msg}) } + + res.status(500) + return res.json({ error: `Error in whDecodeTx: ${err.message}` }) } -) +} router.post( "/create/:inputs/:outputs", @@ -552,6 +576,7 @@ module.exports = { whChangeOutput, whInput, whOpReturn, - whReference + whReference, + whDecodeTx } } diff --git a/src/routes/v2/route-utils.js b/src/routes/v2/route-utils.js index 09010342..f642d427 100644 --- a/src/routes/v2/route-utils.js +++ b/src/routes/v2/route-utils.js @@ -6,12 +6,16 @@ const axios = require("axios") +const util = require("util") +util.inspect.defaultOptions = { depth: 1 } + const BITBOXCli = require("bitbox-cli/lib/bitbox-cli").default const BITBOX = new BITBOXCli() module.exports = { validateNetwork, // Prevents a common user error - setEnvVars // Allows RPC variables to be set dynamically based on changing env vars. + setEnvVars, // Allows RPC variables to be set dynamically based on changing env vars. + getNodeError // Extract error message from the full node. } // Returns true if user-provided cash address matches the correct network, @@ -69,3 +73,23 @@ function setEnvVars() { return { BitboxHTTP, username, password, requestConfig } } + +// Error messages returned by a full node can be burried pretty deep inside the +// error object returned by Axios. This function attempts to extract the message. +// If successful, it returns a string. If not, it returns false. +function getNodeError(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 err.response.data.error.message + + return false + } catch (err) { + return false + } +} diff --git a/test/v2/mocks/raw-transactions-mocks.js b/test/v2/mocks/raw-transactions-mocks.js index 28c1b249..5222624c 100644 --- a/test/v2/mocks/raw-transactions-mocks.js +++ b/test/v2/mocks/raw-transactions-mocks.js @@ -114,9 +114,30 @@ const mockRawTransactionVerbose = { blocktime: 1542646373 } +const mockWHDecode = { + txid: "f8a9857fe3b8a288b5fcafb1b0fc196731f433add6d962f77acd7c10b970ff89", + fee: "500", + sendingaddress: "bchtest:qzjtnzcvzxx7s0na88yrg3zl28wwvfp97538sgrrmr", + referenceaddress: "bchtest:qzjtnzcvzxx7s0na88yrg3zl28wwvfp97538sgrrmr", + ismine: false, + version: 0, + type_int: 0, + type: "Simple Send", + propertyid: 368, + precision: "8", + amount: "10.00000000", + valid: true, + blockhash: "0000000046ba0bcef78caaa4492622176bbc563cf249ab52340a0449bc8e26f6", + blocktime: 1542814183, + positioninblock: 57, + block: 1269008, + confirmations: 2 +} + module.exports = { mockDecodeRawTransaction, mockDecodeScript, mockRawTransactionConcise, - mockRawTransactionVerbose + mockRawTransactionVerbose, + mockWHDecode } diff --git a/test/v2/raw-transactions.js b/test/v2/raw-transactions.js index 9e575a02..510fad81 100644 --- a/test/v2/raw-transactions.js +++ b/test/v2/raw-transactions.js @@ -5,6 +5,8 @@ and integration tests. By default, TEST is set to 'unit'. Set this variable to 'integration' to run the tests against BCH mainnet. + TODO: + -Network 500 errors */ "use strict" @@ -648,4 +650,72 @@ describe("#Raw-Transactions", () => { assert.equal(result, expected) }) }) + + describe("whDecodeTx()", () => { + const whDecodeTx = rawtransactions.testableComponents.whDecodeTx + + it("should throw 400 error if rawtx is empty", async () => { + const result = await whDecodeTx(req, res) + //console.log(`result: ${util.inspect(result)}`) + + assert.hasAllKeys(result, ["error"]) + assert.include(result.error, "rawtx can not be empty") + }) + + it("should return node-error if rawtx is not a WH TX", async () => { + // Mock the RPC call for unit tests. + if (process.env.TEST === "unit") { + nock(`${process.env.RPC_BASEURL}`) + .post(``) + .reply(500, { + error: { message: "Not a Wormhole Protocol transaction" } + }) + } + + req.params.rawtx = + "020000000189ff70b9107ccd7af762d9d6ad33f4316719fcb0b1affcb588a2b8e37f85a9f8000000006b483045022100abcee73654cf4fb5ad951b3967a3577a5049435a35bc23e2d064026dc49f6b07022062cba94df0a90fa5bde2d2973eae1dfd3027126b7dec579baf9dc123a2a79fd44121033dab7ef8681396e7c95f88a2733c470fdb11e2f9825838ecd059fc9fe7301275ffffffff0210270000000000001976a914a4b98b0c118de83e7d39c834445f51dce62425f588ac8a589800000000001976a914a4b98b0c118de83e7d39c834445f51dce62425f588ac00000000" + + const result = await whDecodeTx(req, res) + //console.log(`result: ${util.inspect(result)}`) + + assert.hasAllKeys(result, ["error"]) + assert.equal(res.statusCode, 400, "HTTP status code 400 expected.") + assert.include(result.error, "Not a Wormhole Protocol transaction") + }) + + it("should decode WH TX", async () => { + // Mock the RPC call for unit tests. + if (process.env.TEST === "unit") { + nock(`${process.env.RPC_BASEURL}`) + .post(``) + .reply(200, { result: mockData.mockWHDecode }) + } + + req.params.rawtx = + "0200000001f4158c5ec0424656626d201a50f8b51fbe94468aaec88d211bbc59c306e9df01000000006b483045022100c8397a4dd8c8cf1cdc80fb3d86665a8d88379f2583c2efa4165219939eebe32202207ec12283d6a5fe764e2b2efa3934357da161526d497eff20ac221d5f737d68d24121033dab7ef8681396e7c95f88a2733c470fdb11e2f9825838ecd059fc9fe7301275ffffffff0392809800000000001976a914a4b98b0c118de83e7d39c834445f51dce62425f588ac0000000000000000166a14087768630000000000000170000000003b9aca0022020000000000001976a914a4b98b0c118de83e7d39c834445f51dce62425f588ac00000000" + + const result = await whDecodeTx(req, res) + //console.log(`result: ${util.inspect(result)}`) + + assert.hasAnyKeys(result, [ + "txid", + "fee", + "sendingaddress", + "referenceaddress", + "ismine", + "version", + "type_int", + "type", + "propertyid", + "precision", + "amount", + "valid", + "blockhash", + "blocktime", + "positioninblock", + "block", + "confirmations" + ]) + }) + }) }) From 7cd0680394481288f9b6d6c7ddbeb16fe003746a Mon Sep 17 00:00:00 2001 From: Chris Troutner Date: Wed, 21 Nov 2018 09:08:45 -0800 Subject: [PATCH 10/14] Better handling of 503 network errors --- src/routes/v2/rawtransactions.ts | 8 ++++---- src/routes/v2/route-utils.js | 14 +++++++++++--- test/v2/raw-transactions.js | 24 ++++++++++++++++++++++++ 3 files changed, 39 insertions(+), 7 deletions(-) diff --git a/src/routes/v2/rawtransactions.ts b/src/routes/v2/rawtransactions.ts index 2a11e6e2..c158bdec 100644 --- a/src/routes/v2/rawtransactions.ts +++ b/src/routes/v2/rawtransactions.ts @@ -527,10 +527,10 @@ async function whDecodeTx( return res.json(response.data.result) } catch (err) { // Return the error message from the node, if it exists. - const msg = routeUtils.getNodeError(err) - if(msg) { - res.status(400) - return res.json({error: msg}) + const { msg, status } = routeUtils.getNodeError(err) + if (msg) { + res.status(status) + return res.json({ error: msg }) } res.status(500) diff --git a/src/routes/v2/route-utils.js b/src/routes/v2/route-utils.js index f642d427..61095987 100644 --- a/src/routes/v2/route-utils.js +++ b/src/routes/v2/route-utils.js @@ -86,10 +86,18 @@ function getNodeError(err) { err.response.data.error && err.response.data.error.message ) - return err.response.data.error.message + return { msg: err.response.data.error.message, status: 400 } + + // 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.", + status: 503 + } + } - return false + return { msg: false, status: 500 } } catch (err) { - return false + return { msg: false, status: 500 } } } diff --git a/test/v2/raw-transactions.js b/test/v2/raw-transactions.js index 510fad81..0603678f 100644 --- a/test/v2/raw-transactions.js +++ b/test/v2/raw-transactions.js @@ -662,6 +662,30 @@ describe("#Raw-Transactions", () => { assert.include(result.error, "rawtx 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.rawtx = + "0200000001f4158c5ec0424656626d201a50f8b51fbe94468aaec88d211bbc59c306e9df01000000006b483045022100c8397a4dd8c8cf1cdc80fb3d86665a8d88379f2583c2efa4165219939eebe32202207ec12283d6a5fe764e2b2efa3934357da161526d497eff20ac221d5f737d68d24121033dab7ef8681396e7c95f88a2733c470fdb11e2f9825838ecd059fc9fe7301275ffffffff0392809800000000001976a914a4b98b0c118de83e7d39c834445f51dce62425f588ac0000000000000000166a14087768630000000000000170000000003b9aca0022020000000000001976a914a4b98b0c118de83e7d39c834445f51dce62425f588ac00000000" + + const result = await whDecodeTx(req, res) + //console.log(`result: ${util.inspect(result)}`) + + // Restore the saved URL. + process.env.RPC_BASEURL = savedUrl2 + + assert.equal(res.statusCode, 503, "HTTP status code 503 expected.") + assert.include( + result.error, + "Network error: Could not communicate with full node.", + "Error message expected" + ) + }) + it("should return node-error if rawtx is not a WH TX", async () => { // Mock the RPC call for unit tests. if (process.env.TEST === "unit") { From b82c470647e612940f9f97e3c10a4b3be5c23ee8 Mon Sep 17 00:00:00 2001 From: Chris Troutner Date: Wed, 21 Nov 2018 09:59:15 -0800 Subject: [PATCH 11/14] Refactored whCreateTx, added unit and integration tests --- src/routes/v2/rawtransactions.ts | 84 +++++++++++++----- src/routes/v2/route-utils.js | 10 ++- test/v2/mocks/raw-transactions-mocks.js | 16 +++- test/v2/raw-transactions.js | 111 ++++++++++++++++++++++++ 4 files changed, 196 insertions(+), 25 deletions(-) diff --git a/src/routes/v2/rawtransactions.ts b/src/routes/v2/rawtransactions.ts index c158bdec..743d90b4 100644 --- a/src/routes/v2/rawtransactions.ts +++ b/src/routes/v2/rawtransactions.ts @@ -115,6 +115,11 @@ router.post( config.rawTransactionsRateLimit10, whDecodeTx ) +router.post( + "/create/:inputs/:outputs", + config.rawTransactionsRateLimit11, + whCreateTx +) function root( req: express.Request, @@ -493,6 +498,7 @@ async function whReference( } } +// Decode the raw hex of a WH transaction. async function whDecodeTx( req: express.Request, res: express.Response, @@ -527,7 +533,7 @@ async function whDecodeTx( return res.json(response.data.result) } catch (err) { // Return the error message from the node, if it exists. - const { msg, status } = routeUtils.getNodeError(err) + const { msg, status } = routeUtils.decodeError(err) if (msg) { res.status(status) return res.json({ error: msg }) @@ -538,32 +544,69 @@ async function whDecodeTx( } } -router.post( - "/create/:inputs/:outputs", - config.rawTransactionsRateLimit11, - async ( - req: express.Request, - res: express.Response, - next: express.NextFunction - ) => { - const params = [ - JSON.parse(req.params.inputs), - JSON.parse(req.params.outputs) - ] +// Create a transaction spending the given inputs and creating new outputs. +async function whCreateTx( + req: express.Request, + res: express.Response, + next: express.NextFunction +) { + try { + // Validate input parameters + let inputs = req.params.inputs + if (!inputs || inputs === "") { + res.status(400) + return res.json({ error: "inputs can not be empty" }) + } + + try { + inputs = JSON.parse(inputs) + } catch (err) { + res.status(400) + return res.json({ error: "could not parse inputs" }) + } + + let outputs = req.params.outputs + if (!outputs || outputs === "") { + res.status(400) + return res.json({ error: "outputs can not be empty" }) + } + + try { + outputs = JSON.parse(outputs) + } catch (err) { + res.status(400) + return res.json({ error: "could not parse outputs" }) + } + + const { + BitboxHTTP, + username, + password, + requestConfig + } = routeUtils.setEnvVars() + + const params = [inputs, outputs] if (req.query.locktime) params.push(req.query.locktime) requestConfig.data.id = "createrawtransaction" requestConfig.data.method = "createrawtransaction" requestConfig.data.params = params - try { - const response = await BitboxHTTP(requestConfig) - return res.json(response.data.result) - } catch (error) { - res.status(500).send(error.response.data.error.message) + const response = await BitboxHTTP(requestConfig) + + return res.json(response.data.result) + } catch (err) { + // Return the error message from the node, if it exists. + const { msg, status } = routeUtils.decodeError(err) + if (msg) { + res.status(status) + return res.json({ error: msg }) } + + res.status(500) + return res.json({ error: `Error in whCreateTx: ${err.message}` }) } -) +} module.exports = { router, @@ -577,6 +620,7 @@ module.exports = { whInput, whOpReturn, whReference, - whDecodeTx + whDecodeTx, + whCreateTx } } diff --git a/src/routes/v2/route-utils.js b/src/routes/v2/route-utils.js index 61095987..2976af74 100644 --- a/src/routes/v2/route-utils.js +++ b/src/routes/v2/route-utils.js @@ -15,7 +15,7 @@ const BITBOX = new BITBOXCli() module.exports = { validateNetwork, // Prevents a common user error setEnvVars, // Allows RPC variables to be set dynamically based on changing env vars. - getNodeError // Extract error message from the full node. + decodeError // Extract and interpret error messages. } // Returns true if user-provided cash address matches the correct network, @@ -75,9 +75,11 @@ function setEnvVars() { } // Error messages returned by a full node can be burried pretty deep inside the -// error object returned by Axios. This function attempts to extract the message. -// If successful, it returns a string. If not, it returns false. -function getNodeError(err) { +// 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. +function decodeError(err) { try { // Attempt to extract the full node error message. if ( diff --git a/test/v2/mocks/raw-transactions-mocks.js b/test/v2/mocks/raw-transactions-mocks.js index 5222624c..08620a32 100644 --- a/test/v2/mocks/raw-transactions-mocks.js +++ b/test/v2/mocks/raw-transactions-mocks.js @@ -134,10 +134,24 @@ const mockWHDecode = { confirmations: 2 } +const mockWHCreateInput = { + txid: "f7ed9cf23dee85910f6269c9a101a75fcfd2f3c6fc81f17fad824ff7aaf99ab2", + vout: 1, + scriptPubKey: "76a914a4b98b0c118de83e7d39c834445f51dce62425f588ac", + amount: 0.09984138, + satoshis: 9984138, + height: 1269011, + confirmations: 4, + legacyAddress: "mvXwPH74hW2yVTWwDwzsjGoaUAqJvWk7ZJ", + cashAddress: "bchtest:qzjtnzcvzxx7s0na88yrg3zl28wwvfp97538sgrrmr", + value: 0.09984138 +} + module.exports = { mockDecodeRawTransaction, mockDecodeScript, mockRawTransactionConcise, mockRawTransactionVerbose, - mockWHDecode + mockWHDecode, + mockWHCreateInput } diff --git a/test/v2/raw-transactions.js b/test/v2/raw-transactions.js index 0603678f..383d6b98 100644 --- a/test/v2/raw-transactions.js +++ b/test/v2/raw-transactions.js @@ -742,4 +742,115 @@ describe("#Raw-Transactions", () => { ]) }) }) + + describe("whCreateTx()", () => { + const whCreateTx = rawtransactions.testableComponents.whCreateTx + + it("should throw 400 error if inputs are empty", async () => { + const result = await whCreateTx(req, res) + //console.log(`result: ${util.inspect(result)}`) + + assert.hasAllKeys(result, ["error"]) + assert.include(result.error, "inputs can not be empty") + }) + + it("should throw 400 error if inputs are not parsable JSON", async () => { + req.params.inputs = "fakeTx" + + const result = await whCreateTx(req, res) + //console.log(`result: ${util.inspect(result)}`) + + assert.hasAllKeys(result, ["error"]) + assert.include(result.error, "could not parse inputs") + }) + + it("should throw 400 error if outputs are empty", async () => { + req.params.inputs = JSON.stringify([{ txid: "myid", vout: 0 }]) + const result = await whCreateTx(req, res) + //console.log(`result: ${util.inspect(result)}`) + + assert.hasAllKeys(result, ["error"]) + assert.include(result.error, "outputs can not be empty") + }) + + it("should throw 400 error if outputs are not parsable JSON", async () => { + req.params.inputs = JSON.stringify([{ txid: "myid", vout: 0 }]) + req.params.outputs = "fakeTx" + + const result = await whCreateTx(req, res) + //console.log(`result: ${util.inspect(result)}`) + + assert.hasAllKeys(result, ["error"]) + assert.include(result.error, "could not parse outputs") + }) + + 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.inputs = JSON.stringify([mockData.mockWHCreateInput]) + req.params.outputs = JSON.stringify({}) + + const result = await whCreateTx(req, res) + //console.log(`result: ${util.inspect(result)}`) + + // Restore the saved URL. + process.env.RPC_BASEURL = savedUrl2 + + assert.equal(res.statusCode, 503, "HTTP status code 503 expected.") + assert.include( + result.error, + "Network error: Could not communicate with full node.", + "Error message expected" + ) + }) + + it("should return node-error", async () => { + // Mock the RPC call for unit tests. + if (process.env.TEST === "unit") { + nock(`${process.env.RPC_BASEURL}`) + .post(``) + .reply(500, { + error: { message: "txid must be hexadecimal string (not 'myid')" } + }) + } + + req.params.inputs = JSON.stringify([{ txid: "myid", vout: 0 }]) + req.params.outputs = JSON.stringify({}) + + const result = await whCreateTx(req, res) + //console.log(`result: ${util.inspect(result)}`) + + assert.hasAllKeys(result, ["error"]) + assert.equal(res.statusCode, 400, "HTTP status code 400 expected.") + assert.include( + result.error, + "txid must be hexadecimal string (not 'myid')" + ) + }) + + it("should create WH TX", async () => { + const expected = + "0200000001b29af9aaf74f82ad7ff181fcc6f3d2cf5fa701a1c969620f9185ee3df29cedf70100000000ffffffff0000000000" + + // Mock the RPC call for unit tests. + if (process.env.TEST === "unit") { + nock(`${process.env.RPC_BASEURL}`) + .post(``) + .reply(200, { result: expected }) + } + + req.params.inputs = JSON.stringify([mockData.mockWHCreateInput]) + req.params.outputs = JSON.stringify({}) + + const result = await whCreateTx(req, res) + //console.log(`result: ${util.inspect(result)}`) + + assert.isString(result) + assert.equal(result, expected) + }) + }) }) From 16f80eff438b2c61f572934343f3f8d2e97b6f0f Mon Sep 17 00:00:00 2001 From: Chris Troutner Date: Wed, 21 Nov 2018 10:43:50 -0800 Subject: [PATCH 12/14] updating 500 network issue unit tests --- src/routes/v2/rawtransactions.ts | 67 ++++++++++++++++++++++++++++---- test/v2/raw-transactions.js | 22 +++++++---- 2 files changed, 74 insertions(+), 15 deletions(-) diff --git a/src/routes/v2/rawtransactions.ts b/src/routes/v2/rawtransactions.ts index 743d90b4..3ecaad9a 100644 --- a/src/routes/v2/rawtransactions.ts +++ b/src/routes/v2/rawtransactions.ts @@ -158,12 +158,19 @@ async function decodeRawTransaction( const response = await BitboxHTTP(requestConfig) return res.json(response.data.result) - } catch (error) { + } 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) res.status(500) - return res.json({ error: util.inspect(error) }) + return res.json({ error: util.inspect(err) }) } } @@ -196,12 +203,19 @@ async function decodeScript( const response = await BitboxHTTP(requestConfig) return res.json(response.data.result) - } catch (error) { + } 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/decodeScript: `, err) res.status(500) - return res.json({ error: util.inspect(error) }) + return res.json({ error: util.inspect(err) }) } } @@ -255,6 +269,13 @@ async function getRawTransaction( return res.json(results) } 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/getRawTransaction: `, err) @@ -310,8 +331,12 @@ async function sendRawTransaction( return res.json(results) } catch (err) { - // Write out error to error log. - //logger.error(`Error in rawtransactions/sendRawTransaction: `, err) + // Attempt to decode the error message. + const { msg, status } = routeUtils.decodeError(err) + if (msg) { + res.status(status) + return res.json({ error: msg }) + } res.status(500) return res.json({ error: util.inspect(err) }) @@ -371,6 +396,13 @@ async function whChangeOutput( 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 }) + } + res.status(500) return res.json({ error: `Error in /change: ${err.message}` }) } @@ -413,6 +445,13 @@ async function whInput( 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 }) + } + res.status(500) return res.json({ error: `Error in whInput: ${err.message}` }) } @@ -452,6 +491,13 @@ async function whOpReturn( 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 }) + } + res.status(500) return res.json({ error: `Error in whOpReturn: ${err.message}` }) } @@ -493,6 +539,13 @@ async function whReference( 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 }) + } + res.status(500) return res.json({ error: `Error in whReference: ${err.message}` }) } @@ -596,7 +649,7 @@ async function whCreateTx( return res.json(response.data.result) } catch (err) { - // Return the error message from the node, if it exists. + // Attempt to decode the error message. const { msg, status } = routeUtils.decodeError(err) if (msg) { res.status(status) diff --git a/test/v2/raw-transactions.js b/test/v2/raw-transactions.js index 383d6b98..208a0ee7 100644 --- a/test/v2/raw-transactions.js +++ b/test/v2/raw-transactions.js @@ -5,8 +5,6 @@ and integration tests. By default, TEST is set to 'unit'. Set this variable to 'integration' to run the tests against BCH mainnet. - TODO: - -Network 500 errors */ "use strict" @@ -103,7 +101,7 @@ describe("#Raw-Transactions", () => { assert.include(result.error, "hex can not be empty") }) - it("should throw 500 when network issues", async () => { + it("should throw 503 when network issues", async () => { // Save the existing RPC URL. const savedUrl2 = process.env.RPC_BASEURL @@ -119,8 +117,12 @@ describe("#Raw-Transactions", () => { // Restore the saved URL. process.env.RPC_BASEURL = savedUrl2 - assert.equal(res.statusCode, 500, "HTTP status code 500 expected.") - assert.include(result.error, "ENOTFOUND", "Error message expected") + assert.equal(res.statusCode, 503, "HTTP status code 503 expected.") + assert.include( + result.error, + "Network error: Could not communicate with full node.", + "Error message expected" + ) }) it("should GET /decodeRawTransaction", async () => { @@ -163,7 +165,7 @@ describe("#Raw-Transactions", () => { assert.include(result.error, "hex can not be empty") }) - it("should throw 500 when network issues", async () => { + it("should throw 503 when network issues", async () => { // Save the existing RPC URL. const savedUrl2 = process.env.RPC_BASEURL @@ -179,8 +181,12 @@ describe("#Raw-Transactions", () => { // Restore the saved URL. process.env.RPC_BASEURL = savedUrl2 - assert.equal(res.statusCode, 500, "HTTP status code 500 expected.") - assert.include(result.error, "ENOTFOUND", "Error message expected") + assert.equal(res.statusCode, 503, "HTTP status code 503 expected.") + assert.include( + result.error, + "Network error: Could not communicate with full node.", + "Error message expected" + ) }) it("should GET /decodeScript", async () => { From f3b9518d68d7715fe81a71fa84585b64f8b312e4 Mon Sep 17 00:00:00 2001 From: Chris Troutner Date: Wed, 21 Nov 2018 11:13:15 -0800 Subject: [PATCH 13/14] Updated error handlers and tests with routeUtils.decodeError --- test/v2/raw-transactions.js | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/test/v2/raw-transactions.js b/test/v2/raw-transactions.js index 208a0ee7..fec380f2 100644 --- a/test/v2/raw-transactions.js +++ b/test/v2/raw-transactions.js @@ -243,12 +243,14 @@ describe("#Raw-Transactions", () => { assert.include(result.error, "Encountered empty TXID") }) - it("should throw 500 error if txid is invalid", async () => { + it("should throw 400 error if txid is invalid", async () => { // Mock the RPC call for unit tests. if (process.env.TEST === "unit") { nock(`${process.env.RPC_BASEURL}`) .post(``) - .reply(500, { result: "Error: Request failed with status code 500" }) + .reply(500, { + error: { message: "parameter 1 must be of length 64 (not 6)" } + }) } req.body.txids = ["abc123"] @@ -257,7 +259,8 @@ describe("#Raw-Transactions", () => { //console.log(`result: ${util.inspect(result)}`) assert.hasAllKeys(result, ["error"]) - assert.include(result.error, "Request failed with status code 500") + assert.equal(res.statusCode, 400, "HTTP status code 400 expected.") + assert.include(result.error, "parameter 1 must be of length 64 (not 6)") }) it("should get concise transaction data", async () => { @@ -356,7 +359,9 @@ describe("#Raw-Transactions", () => { if (process.env.TEST === "unit") { nock(`${process.env.RPC_BASEURL}`) .post(``) - .reply(500, { result: "Error: Request failed with status code 500" }) + .reply(500, { + error: { message: "TX decode failed" } + }) } req.body.hex = ["abc123"] @@ -365,7 +370,8 @@ describe("#Raw-Transactions", () => { //console.log(`result: ${util.inspect(result)}`) assert.hasAllKeys(result, ["error"]) - assert.include(result.error, "Request failed with status code 500") + assert.equal(res.statusCode, 400, "HTTP status code 400 expected.") + assert.include(result.error, "TX decode failed") }) it("should submit hex encoded transaction", async () => { @@ -397,7 +403,7 @@ describe("#Raw-Transactions", () => { // Integration test } else { assert.hasAllKeys(result, ["error"]) - assert.include(result.error, "Request failed with status code 500") + assert.include(result.error, "transaction already in block chain") } }) }) From 31a9bfb41403d4cd4edd0ba1c2696f5f2c66dd5d Mon Sep 17 00:00:00 2001 From: Gabriel Cardona Date: Thu, 22 Nov 2018 07:48:33 +0900 Subject: [PATCH 14/14] Update cashaddrs --- test/v1/address.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/v1/address.js b/test/v1/address.js index abf76921..dc857e48 100644 --- a/test/v1/address.js +++ b/test/v1/address.js @@ -101,7 +101,7 @@ describe("#AddressRouter", () => { it("should GET /utxo/:address single address", done => { const mockRequest = httpMocks.createRequest({ method: "GET", - url: '/utxo/["qzs02v05l7qs5s24srqju498qu55dwuj0cx5ehjm2c"%5D' + url: '/utxo/["qpk4hk3wuxe2uqtqc97n8atzrrr6r5mleczf9sur4h"%5D' }) const mockResponse = httpMocks.createResponse({ eventEmitter: require("events").EventEmitter @@ -131,7 +131,7 @@ describe("#AddressRouter", () => { const mockRequest = httpMocks.createRequest({ method: "GET", url: - '/utxo/["qql6r7khtjgwy3ufnjtsczvaf925hyw49cudht57tr", "qzs02v05l7qs5s24srqju498qu55dwuj0cx5ehjm2c"%5D' + '/utxo/["qpk4hk3wuxe2uqtqc97n8atzrrr6r5mleczf9sur4h", "qq49jys0hgfl4qd3d6qc0lp9e4mgefltlujef7u7rd"%5D' }) const mockResponse = httpMocks.createResponse({ eventEmitter: require("events").EventEmitter