From 7b8268d0e03e13d2a220718aefe0014413feec87 Mon Sep 17 00:00:00 2001 From: Braydon Fuller Date: Wed, 22 Jul 2015 15:34:15 -0400 Subject: [PATCH 1/4] Use prevHash from bitcoind block index - Changed method getChainWork into getBlockIndex - Added prevHash to getBlockIndex result --- integration/regtest.js | 17 +++++++++++++++++ lib/chain.js | 7 ++++--- lib/daemon.js | 4 ++-- lib/db.js | 21 ++++++++++++++++++--- src/bitcoindjs.cc | 24 +++++++++++++++++------- src/bitcoindjs.h | 2 +- test/chain.unit.js | 8 +++++--- test/db.unit.js | 4 +--- 8 files changed, 65 insertions(+), 22 deletions(-) diff --git a/integration/regtest.js b/integration/regtest.js index d71010b4..66adff3a 100644 --- a/integration/regtest.js +++ b/integration/regtest.js @@ -12,6 +12,7 @@ if (process.env.BITCOINDJS_ENV !== 'test') { var chai = require('chai'); var bitcore = require('bitcore'); +var BN = bitcore.crypto.BN; var rimraf = require('rimraf'); var bitcoind; @@ -261,4 +262,20 @@ describe('Daemon Binding Functionality', function() { }); + describe('get block index', function() { + var expectedWork = new BN(6); + [1,2,3,4,5,6,7,8,9].forEach(function(i) { + it('generate block ' + i, function() { + var blockIndex = bitcoind.getBlockIndex(blockHashes[i]); + should.exist(blockIndex); + should.exist(blockIndex.chainWork); + var work = new BN(blockIndex.chainWork, 'hex'); + work.cmp(expectedWork).should.equal(0); + expectedWork = expectedWork.add(new BN(2)); + should.exist(blockIndex.prevHash); + blockIndex.prevHash.should.equal(blockHashes[i - 1]); + }); + }); + }); + }); diff --git a/lib/chain.js b/lib/chain.js index b8cb942d..f9e7c200 100644 --- a/lib/chain.js +++ b/lib/chain.js @@ -85,13 +85,14 @@ Chain.prototype.buildGenesisBlock = function buildGenesisBlock(options) { Chain.prototype.getWeight = function getWeight(blockHash, callback) { var self = this; + var blockIndex = self.db.bitcoind.getBlockIndex(blockHash); setImmediate(function() { - var weight = self.db.bitcoind.getChainWork(blockHash); - if(weight === undefined) { + if (blockIndex) { + callback(null, new BN(blockIndex.chainWork, 'hex')); + } else { return callback(new Error('Weight not found for ' + blockHash)); } - callback(null, new BN(weight, 'hex')); }); }; diff --git a/lib/daemon.js b/lib/daemon.js index e5487e40..b2aaa3f2 100644 --- a/lib/daemon.js +++ b/lib/daemon.js @@ -340,8 +340,8 @@ Daemon.prototype.isSpent = function(txid, outputIndex) { return bitcoindjs.isSpent(txid, outputIndex); }; -Daemon.prototype.getChainWork = function(blockHash) { - return bitcoindjs.getChainWork(blockHash); +Daemon.prototype.getBlockIndex = function(blockHash) { + return bitcoindjs.getBlockIndex(blockHash); }; Daemon.prototype.sendTransaction = function(transaction, allowAbsurdFees) { diff --git a/lib/db.js b/lib/db.js index de7939fc..f950ca78 100644 --- a/lib/db.js +++ b/lib/db.js @@ -46,10 +46,20 @@ DB.prototype.getBlock = function(hash, callback) { }); }; +DB.prototype.getPrevHash = function(blockHash, callback) { + var blockIndex = this.bitcoind.getBlockIndex(blockHash); + setImmediate(function() { + if (blockIndex) { + callback(null, blockIndex.prevHash); + } else { + callback(new Error('Could not get prevHash, block not found')); + } + }); +}; + DB.prototype.putBlock = function(block, callback) { - // block is already stored in bitcoind, but we need to update - // our prevhash index still - this._updatePrevHashIndex(block, callback); + // block is already stored in bitcoind + setImmediate(callback); }; DB.prototype.getTransaction = function(txid, queryMempool, callback) { @@ -67,6 +77,11 @@ DB.prototype.validateBlockData = function(block, callback) { setImmediate(callback); }; +DB.prototype._updatePrevHashIndex = function(block, callback) { + // bitcoind has the previous hash for each block + setImmediate(callback); +}; + DB.prototype._updateWeight = function(hash, weight, callback) { // bitcoind has all work for each block setImmediate(callback); diff --git a/src/bitcoindjs.cc b/src/bitcoindjs.cc index c569d54b..454c61e5 100644 --- a/src/bitcoindjs.cc +++ b/src/bitcoindjs.cc @@ -980,12 +980,14 @@ NAN_METHOD(IsSpent) { }; /** - * GetChainWork() - * bitcoindjs.getChainWork() - * Get the total amount of work (expected number of hashes) in the chain up to - * and including this block. + * GetBlockIndex() + * bitcoindjs.getBlockIndex() + * Get index information about a block by hash including: + * - the total amount of work (expected number of hashes) in the chain up to + * and including this block. + * - the previous hash of the block */ -NAN_METHOD(GetChainWork) { +NAN_METHOD(GetBlockIndex) { Isolate* isolate = Isolate::GetCurrent(); HandleScope scope(isolate); @@ -1000,7 +1002,15 @@ NAN_METHOD(GetChainWork) { } else { blockIndex = mapBlockIndex[hash]; arith_uint256 cw = blockIndex->nChainWork; - NanReturnValue(Local::New(isolate, NanNew(cw.GetHex()))); + CBlockIndex* prevBlockIndex = blockIndex->pprev; + const uint256* prevHash = prevBlockIndex->phashBlock; + + Local obj = NanNew(); + + obj->Set(NanNew("chainWork"), NanNew(cw.GetHex())); + obj->Set(NanNew("prevHash"), NanNew(prevHash->GetHex())); + + NanReturnValue(obj); } }; @@ -1229,7 +1239,7 @@ init(Handle target) { NODE_SET_METHOD(target, "getTransaction", GetTransaction); NODE_SET_METHOD(target, "getInfo", GetInfo); NODE_SET_METHOD(target, "isSpent", IsSpent); - NODE_SET_METHOD(target, "getChainWork", GetChainWork); + NODE_SET_METHOD(target, "getBlockIndex", GetBlockIndex); NODE_SET_METHOD(target, "getMempoolOutputs", GetMempoolOutputs); NODE_SET_METHOD(target, "addMempoolUncheckedTransaction", AddMempoolUncheckedTransaction); NODE_SET_METHOD(target, "verifyScript", VerifyScript); diff --git a/src/bitcoindjs.h b/src/bitcoindjs.h index 050d6830..c863e226 100644 --- a/src/bitcoindjs.h +++ b/src/bitcoindjs.h @@ -23,7 +23,7 @@ NAN_METHOD(GetBlock); NAN_METHOD(GetTransaction); NAN_METHOD(GetInfo); NAN_METHOD(IsSpent); -NAN_METHOD(GetChainWork); +NAN_METHOD(GetBlockIndex); NAN_METHOD(GetMempoolOutputs); NAN_METHOD(AddMempoolUncheckedTransaction); NAN_METHOD(VerifyScript); diff --git a/test/chain.unit.js b/test/chain.unit.js index 78ea3097..9d463005 100644 --- a/test/chain.unit.js +++ b/test/chain.unit.js @@ -93,20 +93,22 @@ describe('Bitcoin Chain', function() { var chain = new Chain(); chain.db = { bitcoind: { - getChainWork: sinon.stub().returns(work) + getBlockIndex: sinon.stub().returns({ + chainWork: work + }) } }; it('should give the weight as a BN', function(done) { chain.getWeight('hash', function(err, weight) { should.not.exist(err); - weight.toString(16).should.equal('5a7b3c42ea8b844374e9'); + weight.toString(16, 64).should.equal(work); done(); }); }); it('should give an error if the weight is undefined', function(done) { - chain.db.bitcoind.getChainWork = sinon.stub().returns(undefined); + chain.db.bitcoind.getBlockIndex = sinon.stub().returns(undefined); chain.getWeight('hash2', function(err, weight) { should.exist(err); done(); diff --git a/test/db.unit.js b/test/db.unit.js index d4334fb5..580f5292 100644 --- a/test/db.unit.js +++ b/test/db.unit.js @@ -42,12 +42,10 @@ describe('Bitcoin DB', function() { }); describe('#putBlock', function() { - it('should call _updatePrevHashIndex', function(done) { + it('should call callback', function(done) { var db = new DB({store: memdown}); - db._updatePrevHashIndex = sinon.stub().callsArg(1); db.putBlock('block', function(err) { should.not.exist(err); - db._updatePrevHashIndex.called.should.equal(true); done(); }); }); From 40c0275677cd4a5b2823f0f3dd969f6d3b7f77f2 Mon Sep 17 00:00:00 2001 From: Braydon Fuller Date: Wed, 22 Jul 2015 18:19:50 -0400 Subject: [PATCH 2/4] Make sure to select the correct utxo in regtest. --- integration/regtest.js | 76 ++++++++++++++++++++++-------------------- 1 file changed, 40 insertions(+), 36 deletions(-) diff --git a/integration/regtest.js b/integration/regtest.js index 66adff3a..9ee7bae6 100644 --- a/integration/regtest.js +++ b/integration/regtest.js @@ -22,7 +22,7 @@ var assert = chai.assert; var sinon = require('sinon'); var BitcoinRPC = require('bitcoind-rpc'); var blockHashes = []; -var unspentTransactions = []; +var utxo; var coinbasePrivateKey; var privateKey = bitcore.PrivateKey(); var destKey = bitcore.PrivateKey(); @@ -119,9 +119,22 @@ describe('Daemon Binding Functionality', function() { throw err; } - var tx = bitcore.Transaction(); - tx.fromString(response.result.hex); - unspentTransactions.push(tx); + var unspentTransaction = bitcore.Transaction(); + var outputIndex; + unspentTransaction.fromString(response.result.hex); + for (var i = 0; i < unspentTransaction.outputs.length; i++) { + var output = unspentTransaction.outputs[i]; + if (output.script.toAddress(network).toString() === address.toString(network)) { + outputIndex = i; + } + } + + utxo = { + txid: unspentTransaction.hash, + outputIndex: outputIndex, + script: unspentTransaction.outputs[outputIndex].script, + satoshis: unspentTransaction.outputs[outputIndex].satoshis + }; // Include this transaction in a block so that it can // be spent in tests @@ -151,7 +164,7 @@ describe('Daemon Binding Functionality', function() { describe('mempool functionality', function() { var fromAddress = 'mszYqVnqKoQx4jcTdJXxwKAissE3Jbrrc1'; - var utxo = { + var utxo1 = { address: fromAddress, txId: 'a477af6b2667c29670467e4e0728b685ee07b240235771862318e29ddbe58458', outputIndex: 0, @@ -161,14 +174,14 @@ describe('Daemon Binding Functionality', function() { var toAddress = 'mrU9pEmAx26HcbKVrABvgL7AwA5fjNFoDc'; var changeAddress = 'mgBCJAsvzgT2qNNeXsoECg2uPKrUsZ76up'; var changeAddressP2SH = '2N7T3TAetJrSCruQ39aNrJvYLhG1LJosujf'; - var privateKey = 'cSBnVM4xvxarwGQuAfQFwqDg9k5tErHUHzgWsEfD4zdwUasvqRVY'; + var privateKey1 = 'cSBnVM4xvxarwGQuAfQFwqDg9k5tErHUHzgWsEfD4zdwUasvqRVY'; var private1 = '6ce7e97e317d2af16c33db0b9270ec047a91bff3eff8558afb5014afb2bb5976'; var private2 = 'c9b26b0f771a0d2dad88a44de90f05f416b3b385ff1d989343005546a0032890'; var tx = new bitcore.Transaction(); - tx.from(utxo); + tx.from(utxo1); tx.to(toAddress, 50000); tx.change(changeAddress); - tx.sign(privateKey); + tx.sign(privateKey1); it('will add an unchecked transaction', function() { var added = bitcoind.addMempoolUncheckedTransaction(tx.serialize()); @@ -234,34 +247,6 @@ describe('Daemon Binding Functionality', function() { }); }); - describe('send transaction functionality', function() { - - it('will not error and return the transaction hash', function() { - - var unspentTx = unspentTransactions.shift(); - - var utxo = { - txid: unspentTx.hash, - outputIndex: 1, - script: unspentTx.outputs[1].script, - satoshis: unspentTx.outputs[1].satoshis - }; - - // create and sign the transaction - var tx = bitcore.Transaction(); - tx.from(utxo); - tx.change(privateKey.toAddress()); - tx.to(destKey.toAddress(), 100000); - tx.sign(privateKey); - - // test sending the transaction - var hash = bitcoind.sendTransaction(tx.serialize()); - hash.should.equal(tx.hash); - - }); - - }); - describe('get block index', function() { var expectedWork = new BN(6); [1,2,3,4,5,6,7,8,9].forEach(function(i) { @@ -278,4 +263,23 @@ describe('Daemon Binding Functionality', function() { }); }); + describe('send transaction functionality', function() { + + it('will not error and return the transaction hash', function() { + + // create and sign the transaction + var tx = bitcore.Transaction(); + tx.from(utxo); + tx.change(privateKey.toAddress()); + tx.to(destKey.toAddress(), 100000); + tx.sign(privateKey); + + // test sending the transaction + var hash = bitcoind.sendTransaction(tx.serialize()); + hash.should.equal(tx.hash); + + }); + + }); + }); From 747942cec19213142decd36f4c8a9e15a1ba4313 Mon Sep 17 00:00:00 2001 From: Braydon Fuller Date: Wed, 22 Jul 2015 19:51:49 -0400 Subject: [PATCH 3/4] Log sync status less frequently, and include the current height. --- example/node.js | 26 +++++++++----------------- 1 file changed, 9 insertions(+), 17 deletions(-) diff --git a/example/node.js b/example/node.js index 3ab2d5a4..446dbb0c 100644 --- a/example/node.js +++ b/example/node.js @@ -4,22 +4,22 @@ var BitcoindJS = require('..'); var BitcoinNode = BitcoindJS.Node; var chainlib = require('chainlib'); var log = chainlib.log; -//log.debug = function() {}; +log.debug = function() {}; var configuration = { - datadir: process.env.BITCOINDJS_DIR || '~/.bitcoin', - network: 'testnet' + datadir: process.env.BITCOINDJS_DIR || '~/.bitcoin' }; var node = new BitcoinNode(configuration); -var startHeight; -var count = 100; -var times = new Array(count); +var count = 0; +var interval; node.on('ready', function() { - times[node.chain.tip.__height % count] = Date.now(); - startHeight = node.chain.tip.__height; + interval = setInterval(function() { + log.info('Sync Status: Tip:', node.chain.tip.hash, 'Height:', node.chain.tip.__height, 'Rate:', count/10, 'blocks per second'); + count = 0; + }, 10000); }); node.on('error', function(err) { @@ -27,13 +27,5 @@ node.on('error', function(err) { }); node.chain.on('addblock', function(block) { - console.log('New Best Tip:', block.hash); - var startTime = times[node.chain.tip.__height % count]; - - if(startTime) { - var timeElapsed = (Date.now() - startTime) / 1000; - console.log(Math.round(count / timeElapsed) + ' blocks per second'); - } - - times[node.chain.tip.__height % count] = Date.now(); + count++; }); From 509b424262b0d3db405bdb030a806a2d5bee61e9 Mon Sep 17 00:00:00 2001 From: Braydon Fuller Date: Wed, 22 Jul 2015 23:36:23 -0400 Subject: [PATCH 4/4] Fix bug with loading the genesis block for testnet. --- lib/node.js | 2 +- test/node.unit.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/node.js b/lib/node.js index 1c0ef8dd..831c2a8f 100644 --- a/lib/node.js +++ b/lib/node.js @@ -209,7 +209,7 @@ Node.prototype._loadConsensus = function(config) { var genesisBlock; if (config.genesis) { genesisBlock = config.genesis; - } else if (config.testnet) { + } else if (config.network === 'testnet') { genesisBlock = genesis.testnet; } else { genesisBlock = genesis.livenet; diff --git a/test/node.unit.js b/test/node.unit.js index 1660836e..f482f1dc 100644 --- a/test/node.unit.js +++ b/test/node.unit.js @@ -309,7 +309,7 @@ describe('Bitcoind Node', function() { }); it('should use the testnet genesis if testnet is specified', function() { var config = { - testnet: true + network: 'testnet' }; node._loadConsensus(config); should.exist(node.chain);