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;
}
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
*/

View File

@ -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(

View File

@ -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)

View File

@ -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;
}