From 9b4e363a80180dbd1943c6d8000eabf8eccfd54a Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Wed, 30 Mar 2016 17:32:39 -0700 Subject: [PATCH] more chain refactoring. --- lib/bcoin/abstractblock.js | 2 - lib/bcoin/block.js | 5 - lib/bcoin/chain.js | 355 ++++++++++++++++++++------------ lib/bcoin/mempool.js | 182 +++++----------- lib/bcoin/merkleblock.js | 1 - lib/bcoin/protocol/constants.js | 15 +- lib/bcoin/utils.js | 7 + 7 files changed, 299 insertions(+), 268 deletions(-) diff --git a/lib/bcoin/abstractblock.js b/lib/bcoin/abstractblock.js index 69788998..bb76818a 100644 --- a/lib/bcoin/abstractblock.js +++ b/lib/bcoin/abstractblock.js @@ -75,7 +75,6 @@ AbstractBlock.prototype.verifyHeaders = function verifyHeaders(ret) { // Check proof of work if (!utils.testTarget(this.bits, this.hash())) { - utils.debug('Block failed POW test: %s', this.rhash); ret.reason = 'high-hash'; ret.score = 50; return false; @@ -83,7 +82,6 @@ AbstractBlock.prototype.verifyHeaders = function verifyHeaders(ret) { // Check timestamp against now + 2 hours if (this.ts > utils.now() + 2 * 60 * 60) { - utils.debug('Block timestamp is too high: %s', this.rhash); ret.reason = 'time-too-new'; ret.score = 0; return false; diff --git a/lib/bcoin/block.js b/lib/bcoin/block.js index 17704013..05c1dbda 100644 --- a/lib/bcoin/block.js +++ b/lib/bcoin/block.js @@ -181,7 +181,6 @@ Block.prototype._verify = function _verify(ret) { // Size can't be bigger than MAX_BLOCK_SIZE if (this.txs.length > constants.block.maxSize || this.getVirtualSize() > constants.block.maxSize) { - utils.debug('Block is too large: %s', this.rhash); ret.reason = 'bad-blk-length'; ret.score = 100; return false; @@ -189,7 +188,6 @@ Block.prototype._verify = function _verify(ret) { // First TX must be a coinbase if (!this.txs.length || !this.txs[0].isCoinbase()) { - utils.debug('Block has no coinbase: %s', this.rhash); ret.reason = 'bad-cb-missing'; ret.score = 100; return false; @@ -201,7 +199,6 @@ Block.prototype._verify = function _verify(ret) { // The rest of the txs must not be coinbases if (i > 0 && tx.isCoinbase()) { - utils.debug('Block more than one coinbase: %s', this.rhash); ret.reason = 'bad-cb-multiple'; ret.score = 100; return false; @@ -210,7 +207,6 @@ Block.prototype._verify = function _verify(ret) { // Check for duplicate txids hash = tx.hash('hex'); if (uniq[hash]) { - utils.debug('Block has duplicate txids: %s', this.rhash); ret.reason = 'bad-txns-duplicate'; ret.score = 100; return false; @@ -220,7 +216,6 @@ Block.prototype._verify = function _verify(ret) { // Check merkle root if (this.getMerkleRoot() !== this.merkleRoot) { - utils.debug('Block failed merkleroot test: %s', this.rhash); ret.reason = 'bad-txnmrkleroot'; ret.score = 100; return false; diff --git a/lib/bcoin/chain.js b/lib/bcoin/chain.js index bae5f90c..861633b3 100644 --- a/lib/bcoin/chain.js +++ b/lib/bcoin/chain.js @@ -62,25 +62,28 @@ utils.inherits(Chain, EventEmitter); Chain.prototype._init = function _init() { var self = this; - function getPeer() { + function getHost() { + var peer; + if (!self.node || !self.node.pool) return; - return self.node.pool.peers.load; - } + peer = self.node.pool.peers.load; - function getHost() { - var peer = getPeer(); - if (peer) - return peer.host; - return 'unknown'; + if (!peer) + return 'unknown'; + + return peer.host; } // Hook into events for debugging - // this.on('block', function(block, entry) { - // utils.debug('Block %s (%d) added to chain (%s)', - // utils.revHex(entry.hash), entry.height, getHost()); - // }); + this.on('block', function(block, entry) { + if (self.height < 400000) + return; + + utils.debug('Block %s (%d) added to chain (%s)', + utils.revHex(entry.hash), entry.height, getHost()); + }); this.on('competitor', function(block, entry) { utils.debug('Heads up: Competing chain at height %d:' @@ -397,8 +400,9 @@ Chain.prototype._verifyContext = function _verifyContext(block, prev, callback) Chain.prototype._verify = function _verify(block, prev, callback) { var self = this; var flags = constants.flags.MANDATORY_VERIFY_FLAGS; + var lockFlags = constants.flags.MANDATORY_LOCKTIME_FLAGS; var height, ts, i, tx, coinbaseHeight; - var medianTime, locktimeMedian, segwit; + var medianTime, segwit; var ret = {}; function done(err, result) { @@ -414,10 +418,8 @@ Chain.prototype._verify = function _verify(block, prev, callback) { return done(null, flags); // Ensure it's not an orphan - if (!prev) { - utils.debug('Block has no previous entry: %s', block.rhash); + if (!prev) return done(new VerifyError(block, 'invalid', 'bad-prevblk', 0)); - } prev.ensureAncestors(function(err) { if (err) @@ -427,15 +429,11 @@ Chain.prototype._verify = function _verify(block, prev, callback) { medianTime = prev.getMedianTime(); // Ensure the timestamp is correct - if (block.ts <= medianTime) { - utils.debug('Block time is lower than median: %s', block.rhash); + if (block.ts <= medianTime) return done(new VerifyError(block, 'invalid', 'time-too-old', 0)); - } - if (block.bits !== self.getTarget(prev, block)) { - utils.debug('Block is using wrong target: %s', block.rhash); + if (block.bits !== self.getTarget(prev, block)) return done(new VerifyError(block, 'invalid', 'bad-diffbits', 100)); - } // For some reason bitcoind has p2sh in the // mandatory flags by default, when in reality @@ -443,47 +441,37 @@ Chain.prototype._verify = function _verify(block, prev, callback) { // The first p2sh output and redeem script // appeared on march 7th 2012, only it did // not have a signature. See: - // https://blockchain.info/tx/6a26d2ecb67f27d1fa5524763b49029d7106e91e3cc05743073461a719776192 - // https://blockchain.info/tx/9c08a4d78931342b37fd5f72900fb9983087e6f46c4a097d8a1f52c74e28eaf6 + // 6a26d2ecb67f27d1fa5524763b49029d7106e91e3cc05743073461a719776192 + // 9c08a4d78931342b37fd5f72900fb9983087e6f46c4a097d8a1f52c74e28eaf6 if (block.ts < constants.block.bip16time) flags &= ~constants.flags.VERIFY_P2SH; // Only allow version 2 blocks (coinbase height) // once the majority of blocks are using it. - if (block.version < 2 && prev.isOutdated(2)) { - utils.debug('Block is outdated (v2): %s', block.rhash); + if (block.version < 2 && prev.isOutdated(2)) return done(new VerifyError(block, 'obsolete', 'bad-version', 0)); - } // Only allow version 3 blocks (sig validation) // once the majority of blocks are using it. - if (block.version < 3 && prev.isOutdated(3)) { - utils.debug('Block is outdated (v3): %s', block.rhash); + if (block.version < 3 && prev.isOutdated(3)) return done(new VerifyError(block, 'obsolete', 'bad-version', 0)); - } // Only allow version 4 blocks (checklocktimeverify) // once the majority of blocks are using it. - if (block.version < 4 && prev.isOutdated(4)) { - utils.debug('Block is outdated (v4): %s', block.rhash); + if (block.version < 4 && prev.isOutdated(4)) return done(new VerifyError(block, 'obsolete', 'bad-version', 0)); - } // Only allow version 5 blocks (segwit) // once the majority of blocks are using it. if (network.segwitHeight !== -1 && height >= network.segwitHeight) { - if (block.version < 5 && prev.isOutdated(5)) { - utils.debug('Block is outdated (v5): %s', block.rhash); + if (block.version < 5 && prev.isOutdated(5)) return done(new VerifyError(block, 'obsolete', 'bad-version', 0)); - } } // Only allow version 8 blocks (locktime median past) // once the majority of blocks are using it. - // if (block.version < 8 && prev.isOutdated(8)) { - // utils.debug('Block is outdated (v8): %s', block.rhash); - // return false); - // } + // if (block.version < 8 && prev.isOutdated(8)) + // return done(new VerifyError(block, 'obsolete', 'bad-version', 0)); // Make sure the height contained in the coinbase is correct. if (network.block.bip34height !== -1 && height >= network.block.bip34height) { @@ -499,7 +487,7 @@ Chain.prototype._verify = function _verify(block, prev, callback) { if (block.version >= 4 && prev.isUpgraded(4)) flags |= constants.flags.VERIFY_CHECKLOCKTIMEVERIFY; - // Segregrated witness is now usable (the-bip-that-really-needs-to-be-rewritten) + // Segregrated witness is now usable if (network.segwitHeight !== -1 && height >= network.segwitHeight) { if (block.version >= 5 && prev.isUpgraded(5)) { flags |= constants.flags.VERIFY_WITNESS; @@ -510,32 +498,40 @@ Chain.prototype._verify = function _verify(block, prev, callback) { } } + // Locktime median time past is now enforced. + // if (block.version >= 8 && prev.isUpgraded(8)) + // lockFlags |= constants.flags.MEDIAN_TIME_PAST; + // Can't verify any further when merkleblock or headers. if (block.type !== 'block') return done(null, flags); // Make sure the height contained in the coinbase is correct. if (coinbaseHeight) { - if (block.getCoinbaseHeight() !== height) { - utils.debug('Block has bad coinbase height: %s', block.rhash); + if (block.getCoinbaseHeight() !== height) return done(new VerifyError(block, 'invalid', 'bad-cb-height', 100)); - } } if (block.version >= 5 && segwit) { if (block.commitmentHash !== block.getCommitmentHash()) { - utils.debug('Block failed witnessroot test: %s', block.rhash); - return done(new VerifyError(block, 'invalid', 'bad-blk-wit-length', 100)); + return done(new VerifyError(block, + 'invalid', + 'bad-blk-wit-length', + 100)); } } else { if (block.hasWitness()) { - utils.debug('Unexpected witness data found: %s', block.rhash); - return done(new VerifyError(block, 'invalid', 'unexpected-witness', 100)); + return done(new VerifyError(block, + 'invalid', + 'unexpected-witness', + 100)); } } // Get timestamp for tx.isFinal(). - ts = locktimeMedian ? medianTime : block.ts; + ts = (lockFlags & constants.flags.MEDIAN_TIME_PAST) + ? medianTime + : block.ts; // Check all transactions for (i = 0; i < block.txs.length; i++) { @@ -544,8 +540,10 @@ Chain.prototype._verify = function _verify(block, prev, callback) { // Transactions must be finalized with // regards to nSequence and nLockTime. if (!tx.isFinal(height, ts)) { - utils.debug('TX is not final: %s (%s)', block.rhash, i); - return done(new VerifyError(block, 'invalid', 'bad-txns-nonfinal', 10)); + return done(new VerifyError(block, + 'invalid', + 'bad-txns-nonfinal', + 10)); } } @@ -572,13 +570,14 @@ Chain.prototype._checkDuplicates = function _checkDuplicates(block, prev, callba if (err) return next(err); - // Blocks 91842 and 91880 created duplicate - // txids by using the same exact output script - // and extraNonce. if (result) { - utils.debug('Block is overwriting txids: %s', block.rhash); - if (network.type === 'main' && (height === 91842 || height === 91880)) - return next(); + // Blocks 91842 and 91880 created duplicate + // txids by using the same exact output script + // and extraNonce. + if (network.type === 'main') { + if (height === 91842 || height === 91880) + return next(); + } return next(new VerifyError(block, 'invalid', 'bad-txns-BIP30', 100)); } @@ -588,6 +587,7 @@ Chain.prototype._checkDuplicates = function _checkDuplicates(block, prev, callba }; Chain.prototype._checkInputs = function _checkInputs(block, prev, flags, callback) { + var self = this; var height = prev.height + 1; var scriptCheck = true; var historical = false; @@ -631,25 +631,28 @@ Chain.prototype._checkInputs = function _checkInputs(block, prev, flags, callbac sigops += tx.getSigops(); if (sigops > constants.block.maxSigops) { - utils.debug('Block has too many sigops: %s', block.rhash); - return callback(new VerifyError(block, 'invalid', 'bad-blk-sigops', 100)); + return callback(new VerifyError(block, + 'invalid', + 'bad-blk-sigops', + 100)); } // Coinbases do not have prevouts if (tx.isCoinbase()) continue; - if (!tx.checkInputs(height, ret)) - return callback(new VerifyError(block, 'invalid', ret.reason, ret.score)); + if (!tx.checkInputs(height, ret)) { + return callback(new VerifyError(block, + 'invalid', + ret.reason, + ret.score)); + } for (j = 0; j < tx.inputs.length; j++) { input = tx.inputs[j]; // Ensure tx is not double spending an output if (!input.coin) { - utils.debug('Block is using spent inputs: %s (tx: %s, output: %s)', - block.rhash, tx.rhash, - utils.revHex(input.prevout.hash) + '/' + input.prevout.index); assert(!historical, 'BUG: Spent inputs in historical data!'); return callback(new VerifyError(block, 'invalid', @@ -665,17 +668,6 @@ Chain.prototype._checkInputs = function _checkInputs(block, prev, flags, callbac // Verify the scripts if (!tx.verify(j, true, flags)) { - utils.debug('Block has invalid inputs: %s (%s/%d)', - block.rhash, tx.rhash, j); - utils.debug(input); - utils.debug('Signature Hash v0: %s', - utils.toHex(tx.signatureHash(j, input.coin.script, 'all', 0))); - utils.debug('Signature Hash v1: %s', - utils.toHex(tx.signatureHash(j, input.coin.script, 'all', 1))); - utils.debug('Raw Script: %s', - utils.toHex(input.coin.script.encode())); - utils.debug('Reserialized Script: %s', - utils.toHex(input.coin.script.clone().encode())); assert(!historical, 'BUG: Invalid inputs in historical data!'); return callback(new VerifyError(block, 'invalid', @@ -699,7 +691,6 @@ Chain.prototype._checkInputs = function _checkInputs(block, prev, flags, callbac return callback(err); if (!verified) { - utils.debug('Block has invalid inputs: %s', block.rhash); assert(!historical, 'BUG: Invalid inputs in historical data!'); return callback(new VerifyError(block, 'invalid', @@ -1011,17 +1002,74 @@ Chain.prototype.add = function add(block, callback, force) { chain: !!self.invalid[prevHash] }); self.invalid[hash] = true; - return done(new VerifyError(block, 'duplicate', 'duplicate', 0)); + return done(new VerifyError(block, 'duplicate', 'duplicate', 100)); } // Do we already have this block? + if (self.hasPending(hash)) { + self.emit('exists', block, { + height: block.getCoinbaseHeight(), + hash: hash + }); + return done(new VerifyError(block, 'duplicate', 'duplicate', 0)); + } + + // If the block is already known to be + // an orphan, ignore it. + orphan = self.orphan.map[prevHash]; + if (orphan) { + // If the orphan chain forked, simply + // reset the orphans. + if (orphan.hash('hex') !== hash) { + self.purgeOrphans(); + self.purgePending(); + + self.emit('fork', block, { + height: block.getCoinbaseHeight(), + expected: orphan.hash('hex'), + received: hash, + checkpoint: false + }); + + return done(new VerifyError(block, 'duplicate', 'duplicate', 0)); + } + + self.emit('orphan', block, { + height: block.getCoinbaseHeight(), + hash: hash, + seen: true + }); + + return done(new VerifyError(block, 'invalid', 'bad-prevblk', 0)); + } + + // Special case for genesis block. + if (block.isGenesis()) + return done(); + + // Validate the block we want to add. + // This is only necessary for new + // blocks coming in, not the resolving + // orphans. + if (initial && !block.verify(ret)) { + self.invalid[hash] = true; + self.emit('invalid', block, { + height: block.getCoinbaseHeight(), + hash: hash, + seen: false, + chain: false + }); + return done(new VerifyError(block, 'invalid', ret.reason, ret.score)); + } + self.db.has(hash, function(err, existing) { if (err) return done(err); - if (existing || self.hasPending(hash)) { + // Do we already have this block? + if (existing) { self.emit('exists', block, { - height: -1, + height: block.getCoinbaseHeight(), hash: hash }); return done(new VerifyError(block, 'duplicate', 'duplicate', 0)); @@ -1034,54 +1082,6 @@ Chain.prototype.add = function add(block, callback, force) { height = !prev ? -1 : prev.height + 1; - // Validate the block we want to add. - // This is only necessary for new - // blocks coming in, not the resolving - // orphans. - if (initial && !block.verify(ret)) { - self.invalid[hash] = true; - self.emit('invalid', block, { - height: height, - hash: hash, - seen: false, - chain: false - }); - return done(new VerifyError(block, 'invalid', ret.reason, ret.score)); - } - - // Special case for genesis block. - if (block.isGenesis()) - return done(); - - // If the block is already known to be - // an orphan, ignore it. - orphan = self.orphan.map[prevHash]; - if (orphan) { - // If the orphan chain forked, simply - // reset the orphans. - if (orphan.hash('hex') !== hash) { - self.purgeOrphans(); - self.purgePending(); - - self.emit('fork', block, { - height: block.getCoinbaseHeight(), - expected: orphan.hash('hex'), - received: hash, - checkpoint: false - }); - - return done(); - } - - self.emit('orphan', block, { - height: block.getCoinbaseHeight(), - hash: hash, - seen: true - }); - - return done(new VerifyError(block, 'invalid', 'bad-prevblk', 0)); - } - // Update the best height based on the coinbase. // We do this even for orphans (peers will send // us their highest block during the initial @@ -1590,13 +1590,13 @@ Chain.prototype.getLocator = function getLocator(start, callback, force) { if (typeof height === 'string') return next(); - self.db.get(height, function(err, existing) { + self.db.getHash(height, function(err, hash) { if (err) return next(err); - assert(existing); + assert(hash); - hashes[i] = existing.hash; + hashes[i] = hash; next(); }); @@ -1949,6 +1949,101 @@ Chain.prototype.isSegwitActive = function isSegwitActive(callback) { }); }; +Chain.prototype.checkFinal = function checkFinal(prev, tx, flags, callback) { + var height = prev.height + 1; + + function check(err, ts) { + if (err) + return callback(err); + + return callback(null, tx.isFinal(ts, height)); + } + + if (flags & constants.flags.MEDIAN_TIME_PAST) + return prev.getMedianTimeAsync(check); + + utils.asyncify(check)(null, utils.now()); +}; + +Chain.prototype.getLocks = function getLocks(tx, flags, entry, callback) { + var self = this; + var mask = constants.sequenceLocktimeMask; + var granularity = constants.sequenceLocktimeGranularity; + var disableFlag = constants.sequenceLocktimeDisableFlag; + var typeFlag = constants.sequenceLocktimeTypeFlag; + var hasFlag = flags & constants.flags.VERIFY_SEQUENCE; + var minHeight = -1; + var minTime = -1; + var coinHeight; + + if (tx.version < 2 || !hasFlag) + return utils.asyncify(callback)(null, minHeight, minTime); + + utils.forEachSerial(tx.inputs, function(input, next) { + if (input.sequence & disableFlag) + return next(); + + coinHeight = input.coin.height === -1 + ? self.chain.tip + 1 + : input.coin.height; + + if ((input.sequence & typeFlag) === 0) { + coinHeight += (input.sequence & mask) - 1; + minHeight = Math.max(minHeight, coinHeight); + return next(); + } + + entry.getAncestorByHeight(Math.max(coinHeight - 1, 0), function(err, entry) { + if (err) + return next(err); + + assert(entry, 'Database is corrupt.'); + + entry.getMedianTimeAsync(function(err, coinTime) { + if (err) + return next(err); + + coinTime += ((input.sequence & mask) << granularity) - 1; + minTime = Math.max(minTime, coinTime); + + next(); + }); + }); + }, function(err) { + if (err) + return callback(err); + return callback(null, minHeight, minTime); + }); +}; + +Chain.prototype.evalLocks = function evalLocks(entry, minHeight, minTime, callback) { + if (minHeight >= entry.height) + return utils.asyncify(callback)(null, false); + + if (minTime === -1) + return utils.asyncify(callback)(null, true); + + entry.getMedianTimeAsync(function(err, medianTime) { + if (err) + return callback(err); + + if (minTime >= medianTime) + return callback(null, false); + + return callback(null, true); + }); +}; + +Chain.prototype.checkLocks = function checkLocks(tx, flags, entry, callback) { + var self = this; + this.getLocks(tx, flags, entry, function(err, minHeight, minTime) { + if (err) + return callback(err); + + self.evalLocks(entry, minHeight, minTime, callback); + }); +}; + /** * Expose */ diff --git a/lib/bcoin/mempool.js b/lib/bcoin/mempool.js index 80d90f57..e96e3c33 100644 --- a/lib/bcoin/mempool.js +++ b/lib/bcoin/mempool.js @@ -61,6 +61,7 @@ utils.inherits(Mempool, EventEmitter); Mempool.flags = constants.flags.STANDARD_VERIFY_FLAGS; Mempool.mandatory = constants.flags.MANDATORY_VERIFY_FLAGS; +Mempool.lockFlags = constants.flags.STANDARD_LOCKTIME_FLAGS; Mempool.ANCESTOR_LIMIT = 25; Mempool.MAX_MEMPOOL_SIZE = 300 << 20; @@ -109,9 +110,13 @@ Mempool.prototype._init = function _init() { else self.size = size; - unlock(); - self.loaded = true; - self.emit('open'); + self.chain.open(function(err) { + if (err) + self.emit('error', err); + unlock(); + self.loaded = true; + self.emit('open'); + }); }); }); }); @@ -285,9 +290,10 @@ Mempool.prototype.hasTX = function hasTX(hash, callback) { Mempool.prototype.add = Mempool.prototype.addTX = function addTX(tx, callback, force) { var self = this; - var hash, ts, height, now; var flags = Mempool.flags; + var lockFlags = Mempool.lockFlags; var ret = {}; + var now; var unlock = this._lock(addTX, [tx, callback], force); if (!unlock) @@ -298,8 +304,6 @@ Mempool.prototype.addTX = function addTX(tx, callback, force) { flags |= constants.flags.VERIFY_DISCOURAGE_UPGRADABLE_WITNESS_PROGRAM; } - hash = tx.hash('hex'); - callback = utils.wrap(callback, unlock); callback = utils.asyncify(callback); @@ -321,70 +325,72 @@ Mempool.prototype.addTX = function addTX(tx, callback, force) { if (tx.isCoinbase()) return callback(new VerifyError(tx, 'invalid', 'coinbase', 100)); - // ts = locktimeFlags & LOCKTIME_MEDIAN_PAST - // ? self.chain.tip.getMedianTime() - // : utils.now(); - - ts = utils.now(); - height = this.chain.height + 1; - - if (!tx.isFinal(ts, height)) - return callback(new VerifyError(tx, 'nonstandard', 'non-final', 0)); - - if (this.requireStandard) { - if (!tx.isStandard(flags, ret)) - return callback(new VerifyError(tx, ret.reason, 0)); - } - - this._hasTX(tx, function(err, exists) { + self.chain.checkFinal(self.chain.tip, tx, lockFlags, function(err, isFinal) { if (err) return callback(err); - if (exists) - return callback(new VerifyError(tx, 'alreadyknown', 'txn-already-in-mempool', 0)); + if (!isFinal) + return callback(new VerifyError(tx, 'nonstandard', 'non-final', 0)); - self.tx.isDoubleSpend(tx, function(err, doubleSpend) { + if (self.requireStandard) { + if (!tx.isStandard(flags, ret)) + return callback(new VerifyError(tx, ret.reason, 0)); + } + + self._hasTX(tx, function(err, exists) { if (err) return callback(err); - if (doubleSpend) { + if (exists) { return callback(new VerifyError(tx, - 'duplicate', - 'bad-txns-inputs-spent', + 'alreadyknown', + 'txn-already-in-mempool', 0)); } - self.node.fillCoins(tx, function(err) { + self.tx.isDoubleSpend(tx, function(err, doubleSpend) { if (err) return callback(err); - if (!tx.hasCoins()) { - if (self.size > Mempool.MAX_MEMPOOL_SIZE) { - return callback(new VerifyError(tx, - 'insufficientfee', - 'mempool full', - 0)); - } - utils.debug('Added orphan %s to mempool.', tx.rhash); - return self.storeOrphan(tx, callback); + if (doubleSpend) { + return callback(new VerifyError(tx, + 'duplicate', + 'bad-txns-inputs-spent', + 0)); } - self.verify(tx, function(err) { + self.node.fillCoins(tx, function(err) { if (err) return callback(err); - self.limitMempoolSize(function(err, result) { - if (err) - return callback(err); - - if (!result) { + if (!tx.hasCoins()) { + if (self.size > Mempool.MAX_MEMPOOL_SIZE) { return callback(new VerifyError(tx, 'insufficientfee', 'mempool full', 0)); } + utils.debug('Added orphan %s to mempool.', tx.rhash); + return self.storeOrphan(tx, callback); + } - self.addUnchecked(tx, callback); + self.verify(tx, function(err) { + if (err) + return callback(err); + + self.limitMempoolSize(function(err, result) { + if (err) + return callback(err); + + if (!result) { + return callback(new VerifyError(tx, + 'insufficientfee', + 'mempool full', + 0)); + } + + self.addUnchecked(tx, callback); + }); }); }); }); @@ -437,6 +443,7 @@ Mempool.prototype.removeUnchecked = function removeUnchecked(tx, callback) { Mempool.prototype.verify = function verify(tx, callback) { var self = this; var height = this.chain.height + 1; + var lockFlags = Mempool.lockFlags; var flags = Mempool.flags; var mandatory = Mempool.mandatory; var ret = {}; @@ -448,7 +455,7 @@ Mempool.prototype.verify = function verify(tx, callback) { mandatory |= constants.flags.VERIFY_WITNESS; } - this.checkMempoolLocks(tx, flags, function(err, result) { + this.checkLocks(tx, lockFlags, function(err, result) { if (err) return callback(err); @@ -756,86 +763,7 @@ Mempool.prototype.getSnapshot = function getSnapshot(callback) { return this.tx.getAllHashes(callback); }; -Mempool.prototype.getLocks = function getLocks(tx, flags, entry, callback) { - var self = this; - var mask = constants.sequenceLocktimeMask - var granularity = constants.sequenceLocktimeGranularity; - var disableFlag = constants.sequenceLocktimeDisableFlag; - var typeFlag = constants.sequenceLocktimeTypeFlag; - var hasFlag = flags & constants.flags.VERIFY_CHECKSEQUENCEVERIFY; - var minHeight = -1; - var minTime = -1; - var coinHeight; - - if (tx.version < 2 || !hasFlag) - return utils.asyncify(callback)(null, minHeight, minTime); - - utils.forEachSerial(tx.inputs, function(input, next) { - if (input.sequence & disableFlag) - return next(); - - coinHeight = input.coin.height === -1 - ? self.chain.tip + 1 - : input.coin.height; - - if ((input.sequence & typeFlag) === 0) { - coinHeight += (input.sequence & mask) - 1; - minHeight = Math.max(minHeight, coinHeight); - return next(); - } - - entry.getAncestorByHeight(Math.max(coinHeight - 1, 0), function(err, entry) { - if (err) - return next(err); - - assert(entry, 'Database is corrupt.'); - - entry.getMedianTimeAsync(function(err, coinTime) { - if (err) - return next(err); - - coinTime += ((input.sequence & mask) << granularity) - 1; - minTime = Math.max(minTime, coinTime); - - next(); - }); - }); - }, function(err) { - if (err) - return callback(err); - return callback(null, minHeight, minTime); - }); -}; - -Mempool.prototype.evalLocks = function evalLocks(entry, minHeight, minTime, callback) { - if (minHeight >= entry.height) - return utils.asyncify(callback)(null, false); - - if (minTime === -1) - return utils.asyncify(callback)(null, true); - - entry.getMedianTimeAsync(function(err, medianTime) { - if (err) - return callback(err); - - if (minTime >= medianTime) - return callback(null, false); - - return callback(null, true); - }); -}; - -Mempool.prototype.checkLocks = function checkLocks(tx, flags, entry, callback) { - var self = this; - this.getLocks(tx, flags, entry, function(err, minHeight, minTime) { - if (err) - return callback(err); - - self.evalLocks(entry, minHeight, minTime, callback); - }); -}; - -Mempool.prototype.checkMempoolLocks = function checkMempoolLocks(tx, flags, callback) { +Mempool.prototype.checkLocks = function checkLocks(tx, flags, callback) { var self = this; var tip = this.chain.tip; @@ -851,7 +779,7 @@ Mempool.prototype.checkMempoolLocks = function checkMempoolLocks(tx, flags, call chainwork: tip.chainwork }); - this.checkLocks(tx, flags, index, callback); + return this.chain.checkLocks(tx, flags, index, callback); }; /** diff --git a/lib/bcoin/merkleblock.js b/lib/bcoin/merkleblock.js index d5ee5dd2..47a418bd 100644 --- a/lib/bcoin/merkleblock.js +++ b/lib/bcoin/merkleblock.js @@ -133,7 +133,6 @@ MerkleBlock.prototype._verify = function _verify(ret) { // Verify the partial merkle tree if we are a merkleblock. if (!this._verifyPartial()) { - utils.debug('Block failed merkle test: %s', this.rhash); ret.reason = 'bad-txnmrklroot'; ret.score = 100; return false; diff --git a/lib/bcoin/protocol/constants.js b/lib/bcoin/protocol/constants.js index 39f0c8a2..b1d024b5 100644 --- a/lib/bcoin/protocol/constants.js +++ b/lib/bcoin/protocol/constants.js @@ -294,7 +294,10 @@ exports.flags = { VERIFY_WITNESS: (1 << 10), VERIFY_DISCOURAGE_UPGRADABLE_WITNESS_PROGRAM: (1 << 11), // NOTE: Should be (1 << 10) - but that conflicts with segwit - VERIFY_CHECKSEQUENCEVERIFY: (1 << 12) + VERIFY_CHECKSEQUENCEVERIFY: (1 << 12), + + VERIFY_SEQUENCE: (1 << 0), + MEDIAN_TIME_PAST: (1 << 1) }; // Block validation @@ -310,10 +313,16 @@ exports.flags.STANDARD_VERIFY_FLAGS = | exports.flags.VERIFY_DISCOURAGE_UPGRADABLE_NOPS | exports.flags.VERIFY_CLEANSTACK | exports.flags.VERIFY_CHECKLOCKTIMEVERIFY - | exports.flags.VERIFY_LOW_S; + | exports.flags.VERIFY_LOW_S + | exports.flags.VERIFY_CHECKSEQUENCEVERIFY; // | exports.flags.VERIFY_WITNESS // | exports.flags.VERIFY_DISCOURAGE_UPGRADABLE_WITNESS_PROGRAM - // | exports.flags.VERIFY_CHECKSEQUENCEVERIFY; + +exports.flags.MANDATORY_LOCKTIME_FLAGS = 0; + +exports.flags.STANDARD_LOCKTIME_FLAGS = + exports.flags.VERIFY_SEQUENCE + | exports.flags.MEDIAN_TIME_PAST; exports.versionbits = { // What block version to use for new blocks (pre versionbits) diff --git a/lib/bcoin/utils.js b/lib/bcoin/utils.js index 54a15327..475289ce 100644 --- a/lib/bcoin/utils.js +++ b/lib/bcoin/utils.js @@ -1997,11 +1997,18 @@ if (utils.isBrowser) { function VerifyError(object, code, reason, score) { Error.call(this); + if (Error.captureStackTrace) Error.captureStackTrace(this, VerifyError); + this.type = 'VerifyError'; + this.hash = object.hash(); this.height = object.height; + + if (object.getCoinbaseHeight && this.height === -1) + this.height = object.getCoinbaseHeight(); + this.code = code; this.reason = score === -1 ? null : reason; this.score = score;