fee work.

This commit is contained in:
Christopher Jeffrey 2016-05-13 17:13:23 -07:00
parent 6a45d693ee
commit 6d55077818
No known key found for this signature in database
GPG Key ID: 8962AB9DE6666BBD
8 changed files with 166 additions and 40 deletions

View File

@ -95,8 +95,8 @@ function Mempool(options) {
this.minFeeRate = 0; this.minFeeRate = 0;
this.blockSinceBump = false; this.blockSinceBump = false;
this.lastFeeUpdate = utils.now(); this.lastFeeUpdate = utils.now();
this.minReasonableFee = constants.tx.MIN_FEE; this.minReasonableFee = constants.tx.MIN_RELAY;
this.minRelayFee = constants.tx.MIN_FEE; this.minRelayFee = constants.tx.MIN_RELAY;
this._init(); this._init();
} }
@ -911,7 +911,7 @@ Mempool.prototype.verify = function verify(entry, callback) {
var mandatory = constants.flags.MANDATORY_VERIFY_FLAGS; var mandatory = constants.flags.MANDATORY_VERIFY_FLAGS;
var tx = entry.tx; var tx = entry.tx;
var ret = {}; var ret = {};
var fee, modFee, now, size, rejectFee, minRelayFee; var fee, modFee, now, size, rejectFee, minRelayFee, minRate;
if (this.chain.segwitActive) if (this.chain.segwitActive)
mandatory |= constants.flags.VERIFY_WITNESS; mandatory |= constants.flags.VERIFY_WITNESS;
@ -944,7 +944,12 @@ Mempool.prototype.verify = function verify(entry, callback) {
fee = tx.getFee(); fee = tx.getFee();
modFee = entry.fees; modFee = entry.fees;
size = entry.size; size = entry.size;
rejectFee = tx.getMinFee(size, self.getMinRate()); minRate = self.getMinRate();
if (minRate > self.minRelayFee)
self.network.updateMinRelay(minRate);
rejectFee = tx.getMinFee(size, minRate);
minRelayFee = tx.getMinFee(size, self.minRelayFee); minRelayFee = tx.getMinFee(size, self.minRelayFee);
if (rejectFee.cmpn(0) > 0 && modFee.cmp(rejectFee) < 0) { if (rejectFee.cmpn(0) > 0 && modFee.cmp(rejectFee) < 0) {

View File

@ -1055,7 +1055,7 @@ MTX.prototype.selectCoins = function selectCoins(coins, options) {
var index = 0; var index = 0;
var tx = this.clone(); var tx = this.clone();
var outputValue = tx.getOutputValue(); var outputValue = tx.getOutputValue();
var tryFree, i, size, change, fee, minValue; var tryFree, i, size, change, fee, min, output;
if (!options) if (!options)
options = {}; options = {};
@ -1145,7 +1145,7 @@ MTX.prototype.selectCoins = function selectCoins(coins, options) {
// Calculate max possible size after signing. // Calculate max possible size after signing.
size = tx.maxSize(options, true); size = tx.maxSize(options, true);
if (tryFree && options.height != null) { if (tryFree && options.height > 0) {
// Note that this will only work // Note that this will only work
// if the mempool's rolling reject // if the mempool's rolling reject
// fee is zero (i.e. the mempool is // fee is zero (i.e. the mempool is
@ -1175,26 +1175,33 @@ MTX.prototype.selectCoins = function selectCoins(coins, options) {
throw err; throw err;
} }
if (fee.cmp(constants.tx.MAX_FEE) > 0)
fee = constants.tx.MAX_FEE.clone();
// How much money is left after filling outputs. // How much money is left after filling outputs.
change = tx.getInputValue().sub(total()); change = tx.getInputValue().sub(total());
// Attempt to subtract fee. // Attempt to subtract fee.
if (options.subtractFee != null) { if (options.subtractFee != null) {
minValue = fee.addn(constants.tx.DUST_THRESHOLD);
if (typeof options.subtractFee === 'number') { if (typeof options.subtractFee === 'number') {
i = options.subtractFee; i = options.subtractFee;
output = tx.outputs[i];
if (!tx.outputs[i]) if (!output)
throw new Error('Subtraction index does not exist.'); throw new Error('Subtraction index does not exist.');
if (tx.outputs[i].value.cmp(minValue) < 0) min = fee.add(output.getDustThreshold());
if (output.value.cmp(min) < 0)
throw new Error('Could not subtract fee.'); throw new Error('Could not subtract fee.');
tx.outputs[i].value.isub(fee); output.value.isub(fee);
} else { } else {
for (i = 0; i < tx.outputs.length; i++) { for (i = 0; i < tx.outputs.length; i++) {
if (tx.outputs[i].value.cmp(minValue) >= 0) { output = tx.outputs[i];
tx.outputs[i].value.isub(fee); min = fee.add(output.getDustThreshold());
if (output.value.cmp(min) >= 0) {
output.value.isub(fee);
break; break;
} }
} }
@ -1220,7 +1227,7 @@ MTX.prototype.selectCoins = function selectCoins(coins, options) {
*/ */
MTX.prototype.fill = function fill(coins, options) { MTX.prototype.fill = function fill(coins, options) {
var result, i; var result, i, change;
assert(this.ts === 0, 'Cannot modify a confirmed tx.'); assert(this.ts === 0, 'Cannot modify a confirmed tx.');
assert(this.inputs.length === 0, 'TX is already filled.'); assert(this.inputs.length === 0, 'TX is already filled.');
@ -1234,16 +1241,20 @@ MTX.prototype.fill = function fill(coins, options) {
for (i = 0; i < result.coins.length; i++) for (i = 0; i < result.coins.length; i++)
this.addInput(result.coins[i]); this.addInput(result.coins[i]);
if (result.change.cmpn(constants.tx.DUST_THRESHOLD) < 0) { // Add a change output.
this.addOutput({
address: options.changeAddress,
value: result.change
});
change = this.outputs[this.outputs.length - 1];
if (change.isDust(constants.tx.MIN_RELAY)) {
// Do nothing. Change is added to fee. // Do nothing. Change is added to fee.
assert(this.getFee().cmp(result.fee.add(result.change)) === 0); this.outputs.pop();
this.changeIndex = -1; this.changeIndex = -1;
assert(this.getFee().cmp(result.fee.add(result.change)) === 0);
} else { } else {
// Add a change output.
this.addOutput({
address: options.changeAddress,
value: result.change
});
this.changeIndex = this.outputs.length - 1; this.changeIndex = this.outputs.length - 1;
assert(this.getFee().cmp(result.fee) === 0); assert(this.getFee().cmp(result.fee) === 0);
} }

View File

@ -30,10 +30,16 @@ var network = require('./protocol/network');
function Network(options) { function Network(options) {
var i, keys, key, value; var i, keys, key, value;
if (!(this instanceof Network))
return new Network(options);
if (typeof options === 'string') if (typeof options === 'string')
options = network[options]; options = network[options];
assert(options, 'Network requires a type or options.'); assert(options, 'Unknown network.');
if (Network[options.type])
return Network[options.type];
keys = Object.keys(options); keys = Object.keys(options);
@ -68,7 +74,25 @@ Network.prototype.updateHeight = function updateHeight(height) {
*/ */
Network.prototype.updateRate = function updateRate(rate) { Network.prototype.updateRate = function updateRate(rate) {
this.rate = rate; this.feeRate = rate;
};
Network.prototype.updateMinRelay = function updateMinRelay(rate) {
this.minRelay = rate;
};
Network.prototype.getMinRelay = function getMinRelay() {
if (this.height === -1)
return this.minRate;
return Math.min(this.minRelay, this.maxRate);
};
Network.prototype.getRate = function getRate() {
if (this.height === -1)
return this.maxRate;
return Math.min(this.feeRate, this.maxRate);
}; };
/** /**
@ -78,14 +102,9 @@ Network.prototype.updateRate = function updateRate(rate) {
*/ */
Network.set = function set(type) { Network.set = function set(type) {
assert(type, 'Bad network.'); assert(typeof type === 'string', 'Bad network.');
if (!Network[type])
Network[type] = new Network(type);
Network.primary = type; Network.primary = type;
return Network(network[type]);
return Network[type];
}; };
Network.get = function get(options) { Network.get = function get(options) {
@ -97,16 +116,14 @@ Network.get = function get(options) {
if (options instanceof Network) if (options instanceof Network)
return options; return options;
if (typeof options === 'string') { if (typeof options === 'string')
assert(Network[options], 'Network not created.'); return Network(network[options]);
return Network[options];
}
assert(false, 'Unknown network.'); assert(false, 'Unknown network.');
}; };
Network.prototype.inspect = function inspect() { Network.prototype.inspect = function inspect() {
return this.type; return '<Network: ' + this.type + '>';
}; };
module.exports = Network; module.exports = Network;

View File

@ -9,6 +9,7 @@ var bcoin = require('./env');
var bn = require('bn.js'); var bn = require('bn.js');
var utils = require('./utils'); var utils = require('./utils');
var assert = utils.assert; var assert = utils.assert;
var BufferWriter = require('./writer');
/** /**
* Represents a transaction output. * Represents a transaction output.
@ -151,6 +152,26 @@ Output.prototype.toJSON = function toJSON() {
}; };
}; };
Output.prototype.getDustThreshold = function getDustThreshold(rate) {
var framer = bcoin.protocol.framer;
var size;
if (rate == null)
rate = constants.tx.MIN_RELAY;
if (this.script.isUnspendable())
return new bn(0);
size = framer.output(this, new BufferWriter()).written;
size += 148;
return bcoin.tx.getMinFee(size, rate).muln(3);
};
Output.prototype.isDust = function isDust(rate) {
return this.value.cmp(this.getDustThreshold(rate)) < 0;
};
/** /**
* Handle a deserialized JSON output object. * Handle a deserialized JSON output object.
* @returns {NakedOutput} A "naked" output (a * @returns {NakedOutput} A "naked" output (a

View File

@ -410,6 +410,8 @@ exports.tx = {
MAX_SIZE: 100000, MAX_SIZE: 100000,
MAX_COST: 400000, MAX_COST: 400000,
MIN_FEE: 10000, MIN_FEE: 10000,
MAX_FEE: exports.COIN.divn(10),
MIN_RELAY: 10000,
BARE_MULTISIG: true, BARE_MULTISIG: true,
FREE_THRESHOLD: exports.COIN.muln(144).divn(250), FREE_THRESHOLD: exports.COIN.muln(144).divn(250),
MAX_SIGOPS: exports.block.MAX_SIGOPS / 5, MAX_SIGOPS: exports.block.MAX_SIGOPS / 5,
@ -417,7 +419,7 @@ exports.tx = {
COINBASE_MATURITY: 100 COINBASE_MATURITY: 100
}; };
exports.tx.DUST_THRESHOLD = 182 * exports.tx.MIN_FEE / 1000 * 3; exports.tx.DUST_THRESHOLD = 182 * exports.tx.MIN_RELAY / 1000 * 3;
/** /**
* Script-related constants. * Script-related constants.

View File

@ -414,6 +414,38 @@ main.requireStandard = true;
main.rpcPort = 8332; main.rpcPort = 8332;
/**
* Default min relay rate (the rate for mempoolRejectFee).
* @const {Number}
* @default
*/
main.minRelay = 10000;
/**
* Default normal relay rate.
* @const {Number}
* @default
*/
main.feeRate = 40000;
/**
* Default min rate.
* @const {Number}
* @default
*/
main.minRate = 10000;
/**
* Default max rate.
* @const {Number}
* @default
*/
main.maxRate = 40000;
/* /*
* Testnet (v3) * Testnet (v3)
* https://en.bitcoin.it/wiki/Testnet * https://en.bitcoin.it/wiki/Testnet
@ -552,6 +584,14 @@ testnet.requireStandard = false;
testnet.rpcPort = 18332; testnet.rpcPort = 18332;
testnet.minRelay = 10000;
testnet.feeRate = 20000;
testnet.minRate = 10000;
testnet.maxRate = 40000;
/* /*
* Regtest * Regtest
*/ */
@ -683,6 +723,14 @@ regtest.requireStandard = false;
regtest.rpcPort = 18332; regtest.rpcPort = 18332;
regtest.minRelay = 10000;
regtest.feeRate = 20000;
regtest.minRate = 10000;
regtest.maxRate = 40000;
/* /*
* segnet3 * segnet3
*/ */
@ -797,6 +845,14 @@ segnet3.requireStandard = false;
segnet3.rpcPort = 28332; segnet3.rpcPort = 28332;
segnet3.minRelay = 10000;
segnet3.feeRate = 20000;
segnet3.minRate = 10000;
segnet3.maxRate = 40000;
/* /*
* segnet4 * segnet4
*/ */
@ -924,3 +980,11 @@ segnet4.address.versionsByVal = utils.revMap(segnet4.address.versions);
segnet4.requireStandard = false; segnet4.requireStandard = false;
segnet4.rpcPort = 28902; segnet4.rpcPort = 28902;
segnet4.minRelay = 10000;
segnet4.feeRate = 20000;
segnet4.minRate = 10000;
segnet4.maxRate = 40000;

View File

@ -1203,7 +1203,7 @@ TX.prototype.isStandard = function isStandard(flags, ret) {
return false; return false;
} }
if (output.value.cmpn(constants.tx.DUST_THRESHOLD) < 0) { if (output.isDust(constants.tx.MIN_RELAY)) {
ret.reason = 'dust'; ret.reason = 'dust';
return false; return false;
} }
@ -1464,13 +1464,17 @@ TX.prototype.isFree = function isFree(height, size) {
*/ */
TX.prototype.getMinFee = function getMinFee(size, rate) { TX.prototype.getMinFee = function getMinFee(size, rate) {
var fee;
if (size == null) if (size == null)
size = this.maxSize(); size = this.maxSize();
return TX.getMinFee(size, rate);
};
TX.getMinFee = function getMinFee(size, rate) {
var fee;
if (rate == null) if (rate == null)
rate = constants.tx.MIN_FEE; rate = constants.tx.MIN_RELAY;
fee = new bn(rate).muln(size).divn(1000); fee = new bn(rate).muln(size).divn(1000);
@ -1497,7 +1501,7 @@ TX.prototype.getMaxFee = function getMaxFee(size, rate) {
size = this.maxSize(); size = this.maxSize();
if (rate == null) if (rate == null)
rate = constants.tx.MIN_FEE; rate = constants.tx.MIN_RELAY;
fee = new bn(rate).muln(Math.ceil(size / 1000)); fee = new bn(rate).muln(Math.ceil(size / 1000));

View File

@ -713,6 +713,8 @@ Wallet.prototype.fill = function fill(tx, options, callback) {
fee: options.fee, fee: options.fee,
subtractFee: options.subtractFee, subtractFee: options.subtractFee,
changeAddress: self.changeAddress.getAddress(), changeAddress: self.changeAddress.getAddress(),
height: self.network.height,
rate: self.network.getMinRelay(),
wallet: self, wallet: self,
m: self.m, m: self.m,
n: self.n n: self.n