Merge pull request #142 from braydonf/bug/reorg
Adds node reorg regtests
This commit is contained in:
commit
51837fc169
@ -9,6 +9,7 @@ before_install:
|
||||
- git config --global user.name "BitPay, Inc."
|
||||
script:
|
||||
- _mocha -R spec integration/regtest.js
|
||||
- _mocha -R spec integration/regtest-node.js
|
||||
- _mocha -R spec --recursive
|
||||
cache:
|
||||
directories:
|
||||
|
||||
@ -4,5 +4,3 @@ txindex=1
|
||||
rpcallowip=127.0.0.1
|
||||
rpcuser=bitcoin
|
||||
rpcpassword=local321
|
||||
|
||||
|
||||
|
||||
184
integration/regtest-node.js
Normal file
184
integration/regtest-node.js
Normal file
@ -0,0 +1,184 @@
|
||||
'use strict';
|
||||
|
||||
// These tests require bitcoind.js Bitcoin Core bindings to be compiled with
|
||||
// the environment variable BITCOINDJS_ENV=test. This enables the use of regtest
|
||||
// functionality by including the wallet in the build.
|
||||
// To run the tests: $ mocha -R spec integration/regtest-node.js
|
||||
|
||||
var chainlib = require('chainlib');
|
||||
var async = require('async');
|
||||
var log = chainlib.log;
|
||||
log.debug = function() {};
|
||||
|
||||
if (process.env.BITCORENODE_ENV !== 'test') {
|
||||
log.info('Please set the environment variable BITCORENODE_ENV=test and make sure bindings are compiled for testing');
|
||||
process.exit();
|
||||
}
|
||||
|
||||
var chai = require('chai');
|
||||
var bitcore = require('bitcore');
|
||||
var rimraf = require('rimraf');
|
||||
var node;
|
||||
|
||||
var should = chai.should();
|
||||
|
||||
var BitcoinRPC = require('bitcoind-rpc');
|
||||
var BitcoreNode = require('..').Node;
|
||||
var testWIF = 'cSdkPxkAjA4HDr5VHgsebAPDEh9Gyub4HK8UJr2DFGGqKKy4K5sG';
|
||||
var testKey;
|
||||
var client;
|
||||
|
||||
describe('Node Functionality', function() {
|
||||
|
||||
before(function(done) {
|
||||
this.timeout(30000);
|
||||
|
||||
// Add the regtest network
|
||||
bitcore.Networks.remove(bitcore.Networks.testnet);
|
||||
bitcore.Networks.add({
|
||||
name: 'regtest',
|
||||
alias: 'regtest',
|
||||
pubkeyhash: 0x6f,
|
||||
privatekey: 0xef,
|
||||
scripthash: 0xc4,
|
||||
xpubkey: 0x043587cf,
|
||||
xprivkey: 0x04358394,
|
||||
networkMagic: 0xfabfb5da,
|
||||
port: 18444,
|
||||
dnsSeeds: [ ]
|
||||
});
|
||||
|
||||
var datadir = __dirname + '/data';
|
||||
|
||||
testKey = bitcore.PrivateKey(testWIF);
|
||||
|
||||
rimraf(datadir + '/regtest', function(err) {
|
||||
|
||||
if (err) {
|
||||
throw err;
|
||||
}
|
||||
|
||||
var configuration = {
|
||||
datadir: datadir,
|
||||
network: 'regtest'
|
||||
};
|
||||
|
||||
node = new BitcoreNode(configuration);
|
||||
|
||||
node.on('error', function(err) {
|
||||
log.error(err);
|
||||
});
|
||||
|
||||
node.on('ready', function() {
|
||||
|
||||
client = new BitcoinRPC({
|
||||
protocol: 'http',
|
||||
host: '127.0.0.1',
|
||||
port: 18332,
|
||||
user: 'bitcoin',
|
||||
pass: 'local321'
|
||||
});
|
||||
|
||||
var syncedHandler = function() {
|
||||
if (node.chain.tip.__height === 150) {
|
||||
node.removeListener('synced', syncedHandler);
|
||||
done();
|
||||
}
|
||||
};
|
||||
|
||||
node.on('synced', syncedHandler);
|
||||
|
||||
client.generate(150, function(err, response) {
|
||||
if (err) {
|
||||
throw err;
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
});
|
||||
});
|
||||
|
||||
after(function(done) {
|
||||
this.timeout(20000);
|
||||
node.db.bitcoind.stop(function(err, result) {
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('will handle a reorganization', function(done) {
|
||||
|
||||
var count;
|
||||
var blockHash;
|
||||
|
||||
async.series([
|
||||
function(next) {
|
||||
client.getBlockCount(function(err, response) {
|
||||
if (err) {
|
||||
return next(err);
|
||||
}
|
||||
count = response.result;
|
||||
next();
|
||||
});
|
||||
},
|
||||
function(next) {
|
||||
client.getBlockHash(count, function(err, response) {
|
||||
if (err) {
|
||||
return next(err);
|
||||
}
|
||||
blockHash = response.result;
|
||||
next();
|
||||
});
|
||||
},
|
||||
function(next) {
|
||||
client.invalidateBlock(blockHash, next);
|
||||
},
|
||||
function(next) {
|
||||
client.getBlockCount(function(err, response) {
|
||||
if (err) {
|
||||
return next(err);
|
||||
}
|
||||
response.result.should.equal(count - 1);
|
||||
next();
|
||||
});
|
||||
}
|
||||
], function(err) {
|
||||
if (err) {
|
||||
throw err;
|
||||
}
|
||||
var blocksRemoved = 0;
|
||||
var blocksAdded = 0;
|
||||
|
||||
var removeBlock = function() {
|
||||
blocksRemoved++;
|
||||
};
|
||||
|
||||
node.chain.on('removeblock', removeBlock);
|
||||
|
||||
var addBlock = function() {
|
||||
blocksAdded++;
|
||||
if (blocksAdded === 2 && blocksRemoved === 1) {
|
||||
node.chain.removeListener('addblock', addBlock);
|
||||
node.chain.removeListener('removeblock', removeBlock);
|
||||
done();
|
||||
}
|
||||
};
|
||||
|
||||
node.chain.on('addblock', addBlock);
|
||||
|
||||
// We need to add a transaction to the mempool so that the next block will
|
||||
// have a different hash as the hash has been invalidated.
|
||||
client.sendToAddress(testKey.toAddress().toString(), 10, function(err) {
|
||||
if (err) {
|
||||
throw err;
|
||||
}
|
||||
client.generate(2, function(err, response) {
|
||||
if (err) {
|
||||
throw err;
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
});
|
||||
@ -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() {
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@ -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();
|
||||
};
|
||||
|
||||
@ -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);
|
||||
});
|
||||
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
@ -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
|
||||
|
||||
@ -1,4 +0,0 @@
|
||||
{
|
||||
"livenet": "0100000000000000000000000000000000000000000000000000000000000000000000003ba3edfd7a7b12b27ac72c3e67768f617fc81bc3888a51323a9fb8aa4b1e5e4a29ab5f49ffff001d1dac2b7c0101000000010000000000000000000000000000000000000000000000000000000000000000ffffffff4d04ffff001d0104455468652054696d65732030332f4a616e2f32303039204368616e63656c6c6f72206f6e206272696e6b206f66207365636f6e64206261696c6f757420666f722062616e6b73ffffffff0100f2052a01000000434104678afdb0fe5548271967f1a67130b7105cd6a828e03909a67962e0ea1f61deb649f6bc3f4cef38c4f35504e51ec112de5c384df7ba0b8d578a4c702b6bf11d5fac00000000",
|
||||
"testnet": "0100000000000000000000000000000000000000000000000000000000000000000000003ba3edfd7a7b12b27ac72c3e67768f617fc81bc3888a51323a9fb8aa4b1e5e4adae5494dffff001d1aa4ae180101000000010000000000000000000000000000000000000000000000000000000000000000ffffffff4d04ffff001d0104455468652054696d65732030332f4a616e2f32303039204368616e63656c6c6f72206f6e206272696e6b206f66207365636f6e64206261696c6f757420666f722062616e6b73ffffffff0100f2052a01000000434104678afdb0fe5548271967f1a67130b7105cd6a828e03909a67962e0ea1f61deb649f6bc3f4cef38c4f35504e51ec112de5c384df7ba0b8d578a4c702b6bf11d5fac00000000"
|
||||
}
|
||||
203
lib/node.js
203
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});
|
||||
};
|
||||
@ -92,7 +85,7 @@ Node.prototype._loadBitcoinConf = function(config) {
|
||||
}
|
||||
}
|
||||
|
||||
$.checkState((this.bitcoinConfiguration.txindex && this.bitcoinConfiguration.txindex == 1),
|
||||
$.checkState((this.bitcoinConfiguration.txindex && this.bitcoinConfiguration.txindex == 1),
|
||||
'Txindex option is required in order to use most of the features of bitcore-node. \
|
||||
Please add "txindex=1" to your configuration and reindex an existing database if necessary with reindex=1');
|
||||
};
|
||||
@ -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);
|
||||
@ -135,31 +129,21 @@ Node.prototype._syncBitcoindAncestor = function(block, done) {
|
||||
// We only need to go back until we meet the main chain for the forked block
|
||||
// and thus don't need to find the entire chain of hashes.
|
||||
|
||||
async.whilst(function() {
|
||||
// Wait until the previous hash is in the current chain
|
||||
return ancestorHash && !currentHashesMap[ancestorHash];
|
||||
}, function(next) {
|
||||
self.bitcoind.getBlockIndex(ancestorHash, function(err, blockIndex) {
|
||||
if (err) {
|
||||
return next(err);
|
||||
}
|
||||
ancestorHash = blockIndex.prevHash;
|
||||
next();
|
||||
});
|
||||
}, function(err) {
|
||||
while(ancestorHash && !currentHashesMap[ancestorHash]) {
|
||||
var blockIndex = self.bitcoind.getBlockIndex(ancestorHash);
|
||||
ancestorHash = blockIndex ? blockIndex.prevHash : null;
|
||||
}
|
||||
|
||||
// Hash map is no-longer needed, quickly let
|
||||
// scavenging garbage collection know to cleanup
|
||||
currentHashesMap = null;
|
||||
// Hash map is no-longer needed, quickly let
|
||||
// scavenging garbage collection know to cleanup
|
||||
currentHashesMap = null;
|
||||
|
||||
if (err) {
|
||||
return done(err);
|
||||
} else if (!ancestorHash) {
|
||||
return done(new Error('Unknown common ancestor.'));
|
||||
}
|
||||
if (!ancestorHash) {
|
||||
return done(new Error('Unknown common ancestor.'));
|
||||
}
|
||||
|
||||
done(null, ancestorHash);
|
||||
|
||||
done(null, ancestorHash);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
@ -174,7 +158,9 @@ Node.prototype._syncBitcoindRewind = function(block, done) {
|
||||
var self = this;
|
||||
|
||||
self._syncBitcoindAncestor(block, function(err, ancestorHash) {
|
||||
|
||||
if (err) {
|
||||
return done(err);
|
||||
}
|
||||
// Rewind the chain to the common ancestor
|
||||
async.whilst(
|
||||
function() {
|
||||
@ -236,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) {
|
||||
@ -244,6 +230,7 @@ Node.prototype._syncBitcoind = function() {
|
||||
}
|
||||
|
||||
var block = self.Block.fromBuffer(blockBuffer);
|
||||
|
||||
if (block.prevHash === self.chain.tip.hash) {
|
||||
|
||||
// This block appends to the current chain tip and we can
|
||||
@ -252,22 +239,27 @@ Node.prototype._syncBitcoind = function() {
|
||||
// Populate height
|
||||
block.__height = self.chain.tip.__height + 1;
|
||||
|
||||
// Update chain hashes
|
||||
// Update chain.cache.hashes
|
||||
self.chain.cache.hashes[block.hash] = block.prevHash;
|
||||
|
||||
// Create indexes
|
||||
self.db._onChainAddBlock(block, function(err) {
|
||||
// Update chain.cache.chainHashes
|
||||
self.chain.getHashes(block.hash, function(err, hashes) {
|
||||
if (err) {
|
||||
return done(err);
|
||||
}
|
||||
|
||||
delete self.chain.tip.__transactions;
|
||||
self.chain.tip = block;
|
||||
log.debug('Saving metadata');
|
||||
self.chain.saveMetadata();
|
||||
log.debug('Chain added block to main chain');
|
||||
self.chain.emit('addblock', block);
|
||||
setImmediate(done);
|
||||
// Create indexes
|
||||
self.db._onChainAddBlock(block, function(err) {
|
||||
if (err) {
|
||||
return done(err);
|
||||
}
|
||||
delete self.chain.tip.__transactions;
|
||||
self.chain.tip = block;
|
||||
log.debug('Saving metadata');
|
||||
self.chain.saveMetadata();
|
||||
log.debug('Chain added block to main chain');
|
||||
self.chain.emit('addblock', block);
|
||||
setImmediate(done);
|
||||
});
|
||||
});
|
||||
|
||||
} else {
|
||||
@ -325,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');
|
||||
@ -341,46 +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;
|
||||
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() {
|
||||
@ -394,12 +373,24 @@ Node.prototype._initialize = function() {
|
||||
// Chain References
|
||||
this.chain.db = this.db;
|
||||
|
||||
// Bitcoind
|
||||
this.bitcoind.on('ready', function(status) {
|
||||
this._initializeBitcoind();
|
||||
this._initializeDatabase();
|
||||
this._initializeChain();
|
||||
|
||||
this.start(function(err) {
|
||||
if(err) {
|
||||
return self.emit('error', err);
|
||||
}
|
||||
self.emit('ready');
|
||||
});
|
||||
};
|
||||
|
||||
Node.prototype._initializeBitcoind = function() {
|
||||
var self = this;
|
||||
|
||||
// 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;
|
||||
});
|
||||
|
||||
// Notify that there is a new tip
|
||||
@ -407,7 +398,6 @@ Node.prototype._initialize = function() {
|
||||
if(!self.stopping) {
|
||||
var percentage = self.bitcoind.syncPercentage();
|
||||
log.info('Bitcoin Core Daemon New Height:', height, 'Percentage:', percentage);
|
||||
self.bitcoindHeight = height;
|
||||
self._syncBitcoind();
|
||||
}
|
||||
});
|
||||
@ -417,18 +407,10 @@ Node.prototype._initialize = function() {
|
||||
self.emit('error', err);
|
||||
});
|
||||
|
||||
// Chain
|
||||
this.chain.on('ready', function() {
|
||||
log.info('Bitcoin Chain Ready');
|
||||
self._syncBitcoind();
|
||||
});
|
||||
};
|
||||
|
||||
this.chain.on('error', function(err) {
|
||||
Error.captureStackTrace(err);
|
||||
self.emit('error', err);
|
||||
});
|
||||
|
||||
// Database
|
||||
Node.prototype._initializeDatabase = function() {
|
||||
var self = this;
|
||||
this.db.on('ready', function() {
|
||||
log.info('Bitcoin Database Ready');
|
||||
});
|
||||
@ -437,20 +419,32 @@ Node.prototype._initialize = function() {
|
||||
Error.captureStackTrace(err);
|
||||
self.emit('error', err);
|
||||
});
|
||||
};
|
||||
|
||||
this.start(function(err) {
|
||||
if(err) {
|
||||
return self.emit('error', err);
|
||||
}
|
||||
|
||||
self.emit('ready');
|
||||
Node.prototype._initializeChain = function() {
|
||||
var self = this;
|
||||
this.chain.on('ready', 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);
|
||||
@ -461,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);
|
||||
|
||||
@ -28,6 +28,9 @@ describe('Bitcoin Chain', function() {
|
||||
describe('#start', function() {
|
||||
it('should call the callback when base chain is initialized', function(done) {
|
||||
var chain = new Chain();
|
||||
chain.node = {};
|
||||
chain.node.bitcoind = {};
|
||||
chain.node.bitcoind.genesisBuffer = new Buffer('0100000043497fd7f826957108f4a30fd9cec3aeba79972084e90ead01ea330900000000bac8b0fa927c0ac8234287e33c5f74d38d354820e24756ad709d7038fc5f31f020e7494dffff001d03e4b6720101000000010000000000000000000000000000000000000000000000000000000000000000ffffffff0e0420e7494d017f062f503253482fffffffff0100f2052a010000002321021aeaf2f8638a129a3156fbe7e5ef635226b0bafd495ff03afe2c843d7e3a4b51ac00000000', 'hex');
|
||||
chain.initialize = function() {
|
||||
chain.emit('initialized');
|
||||
};
|
||||
|
||||
@ -227,18 +227,19 @@ 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: {
|
||||
__height: 0,
|
||||
hash: block.prevHash
|
||||
},
|
||||
getHashes: sinon.stub().callsArgWith(1, null),
|
||||
saveMetadata: sinon.stub(),
|
||||
emit: sinon.stub(),
|
||||
cache: {
|
||||
@ -258,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: {
|
||||
@ -276,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: {
|
||||
@ -433,42 +434,34 @@ 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() {
|
||||
var node = new Node({});
|
||||
node.chain = {
|
||||
on: sinon.spy()
|
||||
};
|
||||
node.Block = 'Block';
|
||||
node.bitcoind = {
|
||||
on: sinon.spy()
|
||||
};
|
||||
node.db = {
|
||||
on: sinon.spy()
|
||||
};
|
||||
|
||||
var node;
|
||||
|
||||
before(function() {
|
||||
node = new Node({});
|
||||
node.chain = {
|
||||
on: sinon.spy()
|
||||
};
|
||||
node.Block = 'Block';
|
||||
node.bitcoind = {
|
||||
on: sinon.spy()
|
||||
};
|
||||
node._initializeBitcoind = sinon.spy();
|
||||
node._initializeDatabase = sinon.spy();
|
||||
node._initializeChain = sinon.spy();
|
||||
node.db = {
|
||||
on: sinon.spy()
|
||||
};
|
||||
});
|
||||
|
||||
it('should initialize', function(done) {
|
||||
node.once('ready', function() {
|
||||
@ -486,8 +479,10 @@ describe('Bitcoind Node', function() {
|
||||
node.chain.db.should.equal(node.db);
|
||||
node.chain.db.should.equal(node.db);
|
||||
|
||||
// start syncing
|
||||
node.setSyncStrategy = sinon.spy();
|
||||
// event handlers
|
||||
node._initializeBitcoind.callCount.should.equal(1);
|
||||
node._initializeDatabase.callCount.should.equal(1);
|
||||
node._initializeChain.callCount.should.equal(1);
|
||||
|
||||
});
|
||||
|
||||
@ -497,26 +492,121 @@ describe('Bitcoind Node', function() {
|
||||
err.message.should.equal('error');
|
||||
done();
|
||||
});
|
||||
|
||||
node.start = sinon.stub().callsArgWith(0, new Error('error'));
|
||||
|
||||
node._initialize();
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe('#getServiceOrder', function() {
|
||||
var services = {
|
||||
'chain': ['db'],
|
||||
'db': ['daemon', 'p2p'],
|
||||
'daemon': [],
|
||||
'p2p': []
|
||||
};
|
||||
describe('#_initalizeBitcoind', function() {
|
||||
|
||||
it('will call emit an error from libbitcoind', function(done) {
|
||||
var node = new Node({});
|
||||
node.bitcoind = new EventEmitter();
|
||||
node.on('error', function(err) {
|
||||
should.exist(err);
|
||||
err.message.should.equal('test error');
|
||||
done();
|
||||
});
|
||||
node._initializeBitcoind();
|
||||
node.bitcoind.emit('error', new Error('test error'));
|
||||
});
|
||||
it('will call sync when there is a new tip', function(done) {
|
||||
var node = new Node({});
|
||||
node.bitcoind = new EventEmitter();
|
||||
node.bitcoind.syncPercentage = sinon.spy();
|
||||
node._syncBitcoind = function() {
|
||||
node.bitcoind.syncPercentage.callCount.should.equal(1);
|
||||
done();
|
||||
};
|
||||
node._initializeBitcoind();
|
||||
node.bitcoind.emit('tip', 10);
|
||||
});
|
||||
it('will not call sync when there is a new tip and shutting down', function(done) {
|
||||
var node = new Node({});
|
||||
node.bitcoind = new EventEmitter();
|
||||
node._syncBitcoind = sinon.spy();
|
||||
node.bitcoind.syncPercentage = sinon.spy();
|
||||
node.stopping = true;
|
||||
node.bitcoind.on('tip', function() {
|
||||
setImmediate(function() {
|
||||
node.bitcoind.syncPercentage.callCount.should.equal(0);
|
||||
node._syncBitcoind.callCount.should.equal(0);
|
||||
done();
|
||||
});
|
||||
});
|
||||
node._initializeBitcoind();
|
||||
node.bitcoind.emit('tip', 10);
|
||||
});
|
||||
});
|
||||
|
||||
describe('#_initializeDatabase', function() {
|
||||
it('will log on ready event', function(done) {
|
||||
var node = new Node({});
|
||||
node.db = new EventEmitter();
|
||||
sinon.stub(chainlib.log, 'info');
|
||||
node.db.on('ready', function() {
|
||||
setImmediate(function() {
|
||||
chainlib.log.info.callCount.should.equal(1);
|
||||
chainlib.log.info.restore();
|
||||
done();
|
||||
});
|
||||
});
|
||||
node._initializeDatabase();
|
||||
node.db.emit('ready');
|
||||
});
|
||||
it('will call emit an error from db', function(done) {
|
||||
var node = new Node({});
|
||||
node.db = new EventEmitter();
|
||||
node.on('error', function(err) {
|
||||
should.exist(err);
|
||||
err.message.should.equal('test error');
|
||||
done();
|
||||
});
|
||||
node._initializeDatabase();
|
||||
node.db.emit('error', new Error('test error'));
|
||||
});
|
||||
});
|
||||
|
||||
describe('#_initializeChain', function() {
|
||||
it('will call _syncBitcoind on ready', function(done) {
|
||||
var node = new Node({});
|
||||
node._syncBitcoind = sinon.spy();
|
||||
node.chain = new EventEmitter();
|
||||
node.chain.on('ready', function(err) {
|
||||
setImmediate(function() {
|
||||
node._syncBitcoind.callCount.should.equal(1);
|
||||
done();
|
||||
});
|
||||
});
|
||||
node._initializeChain();
|
||||
node.chain.emit('ready');
|
||||
});
|
||||
it('will emit an error from the chain', function(done) {
|
||||
var node = new Node({});
|
||||
node.chain = new EventEmitter();
|
||||
node.on('error', function(err) {
|
||||
should.exist(err);
|
||||
err.message.should.equal('test error');
|
||||
done();
|
||||
});
|
||||
node._initializeChain();
|
||||
node.chain.emit('error', new Error('test error'));
|
||||
});
|
||||
});
|
||||
|
||||
describe('#getServiceOrder', function() {
|
||||
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']);
|
||||
});
|
||||
});
|
||||
|
||||
Loading…
Reference in New Issue
Block a user