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 consensus = require('bcoin').consensus;
var assert = require('assert'); var assert = require('assert');
var constants = require('../../constants'); var constants = require('../../constants');
var Header = require('bitcore-lib').BlockHeader;
var HeaderService = function(options) { var HeaderService = function(options) {
@ -26,6 +25,7 @@ var HeaderService = function(options) {
this.subscriptions.block = []; this.subscriptions.block = [];
this._checkpoint = options.checkpoint || 2000; this._checkpoint = options.checkpoint || 2000;
this.GENESIS_HASH = constants.BITCOIN_GENESIS_HASH[this.node.network]; this.GENESIS_HASH = constants.BITCOIN_GENESIS_HASH[this.node.network];
this._initiallySynced = false;
}; };
inherits(HeaderService, BaseService); 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 height is: ' + self._tip.height);
log.debug('Header Service: original tip hash is: ' + self._tip.hash); 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) { if (self._tip.height === 0) {
@ -134,7 +134,8 @@ HeaderService.prototype.start = function(callback) {
} }
self._setListeners(); self._setListeners();
self._startSubscriptions(); self._bus = self.node.openBus({remoteAddress: 'localhost-header'});
self._startHeaderSubscription();
callback(); callback();
}); });
@ -145,23 +146,11 @@ HeaderService.prototype.stop = function(callback) {
setImmediate(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/headers', this._onHeaders.bind(this));
this._bus.on('p2p/block', this._onBlock.bind(this));
this._bus.subscribe('p2p/headers'); 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); log.debug('Header Service: new block: ' + hash);
var header = this._headers.get(hash); var header = block.toHeaders().toJSON();
if (!header) { header.timestamp = header.ts;
header = block.toHeaders().toJSON(); header.prevHash = header.prevBlock;
header.timestamp = header.ts; this._saveHeaders([this._onHeader(header)]);
header.prevHash = header.prevBlock;
this._onHeaders([header]);
}
for (var i = 0; i < this.subscriptions.block.length; i++) { for (var i = 0; i < this.subscriptions.block.length; i++) {
var prevHeader = this._headers.get(header.prevHash); var prevHeader = this._headers.get(header.prevHash);
@ -198,54 +184,59 @@ HeaderService.prototype._onBlock = function(block) {
block.height = prevHeader.height + 1; block.height = prevHeader.height + 1;
this.subscriptions.block[i].emit('header/block', block, header); 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) { HeaderService.prototype._onHeaders = function(headers) {
var self = this;
log.debug('Header Service: Received: ' + headers.length + ' header(s).'); log.debug('Header Service: Received: ' + headers.length + ' header(s).');
var dbOps = []; var dbOps = [];
var headerListLength = 0;
for(var i = 0; i < headers.length; i++) { for(var i = 0; i < headers.length; i++) {
var header = headers[i]; var header = headers[i];
// headers that come from a call to getheaders and not a new block comoing in header = header.toObject();
if (header instanceof Header) {
header = header.toObject();
self._tip.height++;
self._tip.hash = header.hash;
headerListLength = headers.length;
}
var prevHeader = self._headers.get(header.prevHash); dbOps.push(this._onHeader(header));
assert(prevHeader, 'We must have a previous header in order to calculate this header\'s data.');
header.height = prevHeader.height + 1; this._tip.height = header.height;
header.chainwork = self._getChainwork(header, prevHeader).toString(16, 64); this._tip.hash = header.hash;
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)
});
} }
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({ dbOps.push({
type: 'put', type: 'put',
@ -253,47 +244,71 @@ HeaderService.prototype._onHeaders = function(headers) {
value: tipOps.value value: tipOps.value
}); });
self._db.batch(dbOps, function(err) { this._db.batch(dbOps, this._onHeadersSave.bind(this));
};
HeaderService.prototype._onHeadersSave = function(err) {
if(err) { if(err) {
log.error(err); log.error(err);
self.node.stop(); this.node.stop();
return; return;
} }
// once we've received less than 2000 headers, we know we are fully sync as far as headers if (!this._syncComplete()) {
// 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 (headerListLength === 2000) { this._sync();
self._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; return;
} }
// at this point, we should have no empty items in this._headers, everything should be filled in this._populateNextHashes();
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;
}
log.debug('Header Service: emitting headers to block service.'); log.debug('Header Service: emitting headers to block service.');
// populate next hash fields on each header this._populateNextHashes();
self._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() { HeaderService.prototype._populateNextHashes = function() {

32
package-lock.json generated
View File

@ -1,6 +1,6 @@
{ {
"name": "bitcore-node", "name": "bitcore-node",
"version": "4.0.0", "version": "5.0.0",
"lockfileVersion": 1, "lockfileVersion": 1,
"requires": true, "requires": true,
"dependencies": { "dependencies": {
@ -828,21 +828,11 @@
"dev": true "dev": true
}, },
"deferred-leveldown": { "deferred-leveldown": {
"version": "1.2.1", "version": "1.2.2",
"resolved": "https://registry.npmjs.org/deferred-leveldown/-/deferred-leveldown-1.2.1.tgz", "resolved": "https://registry.npmjs.org/deferred-leveldown/-/deferred-leveldown-1.2.2.tgz",
"integrity": "sha1-XSXDMQ9f6QmUb2JA3J+Q3RCace8=", "integrity": "sha512-uukrWD2bguRtXilKt6cAWKyoXrTSMo5m7crUdLfWQmu8kIm88w3QZoUL+6nhpfKVmhHANER6Re3sKoNoZ3IKMA==",
"requires": { "requires": {
"abstract-leveldown": "2.4.1" "abstract-leveldown": "2.6.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"
}
}
} }
}, },
"delayed-stream": { "delayed-stream": {
@ -2148,7 +2138,7 @@
"resolved": "https://registry.npmjs.org/levelup/-/levelup-1.3.9.tgz", "resolved": "https://registry.npmjs.org/levelup/-/levelup-1.3.9.tgz",
"integrity": "sha512-VVGHfKIlmw8w1XqpGOAGwq6sZm2WwWLmlDcULkKWQXEA5EopA8OBNJ2Ck2v6bdk8HeEZSbCSEgzXadyQFm76sQ==", "integrity": "sha512-VVGHfKIlmw8w1XqpGOAGwq6sZm2WwWLmlDcULkKWQXEA5EopA8OBNJ2Ck2v6bdk8HeEZSbCSEgzXadyQFm76sQ==",
"requires": { "requires": {
"deferred-leveldown": "1.2.1", "deferred-leveldown": "1.2.2",
"level-codec": "7.0.0", "level-codec": "7.0.0",
"level-errors": "1.0.4", "level-errors": "1.0.4",
"level-iterator-stream": "1.3.1", "level-iterator-stream": "1.3.1",
@ -2161,11 +2151,6 @@
"version": "1.0.1", "version": "1.0.1",
"resolved": "https://registry.npmjs.org/prr/-/prr-1.0.1.tgz", "resolved": "https://registry.npmjs.org/prr/-/prr-1.0.1.tgz",
"integrity": "sha1-0/wRS6BplaRexok/SEzrHXj19HY=" "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" "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": { "send": {
"version": "0.15.3", "version": "0.15.3",
"resolved": "https://registry.npmjs.org/send/-/send-0.15.3.tgz", "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');
});
});