diff --git a/src/wallet/spend.cpp b/src/wallet/spend.cpp index 7b26cf15bd3..ea70287f3f7 100644 --- a/src/wallet/spend.cpp +++ b/src/wallet/spend.cpp @@ -1161,7 +1161,19 @@ static util::Result CreateTransactionInternal( if (!select_coins_res) { // 'SelectCoins' either returns a specific error message or, if empty, means a general "Insufficient funds". const bilingual_str& err = util::ErrorString(select_coins_res); - return util::Error{err.empty() ?_("Insufficient funds") : err}; + if (!err.empty()) return util::Error{err}; + + // Check if we have enough balance but cannot cover the fees + CAmount available_balance = preset_inputs.total_amount + available_coins.GetTotalAmount(); + if (available_balance >= recipients_sum) { + CAmount available_effective_balance = preset_inputs.total_amount + available_coins.GetEffectiveTotalAmount().value_or(available_coins.GetTotalAmount()); + if (available_effective_balance < selection_target) { + return util::Error{_("The total transaction amount exceeds your balance when fees are included")}; + } + } + + // General failure description + return util::Error{_("Insufficient funds")}; } const SelectionResult& result = *select_coins_res; TRACEPOINT(coin_selection, selected_coins, diff --git a/test/functional/wallet_fundrawtransaction.py b/test/functional/wallet_fundrawtransaction.py index 827f27b431c..e6a8f7507d3 100755 --- a/test/functional/wallet_fundrawtransaction.py +++ b/test/functional/wallet_fundrawtransaction.py @@ -151,6 +151,7 @@ class RawTransactionsTest(BitcoinTestFramework): self.test_feerate_rounding() self.test_input_confs_control() self.test_duplicate_outputs() + self.test_cannot_cover_fees() def test_duplicate_outputs(self): self.log.info("Test deserializing and funding a transaction with duplicate outputs") @@ -1459,7 +1460,8 @@ class RawTransactionsTest(BitcoinTestFramework): # To test this does not happen, we subtract 202 sats from the input value. If working correctly, this should # fail with insufficient funds rather than bitcoind asserting. rawtx = w.createrawtransaction(inputs=[], outputs=[{self.nodes[0].getnewaddress(address_type="bech32"): 1 - 0.00000202}]) - assert_raises_rpc_error(-4, "Insufficient funds", w.fundrawtransaction, rawtx, fee_rate=1.85) + expected_err_msg = "The total transaction amount exceeds your balance when fees are included" + assert_raises_rpc_error(-4, expected_err_msg, w.fundrawtransaction, rawtx, fee_rate=1.85) def test_input_confs_control(self): self.nodes[0].createwallet("minconf") @@ -1522,5 +1524,20 @@ class RawTransactionsTest(BitcoinTestFramework): wallet.unloadwallet() + def test_cannot_cover_fees(self): + self.log.info("Test tx amount exceeds available balance when fees are included") + + self.nodes[1].createwallet("cannot_cover_fees") + wallet = self.nodes[1].get_wallet_rpc("cannot_cover_fees") + + self.nodes[0].sendtoaddress(wallet.getnewaddress(), 0.3) + self.generate(self.nodes[0], 1) + + rawtx = wallet.createrawtransaction(inputs=[], outputs=[{self.nodes[0].getnewaddress(): 0.3}]) + expected_err_msg = "The total transaction amount exceeds your balance when fees are included" + assert_raises_rpc_error(-4, expected_err_msg, wallet.fundrawtransaction, rawtx) + wallet.unloadwallet() + + if __name__ == '__main__': RawTransactionsTest(__file__).main()