Skip to content

Commit 72b2268

Browse files
committed
wallet: notify when preset + automatic inputs exceed max weight
This also avoids signing all inputs prior to erroring out.
1 parent 2d21060 commit 72b2268

File tree

3 files changed

+74
-0
lines changed

3 files changed

+74
-0
lines changed

src/wallet/spend.cpp

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -799,6 +799,13 @@ util::Result<SelectionResult> SelectCoins(const CWallet& wallet, CoinsResult& av
799799
op_selection_result->RecalculateWaste(coin_selection_params.min_viable_change,
800800
coin_selection_params.m_cost_of_change,
801801
coin_selection_params.m_change_fee);
802+
803+
// Verify we haven't exceeded the maximum allowed weight
804+
int max_inputs_weight = MAX_STANDARD_TX_WEIGHT - (coin_selection_params.tx_noinputs_size * WITNESS_SCALE_FACTOR);
805+
if (op_selection_result->GetWeight() > max_inputs_weight) {
806+
return util::Error{_("The combination of the pre-selected inputs and the wallet automatic inputs selection exceeds the transaction maximum weight. "
807+
"Please try sending a smaller amount or manually consolidating your wallet's UTXOs")};
808+
}
802809
}
803810
return op_selection_result;
804811
}

test/functional/wallet_fundrawtransaction.py

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,7 @@ def run_test(self):
114114
self.test_add_inputs_default_value()
115115
self.test_preset_inputs_selection()
116116
self.test_weight_calculation()
117+
self.test_weight_limits()
117118
self.test_change_position()
118119
self.test_simple()
119120
self.test_simple_two_coins()
@@ -1312,6 +1313,38 @@ def test_weight_calculation(self):
13121313

13131314
self.nodes[2].unloadwallet("test_weight_calculation")
13141315

1316+
def test_weight_limits(self):
1317+
self.log.info("Test weight limits")
1318+
1319+
self.nodes[2].createwallet("test_weight_limits")
1320+
wallet = self.nodes[2].get_wallet_rpc("test_weight_limits")
1321+
1322+
outputs = []
1323+
for _ in range(1472):
1324+
outputs.append({wallet.getnewaddress(address_type="legacy"): 0.1})
1325+
txid = self.nodes[0].send(outputs=outputs)["txid"]
1326+
self.generate(self.nodes[0], 1)
1327+
1328+
# 272 WU per input (273 when high-s); picking 1471 inputs will exceed the max standard tx weight.
1329+
rawtx = wallet.createrawtransaction([], [{wallet.getnewaddress(): 0.1 * 1471}])
1330+
1331+
# 1) Try to fund transaction only using the preset inputs
1332+
input_weights = []
1333+
for i in range(1471):
1334+
input_weights.append({"txid": txid, "vout": i, "weight": 273})
1335+
assert_raises_rpc_error(-4, "Transaction too large", wallet.fundrawtransaction, hexstring=rawtx, input_weights=input_weights)
1336+
1337+
# 2) Let the wallet fund the transaction
1338+
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",
1339+
wallet.fundrawtransaction, hexstring=rawtx)
1340+
1341+
# 3) Pre-select some inputs and let the wallet fill-up the remaining amount
1342+
inputs = input_weights[0:1000]
1343+
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",
1344+
wallet.fundrawtransaction, hexstring=rawtx, input_weights=inputs)
1345+
1346+
self.nodes[2].unloadwallet("test_weight_limits")
1347+
13151348
def test_include_unsafe(self):
13161349
self.log.info("Test fundrawtxn with unsafe inputs")
13171350

test/functional/wallet_send.py

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -577,5 +577,39 @@ def run_test(self):
577577
# but rounded to nearest integer, it should be the same as the target fee rate
578578
assert_equal(round(actual_fee_rate_sat_vb), target_fee_rate_sat_vb)
579579

580+
# Check tx creation size limits
581+
self.test_weight_limits()
582+
583+
def test_weight_limits(self):
584+
self.log.info("Test weight limits")
585+
586+
self.nodes[1].createwallet("test_weight_limits")
587+
wallet = self.nodes[1].get_wallet_rpc("test_weight_limits")
588+
589+
# Generate future inputs; 272 WU per input (273 when high-s).
590+
# Picking 1471 inputs will exceed the max standard tx weight.
591+
outputs = []
592+
for _ in range(1472):
593+
outputs.append({wallet.getnewaddress(address_type="legacy"): 0.1})
594+
self.nodes[0].send(outputs=outputs)
595+
self.generate(self.nodes[0], 1)
596+
597+
# 1) Try to fund transaction only using the preset inputs
598+
inputs = wallet.listunspent()
599+
assert_raises_rpc_error(-4, "Transaction too large",
600+
wallet.send, outputs=[{wallet.getnewaddress(): 0.1 * 1471}], options={"inputs": inputs, "add_inputs": False})
601+
602+
# 2) Let the wallet fund the transaction
603+
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",
604+
wallet.send, outputs=[{wallet.getnewaddress(): 0.1 * 1471}])
605+
606+
# 3) Pre-select some inputs and let the wallet fill-up the remaining amount
607+
inputs = inputs[0:1000]
608+
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",
609+
wallet.send, outputs=[{wallet.getnewaddress(): 0.1 * 1471}], options={"inputs": inputs, "add_inputs": True})
610+
611+
self.nodes[1].unloadwallet("test_weight_limits")
612+
613+
580614
if __name__ == '__main__':
581615
WalletSendTest().main()

0 commit comments

Comments
 (0)