mtx: cleanup coin selector.
This commit is contained in:
parent
1b87514542
commit
c43c84cde7
@ -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);
|
||||
|
||||
|
||||
@ -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(),
|
||||
|
||||
@ -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,
|
||||
|
||||
Loading…
Reference in New Issue
Block a user