mempool: refactor.
This commit is contained in:
parent
c9fbaae0da
commit
c300df7340
@ -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);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@ -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) {
|
||||
|
||||
Loading…
Reference in New Issue
Block a user