diff --git a/lib/services/bitcoind.js b/lib/services/bitcoind.js index 971c6da1..89b9cf58 100644 --- a/lib/services/bitcoind.js +++ b/lib/services/bitcoind.js @@ -113,7 +113,8 @@ Bitcoin.prototype._initCaches = function() { this.txidsCache = LRU(50000); this.balanceCache = LRU(50000); this.summaryCache = LRU(50000); - this.transactionInfoCache = LRU(100000); + this.blockOverviewCache = LRU(144); + this.transactionDetailedCache = LRU(100000); // caches valid indefinitely this.transactionCache = LRU(100000); @@ -150,6 +151,7 @@ Bitcoin.prototype.getAPIMethods = function() { ['getBlock', this, this.getBlock, 1], ['getRawBlock', this, this.getRawBlock, 1], ['getBlockHeader', this, this.getBlockHeader, 1], + ['getBlockOverview', this, this.getBlockOverview, 1], ['getBlockHashesByTimestamp', this, this.getBlockHashesByTimestamp, 2], ['getBestBlockHash', this, this.getBestBlockHash, 0], ['getSpentInfo', this, this.getSpentInfo, 1], @@ -157,8 +159,8 @@ Bitcoin.prototype.getAPIMethods = function() { ['syncPercentage', this, this.syncPercentage, 0], ['isSynced', this, this.isSynced, 0], ['getRawTransaction', this, this.getRawTransaction, 1], - ['getTransaction', this, this.getTransaction, 2], - ['getTransactionWithBlockInfo', this, this.getTransactionWithBlockInfo, 2], + ['getTransaction', this, this.getTransaction, 1], + ['getDetailedTransaction', this, this.getDetailedTransaction, 1], ['sendTransaction', this, this.sendTransaction, 1], ['estimateFee', this, this.estimateFee, 1], ['getAddressTxids', this, this.getAddressTxids, 2], @@ -320,11 +322,12 @@ Bitcoin.prototype._checkConfigIndexes = function(spawnConfig, node) { }; Bitcoin.prototype._resetCaches = function() { - this.transactionInfoCache.reset(); + this.transactionDetailedCache.reset(); this.utxosCache.reset(); this.txidsCache.reset(); this.balanceCache.reset(); this.summaryCache.reset(); + this.blockOverviewCache.reset(); }; Bitcoin.prototype._tryAll = function(func, callback) { @@ -1096,8 +1099,8 @@ Bitcoin.prototype.getAddressTxids = function(addressArg, options, callback) { Bitcoin.prototype._getConfirmationsDetail = function(transaction) { $.checkState(this.height > 0, 'current height is unknown'); var confirmations = 0; - if (transaction.__height >= 0) { - confirmations = this.height - transaction.__height + 1; + if (transaction.height >= 0) { + confirmations = this.height - transaction.height + 1; } if (confirmations < 0) { log.warn('Negative confirmations calculated for transaction:', transaction.hash); @@ -1106,44 +1109,38 @@ Bitcoin.prototype._getConfirmationsDetail = function(transaction) { }; Bitcoin.prototype._getAddressDetailsForInput = function(input, inputIndex, result, addressStrings) { - if (!input.script) { + if (!input.address) { return; } - var inputAddress = input.script.toAddress(this.node.network); - if (inputAddress) { - var inputAddressString = inputAddress.toString(); - if (addressStrings.indexOf(inputAddressString) >= 0) { - if (!result.addresses[inputAddressString]) { - result.addresses[inputAddressString] = { - inputIndexes: [inputIndex], - outputIndexes: [] - }; - } else { - result.addresses[inputAddressString].inputIndexes.push(inputIndex); - } - result.satoshis -= input.output.satoshis; + var address = input.address; + if (addressStrings.indexOf(address) >= 0) { + if (!result.addresses[address]) { + result.addresses[address] = { + inputIndexes: [inputIndex], + outputIndexes: [] + }; + } else { + result.addresses[address].inputIndexes.push(inputIndex); } + result.satoshis -= input.satoshis; } }; Bitcoin.prototype._getAddressDetailsForOutput = function(output, outputIndex, result, addressStrings) { - if (!output.script) { + if (!output.address) { return; } - var outputAddress = output.script.toAddress(this.node.network); - if (outputAddress) { - var outputAddressString = outputAddress.toString(); - if (addressStrings.indexOf(outputAddressString) >= 0) { - if (!result.addresses[outputAddressString]) { - result.addresses[outputAddressString] = { - inputIndexes: [], - outputIndexes: [outputIndex] - }; - } else { - result.addresses[outputAddressString].outputIndexes.push(outputIndex); - } - result.satoshis += output.satoshis; + var address = output.address; + if (addressStrings.indexOf(address) >= 0) { + if (!result.addresses[address]) { + result.addresses[address] = { + inputIndexes: [], + outputIndexes: [outputIndex] + }; + } else { + result.addresses[address].outputIndexes.push(outputIndex); } + result.satoshis += output.satoshis; } }; @@ -1163,6 +1160,8 @@ Bitcoin.prototype._getAddressDetailsForTransaction = function(transaction, addre this._getAddressDetailsForOutput(output, outputIndex, result, addressStrings); } + $.checkState(Number.isFinite(result.satoshis)); + return result; }; @@ -1171,35 +1170,25 @@ Bitcoin.prototype._getAddressDetailsForTransaction = function(transaction, addre * @param {Object} txid - A bitcoin transaction id * @param {Function} callback */ -Bitcoin.prototype._getDetailedTransaction = function(txid, options, next) { +Bitcoin.prototype._getAddressDetailedTransaction = function(txid, options, next) { var self = this; - self.getTransactionWithBlockInfo( + self.getDetailedTransaction( txid, function(err, transaction) { if (err) { return next(err); } - transaction.populateInputs(self, [], function(err) { - if (err) { - return next(err); - } + var addressDetails = self._getAddressDetailsForTransaction(transaction, options.addressStrings); - var addressDetails = self._getAddressDetailsForTransaction(transaction, options.addressStrings); - - var details = { - addresses: addressDetails.addresses, - satoshis: addressDetails.satoshis, - height: transaction.__height, - confirmations: self._getConfirmationsDetail(transaction), - timestamp: transaction.__timestamp, - // TODO bitcore-lib should return null instead of throwing error on coinbase - fees: !transaction.isCoinbase() ? transaction.getFee() : null, - tx: transaction - }; - next(null, details); - }); + var details = { + addresses: addressDetails.addresses, + satoshis: addressDetails.satoshis, + confirmations: self._getConfirmationsDetail(transaction), + tx: transaction + }; + next(null, details); } ); }; @@ -1270,7 +1259,7 @@ Bitcoin.prototype.getAddressHistory = function(addressArg, options, callback) { async.mapSeries( txids, function(txid, next) { - self._getDetailedTransaction(txid, { + self._getAddressDetailedTransaction(txid, { queryMempool: queryMempool, addressStrings: addressStrings }, next); @@ -1437,6 +1426,69 @@ Bitcoin.prototype.getRawBlock = function(blockArg, callback) { } }; +/** + * Similar to getBlockHeader but will include a list of txids + * @param {String|Number} block - A block hash or block height number + * @param {Function} callback + */ +Bitcoin.prototype.getBlockOverview = function(blockArg, callback) { + var self = this; + + function queryBlock(blockhash) { + var cachedBlock = self.blockOverviewCache.get(blockhash); + if (cachedBlock) { + return setImmediate(function() { + callback(null, cachedBlock); + }); + } else { + self._tryAll(function(done) { + self.client.getBlock(blockhash, true, function(err, response) { + if (err) { + return done(self._wrapRPCError(err)); + } + var result = response.result; + var blockOverview = { + hash: result.hash, + version: result.version, + confirmations: result.confirmations, + height: result.height, + chainWork: result.chainwork, + prevHash: result.previousblockhash, + nextHash: result.nextblockhash, + merkleRoot: result.merkleroot, + time: result.time, + medianTime: result.mediantime, + nonce: result.nonce, + bits: result.bits, + difficulty: result.difficulty, + txids: result.tx + }; + self.blockOverviewCache.set(blockhash, blockOverview); + done(null, blockOverview); + }); + }, callback); + } + } + + if (_.isNumber(blockArg)) { + self._tryAll(function(done) { + self.client.getBlockHash(blockArg, function(err, response) { + if (err) { + return done(self._wrapRPCError(err)); + } + done(null, response.result); + }); + }, function(err, blockhash) { + if (err) { + return callback(err); + } + queryBlock(blockhash); + }); + } else { + queryBlock(blockArg); + } +}; + /** * Will retrieve a block as a Bitcore object * @param {String|Number} block - A block hash or block height number @@ -1672,19 +1724,101 @@ Bitcoin.prototype.getTransaction = function(txid, callback) { }; /** - * Will get a transaction as Bitcore Transaction with additional fields: + * Will get a detailed view of a transaction including addresses, amounts and fees. + * + * Example result: * { - * __blockHash: '2725743288feae6bdaa976590af7cb12d7b535b5a242787de6d2789c73682ed1', - * __height: 48, - * __timestamp: 1442951110, // in seconds - * } - * @param {String} txid - The transaction hash + * blockHash: '000000000000000002cd0ba6e8fae058747d2344929ed857a18d3484156c9250', + * height: 411462, + * blockTimestamp: 1463070382, + * version: 1, + * hash: 'de184cc227f6d1dc0316c7484aa68b58186a18f89d853bb2428b02040c394479', + * locktime: 411451, + * coinbase: true, + * inputs: [ + * { + * prevTxId: '3d003413c13eec3fa8ea1fe8bbff6f40718c66facffe2544d7516c9e2900cac2', + * outputIndex: 0, + * sequence: 123456789, + * script: [hexString], + * scriptAsm: [asmString], + * satoshis: 771146 + * } + * ], + * outputs: [ + * { + * satoshis: 811146, + * script: '76a914d2955017f4e3d6510c57b427cf45ae29c372c99088ac', + * scriptAsm: 'OP_DUP OP_HASH160 d2955017f4e3d6510c57b427cf45ae29c372c990 OP_EQUALVERIFY OP_CHECKSIG', + * address: '1LCTmj15p7sSXv3jmrPfA6KGs6iuepBiiG', + * spentTxId: '4316b98e7504073acd19308b4b8c9f4eeb5e811455c54c0ebfe276c0b1eb6315', + * spentIndex: 1, + * spentHeight: 100 + * } + * ], + * inputSatoshis: 771146, + * outputSatoshis: 811146, + * feeSatoshis: 40000 + * }; + * + * @param {String} txid - The hex string of the transaction * @param {Function} callback */ -Bitcoin.prototype.getTransactionWithBlockInfo = function(txid, callback) { - // TODO give response back as standard js object with bitcore tx +Bitcoin.prototype.getDetailedTransaction = function(txid, callback) { var self = this; - var tx = self.transactionInfoCache.get(txid); + var tx = self.transactionDetailedCache.get(txid); + + function addInputsToTx(tx, result) { + tx.inputs = []; + tx.inputSatoshis = 0; + for(var inputIndex = 0; inputIndex < result.vin.length; inputIndex++) { + var input = result.vin[inputIndex]; + if (!tx.coinbase) { + tx.inputSatoshis += input.valueSat; + } + var script; + var scriptAsm; + if (input.scriptSig) { + script = input.scriptSig.hex; + scriptAsm = input.scriptSig.asm; + } else if (input.coinbase) { + script = input.coinbase; + scriptAsm = null; + } + tx.inputs.push({ + prevTxId: input.txid || null, + outputIndex: _.isUndefined(input.vout) ? null : input.vout, + script: script, + scriptAsm: scriptAsm || null, + sequence: input.sequence, + address: input.address || null, + satoshis: _.isUndefined(input.valueSat) ? null : input.valueSat + }); + } + } + + function addOutputsToTx(tx, result) { + tx.outputs = []; + tx.outputSatoshis = 0; + for(var outputIndex = 0; outputIndex < result.vout.length; outputIndex++) { + var out = result.vout[outputIndex]; + tx.outputSatoshis += out.valueSat; + var address = null; + if (out.scriptPubKey && out.scriptPubKey.addresses && out.scriptPubKey.addresses.length > 0) { + address = out.scriptPubKey.addresses[0]; + } + tx.outputs.push({ + satoshis: out.valueSat, + script: out.scriptPubKey.hex, + scriptAsm: out.scriptPubKey.asm, + spentTxId: out.spentTxId, + spentIndex: out.spentIndex, + spentHeight: out.spentHeight, + address: address + }); + } + } + if (tx) { return setImmediate(function() { callback(null, tx); @@ -1695,18 +1829,32 @@ Bitcoin.prototype.getTransactionWithBlockInfo = function(txid, callback) { if (err) { return done(self._wrapRPCError(err)); } - var tx = Transaction(); - tx.fromString(response.result.hex); - tx.__blockHash = response.result.blockhash; - tx.__height = response.result.height ? response.result.height : -1; - tx.__timestamp = response.result.time; + var result = response.result; + var tx = { + hex: result.hex, + blockHash: result.blockhash, + height: result.height ? result.height : -1, + blockTimestamp: result.time, + version: result.version, + hash: txid, + locktime: result.locktime, + }; - for (var i = 0; i < response.result.vout.length; i++) { - tx.outputs[i].__spentTxId = response.result.vout[i].spentTxId; - tx.outputs[i].__spentIndex = response.result.vout[i].spentIndex; - tx.outputs[i].__spentHeight = response.result.vout[i].spentHeight; + if (result.vin[0] && result.vin[0].coinbase) { + tx.coinbase = true; } - self.transactionInfoCache.set(txid, tx); + + addInputsToTx(tx, result); + addOutputsToTx(tx, result); + + if (!tx.coinbase) { + tx.feeSatoshis = tx.inputSatoshis - tx.outputSatoshis; + } else { + tx.feeSatoshis = 0; + } + + self.transactionDetailedCache.set(txid, tx); + done(null, tx); }); }, callback); diff --git a/regtest/bitcoind.js b/regtest/bitcoind.js index aa413660..13346416 100644 --- a/regtest/bitcoind.js +++ b/regtest/bitcoind.js @@ -424,16 +424,38 @@ describe('Bitcoind Functionality', function() { }); }); - describe('get transaction with block info', function() { - it('should include tx with height and timestamp', function(done) { - bitcoind.getTransactionWithBlockInfo(utxos[0].txid, function(err, tx) { + describe('get detailed transaction', function() { + it('should include details for coinbase tx', function(done) { + bitcoind.getDetailedTransaction(utxos[0].txid, function(err, tx) { if (err) { return done(err); } - should.exist(tx.__height); - tx.__height.should.be.a('number'); - should.exist(tx.__timestamp); - should.exist(tx.__blockHash); + should.exist(tx.height); + tx.height.should.be.a('number'); + should.exist(tx.blockTimestamp); + should.exist(tx.blockHash); + tx.coinbase.should.equal(true); + tx.version.should.equal(1); + tx.hex.should.be.a('string'); + tx.locktime.should.equal(0); + tx.feeSatoshis.should.equal(0); + tx.outputSatoshis.should.equal(50 * 1e8); + tx.inputSatoshis.should.equal(0); + tx.inputs.length.should.equal(1); + tx.outputs.length.should.equal(1); + should.equal(tx.inputs[0].prevTxId, null); + should.equal(tx.inputs[0].outputIndex, null); + tx.inputs[0].script.should.be.a('string'); + should.equal(tx.inputs[0].scriptAsm, null); + should.equal(tx.inputs[0].address, null); + should.equal(tx.inputs[0].satoshis, null); + tx.outputs[0].satoshis.should.equal(50 * 1e8); + tx.outputs[0].script.should.be.a('string'); + tx.outputs[0].scriptAsm.should.be.a('string'); + tx.outputs[0].spentTxId.should.be.a('string'); + tx.outputs[0].spentIndex.should.equal(0); + tx.outputs[0].spentHeight.should.be.a('number'); + tx.outputs[0].address.should.be.a('string'); done(); }); }); diff --git a/regtest/node.js b/regtest/node.js index 40de6de2..9593e4fd 100644 --- a/regtest/node.js +++ b/regtest/node.js @@ -205,9 +205,8 @@ describe('Node Functionality', function() { info.addresses[address].inputIndexes.should.deep.equal([]); info.satoshis.should.equal(10 * 1e8); info.confirmations.should.equal(3); - info.timestamp.should.be.a('number'); - info.fees.should.be.within(950, 4000); - info.tx.should.be.an.instanceof(Transaction); + info.tx.blockTimestamp.should.be.a('number'); + info.tx.feeSatoshis.should.be.within(950, 4000); done(); }); }); @@ -395,13 +394,13 @@ describe('Node Functionality', function() { results.totalCount.should.equal(4); var history = results.items; history.length.should.equal(4); - history[0].height.should.equal(159); + history[0].tx.height.should.equal(159); history[0].confirmations.should.equal(1); - history[1].height.should.equal(158); + history[1].tx.height.should.equal(158); should.exist(history[1].addresses[address4]); - history[2].height.should.equal(157); + history[2].tx.height.should.equal(157); should.exist(history[2].addresses[address3]); - history[3].height.should.equal(156); + history[3].tx.height.should.equal(156); should.exist(history[3].addresses[address2]); history[3].satoshis.should.equal(tx2Amount); history[3].tx.hash.should.equal(tx2Hash); @@ -429,9 +428,9 @@ describe('Node Functionality', function() { results.totalCount.should.equal(2); var history = results.items; history.length.should.equal(2); - history[0].height.should.equal(158); + history[0].tx.height.should.equal(158); history[0].confirmations.should.equal(2); - history[1].height.should.equal(157); + history[1].tx.height.should.equal(157); should.exist(history[1].addresses[address3]); done(); }); @@ -456,8 +455,8 @@ describe('Node Functionality', function() { results.totalCount.should.equal(2); var history = results.items; history.length.should.equal(2); - history[0].height.should.equal(157); - history[1].height.should.equal(156); + history[0].tx.height.should.equal(157); + history[1].tx.height.should.equal(156); done(); }); }); @@ -481,9 +480,9 @@ describe('Node Functionality', function() { results.totalCount.should.equal(4); var history = results.items; history.length.should.equal(3); - history[0].height.should.equal(159); + history[0].tx.height.should.equal(159); history[0].confirmations.should.equal(1); - history[1].height.should.equal(158); + history[1].tx.height.should.equal(158); should.exist(history[1].addresses[address4]); done(); }); @@ -501,18 +500,18 @@ describe('Node Functionality', function() { results.totalCount.should.equal(6); var history = results.items; history.length.should.equal(6); - history[0].height.should.equal(159); + history[0].tx.height.should.equal(159); history[0].addresses[address].inputIndexes.should.deep.equal([0, 1]); history[0].addresses[address].outputIndexes.should.deep.equal([2]); history[0].confirmations.should.equal(1); - history[1].height.should.equal(158); - history[2].height.should.equal(157); - history[3].height.should.equal(156); - history[4].height.should.equal(155); + history[1].tx.height.should.equal(158); + history[2].tx.height.should.equal(157); + history[3].tx.height.should.equal(156); + history[4].tx.height.should.equal(155); history[4].satoshis.should.equal(-10000); history[4].addresses[address].outputIndexes.should.deep.equal([0, 1, 2, 3, 4]); history[4].addresses[address].inputIndexes.should.deep.equal([0]); - history[5].height.should.equal(152); + history[5].tx.height.should.equal(152); history[5].satoshis.should.equal(10 * 1e8); done(); }); @@ -561,7 +560,7 @@ describe('Node Functionality', function() { } var history = results.items; history.length.should.equal(1); - history[0].height.should.equal(159); + history[0].tx.height.should.equal(159); done(); }); }); @@ -576,7 +575,7 @@ describe('Node Functionality', function() { } var history = results.items; history.length.should.equal(1); - history[0].height.should.equal(158); + history[0].tx.height.should.equal(158); done(); }); }); @@ -591,7 +590,7 @@ describe('Node Functionality', function() { } var history = results.items; history.length.should.equal(1); - history[0].height.should.equal(157); + history[0].tx.height.should.equal(157); done(); }); }); @@ -606,7 +605,7 @@ describe('Node Functionality', function() { } var history = results.items; history.length.should.equal(1); - history[0].height.should.equal(156); + history[0].tx.height.should.equal(156); done(); }); }); @@ -621,7 +620,7 @@ describe('Node Functionality', function() { } var history = results.items; history.length.should.equal(1); - history[0].height.should.equal(155); + history[0].tx.height.should.equal(155); history[0].satoshis.should.equal(-10000); history[0].addresses[address].outputIndexes.should.deep.equal([0, 1, 2, 3, 4]); history[0].addresses[address].inputIndexes.should.deep.equal([0]); @@ -639,7 +638,7 @@ describe('Node Functionality', function() { } var history = results.items; history.length.should.equal(1); - history[0].height.should.equal(152); + history[0].tx.height.should.equal(152); history[0].satoshis.should.equal(10 * 1e8); done(); }); @@ -744,13 +743,13 @@ describe('Node Functionality', function() { it('will not show confirmation count for orphaned transaction', function(done) { // This test verifies that in the situation that the transaction is not in the mempool and // is included in an orphaned block transaction index that the confirmation count will be unconfirmed. - node.getTransactionWithBlockInfo(orphanedTransaction, function(err, data) { + node.getDetailedTransaction(orphanedTransaction, function(err, data) { if (err) { return done(err); } should.exist(data); - should.exist(data.__height); - data.__height.should.equal(-1); + should.exist(data.height); + data.height.should.equal(-1); done(); }); }); diff --git a/scripts/download b/scripts/download index 0e175eca..081888c9 100755 --- a/scripts/download +++ b/scripts/download @@ -6,8 +6,8 @@ root_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/.." platform=`uname -a | awk '{print tolower($1)}'` arch=`uname -m` version="0.12.0" -url="https://github.com/bitpay/bitcoin/releases/download" -tag="v0.12-bitcore-rc1" +url="https://github.com/braydonf/bitcoin/releases/download" +tag="v0.12-bitcore-rc2-spent" if [ "${platform}" == "linux" ]; then if [ "${arch}" == "x86_64" ]; then diff --git a/test/services/bitcoind.unit.js b/test/services/bitcoind.unit.js index ce149c1a..28d70494 100644 --- a/test/services/bitcoind.unit.js +++ b/test/services/bitcoind.unit.js @@ -5,6 +5,7 @@ var EventEmitter = require('events').EventEmitter; var should = require('chai').should(); var crypto = require('crypto'); var bitcore = require('bitcore-lib'); +var _ = bitcore.deps._; var sinon = require('sinon'); var proxyquire = require('proxyquire'); var fs = require('fs'); @@ -51,7 +52,7 @@ describe('Bitcoin Service', function() { should.exist(bitcoind.txidsCache); should.exist(bitcoind.balanceCache); should.exist(bitcoind.summaryCache); - should.exist(bitcoind.transactionInfoCache); + should.exist(bitcoind.transactionDetailedCache); should.exist(bitcoind.transactionCache); should.exist(bitcoind.rawTransactionCache); @@ -90,7 +91,7 @@ describe('Bitcoin Service', function() { var bitcoind = new BitcoinService(baseConfig); var methods = bitcoind.getAPIMethods(); should.exist(methods); - methods.length.should.equal(20); + methods.length.should.equal(21); }); }); @@ -295,14 +296,14 @@ describe('Bitcoin Service', function() { var keys = []; for (var i = 0; i < 10; i++) { keys.push(crypto.randomBytes(32)); - bitcoind.transactionInfoCache.set(keys[i], {}); + bitcoind.transactionDetailedCache.set(keys[i], {}); bitcoind.utxosCache.set(keys[i], {}); bitcoind.txidsCache.set(keys[i], {}); bitcoind.balanceCache.set(keys[i], {}); bitcoind.summaryCache.set(keys[i], {}); } bitcoind._resetCaches(); - should.equal(bitcoind.transactionInfoCache.get(keys[0]), undefined); + should.equal(bitcoind.transactionDetailedCache.get(keys[0]), undefined); should.equal(bitcoind.utxosCache.get(keys[0]), undefined); should.equal(bitcoind.txidsCache.get(keys[0]), undefined); should.equal(bitcoind.balanceCache.get(keys[0]), undefined); @@ -1919,7 +1920,7 @@ describe('Bitcoin Service', function() { }); it('should get 1 confirmation', function() { var tx = new Transaction(txhex); - tx.__height = 10; + tx.height = 10; var bitcoind = new BitcoinService(baseConfig); bitcoind.height = 10; var confirmations = bitcoind._getConfirmationsDetail(tx); @@ -1929,7 +1930,7 @@ describe('Bitcoin Service', function() { var bitcoind = new BitcoinService(baseConfig); var tx = new Transaction(txhex); bitcoind.height = 11; - tx.__height = 10; + tx.height = 10; var confirmations = bitcoind._getConfirmationsDetail(tx); confirmations.should.equal(2); }); @@ -1937,7 +1938,7 @@ describe('Bitcoin Service', function() { var bitcoind = new BitcoinService(baseConfig); var tx = new Transaction(txhex); bitcoind.height = 3; - tx.__height = 10; + tx.height = 10; var confirmations = bitcoind._getConfirmationsDetail(tx); log.warn.callCount.should.equal(1); confirmations.should.equal(0); @@ -1946,7 +1947,7 @@ describe('Bitcoin Service', function() { var bitcoind = new BitcoinService(baseConfig); var tx = new Transaction(txhex); bitcoind.height = 1000; - tx.__height = 1; + tx.height = 1; var confirmations = bitcoind._getConfirmationsDetail(tx); confirmations.should.equal(1000); }); @@ -1955,46 +1956,37 @@ describe('Bitcoin Service', function() { describe('#_getAddressDetailsForTransaction', function() { it('will calculate details for the transaction', function(done) { /* jshint sub:true */ - var tx = bitcore.Transaction({ - 'hash': 'b12b3ae8489c5a566b629a3c62ce4c51c3870af550fb5dc77d715b669a91343c', - 'version': 1, - 'inputs': [ + var tx = { + inputs: [ { - 'prevTxId': 'a2b7ea824a92f4a4944686e67ec1001bc8785348b8c111c226f782084077b543', - 'outputIndex': 0, - 'sequenceNumber': 4294967295, - 'script': '47304402201b81c933297241960a57ae1b2952863b965ac8c9ec7466ff0b715712d27548d50220576e115b63864f003889443525f47c7cf0bc1e2b5108398da085b221f267ba2301210229766f1afa25ca499a51f8e01c292b0255a21a41bb6685564a1607a811ffe924', - 'scriptString': '71 0x304402201b81c933297241960a57ae1b2952863b965ac8c9ec7466ff0b715712d27548d50220576e115b63864f003889443525f47c7cf0bc1e2b5108398da085b221f267ba2301 33 0x0229766f1afa25ca499a51f8e01c292b0255a21a41bb6685564a1607a811ffe924', - 'output': { - 'satoshis': 1000000000, - 'script': '76a9140b2f0a0c31bfe0406b0ccc1381fdbe311946dadc88ac' - } + satoshis: 1000000000, + address: 'mgY65WSfEmsyYaYPQaXhmXMeBhwp4EcsQW' } ], - 'outputs': [ + outputs: [ { - 'satoshis': 100000000, - 'script': '76a9140b2f0a0c31bfe0406b0ccc1381fdbe311946dadc88ac' + satoshis: 100000000, + address: 'mgY65WSfEmsyYaYPQaXhmXMeBhwp4EcsQW' }, { - 'satoshis': 200000000, - 'script': '76a9140b2f0a0c31bfe0406b0ccc1381fdbe311946dadc88ac' + satoshis: 200000000, + address: 'mgY65WSfEmsyYaYPQaXhmXMeBhwp4EcsQW' }, { - 'satoshis': 50000000, - 'script': '76a9140b2f0a0c31bfe0406b0ccc1381fdbe311946dadc88ac' + satoshis: 50000000, + address: 'mgY65WSfEmsyYaYPQaXhmXMeBhwp4EcsQW' }, { - 'satoshis': 300000000, - 'script': '76a9140b2f0a0c31bfe0406b0ccc1381fdbe311946dadc88ac' + satoshis: 300000000, + address: 'mgY65WSfEmsyYaYPQaXhmXMeBhwp4EcsQW' }, { - 'satoshis': 349990000, - 'script': '76a9140b2f0a0c31bfe0406b0ccc1381fdbe311946dadc88ac' + satoshis: 349990000, + address: 'mgY65WSfEmsyYaYPQaXhmXMeBhwp4EcsQW' } ], - 'nLockTime': 0 - }); + locktime: 0 + }; var bitcoind = new BitcoinService(baseConfig); var addresses = ['mgY65WSfEmsyYaYPQaXhmXMeBhwp4EcsQW']; var details = bitcoind._getAddressDetailsForTransaction(tx, addresses); @@ -2008,105 +2000,40 @@ describe('Bitcoin Service', function() { }); }); - describe('#_getDetailedTransaction', function() { + describe('#_getAddressDetailedTransaction', function() { it('will get detailed transaction info', function(done) { var txid = '46f24e0c274fc07708b781963576c4c5d5625d926dbb0a17fa865dcd9fe58ea0'; var tx = { - populateInputs: sinon.stub().callsArg(2), - __height: 20, - __timestamp: 1453134151, - isCoinbase: sinon.stub().returns(false), - getFee: sinon.stub().returns(1000) + height: 20, }; var bitcoind = new BitcoinService(baseConfig); - bitcoind.getTransactionWithBlockInfo = sinon.stub().callsArgWith(1, null, tx); + bitcoind.getDetailedTransaction = sinon.stub().callsArgWith(1, null, tx); bitcoind.height = 300; + var addresses = {}; bitcoind._getAddressDetailsForTransaction = sinon.stub().returns({ - addresses: {}, + addresses: addresses, satoshis: 1000, }); - bitcoind._getDetailedTransaction(txid, {}, function(err) { + bitcoind._getAddressDetailedTransaction(txid, {}, function(err, details) { if (err) { return done(err); } + details.addresses.should.equal(addresses); + details.satoshis.should.equal(1000); + details.confirmations.should.equal(281); + details.tx.should.equal(tx); done(); }); }); - it('give error from getTransactionWithBlockInfo', function(done) { + it('give error from getDetailedTransaction', function(done) { var txid = '46f24e0c274fc07708b781963576c4c5d5625d926dbb0a17fa865dcd9fe58ea0'; var bitcoind = new BitcoinService(baseConfig); - bitcoind.getTransactionWithBlockInfo = sinon.stub().callsArgWith(1, new Error('test')); - bitcoind._getDetailedTransaction(txid, {}, function(err) { + bitcoind.getDetailedTransaction = sinon.stub().callsArgWith(1, new Error('test')); + bitcoind._getAddressDetailedTransaction(txid, {}, function(err) { err.should.be.instanceof(Error); done(); }); }); - it('give error from populateInputs', function(done) { - var txid = '46f24e0c274fc07708b781963576c4c5d5625d926dbb0a17fa865dcd9fe58ea0'; - var tx = { - populateInputs: sinon.stub().callsArgWith(2, new Error('test')), - }; - var bitcoind = new BitcoinService(baseConfig); - bitcoind.getTransactionWithBlockInfo = sinon.stub().callsArgWith(1, null, tx); - bitcoind._getDetailedTransaction(txid, {}, function(err) { - err.should.be.instanceof(Error); - done(); - }); - }); - - it('will correct detailed info', function(done) { - // block #314159 - // txid 30169e8bf78bc27c4014a7aba3862c60e2e3cce19e52f1909c8255e4b7b3174e - // outputIndex 1 - var txAddress = '1Cj4UZWnGWAJH1CweTMgPLQMn26WRMfXmo'; - var txString = '0100000001a08ee59fcd5d86fa170abb6d925d62d5c5c476359681b70877c04f270c4ef246000000008a47304402203fb9b476bb0c37c9b9ed5784ebd67ae589492be11d4ae1612be29887e3e4ce750220741ef83781d1b3a5df8c66fa1957ad0398c733005310d7d9b1d8c2310ef4f74c0141046516ad02713e51ecf23ac9378f1069f9ae98e7de2f2edbf46b7836096e5dce95a05455cc87eaa1db64f39b0c63c0a23a3b8df1453dbd1c8317f967c65223cdf8ffffffff02b0a75fac000000001976a91484b45b9bf3add8f7a0f3daad305fdaf6b73441ea88ac20badc02000000001976a914809dc14496f99b6deb722cf46d89d22f4beb8efd88ac00000000'; - var previousTxString = '010000000155532fad2869bb951b0bd646a546887f6ee668c4c0ee13bf3f1c4bce6d6e3ed9000000008c4930460221008540795f4ef79b1d2549c400c61155ca5abbf3089c84ad280e1ba6db2a31abce022100d7d162175483d51174d40bba722e721542c924202a0c2970b07e680b51f3a0670141046516ad02713e51ecf23ac9378f1069f9ae98e7de2f2edbf46b7836096e5dce95a05455cc87eaa1db64f39b0c63c0a23a3b8df1453dbd1c8317f967c65223cdf8ffffffff02f0af3caf000000001976a91484b45b9bf3add8f7a0f3daad305fdaf6b73441ea88ac80969800000000001976a91421277e65777760d1f3c7c982ba14ed8f934f005888ac00000000'; - var transaction = new Transaction(); - var previousTransaction = new Transaction(); - previousTransaction.fromString(previousTxString); - var previousTransactionTxid = '46f24e0c274fc07708b781963576c4c5d5625d926dbb0a17fa865dcd9fe58ea0'; - transaction.fromString(txString); - var txid = transaction.hash; - transaction.__blockHash = '00000000000000001bb82a7f5973618cfd3185ba1ded04dd852a653f92a27c45'; - transaction.__height = 314159; - transaction.__timestamp = 1407292005; - var bitcoind = new BitcoinService(baseConfig); - bitcoind.height = 314159; - bitcoind.getTransactionWithBlockInfo = sinon.stub().callsArgWith(1, null, transaction); - bitcoind.getTransaction = function(prevTxid, callback) { - prevTxid.should.equal(previousTransactionTxid); - setImmediate(function() { - callback(null, previousTransaction); - }); - }; - var transactionInfo = { - addresses: {}, - txid: txid, - timestamp: 1407292005, - satoshis: 48020000, - address: txAddress - }; - transactionInfo.addresses[txAddress] = {}; - transactionInfo.addresses[txAddress].outputIndexes = [1]; - transactionInfo.addresses[txAddress].inputIndexes = []; - bitcoind._getAddressDetailsForTransaction = sinon.stub().returns(transactionInfo); - bitcoind._getDetailedTransaction(txid, {}, function(err, info) { - if (err) { - return done(err); - } - info.addresses[txAddress].should.deep.equal({ - outputIndexes: [1], - inputIndexes: [] - }); - info.satoshis.should.equal(48020000); - info.height.should.equal(314159); - info.confirmations.should.equal(1); - info.timestamp.should.equal(1407292005); - info.fees.should.equal(20000); - info.tx.should.equal(transaction); - done(); - }); - }); }); describe('#_getAddressStrings', function() { @@ -2221,7 +2148,7 @@ describe('Bitcoin Service', function() { }); it('will paginate', function(done) { var bitcoind = new BitcoinService(baseConfig); - bitcoind._getDetailedTransaction = function(txid, options, callback) { + bitcoind._getAddressDetailedTransaction = function(txid, options, callback) { callback(null, txid); }; var txids = ['one', 'two', 'three', 'four']; @@ -3075,7 +3002,7 @@ describe('Bitcoin Service', function() { }); }); - describe('#getTransactionWithBlockInfo', function() { + describe('#getDetailedTransaction', function() { var txBuffer = new Buffer('01000000016f95980911e01c2c664b3e78299527a47933aac61a515930a8fe0213d1ac9abe01000000da0047304402200e71cda1f71e087c018759ba3427eb968a9ea0b1decd24147f91544629b17b4f0220555ee111ed0fc0f751ffebf097bdf40da0154466eb044e72b6b3dcd5f06807fa01483045022100c86d6c8b417bff6cc3bbf4854c16bba0aaca957e8f73e19f37216e2b06bb7bf802205a37be2f57a83a1b5a8cc511dc61466c11e9ba053c363302e7b99674be6a49fc0147522102632178d046673c9729d828cfee388e121f497707f810c131e0d3fc0fe0bd66d62103a0951ec7d3a9da9de171617026442fcd30f34d66100fab539853b43f508787d452aeffffffff0240420f000000000017a9148a31d53a448c18996e81ce67811e5fb7da21e4468738c9d6f90000000017a9148ce5408cfeaddb7ccb2545ded41ef478109454848700000000', 'hex'); var info = { blockHash: '00000000000ec715852ea2ecae4dc8563f62d603c820f81ac284cd5be0a944d6', @@ -3083,7 +3010,40 @@ describe('Bitcoin Service', function() { timestamp: 1439559434000, buffer: txBuffer }; - + var rpcRawTransaction = { + hex: txBuffer.toString('hex'), + blockhash: info.blockHash, + height: info.height, + version: 1, + locktime: 411451, + time: info.timestamp, + vin: [ + { + valueSat: 110, + address: 'mgY65WSfEmsyYaYPQaXhmXMeBhwp4EcsQW', + txid: '3d003413c13eec3fa8ea1fe8bbff6f40718c66facffe2544d7516c9e2900cac2', + sequence: 0xFFFFFFFF, + vout: 0, + scriptSig: { + hex: 'scriptSigHex', + asm: 'scriptSigAsm' + } + } + ], + vout: [ + { + spentTxId: '4316b98e7504073acd19308b4b8c9f4eeb5e811455c54c0ebfe276c0b1eb6315', + spentIndex: 2, + spentHeight: 100, + valueSat: 100, + scriptPubKey: { + hex: '76a9140b2f0a0c31bfe0406b0ccc1381fdbe311946dadc88ac', + asm: 'OP_DUP OP_HASH160 0b2f0a0c31bfe0406b0ccc1381fdbe311946dadc OP_EQUALVERIFY OP_CHECKSIG', + addresses: ['mgY65WSfEmsyYaYPQaXhmXMeBhwp4EcsQW'] + } + } + ] + }; it('should give a transaction with height and timestamp', function(done) { var bitcoind = new BitcoinService(baseConfig); bitcoind.nodes.push({ @@ -3092,39 +3052,74 @@ describe('Bitcoin Service', function() { } }); var txid = '2d950d00494caf6bfc5fff2a3f839f0eb50f663ae85ce092bc5f9d45296ae91f'; - bitcoind.getTransactionWithBlockInfo(txid, function(err) { + bitcoind.getDetailedTransaction(txid, function(err) { should.exist(err); err.should.be.instanceof(errors.RPCError); done(); }); }); - it('should give a transaction with height and timestamp', function(done) { + it('should give a transaction with all properties', function(done) { var bitcoind = new BitcoinService(baseConfig); bitcoind.nodes.push({ client: { getRawTransaction: sinon.stub().callsArgWith(2, null, { - result: { - hex: txBuffer.toString('hex'), - blockhash: info.blockHash, - height: info.height, - time: info.timestamp, - vout: [ - { - spentTxId: 'txid', - spentIndex: 2, - spentHeight: 100 - } - ] - } + result: rpcRawTransaction }) } }); var txid = '2d950d00494caf6bfc5fff2a3f839f0eb50f663ae85ce092bc5f9d45296ae91f'; - bitcoind.getTransactionWithBlockInfo(txid, function(err, tx) { - should.equal(tx.__blockHash, '00000000000ec715852ea2ecae4dc8563f62d603c820f81ac284cd5be0a944d6'); - should.equal(tx.__height, 530482); - should.equal(tx.__timestamp, 1439559434000); + bitcoind.getDetailedTransaction(txid, function(err, tx) { should.exist(tx); + should.not.exist(tx.coinbase); + should.equal(tx.hex, txBuffer.toString('hex')); + should.equal(tx.blockHash, '00000000000ec715852ea2ecae4dc8563f62d603c820f81ac284cd5be0a944d6'); + should.equal(tx.height, 530482); + should.equal(tx.blockTimestamp, 1439559434000); + should.equal(tx.version, 1); + should.equal(tx.locktime, 411451); + should.equal(tx.feeSatoshis, 10); + should.equal(tx.inputSatoshis, 110); + should.equal(tx.outputSatoshis, 100); + should.equal(tx.hash, txid); + var input = tx.inputs[0]; + should.equal(input.prevTxId, '3d003413c13eec3fa8ea1fe8bbff6f40718c66facffe2544d7516c9e2900cac2'); + should.equal(input.outputIndex, 0); + should.equal(input.satoshis, 110); + should.equal(input.sequence, 0xFFFFFFFF); + should.equal(input.script, 'scriptSigHex'); + should.equal(input.scriptAsm, 'scriptSigAsm'); + should.equal(input.address, 'mgY65WSfEmsyYaYPQaXhmXMeBhwp4EcsQW'); + var output = tx.outputs[0]; + should.equal(output.satoshis, 100); + should.equal(output.script, '76a9140b2f0a0c31bfe0406b0ccc1381fdbe311946dadc88ac'); + should.equal(output.scriptAsm, 'OP_DUP OP_HASH160 0b2f0a0c31bfe0406b0ccc1381fdbe311946dadc OP_EQUALVERIFY OP_CHECKSIG'); + should.equal(output.address, 'mgY65WSfEmsyYaYPQaXhmXMeBhwp4EcsQW'); + should.equal(output.spentTxId, '4316b98e7504073acd19308b4b8c9f4eeb5e811455c54c0ebfe276c0b1eb6315'); + should.equal(output.spentIndex, 2); + should.equal(output.spentHeight, 100); + done(); + }); + }); + it('should set coinbase to true', function(done) { + var bitcoind = new BitcoinService(baseConfig); + var rawTransaction = _.clone(rpcRawTransaction); + delete rawTransaction.vin[0]; + rawTransaction.vin = [ + { + coinbase: 'abcdef' + } + ]; + bitcoind.nodes.push({ + client: { + getRawTransaction: sinon.stub().callsArgWith(2, null, { + result: rawTransaction + }) + } + }); + var txid = '2d950d00494caf6bfc5fff2a3f839f0eb50f663ae85ce092bc5f9d45296ae91f'; + bitcoind.getDetailedTransaction(txid, function(err, tx) { + should.exist(tx); + should.equal(tx.coinbase, true); done(); }); });