Merge pull request #530 from isocolsky/fix/dust

Improve DUST check
This commit is contained in:
Matias Alejo Garcia 2016-06-10 14:34:49 -03:00 committed by GitHub
commit ab8ee233e0
3 changed files with 32 additions and 43 deletions

View File

@ -67,7 +67,7 @@ Defaults.UTXO_SELECTION_MAX_FEE_VS_TX_AMOUNT_FACTOR = 0.05;
// when fees are significant (proportional to how much we would pay for using that big input only).
Defaults.UTXO_SELECTION_MAX_FEE_VS_SINGLE_UTXO_FEE_FACTOR = 5;
// Do not generate change for less than the specified amount
Defaults.UTXO_SELECTION_MIN_CHANGE_AMOUNT = 5000;
// Minimum allowed amount for tx outputs (including change) in SAT
Defaults.MIN_OUTPUT_AMOUNT = 5000;
module.exports = Defaults;

View File

@ -1528,9 +1528,9 @@ WalletService.prototype._selectTxInputs = function(txp, utxosToExclude, cb) {
var changeAmount = Math.round(total - txpAmount - fee);
log.debug('Tx change: ', Utils.formatAmountInBtc(changeAmount));
var smallChangeThreshold = Math.max(Defaults.UTXO_SELECTION_MIN_CHANGE_AMOUNT, Bitcore.Transaction.DUST_AMOUNT);
if (changeAmount > 0 && changeAmount <= smallChangeThreshold) {
log.debug('Change below threshold (' + Utils.formatAmountInBtc(smallChangeThreshold) + '). Incrementing fee to remove change.');
var dustThreshold = Math.max(Defaults.MIN_OUTPUT_AMOUNT, Bitcore.Transaction.DUST_AMOUNT);
if (changeAmount > 0 && changeAmount <= dustThreshold) {
log.debug('Change below dust threshold (' + Utils.formatAmountInBtc(dustThreshold) + '). Incrementing fee to remove change.');
// Remove dust change by incrementing fee
fee += changeAmount;
}
@ -1903,6 +1903,15 @@ WalletService.prototype._validateAndSanitizeTxOpts = function(wallet, opts, cb)
return next();
});
},
function(next) {
var dustThreshold = Math.max(Defaults.MIN_OUTPUT_AMOUNT, Bitcore.Transaction.DUST_AMOUNT);
if (_.any(opts.outputs, function(output) {
return output.amount < dustThreshold;
})) {
return next(Errors.DUST_AMOUNT);
}
next();
},
function(next) {
if (opts.validateOutputs === false) return next();
var validationError = self._validateOutputs(opts, wallet, next);

View File

@ -2248,7 +2248,6 @@ describe('Wallet service', function() {
});
});
});
it('should generate new change address for each created tx', function(done) {
helpers.stubUtxos(server, wallet, [1, 2], function() {
var txOpts = helpers.createSimpleProposalOpts('18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7', 0.8, TestData.copayers[0].privKey_1H_0);
@ -2265,7 +2264,6 @@ describe('Wallet service', function() {
});
});
});
it('should create a tx with legacy signature', function(done) {
helpers.stubUtxos(server, wallet, [100, 200], function() {
var txOpts = helpers.createProposalOptsLegacy('18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7', 80, 'some message', TestData.copayers[0].privKey_1H_0);
@ -2276,7 +2274,6 @@ 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);
@ -2290,7 +2287,6 @@ describe('Wallet service', function() {
});
});
});
it('should support creating a tx with no change address', function(done) {
helpers.stubUtxos(server, wallet, [1, 2], function() {
var max = 3 - (7200 / 1e8); // Fees for this tx at 100bits/kB = 7200 sat
@ -2307,7 +2303,6 @@ describe('Wallet service', function() {
});
});
});
it('should create a tx using confirmed utxos first', function(done) {
helpers.stubUtxos(server, wallet, [1.3, 'u0.5', 'u0.1', 1.2], function(utxos) {
var txOpts = helpers.createSimpleProposalOpts('18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7', 1.5, TestData.copayers[0].privKey_1H_0, {
@ -2322,7 +2317,6 @@ describe('Wallet service', function() {
});
});
});
it('should use unconfirmed utxos only when no more confirmed utxos are available', function(done) {
helpers.stubUtxos(server, wallet, [1.3, 'u0.5', 'u0.1', 1.2], function(utxos) {
var txOpts = helpers.createSimpleProposalOpts('18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7', 2.55, TestData.copayers[0].privKey_1H_0, {
@ -2339,7 +2333,6 @@ describe('Wallet service', function() {
});
});
});
it('should exclude unconfirmed utxos if specified', function(done) {
helpers.stubUtxos(server, wallet, [1.3, 'u2', 'u0.1', 1.2], function(utxos) {
var txOpts = helpers.createSimpleProposalOpts('18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7', 3, TestData.copayers[0].privKey_1H_0, {
@ -2363,7 +2356,6 @@ describe('Wallet service', function() {
});
});
});
it('should use non-locked confirmed utxos when specified', function(done) {
helpers.stubUtxos(server, wallet, [1.3, 'u2', 'u0.1', 1.2], function(utxos) {
var txOpts = helpers.createSimpleProposalOpts('18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7', 1.4, TestData.copayers[0].privKey_1H_0, {
@ -2391,7 +2383,6 @@ describe('Wallet service', function() {
});
});
});
it('should fail gracefully if unable to reach the blockchain', function(done) {
blockchainExplorer.getUtxos = sinon.stub().callsArgWith(1, 'dummy error');
server.createAddress({}, function(err, address) {
@ -2406,7 +2397,6 @@ describe('Wallet service', function() {
});
});
});
it('should fail to create tx with invalid proposal signature', function(done) {
helpers.stubUtxos(server, wallet, [100, 200], function() {
var txOpts = helpers.createSimpleProposalOpts('18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7', 80, 'dummy');
@ -2419,7 +2409,6 @@ describe('Wallet service', function() {
});
});
});
it('should fail to create tx with proposal signed by another copayer', function(done) {
helpers.stubUtxos(server, wallet, [100, 200], function() {
var txOpts = helpers.createSimpleProposalOpts('18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7', 80, TestData.copayers[1].privKey_1H_0);
@ -2432,7 +2421,6 @@ describe('Wallet service', function() {
});
});
});
it('should fail to create tx for invalid address', function(done) {
helpers.stubUtxos(server, wallet, [100, 200], function() {
var txOpts = helpers.createSimpleProposalOpts('invalid address', 80, TestData.copayers[0].privKey_1H_0);
@ -2445,7 +2433,6 @@ describe('Wallet service', function() {
});
});
});
it('should fail to create tx for address of different network', function(done) {
helpers.stubUtxos(server, wallet, [100, 200], function() {
var txOpts = helpers.createSimpleProposalOpts('myE38JHdxmQcTJGP1ZiX4BiGhDxMJDvLJD', 80, TestData.copayers[0].privKey_1H_0);
@ -2459,7 +2446,6 @@ describe('Wallet service', function() {
});
});
});
it('should fail to create tx for invalid amount', function(done) {
var txOpts = helpers.createSimpleProposalOpts('18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7', 0, TestData.copayers[0].privKey_1H_0);
server.createTxLegacy(txOpts, function(err, tx) {
@ -2469,7 +2455,6 @@ describe('Wallet service', function() {
done();
});
});
it('should fail to create tx when insufficient funds', function(done) {
helpers.stubUtxos(server, wallet, [100], function() {
var txOpts = helpers.createSimpleProposalOpts('18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7', 120, TestData.copayers[0].privKey_1H_0);
@ -2490,7 +2475,6 @@ describe('Wallet service', function() {
});
});
});
it('should fail to create tx when insufficient funds for fee', function(done) {
helpers.stubUtxos(server, wallet, 0.048222, function() {
var txOpts = helpers.createSimpleProposalOpts('18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7', 0.048200, TestData.copayers[0].privKey_1H_0);
@ -2502,7 +2486,6 @@ describe('Wallet service', function() {
});
});
});
it('should scale fees according to tx size', function(done) {
helpers.stubUtxos(server, wallet, [1, 1, 1, 1], function() {
var txOpts = helpers.createSimpleProposalOpts('18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7', 3.5, TestData.copayers[0].privKey_1H_0);
@ -2514,7 +2497,6 @@ describe('Wallet service', function() {
});
});
});
it('should be possible to use a smaller fee', function(done) {
helpers.stubUtxos(server, wallet, 1, function() {
var txOpts = helpers.createSimpleProposalOpts('18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7', 0.9999, TestData.copayers[0].privKey_1H_0, {
@ -2544,7 +2526,6 @@ describe('Wallet service', function() {
});
});
});
it('should fail to create a tx exceeding max size in kb', function(done) {
var _oldDefault = Defaults.MAX_TX_SIZE_IN_KB;
Defaults.MAX_TX_SIZE_IN_KB = 1;
@ -2558,7 +2539,6 @@ describe('Wallet service', function() {
});
});
});
it('should fail to create tx for dust amount', function(done) {
helpers.stubUtxos(server, wallet, [1], function() {
var txOpts = helpers.createSimpleProposalOpts('18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7', 0.00000001, TestData.copayers[0].privKey_1H_0);
@ -2570,7 +2550,6 @@ describe('Wallet service', function() {
});
});
});
it('should modify fee if tx would return change for dust amount', function(done) {
helpers.stubUtxos(server, wallet, [1], function() {
var fee = 4095; // The exact fee of the resulting tx (based exclusively on feePerKB && size)
@ -2587,7 +2566,6 @@ describe('Wallet service', function() {
});
});
});
it('should fail with different error for insufficient funds and locked funds', function(done) {
helpers.stubUtxos(server, wallet, [10, 10], function() {
var txOpts = helpers.createSimpleProposalOpts('18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7', 11, TestData.copayers[0].privKey_1H_0);
@ -2608,7 +2586,6 @@ describe('Wallet service', function() {
});
});
});
it('should create tx with 0 change output', function(done) {
helpers.stubUtxos(server, wallet, [1], function() {
var fee = 4100 / 1e8; // The exact fee of the resulting tx
@ -2627,7 +2604,6 @@ describe('Wallet service', function() {
});
});
});
it('should fail gracefully when bitcore throws exception on raw tx creation', function(done) {
helpers.stubUtxos(server, wallet, [10], function() {
var bitcoreStub = sinon.stub(Bitcore, 'Transaction');
@ -2644,7 +2620,6 @@ describe('Wallet service', function() {
});
});
});
it('should create tx when there is a pending tx and enough UTXOs', function(done) {
helpers.stubUtxos(server, wallet, [10.1, 10.2, 10.3], function() {
var txOpts = helpers.createSimpleProposalOpts('18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7', 12, TestData.copayers[0].privKey_1H_0);
@ -2669,7 +2644,6 @@ describe('Wallet service', function() {
});
});
});
it('should fail to create tx when there is a pending tx and not enough UTXOs', function(done) {
helpers.stubUtxos(server, wallet, [10.1, 10.2, 10.3], function() {
var txOpts = helpers.createSimpleProposalOpts('18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7', 12, TestData.copayers[0].privKey_1H_0);
@ -2697,7 +2671,6 @@ describe('Wallet service', function() {
});
});
});
it('should create tx using different UTXOs for simultaneous requests', function(done) {
var N = 5;
helpers.stubUtxos(server, wallet, _.range(100, 100 + N, 0), function(utxos) {
@ -2726,7 +2699,6 @@ describe('Wallet service', function() {
});
});
});
it('should create tx for type multiple_outputs', function(done) {
helpers.stubUtxos(server, wallet, [100, 200], function() {
var outputs = [{
@ -2749,7 +2721,6 @@ describe('Wallet service', function() {
});
});
});
it('should support creating a multiple output tx with no change address', function(done) {
helpers.stubUtxos(server, wallet, [1, 2], function() {
var max = 3 - (7560 / 1e8); // Fees for this tx at 100bits/kB = 7560 sat
@ -2777,7 +2748,6 @@ describe('Wallet service', function() {
});
});
});
it('should fail to create tx for type multiple_outputs with missing output argument', function(done) {
helpers.stubUtxos(server, wallet, [100, 200], function() {
var outputs = [{
@ -2798,7 +2768,6 @@ describe('Wallet service', function() {
});
});
});
it('should fail to create tx for unsupported proposal type', function(done) {
helpers.stubUtxos(server, wallet, [100, 200], function() {
var txOpts = helpers.createSimpleProposalOpts('18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7', 80, TestData.copayers[0].privKey_1H_0, {
@ -2812,7 +2781,6 @@ describe('Wallet service', function() {
});
});
});
it('should be able to create tx with inputs argument', function(done) {
helpers.stubUtxos(server, wallet, [1, 3, 2], function(utxos) {
server.getUtxos({}, function(err, utxos) {
@ -2832,7 +2800,6 @@ describe('Wallet service', function() {
});
});
});
it('should be able to send max amount', function(done) {
helpers.stubUtxos(server, wallet, _.range(1, 10, 0), function() {
server.getBalance({}, function(err, balance) {
@ -2863,7 +2830,6 @@ describe('Wallet service', function() {
});
});
});
it('should be able to send max non-locked amount', function(done) {
helpers.stubUtxos(server, wallet, _.range(1, 10, 0), function() {
var txOpts = helpers.createSimpleProposalOpts('18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7', 3.5, TestData.copayers[0].privKey_1H_0);
@ -2897,7 +2863,6 @@ describe('Wallet service', function() {
});
});
});
it('should be able to send max confirmed', function(done) {
helpers.stubUtxos(server, wallet, [1, 1, 'u1', 'u1'], function() {
server.getBalance({}, function(err, balance) {
@ -2931,7 +2896,6 @@ describe('Wallet service', function() {
});
});
});
it('should not use UTXO provided in utxosToExclude option', function(done) {
helpers.stubUtxos(server, wallet, [1, 2, 3], function(utxos) {
var txOpts = helpers.createSimpleProposalOpts('18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7', 4.5, TestData.copayers[0].privKey_1H_0);
@ -2944,7 +2908,6 @@ describe('Wallet service', function() {
});
});
});
it('should use non-excluded UTXOs', function(done) {
helpers.stubUtxos(server, wallet, [1, 2], function(utxos) {
var txOpts = helpers.createSimpleProposalOpts('18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7', 0.5, TestData.copayers[0].privKey_1H_0);
@ -3398,6 +3361,24 @@ describe('Wallet service', function() {
});
});
});
it('should fail to create tx for dust amount in outputs', function(done) {
helpers.stubUtxos(server, wallet, 1, function() {
var txOpts = {
outputs: [{
toAddress: '18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7',
amount: 20e2,
}],
feePerKb: 100e2,
};
server.createTx(txOpts, function(err, tx) {
should.exist(err);
err.code.should.equal('DUST_AMOUNT');
err.message.should.equal('Amount below dust threshold');
done();
});
});
});
});
describe('Backoff time', function(done) {
@ -3918,7 +3899,6 @@ describe('Wallet service', function() {
});
});
});
});
describe('Transaction notes', function(done) {