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.');
|
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)
|
if (params.fee)
|
||||||
options.fee = utils.satoshi(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) {
|
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.');
|
assert(this.inputs.length === 0, 'TX is already filled.');
|
||||||
|
|
||||||
// Select necessary coins.
|
// Select necessary coins.
|
||||||
result = this.selectCoins(coins, options);
|
select = this.selectCoins(coins, options);
|
||||||
|
|
||||||
// We need a change address.
|
// We need a change address.
|
||||||
changeAddress = result.changeAddress;
|
changeAddress = select.changeAddress;
|
||||||
|
|
||||||
// If change address is not available,
|
// If change address is not available,
|
||||||
// send back to one of the coins' addresses.
|
// send back to one of the coins' addresses.
|
||||||
for (i = 0; i < result.chosen.length && !changeAddress; i++)
|
for (i = 0; i < select.chosen.length && !changeAddress; i++)
|
||||||
changeAddress = result.chosen[i].getAddress();
|
changeAddress = select.chosen[i].getAddress();
|
||||||
|
|
||||||
// Will only happen in rare cases where
|
// Will only happen in rare cases where
|
||||||
// we're redeeming all non-standard coins.
|
// 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.');
|
throw new Error('No change address available.');
|
||||||
|
|
||||||
// Add coins to transaction.
|
// Add coins to transaction.
|
||||||
for (i = 0; i < result.chosen.length; i++)
|
for (i = 0; i < select.chosen.length; i++)
|
||||||
this.addInput(result.chosen[i]);
|
this.addInput(select.chosen[i]);
|
||||||
|
|
||||||
// Attempt to subtract fee.
|
// Attempt to subtract fee.
|
||||||
if (result.shouldSubtract)
|
if (select.shouldSubtract)
|
||||||
this.subtractFee(result.fee, result.subtractFee);
|
this.subtractFee(select.fee, select.subtractFee);
|
||||||
|
|
||||||
// Add a change output.
|
// Add a change output.
|
||||||
this.addOutput({
|
this.addOutput({
|
||||||
address: changeAddress,
|
address: changeAddress,
|
||||||
value: result.change
|
value: select.change
|
||||||
});
|
});
|
||||||
|
|
||||||
change = this.outputs[this.outputs.length - 1];
|
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.
|
// Do nothing. Change is added to fee.
|
||||||
this.outputs.pop();
|
this.outputs.pop();
|
||||||
this.changeIndex = -1;
|
this.changeIndex = -1;
|
||||||
assert.equal(this.getFee(), result.fee + result.change);
|
assert.equal(this.getFee(), select.fee + select.change);
|
||||||
} else {
|
} else {
|
||||||
this.changeIndex = this.outputs.length - 1;
|
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))
|
if (!(this instanceof CoinSelector))
|
||||||
return new CoinSelector(tx, options);
|
return new CoinSelector(tx, options);
|
||||||
|
|
||||||
if (!options)
|
|
||||||
options = {};
|
|
||||||
|
|
||||||
this.tx = tx.clone();
|
this.tx = tx.clone();
|
||||||
this.coins = [];
|
this.coins = [];
|
||||||
this.outputValue = -1;
|
this.outputValue = 0;
|
||||||
this.index = -1;
|
this.index = 0;
|
||||||
this.chosen = [];
|
this.chosen = [];
|
||||||
this.change = -1;
|
this.change = 0;
|
||||||
this.fee = -1;
|
this.fee = 0;
|
||||||
|
|
||||||
this.type = options.selection || 'age';
|
this.selection = 'age';
|
||||||
this.shouldSubtract = !!options.subtractFee || options.subtractFee === 0;
|
this.shouldSubtract = false;
|
||||||
this.free = options.free || false;
|
this.subtractFee = null;
|
||||||
this.subtractFee = options.subtractFee || null;
|
this.free = false;
|
||||||
this.height = options.height || -1;
|
this.height = -1;
|
||||||
this.confirmations = options.confirmations || -1;
|
this.confirmations = -1;
|
||||||
this.hardFee = options.hardFee || null;
|
this.hardFee = -1;
|
||||||
this.changeAddress = options.changeAddress || null;
|
this.rate = constants.tx.MIN_RELAY;
|
||||||
this.round = options.round || false;
|
this.maxFee = -1;
|
||||||
this.rate = options.rate || null;
|
this.round = false;
|
||||||
this.maxFee = options.maxFee || null;
|
this.changeAddress = null;
|
||||||
|
|
||||||
// Needed for size estimation.
|
// Needed for size estimation.
|
||||||
this.m = options.m || null;
|
this.m = null;
|
||||||
this.n = options.n || null;
|
this.n = null;
|
||||||
this.witness = options.witness || false;
|
this.witness = false;
|
||||||
this.script = options.script || null;
|
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.
|
* Initialize the selector with coins to select from.
|
||||||
* @param {Coin[]} coins
|
* @param {Coin[]} coins
|
||||||
@ -1484,7 +1578,7 @@ CoinSelector.prototype.init = function init(coins) {
|
|||||||
this.fee = 0;
|
this.fee = 0;
|
||||||
this.tx.inputs.length = 0;
|
this.tx.inputs.length = 0;
|
||||||
|
|
||||||
switch (this.type) {
|
switch (this.selection) {
|
||||||
case 'all':
|
case 'all':
|
||||||
case 'random':
|
case 'random':
|
||||||
this.coins.sort(sortRandom);
|
this.coins.sort(sortRandom);
|
||||||
@ -1493,7 +1587,7 @@ CoinSelector.prototype.init = function init(coins) {
|
|||||||
this.coins.sort(sortAge);
|
this.coins.sort(sortAge);
|
||||||
break;
|
break;
|
||||||
default:
|
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) {
|
CoinSelector.prototype.isSpendable = function isSpendable(coin) {
|
||||||
var height = this.height;
|
|
||||||
var maturity = constants.tx.COINBASE_MATURITY;
|
var maturity = constants.tx.COINBASE_MATURITY;
|
||||||
var conf;
|
var conf;
|
||||||
|
|
||||||
if (!(height >= 0))
|
if (this.height === -1)
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
if (this.confirmations > 0) {
|
if (this.confirmations > 0) {
|
||||||
if (coin.height === -1)
|
if (coin.height === -1)
|
||||||
return this.confirmations <= 0;
|
return this.confirmations <= 0;
|
||||||
|
|
||||||
conf = height - coin.height;
|
conf = this.height - coin.height;
|
||||||
|
|
||||||
if (conf < 0)
|
if (conf < 0)
|
||||||
return false;
|
return false;
|
||||||
@ -1549,7 +1642,7 @@ CoinSelector.prototype.isSpendable = function isSpendable(coin) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (coin.coinbase) {
|
if (coin.coinbase) {
|
||||||
if (height + 1 < coin.height + maturity)
|
if (this.height + 1 < coin.height + maturity)
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1596,7 +1689,7 @@ CoinSelector.prototype.fund = function fund() {
|
|||||||
this.tx.addInput(coin);
|
this.tx.addInput(coin);
|
||||||
this.chosen.push(coin);
|
this.chosen.push(coin);
|
||||||
|
|
||||||
if (this.type === 'all')
|
if (this.selection === 'all')
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
// Stop once we're full.
|
// Stop once we're full.
|
||||||
@ -1614,7 +1707,7 @@ CoinSelector.prototype.fund = function fund() {
|
|||||||
CoinSelector.prototype.select = function select(coins) {
|
CoinSelector.prototype.select = function select(coins) {
|
||||||
this.init(coins);
|
this.init(coins);
|
||||||
|
|
||||||
if (this.hardFee != null)
|
if (this.hardFee !== -1)
|
||||||
this.selectHard(this.hardFee);
|
this.selectHard(this.hardFee);
|
||||||
else
|
else
|
||||||
this.selectEstimate(constants.tx.MIN_FEE);
|
this.selectEstimate(constants.tx.MIN_FEE);
|
||||||
@ -1658,7 +1751,7 @@ CoinSelector.prototype.selectEstimate = function selectEstimate(fee) {
|
|||||||
value: 0
|
value: 0
|
||||||
});
|
});
|
||||||
|
|
||||||
if (this.free && this.height >= 0) {
|
if (this.free && this.height !== -1) {
|
||||||
size = this.tx.maxSize(this);
|
size = this.tx.maxSize(this);
|
||||||
|
|
||||||
// Note that this will only work
|
// Note that this will only work
|
||||||
@ -1678,7 +1771,7 @@ CoinSelector.prototype.selectEstimate = function selectEstimate(fee) {
|
|||||||
|
|
||||||
this.fee = this.getFee(size);
|
this.fee = this.getFee(size);
|
||||||
|
|
||||||
if (this.maxFee && this.fee > this.maxFee) {
|
if (this.maxFee > 0 && this.fee > this.maxFee) {
|
||||||
throw new FundingError(
|
throw new FundingError(
|
||||||
'Fee is too high.',
|
'Fee is too high.',
|
||||||
this.tx.getInputValue(),
|
this.tx.getInputValue(),
|
||||||
|
|||||||
@ -965,9 +965,9 @@ Wallet.prototype.fund = function fund(tx, options, callback, force) {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
tx.fund(coins, {
|
tx.fund(coins, {
|
||||||
selection: options.selection || 'age',
|
selection: options.selection,
|
||||||
round: options.round,
|
round: options.round,
|
||||||
confirmed: options.confirmed,
|
confirmations: options.confirmations,
|
||||||
free: options.free,
|
free: options.free,
|
||||||
hardFee: options.hardFee,
|
hardFee: options.hardFee,
|
||||||
subtractFee: options.subtractFee,
|
subtractFee: options.subtractFee,
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user