more reorg stuff.
This commit is contained in:
parent
67c2c07ae5
commit
09b365772c
@ -25,8 +25,9 @@ var BlockService = function(options) {
|
|||||||
this._blockCount = 0;
|
this._blockCount = 0;
|
||||||
this.GENESIS_HASH = constants.BITCOIN_GENESIS_HASH[this.node.network];
|
this.GENESIS_HASH = constants.BITCOIN_GENESIS_HASH[this.node.network];
|
||||||
this._initialSync = true;
|
this._initialSync = true;
|
||||||
this._reorgBackToBlock = null; // use this to rewind your indexes to a specific point by height or hash
|
this._reorgBackToBlock = 1202419; // use this to rewind your indexes to a specific point by height or hash
|
||||||
this._timeOfLastBlockReport = Date.now() - 30000;
|
this._timeOfLastBlockReport = Date.now() - 30000;
|
||||||
|
this._blocksInQueue = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
inherits(BlockService, BaseService);
|
inherits(BlockService, BaseService);
|
||||||
@ -188,7 +189,6 @@ BlockService.prototype._checkTip = function(callback) {
|
|||||||
return callback();
|
return callback();
|
||||||
}
|
}
|
||||||
|
|
||||||
log.warn('Block Service: reorganization spotted...');
|
|
||||||
self._findCommonAncestor(function(err, commonAncestorHash) {
|
self._findCommonAncestor(function(err, commonAncestorHash) {
|
||||||
if(err) {
|
if(err) {
|
||||||
return callback(err);
|
return callback(err);
|
||||||
@ -361,6 +361,7 @@ BlockService.prototype._queueBlock = function(block) {
|
|||||||
|
|
||||||
var self = this;
|
var self = this;
|
||||||
|
|
||||||
|
self._blocksInQueue++;
|
||||||
self._blockProcessor.push(block, function(err) {
|
self._blockProcessor.push(block, function(err) {
|
||||||
|
|
||||||
if (err) {
|
if (err) {
|
||||||
@ -375,6 +376,8 @@ BlockService.prototype._queueBlock = function(block) {
|
|||||||
log.debug('Block Service: completed processing block: ' + block.rhash() +
|
log.debug('Block Service: completed processing block: ' + block.rhash() +
|
||||||
' prev hash: ' + bcoin.util.revHex(block.prevBlock) + ' height: ' + self._tip.height);
|
' prev hash: ' + bcoin.util.revHex(block.prevBlock) + ' height: ' + self._tip.height);
|
||||||
|
|
||||||
|
self._blocksInQueue--;
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
};
|
};
|
||||||
@ -490,6 +493,12 @@ BlockService.prototype.onHeaders = function(callback) {
|
|||||||
|
|
||||||
var self = this;
|
var self = this;
|
||||||
|
|
||||||
|
// when this fires, we switch into initial sync mode (like first boot)
|
||||||
|
// this includes finishing any blocks that may be in the queue, then
|
||||||
|
// checking the tip, etc.
|
||||||
|
// we may be in a reorg situation at the time this function runs
|
||||||
|
// because the header service runs this function after it completes its
|
||||||
|
// reorg routines.
|
||||||
self._removeAllSubscriptions();
|
self._removeAllSubscriptions();
|
||||||
|
|
||||||
self._resetTip(function(err) {
|
self._resetTip(function(err) {
|
||||||
@ -498,7 +507,7 @@ BlockService.prototype.onHeaders = function(callback) {
|
|||||||
}
|
}
|
||||||
async.retry(function(next) {
|
async.retry(function(next) {
|
||||||
|
|
||||||
next(self._blockProcessor.length() !== 0);
|
next(self._blocksInQueue > 0);
|
||||||
|
|
||||||
}, function() {
|
}, function() {
|
||||||
|
|
||||||
@ -535,6 +544,9 @@ BlockService.prototype._handleReorg = function(commonAncestorHash, callback) {
|
|||||||
|
|
||||||
var self = this;
|
var self = this;
|
||||||
|
|
||||||
|
// we want to ensure that we can reask for previously delievered inventory
|
||||||
|
self._p2p.clearInventoryCache();
|
||||||
|
|
||||||
log.warn('Block Service: chain reorganization detected, current height/hash: ' + self._tip.height + '/' +
|
log.warn('Block Service: chain reorganization detected, current height/hash: ' + self._tip.height + '/' +
|
||||||
self._tip.hash + ' common ancestor hash: ' + commonAncestorHash);
|
self._tip.hash + ' common ancestor hash: ' + commonAncestorHash);
|
||||||
|
|
||||||
@ -643,18 +655,16 @@ BlockService.prototype._processBlock = function(block, callback) {
|
|||||||
|
|
||||||
// common case
|
// common case
|
||||||
if (!self._detectReorg(block)) {
|
if (!self._detectReorg(block)) {
|
||||||
return setImmediate(function() {
|
return self._saveBlock(block, callback);
|
||||||
self._saveBlock(block, callback);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// reorg
|
// reorg -- in this case, we will not handle the reorg right away
|
||||||
self._handleReorg(bcoin.util.revHex(block.prevBlock), function(err) {
|
// instead, we will skip the block and wait for the eventual call to
|
||||||
if(err) {
|
// "onHeaders" function. When the header service calls this function,
|
||||||
return callback(err);
|
// we will have a chance to clear out our block queue, check our tip,
|
||||||
}
|
// discover where to reorg to, reorg all the services that rely on
|
||||||
self._saveBlock(block, callback);
|
// blocks and sync from there.
|
||||||
});
|
return callback();
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -683,13 +693,16 @@ BlockService.prototype._saveBlock = function(block, callback) {
|
|||||||
return callback(err);
|
return callback(err);
|
||||||
}
|
}
|
||||||
|
|
||||||
self._setTip({ hash: block.rhash(), height: self._tip.height + 1 });
|
var tipOps = utils.encodeTip({
|
||||||
var tipOps = utils.encodeTip(self._tip, self.name);
|
hash: block.rhash(),
|
||||||
|
height: self._tip.height + 1
|
||||||
|
}, self.name);
|
||||||
|
|
||||||
self._db.put(tipOps.key, tipOps.value, function(err) {
|
self._db.put(tipOps.key, tipOps.value, function(err) {
|
||||||
if(err) {
|
if(err) {
|
||||||
return callback(err);
|
return callback(err);
|
||||||
}
|
}
|
||||||
|
self._setTip({ hash: block.rhash(), height: self._tip.height + 1 });
|
||||||
callback();
|
callback();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@ -753,6 +766,8 @@ BlockService.prototype._startSync = function() {
|
|||||||
if (numNeeded > 0) {
|
if (numNeeded > 0) {
|
||||||
this.on('next block', this._sync.bind(this));
|
this.on('next block', this._sync.bind(this));
|
||||||
this.on('synced', this._onSynced.bind(this));
|
this.on('synced', this._onSynced.bind(this));
|
||||||
|
clearInterval(this._reportInterval);
|
||||||
|
this._reportingInterval = setInterval(this._reportStatus.bind(this), 5000);
|
||||||
return this._sync();
|
return this._sync();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -760,6 +775,13 @@ BlockService.prototype._startSync = function() {
|
|||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
BlockService.prototype._reportStatus = function() {
|
||||||
|
if (this._tip.height % 144 === 0 || Date.now() - this._timeOfLastBlockReport > 10000) {
|
||||||
|
this._timeOfLastBlockReport = Date.now();
|
||||||
|
this._logProgress();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
BlockService.prototype._sync = function() {
|
BlockService.prototype._sync = function() {
|
||||||
|
|
||||||
var self = this;
|
var self = this;
|
||||||
@ -768,11 +790,6 @@ BlockService.prototype._sync = function() {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (self._tip.height % 144 === 0 || Date.now() - self._timeOfLastBlockReport > 10000) {
|
|
||||||
self._timeOfLastBlockReport = Date.now();
|
|
||||||
self._logProgress();
|
|
||||||
}
|
|
||||||
|
|
||||||
self._header.getNextHash(self._tip, function(err, targetHash, nextHash) {
|
self._header.getNextHash(self._tip, function(err, targetHash, nextHash) {
|
||||||
|
|
||||||
if(err) {
|
if(err) {
|
||||||
|
|||||||
@ -307,6 +307,10 @@ HeaderService.prototype._persistHeader = function(block, callback) {
|
|||||||
return self._syncBlock(block, callback);
|
return self._syncBlock(block, callback);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// this will lead to rechecking the network for more headers after our last header
|
||||||
|
// and calling on=Headers on each service that implements it
|
||||||
|
self._initialSync = true;
|
||||||
|
|
||||||
self._findCommonAncestor(block, function(err, commonHeader) {
|
self._findCommonAncestor(block, function(err, commonHeader) {
|
||||||
|
|
||||||
if (err || !commonHeader) {
|
if (err || !commonHeader) {
|
||||||
@ -505,14 +509,16 @@ HeaderService.prototype._onHeadersSave = function(callback) {
|
|||||||
return callback();
|
return callback();
|
||||||
}
|
}
|
||||||
|
|
||||||
self._endHeaderSubscription();
|
self._endHeaderSubscription(); // we don't need headers any more
|
||||||
self._startBlockSubscription();
|
self._startBlockSubscription(); // we need new blocks coming tu us aynchronuously
|
||||||
|
|
||||||
self._setBestHeader();
|
self._setBestHeader();
|
||||||
|
|
||||||
if (!self._initialSync) {
|
if (!self._initialSync) {
|
||||||
return callback();
|
return callback();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// this will happen after an inital start up and sync -and- also after a chain reorg
|
||||||
log.info('Header Service: sync complete.');
|
log.info('Header Service: sync complete.');
|
||||||
self._initialSync = false;
|
self._initialSync = false;
|
||||||
|
|
||||||
@ -520,7 +526,7 @@ HeaderService.prototype._onHeadersSave = function(callback) {
|
|||||||
if (service.onHeaders) {
|
if (service.onHeaders) {
|
||||||
return service.onHeaders.call(service, next);
|
return service.onHeaders.call(service, next);
|
||||||
}
|
}
|
||||||
setImmediate(next);
|
next();
|
||||||
}, callback);
|
}, callback);
|
||||||
|
|
||||||
};
|
};
|
||||||
@ -623,15 +629,23 @@ HeaderService.prototype._handleReorg = function(block, commonHeader, callback) {
|
|||||||
|
|
||||||
HeaderService.prototype._onReorg = function(reorgHeader, headers, commonHeader, callback) {
|
HeaderService.prototype._onReorg = function(reorgHeader, headers, commonHeader, callback) {
|
||||||
// remove all headers with a height greater than commonHeader
|
// remove all headers with a height greater than commonHeader
|
||||||
|
|
||||||
|
assert(this._tip.hash !== commonHeader.hash, 'Header Service: we were asked to reorg from and to the same hash.');
|
||||||
|
|
||||||
var ops = [];
|
var ops = [];
|
||||||
var startingHeight = this._tip.height;
|
var startingHeight = this._tip.height;
|
||||||
var hash = this._tip.hash;
|
var hash = this._tip.hash;
|
||||||
var headerCount = 0;
|
var headerCount = 0;
|
||||||
|
|
||||||
while(hash !== commonHeader.hash) {
|
while(hash !== commonHeader.hash) {
|
||||||
|
|
||||||
headerCount++;
|
headerCount++;
|
||||||
|
|
||||||
var header = headers.getIndex(startingHeight--);
|
var header = headers.getIndex(startingHeight--);
|
||||||
|
|
||||||
assert(header, 'Expected to have a header at this height, but did not. Reorg failed.');
|
assert(header, 'Expected to have a header at this height, but did not. Reorg failed.');
|
||||||
hash = header.prevHash;
|
hash = header.prevHash;
|
||||||
|
|
||||||
ops.push({
|
ops.push({
|
||||||
type: 'del',
|
type: 'del',
|
||||||
key: this._encoding.encodeHeaderHashKey(header.hash)
|
key: this._encoding.encodeHeaderHashKey(header.hash)
|
||||||
@ -640,7 +654,9 @@ HeaderService.prototype._onReorg = function(reorgHeader, headers, commonHeader,
|
|||||||
type: 'del',
|
type: 'del',
|
||||||
key: this._encoding.encodeHeaderHeightKey(header.height)
|
key: this._encoding.encodeHeaderHeightKey(header.height)
|
||||||
});
|
});
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// setting our tip to the common ancestor
|
// setting our tip to the common ancestor
|
||||||
this._tip.hash = commonHeader.hash;
|
this._tip.hash = commonHeader.hash;
|
||||||
this._tip.height = commonHeader.height;
|
this._tip.height = commonHeader.height;
|
||||||
@ -805,6 +821,7 @@ HeaderService.prototype._handleLowTipHeight = function() {
|
|||||||
|
|
||||||
// our peer has reorg'ed to lower overall height.
|
// our peer has reorg'ed to lower overall height.
|
||||||
// we should get the first block after the split, reorg back to this height and then continue.
|
// we should get the first block after the split, reorg back to this height and then continue.
|
||||||
|
// we should still have no listeners for anything, blocks, headers, etc. at this point
|
||||||
self._p2p.getP2PBlock({
|
self._p2p.getP2PBlock({
|
||||||
filter: {
|
filter: {
|
||||||
startHash: reorgInfo.commonHeader.hash,
|
startHash: reorgInfo.commonHeader.hash,
|
||||||
@ -813,6 +830,7 @@ HeaderService.prototype._handleLowTipHeight = function() {
|
|||||||
blockHash: reorgInfo.blockHash
|
blockHash: reorgInfo.blockHash
|
||||||
}, function(block) {
|
}, function(block) {
|
||||||
|
|
||||||
|
self._initialSync = true;
|
||||||
self._handleReorg(block, reorgInfo.commonHeader, function(err) {
|
self._handleReorg(block, reorgInfo.commonHeader, function(err) {
|
||||||
|
|
||||||
if(err) {
|
if(err) {
|
||||||
@ -873,7 +891,7 @@ HeaderService.prototype._getP2PHeaders = function(hash) {
|
|||||||
|
|
||||||
HeaderService.prototype._sync = function() {
|
HeaderService.prototype._sync = function() {
|
||||||
|
|
||||||
this._startHeaderSubscription();
|
this._startHeaderSubscription(); // ensures only one listener will ever be registered
|
||||||
this._getP2PHeaders(this._tip.hash);
|
this._getP2PHeaders(this._tip.hash);
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user