check sequence locks in mempool.
This commit is contained in:
parent
e89ed67843
commit
aff7c0e360
@ -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
|
||||
*/
|
||||
|
||||
@ -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(
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user