start estimating fees.

This commit is contained in:
Christopher Jeffrey 2016-06-11 15:45:26 -07:00
parent 84c1ec45c9
commit 59f06bb06c
No known key found for this signature in database
GPG Key ID: 8962AB9DE6666BBD
6 changed files with 94 additions and 55 deletions

View File

@ -138,8 +138,8 @@ function Environment(options) {
this.lowlevelup = require('./lowlevelup'); this.lowlevelup = require('./lowlevelup');
this.uri = require('./uri'); this.uri = require('./uri');
this.network = require('./network');
this.protocol = require('./protocol'); this.protocol = require('./protocol');
this.network = require('./network');
this.errors = require('./errors'); this.errors = require('./errors');
this.ldb = require('./ldb'); this.ldb = require('./ldb');
this.profiler = require('./profiler'); this.profiler = require('./profiler');

View File

@ -8,6 +8,9 @@
var bcoin = require('./env'); var bcoin = require('./env');
var constants = bcoin.protocol.constants; var constants = bcoin.protocol.constants;
var global = bcoin.utils.global;
var Float64Array = global.Float64Array || Array;
var Int32Array = global.Int32Array || Array;
/* /*
* Constants * Constants
@ -27,7 +30,7 @@ var MAX_PRIORITY = 1e16;
var INF_PRIORITY = 1e9 * constants.MAX_MONEY; var INF_PRIORITY = 1e9 * constants.MAX_MONEY;
var FEE_SPACING = 1.1; var FEE_SPACING = 1.1;
var PRI_SPACING = 2; var PRI_SPACING = 2;
var FREE_THRESHOLD = constants.FREE_THRESHOLD; var FREE_THRESHOLD = constants.tx.FREE_THRESHOLD;
/** /**
* Confirmation stats. * Confirmation stats.
@ -45,7 +48,11 @@ function ConfirmStats(buckets, maxConfirms, decay, type) {
if (!(this instanceof ConfirmStats)) if (!(this instanceof ConfirmStats))
return new ConfirmStats(buckets, maxConfirms, decay, type); return new ConfirmStats(buckets, maxConfirms, decay, type);
this.buckets = new Array(buckets.length); this.maxConfirms = maxConfirms;
this.decay = decay;
this.type = type;
this.buckets = new Float64Array(buckets.length);
this.bucketMap = {}; this.bucketMap = {};
for (i = 0; i < buckets.length; i++) { for (i = 0; i < buckets.length; i++) {
@ -53,36 +60,23 @@ function ConfirmStats(buckets, maxConfirms, decay, type) {
this.bucketMap[buckets[i]] = i; this.bucketMap[buckets[i]] = i;
} }
this.maxConfirms = maxConfirms;
this.decay = decay;
this.type = type;
this.confAvg = new Array(maxConfirms); this.confAvg = new Array(maxConfirms);
this.curBlockConf = new Array(maxConfirms); this.curBlockConf = new Array(maxConfirms);
this.unconfTX = new Array(maxConfirms); this.unconfTX = new Array(maxConfirms);
for (i = 0; i < maxConfirms; i++) { for (i = 0; i < maxConfirms; i++) {
this.confAvg[i] = new Array(buckets.length); this.confAvg[i] = new Float64Array(buckets.length);
this.curBlockConf[i] = new Array(buckets.length); this.curBlockConf[i] = new Int32Array(buckets.length);
this.unconfTX[i] = new Array(buckets.length); this.unconfTX[i] = new Int32Array(buckets.length);
} }
this.oldUnconfTX = new Array(buckets.length); this.oldUnconfTX = new Int32Array(buckets.length);
this.curBlockTX = new Array(buckets.length); this.curBlockTX = new Int32Array(buckets.length);
this.txAvg = new Array(buckets.length); this.txAvg = new Float64Array(buckets.length);
this.curBlockVal = new Array(buckets.length); this.curBlockVal = new Float64Array(buckets.length);
this.avg = new Array(buckets.length); this.avg = new Float64Array(buckets.length);
} }
/**
* Get max confirmations.
* @returns {Number}
*/
ConfirmStats.prototype.getMaxConfirms = function getMaxConfirms() {
return this.confAvg.length;
};
/** /**
* Clear data for the current block. * Clear data for the current block.
* @param {Number} height * @param {Number} height
@ -114,6 +108,7 @@ ConfirmStats.prototype.record = function record(blocks, val) {
return; return;
bucketIndex = this.bucketMap[val]; bucketIndex = this.bucketMap[val];
for (i = blocks; i <= this.curBlockConf.length; i++) for (i = blocks; i <= this.curBlockConf.length; i++)
this.curBlockConf[i - 1][bucketIndex]++; this.curBlockConf[i - 1][bucketIndex]++;
@ -168,8 +163,8 @@ ConfirmStats.prototype.estimateMedian = function estimateMedian(target, needed,
conf += this.confAvg[target - 1][i]; conf += this.confAvg[target - 1][i];
total += this.txAvg[i]; total += this.txAvg[i];
for (j = target; j < this.getMaxConfirms(); j++) for (j = target; j < this.maxConfirms; j++)
extra += this.unconfTX[(height - j) % bins][i]; extra += this.unconfTX[Math.max(height - j, 0) % bins][i];
extra += this.oldUnconfTX[i]; extra += this.oldUnconfTX[i];
@ -198,8 +193,8 @@ ConfirmStats.prototype.estimateMedian = function estimateMedian(target, needed,
minBucket = bestNear < bestFar ? bestNear : bestFar; minBucket = bestNear < bestFar ? bestNear : bestFar;
maxBucket = bestNear > bestFar ? bestNear : bestFar; maxBucket = bestNear > bestFar ? bestNear : bestFar;
for (j = minBucket; j <= maxBucket; j++) for (i = minBucket; i <= maxBucket; i++)
sum += this.txAvg[j]; sum += this.txAvg[i];
if (found && sum !== 0) { if (found && sum !== 0) {
sum = sum / 2; sum = sum / 2;
@ -213,7 +208,7 @@ ConfirmStats.prototype.estimateMedian = function estimateMedian(target, needed,
} }
} }
bcoin.debug('estimatefee: %d:' bcoin.debug('estimatefee: target=%d.'
+ ' For conf success %s %d need %s %s: %d from buckets %d - %d.' + ' For conf success %s %d need %s %s: %d from buckets %d - %d.'
+ ' Cur Bucket stats %d% %d/%d (%d mempool).', + ' Cur Bucket stats %d% %d/%d (%d mempool).',
target, target,
@ -224,7 +219,7 @@ ConfirmStats.prototype.estimateMedian = function estimateMedian(target, needed,
median, median,
this.buckets[minBucket], this.buckets[minBucket],
this.buckets[maxBucket], this.buckets[maxBucket],
100 * conf / (total + extra), 100 * conf / Math.max(1, total + extra),
conf, conf,
total, total,
extra); extra);
@ -295,10 +290,10 @@ ConfirmStats.prototype.removeTX = function removeTX(entryHeight, bestHeight, buc
*/ */
function PolicyEstimator(minRelay, network) { function PolicyEstimator(minRelay, network) {
var feelist, prilist, boundary; var fee, priority, boundary;
if (!(this instanceof PolicyEstimator)) if (!(this instanceof PolicyEstimator))
return new PolicyEstimator(minRelay); return new PolicyEstimator(minRelay, network);
this.network = bcoin.network.get(network); this.network = bcoin.network.get(network);
@ -306,36 +301,36 @@ function PolicyEstimator(minRelay, network) {
? MIN_FEERATE ? MIN_FEERATE
: minRelay; : minRelay;
feelist = []; fee = [];
for (boundary = this.minTrackedFee; for (boundary = this.minTrackedFee;
boundary <= MAX_FEERATE; boundary <= MAX_FEERATE;
boundary *= FEE_SPACING) { boundary *= FEE_SPACING) {
feelist.push(boundary); fee.push(boundary);
} }
feelist.push(INF_FEERATE); fee.push(INF_FEERATE);
this.feeStats = new ConfirmStats( this.feeStats = new ConfirmStats(
feelist, MAX_BLOCK_CONFIRMS, fee, MAX_BLOCK_CONFIRMS,
DEFAULT_DECAY, 'FeeRate'); DEFAULT_DECAY, 'FeeRate');
this.minTrackedPri = FREE_THRESHOLD < MIN_PRIORITY this.minTrackedPri = FREE_THRESHOLD < MIN_PRIORITY
? MIN_PRIORITY ? MIN_PRIORITY
: FREE_THRESHOLD; : FREE_THRESHOLD;
prilist = []; priority = [];
for (boundary = this.minTrackedPri; for (boundary = this.minTrackedPri;
boundary <= MAX_PRIORITY; boundary <= MAX_PRIORITY;
boundary *= PRI_SPACING) { boundary *= PRI_SPACING) {
prilist.push(boundary); priority.push(boundary);
} }
prilist.push(INF_PRIORITY); priority.push(INF_PRIORITY);
this.priStats = new ConfirmStats( this.priStats = new ConfirmStats(
prilist, MAX_BLOCK_CONFIRMS, priority, MAX_BLOCK_CONFIRMS,
DEFAULT_DECAY, 'Priority'); DEFAULT_DECAY, 'Priority');
this.feeUnlikely = 0; this.feeUnlikely = 0;
@ -422,8 +417,8 @@ PolicyEstimator.prototype.processTX = function processTX(entry, current) {
if (entry.dependencies) if (entry.dependencies)
return; return;
fee = entry.tx.getFee(); fee = entry.getFee();
rate = entry.tx.getRate(); rate = entry.getRate();
priority = entry.getPriority(height); priority = entry.getPriority(height);
bcoin.debug('estimatefee: Processing mempool tx %s.', hash); bcoin.debug('estimatefee: Processing mempool tx %s.', hash);
@ -438,9 +433,11 @@ PolicyEstimator.prototype.processTX = function processTX(entry, current) {
blockHeight: height, blockHeight: height,
bucketIndex: this.feeStats.addTX(height, rate) bucketIndex: this.feeStats.addTX(height, rate)
}; };
bcoin.debug('estimatefee: Rate: %d.', this.estimateFee(1, true));
} else { } else {
bcoin.debug('estimatefee: Not adding ts %s.', hash); bcoin.debug('estimatefee: Not adding ts %s.', hash);
} }
}; };
/** /**
@ -462,8 +459,8 @@ PolicyEstimator.prototype.processBlockTX = function processBlockTX(height, entry
return; return;
} }
fee = entry.tx.getFee(); fee = entry.getFee();
rate = entry.tx.getRate(); rate = entry.getRate();
priority = entry.getPriority(height); priority = entry.getPriority(height);
if (fee === 0 || this.isPriPoint(rate, priority)) if (fee === 0 || this.isPriPoint(rate, priority))
@ -544,7 +541,7 @@ PolicyEstimator.prototype.processBlock = function processBlock(height, entries,
PolicyEstimator.prototype.estimateFee = function estimateFee(target, smart) { PolicyEstimator.prototype.estimateFee = function estimateFee(target, smart) {
var rate, minPoolFee; var rate, minPoolFee;
if (target <= 0 || target > this.feeStats.getMaxConfirms()) if (target <= 0 || target > this.feeStats.maxConfirms)
return 0; return 0;
if (!smart) { if (!smart) {
@ -559,7 +556,7 @@ PolicyEstimator.prototype.estimateFee = function estimateFee(target, smart) {
} }
rate = -1; rate = -1;
while (rate < 0 && target <= this.feeStats.getMaxConfirms()) { while (rate < 0 && target <= this.feeStats.maxConfirms) {
rate = this.feeStats.estimateMedian( rate = this.feeStats.estimateMedian(
target++, SUFFICIENT_FEETXS, MIN_SUCCESS_PCT, target++, SUFFICIENT_FEETXS, MIN_SUCCESS_PCT,
true, this.bestHeight); true, this.bestHeight);
@ -587,7 +584,7 @@ PolicyEstimator.prototype.estimateFee = function estimateFee(target, smart) {
PolicyEstimator.prototype.estimatePriority = function estimatePriority(target, smart) { PolicyEstimator.prototype.estimatePriority = function estimatePriority(target, smart) {
var minPoolFee, priority; var minPoolFee, priority;
if (target <= 0 || target > this.priStats.getMaxConfirms()) if (target <= 0 || target > this.priStats.maxConfirms)
return -1; return -1;
if (!smart) { if (!smart) {
@ -602,7 +599,7 @@ PolicyEstimator.prototype.estimatePriority = function estimatePriority(target, s
return INF_PRIORITY; return INF_PRIORITY;
priority = -1; priority = -1;
while (priority < 0 && target <= this.priStats.getMaxConfirms()) { while (priority < 0 && target <= this.priStats.maxConfirms) {
priority = this.priStats.estimateMedian( priority = this.priStats.estimateMedian(
target++, SUFFICIENT_PRITXS, MIN_SUCCESS_PCT, target++, SUFFICIENT_PRITXS, MIN_SUCCESS_PCT,
true, this.bestHeight); true, this.bestHeight);

View File

@ -228,11 +228,13 @@ Mempool.prototype.destroy = function destroy(callback) {
Mempool.prototype.addBlock = function addBlock(block, callback, force) { Mempool.prototype.addBlock = function addBlock(block, callback, force) {
var self = this; var self = this;
var unlock = this._lock(addBlock, [block, callback], force); var unlock = this._lock(addBlock, [block, callback], force);
var entries;
if (!unlock) if (!unlock)
return; return;
callback = utils.wrap(callback, unlock); callback = utils.wrap(callback, unlock);
entries = [];
utils.forEachSerial(block.txs.slice().reverse(), function(tx, next) { utils.forEachSerial(block.txs.slice().reverse(), function(tx, next) {
var hash = tx.hash('hex'); var hash = tx.hash('hex');
@ -255,6 +257,8 @@ Mempool.prototype.addBlock = function addBlock(block, callback, force) {
self.emit('confirmed', tx, block); self.emit('confirmed', tx, block);
entries.push(entry);
return next(); return next();
}, true); }, true);
}); });
@ -265,6 +269,8 @@ Mempool.prototype.addBlock = function addBlock(block, callback, force) {
self.blockSinceBump = true; self.blockSinceBump = true;
self.lastFeeUpdate = utils.now(); self.lastFeeUpdate = utils.now();
self.network.fees.processBlock(block.height, entries, self.chain.isFull());
return callback(); return callback();
}); });
}; };
@ -792,6 +798,8 @@ Mempool.prototype.addUnchecked = function addUnchecked(entry, callback, force) {
self.emit('tx', entry.tx); self.emit('tx', entry.tx);
self.emit('add tx', entry.tx); self.emit('add tx', entry.tx);
self.network.fees.processTX(entry, self.chain.isFull());
bcoin.debug('Added tx %s to the mempool.', entry.tx.rhash); bcoin.debug('Added tx %s to the mempool.', entry.tx.rhash);
resolved = self.resolveOrphans(entry.tx); resolved = self.resolveOrphans(entry.tx);
@ -855,6 +863,8 @@ Mempool.prototype.removeUnchecked = function removeUnchecked(entry, limit, callb
self.size -= self.memUsage(entry.tx); self.size -= self.memUsage(entry.tx);
self.total--; self.total--;
self.network.fees.removeTX(entry.tx.hash('hex'));
if (limit) { if (limit) {
rate = bcoin.tx.getRate(entry.size, entry.fees); rate = bcoin.tx.getRate(entry.size, entry.fees);
rate += self.minReasonableFee; rate += self.minReasonableFee;
@ -1788,12 +1798,14 @@ function MempoolEntry(options) {
this.tx = options.tx; this.tx = options.tx;
this.height = options.height; this.height = options.height;
this.size = options.size;
this.priority = options.priority; this.priority = options.priority;
this.fee = options.fee;
this.ts = options.ts; this.ts = options.ts;
this.chainValue = options.chainValue; this.chainValue = options.chainValue;
this.count = options.count; this.count = options.count;
this.size = options.size; this.sizes = options.sizes;
this.fees = options.fees; this.fees = options.fees;
this.dependencies = options.dependencies; this.dependencies = options.dependencies;
} }
@ -1808,6 +1820,8 @@ function MempoolEntry(options) {
MempoolEntry.fromTX = function fromTX(tx, height) { MempoolEntry.fromTX = function fromTX(tx, height) {
var data = tx.getPriority(height); var data = tx.getPriority(height);
var dependencies = false; var dependencies = false;
var size = tx.getVirtualSize();
var fee = tx.getFee();
var i; var i;
for (i = 0; i < tx.inputs.length; i++) { for (i = 0; i < tx.inputs.length; i++) {
@ -1820,12 +1834,14 @@ MempoolEntry.fromTX = function fromTX(tx, height) {
return new MempoolEntry({ return new MempoolEntry({
tx: tx, tx: tx,
height: height, height: height,
size: size,
priority: data.priority, priority: data.priority,
fee: fee,
chainValue: data.value, chainValue: data.value,
ts: utils.now(), ts: utils.now(),
count: 1, count: 1,
size: tx.getVirtualSize(), sizes: size,
fees: tx.getFee(), fees: fee,
dependencies: dependencies dependencies: dependencies
}); });
}; };
@ -1844,11 +1860,13 @@ MempoolEntry.prototype.toRaw = function toRaw(writer) {
bcoin.protocol.framer.renderTX(this.tx, true, p); bcoin.protocol.framer.renderTX(this.tx, true, p);
p.writeU32(this.height); p.writeU32(this.height);
p.writeU32(this.size);
p.writeDouble(this.priority); p.writeDouble(this.priority);
p.writeVarint(this.fee);
p.writeVarint(this.chainValue); p.writeVarint(this.chainValue);
p.writeU32(this.ts); p.writeU32(this.ts);
p.writeU32(this.count); p.writeU32(this.count);
p.writeU32(this.size); p.writeU32(this.sizes);
p.writeVarint(this.fees); p.writeVarint(this.fees);
p.writeU8(this.dependencies ? 1 : 0); p.writeU8(this.dependencies ? 1 : 0);
@ -1869,11 +1887,13 @@ MempoolEntry.fromRaw = function fromRaw(data) {
return new MempoolEntry({ return new MempoolEntry({
tx: bcoin.tx.fromRaw(p), tx: bcoin.tx.fromRaw(p),
height: p.readU32(), height: p.readU32(),
size: p.readU32(),
priority: p.readDouble(), priority: p.readDouble(),
fee: p.readVarint(),
chainValue: p.readVarint(), chainValue: p.readVarint(),
ts: p.readU32(), ts: p.readU32(),
count: p.readU32(), count: p.readU32(),
size: p.readU32(), sizes: p.readU32(),
fees: p.readVarint(), fees: p.readVarint(),
dependencies: p.readU8() === 1 dependencies: p.readU8() === 1
}); });
@ -1897,6 +1917,24 @@ MempoolEntry.prototype.getPriority = function getPriority(height) {
return result; return result;
}; };
/**
* Get fee.
* @returns {Amount}
*/
MempoolEntry.prototype.getFee = function getFee() {
return this.fee;
};
/**
* Calculate fee rate.
* @returns {Rate}
*/
MempoolEntry.prototype.getRate = function getRate() {
return bcoin.tx.getRate(this.size, this.fee);
};
/** /**
* Test whether the entry is free with * Test whether the entry is free with
* the current priority (calculated by * the current priority (calculated by

View File

@ -7,7 +7,9 @@
var utils = require('./utils'); var utils = require('./utils');
var assert = utils.assert; var assert = utils.assert;
var constants = require('./protocol/constants');
var network = require('./protocol/network'); var network = require('./protocol/network');
var PolicyEstimator = require('./fees');
/** /**
* Represents a network. * Represents a network.
@ -41,6 +43,8 @@ function Network(options) {
this[key] = value; this[key] = value;
} }
this.fees = new PolicyEstimator(constants.tx.MIN_RELAY, this);
if (!Network[this.type]) if (!Network[this.type])
Network[this.type] = this; Network[this.type] = this;

View File

@ -1304,7 +1304,7 @@ Peer.prototype._handleGetData = function _handleGetData(items) {
// the `mempool` handler, but it would be // the `mempool` handler, but it would be
// too slow. // too slow.
if (self.feeRate !== -1) { if (self.feeRate !== -1) {
if (bcoin.tx.getRate(entry.size, entry.fees) < self.feeRate) if (entry.getRate() < self.feeRate)
return next(); return next();
} }

View File

@ -1744,7 +1744,7 @@ TX.getMinFee = function getMinFee(size, rate) {
* @returns {Rate} * @returns {Rate}
*/ */
TX.getRate = function(size, fee) { TX.getRate = function getRate(size, fee) {
return Math.floor(fee * 1000 / size); return Math.floor(fee * 1000 / size);
}; };