20 #include <boost/test/unit_test.hpp> 31 #define RANDOM_REPEATS 5 33 typedef std::set<std::shared_ptr<COutput>>
CoinSet;
40 static void add_coin(
const CAmount& nValue,
int nInput, std::vector<COutput>&
set)
43 tx.
vout.resize(nInput + 1);
44 tx.
vout[nInput].nValue = nValue;
46 set.emplace_back(
COutPoint(tx.
GetHash(), nInput), tx.
vout.at(nInput), 1, -1,
true,
true,
true, 0,
false, 0);
52 tx.
vout.resize(nInput + 1);
53 tx.
vout[nInput].nValue = nValue;
55 COutput output(
COutPoint(tx.
GetHash(), nInput), tx.
vout.at(nInput), 1, -1,
true,
true,
true, 0,
false, 0);
57 group.Insert(std::make_shared<COutput>(output), 0, 0);
64 tx.
vout.resize(nInput + 1);
65 tx.
vout[nInput].nValue = nValue;
67 std::shared_ptr<COutput> coin = std::make_shared<COutput>(
COutPoint(tx.
GetHash(), nInput), tx.
vout.at(nInput), 1, 148,
true,
true,
true, 0,
false,
fee);
69 group.Insert(coin, 0, 0);
70 coin->long_term_fee = long_term_fee;
78 tx.
vout.resize(nInput + 1);
79 tx.
vout[nInput].nValue = nValue;
89 const auto& txout = wtx.
tx->vout.at(nInput);
90 available_coins.
Add(
OutputType::BECH32, {
COutPoint(wtx.
GetHash(), nInput), txout, nAge, custom_size == 0 ?
CalculateMaximumSignedInputSize(txout, &
wallet,
nullptr) : custom_size,
true,
true,
true, wtx.
GetTxTime(), fIsFromMe, feerate});
98 return res ? std::optional<SelectionResult>(*res) : std::nullopt;
104 return res ? std::optional<SelectionResult>(*res) : std::nullopt;
111 std::vector<CAmount> a_amts;
112 std::vector<CAmount> b_amts;
114 a_amts.push_back(coin->txout.nValue);
117 b_amts.push_back(coin->txout.nValue);
119 std::sort(a_amts.begin(), a_amts.end());
120 std::sort(b_amts.begin(), b_amts.end());
122 std::pair<std::vector<CAmount>::iterator, std::vector<CAmount>::iterator>
ret = std::mismatch(a_amts.begin(), a_amts.end(), b_amts.begin());
123 return ret.first == a_amts.end() &&
ret.second == b_amts.end();
130 [](
const std::shared_ptr<COutput>& a,
const std::shared_ptr<COutput>& b) {
131 return a->outpoint == b->outpoint;
133 return ret.first == a.GetInputSet().end() &&
ret.second == b.
GetInputSet().end();
140 for (
int i = 0; i < utxos; ++i) {
141 target +=
CAmount{1} << (utxos+i);
148 inline std::vector<OutputGroup>&
GroupCoins(
const std::vector<COutput>& available_coins,
bool subtract_fee_outputs =
false)
150 static std::vector<OutputGroup> static_groups;
151 static_groups.clear();
152 for (
auto& coin : available_coins) {
153 static_groups.emplace_back();
155 group.Insert(std::make_shared<COutput>(coin), 0, 0);
156 group.m_subtract_fee_outputs = subtract_fee_outputs;
158 return static_groups;
176 static_groups =
GroupOutputs(
wallet, available_coins, coin_selection_params, {{filter}})[filter];
186 wallet->SetupDescriptorScriptPubKeyMans();
195 std::vector<COutput> utxo_pool;
217 expected_result.
Clear();
225 expected_result.
Clear();
234 expected_result.
Clear();
238 expected_result.
Clear();
246 expected_result.
Clear();
250 expected_result.
Clear();
262 expected_result.
Clear();
266 expected_result.
Clear();
287 for (
int i = 0; i < 50000; ++i) {
300 for (
int i = 5; i <= 20; ++i) {
304 for (
int i = 0; i < 100; ++i) {
320 coin_selection_params_bnb.m_change_fee = coin_selection_params_bnb.m_effective_feerate.
GetFee(coin_selection_params_bnb.change_output_size);
321 coin_selection_params_bnb.m_cost_of_change = coin_selection_params_bnb.m_effective_feerate.GetFee(coin_selection_params_bnb.change_spend_size) + coin_selection_params_bnb.m_change_fee;
322 coin_selection_params_bnb.min_viable_change = coin_selection_params_bnb.m_effective_feerate.GetFee(coin_selection_params_bnb.change_spend_size);
329 add_coin(available_coins, *
wallet, 1, coin_selection_params_bnb.m_effective_feerate);
330 available_coins.
All().at(0).input_bytes = 40;
334 available_coins.
Clear();
335 add_coin(available_coins, *
wallet, 1 *
CENT, coin_selection_params_bnb.m_effective_feerate);
336 available_coins.
All().at(0).input_bytes = 40;
347 coin_selection_params_bnb.m_effective_feerate =
CFeeRate(0);
348 add_coin(available_coins, *
wallet, 5 *
CENT, coin_selection_params_bnb.m_effective_feerate, 6 * 24,
false, 0,
true);
349 add_coin(available_coins, *
wallet, 3 *
CENT, coin_selection_params_bnb.m_effective_feerate, 6 * 24,
false, 0,
true);
350 add_coin(available_coins, *
wallet, 2 *
CENT, coin_selection_params_bnb.m_effective_feerate, 6 * 24,
false, 0,
true);
353 COutput select_coin = available_coins.
All().at(0);
356 selected_input.
Insert(select_coin, coin_selection_params_bnb.m_subtract_fee_outputs);
360 const auto result10 =
SelectCoins(*
wallet, available_coins, selected_input, 10 *
CENT, coin_control, coin_selection_params_bnb);
370 coin_selection_params_bnb.m_effective_feerate =
CFeeRate(5000);
371 coin_selection_params_bnb.m_long_term_feerate =
CFeeRate(3000);
374 CAmount input_fee = coin_selection_params_bnb.m_effective_feerate.GetFee(68);
375 add_coin(available_coins, *
wallet, 10 *
CENT + input_fee, coin_selection_params_bnb.m_effective_feerate, 6 * 24,
false, 0,
true);
376 add_coin(available_coins, *
wallet, 9 *
CENT + input_fee, coin_selection_params_bnb.m_effective_feerate, 6 * 24,
false, 0,
true);
377 add_coin(available_coins, *
wallet, 1 *
CENT + input_fee, coin_selection_params_bnb.m_effective_feerate, 6 * 24,
false, 0,
true);
379 expected_result.
Clear();
382 const auto result11 =
SelectCoins(*
wallet, available_coins, {}, 10 *
CENT, coin_control, coin_selection_params_bnb);
384 available_coins.
Clear();
387 coin_selection_params_bnb.m_effective_feerate =
CFeeRate(3000);
388 coin_selection_params_bnb.m_long_term_feerate =
CFeeRate(5000);
391 input_fee = coin_selection_params_bnb.m_effective_feerate.GetFee(68);
392 add_coin(available_coins, *
wallet, 10 *
CENT + input_fee, coin_selection_params_bnb.m_effective_feerate, 6 * 24,
false, 0,
true);
393 add_coin(available_coins, *
wallet, 9 *
CENT + input_fee, coin_selection_params_bnb.m_effective_feerate, 6 * 24,
false, 0,
true);
394 add_coin(available_coins, *
wallet, 1 *
CENT + input_fee, coin_selection_params_bnb.m_effective_feerate, 6 * 24,
false, 0,
true);
396 expected_result.
Clear();
399 const auto result12 =
SelectCoins(*
wallet, available_coins, {}, 10 *
CENT, coin_control, coin_selection_params_bnb);
401 available_coins.
Clear();
404 coin_selection_params_bnb.m_effective_feerate =
CFeeRate(5000);
405 coin_selection_params_bnb.m_long_term_feerate =
CFeeRate(3000);
408 input_fee = coin_selection_params_bnb.m_effective_feerate.GetFee(68);
409 add_coin(available_coins, *
wallet, 10 *
CENT + input_fee, coin_selection_params_bnb.m_effective_feerate, 6 * 24,
false, 0,
true);
410 add_coin(available_coins, *
wallet, 9 *
CENT + input_fee, coin_selection_params_bnb.m_effective_feerate, 6 * 24,
false, 0,
true);
411 add_coin(available_coins, *
wallet, 1 *
CENT + input_fee, coin_selection_params_bnb.m_effective_feerate, 6 * 24,
false, 0,
true);
413 expected_result.
Clear();
416 coin_control.m_allow_other_inputs =
true;
417 COutput select_coin = available_coins.
All().at(1);
418 coin_control.Select(select_coin.
outpoint);
420 selected_input.
Insert(select_coin, coin_selection_params_bnb.m_subtract_fee_outputs);
422 const auto result13 =
SelectCoins(*
wallet, available_coins, selected_input, 10 *
CENT, coin_control, coin_selection_params_bnb);
433 add_coin(available_coins, *
wallet, 10 *
CENT, coin_selection_params_bnb.m_effective_feerate, 6 * 24,
false, 0,
true);
434 add_coin(available_coins, *
wallet, 9 *
CENT, coin_selection_params_bnb.m_effective_feerate, 6 * 24,
false, 0,
true);
435 add_coin(available_coins, *
wallet, 8 *
CENT, coin_selection_params_bnb.m_effective_feerate, 6 * 24,
false, 0,
true);
437 add_coin(available_coins, *
wallet, 3 *
CENT, coin_selection_params_bnb.m_effective_feerate, 6 * 24,
false, 0,
true);
438 add_coin(available_coins, *
wallet, 1 *
CENT, coin_selection_params_bnb.m_effective_feerate, 6 * 24,
false, 0,
true);
443 BOOST_REQUIRE(!no_res);
447 add_coin(available_coins, *
wallet, 5 *
CENT, coin_selection_params_bnb.m_effective_feerate, 6 * 24,
false, 0,
true);
450 expected_result.
Clear();
477 params.m_subtract_fee_outputs =
true;
478 params.m_change_fee = params.m_effective_feerate.
GetFee(params.change_output_size);
479 params.m_cost_of_change = params.m_discard_feerate.GetFee(params.change_spend_size) + params.m_change_fee;
480 params.m_min_change_target = params.m_cost_of_change + 1;
483 add_coin(available_coins, *
wallet,
COIN + params.m_cost_of_change, params.m_effective_feerate, 6,
true, 0,
true);
484 add_coin(available_coins, *
wallet, 0.5 *
COIN + params.m_cost_of_change, params.m_effective_feerate, 6,
true, 0,
true);
485 add_coin(available_coins, *
wallet, 0.5 *
COIN, params.m_effective_feerate, 6,
true, 0,
true);
508 available_coins.
Clear();
577 available_coins.
Clear();
635 available_coins.
Clear();
667 available_coins.
Clear();
668 for (
int j = 0; j < 20; j++)
680 available_coins.
Clear();
691 available_coins.
Clear();
702 available_coins.
Clear();
722 available_coins.
Clear();
724 for (uint16_t j = 0; j < 676; j++)
732 if (amt - 2000 <
CENT) {
734 uint16_t returnSize = std::ceil((2000.0 +
CENT)/amt);
735 CAmount returnValue = amt * returnSize;
748 available_coins.
Clear();
749 for (
int i2 = 0; i2 < 100; i2++)
812 for (
int i = 0; i < 1000; i++)
829 std::default_random_engine generator;
830 std::exponential_distribution<double> distribution (100);
834 for (
int i = 0; i < 100; ++i)
840 for (
int j = 0; j < 1000; ++j)
842 CAmount val = distribution(generator)*10000000;
865 cs_params.m_cost_of_change = 1;
866 cs_params.min_viable_change = 1;
870 BOOST_CHECK_GE(
result->GetSelectedValue(), target);
877 const CAmount min_viable_change{300};
878 const CAmount change_cost{125};
895 selection1.RecalculateWaste(min_viable_change, change_cost, change_fee);
902 selection2.RecalculateWaste(min_viable_change, change_cost, change_fee);
903 BOOST_CHECK_GT(selection2.GetWaste(), selection1.GetWaste());
910 selection3.RecalculateWaste(min_viable_change, change_cost, change_fee);
912 BOOST_CHECK_LT(selection3.GetWaste(), selection1.GetWaste());
920 selection_nochange1.RecalculateWaste(min_viable_change, change_cost, change_fee);
928 selection_nochange2.RecalculateWaste(min_viable_change, change_cost, change_fee);
930 BOOST_CHECK_LT(selection_nochange2.GetWaste(), selection_nochange1.GetWaste());
938 selection.RecalculateWaste(min_viable_change, change_cost, change_fee);
947 selection.RecalculateWaste(min_viable_change, change_cost, change_fee);
956 selection.RecalculateWaste(min_viable_change, change_cost , change_fee);
965 selection.RecalculateWaste(min_viable_change, fee_diff * 2, change_fee);
971 const CAmount new_target{exact_target - fee_diff * 2};
975 selection.RecalculateWaste(min_viable_change, change_cost, change_fee);
982 const CAmount target_waste1{-2 * fee_diff};
985 selection.RecalculateWaste(min_viable_change, change_cost, change_fee);
992 const CAmount large_fee_diff{90};
993 const CAmount target_waste2{-2 * large_fee_diff + change_cost};
997 assert(target_waste2 == -55);
1000 selection.RecalculateWaste(min_viable_change, change_cost, change_fee);
1009 const CAmount min_viable_change{200};
1010 const CAmount change_cost{125};
1019 const std::vector<std::shared_ptr<COutput>> inputs = selection.GetShuffledInputVector();
1021 for (
size_t i = 0; i < inputs.size(); ++i) {
1022 inputs[i]->ApplyBumpFee(20*(i+1));
1025 selection.RecalculateWaste(min_viable_change, change_cost, change_fee);
1026 CAmount expected_waste = fee_diff * -2 + change_cost + 60;
1029 selection.SetBumpFeeDiscount(30);
1030 selection.RecalculateWaste(min_viable_change, change_cost, change_fee);
1031 expected_waste = fee_diff * -2 + change_cost + 60 - 30;
1045 const std::vector<std::shared_ptr<COutput>> inputs = selection.GetShuffledInputVector();
1047 for (
size_t i = 0; i < inputs.size(); ++i) {
1048 inputs[i]->ApplyBumpFee(20*(i+1));
1051 selection.RecalculateWaste(min_viable_change, change_cost, change_fee);
1052 CAmount expected_waste = fee_diff * -2 + 60 + 40;
1055 selection.SetBumpFeeDiscount(30);
1056 selection.RecalculateWaste(min_viable_change, change_cost, change_fee);
1057 expected_waste = fee_diff * -2 + 60 - 30 + 70;
1064 const int input_bytes = 148;
1067 const int nInput = 0;
1071 tx.
vout[nInput].nValue = nValue;
1074 COutput output1(
COutPoint(tx.
GetHash(), nInput), tx.
vout.at(nInput), 1, input_bytes,
true,
true,
true, 0,
false, feerate);
1075 const CAmount expected_ev1 = 9852;
1079 COutput output2(
COutPoint(tx.
GetHash(), nInput), tx.
vout.at(nInput), 1, -1,
true,
true,
true, 0,
false, feerate);
1083 COutput output3(
COutPoint(tx.
GetHash(), nInput), tx.
vout.at(nInput), 1, input_bytes,
true,
true,
true, 0,
false,
CFeeRate(100000));
1084 const CAmount expected_ev3 = -4800;
1089 COutput output4(
COutPoint(tx.
GetHash(), nInput), tx.
vout.at(nInput), 1, input_bytes,
true,
true,
true, 0,
false, fees);
1093 COutput output5(
COutPoint(tx.
GetHash(), nInput), tx.
vout.at(nInput), 1, -1,
true,
true,
true, 0,
false, 0);
1100 int max_selection_weight,
1138 int max_selection_weight = 10
'000; // high enough to not fail for this reason. 1139 const auto& res = CoinGrinder(target, dummy_params, m_node, max_selection_weight, [&](CWallet& wallet) { 1140 CoinsResult available_coins; 1141 for (int j = 0; j < 10; ++j) { 1142 add_coin(available_coins, wallet, CAmount(1 * COIN)); 1143 add_coin(available_coins, wallet, CAmount(2 * COIN)); 1145 return available_coins; 1148 BOOST_CHECK(util::ErrorString(res).empty()); // empty means "insufficient funds" 1152 // ########################### 1153 // 2) Test max weight exceeded 1154 // ########################### 1155 CAmount target = 29.5L * COIN; 1156 int max_selection_weight = 3000; 1157 const auto& res = CoinGrinder(target, dummy_params, m_node, max_selection_weight, [&](CWallet& wallet) { 1158 CoinsResult available_coins; 1159 for (int j = 0; j < 10; ++j) { 1160 add_coin(available_coins, wallet, CAmount(1 * COIN), CFeeRate(5000), 144, false, 0, true); 1161 add_coin(available_coins, wallet, CAmount(2 * COIN), CFeeRate(5000), 144, false, 0, true); 1163 return available_coins; 1166 BOOST_CHECK(util::ErrorString(res).original.find("The inputs size exceeds the maximum weight") != std::string::npos); 1170 // ############################################################################################################### 1171 // 3) Test selection when some coins surpass the max allowed weight while others not. --> must find a good solution 1172 // ################################################################################################################ 1173 CAmount target = 25.33L * COIN; 1174 int max_selection_weight = 10'000;
1177 for (
int j = 0; j < 60; ++j) {
1180 for (
int i = 0; i < 10; i++) {
1183 return available_coins;
1187 size_t expected_attempts = 37;
1188 BOOST_CHECK_MESSAGE(res->GetSelectionsEvaluated() == expected_attempts,
strprintf(
"Expected %i attempts, but got %i", expected_attempts, res->GetSelectionsEvaluated()));
1196 int max_selection_weight = 400
'000; // WU 1197 const auto& res = CoinGrinder(target, dummy_params, m_node, max_selection_weight, [&](CWallet& wallet) { 1198 CoinsResult available_coins; 1199 add_coin(available_coins, wallet, CAmount(2 * COIN), CFeeRate(5000), 144, false, 0, true, 148); 1200 add_coin(available_coins, wallet, CAmount(1 * COIN), CFeeRate(5000), 144, false, 0, true, 68); 1201 add_coin(available_coins, wallet, CAmount(1 * COIN), CFeeRate(5000), 144, false, 0, true, 68); 1202 return available_coins; 1204 SelectionResult expected_result(CAmount(0), SelectionAlgorithm::CG); 1205 add_coin(1 * COIN, 1, expected_result); 1206 add_coin(1 * COIN, 2, expected_result); 1207 BOOST_CHECK(EquivalentResult(expected_result, *res)); 1208 // Demonstrate how following improvements reduce iteration count and catch any regressions in the future. 1209 size_t expected_attempts = 3; 1210 BOOST_CHECK_MESSAGE(res->GetSelectionsEvaluated() == expected_attempts, strprintf("Expected %i attempts, but got %i", expected_attempts, res->GetSelectionsEvaluated())); 1214 // ############################################################################################################### 1215 // 5) Test finding a solution in a UTXO pool with mixed weights 1216 // ################################################################################################################ 1217 CAmount target = 30L * COIN; 1218 int max_selection_weight = 400'000;
1221 for (
int j = 0; j < 5; ++j) {
1229 return available_coins;
1238 size_t expected_attempts = 92;
1239 BOOST_CHECK_MESSAGE(res->GetSelectionsEvaluated() == expected_attempts,
strprintf(
"Expected %i attempts, but got %i", expected_attempts, res->GetSelectionsEvaluated()));
1247 int max_selection_weight = 400
'000; // WU 1248 const auto& res = CoinGrinder(target, dummy_params, m_node, max_selection_weight, [&](CWallet& wallet) { 1249 CoinsResult available_coins; 1250 // Expected Result: 4 + 3 + 2 + 1 = 10 BTC at 400 vB 1251 add_coin(available_coins, wallet, CAmount(4 * COIN), CFeeRate(5000), 144, false, 0, true, 100); 1252 add_coin(available_coins, wallet, CAmount(3 * COIN), CFeeRate(5000), 144, false, 0, true, 100); 1253 add_coin(available_coins, wallet, CAmount(2 * COIN), CFeeRate(5000), 144, false, 0, true, 100); 1254 add_coin(available_coins, wallet, CAmount(1 * COIN), CFeeRate(5000), 144, false, 0, true, 100); 1255 // Distracting clones: 1256 for (int j = 0; j < 100; ++j) { 1257 add_coin(available_coins, wallet, CAmount(8 * COIN), CFeeRate(5000), 144, false, 0, true, 1000); 1259 for (int j = 0; j < 100; ++j) { 1260 add_coin(available_coins, wallet, CAmount(7 * COIN), CFeeRate(5000), 144, false, 0, true, 800); 1262 for (int j = 0; j < 100; ++j) { 1263 add_coin(available_coins, wallet, CAmount(6 * COIN), CFeeRate(5000), 144, false, 0, true, 600); 1265 for (int j = 0; j < 100; ++j) { 1266 add_coin(available_coins, wallet, CAmount(5 * COIN), CFeeRate(5000), 144, false, 0, true, 400); 1268 return available_coins; 1270 SelectionResult expected_result(CAmount(0), SelectionAlgorithm::CG); 1271 add_coin(4 * COIN, 0, expected_result); 1272 add_coin(3 * COIN, 0, expected_result); 1273 add_coin(2 * COIN, 0, expected_result); 1274 add_coin(1 * COIN, 0, expected_result); 1275 BOOST_CHECK(EquivalentResult(expected_result, *res)); 1276 // Demonstrate how following improvements reduce iteration count and catch any regressions in the future. 1277 size_t expected_attempts = 38; 1278 BOOST_CHECK_MESSAGE(res->GetSelectionsEvaluated() == expected_attempts, strprintf("Expected %i attempts, but got %i", expected_attempts, res->GetSelectionsEvaluated())); 1282 // ################################################################################################################# 1283 // 7) Test that lots of tiny UTXOs can be skipped if they are too heavy while there are enough funds in lookahead 1284 // ################################################################################################################# 1285 CAmount target = 1.9L * COIN; 1286 int max_selection_weight = 40000; // WU 1287 const auto& res = CoinGrinder(target, dummy_params, m_node, max_selection_weight, [&](CWallet& wallet) { 1288 CoinsResult available_coins; 1289 add_coin(available_coins, wallet, CAmount(1.8 * COIN), CFeeRate(5000), 144, false, 0, true, 2500); 1290 add_coin(available_coins, wallet, CAmount(1 * COIN), CFeeRate(5000), 144, false, 0, true, 1000); 1291 add_coin(available_coins, wallet, CAmount(1 * COIN), CFeeRate(5000), 144, false, 0, true, 1000); 1292 for (int j = 0; j < 100; ++j) { 1293 // make a 100 unique coins only differing by one sat 1294 add_coin(available_coins, wallet, CAmount(0.01 * COIN + j), CFeeRate(5000), 144, false, 0, true, 110); 1296 return available_coins; 1298 SelectionResult expected_result(CAmount(0), SelectionAlgorithm::CG); 1299 add_coin(1 * COIN, 1, expected_result); 1300 add_coin(1 * COIN, 2, expected_result); 1301 BOOST_CHECK(EquivalentResult(expected_result, *res)); 1302 // Demonstrate how following improvements reduce iteration count and catch any regressions in the future. 1303 size_t expected_attempts = 7; 1304 BOOST_CHECK_MESSAGE(res->GetSelectionsEvaluated() == expected_attempts, strprintf("Expected %i attempts, but got %i", expected_attempts, res->GetSelectionsEvaluated())); 1308 static util::Result<SelectionResult> SelectCoinsSRD(const CAmount& target, 1309 const CoinSelectionParams& cs_params, 1310 const node::NodeContext& m_node, 1311 int max_selection_weight, 1312 std::function<CoinsResult(CWallet&)> coin_setup) 1314 std::unique_ptr<CWallet> wallet = NewWallet(m_node); 1315 CoinEligibilityFilter filter(0, 0, 0); // accept all coins without ancestors 1316 Groups group = GroupOutputs(*wallet, coin_setup(*wallet), cs_params, {{filter}})[filter].all_groups; 1317 return SelectCoinsSRD(group.positive_group, target, cs_params.m_change_fee, cs_params.rng_fast, max_selection_weight); 1320 BOOST_AUTO_TEST_CASE(srd_tests) 1323 // 1) Insufficient funds, select all provided coins and fail. 1324 // 2) Exceeded max weight, coin selection always surpasses the max allowed weight. 1325 // 3) Select coins without surpassing the max weight (some coins surpasses the max allowed weight, some others not) 1327 FastRandomContext rand; 1328 CoinSelectionParams dummy_params{ // Only used to provide the 'avoid_partial
' flag. 1330 /*change_output_size=*/34, 1331 /*change_spend_size=*/68, 1332 /*min_change_target=*/CENT, 1333 /*effective_feerate=*/CFeeRate(0), 1334 /*long_term_feerate=*/CFeeRate(0), 1335 /*discard_feerate=*/CFeeRate(0), 1336 /*tx_noinputs_size=*/10 + 34, // static header size + output size 1337 /*avoid_partial=*/false, 1341 // ######################################################### 1342 // 1) Insufficient funds, select all provided coins and fail 1343 // ######################################################### 1344 CAmount target = 49.5L * COIN; 1345 int max_selection_weight = 10000; // high enough to not fail for this reason. 1346 const auto& res = SelectCoinsSRD(target, dummy_params, m_node, max_selection_weight, [&](CWallet& wallet) { 1347 CoinsResult available_coins; 1348 for (int j = 0; j < 10; ++j) { 1349 add_coin(available_coins, wallet, CAmount(1 * COIN)); 1350 add_coin(available_coins, wallet, CAmount(2 * COIN)); 1352 return available_coins; 1355 BOOST_CHECK(util::ErrorString(res).empty()); // empty means "insufficient funds" 1359 // ########################### 1360 // 2) Test max weight exceeded 1361 // ########################### 1362 CAmount target = 49.5L * COIN; 1363 int max_selection_weight = 3000; 1364 const auto& res = SelectCoinsSRD(target, dummy_params, m_node, max_selection_weight, [&](CWallet& wallet) { 1365 CoinsResult available_coins; 1366 for (int j = 0; j < 10; ++j) { 1367 /* 10 × 1 BTC + 10 × 2 BTC = 30 BTC. 20 × 272 WU = 5440 WU */ 1368 add_coin(available_coins, wallet, CAmount(1 * COIN), CFeeRate(0), 144, false, 0, true); 1369 add_coin(available_coins, wallet, CAmount(2 * COIN), CFeeRate(0), 144, false, 0, true); 1371 return available_coins; 1374 BOOST_CHECK(util::ErrorString(res).original.find("The inputs size exceeds the maximum weight") != std::string::npos); 1378 // ################################################################################################################ 1379 // 3) Test selection when some coins surpass the max allowed weight while others not. --> must find a good solution 1380 // ################################################################################################################ 1381 CAmount target = 25.33L * COIN; 1382 int max_selection_weight = 10000; // WU 1383 const auto& res = SelectCoinsSRD(target, dummy_params, m_node, max_selection_weight, [&](CWallet& wallet) { 1384 CoinsResult available_coins; 1385 for (int j = 0; j < 60; ++j) { // 60 UTXO --> 19,8 BTC total --> 60 × 272 WU = 16320 WU 1386 add_coin(available_coins, wallet, CAmount(0.33 * COIN), CFeeRate(0), 144, false, 0, true); 1388 for (int i = 0; i < 10; i++) { // 10 UTXO --> 20 BTC total --> 10 × 272 WU = 2720 WU 1389 add_coin(available_coins, wallet, CAmount(2 * COIN), CFeeRate(0), 144, false, 0, true); 1391 return available_coins; 1397 static util::Result<SelectionResult> select_coins(const CAmount& target, const CoinSelectionParams& cs_params, const CCoinControl& cc, std::function<CoinsResult(CWallet&)> coin_setup, const node::NodeContext& m_node) 1399 std::unique_ptr<CWallet> wallet = NewWallet(m_node); 1400 auto available_coins = coin_setup(*wallet); 1402 LOCK(wallet->cs_wallet); 1403 auto result = SelectCoins(*wallet, available_coins, /*pre_set_inputs=*/ {}, target, cc, cs_params); 1405 const auto signedTxSize = 10 + 34 + 68 * result->GetInputSet().size(); // static header size + output size + inputs size (P2WPKH) 1406 BOOST_CHECK_LE(signedTxSize * WITNESS_SCALE_FACTOR, MAX_STANDARD_TX_WEIGHT); 1408 BOOST_CHECK_GE(result->GetSelectedValue(), target); 1413 static bool has_coin(const CoinSet& set, CAmount amount) 1415 return std::any_of(set.begin(), set.end(), [&](const auto& coin) { return coin->GetEffectiveValue() == amount; }); 1418 BOOST_AUTO_TEST_CASE(check_max_selection_weight) 1420 const CAmount target = 49.5L * COIN; 1423 FastRandomContext rand; 1424 CoinSelectionParams cs_params{ 1426 /*change_output_size=*/34, 1427 /*change_spend_size=*/68, 1428 /*min_change_target=*/CENT, 1429 /*effective_feerate=*/CFeeRate(0), 1430 /*long_term_feerate=*/CFeeRate(0), 1431 /*discard_feerate=*/CFeeRate(0), 1432 /*tx_noinputs_size=*/10 + 34, // static header size + output size 1433 /*avoid_partial=*/false, 1436 int max_weight = MAX_STANDARD_TX_WEIGHT - WITNESS_SCALE_FACTOR * (cs_params.tx_noinputs_size + cs_params.change_output_size); 1439 // The actor starts with 1x 50.0 BTC and 1515x 0.033 BTC (~100.0 BTC total) unspent outputs 1440 // Then tries to spend 49.5 BTC 1441 // The 50.0 BTC output should be selected, because the transaction would otherwise be too large 1443 // Perform selection 1445 const auto result = select_coins( 1446 target, cs_params, cc, [&](CWallet& wallet) { 1447 CoinsResult available_coins; 1448 for (int j = 0; j < 1515; ++j) { 1449 add_coin(available_coins, wallet, CAmount(0.033 * COIN), CFeeRate(0), 144, false, 0, true); 1452 add_coin(available_coins, wallet, CAmount(50 * COIN), CFeeRate(0), 144, false, 0, true); 1453 return available_coins; 1457 BOOST_CHECK(result); 1458 // Verify that the 50 BTC UTXO was selected, and result is below max_weight 1459 BOOST_CHECK(has_coin(result->GetInputSet(), CAmount(50 * COIN))); 1460 BOOST_CHECK_LE(result->GetWeight(), max_weight); 1466 // The actor starts with 400x 0.0625 BTC and 2000x 0.025 BTC (75.0 BTC total) unspent outputs 1467 // Then tries to spend 49.5 BTC 1468 // A combination of coins should be selected, such that the created transaction is not too large 1470 // Perform selection 1471 const auto result = select_coins( 1472 target, cs_params, cc, [&](CWallet& wallet) { 1473 CoinsResult available_coins; 1474 for (int j = 0; j < 400; ++j) { 1475 add_coin(available_coins, wallet, CAmount(0.0625 * COIN), CFeeRate(0), 144, false, 0, true); 1477 for (int j = 0; j < 2000; ++j) { 1478 add_coin(available_coins, wallet, CAmount(0.025 * COIN), CFeeRate(0), 144, false, 0, true); 1480 return available_coins; 1484 BOOST_CHECK(has_coin(result->GetInputSet(), CAmount(0.0625 * COIN))); 1485 BOOST_CHECK(has_coin(result->GetInputSet(), CAmount(0.025 * COIN))); 1486 BOOST_CHECK_LE(result->GetWeight(), max_weight); 1492 // The actor starts with 1515x 0.033 BTC (49.995 BTC total) unspent outputs 1493 // No results should be returned, because the transaction would be too large 1495 // Perform selection 1496 const auto result = select_coins( 1497 target, cs_params, cc, [&](CWallet& wallet) { 1498 CoinsResult available_coins; 1499 for (int j = 0; j < 1515; ++j) { 1500 add_coin(available_coins, wallet, CAmount(0.033 * COIN), CFeeRate(0), 144, false, 0, true); 1502 return available_coins; 1507 // 1515 inputs * 68 bytes = 103,020 bytes 1508 // 103,020 bytes * 4 = 412,080 weight, which is above the MAX_STANDARD_TX_WEIGHT of 400,000 1509 BOOST_CHECK(!result); 1513 BOOST_AUTO_TEST_CASE(SelectCoins_effective_value_test) 1515 // Test that the effective value is used to check whether preset inputs provide sufficient funds when subtract_fee_outputs is not used. 1516 // This test creates a coin whose value is higher than the target but whose effective value is lower than the target. 1517 // The coin is selected using coin control, with m_allow_other_inputs = false. SelectCoins should fail due to insufficient funds. 1519 std::unique_ptr<CWallet> wallet = NewWallet(m_node); 1521 CoinsResult available_coins; 1523 std::unique_ptr<CWallet> dummyWallet = NewWallet(m_node, /*wallet_name=*/"dummy"); 1524 add_coin(available_coins, *dummyWallet, 100000); // 0.001 BTC 1527 CAmount target{99900}; // 0.000999 BTC 1529 FastRandomContext rand; 1530 CoinSelectionParams cs_params{ 1532 /*change_output_size=*/34, 1533 /*change_spend_size=*/148, 1534 /*min_change_target=*/1000, 1535 /*effective_feerate=*/CFeeRate(3000), 1536 /*long_term_feerate=*/CFeeRate(1000), 1537 /*discard_feerate=*/CFeeRate(1000), 1538 /*tx_noinputs_size=*/0, 1539 /*avoid_partial=*/false, 1542 cc.m_allow_other_inputs = false; 1543 COutput output = available_coins.All().at(0); 1544 cc.SetInputWeight(output.outpoint, 148); 1545 cc.Select(output.outpoint).SetTxOut(output.txout); 1547 LOCK(wallet->cs_wallet); 1548 const auto preset_inputs = *Assert(FetchSelectedInputs(*wallet, cc, cs_params)); 1549 available_coins.Erase({available_coins.coins[OutputType::BECH32].begin()->outpoint}); 1551 const auto result = SelectCoins(*wallet, available_coins, preset_inputs, target, cc, cs_params); 1552 BOOST_CHECK(!result); 1555 BOOST_FIXTURE_TEST_CASE(wallet_coinsresult_test, BasicTestingSetup) 1557 // Test case to verify CoinsResult object sanity. 1558 CoinsResult available_coins; 1560 std::unique_ptr<CWallet> dummyWallet = NewWallet(m_node, /*wallet_name=*/"dummy"); 1562 // Add some coins to 'available_coins
' 1563 for (int i=0; i<10; i++) { 1564 add_coin(available_coins, *dummyWallet, 1 * COIN); 1570 // By trying to erase two elements from the 'available_coins
' object. 1571 std::unordered_set<COutPoint, SaltedOutpointHasher> outs_to_remove; 1572 const auto& coins = available_coins.All(); 1573 for (int i = 0; i < 2; i++) { 1574 outs_to_remove.emplace(coins[i].outpoint); 1576 available_coins.Erase(outs_to_remove); 1578 // Check that the elements were actually removed. 1579 const auto& updated_coins = available_coins.All(); 1580 for (const auto& out: outs_to_remove) { 1581 auto it = std::find_if(updated_coins.begin(), updated_coins.end(), [&out](const COutput &coin) { 1582 return coin.outpoint == out; 1584 BOOST_CHECK(it == updated_coins.end()); 1586 // And verify that no extra element were removed 1587 BOOST_CHECK_EQUAL(available_coins.Size(), 8); 1591 BOOST_AUTO_TEST_SUITE_END() 1592 } // namespace wallet
COutPoint outpoint
The outpoint identifying this UTXO.
std::unique_ptr< interfaces::Chain > chain
util::Result< SelectionResult > KnapsackSolver(std::vector< OutputGroup > &groups, const CAmount &nTargetValue, CAmount change_target, FastRandomContext &rng, int max_selection_weight)
std::set< std::shared_ptr< COutput > > CoinSet
Stores several 'Groups' whose were mapped by output type.
static bool EquivalentResult(const SelectionResult &a, const SelectionResult &b)
Check if SelectionResult a is equivalent to SelectionResult b.
std::vector< COutput > All() const
Concatenate and return all COutputs as one vector.
static std::unique_ptr< CWallet > NewWallet(const node::NodeContext &m_node, const std::string &wallet_name="")
std::vector< OutputGroup > & GroupCoins(const std::vector< COutput > &available_coins, bool subtract_fee_outputs=false)
State of transaction not confirmed or conflicting with a known block and not in the mempool...
util::Result< SelectionResult > CoinGrinder(std::vector< OutputGroup > &utxo_pool, const CAmount &selection_target, CAmount change_target, int max_selection_weight)
int64_t GetTxTime() const
static void ApproximateBestSubset(FastRandomContext &insecure_rand, const std::vector< OutputGroup > &groups, const CAmount &nTotalLower, const CAmount &nTargetValue, std::vector< char > &vfBest, CAmount &nBest, int max_selection_weight, int iterations=1000)
Find a subset of the OutputGroups that is at least as large as, but as close as possible to...
static bool EqualResult(const SelectionResult &a, const SelectionResult &b)
Check if this selection is equal to another one.
std::map< OutputType, std::vector< COutput > > coins
int64_t CAmount
Amount in satoshis (Can be negative)
COutputs available for spending, stored by OutputType.
A transaction with a bunch of additional info that only the owner cares about.
NodeContext struct containing references to chain state and connection state.
CAmount m_min_change_target
Mininmum change to target in Knapsack solver and CoinGrinder: select coins to cover the payment and a...
static void add_coin(const CAmount &nValue, int nInput, std::vector< COutput > &set)
BOOST_FIXTURE_TEST_SUITE(cuckoocache_tests, BasicTestingSetup)
Test Suite for CuckooCache.
int CalculateMaximumSignedInputSize(const CTxOut &txout, const COutPoint outpoint, const SigningProvider *provider, bool can_grind_r, const CCoinControl *coin_control)
util::Result< SelectionResult > SelectCoins(const CWallet &wallet, CoinsResult &available_coins, const PreSelectedInputs &pre_set_inputs, const CAmount &nTargetValue, const CCoinControl &coin_control, const CoinSelectionParams &coin_selection_params)
Select all coins from coin_control, and if coin_control 'm_allow_other_inputs=true', call 'AutomaticCoinSelection' to select a set of coins such that nTargetValue - pre_set_inputs.total_amount is met.
const std::set< std::shared_ptr< COutput > > & GetInputSet() const
Get m_selected_inputs.
Indicate that this wallet supports DescriptorScriptPubKeyMan.
A CWallet maintains a set of transactions and balances, and provides the ability to create new transa...
A group of UTXOs paid to the same output script.
Txid GetHash() const
Compute the hash of this CMutableTransaction.
An outpoint - a combination of a transaction hash and an index n into its vout.
static const CoinEligibilityFilter filter_standard_extra(6, 6, 0)
std::vector< CTxOut > vout
#define WITH_LOCK(cs, code)
Run code while locking a mutex.
Parameters for one iteration of Coin Selection.
util::Result< SelectionResult > SelectCoinsBnB(std::vector< OutputGroup > &utxo_pool, const CAmount &selection_target, const CAmount &cost_of_change, int max_selection_weight)
CScript GetScriptForDestination(const CTxDestination &dest)
Generate a Bitcoin scriptPubKey for the given CTxDestination.
static CTransactionRef MakeTransactionRef(Tx &&txIn)
Parameters for filtering which OutputGroups we may use in coin selection.
std::vector< OutputGroup > mixed_group
const Txid & GetHash() const LIFETIMEBOUND
static constexpr int32_t MAX_STANDARD_TX_WEIGHT
The maximum weight for transactions we're willing to relay/mine.
static const CoinEligibilityFilter filter_standard(1, 6, 0)
std::unique_ptr< WalletDatabase > CreateMockableWalletDatabase(MockableData records)
void Erase(const std::unordered_set< COutPoint, SaltedOutpointHasher > &coins_to_remove)
#define BOOST_CHECK_EQUAL(v1, v2)
std::vector< OutputGroup > & KnapsackGroupOutputs(const CoinsResult &available_coins, CWallet &wallet, const CoinEligibilityFilter &filter)
bool m_allow_other_inputs
If true, the selection process can add extra unselected inputs from the wallet while requires all sel...
Fee rate in satoshis per kilovirtualbyte: CAmount / kvB.
I randrange(I range) noexcept
Generate a random integer in the range [0..range), with range > 0.
bilingual_str ErrorString(const Result< T > &result)
CAmount GetFee(uint32_t num_bytes) const
Return the fee in satoshis for the given vsize in vbytes.
A mutable version of CTransaction.
static constexpr CAmount CENT
void Add(OutputType type, const COutput &out)
BOOST_AUTO_TEST_CASE(bnb_search_test)
A UTXO under consideration for use in funding a new transaction.
FilteredOutputGroups GroupOutputs(const CWallet &wallet, const CoinsResult &coins, const CoinSelectionParams &coin_sel_params, const std::vector< SelectionFilter > &filters, std::vector< OutputGroup > &ret_discarded_groups)
PreselectedInput & Select(const COutPoint &outpoint)
Lock-in the given output for spending.
#define Assert(val)
Identity function.
static CAmount make_hard_case(int utxos, std::vector< COutput > &utxo_pool)
#define BOOST_CHECK(expr)
static const CoinEligibilityFilter filter_confirmed(1, 1, 0)
static constexpr CAmount COIN
The amount of satoshis in one BTC.