mtx: refactor coin selection.
This commit is contained in:
parent
1edb5aa4cf
commit
1b87514542
@ -947,7 +947,7 @@ MTX.prototype.isInputScripted = function isInputScripted(index) {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Estimate maximum possible size.
|
* Estimate maximum possible size.
|
||||||
* @param {(Wallet|Object)?} options - Wallet or options object.
|
* @param {Object?} options - Wallet or options object.
|
||||||
* @param {Number} options.m - Multisig `m` value.
|
* @param {Number} options.m - Multisig `m` value.
|
||||||
* @param {Number} options.n - Multisig `n` value.
|
* @param {Number} options.n - Multisig `n` value.
|
||||||
* @returns {Number}
|
* @returns {Number}
|
||||||
@ -1069,7 +1069,7 @@ MTX.prototype.maxSize = function maxSize(options) {
|
|||||||
// simply add more of the fee to the change
|
// simply add more of the fee to the change
|
||||||
// output.
|
// output.
|
||||||
// m value
|
// m value
|
||||||
m = options.m || 3;
|
m = options.m || 2;
|
||||||
// n value
|
// n value
|
||||||
n = options.n || 3;
|
n = options.n || 3;
|
||||||
// OP_0
|
// OP_0
|
||||||
@ -1144,7 +1144,8 @@ MTX.prototype._guessRedeem = function guessRedeem(options, hash) {
|
|||||||
* See {@link TX#getMinFee} vs. {@link TX#getRoundFee}.
|
* See {@link TX#getMinFee} vs. {@link TX#getRoundFee}.
|
||||||
* @param {Boolean} options.free - Do not apply a fee if the
|
* @param {Boolean} options.free - Do not apply a fee if the
|
||||||
* transaction priority is high enough to be considered free.
|
* transaction priority is high enough to be considered free.
|
||||||
* @param {Amount?} options.fee - Use a hard fee rather than calculating one.
|
* @param {Amount?} options.hardFee - Use a hard fee rather
|
||||||
|
* than calculating one.
|
||||||
* @param {Rate?} options.rate - Rate used for fee calculation.
|
* @param {Rate?} options.rate - Rate used for fee calculation.
|
||||||
* @param {Number|Boolean} options.subtractFee - Whether to subtract the
|
* @param {Number|Boolean} options.subtractFee - Whether to subtract the
|
||||||
* fee from * existing outputs rather than adding more inputs.
|
* fee from * existing outputs rather than adding more inputs.
|
||||||
@ -1154,155 +1155,8 @@ MTX.prototype._guessRedeem = function guessRedeem(options, hash) {
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
MTX.prototype.selectCoins = function selectCoins(coins, options) {
|
MTX.prototype.selectCoins = function selectCoins(coins, options) {
|
||||||
var chosen = [];
|
var selector = new CoinSelector(this, options);
|
||||||
var index = 0;
|
return selector.select(coins);
|
||||||
var tx = this.clone();
|
|
||||||
var outputValue = tx.getOutputValue();
|
|
||||||
var tryFree, size, change, fee;
|
|
||||||
|
|
||||||
if (!options)
|
|
||||||
options = {};
|
|
||||||
|
|
||||||
tryFree = options.free;
|
|
||||||
|
|
||||||
// Null the inputs if there are any.
|
|
||||||
tx.inputs.length = 0;
|
|
||||||
|
|
||||||
if (!options.selection || options.selection === 'age') {
|
|
||||||
// Oldest unspents first
|
|
||||||
coins = coins.slice().sort(function(a, b) {
|
|
||||||
a = a.height === -1 ? 0x7fffffff : a.height;
|
|
||||||
b = b.height === -1 ? 0x7fffffff : b.height;
|
|
||||||
return a - b;
|
|
||||||
});
|
|
||||||
} else if (options.selection === 'random' || options.selection === 'all') {
|
|
||||||
// Random unspents
|
|
||||||
coins = coins.slice().sort(function() {
|
|
||||||
return Math.random() > 0.5 ? 1 : -1;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function total() {
|
|
||||||
if (options.subtractFee || options.subtractFee === 0)
|
|
||||||
return outputValue;
|
|
||||||
return outputValue + fee;
|
|
||||||
}
|
|
||||||
|
|
||||||
function isFull() {
|
|
||||||
return tx.getInputValue() >= total();
|
|
||||||
}
|
|
||||||
|
|
||||||
function addCoins() {
|
|
||||||
var coin;
|
|
||||||
|
|
||||||
while (index < coins.length) {
|
|
||||||
coin = coins[index++];
|
|
||||||
|
|
||||||
if (options.confirmed && coin.height === -1)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
if (options.height >= 0 && coin.coinbase) {
|
|
||||||
if (options.height + 1 < coin.height + constants.tx.COINBASE_MATURITY)
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add new inputs until TX will have enough
|
|
||||||
// funds to cover both minimum post cost
|
|
||||||
// and fee.
|
|
||||||
tx.addInput(coin);
|
|
||||||
chosen.push(coin);
|
|
||||||
|
|
||||||
if (options.selection === 'all')
|
|
||||||
continue;
|
|
||||||
|
|
||||||
// Stop once we're full.
|
|
||||||
if (isFull())
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (options.fee != null) {
|
|
||||||
fee = options.fee;
|
|
||||||
|
|
||||||
if (fee > constants.tx.MAX_FEE)
|
|
||||||
fee = constants.tx.MAX_FEE;
|
|
||||||
|
|
||||||
// Transfer `total` funds maximum.
|
|
||||||
addCoins();
|
|
||||||
} else {
|
|
||||||
fee = constants.tx.MIN_FEE;
|
|
||||||
|
|
||||||
// Transfer `total` funds maximum.
|
|
||||||
addCoins();
|
|
||||||
|
|
||||||
// Add dummy output (for `change`) to
|
|
||||||
// calculate maximum TX size.
|
|
||||||
tx.addOutput({
|
|
||||||
// In case we don't have a change address,
|
|
||||||
// use a fake p2pkh output to gauge size.
|
|
||||||
script: options.changeAddress
|
|
||||||
? Script.fromAddress(options.changeAddress)
|
|
||||||
: Script.fromPubkeyhash(constants.ZERO_HASH160),
|
|
||||||
value: 0
|
|
||||||
});
|
|
||||||
|
|
||||||
// Change fee value if it is more than 1024
|
|
||||||
// bytes (10000 satoshi for every 1024 bytes).
|
|
||||||
do {
|
|
||||||
// Calculate max possible size after signing.
|
|
||||||
size = tx.maxSize(options);
|
|
||||||
|
|
||||||
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
|
|
||||||
// not full).
|
|
||||||
if (tx.isFree(options.height + 1, size)) {
|
|
||||||
fee = 0;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
tryFree = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (options.round)
|
|
||||||
fee = tx.getRoundFee(size, options.rate);
|
|
||||||
else
|
|
||||||
fee = tx.getMinFee(size, options.rate);
|
|
||||||
|
|
||||||
if (fee > constants.tx.MAX_FEE)
|
|
||||||
fee = constants.tx.MAX_FEE;
|
|
||||||
|
|
||||||
if (options.maxFee && fee > options.maxFee) {
|
|
||||||
throw new FundingError(
|
|
||||||
'Fee is too high.',
|
|
||||||
tx.getInputValue(),
|
|
||||||
total());
|
|
||||||
}
|
|
||||||
|
|
||||||
// Failed to get enough funds, add more coins.
|
|
||||||
if (!isFull())
|
|
||||||
addCoins();
|
|
||||||
} while (!isFull() && index < coins.length);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!isFull()) {
|
|
||||||
// Still failing to get enough funds.
|
|
||||||
throw new FundingError(
|
|
||||||
'Not enough funds.',
|
|
||||||
tx.getInputValue(),
|
|
||||||
total());
|
|
||||||
}
|
|
||||||
|
|
||||||
// How much money is left after filling outputs.
|
|
||||||
change = tx.getInputValue() - total();
|
|
||||||
|
|
||||||
// Return necessary inputs and change.
|
|
||||||
return {
|
|
||||||
coins: chosen,
|
|
||||||
change: change,
|
|
||||||
fee: fee,
|
|
||||||
total: total()
|
|
||||||
};
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -1370,27 +1224,24 @@ MTX.prototype.subtractFee = function subtractFee(fee, index) {
|
|||||||
* Select coins and fill the inputs.
|
* Select coins and fill the inputs.
|
||||||
* @param {Coin[]} coins
|
* @param {Coin[]} coins
|
||||||
* @param {Object} options - See {@link MTX#selectCoins} options.
|
* @param {Object} options - See {@link MTX#selectCoins} options.
|
||||||
* @returns {Object} See {@link MTX#selectCoins} return value.
|
* @returns {CoinSelector}
|
||||||
*/
|
*/
|
||||||
|
|
||||||
MTX.prototype.fund = function fund(coins, options) {
|
MTX.prototype.fund = function fund(coins, options) {
|
||||||
var result, i, change, changeAddress;
|
var i, result, change, changeAddress;
|
||||||
|
|
||||||
assert(this.inputs.length === 0, 'TX is already filled.');
|
assert(this.inputs.length === 0, 'TX is already filled.');
|
||||||
|
|
||||||
if (!options)
|
|
||||||
options = {};
|
|
||||||
|
|
||||||
// Select necessary coins.
|
// Select necessary coins.
|
||||||
result = this.selectCoins(coins, options);
|
result = this.selectCoins(coins, options);
|
||||||
|
|
||||||
// We need a change address.
|
// We need a change address.
|
||||||
changeAddress = options.changeAddress;
|
changeAddress = result.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.coins.length && !changeAddress; i++)
|
for (i = 0; i < result.chosen.length && !changeAddress; i++)
|
||||||
changeAddress = result.coins[i].getAddress();
|
changeAddress = result.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.
|
||||||
@ -1398,12 +1249,12 @@ 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.coins.length; i++)
|
for (i = 0; i < result.chosen.length; i++)
|
||||||
this.addInput(result.coins[i]);
|
this.addInput(result.chosen[i]);
|
||||||
|
|
||||||
// Attempt to subtract fee.
|
// Attempt to subtract fee.
|
||||||
if (options.subtractFee || options.subtractFee === 0)
|
if (result.shouldSubtract)
|
||||||
this.subtractFee(result.fee, options.subtractFee);
|
this.subtractFee(result.fee, result.subtractFee);
|
||||||
|
|
||||||
// Add a change output.
|
// Add a change output.
|
||||||
this.addOutput({
|
this.addOutput({
|
||||||
@ -1439,21 +1290,8 @@ MTX.prototype.sortMembers = function sortMembers() {
|
|||||||
assert(changeOutput);
|
assert(changeOutput);
|
||||||
}
|
}
|
||||||
|
|
||||||
this.inputs = this.inputs.slice().sort(function(a, b) {
|
this.inputs.sort(sortInputs);
|
||||||
var h1 = new Buffer(a.prevout.hash, 'hex');
|
this.outputs.sort(sortOutputs);
|
||||||
var h2 = new Buffer(b.prevout.hash, 'hex');
|
|
||||||
var res = utils.cmp(h1, h2);
|
|
||||||
if (res !== 0)
|
|
||||||
return res;
|
|
||||||
return a.prevout.index - b.prevout.index;
|
|
||||||
});
|
|
||||||
|
|
||||||
this.outputs = this.outputs.slice().sort(function(a, b) {
|
|
||||||
var res = a.value - b.value;
|
|
||||||
if (res !== 0)
|
|
||||||
return res;
|
|
||||||
return utils.cmp(a.script.toRaw(), b.script.toRaw());
|
|
||||||
});
|
|
||||||
|
|
||||||
if (this.changeIndex !== -1) {
|
if (this.changeIndex !== -1) {
|
||||||
this.changeIndex = this.outputs.indexOf(changeOutput);
|
this.changeIndex = this.outputs.indexOf(changeOutput);
|
||||||
@ -1591,6 +1429,306 @@ MTX.isMTX = function isMTX(obj) {
|
|||||||
&& typeof obj.scriptInput === 'function';
|
&& typeof obj.scriptInput === 'function';
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Coin Selector
|
||||||
|
* @constructor
|
||||||
|
* @param {TX} tx
|
||||||
|
* @param {Object?} options
|
||||||
|
*/
|
||||||
|
|
||||||
|
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.chosen = [];
|
||||||
|
this.change = -1;
|
||||||
|
this.fee = -1;
|
||||||
|
|
||||||
|
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;
|
||||||
|
|
||||||
|
// Needed for size estimation.
|
||||||
|
this.m = options.m || null;
|
||||||
|
this.n = options.n || null;
|
||||||
|
this.witness = options.witness || false;
|
||||||
|
this.script = options.script || null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialize the selector with coins to select from.
|
||||||
|
* @param {Coin[]} coins
|
||||||
|
*/
|
||||||
|
|
||||||
|
CoinSelector.prototype.init = function init(coins) {
|
||||||
|
this.coins = coins.slice();
|
||||||
|
this.outputValue = this.tx.getOutputValue();
|
||||||
|
this.index = 0;
|
||||||
|
this.chosen = [];
|
||||||
|
this.change = 0;
|
||||||
|
this.fee = 0;
|
||||||
|
this.tx.inputs.length = 0;
|
||||||
|
|
||||||
|
switch (this.type) {
|
||||||
|
case 'all':
|
||||||
|
case 'random':
|
||||||
|
this.coins.sort(sortRandom);
|
||||||
|
break;
|
||||||
|
case 'age':
|
||||||
|
this.coins.sort(sortAge);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw new FundingError('Bad selection type: ' + this.type);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calculate total value required.
|
||||||
|
* @returns {Amount}
|
||||||
|
*/
|
||||||
|
|
||||||
|
CoinSelector.prototype.total = function total() {
|
||||||
|
if (this.shouldSubtract)
|
||||||
|
return this.outputValue;
|
||||||
|
return this.outputValue + this.fee;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test whether the selector has
|
||||||
|
* completely funded the transaction.
|
||||||
|
* @returns {Boolean}
|
||||||
|
*/
|
||||||
|
|
||||||
|
CoinSelector.prototype.isFull = function isFull() {
|
||||||
|
return this.tx.getInputValue() >= this.total();
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test whether a coin is spendable
|
||||||
|
* with regards to the options.
|
||||||
|
* @param {Coin}
|
||||||
|
* @returns {Boolean}
|
||||||
|
*/
|
||||||
|
|
||||||
|
CoinSelector.prototype.isSpendable = function isSpendable(coin) {
|
||||||
|
var height = this.height;
|
||||||
|
var maturity = constants.tx.COINBASE_MATURITY;
|
||||||
|
var conf;
|
||||||
|
|
||||||
|
if (!(height >= 0))
|
||||||
|
return true;
|
||||||
|
|
||||||
|
if (this.confirmations > 0) {
|
||||||
|
if (coin.height === -1)
|
||||||
|
return this.confirmations <= 0;
|
||||||
|
|
||||||
|
conf = height - coin.height;
|
||||||
|
|
||||||
|
if (conf < 0)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
conf += 1;
|
||||||
|
|
||||||
|
if (conf < this.confirmations)
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (coin.coinbase) {
|
||||||
|
if (height + 1 < coin.height + maturity)
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the current fee based on a size.
|
||||||
|
* @param {Number} size
|
||||||
|
* @returns {Amount}
|
||||||
|
*/
|
||||||
|
|
||||||
|
CoinSelector.prototype.getFee = function getFee(size) {
|
||||||
|
var fee;
|
||||||
|
|
||||||
|
if (this.round)
|
||||||
|
fee = bcoin.tx.getRoundFee(size, this.rate);
|
||||||
|
else
|
||||||
|
fee = bcoin.tx.getMinFee(size, this.rate);
|
||||||
|
|
||||||
|
if (fee > constants.tx.MAX_FEE)
|
||||||
|
fee = constants.tx.MAX_FEE;
|
||||||
|
|
||||||
|
return fee;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fund the transaction with more
|
||||||
|
* coins if the `total` was updated.
|
||||||
|
*/
|
||||||
|
|
||||||
|
CoinSelector.prototype.fund = function fund() {
|
||||||
|
var coin;
|
||||||
|
|
||||||
|
while (this.index < this.coins.length) {
|
||||||
|
coin = this.coins[this.index++];
|
||||||
|
|
||||||
|
if (!this.isSpendable(coin))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
// Add new inputs until TX will have enough
|
||||||
|
// funds to cover both minimum post cost
|
||||||
|
// and fee.
|
||||||
|
this.tx.addInput(coin);
|
||||||
|
this.chosen.push(coin);
|
||||||
|
|
||||||
|
if (this.type === 'all')
|
||||||
|
continue;
|
||||||
|
|
||||||
|
// Stop once we're full.
|
||||||
|
if (this.isFull())
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initiate selection from `coins`.
|
||||||
|
* @param {Coin[]} coins
|
||||||
|
* @returns {CoinSelector}
|
||||||
|
*/
|
||||||
|
|
||||||
|
CoinSelector.prototype.select = function select(coins) {
|
||||||
|
this.init(coins);
|
||||||
|
|
||||||
|
if (this.hardFee != null)
|
||||||
|
this.selectHard(this.hardFee);
|
||||||
|
else
|
||||||
|
this.selectEstimate(constants.tx.MIN_FEE);
|
||||||
|
|
||||||
|
if (!this.isFull()) {
|
||||||
|
// Still failing to get enough funds.
|
||||||
|
throw new FundingError(
|
||||||
|
'Not enough funds.',
|
||||||
|
this.tx.getInputValue(),
|
||||||
|
this.total());
|
||||||
|
}
|
||||||
|
|
||||||
|
// How much money is left after filling outputs.
|
||||||
|
this.change = this.tx.getInputValue() - this.total();
|
||||||
|
|
||||||
|
return this;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialize selection based on size estimate.
|
||||||
|
* @param {Amount} fee
|
||||||
|
*/
|
||||||
|
|
||||||
|
CoinSelector.prototype.selectEstimate = function selectEstimate(fee) {
|
||||||
|
var size;
|
||||||
|
|
||||||
|
// Initial fee.
|
||||||
|
this.fee = fee;
|
||||||
|
|
||||||
|
// Transfer `total` funds maximum.
|
||||||
|
this.fund();
|
||||||
|
|
||||||
|
// Add dummy output (for `change`) to
|
||||||
|
// calculate maximum TX size.
|
||||||
|
this.tx.addOutput({
|
||||||
|
// In case we don't have a change address,
|
||||||
|
// use a fake p2pkh output to gauge size.
|
||||||
|
script: this.changeAddress
|
||||||
|
? Script.fromAddress(this.changeAddress)
|
||||||
|
: Script.fromPubkeyhash(constants.ZERO_HASH160),
|
||||||
|
value: 0
|
||||||
|
});
|
||||||
|
|
||||||
|
if (this.free && this.height >= 0) {
|
||||||
|
size = this.tx.maxSize(this);
|
||||||
|
|
||||||
|
// Note that this will only work
|
||||||
|
// if the mempool's rolling reject
|
||||||
|
// fee is zero (i.e. the mempool is
|
||||||
|
// not full).
|
||||||
|
if (this.tx.isFree(this.height + 1, size)) {
|
||||||
|
this.fee = 0;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Keep recalculating fee and funding
|
||||||
|
// until we reach some sort of equilibrium.
|
||||||
|
do {
|
||||||
|
size = this.tx.maxSize(this);
|
||||||
|
|
||||||
|
this.fee = this.getFee(size);
|
||||||
|
|
||||||
|
if (this.maxFee && this.fee > this.maxFee) {
|
||||||
|
throw new FundingError(
|
||||||
|
'Fee is too high.',
|
||||||
|
this.tx.getInputValue(),
|
||||||
|
this.total());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Failed to get enough funds, add more coins.
|
||||||
|
if (!this.isFull())
|
||||||
|
this.fund();
|
||||||
|
} while (!this.isFull() && this.index < this.coins.length);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initiate selection based on a hard fee.
|
||||||
|
* @param {Amount} fee
|
||||||
|
*/
|
||||||
|
|
||||||
|
CoinSelector.prototype.selectHard = function selectHard(fee) {
|
||||||
|
// Initial fee.
|
||||||
|
this.fee = fee;
|
||||||
|
|
||||||
|
if (this.fee > constants.tx.MAX_FEE)
|
||||||
|
this.fee = constants.tx.MAX_FEE;
|
||||||
|
|
||||||
|
// Transfer `total` funds maximum.
|
||||||
|
this.fund();
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Helpers
|
||||||
|
*/
|
||||||
|
|
||||||
|
function sortAge(a, b) {
|
||||||
|
a = a.height === -1 ? 0x7fffffff : a.height;
|
||||||
|
b = b.height === -1 ? 0x7fffffff : b.height;
|
||||||
|
return a - b;
|
||||||
|
}
|
||||||
|
|
||||||
|
function sortRandom(a, b) {
|
||||||
|
return Math.random() > 0.5 ? 1 : -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
function sortInputs(a, b) {
|
||||||
|
return utils.cmp(a.prevout.toRaw(), b.prevout.toRaw());
|
||||||
|
}
|
||||||
|
|
||||||
|
function sortOutputs(a, b) {
|
||||||
|
return utils.cmp(a.toRaw(), b.toRaw());
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Expose
|
* Expose
|
||||||
*/
|
*/
|
||||||
|
|||||||
@ -1580,6 +1580,23 @@ TX.prototype.getMinFee = function getMinFee(size, rate) {
|
|||||||
return TX.getMinFee(size, rate);
|
return TX.getMinFee(size, rate);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calculate the minimum fee in order for the transaction
|
||||||
|
* to be relayable, but _round to the nearest kilobyte
|
||||||
|
* when taking into account size.
|
||||||
|
* @param {Number?} size - If not present, max size
|
||||||
|
* estimation will be calculated and used.
|
||||||
|
* @param {Rate?} rate - Rate of satoshi per kB.
|
||||||
|
* @returns {Amount} fee
|
||||||
|
*/
|
||||||
|
|
||||||
|
TX.prototype.getRoundFee = function getRoundFee(size, rate) {
|
||||||
|
if (size == null)
|
||||||
|
size = this.maxSize();
|
||||||
|
|
||||||
|
return TX.getRoundFee(size, rate);
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Calculate the transaction's rate based on size
|
* Calculate the transaction's rate based on size
|
||||||
* and fees. Size will be calculated if not present.
|
* and fees. Size will be calculated if not present.
|
||||||
@ -1594,33 +1611,6 @@ TX.prototype.getRate = function getRate(size) {
|
|||||||
return TX.getRate(size, this.getFee());
|
return TX.getRate(size, this.getFee());
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
|
||||||
* Calculate the minimum fee in order for the transaction
|
|
||||||
* to be relayable, but _round to the nearest kilobyte
|
|
||||||
* when taking into account size.
|
|
||||||
* @param {Number?} size - If not present, max size
|
|
||||||
* estimation will be calculated and used.
|
|
||||||
* @param {Rate?} rate - Rate of satoshi per kB.
|
|
||||||
* @returns {Amount} fee
|
|
||||||
*/
|
|
||||||
|
|
||||||
TX.prototype.getRoundFee = function getRoundFee(size, rate) {
|
|
||||||
var fee;
|
|
||||||
|
|
||||||
if (size == null)
|
|
||||||
size = this.maxSize();
|
|
||||||
|
|
||||||
if (rate == null)
|
|
||||||
rate = constants.tx.MIN_RELAY;
|
|
||||||
|
|
||||||
fee = rate * Math.ceil(size / 1000);
|
|
||||||
|
|
||||||
if (fee === 0 && rate > 0)
|
|
||||||
fee = rate;
|
|
||||||
|
|
||||||
return fee;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Calculate current number of transaction confirmations.
|
* Calculate current number of transaction confirmations.
|
||||||
* @param {Number?} height - Current chain height. If not
|
* @param {Number?} height - Current chain height. If not
|
||||||
@ -1780,6 +1770,29 @@ TX.getMinFee = function getMinFee(size, rate) {
|
|||||||
return fee;
|
return fee;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calculate the minimum fee in order for the transaction
|
||||||
|
* to be relayable, but _round to the nearest kilobyte
|
||||||
|
* when taking into account size.
|
||||||
|
* @param {Number?} size
|
||||||
|
* @param {Rate?} rate - Rate of satoshi per kB.
|
||||||
|
* @returns {Amount} fee
|
||||||
|
*/
|
||||||
|
|
||||||
|
TX.getRoundFee = function getRoundFee(size, rate) {
|
||||||
|
var fee;
|
||||||
|
|
||||||
|
if (rate == null)
|
||||||
|
rate = constants.tx.MIN_RELAY;
|
||||||
|
|
||||||
|
fee = rate * Math.ceil(size / 1000);
|
||||||
|
|
||||||
|
if (fee === 0 && rate > 0)
|
||||||
|
fee = rate;
|
||||||
|
|
||||||
|
return fee;
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Calculate a fee rate based on size and fees.
|
* Calculate a fee rate based on size and fees.
|
||||||
* @param {Number} size
|
* @param {Number} size
|
||||||
|
|||||||
@ -489,7 +489,7 @@ Account.prototype.derivePath = function derivePath(path, master) {
|
|||||||
|
|
||||||
// Custom redeem script.
|
// Custom redeem script.
|
||||||
if (path.script)
|
if (path.script)
|
||||||
script = new bcoin.script(path.script);
|
script = bcoin.script.fromRaw(path.script);
|
||||||
|
|
||||||
ring = this.deriveAddress(path.change, path.index, master, script);
|
ring = this.deriveAddress(path.change, path.index, master, script);
|
||||||
|
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user