allow feeLevel in getSendMaxInfo
This commit is contained in:
parent
db34c557bc
commit
281e8abe5d
147
lib/server.js
147
lib/server.js
|
@ -1150,7 +1150,8 @@ WalletService.prototype.getBalance = function(opts, cb) {
|
|||
/**
|
||||
* Return info needed to send all funds in the wallet
|
||||
* @param {Object} opts
|
||||
* @param {string} opts.feePerKb - The fee per KB used to compute the TX.
|
||||
* @param {number} opts.feeLevel[='normal'] - Optional. Specify the fee level for this TX ('priority', 'normal', 'economy', 'superEconomy') as defined in Defaults.FEE_LEVELS.
|
||||
* @param {number} opts.feePerKb - Optional. Specify the fee per KB for this TX (in satoshi).
|
||||
* @param {string} opts.excludeUnconfirmedUtxos[=false] - Optional. Do not use UTXOs of unconfirmed transactions as inputs
|
||||
* @param {string} opts.returnInputs[=false] - Optional. Return the list of UTXOs that would be included in the tx.
|
||||
* @returns {Object} sendMaxInfo
|
||||
|
@ -1161,7 +1162,26 @@ WalletService.prototype.getSendMaxInfo = function(opts, cb) {
|
|||
|
||||
opts = opts || {};
|
||||
|
||||
if (!checkRequired(opts, ['feePerKb'], cb)) return;
|
||||
var feeArgs = !!opts.feeLevel + _.isNumber(opts.feePerKb);
|
||||
if (feeArgs > 1)
|
||||
return cb(new ClientError('Only one of feeLevel/feePerKb can be specified'));
|
||||
|
||||
if (feeArgs == 0) {
|
||||
log.debug('No fee provided, using "normal" fee level');
|
||||
opts.feeLevel = 'normal';
|
||||
}
|
||||
|
||||
if (opts.feeLevel) {
|
||||
if (!_.any(Defaults.FEE_LEVELS, {
|
||||
name: opts.feeLevel
|
||||
}))
|
||||
return cb(new ClientError('Invalid fee level. Valid values are ' + _.pluck(Defaults.FEE_LEVELS, 'name').join(', ')));
|
||||
}
|
||||
|
||||
if (_.isNumber(opts.feePerKb)) {
|
||||
if (opts.feePerKb < Defaults.MIN_FEE_PER_KB || opts.feePerKb > Defaults.MAX_FEE_PER_KB)
|
||||
return cb(new ClientError('Invalid fee per KB'));
|
||||
}
|
||||
|
||||
self.getWallet({}, function(err, wallet) {
|
||||
if (err) return cb(err);
|
||||
|
@ -1173,6 +1193,7 @@ WalletService.prototype.getSendMaxInfo = function(opts, cb) {
|
|||
size: 0,
|
||||
amount: 0,
|
||||
fee: 0,
|
||||
feePerKb: 0,
|
||||
inputs: [],
|
||||
utxosBelowFee: 0,
|
||||
amountBelowFee: 0,
|
||||
|
@ -1190,47 +1211,53 @@ WalletService.prototype.getSendMaxInfo = function(opts, cb) {
|
|||
|
||||
if (_.isEmpty(inputs)) return cb(null, info);
|
||||
|
||||
var txp = Model.TxProposal.create({
|
||||
walletId: self.walletId,
|
||||
network: wallet.network,
|
||||
walletM: wallet.m,
|
||||
walletN: wallet.n,
|
||||
feePerKb: opts.feePerKb,
|
||||
});
|
||||
self._getFeePerKb(wallet, opts, function(err, feePerKb) {
|
||||
if (err) return cb(err);
|
||||
|
||||
var baseTxpSize = txp.getEstimatedSize();
|
||||
var baseTxpFee = baseTxpSize * txp.feePerKb / 1000.;
|
||||
var sizePerInput = txp.getEstimatedSizeForSingleInput();
|
||||
var feePerInput = sizePerInput * txp.feePerKb / 1000.;
|
||||
info.feePerKb = feePerKb;
|
||||
|
||||
var partitionedByAmount = _.partition(inputs, function(input) {
|
||||
return input.satoshis > feePerInput;
|
||||
});
|
||||
var txp = Model.TxProposal.create({
|
||||
walletId: self.walletId,
|
||||
network: wallet.network,
|
||||
walletM: wallet.m,
|
||||
walletN: wallet.n,
|
||||
feePerKb: feePerKb,
|
||||
});
|
||||
|
||||
info.utxosBelowFee = partitionedByAmount[1].length;
|
||||
info.amountBelowFee = _.sum(partitionedByAmount[1], 'satoshis');
|
||||
inputs = partitionedByAmount[0];
|
||||
var baseTxpSize = txp.getEstimatedSize();
|
||||
var baseTxpFee = baseTxpSize * txp.feePerKb / 1000.;
|
||||
var sizePerInput = txp.getEstimatedSizeForSingleInput();
|
||||
var feePerInput = sizePerInput * txp.feePerKb / 1000.;
|
||||
|
||||
_.each(inputs, function(input, i) {
|
||||
var sizeInKb = (baseTxpSize + (i + 1) * sizePerInput) / 1000.;
|
||||
if (sizeInKb > Defaults.MAX_TX_SIZE_IN_KB) {
|
||||
info.utxosAboveMaxSize = inputs.length - i;
|
||||
info.amountAboveMaxSize = _.sum(_.slice(inputs, i), 'satoshis');
|
||||
return false;
|
||||
var partitionedByAmount = _.partition(inputs, function(input) {
|
||||
return input.satoshis > feePerInput;
|
||||
});
|
||||
|
||||
info.utxosBelowFee = partitionedByAmount[1].length;
|
||||
info.amountBelowFee = _.sum(partitionedByAmount[1], 'satoshis');
|
||||
inputs = partitionedByAmount[0];
|
||||
|
||||
_.each(inputs, function(input, i) {
|
||||
var sizeInKb = (baseTxpSize + (i + 1) * sizePerInput) / 1000.;
|
||||
if (sizeInKb > Defaults.MAX_TX_SIZE_IN_KB) {
|
||||
info.utxosAboveMaxSize = inputs.length - i;
|
||||
info.amountAboveMaxSize = _.sum(_.slice(inputs, i), 'satoshis');
|
||||
return false;
|
||||
}
|
||||
txp.inputs.push(input);
|
||||
});
|
||||
|
||||
if (_.isEmpty(txp.inputs)) return cb(null, info);
|
||||
|
||||
info.size = txp.getEstimatedSize();
|
||||
info.fee = txp.getEstimatedFee();
|
||||
info.amount = _.sum(txp.inputs, 'satoshis') - info.fee;
|
||||
if (opts.returnInputs) {
|
||||
info.inputs = _.shuffle(txp.inputs);
|
||||
}
|
||||
txp.inputs.push(input);
|
||||
|
||||
return cb(null, info);
|
||||
});
|
||||
|
||||
if (_.isEmpty(txp.inputs)) return cb(null, info);
|
||||
|
||||
info.size = txp.getEstimatedSize();
|
||||
info.fee = txp.getEstimatedFee();
|
||||
info.amount = _.sum(txp.inputs, 'satoshis') - info.fee;
|
||||
if (opts.returnInputs) {
|
||||
info.inputs = _.shuffle(txp.inputs);
|
||||
}
|
||||
|
||||
return cb(null, info);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
@ -1704,7 +1731,7 @@ WalletService.prototype._validateAndSanitizeTxOpts = function(wallet, opts, cb)
|
|||
return next(new ClientError('Invalid fee per KB'));
|
||||
}
|
||||
|
||||
if (_.isNumber(opts.fee) && !opts.inputs)
|
||||
if (_.isNumber(opts.fee) && _.isEmpty(opts.inputs))
|
||||
return next(new ClientError('fee can only be set when inputs are specified'));
|
||||
|
||||
next();
|
||||
|
@ -1745,6 +1772,27 @@ WalletService.prototype._validateAndSanitizeTxOpts = function(wallet, opts, cb)
|
|||
], cb);
|
||||
};
|
||||
|
||||
WalletService.prototype._getFeePerKb = function(wallet, opts, cb) {
|
||||
var self = this;
|
||||
|
||||
if (_.isNumber(opts.feePerKb)) return cb(null, opts.feePerKb);
|
||||
self.getFeeLevels({
|
||||
network: wallet.network
|
||||
}, function(err, levels) {
|
||||
if (err) return cb(err);
|
||||
var level = _.find(levels, {
|
||||
level: opts.feeLevel
|
||||
});
|
||||
if (!level) {
|
||||
var msg = 'Could not compute fee for "' + opts.feeLevel + '" level';
|
||||
log.error(msg);
|
||||
return cb(new ClientError(msg));
|
||||
}
|
||||
return cb(null, level.feePerKb);
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Creates a new transaction proposal.
|
||||
* @param {Object} opts
|
||||
|
@ -1791,26 +1839,6 @@ WalletService.prototype.createTx = function(opts, cb) {
|
|||
}
|
||||
};
|
||||
|
||||
|
||||
function getFeePerKb(wallet, cb) {
|
||||
if (opts.inputs && _.isNumber(opts.fee)) return cb();
|
||||
if (_.isNumber(opts.feePerKb)) return cb(null, opts.feePerKb);
|
||||
self.getFeeLevels({
|
||||
network: wallet.network
|
||||
}, function(err, levels) {
|
||||
if (err) return cb(err);
|
||||
var level = _.find(levels, {
|
||||
level: opts.feeLevel
|
||||
});
|
||||
if (!level) {
|
||||
var msg = 'Could not compute fee for "' + opts.feeLevel + '" level';
|
||||
log.error(msg);
|
||||
return cb(new ClientError(msg));
|
||||
}
|
||||
return cb(null, level.feePerKb);
|
||||
});
|
||||
};
|
||||
|
||||
function checkTxpAlreadyExists(txProposalId, cb) {
|
||||
if (!txProposalId) return cb();
|
||||
self.storage.fetchTx(self.walletId, txProposalId, cb);
|
||||
|
@ -1847,7 +1875,8 @@ WalletService.prototype.createTx = function(opts, cb) {
|
|||
});
|
||||
},
|
||||
function(next) {
|
||||
getFeePerKb(wallet, function(err, fee) {
|
||||
if (_.isNumber(opts.fee) && !_.isEmpty(opts.inputs)) return next();
|
||||
self._getFeePerKb(wallet, opts, function(err, fee) {
|
||||
feePerKb = fee;
|
||||
next();
|
||||
});
|
||||
|
|
|
@ -4184,6 +4184,57 @@ describe('Wallet service', function() {
|
|||
});
|
||||
});
|
||||
});
|
||||
describe('Fee level', function() {
|
||||
it('should correctly get send max info using feeLevel', function(done) {
|
||||
helpers.stubFeeLevels({
|
||||
1: 400e2,
|
||||
2: 200e2,
|
||||
6: 180e2,
|
||||
24: 90e2,
|
||||
});
|
||||
helpers.stubUtxos(server, wallet, [0.1, 0.2, 0.3, 0.4], function() {
|
||||
server.getSendMaxInfo({
|
||||
feeLevel: 'economy',
|
||||
returnInputs: true,
|
||||
}, function(err, info) {
|
||||
should.not.exist(err);
|
||||
should.exist(info);
|
||||
info.feePerKb.should.equal(180e2);
|
||||
info.fee.should.equal(info.size * 180e2 / 1000.);
|
||||
sendTx(info, done);
|
||||
});
|
||||
});
|
||||
});
|
||||
it('should assume "normal" fee level if not specified', function(done) {
|
||||
helpers.stubFeeLevels({
|
||||
1: 400e2,
|
||||
2: 200e2,
|
||||
6: 180e2,
|
||||
24: 90e2,
|
||||
});
|
||||
helpers.stubUtxos(server, wallet, [0.1, 0.2, 0.3, 0.4], function() {
|
||||
server.getSendMaxInfo({}, function(err, info) {
|
||||
should.not.exist(err);
|
||||
should.exist(info);
|
||||
info.feePerKb.should.equal(200e2);
|
||||
info.fee.should.equal(info.size * 200e2 / 1000.);
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
it('should fail on invalid fee level', function(done) {
|
||||
helpers.stubUtxos(server, wallet, [0.1, 0.2, 0.3, 0.4], function() {
|
||||
server.getSendMaxInfo({
|
||||
feeLevel: 'madeUpLevel',
|
||||
}, function(err, info) {
|
||||
should.exist(err);
|
||||
should.not.exist(info);
|
||||
err.toString().should.contain('Invalid fee level');
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
it('should return inputs in random order', function(done) {
|
||||
// NOTE: this test has a chance of failing of 1 in 1'073'741'824 :P
|
||||
helpers.stubUtxos(server, wallet, _.range(1, 31), function(utxos) {
|
||||
|
|
Loading…
Reference in New Issue