|
4 | 4 |
|
5 | 5 | #include <consensus/amount.h>
|
6 | 6 | #include <node/context.h>
|
| 7 | +#include <policy/policy.h> |
7 | 8 | #include <primitives/transaction.h>
|
8 | 9 | #include <random.h>
|
9 | 10 | #include <test/util/setup_common.h>
|
@@ -930,6 +931,124 @@ BOOST_AUTO_TEST_CASE(effective_value_test)
|
930 | 931 | BOOST_CHECK_EQUAL(output5.GetEffectiveValue(), nValue); // The effective value should be equal to the absolute value if input_bytes is -1
|
931 | 932 | }
|
932 | 933 |
|
| 934 | +static std::optional<SelectionResult> select_coins(const CAmount& target, const CoinSelectionParams& cs_params, const CCoinControl& cc, std::function<CoinsResult(CWallet&)> coin_setup, interfaces::Chain* chain, const ArgsManager& args) |
| 935 | +{ |
| 936 | + std::unique_ptr<CWallet> wallet = std::make_unique<CWallet>(chain, "", args, CreateMockWalletDatabase()); |
| 937 | + wallet->LoadWallet(); |
| 938 | + LOCK(wallet->cs_wallet); |
| 939 | + wallet->SetWalletFlag(WALLET_FLAG_DESCRIPTORS); |
| 940 | + wallet->SetupDescriptorScriptPubKeyMans(); |
| 941 | + |
| 942 | + auto available_coins = coin_setup(*wallet); |
| 943 | + |
| 944 | + const auto result = SelectCoins(*wallet, available_coins, /*pre_set_inputs=*/ {}, target, cc, cs_params); |
| 945 | + if (result) { |
| 946 | + const auto signedTxSize = 10 + 34 + 68 * result->GetInputSet().size(); // static header size + output size + inputs size (P2WPKH) |
| 947 | + BOOST_CHECK_LE(signedTxSize * WITNESS_SCALE_FACTOR, MAX_STANDARD_TX_WEIGHT); |
| 948 | + |
| 949 | + BOOST_CHECK_GE(result->GetSelectedValue(), target); |
| 950 | + } |
| 951 | + return result; |
| 952 | +} |
| 953 | + |
| 954 | +static bool has_coin(const CoinSet& set, CAmount amount) |
| 955 | +{ |
| 956 | + return std::any_of(set.begin(), set.end(), [&](const auto& coin) { return coin.GetEffectiveValue() == amount; }); |
| 957 | +} |
| 958 | + |
| 959 | +BOOST_AUTO_TEST_CASE(check_max_weight) |
| 960 | +{ |
| 961 | + const CAmount target = 49.5L * COIN; |
| 962 | + CCoinControl cc; |
| 963 | + |
| 964 | + FastRandomContext rand; |
| 965 | + CoinSelectionParams cs_params{ |
| 966 | + rand, |
| 967 | + /*change_output_size=*/34, |
| 968 | + /*change_spend_size=*/68, |
| 969 | + /*min_change_target=*/CENT, |
| 970 | + /*effective_feerate=*/CFeeRate(0), |
| 971 | + /*long_term_feerate=*/CFeeRate(0), |
| 972 | + /*discard_feerate=*/CFeeRate(0), |
| 973 | + /*tx_noinputs_size=*/10 + 34, // static header size + output size |
| 974 | + /*avoid_partial=*/false, |
| 975 | + }; |
| 976 | + |
| 977 | + auto chain{m_node.chain.get()}; |
| 978 | + |
| 979 | + { |
| 980 | + // Scenario 1: |
| 981 | + // The actor starts with 1x 50.0 BTC and 1515x 0.033 BTC (~100.0 BTC total) unspent outputs |
| 982 | + // Then tries to spend 49.5 BTC |
| 983 | + // The 50.0 BTC output should be selected, because the transaction would otherwise be too large |
| 984 | + |
| 985 | + // Perform selection |
| 986 | + |
| 987 | + const auto result = select_coins( |
| 988 | + target, cs_params, cc, [&](CWallet& wallet) { |
| 989 | + CoinsResult available_coins; |
| 990 | + for (int j = 0; j < 1515; ++j) { |
| 991 | + add_coin(available_coins, wallet, CAmount(0.033 * COIN), CFeeRate(0), 144, false, 0, true); |
| 992 | + } |
| 993 | + |
| 994 | + add_coin(available_coins, wallet, CAmount(50 * COIN), CFeeRate(0), 144, false, 0, true); |
| 995 | + return available_coins; |
| 996 | + }, |
| 997 | + chain, m_args); |
| 998 | + |
| 999 | + BOOST_CHECK(result); |
| 1000 | + BOOST_CHECK(has_coin(result->GetInputSet(), CAmount(50 * COIN))); |
| 1001 | + } |
| 1002 | + |
| 1003 | + { |
| 1004 | + // Scenario 2: |
| 1005 | + |
| 1006 | + // The actor starts with 400x 0.0625 BTC and 2000x 0.025 BTC (75.0 BTC total) unspent outputs |
| 1007 | + // Then tries to spend 49.5 BTC |
| 1008 | + // A combination of coins should be selected, such that the created transaction is not too large |
| 1009 | + |
| 1010 | + // Perform selection |
| 1011 | + const auto result = select_coins( |
| 1012 | + target, cs_params, cc, [&](CWallet& wallet) { |
| 1013 | + CoinsResult available_coins; |
| 1014 | + for (int j = 0; j < 400; ++j) { |
| 1015 | + add_coin(available_coins, wallet, CAmount(0.0625 * COIN), CFeeRate(0), 144, false, 0, true); |
| 1016 | + } |
| 1017 | + for (int j = 0; j < 2000; ++j) { |
| 1018 | + add_coin(available_coins, wallet, CAmount(0.025 * COIN), CFeeRate(0), 144, false, 0, true); |
| 1019 | + } |
| 1020 | + return available_coins; |
| 1021 | + }, |
| 1022 | + chain, m_args); |
| 1023 | + |
| 1024 | + BOOST_CHECK(has_coin(result->GetInputSet(), CAmount(0.0625 * COIN))); |
| 1025 | + BOOST_CHECK(has_coin(result->GetInputSet(), CAmount(0.025 * COIN))); |
| 1026 | + } |
| 1027 | + |
| 1028 | + { |
| 1029 | + // Scenario 3: |
| 1030 | + |
| 1031 | + // The actor starts with 1515x 0.033 BTC (49.995 BTC total) unspent outputs |
| 1032 | + // No results should be returned, because the transaction would be too large |
| 1033 | + |
| 1034 | + // Perform selection |
| 1035 | + const auto result = select_coins( |
| 1036 | + target, cs_params, cc, [&](CWallet& wallet) { |
| 1037 | + CoinsResult available_coins; |
| 1038 | + for (int j = 0; j < 1515; ++j) { |
| 1039 | + add_coin(available_coins, wallet, CAmount(0.033 * COIN), CFeeRate(0), 144, false, 0, true); |
| 1040 | + } |
| 1041 | + return available_coins; |
| 1042 | + }, |
| 1043 | + chain, m_args); |
| 1044 | + |
| 1045 | + // No results |
| 1046 | + // 1515 inputs * 68 bytes = 103,020 bytes |
| 1047 | + // 103,020 bytes * 4 = 412,080 weight, which is above the MAX_STANDARD_TX_WEIGHT of 400,000 |
| 1048 | + BOOST_CHECK(!result); |
| 1049 | + } |
| 1050 | +} |
| 1051 | + |
933 | 1052 | BOOST_AUTO_TEST_CASE(SelectCoins_effective_value_test)
|
934 | 1053 | {
|
935 | 1054 | // Test that the effective value is used to check whether preset inputs provide sufficient funds when subtract_fee_outputs is not used.
|
|
0 commit comments