diff options
author | Ava Chow <github@achow101.com> | 2024-06-26 12:16:16 -0400 |
---|---|---|
committer | Ava Chow <github@achow101.com> | 2024-06-26 12:16:16 -0400 |
commit | 1d00601b9b559b305e75830121dd30e207ef0d08 (patch) | |
tree | 14768084d14b1eefa826d9d570096a833ed66016 | |
parent | d3d2bbf576d07f729dbb5eafce4ae6d4cc7dd763 (diff) | |
parent | 72b226882fe2348a9a66aee1d8d21b4e2d275e68 (diff) |
Merge bitcoin/bitcoin#30309: wallet: notify when preset + automatic inputs exceed max weight
72b226882fe2348a9a66aee1d8d21b4e2d275e68 wallet: notify when preset + automatic inputs exceed max weight (furszy)
Pull request description:
Small change. Found it while finishing my review on #29523. This does not interfere with it.
Basically, we are erroring out early when the automatic coin selection process exceeds the maximum weight, but we are not doing so when the user-preselected inputs combined with the wallet-selected inputs exceed the maximum weight.
This change avoids signing all inputs before erroring out and introduces test coverage for `fundrawtransaction`.
ACKs for top commit:
achow101:
ACK 72b226882fe2348a9a66aee1d8d21b4e2d275e68
tdb3:
re ACK for 72b226882fe2348a9a66aee1d8d21b4e2d275e68
rkrux:
tACK [72b2268](https://github.com/bitcoin/bitcoin/pull/30309/commits/72b226882fe2348a9a66aee1d8d21b4e2d275e68)
ismaelsadeeq:
utACK 72b226882fe2348a9a66aee1d8d21b4e2d275e68
Tree-SHA512: d77be19231023383a9c79a5d66b642dcbc6ebfc31a363e0b9f063c44898720a7859ec211cdbc0914ac7a3bfdf15e52fb8fc20d97f171431f70492c0f159dbc36
-rw-r--r-- | src/wallet/spend.cpp | 7 | ||||
-rwxr-xr-x | test/functional/wallet_fundrawtransaction.py | 33 | ||||
-rwxr-xr-x | test/functional/wallet_send.py | 34 |
3 files changed, 74 insertions, 0 deletions
diff --git a/src/wallet/spend.cpp b/src/wallet/spend.cpp index b9b4666208..c16b8a9d4f 100644 --- a/src/wallet/spend.cpp +++ b/src/wallet/spend.cpp @@ -799,6 +799,13 @@ util::Result<SelectionResult> SelectCoins(const CWallet& wallet, CoinsResult& av op_selection_result->RecalculateWaste(coin_selection_params.min_viable_change, coin_selection_params.m_cost_of_change, coin_selection_params.m_change_fee); + + // Verify we haven't exceeded the maximum allowed weight + int max_inputs_weight = MAX_STANDARD_TX_WEIGHT - (coin_selection_params.tx_noinputs_size * WITNESS_SCALE_FACTOR); + if (op_selection_result->GetWeight() > max_inputs_weight) { + return util::Error{_("The combination of the pre-selected inputs and the wallet automatic inputs selection exceeds the transaction maximum weight. " + "Please try sending a smaller amount or manually consolidating your wallet's UTXOs")}; + } } return op_selection_result; } diff --git a/test/functional/wallet_fundrawtransaction.py b/test/functional/wallet_fundrawtransaction.py index 71c883f166..3c1b2deb1d 100755 --- a/test/functional/wallet_fundrawtransaction.py +++ b/test/functional/wallet_fundrawtransaction.py @@ -114,6 +114,7 @@ class RawTransactionsTest(BitcoinTestFramework): self.test_add_inputs_default_value() self.test_preset_inputs_selection() self.test_weight_calculation() + self.test_weight_limits() self.test_change_position() self.test_simple() self.test_simple_two_coins() @@ -1312,6 +1313,38 @@ class RawTransactionsTest(BitcoinTestFramework): self.nodes[2].unloadwallet("test_weight_calculation") + def test_weight_limits(self): + self.log.info("Test weight limits") + + self.nodes[2].createwallet("test_weight_limits") + wallet = self.nodes[2].get_wallet_rpc("test_weight_limits") + + outputs = [] + for _ in range(1472): + outputs.append({wallet.getnewaddress(address_type="legacy"): 0.1}) + txid = self.nodes[0].send(outputs=outputs)["txid"] + self.generate(self.nodes[0], 1) + + # 272 WU per input (273 when high-s); picking 1471 inputs will exceed the max standard tx weight. + rawtx = wallet.createrawtransaction([], [{wallet.getnewaddress(): 0.1 * 1471}]) + + # 1) Try to fund transaction only using the preset inputs + input_weights = [] + for i in range(1471): + input_weights.append({"txid": txid, "vout": i, "weight": 273}) + assert_raises_rpc_error(-4, "Transaction too large", wallet.fundrawtransaction, hexstring=rawtx, input_weights=input_weights) + + # 2) Let the wallet fund the transaction + assert_raises_rpc_error(-4, "The inputs size exceeds the maximum weight. Please try sending a smaller amount or manually consolidating your wallet's UTXOs", + wallet.fundrawtransaction, hexstring=rawtx) + + # 3) Pre-select some inputs and let the wallet fill-up the remaining amount + inputs = input_weights[0:1000] + assert_raises_rpc_error(-4, "The combination of the pre-selected inputs and the wallet automatic inputs selection exceeds the transaction maximum weight. Please try sending a smaller amount or manually consolidating your wallet's UTXOs", + wallet.fundrawtransaction, hexstring=rawtx, input_weights=inputs) + + self.nodes[2].unloadwallet("test_weight_limits") + def test_include_unsafe(self): self.log.info("Test fundrawtxn with unsafe inputs") diff --git a/test/functional/wallet_send.py b/test/functional/wallet_send.py index 0a0a8dba0d..bbb0d658d9 100755 --- a/test/functional/wallet_send.py +++ b/test/functional/wallet_send.py @@ -577,5 +577,39 @@ class WalletSendTest(BitcoinTestFramework): # but rounded to nearest integer, it should be the same as the target fee rate assert_equal(round(actual_fee_rate_sat_vb), target_fee_rate_sat_vb) + # Check tx creation size limits + self.test_weight_limits() + + def test_weight_limits(self): + self.log.info("Test weight limits") + + self.nodes[1].createwallet("test_weight_limits") + wallet = self.nodes[1].get_wallet_rpc("test_weight_limits") + + # Generate future inputs; 272 WU per input (273 when high-s). + # Picking 1471 inputs will exceed the max standard tx weight. + outputs = [] + for _ in range(1472): + outputs.append({wallet.getnewaddress(address_type="legacy"): 0.1}) + self.nodes[0].send(outputs=outputs) + self.generate(self.nodes[0], 1) + + # 1) Try to fund transaction only using the preset inputs + inputs = wallet.listunspent() + assert_raises_rpc_error(-4, "Transaction too large", + wallet.send, outputs=[{wallet.getnewaddress(): 0.1 * 1471}], options={"inputs": inputs, "add_inputs": False}) + + # 2) Let the wallet fund the transaction + assert_raises_rpc_error(-4, "The inputs size exceeds the maximum weight. Please try sending a smaller amount or manually consolidating your wallet's UTXOs", + wallet.send, outputs=[{wallet.getnewaddress(): 0.1 * 1471}]) + + # 3) Pre-select some inputs and let the wallet fill-up the remaining amount + inputs = inputs[0:1000] + assert_raises_rpc_error(-4, "The combination of the pre-selected inputs and the wallet automatic inputs selection exceeds the transaction maximum weight. Please try sending a smaller amount or manually consolidating your wallet's UTXOs", + wallet.send, outputs=[{wallet.getnewaddress(): 0.1 * 1471}], options={"inputs": inputs, "add_inputs": True}) + + self.nodes[1].unloadwallet("test_weight_limits") + + if __name__ == '__main__': WalletSendTest().main() |