From a5208110b92f448b60a1591ba75287a8871c286e Mon Sep 17 00:00:00 2001 From: Chris Troutner Date: Fri, 29 Jan 2021 12:43:02 -0800 Subject: [PATCH] feat(getTxData): RawTransactions.getTxData() returns tx info with vin addresses --- package.json | 2 +- src/raw-transactions.js | 71 ++++++++++++++++++++--- test/integration/rawtransaction.js | 15 ++++- test/unit/fixtures/rawtransaction-mock.js | 10 +++- test/unit/raw-tranactions.js | 56 +++++++++++++++--- 5 files changed, 134 insertions(+), 20 deletions(-) diff --git a/package.json b/package.json index a0aa695..4fa743b 100644 --- a/package.json +++ b/package.json @@ -17,7 +17,7 @@ "test:integration:local:bchn": "RESTURL=http://localhost:3000/v4/ mocha --timeout 30000 test/integration/ && mocha --timeout 30000 test/integration/chains/bchn/", "test:integration:local:testnet": "RESTURL=http://localhost:4000/v4/ mocha --timeout 30000 test/integration/testnet", "test:temp": "export RESTURL=https://bchn.fullstack.cash/v4/ && mocha --timeout 30000 -g '#rawtransaction' test/integration/", - "test:temp2": "mocha --timeout=30000 -g '#_getInputAddrs' test/unit/", + "test:temp2": "mocha --timeout=30000 -g '#getTxData' test/unit/", "coverage": "nyc report --reporter=text-lcov | coveralls", "coverage:report": "nyc --reporter=html mocha --timeout 25000 test/unit/", "docs": "./node_modules/.bin/apidoc -i src/ -o docs", diff --git a/src/raw-transactions.js b/src/raw-transactions.js index f2d126c..5fcc19b 100644 --- a/src/raw-transactions.js +++ b/src/raw-transactions.js @@ -289,20 +289,19 @@ class RawTransactions { } } - // Retrieve the parent TX (pTX) for the given TXID in order to retrieve the BCH - // address of the inputs for the given TXID. - // Assumes a single TXID. Does not yet work with an array of TXIDs. + // Given verbose transaction details, this function retrieves the transaction + // data for the inputs (the parent transactions). It returns an array of + // objects. Each object corresponds to a transaction input, and contains + // the address that generated that input UTXO. + // + // Assumes a single TX. Does not yet work with an array of TXs. // This function returns an array of objects, each object if formated as follows: // { // vin: 0, // The position of the input for the given txid // address: bitcoincash:qzhrpmu7nruyfcemeanqh5leuqcnf6zkjq4qm9nqh0 // } - async _getInputAddrs (txid) { + async _getInputAddrs (txDetails) { try { - // Get the TX details for the transaction under consideration. - const txDetails = await this.getRawTransaction(txid, true) - // console.log(`txDetails: ${JSON.stringify(txDetails, null, 2)}`) - const retArray = [] // Return array for (let i = 0; i < txDetails.vin.length; i++) { @@ -314,7 +313,9 @@ class RawTransactions { // Get the TX details for the input, in order to retrieve the address of // the sender. const txDetailsParent = await this.getRawTransaction(inputTxid, true) - console.log(`txDetailsParent: ${JSON.stringify(txDetailsParent, null, 2)}`) + // console.log( + // `txDetailsParent: ${JSON.stringify(txDetailsParent, null, 2)}` + // ) // The vout from the previous tx that represents the sender. const voutSender = txDetailsParent.vout[inputVout] @@ -332,6 +333,58 @@ class RawTransactions { } } + /** + * @api RawTransactions.getTxData() getTxData() + * @apiName getTxData + * @apiGroup RawTransactions + * @apiDescription + * Returns an object of transaction data, including addresses for input UTXOs. + * + * This function is equivalent to running `getRawTransaction (txid, true)`, + * execept the `vin` array will be populated with an `address` property that + * contains the `bitcoincash:` address of the sender for each input. + * + * This function will only work with a single txid. It does not yet support an + * array of TXIDs. + * + * @apiExample Example usage: + * (async () => { + * try { + * let txData = await bchjs.RawTransactions.getTxData("0e3e2357e806b6cdb1f70b54c3a3a17b6714ee1f0e68bebb44a74b1efd512098"); + * console.log(txData); + * } catch(error) { + * console.error(error) + * } + * })() + */ + // Equivalent to running: async getRawTransaction (txid, verbose = true) + // Only handles a single TXID (not arrays). + // Appends the BCH address to the inputs of the transaction. + async getTxData (txid) { + try { + if (typeof txid !== 'string') { + throw new Error('Input must be a string or array of strings.') + } + + // Get the TX details for the transaction under consideration. + const txDetails = await this.getRawTransaction(txid, true) + // console.log(`txDetails: ${JSON.stringify(txDetails, null, 2)}`) + + const inAddrs = await this._getInputAddrs(txDetails) + // console.log(`inAddrs: ${JSON.stringify(inAddrs, null, 2)}`) + + // Add the input address to the transaction data. + for (let i = 0; i < inAddrs.length; i++) { + txDetails.vin[i].address = inAddrs[i].address + } + + return txDetails + } catch (error) { + if (error.response && error.response.data) throw error.response.data + else throw error + } + } + /** * @api RawTransactions.sendRawTransaction() sendRawTransaction() * @apiName sendRawTransaction diff --git a/test/integration/rawtransaction.js b/test/integration/rawtransaction.js index 0d4d68b..5d06fd1 100644 --- a/test/integration/rawtransaction.js +++ b/test/integration/rawtransaction.js @@ -254,7 +254,9 @@ describe('#rawtransaction', () => { // const txid = '32233db13f2ae6d82b6262f335643dccf09fc0bcfcef4bc3fbe023355f02e112' const txid = '05f7d4a4e25f53d63a360434eb54f221abf159112b7fffc91da1072a079cded3' - const result = await bchjs.RawTransactions._getInputAddrs(txid) + const txDetails = await bchjs.RawTransactions.getRawTransaction(txid, true) + + const result = await bchjs.RawTransactions._getInputAddrs(txDetails) // console.log(`result: ${JSON.stringify(result, null, 2)}`) assert.isArray(result) @@ -263,6 +265,17 @@ describe('#rawtransaction', () => { assert.property(result[0], 'address') }) }) + + describe('#getTxData', () => { + it('should return tx data with input addresses', async () => { + const txid = '05f7d4a4e25f53d63a360434eb54f221abf159112b7fffc91da1072a079cded3' + + const result = await bchjs.RawTransactions.getTxData(txid) + // console.log(`result: ${JSON.stringify(result, null, 2)}`) + + assert.property(result.vin[0], 'address') + }) + }) }) function sleep (ms) { diff --git a/test/unit/fixtures/rawtransaction-mock.js b/test/unit/fixtures/rawtransaction-mock.js index 03ecfc7..e844de2 100644 --- a/test/unit/fixtures/rawtransaction-mock.js +++ b/test/unit/fixtures/rawtransaction-mock.js @@ -48,7 +48,15 @@ const mockParentTx1 = { ] } +const mockGetInputAddrsOutput = [ + { + vin: 0, + address: 'bitcoincash:qr2jtznnkhy0jnynn4l7jmmce6teqcyrhc8herhlgt' + } +] + module.exports = { mockTx, - mockParentTx1 + mockParentTx1, + mockGetInputAddrsOutput } diff --git a/test/unit/raw-tranactions.js b/test/unit/raw-tranactions.js index 54c3c13..a75c846 100644 --- a/test/unit/raw-tranactions.js +++ b/test/unit/raw-tranactions.js @@ -141,15 +141,9 @@ describe('#RawTransactions', () => { it('should return an array of input addresses', async () => { sandbox .stub(bchjs.RawTransactions, 'getRawTransaction') - .onCall(0) - .resolves(mockData.mockTx) - .onCall(1) .resolves(mockData.mockParentTx1) - const txid = - '32233db13f2ae6d82b6262f335643dccf09fc0bcfcef4bc3fbe023355f02e112' - - const result = await bchjs.RawTransactions._getInputAddrs(txid) + const result = await bchjs.RawTransactions._getInputAddrs(mockData.mockTx) // console.log(`result: ${JSON.stringify(result, null, 2)}`) assert2.isArray(result) @@ -164,7 +158,53 @@ describe('#RawTransactions', () => { .stub(bchjs.RawTransactions, 'getRawTransaction') .rejects(new Error('test error')) - await bchjs.RawTransactions._getInputAddrs('fake txid') + await bchjs.RawTransactions._getInputAddrs(mockData.mockTx) + + assert.fail('Unexpected result') + } catch (err) { + // console.log(err) + + assert2.equal(err.message, 'test error') + } + }) + }) + + describe('#getTxData', () => { + it('should return tx data with input addresses', async () => { + // Mock dependencies + sandbox.stub(bchjs.RawTransactions, 'getRawTransaction').resolves(mockData.mockTx) + sandbox.stub(bchjs.RawTransactions, '_getInputAddrs').resolves(mockData.mockGetInputAddrsOutput) + + const txid = '05f7d4a4e25f53d63a360434eb54f221abf159112b7fffc91da1072a079cded3' + + const result = await bchjs.RawTransactions.getTxData(txid) + // console.log(`result: ${JSON.stringify(result, null, 2)}`) + + assert2.property(result.vin[0], 'address') + }) + + it('should throw an error for a non-txid input', async () => { + try { + await bchjs.RawTransactions.getTxData(1234) + + assert.fail('Unexpected result') + } catch (err) { + // console.log(err) + + assert2.include(err.message, 'Input must be a string or array of strings') + } + }) + + it('should catch and throw an error', async () => { + try { + // Force a network error. + sandbox + .stub(bchjs.RawTransactions, 'getRawTransaction') + .rejects(new Error('test error')) + + const txid = '05f7d4a4e25f53d63a360434eb54f221abf159112b7fffc91da1072a079cded3' + + await bchjs.RawTransactions.getTxData(txid) assert.fail('Unexpected result') } catch (err) {