diff --git a/lib/blockchain/chain.js b/lib/blockchain/chain.js index 16d7625f..a4674eeb 100644 --- a/lib/blockchain/chain.js +++ b/lib/blockchain/chain.js @@ -164,8 +164,23 @@ Chain.prototype.verifyContext = async function verifyContext(block, prev, flags) // Initial non-contextual verification. const state = await this.verify(block, prev, flags); + // Skip everything if we're in SPV mode. + if (this.options.spv) { + const view = new CoinView(); + return [view, state]; + } + + // Skip everything if we're using checkpoints. + if (this.isHistorical(prev)) { + const view = await this.updateInputs(block, prev); + return [view, state]; + } + // BIP30 - Verify there are no duplicate txids. - await this.verifyDuplicates(block, prev, state); + // Note that BIP34 made it impossible to create + // duplicate txids. + if (!state.hasBIP34()) + await this.verifyDuplicates(block, prev); // Verify scripts, spend and add coins. const view = await this.verifyInputs(block, prev, state); @@ -202,16 +217,6 @@ Chain.prototype._verifyBlock = async function _verifyBlock(block) { return await this.verifyContext(block, this.tip, flags); }; -/** - * Test whether a block is the genesis block. - * @param {Block} block - * @returns {Boolean} - */ - -Chain.prototype.isGenesis = function isGenesis(block) { - return block.hash('hex') === this.network.genesis.hash; -}; - /** * Test whether the hash is in the main chain. * @param {Hash} hash @@ -348,11 +353,6 @@ Chain.prototype.isHistorical = function isHistorical(prev) { */ Chain.prototype.verify = async function verify(block, prev, flags) { - const deployments = this.network.deployments; - const hash = block.hash('hex'); - const now = this.network.now(); - const height = prev.height + 1; - assert(typeof flags === 'number'); // Extra sanity check. @@ -360,6 +360,7 @@ Chain.prototype.verify = async function verify(block, prev, flags) { throw new VerifyError(block, 'invalid', 'bad-prevblk', 0); // Verify a checkpoint if there is one. + const hash = block.hash('hex'); if (!this.verifyCheckpoint(prev, hash)) { throw new VerifyError(block, 'checkpoint', @@ -373,7 +374,7 @@ Chain.prototype.verify = async function verify(block, prev, flags) { // validated outside in the header chain. if (this.isHistorical(prev)) { if (this.options.spv) - return new DeploymentState(); + return this.state; // Once segwit is active, we will still // need to check for block mutability. @@ -391,10 +392,6 @@ Chain.prototype.verify = async function verify(block, prev, flags) { throw new VerifyError(block, 'invalid', reason, score, true); } - // Skip all blocks in spv mode. - if (this.options.spv) - return this.state; - // Ensure the POW is what we expect. const bits = await this.getTarget(block.time, prev); @@ -405,6 +402,11 @@ Chain.prototype.verify = async function verify(block, prev, flags) { 100); } + // Skip all blocks in spv mode once + // we've verified the network target. + if (this.options.spv) + return this.state; + // Ensure the timestamp is correct. const mtp = await this.getMedianTime(prev); @@ -418,7 +420,7 @@ Chain.prototype.verify = async function verify(block, prev, flags) { // Check timestamp against adj-time+2hours. // If this fails we may be able to accept // the block later. - if (block.time > now + 2 * 60 * 60) { + if (block.time > this.network.now() + 2 * 60 * 60) { throw new VerifyError(block, 'invalid', 'time-too-new', @@ -426,6 +428,9 @@ Chain.prototype.verify = async function verify(block, prev, flags) { true); } + // Calculate height of current block. + const height = prev.height + 1; + // Only allow version 2 blocks (coinbase height) // once the majority of blocks are using it. if (block.version < 2 && height >= this.network.block.bip34height) @@ -446,7 +451,8 @@ Chain.prototype.verify = async function verify(block, prev, flags) { // Enforce BIP91/BIP148. if (state.hasBIP91() || state.hasBIP148()) { - if (!consensus.hasBit(block.version, deployments.segwit.bit)) + const {segwit} = this.network.deployments; + if (!consensus.hasBit(block.version, segwit.bit)) throw new VerifyError(block, 'invalid', 'bad-no-segwit', 0); } @@ -476,7 +482,7 @@ Chain.prototype.verify = async function verify(block, prev, flags) { } // Check the commitment hash for segwit. - let commit; + let commit = null; if (state.hasWitness()) { commit = block.getCommitmentHash(); if (commit) { @@ -658,38 +664,49 @@ Chain.prototype.setDeploymentState = function setDeploymentState(state) { * @returns {Promise} */ -Chain.prototype.verifyDuplicates = async function verifyDuplicates(block, prev, state) { - if (this.options.spv) - return; - - if (this.isHistorical(prev)) - return; - - // BIP34 made it impossible to - // create duplicate txids. - if (state.hasBIP34()) - return; - - // Check all transactions. +Chain.prototype.verifyDuplicates = async function verifyDuplicates(block, prev) { for (const tx of block.txs) { - const result = await this.db.hasCoins(tx); + if (!await this.hasCoins(tx)) + continue; - if (result) { - const height = prev.height + 1; - - // Blocks 91842 and 91880 created duplicate - // txids by using the same exact output script - // and extraNonce. - if (this.network.bip30[height]) { - if (block.hash('hex') === this.network.bip30[height]) - continue; - } + const height = prev.height + 1; + const hash = this.network.bip30[height]; + // Blocks 91842 and 91880 created duplicate + // txids by using the same exact output script + // and extraNonce. + if (!hash || block.hash('hex') !== hash) throw new VerifyError(block, 'invalid', 'bad-txns-BIP30', 100); - } } }; +/** + * Spend and update inputs (checkpoints only). + * @private + * @param {Block} block + * @param {ChainEntry} prev + * @returns {Promise} - Returns {@link CoinView}. + */ + +Chain.prototype.updateInputs = async function updateInputs(block, prev) { + const view = new CoinView(); + const height = prev.height + 1; + const cb = block.txs[0]; + + view.addTX(cb, height); + + for (let i = 1; i < block.txs.length; i++) { + const tx = block.txs[i]; + + assert(await view.spendInputs(this.db, tx), + 'BUG: Spent inputs in historical data!'); + + view.addTX(tx, height); + } + + return view; +}; + /** * Check block transactions for all things pertaining * to inputs. This function is important because it is @@ -710,13 +727,8 @@ Chain.prototype.verifyDuplicates = async function verifyDuplicates(block, prev, Chain.prototype.verifyInputs = async function verifyInputs(block, prev, state) { const view = new CoinView(); - - if (this.options.spv) - return view; - - const interval = this.network.halvingInterval; const height = prev.height + 1; - const historical = this.isHistorical(prev); + const interval = this.network.halvingInterval; let sigops = 0; let reward = 0; @@ -728,7 +740,6 @@ Chain.prototype.verifyInputs = async function verifyInputs(block, prev, state) { // Ensure tx is not double spending an output. if (i > 0) { if (!await view.spendInputs(this.db, tx)) { - assert(!historical, 'BUG: Spent inputs in historical data!'); throw new VerifyError(block, 'invalid', 'bad-txns-inputs-missingorspent', @@ -736,13 +747,6 @@ Chain.prototype.verifyInputs = async function verifyInputs(block, prev, state) { } } - // Skip everything if we're - // using checkpoints. - if (historical) { - view.addTX(tx, height); - continue; - } - // Verify sequence locks. if (i > 0 && tx.version >= 2) { const valid = await this.verifyLocks(prev, tx, view, state.lockFlags); @@ -790,10 +794,6 @@ Chain.prototype.verifyInputs = async function verifyInputs(block, prev, state) { view.addTX(tx, height); } - // Skip script verification. - if (historical) - return view; - // Make sure the miner isn't trying to conjure more coins. reward += consensus.getReward(height, interval); @@ -967,7 +967,7 @@ Chain.prototype.reorganizeSPV = async function reorganizeSPV(competitor) { */ Chain.prototype.disconnect = async function disconnect(entry) { - let block = await this.db.getBlock(entry.hash); + let block = await this.getBlock(entry.hash); if (!block) { if (!this.options.spv) @@ -1001,7 +1001,7 @@ Chain.prototype.disconnect = async function disconnect(entry) { Chain.prototype.reconnect = async function reconnect(entry) { const flags = common.flags.VERIFY_NONE; - let block = await this.db.getBlock(entry.hash); + let block = await this.getBlock(entry.hash); if (!block) { if (!this.options.spv) @@ -1444,7 +1444,7 @@ Chain.prototype.connect = async function connect(prev, block, flags) { // chainwork is less than or equal to // our tip's. Add the block but do _not_ // connect the inputs. - if (entry.chainwork.cmp(this.tip.chainwork) <= 0) { + if (entry.chainwork.lte(this.tip.chainwork)) { // Save block to an alternate chain. await this.saveAlternate(entry, block, prev, flags); } else { @@ -1725,7 +1725,7 @@ Chain.prototype.purgeOrphans = function purgeOrphans() { Chain.prototype.limitOrphans = function limitOrphans() { const now = util.now(); - let oldest; + let oldest = null; for (const orphan of this.orphanMap.values()) { if (now < orphan.time + 60 * 60) { if (!oldest || orphan.time < oldest.time) @@ -2294,12 +2294,11 @@ Chain.prototype.getTarget = async function getTarget(time, prev) { && prev.bits === pow.bits) { const cache = this.getPrevCache(prev); - if (cache) { + if (cache) prev = cache; - continue; - } + else + prev = await this.getPrevious(prev); - prev = await this.getPrevious(prev); assert(prev); } } @@ -2392,9 +2391,10 @@ Chain.prototype.isActive = async function isActive(prev, deployment) { */ Chain.prototype.getState = async function getState(prev, deployment) { + const bit = deployment.bit; + let window = this.network.minerWindow; let threshold = this.network.activationThreshold; - const bit = deployment.bit; if (deployment.threshold !== -1) threshold = deployment.threshold; @@ -2404,6 +2404,7 @@ Chain.prototype.getState = async function getState(prev, deployment) { if (((prev.height + 1) % window) !== 0) { const height = prev.height - ((prev.height + 1) % window); + prev = await this.getAncestor(prev, height); if (!prev) @@ -2415,6 +2416,7 @@ Chain.prototype.getState = async function getState(prev, deployment) { let entry = prev; let state = thresholdStates.DEFINED; + const compute = []; while (entry) { @@ -2436,6 +2438,7 @@ Chain.prototype.getState = async function getState(prev, deployment) { compute.push(entry); const height = entry.height - window; + entry = await this.getAncestor(entry, height); } @@ -2460,14 +2463,15 @@ Chain.prototype.getState = async function getState(prev, deployment) { } case thresholdStates.STARTED: { const time = await this.getMedianTime(entry); - let block = entry; - let count = 0; if (time >= deployment.timeout) { state = thresholdStates.FAILED; break; } + let block = entry; + let count = 0; + for (let i = 0; i < window; i++) { if (block.hasBit(bit)) count++; diff --git a/lib/mempool/mempool.js b/lib/mempool/mempool.js index 683f00b3..d7621ace 100644 --- a/lib/mempool/mempool.js +++ b/lib/mempool/mempool.js @@ -800,7 +800,7 @@ Mempool.prototype.insertTX = async function insertTX(tx, id) { // We can test whether this is an // non-fully-spent transaction on // the chain. - if (await this.chain.db.hasCoins(tx)) { + if (await this.chain.hasCoins(tx)) { throw new VerifyError(tx, 'alreadyknown', 'txn-already-known', @@ -1657,7 +1657,7 @@ Mempool.prototype.getCoinView = async function getCoinView(tx) { continue; } - const coin = await this.chain.db.readCoin(prevout); + const coin = await this.chain.readCoin(prevout); if (!coin) { const coins = new Coins();