From ad120213e18d3d2d40a27085479aac7c7d820666 Mon Sep 17 00:00:00 2001 From: Braydon Fuller Date: Wed, 16 Sep 2015 13:35:54 -0400 Subject: [PATCH 1/5] Start of mempool address index. --- docs/services/bitcoind.md | 2 +- integration/regtest-node.js | 34 +++++++++++++ integration/regtest.js | 37 +++++++++----- lib/services/address/index.js | 75 ++++++++++++++++++++++++++-- lib/services/bitcoind.js | 9 ++-- lib/services/db.js | 29 +++++++++-- src/libbitcoind.cc | 92 +++++++++-------------------------- src/libbitcoind.h | 2 +- 8 files changed, 185 insertions(+), 95 deletions(-) diff --git a/docs/services/bitcoind.md b/docs/services/bitcoind.md index 8251aea8..50f11064 100644 --- a/docs/services/bitcoind.md +++ b/docs/services/bitcoind.md @@ -13,7 +13,7 @@ The bitcoin service adds a native interface to Bitcoin Core for querying informa - `bitcoind.sendTransaction(transaction, allowAbsurdFees)` - Will attempt to add a transaction to the mempool and broadcast to peers. - `bitcoind.getTransaction(txid, queryMempool, callback)` - Get any tx asynchronously by reading it from disk, with an argument to optionally not include the mempool. - `bitcoind.getTransactionWithBlockInfo(txid, queryMempool, callback)` - Similar to getTransaction but will also include the block timestamp and height. -- `bitcoind.getMempoolOutputs(address)` - Will return an array of outputs that match an address from the mempool. +- `bitcoind.getMempoolTransactions()` - Will return an array of transaction buffers. - `bitcoind.getInfo()` - Basic information about the chain including total number of blocks. - `bitcoind.isSynced()` - Returns a boolean if the daemon is fully synced (not the initial block download) - `bitcoind.syncPercentage()` - Returns the current estimate of blockchain download as a percentage. diff --git a/integration/regtest-node.js b/integration/regtest-node.js index 4fcbafa4..5d070b1d 100644 --- a/integration/regtest-node.js +++ b/integration/regtest-node.js @@ -661,5 +661,39 @@ describe('Node Functionality', function() { }); }); + + describe('Mempool Index', function() { + var unspentOutput; + before(function(done) { + node.services.address.getUnspentOutputs(address, false, function(err, results) { + if (err) { + throw err; + } + results.length.should.equal(1); + unspentOutput = results[0]; + done(); + }); + }); + + it('will update the mempool index after new tx', function(done) { + + var tx = new Transaction(); + tx.from(unspentOutput); + tx.to(address, unspentOutput.satoshis - 1000); + tx.fee(1000); + tx.sign(testKey); + + node.services.bitcoind.sendTransaction(tx.serialize()); + + setImmediate(function() { + var length = node.services.address.mempoolIndex[address].length; + length.should.equal(1); + should.exist(node.services.address.mempoolIndex[address]); + done(); + }); + + }); + + }); }); }); diff --git a/integration/regtest.js b/integration/regtest.js index 3f84b772..76582ea8 100644 --- a/integration/regtest.js +++ b/integration/regtest.js @@ -300,7 +300,7 @@ describe('Daemon Binding Functionality', function() { var serialized = tx.serialize(); - bitcoind.on('tx', function(result) { + bitcoind.once('tx', function(result) { result.buffer.toString('hex').should.equal(serialized); result.hash.should.equal(tx.hash); result.mempool.should.equal(true); @@ -357,6 +357,18 @@ describe('Daemon Binding Functionality', function() { tx.change(changeAddress); tx.sign(privateKey1); + var tx2; + var tx2Key; + + before(function() { + tx2 = bitcore.Transaction(); + tx2.from(utxos[3]); + tx2.change(privateKey.toAddress()); + tx2.to(destKey.toAddress(), utxos[3].amount * 1e8 - 1000); + tx2Key = bitcore.PrivateKey.fromWIF(utxos[3].privateKeyWIF); + tx2.sign(tx2Key); + }); + it('will add an unchecked transaction', function() { var added = bitcoind.addMempoolUncheckedTransaction(tx.serialize()); added.should.equal(true); @@ -370,18 +382,17 @@ describe('Daemon Binding Functionality', function() { }); - it('get outputs by address', function() { - var outputs = bitcoind.getMempoolOutputs(changeAddress); - var expected = [ - { - address: 'mgBCJAsvzgT2qNNeXsoECg2uPKrUsZ76up', - script: '76a914073b7eae2823efa349e3b9155b8a735526463a0f88ac', - satoshis: 40000, - txid: tx.hash, - outputIndex: 1 - } - ]; - outputs.should.deep.equal(expected); + it('get one transaction', function() { + var transactions = bitcoind.getMempoolTransactions(); + transactions[0].toString('hex').should.equal(tx.serialize()); + }); + + it('get multiple transactions', function() { + bitcoind.sendTransaction(tx2.serialize()); + var transactions = bitcoind.getMempoolTransactions(); + var expected = [tx.serialize(), tx2.serialize()]; + expected.should.contain(transactions[0].toString('hex')); + expected.should.contain(transactions[1].toString('hex')); }); }); diff --git a/lib/services/address/index.js b/lib/services/address/index.js index 60a935ac..8dc8ed81 100644 --- a/lib/services/address/index.js +++ b/lib/services/address/index.js @@ -6,6 +6,7 @@ var async = require('async'); var index = require('../../'); var log = index.log; var errors = index.errors; +var Transaction = require('../../transaction'); var bitcore = require('bitcore'); var $ = bitcore.util.preconditions; var _ = bitcore.deps._; @@ -25,6 +26,8 @@ var AddressService = function(options) { this.node.services.bitcoind.on('tx', this.transactionHandler.bind(this)); + this.mempoolIndex = {}; + }; inherits(AddressService, BaseService); @@ -128,11 +131,67 @@ AddressService.prototype.transactionHandler = function(txInfo) { this.transactionOutputHandler(messages, tx, i, !txInfo.mempool); } + // Update mempool index + if (txInfo.mempool) { + this.updateMempoolIndex(tx); + } + for (var key in messages) { this.transactionEventHandler(messages[key]); } }; +AddressService.prototype.updateMempoolIndex = function(tx) { + var outputLength = tx.outputs.length; + for (var outputIndex = 0; outputIndex < outputLength; outputIndex++) { + var output = tx.outputs[outputIndex]; + if (!output.script) { + continue; + } + var addressInfo = this._extractAddressInfoFromScript(output.script); + if (!addressInfo) { + continue; + } + + var addressStr = bitcore.Address({ + hashBuffer: addressInfo.hashBuffer, + type: addressInfo.addressType, + network: this.node.network + }).toString(); + + if (!this.mempoolIndex[addressStr]) { + this.mempoolIndex[addressStr] = []; + } + + this.mempoolIndex[addressStr].push({ + txid: tx.hash, // TODO use buffer + outputIndex: outputIndex, + satoshis: output.satoshis, + script: output._scriptBuffer.toString('hex') //TODO use a buffer + }); + + } + +}; + +AddressService.prototype.resetMempoolIndex = function(callback) { + log.info('Starting reset of mempool index for address service.'); + var self = this; + var transactionBuffers = self.node.services.bitcoind.getMempoolTransactions(); + this.mempoolIndex = {}; + async.each(transactionBuffers, function(txBuffer, next) { + var tx = Transaction.fromBuffer(txBuffer); + this.updateMempoolIndex(tx); + setImmediate(next); + }, function(err) { + if (err) { + return callback(err); + } + log.info('Mempool index update with address size:', Object.keys(self.mempoolIndex).length); + callback(); + }); +}; + AddressService.prototype._extractAddressInfoFromScript = function(script) { var hashBuffer; var addressType; @@ -635,11 +694,11 @@ AddressService.prototype.getOutputs = function(addressStr, options, callback) { var output = { address: addressStr, - txid: key.txid.toString('hex'), + txid: key.txid.toString('hex'), //TODO use a buffer outputIndex: key.outputIndex, height: key.height, satoshis: value.satoshis, - script: value.scriptBuffer.toString('hex'), + script: value.scriptBuffer.toString('hex'), //TODO use a buffer confirmations: self.node.services.db.tip.__height - key.height + 1 }; @@ -661,7 +720,17 @@ AddressService.prototype.getOutputs = function(addressStr, options, callback) { } if(options.queryMempool) { - outputs = outputs.concat(self.node.services.bitcoind.getMempoolOutputs(addressStr)); + var mempoolOutputs = self.mempoolIndex[addressStr]; + if (mempoolOutputs) { + for(var i = 0; i < mempoolOutputs.length; i++) { + // TODO copy + var newOutput = mempoolOutputs[i]; + newOutput.address = addressStr; + newOutput.height = -1; + newOutput.confirmations = 0; + outputs.push(newOutput); + } + } } callback(null, outputs); }); diff --git a/lib/services/bitcoind.js b/lib/services/bitcoind.js index 2eca3cb4..f0ddecd0 100644 --- a/lib/services/bitcoind.js +++ b/lib/services/bitcoind.js @@ -1,12 +1,13 @@ 'use strict'; +var fs = require('fs'); var util = require('util'); var bindings = require('bindings')('bitcoind.node'); var mkdirp = require('mkdirp'); -var fs = require('fs'); +var async = require('async'); var bitcore = require('bitcore'); +var Transaction = require('../transaction'); var $ = bitcore.util.preconditions; -var _ = bitcore.deps._; var index = require('../'); var log = index.log; var Service = require('../service'); @@ -215,8 +216,8 @@ Bitcoin.prototype.getTransactionWithBlockInfo = function(txid, queryMempool, cal return bindings.getTransactionWithBlockInfo(txid, queryMempool, callback); }; -Bitcoin.prototype.getMempoolOutputs = function(address) { - return bindings.getMempoolOutputs(address); +Bitcoin.prototype.getMempoolTransactions = function() { + return bindings.getMempoolTransactions(); }; Bitcoin.prototype.addMempoolUncheckedTransaction = function(txBuffer) { diff --git a/lib/services/db.js b/lib/services/db.js index faf42c8b..99f80443 100644 --- a/lib/services/db.js +++ b/lib/services/db.js @@ -408,6 +408,20 @@ DB.prototype.disconnectBlock = function(block, callback) { this.runAllBlockHandlers(block, false, callback); }; +DB.prototype.runAllMempoolIndexes = function(callback) { + async.eachSeries( + this.node.services, + function(service, next) { + if (service.resetMempoolIndex) { + service.resetMempoolIndex(next); + } else { + setImmediate(next); + } + }, + callback + ); +}; + /** * Will collect all database operations for a block from other services * and save to the database. @@ -670,15 +684,22 @@ DB.prototype.sync = function() { return self.node.emit('error', err); } - self.bitcoindSyncing = false; - if(self.node.stopping) { return; } - // If bitcoind is completely synced if (self.node.services.bitcoind.isSynced()) { - self.node.emit('synced'); + self.runAllMempoolIndexes(function(err) { + if (err) { + Error.captureStackTrace(err); + return self.node.emit('error', err); + } + + self.bitcoindSyncing = false; + self.node.emit('synced'); + }); + } else { + self.bitcoindSyncing = false; } }); diff --git a/src/libbitcoind.cc b/src/libbitcoind.cc index 5f94d9e7..6759b9f3 100644 --- a/src/libbitcoind.cc +++ b/src/libbitcoind.cc @@ -1531,84 +1531,38 @@ NAN_METHOD(SendTransaction) { } /** - * GetMempoolOutputs - * bitcoindjs.getMempoolOutputs() - * Will return outputs by address from the mempool. + * GetMempoolTransactions + * bitcoind.getMempoolTransactions() + * Will return an array of transaction buffers. */ -NAN_METHOD(GetMempoolOutputs) { - Isolate* isolate = Isolate::GetCurrent(); +NAN_METHOD(GetMempoolTransactions) { + Isolate* isolate = args.GetIsolate(); HandleScope scope(isolate); - // Instatiate an empty array that we will fill later - // with matching outputs. - Local outputs = Array::New(isolate); + Local transactions = Array::New(isolate); int arrayIndex = 0; - // Decode the input address into the hash bytes - // that we can then match to the scriptPubKeys data - v8::String::Utf8Value param1(args[0]->ToString()); - std::string *input = new std::string(*param1); - const char* psz = input->c_str(); - std::vector vAddress; - DecodeBase58(psz, vAddress); - vector hashBytes(vAddress.begin()+1, vAddress.begin()+21); + { + LOCK(mempool.cs); - // Iterate through the entire mempool - std::map mapTx = mempool.mapTx; + // Iterate through the entire mempool + std::map mapTx = mempool.mapTx; - for(std::map::iterator it = mapTx.begin(); it != mapTx.end(); it++) { - - uint256 txid = it->first; - CTxMemPoolEntry entry = it->second; - const CTransaction tx = entry.GetTx(); - - int outputIndex = 0; - - // Iterate through each output - BOOST_FOREACH(const CTxOut& txout, tx.vout) { - - CScript script = txout.scriptPubKey; - - txnouttype type; - std::vector > hashResults; - - if (Solver(script, type, hashResults)) { - - // See if the script is any of the standard address types - if (type == TX_PUBKEYHASH || type == TX_SCRIPTHASH) { - - vector scripthashBytes = hashResults.front(); - - // Compare the hash bytes with the input hash bytes - if(equal(hashBytes.begin(), hashBytes.end(), scripthashBytes.begin())) { - - Local output = NanNew(); - - output->Set(NanNew("address"), NanNew(psz)); - - std::string scriptHex = HexStr(script.begin(), script.end()); - output->Set(NanNew("script"), NanNew(scriptHex)); - - uint64_t satoshis = txout.nValue; - output->Set(NanNew("satoshis"), NanNew(satoshis)); // can't go above 2 ^ 53 -1 - output->Set(NanNew("txid"), NanNew(txid.GetHex())); - - output->Set(NanNew("outputIndex"), NanNew(outputIndex)); - - // We have a match and push the results to the array - // that is returned as the result - outputs->Set(arrayIndex, output); - arrayIndex++; - - } - } - } - - outputIndex++; + for(std::map::iterator it = mapTx.begin(); + it != mapTx.end(); + it++) { + CTxMemPoolEntry entry = it->second; + const CTransaction tx = entry.GetTx(); + CDataStream dataStreamTx(SER_NETWORK, PROTOCOL_VERSION); + dataStreamTx << tx; + std::string txString = dataStreamTx.str(); + Local txBuffer = node::Buffer::New(isolate, txString.c_str(), txString.size()); + transactions->Set(arrayIndex, txBuffer); + arrayIndex++; } } - NanReturnValue(outputs); + NanReturnValue(transactions); } @@ -1668,7 +1622,7 @@ init(Handle target) { NODE_SET_METHOD(target, "isSpent", IsSpent); NODE_SET_METHOD(target, "getBlockIndex", GetBlockIndex); NODE_SET_METHOD(target, "isMainChain", IsMainChain); - NODE_SET_METHOD(target, "getMempoolOutputs", GetMempoolOutputs); + NODE_SET_METHOD(target, "getMempoolTransactions", GetMempoolTransactions); NODE_SET_METHOD(target, "addMempoolUncheckedTransaction", AddMempoolUncheckedTransaction); NODE_SET_METHOD(target, "sendTransaction", SendTransaction); NODE_SET_METHOD(target, "estimateFee", EstimateFee); diff --git a/src/libbitcoind.h b/src/libbitcoind.h index 2d05341f..45010446 100644 --- a/src/libbitcoind.h +++ b/src/libbitcoind.h @@ -29,7 +29,7 @@ NAN_METHOD(GetTransaction); NAN_METHOD(GetInfo); NAN_METHOD(IsSpent); NAN_METHOD(GetBlockIndex); -NAN_METHOD(GetMempoolOutputs); +NAN_METHOD(GetMempoolTransactions); NAN_METHOD(AddMempoolUncheckedTransaction); NAN_METHOD(SendTransaction); NAN_METHOD(EstimateFee); From 62faecb9ae89d6dfcc76accb5dfd64b2f970293c Mon Sep 17 00:00:00 2001 From: Braydon Fuller Date: Wed, 16 Sep 2015 19:58:19 -0400 Subject: [PATCH 2/5] Address Mempool Index: Fix bugs with reset mempool. --- lib/services/address/index.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/services/address/index.js b/lib/services/address/index.js index 8dc8ed81..eda7b139 100644 --- a/lib/services/address/index.js +++ b/lib/services/address/index.js @@ -180,8 +180,8 @@ AddressService.prototype.resetMempoolIndex = function(callback) { var transactionBuffers = self.node.services.bitcoind.getMempoolTransactions(); this.mempoolIndex = {}; async.each(transactionBuffers, function(txBuffer, next) { - var tx = Transaction.fromBuffer(txBuffer); - this.updateMempoolIndex(tx); + var tx = Transaction().fromBuffer(txBuffer); + self.updateMempoolIndex(tx); setImmediate(next); }, function(err) { if (err) { From c3654120ee839975f0aab1342079373b9a398151 Mon Sep 17 00:00:00 2001 From: Chris Kleeschulte Date: Thu, 17 Sep 2015 14:26:38 -0400 Subject: [PATCH 3/5] MempoolInputIndex and MempoolOutputIndex - Added in memory indexes for mempool input and output by address. --- integration/regtest-node.js | 4 +- lib/services/address/index.js | 47 +++++++++++++++++---- test/data/transaction.json | 3 ++ test/services/address/index.unit.js | 65 +++++++++++++++++++++++------ test/services/bitcoind.unit.js | 2 +- test/services/db.unit.js | 2 + 6 files changed, 99 insertions(+), 24 deletions(-) create mode 100644 test/data/transaction.json diff --git a/integration/regtest-node.js b/integration/regtest-node.js index 5d070b1d..8cd91990 100644 --- a/integration/regtest-node.js +++ b/integration/regtest-node.js @@ -686,9 +686,9 @@ describe('Node Functionality', function() { node.services.bitcoind.sendTransaction(tx.serialize()); setImmediate(function() { - var length = node.services.address.mempoolIndex[address].length; + var length = node.services.address.mempoolOutputIndex[address].length; length.should.equal(1); - should.exist(node.services.address.mempoolIndex[address]); + should.exist(node.services.address.mempoolOutputIndex[address]); done(); }); diff --git a/lib/services/address/index.js b/lib/services/address/index.js index eda7b139..35c9d425 100644 --- a/lib/services/address/index.js +++ b/lib/services/address/index.js @@ -26,7 +26,8 @@ var AddressService = function(options) { this.node.services.bitcoind.on('tx', this.transactionHandler.bind(this)); - this.mempoolIndex = {}; + this.mempoolOutputIndex = {}; + this.mempoolInputIndex = {}; }; @@ -159,11 +160,11 @@ AddressService.prototype.updateMempoolIndex = function(tx) { network: this.node.network }).toString(); - if (!this.mempoolIndex[addressStr]) { - this.mempoolIndex[addressStr] = []; + if (!this.mempoolOutputIndex[addressStr]) { + this.mempoolOutputIndex[addressStr] = []; } - this.mempoolIndex[addressStr].push({ + this.mempoolOutputIndex[addressStr].push({ txid: tx.hash, // TODO use buffer outputIndex: outputIndex, satoshis: output.satoshis, @@ -171,14 +172,31 @@ AddressService.prototype.updateMempoolIndex = function(tx) { }); } + var inputLength = tx.inputs.length; + for (var inputIndex = 0; inputIndex < inputLength; inputIndex++) { + + var input = tx.inputs[inputIndex]; + var address = input.script.toAddress(this.node.network); + if (!address) { + continue; + } + var addressStr = address.toString(); + if (!this.mempoolInputIndex[addressStr]) { + this.mempoolInputIndex[addressStr] = []; + } + this.mempoolInputIndex[addressStr].push({ + txid: tx.hash, // TODO use buffer + inputIndex: inputIndex + }); + } }; AddressService.prototype.resetMempoolIndex = function(callback) { - log.info('Starting reset of mempool index for address service.'); var self = this; var transactionBuffers = self.node.services.bitcoind.getMempoolTransactions(); - this.mempoolIndex = {}; + this.mempoolInputIndex = {}; + this.mempoolOutputIndex = {}; async.each(transactionBuffers, function(txBuffer, next) { var tx = Transaction().fromBuffer(txBuffer); self.updateMempoolIndex(tx); @@ -187,7 +205,6 @@ AddressService.prototype.resetMempoolIndex = function(callback) { if (err) { return callback(err); } - log.info('Mempool index update with address size:', Object.keys(self.mempoolIndex).length); callback(); }); }; @@ -626,7 +643,19 @@ AddressService.prototype.getInputs = function(addressStr, options, callback) { return callback(error); } - // TODO include results from mempool + if(options.queryMempool) { + var mempoolInputs = self.mempoolInputIndex[addressStr]; + if (mempoolInputs) { + for(var i = 0; i < mempoolInputs.length; i++) { + // TODO copy + var newInput = mempooInputs[i]; + newInput.address = addressStr; + newInput.height = -1; + newInput.confirmations = 0; + inputs.push(newInput); + } + } + } callback(null, inputs); @@ -720,7 +749,7 @@ AddressService.prototype.getOutputs = function(addressStr, options, callback) { } if(options.queryMempool) { - var mempoolOutputs = self.mempoolIndex[addressStr]; + var mempoolOutputs = self.mempoolOutputIndex[addressStr]; if (mempoolOutputs) { for(var i = 0; i < mempoolOutputs.length; i++) { // TODO copy diff --git a/test/data/transaction.json b/test/data/transaction.json new file mode 100644 index 00000000..0357cf05 --- /dev/null +++ b/test/data/transaction.json @@ -0,0 +1,3 @@ +[ + "010000000ec9e1d96d51f7d0a5b726184cda744207e51a596f13b564109de6ffc0653055cf000000006a4730440220263d48a2f4c3a2aa6032f96253c853531131171d8ae3d30586a45de7ba7c4006022077db4b39926877939baf59e8d357effe7674e7b12ad986c0a4cd99f2b7acafca012103e9100732bb534bea2f6d3b971914ec8403557306600c01c5ce63ec185737c732ffffffff0c16800642bdf90cbbd340be2e801bee9b907db6d59dc4c7cb269358c1e2593a000000006a4730440220085e39cb3a948559c1b1c88ba635eeef37a767322c1e4d96556e998302acb5dc0220421c03de78121692c538a9bc85a58fbe796384541fe87b6e91c39404318c390d012103e9100732bb534bea2f6d3b971914ec8403557306600c01c5ce63ec185737c732ffffffff559fde3733e950db9e3faea1a27872047a5c8bc14e8e6ac4a7b4f7b5f90c42c2000000006b483045022100a46a109a5acfc34b13c591ab69392e2dc2c3ea12d0900c5f7a539ea888e57ae0022015372bad56d63c6d08a5e66e0c9a63b2fc8dce245aa765e37dac06acb84c18d501210207e2a01d4a334c2d7c9ebacc5ea8a0d4b86fd54599b1aebe125d9e664be012c2ffffffff92760c236f6f18751c4ecab1dfcfebac7358a431b31bcd72b0a09d336233bdce000000006a47304402200857fa82e5b287c6ed5624cbc4fcd068a756a7a9ef785a73ce4e51b15a8aa34b022042cbc6f478b711539c6345d0b05d4bc9a9b5c34b2e4d25f234cf6784ff2eed19012103cab1f64f3d5f20a3f4a070694e6f93f5248b502b3842e369d81f05d07dec01e3ffffffff158669557e8a0b71288df37626601e636c5a8b3797f7f3357590313e8efe790a000000006b48304502210085e62cb95066540730b74aeae133277e511b5bf79de8c0ad60e784638e681ddf022043b456285569e0da133f527412218893f05a781d6f77f8cddd12eb90bfdc5937012103e9100732bb534bea2f6d3b971914ec8403557306600c01c5ce63ec185737c732ffffffffc1f9f57f1eda20f3b45669b3f0d1eae73b410ddf2b4fc1cfe10051a6051eff68000000006a47304402200fbe15c73446309040f4264567d0e8cc46691cf5d0626c443fc2dde716211e5402207f84e68e273755d140f346e029213dbc42b65cfb1e2ac41f72402c7ff45feffc012103e9100732bb534bea2f6d3b971914ec8403557306600c01c5ce63ec185737c732ffffffff39fa27e16c540a9bb796e65d4ac624fc33878e522461d6955764bf7b83c03ce8000000006a4730440220131e6aed76da389f21dfd7cd271fad73c5c533b1c18fbfabd6799a0d0e7dc80602205c38855bea0f1dfbbb608bc1b56c75b91a39980de121ffd9f880b522d412d821012103e9100732bb534bea2f6d3b971914ec8403557306600c01c5ce63ec185737c732ffffffff375fb7ae26eb637ccc007a669b9f50ed10fa53127504b80e0fd2be34c50becd7000000006b483045022100f0a9e585aa3113eae4bfb204f38f96e60dc613c04313aae931b677e4d8d7081d022014664874859f3d47447c6c0f257c28c74e8fdaedd5f781d752f3a4b651d3d179012103e9100732bb534bea2f6d3b971914ec8403557306600c01c5ce63ec185737c732ffffffff6f9d6a3c847e6112bb920424ca0d8d6e0956b128acb206e8fb58b2d2f2d7d46b000000006a4730440220736b198484cf5226616a539146e037a93cc75963885eefe58fc29a7be8123c750220619a456c0fe7437ec67c642d88e890344fc1c51a7b3cfc0ae283d61d0f176c5e012103e9100732bb534bea2f6d3b971914ec8403557306600c01c5ce63ec185737c732ffffffff3cccbd8090d60fcf28064333cf2f51ef0f838ba5e26a5e0f38807ee16d38a649000000006b483045022100e1ed25e9365e596d4fc3cbf278490d8ea765c4266c55f19311cf5da33f8d00750220361888a1738ebba827c0c947690b5f2a5f20e9f1be8956c3a34a4ba03f9e60f5012103e9100732bb534bea2f6d3b971914ec8403557306600c01c5ce63ec185737c732ffffffff7f4d60a2e961490aa465a7df461bf09b151bdc0c162f3bef0c1cbed6160d02c7000000006a47304402204e79b15a1db9a356f00dc9f2d559e31561cad1343ba5809a65b52bd868e3963e022055b9326ed5de9aa9970ec67a2ebf1a9dbf9ee513b64bd13837c87320bb4d6947012103e9100732bb534bea2f6d3b971914ec8403557306600c01c5ce63ec185737c732ffffffffe63b9370ba49a129e071750cbb300128015fdd90d7399f9c4e44934eabbaa2f7000000006b483045022100b9ceb2e376c0334d45bf08bfeb06dc250e7cb01d3a08e2fb3506388683552417022024c7c5bda385b904cca691fb6e1ad8c5eba5858a88a2112cb824dca72793b7a7012103e9100732bb534bea2f6d3b971914ec8403557306600c01c5ce63ec185737c732ffffffffc78b96fddededb6cbc1dff9de51f2743fd42e91de2506794b121928af4729528000000006a47304402201f05caddee5a0ff590b27c4ce25be1cbbeb45dc39679a1b8b0e10b1a378d84bc02203e31b01e14d891e9809c43a4df54494c626c5e47eaeeeb99ab4e02bd73c3d6cd012103e9100732bb534bea2f6d3b971914ec8403557306600c01c5ce63ec185737c732ffffffff30093916240e981a77cb869924fa0c38a894c24b1a6e7d26b117bb9caa7d5bbe000000006a4730440220483f297379daacee14babbf929708a861a991373bca3ed4eef240e2c156a162602205f1e93e375a897c6a9ddc3dc616ccf14137096ebd7888040e1053a769d21b945012103e9100732bb534bea2f6d3b971914ec8403557306600c01c5ce63ec185737c732ffffffff022897d411000000001976a91415354ee1828ed12f243f80dcb92c3a8205285f0188ac3be68c02000000001976a9143ecf8ff79932c3de33829a001236985872d10be188ac00000000" +] diff --git a/test/services/address/index.unit.js b/test/services/address/index.unit.js index 2d220b03..e588e0e5 100644 --- a/test/services/address/index.unit.js +++ b/test/services/address/index.unit.js @@ -10,6 +10,8 @@ var bitcore = require('bitcore'); var Networks = bitcore.Networks; var EventEmitter = require('events').EventEmitter; var errors = bitcorenode.errors; +var Transaction = require('../../../lib/transaction'); +var txData = require('../../data/transaction.json'); var mockdb = { }; @@ -24,7 +26,7 @@ var mocknode = { }; describe('Address Service', function() { - + var txBuf = new Buffer(txData[0], 'hex'); describe('#getAPIMethods', function() { it('should return the correct methods', function() { var am = new AddressService({node: mocknode}); @@ -547,9 +549,6 @@ describe('Address Service', function() { return testStream; } }; - am.node.services.bitcoind = { - getMempoolOutputs: sinon.stub().returns([]) - }; am.getOutputs(address, args, function(err, outputs) { should.not.exist(err); outputs.length.should.equal(1); @@ -575,18 +574,17 @@ describe('Address Service', function() { am.node.services.db.store = { createReadStream: sinon.stub().returns(readStream1) }; - var mempoolOutputs = [ + + am.mempoolOutputIndex = {}; + + am.mempoolOutputIndex[address] = [ { - address: '1KiW1A4dx1oRgLHtDtBjcunUGkYtFgZ1W', txid: 'aa2db23f670596e96ed94c405fd11848c8f236d266ee96da37ecd919e53b4371', satoshis: 307627737, script: '76a914f6db95c81dea3d10f0ff8d890927751bf7b203c188ac', - blockHeight: 352532 + outputIndex: 0 } - ]; - am.node.services.bitcoind = { - getMempoolOutputs: sinon.stub().returns(mempoolOutputs) - }; + ] am.getOutputs(address, options, function(err, outputs) { should.not.exist(err); @@ -606,7 +604,8 @@ describe('Address Service', function() { outputs[2].address.should.equal(address); outputs[2].txid.should.equal('aa2db23f670596e96ed94c405fd11848c8f236d266ee96da37ecd919e53b4371'); outputs[2].script.should.equal('76a914f6db95c81dea3d10f0ff8d890927751bf7b203c188ac'); - outputs[2].blockHeight.should.equal(352532); + outputs[2].height.should.equal(-1); + outputs[2].confirmations.should.equal(0); done(); }); @@ -875,5 +874,47 @@ describe('Address Service', function() { }); }); }); + describe('#updateMempoolIndex', function() { + var am; + var db = {}; + var tx = Transaction().fromBuffer(txBuf); + before(function() { + am = new AddressService({node: mocknode}); + }); + + it('will update the input and output indexes', function() { + am.updateMempoolIndex(tx); + am.mempoolInputIndex['18Z29uNgWyUDtNyTKE1PaurbSR131EfANc'][0].txid.should.equal('45202ffdeb8344af4dec07cddf0478485dc65cc7d08303e45959630c89b51ea2'); + am.mempoolOutputIndex['12w93weN8oti3P1e5VYEuygqyujhADF7J5'][0].txid.should.equal('45202ffdeb8344af4dec07cddf0478485dc65cc7d08303e45959630c89b51ea2'); + am.mempoolInputIndex['1JT7KDYwT9JY9o2vyqcKNSJgTWeKfV3ui8'].length.should.equal(12); + am.mempoolOutputIndex['12w93weN8oti3P1e5VYEuygqyujhADF7J5'].length.should.equal(1); + }); + + }); + describe('#resetMempoolIndex', function() { + var am; + var db = {}; + + before(function() { + var testnode = { + db: db, + services: { + bitcoind: { + getMempoolTransactions: sinon.stub().returns([txBuf]), + on: sinon.stub() + } + } + }; + am = new AddressService({node: testnode}); + am.updateMempoolIndex = sinon.stub(); + + }); + it('will reset the input and output indexes', function(done) { + am.resetMempoolIndex(function() { + am.updateMempoolIndex.callCount.should.equal(1); + done(); + }); + }); + }); }); diff --git a/test/services/bitcoind.unit.js b/test/services/bitcoind.unit.js index 87633edc..15d87260 100644 --- a/test/services/bitcoind.unit.js +++ b/test/services/bitcoind.unit.js @@ -406,7 +406,7 @@ describe('Bitcoin Service', function() { ['sendTransaction', 2], ['getTransaction', 3], ['getTransactionWithBlockInfo', 3], - ['getMempoolOutputs', 1], + ['getMempoolTransactions', 0], ['addMempoolUncheckedTransaction', 1], ['getInfo', 0] ]; diff --git a/test/services/db.unit.js b/test/services/db.unit.js index c3106145..10631e9e 100644 --- a/test/services/db.unit.js +++ b/test/services/db.unit.js @@ -935,6 +935,7 @@ describe('DB Service', function() { var blockBuffer = new Buffer(blockData, 'hex'); var block = Block.fromBuffer(blockBuffer); db.node.services = {}; + db.runAllMempoolIndexes = sinon.stub().callsArg(0); db.node.services.bitcoind = { getBlock: sinon.stub().callsArgWith(1, null, blockBuffer), isSynced: sinon.stub().returns(true), @@ -954,6 +955,7 @@ describe('DB Service', function() { callback(); }; db.node.once('synced', function() { + db.runAllMempoolIndexes.callCount.should.equal(1); done(); }); db.sync(); From 6b904dda70b3401b739fb924edf402b0e04e4751 Mon Sep 17 00:00:00 2001 From: Chris Kleeschulte Date: Thu, 17 Sep 2015 14:35:36 -0400 Subject: [PATCH 4/5] Updated travis to install v0.12.7 of node. --- .travis.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index 52844c32..e3812578 100644 --- a/.travis.yml +++ b/.travis.yml @@ -3,7 +3,7 @@ language: node_js env: - BITCORENODE_ENV=test BITCORENODE_ASSUME_YES=true node_js: - - "0.12" + - "v0.12.7" before_install: - git config --global user.email "dev@bitpay.com" - git config --global user.name "BitPay, Inc." @@ -13,4 +13,4 @@ script: - _mocha -R spec --recursive cache: directories: - - cache + - cache From 0ba168e2a22d23b4adab6ba976cf75b7c80763a3 Mon Sep 17 00:00:00 2001 From: Chris Kleeschulte Date: Thu, 17 Sep 2015 17:20:35 -0400 Subject: [PATCH 5/5] Fixed typo and added a test to prove that the inputs from the mempoolInputIndex are added on stream close. --- lib/services/address/index.js | 2 +- test/services/address/index.unit.js | 37 +++++++++++++++++++++++++++++ 2 files changed, 38 insertions(+), 1 deletion(-) diff --git a/lib/services/address/index.js b/lib/services/address/index.js index 35c9d425..af9ff7df 100644 --- a/lib/services/address/index.js +++ b/lib/services/address/index.js @@ -648,7 +648,7 @@ AddressService.prototype.getInputs = function(addressStr, options, callback) { if (mempoolInputs) { for(var i = 0; i < mempoolInputs.length; i++) { // TODO copy - var newInput = mempooInputs[i]; + var newInput = mempoolInputs[i]; newInput.address = addressStr; newInput.height = -1; newInput.confirmations = 0; diff --git a/test/services/address/index.unit.js b/test/services/address/index.unit.js index e588e0e5..9bf9622d 100644 --- a/test/services/address/index.unit.js +++ b/test/services/address/index.unit.js @@ -412,6 +412,43 @@ describe('Address Service', function() { am = new AddressService({node: testnode}); }); + it('will add mempool inputs on close', function(done) { + var testStream = new EventEmitter(); + var db = { + store: { + createReadStream: sinon.stub().returns(testStream) + } + } + var testnode = { + services: { + db: db, + bitcoind: { + on: sinon.stub() + } + } + }; + var am = new AddressService({node: testnode}); + var args = { + start: 15, + end: 12, + queryMempool: true + }; + am.mempoolInputIndex[address] = [ + { + address: address, + height: -1, + confirmations: 0 + } + ] + am.getInputs(address, args, function(err, inputs) { + should.not.exist(err); + inputs.length.should.equal(1); + inputs[0].address.should.equal(address); + inputs[0].height.should.equal(-1); + done(); + }); + testStream.emit('close'); + }); it('will get inputs for an address and timestamp', function(done) { var testStream = new EventEmitter(); var args = {