mempool. fees.
This commit is contained in:
parent
62bc8b077d
commit
d3383499c0
@ -38,7 +38,7 @@ var DUMMY = new Buffer([0]);
|
|||||||
* @param {Number?} options.limitFreeRelay
|
* @param {Number?} options.limitFreeRelay
|
||||||
* @param {Boolean?} options.relayPriority
|
* @param {Boolean?} options.relayPriority
|
||||||
* @param {Boolean?} options.requireStandard
|
* @param {Boolean?} options.requireStandard
|
||||||
* @param {Boolean?} options.rejectInsaneFees
|
* @param {Boolean?} options.rejectAbsurdFees
|
||||||
* @param {Boolean?} options.relay
|
* @param {Boolean?} options.relay
|
||||||
* @property {Boolean} loaded
|
* @property {Boolean} loaded
|
||||||
* @property {Object} db
|
* @property {Object} db
|
||||||
@ -47,7 +47,6 @@ var DUMMY = new Buffer([0]);
|
|||||||
* @property {Locker} locker
|
* @property {Locker} locker
|
||||||
* @property {Number} freeCount
|
* @property {Number} freeCount
|
||||||
* @property {Number} lastTime
|
* @property {Number} lastTime
|
||||||
* @property {String} backend
|
|
||||||
* @emits Mempool#open
|
* @emits Mempool#open
|
||||||
* @emits Mempool#error
|
* @emits Mempool#error
|
||||||
* @emits Mempool#tx
|
* @emits Mempool#tx
|
||||||
@ -88,10 +87,15 @@ function Mempool(options) {
|
|||||||
this.requireStandard = this.options.requireStandard != null
|
this.requireStandard = this.options.requireStandard != null
|
||||||
? this.options.requireStandard
|
? this.options.requireStandard
|
||||||
: network.requireStandard;
|
: network.requireStandard;
|
||||||
this.rejectInsaneFees = this.options.rejectInsaneFees !== false;
|
this.rejectAbsurdFees = this.options.rejectAbsurdFees !== false;
|
||||||
|
this.prematureWitness = !!this.options.prematureWitness;
|
||||||
|
|
||||||
// Use an in-memory binary search tree by default
|
this.maxSize = options.maxSize || constants.mempool.MAX_MEMPOOL_SIZE;
|
||||||
this.backend = this.options.memory === false ? 'leveldb' : 'memory';
|
this.minFeeRate = 0;
|
||||||
|
this.blockSinceBump = false;
|
||||||
|
this.lastFeeUpdate = utils.now();
|
||||||
|
this.minReasonableFee = constants.tx.MIN_FEE;
|
||||||
|
this.minRelayFee = constants.tx.MIN_FEE;
|
||||||
|
|
||||||
this._init();
|
this._init();
|
||||||
}
|
}
|
||||||
@ -116,7 +120,7 @@ Mempool.prototype._init = function _init() {
|
|||||||
var options = {
|
var options = {
|
||||||
name: this.options.name || 'mempool',
|
name: this.options.name || 'mempool',
|
||||||
location: this.options.location,
|
location: this.options.location,
|
||||||
db: this.options.db || this.backend
|
db: this.options.db || 'memory'
|
||||||
};
|
};
|
||||||
|
|
||||||
assert(unlock);
|
assert(unlock);
|
||||||
@ -269,7 +273,15 @@ Mempool.prototype.addBlock = function addBlock(block, callback, force) {
|
|||||||
return next();
|
return next();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}, callback);
|
}, function(err) {
|
||||||
|
if (err)
|
||||||
|
return callback(err);
|
||||||
|
|
||||||
|
self.blockSinceBump = true;
|
||||||
|
self.lastFeeUpdate = utils.now();
|
||||||
|
|
||||||
|
return callback();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -327,8 +339,9 @@ Mempool.prototype.removeBlock = function removeBlock(block, callback, force) {
|
|||||||
|
|
||||||
Mempool.prototype.limitMempoolSize = function limitMempoolSize(callback) {
|
Mempool.prototype.limitMempoolSize = function limitMempoolSize(callback) {
|
||||||
var self = this;
|
var self = this;
|
||||||
|
var rate;
|
||||||
|
|
||||||
if (this.size <= constants.mempool.MAX_MEMPOOL_SIZE)
|
if (this.size <= this.maxSize)
|
||||||
return callback(null, true);
|
return callback(null, true);
|
||||||
|
|
||||||
this.tx.getRange({
|
this.tx.getRange({
|
||||||
@ -339,7 +352,20 @@ Mempool.prototype.limitMempoolSize = function limitMempoolSize(callback) {
|
|||||||
return callback(err);
|
return callback(err);
|
||||||
|
|
||||||
utils.forEachSerial(function(tx, next) {
|
utils.forEachSerial(function(tx, next) {
|
||||||
self.removeUnchecked(tx, next);
|
self.removeUnchecked(tx, function(err) {
|
||||||
|
if (err)
|
||||||
|
return next(err);
|
||||||
|
|
||||||
|
rate = tx.getFee().muln(1000).divn(tx.getVirtualSize()).toNumber();
|
||||||
|
rate += self.minReasonableFee;
|
||||||
|
|
||||||
|
if (rate > self.minFeeRate) {
|
||||||
|
self.minFeeRate = rate;
|
||||||
|
self.blockSinceBump = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
next();
|
||||||
|
});
|
||||||
}, function(err) {
|
}, function(err) {
|
||||||
if (err)
|
if (err)
|
||||||
return callback(err);
|
return callback(err);
|
||||||
@ -348,7 +374,7 @@ Mempool.prototype.limitMempoolSize = function limitMempoolSize(callback) {
|
|||||||
if (err)
|
if (err)
|
||||||
return callback(err);
|
return callback(err);
|
||||||
|
|
||||||
return callback(self.size <= constants.mempool.MAX_MEMPOOL_SIZE);
|
return callback(self.size <= self.maxSize);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@ -362,6 +388,7 @@ Mempool.prototype.limitMempoolSize = function limitMempoolSize(callback) {
|
|||||||
Mempool.prototype.purgeOrphans = function purgeOrphans(callback) {
|
Mempool.prototype.purgeOrphans = function purgeOrphans(callback) {
|
||||||
var self = this;
|
var self = this;
|
||||||
var batch = this.db.batch();
|
var batch = this.db.batch();
|
||||||
|
var tx;
|
||||||
|
|
||||||
callback = utils.ensure(callback);
|
callback = utils.ensure(callback);
|
||||||
|
|
||||||
@ -370,7 +397,7 @@ Mempool.prototype.purgeOrphans = function purgeOrphans(callback) {
|
|||||||
gte: type,
|
gte: type,
|
||||||
lte: type + '~',
|
lte: type + '~',
|
||||||
keys: true,
|
keys: true,
|
||||||
values: false,
|
values: true,
|
||||||
fillCache: false,
|
fillCache: false,
|
||||||
keyAsBuffer: false
|
keyAsBuffer: false
|
||||||
});
|
});
|
||||||
@ -383,6 +410,15 @@ Mempool.prototype.purgeOrphans = function purgeOrphans(callback) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (type === 'O') {
|
||||||
|
try {
|
||||||
|
tx = bcoin.tx.fromExtended(value, true);
|
||||||
|
} catch (e) {
|
||||||
|
return callback(e);
|
||||||
|
}
|
||||||
|
self.size -= memOrphan(tx);
|
||||||
|
}
|
||||||
|
|
||||||
if (key === undefined)
|
if (key === undefined)
|
||||||
return iter.end(callback);
|
return iter.end(callback);
|
||||||
|
|
||||||
@ -399,15 +435,9 @@ Mempool.prototype.purgeOrphans = function purgeOrphans(callback) {
|
|||||||
if (err)
|
if (err)
|
||||||
return callback(err);
|
return callback(err);
|
||||||
|
|
||||||
self.dynamicMemoryUsage(function(err, size) {
|
self.orphans = 0;
|
||||||
if (err)
|
|
||||||
return callback(err);
|
|
||||||
|
|
||||||
self.size = size;
|
return callback();
|
||||||
self.orphans = 0;
|
|
||||||
|
|
||||||
return callback();
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
@ -539,6 +569,7 @@ Mempool.prototype.addTX = function addTX(tx, callback, force) {
|
|||||||
var self = this;
|
var self = this;
|
||||||
var flags = constants.flags.STANDARD_VERIFY_FLAGS;
|
var flags = constants.flags.STANDARD_VERIFY_FLAGS;
|
||||||
var lockFlags = constants.flags.STANDARD_LOCKTIME_FLAGS;
|
var lockFlags = constants.flags.STANDARD_LOCKTIME_FLAGS;
|
||||||
|
var hash = tx.hash('hex');
|
||||||
var ret = {};
|
var ret = {};
|
||||||
var now;
|
var now;
|
||||||
|
|
||||||
@ -559,11 +590,6 @@ Mempool.prototype.addTX = function addTX(tx, callback, force) {
|
|||||||
0));
|
0));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!this.chain.segwitActive) {
|
|
||||||
if (tx.hasWitness())
|
|
||||||
return callback(new VerifyError(tx, 'nonstandard', 'no-witness-yet', 0));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!tx.isSane(ret))
|
if (!tx.isSane(ret))
|
||||||
return callback(new VerifyError(tx, 'invalid', ret.reason, ret.score));
|
return callback(new VerifyError(tx, 'invalid', ret.reason, ret.score));
|
||||||
|
|
||||||
@ -582,6 +608,11 @@ Mempool.prototype.addTX = function addTX(tx, callback, force) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!this.chain.segwitActive && !this.prematureWitness) {
|
||||||
|
if (tx.hasWitness())
|
||||||
|
return callback(new VerifyError(tx, 'nonstandard', 'no-witness-yet', 0));
|
||||||
|
}
|
||||||
|
|
||||||
this.chain.checkFinal(this.chain.tip, tx, lockFlags, function(err, isFinal) {
|
this.chain.checkFinal(this.chain.tip, tx, lockFlags, function(err, isFinal) {
|
||||||
if (err)
|
if (err)
|
||||||
return callback(err);
|
return callback(err);
|
||||||
@ -589,7 +620,7 @@ Mempool.prototype.addTX = function addTX(tx, callback, force) {
|
|||||||
if (!isFinal)
|
if (!isFinal)
|
||||||
return callback(new VerifyError(tx, 'nonstandard', 'non-final', 0));
|
return callback(new VerifyError(tx, 'nonstandard', 'non-final', 0));
|
||||||
|
|
||||||
self.seenTX(tx, function(err, exists) {
|
self.has(hash, function(err, exists) {
|
||||||
if (err)
|
if (err)
|
||||||
return callback(err);
|
return callback(err);
|
||||||
|
|
||||||
@ -600,47 +631,59 @@ Mempool.prototype.addTX = function addTX(tx, callback, force) {
|
|||||||
0));
|
0));
|
||||||
}
|
}
|
||||||
|
|
||||||
self.isDoubleSpend(tx, function(err, doubleSpend) {
|
self.chain.db.isUnspentTX(hash, function(err, exists) {
|
||||||
if (err)
|
if (err)
|
||||||
return callback(err);
|
return callback(err);
|
||||||
|
|
||||||
if (doubleSpend) {
|
if (exists) {
|
||||||
return callback(new VerifyError(tx,
|
return callback(new VerifyError(tx,
|
||||||
'duplicate',
|
'alreadyknown',
|
||||||
'bad-txns-inputs-spent',
|
'txn-already-known',
|
||||||
0));
|
0));
|
||||||
}
|
}
|
||||||
|
|
||||||
self.fillAllCoins(tx, function(err) {
|
self.isDoubleSpend(tx, function(err, doubleSpend) {
|
||||||
if (err)
|
if (err)
|
||||||
return callback(err);
|
return callback(err);
|
||||||
|
|
||||||
if (!tx.hasCoins()) {
|
if (doubleSpend) {
|
||||||
if (self.totalSize > constants.mempool.MAX_MEMPOOL_SIZE) {
|
return callback(new VerifyError(tx,
|
||||||
return callback(new VerifyError(tx,
|
'duplicate',
|
||||||
'insufficientfee',
|
'bad-txns-inputs-spent',
|
||||||
'mempool full',
|
0));
|
||||||
0));
|
|
||||||
}
|
|
||||||
return self.storeOrphan(tx, callback);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
self.verify(tx, function(err) {
|
self.fillAllCoins(tx, function(err) {
|
||||||
if (err)
|
if (err)
|
||||||
return callback(err);
|
return callback(err);
|
||||||
|
|
||||||
self.limitMempoolSize(function(err, result) {
|
if (!tx.hasCoins()) {
|
||||||
if (err)
|
if (self.size > self.maxSize) {
|
||||||
return callback(err);
|
|
||||||
|
|
||||||
if (!result) {
|
|
||||||
return callback(new VerifyError(tx,
|
return callback(new VerifyError(tx,
|
||||||
'insufficientfee',
|
'insufficientfee',
|
||||||
'mempool full',
|
'mempool full',
|
||||||
0));
|
0));
|
||||||
}
|
}
|
||||||
|
return self.storeOrphan(tx, callback);
|
||||||
|
}
|
||||||
|
|
||||||
self.addUnchecked(tx, callback);
|
self.verify(tx, function(err) {
|
||||||
|
if (err)
|
||||||
|
return callback(err);
|
||||||
|
|
||||||
|
self.limitMempoolSize(function(err, result) {
|
||||||
|
if (err)
|
||||||
|
return callback(err);
|
||||||
|
|
||||||
|
if (!result) {
|
||||||
|
return callback(new VerifyError(tx,
|
||||||
|
'insufficientfee',
|
||||||
|
'mempool full',
|
||||||
|
0));
|
||||||
|
}
|
||||||
|
|
||||||
|
self.addUnchecked(tx, callback);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@ -665,7 +708,7 @@ Mempool.prototype.addUnchecked = function addUnchecked(tx, callback) {
|
|||||||
if (err)
|
if (err)
|
||||||
return callback(err);
|
return callback(err);
|
||||||
|
|
||||||
self.size += tx.getSize();
|
self.size += mem(tx);
|
||||||
self.emit('tx', tx);
|
self.emit('tx', tx);
|
||||||
self.emit('add tx', tx);
|
self.emit('add tx', tx);
|
||||||
|
|
||||||
@ -708,8 +751,10 @@ Mempool.prototype.addUnchecked = function addUnchecked(tx, callback) {
|
|||||||
* @param {Function} callback
|
* @param {Function} callback
|
||||||
*/
|
*/
|
||||||
|
|
||||||
Mempool.prototype.removeUnchecked = function removeUnchecked(tx, callback) {
|
Mempool.prototype.removeUnchecked = function removeUnchecked(tx, callback, limit) {
|
||||||
var self = this;
|
var self = this;
|
||||||
|
var rate;
|
||||||
|
|
||||||
this.fillAllHistory(tx, function(err, tx) {
|
this.fillAllHistory(tx, function(err, tx) {
|
||||||
if (err)
|
if (err)
|
||||||
return callback(err);
|
return callback(err);
|
||||||
@ -721,14 +766,57 @@ Mempool.prototype.removeUnchecked = function removeUnchecked(tx, callback) {
|
|||||||
self._removeUnchecked(tx, function(err) {
|
self._removeUnchecked(tx, function(err) {
|
||||||
if (err)
|
if (err)
|
||||||
return callback(err);
|
return callback(err);
|
||||||
self.size -= tx.getSize();
|
|
||||||
|
self.size -= mem(tx);
|
||||||
|
|
||||||
self.emit('remove tx', tx);
|
self.emit('remove tx', tx);
|
||||||
|
|
||||||
return callback();
|
return callback();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calculate and update the minimum rolling fee rate.
|
||||||
|
* @returns {Number} Rate.
|
||||||
|
*/
|
||||||
|
|
||||||
|
Mempool.prototype.getMinRate = function getMinRate() {
|
||||||
|
var now, halflife, exp;
|
||||||
|
|
||||||
|
if (!this.blockSinceBump || this.minFeeRate == 0)
|
||||||
|
return this.minFeeRate;
|
||||||
|
|
||||||
|
now = utils.now();
|
||||||
|
|
||||||
|
if (now > this.lastFeeUpdate + 10) {
|
||||||
|
halflife = constants.mempool.FEE_HALFLIFE;
|
||||||
|
|
||||||
|
if (this.size < this.maxSize / 4) {
|
||||||
|
halflife /= 4;
|
||||||
|
halflife |= 0;
|
||||||
|
} else if (this.size < this.maxSize / 2) {
|
||||||
|
halflife /= 2;
|
||||||
|
halflife |= 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.minFeeRate /= Math.pow(2.0, (now - this.lastFeeUpdate) / halflife | 0);
|
||||||
|
this.minFeeRate |= 0;
|
||||||
|
this.lastFeeUpdate = now;
|
||||||
|
|
||||||
|
if (this.minFeeRate < this.minReasonableFee / 2) {
|
||||||
|
this.minFeeRate = 0;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.minFeeRate > this.minReasonableFee)
|
||||||
|
return this.minFeeRate;
|
||||||
|
|
||||||
|
return this.minReasonableFee;
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Verify a transaction with mempool standards.
|
* Verify a transaction with mempool standards.
|
||||||
* @param {TX} tx
|
* @param {TX} tx
|
||||||
@ -742,7 +830,7 @@ Mempool.prototype.verify = function verify(tx, callback) {
|
|||||||
var flags = constants.flags.STANDARD_VERIFY_FLAGS;
|
var flags = constants.flags.STANDARD_VERIFY_FLAGS;
|
||||||
var mandatory = constants.flags.MANDATORY_VERIFY_FLAGS;
|
var mandatory = constants.flags.MANDATORY_VERIFY_FLAGS;
|
||||||
var ret = {};
|
var ret = {};
|
||||||
var fee, now, free, minFee;
|
var fee, modFee, now, size, rejectFee, minRelayFee;
|
||||||
|
|
||||||
if (this.chain.segwitActive)
|
if (this.chain.segwitActive)
|
||||||
mandatory |= constants.flags.VERIFY_WITNESS;
|
mandatory |= constants.flags.VERIFY_WITNESS;
|
||||||
@ -772,37 +860,47 @@ Mempool.prototype.verify = function verify(tx, callback) {
|
|||||||
0));
|
0));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!tx.checkInputs(height, ret))
|
// In reality we would apply deltas
|
||||||
return callback(new VerifyError(tx, 'invalid', ret.reason, ret.score));
|
// to the modified fee (only for mining).
|
||||||
|
|
||||||
fee = tx.getFee();
|
fee = tx.getFee();
|
||||||
minFee = tx.getMinFee();
|
modFee = fee;
|
||||||
if (fee.cmp(minFee) < 0) {
|
size = tx.getVirtualSize();
|
||||||
if (self.relayPriority) {
|
rejectFee = tx.getMinFee(size, self.getMinRate());
|
||||||
free = tx.isFree(height);
|
minRelayFee = tx.getMinFee(size, self.minRelayFee);
|
||||||
if (!free) {
|
|
||||||
return callback(new VerifyError(tx,
|
if (rejectFee.cmpn(0) > 0 && modFee.cmp(rejectFee) < 0) {
|
||||||
'insufficientfee',
|
return callback(new VerifyError(tx,
|
||||||
'insufficient priority',
|
'insufficientfee',
|
||||||
0));
|
'mempool min fee not met',
|
||||||
}
|
0));
|
||||||
} else {
|
}
|
||||||
|
|
||||||
|
if (self.relayPriority && modFee.cmp(minRelayFee) < 0) {
|
||||||
|
if (!tx.isFree(height, size)) {
|
||||||
return callback(new VerifyError(tx,
|
return callback(new VerifyError(tx,
|
||||||
'insufficientfee',
|
'insufficientfee',
|
||||||
'insufficient fee',
|
'insufficient priority',
|
||||||
0));
|
0));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (self.limitFree && free) {
|
// 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.
|
||||||
|
if (self.limitFree && modFee.cmp(minRelayFee) < 0) {
|
||||||
now = utils.now();
|
now = utils.now();
|
||||||
|
|
||||||
if (!self.lastTime)
|
if (!self.lastTime)
|
||||||
self.lastTime = now;
|
self.lastTime = now;
|
||||||
|
|
||||||
|
// Use an exponentially decaying ~10-minute window:
|
||||||
self.freeCount *= Math.pow(1 - 1 / 600, now - self.lastTime);
|
self.freeCount *= Math.pow(1 - 1 / 600, now - self.lastTime);
|
||||||
self.lastTime = now;
|
self.lastTime = now;
|
||||||
|
|
||||||
|
// The limitFreeRelay unit is thousand-bytes-per-minute
|
||||||
|
// At default rate it would take over a month to fill 1GB
|
||||||
if (self.freeCount > self.limitFreeRelay * 10 * 1000) {
|
if (self.freeCount > self.limitFreeRelay * 10 * 1000) {
|
||||||
return callback(new VerifyError(tx,
|
return callback(new VerifyError(tx,
|
||||||
'insufficientfee',
|
'insufficientfee',
|
||||||
@ -810,12 +908,15 @@ Mempool.prototype.verify = function verify(tx, callback) {
|
|||||||
0));
|
0));
|
||||||
}
|
}
|
||||||
|
|
||||||
self.freeCount += tx.getSize();
|
self.freeCount += size;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (self.rejectInsaneFees && fee.cmp(minFee.muln(10000)) > 0)
|
if (self.rejectAbsurdFees && fee.cmp(minRelayFee.muln(10000)) > 0)
|
||||||
return callback(new VerifyError(tx, 'highfee', 'absurdly-high-fee', 0));
|
return callback(new VerifyError(tx, 'highfee', 'absurdly-high-fee', 0));
|
||||||
|
|
||||||
|
if (!tx.checkInputs(height, ret))
|
||||||
|
return callback(new VerifyError(tx, 'invalid', ret.reason, ret.score));
|
||||||
|
|
||||||
self.countAncestors(tx, function(err, count) {
|
self.countAncestors(tx, function(err, count) {
|
||||||
if (err)
|
if (err)
|
||||||
return callback(err);
|
return callback(err);
|
||||||
@ -943,6 +1044,7 @@ Mempool.prototype.storeOrphan = function storeOrphan(tx, callback, force) {
|
|||||||
return callback(err);
|
return callback(err);
|
||||||
|
|
||||||
self.orphans++;
|
self.orphans++;
|
||||||
|
self.size += memOrphan(tx);
|
||||||
|
|
||||||
batch.put('O/' + hash, tx.toExtended(true));
|
batch.put('O/' + hash, tx.toExtended(true));
|
||||||
|
|
||||||
@ -1164,6 +1266,9 @@ Mempool.prototype.removeOrphan = function removeOrphan(tx, callback) {
|
|||||||
}, function(err) {
|
}, function(err) {
|
||||||
if (err)
|
if (err)
|
||||||
return callback(err);
|
return callback(err);
|
||||||
|
|
||||||
|
self.size -= memOrphan(tx);
|
||||||
|
|
||||||
batch.write(callback);
|
batch.write(callback);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@ -1559,5 +1664,95 @@ Mempool.prototype._removeUnchecked = function removeUnchecked(hash, callback, fo
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
function MempoolTX(options) {
|
||||||
|
this.tx = options.tx;
|
||||||
|
this.height = options.height;
|
||||||
|
this.priority = options.priority;
|
||||||
|
this.chainValue = options.chainValue;
|
||||||
|
|
||||||
|
this.ts = options.ts;
|
||||||
|
this.count = options.count;
|
||||||
|
this.size = options.size;
|
||||||
|
this.fees = options.fees;
|
||||||
|
}
|
||||||
|
|
||||||
|
MempoolTX.fromTX = function fromTX(tx, height) {
|
||||||
|
var data = tx.getPriority(height);
|
||||||
|
|
||||||
|
return new MempoolTX({
|
||||||
|
tx: tx,
|
||||||
|
height: height,
|
||||||
|
priority: data.priority,
|
||||||
|
chainValue: data.value,
|
||||||
|
ts: utils.now(),
|
||||||
|
count: 1,
|
||||||
|
size: tx.getVirtualSize(),
|
||||||
|
fees: tx.getFee()
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
MempoolTX.prototype.toRaw = function toRaw() {
|
||||||
|
var p = new BufferWriter();
|
||||||
|
bcoin.protocol.framer.tx(this.tx, p);
|
||||||
|
p.writeU32(this.height);
|
||||||
|
p.writeVarint(this.priority);
|
||||||
|
p.writeVarint(this.chainValue);
|
||||||
|
p.writeU32(this.ts);
|
||||||
|
p.writeU32(this.count);
|
||||||
|
p.writeU32(this.size);
|
||||||
|
p.writeVarint(this.fees);
|
||||||
|
return p.render();
|
||||||
|
};
|
||||||
|
|
||||||
|
MempoolTX.fromRaw = function fromRaw(data, saveCoins) {
|
||||||
|
var p = new BufferReader(data);
|
||||||
|
return new MempoolTX({
|
||||||
|
tx: bcoin.protocol.parser.parseTX(p),
|
||||||
|
height: p.readU32(),
|
||||||
|
priority: p.readVarint(true),
|
||||||
|
chainValue: p.readVarint(true),
|
||||||
|
ts: p.readU32(),
|
||||||
|
count: p.readU32(),
|
||||||
|
size: p.readU32(),
|
||||||
|
fees: p.readVarint(true)
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
MempoolTX.prototype.getPriority = function getPriority(height) {
|
||||||
|
var heightDelta = Math.max(0, height - this.height);
|
||||||
|
var modSize = this.tx.getModifiedSize();
|
||||||
|
var deltaPriority = new bn(heightDelta).mul(this.chainValue).divn(modSize);
|
||||||
|
var result = this.priority.add(deltaPriority);
|
||||||
|
if (result.cmpn(0) < 0)
|
||||||
|
result = new bn(0);
|
||||||
|
return result;
|
||||||
|
};
|
||||||
|
|
||||||
|
MempoolTX.prototype.isFree = function isFree(height) {
|
||||||
|
var priority = this.getPriority(height);
|
||||||
|
return priority.cmp(constants.tx.FREE_THRESHOLD) > 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Helpers
|
||||||
|
*/
|
||||||
|
|
||||||
|
function mem(tx) {
|
||||||
|
return 0
|
||||||
|
+ (tx.getSize() + 4 + 32 + 4 + 4 + 4) // extended
|
||||||
|
+ (2 + 64) // t
|
||||||
|
+ (2 + 10 + 1 + 64) // m
|
||||||
|
+ (tx.inputs.length * (2 + 64 + 1 + 2 + 32)) // s
|
||||||
|
+ (tx.outputs.length * (2 + 64 + 1 + 2 + 80)); // c
|
||||||
|
}
|
||||||
|
|
||||||
|
function memOrphan(tx) {
|
||||||
|
return 0
|
||||||
|
+ (2 + 64 + 32) // o
|
||||||
|
+ (2 + 64) // O
|
||||||
|
+ (tx.getSize() + 4 + 32 + 4 + 4 + 4
|
||||||
|
+ (tx.inputs.length >>> 1) * 80); // extended
|
||||||
|
}
|
||||||
|
|
||||||
return Mempool;
|
return Mempool;
|
||||||
};
|
};
|
||||||
|
|||||||
@ -1148,6 +1148,10 @@ MTX.prototype.selectCoins = function selectCoins(coins, options) {
|
|||||||
size = tx.maxSize(options, true);
|
size = tx.maxSize(options, true);
|
||||||
|
|
||||||
if (tryFree) {
|
if (tryFree) {
|
||||||
|
// Note that this will only work
|
||||||
|
// if the mempool's rolling reject
|
||||||
|
// fee is zero (i.e. the mempool is
|
||||||
|
// not full).
|
||||||
if (tx.isFree(network.height + 1, size)) {
|
if (tx.isFree(network.height + 1, size)) {
|
||||||
fee = new bn(0);
|
fee = new bn(0);
|
||||||
break;
|
break;
|
||||||
@ -1156,9 +1160,9 @@ MTX.prototype.selectCoins = function selectCoins(coins, options) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (options.accurate)
|
if (options.accurate)
|
||||||
fee = tx.getMinFee(size);
|
fee = tx.getMinFee(size, options.rate);
|
||||||
else
|
else
|
||||||
fee = tx.getMaxFee(size);
|
fee = tx.getMaxFee(size, options.rate);
|
||||||
|
|
||||||
// Failed to get enough funds, add more coins.
|
// Failed to get enough funds, add more coins.
|
||||||
if (!isFull())
|
if (!isFull())
|
||||||
@ -1182,7 +1186,7 @@ MTX.prototype.selectCoins = function selectCoins(coins, options) {
|
|||||||
if (typeof options.subtractFee === 'number') {
|
if (typeof options.subtractFee === 'number') {
|
||||||
i = options.subtractFee;
|
i = options.subtractFee;
|
||||||
|
|
||||||
if (i > tx.outputs.length - 1)
|
if (!tx.outputs[i])
|
||||||
throw new Error('Subtraction index does not exist.');
|
throw new Error('Subtraction index does not exist.');
|
||||||
|
|
||||||
if (tx.outputs[i].value.cmp(minValue) < 0)
|
if (tx.outputs[i].value.cmp(minValue) < 0)
|
||||||
|
|||||||
@ -453,7 +453,7 @@ exports.mempool = {
|
|||||||
* Maximum mempool size in bytes.
|
* Maximum mempool size in bytes.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
MAX_MEMPOOL_SIZE: 300 << 20,
|
MAX_MEMPOOL_SIZE: 300 * 1000000,
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The time at which transactions
|
* The time at which transactions
|
||||||
@ -466,7 +466,13 @@ exports.mempool = {
|
|||||||
* Maximum number of orphan transactions.
|
* Maximum number of orphan transactions.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
MAX_ORPHAN_TX: 100
|
MAX_ORPHAN_TX: 100,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Decay of minimum fee rate.
|
||||||
|
*/
|
||||||
|
|
||||||
|
FEE_HALFLIFE: 60 * 60 * 12
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@ -1357,7 +1357,7 @@ TX.prototype.getModifiedSize = function getModifiedSize(size) {
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
TX.prototype.getPriority = function getPriority(height, size) {
|
TX.prototype.getPriority = function getPriority(height, size) {
|
||||||
var sum, i, input, age;
|
var sum, i, input, age, value;
|
||||||
|
|
||||||
if (this.isCoinbase())
|
if (this.isCoinbase())
|
||||||
return new bn(0);
|
return new bn(0);
|
||||||
@ -1369,9 +1369,10 @@ TX.prototype.getPriority = function getPriority(height, size) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (size == null)
|
if (size == null)
|
||||||
size = this.getModifiedSize();
|
size = this.maxSize();
|
||||||
|
|
||||||
sum = new bn(0);
|
sum = new bn(0);
|
||||||
|
value = new bn(0);
|
||||||
|
|
||||||
for (i = 0; i < this.inputs.length; i++) {
|
for (i = 0; i < this.inputs.length; i++) {
|
||||||
input = this.inputs[i];
|
input = this.inputs[i];
|
||||||
@ -1384,11 +1385,15 @@ TX.prototype.getPriority = function getPriority(height, size) {
|
|||||||
|
|
||||||
if (input.coin.height <= height) {
|
if (input.coin.height <= height) {
|
||||||
age = height - input.coin.height;
|
age = height - input.coin.height;
|
||||||
|
value.iadd(input.coin.value);
|
||||||
sum.iadd(input.coin.value.muln(age));
|
sum.iadd(input.coin.value.muln(age));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return sum.divn(size);
|
return {
|
||||||
|
value: value,
|
||||||
|
priority: sum.divn(size)
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -1412,7 +1417,7 @@ TX.prototype.isFree = function isFree(height, size) {
|
|||||||
height = network.height + 1;
|
height = network.height + 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
priority = this.getPriority(height, size);
|
priority = this.getPriority(height, size).priority;
|
||||||
|
|
||||||
return priority.cmp(constants.tx.FREE_THRESHOLD) > 0;
|
return priority.cmp(constants.tx.FREE_THRESHOLD) > 0;
|
||||||
};
|
};
|
||||||
@ -1422,19 +1427,23 @@ TX.prototype.isFree = function isFree(height, size) {
|
|||||||
* to be relayable (not the constant min relay fee).
|
* to be relayable (not the constant min relay fee).
|
||||||
* @param {Number?} size - If not present, max size
|
* @param {Number?} size - If not present, max size
|
||||||
* estimation will be calculated and used.
|
* estimation will be calculated and used.
|
||||||
|
* @param {Number?} rate - Rate of satoshi per kB.
|
||||||
* @returns {BN} fee
|
* @returns {BN} fee
|
||||||
*/
|
*/
|
||||||
|
|
||||||
TX.prototype.getMinFee = function getMinFee(size) {
|
TX.prototype.getMinFee = function getMinFee(size, rate) {
|
||||||
var fee;
|
var fee;
|
||||||
|
|
||||||
if (size == null)
|
if (size == null)
|
||||||
size = this.maxSize();
|
size = this.maxSize();
|
||||||
|
|
||||||
fee = new bn(constants.tx.MIN_FEE).muln(size).divn(1000);
|
if (rate == null)
|
||||||
|
rate = constants.tx.MIN_FEE;
|
||||||
|
|
||||||
if (fee.cmpn(0) === 0 && constants.tx.MIN_FEE > 0)
|
fee = new bn(rate).muln(size).divn(1000);
|
||||||
fee = new bn(constants.tx.MIN_FEE);
|
|
||||||
|
if (fee.cmpn(0) === 0 && rate > 0)
|
||||||
|
fee = new bn(rate);
|
||||||
|
|
||||||
return fee;
|
return fee;
|
||||||
};
|
};
|
||||||
@ -1445,19 +1454,23 @@ TX.prototype.getMinFee = function getMinFee(size) {
|
|||||||
* when taking into account size.
|
* when taking into account size.
|
||||||
* @param {Number?} size - If not present, max size
|
* @param {Number?} size - If not present, max size
|
||||||
* estimation will be calculated and used.
|
* estimation will be calculated and used.
|
||||||
|
* @param {Number?} rate - Rate of satoshi per kB.
|
||||||
* @returns {BN} fee
|
* @returns {BN} fee
|
||||||
*/
|
*/
|
||||||
|
|
||||||
TX.prototype.getMaxFee = function getMaxFee(size) {
|
TX.prototype.getMaxFee = function getMaxFee(size, rate) {
|
||||||
var fee;
|
var fee;
|
||||||
|
|
||||||
if (size == null)
|
if (size == null)
|
||||||
size = this.maxSize();
|
size = this.maxSize();
|
||||||
|
|
||||||
fee = new bn(constants.tx.MIN_FEE).muln(Math.ceil(size / 1000));
|
if (rate == null)
|
||||||
|
rate = constants.tx.MIN_FEE;
|
||||||
|
|
||||||
if (fee.cmpn(0) === 0 && constants.tx.MIN_FEE > 0)
|
fee = new bn(rate).muln(Math.ceil(size / 1000));
|
||||||
fee = new bn(constants.tx.MIN_FEE);
|
|
||||||
|
if (fee.cmpn(0) === 0 && rate > 0)
|
||||||
|
fee = new bn(rate);
|
||||||
|
|
||||||
return fee;
|
return fee;
|
||||||
};
|
};
|
||||||
@ -1617,13 +1630,13 @@ TX.prototype.inspect = function inspect() {
|
|||||||
hash: utils.revHex(this.hash('hex')),
|
hash: utils.revHex(this.hash('hex')),
|
||||||
witnessHash: utils.revHex(this.witnessHash('hex')),
|
witnessHash: utils.revHex(this.witnessHash('hex')),
|
||||||
size: this.getSize(),
|
size: this.getSize(),
|
||||||
virtualSize: this.getVirtualSize(),
|
virtualSize: this.maxSize(),
|
||||||
height: this.height,
|
height: this.height,
|
||||||
value: utils.btc(this.getOutputValue()),
|
value: utils.btc(this.getOutputValue()),
|
||||||
fee: utils.btc(this.getFee()),
|
fee: utils.btc(this.getFee()),
|
||||||
minFee: utils.btc(this.getMinFee()),
|
minFee: utils.btc(this.getMinFee()),
|
||||||
confirmations: this.getConfirmations(),
|
confirmations: this.getConfirmations(),
|
||||||
priority: this.getPriority().toString(10),
|
priority: this.getPriority().priority.toString(10),
|
||||||
date: utils.date(this.ts || this.ps),
|
date: utils.date(this.ts || this.ps),
|
||||||
block: this.block ? utils.revHex(this.block) : null,
|
block: this.block ? utils.revHex(this.block) : null,
|
||||||
ts: this.ts,
|
ts: this.ts,
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user