From 900e5ed51bb9d52ae496f1ed4a3b58703de53a12 Mon Sep 17 00:00:00 2001 From: furszy Date: Tue, 21 May 2024 11:18:53 -0300 Subject: [PATCH] wallet: introduce "tx amount exceeds balance when fees are included" error This was previously implemented at the GUI level but has been broken since #20640 --- src/wallet/spend.cpp | 14 +++++++++++++- test/functional/wallet_fundrawtransaction.py | 19 ++++++++++++++++++- 2 files changed, 31 insertions(+), 2 deletions(-) diff --git a/src/wallet/spend.cpp b/src/wallet/spend.cpp index 9a7e166e689..24053a4b7b5 100644 --- a/src/wallet/spend.cpp +++ b/src/wallet/spend.cpp @@ -1131,7 +1131,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; TRACE5(coin_selection, selected_coins, diff --git a/test/functional/wallet_fundrawtransaction.py b/test/functional/wallet_fundrawtransaction.py index 71c883f166b..6a229c6d32a 100755 --- a/test/functional/wallet_fundrawtransaction.py +++ b/test/functional/wallet_fundrawtransaction.py @@ -150,6 +150,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") @@ -1426,7 +1427,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") @@ -1489,5 +1491,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().main()