mempool: refactor.

This commit is contained in:
Christopher Jeffrey 2016-12-07 18:38:56 -08:00
parent c9fbaae0da
commit c300df7340
No known key found for this signature in database
GPG Key ID: 8962AB9DE6666BBD
2 changed files with 70 additions and 54 deletions

View File

@ -140,14 +140,15 @@ Mempool.prototype._close = function close() {
* Notify the mempool that a new block has come
* in (removes all transactions contained in the
* block from the mempool).
* @param {Block} block
* @param {ChainEntry} block
* @param {TX[]} txs
* @returns {Promise}
*/
Mempool.prototype.addBlock = co(function* addBlock(block) {
Mempool.prototype.addBlock = co(function* addBlock(block, txs) {
var unlock = yield this.locker.lock();
try {
return this._addBlock(block);
return this._addBlock(block, txs);
} finally {
unlock();
}
@ -157,16 +158,17 @@ Mempool.prototype.addBlock = co(function* addBlock(block) {
* Notify the mempool that a new block
* has come without a lock.
* @private
* @param {Block} block
* @param {ChainEntry} block
* @param {TX[]} txs
* @returns {Promise}
*/
Mempool.prototype._addBlock = function addBlock(block) {
Mempool.prototype._addBlock = function addBlock(block, txs) {
var entries = [];
var i, entry, tx, hash;
for (i = block.txs.length - 1; i >= 1; i--) {
tx = block.txs[i];
for (i = txs.length - 1; i >= 1; i--) {
tx = txs[i];
hash = tx.hash('hex');
entry = this.getEntry(hash);
@ -175,7 +177,7 @@ Mempool.prototype._addBlock = function addBlock(block) {
continue;
}
this.removeUnchecked(entry);
this.removeEntry(entry);
this.emit('confirmed', tx, block);
entries.push(entry);
@ -192,14 +194,15 @@ Mempool.prototype._addBlock = function addBlock(block) {
/**
* Notify the mempool that a block has been disconnected
* from the main chain (reinserts transactions into the mempool).
* @param {Block} block
* @param {ChainEntry} block
* @param {TX[]} txs
* @returns {Promise}
*/
Mempool.prototype.removeBlock = co(function* removeBlock(block) {
Mempool.prototype.removeBlock = co(function* removeBlock(block, txs) {
var unlock = yield this.locker.lock();
try {
return this._removeBlock(block);
return this._removeBlock(block, txs);
} finally {
unlock();
}
@ -209,15 +212,16 @@ Mempool.prototype.removeBlock = co(function* removeBlock(block) {
* Notify the mempool that a block
* has been disconnected without a lock.
* @private
* @param {Block} block
* @param {ChainEntry} block
* @param {TX[]} txs
* @returns {Promise}
*/
Mempool.prototype._removeBlock = function removeBlock(block) {
Mempool.prototype._removeBlock = function removeBlock(block, txs) {
var i, entry, tx, hash;
for (i = 1; i < block.txs.length; i++) {
tx = block.txs[i];
for (i = 1; i < txs.length; i++) {
tx = txs[i];
hash = tx.hash('hex');
if (this.hasTX(hash))
@ -307,7 +311,7 @@ Mempool.prototype.limitMempoolSize = function limitMempoolSize(entryHash) {
if (!trimmed && hash === entryHash)
trimmed = true;
this.removeUnchecked(entry, true);
this.removeEntry(entry, true);
if (this.getSize() <= this.maxSize)
return trimmed;
@ -325,7 +329,7 @@ Mempool.prototype.limitMempoolSize = function limitMempoolSize(entryHash) {
if (!trimmed && hash === entryHash)
trimmed = true;
this.removeUnchecked(entry, true);
this.removeEntry(entry, true);
if (this.getSize() <= this.maxSize)
return trimmed;
@ -594,6 +598,9 @@ Mempool.prototype._addTX = co(function* _addTX(tx) {
assert(!tx.mutable, 'Cannot add mutable TX to mempool.');
// Basic sanity checks.
// This is important because it ensures
// other functions will be overflow safe.
if (!tx.isSane(ret)) {
throw new VerifyError(tx,
'invalid',
@ -601,6 +608,8 @@ Mempool.prototype._addTX = co(function* _addTX(tx) {
ret.score);
}
// Coinbases are an insta-ban.
// Why? Who knows.
if (tx.isCoinbase()) {
throw new VerifyError(tx,
'invalid',
@ -608,6 +617,7 @@ Mempool.prototype._addTX = co(function* _addTX(tx) {
100);
}
// Do not allow CSV until it's activated.
if (this.requireStandard) {
if (!this.chain.state.hasCSV() && tx.version >= 2) {
throw new VerifyError(tx,
@ -617,6 +627,7 @@ Mempool.prototype._addTX = co(function* _addTX(tx) {
}
}
// Do not allow segwit until it's activated.
if (!this.chain.state.hasWitness() && !this.prematureWitness) {
if (tx.hasWitness()) {
throw new VerifyError(tx,
@ -626,6 +637,7 @@ Mempool.prototype._addTX = co(function* _addTX(tx) {
}
}
// Non-contextual standardness checks.
if (this.requireStandard) {
if (!tx.isStandard(ret)) {
throw new VerifyError(tx,
@ -643,6 +655,7 @@ Mempool.prototype._addTX = co(function* _addTX(tx) {
}
}
// Verify transaction finality (see isFinal()).
if (!(yield this.verifyFinal(tx, lockFlags))) {
throw new VerifyError(tx,
'nonstandard',
@ -650,6 +663,7 @@ Mempool.prototype._addTX = co(function* _addTX(tx) {
0);
}
// We can maybe ignore this.
if (this.exists(hash)) {
throw new VerifyError(tx,
'alreadyknown',
@ -657,6 +671,9 @@ Mempool.prototype._addTX = co(function* _addTX(tx) {
0);
}
// We can test whether this is an
// non-fully-spent transaction on
// the chain.
if (yield this.chain.db.hasCoins(hash)) {
throw new VerifyError(tx,
'alreadyknown',
@ -664,6 +681,9 @@ Mempool.prototype._addTX = co(function* _addTX(tx) {
0);
}
// Quick and dirty test to verify we're
// not double-spending an output in the
// mempool.
if (this.isDoubleSpend(tx)) {
throw new VerifyError(tx,
'duplicate',
@ -671,23 +691,30 @@ Mempool.prototype._addTX = co(function* _addTX(tx) {
0);
}
// if (!(yield this.fillAllCoins(tx)))
// return this.maybeStoreOrphan(tx);
// Get coin viewpoint as it
// pertains to the mempool.
view = yield this.getCoinView(tx);
// Find missing outpoints.
missing = this.injectCoins(tx, view);
// Maybe store as an orphan.
if (missing.length > 0) {
if (this.storeOrphan(tx, missing))
return missing;
return;
}
// Create a mempool entry and
// begin contextual verification.
entry = MempoolEntry.fromTX(tx, this.chain.height);
yield this.verify(entry);
yield this._addUnchecked(entry);
// Add and index the entry.
yield this.addEntry(entry);
// Trim size if we're too big.
if (this.limitMempoolSize(hash)) {
throw new VerifyError(tx,
'insufficientfee',
@ -706,23 +733,7 @@ Mempool.prototype._addTX = co(function* _addTX(tx) {
* @returns {Promise}
*/
Mempool.prototype.addUnchecked = co(function* addUnchecked(entry) {
var unlock = yield this.locker.lock();
try {
return yield this._addUnchecked(entry);
} finally {
unlock();
}
});
/**
* Add a transaction to the mempool without a lock.
* @private
* @param {MempoolEntry} entry
* @returns {Promise}
*/
Mempool.prototype._addUnchecked = co(function* addUnchecked(entry) {
Mempool.prototype.addEntry = co(function* addEntry(entry) {
var i, resolved, tx;
this.trackEntry(entry);
@ -757,7 +768,7 @@ Mempool.prototype._addUnchecked = co(function* addUnchecked(entry) {
continue;
}
this.logger.spam('Resolved orphan %s in mempool.', tx.txid());
this.logger.debug('Resolved orphan %s in mempool.', tx.txid());
}
});
@ -768,7 +779,7 @@ Mempool.prototype._addUnchecked = co(function* addUnchecked(entry) {
* @param {Boolean} limit
*/
Mempool.prototype.removeUnchecked = function removeUnchecked(entry, limit) {
Mempool.prototype.removeEntry = function removeEntry(entry, limit) {
var tx = entry.tx;
var hash = tx.hash('hex');
@ -810,6 +821,7 @@ Mempool.prototype.verify = co(function* verify(entry) {
var ret = new VerifyResult();
var now, minFee, result;
// Verify sequence locks.
if (!(yield this.verifyLocks(tx, lockFlags))) {
throw new VerifyError(tx,
'nonstandard',
@ -817,6 +829,7 @@ Mempool.prototype.verify = co(function* verify(entry) {
0);
}
// Check input an witness standardness.
if (this.requireStandard) {
if (!tx.hasStandardInputs()) {
throw new VerifyError(tx,
@ -836,6 +849,7 @@ Mempool.prototype.verify = co(function* verify(entry) {
}
}
// Annoying process known as sigops counting.
if (tx.getSigopsWeight(flags) > constants.tx.MAX_SIGOPS_WEIGHT) {
throw new VerifyError(tx,
'nonstandard',
@ -843,6 +857,7 @@ Mempool.prototype.verify = co(function* verify(entry) {
0);
}
// Make sure this guy gave a decent fee.
minFee = tx.getMinFee(entry.size, this.minRelay);
if (this.relayPriority && entry.fee < minFee) {
@ -855,19 +870,16 @@ Mempool.prototype.verify = co(function* verify(entry) {
}
// Continuously rate-limit free (really, very-low-fee)
// transactions. This mitigates 'penny-flooding'. i.e.
// sending thousands of free transactions just to be
// annoying or make others' transactions take longer
// to confirm.
// transactions. This mitigates 'penny-flooding'.
if (this.limitFree && entry.fee < minFee) {
now = util.now();
// Use an exponentially decaying ~10-minute window:
// Use an exponentially decaying ~10-minute window.
this.freeCount *= Math.pow(1 - 1 / 600, now - this.lastTime);
this.lastTime = now;
// The limitFreeRelay unit is thousand-bytes-per-minute
// At default rate it would take over a month to fill 1GB
// At default rate it would take over a month to fill 1GB.
if (this.freeCount > this.limitFreeRelay * 10 * 1000) {
throw new VerifyError(tx,
'insufficientfee',
@ -878,9 +890,11 @@ Mempool.prototype.verify = co(function* verify(entry) {
this.freeCount += entry.size;
}
// Important safety feature.
if (this.rejectAbsurdFees && entry.fee > minFee * 10000)
throw new VerifyError(tx, 'highfee', 'absurdly-high-fee', 0);
// Why do we have this here? Nested transactions are cool.
if (this.countAncestors(tx) > this.maxAncestors) {
throw new VerifyError(tx,
'nonstandard',
@ -888,10 +902,11 @@ Mempool.prototype.verify = co(function* verify(entry) {
0);
}
// Contextual sanity checks.
if (!tx.checkInputs(height, ret))
throw new VerifyError(tx, 'invalid', ret.reason, ret.score);
// Standard verification
// Script verification.
try {
yield this.verifyInputs(tx, flags1);
} catch (error) {
@ -1405,8 +1420,8 @@ Mempool.prototype.resolveOrphans = function resolveOrphans(tx) {
continue;
if (--orphan.missing === 0) {
this.totalOrphans--;
delete this.orphans[orphanHash];
this.totalOrphans--;
try {
resolved.push(orphan.toTX());
} catch (e) {
@ -1438,9 +1453,11 @@ Mempool.prototype.removeOrphan = function removeOrphan(hash) {
tx = orphan.toTX();
} catch (e) {
delete this.orphans[hash];
this.totalOrphans--;
this.logger.warning('%s %s',
'Warning: possible memory corruption.',
'Orphan failed deserialization.');
return;
}
prevout = tx.getPrevout();
@ -1465,10 +1482,9 @@ Mempool.prototype.removeOrphan = function removeOrphan(hash) {
}
delete this.orphans[hash];
this.totalOrphans--;
this.emit('remove orphan', tx);
this.totalOrphans--;
};
/**
@ -1585,7 +1601,7 @@ Mempool.prototype.fillAllHistory = function fillAllHistory(tx) {
if (tx.hasCoins())
return Promise.resolve(tx);
return this.chain.db.fillCoins(tx);
return this.chain.db.fillHistory(tx);
};
/**
@ -1809,7 +1825,7 @@ Mempool.prototype.removeSpenders = function removeSpenders(entry) {
if (!spender)
continue;
this.removeUnchecked(spender, true);
this.removeEntry(spender, true);
}
};

View File

@ -208,14 +208,14 @@ FullNode.prototype._init = function _init() {
self.emit('connect', entry, block);
if (self.chain.synced)
self.mempool.addBlock(block).catch(onError);
self.mempool.addBlock(entry, block.txs).catch(onError);
});
this.chain.on('disconnect', function(entry, block) {
self.emit('disconnect', entry, block);
if (self.chain.synced)
self.mempool.removeBlock(block).catch(onError);
self.mempool.removeBlock(entry, block.txs).catch(onError);
});
this.chain.on('reset', function(tip) {