mtx: cleanup coin selector.

This commit is contained in:
Christopher Jeffrey 2016-09-06 00:21:26 -07:00
parent 1b87514542
commit c43c84cde7
No known key found for this signature in database
GPG Key ID: 8962AB9DE6666BBD
3 changed files with 146 additions and 47 deletions

View File

@ -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);

View File

@ -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(),

View File

@ -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,