diff --git a/lib/services/block.js b/lib/services/block.js index a4840e2e..48a38946 100644 --- a/lib/services/block.js +++ b/lib/services/block.js @@ -125,7 +125,13 @@ var blockNotFound = function(err) { * @return {Promise} */ BlockService.prototype.getBlock = function(blockHash) { - $.checkArgument(JSUtil.isHexa(blockHash), 'Block hash must be hexa'); + $.checkArgument( + JSUtil.isHexa(blockHash) || bitcore.util.buffer.isBuffer(blockHash), + 'Block hash must be a buffer or hexa' + ); + if (bitcore.util.buffer.isBuffer(blockHash)) { + blockHash = bitcore.util.buffer.reverse(blockHash).toString('hex'); + } var blockData; var self = this; @@ -238,6 +244,9 @@ BlockService.prototype._confirmBlock = function(block) { }; BlockService.prototype._setNextBlock = function(ops, prevBlockHash, block) { + if (bitcore.util.buffer.isBuffer(prevBlockHash)) { + prevBlockHash = bitcore.util.buffer.reverse(prevBlockHash).toString('hex'); + } return Promise.try(function() { ops.push({ type: 'put', diff --git a/lib/services/transaction.js b/lib/services/transaction.js index 4e2965eb..c2fc3e49 100644 --- a/lib/services/transaction.js +++ b/lib/services/transaction.js @@ -37,12 +37,14 @@ var helper = function(name) { }; }; var helperAddress = function(index) { - return function(address) { + return function(address, txid, number) { if (_.isString(address)) { address = new bitcore.Address(address); } $.checkArgument(address instanceof bitcore.Address, 'address must be a string or bitcore.Address'); - return index + address.toString(); + $.checkArgument(bitcore.util.js.isHexa(txid), 'TXID must be an hexa string'); + $.checkArgument(_.isNumber(number), 'Input number must be a number'); + return index + address.toString() + '-' + txid + '-' + number; }; }; @@ -115,7 +117,7 @@ TransactionService.prototype._confirmOutput = function(ops, block, transaction) if (address) { ops.push({ type: 'put', - key: Index.getOutputsForAddress(address), + key: Index.getOutputsForAddress(address, transaction.id, index), value: output.toObject() }); } @@ -123,29 +125,28 @@ TransactionService.prototype._confirmOutput = function(ops, block, transaction) }; TransactionService.prototype._confirmInput = function(ops, block, transaction) { + var self = this; return function(input, index) { - if (input.prevTxId.toString('hex') !== NULLTXHASH) { - ops.push({ - type: 'put', - key: Index.getOutput(transaction.id, index), - value: _.extend(input.toObject(), { - heightConfirmed: block.height - }) - }); - var script = input.script; - if (script.isPublicKeyHashIn() || script.isScriptHashIn()) { - // TODO: Move this logic to bitcore - var address = script.isPublicKeyHashIn() - ? new PublicKey(script.chunks[0].buf).toAddress() - : new Script(script.chunks[script.chunks.length - 1]).toAddress(); + if (input.prevTxId.toString('hex') === NULLTXHASH) { + return Promise.resolve(); + } + ops.push({ + type: 'put', + key: Index.getOutput(transaction.id, index), + value: _.extend(input.toObject(), { + heightConfirmed: block.height + }) + }); + var script = input.script; + if (!(script.isPublicKeyHashIn() || script.isPublicKeyIn() || script.isScriptHashIn())) { + return Promise.resolve(); + } + + return self._getAddressForInput(input).then(function(address) { + if (address) { ops.push({ type: 'put', - key: Index.getOutputsForAddress(address), - value: input.toObject() - }); - ops.push({ - type: 'put', - key: Index.getSpentOutputsForAddress(address), + key: Index.getSpentOutputsForAddress(address, transaction.id, index), value: { heightSpent: block.height, spentTx: transaction.id, @@ -154,10 +155,36 @@ TransactionService.prototype._confirmInput = function(ops, block, transaction) { } }); } - } + }); }; }; +TransactionService.prototype._getAddressForInput = function(input) { + var script = input.script; + var self = this; + + if (script.isPublicKeyHashIn()) { + var hash = bitcore.crypto.Hash.sha256ripemd160(script.chunks[0].buf); + return Promise.resolve(new bitcore.Address( + hash, bitcore.Networks.defaultNetwork, bitcore.Address.PayToPublicKeyHash + )); + } else if (script.isPublicKeyIn()) { + return self.getTransaction(input.prevTxId.toString('hex')).then(function(transaction) { + var outputScript = transaction.outputs[input.outputIndex].script; + if (outputScript.isPublicKeyOut()) { + return Promise.resolve(new bitcore.Address( + bitcore.crypto.Hash.sha256ripemd160(outputScript.chunks[0].buf), + bitcore.Networks.defaultNetwork, bitcore.Address.PayToPublicKeyHash + )); + } else { + return Promise.resolve(undefined); + } + }); + } else { + return Promise.resolve(new bitcore.Script(script.chunks[script.chunks.length - 1]).toAddress()); + } +}; + TransactionService.prototype._confirmTransaction = function(ops, block, transaction) { var self = this; return Promise.try(function() { @@ -166,8 +193,11 @@ TransactionService.prototype._confirmTransaction = function(ops, block, transact key: Index.getBlockForTransaction(transaction), value: block.id }); - _.each(transaction.outputs, self._confirmOutput(ops, block, transaction)); - _.each(transaction.inputs, self._confirmInput(ops, block, transaction)); + return Promise.all( + _.each(transaction.outputs, self._confirmOutput(ops, block, transaction)) + .concat( + _.each(transaction.inputs, self._confirmInput(ops, block, transaction)) + )); }); }; diff --git a/package.json b/package.json index ecce833d..77ff62e5 100644 --- a/package.json +++ b/package.json @@ -71,7 +71,6 @@ "bitcore-build": "bitpay/bitcore-build", "chai": "^2.1.1", "gulp": "^3.8.10", - "mocha": "^2.2.1", "should": "^5.1.0", "sinon": "^1.13.0", "supertest": "^0.15.0" diff --git a/test/data/169.js b/test/data/169.js new file mode 100644 index 00000000..f9c9ac29 --- /dev/null +++ b/test/data/169.js @@ -0,0 +1,17 @@ +'use strict'; + +var bitcore = require('bitcore'); + +var block169 = new bitcore.Block( + new Buffer( + '01000000696aa63f0f22d9189c8536bb83b18737ae8336c25a67937f79957e5600000000982db9870a' + +'5e30d8f0b2a4ebccc5852b5a1e2413e9274c4947bfec6bdaa9b9d75bb76a49ffff001d2b719fdd0101' + +'000000010000000000000000000000000000000000000000000000000000000000000000ffffffff07' + +'04ffff001d0101ffffffff0100f2052a010000004341045da87c7b825c75ca17ade8bb5cdbcd27af4c' + +'e97373aa9848c0c84693ca857cf379e14c2ce61ea2aaee9450d0939e21bd26894aa6dcc808656fa997' + +'4dc296589eac00000000' + , 'hex') +); +block169.height = 169; + +module.exports = block169; diff --git a/test/data/170.js b/test/data/170.js new file mode 100644 index 00000000..c2da17bf --- /dev/null +++ b/test/data/170.js @@ -0,0 +1,23 @@ +'use strict'; + +var bitcore = require('bitcore'); + +var block170 = new bitcore.Block( + new Buffer( + '0100000055bd840a78798ad0da853f68974f3d183e2bd1db6a842c1feecf222a00000000ff104ccb054' + +'21ab93e63f8c3ce5c2c2e9dbb37de2764b3a3175c8166562cac7d51b96a49ffff001d283e9e7002010' + +'00000010000000000000000000000000000000000000000000000000000000000000000ffffffff070' + +'4ffff001d0102ffffffff0100f2052a01000000434104d46c4968bde02899d2aa0963367c7a6ce34ee' + +'c332b32e42e5f3407e052d64ac625da6f0718e7b302140434bd725706957c092db53805b821a85b23a' + +'7ac61725bac000000000100000001c997a5e56e104102fa209c6a852dd90660a20b2d9c352423edce2' + +'5857fcd3704000000004847304402204e45e16932b8af514961a1d3a1a25fdf3f4f7732e9d624c6c61' + +'548ab5fb8cd410220181522ec8eca07de4860a4acdd12909d831cc56cbbac4622082221a8768d1d090' + +'1ffffffff0200ca9a3b00000000434104ae1a62fe09c5f51b13905f07f06b99a2f7159b2225f374cd3' + +'78d71302fa28414e7aab37397f554a7df5f142c21c1b7303b8a0626f1baded5c72a704f7e6cd84cac0' + +'0286bee0000000043410411db93e1dcdb8a016b49840f8c53bc1eb68a382e97b1482ecad7b148a6909' + +'a5cb2e0eaddfb84ccf9744464f82e160bfa9b8b64f9d4c03f999b8643f656b412a3ac00000000' + , 'hex') +); +block170.height = 170; + +module.exports = block170; diff --git a/test/data/firstTxSpent.js b/test/data/firstTxSpent.js new file mode 100644 index 00000000..765e51fd --- /dev/null +++ b/test/data/firstTxSpent.js @@ -0,0 +1,5 @@ +'use strict'; + +var bitcore = require('bitcore'); + +module.exports = new bitcore.Transaction('01000000010000000000000000000000000000000000000000000000000000000000000000ffffffff0704ffff001d0134ffffffff0100f2052a0100000043410411db93e1dcdb8a016b49840f8c53bc1eb68a382e97b1482ecad7b148a6909a5cb2e0eaddfb84ccf9744464f82e160bfa9b8b64f9d4c03f999b8643f656b412a3ac00000000'); diff --git a/test/data/genesis.js b/test/data/genesis.js new file mode 100644 index 00000000..c967a501 --- /dev/null +++ b/test/data/genesis.js @@ -0,0 +1,15 @@ +'use strict'; + +var bitcore = require('bitcore'); + +module.exports = new bitcore.Block( + new Buffer( + '0100000000000000000000000000000000000000000000000000000000000000000000003ba3edfd7a' + +'7b12b27ac72c3e67768f617fc81bc3888a51323a9fb8aa4b1e5e4a29ab5f49ffff001d1dac2b7c010' + +'1000000010000000000000000000000000000000000000000000000000000000000000000ffffffff' + +'4d04ffff001d0104455468652054696d65732030332f4a616e2f32303039204368616e63656c6c6f7' + +'2206f6e206272696e6b206f66207365636f6e64206261696c6f757420666f722062616e6b73ffffff' + +'ff0100f2052a01000000434104678afdb0fe5548271967f1a67130b7105cd6a828e03909a67962e0e' + +'a1f61deb649f6bc3f4cef38c4f35504e51ec112de5c384df7ba0b8d578a4c702b6bf11d5fac00000000' + , 'hex') +); diff --git a/test/services/block.js b/test/services/block.js index 63de875b..9834a528 100644 --- a/test/services/block.js +++ b/test/services/block.js @@ -87,17 +87,9 @@ describe('BlockService', function() { return arg(); } }; - var genesisBlock = new bitcore.Block( - new Buffer( - '0100000000000000000000000000000000000000000000000000000000000000000000003ba3edfd7a' - +'7b12b27ac72c3e67768f617fc81bc3888a51323a9fb8aa4b1e5e4a29ab5f49ffff001d1dac2b7c010' - +'1000000010000000000000000000000000000000000000000000000000000000000000000ffffffff' - +'4d04ffff001d0104455468652054696d65732030332f4a616e2f32303039204368616e63656c6c6f7' - +'2206f6e206272696e6b206f66207365636f6e64206261696c6f757420666f722062616e6b73ffffff' - +'ff0100f2052a01000000434104678afdb0fe5548271967f1a67130b7105cd6a828e03909a67962e0e' - +'a1f61deb649f6bc3f4cef38c4f35504e51ec112de5c384df7ba0b8d578a4c702b6bf11d5fac00000000' - , 'hex') - ); + var genesisBlock = require('../data/genesis'); + var block169 = require('../data/169'); + var block170 = require('../data/170'); beforeEach(function() { database = sinon.mock(); @@ -110,7 +102,6 @@ describe('BlockService', function() { database: database }); blockService.writeLock = sinon.mock(); - blockService.getBlock = sinon.mock(); }); it('makes the expected calls when confirming the genesis block', function(callback) { @@ -133,11 +124,42 @@ describe('BlockService', function() { }; blockService.unlock = callback; blockService.writeLock.onFirstCall().returns(thenCaller); + blockService.getBlock = sinon.mock(); database.getAsync = function() { return Promise.reject({notFound: true}); }; transactionMock._confirmTransaction = sinon.mock(); blockService._confirmBlock(genesisBlock); }); + + it('makes the expected calls when confirming the block #170', function(callback) { + database.batchAsync = function(ops) { + ops.should.deep.equal([ + { type: 'put', + key: 'nxt-000000002a22cfee1f2c846adbd12b3e183d4f97683f85dad08a79780a84bd55', + value: '00000000d1145790a8694403d4063f323d499e655c83426834d4ce2f8dd4a2ee' }, + { type: 'put', + key: 'prev-00000000d1145790a8694403d4063f323d499e655c83426834d4ce2f8dd4a2ee', + value: '000000002a22cfee1f2c846adbd12b3e183d4f97683f85dad08a79780a84bd55' }, + { type: 'put', + key: 'bh-00000000d1145790a8694403d4063f323d499e655c83426834d4ce2f8dd4a2ee', + value: 170 }, + { type: 'put', + key: 'bts-1231731025', + value: '00000000d1145790a8694403d4063f323d499e655c83426834d4ce2f8dd4a2ee' } + ]); + return thenCaller; + }; + blockService.unlock = callback; + blockService.writeLock.onFirstCall().returns(thenCaller); + blockService.getBlock = function() { + return Promise.resolve(block169); + }; + database.getAsync = function() { + return Promise.reject({notFound: true}); + }; + transactionMock._confirmTransaction = sinon.spy(); + blockService._confirmBlock(block170); + }); }); }); diff --git a/test/services/transaction.js b/test/services/transaction.js index 659fa67b..bdd0b4ae 100644 --- a/test/services/transaction.js +++ b/test/services/transaction.js @@ -5,6 +5,7 @@ var should = require('chai').should(); var Promise = require('bluebird'); var bitcore = require('bitcore'); +var _ = bitcore.deps._; var TransactionService = require('../../lib/services/transaction'); @@ -63,17 +64,7 @@ describe('TransactionService', function() { }); }); - var genesisBlock = new bitcore.Block( - new Buffer( - '0100000000000000000000000000000000000000000000000000000000000000000000003ba3edfd7a' - +'7b12b27ac72c3e67768f617fc81bc3888a51323a9fb8aa4b1e5e4a29ab5f49ffff001d1dac2b7c010' - +'1000000010000000000000000000000000000000000000000000000000000000000000000ffffffff' - +'4d04ffff001d0104455468652054696d65732030332f4a616e2f32303039204368616e63656c6c6f7' - +'2206f6e206272696e6b206f66207365636f6e64206261696c6f757420666f722062616e6b73ffffff' - +'ff0100f2052a01000000434104678afdb0fe5548271967f1a67130b7105cd6a828e03909a67962e0e' - +'a1f61deb649f6bc3f4cef38c4f35504e51ec112de5c384df7ba0b8d578a4c702b6bf11d5fac00000000' - , 'hex') - ); + var genesisBlock = require('../data/genesis'); genesisBlock.height = 0; var genesisTx = genesisBlock.transactions[0]; @@ -90,7 +81,7 @@ describe('TransactionService', function() { { satoshis: 5000000000, script: '65 0x04678afdb0fe5548271967f1a67130b7105cd6a828e03909a67962e0ea1f61deb649f6bc3f4cef38c4f35504e51ec112de5c384df7ba0b8d578a4c702b6bf11d5f OP_CHECKSIG' } }, { type: 'put', - key: 'txa-1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa', + key: 'txa-1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa-4a5e1e4baab89f3a32518a88c31bc87f618f76673e2cc77ab2127b7afdeda33b-0', value: { satoshis: 5000000000, script: '65 0x04678afdb0fe5548271967f1a67130b7105cd6a828e03909a67962e0ea1f61deb649f6bc3f4cef38c4f35504e51ec112de5c384df7ba0b8d578a4c702b6bf11d5f OP_CHECKSIG' } } @@ -98,5 +89,65 @@ describe('TransactionService', function() { callback(); }); }); + + var block170 = require('../data/170'); + + it('confirms correctly the first non-coinbase transaction (block 170)', function(callback) { + var ops = []; + service.getTransaction = sinon.stub(); + var firstTxSpent = require('../data/firstTxSpent'); + service.getTransaction.onFirstCall().returns({ + then: function(arg) { + return arg(firstTxSpent); + } + }); + service._confirmTransaction(ops, block170, block170.transactions[1]).then(function() { + ops.should.deep.equal([ + { type: 'put', + key: 'btx-f4184fc596403b9d638783cf57adfe4c75c605f6356fbc91338530e9831e9e16', + value: '00000000d1145790a8694403d4063f323d499e655c83426834d4ce2f8dd4a2ee' }, + { type: 'put', + key: 'txo-f4184fc596403b9d638783cf57adfe4c75c605f6356fbc91338530e9831e9e16-0', + value: + { satoshis: 1000000000, + script: '65 0x04ae1a62fe09c5f51b13905f07f06b99a2f7159b2225f374cd378d71302fa28414e7aab37397f554a7df5f142c21c1b7303b8a0626f1baded5c72a704f7e6cd84c OP_CHECKSIG' } }, + { type: 'put', + key: 'txa-1Q2TWHE3GMdB6BZKafqwxXtWAWgFt5Jvm3-f4184fc596403b9d638783cf57adfe4c75c605f6356fbc91338530e9831e9e16-0', + value: + { satoshis: 1000000000, + script: '65 0x04ae1a62fe09c5f51b13905f07f06b99a2f7159b2225f374cd378d71302fa28414e7aab37397f554a7df5f142c21c1b7303b8a0626f1baded5c72a704f7e6cd84c OP_CHECKSIG' } }, + { type: 'put', + key: 'txo-f4184fc596403b9d638783cf57adfe4c75c605f6356fbc91338530e9831e9e16-1', + value: + { satoshis: 4000000000, + script: '65 0x0411db93e1dcdb8a016b49840f8c53bc1eb68a382e97b1482ecad7b148a6909a5cb2e0eaddfb84ccf9744464f82e160bfa9b8b64f9d4c03f999b8643f656b412a3 OP_CHECKSIG' } }, + { type: 'put', + key: 'txa-12cbQLTFMXRnSzktFkuoG3eHoMeFtpTu3S-f4184fc596403b9d638783cf57adfe4c75c605f6356fbc91338530e9831e9e16-1', + value: + { satoshis: 4000000000, + script: '65 0x0411db93e1dcdb8a016b49840f8c53bc1eb68a382e97b1482ecad7b148a6909a5cb2e0eaddfb84ccf9744464f82e160bfa9b8b64f9d4c03f999b8643f656b412a3 OP_CHECKSIG' } }, + { type: 'put', + key: 'txo-f4184fc596403b9d638783cf57adfe4c75c605f6356fbc91338530e9831e9e16-0', + value: + { prevTxId: '0437cd7f8525ceed2324359c2d0ba26006d92d856a9c20fa0241106ee5a597c9', + outputIndex: 0, + sequenceNumber: 4294967295, + script: '71 0x304402204e45e16932b8af514961a1d3a1a25fdf3f4f7732e9d624c6c61548ab5fb8cd410220181522ec8eca07de4860a4acdd12909d831cc56cbbac4622082221a8768d1d0901', + output: undefined, + heightConfirmed: 170 } }, + { type: 'put', + key: 'txas-12cbQLTFMXRnSzktFkuoG3eHoMeFtpTu3S-f4184fc596403b9d638783cf57adfe4c75c605f6356fbc91338530e9831e9e16-0', + value: + { heightSpent: 170, + spentTx: 'f4184fc596403b9d638783cf57adfe4c75c605f6356fbc91338530e9831e9e16', + spentTxInputIndex: 0, + spendInput: { prevTxId: '0437cd7f8525ceed2324359c2d0ba26006d92d856a9c20fa0241106ee5a597c9', + outputIndex: 0, + sequenceNumber: 4294967295, + script: '71 0x304402204e45e16932b8af514961a1d3a1a25fdf3f4f7732e9d624c6c61548ab5fb8cd410220181522ec8eca07de4860a4acdd12909d831cc56cbbac4622082221a8768d1d0901', + output: undefined }}}]); + callback(); + }); + }); }); });