check sequence locks in mempool.

This commit is contained in:
Christopher Jeffrey 2016-03-28 03:10:36 -07:00
parent e89ed67843
commit aff7c0e360
4 changed files with 221 additions and 106 deletions

View File

@ -430,138 +430,154 @@ Mempool.prototype.verify = function verify(tx, callback) {
mandatory |= constants.flags.VERIFY_WITNESS; mandatory |= constants.flags.VERIFY_WITNESS;
} }
if (this.requireStandard && !tx.isStandardInputs(flags)) { this.checkMempoolLocks(tx, flags, function(err, result) {
return callback(new VerifyError( if (err)
'nonstandard', return callback(err);
'bad-txns-nonstandard-inputs',
0));
}
if (tx.getSigops(true) > constants.script.maxSigops) { if (!result) {
return callback(new VerifyError( return callback(new VerifyError(
'nonstandard', 'nonstandard',
'bad-txns-too-many-sigops', 'non-BIP68-final',
0)); 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 (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( return callback(new VerifyError(
'invalid', 'invalid',
'bad-txns-inputvalues-outofrange', 'bad-txns-inputvalues-outofrange',
100)); 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) { fee = total.sub(tx.getOutputValue());
return callback(new VerifyError(
'invalid',
'bad-txns-inputvalues-outofrange',
100));
}
if (tx.getOutputValue().cmp(total) > 0) if (fee.cmpn(0) < 0)
return callback(new VerifyError('invalid', 'bad-txns-in-belowout', 100)); 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) minFee = tx.getMinFee();
return callback(new VerifyError('invalid', 'bad-txns-fee-negative', 100)); if (fee.cmp(minFee) < 0) {
if (self.relayPriority && fee.cmpn(0) === 0) {
if (fee.cmp(constants.maxMoney) > 0) free = tx.isFree(height);
return callback(new VerifyError('invalid', 'bad-txns-fee-outofrange', 100)); if (!free) {
return callback(new VerifyError(
minFee = tx.getMinFee(); 'insufficientfee',
if (fee.cmp(minFee) < 0) { 'insufficient priority',
if (this.relayPriority && fee.cmpn(0) === 0) { 0));
free = tx.isFree(height); }
if (!free) { } else {
return callback(new VerifyError( return callback(new VerifyError(
'insufficientfee', 'insufficientfee',
'insufficient priority', 'insufficient fee',
0)); 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) if (!self.lastTime)
return callback(new VerifyError('highfee', 'absurdly-high-fee', 0)); self.lastTime = now;
this.countAncestors(tx, function(err, count) { self.freeCount *= Math.pow(1 - 1 / 600, now - self.lastTime);
if (err) self.lastTime = now;
return callback(err);
if (count > Mempool.ANCESTOR_LIMIT) { if (self.freeCount > self.limitFreeRelay * 10 * 1000) {
return callback(new VerifyError( return callback(new VerifyError(
'nonstandard', 'insufficientfee',
'too-long-mempool-chain', 'rate limited free transaction',
0)); 0));
}
self.freeCount += tx.getSize();
} }
// Do this in the worker pool. if (self.rejectInsaneFees && fee.cmp(minFee.muln(10000)) > 0)
tx.verifyAsync(null, true, flags, function(err, result) { return callback(new VerifyError('highfee', 'absurdly-high-fee', 0));
self.countAncestors(tx, function(err, count) {
if (err) if (err)
return callback(err); return callback(err);
if (!result) { if (count > Mempool.ANCESTOR_LIMIT) {
return tx.verifyAsync(null, true, mandatory, function(err, result) { return callback(new VerifyError(
if (err) 'nonstandard',
return callback(err); 'too-long-mempool-chain',
0));
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(); // 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; 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 * VerifyError
*/ */

View File

@ -273,8 +273,9 @@ exports.hd = {
exports.locktimeThreshold = 500000000; // Tue Nov 5 00:53:20 1985 UTC 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.sequenceLocktimeTypeFlag = 1 << 22;
exports.sequenceLocktimeGranularity = 9;
exports.sequenceLocktimeMask = 0x0000ffff; exports.sequenceLocktimeMask = 0x0000ffff;
exports.oneHash = new Buffer( exports.oneHash = new Buffer(

View File

@ -1084,7 +1084,7 @@ Script.checkSequence = function checkSequence(sequence, tx, i) {
var txSequence = tx.inputs[i].sequence; var txSequence = tx.inputs[i].sequence;
var locktimeMask, txSequenceMasked, sequenceMasked; var locktimeMask, txSequenceMasked, sequenceMasked;
if (tx.version < 2) if ((tx.version >>> 0) < 2)
return false; return false;
if (txSequence & constants.sequenceLocktimeDisableFlag) if (txSequence & constants.sequenceLocktimeDisableFlag)

View File

@ -707,7 +707,7 @@ TX.prototype.isStandard = function isStandard(flags, ts, height, ret) {
if (flags & constants.flags.VERIFY_CHECKSEQUENCEVERIFY) if (flags & constants.flags.VERIFY_CHECKSEQUENCEVERIFY)
maxVersion = Math.max(maxVersion, 2); maxVersion = Math.max(maxVersion, 2);
if (this.version < 1 || this.version > maxVersion) { if ((this.version >>> 0) < 1 || (this.version >>> 0) > maxVersion) {
ret.reason = 'version'; ret.reason = 'version';
return false; return false;
} }