From aff7c0e36090b20c381f2b18c8b1563b6b0b485e Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Mon, 28 Mar 2016 03:10:36 -0700 Subject: [PATCH] check sequence locks in mempool. --- lib/bcoin/mempool.js | 320 ++++++++++++++++++++++---------- lib/bcoin/protocol/constants.js | 3 +- lib/bcoin/script.js | 2 +- lib/bcoin/tx.js | 2 +- 4 files changed, 221 insertions(+), 106 deletions(-) diff --git a/lib/bcoin/mempool.js b/lib/bcoin/mempool.js index 3dff81af..ae27ebd5 100644 --- a/lib/bcoin/mempool.js +++ b/lib/bcoin/mempool.js @@ -430,138 +430,154 @@ Mempool.prototype.verify = function verify(tx, callback) { mandatory |= constants.flags.VERIFY_WITNESS; } - if (this.requireStandard && !tx.isStandardInputs(flags)) { - return callback(new VerifyError( - 'nonstandard', - 'bad-txns-nonstandard-inputs', - 0)); - } + this.checkMempoolLocks(tx, flags, function(err, result) { + if (err) + return callback(err); - if (tx.getSigops(true) > constants.script.maxSigops) { - return callback(new VerifyError( - 'nonstandard', - 'bad-txns-too-many-sigops', - 0)); - } - - total = new bn(0); - for (i = 0; i < tx.inputs.length; i++) { - input = tx.inputs[i]; - coin = input.output; - - if (coin.coinbase) { - if (this.chain.height - coin.height < constants.tx.coinbaseMaturity) { - return callback(new VerifyError( - 'invalid', - 'bad-txns-premature-spend-of-coinbase', - 0)); - } + if (!result) { + return callback(new VerifyError( + 'nonstandard', + 'non-BIP68-final', + 0)); } - if (coin.value.cmpn(0) < 0 || coin.value.cmp(constants.maxMoney) > 0) { + if (self.requireStandard && !tx.isStandardInputs(flags)) { + return callback(new VerifyError( + 'nonstandard', + 'bad-txns-nonstandard-inputs', + 0)); + } + + if (tx.getSigops(true) > constants.script.maxSigops) { + return callback(new VerifyError( + 'nonstandard', + 'bad-txns-too-many-sigops', + 0)); + } + + total = new bn(0); + for (i = 0; i < tx.inputs.length; i++) { + input = tx.inputs[i]; + coin = input.output; + + if (coin.coinbase) { + if (self.chain.height - coin.height < constants.tx.coinbaseMaturity) { + return callback(new VerifyError( + 'invalid', + 'bad-txns-premature-spend-of-coinbase', + 0)); + } + } + + if (coin.value.cmpn(0) < 0 || coin.value.cmp(constants.maxMoney) > 0) { + return callback(new VerifyError( + 'invalid', + 'bad-txns-inputvalues-outofrange', + 100)); + } + + total.iadd(coin.value); + } + + if (total.cmpn(0) < 0 || total.cmp(constants.maxMoney) > 0) { return callback(new VerifyError( 'invalid', 'bad-txns-inputvalues-outofrange', 100)); } - total.iadd(coin.value); - } + if (tx.getOutputValue().cmp(total) > 0) + return callback(new VerifyError('invalid', 'bad-txns-in-belowout', 100)); - if (total.cmpn(0) < 0 || total.cmp(constants.maxMoney) > 0) { - return callback(new VerifyError( - 'invalid', - 'bad-txns-inputvalues-outofrange', - 100)); - } + fee = total.sub(tx.getOutputValue()); - if (tx.getOutputValue().cmp(total) > 0) - return callback(new VerifyError('invalid', 'bad-txns-in-belowout', 100)); + if (fee.cmpn(0) < 0) + return callback(new VerifyError('invalid', 'bad-txns-fee-negative', 100)); - fee = total.sub(tx.getOutputValue()); + if (fee.cmp(constants.maxMoney) > 0) { + return callback(new VerifyError( + 'invalid', + 'bad-txns-fee-outofrange', + 100)); + } - if (fee.cmpn(0) < 0) - return callback(new VerifyError('invalid', 'bad-txns-fee-negative', 100)); - - if (fee.cmp(constants.maxMoney) > 0) - return callback(new VerifyError('invalid', 'bad-txns-fee-outofrange', 100)); - - minFee = tx.getMinFee(); - if (fee.cmp(minFee) < 0) { - if (this.relayPriority && fee.cmpn(0) === 0) { - free = tx.isFree(height); - if (!free) { + minFee = tx.getMinFee(); + if (fee.cmp(minFee) < 0) { + if (self.relayPriority && fee.cmpn(0) === 0) { + free = tx.isFree(height); + if (!free) { + return callback(new VerifyError( + 'insufficientfee', + 'insufficient priority', + 0)); + } + } else { return callback(new VerifyError( 'insufficientfee', - 'insufficient priority', + 'insufficient fee', 0)); } - } else { - return callback(new VerifyError( - 'insufficientfee', - 'insufficient fee', - 0)); - } - } - - if (this.limitFree && free) { - now = utils.now(); - - if (!this.lastTime) - this.lastTime = now; - - this.freeCount *= Math.pow(1 - 1 / 600, now - this.lastTime); - this.lastTime = now; - - if (this.freeCount > this.limitFreeRelay * 10 * 1000) { - return callback(new VerifyError( - 'insufficientfee', - 'rate limited free transaction', - 0)); } - this.freeCount += tx.getSize(); - } + if (self.limitFree && free) { + now = utils.now(); - if (this.rejectInsaneFees && fee.cmp(minFee.muln(10000)) > 0) - return callback(new VerifyError('highfee', 'absurdly-high-fee', 0)); + if (!self.lastTime) + self.lastTime = now; - this.countAncestors(tx, function(err, count) { - if (err) - return callback(err); + self.freeCount *= Math.pow(1 - 1 / 600, now - self.lastTime); + self.lastTime = now; - if (count > Mempool.ANCESTOR_LIMIT) { - return callback(new VerifyError( - 'nonstandard', - 'too-long-mempool-chain', - 0)); + if (self.freeCount > self.limitFreeRelay * 10 * 1000) { + return callback(new VerifyError( + 'insufficientfee', + 'rate limited free transaction', + 0)); + } + + self.freeCount += tx.getSize(); } - // Do this in the worker pool. - tx.verifyAsync(null, true, flags, function(err, result) { + if (self.rejectInsaneFees && fee.cmp(minFee.muln(10000)) > 0) + return callback(new VerifyError('highfee', 'absurdly-high-fee', 0)); + + self.countAncestors(tx, function(err, count) { if (err) return callback(err); - if (!result) { - return tx.verifyAsync(null, true, mandatory, function(err, result) { - if (err) - return callback(err); - - if (result) { - return callback(new VerifyError( - 'nonstandard', - 'non-mandatory-script-verify-flag', - 0)); - } - - return callback(new VerifyError( - 'nonstandard', - 'mandatory-script-verify-flag', - 0)); - }); + if (count > Mempool.ANCESTOR_LIMIT) { + return callback(new VerifyError( + 'nonstandard', + 'too-long-mempool-chain', + 0)); } - return callback(); + // Do this in the worker pool. + tx.verifyAsync(null, true, flags, function(err, result) { + if (err) + return callback(err); + + if (!result) { + return tx.verifyAsync(null, true, mandatory, function(err, result) { + if (err) + return callback(err); + + if (result) { + return callback(new VerifyError( + 'nonstandard', + 'non-mandatory-script-verify-flag', + 0)); + } + + return callback(new VerifyError( + 'nonstandard', + 'mandatory-script-verify-flag', + 0)); + }); + } + + return callback(); + }); }); }); }; @@ -814,6 +830,104 @@ Mempool.checkTX = function checkTX(tx, peer) { return true; }; +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 >>> 0) < 2 || !hasFlag) + return utils.asyncify(callback)(null, minHeight, minTime); + + utils.forEachSerial(tx.inputs, function(input, next) { + if (input.sequence & disableFlag) + return next(); + + coinHeight = coin.height === -1 + ? self.chain.tip + 1 + : 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) { + var self = this; + var tip = this.chain.tip; + + var index = new bcoin.chainblock(this.chain, { + hash: utils.toHex(constants.zeroHash), + version: tip.version, + prevBlock: tip.hash, + merkleRoot: utils.toHex(constants.zeroHash), + ts: utils.now(), + bits: 0, + nonce: 0, + height: tip.height + 1, + chainwork: tip.chainwork + }); + + this.checkLocks(tx, flags, index, callback); +} + /** * VerifyError */ diff --git a/lib/bcoin/protocol/constants.js b/lib/bcoin/protocol/constants.js index dd6e8ded..497c90be 100644 --- a/lib/bcoin/protocol/constants.js +++ b/lib/bcoin/protocol/constants.js @@ -273,8 +273,9 @@ exports.hd = { exports.locktimeThreshold = 500000000; // Tue Nov 5 00:53:20 1985 UTC -exports.sequenceLocktimeDisableFlag = 0x80000000; // (1 << 31) +exports.sequenceLocktimeDisableFlag = (1 << 31) >>> 0; exports.sequenceLocktimeTypeFlag = 1 << 22; +exports.sequenceLocktimeGranularity = 9; exports.sequenceLocktimeMask = 0x0000ffff; exports.oneHash = new Buffer( diff --git a/lib/bcoin/script.js b/lib/bcoin/script.js index 645ea951..d8585ba6 100644 --- a/lib/bcoin/script.js +++ b/lib/bcoin/script.js @@ -1084,7 +1084,7 @@ Script.checkSequence = function checkSequence(sequence, tx, i) { var txSequence = tx.inputs[i].sequence; var locktimeMask, txSequenceMasked, sequenceMasked; - if (tx.version < 2) + if ((tx.version >>> 0) < 2) return false; if (txSequence & constants.sequenceLocktimeDisableFlag) diff --git a/lib/bcoin/tx.js b/lib/bcoin/tx.js index 451c6458..d167c90a 100644 --- a/lib/bcoin/tx.js +++ b/lib/bcoin/tx.js @@ -707,7 +707,7 @@ TX.prototype.isStandard = function isStandard(flags, ts, height, ret) { if (flags & constants.flags.VERIFY_CHECKSEQUENCEVERIFY) maxVersion = Math.max(maxVersion, 2); - if (this.version < 1 || this.version > maxVersion) { + if ((this.version >>> 0) < 1 || (this.version >>> 0) > maxVersion) { ret.reason = 'version'; return false; }