diff --git a/lib/blockchain/chain.js b/lib/blockchain/chain.js index 287e011b..19d95b93 100644 --- a/lib/blockchain/chain.js +++ b/lib/blockchain/chain.js @@ -286,7 +286,8 @@ Chain.prototype.verify = co(function* verify(block, prev) { if (this.isGenesis(block)) return this.state; - if (!block.verify(now, ret)) { + // Non-contextual checks. + if (!block.verify(ret)) { err = new VerifyError(block, 'invalid', ret.reason, @@ -295,11 +296,7 @@ Chain.prototype.verify = co(function* verify(block, prev) { // High hash is the only thing an // adversary couldn't mutate in // otherwise valid non-contextual - // checks. The block timestamp - // can't be mutated, but our - // adjusted time might be off. - // We may be able to accept the - // block later. + // checks. if (ret.reason !== 'high-hash') err.malleated = true; @@ -322,17 +319,7 @@ Chain.prototype.verify = co(function* verify(block, prev) { return this.state; } - height = prev.height + 1; ancestors = yield prev.getRetargetAncestors(); - medianTime = prev.getMedianTime(ancestors); - - // Ensure the timestamp is correct. - if (block.ts <= medianTime) { - throw new VerifyError(block, - 'invalid', - 'time-too-old', - 0); - } // Ensure the POW is what we expect. if (block.bits !== this.getTarget(block, prev, ancestors)) { @@ -342,9 +329,48 @@ Chain.prototype.verify = co(function* verify(block, prev) { 100); } + // Ensure the timestamp is correct. + medianTime = prev.getMedianTime(ancestors); + + if (block.ts <= medianTime) { + throw new VerifyError(block, + 'invalid', + 'time-too-old', + 0); + } + + // Check timestamp against adj-time+2hours. + // If this fails we may be able to accept + // the block later. + if (block.ts > now + 2 * 60 * 60) { + err = new VerifyError(block, + 'invalid', + 'time-too-new', + 0); + err.malleated = true; + throw err; + } + // Get the new deployment state. state = yield this.getDeployments(block, prev); + // Get timestamp for tx.isFinal(). + ts = state.hasMTP() ? medianTime : block.ts; + height = prev.height + 1; + + // Transactions must be finalized with + // regards to nSequence and nLockTime. + for (i = 0; i < block.txs.length; i++) { + tx = block.txs[i]; + + if (!tx.isFinal(height, ts)) { + throw new VerifyError(block, + 'invalid', + 'bad-txns-nonfinal', + 10); + } + } + // Make sure the height contained // in the coinbase is correct. if (state.hasBIP34()) { @@ -408,22 +434,6 @@ Chain.prototype.verify = co(function* verify(block, prev) { 100); } - // Get timestamp for tx.isFinal(). - ts = state.hasMTP() ? medianTime : block.ts; - - // Transactions must be finalized with - // regards to nSequence and nLockTime. - for (i = 0; i < block.txs.length; i++) { - tx = block.txs[i]; - - if (!tx.isFinal(height, ts)) { - throw new VerifyError(block, - 'invalid', - 'bad-txns-nonfinal', - 10); - } - } - return state; }); @@ -441,17 +451,6 @@ Chain.prototype.getDeployments = co(function* getDeployments(block, prev) { var state = new DeploymentState(); var active; - // For some reason bitcoind has p2sh in the - // mandatory flags by default, when in reality - // it wasn't activated until march 30th 2012. - // The first p2sh output and redeem script - // appeared on march 7th 2012, only it did - // not have a signature. See: - // 6a26d2ecb67f27d1fa5524763b49029d7106e91e3cc05743073461a719776192 - // 9c08a4d78931342b37fd5f72900fb9983087e6f46c4a097d8a1f52c74e28eaf6 - if (block.ts >= constants.block.BIP16_TIME) - state.flags |= constants.flags.VERIFY_P2SH; - // Only allow version 2 blocks (coinbase height) // once the majority of blocks are using it. if (block.version < 2 && height >= this.network.block.bip34height) @@ -474,6 +473,17 @@ Chain.prototype.getDeployments = co(function* getDeployments(block, prev) { throw new VerifyError(block, 'obsolete', 'bad-version', 0); } + // For some reason bitcoind has p2sh in the + // mandatory flags by default, when in reality + // it wasn't activated until march 30th 2012. + // The first p2sh output and redeem script + // appeared on march 7th 2012, only it did + // not have a signature. See: + // 6a26d2ecb67f27d1fa5524763b49029d7106e91e3cc05743073461a719776192 + // 9c08a4d78931342b37fd5f72900fb9983087e6f46c4a097d8a1f52c74e28eaf6 + if (block.ts >= constants.block.BIP16_TIME) + state.flags |= constants.flags.VERIFY_P2SH; + // Coinbase heights are now enforced (bip34). if (height >= this.network.block.bip34height) state.bip34 = true; @@ -1249,7 +1259,7 @@ Chain.prototype._add = co(function* add(block) { // This is only necessary for new // blocks coming in, not the resolving // orphans. - if (initial && !block.verify(now, ret)) { + if (initial && !block.verify(ret)) { if (ret.reason === 'high-hash') this.invalid[hash] = true; this.emit('invalid', block, block.getCoinbaseHeight()); diff --git a/lib/http/rpc.js b/lib/http/rpc.js index 3a65cba9..5b90bb80 100644 --- a/lib/http/rpc.js +++ b/lib/http/rpc.js @@ -1241,7 +1241,6 @@ RPC.prototype.gettxoutproof = co(function* gettxoutproof(args) { }); RPC.prototype.verifytxoutproof = co(function* verifytxoutproof(args) { - var now = this.network.now(); var res = []; var i, block, hash, entry; @@ -1250,7 +1249,7 @@ RPC.prototype.verifytxoutproof = co(function* verifytxoutproof(args) { block = MerkleBlock.fromRaw(toString(args[0]), 'hex'); - if (!block.verify(now)) + if (!block.verify()) return res; entry = yield this.chain.db.getEntry(block.hash('hex')); @@ -1311,7 +1310,6 @@ RPC.prototype._submitwork = co(function* _submitwork(data) { }); RPC.prototype.__submitwork = co(function* _submitwork(data) { - var now = this.network.now(); var attempt = this.attempt; var block, header, cb, cur; @@ -1333,7 +1331,7 @@ RPC.prototype.__submitwork = co(function* _submitwork(data) { return false; } - if (!header.verify(now)) + if (!header.verify()) return false; cb = this.coinbase[header.merkleRoot]; @@ -4012,7 +4010,6 @@ RPC.prototype.walletpassphrase = co(function* walletpassphrase(args) { }); RPC.prototype.importprunedfunds = co(function* importprunedfunds(args) { - var now = this.network.now(); var tx, block, label, height; if (args.help || args.length < 2 || args.length > 3) { @@ -4032,7 +4029,7 @@ RPC.prototype.importprunedfunds = co(function* importprunedfunds(args) { if (args.length === 3) label = toString(args[2]); - if (!block.verify(now)) + if (!block.verify()) throw new RPCError('Invalid proof.'); if (!block.hasTX(tx)) diff --git a/lib/net/bip152.js b/lib/net/bip152.js index ac174ae3..d486c12c 100644 --- a/lib/net/bip152.js +++ b/lib/net/bip152.js @@ -52,8 +52,8 @@ function CompactBlock(options) { util.inherits(CompactBlock, AbstractBlock); -CompactBlock.prototype._verify = function _verify(now, ret) { - return this.verifyHeaders(now, ret); +CompactBlock.prototype._verify = function _verify(ret) { + return this.verifyHeaders(ret); }; CompactBlock.prototype.fromOptions = function fromOptions(options) { diff --git a/lib/net/pool.js b/lib/net/pool.js index b0b90386..ed170437 100644 --- a/lib/net/pool.js +++ b/lib/net/pool.js @@ -756,12 +756,11 @@ Pool.prototype._handleHeaders = co(function* _handleHeaders(headers, peer) { */ Pool.prototype.__handleHeaders = co(function* _handleHeaders(headers, peer) { - var i, now, ret, header, hash, last; + var i, ret, header, hash, last; if (!this.options.headers) return; - now = this.network.now(); ret = new VerifyResult(); this.logger.debug( @@ -787,7 +786,7 @@ Pool.prototype.__handleHeaders = co(function* _handleHeaders(headers, peer) { throw new Error('Bad header chain.'); } - if (!header.verify(now, ret)) { + if (!header.verify(ret)) { peer.reject(header, 'invalid', ret.reason, 100); throw new Error('Invalid header.'); } diff --git a/lib/primitives/abstractblock.js b/lib/primitives/abstractblock.js index 5ad0855f..ced1cccd 100644 --- a/lib/primitives/abstractblock.js +++ b/lib/primitives/abstractblock.js @@ -187,11 +187,11 @@ AbstractBlock.prototype.abbr = function abbr(writer) { * @returns {Boolean} */ -AbstractBlock.prototype.verify = function verify(now, ret) { +AbstractBlock.prototype.verify = function verify(ret) { var valid = this._valid; if (valid == null) { - valid = this._verify(now, ret); + valid = this._verify(ret); if (!this.mutable) this._valid = valid; } @@ -202,16 +202,12 @@ AbstractBlock.prototype.verify = function verify(now, ret) { /** * Verify the block headers (called by `verify()` in * all objects which inherit from AbstractBlock). - * @param {Number|null} - Adjusted time. * @param {Object?} ret - Return object, may be * set with properties `reason` and `score`. * @returns {Boolean} */ -AbstractBlock.prototype.verifyHeaders = function verifyHeaders(now, ret) { - if (!now) - now = util.now(); - +AbstractBlock.prototype.verifyHeaders = function verifyHeaders(ret) { if (!ret) ret = new VerifyResult(); @@ -222,13 +218,6 @@ AbstractBlock.prototype.verifyHeaders = function verifyHeaders(now, ret) { return false; } - // Check timestamp against adjusted-time + 2 hours. - if (this.ts > now + 2 * 60 * 60) { - ret.reason = 'time-too-new'; - ret.score = 0; - return false; - } - return true; }; diff --git a/lib/primitives/block.js b/lib/primitives/block.js index ada5f533..9f302829 100644 --- a/lib/primitives/block.js +++ b/lib/primitives/block.js @@ -427,13 +427,12 @@ Block.prototype.getCommitmentHash = function getCommitmentHash(enc) { * Do non-contextual verification on the block. Including checking the block * size, the coinbase and the merkle root. This is consensus-critical. * @alias Block#verify - * @param {Number|null} - Adjusted time. * @param {Object?} ret - Return object, may be * set with properties `reason` and `score`. * @returns {Boolean} */ -Block.prototype._verify = function _verify(now, ret) { +Block.prototype._verify = function _verify(ret) { var sigops = 0; var scale = constants.WITNESS_SCALE_FACTOR; var i, tx, merkle; @@ -441,11 +440,28 @@ Block.prototype._verify = function _verify(now, ret) { if (!ret) ret = new VerifyResult(); - if (!this.verifyHeaders(now, ret)) + if (!this.verifyHeaders(ret)) return false; - // Size can't be bigger than MAX_BLOCK_SIZE - if (this.txs.length > constants.block.MAX_SIZE + // Check merkle root + merkle = this.createMerkleRoot('hex'); + + // If the merkle is mutated, + // we have duplicate txs. + if (!merkle) { + ret.reason = 'bad-txns-duplicate'; + ret.score = 100; + return false; + } + + if (this.merkleRoot !== merkle) { + ret.reason = 'bad-txnmrklroot'; + ret.score = 100; + return false; + } + + if (this.txs.length === 0 + || this.txs.length > constants.block.MAX_SIZE || this.getBaseSize() > constants.block.MAX_SIZE) { ret.reason = 'bad-blk-length'; ret.score = 100; @@ -483,23 +499,6 @@ Block.prototype._verify = function _verify(now, ret) { } } - // Check merkle root - merkle = this.createMerkleRoot('hex'); - - // If the merkle is mutated, - // we have duplicate txs. - if (!merkle) { - ret.reason = 'bad-txns-duplicate'; - ret.score = 100; - return false; - } - - if (this.merkleRoot !== merkle) { - ret.reason = 'bad-txnmrklroot'; - ret.score = 100; - return false; - } - return true; }; @@ -584,12 +583,9 @@ Block.prototype.getPrevout = function getPrevout() { var prevout = {}; var i, j, tx, input; - for (i = 0; i < this.txs.length; i++) { + for (i = 1; i < this.txs.length; i++) { tx = this.txs[i]; - if (tx.isCoinbase()) - continue; - for (j = 0; j < tx.inputs.length; j++) { input = tx.inputs[j]; prevout[input.prevout.hash] = true; diff --git a/lib/primitives/headers.js b/lib/primitives/headers.js index 9ce9a850..c09bd017 100644 --- a/lib/primitives/headers.js +++ b/lib/primitives/headers.js @@ -32,14 +32,13 @@ util.inherits(Headers, AbstractBlock); /** * Do non-contextual verification on the headers. * @alias Headers#verify - * @param {Number|null} - Adjusted time. * @param {Object?} ret - Return object, may be * set with properties `reason` and `score`. * @returns {Boolean} */ -Headers.prototype._verify = function _verify(now, ret) { - return this.verifyHeaders(now, ret); +Headers.prototype._verify = function _verify(ret) { + return this.verifyHeaders(ret); }; /** diff --git a/lib/primitives/memblock.js b/lib/primitives/memblock.js index 4156d1ed..3ca10296 100644 --- a/lib/primitives/memblock.js +++ b/lib/primitives/memblock.js @@ -106,14 +106,13 @@ MemBlock.prototype.getSize = function getSize() { /** * Verify the block headers. * @alias MemBlock#verify - * @param {Number|null} - Adjusted time. * @param {Object?} ret - Return object, may be * set with properties `reason` and `score`. * @returns {Boolean} */ -MemBlock.prototype._verify = function _verify(now, ret) { - return this.verifyHeaders(now, ret); +MemBlock.prototype._verify = function _verify(ret) { + return this.verifyHeaders(ret); }; /** diff --git a/lib/primitives/merkleblock.js b/lib/primitives/merkleblock.js index dcd9f43e..d470b518 100644 --- a/lib/primitives/merkleblock.js +++ b/lib/primitives/merkleblock.js @@ -279,17 +279,16 @@ MerkleBlock.prototype.extractTree = function extractTree() { * Do non-contextual verification on the block. * Verify the headers and the partial merkle tree. * @alias MerkleBlock#verify - * @param {Number|null} - Adjusted time. * @param {Object?} ret - Return object, may be * set with properties `reason` and `score`. * @returns {Boolean} */ -MerkleBlock.prototype._verify = function _verify(now, ret) { +MerkleBlock.prototype._verify = function _verify(ret) { if (!ret) ret = new VerifyResult(); - if (!this.verifyHeaders(now, ret)) + if (!this.verifyHeaders(ret)) return false; if (!this.verifyPartial()) { diff --git a/test/block-test.js b/test/block-test.js index 336f4adf..25a57254 100644 --- a/test/block-test.js +++ b/test/block-test.js @@ -166,7 +166,7 @@ describe('Block', function() { block2.merkleRoot = constants.NULL_HASH; delete block2._valid; var ret = {}; - assert(!block2.verify(0, ret)); + assert(!block2.verify(ret)); assert.equal(ret.reason, 'bad-txnmrklroot'); delete block2._valid; delete block2._hash; @@ -179,7 +179,7 @@ describe('Block', function() { mblock2.hash(); mblock2.merkleRoot = constants.NULL_HASH; var ret = {}; - assert(!mblock2.verify(0, ret)); + assert(!mblock2.verify(ret)); assert.equal(ret.reason, 'bad-txnmrklroot'); delete mblock2._validPartial; delete mblock2._valid; @@ -193,7 +193,7 @@ describe('Block', function() { block2.hash(); block2.bits = 403014710; var ret = {}; - assert(!block2.verify(0, ret)); + assert(!block2.verify(ret)); assert.equal(ret.reason, 'high-hash'); delete block2._valid; delete block2._hash; @@ -205,7 +205,7 @@ describe('Block', function() { var block2 = new bcoin.block(block); block2.txs.push(block2.txs[block2.txs.length - 1]); var ret = {}; - assert(!block2.verify(0, ret)); + assert(!block2.verify(ret)); assert.equal(ret.reason, 'bad-txns-duplicate'); });