mempool: evict by lowest fee rate.

This commit is contained in:
Christopher Jeffrey 2017-03-05 02:03:14 -08:00
parent ef92070a28
commit 210ce4c767
No known key found for this signature in database
GPG Key ID: 8962AB9DE6666BBD
2 changed files with 92 additions and 27 deletions

View File

@ -31,6 +31,7 @@ var Fees = require('./fees');
var Map = require('../utils/map'); var Map = require('../utils/map');
var CoinView = require('../coins/coinview'); var CoinView = require('../coins/coinview');
var Coins = require('../coins/coins'); var Coins = require('../coins/coins');
var Heap = require('../utils/heap');
var VerifyError = errors.VerifyError; var VerifyError = errors.VerifyError;
var VerifyResult = errors.VerifyResult; var VerifyResult = errors.VerifyResult;
@ -357,58 +358,74 @@ Mempool.prototype._reset = co(function* reset() {
}); });
/** /**
* Ensure the size of the mempool stays below 300mb. * Ensure the size of the mempool stays below `maxSize`.
* @param {Hash} entryHash - TX that initiated the trim. * Evicts entries by timestamp and cumulative fee rate.
* @param {MempoolEntry} added
* @returns {Promise} * @returns {Promise}
*/ */
Mempool.prototype.limitSize = function limitSize(entryHash) { Mempool.prototype.limitSize = function limitSize(added) {
var maxSize = this.options.maxSize;
var expiryTime = this.options.expiryTime;
var now = util.now();
var trimmed = false; var trimmed = false;
var i, hashes, hash, end, entry; var i, queue, entry, keys, hash, start;
if (this.getSize() <= this.options.maxSize) if (this.size <= maxSize)
return trimmed; return trimmed;
hashes = this.getSnapshot(); queue = new Heap(cmpRate);
end = util.now() - this.options.expiryTime; keys = this.getSnapshot();
for (i = 0; i < hashes.length; i++) { start = util.hrtime();
hash = hashes[i];
for (i = 0; i < keys.length; i++) {
hash = keys[i];
entry = this.getEntry(hash); entry = this.getEntry(hash);
if (!entry) if (!entry)
continue; continue;
if (entry.ts >= end) if (now < entry.ts + expiryTime) {
queue.insert(entry);
continue; continue;
}
if (!trimmed && hash === entryHash) if (entry === added)
trimmed = true; trimmed = true;
this.evictEntry(entry); this.evictEntry(entry);
if (this.getSize() <= this.options.maxSize)
return trimmed;
} }
hashes = this.getSnapshot(); if (this.size <= maxSize)
return trimmed;
for (i = 0; i < hashes.length; i++) { this.logger.debug(
hash = hashes[i]; '(bench) Heap mempool traversal: %d.',
entry = this.getEntry(hash); util.hrtime(start));
if (!entry) start = util.hrtime();
while (queue.size() > 0) {
entry = queue.shift();
hash = entry.hash('hex');
if (!this.hasTX(hash))
continue; continue;
if (!trimmed && hash === entryHash)
trimmed = true;
this.evictEntry(entry); this.evictEntry(entry);
if (this.getSize() <= this.options.maxSize) if (entry === added)
return trimmed; trimmed = true;
if (this.size <= maxSize - (maxSize / 10))
break;
} }
this.logger.debug(
'(bench) Heap mempool map removal: %d.',
util.hrtime(start));
return trimmed; return trimmed;
}; };
@ -860,7 +877,7 @@ Mempool.prototype.insertTX = co(function* insertTX(tx) {
yield this.addEntry(entry, view); yield this.addEntry(entry, view);
// Trim size if we're too big. // Trim size if we're too big.
if (this.limitSize(hash)) { if (this.limitSize(entry)) {
throw new VerifyError(tx, throw new VerifyError(tx,
'insufficientfee', 'insufficientfee',
'mempool full', 'mempool full',
@ -1084,7 +1101,9 @@ Mempool.prototype.addEntry = co(function* addEntry(entry, view) {
if (this.fees) if (this.fees)
this.fees.processTX(entry, this.chain.synced); this.fees.processTX(entry, this.chain.synced);
this.logger.debug('Added tx %s to mempool.', tx.txid()); this.logger.debug(
'Added %s to mempool (txs=%d).',
tx.txid(), this.totalTX);
this.cache.save(entry); this.cache.save(entry);
@ -1517,7 +1536,9 @@ Mempool.prototype.handleOrphans = co(function* handleOrphans(tx) {
throw err; throw err;
} }
this.logger.debug('Resolved orphan %s in mempool.', orphan.txid()); this.logger.debug(
'Resolved orphan %s in mempool (txs=%d).',
orphan.txid(), this.totalTX);
} }
return resolved; return resolved;
@ -2517,6 +2538,40 @@ function removeFee(parent, child) {
parent.descSize -= child.descSize; parent.descSize -= child.descSize;
} }
function cmpRate(a, b) {
var xf = a.fee;
var xs = a.size;
var yf = b.fee;
var ys = b.size;
var x, y;
if (useDesc(a)) {
xf = a.descFee;
xs = a.descSize;
}
if (useDesc(b)) {
yf = b.descFee;
ys = b.descSize;
}
x = xf * ys;
y = xs * yf;
if (x === y) {
x = a.ts;
y = b.ts;
}
return x - y;
}
function useDesc(a) {
var x = a.fee * a.descSize;
var y = a.descFee * a.size;
return y > x;
}
/* /*
* Expose * Expose
*/ */

View File

@ -132,6 +132,16 @@ MempoolEntry.fromTX = function fromTX(tx, view, height) {
return new MempoolEntry().fromTX(tx, view, height); return new MempoolEntry().fromTX(tx, view, height);
}; };
/**
* Calculate transaction hash.
* @param {String?} enc
* @returns {Hash}
*/
MempoolEntry.prototype.hash = function hash(enc) {
return this.tx.hash(enc);
};
/** /**
* Calculate priority, taking into account * Calculate priority, taking into account
* the entry height delta, modified size, * the entry height delta, modified size,