diff --git a/lib/services/header/index.js b/lib/services/header/index.js index ca35d76c..f55da4a6 100644 --- a/lib/services/header/index.js +++ b/lib/services/header/index.js @@ -263,7 +263,7 @@ HeaderService.prototype._queueBlock = function(block) { return self.node.stop(); } - log.info('Header Service: completed processing block: ' + block.rhash() + ' prev hash: ' + bcoin.util.revHex(block.prevBlock)); + log.debug('Header Service: completed processing block: ' + block.rhash() + ' prev hash: ' + bcoin.util.revHex(block.prevBlock)); }); @@ -373,7 +373,6 @@ HeaderService.prototype._onHeader = function(header) { header.timestamp = header.time; } - console.log('about to set last header from onHeader function'); this._lastHeader = header; return [ @@ -546,13 +545,24 @@ HeaderService.prototype._detectReorg = function(block, callback) { return callback(null, false); } - this.getBlockHeader(prevHash, function(err, header) { + // first we check if the new block's prev hash is already in our data set + // if it is and this block isn't already in our data set too, then we have a reorg + async.waterfall([ + function(next) { + self.getBlockHeader(block.rhash(), next); + }, + function(header, next) { + if (header) { + return callback(null, false); + } + self.getBlockHeader(prevHash, next); + } + ], function(err, header) { if (err) { return callback(err); } - // is this block's prevHash already referenced in the database? If so, reorg if (header) { return callback(null, header); } @@ -617,7 +627,6 @@ HeaderService.prototype._onReorg = function(reorgHeader, headers, commonHeader, // setting our tip to the common ancestor this._tip.hash = commonHeader.hash; this._tip.height = commonHeader.height; - console.log('about to set last header from reorg function'); this._lastHeader = commonHeader; this._db.batch(ops, callback); @@ -638,6 +647,7 @@ HeaderService.prototype._onBestHeight = function(height) { HeaderService.prototype._startSync = function() { var self = this; + // if our tip height is less than the best height of this peer, then: // 1. the peer is not fully synced. // 2. the peer has reorg'ed and we need to handle this @@ -656,10 +666,12 @@ HeaderService.prototype._startSync = function() { self._numNeeded = self._bestHeight - self._tip.height; + // common case if (self._numNeeded > 0) { log.info('Header Service: Gathering: ' + self._numNeeded + ' ' + 'header(s) from the peer-to-peer network.'); self._sync(); } else if (self._numNeeded < 0) { + // this should be very uncommon self._handleLowTipHeight(); } @@ -667,38 +679,74 @@ HeaderService.prototype._startSync = function() { }; +HeaderService.prototype._removeAllSubscriptions = function() { + this._bus.unsubscribe('p2p/headers'); + this._bus.unsubscribe('p2p/block'); + this._bus.removeAllListeners(); +}; + HeaderService.prototype._findReorgConditionInNewPeer = function(callback) { + var self = this; - var allHeaders = ; + self._removeAllSubscriptions(); + + var newPeerHeaders = new utils.SimpleMap(); var headerCount = 0; - var tipHash = self.GENESIS_HASH; - self._bus.removeAllListeners(); + self.getAllHeaders(function(err, allHeaders) { - self._bus.on('p2p/headers', function(headers) { - - headers.forEach(function(header) { - allHeaders[header.hash] = header.prevHash; - headerCount++; - }); - - if (headerCount < self._bestHeight) { - return self._p2p.getHeaders({ startHash: tipHash }); + if (err) { + return callback(err); } - self.getAllHeaders(function(err, allHeaders) { + self._bus.subscribe('p2p/headers'); + self._bus.on('p2p/headers', function(headers) { - if (err) { - return callback(err); + headers.forEach(function(header) { + newPeerHeaders.set(header.hash, header); + headerCount++; + }); + + if (headerCount < self._bestHeight) { + return self._getP2PHeaders(headers[headers.length - 1].hash); } - if (allHeaders.get( + // We should have both sets of headers, work from latest header to oldest and find the common header. + // Use the new set since we know this is a shorter list. + var reorgInfo = { commonHeader: null, blockHash: null }; + for(var i = newPeerHeaders.length - 1; i >= 0; i--) { + + var newHeader = newPeerHeaders.getIndex(i); + var oldHeader = allHeaders.get(newHeader.hash); + + if (oldHeader) { + + self._removeAllSubscriptions(); + + // we found a common header, but no headers that at a greater height, this peer is not synced + if (!reorgInfo.blockHash) { + return callback(); + } + + reorgInfo.commonHeader = oldHeader; + return callback(null, reorgInfo); + } + + reorgInfo.blockHash = newHeader.hash; + } + + // nothing matched... + // at this point, we should wonder if we are connected to the wrong network + assert(true, 'We tried to find a common header between current set of headers ' + + 'and the new peer\'s set of headers, but there were none. This should be impossible ' + + ' if the new peer is using the same genesis block.'); }); - }); + self._getP2PHeaders(self.GENESIS_HASH); + }); }; @@ -707,7 +755,7 @@ HeaderService.prototype._handleLowTipHeight = function() { log.warn('Header Service: Connected Peer has a best height (' + self._bestHeight + ') which is lower than our tip height (' + self._tip.height + '). This means that this peer is not fully synchronized with the network -or- the peer has reorganized itself.' + - ' Checking the new peer\'s header for a reorganization event.'); + ' Checking the new peer\'s headers for a reorganization event.'); self._findReorgConditionInNewPeer(function(err, reorgInfo) { @@ -716,41 +764,43 @@ HeaderService.prototype._handleLowTipHeight = function() { return self.node.stop(); } - // case 1: there is a reorg condition in the new peer - if (reorgInfo) { - return self._p2p.getP2PBlock({ - filter: { - startHash: reorgInfo.commonHeader.hash, - endHash: 0 - }, - blockHash: reorgInfo.blockHash - }, function(block) { + // Our peer is not yet sync'ed. + // We will just turn on our block subscription and wait until we get a block we haven't seen + if (!reorgInfo) { + self._onHeadersSave(); + } - self._handleReorg(block, reorgInfo.commonHeader, function(err) { + // 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. + self._p2p.getP2PBlock({ + filter: { + startHash: reorgInfo.commonHeader.hash, + endHash: 0 + }, + blockHash: reorgInfo.blockHash + }, function(block) { - if(err) { + self._handleReorg(block, reorgInfo.commonHeader, function(err) { + + if(err) { + log.error(err); + return self.node.stop(); + } + + self._syncBlock(block, function(err) { + + if (err) { log.error(err); return self.node.stop(); } - self._syncBlock(block, function(err) { - - if (err) { - log.error(err); - return self.node.stop(); - } - - self._sync(); - - }); + self._sync(); }); + }); + }); - } - - // case 2: the new peer is just not synced up, so we must wait - self._sync(); }); @@ -776,21 +826,26 @@ HeaderService.prototype._logProgress = function() { }; -HeaderService.prototype._sync = function() { +HeaderService.prototype._getP2PHeaders = function(hash) { var self = this; - - self._p2p.getHeaders({ startHash: self._tip.hash }); - if (!self._headerInterval) { - // when connecting to a peer that isn't yet responding to getHeaders, we will start a interval timer - // to retry until we can get headers, this may be a very long interval + self._headerInterval = setInterval(function() { - log.info('Header Service: retrying get headers since ' + self._tip.hash); - self._p2p.getHeaders({ startHash: self._tip.hash }); + log.info('Header Service: we have not received a response to getHeaders from the network, retrying.'); + self._p2p.getHeaders({ startHash: hash }); }, 2000); + } + self._p2p.getHeaders({ startHash: hash }); + +}; + +HeaderService.prototype._sync = function() { + + this._getP2PHeaders(this._tip.hash); + }; // this gets the header that is +2 places from hash or returns 0 if there is no such diff --git a/lib/services/p2p/index.js b/lib/services/p2p/index.js index 09b56004..0248f19b 100644 --- a/lib/services/p2p/index.js +++ b/lib/services/p2p/index.js @@ -251,6 +251,7 @@ P2P.prototype._initPool = function() { opts.listenAddr = false; opts.maxPeers = this._maxPeers; opts.network = this.node.network; + p2p.Pool.RetrySeconds = 5; this._pool = new p2p.Pool(opts); };