mempool: use coin viewpoint.

This commit is contained in:
Christopher Jeffrey 2016-12-07 17:39:27 -08:00
parent 8b99b5103b
commit 1269aab7a5
No known key found for this signature in database
GPG Key ID: 8962AB9DE6666BBD
4 changed files with 156 additions and 50 deletions

View File

@ -2229,7 +2229,7 @@ Chain.prototype.getDeploymentState = co(function* getDeploymentState() {
* @returns {Promise} - Returns Boolean. * @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 height = prev.height + 1;
var ts; var ts;

View File

@ -36,6 +36,16 @@ CoinView.prototype.get = function get(hash) {
return this.unspent[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. * Add coins to the collection.
* @param {Coins} coins * @param {Coins} coins
@ -95,6 +105,23 @@ CoinView.prototype.spendFrom = function spendFrom(coins, index) {
return entry.toCoin(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. * Retrieve coins from database.
* @param {TX} tx * @param {TX} tx

View File

@ -106,6 +106,8 @@ function Mempool(options) {
this.replaceByFee = !!this.options.replaceByFee; this.replaceByFee = !!this.options.replaceByFee;
this.maxSize = options.maxSize || constants.mempool.MAX_MEMPOOL_SIZE; 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.expiryTime = options.expiryTime || constants.mempool.MEMPOOL_EXPIRY;
this.minRelay = this.network.minRelay; this.minRelay = this.network.minRelay;
} }
@ -340,7 +342,7 @@ Mempool.prototype.limitOrphans = function limitOrphans() {
var orphans = Object.keys(this.orphans); var orphans = Object.keys(this.orphans);
var i, hash; var i, hash;
while (this.totalOrphans > constants.mempool.MAX_ORPHAN_TX) { while (this.totalOrphans > this.maxOrphans) {
i = crypto.randomRange(0, orphans.length); i = crypto.randomRange(0, orphans.length);
hash = orphans[i]; hash = orphans[i];
orphans.splice(i, 1); orphans.splice(i, 1);
@ -587,19 +589,11 @@ Mempool.prototype.addTX = co(function* addTX(tx) {
Mempool.prototype._addTX = co(function* _addTX(tx) { Mempool.prototype._addTX = co(function* _addTX(tx) {
var lockFlags = constants.flags.STANDARD_LOCKTIME_FLAGS; var lockFlags = constants.flags.STANDARD_LOCKTIME_FLAGS;
var hash = tx.hash('hex'); 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.'); 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)) { if (!tx.isSane(ret)) {
throw new VerifyError(tx, throw new VerifyError(tx,
'invalid', 'invalid',
@ -649,9 +643,7 @@ Mempool.prototype._addTX = co(function* _addTX(tx) {
} }
} }
result = yield this.chain.checkFinal(this.chain.tip, tx, lockFlags); if (!(yield this.verifyFinal(tx, lockFlags))) {
if (!result) {
throw new VerifyError(tx, throw new VerifyError(tx,
'nonstandard', 'nonstandard',
'non-final', 'non-final',
@ -665,9 +657,7 @@ Mempool.prototype._addTX = co(function* _addTX(tx) {
0); 0);
} }
exists = yield this.chain.db.hasCoins(hash); if (yield this.chain.db.hasCoins(hash)) {
if (exists) {
throw new VerifyError(tx, throw new VerifyError(tx,
'alreadyknown', 'alreadyknown',
'txn-already-known', 'txn-already-known',
@ -681,8 +671,17 @@ Mempool.prototype._addTX = co(function* _addTX(tx) {
0); 0);
} }
if (!(yield this.fillAllCoins(tx))) // if (!(yield this.fillAllCoins(tx)))
return this.storeOrphan(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); 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 flags3 = flags1 & ~flags.VERIFY_CLEANSTACK;
var mandatory = flags.MANDATORY_VERIFY_FLAGS; var mandatory = flags.MANDATORY_VERIFY_FLAGS;
var ret = new VerifyResult(); var ret = new VerifyResult();
var now, minFee, count, result; var now, minFee, result;
result = yield this.verifyLocks(tx, lockFlags); if (!(yield this.verifyLocks(tx, lockFlags))) {
if (!result) {
throw new VerifyError(tx, throw new VerifyError(tx,
'nonstandard', 'nonstandard',
'non-BIP68-final', 'non-BIP68-final',
@ -884,9 +881,7 @@ Mempool.prototype.verify = co(function* verify(entry) {
if (this.rejectAbsurdFees && entry.fee > minFee * 10000) if (this.rejectAbsurdFees && entry.fee > minFee * 10000)
throw new VerifyError(tx, 'highfee', 'absurdly-high-fee', 0); throw new VerifyError(tx, 'highfee', 'absurdly-high-fee', 0);
count = this.countAncestors(tx); if (this.countAncestors(tx) > this.maxAncestors) {
if (count > constants.mempool.ANCESTOR_LIMIT) {
throw new VerifyError(tx, throw new VerifyError(tx,
'nonstandard', 'nonstandard',
'too-long-mempool-chain', 'too-long-mempool-chain',
@ -898,13 +893,13 @@ Mempool.prototype.verify = co(function* verify(entry) {
// Standard verification // Standard verification
try { try {
yield this.checkInputs(tx, flags1); yield this.verifyInputs(tx, flags1);
} catch (error) { } catch (error) {
if (tx.hasWitness()) if (tx.hasWitness())
throw error; throw error;
// Try without segwit and cleanstack. // Try without segwit and cleanstack.
result = yield this.checkResult(tx, flags2); result = yield this.verifyResult(tx, flags2);
// If it failed, the first verification // If it failed, the first verification
// was the only result we needed. // 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 // If it succeeded, segwit may be causing the
// failure. Try with segwit but without cleanstack. // failure. Try with segwit but without cleanstack.
result = yield this.checkResult(tx, flags3); result = yield this.verifyResult(tx, flags3);
// Cleanstack was causing the failure. // Cleanstack was causing the failure.
if (result) if (result)
@ -926,7 +921,7 @@ Mempool.prototype.verify = co(function* verify(entry) {
// Paranoid checks. // Paranoid checks.
if (this.paranoid) { 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.'); assert(result, 'BUG: Verify failed for mandatory but not standard.');
} }
}); });
@ -939,9 +934,9 @@ Mempool.prototype.verify = co(function* verify(entry) {
* @returns {Promise} * @returns {Promise}
*/ */
Mempool.prototype.checkResult = co(function* checkResult(tx, flags) { Mempool.prototype.verifyResult = co(function* verifyResult(tx, flags) {
try { try {
yield this.checkInputs(tx, flags); yield this.verifyInputs(tx, flags);
} catch (err) { } catch (err) {
if (err.type === 'VerifyError') if (err.type === 'VerifyError')
return false; return false;
@ -958,9 +953,8 @@ Mempool.prototype.checkResult = co(function* checkResult(tx, flags) {
* @returns {Promise} * @returns {Promise}
*/ */
Mempool.prototype.checkInputs = co(function* checkInputs(tx, flags) { Mempool.prototype.verifyInputs = co(function* verifyInputs(tx, flags) {
var result = yield tx.verifyAsync(flags); if (yield tx.verifyAsync(flags))
if (result)
return; return;
if (!(flags & constants.flags.UNSTANDARD_VERIFY_FLAGS)) { 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; flags &= ~constants.flags.UNSTANDARD_VERIFY_FLAGS;
result = yield tx.verifyAsync(flags); if (yield tx.verifyAsync(flags)) {
if (result) {
throw new VerifyError(tx, throw new VerifyError(tx,
'nonstandard', 'nonstandard',
'non-mandatory-script-verify-flag', 'non-mandatory-script-verify-flag',
@ -1024,12 +1016,12 @@ Mempool.prototype._countAncestors = function countAncestors(tx, count, set) {
set[hash] = true; set[hash] = true;
count += 1; count += 1;
if (count > constants.mempool.ANCESTOR_LIMIT) if (count > this.maxAncestors)
break; break;
count = this._countAncestors(prev, count, set); count = this._countAncestors(prev, count, set);
if (count > constants.mempool.ANCESTOR_LIMIT) if (count > this.maxAncestors)
break; break;
} }
@ -1263,7 +1255,87 @@ Mempool.prototype.hasOrphan = function hasOrphan(hash) {
* @param {TX} tx * @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 missing = {};
var i, hash, input, prev; var i, hash, input, prev;
@ -1596,8 +1668,10 @@ Mempool.prototype.getCoinView = co(function* getCoinView(tx) {
if (!entry) if (!entry)
continue; 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); 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. * Map a transaction to the mempool.
* @private * @private

View File

@ -478,13 +478,7 @@ exports.mempool = {
* Maximum number of orphan transactions. * Maximum number of orphan transactions.
*/ */
MAX_ORPHAN_TX: 100, MAX_ORPHAN_TX: 100
/**
* Decay of minimum fee rate.
*/
FEE_HALFLIFE: 60 * 60 * 12
}; };
/** /**