diff --git a/lib/blockchain/chain.js b/lib/blockchain/chain.js index 5023631b..8591b22d 100644 --- a/lib/blockchain/chain.js +++ b/lib/blockchain/chain.js @@ -1012,7 +1012,7 @@ Chain.prototype._reset = co(function* reset(block, silent) { // Reset state. this.tip = tip; this.height = tip.height; - this.synced = this.isFull(true); + this.synced = this.isFull(); state = yield this.getDeploymentState(); @@ -1669,8 +1669,8 @@ Chain.prototype.getCoinView = co(function* getCoinView(tx) { * @returns {Boolean} */ -Chain.prototype.isFull = function isFull(force) { - return !this.isInitial(force); +Chain.prototype.isFull = function isFull() { + return !this.isInitial(); }; /** @@ -1681,10 +1681,7 @@ Chain.prototype.isFull = function isFull(force) { * @returns {Boolean} */ -Chain.prototype.isInitial = function isInitial(force) { - if (!force && this.synced) - return false; - +Chain.prototype.isInitial = function isInitial() { if (this.options.useCheckpoints) { if (this.height < this.network.lastCheckpoint) return true; @@ -1700,7 +1697,7 @@ Chain.prototype.isInitial = function isInitial(force) { }; /** - * Potentially emit a `sync` event. + * Potentially emit a `full` event. * @private */ @@ -1711,6 +1708,20 @@ Chain.prototype.maybeSync = function maybeSync() { } }; +/** + * Test the chain to see if it has the + * minimum required chainwork for the + * network. + * @returns {Boolean} + */ + +Chain.prototype.hasChainwork = function hasChainwork() { + if (this.options.useCheckpoints) + return this.height >= this.network.lastCheckpoint; + + return this.tip.chainwork.cmp(this.network.pow.chainwork) >= 0; +}; + /** * Get the fill percentage. * @returns {Number} percent - Ranges from 0.0 to 1.0. diff --git a/lib/http/rpc.js b/lib/http/rpc.js index e5920ed4..26e4f1d2 100644 --- a/lib/http/rpc.js +++ b/lib/http/rpc.js @@ -1571,7 +1571,7 @@ RPC.prototype.getblocktemplate = co(function* getblocktemplate(args) { if (this.pool.peers.size() === 0) throw new RPCError('Bitcoin is not connected!'); - if (!this.chain.isFull()) + if (!this.chain.synced) throw new RPCError('Bitcoin is downloading blocks...'); } diff --git a/lib/mempool/mempool.js b/lib/mempool/mempool.js index a8d1bb29..09892c71 100644 --- a/lib/mempool/mempool.js +++ b/lib/mempool/mempool.js @@ -164,7 +164,7 @@ Mempool.prototype._addBlock = function addBlock(block, txs) { } if (this.fees) - this.fees.processBlock(block.height, entries, this.chain.isFull()); + this.fees.processBlock(block.height, entries, this.chain.synced); // We need to reset the rejects filter periodically. // There may be a locktime in a TX that is now valid. @@ -972,7 +972,7 @@ Mempool.prototype.addEntry = co(function* addEntry(entry, view) { this.emit('add entry', entry); if (this.fees) - this.fees.processTX(entry, this.chain.isFull()); + this.fees.processTX(entry, this.chain.synced); this.logger.debug('Added tx %s to mempool.', tx.txid()); diff --git a/lib/net/pool.js b/lib/net/pool.js index 91c3813a..42a0b586 100644 --- a/lib/net/pool.js +++ b/lib/net/pool.js @@ -232,7 +232,6 @@ Pool.prototype._open = co(function* _open() { Pool.prototype.resetChain = function resetChain() { var tip = this.chain.tip; - var watermark; if (!this.options.headers) return; @@ -242,16 +241,14 @@ Pool.prototype.resetChain = function resetChain() { this.headerChain.reset(); this.headerNext = null; - watermark = this.getNextTip(tip.height); - - if (watermark) { + if (!this.chain.hasChainwork()) { this.headersFirst = true; - this.headerTip = watermark; - this.headerChain.push(new BlockNode(tip.hash, tip.height)); + this.headerTip = this.getNextTip(tip.height); + this.headerChain.push(new HeaderEntry(tip.hash, tip.height)); this.logger.info( 'Initialized header chain to height %d (watermark=%d).', tip.height, - watermark.height); + this.headerTip.height); } }; @@ -665,33 +662,25 @@ Pool.prototype.sendGetAddr = function sendGetAddr() { /** * Request current header chain blocks. + * @private * @param {Peer} peer - * @returns {Promise} */ -Pool.prototype.requestHeaders = co(function* requestHeaders(peer) { +Pool.prototype.resolveHeaders = function resolveHeaders(peer) { var items = []; var node; - if (!this.headerNext) - return; - for (node = this.headerNext; node; node = node.next) { this.headerNext = node.next; - if (yield this.hasBlock(node.hash)) - continue; - items.push(node.hash); + + if (items.length === 50000) + break; } - // Checkpoints should never be - // spaced more than 50k blocks - // apart from each other. - assert(items.length <= 50000); - this.getBlock(peer, items); -}); +}; /** * Find the next highest watermark block. @@ -707,15 +696,15 @@ Pool.prototype.getNextTip = function getNextTip(height) { for (i = 0; i < this.network.checkpoints.length; i++) { next = this.network.checkpoints[i]; if (next.height > height) - return next; + return new HeaderEntry(next.hash, next.height); } - return; + throw new Error('Could not find next tip.'); } - if (this.chain.isFull()) - return; + if (this.chain.hasChainwork()) + throw new Error('Could not find next tip.'); - return new BlockNode(null, height + 20000); + return new HeaderEntry(null, height + 20000); }; /** @@ -1788,15 +1777,6 @@ Pool.prototype._handleHeaders = co(function* handleHeaders(peer, packet) { return; } - if (this.headerChain.size > 40000) { - this.logger.warning( - 'Peer is sending too many headers (%s).', - peer.hostname()); - peer.increaseBan(100); - peer.destroy(); - return; - } - assert(this.headerChain.size > 0); for (i = 0; i < headers.length; i++) { @@ -1822,10 +1802,7 @@ Pool.prototype._handleHeaders = co(function* handleHeaders(peer, packet) { return; } - node = new BlockNode(hash, height); - - if (!this.headerNext) - this.headerNext = node; + node = new HeaderEntry(hash, height); if (node.height === this.headerTip.height) { if (this.options.useCheckpoints) { @@ -1841,6 +1818,9 @@ Pool.prototype._handleHeaders = co(function* handleHeaders(peer, packet) { isWatermark = true; } + if (!this.headerNext) + this.headerNext = node; + this.headerChain.push(node); } @@ -1849,14 +1829,14 @@ Pool.prototype._handleHeaders = co(function* handleHeaders(peer, packet) { headers.length, peer.hostname()); - // Request the hashes we just added. + // Request the blocks we just added. if (isWatermark) { this.headerChain.shift(); - yield this.requestHeaders(peer); + this.resolveHeaders(peer); return; } - // Restart the getheaders process. + // Request more headers. peer.sendGetHeaders([node.hash], this.headerTip.hash); }); @@ -1919,8 +1899,6 @@ Pool.prototype.addBlock = co(function* addBlock(peer, block) { Pool.prototype._addBlock = co(function* addBlock(peer, block) { var hash = block.hash('hex'); - var isWatermark = false; - var node, watermark; if (!this.syncing) return; @@ -1933,22 +1911,6 @@ Pool.prototype._addBlock = co(function* addBlock(peer, block) { return; } - if (this.headersFirst) { - node = this.headerChain.head; - assert(node); - - if (hash === node.hash) { - if (node.height === this.headerTip.height) { - this.logger.info( - 'Received checkpoint block %s (%d).', - block.rhash(), node.height); - isWatermark = true; - } else { - this.headerChain.shift(); - } - } - } - peer.blockTime = util.ms(); try { @@ -1974,23 +1936,66 @@ Pool.prototype._addBlock = co(function* addBlock(peer, block) { this.logStatus(block); + yield this.resolveChain(peer, hash); +}); + +/** + * Resolve header chain. + * @private + * @param {Peer} peer + * @param {Hash} hash + * @returns {Promise} + */ + +Pool.prototype.resolveChain = co(function* resolveChain(peer, hash) { + var node = this.headerChain.head; + if (!this.headersFirst) return; - if (!isWatermark && !this.chain.isFull()) { - yield this.requestHeaders(peer); + if (!peer.loader) + return; + + if (peer.destroyed) + throw new Error('Peer was destroyed (header chain resolution).'); + + assert(node); + + if (hash !== node.hash) { + this.logger.warning( + 'Header hash mismatch %s != %s (%s).', + util.revHex(hash), + util.revHex(node.hash), + peer.hostname()); + + peer.destroy(); + return; } - node = this.headerTip; - watermark = this.getNextTip(node.height); + if (!this.chain.hasChainwork()) { + if (node.height === this.headerTip.height) { + this.logger.info( + 'Hit high watermark %s (%d).', + util.revHex(node.hash), node.height); + + this.headerTip = this.getNextTip(node.height); + + peer.sendGetHeaders([hash], this.headerTip.hash); + + return; + } + + this.headerChain.shift(); + this.resolveHeaders(peer); - if (watermark) { - this.headerTip = watermark; - peer.sendGetHeaders([hash], watermark.hash); return; } + this.logger.info( + 'Chain hit target chainwork of %s. Switching to getblocks.', + this.chain.tip.chainwork.toString('hex', 64)); + this.headersFirst = false; this.headerTip = null; this.headerChain.reset(); @@ -3871,11 +3876,11 @@ NonceList.prototype.remove = function remove(hostname) { }; /** - * BlockNode + * HeaderEntry * @constructor */ -function BlockNode(hash, height) { +function HeaderEntry(hash, height) { this.hash = hash; this.height = height; this.prev = null;