diff --git a/integration/regtest.js b/integration/regtest.js index 97df6d4f..468d5517 100644 --- a/integration/regtest.js +++ b/integration/regtest.js @@ -66,21 +66,14 @@ describe('Daemon Binding Functionality', function() { network: 'regtest' }); - bitcoind.start(function() { - log.info('Bitcoind started'); - }); - bitcoind.on('error', function(err) { log.error('error="%s"', err.message); }); - bitcoind.on('open', function(status) { - log.info('status="%s"', status); - }); - log.info('Waiting for Bitcoin Core to initialize...'); - bitcoind.on('ready', function() { + bitcoind.start(function() { + log.info('Bitcoind started'); client = new BitcoinRPC({ protocol: 'http', @@ -96,6 +89,7 @@ describe('Daemon Binding Functionality', function() { // can be spent. client.generate(150, function(err, response) { + if (err) { throw err; } @@ -139,7 +133,9 @@ describe('Daemon Binding Functionality', function() { }); }); }); + }); + }); }); diff --git a/lib/chain.js b/lib/chain.js index 7e93d38b..e6c17665 100644 --- a/lib/chain.js +++ b/lib/chain.js @@ -45,12 +45,15 @@ function Chain(options) { this.targetTimespan = options.targetTimespan || Chain.DEFAULTS.TARGET_TIMESPAN; this.targetSpacing = options.targetSpacing || Chain.DEFAULTS.TARGET_SPACING; + this.node = options.node; + return this; } util.inherits(Chain, BaseChain); Chain.prototype.start = function(callback) { + this.genesis = Block.fromBuffer(this.node.bitcoind.genesisBuffer); this.on('initialized', callback); this.initialize(); }; diff --git a/lib/daemon.js b/lib/daemon.js index 54717ad3..24508922 100644 --- a/lib/daemon.js +++ b/lib/daemon.js @@ -27,6 +27,8 @@ function Daemon(options) { this.options.datadir = this.options.datadir.replace(/^~/, process.env.HOME); this.datadir = this.options.datadir; + this.node = options.node; + this.config = this.datadir + '/bitcoin.conf'; Object.keys(exports).forEach(function(key) { @@ -68,6 +70,7 @@ Daemon.prototype.start = function(callback) { function onTipUpdateListener(result) { if (result) { // Emit and event that the tip was updated + self.height = result; self.emit('tip', result); // Recursively wait until the next update bitcoind.onTipUpdate(onTipUpdateListener); @@ -82,10 +85,17 @@ Daemon.prototype.start = function(callback) { } }); - setImmediate(function() { + // Set the current chain height + var info = self.getInfo(); + self.height = info.blocks; + + // Get the genesis block + self.getBlock(0, function(err, block) { + self.genesisBuffer = block; self.emit('ready', result); - callback(); + setImmediate(callback); }); + }); }); }; diff --git a/lib/db.js b/lib/db.js index ea287a5b..cfab74f3 100644 --- a/lib/db.js +++ b/lib/db.js @@ -26,6 +26,8 @@ function DB(options) { this.network = bitcore.Networks.get(options.network) || bitcore.Networks.testnet; + this.node = options.node; + // Modules to be loaded when ready this._modules = options.modules || []; this._modules.push(AddressModule); @@ -49,7 +51,7 @@ DB.prototype.start = function(callback) { } this.bitcoind.on('tx', this.transactionHandler.bind(this)); this.emit('ready'); - callback(); + setImmediate(callback); }; DB.prototype.stop = function(callback) { @@ -132,7 +134,7 @@ DB.prototype.estimateFee = function(blocks, callback) { setImmediate(function() { callback(null, self.bitcoind.estimateFee(blocks)); }); -} +}; DB.prototype.validateBlockData = function(block, callback) { // bitcoind does the validation diff --git a/lib/genesis.json b/lib/genesis.json deleted file mode 100644 index 2c06930b..00000000 --- a/lib/genesis.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "livenet": "0100000000000000000000000000000000000000000000000000000000000000000000003ba3edfd7a7b12b27ac72c3e67768f617fc81bc3888a51323a9fb8aa4b1e5e4a29ab5f49ffff001d1dac2b7c0101000000010000000000000000000000000000000000000000000000000000000000000000ffffffff4d04ffff001d0104455468652054696d65732030332f4a616e2f32303039204368616e63656c6c6f72206f6e206272696e6b206f66207365636f6e64206261696c6f757420666f722062616e6b73ffffffff0100f2052a01000000434104678afdb0fe5548271967f1a67130b7105cd6a828e03909a67962e0ea1f61deb649f6bc3f4cef38c4f35504e51ec112de5c384df7ba0b8d578a4c702b6bf11d5fac00000000", - "testnet": "0100000000000000000000000000000000000000000000000000000000000000000000003ba3edfd7a7b12b27ac72c3e67768f617fc81bc3888a51323a9fb8aa4b1e5e4adae5494dffff001d1aa4ae180101000000010000000000000000000000000000000000000000000000000000000000000000ffffffff4d04ffff001d0104455468652054696d65732030332f4a616e2f32303039204368616e63656c6c6f72206f6e206272696e6b206f66207365636f6e64206261696c6f757420666f722062616e6b73ffffffff0100f2052a01000000434104678afdb0fe5548271967f1a67130b7105cd6a828e03909a67962e0ea1f61deb649f6bc3f4cef38c4f35504e51ec112de5c384df7ba0b8d578a4c702b6bf11d5fac00000000" -} diff --git a/lib/node.js b/lib/node.js index f53ca865..18712823 100644 --- a/lib/node.js +++ b/lib/node.js @@ -14,7 +14,6 @@ var bitcore = require('bitcore'); var Networks = bitcore.Networks; var _ = bitcore.deps._; var $ = bitcore.util.preconditions; -var genesis = require('./genesis.json'); var daemon = require('./daemon'); var Bus = require('./bus'); @@ -25,12 +24,6 @@ function Node(config) { util.inherits(Node, BaseNode); -var defaultServices = { - 'bitcoind': [], - 'db': ['bitcoind'], - 'chain': ['db'], -}; - Node.prototype.openBus = function() { return new Bus({db: this.db}); }; @@ -101,6 +94,7 @@ Node.prototype._loadBitcoind = function(config) { var bitcoindConfig = {}; bitcoindConfig.datadir = config.datadir; bitcoindConfig.network = config.network; + bitcoindConfig.node = this; // start the bitcoind daemon this.bitcoind = daemon(bitcoindConfig); @@ -228,7 +222,7 @@ Node.prototype._syncBitcoind = function() { async.whilst(function() { height = self.chain.tip.__height; - return height < self.bitcoindHeight && !self.stopping; + return height < self.bitcoind.height && !self.stopping; }, function(done) { self.bitcoind.getBlock(height + 1, function(err, blockBuffer) { if (err) { @@ -323,15 +317,13 @@ Node.prototype._loadNetwork = function(config) { }; Node.prototype._loadDB = function(config) { + var options = _.clone(config.db || {}); + if (config.DB) { // Other modules can inherit from our DB and replace it with their own DB = config.DB; } - if(!config.db) { - config.db = {}; - } - // Store the additional indexes in a new directory // based on the network configuration and the datadir $.checkArgument(config.datadir, 'Please specify "datadir" in configuration options'); @@ -339,50 +331,35 @@ Node.prototype._loadDB = function(config) { var regtest = Networks.get('regtest'); var datadir = config.datadir.replace(/^~/, process.env.HOME); if (this.network === Networks.livenet) { - config.db.path = datadir + '/bitcore-node.db'; + options.path = datadir + '/bitcore-node.db'; } else if (this.network === Networks.testnet) { - config.db.path = datadir + '/testnet3/bitcore-node.db'; + options.path = datadir + '/testnet3/bitcore-node.db'; } else if (this.network === regtest) { - config.db.path = datadir + '/regtest/bitcore-node.db'; + options.path = datadir + '/regtest/bitcore-node.db'; } else { throw new Error('Unknown network: ' + this.network); } - config.db.network = this.network; + options.network = this.network; - if (!fs.existsSync(config.db.path)) { - mkdirp.sync(config.db.path); + if (!fs.existsSync(options.path)) { + mkdirp.sync(options.path); } - this.db = new DB(config.db); + options.node = this; + + this.db = new DB(options); }; Node.prototype._loadConsensus = function(config) { - if (!config.consensus) { - config.consensus = {}; - } - - this.Block = Block; - - var genesisBlock; - if (config.genesis) { - genesisBlock = config.genesis; - } else if (config.network === 'testnet') { - genesisBlock = genesis.testnet; - } else if (config.network === 'regtest') { - // initializeBitcoind will set the genesis block later in the chain - // todo: we should be able to refactor since the genesis is set later - genesisBlock = null; + var options; + if (!config) { + options = {}; } else { - genesisBlock = genesis.livenet; + options = _.clone(config.consensus || {}); } - - if (_.isString(genesisBlock)) { - genesisBlock = this.Block.fromBuffer(new Buffer(genesisBlock, 'hex')); - } - - // pass genesis to chain - config.consensus.genesis = genesisBlock; - this.chain = new Chain(config.consensus); + options.node = this; + this.Block = Block; + this.chain = new Chain(options); }; Node.prototype._initialize = function() { @@ -396,10 +373,9 @@ Node.prototype._initialize = function() { // Chain References this.chain.db = this.db; - // Initialize the event listeners this._initializeBitcoind(); - this._initializeChain(); this._initializeDatabase(); + this._initializeChain(); this.start(function(err) { if(err) { @@ -412,22 +388,9 @@ Node.prototype._initialize = function() { Node.prototype._initializeBitcoind = function() { var self = this; - // Bitcoind - this.bitcoind.on('ready', function(status) { + // Notify that there is a new tip + this.bitcoind.on('ready', function() { log.info('Bitcoin Daemon Ready'); - // Set the current chain height - var info = self.bitcoind.getInfo(); - self.bitcoindHeight = info.blocks; - - // Get the genesis block - self.bitcoind.getBlock(0, function(err, block) { - self.chain.genesis = self.Block.fromBuffer(block); - }); - - }); - - this.bitcoind.on('open', function(status) { - log.info('Bitcoin Core Daemon Status:', status); }); // Notify that there is a new tip @@ -435,7 +398,6 @@ Node.prototype._initializeBitcoind = function() { if(!self.stopping) { var percentage = self.bitcoind.syncPercentage(); log.info('Bitcoin Core Daemon New Height:', height, 'Percentage:', percentage); - self.bitcoindHeight = height; self._syncBitcoind(); } }); @@ -465,17 +427,24 @@ Node.prototype._initializeChain = function() { log.info('Bitcoin Chain Ready'); self._syncBitcoind(); }); - this.chain.on('error', function(err) { Error.captureStackTrace(err); self.emit('error', err); }); }; -Node.prototype.getServiceOrder = function(services, keys, stack) { - if(!services) { - services = defaultServices; - } +Node.prototype.getServices = function() { + var defaultServices = { + 'bitcoind': [], + 'db': ['bitcoind'], + 'chain': ['db'] + }; + return defaultServices; +}; + +Node.prototype.getServiceOrder = function(keys, stack) { + + var services = this.getServices(); if(!keys) { keys = Object.keys(services); @@ -486,21 +455,20 @@ Node.prototype.getServiceOrder = function(services, keys, stack) { } for(var i = 0; i < keys.length; i++) { - this.getServiceOrder(services, services[keys[i]], stack); + this.getServiceOrder(services[keys[i]], stack); if(stack.indexOf(keys[i]) === -1) { stack.push(keys[i]); } } - return stack; }; Node.prototype.start = function(callback) { var self = this; - var services = this.getServiceOrder(); + var servicesOrder = this.getServiceOrder(); async.eachSeries( - services, + servicesOrder, function(service, next) { log.info('Starting ' + service); self[service].start(next); diff --git a/test/chain.unit.js b/test/chain.unit.js index ff432419..3b4c9d00 100644 --- a/test/chain.unit.js +++ b/test/chain.unit.js @@ -28,6 +28,8 @@ describe('Bitcoin Chain', function() { describe('#start', function() { it('should call the callback when base chain is initialized', function(done) { var chain = new Chain(); + chain.bitcoind = {}; + chain.bitcoind.genesisBuffer = new Buffer('0100000043497fd7f826957108f4a30fd9cec3aeba79972084e90ead01ea330900000000bac8b0fa927c0ac8234287e33c5f74d38d354820e24756ad709d7038fc5f31f020e7494dffff001d03e4b6720101000000010000000000000000000000000000000000000000000000000000000000000000ffffffff0e0420e7494d017f062f503253482fffffffff0100f2052a010000002321021aeaf2f8638a129a3156fbe7e5ef635226b0bafd495ff03afe2c843d7e3a4b51ac00000000', 'hex'); chain.initialize = function() { chain.emit('initialized'); }; diff --git a/test/node.unit.js b/test/node.unit.js index d56d7779..552b3e40 100644 --- a/test/node.unit.js +++ b/test/node.unit.js @@ -227,12 +227,12 @@ describe('Bitcoind Node', function() { it('will get and add block up to the tip height', function(done) { var node = new Node({}); node.Block = Block; - node.bitcoindHeight = 1; var blockBuffer = new Buffer(blockData); var block = Block.fromBuffer(blockBuffer); node.bitcoind = { getBlock: sinon.stub().callsArgWith(1, null, blockBuffer), - isSynced: sinon.stub().returns(true) + isSynced: sinon.stub().returns(true), + height: 1 }; node.chain = { tip: { @@ -259,9 +259,9 @@ describe('Bitcoind Node', function() { }); it('will exit and emit error with error from bitcoind.getBlock', function(done) { var node = new Node({}); - node.bitcoindHeight = 1; node.bitcoind = { - getBlock: sinon.stub().callsArgWith(1, new Error('test error')) + getBlock: sinon.stub().callsArgWith(1, new Error('test error')), + height: 1 }; node.chain = { tip: { @@ -277,12 +277,12 @@ describe('Bitcoind Node', function() { it('will stop syncing when the node is stopping', function(done) { var node = new Node({}); node.Block = Block; - node.bitcoindHeight = 1; var blockBuffer = new Buffer(blockData); var block = Block.fromBuffer(blockBuffer); node.bitcoind = { getBlock: sinon.stub().callsArgWith(1, null, blockBuffer), - isSynced: sinon.stub().returns(true) + isSynced: sinon.stub().returns(true), + height: 1 }; node.chain = { tip: { @@ -434,28 +434,12 @@ describe('Bitcoind Node', function() { describe('#_loadConsensus', function() { var node = new Node({}); - it('should use the genesis specified in the config', function() { - var config = { - genesis: '0100000043497fd7f826957108f4a30fd9cec3aeba79972084e90ead01ea330900000000bac8b0fa927c0ac8234287e33c5f74d38d354820e24756ad709d7038fc5f31f020e7494dffff001d03e4b6720101000000010000000000000000000000000000000000000000000000000000000000000000ffffffff0e0420e7494d017f062f503253482fffffffff0100f2052a010000002321021aeaf2f8638a129a3156fbe7e5ef635226b0bafd495ff03afe2c843d7e3a4b51ac00000000' - }; - node._loadConsensus(config); + it('will set properties', function() { + node._loadConsensus(); + should.exist(node.Block); should.exist(node.chain); - node.chain.genesis.hash.should.equal('00000000b873e79784647a6c82962c70d228557d24a747ea4d1b8bbe878e1206'); - }); - it('should use the testnet genesis if testnet is specified', function() { - var config = { - network: 'testnet' - }; - node._loadConsensus(config); - should.exist(node.chain); - node.chain.genesis.hash.should.equal('000000000933ea01ad0ee984209779baaec3ced90fa3f408719526f8d77f4943'); - }); - it('should use the livenet genesis if nothing is specified', function() { - var config = {}; - node._loadConsensus(config); - should.exist(node.chain); - node.chain.genesis.hash.should.equal('000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f'); }); + }); describe('#_initialize', function() { @@ -516,29 +500,6 @@ describe('Bitcoind Node', function() { describe('#_initalizeBitcoind', function() { - it('set the genesis block when ready and set height', function(done) { - var genesisBuffer = new Buffer('0100000043497fd7f826957108f4a30fd9cec3aeba79972084e90ead01ea330900000000bac8b0fa927c0ac8234287e33c5f74d38d354820e24756ad709d7038fc5f31f020e7494dffff001d03e4b6720101000000010000000000000000000000000000000000000000000000000000000000000000ffffffff0e0420e7494d017f062f503253482fffffffff0100f2052a010000002321021aeaf2f8638a129a3156fbe7e5ef635226b0bafd495ff03afe2c843d7e3a4b51ac00000000', 'hex'); - var node = new Node({}); - node.Block = Block; - node.chain = {}; - node.bitcoind = new EventEmitter(); - node.bitcoind.getInfo = sinon.stub().returns({blocks: 10}); - node.bitcoind.getBlock = sinon.stub().callsArgWith(1, null, genesisBuffer); - node.db = { - initialize: sinon.spy() - }; - sinon.stub(chainlib.log, 'info'); - node.bitcoind.on('ready', function() { - setImmediate(function() { - chainlib.log.info.callCount.should.equal(1); - chainlib.log.info.restore(); - node.bitcoindHeight.should.equal(10); - done(); - }); - }); - node._initializeBitcoind(); - node.bitcoind.emit('ready'); - }); it('will call emit an error from libbitcoind', function(done) { var node = new Node({}); node.bitcoind = new EventEmitter(); @@ -556,7 +517,6 @@ describe('Bitcoind Node', function() { node.bitcoind.syncPercentage = sinon.spy(); node._syncBitcoind = function() { node.bitcoind.syncPercentage.callCount.should.equal(1); - node.bitcoindHeight.should.equal(10); done(); }; node._initializeBitcoind(); @@ -636,17 +596,17 @@ describe('Bitcoind Node', function() { }); describe('#getServiceOrder', function() { - var services = { - 'chain': ['db'], - 'db': ['daemon', 'p2p'], - 'daemon': [], - 'p2p': [] - }; - it('should return the services in the correct order', function() { var node = new Node({}); - var order = node.getServiceOrder(services); - + node.getServices = function() { + return { + 'chain': ['db'], + 'db': ['daemon', 'p2p'], + 'daemon': [], + 'p2p': [] + }; + }; + var order = node.getServiceOrder(); order.should.deep.equal(['daemon', 'p2p', 'db', 'chain']); }); });