From 210ce4c7676adebeeb62dd7375e47fc40a7a10f5 Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Sun, 5 Mar 2017 02:03:14 -0800 Subject: [PATCH] mempool: evict by lowest fee rate. --- lib/mempool/mempool.js | 109 +++++++++++++++++++++++++++--------- lib/mempool/mempoolentry.js | 10 ++++ 2 files changed, 92 insertions(+), 27 deletions(-) diff --git a/lib/mempool/mempool.js b/lib/mempool/mempool.js index 8e59a38c..674022a5 100644 --- a/lib/mempool/mempool.js +++ b/lib/mempool/mempool.js @@ -31,6 +31,7 @@ var Fees = require('./fees'); var Map = require('../utils/map'); var CoinView = require('../coins/coinview'); var Coins = require('../coins/coins'); +var Heap = require('../utils/heap'); var VerifyError = errors.VerifyError; var VerifyResult = errors.VerifyResult; @@ -357,58 +358,74 @@ Mempool.prototype._reset = co(function* reset() { }); /** - * Ensure the size of the mempool stays below 300mb. - * @param {Hash} entryHash - TX that initiated the trim. + * Ensure the size of the mempool stays below `maxSize`. + * Evicts entries by timestamp and cumulative fee rate. + * @param {MempoolEntry} added * @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 i, hashes, hash, end, entry; + var i, queue, entry, keys, hash, start; - if (this.getSize() <= this.options.maxSize) + if (this.size <= maxSize) return trimmed; - hashes = this.getSnapshot(); - end = util.now() - this.options.expiryTime; + queue = new Heap(cmpRate); + keys = this.getSnapshot(); - for (i = 0; i < hashes.length; i++) { - hash = hashes[i]; + start = util.hrtime(); + + for (i = 0; i < keys.length; i++) { + hash = keys[i]; entry = this.getEntry(hash); if (!entry) continue; - if (entry.ts >= end) + if (now < entry.ts + expiryTime) { + queue.insert(entry); continue; + } - if (!trimmed && hash === entryHash) + if (entry === added) trimmed = true; 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++) { - hash = hashes[i]; - entry = this.getEntry(hash); + this.logger.debug( + '(bench) Heap mempool traversal: %d.', + util.hrtime(start)); - if (!entry) + start = util.hrtime(); + + while (queue.size() > 0) { + entry = queue.shift(); + hash = entry.hash('hex'); + + if (!this.hasTX(hash)) continue; - if (!trimmed && hash === entryHash) - trimmed = true; - this.evictEntry(entry); - if (this.getSize() <= this.options.maxSize) - return trimmed; + if (entry === added) + trimmed = true; + + if (this.size <= maxSize - (maxSize / 10)) + break; } + this.logger.debug( + '(bench) Heap mempool map removal: %d.', + util.hrtime(start)); + return trimmed; }; @@ -860,7 +877,7 @@ Mempool.prototype.insertTX = co(function* insertTX(tx) { yield this.addEntry(entry, view); // Trim size if we're too big. - if (this.limitSize(hash)) { + if (this.limitSize(entry)) { throw new VerifyError(tx, 'insufficientfee', 'mempool full', @@ -1084,7 +1101,9 @@ Mempool.prototype.addEntry = co(function* addEntry(entry, view) { if (this.fees) 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); @@ -1517,7 +1536,9 @@ Mempool.prototype.handleOrphans = co(function* handleOrphans(tx) { 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; @@ -2517,6 +2538,40 @@ function removeFee(parent, child) { 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 */ diff --git a/lib/mempool/mempoolentry.js b/lib/mempool/mempoolentry.js index c9a3832b..00679d52 100644 --- a/lib/mempool/mempoolentry.js +++ b/lib/mempool/mempoolentry.js @@ -132,6 +132,16 @@ MempoolEntry.fromTX = function 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 * the entry height delta, modified size,