Refactored onHeaders.

This commit is contained in:
Chris Kleeschulte 2017-08-11 11:49:57 -04:00
parent c7ee57f224
commit a1fbf20317
4 changed files with 107 additions and 152 deletions

View File

@ -1,22 +0,0 @@
{
"network": "regtest",
"port": 3001,
"datadir": "/Users/chrisk",
"services": [
"p2p",
"db",
"transaction",
"timestamp",
"address",
"mempool",
"wallet-api",
"web"
],
"servicesConfig": {
"p2p": {
"connect": [
{ "rpchost": "127.0.0.1", "rpcport": 58332, "rpcuser": "bitcoin", "rpcpassword": "local321", "zmqpubrawtx": "tcp://127.0.0.1:28332" }
]
}
}
}

View File

@ -11,7 +11,6 @@ var BN = require('bn.js');
var consensus = require('bcoin').consensus;
var assert = require('assert');
var constants = require('../../constants');
var Header = require('bitcore-lib').BlockHeader;
var HeaderService = function(options) {
@ -26,6 +25,7 @@ var HeaderService = function(options) {
this.subscriptions.block = [];
this._checkpoint = options.checkpoint || 2000;
this.GENESIS_HASH = constants.BITCOIN_GENESIS_HASH[this.node.network];
this._initiallySynced = false;
};
inherits(HeaderService, BaseService);
@ -97,7 +97,7 @@ HeaderService.prototype.start = function(callback) {
log.debug('Header Service: original tip height is: ' + self._tip.height);
log.debug('Header Service: original tip hash is: ' + self._tip.hash);
self._originalTip = Object.assign(self._tip, {});
self._originalTip = { height: self._tip.height, hash: self._tip.hash };
if (self._tip.height === 0) {
@ -134,7 +134,8 @@ HeaderService.prototype.start = function(callback) {
}
self._setListeners();
self._startSubscriptions();
self._bus = self.node.openBus({remoteAddress: 'localhost-header'});
self._startHeaderSubscription();
callback();
});
@ -145,23 +146,11 @@ HeaderService.prototype.stop = function(callback) {
setImmediate(callback);
};
HeaderService.prototype._startSubscriptions = function() {
HeaderService.prototype._startHeaderSubscription = function() {
if (this._subscribed) {
return;
}
this._subscribed = true;
if (!this._bus) {
this._bus = this.node.openBus({remoteAddress: 'localhost-header'});
}
this._bus.on('p2p/headers', this._onHeaders.bind(this));
this._bus.on('p2p/block', this._onBlock.bind(this));
this._bus.subscribe('p2p/headers');
this._bus.subscribe('p2p/block');
};
@ -184,13 +173,10 @@ HeaderService.prototype._onBlock = function(block) {
log.debug('Header Service: new block: ' + hash);
var header = this._headers.get(hash);
if (!header) {
header = block.toHeaders().toJSON();
header.timestamp = header.ts;
header.prevHash = header.prevBlock;
this._onHeaders([header]);
}
var header = block.toHeaders().toJSON();
header.timestamp = header.ts;
header.prevHash = header.prevBlock;
this._saveHeaders([this._onHeader(header)]);
for (var i = 0; i < this.subscriptions.block.length; i++) {
var prevHeader = this._headers.get(header.prevHash);
@ -198,54 +184,59 @@ HeaderService.prototype._onBlock = function(block) {
block.height = prevHeader.height + 1;
this.subscriptions.block[i].emit('header/block', block, header);
}
};
HeaderService.prototype._onHeader = function(header) {
var prevHeader = this._headers.get(header.prevHash);
assert(prevHeader, 'We must have a previous header in order to calculate this header\'s data, current header is: ' + header.hash);
header.height = prevHeader.height + 1;
header.chainwork = this._getChainwork(header, prevHeader).toString(16, 64);
var newHdr = {
hash: header.hash,
prevHash: header.prevHash,
height: header.height,
chainwork: header.chainwork
};
this._headers.set(header.hash, newHdr, header.height);
return {
type: 'put',
key: this._encoding.encodeHeaderKey(header.height, header.hash),
value: this._encoding.encodeHeaderValue(header)
};
};
HeaderService.prototype._onHeaders = function(headers) {
var self = this;
log.debug('Header Service: Received: ' + headers.length + ' header(s).');
var dbOps = [];
var headerListLength = 0;
for(var i = 0; i < headers.length; i++) {
var header = headers[i];
// headers that come from a call to getheaders and not a new block comoing in
if (header instanceof Header) {
header = header.toObject();
self._tip.height++;
self._tip.hash = header.hash;
headerListLength = headers.length;
}
header = header.toObject();
var prevHeader = self._headers.get(header.prevHash);
assert(prevHeader, 'We must have a previous header in order to calculate this header\'s data.');
dbOps.push(this._onHeader(header));
header.height = prevHeader.height + 1;
header.chainwork = self._getChainwork(header, prevHeader).toString(16, 64);
var newHdr = {
hash: header.hash,
prevHash: header.prevHash,
height: header.height,
chainwork: header.chainwork
};
// note: this could lead to nulls in positions we don't yet have headers for
// that should be ok because eventually all the missing headers will fill in
self._headers.set(header.hash, newHdr, header.height);
dbOps.push({
type: 'put',
key: self._encoding.encodeHeaderKey(header.height, header.hash),
value: self._encoding.encodeHeaderValue(header)
});
this._tip.height = header.height;
this._tip.hash = header.hash;
}
var tipOps = utils.encodeTip(self._tip, self.name);
this._saveHeaders(dbOps);
};
HeaderService.prototype._saveHeaders = function(dbOps) {
var tipOps = utils.encodeTip(this._tip, this.name);
dbOps.push({
type: 'put',
@ -253,47 +244,71 @@ HeaderService.prototype._onHeaders = function(headers) {
value: tipOps.value
});
self._db.batch(dbOps, function(err) {
this._db.batch(dbOps, this._onHeadersSave.bind(this));
};
HeaderService.prototype._onHeadersSave = function(err) {
if(err) {
log.error(err);
self.node.stop();
this.node.stop();
return;
}
// once we've received less than 2000 headers, we know we are fully sync as far as headers
// if we reveive 2000 headers and there are no more after that, (the best height is a multiple of 2000),
// then we'll just wait for the next block to come in. This should be pretty rare occurrence
if (!this._syncComplete()) {
if (headerListLength === 2000) {
self._sync();
this._sync();
return;
}
assert(!this._headers.hasNullItems(), 'Header list is not complete yet peer has sent all available headers.');
this._startBlockSubscription();
this._setBestHeader();
if (this._detectReorg()) {
this._handleReorg();
return;
}
// at this point, we should have no empty items in this._headers, everything should be filled in
assert(!self._headers.hasNullItems(), 'Header list is not complete yet peer has sent all available headers.');
var bestHeader = self._headers.getLastIndex();
self._tip.height = bestHeader.height;
self._tip.hash = bestHeader.hash;
log.debug('Header Service: ' + bestHeader.hash + ' is the best block hash.');
// at this point, we can check our header list to see if our starting tip diverged from the tip
// that we have now
if (self._detectReorg()) {
self._handleReorg();
return;
}
this._populateNextHashes();
log.debug('Header Service: emitting headers to block service.');
// populate next hash fields on each header
self._populateNextHashes();
this._populateNextHashes();
self.emit('headers', self._headers);
this.emit('headers', this._headers);
});
};
HeaderService.prototype._startBlockSubscription = function() {
if (this._subscribedBlock) {
return;
}
this._subscribedBlock = true;
this._bus.on('p2p/block', this._onBlock.bind(this));
this._bus.subscribe('p2p/block');
};
HeaderService.prototype._syncComplete = function() {
return this._tip.height >= this._bestHeight;
};
HeaderService.prototype._setBestHeader = function() {
var bestHeader = this._headers.getLastIndex();
this._tip.height = bestHeader.height;
this._tip.hash = bestHeader.hash;
log.debug('Header Service: ' + bestHeader.hash + ' is the best block hash.');
};
HeaderService.prototype._populateNextHashes = function() {

32
package-lock.json generated
View File

@ -1,6 +1,6 @@
{
"name": "bitcore-node",
"version": "4.0.0",
"version": "5.0.0",
"lockfileVersion": 1,
"requires": true,
"dependencies": {
@ -828,21 +828,11 @@
"dev": true
},
"deferred-leveldown": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/deferred-leveldown/-/deferred-leveldown-1.2.1.tgz",
"integrity": "sha1-XSXDMQ9f6QmUb2JA3J+Q3RCace8=",
"version": "1.2.2",
"resolved": "https://registry.npmjs.org/deferred-leveldown/-/deferred-leveldown-1.2.2.tgz",
"integrity": "sha512-uukrWD2bguRtXilKt6cAWKyoXrTSMo5m7crUdLfWQmu8kIm88w3QZoUL+6nhpfKVmhHANER6Re3sKoNoZ3IKMA==",
"requires": {
"abstract-leveldown": "2.4.1"
},
"dependencies": {
"abstract-leveldown": {
"version": "2.4.1",
"resolved": "https://registry.npmjs.org/abstract-leveldown/-/abstract-leveldown-2.4.1.tgz",
"integrity": "sha1-s7/tuITraToSd18MVenwpCDM7mQ=",
"requires": {
"xtend": "4.0.1"
}
}
"abstract-leveldown": "2.6.1"
}
},
"delayed-stream": {
@ -2148,7 +2138,7 @@
"resolved": "https://registry.npmjs.org/levelup/-/levelup-1.3.9.tgz",
"integrity": "sha512-VVGHfKIlmw8w1XqpGOAGwq6sZm2WwWLmlDcULkKWQXEA5EopA8OBNJ2Ck2v6bdk8HeEZSbCSEgzXadyQFm76sQ==",
"requires": {
"deferred-leveldown": "1.2.1",
"deferred-leveldown": "1.2.2",
"level-codec": "7.0.0",
"level-errors": "1.0.4",
"level-iterator-stream": "1.3.1",
@ -2161,11 +2151,6 @@
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/prr/-/prr-1.0.1.tgz",
"integrity": "sha1-0/wRS6BplaRexok/SEzrHXj19HY="
},
"semver": {
"version": "5.4.1",
"resolved": "https://registry.npmjs.org/semver/-/semver-5.4.1.tgz",
"integrity": "sha512-WfG/X9+oATh81XtllIo/I8gOiY9EXRdv1cQdyykeXK17YcUW3EXUAi2To4pcH6nZtJPr7ZOpM5OMyWJZm+8Rsg=="
}
}
},
@ -3120,6 +3105,11 @@
"prebuild-install": "2.2.0"
}
},
"semver": {
"version": "5.4.1",
"resolved": "https://registry.npmjs.org/semver/-/semver-5.4.1.tgz",
"integrity": "sha512-WfG/X9+oATh81XtllIo/I8gOiY9EXRdv1cQdyykeXK17YcUW3EXUAi2To4pcH6nZtJPr7ZOpM5OMyWJZm+8Rsg=="
},
"send": {
"version": "0.15.3",
"resolved": "https://registry.npmjs.org/send/-/send-0.15.3.tgz",

View File

@ -1,28 +0,0 @@
'use strict';
var should = require('chai').should();
var path = require('path');
var defaultBaseConfig = require('../../lib/scaffold/default-base-config');
describe('#defaultBaseConfig', function() {
it('will return expected configuration', function() {
var cwd = process.cwd();
var home = process.env.HOME;
var info = defaultBaseConfig();
info.path.should.equal(cwd);
info.config.network.should.equal('livenet');
info.config.port.should.equal(3001);
info.config.services.should.deep.equal(['bitcoind', 'web']);
var bitcoind = info.config.servicesConfig.bitcoind;
bitcoind.spawn.datadir.should.equal(home + '/.bitcoin');
bitcoind.spawn.exec.should.equal(path.resolve(__dirname, '../../bin/bitcoind'));
});
it('be able to specify a network', function() {
var info = defaultBaseConfig({network: 'testnet'});
info.config.network.should.equal('testnet');
});
it('be able to specify a datadir', function() {
var info = defaultBaseConfig({datadir: './data2', network: 'testnet'});
info.config.servicesConfig.bitcoind.spawn.datadir.should.equal('./data2');
});
});