This commit is contained in:
Chris Kleeschulte 2017-07-05 19:12:25 -04:00
parent 314c37db49
commit 14c54a2fbf
4 changed files with 1042 additions and 662 deletions

File diff suppressed because it is too large Load Diff

View File

@ -36,7 +36,7 @@ function DB(options) {
this.subscriptions = {};
this._operationsQueue = [];
this._operationsQueue = 0;
}
util.inherits(DB, Service);
@ -142,30 +142,65 @@ DB.prototype.get = function(key, options, callback) {
};
DB.prototype.put = function(key, value, options, callback) {
var self = this;
var cb = callback;
var opts = options;
if (typeof callback !== 'function') {
cb = options;
opts = {};
}
if (!this._stopping) {
this._store.put(key, value, opts, cb);
if (!self._stopping) {
self._operationsQueue++;
self._store.put(key, value, opts, function(err) {
self._operationsQueue--;
if (err) {
self.emit('error', err);
return;
}
if (typeof cb === 'function') {
cb(err);
}
});
} else {
setImmediate(cb);
}
};
DB.prototype.batch = function(ops, options, callback) {
var self = this;
var cb = callback;
var opts = options;
if (typeof callback !== 'function') {
cb = options;
opts = {};
}
if (!this._stopping) {
this._store.batch(ops, opts, cb);
} else if (cb) {
if (!self._stopping) {
self._operationsQueue += ops.length;
self._store.batch(ops, opts, function(err) {
self._operationsQueue -= ops.length;
if (err) {
self.emit('error', err);
return;
}
if (typeof cb === 'function') {
cb(err);
}
});
} else {
setImmediate(cb);
}
};

View File

@ -1,10 +1,7 @@
'use strict';
var assert = require('assert');
var BaseService = require('../../service');
var inherits = require('util').inherits;
var index = require('../../');
var log = index.log;
var BitcoreRPC = require('bitcoind-rpc');
var FeeService = function(options) {

View File

@ -10,6 +10,7 @@ var Block = require('bitcore-lib').Block;
var Encoding = require('../../../lib/services/block/encoding');
var EventEmitter = require('events').EventEmitter;
var LRU = require('lru-cache');
var constants = require('../../../lib/constants');
describe('Block Service', function() {
@ -32,63 +33,156 @@ describe('Block Service', function() {
});
describe('#_updateChainTips', function() {
describe('#_cacheBlock', function() {
it('should set chain tips under normal block arrival conditions, in order arrival' , function() {
var blocks = ['aa','bb','cc','dd','ee'];
blocks.forEach(function(n, index) {
var buf = new Buffer('00', 'hex');
if (index) {
buf = new Buffer(blocks[index-1], 'hex');
}
var block = { header: { prevHash: buf }, hash: n };
blockService._updateChainTips(block, 'normal');
});
expect(blockService._chainTips.length).to.equal(1);
expect(blockService._chainTips).to.deep.equal(['ee']);
it('should set the block in the block queue and db', function() {
var sandbox = sinon.sandbox.create();
var spy1 = sandbox.spy();
var stub1 = sandbox.stub();
blockService._blockQueue = { set: stub1 };
var block = {};
sandbox.stub(blockService, '_getBlockOperations');
blockService._db = { batch: spy1 };
blockService._cacheBlock(block);
expect(spy1.calledOnce).to.be.true;
expect(stub1.calledOnce).to.be.true;
sandbox.restore();
});
it('should not set chain tips if not in normal or reorg state' , function() {
var block = { header: { prevHash: new Buffer('aa', 'hex') }};
blockService._updateChainTips(block, 'orphan');
expect(blockService._chainTips).to.deep.equal(['00']);
});
it('should set chain tips when there is a reorg taking place' , function() {
var block = { hash: 'ee', header: { prevHash: 'dd' } };
blockService._updateChainTips(block, 'reorg');
expect(blockService._chainTips).to.deep.equal(['00', 'ee']);
});
describe('#_cacheHeader', function() {
it('should cache header', function() {
var toObj = sinon.stub().returns('header');
var header = { hash: 'aa', toObject: toObj };
blockService._blockHeaderQueue = LRU(1);
blockService._cacheHeader(header);
expect(toObj.calledOnce);
expect(blockService._blockHeaderQueue.get('aa')).to.equal('header');
});
});
describe('#_isOrphanBlock', function() {
describe('#_checkChain', function() {
var sandbox;
beforeEach(function() {
var prevHash = '00000000';
for(var i = 0; i < 110; i++) {
var newHash = crypto.randomBytes(4);
blockService._blockHeaderQueue.set(newHash, { prevHash: new Buffer(prevHash, 'hex') });
prevHash = newHash;
}
sandbox = sinon.sandbox.create();
});
it('should detect an orphaned block', function() {
var block = { hash: 'ee', header: { prevHash: new Buffer('aa', 'hex') }};
expect(blockService._isOrphanBlock(block)).to.be.true;
after(function() {
sandbox.restore();
});
it('should not detect an orphaned block', function() {
var block = { hash: 'new', header: { prevHash: '00' }};
expect(blockService._isOrphanBlock(block)).to.be.true;
it('should check that blocks between the active chain tip and the block service tip are in the same chain and the chain is complete.', function() {
blockService._tip = { hash: 'aa' };
blockService._blockHeaderQueue = LRU(5);
blockService._blockQueue = LRU(5);
blockService._blockHeaderQueue.set('cc', { prevHash: 'bb' });
blockService._blockHeaderQueue.set('bb', { prevHash: 'aa' });
blockService._blockQueue.set('bb', 'block cc');
blockService._blockQueue.set('cc', 'block bb');
var result = blockService._checkChain('cc');
expect(result).to.be.true;
blockService._blockQueue.reset();
blockService._blockHeaderQueue.reset();
});
it('should check that blocks between the active chain tip and the block service tip are in a different chain.', function() {
blockService._tip = { hash: 'aa' };
blockService._blockHeaderQueue = LRU(5);
blockService._blockQueue = LRU(5);
blockService._blockHeaderQueue.set('cc', { prevHash: 'xx' });
blockService._blockHeaderQueue.set('bb', { prevHash: 'aa' });
blockService._blockQueue.set('bb', 'block cc');
blockService._blockQueue.set('cc', 'block bb');
var result = blockService._checkChain('cc');
expect(result).to.be.false;
});
});
describe('#_computeChainwork', function() {
it('should calculate chain work correctly', function() {
var expected = new BN(new Buffer('000000000000000000000000000000000000000000677c7b8122f9902c79f4e0', 'hex'));
var prev = new BN(new Buffer('000000000000000000000000000000000000000000677bd68118a98f8779ea90', 'hex'));
var actual = blockService._computeChainwork(0x18018d30, prev);
assert(actual.eq(expected), 'not equal: actual: ' + actual + ' expected: ' + expected);
});
});
describe('#_determineBlockState', function() {
it('should determine the block in a normal state', function() {
var sandbox = sinon.sandbox.create();
var stub1 = sandbox.stub(blockService, '_isChainReorganizing').returns(false);
var stub2 = sandbox.stub(blockService, '_isOrphanBlock').returns(false);
expect(blockService._determineBlockState({})).to.equal('normal');
sandbox.restore();
});
it('should determine the block in a orphan state', function() {
var sandbox = sinon.sandbox.create();
var stub1 = sandbox.stub(blockService, '_isChainReorganizing').returns(false);
var stub2 = sandbox.stub(blockService, '_isOrphanBlock').returns(true);
expect(blockService._determineBlockState({})).to.equal('orphaned');
sandbox.restore();
});
it('should determine the block in a reorg state', function() {
var sandbox = sinon.sandbox.create();
var stub1 = sandbox.stub(blockService, '_isChainReorganizing').returns(true);
var stub2 = sandbox.stub(blockService, '_isOrphanBlock').returns(false);
expect(blockService._determineBlockState({})).to.equal('reorg');
sandbox.restore();
});
});
describe('#_findCommonAncestor', function() {
var sandbox;
beforeEach(function() {
sandbox = sinon.sandbox.create();
});
after(function() {
sandbox.restore();
});
it('should find the common ancestor between the current chain and the new chain', function() {
var block = { hash: 'cc' };
blockService._tip = { hash: 'bb' }
blockService._blockHeaderQueue = new LRU(5);
blockService._blockHeaderQueue.set('aa', { prevHash: '00' });
blockService._blockHeaderQueue.set('bb', { prevHash: 'aa' });
blockService._blockHeaderQueue.set('cc', { prevHash: 'aa' });
blockService._chainTips = [ 'cc', 'bb' ];
var commonAncestor = blockService._findCommonAncestor(block);
expect(commonAncestor).to.equal('aa');
});
});
describe('#_getBlockOperations', function() {
it('should get block operations when given one block', function() {
var block = new Block(new Buffer('0100000095194b8567fe2e8bbda931afd01a7acd399b9325cb54683e64129bcd00000000660802c98f18fd34fd16d61c63cf447568370124ac5f3be626c2e1c3c9f0052d19a76949ffff001d33f3c25d0101000000010000000000000000000000000000000000000000000000000000000000000000ffffffff0704ffff001d014dffffffff0100f2052a01000000434104e70a02f5af48a1989bf630d92523c9d14c45c75f7d1b998e962bff6ff9995fc5bdb44f1793b37495d80324acba7c8f537caaf8432b8d47987313060cc82d8a93ac00000000', 'hex'));
var ops = blockService._getBlockOperations(block);
expect(ops[0]).to.deep.equal({ type: 'put', key: blockService._encoding.encodeBlockKey(block.hash), value: blockService._encoding.encodeBlockValue(block) });
});
it('should get block operations when given more than one block', function() {
var block = new Block(new Buffer('0100000095194b8567fe2e8bbda931afd01a7acd399b9325cb54683e64129bcd00000000660802c98f18fd34fd16d61c63cf447568370124ac5f3be626c2e1c3c9f0052d19a76949ffff001d33f3c25d0101000000010000000000000000000000000000000000000000000000000000000000000000ffffffff0704ffff001d014dffffffff0100f2052a01000000434104e70a02f5af48a1989bf630d92523c9d14c45c75f7d1b998e962bff6ff9995fc5bdb44f1793b37495d80324acba7c8f537caaf8432b8d47987313060cc82d8a93ac00000000', 'hex'));
var ops = blockService._getBlockOperations([block, block]);
expect(ops[0]).to.deep.equal({ type: 'put', key: blockService._encoding.encodeBlockKey(block.hash), value: blockService._encoding.encodeBlockValue(block) });
expect(ops[1]).to.deep.equal({ type: 'put', key: blockService._encoding.encodeBlockKey(block.hash), value: blockService._encoding.encodeBlockValue(block) });
});
});
@ -114,52 +208,31 @@ describe('Block Service', function() {
});
});
describe('#_getDelta', function() {
describe('#_computeChainwork', function() {
it('should calculate chain work correctly', function() {
var expected = new BN(new Buffer('000000000000000000000000000000000000000000677c7b8122f9902c79f4e0', 'hex'));
var prev = new BN(new Buffer('000000000000000000000000000000000000000000677bd68118a98f8779ea90', 'hex'));
var actual = blockService._computeChainwork(0x18018d30, prev);
assert(actual.eq(expected), 'not equal: actual: ' + actual + ' expected: ' + expected);
var sandbox;
beforeEach(function() {
sandbox = sinon.sandbox.create();
});
});
describe('#_selectActiveChain', function() {
it('should select active chain based on most chain work', function() {
blockService._blockHeaderQueue.set('cc', { prevHash: '00', bits: 0x18018d30 });
blockService._blockHeaderQueue.set('bb', { prevHash: 'aa', bits: 0x18018d30 });
blockService._blockHeaderQueue.set('aa', { chainwork: '000000000000000000000000000000000000000000677bd68118a98f8779ea90'});
blockService._blockHeaderQueue.set('00', { chainwork: '000000000000000000000000000000000000000000677bd68118a98f8779ea8f'});
blockService._chainTips.push('bb');
blockService._chainTips.push('cc');
var expected = 'bb';
var actual = blockService._selectActiveChain();
expect(actual).to.equal(expected);
});
});
describe('#_cacheBlock', function() {
it('should set the block in the block queue and db', function() {
var sandbox = sinon.sandbox.create();
var spy1 = sandbox.spy();
var stub1 = sandbox.stub();
blockService._blockQueue = { set: stub1 };
var block = {};
sandbox.stub(blockService, '_getBlockOperations');
blockService._db = { batch: spy1 };
blockService._cacheBlock(block);
expect(spy1.calledOnce).to.be.true;
expect(stub1.calledOnce).to.be.true;
after(function() {
sandbox.restore();
});
it('should get all unsent blocks for the active chain', function() {
var expected = [ 'block bb', 'block cc' ];
blockService._tip = { hash: 'aa' };
blockService._blockHeaderQueue = LRU(5);
blockService._blockQueue = LRU(5);
blockService._blockHeaderQueue.set('cc', { prevHash: 'bb' });
blockService._blockHeaderQueue.set('bb', { prevHash: 'aa' });
blockService._blockQueue.set('bb', 'block cc');
blockService._blockQueue.set('aa', 'block 00');
blockService._blockQueue.set('cc', 'block bb');
var actual = blockService._getDelta('cc');
expect(actual).to.deep.equal(expected);
});
});
describe('#_isChainReorganizing', function() {
@ -178,23 +251,77 @@ describe('Block Service', function() {
});
describe('#_getBlockOperations', function() {
it('should get block operations when given one block', function() {
var block = new Block(new Buffer('0100000095194b8567fe2e8bbda931afd01a7acd399b9325cb54683e64129bcd00000000660802c98f18fd34fd16d61c63cf447568370124ac5f3be626c2e1c3c9f0052d19a76949ffff001d33f3c25d0101000000010000000000000000000000000000000000000000000000000000000000000000ffffffff0704ffff001d014dffffffff0100f2052a01000000434104e70a02f5af48a1989bf630d92523c9d14c45c75f7d1b998e962bff6ff9995fc5bdb44f1793b37495d80324acba7c8f537caaf8432b8d47987313060cc82d8a93ac00000000', 'hex'));
var ops = blockService._getBlockOperations(block);
expect(ops[0]).to.deep.equal({ type: 'put', key: blockService._encoding.encodeBlockKey(block.hash), value: blockService._encoding.encodeBlockValue(block) });
describe('#_isOrphanBlock', function() {
beforeEach(function() {
var prevHash = '00000000';
for(var i = 0; i < 110; i++) {
var newHash = crypto.randomBytes(4);
blockService._blockHeaderQueue.set(newHash, { prevHash: new Buffer(prevHash, 'hex') });
prevHash = newHash;
}
});
it('should get block operations when given more than one block', function() {
var block = new Block(new Buffer('0100000095194b8567fe2e8bbda931afd01a7acd399b9325cb54683e64129bcd00000000660802c98f18fd34fd16d61c63cf447568370124ac5f3be626c2e1c3c9f0052d19a76949ffff001d33f3c25d0101000000010000000000000000000000000000000000000000000000000000000000000000ffffffff0704ffff001d014dffffffff0100f2052a01000000434104e70a02f5af48a1989bf630d92523c9d14c45c75f7d1b998e962bff6ff9995fc5bdb44f1793b37495d80324acba7c8f537caaf8432b8d47987313060cc82d8a93ac00000000', 'hex'));
var ops = blockService._getBlockOperations([block, block]);
it('should detect an orphaned block', function() {
var block = { hash: 'ee', header: { prevHash: new Buffer('aa', 'hex') }};
expect(blockService._isOrphanBlock(block)).to.be.true;
});
expect(ops[0]).to.deep.equal({ type: 'put', key: blockService._encoding.encodeBlockKey(block.hash), value: blockService._encoding.encodeBlockValue(block) });
expect(ops[1]).to.deep.equal({ type: 'put', key: blockService._encoding.encodeBlockKey(block.hash), value: blockService._encoding.encodeBlockValue(block) });
it('should not detect an orphaned block', function() {
var block = { hash: 'new', header: { prevHash: '00' }};
expect(blockService._isOrphanBlock(block)).to.be.true;
});
});
describe('#_loadTip', function() {
var tip = { hash: 'aa', height: 1 };
var sandbox;
var testEmitter = new EventEmitter();
beforeEach(function() {
sandbox = sinon.sandbox.create();
blockService._db = {
getServiceTip: function(name) {
testEmitter.emit('tip-' + name, tip);
}
};
});
after(function() {
sandbox.restore();
});
it('should load the tip from the db service', function() {
testEmitter.on('tip-block', function(_tip) {
expect(_tip).to.deep.equal(tip);
});
blockService._loadTip();
});
});
describe('#_onBestHeight', function() {
var sandbox;
beforeEach(function() {
sandbox = sinon.sandbox.create();
});
after(function() {
sandbox.restore();
});
it('should set best height', function() {
var tips = sandbox.stub();
var loadTip = sandbox.stub(blockService, '_loadTip');
blockService._db = { once: tips };
blockService._onBestHeight(123);
expect(blockService._bestHeight).to.equal(123);
expect(tips.calledTwice).to.be.true;
expect(loadTip.calledOnce).to.be.true;
});
});
@ -263,34 +390,256 @@ describe('Block Service', function() {
});
});
describe('#_determineBlockState', function() {
describe('#_onDbError', function() {
it('should handle db errors', function() {
var stop = sinon.stub();
blockService.node = { stop: stop };
expect(stop.calledOnce);
});
});
it('should determine the block in a normal state', function() {
var sandbox = sinon.sandbox.create();
var stub1 = sandbox.stub(blockService, '_isChainReorganizing').returns(false);
var stub2 = sandbox.stub(blockService, '_isOrphanBlock').returns(false);
expect(blockService._determineBlockState({})).to.equal('normal');
describe('#_onHeaders', function() {
var sandbox;
beforeEach(function() {
sandbox = sinon.sandbox.create();
});
after(function() {
sandbox.restore();
});
it('should determine the block in a orphan state', function() {
var sandbox = sinon.sandbox.create();
var stub1 = sandbox.stub(blockService, '_isChainReorganizing').returns(false);
var stub2 = sandbox.stub(blockService, '_isOrphanBlock').returns(true);
expect(blockService._determineBlockState({})).to.equal('orphaned');
sandbox.restore();
});
it('should determine the block in a reorg state', function() {
var sandbox = sinon.sandbox.create();
var stub1 = sandbox.stub(blockService, '_isChainReorganizing').returns(true);
var stub2 = sandbox.stub(blockService, '_isOrphanBlock').returns(false);
expect(blockService._determineBlockState({})).to.equal('reorg');
sandbox.restore();
it('should handle header delivery', function() {
var headers = [ { hash: '00' }, { hash: 'aa' } ];
var cache = sandbox.stub(blockService, '_cacheHeaders');
blockService._onHeaders(headers);
expect(cache.calledOnce).to.be.true;
expect(blockService._latestHeaderHash).to.equal('aa');
});
});
describe('#_onTipHeader', function() {
var sandbox;
beforeEach(function() {
sandbox = sinon.sandbox.create();
});
after(function() {
sandbox.restore();
});
it('should set initial tip after receiving from db', function() {
var startSubs = sandbox.stub(blockService, '_startSubscriptions');
var startSync = sandbox.stub(blockService, '_startSync');
var tip = { height: 100, hash: 'aa' };
blockService._onTipHeader(tip);
expect(blockService._headerTip).to.deep.equal(tip);
expect(startSubs.calledOnce).to.be.true;
expect(startSync.calledOnce).to.be.true;
});
it('should set initial tip after not receiving from db', function() {
var startSubs = sandbox.stub(blockService, '_startSubscriptions');
var startSync = sandbox.stub(blockService, '_startSync');
var tip = null;
blockService.node = { getNetworkName: function() { return 'regtest'; } };
blockService._onTipHeader(tip);
expect(blockService._headerTip).to.deep.equal({ height: 0, hash: constants.BITCOIN_GENESIS_HASH['regtest']});
expect(startSubs.calledOnce).to.be.true;
expect(startSync.calledOnce).to.be.true;
});
});
describe('#_onTipBlock', function() {
var sandbox;
beforeEach(function() {
sandbox = sinon.sandbox.create();
});
after(function() {
sandbox.restore();
});
it('should set initial tip after receiving from db', function() {
var tip = { height: 100, hash: 'aa' };
blockService._onTipBlock(tip);
expect(blockService._tip).to.deep.equal(tip);
});
it('should set initial tip after not receiving from db', function() {
var tip = null;
blockService.node = { getNetworkName: function() { return 'regtest'; } };
blockService._onTipBlock(tip);
expect(blockService._tip).to.deep.equal({ height: 0, hash: constants.BITCOIN_GENESIS_HASH['regtest']});
});
});
describe('#_reportBootStatus', function() {
it('should report info on boot', function() {
blockService._tip = { height: 100, hash: 'aa' };
blockService._reportBootStatus();
});
});
describe('#_selectActiveChain', function() {
it('should select active chain based on most chain work', function() {
blockService._blockHeaderQueue.set('cc', { prevHash: '00', bits: 0x18018d30 });
blockService._blockHeaderQueue.set('bb', { prevHash: 'aa', bits: 0x18018d30 });
blockService._blockHeaderQueue.set('aa', { chainwork: '000000000000000000000000000000000000000000677bd68118a98f8779ea90'});
blockService._blockHeaderQueue.set('00', { chainwork: '000000000000000000000000000000000000000000677bd68118a98f8779ea8f'});
blockService._chainTips.push('bb');
blockService._chainTips.push('cc');
var expected = 'bb';
var actual = blockService._selectActiveChain();
expect(actual).to.equal(expected);
});
});
describe('#_sendDelta', function() {
var sandbox;
beforeEach(function() {
sandbox = sinon.sandbox.create();
});
after(function() {
sandbox.restore();
});
it('should send all unsent blocks for the active chain', function() {
var activeChain = sandbox.stub(blockService, '_selectActiveChain').returns('aa');
var checkChain = sandbox.stub(blockService, '_checkChain').returns(true);
var getDelta = sandbox.stub(blockService, '_getDelta').returns(['aa', '00']);
var broadcast = sandbox.stub(blockService, '_broadcast');
var setTip = sandbox.stub(blockService, '_setTip');
blockService._sendDelta();
expect(activeChain.calledOnce).to.be.true;
expect(checkChain.calledOnce).to.be.true;
expect(getDelta.calledOnce).to.be.true;
expect(broadcast.calledTwice).to.be.true;
expect(setTip.calledOnce).to.be.true;
});
});
describe('#_setListeners', function() {
var sandbox;
beforeEach(function() {
sandbox = sinon.sandbox.create();
});
after(function() {
sandbox.restore();
});
it('should set listeners for bestHeight, db error and reorg', function() {
var bestHeight = sandbox.stub();
var dbError = sandbox.stub();
var on = sandbox.stub(blockService, 'on');
blockService._p2p = { once: bestHeight };
blockService._db = { on: dbError };
blockService._setListeners();
expect(bestHeight.calledOnce).to.be.true;
expect(dbError.calledOnce).to.be.true;
expect(on.calledOnce).to.be.true;
});
});
describe('#_setTip', function() {
it('should set the tip if given a block', function() {
var stub = sinon.stub();
blockService._db = {};
blockService._db.setServiceTip = stub;
blockService._tip = { height: 99, hash: '00' };
blockService._setTip({ height: 100, hash: 'aa' });
expect(stub.calledOnce).to.be.true;
});
});
describe('#_startSubscriptions', function() {
it('should start the subscriptions if not already subscribed', function() {
var on = sinon.stub();
var subscribe = sinon.stub();
var openBus = sinon.stub().returns({ on: on, subscribe: subscribe });
blockService.node = { openBus: openBus };
blockService._startSubscriptions();
expect(blockService._subscribed).to.be.true;
expect(openBus.calledOnce).to.be.true;
expect(on.calledTwice).to.be.true;
expect(subscribe.calledTwice).to.be.true;
});
});
describe('#_startSync', function() {
var sandbox;
beforeEach(function() {
sandbox = sinon.sandbox.create();
blockService.node.getNetworkName = function() { return 'regtest'; };
blockService._tip = { height: 100 };
blockService._bestHeight = 201;
});
after(function() {
sandbox.restore();
});
it('should start the sync of headers if type set', function() {
blockService._tip = { height: 100 };
blockService._bestHeight = 200;
var stub = sandbox.stub(blockService, '_sync');
blockService._startSync('header');
expect(stub.calledOnce).to.be.true;
expect(stub.calledWith('header')).to.be.true;
expect(blockService._latestHeaderHash).to.equal(constants.BITCOIN_GENESIS_HASH[blockService.node.getNetworkName()]);
expect(blockService._p2pHeaderCallsNeeded).to.equal(1);
});
it('should start the sync of blocks if type set', function() {
var stub = sandbox.stub(blockService, '_sync');
blockService._startSync('block');
expect(stub.calledOnce).to.be.true;
expect(stub.calledWith('block')).to.be.true;
expect(blockService._latestBlockHash).to.equal(constants.BITCOIN_GENESIS_HASH[blockService.node.getNetworkName()]);
expect(blockService._p2pBlockCallsNeeded).to.equal(1);
});
});
describe('#_sync', function() {
it('should sync blocks', function() {
blockService._p2pBlockCallsNeeded = 2;
var getBlocks = sinon.stub();
blockService._p2p = { getBlocks: getBlocks };
blockService._sync('block');
expect(blockService._p2pBlockCallsNeeded).to.equal(1);
expect(getBlocks.calledOnce).to.be.true;
});
});
describe('#_sync', function() {
it('should sync headers', function() {
blockService._p2pHeaderCallsNeeded = 2;
var getHeaders = sinon.stub();
blockService._p2p = { getHeaders: getHeaders };
blockService._sync('header');
expect(blockService._p2pHeaderCallsNeeded).to.equal(1);
expect(getHeaders.calledOnce).to.be.true;
});
});
describe('#start', function() {
var sandbox;
@ -322,128 +671,43 @@ describe('Block Service', function() {
});
describe('#_loadTip', function() {
describe('#_updateChainTips', function() {
var tip = { hash: 'aa', height: 1 };
var sandbox;
var testEmitter = new EventEmitter();
it('should set chain tips under normal block arrival conditions, in order arrival' , function() {
beforeEach(function() {
sandbox = sinon.sandbox.create();
var blocks = ['aa','bb','cc','dd','ee'];
blockService._db = {
blocks.forEach(function(n, index) {
getServiceTip: function(name) {
testEmitter.emit('tip-' + name, tip);
var buf = new Buffer('00', 'hex');
if (index) {
buf = new Buffer(blocks[index-1], 'hex');
}
};
});
after(function() {
sandbox.restore();
});
it('should load the tip from the db service', function() {
testEmitter.on('tip-block', function(_tip) {
expect(_tip).to.deep.equal(tip);
var block = { header: { prevHash: buf }, hash: n };
blockService._updateChainTips(block, 'normal');
});
blockService._loadTip();
expect(blockService._chainTips.length).to.equal(1);
expect(blockService._chainTips).to.deep.equal(['ee']);
});
it('should not set chain tips if not in normal or reorg state' , function() {
var block = { header: { prevHash: new Buffer('aa', 'hex') }};
blockService._updateChainTips(block, 'orphan');
expect(blockService._chainTips).to.deep.equal(['00']);
});
it('should set chain tips when there is a reorg taking place' , function() {
var block = { hash: 'ee', header: { prevHash: 'dd' } };
blockService._updateChainTips(block, 'reorg');
expect(blockService._chainTips).to.deep.equal(['00', 'ee']);
});
});
describe('#_sendDelta', function() {
var sandbox;
beforeEach(function() {
sandbox = sinon.sandbox.create();
});
after(function() {
sandbox.restore();
});
it('should send all unsent blocks for the active chain', function() {
var activeChain = sandbox.stub(blockService, '_selectActiveChain').returns('aa');
var checkChain = sandbox.stub(blockService, '_checkChain').returns(true);
var getDelta = sandbox.stub(blockService, '_getDelta').returns(['aa', '00']);
var broadcast = sandbox.stub(blockService, '_broadcast');
var setTip = sandbox.stub(blockService, '_setTip');
blockService._sendDelta();
expect(activeChain.calledOnce).to.be.true;
expect(checkChain.calledOnce).to.be.true;
expect(getDelta.calledOnce).to.be.true;
expect(broadcast.calledTwice).to.be.true;
expect(setTip.calledOnce).to.be.true;
});
});
describe('#_getDelta', function() {
var sandbox;
beforeEach(function() {
sandbox = sinon.sandbox.create();
});
after(function() {
sandbox.restore();
});
it('should get all unsent blocks for the active chain', function() {
var expected = [ 'block bb', 'block cc' ];
blockService._tip = { hash: 'aa' };
blockService._blockHeaderQueue = LRU(5);
blockService._blockQueue = LRU(5);
blockService._blockHeaderQueue.set('cc', { prevHash: 'bb' });
blockService._blockHeaderQueue.set('bb', { prevHash: 'aa' });
blockService._blockQueue.set('bb', 'block cc');
blockService._blockQueue.set('aa', 'block 00');
blockService._blockQueue.set('cc', 'block bb');
var actual = blockService._getDelta('cc');
expect(actual).to.deep.equal(expected);
});
});
describe('#_checkChain', function() {
var sandbox;
beforeEach(function() {
sandbox = sinon.sandbox.create();
});
after(function() {
sandbox.restore();
});
it('should check that blocks between the active chain tip and the block service tip are in the same chain and the chain is complete.', function() {
blockService._tip = { hash: 'aa' };
blockService._blockHeaderQueue = LRU(5);
blockService._blockQueue = LRU(5);
blockService._blockHeaderQueue.set('cc', { prevHash: 'bb' });
blockService._blockHeaderQueue.set('bb', { prevHash: 'aa' });
blockService._blockQueue.set('bb', 'block cc');
blockService._blockQueue.set('cc', 'block bb');
var result = blockService._checkChain('cc');
expect(result).to.be.true;
blockService._blockQueue.reset();
blockService._blockHeaderQueue.reset();
});
it('should check that blocks between the active chain tip and the block service tip are in a different chain.', function() {
blockService._tip = { hash: 'aa' };
blockService._blockHeaderQueue = LRU(5);
blockService._blockQueue = LRU(5);
blockService._blockHeaderQueue.set('cc', { prevHash: 'xx' });
blockService._blockHeaderQueue.set('bb', { prevHash: 'aa' });
blockService._blockQueue.set('bb', 'block cc');
blockService._blockQueue.set('cc', 'block bb');
var result = blockService._checkChain('cc');
expect(result).to.be.false;
});
});
});