diff --git a/lib/server.js b/lib/server.js index 81acde4..7d4e569 100644 --- a/lib/server.js +++ b/lib/server.js @@ -1243,6 +1243,9 @@ WalletService.prototype._checkTxAndEstimateFee = function(txp) { txp.estimateFee(); + if (txp.getEstimatedSize() / 1000 > Defaults.MAX_TX_SIZE_IN_KB) + return Errors.TX_MAX_SIZE_EXCEEDED; + try { var bitcoreTx = txp.getBitcoreTx(); bitcoreError = bitcoreTx.getSerializationError(serializationOpts); @@ -1337,8 +1340,6 @@ WalletService.prototype._selectTxInputs = function(txp, utxosToExclude, cb) { txp.setInputs(selected); bitcoreError = self._checkTxAndEstimateFee(txp); if (!bitcoreError) return cb(); - if (txp.getEstimatedSize() / 1000 > Defaults.MAX_TX_SIZE_IN_KB) - return cb(Errors.TX_MAX_SIZE_EXCEEDED); } setTimeout(select, 0); }; @@ -1404,10 +1405,15 @@ WalletService.prototype._selectTxInputs2 = function(txp, utxosToExclude, cb) { var sizePerInput = txp.getEstimatedSizeForSingleInput(); var feePerInput = sizePerInput * txp.feePerKb / 1000.; - var totalValueInUtxos = _.sum(utxos, 'satoshis') - baseTxpFee - (utxos.length * feePerInput); + var totalValueInUtxos = _.sum(utxos, 'satoshis'); + var netValueInUtxos = totalValueInUtxos - baseTxpFee - (utxos.length * feePerInput); if (totalValueInUtxos < txpAmount) { - log.debug('Value in all utxos (' + formatAmount(totalValueInUtxos) + ') is inusufficient to cover for txp amount (' + formatAmount(txpAmount) + ')'); // TODO - return false; + log.debug('Total value in all utxos (' + formatAmount(totalValueInUtxos) + ') is insufficient to cover for txp amount (' + formatAmount(txpAmount) + ')'); // TODO + return Errors.INSUFFICIENT_FUNDS; + } + if (netValueInUtxos < txpAmount) { + log.debug('Value after fees in all utxos (' + formatAmount(netValueInUtxos) + ') is insufficient to cover for txp amount (' + formatAmount(txpAmount) + ')'); // TODO + return Errors.INSUFFICIENT_FUNDS_FOR_FEE; } var bigInputThreshold = txpAmount * Defaults.UTXO_SELECTION_MAX_SINGLE_UTXO_FACTOR + (baseTxpFee + feePerInput); @@ -1427,6 +1433,7 @@ WalletService.prototype._selectTxInputs2 = function(txp, utxosToExclude, cb) { var total = 0; var selected = []; + var error; _.each(smallInputs, function(input, i) { log.debug('Input #' + i + ': ' + formatInputs(input)); @@ -1444,6 +1451,8 @@ WalletService.prototype._selectTxInputs2 = function(txp, utxosToExclude, cb) { var txpSize = baseTxpSize + selected.length * sizePerInput; var txpFee = baseTxpFee + selected.length * feePerInput; + log.debug('Tx size: ' + formatSize(txpSize) + ', Tx fee: ' + formatAmount(txpFee)); + var amountVsFeeRatio = txpFee / txpAmount; var singleInputFeeVsFeeRatio = txpFee / (baseTxpFee + feePerInput); var amountVsUtxoRatio = inputAmount / txpAmount; @@ -1452,6 +1461,12 @@ WalletService.prototype._selectTxInputs2 = function(txp, utxosToExclude, cb) { log.debug('Single-input fee/Multi-input fee: ' + formatRatio(singleInputFeeVsFeeRatio) + ' (max: ' + formatRatio(Defaults.UTXO_SELECTION_MAX_FEE_VS_SINGLE_UTXO_FEE_FACTOR) + ')' + ' loses wrt single-input tx: ' + formatAmount((selected.length - 1) * feePerInput)); log.debug('Tx amount/input amount:' + formatRatio(amountVsUtxoRatio) + ' (min: ' + formatRatio(Defaults.UTXO_SELECTION_MIN_TX_AMOUNT_VS_UTXO_FACTOR) + ')'); + if (txpSize / 1000. > Defaults.MAX_TX_SIZE_IN_KB) { + log.debug('Breaking because tx size (' + formatSize(txpSize) + ') is too big (max: ' + formatSize(Defaults.MAX_TX_SIZE_IN_KB * 1000.) + ')'); // TODO + error = Errors.TX_MAX_SIZE_EXCEEDED; + return false; + } + if (!_.isEmpty(bigInputs)) { if ((amountVsFeeRatio > Defaults.UTXO_SELECTION_MAX_TX_AMOUNT_VS_FEE_FACTOR && singleInputFeeVsFeeRatio > Defaults.UTXO_SELECTION_MAX_FEE_VS_SINGLE_UTXO_FEE_FACTOR)) { @@ -1463,12 +1478,6 @@ WalletService.prototype._selectTxInputs2 = function(txp, utxosToExclude, cb) { log.debug('Breaking because utxo is too small compared to tx amount'); return false; } - - if (txpSize / 1000. > Defaults.MAX_TX_SIZE_IN_KB) { - log.debug('Breaking because tx size is too big (' + formatSize(txpSize) + ')'); // TODO - - return false; - } } total += inputAmount; @@ -1489,11 +1498,9 @@ WalletService.prototype._selectTxInputs2 = function(txp, utxosToExclude, cb) { } } - if (!_.isEmpty(selected)) { - var lockedOverhead = total - txpAmount; - log.debug('SUCCESS! Total locked: ' + formatAmount(total) + ', overhead: ' + formatAmount(lockedOverhead) + ' (' + formatRatio(lockedOverhead / txpAmount) + ')'); - } else { - log.debug('Could not find enough funds within this utxso subset'); + if (_.isEmpty(selected)) { + log.debug('Could not find enough funds within this utxo subset'); + return error || Errors.INSUFFICIENT_FUNDS_FOR_FEE; } return selected; @@ -1531,6 +1538,7 @@ WalletService.prototype._selectTxInputs2 = function(txp, utxosToExclude, cb) { var inputs = []; var groups = [6, 1, 0]; + var error; var lastGroupLength; _.each(groups, function(group) { var candidateUtxos = _.filter(utxos, function(utxo) { @@ -1549,20 +1557,25 @@ WalletService.prototype._selectTxInputs2 = function(txp, utxosToExclude, cb) { lastGroupLength = candidateUtxos.length; - inputs = select(candidateUtxos); + var result = select(candidateUtxos); + if (result && !_.isArray(result)) { + error = result; + } else { + inputs = result; + error = null; + } log.debug('Selected inputs from this group: ' + formatInputs(inputs)); if (!_.isEmpty(inputs)) return false; }); - if (_.isEmpty(inputs)) return cb(Errors.INSUFFICIENT_FUNDS_FOR_FEE); + if (error) return cb(error); txp.setInputs(inputs); - if (txp.getEstimatedSize() / 1000 > Defaults.MAX_TX_SIZE_IN_KB) - return cb(Errors.TX_MAX_SIZE_EXCEEDED); var err = self._checkTxAndEstimateFee(txp); + if (!err) { log.debug('Successfully built transaction. Total fees: ', formatAmount(txp.fee)); } else { diff --git a/test/integration/server.js b/test/integration/server.js index 2dded8c..507d10d 100644 --- a/test/integration/server.js +++ b/test/integration/server.js @@ -2132,6 +2132,7 @@ describe('Wallet service', function() { }); it('should use unconfirmed utxos only when no more confirmed utxos are available', function(done) { + // log.level = 'debug'; 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, { message: 'some message' @@ -2354,10 +2355,11 @@ describe('Wallet service', function() { }); it('should fail to create a tx exceeding max size in kb', function(done) { + log.level = 'debug'; + var _oldDefault = Defaults.MAX_TX_SIZE_IN_KB; + Defaults.MAX_TX_SIZE_IN_KB = 1; helpers.stubUtxos(server, wallet, _.range(1, 10, 0), function() { var txOpts = helpers.createSimpleProposalOpts('18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7', 8, TestData.copayers[0].privKey_1H_0); - var _oldDefault = Defaults.MAX_TX_SIZE_IN_KB; - Defaults.MAX_TX_SIZE_IN_KB = 1; server.createTxLegacy(txOpts, function(err, tx) { should.exist(err); err.code.should.equal('TX_MAX_SIZE_EXCEEDED');