diff --git a/lib/common/defaults.js b/lib/common/defaults.js index 2a0af108f..b57568bc2 100644 --- a/lib/common/defaults.js +++ b/lib/common/defaults.js @@ -63,4 +63,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; + module.exports = Defaults; diff --git a/lib/server.js b/lib/server.js index c3604b5fb..d07ec6672 100644 --- a/lib/server.js +++ b/lib/server.js @@ -1480,8 +1480,9 @@ WalletService.prototype._selectTxInputs = function(txp, utxosToExclude, cb) { var changeAmount = Math.round(total - txpAmount - fee); log.debug('Tx change: ', Utils.formatAmountInBtc(changeAmount)); - if (changeAmount > 0 && changeAmount <= Bitcore.Transaction.DUST_AMOUNT) { - log.debug('Change below dust amount (' + Utils.formatAmountInBtc(Bitcore.Transaction.DUST_AMOUNT) + ')'); + 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.'); // Remove dust change by incrementing fee fee += changeAmount; } diff --git a/test/integration/server.js b/test/integration/server.js index 10d113be7..f8ac9bbc2 100644 --- a/test/integration/server.js +++ b/test/integration/server.js @@ -2326,18 +2326,18 @@ 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.99995, TestData.copayers[0].privKey_1H_0, { + var txOpts = helpers.createSimpleProposalOpts('18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7', 0.9999, TestData.copayers[0].privKey_1H_0, { feePerKb: 80000 }); server.createTxLegacy(txOpts, function(err, tx) { should.exist(err); err.code.should.equal('INSUFFICIENT_FUNDS_FOR_FEE'); - var txOpts = helpers.createSimpleProposalOpts('18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7', 0.99995, TestData.copayers[0].privKey_1H_0, { + var txOpts = helpers.createSimpleProposalOpts('18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7', 0.9999, TestData.copayers[0].privKey_1H_0, { feePerKb: 5000 }); server.createTxLegacy(txOpts, function(err, tx) { should.not.exist(err); - var estimatedFee = 5000 * 400 / 1000; // fully signed tx should have about 400 bytes + var estimatedFee = 5000 * 410 / 1000; // fully signed tx should have about 410 bytes tx.fee.should.be.within(0.9 * estimatedFee, 1.1 * estimatedFee); // Sign it to make sure Bitcore doesn't complain about the fees @@ -3597,17 +3597,18 @@ describe('Wallet service', function() { }); }); }); - it('should correct fee if resulting change would be below dust', function(done) { + it('should correct fee if resulting change would be below threshold', function(done) { helpers.stubUtxos(server, wallet, ['200bit', '500sat'], function() { var txOpts = { outputs: [{ toAddress: '18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7', - amount: 200e2, + amount: 150e2, }], - feePerKb: 400, + feePerKb: 100e2, }; server.createTx(txOpts, function(err, txp) { should.not.exist(err); + txp.inputs.length.should.equal(1); (_.sum(txp.inputs, 'satoshis') - txp.outputs[0].amount - txp.fee).should.equal(0); var changeOutput = txp.getBitcoreTx().getChangeOutput(); should.not.exist(changeOutput);