From dcabaeba8c486ef1b64c2423b2da9996c5df8631 Mon Sep 17 00:00:00 2001 From: Braydon Fuller Date: Wed, 30 Sep 2015 23:02:29 -0400 Subject: [PATCH 1/2] Include ASM in transaction endpoints. --- lib/transactions.js | 25 ++++++++++---- test/transactions.js | 80 ++++++-------------------------------------- 2 files changed, 29 insertions(+), 76 deletions(-) diff --git a/lib/transactions.js b/lib/transactions.js index 01256b3..06790c2 100644 --- a/lib/transactions.js +++ b/lib/transactions.js @@ -91,11 +91,13 @@ TxController.prototype.transformTransaction = function(transaction) { }; TxController.prototype.transformInput = function(input, index) { + // Input scripts are validated and can be assumed to be valid + var script = new bitcore.Script(input.script); var transformed = { txid: input.prevTxId, vout: input.outputIndex, scriptSig: { - asm: null, // TODO + asm: script.toASM(), hex: input.script }, sequence: input.sequenceNumber, @@ -120,19 +122,28 @@ TxController.prototype.transformOutput = function(output, index) { value: (output.satoshis / 1e8).toFixed(8), n: index, scriptPubKey: { - asm: null, // TODO hex: output.script, - reqSigs: null, // TODO - type: null // TODO + //reqSigs: null, // TODO } //spentTxId: undefined, // TODO //spentIndex: undefined, // TODO //spentTs: undefined // TODO }; - var address = bitcore.Script(output.script).toAddress(this.node.network).toString(); - if(address !== 'false') { - transformed.scriptPubKey.addresses = [address]; + var script; + try { + // Output scripts can be invalid, so we need to try/catch + script = new bitcore.Script(output.script); + } catch (err) { + script = false; + } + if (script) { + transformed.scriptPubKey.asm = script.toASM(); + var address = script.toAddress(this.node.network); + if (address) { + transformed.scriptPubKey.addresses = [address.toString()]; + transformed.scriptPubKey.type = address.type; + } } return transformed; diff --git a/test/transactions.js b/test/transactions.js index f60e5ff..fb1fc11 100644 --- a/test/transactions.js +++ b/test/transactions.js @@ -134,33 +134,23 @@ describe('Transactions', function() { { confirmations: 242, isConfirmed: true, - scriptSig: { - asm: '30450221008e5df62719cd92d7b137d00bbd27f153f2909bcad3a300960bc1020ec6d5e961022039df51600ff4fb5da5a794d1648c6b47c1f7d277fd5877fb5e52a730a3595f8c01 04eb1e0ccd9afcac42229348dd776e991c69551ae3474340fada12e787e51758397e1d3afdba360d6374261125ea3b6ea079a5f202c150dfd729e1062d9176a307' - }, unconfirmedInput: false }, { confirmations: 242, isConfirmed: true, - scriptSig: { - asm: '30440220761464d7bab9515d92260762a97af82a9b25d202d8f7197b1aaec81b6fed541f022059f99606de6b06e17b2cd102dceb3807ebdd9e777a5b77c9a0b3672f5eabcb3101 04eb1e0ccd9afcac42229348dd776e991c69551ae3474340fada12e787e51758397e1d3afdba360d6374261125ea3b6ea079a5f202c150dfd729e1062d9176a307' - }, unconfirmedInput: false } ], vout: [ { scriptPubKey: { - asm: 'OP_DUP OP_HASH160 4b7b335f978f130269fe661423258ae9642df8a1 OP_EQUALVERIFY OP_CHECKSIG', - reqSigs: 1, - type: 'pubkeyhash' + reqSigs: 1 } }, { scriptPubKey: { - asm: 'OP_DUP OP_HASH160 6efcf883b4b6f9997be9a0600f6c095fe2bd2d92 OP_EQUALVERIFY OP_CHECKSIG', - reqSigs: 1, - type: 'pubkeyhash' + reqSigs: 1 }, spentIndex: 1, spentTs: 1440997099, @@ -453,39 +443,23 @@ describe('Transactions', function() { vout: [ { scriptPubKey: { - asm: 'OP_DUP OP_HASH160 68bedce8982d25c3b6b03f6238cbad00378b8ead OP_EQUALVERIFY OP_CHECKSIG', - reqSigs: 1, - type: 'pubkeyhash' + reqSigs: 1 } } ] }, { vin: [ - { - scriptSig: { - asm: '30450221008e5df62719cd92d7b137d00bbd27f153f2909bcad3a300960bc1020ec6d5e961022039df51600ff4fb5da5a794d1648c6b47c1f7d277fd5877fb5e52a730a3595f8c01 04eb1e0ccd9afcac42229348dd776e991c69551ae3474340fada12e787e51758397e1d3afdba360d6374261125ea3b6ea079a5f202c150dfd729e1062d9176a307' - } - }, - { - scriptSig: { - asm: '30440220761464d7bab9515d92260762a97af82a9b25d202d8f7197b1aaec81b6fed541f022059f99606de6b06e17b2cd102dceb3807ebdd9e777a5b77c9a0b3672f5eabcb3101 04eb1e0ccd9afcac42229348dd776e991c69551ae3474340fada12e787e51758397e1d3afdba360d6374261125ea3b6ea079a5f202c150dfd729e1062d9176a307' - } - } ], vout: [ { scriptPubKey: { - asm: 'OP_DUP OP_HASH160 4b7b335f978f130269fe661423258ae9642df8a1 OP_EQUALVERIFY OP_CHECKSIG', - reqSigs: 1, - type: 'pubkeyhash' + reqSigs: 1 } }, { scriptPubKey: { - asm: 'OP_DUP OP_HASH160 6efcf883b4b6f9997be9a0600f6c095fe2bd2d92 OP_EQUALVERIFY OP_CHECKSIG', - reqSigs: 1, - type: 'pubkeyhash' + reqSigs: 1 }, spentIndex: 1, spentTs: 1440997099, @@ -495,23 +469,11 @@ describe('Transactions', function() { }, { vin: [ - { - scriptSig: { - asm: '3045022100f67cffc0ae23adb236ff3edb4a9736e277605db30cc7708dfab8cf1e1483bbce022052396aa5d664ec1cb65992c423fd9a17e94dc7af328d2d559e90746dd195ca5901 0346134da14907581d8190d3980caaf46d95e4eb9c1ca8e70f1fc6007fefb1909d' - } - }, - { - scriptSig: { - asm: '3044022077222a91cda23af69179377c62d84a176fb12caff6c5cbf6ae9e5957ff3b1afe0220768edead76819228dcba18cca3c9a5a5d4c32919720f21df21a297ba375bbe5c01 03371ea5a4dfe356b3ea4042a537d7ab7ee0faabd43e21b6cc076fda2240629eee' - } - } ], vout: [ { scriptPubKey: { - asm: 'OP_DUP OP_HASH160 8e451eec7ca0a1764b4ab119274efdd2727b3c85 OP_EQUALVERIFY OP_CHECKSIG', - reqSigs: 1, - type: 'pubkeyhash' + reqSigs: 1 }, spentIndex: 1, spentTs: 1440992946, @@ -519,9 +481,7 @@ describe('Transactions', function() { }, { scriptPubKey: { - asm: 'OP_DUP OP_HASH160 d0fce8f064cd1059a6a11501dd66fe42368572b0 OP_EQUALVERIFY OP_CHECKSIG', - reqSigs: 1, - type: 'pubkeyhash' + reqSigs: 1 }, spentIndex: 34, spentTs: 1440999118, @@ -771,18 +731,11 @@ describe('Transactions', function() { "txs": [ { "vin": [ - { - "scriptSig": { - "asm": "3045022100f4d169783bef70e3943d2a617cce55d9fe4e33fc6f9880b8277265e2f619a97002201238648abcdf52960500664e969046d41755f7fc371971ebc78002fc418465a601 03acdcd31d51272403ce0829447e59e2ac9e08ed0bf92011cbf7420addf24534e6" - } - } ], "vout": [ { "scriptPubKey": { - "asm": "OP_DUP OP_HASH160 3583efb5e64a4668c6c54bb5fcc30af4417b4f2d OP_EQUALVERIFY OP_CHECKSIG", - "reqSigs": 1, - "type": "pubkeyhash" + "reqSigs": 1 }, "spentTxId": "01f700df84c466f2a389440e5eeacdc47d04f380c39e5d19dce2ce91a11ecba3", "spentIndex": 0, @@ -790,27 +743,18 @@ describe('Transactions', function() { }, { "scriptPubKey": { - "asm": "OP_DUP OP_HASH160 9713201957f42379e574d7c70d506ee49c2c8ad6 OP_EQUALVERIFY OP_CHECKSIG", - "reqSigs": 1, - "type": "pubkeyhash" + "reqSigs": 1 } } ] }, { "vin": [ - { - "scriptSig": { - "asm": "304402201ee69281db6b95bb1aa3074059b67581635b719e8f64e4c2694db6ec56ad9447022011e91528996ea459b1fb2c0b59363fecbefe4bc2ca90f7b2382bdaa358f2d56401 034cc057b12a68ee79df998004b9a1341bbb18b17ea4939bebaa3bac001e940f24" - } - } ], "vout": [ { "scriptPubKey": { - "asm": "OP_DUP OP_HASH160 56e446bc3489543d8324c6d0271524c0bd0506dd OP_EQUALVERIFY OP_CHECKSIG", - "reqSigs": 1, - "type": "pubkeyhash" + "reqSigs": 1 }, "spentTxId": "661194e5533a395ce9076f292b7e0fb28fe94cd8832a81b4aa0517ff58c1ddd2", "spentIndex": 0, @@ -818,9 +762,7 @@ describe('Transactions', function() { }, { "scriptPubKey": { - "asm": "OP_DUP OP_HASH160 011d2963b619186a318f768dddfd98cd553912a0 OP_EQUALVERIFY OP_CHECKSIG", - "reqSigs": 1, - "type": "pubkeyhash" + "reqSigs": 1 }, "spentTxId": "71a9e60c0341c9c258367f1a6d4253276f16e207bf84f41ff7412d8958a81bed", "spentIndex": 0, From fe6a441a523f6da09c1393ae081c29527f41fd4b Mon Sep 17 00:00:00 2001 From: Braydon Fuller Date: Thu, 1 Oct 2015 23:49:28 -0400 Subject: [PATCH 2/2] Added spentTxId and spentIndex --- lib/addresses.js | 31 ++++++++---- lib/transactions.js | 117 +++++++++++++++++++++++++++++++------------ test/addresses.js | 5 +- test/transactions.js | 63 +++++++++++++++++++---- 4 files changed, 161 insertions(+), 55 deletions(-) diff --git a/lib/addresses.js b/lib/addresses.js index 9c33e72..1016623 100644 --- a/lib/addresses.js +++ b/lib/addresses.js @@ -2,6 +2,7 @@ var common = require('./common'); var bitcore = require('bitcore'); +var async = require('async'); var TxController = require('./transactions'); function AddressController(node) { @@ -167,29 +168,39 @@ AddressController.prototype.multitxs = function(req, res, next) { return common.handleErrors(err, res); } - res.jsonp({ - totalItems: result.totalCount, - from: options.from, - to: Math.min(options.to, result.totalCount), - items: self.transformAddressHistoryForMultiTxs(result.items) + self.transformAddressHistoryForMultiTxs(result.items, function(err, items) { + if (err) { + return common.handleErrors(err, res); + } + res.jsonp({ + totalItems: result.totalCount, + from: options.from, + to: Math.min(options.to, result.totalCount), + items: items + }); }); + }); }; -AddressController.prototype.transformAddressHistoryForMultiTxs = function(txinfos) { +AddressController.prototype.transformAddressHistoryForMultiTxs = function(txinfos, callback) { var self = this; var items = txinfos.map(function(txinfo) { return txinfo.tx; }).filter(function(value, index, self) { return self.indexOf(value) === index; - }).map(function(tx) { - return self.txController.transformTransaction(tx); }); - return items; + async.map( + items, + function(item, next) { + self.txController.transformTransaction(item, next); + }, + callback + ); }; -module.exports = AddressController; \ No newline at end of file +module.exports = AddressController; diff --git a/lib/transactions.js b/lib/transactions.js index 06790c2..2c7802d 100644 --- a/lib/transactions.js +++ b/lib/transactions.js @@ -2,6 +2,7 @@ var bitcore = require('bitcore'); var _ = bitcore.deps._; +var $ = bitcore.util.preconditions; var common = require('./common'); var async = require('async'); @@ -35,13 +36,22 @@ TxController.prototype.transaction = function(req, res, next, txid) { }); } - req.transaction = self.transformTransaction(transaction); - next(); + self.transformTransaction(transaction, function(err, transformedTransaction) { + if (err) { + return common.handleErrors(err, res); + } + req.transaction = transformedTransaction; + next(); + }); + }); }); }; -TxController.prototype.transformTransaction = function(transaction) { +TxController.prototype.transformTransaction = function(transaction, callback) { + $.checkArgument(_.isFunction(callback)); + var self = this; + var txid = transaction.id; var txObj = transaction.toObject(); var confirmations = 0; @@ -67,27 +77,44 @@ TxController.prototype.transformTransaction = function(transaction) { transformed.vin = txObj.inputs.map(this.transformInput.bind(this)); } - transformed.vout = txObj.outputs.map(this.transformOutput.bind(this)); + async.map( + Object.keys(txObj.outputs), + function(outputIndex, next) { + outputIndex = parseInt(outputIndex); + var output = txObj.outputs[outputIndex]; + self.transformOutput(txid, output, outputIndex, next); + }, + function(err, vout) { + if (err) { + return callback(err); + } - transformed.blockhash = transaction.__blockHash; - transformed.confirmations = confirmations; - transformed.time = transaction.__timestamp ? transaction.__timestamp : Math.round(Date.now() / 1000); // can we get this from bitcoind? - if (transformed.confirmations) { - transformed.blocktime = transformed.time; - } + transformed.vout = vout; - if(transaction.isCoinbase()) { - transformed.isCoinBase = true; - } + transformed.blockhash = transaction.__blockHash; + transformed.confirmations = confirmations; + var time = transaction.__timestamp ? transaction.__timestamp : Math.round(Date.now() / 1000); + transformed.time = time; + if (transformed.confirmations) { + transformed.blocktime = transformed.time; + } - transformed.valueOut = transaction.outputAmount / 1e8; - transformed.size = transaction.toBuffer().length; - if(transaction.hasAllUtxoInfo()) { - transformed.valueIn = transaction.inputAmount / 1e8; - transformed.fees = transaction.getFee() / 1e8; - } + if(transaction.isCoinbase()) { + transformed.isCoinBase = true; + } + + transformed.valueOut = transaction.outputAmount / 1e8; + transformed.size = transaction.toBuffer().length; + if(transaction.hasAllUtxoInfo()) { + transformed.valueIn = transaction.inputAmount / 1e8; + transformed.fees = transaction.getFee() / 1e8; + } + + callback(null, transformed); + + } + ); - return transformed; }; TxController.prototype.transformInput = function(input, index) { @@ -117,7 +144,8 @@ TxController.prototype.transformInput = function(input, index) { return transformed; }; -TxController.prototype.transformOutput = function(output, index) { +TxController.prototype.transformOutput = function(txid, output, index, callback) { + var self = this; var transformed = { value: (output.satoshis / 1e8).toFixed(8), n: index, @@ -125,8 +153,6 @@ TxController.prototype.transformOutput = function(output, index) { hex: output.script, //reqSigs: null, // TODO } - //spentTxId: undefined, // TODO - //spentIndex: undefined, // TODO //spentTs: undefined // TODO }; @@ -146,7 +172,25 @@ TxController.prototype.transformOutput = function(output, index) { } } - return transformed; + var options = { + queryMempool: true + }; + + self.node.services.address.getInputForOutput( + txid, + index, + options, + function(err, inputResult) { + if (err) { + return callback(err); + } + if (inputResult) { + transformed.spentTxId = inputResult.inputTxId; + transformed.spentIndex = inputResult.inputIndex; + } + callback(null, transformed); + } + ); }; TxController.prototype.transformInvTransaction = function(transaction) { @@ -235,8 +279,7 @@ TxController.prototype.list = function(req, res) { if(err) { return next(err); } - - next(null, self.transformTransaction(tx)); + self.transformTransaction(tx, next); }); }, function(err, transformed) { if(err) { @@ -266,13 +309,21 @@ TxController.prototype.list = function(req, res) { return self.indexOf(value) === index; }); - - var transformed = txs.map(self.transformTransaction.bind(self)); - - res.jsonp({ - pagesTotal: Math.ceil(result.totalCount / pageLength), - txs: transformed - }); + async.map( + txs, + function(tx, next) { + self.transformTransaction(tx, next); + }, + function(err, transformed) { + if (err) { + return common.handleErrors(err, res); + } + res.jsonp({ + pagesTotal: Math.ceil(result.totalCount / pageLength), + txs: transformed + }); + } + ); }); } else { return common.handleErrors(new Error('Block hash or address expected'), res); diff --git a/test/addresses.js b/test/addresses.js index 8bfa00b..86bed66 100644 --- a/test/addresses.js +++ b/test/addresses.js @@ -520,6 +520,9 @@ describe('Addresses', function() { tip: { __height: 534232 } + }, + address: { + getInputForOutput: sinon.stub().callsArgWith(3, null, false), } }, network: 'testnet' @@ -544,4 +547,4 @@ describe('Addresses', function() { addresses.multitxs(req, res); }); }); -}); \ No newline at end of file +}); diff --git a/test/transactions.js b/test/transactions.js index fb1fc11..4fd453e 100644 --- a/test/transactions.js +++ b/test/transactions.js @@ -132,13 +132,13 @@ describe('Transactions', function() { var todos = { vin: [ { - confirmations: 242, isConfirmed: true, + confirmations: 242, unconfirmedInput: false }, { - confirmations: 242, isConfirmed: true, + confirmations: 242, unconfirmedInput: false } ], @@ -152,13 +152,14 @@ describe('Transactions', function() { scriptPubKey: { reqSigs: 1 }, - spentIndex: 1, - spentTs: 1440997099, - spentTxId: '614fe1708825f9c21732394e4784cc6808ac1d8b939736bfdead970567561eec' + spentTs: 1440997099 } ] }; + var spentTxId = '614fe1708825f9c21732394e4784cc6808ac1d8b939736bfdead970567561eec'; + var spentIndex = 1; + var bitcoreTx = bitcore.Transaction(bitcoreTxObj); bitcoreTx.__blockHash = '0000000000000afa0c3c0afd450c793a1e300ec84cbe9555166e06132f19a8f7'; bitcoreTx.__height = 533974; @@ -173,6 +174,21 @@ describe('Transactions', function() { tip: { __height: 534203 } + }, + address: { + getInputForOutput: function(txid, outputIndex, options, callback) { + var data = false; + if (txid === 'b85334bf2df35c6dd5b294efe92ffc793a78edff75a2ca666fc296ffb04bbba0' && + outputIndex === 1) { + data = { + inputTxId: spentTxId, + inputIndex: spentIndex + } + } + setImmediate(function() { + callback(null, data); + }); + } } }, network: 'testnet' @@ -214,6 +230,9 @@ describe('Transactions', function() { tip: { __height: 534209 } + }, + address: { + getInputForOutput: sinon.stub().callsArgWith(3, null, false), } }, network: 'testnet' @@ -593,6 +612,34 @@ describe('Transactions', function() { tip: { __height: 534223 } + }, + address: { + getInputForOutput: function(txid, outputIndex, options, callback) { + var data = false; + if (txid === 'bb0ec3b96209fac9529570ea6f83a86af2cceedde4aaf2bfcc4796680d23f1c7') { + if (outputIndex === 0) { + data = { + inputTxId: '01f700df84c466f2a389440e5eeacdc47d04f380c39e5d19dce2ce91a11ecba3', + inputIndex: 0 + }; + } + } else if (txid === '01f700df84c466f2a389440e5eeacdc47d04f380c39e5d19dce2ce91a11ecba3') { + if (outputIndex === 0) { + data = { + inputTxId: '661194e5533a395ce9076f292b7e0fb28fe94cd8832a81b4aa0517ff58c1ddd2', + inputIndex: 0 + } + } else if (outputIndex === 1) { + data = { + inputTxId: '71a9e60c0341c9c258367f1a6d4253276f16e207bf84f41ff7412d8958a81bed', + inputIndex: 0 + } + } + } + setImmediate(function() { + callback(null, data); + }); + } } }, network: 'testnet' @@ -737,8 +784,6 @@ describe('Transactions', function() { "scriptPubKey": { "reqSigs": 1 }, - "spentTxId": "01f700df84c466f2a389440e5eeacdc47d04f380c39e5d19dce2ce91a11ecba3", - "spentIndex": 0, "spentTs": 1441072817 }, { @@ -756,16 +801,12 @@ describe('Transactions', function() { "scriptPubKey": { "reqSigs": 1 }, - "spentTxId": "661194e5533a395ce9076f292b7e0fb28fe94cd8832a81b4aa0517ff58c1ddd2", - "spentIndex": 0, "spentTs": 1441077236 }, { "scriptPubKey": { "reqSigs": 1 }, - "spentTxId": "71a9e60c0341c9c258367f1a6d4253276f16e207bf84f41ff7412d8958a81bed", - "spentIndex": 0, "spentTs": 1441069523 } ]