Merge bitcoin/bitcoin#30309: wallet: notify when preset + automatic inputs exceed max weight

72b226882f 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 72b226882f
  tdb3:
    re ACK for 72b226882f
  rkrux:
    tACK [72b2268](72b226882f)
  ismaelsadeeq:
    utACK 72b226882f

Tree-SHA512: d77be19231023383a9c79a5d66b642dcbc6ebfc31a363e0b9f063c44898720a7859ec211cdbc0914ac7a3bfdf15e52fb8fc20d97f171431f70492c0f159dbc36
This commit is contained in:
Ava Chow 2024-06-26 12:16:16 -04:00
commit 1d00601b9b
No known key found for this signature in database
GPG Key ID: 17565732E08E5E41
3 changed files with 74 additions and 0 deletions

View File

@ -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;
}

View File

@ -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")

View File

@ -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()