diff --git a/lib/services/block/index.js b/lib/services/block/index.js index 331636a9..d21dec8d 100644 --- a/lib/services/block/index.js +++ b/lib/services/block/index.js @@ -28,6 +28,7 @@ var BlockService = function(options) { this._blockCount = 0; this.GENESIS_HASH = constants.BITCOIN_GENESIS_HASH[this.node.network]; this._initialSync = true; + this._reorgBackToBlock = null; }; inherits(BlockService, BaseService); @@ -167,10 +168,28 @@ BlockService.prototype.getRawBlock = function(hash, callback) { }); }; +BlockService.prototype._reorgBackTo = function(callback) { + var self = this; + self._header.getBlockHeader(self._reorgBackToBlock, function(err, header) { + if (err || !header) { + return callback(err || new Error('Header not found to reorg back to.')); + } + log.info('Block Service: we found the block to reorg back to, commencing reorg...'); + self._handleReorg(header.hash, callback); + }); +}; + BlockService.prototype._checkTip = function(callback) { var self = this; + log.info('Block Service: checking the saved tip...'); + + if (self._reorgBackToBlock) { + log.warn('Block Service: we were asked to reorg back to block: ' + self._reorgBackToBlock); + return self._reorgBackTo(callback); + } + self._header.getBlockHeader(self._tip.height, function(err, header) { if (err) { @@ -209,8 +228,8 @@ BlockService.prototype._findCommonAncestor = function(callback) { return headers.get(hash); }, function(next) { self._getBlock(hash, function(err, block) { - if(err) { - return next(err); + if(err || !block) { + return next(err || new Error('block must be found in order to find common ancestor.')); } hash = bcoin.util.revHex(block.prevBlock); next(); @@ -224,6 +243,85 @@ BlockService.prototype._findCommonAncestor = function(callback) { }); }; +BlockService.prototype._resetTip = function(callback) { + var self = this; + + if (!self._tipResetNeeded) { + return callback(); + } + + self._tipResetNeeded = false; + + log.warn('Block Service: resetting tip due to a non-existent tip block...'); + + var block; + var header = self._header.getLastHeader(); + var height = header.height; + + self._header.getAllHeaders(function(err, headers) { + + if (err || !headers) { + return callback(err || new Error('headers required')); + } + + log.info('Block Service: retrieved all the headers for lookups'); + async.until(function() { + + return block; + + }, function(next) { + + self._getBlock(header.hash, function(err, _block) { + + if (err) { + return callback(err); + } + + if (!_block) { + log.warn('Block Service: block: ' + header.hash + ' was not found, proceeding to older blocks.'); + } + + block = _block; + header = headers.getIndex(--height); + assert(header, 'Header not found for reset.'); + + if (!block) { + log.warn('Block Service: trying block: ' + header.hash); + } + + next(); + + }); + + }, function(err) { + + if (err || !block) { + return callback(err || + new Error('Block Service: none of the blocks from the headers match what is already indexed in the block service.')); + } + self._setTip({ hash: block.rhash(), height: height + 1 }); + callback(); + }); + + }); +}; + +BlockService.prototype._performSanityCheck = function(tip, callback) { + + var self = this; + // is our tip saved in our database? If not, then find the latest block that is in + // in our database and set the tip to that + self._getBlock(tip.hash, function(err, block) { + if (err) { + return callback(err); + } + if (block) { + return callback(null, tip); + } + return callback(null, false); + }); +}; + BlockService.prototype.start = function(callback) { var self = this; @@ -243,13 +341,23 @@ BlockService.prototype.start = function(callback) { return callback(err); } - self._blockProcessor = async.queue(self._onBlock.bind(self)); + self._performSanityCheck(tip, function(err, tip) { - self._setListeners(); - assert(tip.height >= 0, 'tip is not initialized'); - self._setTip(tip); - self._bus = self.node.openBus({remoteAddress: 'localhost-block'}); - callback(); + if (err) { + return callback(err); + } + + if (!tip) { + self._tipResetNeeded = true; + } + + self._blockProcessor = async.queue(self._onBlock.bind(self)); + self._setListeners(); + self._setTip(tip); + self._bus = self.node.openBus({remoteAddress: 'localhost-block'}); + + callback(); + }); }); @@ -410,21 +518,26 @@ BlockService.prototype.onHeaders = function(callback) { var self = this; - async.retry(function(next) { + self._resetTip(function(err) { + if (err) { + return callback(err); + } + async.retry(function(next) { - next(self._blockProcessor.length() !== 0); + next(self._blockProcessor.length() !== 0); - }, function() { + }, function() { - self._checkTip(function(err) { + self._checkTip(function(err) { - if(err) { - return callback(err); - } + if(err) { + return callback(err); + } - self._startSync(); - callback(); + self._startSync(); + callback(); + }); }); });