Fixed initial sync issue with headers.

This commit is contained in:
Chris Kleeschulte 2017-08-10 11:23:28 -04:00
parent d53abfd023
commit abf52f8136
2 changed files with 71 additions and 25 deletions

View File

@ -26,7 +26,6 @@ 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._newBlocksHeight = 0;
}; };
inherits(HeaderService, BaseService); inherits(HeaderService, BaseService);
@ -93,14 +92,17 @@ HeaderService.prototype.start = function(callback) {
self._db.getServiceTip(self.name, next); self._db.getServiceTip(self.name, next);
}, },
function(tip, next) { function(tip, next) {
self._tip = tip; self._tip = tip;
self._originalTip = { log.debug('Header Service: original tip height is: ' + self._tip.height);
hash: self._tip.hash, log.debug('Header Service: original tip hash is: ' + self._tip.hash);
height: self._tip.height
}; self._originalTip = Object.assign(self._tip, {});
if (self._tip.height === 0) { if (self._tip.height === 0) {
assert(self._tip.hash === self.GENESIS_HASH, 'Expected tip hash to be genesis hash, but it was not.');
var genesisHeader = { var genesisHeader = {
hash: self.GENESIS_HASH, hash: self.GENESIS_HASH,
height: 0, height: 0,
@ -113,7 +115,7 @@ HeaderService.prototype.start = function(callback) {
merkleRoot: '4a5e1e4baab89f3a32518a88c31bc87f618f76673e2cc77ab2127b7afdeda33b' merkleRoot: '4a5e1e4baab89f3a32518a88c31bc87f618f76673e2cc77ab2127b7afdeda33b'
}; };
self._headers.set(self.GENESIS_HASH, genesisHeader); self._headers.set(self.GENESIS_HASH, genesisHeader, 0);
self._db._store.put(self._encoding.encodeHeaderKey(0, self.GENESIS_HASH), self._db._store.put(self._encoding.encodeHeaderKey(0, self.GENESIS_HASH),
self._encoding.encodeHeaderValue(genesisHeader), next); self._encoding.encodeHeaderValue(genesisHeader), next);
@ -187,7 +189,6 @@ HeaderService.prototype._onBlock = function(block) {
header = block.toHeaders().toJSON(); header = block.toHeaders().toJSON();
header.timestamp = header.ts; header.timestamp = header.ts;
header.prevHash = header.prevBlock; header.prevHash = header.prevBlock;
header.height = ++this._newBlocksHeight;
this._onHeaders([header]); this._onHeaders([header]);
} }
@ -203,25 +204,25 @@ HeaderService.prototype._onHeaders = function(headers) {
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
if (header instanceof Header) { if (header instanceof Header) {
header = header.toObject(); header = header.toObject();
header.height = ++self._tip.height; self._tip.height++;
self._tip.hash = header.hash;
headerListLength = headers.length;
} }
header.chainwork = self._getChainwork(header).toString(16, 64); var prevHeader = this._headers.get(header.prevHash);
self._lastChainwork = header.chainwork; assert(prevHeader, 'We must have a previous header in order to calculate this header\'s data.');
self._tip.hash = header.hash; header.height = prevHeader.height + 1;
header.chainwork = self._getChainwork(header, prevHeader).toString(16, 64);
dbOps.push({
type: 'put',
key: self._encoding.encodeHeaderKey(header.height, header.hash),
value: self._encoding.encodeHeaderValue(header)
});
var newHdr = { var newHdr = {
hash: header.hash, hash: header.hash,
@ -230,8 +231,15 @@ HeaderService.prototype._onHeaders = function(headers) {
chainwork: header.chainwork 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); 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); var tipOps = utils.encodeTip(self._tip, self.name);
@ -246,16 +254,27 @@ HeaderService.prototype._onHeaders = function(headers) {
if(err) { if(err) {
log.error(err); log.error(err);
this.node.stop(); self.node.stop();
return; return;
} }
if (self._tip.height < self._bestHeight) { // 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 (headerListLength === 2000) {
self._sync(); self._sync();
return; return;
} }
log.debug('Header Service: ' + self._headers.getIndex(self._bestHeight).hash + ' is the best block hash.'); // 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 // at this point, we can check our header list to see if our starting tip diverged from the tip
// that we have now // that we have now
@ -265,23 +284,47 @@ HeaderService.prototype._onHeaders = function(headers) {
} }
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
self._populateNextHashes();
self.emit('headers', self._headers); self.emit('headers', self._headers);
}); });
}; };
HeaderService.prototype._populateNextHashes = function() {
var count = 0;
while (count < this._headers.length) {
var hdr = this._headers.getIndex(count);
var nextHdr = this._headers.getIndex(++count);
if (nextHdr) {
hdr.nextHash = nextHdr.hash;
}
}
};
HeaderService.prototype._detectReorg = function() { HeaderService.prototype._detectReorg = function() {
// is our original tip's height and hash the same after we rewound by the checkpoint amount of blocks // is our original tip's height and hash the same after we rewound by the checkpoint amount of blocks
// and re-imported? If so, then we've reorg'ed since we've been shut down. // and re-imported? If so, then we've reorg'ed since we've been shut down.
var headerHash = this._headers.getIndex(this._originalTip.height).hash; var headerHash = this._headers.getIndex(this._originalTip.height).hash;
log.debug('Header Service: attempting to detect a reorg situation after fully syncing headers, original tip height is: ' +
this._originalTip.height + ' original tip hash: ' + this._originalTip.hash + ' headers list size: ' + this._headers.size +
' hash at original tip height in headers list: ' + headerHash);
assert(headerHash, 'Expected a header to exist at height ' + this._originalTip.height); assert(headerHash, 'Expected a header to exist at height ' + this._originalTip.height);
if (this._originalTip.hash !== headerHash) { if (this._originalTip.hash !== headerHash) {
return true; return true;
} }
return false; return false;
}; };
HeaderService.prototype._handleReorg = function() { HeaderService.prototype._handleReorg = function() {
@ -299,7 +342,6 @@ HeaderService.prototype._onBestHeight = function(height) {
height + ' tip height: ' + this._tip.height); height + ' tip height: ' + this._tip.height);
log.debug('Header Service: Best Height is: ' + height); log.debug('Header Service: Best Height is: ' + height);
this._bestHeight = height; this._bestHeight = height;
this._newBlocksHeight = this._bestHeight;
this._startSync(); this._startSync();
}; };
@ -357,6 +399,7 @@ HeaderService.prototype._getPersistedHeaders = function(callback) {
stream.on('data', function(data) { stream.on('data', function(data) {
var header = self._encoding.decodeHeaderValue(data.value); var header = self._encoding.decodeHeaderValue(data.value);
// any records with a height greater than our current tip height can be scheduled for removal // any records with a height greater than our current tip height can be scheduled for removal
// because they will be replaced shortly // because they will be replaced shortly
if (header.height > self._tip.height) { if (header.height > self._tip.height) {
@ -367,7 +410,6 @@ HeaderService.prototype._getPersistedHeaders = function(callback) {
return; return;
} }
// hold a bit less in memory
var newHdr = { var newHdr = {
hash: header.hash, hash: header.hash,
prevHash: header.prevHash, prevHash: header.prevHash,
@ -395,9 +437,9 @@ HeaderService.prototype._getPersistedHeaders = function(callback) {
}; };
HeaderService.prototype._getChainwork = function(header) { HeaderService.prototype._getChainwork = function(header, prevHeader) {
var prevChainwork = new BN(new Buffer(this._lastChainwork || HeaderService.STARTING_CHAINWORK, 'hex')); var prevChainwork = new BN(new Buffer(prevHeader.chainwork, 'hex'));
return this._computeChainwork(header.bits, prevChainwork); return this._computeChainwork(header.bits, prevChainwork);
}; };

View File

@ -72,6 +72,10 @@ utils.SimpleMap = function SimpleMap() {
this.size = 0; this.size = 0;
this.length = 0; this.length = 0;
this.hasNullItems = function() {
return array.length !== _.compact(array).length;
};
this.get = function (key) { this.get = function (key) {
return array[object[key]]; return array[object[key]];
}; };