Skip to content

Commit c7c7ee9

Browse files
aureleoulesachow101
andcommitted
test: Check max transaction weight in CoinSelection
Co-authored-by: Andrew Chow <[email protected]>
1 parent 6b563ca commit c7c7ee9

File tree

1 file changed

+119
-0
lines changed

1 file changed

+119
-0
lines changed

src/wallet/test/coinselector_tests.cpp

Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
#include <consensus/amount.h>
66
#include <node/context.h>
7+
#include <policy/policy.h>
78
#include <primitives/transaction.h>
89
#include <random.h>
910
#include <test/util/setup_common.h>
@@ -930,6 +931,124 @@ BOOST_AUTO_TEST_CASE(effective_value_test)
930931
BOOST_CHECK_EQUAL(output5.GetEffectiveValue(), nValue); // The effective value should be equal to the absolute value if input_bytes is -1
931932
}
932933

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+
9331052
BOOST_AUTO_TEST_CASE(SelectCoins_effective_value_test)
9341053
{
9351054
// 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

Comments
 (0)