Merge pull request #475 from isocolsky/ref/fee-handling
Ref/fee handling
This commit is contained in:
commit
167f70b02c
|
@ -2,7 +2,6 @@
|
||||||
|
|
||||||
var Defaults = {};
|
var Defaults = {};
|
||||||
|
|
||||||
Defaults.DEFAULT_FEE_PER_KB = 10000;
|
|
||||||
Defaults.MIN_FEE_PER_KB = 0;
|
Defaults.MIN_FEE_PER_KB = 0;
|
||||||
Defaults.MAX_FEE_PER_KB = 1000000;
|
Defaults.MAX_FEE_PER_KB = 1000000;
|
||||||
Defaults.MIN_TX_FEE = 0;
|
Defaults.MIN_TX_FEE = 0;
|
||||||
|
@ -39,6 +38,8 @@ Defaults.FEE_LEVELS = [{
|
||||||
defaultValue: 25000
|
defaultValue: 25000
|
||||||
}];
|
}];
|
||||||
|
|
||||||
|
Defaults.DEFAULT_FEE_PER_KB = Defaults.FEE_LEVELS[1].defaultValue;
|
||||||
|
|
||||||
// Minimum nb of addresses a wallet must have to start using 2-step balance optimization
|
// Minimum nb of addresses a wallet must have to start using 2-step balance optimization
|
||||||
Defaults.TWO_STEP_BALANCE_THRESHOLD = 100;
|
Defaults.TWO_STEP_BALANCE_THRESHOLD = 100;
|
||||||
|
|
||||||
|
|
|
@ -44,7 +44,7 @@ TxProposal.create = function(opts) {
|
||||||
x.requiredRejections = Math.min(x.walletM, x.walletN - x.walletM + 1),
|
x.requiredRejections = Math.min(x.walletM, x.walletN - x.walletM + 1),
|
||||||
x.status = 'temporary';
|
x.status = 'temporary';
|
||||||
x.actions = [];
|
x.actions = [];
|
||||||
x.fee = opts.fee;
|
x.fee = null;
|
||||||
x.feePerKb = opts.feePerKb;
|
x.feePerKb = opts.feePerKb;
|
||||||
x.excludeUnconfirmedUtxos = opts.excludeUnconfirmedUtxos;
|
x.excludeUnconfirmedUtxos = opts.excludeUnconfirmedUtxos;
|
||||||
|
|
||||||
|
|
|
@ -133,8 +133,8 @@ TxProposal.fromObj = function(obj) {
|
||||||
return TxProposalAction.fromObj(action);
|
return TxProposalAction.fromObj(action);
|
||||||
});
|
});
|
||||||
x.outputOrder = obj.outputOrder;
|
x.outputOrder = obj.outputOrder;
|
||||||
x.fee = obj.fee;
|
|
||||||
x.network = obj.network;
|
x.network = obj.network;
|
||||||
|
x.fee = obj.fee;
|
||||||
x.feePerKb = obj.feePerKb;
|
x.feePerKb = obj.feePerKb;
|
||||||
x.excludeUnconfirmedUtxos = obj.excludeUnconfirmedUtxos;
|
x.excludeUnconfirmedUtxos = obj.excludeUnconfirmedUtxos;
|
||||||
x.proposalSignaturePubKey = obj.proposalSignaturePubKey;
|
x.proposalSignaturePubKey = obj.proposalSignaturePubKey;
|
||||||
|
|
|
@ -1223,9 +1223,7 @@ WalletService.prototype._checkTxAndEstimateFee = function(txp) {
|
||||||
serializationOpts.disableLargeFees = true;
|
serializationOpts.disableLargeFees = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_.isNumber(txp.feePerKb)) {
|
|
||||||
txp.estimateFee();
|
txp.estimateFee();
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
var bitcoreTx = txp.getBitcoreTx();
|
var bitcoreTx = txp.getBitcoreTx();
|
||||||
|
@ -1544,8 +1542,7 @@ WalletService.prototype.createTxLegacy = function(opts, cb) {
|
||||||
* @param {string} opts.outputs[].message - A message to attach to this output.
|
* @param {string} opts.outputs[].message - A message to attach to this output.
|
||||||
* @param {string} opts.message - A message to attach to this transaction.
|
* @param {string} opts.message - A message to attach to this transaction.
|
||||||
* @param {Array} opts.inputs - Optional. Inputs for this TX
|
* @param {Array} opts.inputs - Optional. Inputs for this TX
|
||||||
* @param {string} opts.fee - Optional. Use an alternative fee for this TX (mutually exclusive with feePerKb)
|
* @param {string} opts.feePerKb - The fee per kB to use for this TX.
|
||||||
* @param {string} opts.feePerKb - Optional. Use an alternative fee per KB for this TX (mutually exclusive with fee)
|
|
||||||
* @param {string} opts.payProUrl - Optional. Paypro URL for peers to verify TX
|
* @param {string} opts.payProUrl - Optional. Paypro URL for peers to verify TX
|
||||||
* @param {string} opts.excludeUnconfirmedUtxos[=false] - Optional. Do not use UTXOs of unconfirmed transactions as inputs
|
* @param {string} opts.excludeUnconfirmedUtxos[=false] - Optional. Do not use UTXOs of unconfirmed transactions as inputs
|
||||||
* @param {string} opts.validateOutputs[=true] - Optional. Perform validation on outputs.
|
* @param {string} opts.validateOutputs[=true] - Optional. Perform validation on outputs.
|
||||||
|
@ -1554,21 +1551,11 @@ WalletService.prototype.createTxLegacy = function(opts, cb) {
|
||||||
WalletService.prototype.createTx = function(opts, cb) {
|
WalletService.prototype.createTx = function(opts, cb) {
|
||||||
var self = this;
|
var self = this;
|
||||||
|
|
||||||
if (!Utils.checkRequired(opts, ['outputs']))
|
if (!Utils.checkRequired(opts, ['outputs', 'feePerKb']))
|
||||||
return cb(new ClientError('Required argument missing'));
|
return cb(new ClientError('Required argument missing'));
|
||||||
|
|
||||||
if (_.isNumber(opts.fee)) {
|
|
||||||
if (_.isNumber(opts.feePerKb))
|
|
||||||
return cb(new ClientError('Cannot sepcify both fee and feePerKb arguments'));
|
|
||||||
opts.feePerKb = null;
|
|
||||||
if (opts.fee < Defaults.MIN_TX_FEE || opts.fee > Defaults.MAX_TX_FEE)
|
|
||||||
return cb(new ClientError('Invalid fee'));
|
|
||||||
} else {
|
|
||||||
opts.fee = null;
|
|
||||||
opts.feePerKb = opts.feePerKb || Defaults.DEFAULT_FEE_PER_KB;
|
|
||||||
if (opts.feePerKb < Defaults.MIN_FEE_PER_KB || opts.feePerKb > Defaults.MAX_FEE_PER_KB)
|
if (opts.feePerKb < Defaults.MIN_FEE_PER_KB || opts.feePerKb > Defaults.MAX_FEE_PER_KB)
|
||||||
return cb(new ClientError('Invalid fee per KB'));
|
return cb(new ClientError('Invalid fee per KB'));
|
||||||
}
|
|
||||||
|
|
||||||
self._runLocked(cb, function(cb) {
|
self._runLocked(cb, function(cb) {
|
||||||
self.getWallet({}, function(err, wallet) {
|
self.getWallet({}, function(err, wallet) {
|
||||||
|
@ -1593,7 +1580,6 @@ WalletService.prototype.createTx = function(opts, cb) {
|
||||||
outputs: opts.outputs,
|
outputs: opts.outputs,
|
||||||
message: opts.message,
|
message: opts.message,
|
||||||
changeAddress: wallet.createAddress(true),
|
changeAddress: wallet.createAddress(true),
|
||||||
fee: opts.fee,
|
|
||||||
feePerKb: opts.feePerKb,
|
feePerKb: opts.feePerKb,
|
||||||
payProUrl: opts.payProUrl,
|
payProUrl: opts.payProUrl,
|
||||||
walletM: wallet.m,
|
walletM: wallet.m,
|
||||||
|
|
|
@ -2038,10 +2038,26 @@ describe('Wallet service', function() {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should assume default feePerKb for "normal" level when none is specified', function(done) {
|
||||||
|
helpers.stubUtxos(server, wallet, [100, 200], function() {
|
||||||
|
var txOpts = helpers.createProposalOptsLegacy('18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7', 80, 'some message', TestData.copayers[0].privKey_1H_0);
|
||||||
|
server.createTxLegacy(txOpts, function(err, tx) {
|
||||||
|
should.not.exist(err);
|
||||||
|
should.exist(tx);
|
||||||
|
tx.feePerKb.should.equal(_.find(Defaults.FEE_LEVELS, {
|
||||||
|
name: 'normal'
|
||||||
|
}).defaultValue);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
it('should support creating a tx with no change address', function(done) {
|
it('should support creating a tx with no change address', function(done) {
|
||||||
helpers.stubUtxos(server, wallet, [1, 2], function() {
|
helpers.stubUtxos(server, wallet, [1, 2], function() {
|
||||||
var max = 3 - (7200 / 1e8); // Fees for this tx at 100bits/kB = 7200 sat
|
var max = 3 - (7200 / 1e8); // Fees for this tx at 100bits/kB = 7200 sat
|
||||||
var txOpts = helpers.createSimpleProposalOpts('18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7', max, TestData.copayers[0].privKey_1H_0);
|
var txOpts = helpers.createSimpleProposalOpts('18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7', max, TestData.copayers[0].privKey_1H_0, {
|
||||||
|
feePerKb: 100e2
|
||||||
|
});
|
||||||
server.createTxLegacy(txOpts, function(err, txp) {
|
server.createTxLegacy(txOpts, function(err, txp) {
|
||||||
should.not.exist(err);
|
should.not.exist(err);
|
||||||
should.exist(txp);
|
should.exist(txp);
|
||||||
|
@ -2360,7 +2376,9 @@ describe('Wallet service', function() {
|
||||||
var fee = 4100 / 1e8; // The exact fee of the resulting tx
|
var fee = 4100 / 1e8; // The exact fee of the resulting tx
|
||||||
var amount = 1 - fee;
|
var amount = 1 - fee;
|
||||||
|
|
||||||
var txOpts = helpers.createSimpleProposalOpts('18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7', amount, TestData.copayers[0].privKey_1H_0);
|
var txOpts = helpers.createSimpleProposalOpts('18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7', amount, TestData.copayers[0].privKey_1H_0, {
|
||||||
|
feePerKb: 100e2
|
||||||
|
});
|
||||||
server.createTxLegacy(txOpts, function(err, tx) {
|
server.createTxLegacy(txOpts, function(err, tx) {
|
||||||
should.not.exist(err);
|
should.not.exist(err);
|
||||||
should.exist(tx);
|
should.exist(tx);
|
||||||
|
@ -2507,7 +2525,8 @@ describe('Wallet service', function() {
|
||||||
message: 'message #2'
|
message: 'message #2'
|
||||||
}];
|
}];
|
||||||
var txOpts = helpers.createProposalOpts(Model.TxProposalLegacy.Types.MULTIPLEOUTPUTS, outputs, TestData.copayers[0].privKey_1H_0, {
|
var txOpts = helpers.createProposalOpts(Model.TxProposalLegacy.Types.MULTIPLEOUTPUTS, outputs, TestData.copayers[0].privKey_1H_0, {
|
||||||
message: 'some message'
|
message: 'some message',
|
||||||
|
feePerKb: 100e2,
|
||||||
});
|
});
|
||||||
server.createTxLegacy(txOpts, function(err, txp) {
|
server.createTxLegacy(txOpts, function(err, txp) {
|
||||||
should.not.exist(err);
|
should.not.exist(err);
|
||||||
|
@ -2587,7 +2606,9 @@ describe('Wallet service', function() {
|
||||||
balance.totalBytesToSendConfirmedMax.should.equal(2896);
|
balance.totalBytesToSendConfirmedMax.should.equal(2896);
|
||||||
var fee = parseInt((balance.totalBytesToSendMax * 10000 / 1000).toFixed(0));
|
var fee = parseInt((balance.totalBytesToSendMax * 10000 / 1000).toFixed(0));
|
||||||
var max = balance.availableAmount - fee;
|
var max = balance.availableAmount - fee;
|
||||||
var txOpts = helpers.createSimpleProposalOpts('18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7', max / 1e8, TestData.copayers[0].privKey_1H_0);
|
var txOpts = helpers.createSimpleProposalOpts('18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7', max / 1e8, TestData.copayers[0].privKey_1H_0, {
|
||||||
|
feePerKb: 100e2,
|
||||||
|
});
|
||||||
server.createTxLegacy(txOpts, function(err, tx) {
|
server.createTxLegacy(txOpts, function(err, tx) {
|
||||||
should.not.exist(err);
|
should.not.exist(err);
|
||||||
should.exist(tx);
|
should.exist(tx);
|
||||||
|
@ -2652,7 +2673,9 @@ describe('Wallet service', function() {
|
||||||
balance.totalBytesToSendConfirmedMax.should.equal(720);
|
balance.totalBytesToSendConfirmedMax.should.equal(720);
|
||||||
var fee = parseInt((balance.totalBytesToSendConfirmedMax * 10000 / 1000).toFixed(0));
|
var fee = parseInt((balance.totalBytesToSendConfirmedMax * 10000 / 1000).toFixed(0));
|
||||||
var max = balance.availableConfirmedAmount - fee;
|
var max = balance.availableConfirmedAmount - fee;
|
||||||
var txOpts = helpers.createSimpleProposalOpts('18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7', max / 1e8, TestData.copayers[0].privKey_1H_0);
|
var txOpts = helpers.createSimpleProposalOpts('18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7', max / 1e8, TestData.copayers[0].privKey_1H_0, {
|
||||||
|
feePerKb: 100e2,
|
||||||
|
});
|
||||||
server.createTxLegacy(txOpts, function(err, tx) {
|
server.createTxLegacy(txOpts, function(err, tx) {
|
||||||
should.not.exist(err);
|
should.not.exist(err);
|
||||||
should.exist(tx);
|
should.exist(tx);
|
||||||
|
@ -2718,6 +2741,7 @@ describe('Wallet service', function() {
|
||||||
}],
|
}],
|
||||||
message: 'some message',
|
message: 'some message',
|
||||||
customData: 'some custom data',
|
customData: 'some custom data',
|
||||||
|
feePerKb: 123e2,
|
||||||
};
|
};
|
||||||
server.createTx(txOpts, function(err, tx) {
|
server.createTx(txOpts, function(err, tx) {
|
||||||
should.not.exist(err);
|
should.not.exist(err);
|
||||||
|
@ -2731,6 +2755,7 @@ describe('Wallet service', function() {
|
||||||
tx.isPending().should.equal.true;
|
tx.isPending().should.equal.true;
|
||||||
tx.isTemporary().should.equal.true;
|
tx.isTemporary().should.equal.true;
|
||||||
tx.amount.should.equal(helpers.toSatoshi(0.8));
|
tx.amount.should.equal(helpers.toSatoshi(0.8));
|
||||||
|
tx.feePerKb.should.equal(123e2);
|
||||||
server.getPendingTxs({}, function(err, txs) {
|
server.getPendingTxs({}, function(err, txs) {
|
||||||
should.not.exist(err);
|
should.not.exist(err);
|
||||||
txs.should.be.empty;
|
txs.should.be.empty;
|
||||||
|
@ -2740,61 +2765,6 @@ describe('Wallet service', function() {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should be able to specify the final fee', function(done) {
|
|
||||||
helpers.stubUtxos(server, wallet, [1, 2], function() {
|
|
||||||
var txOpts = {
|
|
||||||
outputs: [{
|
|
||||||
toAddress: '18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7',
|
|
||||||
amount: 0.8 * 1e8,
|
|
||||||
}],
|
|
||||||
fee: 123400,
|
|
||||||
};
|
|
||||||
server.createTx(txOpts, function(err, tx) {
|
|
||||||
should.not.exist(err);
|
|
||||||
should.exist(tx);
|
|
||||||
tx.fee.should.equal(123400);
|
|
||||||
var t = tx.getBitcoreTx();
|
|
||||||
t.getFee().should.equal(123400);
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should not be able to specify both final fee & fee per kb', function(done) {
|
|
||||||
helpers.stubUtxos(server, wallet, [1, 2], function() {
|
|
||||||
var txOpts = {
|
|
||||||
outputs: [{
|
|
||||||
toAddress: '18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7',
|
|
||||||
amount: 0.8 * 1e8,
|
|
||||||
}],
|
|
||||||
fee: 123400,
|
|
||||||
feePerKb: 123400,
|
|
||||||
};
|
|
||||||
server.createTx(txOpts, function(err, tx) {
|
|
||||||
should.exist(err);
|
|
||||||
err.message.should.contain('fee');
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should check explicit fee to be below max', function(done) {
|
|
||||||
helpers.stubUtxos(server, wallet, [1, 2], function() {
|
|
||||||
var txOpts = {
|
|
||||||
outputs: [{
|
|
||||||
toAddress: '18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7',
|
|
||||||
amount: 0.8 * 1e8,
|
|
||||||
}],
|
|
||||||
fee: 1e8,
|
|
||||||
};
|
|
||||||
server.createTx(txOpts, function(err, tx) {
|
|
||||||
should.exist(err);
|
|
||||||
err.message.should.contain('fee');
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should be able to publish a temporary tx proposal', function(done) {
|
it('should be able to publish a temporary tx proposal', function(done) {
|
||||||
helpers.stubUtxos(server, wallet, [1, 2], function() {
|
helpers.stubUtxos(server, wallet, [1, 2], function() {
|
||||||
var txOpts = {
|
var txOpts = {
|
||||||
|
@ -2802,6 +2772,7 @@ describe('Wallet service', function() {
|
||||||
toAddress: '18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7',
|
toAddress: '18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7',
|
||||||
amount: 0.8 * 1e8,
|
amount: 0.8 * 1e8,
|
||||||
}],
|
}],
|
||||||
|
feePerKb: 100e2,
|
||||||
message: 'some message',
|
message: 'some message',
|
||||||
customData: 'some custom data',
|
customData: 'some custom data',
|
||||||
};
|
};
|
||||||
|
@ -2829,6 +2800,7 @@ describe('Wallet service', function() {
|
||||||
toAddress: '18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7',
|
toAddress: '18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7',
|
||||||
amount: 0.8 * 1e8,
|
amount: 0.8 * 1e8,
|
||||||
}],
|
}],
|
||||||
|
feePerKb: 100e2,
|
||||||
message: 'some message',
|
message: 'some message',
|
||||||
};
|
};
|
||||||
server.createTx(txOpts, function(err, txp) {
|
server.createTx(txOpts, function(err, txp) {
|
||||||
|
@ -2872,6 +2844,7 @@ describe('Wallet service', function() {
|
||||||
toAddress: '18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7',
|
toAddress: '18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7',
|
||||||
amount: 0.8 * 1e8,
|
amount: 0.8 * 1e8,
|
||||||
}],
|
}],
|
||||||
|
feePerKb: 100e2,
|
||||||
message: 'some message',
|
message: 'some message',
|
||||||
};
|
};
|
||||||
server.createTx(txOpts, function(err, txp) {
|
server.createTx(txOpts, function(err, txp) {
|
||||||
|
@ -2896,6 +2869,7 @@ describe('Wallet service', function() {
|
||||||
toAddress: '18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7',
|
toAddress: '18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7',
|
||||||
amount: 0.8 * 1e8,
|
amount: 0.8 * 1e8,
|
||||||
}],
|
}],
|
||||||
|
feePerKb: 100e2,
|
||||||
message: 'some message',
|
message: 'some message',
|
||||||
};
|
};
|
||||||
server.createTx(txOpts, function(err, txp) {
|
server.createTx(txOpts, function(err, txp) {
|
||||||
|
@ -2938,6 +2912,7 @@ describe('Wallet service', function() {
|
||||||
amount: 0.8 * 1e8,
|
amount: 0.8 * 1e8,
|
||||||
}],
|
}],
|
||||||
message: 'some message',
|
message: 'some message',
|
||||||
|
feePerKb: 100e2,
|
||||||
};
|
};
|
||||||
server.createTx(txOpts, function(err, txp) {
|
server.createTx(txOpts, function(err, txp) {
|
||||||
should.not.exist(err);
|
should.not.exist(err);
|
||||||
|
@ -2973,6 +2948,7 @@ describe('Wallet service', function() {
|
||||||
amount: 0.8 * 1e8,
|
amount: 0.8 * 1e8,
|
||||||
}],
|
}],
|
||||||
message: 'some message',
|
message: 'some message',
|
||||||
|
feePerKb: 100e2,
|
||||||
};
|
};
|
||||||
|
|
||||||
async.waterfall([
|
async.waterfall([
|
||||||
|
@ -3042,6 +3018,7 @@ describe('Wallet service', function() {
|
||||||
}],
|
}],
|
||||||
message: 'some message',
|
message: 'some message',
|
||||||
customData: 'some custom data',
|
customData: 'some custom data',
|
||||||
|
feePerKb: 100e2,
|
||||||
};
|
};
|
||||||
server.createTx(txOpts, function(err, txp) {
|
server.createTx(txOpts, function(err, txp) {
|
||||||
should.not.exist(err);
|
should.not.exist(err);
|
||||||
|
@ -3708,6 +3685,7 @@ describe('Wallet service', function() {
|
||||||
amount: 9e8,
|
amount: 9e8,
|
||||||
}],
|
}],
|
||||||
message: 'some message',
|
message: 'some message',
|
||||||
|
feePerKb: 100e2,
|
||||||
};
|
};
|
||||||
helpers.createAndPublishTx(server, txOpts, TestData.copayers[0].privKey_1H_0, function(txp) {
|
helpers.createAndPublishTx(server, txOpts, TestData.copayers[0].privKey_1H_0, function(txp) {
|
||||||
should.exist(txp);
|
should.exist(txp);
|
||||||
|
|
Loading…
Reference in New Issue