From 6f4cc0f43919f9f84ce0a9a5c6f32e0ed7dfd8bb Mon Sep 17 00:00:00 2001 From: Chris Troutner Date: Wed, 22 Sep 2021 16:58:14 -0700 Subject: [PATCH] fix(Transaction.get): Fixed bug when Genesis TX was input to queried TX --- src/transaction.js | 36 +++++--- test/unit/fixtures/transaction-mock.js | 116 ++++++++++++++++++++++++- test/unit/transaction-unit.js | 49 +++++++++++ 3 files changed, 189 insertions(+), 12 deletions(-) diff --git a/src/transaction.js b/src/transaction.js index 0ee7d91..0d0b647 100644 --- a/src/transaction.js +++ b/src/transaction.js @@ -43,7 +43,7 @@ class Transaction { } const txDetails = await this.rawTransaction.getTxData(txid) - // console.log(`txDetails: ${JSON.stringify(txDetails, null, 2)}`) + console.log(`txDetails: ${JSON.stringify(txDetails, null, 2)}`) // Setup default SLP properties. txDetails.isValidSLPTx = false @@ -53,7 +53,7 @@ class Transaction { let outTokenData try { outTokenData = await this.slpUtils.decodeOpReturn(txid) - // console.log(`outTokenData: ${JSON.stringify(outTokenData, null, 2)}`) + console.log(`outTokenData: ${JSON.stringify(outTokenData, null, 2)}`) // Get Genesis data for this token. const genesisData = await this.slpUtils.decodeOpReturn( @@ -61,7 +61,7 @@ class Transaction { // decodeOpReturnCache // usrObj // pass user data when making an internal call. ) - // console.log(`genesisData: ${JSON.stringify(genesisData, null, 2)}`) + console.log(`genesisData: ${JSON.stringify(genesisData, null, 2)}`) // Add token information to the tx details object. txDetails.tokenTxType = outTokenData.txType @@ -97,15 +97,29 @@ class Transaction { // If decodeOpReturn() throws an error, then this input is not // from an SLP transaction and can be ignored. const inTokenData = await this.slpUtils.decodeOpReturn(thisVin.txid) - // console.log( - // `vin[${i}] tokenData: ${JSON.stringify(inTokenData, null, 2)}` - // ) + console.log( + `vin[${i}] tokenData: ${JSON.stringify(inTokenData, null, 2)}` + ) - // Get the appropriate vout token amount. This may throw an error, - // which means this Vin is not actually a token UTXO, it was just - // associated with a previous token TX. - const tokenQty = inTokenData.amounts[thisVin.vout - 1] - // console.log(`tokenQty: ${JSON.stringify(tokenQty, null, 2)}`) + let tokenQty = 0 + if (inTokenData.txType === 'SEND') { + // Get the appropriate vout token amount. This may throw an error, + // which means this Vin is not actually a token UTXO, it was just + // associated with a previous token TX. + tokenQty = inTokenData.amounts[thisVin.vout - 1] + console.log(`tokenQty: ${JSON.stringify(tokenQty, null, 2)}`) + } else if (inTokenData.txType === 'GENESIS') { + // Only vout[1] of a Genesis transaction represents the tokens. + // Any other outputs in that transaction are normal BCH UTXOs. + if (thisVin.vout === 1) { + tokenQty = inTokenData.qty + console.log(`tokenQty: ${JSON.stringify(tokenQty, null, 2)}`) + } + } else { + console.log('Unexpected code path. Is this a MINT transaction?') + console.log(inTokenData) + throw new Error('Unexpected code path') + } if (tokenQty) { const realQty = diff --git a/test/unit/fixtures/transaction-mock.js b/test/unit/fixtures/transaction-mock.js index abf9ace..3155d01 100644 --- a/test/unit/fixtures/transaction-mock.js +++ b/test/unit/fixtures/transaction-mock.js @@ -175,10 +175,124 @@ const mockOpReturnData03 = { amounts: ['1000000000', '99883400000000'] } +const genesisTestInputTx = { + txid: '874306bda204d3a5dd15e03ea5732cccdca4c33a52df35162cdd64e30ea7f04e', + hash: '874306bda204d3a5dd15e03ea5732cccdca4c33a52df35162cdd64e30ea7f04e', + version: 1, + size: 480, + locktime: 543408, + vin: [ + { + txid: '323a1e35ae0b356316093d20f2d9fbc995d19314b5c0148b78dc8d9c0dab9d35', + vout: 1, + scriptSig: { + asm: + '30440220268dacee1117975d904dd0d45ef8de42b86030d825a9522bae196a38bbf6b271022001ae1ce2536ab300040e597bcfaa8ef9fb2beaf702d0842f3161aae8e9867f55[ALL|FORKID] 028ff9e32b0dbc82c1d5e0fc945b2537b00420513b10684726f312f1b717c0ae11', + hex: + '4730440220268dacee1117975d904dd0d45ef8de42b86030d825a9522bae196a38bbf6b271022001ae1ce2536ab300040e597bcfaa8ef9fb2beaf702d0842f3161aae8e9867f554121028ff9e32b0dbc82c1d5e0fc945b2537b00420513b10684726f312f1b717c0ae11' + }, + sequence: 4294967294, + address: 'bitcoincash:qp2jesd06k8ycj4wvkpl9lcwaemtr04f5yphjsa07v', + value: 0.00000546 + }, + { + txid: '323a1e35ae0b356316093d20f2d9fbc995d19314b5c0148b78dc8d9c0dab9d35', + vout: 3, + scriptSig: { + asm: + '3045022100fa241bb2de46f68688451bfcae3f165b724e3ccf13b219e7bf2d8d2df7712ad60220353017d6e581a06efce478adfcd2047cea2f92531e283845f3d0a345ef101519[ALL|FORKID] 02cc48ad10516f97e914b8836ff25448d07ad96ebb4704c6a828339880280831bc', + hex: + '483045022100fa241bb2de46f68688451bfcae3f165b724e3ccf13b219e7bf2d8d2df7712ad60220353017d6e581a06efce478adfcd2047cea2f92531e283845f3d0a345ef101519412102cc48ad10516f97e914b8836ff25448d07ad96ebb4704c6a828339880280831bc' + }, + sequence: 4294967294, + address: 'bitcoincash:qppj3euc36x5u6twr5cxrrea2rca53vsfu3dxwr86j', + value: 0.00172192 + } + ], + vout: [ + { + value: 0, + n: 0, + scriptPubKey: { + asm: + 'OP_RETURN 5262419 1 1145980243 323a1e35ae0b356316093d20f2d9fbc995d19314b5c0148b78dc8d9c0dab9d35 00000000004c4b40 00000000004c4b40', + hex: + '6a04534c500001010453454e4420323a1e35ae0b356316093d20f2d9fbc995d19314b5c0148b78dc8d9c0dab9d350800000000004c4b400800000000004c4b40', + type: 'nulldata' + } + }, + { + value: 0.00000546, + n: 1, + scriptPubKey: { + asm: + 'OP_DUP OP_HASH160 0a74cf9c0fb3f6dd62c3f5eecd1ed6e1051428e0 OP_EQUALVERIFY OP_CHECKSIG', + hex: '76a9140a74cf9c0fb3f6dd62c3f5eecd1ed6e1051428e088ac', + reqSigs: 1, + type: 'pubkeyhash', + addresses: ['bitcoincash:qq98fnuup7eldhtzc067ang76mss29pguqh7qv9eac'] + } + }, + { + value: 0.00000546, + n: 2, + scriptPubKey: { + asm: + 'OP_DUP OP_HASH160 d4548261e1be0de7e50b7511597799ec4af2b173 OP_EQUALVERIFY OP_CHECKSIG', + hex: '76a914d4548261e1be0de7e50b7511597799ec4af2b17388ac', + reqSigs: 1, + type: 'pubkeyhash', + addresses: ['bitcoincash:qr29fqnpuxlqmel9pd63zkthn8ky4u43wv0v7pg5mn'] + } + }, + { + value: 0.00171165, + n: 3, + scriptPubKey: { + asm: + 'OP_DUP OP_HASH160 d4548261e1be0de7e50b7511597799ec4af2b173 OP_EQUALVERIFY OP_CHECKSIG', + hex: '76a914d4548261e1be0de7e50b7511597799ec4af2b17388ac', + reqSigs: 1, + type: 'pubkeyhash', + addresses: ['bitcoincash:qr29fqnpuxlqmel9pd63zkthn8ky4u43wv0v7pg5mn'] + } + } + ], + hex: + '0100000002359dab0d9c8ddc788b14c0b51493d195c9fbd9f2203d091663350bae351e3a32010000006a4730440220268dacee1117975d904dd0d45ef8de42b86030d825a9522bae196a38bbf6b271022001ae1ce2536ab300040e597bcfaa8ef9fb2beaf702d0842f3161aae8e9867f554121028ff9e32b0dbc82c1d5e0fc945b2537b00420513b10684726f312f1b717c0ae11feffffff359dab0d9c8ddc788b14c0b51493d195c9fbd9f2203d091663350bae351e3a32030000006b483045022100fa241bb2de46f68688451bfcae3f165b724e3ccf13b219e7bf2d8d2df7712ad60220353017d6e581a06efce478adfcd2047cea2f92531e283845f3d0a345ef101519412102cc48ad10516f97e914b8836ff25448d07ad96ebb4704c6a828339880280831bcfeffffff040000000000000000406a04534c500001010453454e4420323a1e35ae0b356316093d20f2d9fbc995d19314b5c0148b78dc8d9c0dab9d350800000000004c4b400800000000004c4b4022020000000000001976a9140a74cf9c0fb3f6dd62c3f5eecd1ed6e1051428e088ac22020000000000001976a914d4548261e1be0de7e50b7511597799ec4af2b17388ac9d9c0200000000001976a914d4548261e1be0de7e50b7511597799ec4af2b17388acb04a0800', + blockhash: '000000000000000000292a9c6150fce48e2edd8df346948494fe6249e6e7f63b', + confirmations: 163095, + time: 1534271330, + blocktime: 1534271330 +} + +genesisTestOpReturnData01 = { + tokenType: 1, + txType: 'SEND', + tokenId: '323a1e35ae0b356316093d20f2d9fbc995d19314b5c0148b78dc8d9c0dab9d35', + amounts: ['5000000', '5000000'] +} + +genesisTestOpReturnData02 = { + tokenType: 1, + txType: 'GENESIS', + ticker: '', + name: '', + tokenId: '323a1e35ae0b356316093d20f2d9fbc995d19314b5c0148b78dc8d9c0dab9d35', + documentUri: '', + documentHash: '', + decimals: 0, + mintBatonVout: 2, + qty: '10000000' +} + module.exports = { nonSlpTxDetails, slpTxDetails, mockOpReturnData01, mockOpReturnData02, - mockOpReturnData03 + mockOpReturnData03, + genesisTestInputTx, + genesisTestOpReturnData01, + genesisTestOpReturnData02 } diff --git a/test/unit/transaction-unit.js b/test/unit/transaction-unit.js index 8d9ea64..d788f99 100644 --- a/test/unit/transaction-unit.js +++ b/test/unit/transaction-unit.js @@ -115,5 +115,54 @@ describe('#TransactionLib', () => { assert.include(err.message, 'test error') } }) + + // This test case was created in response to a bug. When the input TX + // was a Genesis SLP transaction, the inputs of the transaction were not + // being hydrated properly. + it('should get input details when input is a genesis tx', async () => { + // Mock dependencies + sandbox + .stub(bchjs.Transaction.rawTransaction, 'getTxData') + .resolves(mockData.genesisTestInputTx) + sandbox + .stub(bchjs.Transaction.slpUtils, 'decodeOpReturn') + .onCall(0) + .resolves(mockData.genesisTestOpReturnData01) + .onCall(1) + .resolves(mockData.genesisTestOpReturnData02) + .onCall(2) + .resolves(mockData.genesisTestOpReturnData02) + .onCall(3) + .resolves(mockData.genesisTestOpReturnData02) + sandbox + .stub(bchjs.Transaction.slpUtils, 'waterfallValidateTxid') + .resolves(true) + + const txid = + '874306bda204d3a5dd15e03ea5732cccdca4c33a52df35162cdd64e30ea7f04e' + + const result = await bchjs.Transaction.get(txid) + console.log(`result: ${JSON.stringify(result, null, 2)}`) + + // Assert that there are stanardized properties. + assert.property(result, 'txid') + assert.property(result, 'vin') + assert.property(result, 'vout') + assert.property(result.vout[0], 'value') + assert.property(result.vout[1].scriptPubKey, 'addresses') + + // Assert that added properties exist. + assert.property(result.vout[0], 'tokenQty') + assert.equal(result.vout[0].tokenQty, null) + assert.property(result.vin[0], 'address') + assert.property(result.vin[0], 'value') + assert.property(result.vin[0], 'tokenQty') + assert.property(result, 'isValidSLPTx') + assert.equal(result.isValidSLPTx, true) + + // Assert inputs values unique to a Genesis input have the proper values. + assert.equal(result.vin[0].tokenQty, 10000000) + assert.equal(result.vin[1].tokenQty, null) + }) }) })