This commit is contained in:
Chris Kleeschulte 2017-07-21 18:47:50 -04:00
parent 4a294f7058
commit c91155cc35
6 changed files with 103 additions and 139 deletions

View File

@ -48,7 +48,7 @@ inherits(BlockService, BaseService);
BlockService.dependencies = [ 'p2p', 'db', 'header' ];
BlockService.MAX_BLOCKS = 250;
BlockService.MAX_BLOCKS = 10;
// --- public prototype functions
BlockService.prototype.getAPIMethods = function() {

View File

@ -12,6 +12,7 @@ var $ = bitcore.util.preconditions;
var Service = require('../../service');
var constants = require('../../constants');
var log = require('../../index').log;
var assert = require('assert');
function DB(options) {
@ -70,38 +71,10 @@ DB.prototype._setDataPath = function() {
}
};
// _checkVersion only governs db versions from bitcore-node >= 4.0
DB.prototype._checkVersion = function(callback) {
var self = this;
// presupposition is that IF there is a database to open -and- there is a version key
// in the form below, it will be related to us and it must be equal to this service's version.
var versionBuf = Buffer.concat([ self._dbPrefix, new Buffer('version', 'utf8') ]);
self.get(versionBuf, self.dbOptions, function(err, buffer) {
if (err) {
return callback(err);
}
var version;
if (buffer) {
version = buffer.readUInt32BE();
}
if (self.version !== version) {
return callback(new Error('The version of the database "' + version + '" does not match the expected version "'));
}
callback();
});
};
DB.prototype._setVersion = function(callback) {
var versionBuffer = new Buffer(new Array(4));
versionBuffer.writeUInt32BE(this.version);
this.put(this._dbPrefix + 'version', versionBuffer, callback);
this.put(Buffer.concat([ this._dbPrefix, new Buffer('version', 'utf8') ]), versionBuffer, callback);
};
DB.prototype.start = function(callback) {
@ -113,10 +86,7 @@ DB.prototype.start = function(callback) {
self._store = levelup(self.dataPath, { db: self.levelupStore, keyEncoding: 'binary', valueEncoding: 'binary'});
setImmediate(function() {
self._checkVersion(self._setVersion.bind(self, callback));
});
setImmediate(callback);
};
@ -154,6 +124,15 @@ DB.prototype.get = function(key, options, callback) {
};
DB.prototype.put = function(key, value, options) {
assert(Buffer.isBuffer(key), 'key NOT a buffer as expected.');
if (value) {
assert(Buffer.isBuffer(value), 'value exists but NOT a buffer as expected.');
}
var self = this;
if (self._stopping) {
@ -174,12 +153,24 @@ DB.prototype.put = function(key, value, options) {
};
DB.prototype.batch = function(ops, options) {
var self = this;
if (self._stopping) {
return;
}
for(var i = 0; i < ops.length; i++) {
assert(Buffer.isBuffer(ops[i].key), 'key NOT a buffer as expected.');
if (ops[i].value) {
assert(Buffer.isBuffer(ops[i].value), 'value exists but NOT a buffer as expected.');
}
}
self._operationsCount += ops.length;
self._store.batch(ops, options, function(err) {

View File

@ -132,39 +132,24 @@ HeaderService.prototype._onHeaders = function(headers, convert) {
});
}
var operations = this._getHeaderOperations(newHeaders);
var runningHeight = this._tip.height;
var prevHeader = Array.from(this._headers)[this._headers.length - 1];
for(var i = 0; i < headers.length; i++) {
var header = headers[i];
header.height = ++runningHeight;
header.chainwork = this._getChainwork(header, prevHeader).toString(16, 32);
prevHeader = header;
this._headers.set(header.hash, header);
}
this._tip.hash = newHeaders[newHeaders.length - 1].hash;
this._tip.height = this._tip.height + newHeaders.length;
this._db.batch(operations);
this._sync();
};
HeaderService.prototype._getHeaderOperations = function(headers) {
var self = this;
var runningHeight = this._tip.height;
// get the last header placed in the map
var prevHeader = Array.from(this._headers)[this._headers.length - 1];
return headers.map(function(header) {
header.height = ++runningHeight;
header.chainwork = self._getChainwork(header, prevHeader).toString(16, 32);
prevHeader = header;
// set the header in the in-memory map
self._headers.set(header.hash, header);
return {
type: 'put',
key: self._encoding.encodeHeaderKey(header.height, header.hash),
value: self._encoding.encodeHeaderValue(header)
};
});
};
HeaderService.prototype._setListeners = function() {
this._p2p.once('bestHeight', this._onBestHeight.bind(this));

View File

@ -54,13 +54,14 @@ MempoolService.prototype._startSubscriptions = function() {
MempoolService.prototype._onBlock = function(block) {
// remove this block's txs from mempool
var self = this;
var ops = block.txs.map(function(tx) {
return {
type: 'del',
key: tx.txid()
key: self._encoding.encodeMempoolTransactionKey(tx.txid())
};
});
this._db.batch(ops);
self._db.batch(ops);
};
MempoolService.prototype._onTransaction = function(tx) {

View File

@ -6,7 +6,7 @@ var BN = require('bn.js');
var assert = require('chai').assert;
var crypto = require('crypto');
var sinon = require('sinon');
var Block = require('bitcore-lib').Block;
var Block = require('bcoin').block;
var Encoding = require('../../../lib/services/block/encoding');
var LRU = require('lru-cache');
var constants = require('../../../lib/constants');
@ -105,16 +105,18 @@ describe('Block Service', 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._blockQueue = new LRU(5);
blockService._blockQueue.set('aa', { prevHash: '00' });
blockService._blockQueue.set('bb', { prevHash: 'aa' });
blockService._blockQueue.set('cc', { prevHash: 'aa' });
blockService._chainTips = [ 'cc', 'bb' ];
var commonAncestor = blockService._findCommonAncestor(block);
expect(commonAncestor).to.equal('aa');
it('should find the common ancestor between the current chain and the new chain', function(done) {
blockService._header = { getAllHeaders: sinon.stub().returns({}) };
blockService._db = { get: sinon.stub() };
blockService._chainTips = ['aa', 'bb'];
blockService._tip = { hash: 'aa' };
sandbox.stub(blockService, '_getOldBlocks').returns([]);
blockService._findCommonAncestor('cc');
blockService.on('common ancestor', function() {
done();
});
});
});
@ -142,19 +144,19 @@ 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 block = Block.fromRaw('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) });
expect(ops[0]).to.deep.equal({ type: 'put', key: blockService._encoding.encodeBlockKey(block.rhash()), 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 block = Block.fromRaw('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) });
expect(ops[0]).to.deep.equal({ type: 'put', key: blockService._encoding.encodeBlockKey(block.rhash()), value: blockService._encoding.encodeBlockValue(block) });
expect(ops[1]).to.deep.equal({ type: 'put', key: blockService._encoding.encodeBlockKey(block.rhash()), value: blockService._encoding.encodeBlockValue(block) });
});
@ -165,19 +167,6 @@ describe('Block Service', function() {
});
});
describe('#_getChainwork', function() {
it('should get chainwork', function() {
var expected = new BN(new Buffer('000000000000000000000000000000000000000000677c7b8122f9902c79f4e0', 'hex'));
blockService._meta = [ { chainwork: '000000000000000000000000000000000000000000677bd68118a98f8779ea90', hash: 'aa' } ];
blockService._blockQueue = LRU(1);
blockService._blockQueue.set('bb', { header: { bits: 0x18018d30 }});
var actual = blockService._getChainwork('bb');
assert(actual.eq(expected), 'not equal: actual: ' + actual + ' expected: ' + expected);
});
});
describe('#_getDelta', function() {
var sandbox;
@ -191,17 +180,25 @@ describe('Block Service', function() {
it('should get all unsent blocks for the active chain', function() {
var block1 = { header: { prevHash: new Buffer('00', 'hex') }};
var block2 = { header: { prevHash: new Buffer('aa', 'hex') }};
var block3 = { header: { prevHash: new Buffer('bb', 'hex') }};
var expected = [ block2, block3 ];
var toJSON1 = sinon.stub().returns({ prevBlock: 'bb' });
var toJSON2 = sinon.stub().returns({ prevBlock: 'aa' });
var toJSON3 = sinon.stub().returns({ prevBlock: '00' });
var blocks = [
{ toHeaders: sinon.stub().returns({ toJSON: toJSON3 }) },
{ toHeaders: sinon.stub().returns({ toJSON: toJSON2 }) },
{ toHeaders: sinon.stub().returns({ toJSON: toJSON1 }) }
];
var get = sandbox.stub();
get.onCall(0).returns(blocks[2]);
get.onCall(1).returns(blocks[1]);
blockService._blockQueue = { get: get };
var expected = [ blocks[1], blocks[2] ];
blockService._tip = { hash: 'aa' };
blockService._blockQueue = LRU(3);
blockService._blockQueue.set('aa', block1);
blockService._blockQueue.set('bb', block2);
blockService._blockQueue.set('cc', block3);
var actual = blockService._getDelta('cc');
expect(actual).to.deep.equal(expected);
});
});
@ -211,14 +208,16 @@ describe('Block Service', function() {
describe('#_isChainReorganizing', function() {
it('should decide that chain is reorging', function() {
var toJSON = sinon.stub().returns({ prevBlock: 'bb' });
var block = { toHeaders: sinon.stub().returns({ toJSON: toJSON }) };
blockService._tip = { hash: 'aa' };
var block = { header: { prevHash: new Buffer('00', 'hex') }};
expect(blockService._isChainReorganizing(block)).to.be.true;
});
it('should decide that chain is not reorging', function() {
var toJSON = sinon.stub().returns({ prevBlock: 'aa' });
var block = { toHeaders: sinon.stub().returns({ toJSON: toJSON }) };
blockService._tip = { hash: 'aa' };
var block = { header: { prevHash: new Buffer('aa', 'hex') }};
expect(blockService._isChainReorganizing(block)).to.be.false;
});
@ -226,67 +225,39 @@ describe('Block Service', function() {
describe('#_isOutOfOrder', function() {
beforeEach(function() {
var prevHash = '00000000';
for(var i = 0; i < 110; i++) {
var newHash = crypto.randomBytes(4);
var mock = { toBuffer: function() { return new Buffer(new Array(10), 'hex') } };
blockService._blockQueue.set(newHash, mock);
prevHash = newHash;
}
});
it('should detect an orphaned block', function() {
var block = { hash: 'ee', header: { prevHash: new Buffer('aa', 'hex') }};
blockService._chainTips = [ 'cc', 'dd' ];
var toJSON = sinon.stub().returns({ prevBlock: 'aa' });
var block = { toHeaders: sinon.stub().returns({ toJSON: toJSON }) };
expect(blockService._isOutOfOrder(block)).to.be.true;
});
it('should not detect an orphaned block', function() {
var block = { hash: 'new', header: { prevHash: '00' }};
var toJSON = sinon.stub().returns({ prevBlock: 'cc' });
var block = { toHeaders: sinon.stub().returns({ toJSON: toJSON }) };
expect(blockService._isOutOfOrder(block)).to.be.true;
});
});
describe('#_onBestHeight', function() {
var sandbox;
beforeEach(function() {
sandbox = sinon.sandbox.create();
});
after(function() {
sandbox.restore();
});
it('should set best height', function() {
var startSync = sandbox.stub(blockService, '_startSync');
blockService._onBestHeight(123);
expect(blockService._bestHeight).to.equal(123);
expect(startSync.calledOnce).to.be.true;
});
});
describe('#_onBlock', function() {
describe.only('#_onBlock', function() {
it('should perform all the steps for onBlock handler (normal)', function() {
var sandbox = sinon.sandbox.create();
var alreadyProcessed = sandbox.stub(blockService, '_blockAlreadyProcessed').returns(false);
var cacheBlock = sandbox.stub(blockService, '_cacheBlock');
var blockState = sandbox.stub(blockService, '_determineBlockState').returns('normal');
var updateChainTips = sandbox.stub(blockService, '_updateChainInfo');
var sendAllUnsent = sandbox.stub(blockService, '_sendDelta');
var saveMetaData = sandbox.stub(blockService, '_saveMetaData');
blockService._onBlock({ hash: 'aa' });
var rhash = sinon.stub().returns('aa');
var block = { rhash: rhash };
blockService._onBlock(block);
expect(alreadyProcessed.callCount).to.equal(1);
expect(cacheBlock.callCount).to.equal(1);
expect(blockState.callCount).to.equal(1);
expect(updateChainTips.callCount).to.equal(1);
expect(sendAllUnsent.callCount).to.equal(1);
expect(saveMetaData.callCount).to.equal(1);
sandbox.restore();
@ -295,7 +266,10 @@ describe('Block Service', function() {
it('should perform all the steps for onBlock handler (reorg)', function() {
var sandbox = sinon.sandbox.create();
var block = { hash: 'aa' };
var rhash = sinon.stub().returns('aa');
var block = { rhash: rhash };
var alreadyProcessed = sandbox.stub(blockService, '_blockAlreadyProcessed').returns(false);
var cacheBlock = sandbox.stub(blockService, '_cacheBlock');
var blockState = sandbox.stub(blockService, '_determineBlockState').returns('reorg');

View File

@ -134,6 +134,19 @@ describe('Header Service', function() {
});
});
describe('#_getChainwork', function() {
it('should get chainwork', function() {
var expected = new BN(new Buffer('000000000000000000000000000000000000000000677c7b8122f9902c79f4e0', 'hex'));
headerService._meta = [ { chainwork: '000000000000000000000000000000000000000000677bd68118a98f8779ea90', hash: 'aa' } ];
headerService._blockQueue = LRU(1);
headerService._blockQueue.set('bb', { header: { bits: 0x18018d30 }});
var actual = headerService._getChainwork('bb');
assert(actual.eq(expected), 'not equal: actual: ' + actual + ' expected: ' + expected);
});
});
describe('#_computeChainwork', function() {
it('should calculate chain work correctly', function() {