diff --git a/lib/http/server.js b/lib/http/server.js index 4da98233..b0c9fd88 100644 --- a/lib/http/server.js +++ b/lib/http/server.js @@ -231,6 +231,12 @@ HTTPServer.prototype._init = function _init() { assert(utils.isUInt32(options.age), 'Age must be a number.'); } + if (params.confirmations != null) { + options.confirmations = Number(params.confirmations); + assert(utils.isNumber(options.confirmations), + 'Confirmations must be a number.'); + } + if (params.fee) options.fee = utils.satoshi(params.fee); diff --git a/lib/primitives/mtx.js b/lib/primitives/mtx.js index 7ac33f9b..5ac2d31e 100644 --- a/lib/primitives/mtx.js +++ b/lib/primitives/mtx.js @@ -1228,20 +1228,20 @@ MTX.prototype.subtractFee = function subtractFee(fee, index) { */ MTX.prototype.fund = function fund(coins, options) { - var i, result, change, changeAddress; + var i, select, change, changeAddress; assert(this.inputs.length === 0, 'TX is already filled.'); // Select necessary coins. - result = this.selectCoins(coins, options); + select = this.selectCoins(coins, options); // We need a change address. - changeAddress = result.changeAddress; + changeAddress = select.changeAddress; // If change address is not available, // send back to one of the coins' addresses. - for (i = 0; i < result.chosen.length && !changeAddress; i++) - changeAddress = result.chosen[i].getAddress(); + for (i = 0; i < select.chosen.length && !changeAddress; i++) + changeAddress = select.chosen[i].getAddress(); // Will only happen in rare cases where // we're redeeming all non-standard coins. @@ -1249,17 +1249,17 @@ MTX.prototype.fund = function fund(coins, options) { throw new Error('No change address available.'); // Add coins to transaction. - for (i = 0; i < result.chosen.length; i++) - this.addInput(result.chosen[i]); + for (i = 0; i < select.chosen.length; i++) + this.addInput(select.chosen[i]); // Attempt to subtract fee. - if (result.shouldSubtract) - this.subtractFee(result.fee, result.subtractFee); + if (select.shouldSubtract) + this.subtractFee(select.fee, select.subtractFee); // Add a change output. this.addOutput({ address: changeAddress, - value: result.change + value: select.change }); change = this.outputs[this.outputs.length - 1]; @@ -1268,13 +1268,13 @@ MTX.prototype.fund = function fund(coins, options) { // Do nothing. Change is added to fee. this.outputs.pop(); this.changeIndex = -1; - assert.equal(this.getFee(), result.fee + result.change); + assert.equal(this.getFee(), select.fee + select.change); } else { this.changeIndex = this.outputs.length - 1; - assert.equal(this.getFee(), result.fee); + assert.equal(this.getFee(), select.fee); } - return result; + return select; }; /** @@ -1440,36 +1440,130 @@ function CoinSelector(tx, options) { if (!(this instanceof CoinSelector)) return new CoinSelector(tx, options); - if (!options) - options = {}; - this.tx = tx.clone(); this.coins = []; - this.outputValue = -1; - this.index = -1; + this.outputValue = 0; + this.index = 0; this.chosen = []; - this.change = -1; - this.fee = -1; + this.change = 0; + this.fee = 0; - this.type = options.selection || 'age'; - this.shouldSubtract = !!options.subtractFee || options.subtractFee === 0; - this.free = options.free || false; - this.subtractFee = options.subtractFee || null; - this.height = options.height || -1; - this.confirmations = options.confirmations || -1; - this.hardFee = options.hardFee || null; - this.changeAddress = options.changeAddress || null; - this.round = options.round || false; - this.rate = options.rate || null; - this.maxFee = options.maxFee || null; + this.selection = 'age'; + this.shouldSubtract = false; + this.subtractFee = null; + this.free = false; + this.height = -1; + this.confirmations = -1; + this.hardFee = -1; + this.rate = constants.tx.MIN_RELAY; + this.maxFee = -1; + this.round = false; + this.changeAddress = null; // Needed for size estimation. - this.m = options.m || null; - this.n = options.n || null; - this.witness = options.witness || false; - this.script = options.script || null; + this.m = null; + this.n = null; + this.witness = false; + this.script = null; + + if (options) + this.fromOptions(options); } +/** + * Initialize selector options. + * @param {Object} options + * @private + */ + +CoinSelector.prototype.fromOptions = function fromOptions(options) { + var addr; + + if (options.selection) { + assert(typeof options.selection === 'string'); + this.selection = options.selection; + } + + if (options.subtractFee != null) { + this.subtractFee = options.subtractFee; + this.shouldSubtract = options.subtractFee !== false; + } + + if (options.free != null) { + assert(typeof options.free === 'boolean'); + this.free = options.free; + } + + if (options.height != null) { + assert(utils.isNumber(options.height)); + assert(options.height >= -1); + this.height = options.height; + } + + if (options.confirmations != null) { + assert(utils.isNumber(options.confirmations)); + assert(options.confirmations >= -1); + this.confirmations = options.confirmations; + } + + if (options.hardFee != null) { + assert(utils.isNumber(options.hardFee)); + assert(options.hardFee >= -1); + this.hardFee = options.hardFee; + } + + if (options.rate != null) { + assert(utils.isNumber(options.rate)); + assert(options.rate >= 0); + this.rate = options.rate; + } + + if (options.maxFee != null) { + assert(utils.isNumber(options.maxFee)); + assert(options.maxFee >= -1); + this.maxFee = options.maxFee; + } + + if (options.round != null) { + assert(typeof options.round === 'boolean'); + this.round = options.round; + } + + if (options.changeAddress) { + addr = options.changeAddress; + if (typeof addr === 'string') { + this.changeAddress = bcoin.address.fromBase58(addr); + } else { + assert(addr instanceof bcoin.address); + this.changeAddress = addr; + } + } + + if (options.m != null) { + assert(utils.isNumber(options.m)); + assert(options.m >= 1); + this.m = options.m; + } + + if (options.n != null) { + assert(utils.isNumber(options.n)); + assert(options.n >= 1); + this.n = options.n; + } + + if (options.witness != null) { + assert(typeof options.witness === 'boolean'); + this.witness = options.witness; + } + + if (options.script) { + assert(options.script instanceof bcoin.script); + this.script = options.script; + } + + return this; +}; + /** * Initialize the selector with coins to select from. * @param {Coin[]} coins @@ -1484,7 +1578,7 @@ CoinSelector.prototype.init = function init(coins) { this.fee = 0; this.tx.inputs.length = 0; - switch (this.type) { + switch (this.selection) { case 'all': case 'random': this.coins.sort(sortRandom); @@ -1493,7 +1587,7 @@ CoinSelector.prototype.init = function init(coins) { this.coins.sort(sortAge); break; default: - throw new FundingError('Bad selection type: ' + this.type); + throw new FundingError('Bad selection type: ' + this.selection); } }; @@ -1526,18 +1620,17 @@ CoinSelector.prototype.isFull = function isFull() { */ CoinSelector.prototype.isSpendable = function isSpendable(coin) { - var height = this.height; var maturity = constants.tx.COINBASE_MATURITY; var conf; - if (!(height >= 0)) + if (this.height === -1) return true; if (this.confirmations > 0) { if (coin.height === -1) return this.confirmations <= 0; - conf = height - coin.height; + conf = this.height - coin.height; if (conf < 0) return false; @@ -1549,7 +1642,7 @@ CoinSelector.prototype.isSpendable = function isSpendable(coin) { } if (coin.coinbase) { - if (height + 1 < coin.height + maturity) + if (this.height + 1 < coin.height + maturity) return false; } @@ -1596,7 +1689,7 @@ CoinSelector.prototype.fund = function fund() { this.tx.addInput(coin); this.chosen.push(coin); - if (this.type === 'all') + if (this.selection === 'all') continue; // Stop once we're full. @@ -1614,7 +1707,7 @@ CoinSelector.prototype.fund = function fund() { CoinSelector.prototype.select = function select(coins) { this.init(coins); - if (this.hardFee != null) + if (this.hardFee !== -1) this.selectHard(this.hardFee); else this.selectEstimate(constants.tx.MIN_FEE); @@ -1658,7 +1751,7 @@ CoinSelector.prototype.selectEstimate = function selectEstimate(fee) { value: 0 }); - if (this.free && this.height >= 0) { + if (this.free && this.height !== -1) { size = this.tx.maxSize(this); // Note that this will only work @@ -1678,7 +1771,7 @@ CoinSelector.prototype.selectEstimate = function selectEstimate(fee) { this.fee = this.getFee(size); - if (this.maxFee && this.fee > this.maxFee) { + if (this.maxFee > 0 && this.fee > this.maxFee) { throw new FundingError( 'Fee is too high.', this.tx.getInputValue(), diff --git a/lib/wallet/wallet.js b/lib/wallet/wallet.js index c6abe1e1..71a18ccc 100644 --- a/lib/wallet/wallet.js +++ b/lib/wallet/wallet.js @@ -965,9 +965,9 @@ Wallet.prototype.fund = function fund(tx, options, callback, force) { try { tx.fund(coins, { - selection: options.selection || 'age', + selection: options.selection, round: options.round, - confirmed: options.confirmed, + confirmations: options.confirmations, free: options.free, hardFee: options.hardFee, subtractFee: options.subtractFee,