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
This commit is contained in:
furszy 2024-05-21 11:18:53 -03:00
parent a786fd2041
commit 900e5ed51b
No known key found for this signature in database
GPG key ID: 5DD23CCC686AA623
2 changed files with 31 additions and 2 deletions

View file

@ -1131,7 +1131,19 @@ static util::Result<CreatedTransactionResult> CreateTransactionInternal(
if (!select_coins_res) { if (!select_coins_res) {
// 'SelectCoins' either returns a specific error message or, if empty, means a general "Insufficient funds". // 'SelectCoins' either returns a specific error message or, if empty, means a general "Insufficient funds".
const bilingual_str& err = util::ErrorString(select_coins_res); 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; const SelectionResult& result = *select_coins_res;
TRACE5(coin_selection, selected_coins, TRACE5(coin_selection, selected_coins,

View file

@ -150,6 +150,7 @@ class RawTransactionsTest(BitcoinTestFramework):
self.test_feerate_rounding() self.test_feerate_rounding()
self.test_input_confs_control() self.test_input_confs_control()
self.test_duplicate_outputs() self.test_duplicate_outputs()
self.test_cannot_cover_fees()
def test_duplicate_outputs(self): def test_duplicate_outputs(self):
self.log.info("Test deserializing and funding a transaction with duplicate outputs") 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 # 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. # fail with insufficient funds rather than bitcoind asserting.
rawtx = w.createrawtransaction(inputs=[], outputs=[{self.nodes[0].getnewaddress(address_type="bech32"): 1 - 0.00000202}]) 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): def test_input_confs_control(self):
self.nodes[0].createwallet("minconf") self.nodes[0].createwallet("minconf")
@ -1489,5 +1491,20 @@ class RawTransactionsTest(BitcoinTestFramework):
wallet.unloadwallet() 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__': if __name__ == '__main__':
RawTransactionsTest().main() RawTransactionsTest().main()