mempool: classify.

This commit is contained in:
Christopher Jeffrey 2017-11-16 19:02:23 -08:00
parent 32b81f0d95
commit 5c8a755d63
No known key found for this signature in database
GPG Key ID: 8962AB9DE6666BBD
2 changed files with 3093 additions and 3066 deletions

View File

@ -38,15 +38,17 @@ const PRI_SPACING = 2;
/** /**
* Confirmation stats. * Confirmation stats.
* @alias module:mempool.ConfirmStats * @alias module:mempool.ConfirmStats
*/
class ConfirmStats {
/**
* Create confirmation stats.
* @constructor * @constructor
* @param {String} type * @param {String} type
* @param {Logger?} logger * @param {Logger?} logger
*/ */
function ConfirmStats(type, logger) { constructor(type, logger) {
if (!(this instanceof ConfirmStats))
return new ConfirmStats(type, logger);
this.logger = Logger.global; this.logger = Logger.global;
this.type = type; this.type = type;
@ -70,9 +72,9 @@ function ConfirmStats(type, logger) {
assert(typeof logger === 'object'); assert(typeof logger === 'object');
this.logger = logger.context('fees'); this.logger = logger.context('fees');
} }
} }
/** /**
* Initialize stats. * Initialize stats.
* @param {Array} buckets * @param {Array} buckets
* @param {Number} maxConfirms * @param {Number} maxConfirms
@ -80,7 +82,7 @@ function ConfirmStats(type, logger) {
* @private * @private
*/ */
ConfirmStats.prototype.init = function init(buckets, maxConfirms, decay) { init(buckets, maxConfirms, decay) {
this.maxConfirms = maxConfirms; this.maxConfirms = maxConfirms;
this.decay = decay; this.decay = decay;
@ -107,14 +109,14 @@ ConfirmStats.prototype.init = function init(buckets, maxConfirms, decay) {
this.txAvg = new Float64Array(buckets.length); this.txAvg = new Float64Array(buckets.length);
this.curBlockVal = new Float64Array(buckets.length); this.curBlockVal = new Float64Array(buckets.length);
this.avg = new Float64Array(buckets.length); this.avg = new Float64Array(buckets.length);
}; }
/** /**
* Clear data for the current block. * Clear data for the current block.
* @param {Number} height * @param {Number} height
*/ */
ConfirmStats.prototype.clearCurrent = function clearCurrent(height) { clearCurrent(height) {
for (let i = 0; i < this.buckets.length; i++) { for (let i = 0; i < this.buckets.length; i++) {
this.oldUnconfTX[i] = this.unconfTX[height % this.unconfTX.length][i]; this.oldUnconfTX[i] = this.unconfTX[height % this.unconfTX.length][i];
this.unconfTX[height % this.unconfTX.length][i] = 0; this.unconfTX[height % this.unconfTX.length][i] = 0;
@ -123,15 +125,15 @@ ConfirmStats.prototype.clearCurrent = function clearCurrent(height) {
this.curBlockTX[i] = 0; this.curBlockTX[i] = 0;
this.curBlockVal[i] = 0; this.curBlockVal[i] = 0;
} }
}; }
/** /**
* Record a rate or priority based on number of blocks to confirm. * Record a rate or priority based on number of blocks to confirm.
* @param {Number} blocks - Blocks to confirm. * @param {Number} blocks - Blocks to confirm.
* @param {Rate|Number} val - Rate or priority. * @param {Rate|Number} val - Rate or priority.
*/ */
ConfirmStats.prototype.record = function record(blocks, val) { record(blocks, val) {
if (blocks < 1) if (blocks < 1)
return; return;
@ -142,13 +144,13 @@ ConfirmStats.prototype.record = function record(blocks, val) {
this.curBlockTX[bucketIndex]++; this.curBlockTX[bucketIndex]++;
this.curBlockVal[bucketIndex] += val; this.curBlockVal[bucketIndex] += val;
}; }
/** /**
* Update moving averages. * Update moving averages.
*/ */
ConfirmStats.prototype.updateAverages = function updateAverages() { updateAverages() {
for (let i = 0; i < this.buckets.length; i++) { for (let i = 0; i < this.buckets.length; i++) {
for (let j = 0; j < this.confAvg.length; j++) { for (let j = 0; j < this.confAvg.length; j++) {
this.confAvg[j][i] = this.confAvg[j][i] =
@ -157,9 +159,9 @@ ConfirmStats.prototype.updateAverages = function updateAverages() {
this.avg[i] = this.avg[i] * this.decay + this.curBlockVal[i]; this.avg[i] = this.avg[i] * this.decay + this.curBlockVal[i];
this.txAvg[i] = this.txAvg[i] * this.decay + this.curBlockTX[i]; this.txAvg[i] = this.txAvg[i] * this.decay + this.curBlockTX[i];
} }
}; }
/** /**
* Estimate the median value for rate or priority. * Estimate the median value for rate or priority.
* @param {Number} target - Confirmation target. * @param {Number} target - Confirmation target.
* @param {Number} needed - Sufficient tx value. * @param {Number} needed - Sufficient tx value.
@ -169,7 +171,7 @@ ConfirmStats.prototype.updateAverages = function updateAverages() {
* @returns {Rate|Number} Returns -1 on error. * @returns {Rate|Number} Returns -1 on error.
*/ */
ConfirmStats.prototype.estimateMedian = function estimateMedian(target, needed, breakpoint, greater, height) { estimateMedian(target, needed, breakpoint, greater, height) {
const max = this.buckets.length - 1; const max = this.buckets.length - 1;
const start = greater ? max : 0; const start = greater ? max : 0;
const step = greater ? -1 : 1; const step = greater ? -1 : 1;
@ -233,31 +235,31 @@ ConfirmStats.prototype.estimateMedian = function estimateMedian(target, needed,
} }
return median; return median;
}; }
/** /**
* Add a transaction's rate/priority to be tracked. * Add a transaction's rate/priority to be tracked.
* @param {Number} height - Block height. * @param {Number} height - Block height.
* @param {Number} val * @param {Number} val
* @returns {Number} Bucket index. * @returns {Number} Bucket index.
*/ */
ConfirmStats.prototype.addTX = function addTX(height, val) { addTX(height, val) {
const bucketIndex = this.bucketMap.search(val); const bucketIndex = this.bucketMap.search(val);
const blockIndex = height % this.unconfTX.length; const blockIndex = height % this.unconfTX.length;
this.unconfTX[blockIndex][bucketIndex]++; this.unconfTX[blockIndex][bucketIndex]++;
this.logger.spam('Adding tx to %s.', this.type); this.logger.spam('Adding tx to %s.', this.type);
return bucketIndex; return bucketIndex;
}; }
/** /**
* Remove a transaction from tracking. * Remove a transaction from tracking.
* @param {Number} entryHeight * @param {Number} entryHeight
* @param {Number} bestHeight * @param {Number} bestHeight
* @param {Number} bucketIndex * @param {Number} bucketIndex
*/ */
ConfirmStats.prototype.removeTX = function removeTX(entryHeight, bestHeight, bucketIndex) { removeTX(entryHeight, bestHeight, bucketIndex) {
let blocksAgo = bestHeight - entryHeight; let blocksAgo = bestHeight - entryHeight;
if (bestHeight === 0) if (bestHeight === 0)
@ -284,14 +286,14 @@ ConfirmStats.prototype.removeTX = function removeTX(entryHeight, bestHeight, buc
blockIndex, bucketIndex); blockIndex, bucketIndex);
} }
} }
}; }
/** /**
* Get serialization size. * Get serialization size.
* @returns {Number} * @returns {Number}
*/ */
ConfirmStats.prototype.getSize = function getSize() { getSize() {
let size = 0; let size = 0;
size += 8; size += 8;
@ -306,14 +308,14 @@ ConfirmStats.prototype.getSize = function getSize() {
size += sizeArray(this.confAvg[i]); size += sizeArray(this.confAvg[i]);
return size; return size;
}; }
/** /**
* Serialize confirm stats. * Serialize confirm stats.
* @returns {Buffer} * @returns {Buffer}
*/ */
ConfirmStats.prototype.toRaw = function toRaw() { toRaw() {
const size = this.getSize(); const size = this.getSize();
const bw = bio.write(size); const bw = bio.write(size);
@ -327,16 +329,16 @@ ConfirmStats.prototype.toRaw = function toRaw() {
writeArray(bw, this.confAvg[i]); writeArray(bw, this.confAvg[i]);
return bw.render(); return bw.render();
}; }
/** /**
* Inject properties from serialized data. * Inject properties from serialized data.
* @private * @private
* @param {Buffer} data * @param {Buffer} data
* @returns {ConfirmStats} * @returns {ConfirmStats}
*/ */
ConfirmStats.prototype.fromRaw = function fromRaw(data) { fromRaw(data) {
const br = bio.read(data); const br = bio.read(data);
const decay = br.readDouble(); const decay = br.readDouble();
const buckets = readArray(br); const buckets = readArray(br);
@ -375,9 +377,9 @@ ConfirmStats.prototype.fromRaw = function fromRaw(data) {
this.confAvg = confAvg; this.confAvg = confAvg;
return this; return this;
}; }
/** /**
* Instantiate confirm stats from serialized data. * Instantiate confirm stats from serialized data.
* @param {Buffer} data * @param {Buffer} data
* @param {String} type * @param {String} type
@ -385,21 +387,25 @@ ConfirmStats.prototype.fromRaw = function fromRaw(data) {
* @returns {ConfirmStats} * @returns {ConfirmStats}
*/ */
ConfirmStats.fromRaw = function fromRaw(data, type, logger) { static fromRaw(data, type, logger) {
return new ConfirmStats(type, logger).fromRaw(data); return new ConfirmStats(type, logger).fromRaw(data);
}; }
}
/** /**
* Policy Estimator
* Estimator for fees and priority. * Estimator for fees and priority.
* @alias module:mempool.PolicyEstimator * @alias module:mempool.PolicyEstimator
*/
class PolicyEstimator {
/**
* Create an estimator.
* @constructor * @constructor
* @param {Logger?} logger * @param {Logger?} logger
*/ */
function PolicyEstimator(logger) { constructor(logger) {
if (!(this instanceof PolicyEstimator))
return new PolicyEstimator(logger);
this.logger = Logger.global; this.logger = Logger.global;
this.minTrackedFee = MIN_FEERATE; this.minTrackedFee = MIN_FEERATE;
@ -428,22 +434,14 @@ function PolicyEstimator(logger) {
this.feeStats.logger = this.logger; this.feeStats.logger = this.logger;
this.priStats.logger = this.logger; this.priStats.logger = this.logger;
} }
} }
/** /**
* Serialization version.
* @const {Number}
* @default
*/
PolicyEstimator.VERSION = 0;
/**
* Initialize the estimator. * Initialize the estimator.
* @private * @private
*/ */
PolicyEstimator.prototype.init = function init() { init() {
const minFee = this.minTrackedFee; const minFee = this.minTrackedFee;
const minPri = this.minTrackedPri; const minPri = this.minTrackedPri;
@ -463,13 +461,13 @@ PolicyEstimator.prototype.init = function init() {
this.feeStats.init(fee, MAX_BLOCK_CONFIRMS, DEFAULT_DECAY); this.feeStats.init(fee, MAX_BLOCK_CONFIRMS, DEFAULT_DECAY);
this.priStats.init(priority, MAX_BLOCK_CONFIRMS, DEFAULT_DECAY); this.priStats.init(priority, MAX_BLOCK_CONFIRMS, DEFAULT_DECAY);
}; }
/** /**
* Reset the estimator. * Reset the estimator.
*/ */
PolicyEstimator.prototype.reset = function reset() { reset() {
this.feeUnlikely = 0; this.feeUnlikely = 0;
this.feeLikely = INF_FEERATE; this.feeLikely = INF_FEERATE;
this.priUnlikely = 0; this.priUnlikely = 0;
@ -479,14 +477,14 @@ PolicyEstimator.prototype.reset = function reset() {
this.bestHeight = 0; this.bestHeight = 0;
this.init(); this.init();
}; }
/** /**
* Stop tracking a tx. Remove from map. * Stop tracking a tx. Remove from map.
* @param {Hash} hash * @param {Hash} hash
*/ */
PolicyEstimator.prototype.removeTX = function removeTX(hash) { removeTX(hash) {
const item = this.map.get(hash); const item = this.map.get(hash);
if (!item) { if (!item) {
@ -497,45 +495,45 @@ PolicyEstimator.prototype.removeTX = function removeTX(hash) {
this.feeStats.removeTX(item.blockHeight, this.bestHeight, item.bucketIndex); this.feeStats.removeTX(item.blockHeight, this.bestHeight, item.bucketIndex);
this.map.delete(hash); this.map.delete(hash);
}; }
/** /**
* Test whether a fee should be used for calculation. * Test whether a fee should be used for calculation.
* @param {Amount} fee * @param {Amount} fee
* @param {Number} priority * @param {Number} priority
* @returns {Boolean} * @returns {Boolean}
*/ */
PolicyEstimator.prototype.isFeePoint = function isFeePoint(fee, priority) { isFeePoint(fee, priority) {
if ((priority < this.minTrackedPri && fee >= this.minTrackedFee) if ((priority < this.minTrackedPri && fee >= this.minTrackedFee)
|| (priority < this.priUnlikely && fee > this.feeLikely)) { || (priority < this.priUnlikely && fee > this.feeLikely)) {
return true; return true;
} }
return false; return false;
}; }
/** /**
* Test whether a priority should be used for calculation. * Test whether a priority should be used for calculation.
* @param {Amount} fee * @param {Amount} fee
* @param {Number} priority * @param {Number} priority
* @returns {Boolean} * @returns {Boolean}
*/ */
PolicyEstimator.prototype.isPriPoint = function isPriPoint(fee, priority) { isPriPoint(fee, priority) {
if ((fee < this.minTrackedFee && priority >= this.minTrackedPri) if ((fee < this.minTrackedFee && priority >= this.minTrackedPri)
|| (fee < this.feeUnlikely && priority > this.priLikely)) { || (fee < this.feeUnlikely && priority > this.priLikely)) {
return true; return true;
} }
return false; return false;
}; }
/** /**
* Process a mempool entry. * Process a mempool entry.
* @param {MempoolEntry} entry * @param {MempoolEntry} entry
* @param {Boolean} current - Whether the chain is synced. * @param {Boolean} current - Whether the chain is synced.
*/ */
PolicyEstimator.prototype.processTX = function processTX(entry, current) { processTX(entry, current) {
const height = entry.height; const height = entry.height;
const hash = entry.hash('hex'); const hash = entry.hash('hex');
@ -575,15 +573,15 @@ PolicyEstimator.prototype.processTX = function processTX(entry, current) {
} else { } else {
this.logger.spam('Not adding tx %s.', entry.txid()); this.logger.spam('Not adding tx %s.', entry.txid());
} }
}; }
/** /**
* Process an entry being removed from the mempool. * Process an entry being removed from the mempool.
* @param {Number} height - Block height. * @param {Number} height - Block height.
* @param {MempoolEntry} entry * @param {MempoolEntry} entry
*/ */
PolicyEstimator.prototype.processBlockTX = function processBlockTX(height, entry) { processBlockTX(height, entry) {
// Requires other mempool txs in order to be confirmed. Ignore. // Requires other mempool txs in order to be confirmed. Ignore.
if (entry.dependencies) if (entry.dependencies)
return; return;
@ -607,16 +605,16 @@ PolicyEstimator.prototype.processBlockTX = function processBlockTX(height, entry
this.priStats.record(blocks, priority); this.priStats.record(blocks, priority);
else if (this.isFeePoint(rate, priority)) else if (this.isFeePoint(rate, priority))
this.feeStats.record(blocks, rate); this.feeStats.record(blocks, rate);
}; }
/** /**
* Process a block of transaction entries being removed from the mempool. * Process a block of transaction entries being removed from the mempool.
* @param {Number} height - Block height. * @param {Number} height - Block height.
* @param {MempoolEntry[]} entries * @param {MempoolEntry[]} entries
* @param {Boolean} current - Whether the chain is synced. * @param {Boolean} current - Whether the chain is synced.
*/ */
PolicyEstimator.prototype.processBlock = function processBlock(height, entries, current) { processBlock(height, entries, current) {
// Ignore reorgs. // Ignore reorgs.
if (height <= this.bestHeight) if (height <= this.bestHeight)
return; return;
@ -674,16 +672,16 @@ PolicyEstimator.prototype.processBlock = function processBlock(height, entries,
entries.length, this.map.size); entries.length, this.map.size);
this.logger.debug('New fee rate: %d.', this.estimateFee()); this.logger.debug('New fee rate: %d.', this.estimateFee());
}; }
/** /**
* Estimate a fee rate. * Estimate a fee rate.
* @param {Number} [target=1] - Confirmation target. * @param {Number} [target=1] - Confirmation target.
* @param {Boolean} [smart=true] - Smart estimation. * @param {Boolean} [smart=true] - Smart estimation.
* @returns {Rate} * @returns {Rate}
*/ */
PolicyEstimator.prototype.estimateFee = function estimateFee(target, smart) { estimateFee(target, smart) {
if (!target) if (!target)
target = 1; target = 1;
@ -718,16 +716,16 @@ PolicyEstimator.prototype.estimateFee = function estimateFee(target, smart) {
return 0; return 0;
return Math.floor(rate); return Math.floor(rate);
}; }
/** /**
* Estimate a priority. * Estimate a priority.
* @param {Number} [target=1] - Confirmation target. * @param {Number} [target=1] - Confirmation target.
* @param {Boolean} [smart=true] - Smart estimation. * @param {Boolean} [smart=true] - Smart estimation.
* @returns {Number} * @returns {Number}
*/ */
PolicyEstimator.prototype.estimatePriority = function estimatePriority(target, smart) { estimatePriority(target, smart) {
if (!target) if (!target)
target = 1; target = 1;
@ -758,26 +756,26 @@ PolicyEstimator.prototype.estimatePriority = function estimatePriority(target, s
return 0; return 0;
return Math.floor(priority); return Math.floor(priority);
}; }
/** /**
* Get serialization size. * Get serialization size.
* @returns {Number} * @returns {Number}
*/ */
PolicyEstimator.prototype.getSize = function getSize() { getSize() {
let size = 0; let size = 0;
size += 5; size += 5;
size += encoding.sizeVarlen(this.feeStats.getSize()); size += encoding.sizeVarlen(this.feeStats.getSize());
return size; return size;
}; }
/** /**
* Serialize the estimator. * Serialize the estimator.
* @returns {Buffer} * @returns {Buffer}
*/ */
PolicyEstimator.prototype.toRaw = function toRaw() { toRaw() {
const size = this.getSize(); const size = this.getSize();
const bw = bio.write(size); const bw = bio.write(size);
@ -786,16 +784,16 @@ PolicyEstimator.prototype.toRaw = function toRaw() {
bw.writeVarBytes(this.feeStats.toRaw()); bw.writeVarBytes(this.feeStats.toRaw());
return bw.render(); return bw.render();
}; }
/** /**
* Inject properties from serialized data. * Inject properties from serialized data.
* @private * @private
* @param {Buffer} data * @param {Buffer} data
* @returns {PolicyEstimator} * @returns {PolicyEstimator}
*/ */
PolicyEstimator.prototype.fromRaw = function fromRaw(data) { fromRaw(data) {
const br = bio.read(data); const br = bio.read(data);
if (br.readU8() !== PolicyEstimator.VERSION) if (br.readU8() !== PolicyEstimator.VERSION)
@ -805,65 +803,85 @@ PolicyEstimator.prototype.fromRaw = function fromRaw(data) {
this.feeStats.fromRaw(br.readVarBytes()); this.feeStats.fromRaw(br.readVarBytes());
return this; return this;
}; }
/** /**
* Instantiate a policy estimator from serialized data. * Instantiate a policy estimator from serialized data.
* @param {Buffer} data * @param {Buffer} data
* @param {Logger?} logger * @param {Logger?} logger
* @returns {PolicyEstimator} * @returns {PolicyEstimator}
*/ */
PolicyEstimator.fromRaw = function fromRaw(data, logger) { static fromRaw(data, logger) {
return new PolicyEstimator(logger).fromRaw(data); return new PolicyEstimator(logger).fromRaw(data);
}; }
/** /**
* Inject properties from estimator. * Inject properties from estimator.
* @param {PolicyEstimator} estimator * @param {PolicyEstimator} estimator
* @returns {PolicyEstimator} * @returns {PolicyEstimator}
*/ */
PolicyEstimator.prototype.inject = function inject(estimator) { inject(estimator) {
this.bestHeight = estimator.bestHeight; this.bestHeight = estimator.bestHeight;
this.feeStats = estimator.feeStats; this.feeStats = estimator.feeStats;
return this; return this;
}; }
}
/** /**
* StatEntry * Serialization version.
* @const {Number}
* @default
*/
PolicyEstimator.VERSION = 0;
/**
* Stat Entry
* @alias module:mempool.StatEntry * @alias module:mempool.StatEntry
* @ignore * @ignore
*/ */
function StatEntry() { class StatEntry {
/**
* StatEntry
* @constructor
*/
constructor() {
this.blockHeight = -1; this.blockHeight = -1;
this.bucketIndex = -1; this.bucketIndex = -1;
}
} }
/** /**
* DoubleMap * Double Map
* @alias module:mempool.DoubleMap * @alias module:mempool.DoubleMap
* @ignore * @ignore
*/ */
function DoubleMap() { class DoubleMap {
if (!(this instanceof DoubleMap)) /**
return new DoubleMap(); * DoubleMap
* @constructor
*/
constructor() {
this.buckets = []; this.buckets = [];
} }
DoubleMap.prototype.insert = function insert(key, value) { insert(key, value) {
const i = binary.search(this.buckets, key, compare, true); const i = binary.search(this.buckets, key, compare, true);
this.buckets.splice(i, 0, [key, value]); this.buckets.splice(i, 0, [key, value]);
}; }
DoubleMap.prototype.search = function search(key) { search(key) {
assert(this.buckets.length !== 0, 'Cannot search.'); assert(this.buckets.length !== 0, 'Cannot search.');
const i = binary.search(this.buckets, key, compare, true); const i = binary.search(this.buckets, key, compare, true);
return this.buckets[i][1]; return this.buckets[i][1];
}; }
}
/* /*
* Helpers * Helpers
@ -898,8 +916,4 @@ function readArray(br) {
* Expose * Expose
*/ */
exports = PolicyEstimator; module.exports = PolicyEstimator;
exports.PolicyEstimator = PolicyEstimator;
exports.ConfirmStats = ConfirmStats;
module.exports = exports;

File diff suppressed because it is too large Load Diff