From aa2a08ac04c43bae4345474a3382f8c10c1901d1 Mon Sep 17 00:00:00 2001 From: Ivan Socolsky Date: Fri, 10 Jun 2016 13:50:57 -0300 Subject: [PATCH] hardcoded dust threshold --- lib/common/defaults.js | 4 +-- lib/server.js | 15 ++++++++-- test/integration/server.js | 56 ++++++++++++-------------------------- 3 files changed, 32 insertions(+), 43 deletions(-) diff --git a/lib/common/defaults.js b/lib/common/defaults.js index 14c9198..beb4b9f 100644 --- a/lib/common/defaults.js +++ b/lib/common/defaults.js @@ -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; diff --git a/lib/server.js b/lib/server.js index c3bade5..4fdd829 100644 --- a/lib/server.js +++ b/lib/server.js @@ -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); diff --git a/test/integration/server.js b/test/integration/server.js index 78e8bf7..36dc2a3 100644 --- a/test/integration/server.js +++ b/test/integration/server.js @@ -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) {