From c7c7ee9d0b2d7b303b9300f941e37e09e7d8d8b6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A8le=20Oul=C3=A8s?= Date: Tue, 4 Oct 2022 14:17:52 +0200 Subject: [PATCH] test: Check max transaction weight in CoinSelection Co-authored-by: Andrew Chow --- src/wallet/test/coinselector_tests.cpp | 119 +++++++++++++++++++++++++ 1 file changed, 119 insertions(+) diff --git a/src/wallet/test/coinselector_tests.cpp b/src/wallet/test/coinselector_tests.cpp index c764264cd7a..ce875d54429 100644 --- a/src/wallet/test/coinselector_tests.cpp +++ b/src/wallet/test/coinselector_tests.cpp @@ -4,6 +4,7 @@ #include #include +#include #include #include #include @@ -930,6 +931,124 @@ BOOST_AUTO_TEST_CASE(effective_value_test) BOOST_CHECK_EQUAL(output5.GetEffectiveValue(), nValue); // The effective value should be equal to the absolute value if input_bytes is -1 } +static std::optional select_coins(const CAmount& target, const CoinSelectionParams& cs_params, const CCoinControl& cc, std::function coin_setup, interfaces::Chain* chain, const ArgsManager& args) +{ + std::unique_ptr wallet = std::make_unique(chain, "", args, CreateMockWalletDatabase()); + wallet->LoadWallet(); + LOCK(wallet->cs_wallet); + wallet->SetWalletFlag(WALLET_FLAG_DESCRIPTORS); + wallet->SetupDescriptorScriptPubKeyMans(); + + auto available_coins = coin_setup(*wallet); + + const auto result = SelectCoins(*wallet, available_coins, /*pre_set_inputs=*/ {}, target, cc, cs_params); + if (result) { + const auto signedTxSize = 10 + 34 + 68 * result->GetInputSet().size(); // static header size + output size + inputs size (P2WPKH) + BOOST_CHECK_LE(signedTxSize * WITNESS_SCALE_FACTOR, MAX_STANDARD_TX_WEIGHT); + + BOOST_CHECK_GE(result->GetSelectedValue(), target); + } + return result; +} + +static bool has_coin(const CoinSet& set, CAmount amount) +{ + return std::any_of(set.begin(), set.end(), [&](const auto& coin) { return coin.GetEffectiveValue() == amount; }); +} + +BOOST_AUTO_TEST_CASE(check_max_weight) +{ + const CAmount target = 49.5L * COIN; + CCoinControl cc; + + FastRandomContext rand; + CoinSelectionParams cs_params{ + rand, + /*change_output_size=*/34, + /*change_spend_size=*/68, + /*min_change_target=*/CENT, + /*effective_feerate=*/CFeeRate(0), + /*long_term_feerate=*/CFeeRate(0), + /*discard_feerate=*/CFeeRate(0), + /*tx_noinputs_size=*/10 + 34, // static header size + output size + /*avoid_partial=*/false, + }; + + auto chain{m_node.chain.get()}; + + { + // Scenario 1: + // The actor starts with 1x 50.0 BTC and 1515x 0.033 BTC (~100.0 BTC total) unspent outputs + // Then tries to spend 49.5 BTC + // The 50.0 BTC output should be selected, because the transaction would otherwise be too large + + // Perform selection + + const auto result = select_coins( + target, cs_params, cc, [&](CWallet& wallet) { + CoinsResult available_coins; + for (int j = 0; j < 1515; ++j) { + add_coin(available_coins, wallet, CAmount(0.033 * COIN), CFeeRate(0), 144, false, 0, true); + } + + add_coin(available_coins, wallet, CAmount(50 * COIN), CFeeRate(0), 144, false, 0, true); + return available_coins; + }, + chain, m_args); + + BOOST_CHECK(result); + BOOST_CHECK(has_coin(result->GetInputSet(), CAmount(50 * COIN))); + } + + { + // Scenario 2: + + // The actor starts with 400x 0.0625 BTC and 2000x 0.025 BTC (75.0 BTC total) unspent outputs + // Then tries to spend 49.5 BTC + // A combination of coins should be selected, such that the created transaction is not too large + + // Perform selection + const auto result = select_coins( + target, cs_params, cc, [&](CWallet& wallet) { + CoinsResult available_coins; + for (int j = 0; j < 400; ++j) { + add_coin(available_coins, wallet, CAmount(0.0625 * COIN), CFeeRate(0), 144, false, 0, true); + } + for (int j = 0; j < 2000; ++j) { + add_coin(available_coins, wallet, CAmount(0.025 * COIN), CFeeRate(0), 144, false, 0, true); + } + return available_coins; + }, + chain, m_args); + + BOOST_CHECK(has_coin(result->GetInputSet(), CAmount(0.0625 * COIN))); + BOOST_CHECK(has_coin(result->GetInputSet(), CAmount(0.025 * COIN))); + } + + { + // Scenario 3: + + // The actor starts with 1515x 0.033 BTC (49.995 BTC total) unspent outputs + // No results should be returned, because the transaction would be too large + + // Perform selection + const auto result = select_coins( + target, cs_params, cc, [&](CWallet& wallet) { + CoinsResult available_coins; + for (int j = 0; j < 1515; ++j) { + add_coin(available_coins, wallet, CAmount(0.033 * COIN), CFeeRate(0), 144, false, 0, true); + } + return available_coins; + }, + chain, m_args); + + // No results + // 1515 inputs * 68 bytes = 103,020 bytes + // 103,020 bytes * 4 = 412,080 weight, which is above the MAX_STANDARD_TX_WEIGHT of 400,000 + BOOST_CHECK(!result); + } +} + BOOST_AUTO_TEST_CASE(SelectCoins_effective_value_test) { // Test that the effective value is used to check whether preset inputs provide sufficient funds when subtract_fee_outputs is not used.