mempool: use coin viewpoint.
This commit is contained in:
parent
8b99b5103b
commit
1269aab7a5
@ -2229,7 +2229,7 @@ Chain.prototype.getDeploymentState = co(function* getDeploymentState() {
|
||||
* @returns {Promise} - Returns Boolean.
|
||||
*/
|
||||
|
||||
Chain.prototype.checkFinal = co(function* checkFinal(prev, tx, flags) {
|
||||
Chain.prototype.verifyFinal = co(function* verifyFinal(prev, tx, flags) {
|
||||
var height = prev.height + 1;
|
||||
var ts;
|
||||
|
||||
|
||||
@ -36,6 +36,16 @@ CoinView.prototype.get = function get(hash) {
|
||||
return this.unspent[hash];
|
||||
};
|
||||
|
||||
/**
|
||||
* Test whether the view has an entry.
|
||||
* @param {Hash} hash
|
||||
* @returns {Boolean}
|
||||
*/
|
||||
|
||||
CoinView.prototype.has = function has(hash) {
|
||||
return this.unspent[hash] != null;
|
||||
};
|
||||
|
||||
/**
|
||||
* Add coins to the collection.
|
||||
* @param {Coins} coins
|
||||
@ -95,6 +105,23 @@ CoinView.prototype.spendFrom = function spendFrom(coins, index) {
|
||||
return entry.toCoin(coins, index);
|
||||
};
|
||||
|
||||
/**
|
||||
* Get a single coin.
|
||||
* @param {Hash} hash
|
||||
* @param {Number} index
|
||||
* @returns {Coin}
|
||||
*/
|
||||
|
||||
CoinView.prototype.getCoin = function getCoin(hash, index) {
|
||||
var coins = this.get(hash);
|
||||
var entry;
|
||||
|
||||
if (!coins)
|
||||
return;
|
||||
|
||||
return coins.getCoin(index);
|
||||
};
|
||||
|
||||
/**
|
||||
* Retrieve coins from database.
|
||||
* @param {TX} tx
|
||||
|
||||
@ -106,6 +106,8 @@ function Mempool(options) {
|
||||
this.replaceByFee = !!this.options.replaceByFee;
|
||||
|
||||
this.maxSize = options.maxSize || constants.mempool.MAX_MEMPOOL_SIZE;
|
||||
this.maxOrphans = options.maxOrphans || constants.mempool.MAX_ORPHAN_TX;
|
||||
this.maxAncestors = options.maxAncestors || constants.mempool.ANCESTOR_LIMIT;
|
||||
this.expiryTime = options.expiryTime || constants.mempool.MEMPOOL_EXPIRY;
|
||||
this.minRelay = this.network.minRelay;
|
||||
}
|
||||
@ -340,7 +342,7 @@ Mempool.prototype.limitOrphans = function limitOrphans() {
|
||||
var orphans = Object.keys(this.orphans);
|
||||
var i, hash;
|
||||
|
||||
while (this.totalOrphans > constants.mempool.MAX_ORPHAN_TX) {
|
||||
while (this.totalOrphans > this.maxOrphans) {
|
||||
i = crypto.randomRange(0, orphans.length);
|
||||
hash = orphans[i];
|
||||
orphans.splice(i, 1);
|
||||
@ -587,19 +589,11 @@ Mempool.prototype.addTX = co(function* addTX(tx) {
|
||||
Mempool.prototype._addTX = co(function* _addTX(tx) {
|
||||
var lockFlags = constants.flags.STANDARD_LOCKTIME_FLAGS;
|
||||
var hash = tx.hash('hex');
|
||||
var ret, entry, result, exists;
|
||||
var ret = new VerifyResult();
|
||||
var entry, view, missing;
|
||||
|
||||
assert(!tx.mutable, 'Cannot add mutable TX to mempool.');
|
||||
|
||||
ret = new VerifyResult();
|
||||
|
||||
if (tx.ts !== 0) {
|
||||
throw new VerifyError(tx,
|
||||
'alreadyknown',
|
||||
'txn-already-known',
|
||||
0);
|
||||
}
|
||||
|
||||
if (!tx.isSane(ret)) {
|
||||
throw new VerifyError(tx,
|
||||
'invalid',
|
||||
@ -649,9 +643,7 @@ Mempool.prototype._addTX = co(function* _addTX(tx) {
|
||||
}
|
||||
}
|
||||
|
||||
result = yield this.chain.checkFinal(this.chain.tip, tx, lockFlags);
|
||||
|
||||
if (!result) {
|
||||
if (!(yield this.verifyFinal(tx, lockFlags))) {
|
||||
throw new VerifyError(tx,
|
||||
'nonstandard',
|
||||
'non-final',
|
||||
@ -665,9 +657,7 @@ Mempool.prototype._addTX = co(function* _addTX(tx) {
|
||||
0);
|
||||
}
|
||||
|
||||
exists = yield this.chain.db.hasCoins(hash);
|
||||
|
||||
if (exists) {
|
||||
if (yield this.chain.db.hasCoins(hash)) {
|
||||
throw new VerifyError(tx,
|
||||
'alreadyknown',
|
||||
'txn-already-known',
|
||||
@ -681,8 +671,17 @@ Mempool.prototype._addTX = co(function* _addTX(tx) {
|
||||
0);
|
||||
}
|
||||
|
||||
if (!(yield this.fillAllCoins(tx)))
|
||||
return this.storeOrphan(tx);
|
||||
// if (!(yield this.fillAllCoins(tx)))
|
||||
// return this.maybeStoreOrphan(tx);
|
||||
|
||||
view = yield this.getCoinView(tx);
|
||||
missing = this.injectCoins(tx, view);
|
||||
|
||||
if (missing.length > 0) {
|
||||
if (this.storeOrphan(tx, missing))
|
||||
return missing;
|
||||
return;
|
||||
}
|
||||
|
||||
entry = MempoolEntry.fromTX(tx, this.chain.height);
|
||||
|
||||
@ -809,11 +808,9 @@ Mempool.prototype.verify = co(function* verify(entry) {
|
||||
var flags3 = flags1 & ~flags.VERIFY_CLEANSTACK;
|
||||
var mandatory = flags.MANDATORY_VERIFY_FLAGS;
|
||||
var ret = new VerifyResult();
|
||||
var now, minFee, count, result;
|
||||
var now, minFee, result;
|
||||
|
||||
result = yield this.verifyLocks(tx, lockFlags);
|
||||
|
||||
if (!result) {
|
||||
if (!(yield this.verifyLocks(tx, lockFlags))) {
|
||||
throw new VerifyError(tx,
|
||||
'nonstandard',
|
||||
'non-BIP68-final',
|
||||
@ -884,9 +881,7 @@ Mempool.prototype.verify = co(function* verify(entry) {
|
||||
if (this.rejectAbsurdFees && entry.fee > minFee * 10000)
|
||||
throw new VerifyError(tx, 'highfee', 'absurdly-high-fee', 0);
|
||||
|
||||
count = this.countAncestors(tx);
|
||||
|
||||
if (count > constants.mempool.ANCESTOR_LIMIT) {
|
||||
if (this.countAncestors(tx) > this.maxAncestors) {
|
||||
throw new VerifyError(tx,
|
||||
'nonstandard',
|
||||
'too-long-mempool-chain',
|
||||
@ -898,13 +893,13 @@ Mempool.prototype.verify = co(function* verify(entry) {
|
||||
|
||||
// Standard verification
|
||||
try {
|
||||
yield this.checkInputs(tx, flags1);
|
||||
yield this.verifyInputs(tx, flags1);
|
||||
} catch (error) {
|
||||
if (tx.hasWitness())
|
||||
throw error;
|
||||
|
||||
// Try without segwit and cleanstack.
|
||||
result = yield this.checkResult(tx, flags2);
|
||||
result = yield this.verifyResult(tx, flags2);
|
||||
|
||||
// If it failed, the first verification
|
||||
// was the only result we needed.
|
||||
@ -913,7 +908,7 @@ Mempool.prototype.verify = co(function* verify(entry) {
|
||||
|
||||
// If it succeeded, segwit may be causing the
|
||||
// failure. Try with segwit but without cleanstack.
|
||||
result = yield this.checkResult(tx, flags3);
|
||||
result = yield this.verifyResult(tx, flags3);
|
||||
|
||||
// Cleanstack was causing the failure.
|
||||
if (result)
|
||||
@ -926,7 +921,7 @@ Mempool.prototype.verify = co(function* verify(entry) {
|
||||
|
||||
// Paranoid checks.
|
||||
if (this.paranoid) {
|
||||
result = yield this.checkResult(tx, mandatory);
|
||||
result = yield this.verifyResult(tx, mandatory);
|
||||
assert(result, 'BUG: Verify failed for mandatory but not standard.');
|
||||
}
|
||||
});
|
||||
@ -939,9 +934,9 @@ Mempool.prototype.verify = co(function* verify(entry) {
|
||||
* @returns {Promise}
|
||||
*/
|
||||
|
||||
Mempool.prototype.checkResult = co(function* checkResult(tx, flags) {
|
||||
Mempool.prototype.verifyResult = co(function* verifyResult(tx, flags) {
|
||||
try {
|
||||
yield this.checkInputs(tx, flags);
|
||||
yield this.verifyInputs(tx, flags);
|
||||
} catch (err) {
|
||||
if (err.type === 'VerifyError')
|
||||
return false;
|
||||
@ -958,9 +953,8 @@ Mempool.prototype.checkResult = co(function* checkResult(tx, flags) {
|
||||
* @returns {Promise}
|
||||
*/
|
||||
|
||||
Mempool.prototype.checkInputs = co(function* checkInputs(tx, flags) {
|
||||
var result = yield tx.verifyAsync(flags);
|
||||
if (result)
|
||||
Mempool.prototype.verifyInputs = co(function* verifyInputs(tx, flags) {
|
||||
if (yield tx.verifyAsync(flags))
|
||||
return;
|
||||
|
||||
if (!(flags & constants.flags.UNSTANDARD_VERIFY_FLAGS)) {
|
||||
@ -972,9 +966,7 @@ Mempool.prototype.checkInputs = co(function* checkInputs(tx, flags) {
|
||||
|
||||
flags &= ~constants.flags.UNSTANDARD_VERIFY_FLAGS;
|
||||
|
||||
result = yield tx.verifyAsync(flags);
|
||||
|
||||
if (result) {
|
||||
if (yield tx.verifyAsync(flags)) {
|
||||
throw new VerifyError(tx,
|
||||
'nonstandard',
|
||||
'non-mandatory-script-verify-flag',
|
||||
@ -1024,12 +1016,12 @@ Mempool.prototype._countAncestors = function countAncestors(tx, count, set) {
|
||||
set[hash] = true;
|
||||
count += 1;
|
||||
|
||||
if (count > constants.mempool.ANCESTOR_LIMIT)
|
||||
if (count > this.maxAncestors)
|
||||
break;
|
||||
|
||||
count = this._countAncestors(prev, count, set);
|
||||
|
||||
if (count > constants.mempool.ANCESTOR_LIMIT)
|
||||
if (count > this.maxAncestors)
|
||||
break;
|
||||
}
|
||||
|
||||
@ -1263,7 +1255,87 @@ Mempool.prototype.hasOrphan = function hasOrphan(hash) {
|
||||
* @param {TX} tx
|
||||
*/
|
||||
|
||||
Mempool.prototype.storeOrphan = function storeOrphan(tx) {
|
||||
Mempool.prototype.storeOrphan = function storeOrphan(tx, missing) {
|
||||
var hash = tx.hash('hex');
|
||||
var i, input, prev;
|
||||
|
||||
if (tx.getWeight() > constants.tx.MAX_WEIGHT) {
|
||||
this.logger.debug('Ignoring large orphan: %s', tx.txid());
|
||||
if (!tx.hasWitness())
|
||||
this.rejects.add(tx.hash());
|
||||
return false;
|
||||
}
|
||||
|
||||
for (i = 0; i < missing; i++) {
|
||||
prev = missing[i];
|
||||
if (this.hasReject(prev)) {
|
||||
this.logger.debug('Not storing orphan %s (rejected parents).', tx.txid());
|
||||
this.rejects.add(tx.hash());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
for (i = 0; i < missing.length; i++) {
|
||||
prev = missing[i];
|
||||
|
||||
if (!this.waiting[prev])
|
||||
this.waiting[prev] = [];
|
||||
|
||||
this.waiting[prev].push(hash);
|
||||
}
|
||||
|
||||
this.orphans[hash] = new Orphan(tx, missing.length);
|
||||
this.totalOrphans++;
|
||||
|
||||
this.logger.debug('Added orphan %s to mempool.', tx.txid());
|
||||
|
||||
this.emit('add orphan', tx);
|
||||
|
||||
this.limitOrphans();
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
/**
|
||||
* Spend coins for transaction.
|
||||
* @param {TX} tx
|
||||
* @returns {Boolean}
|
||||
*/
|
||||
|
||||
Mempool.prototype.injectCoins = function injectCoins(tx, view) {
|
||||
var missing = [];
|
||||
var i, input, prevout, coin;
|
||||
|
||||
for (i = 0; i < tx.inputs.length; i++) {
|
||||
input = tx.inputs[i];
|
||||
prevout = input.prevout;
|
||||
|
||||
if (!view.has(prevout.hash)) {
|
||||
missing.push(prevout.hash);
|
||||
continue;
|
||||
}
|
||||
|
||||
coin = view.getCoin(prevout.hash, prevout.index);
|
||||
|
||||
if (!coin) {
|
||||
throw new VerifyError(tx,
|
||||
'duplicate',
|
||||
'bad-txns-inputs-spent',
|
||||
0);
|
||||
}
|
||||
|
||||
input.coin = coin;
|
||||
}
|
||||
|
||||
return missing;
|
||||
};
|
||||
|
||||
/**
|
||||
* Store an orphaned transaction.
|
||||
* @param {TX} tx
|
||||
*/
|
||||
|
||||
Mempool.prototype.maybeStoreOrphan = function maybeStoreOrphan(tx) {
|
||||
var missing = {};
|
||||
var i, hash, input, prev;
|
||||
|
||||
@ -1596,8 +1668,10 @@ Mempool.prototype.getCoinView = co(function* getCoinView(tx) {
|
||||
if (!entry)
|
||||
continue;
|
||||
|
||||
view.addTX(entry.tx, entry.height);
|
||||
view.addTX(entry.tx, -1);
|
||||
}
|
||||
|
||||
return view;
|
||||
});
|
||||
|
||||
/**
|
||||
@ -1621,6 +1695,17 @@ Mempool.prototype.verifyLocks = function verifyLocks(tx, flags) {
|
||||
return this.chain.verifyLocks(this.chain.tip, tx, flags);
|
||||
};
|
||||
|
||||
/**
|
||||
* Check locktime on a transaction against the current tip.
|
||||
* @param {TX} tx
|
||||
* @param {LockFlags} flags
|
||||
* @returns {Promise} - Returns Boolean.
|
||||
*/
|
||||
|
||||
Mempool.prototype.verifyFinal = function verifyFinal(tx, flags) {
|
||||
return this.chain.verifyFinal(this.chain.tip, tx, flags);
|
||||
};
|
||||
|
||||
/**
|
||||
* Map a transaction to the mempool.
|
||||
* @private
|
||||
|
||||
@ -478,13 +478,7 @@ exports.mempool = {
|
||||
* Maximum number of orphan transactions.
|
||||
*/
|
||||
|
||||
MAX_ORPHAN_TX: 100,
|
||||
|
||||
/**
|
||||
* Decay of minimum fee rate.
|
||||
*/
|
||||
|
||||
FEE_HALFLIFE: 60 * 60 * 12
|
||||
MAX_ORPHAN_TX: 100
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
Loading…
Reference in New Issue
Block a user