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

View File

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

View File

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

View File

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