aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAva Chow <github@achow101.com>2024-06-26 12:16:16 -0400
committerAva Chow <github@achow101.com>2024-06-26 12:16:16 -0400
commit1d00601b9b559b305e75830121dd30e207ef0d08 (patch)
tree14768084d14b1eefa826d9d570096a833ed66016
parentd3d2bbf576d07f729dbb5eafce4ae6d4cc7dd763 (diff)
parent72b226882fe2348a9a66aee1d8d21b4e2d275e68 (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.cpp7
-rwxr-xr-xtest/functional/wallet_fundrawtransaction.py33
-rwxr-xr-xtest/functional/wallet_send.py34
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()