From 9e0542dba10bbbf7492f1ea1fd7d03b8681dad1b Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Thu, 20 Oct 2016 22:15:22 -0700 Subject: [PATCH] chain: improve reset. fix versionbits checkpoints. --- lib/chain/chain.js | 120 ++++++++++++++++++++----------------------- lib/chain/chaindb.js | 26 ++++++++-- lib/net/peer.js | 3 ++ lib/net/pool.js | 4 ++ 4 files changed, 85 insertions(+), 68 deletions(-) diff --git a/lib/chain/chain.js b/lib/chain/chain.js index ad102dbe..338e6a3c 100644 --- a/lib/chain/chain.js +++ b/lib/chain/chain.js @@ -248,13 +248,12 @@ Chain.prototype._close = function close() { */ Chain.prototype.verifyContext = co(function* verifyContext(block, prev) { - var state, view; + var state = yield this.verify(block, prev); + var view; - state = yield this.verify(block, prev); + yield this.verifyDuplicates(block, prev, state); - yield this.checkDuplicates(block, prev); - - view = yield this.checkInputs(block, prev, state); + view = yield this.verifyInputs(block, prev, state); // Expose the state globally. this.state = state; @@ -288,6 +287,10 @@ Chain.prototype.verify = co(function* verify(block, prev) { var i, err, height, ts, tx, medianTime; var commitmentHash, ancestors, state; + // Skip the genesis block. + if (this.isGenesis(block)) + return this.state; + if (!block.verify(ret)) { err = new VerifyError(block, 'invalid', @@ -308,24 +311,23 @@ Chain.prototype.verify = co(function* verify(block, prev) { throw err; } - // Skip the genesis block. Skip all blocks in spv mode. - if (this.options.spv || this.isGenesis(block)) + // Skip all blocks in spv mode. + if (this.options.spv) return this.state; - // Ensure it's not an orphan - if (!prev) { - throw new VerifyError(block, - 'invalid', - 'bad-prevblk', - 0); + // Skip any blocks below the last checkpoint. + if (!this.options.witness) { + // We can't skip this with segwit + // enabled since the block may have + // been malleated: we don't know + // until we verify the witness + // merkle root. + if (prev.isHistorical()) + return this.state; } - if (prev.isHistorical()) - return this.state; - - ancestors = yield prev.getRetargetAncestors(); - height = prev.height + 1; + ancestors = yield prev.getRetargetAncestors(); medianTime = prev.getMedianTime(ancestors); // Ensure the timestamp is correct @@ -345,10 +347,6 @@ Chain.prototype.verify = co(function* verify(block, prev) { state = yield this.getDeployments(block, prev, ancestors); - // Can't verify any further when merkleblock or headers. - if (this.options.spv) - return state; - // Make sure the height contained in the coinbase is correct. if (state.hasBIP34()) { if (block.getCoinbaseHeight() !== height) { @@ -549,9 +547,9 @@ Chain.prototype.getDeployments = co(function* getDeployments(block, prev, ancest * @returns {Promise} */ -Chain.prototype.checkDuplicates = co(function* checkDuplicates(block, prev) { +Chain.prototype.verifyDuplicates = co(function* verifyDuplicates(block, prev, state) { var height = prev.height + 1; - var entry; + var i, tx, result; if (this.options.spv) return; @@ -562,39 +560,12 @@ Chain.prototype.checkDuplicates = co(function* checkDuplicates(block, prev) { if (prev.isHistorical()) return; - if (this.network.block.bip34height === -1 - || height <= this.network.block.bip34height) { - yield this.findDuplicates(block, prev); - return; - } - - // It was no longer possible to create duplicate - // TXs once bip34 went into effect. We can check - // for this to avoid a DB lookup. - entry = yield this.db.get(this.network.block.bip34height); - - if (entry && entry.hash === this.network.block.bip34hash) + // BIP34 made it impossible to + // create duplicate txids. + if (state.hasBIP34()) return; - yield this.findDuplicates(block, prev); -}); - -/** - * Check block for duplicate txids in blockchain - * history (BIP30). Note that txids are only considered - * duplicate if they are not yet completely spent. - * @private - * @see https://github.com/bitcoin/bips/blob/master/bip-0030.mediawiki - * @param {Block|MerkleBlock} block - * @param {ChainEntry} prev - * @returns {Promise} - */ - -Chain.prototype.findDuplicates = co(function* findDuplicates(block, prev) { - var height = prev.height + 1; - var i, tx, result; - - // Check all transactions + // Check all transactions. for (i = 0; i < block.txs.length; i++) { tx = block.txs[i]; result = yield this.db.hasCoins(tx.hash()); @@ -629,7 +600,7 @@ Chain.prototype.findDuplicates = co(function* findDuplicates(block, prev) { * @returns {Promise} */ -Chain.prototype.checkInputs = co(function* checkInputs(block, prev, state) { +Chain.prototype.verifyInputs = co(function* verifyInputs(block, prev, state) { var height = prev.height + 1; var historical = prev.isHistorical(); var sigops = 0; @@ -967,14 +938,14 @@ Chain.prototype.reset = co(function* reset(height) { */ Chain.prototype._reset = co(function* reset(height) { - var result = yield this.db.reset(height); + yield this.db.reset(height); + + this.state = yield this.getDeploymentState(); // Reset the orphan map completely. There may // have been some orphans on a forked chain we // no longer need. this.purgeOrphans(); - - return result; }); /** @@ -1162,8 +1133,9 @@ Chain.prototype._add = co(function* add(block) { if (this.options.useCheckpoints) { checkpoint = this.network.checkpoints[height]; if (checkpoint) { - // Someone is either trying to fool us, or - // the consensus protocol is broken and + // Someone is either mining on top of + // an old block for no reason, or the + // consensus protocol is broken and // there was a 20k+ block reorg. if (hash !== checkpoint) { this.logger.warning('Checkpoint mismatch!'); @@ -1216,6 +1188,17 @@ Chain.prototype._add = co(function* add(block) { // our tip's. Add the block but do _not_ // connect the inputs. if (entry.chainwork.cmp(this.tip.chainwork) <= 0) { + try { + yield this.verify(block, prev); + } catch (e) { + if (e.type === 'VerifyError') { + if (!e.malleated) + this.invalid[entry.hash] = true; + this.emit('invalid', block, entry.height); + } + throw e; + } + yield this.db.save(entry, block); this.emit('competitor', block, entry); @@ -1327,11 +1310,18 @@ Chain.prototype.finish = function finish(block, entry) { */ Chain.prototype.purgeOrphans = function purgeOrphans() { - this.emit('purge', this.orphan.count, this.orphan.size); + var count = this.orphan.count; + var size = this.orphan.size; + + if (count === 0) + return; + this.orphan.map = {}; this.orphan.bmap = {}; this.orphan.count = 0; this.orphan.size = 0; + + this.emit('purge', count, size); }; /** @@ -1783,8 +1773,10 @@ Chain.prototype.findLocator = co(function* findLocator(locator) { Chain.prototype.isActive = co(function* isActive(prev, id) { var state; - if (prev.isHistorical()) - return false; + if (!this.options.witness) { + if (prev.isHistorical()) + return false; + } state = yield this.getState(prev, id); diff --git a/lib/chain/chaindb.js b/lib/chain/chaindb.js index b3fc274a..ade11f08 100644 --- a/lib/chain/chaindb.js +++ b/lib/chain/chaindb.js @@ -1272,11 +1272,20 @@ ChainDB.prototype.reset = co(function* reset(block) { var tip; if (!entry) - return; + throw new Error('Block not found.'); + + if (!(yield entry.isMainChain())) + throw new Error('Cannot reset on alternate chain.'); + + if (this.prune) + throw new Error('Cannot reset when pruned.'); tip = yield this.getTip(); + assert(tip); - while (tip && !tip.isGenesis()) { + this.logger.debug('Resetting main chain to: %s', entry.rhash); + + while (!tip.isGenesis()) { this.start(); if (tip.hash === entry.hash) { @@ -1300,7 +1309,11 @@ ChainDB.prototype.reset = co(function* reset(block) { yield this.commit(); + this.cacheHeight.remove(tip.height); + this.cacheHash.remove(tip.hash); + tip = yield this.get(tip.prevBlock); + assert(tip); } }); @@ -1332,10 +1345,15 @@ ChainDB.prototype.saveBlock = co(function* saveBlock(block, view) { */ ChainDB.prototype.removeBlock = co(function* removeBlock(hash) { - var block = yield this.getBlock(hash); + var block; + + if (this.options.spv) + return; + + block = yield this.getBlock(hash); if (!block) - return; + throw new Error('Block not found.'); this.del(layout.b(block.hash())); diff --git a/lib/net/peer.js b/lib/net/peer.js index 67428907..e80cdcde 100644 --- a/lib/net/peer.js +++ b/lib/net/peer.js @@ -2492,6 +2492,9 @@ Peer.prototype.sync = function sync() { if (!this.version.hasNetwork()) return Promise.resolve(); + if (this.options.witness && !this.version.hasWitness()) + return; + if (!this.isLoader()) { if (!this.chain.synced) return Promise.resolve(); diff --git a/lib/net/pool.js b/lib/net/pool.js index b8731a40..a07ccf81 100644 --- a/lib/net/pool.js +++ b/lib/net/pool.js @@ -644,6 +644,7 @@ Pool.prototype.setLoader = function setLoader(peer) { this.logger.info('Repurposing peer for loader (%s).', peer.hostname); this.peers.repurpose(peer); this.fillPeers(); + peer.sync(); utils.nextTick(function() { @@ -892,6 +893,9 @@ Pool.prototype.__handleInv = co(function* _handleInv(hashes, peer) { if (!this.chain.synced && !peer.isLoader()) return; + if (this.options.witness && !peer.version.hasWitness()) + return; + if (!this.options.headers) { yield this._handleBlocks(hashes, peer); return;