From 6d55077818077c981dca99b44f516bdbe0762e54 Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Fri, 13 May 2016 17:13:23 -0700 Subject: [PATCH] fee work. --- lib/bcoin/mempool.js | 13 ++++--- lib/bcoin/mtx.js | 43 +++++++++++++--------- lib/bcoin/network.js | 45 +++++++++++++++-------- lib/bcoin/output.js | 21 +++++++++++ lib/bcoin/protocol/constants.js | 4 ++- lib/bcoin/protocol/network.js | 64 +++++++++++++++++++++++++++++++++ lib/bcoin/tx.js | 14 +++++--- lib/bcoin/wallet.js | 2 ++ 8 files changed, 166 insertions(+), 40 deletions(-) diff --git a/lib/bcoin/mempool.js b/lib/bcoin/mempool.js index 52899da5..4f0da2ab 100644 --- a/lib/bcoin/mempool.js +++ b/lib/bcoin/mempool.js @@ -95,8 +95,8 @@ function Mempool(options) { this.minFeeRate = 0; this.blockSinceBump = false; this.lastFeeUpdate = utils.now(); - this.minReasonableFee = constants.tx.MIN_FEE; - this.minRelayFee = constants.tx.MIN_FEE; + this.minReasonableFee = constants.tx.MIN_RELAY; + this.minRelayFee = constants.tx.MIN_RELAY; this._init(); } @@ -911,7 +911,7 @@ Mempool.prototype.verify = function verify(entry, callback) { var mandatory = constants.flags.MANDATORY_VERIFY_FLAGS; var tx = entry.tx; var ret = {}; - var fee, modFee, now, size, rejectFee, minRelayFee; + var fee, modFee, now, size, rejectFee, minRelayFee, minRate; if (this.chain.segwitActive) mandatory |= constants.flags.VERIFY_WITNESS; @@ -944,7 +944,12 @@ Mempool.prototype.verify = function verify(entry, callback) { fee = tx.getFee(); modFee = entry.fees; 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); if (rejectFee.cmpn(0) > 0 && modFee.cmp(rejectFee) < 0) { diff --git a/lib/bcoin/mtx.js b/lib/bcoin/mtx.js index 219c2392..f6262c04 100644 --- a/lib/bcoin/mtx.js +++ b/lib/bcoin/mtx.js @@ -1055,7 +1055,7 @@ MTX.prototype.selectCoins = function selectCoins(coins, options) { var index = 0; var tx = this.clone(); var outputValue = tx.getOutputValue(); - var tryFree, i, size, change, fee, minValue; + var tryFree, i, size, change, fee, min, output; if (!options) options = {}; @@ -1145,7 +1145,7 @@ MTX.prototype.selectCoins = function selectCoins(coins, options) { // Calculate max possible size after signing. size = tx.maxSize(options, true); - if (tryFree && options.height != null) { + if (tryFree && options.height > 0) { // Note that this will only work // if the mempool's rolling reject // fee is zero (i.e. the mempool is @@ -1175,26 +1175,33 @@ MTX.prototype.selectCoins = function selectCoins(coins, options) { throw err; } + if (fee.cmp(constants.tx.MAX_FEE) > 0) + fee = constants.tx.MAX_FEE.clone(); + // How much money is left after filling outputs. change = tx.getInputValue().sub(total()); // Attempt to subtract fee. if (options.subtractFee != null) { - minValue = fee.addn(constants.tx.DUST_THRESHOLD); if (typeof options.subtractFee === 'number') { i = options.subtractFee; + output = tx.outputs[i]; - if (!tx.outputs[i]) + if (!output) 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.'); - tx.outputs[i].value.isub(fee); + output.value.isub(fee); } else { for (i = 0; i < tx.outputs.length; i++) { - if (tx.outputs[i].value.cmp(minValue) >= 0) { - tx.outputs[i].value.isub(fee); + output = tx.outputs[i]; + min = fee.add(output.getDustThreshold()); + if (output.value.cmp(min) >= 0) { + output.value.isub(fee); break; } } @@ -1220,7 +1227,7 @@ MTX.prototype.selectCoins = function selectCoins(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.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++) 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. - assert(this.getFee().cmp(result.fee.add(result.change)) === 0); + this.outputs.pop(); this.changeIndex = -1; + assert(this.getFee().cmp(result.fee.add(result.change)) === 0); } else { - // Add a change output. - this.addOutput({ - address: options.changeAddress, - value: result.change - }); this.changeIndex = this.outputs.length - 1; assert(this.getFee().cmp(result.fee) === 0); } diff --git a/lib/bcoin/network.js b/lib/bcoin/network.js index 1e819d21..5f26a7db 100644 --- a/lib/bcoin/network.js +++ b/lib/bcoin/network.js @@ -30,10 +30,16 @@ var network = require('./protocol/network'); function Network(options) { var i, keys, key, value; + if (!(this instanceof Network)) + return new Network(options); + if (typeof options === 'string') 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); @@ -68,7 +74,25 @@ Network.prototype.updateHeight = function updateHeight(height) { */ 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) { - assert(type, 'Bad network.'); - - if (!Network[type]) - Network[type] = new Network(type); - + assert(typeof type === 'string', 'Bad network.'); Network.primary = type; - - return Network[type]; + return Network(network[type]); }; Network.get = function get(options) { @@ -97,16 +116,14 @@ Network.get = function get(options) { if (options instanceof Network) return options; - if (typeof options === 'string') { - assert(Network[options], 'Network not created.'); - return Network[options]; - } + if (typeof options === 'string') + return Network(network[options]); assert(false, 'Unknown network.'); }; Network.prototype.inspect = function inspect() { - return this.type; + return ''; }; module.exports = Network; diff --git a/lib/bcoin/output.js b/lib/bcoin/output.js index 9f068c3d..f81aa24d 100644 --- a/lib/bcoin/output.js +++ b/lib/bcoin/output.js @@ -9,6 +9,7 @@ var bcoin = require('./env'); var bn = require('bn.js'); var utils = require('./utils'); var assert = utils.assert; +var BufferWriter = require('./writer'); /** * 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. * @returns {NakedOutput} A "naked" output (a diff --git a/lib/bcoin/protocol/constants.js b/lib/bcoin/protocol/constants.js index 3f446db2..cc0029f9 100644 --- a/lib/bcoin/protocol/constants.js +++ b/lib/bcoin/protocol/constants.js @@ -410,6 +410,8 @@ exports.tx = { MAX_SIZE: 100000, MAX_COST: 400000, MIN_FEE: 10000, + MAX_FEE: exports.COIN.divn(10), + MIN_RELAY: 10000, BARE_MULTISIG: true, FREE_THRESHOLD: exports.COIN.muln(144).divn(250), MAX_SIGOPS: exports.block.MAX_SIGOPS / 5, @@ -417,7 +419,7 @@ exports.tx = { 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. diff --git a/lib/bcoin/protocol/network.js b/lib/bcoin/protocol/network.js index 8a730483..d435876d 100644 --- a/lib/bcoin/protocol/network.js +++ b/lib/bcoin/protocol/network.js @@ -414,6 +414,38 @@ main.requireStandard = true; 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) * https://en.bitcoin.it/wiki/Testnet @@ -552,6 +584,14 @@ testnet.requireStandard = false; testnet.rpcPort = 18332; +testnet.minRelay = 10000; + +testnet.feeRate = 20000; + +testnet.minRate = 10000; + +testnet.maxRate = 40000; + /* * Regtest */ @@ -683,6 +723,14 @@ regtest.requireStandard = false; regtest.rpcPort = 18332; +regtest.minRelay = 10000; + +regtest.feeRate = 20000; + +regtest.minRate = 10000; + +regtest.maxRate = 40000; + /* * segnet3 */ @@ -797,6 +845,14 @@ segnet3.requireStandard = false; segnet3.rpcPort = 28332; +segnet3.minRelay = 10000; + +segnet3.feeRate = 20000; + +segnet3.minRate = 10000; + +segnet3.maxRate = 40000; + /* * segnet4 */ @@ -924,3 +980,11 @@ segnet4.address.versionsByVal = utils.revMap(segnet4.address.versions); segnet4.requireStandard = false; segnet4.rpcPort = 28902; + +segnet4.minRelay = 10000; + +segnet4.feeRate = 20000; + +segnet4.minRate = 10000; + +segnet4.maxRate = 40000; diff --git a/lib/bcoin/tx.js b/lib/bcoin/tx.js index 254756c7..ce094d29 100644 --- a/lib/bcoin/tx.js +++ b/lib/bcoin/tx.js @@ -1203,7 +1203,7 @@ TX.prototype.isStandard = function isStandard(flags, ret) { return false; } - if (output.value.cmpn(constants.tx.DUST_THRESHOLD) < 0) { + if (output.isDust(constants.tx.MIN_RELAY)) { ret.reason = 'dust'; return false; } @@ -1464,13 +1464,17 @@ TX.prototype.isFree = function isFree(height, size) { */ TX.prototype.getMinFee = function getMinFee(size, rate) { - var fee; - if (size == null) size = this.maxSize(); + return TX.getMinFee(size, rate); +}; + +TX.getMinFee = function getMinFee(size, rate) { + var fee; + if (rate == null) - rate = constants.tx.MIN_FEE; + rate = constants.tx.MIN_RELAY; fee = new bn(rate).muln(size).divn(1000); @@ -1497,7 +1501,7 @@ TX.prototype.getMaxFee = function getMaxFee(size, rate) { size = this.maxSize(); if (rate == null) - rate = constants.tx.MIN_FEE; + rate = constants.tx.MIN_RELAY; fee = new bn(rate).muln(Math.ceil(size / 1000)); diff --git a/lib/bcoin/wallet.js b/lib/bcoin/wallet.js index 24c7e011..22c65512 100644 --- a/lib/bcoin/wallet.js +++ b/lib/bcoin/wallet.js @@ -713,6 +713,8 @@ Wallet.prototype.fill = function fill(tx, options, callback) { fee: options.fee, subtractFee: options.subtractFee, changeAddress: self.changeAddress.getAddress(), + height: self.network.height, + rate: self.network.getMinRelay(), wallet: self, m: self.m, n: self.n