[refactor]: update coin selection algorithms input parameter max_weight name
- This commit renames the coin selection algorithms input parameter `max_weight` to `max_selection_weight` for clarity. The parameter represent the maximum weight of the UTXOs the coin selection algorithm should select, not the transaction maximum weight. - The commit updates the parameter docstring to provide correct description. - Also updates coin selection unit and fuzzing test variables to match the new name.
This commit is contained in:
1 parent
b27afb7fb7
commit
7f61d31a5c
5 files changed
+55
-55
No files matched your search
@@ -84,14 +84,14 @@ struct {
|
|||||||
* bound of the range.
|
* bound of the range.
|
||||||
* @param const CAmount& cost_of_change This is the cost of creating and spending a change output.
|
* @param const CAmount& cost_of_change This is the cost of creating and spending a change output.
|
||||||
* This plus selection_target is the upper bound of the range.
|
* This plus selection_target is the upper bound of the range.
|
||||||
* @param int max_weight The maximum weight available for the input set.
|
* @param int max_selection_weight The maximum allowed weight for a selection result to be valid.
|
||||||
* @returns The result of this coin selection algorithm, or std::nullopt
|
* @returns The result of this coin selection algorithm, or std::nullopt
|
||||||
*/
|
*/
|
||||||
|
|
||||||
static const size_t TOTAL_TRIES = 100000;
|
static const size_t TOTAL_TRIES = 100000;
|
||||||
|
|
||||||
util::Result<SelectionResult> SelectCoinsBnB(std::vector<OutputGroup>& utxo_pool, const CAmount& selection_target, const CAmount& cost_of_change,
|
util::Result<SelectionResult> SelectCoinsBnB(std::vector<OutputGroup>& utxo_pool, const CAmount& selection_target, const CAmount& cost_of_change,
|
||||||
int max_weight)
|
int max_selection_weight)
|
||||||
{
|
{
|
||||||
SelectionResult result(selection_target, SelectionAlgorithm::BNB);
|
SelectionResult result(selection_target, SelectionAlgorithm::BNB);
|
||||||
CAmount curr_value = 0;
|
CAmount curr_value = 0;
|
||||||
@@ -128,7 +128,7 @@ util::Result<SelectionResult> SelectCoinsBnB(std::vector<OutputGroup>& utxo_pool
|
|||||||
curr_value > selection_target + cost_of_change || // Selected value is out of range, go back and try other branch
|
curr_value > selection_target + cost_of_change || // Selected value is out of range, go back and try other branch
|
||||||
(curr_waste > best_waste && is_feerate_high)) { // Don't select things which we know will be more wasteful if the waste is increasing
|
(curr_waste > best_waste && is_feerate_high)) { // Don't select things which we know will be more wasteful if the waste is increasing
|
||||||
backtrack = true;
|
backtrack = true;
|
||||||
} else if (curr_selection_weight > max_weight) { // Exceeding weight for standard tx, cannot find more solutions by adding more inputs
|
} else if (curr_selection_weight > max_selection_weight) { // Selected UTXOs weight exceeds the maximum weight allowed, cannot find more solutions by adding more inputs
|
||||||
max_tx_weight_exceeded = true; // at least one selection attempt exceeded the max weight
|
max_tx_weight_exceeded = true; // at least one selection attempt exceeded the max weight
|
||||||
backtrack = true;
|
backtrack = true;
|
||||||
} else if (curr_value >= selection_target) { // Selected value is within range
|
} else if (curr_value >= selection_target) { // Selected value is within range
|
||||||
@@ -319,10 +319,10 @@ util::Result<SelectionResult> SelectCoinsBnB(std::vector<OutputGroup>& utxo_pool
|
|||||||
* group with multiple as a heavier UTXO with the combined amount here.)
|
* group with multiple as a heavier UTXO with the combined amount here.)
|
||||||
* @param const CAmount& selection_target This is the minimum amount that we need for the transaction without considering change.
|
* @param const CAmount& selection_target This is the minimum amount that we need for the transaction without considering change.
|
||||||
* @param const CAmount& change_target The minimum budget for creating a change output, by which we increase the selection_target.
|
* @param const CAmount& change_target The minimum budget for creating a change output, by which we increase the selection_target.
|
||||||
* @param int max_weight The maximum permitted weight for the input set.
|
* @param int max_selection_weight The maximum allowed weight for a selection result to be valid.
|
||||||
* @returns The result of this coin selection algorithm, or std::nullopt
|
* @returns The result of this coin selection algorithm, or std::nullopt
|
||||||
*/
|
*/
|
||||||
util::Result<SelectionResult> CoinGrinder(std::vector<OutputGroup>& utxo_pool, const CAmount& selection_target, CAmount change_target, int max_weight)
|
util::Result<SelectionResult> CoinGrinder(std::vector<OutputGroup>& utxo_pool, const CAmount& selection_target, CAmount change_target, int max_selection_weight)
|
||||||
{
|
{
|
||||||
std::sort(utxo_pool.begin(), utxo_pool.end(), descending_effval_weight);
|
std::sort(utxo_pool.begin(), utxo_pool.end(), descending_effval_weight);
|
||||||
// The sum of UTXO amounts after this UTXO index, e.g. lookahead[5] = Σ(UTXO[6+].amount)
|
// The sum of UTXO amounts after this UTXO index, e.g. lookahead[5] = Σ(UTXO[6+].amount)
|
||||||
@@ -359,7 +359,7 @@ util::Result<SelectionResult> CoinGrinder(std::vector<OutputGroup>& utxo_pool, c
|
|||||||
|
|
||||||
// The weight of the currently selected input set, and the weight of the best selection
|
// The weight of the currently selected input set, and the weight of the best selection
|
||||||
int curr_weight = 0;
|
int curr_weight = 0;
|
||||||
int best_selection_weight = max_weight; // Tie is fine, because we prefer lower selection amount
|
int best_selection_weight = max_selection_weight; // Tie is fine, because we prefer lower selection amount
|
||||||
|
|
||||||
// Whether the input sets generated during this search have exceeded the maximum transaction weight at any point
|
// Whether the input sets generated during this search have exceeded the maximum transaction weight at any point
|
||||||
bool max_tx_weight_exceeded = false;
|
bool max_tx_weight_exceeded = false;
|
||||||
@@ -436,8 +436,8 @@ util::Result<SelectionResult> CoinGrinder(std::vector<OutputGroup>& utxo_pool, c
|
|||||||
// Insufficient funds with lookahead: CUT
|
// Insufficient funds with lookahead: CUT
|
||||||
should_cut = true;
|
should_cut = true;
|
||||||
} else if (curr_weight > best_selection_weight) {
|
} else if (curr_weight > best_selection_weight) {
|
||||||
// best_selection_weight is initialized to max_weight
|
// best_selection_weight is initialized to max_selection_weight
|
||||||
if (curr_weight > max_weight) max_tx_weight_exceeded = true;
|
if (curr_weight > max_selection_weight) max_tx_weight_exceeded = true;
|
||||||
// Worse weight than best solution. More UTXOs only increase weight:
|
// Worse weight than best solution. More UTXOs only increase weight:
|
||||||
// CUT if last selected group had minimal weight, else SHIFT
|
// CUT if last selected group had minimal weight, else SHIFT
|
||||||
if (utxo_pool[curr_tail].m_weight <= min_tail_weight[curr_tail]) {
|
if (utxo_pool[curr_tail].m_weight <= min_tail_weight[curr_tail]) {
|
||||||
@@ -535,7 +535,7 @@ public:
|
|||||||
};
|
};
|
||||||
|
|
||||||
util::Result<SelectionResult> SelectCoinsSRD(const std::vector<OutputGroup>& utxo_pool, CAmount target_value, CAmount change_fee, FastRandomContext& rng,
|
util::Result<SelectionResult> SelectCoinsSRD(const std::vector<OutputGroup>& utxo_pool, CAmount target_value, CAmount change_fee, FastRandomContext& rng,
|
||||||
int max_weight)
|
int max_selection_weight)
|
||||||
{
|
{
|
||||||
SelectionResult result(target_value, SelectionAlgorithm::SRD);
|
SelectionResult result(target_value, SelectionAlgorithm::SRD);
|
||||||
std::priority_queue<OutputGroup, std::vector<OutputGroup>, MinOutputGroupComparator> heap;
|
std::priority_queue<OutputGroup, std::vector<OutputGroup>, MinOutputGroupComparator> heap;
|
||||||
@@ -565,14 +565,14 @@ util::Result<SelectionResult> SelectCoinsSRD(const std::vector<OutputGroup>& utx
|
|||||||
|
|
||||||
// If the selection weight exceeds the maximum allowed size, remove the least valuable inputs until we
|
// If the selection weight exceeds the maximum allowed size, remove the least valuable inputs until we
|
||||||
// are below max weight.
|
// are below max weight.
|
||||||
if (weight > max_weight) {
|
if (weight > max_selection_weight) {
|
||||||
max_tx_weight_exceeded = true; // mark it in case we don't find any useful result.
|
max_tx_weight_exceeded = true; // mark it in case we don't find any useful result.
|
||||||
do {
|
do {
|
||||||
const OutputGroup& to_remove_group = heap.top();
|
const OutputGroup& to_remove_group = heap.top();
|
||||||
selected_eff_value -= to_remove_group.GetSelectionAmount();
|
selected_eff_value -= to_remove_group.GetSelectionAmount();
|
||||||
weight -= to_remove_group.m_weight;
|
weight -= to_remove_group.m_weight;
|
||||||
heap.pop();
|
heap.pop();
|
||||||
} while (!heap.empty() && weight > max_weight);
|
} while (!heap.empty() && weight > max_selection_weight);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Now check if we are above the target
|
// Now check if we are above the target
|
||||||
@@ -648,7 +648,7 @@ static void ApproximateBestSubset(FastRandomContext& insecure_rand, const std::v
|
|||||||
}
|
}
|
||||||
|
|
||||||
util::Result<SelectionResult> KnapsackSolver(std::vector<OutputGroup>& groups, const CAmount& nTargetValue,
|
util::Result<SelectionResult> KnapsackSolver(std::vector<OutputGroup>& groups, const CAmount& nTargetValue,
|
||||||
CAmount change_target, FastRandomContext& rng, int max_weight)
|
CAmount change_target, FastRandomContext& rng, int max_selection_weight)
|
||||||
{
|
{
|
||||||
SelectionResult result(nTargetValue, SelectionAlgorithm::KNAPSACK);
|
SelectionResult result(nTargetValue, SelectionAlgorithm::KNAPSACK);
|
||||||
|
|
||||||
@@ -709,7 +709,7 @@ util::Result<SelectionResult> KnapsackSolver(std::vector<OutputGroup>& groups, c
|
|||||||
}
|
}
|
||||||
|
|
||||||
// If the result exceeds the maximum allowed size, return closest UTXO above the target
|
// If the result exceeds the maximum allowed size, return closest UTXO above the target
|
||||||
if (result.GetWeight() > max_weight) {
|
if (result.GetWeight() > max_selection_weight) {
|
||||||
// No coin above target, nothing to do.
|
// No coin above target, nothing to do.
|
||||||
if (!lowest_larger) return ErrorMaxWeightExceeded();
|
if (!lowest_larger) return ErrorMaxWeightExceeded();
|
||||||
|
|
||||||
|
|||||||
@@ -440,9 +440,9 @@ public:
|
|||||||
};
|
};
|
||||||
|
|
||||||
util::Result<SelectionResult> SelectCoinsBnB(std::vector<OutputGroup>& utxo_pool, const CAmount& selection_target, const CAmount& cost_of_change,
|
util::Result<SelectionResult> SelectCoinsBnB(std::vector<OutputGroup>& utxo_pool, const CAmount& selection_target, const CAmount& cost_of_change,
|
||||||
int max_weight);
|
int max_selection_weight);
|
||||||
|
|
||||||
util::Result<SelectionResult> CoinGrinder(std::vector<OutputGroup>& utxo_pool, const CAmount& selection_target, CAmount change_target, int max_weight);
|
util::Result<SelectionResult> CoinGrinder(std::vector<OutputGroup>& utxo_pool, const CAmount& selection_target, CAmount change_target, int max_selection_weight);
|
||||||
|
|
||||||
/** Select coins by Single Random Draw. OutputGroups are selected randomly from the eligible
|
/** Select coins by Single Random Draw. OutputGroups are selected randomly from the eligible
|
||||||
* outputs until the target is satisfied
|
* outputs until the target is satisfied
|
||||||
@@ -450,15 +450,15 @@ util::Result<SelectionResult> CoinGrinder(std::vector<OutputGroup>& utxo_pool, c
|
|||||||
* @param[in] utxo_pool The positive effective value OutputGroups eligible for selection
|
* @param[in] utxo_pool The positive effective value OutputGroups eligible for selection
|
||||||
* @param[in] target_value The target value to select for
|
* @param[in] target_value The target value to select for
|
||||||
* @param[in] rng The randomness source to shuffle coins
|
* @param[in] rng The randomness source to shuffle coins
|
||||||
* @param[in] max_weight The maximum allowed weight for a selection result to be valid
|
* @param[in] max_selection_weight The maximum allowed weight for a selection result to be valid
|
||||||
* @returns If successful, a valid SelectionResult, otherwise, util::Error
|
* @returns If successful, a valid SelectionResult, otherwise, util::Error
|
||||||
*/
|
*/
|
||||||
util::Result<SelectionResult> SelectCoinsSRD(const std::vector<OutputGroup>& utxo_pool, CAmount target_value, CAmount change_fee, FastRandomContext& rng,
|
util::Result<SelectionResult> SelectCoinsSRD(const std::vector<OutputGroup>& utxo_pool, CAmount target_value, CAmount change_fee, FastRandomContext& rng,
|
||||||
int max_weight);
|
int max_selection_weight);
|
||||||
|
|
||||||
// Original coin selection algorithm as a fallback
|
// Original coin selection algorithm as a fallback
|
||||||
util::Result<SelectionResult> KnapsackSolver(std::vector<OutputGroup>& groups, const CAmount& nTargetValue,
|
util::Result<SelectionResult> KnapsackSolver(std::vector<OutputGroup>& groups, const CAmount& nTargetValue,
|
||||||
CAmount change_target, FastRandomContext& rng, int max_weight);
|
CAmount change_target, FastRandomContext& rng, int max_selection_weight);
|
||||||
} // namespace wallet
|
} // namespace wallet
|
||||||
|
|
||||||
#endif // BITCOIN_WALLET_COINSELECTION_H
|
#endif // BITCOIN_WALLET_COINSELECTION_H
|
||||||
@@ -695,26 +695,26 @@ util::Result<SelectionResult> ChooseSelectionResult(interfaces::Chain& chain, co
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Maximum allowed weight
|
// Maximum allowed weight for selected coins.
|
||||||
int max_inputs_weight = MAX_STANDARD_TX_WEIGHT - (coin_selection_params.tx_noinputs_size * WITNESS_SCALE_FACTOR);
|
int max_selection_weight = MAX_STANDARD_TX_WEIGHT - (coin_selection_params.tx_noinputs_size * WITNESS_SCALE_FACTOR);
|
||||||
|
|
||||||
// SFFO frequently causes issues in the context of changeless input sets: skip BnB when SFFO is active
|
// SFFO frequently causes issues in the context of changeless input sets: skip BnB when SFFO is active
|
||||||
if (!coin_selection_params.m_subtract_fee_outputs) {
|
if (!coin_selection_params.m_subtract_fee_outputs) {
|
||||||
if (auto bnb_result{SelectCoinsBnB(groups.positive_group, nTargetValue, coin_selection_params.m_cost_of_change, max_inputs_weight)}) {
|
if (auto bnb_result{SelectCoinsBnB(groups.positive_group, nTargetValue, coin_selection_params.m_cost_of_change, max_selection_weight)}) {
|
||||||
results.push_back(*bnb_result);
|
results.push_back(*bnb_result);
|
||||||
} else append_error(std::move(bnb_result));
|
} else append_error(std::move(bnb_result));
|
||||||
}
|
}
|
||||||
|
|
||||||
// As Knapsack and SRD can create change, also deduce change weight.
|
// As Knapsack and SRD can create change, also deduce change weight.
|
||||||
max_inputs_weight -= (coin_selection_params.change_output_size * WITNESS_SCALE_FACTOR);
|
max_selection_weight -= (coin_selection_params.change_output_size * WITNESS_SCALE_FACTOR);
|
||||||
|
|
||||||
// The knapsack solver has some legacy behavior where it will spend dust outputs. We retain this behavior, so don't filter for positive only here.
|
// The knapsack solver has some legacy behavior where it will spend dust outputs. We retain this behavior, so don't filter for positive only here.
|
||||||
if (auto knapsack_result{KnapsackSolver(groups.mixed_group, nTargetValue, coin_selection_params.m_min_change_target, coin_selection_params.rng_fast, max_inputs_weight)}) {
|
if (auto knapsack_result{KnapsackSolver(groups.mixed_group, nTargetValue, coin_selection_params.m_min_change_target, coin_selection_params.rng_fast, max_selection_weight)}) {
|
||||||
results.push_back(*knapsack_result);
|
results.push_back(*knapsack_result);
|
||||||
} else append_error(std::move(knapsack_result));
|
} else append_error(std::move(knapsack_result));
|
||||||
|
|
||||||
if (coin_selection_params.m_effective_feerate > CFeeRate{3 * coin_selection_params.m_long_term_feerate}) { // Minimize input set for feerates of at least 3×LTFRE (default: 30 ṩ/vB+)
|
if (coin_selection_params.m_effective_feerate > CFeeRate{3 * coin_selection_params.m_long_term_feerate}) { // Minimize input set for feerates of at least 3×LTFRE (default: 30 ṩ/vB+)
|
||||||
if (auto cg_result{CoinGrinder(groups.positive_group, nTargetValue, coin_selection_params.m_min_change_target, max_inputs_weight)}) {
|
if (auto cg_result{CoinGrinder(groups.positive_group, nTargetValue, coin_selection_params.m_min_change_target, max_selection_weight)}) {
|
||||||
cg_result->RecalculateWaste(coin_selection_params.min_viable_change, coin_selection_params.m_cost_of_change, coin_selection_params.m_change_fee);
|
cg_result->RecalculateWaste(coin_selection_params.min_viable_change, coin_selection_params.m_cost_of_change, coin_selection_params.m_change_fee);
|
||||||
results.push_back(*cg_result);
|
results.push_back(*cg_result);
|
||||||
} else {
|
} else {
|
||||||
@@ -722,7 +722,7 @@ util::Result<SelectionResult> ChooseSelectionResult(interfaces::Chain& chain, co
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (auto srd_result{SelectCoinsSRD(groups.positive_group, nTargetValue, coin_selection_params.m_change_fee, coin_selection_params.rng_fast, max_inputs_weight)}) {
|
if (auto srd_result{SelectCoinsSRD(groups.positive_group, nTargetValue, coin_selection_params.m_change_fee, coin_selection_params.rng_fast, max_selection_weight)}) {
|
||||||
results.push_back(*srd_result);
|
results.push_back(*srd_result);
|
||||||
} else append_error(std::move(srd_result));
|
} else append_error(std::move(srd_result));
|
||||||
|
|
||||||
|
|||||||
@@ -1097,13 +1097,13 @@ BOOST_AUTO_TEST_CASE(effective_value_test)
|
|||||||
static util::Result<SelectionResult> CoinGrinder(const CAmount& target,
|
static util::Result<SelectionResult> CoinGrinder(const CAmount& target,
|
||||||
const CoinSelectionParams& cs_params,
|
const CoinSelectionParams& cs_params,
|
||||||
const node::NodeContext& m_node,
|
const node::NodeContext& m_node,
|
||||||
int max_weight,
|
int max_selection_weight,
|
||||||
std::function<CoinsResult(CWallet&)> coin_setup)
|
std::function<CoinsResult(CWallet&)> coin_setup)
|
||||||
{
|
{
|
||||||
std::unique_ptr<CWallet> wallet = NewWallet(m_node);
|
std::unique_ptr<CWallet> wallet = NewWallet(m_node);
|
||||||
CoinEligibilityFilter filter(0, 0, 0); // accept all coins without ancestors
|
CoinEligibilityFilter filter(0, 0, 0); // accept all coins without ancestors
|
||||||
Groups group = GroupOutputs(*wallet, coin_setup(*wallet), cs_params, {{filter}})[filter].all_groups;
|
Groups group = GroupOutputs(*wallet, coin_setup(*wallet), cs_params, {{filter}})[filter].all_groups;
|
||||||
return CoinGrinder(group.positive_group, target, cs_params.m_min_change_target, max_weight);
|
return CoinGrinder(group.positive_group, target, cs_params.m_min_change_target, max_selection_weight);
|
||||||
}
|
}
|
||||||
|
|
||||||
BOOST_AUTO_TEST_CASE(coin_grinder_tests)
|
BOOST_AUTO_TEST_CASE(coin_grinder_tests)
|
||||||
@@ -1135,8 +1135,8 @@ BOOST_AUTO_TEST_CASE(coin_grinder_tests)
|
|||||||
// 1) Insufficient funds, select all provided coins and fail
|
// 1) Insufficient funds, select all provided coins and fail
|
||||||
// #########################################################
|
// #########################################################
|
||||||
CAmount target = 49.5L * COIN;
|
CAmount target = 49.5L * COIN;
|
||||||
int max_weight = 10'000; // high enough to not fail for this reason.
|
int max_selection_weight = 10'000; // high enough to not fail for this reason.
|
||||||
const auto& res = CoinGrinder(target, dummy_params, m_node, max_weight, [&](CWallet& wallet) {
|
const auto& res = CoinGrinder(target, dummy_params, m_node, max_selection_weight, [&](CWallet& wallet) {
|
||||||
CoinsResult available_coins;
|
CoinsResult available_coins;
|
||||||
for (int j = 0; j < 10; ++j) {
|
for (int j = 0; j < 10; ++j) {
|
||||||
add_coin(available_coins, wallet, CAmount(1 * COIN));
|
add_coin(available_coins, wallet, CAmount(1 * COIN));
|
||||||
@@ -1153,8 +1153,8 @@ BOOST_AUTO_TEST_CASE(coin_grinder_tests)
|
|||||||
// 2) Test max weight exceeded
|
// 2) Test max weight exceeded
|
||||||
// ###########################
|
// ###########################
|
||||||
CAmount target = 29.5L * COIN;
|
CAmount target = 29.5L * COIN;
|
||||||
int max_weight = 3000;
|
int max_selection_weight = 3000;
|
||||||
const auto& res = CoinGrinder(target, dummy_params, m_node, max_weight, [&](CWallet& wallet) {
|
const auto& res = CoinGrinder(target, dummy_params, m_node, max_selection_weight, [&](CWallet& wallet) {
|
||||||
CoinsResult available_coins;
|
CoinsResult available_coins;
|
||||||
for (int j = 0; j < 10; ++j) {
|
for (int j = 0; j < 10; ++j) {
|
||||||
add_coin(available_coins, wallet, CAmount(1 * COIN), CFeeRate(5000), 144, false, 0, true);
|
add_coin(available_coins, wallet, CAmount(1 * COIN), CFeeRate(5000), 144, false, 0, true);
|
||||||
@@ -1171,8 +1171,8 @@ BOOST_AUTO_TEST_CASE(coin_grinder_tests)
|
|||||||
// 3) Test selection when some coins surpass the max allowed weight while others not. --> must find a good solution
|
// 3) Test selection when some coins surpass the max allowed weight while others not. --> must find a good solution
|
||||||
// ################################################################################################################
|
// ################################################################################################################
|
||||||
CAmount target = 25.33L * COIN;
|
CAmount target = 25.33L * COIN;
|
||||||
int max_weight = 10'000; // WU
|
int max_selection_weight = 10'000; // WU
|
||||||
const auto& res = CoinGrinder(target, dummy_params, m_node, max_weight, [&](CWallet& wallet) {
|
const auto& res = CoinGrinder(target, dummy_params, m_node, max_selection_weight, [&](CWallet& wallet) {
|
||||||
CoinsResult available_coins;
|
CoinsResult available_coins;
|
||||||
for (int j = 0; j < 60; ++j) { // 60 UTXO --> 19,8 BTC total --> 60 × 272 WU = 16320 WU
|
for (int j = 0; j < 60; ++j) { // 60 UTXO --> 19,8 BTC total --> 60 × 272 WU = 16320 WU
|
||||||
add_coin(available_coins, wallet, CAmount(0.33 * COIN), CFeeRate(5000), 144, false, 0, true);
|
add_coin(available_coins, wallet, CAmount(0.33 * COIN), CFeeRate(5000), 144, false, 0, true);
|
||||||
@@ -1193,8 +1193,8 @@ BOOST_AUTO_TEST_CASE(coin_grinder_tests)
|
|||||||
// 4) Test that two less valuable UTXOs with a combined lower weight are preferred over a more valuable heavier UTXO
|
// 4) Test that two less valuable UTXOs with a combined lower weight are preferred over a more valuable heavier UTXO
|
||||||
// #################################################################################################################
|
// #################################################################################################################
|
||||||
CAmount target = 1.9L * COIN;
|
CAmount target = 1.9L * COIN;
|
||||||
int max_weight = 400'000; // WU
|
int max_selection_weight = 400'000; // WU
|
||||||
const auto& res = CoinGrinder(target, dummy_params, m_node, max_weight, [&](CWallet& wallet) {
|
const auto& res = CoinGrinder(target, dummy_params, m_node, max_selection_weight, [&](CWallet& wallet) {
|
||||||
CoinsResult available_coins;
|
CoinsResult available_coins;
|
||||||
add_coin(available_coins, wallet, CAmount(2 * COIN), CFeeRate(5000), 144, false, 0, true, 148);
|
add_coin(available_coins, wallet, CAmount(2 * COIN), CFeeRate(5000), 144, false, 0, true, 148);
|
||||||
add_coin(available_coins, wallet, CAmount(1 * COIN), CFeeRate(5000), 144, false, 0, true, 68);
|
add_coin(available_coins, wallet, CAmount(1 * COIN), CFeeRate(5000), 144, false, 0, true, 68);
|
||||||
@@ -1215,8 +1215,8 @@ BOOST_AUTO_TEST_CASE(coin_grinder_tests)
|
|||||||
// 5) Test finding a solution in a UTXO pool with mixed weights
|
// 5) Test finding a solution in a UTXO pool with mixed weights
|
||||||
// ################################################################################################################
|
// ################################################################################################################
|
||||||
CAmount target = 30L * COIN;
|
CAmount target = 30L * COIN;
|
||||||
int max_weight = 400'000; // WU
|
int max_selection_weight = 400'000; // WU
|
||||||
const auto& res = CoinGrinder(target, dummy_params, m_node, max_weight, [&](CWallet& wallet) {
|
const auto& res = CoinGrinder(target, dummy_params, m_node, max_selection_weight, [&](CWallet& wallet) {
|
||||||
CoinsResult available_coins;
|
CoinsResult available_coins;
|
||||||
for (int j = 0; j < 5; ++j) {
|
for (int j = 0; j < 5; ++j) {
|
||||||
// Add heavy coins {3, 6, 9, 12, 15}
|
// Add heavy coins {3, 6, 9, 12, 15}
|
||||||
@@ -1244,8 +1244,8 @@ BOOST_AUTO_TEST_CASE(coin_grinder_tests)
|
|||||||
// 6) Test that the lightest solution among many clones is found
|
// 6) Test that the lightest solution among many clones is found
|
||||||
// #################################################################################################################
|
// #################################################################################################################
|
||||||
CAmount target = 9.9L * COIN;
|
CAmount target = 9.9L * COIN;
|
||||||
int max_weight = 400'000; // WU
|
int max_selection_weight = 400'000; // WU
|
||||||
const auto& res = CoinGrinder(target, dummy_params, m_node, max_weight, [&](CWallet& wallet) {
|
const auto& res = CoinGrinder(target, dummy_params, m_node, max_selection_weight, [&](CWallet& wallet) {
|
||||||
CoinsResult available_coins;
|
CoinsResult available_coins;
|
||||||
// Expected Result: 4 + 3 + 2 + 1 = 10 BTC at 400 vB
|
// Expected Result: 4 + 3 + 2 + 1 = 10 BTC at 400 vB
|
||||||
add_coin(available_coins, wallet, CAmount(4 * COIN), CFeeRate(5000), 144, false, 0, true, 100);
|
add_coin(available_coins, wallet, CAmount(4 * COIN), CFeeRate(5000), 144, false, 0, true, 100);
|
||||||
@@ -1283,8 +1283,8 @@ BOOST_AUTO_TEST_CASE(coin_grinder_tests)
|
|||||||
// 7) Test that lots of tiny UTXOs can be skipped if they are too heavy while there are enough funds in lookahead
|
// 7) Test that lots of tiny UTXOs can be skipped if they are too heavy while there are enough funds in lookahead
|
||||||
// #################################################################################################################
|
// #################################################################################################################
|
||||||
CAmount target = 1.9L * COIN;
|
CAmount target = 1.9L * COIN;
|
||||||
int max_weight = 40000; // WU
|
int max_selection_weight = 40000; // WU
|
||||||
const auto& res = CoinGrinder(target, dummy_params, m_node, max_weight, [&](CWallet& wallet) {
|
const auto& res = CoinGrinder(target, dummy_params, m_node, max_selection_weight, [&](CWallet& wallet) {
|
||||||
CoinsResult available_coins;
|
CoinsResult available_coins;
|
||||||
add_coin(available_coins, wallet, CAmount(1.8 * COIN), CFeeRate(5000), 144, false, 0, true, 2500);
|
add_coin(available_coins, wallet, CAmount(1.8 * COIN), CFeeRate(5000), 144, false, 0, true, 2500);
|
||||||
add_coin(available_coins, wallet, CAmount(1 * COIN), CFeeRate(5000), 144, false, 0, true, 1000);
|
add_coin(available_coins, wallet, CAmount(1 * COIN), CFeeRate(5000), 144, false, 0, true, 1000);
|
||||||
@@ -1308,13 +1308,13 @@ BOOST_AUTO_TEST_CASE(coin_grinder_tests)
|
|||||||
static util::Result<SelectionResult> SelectCoinsSRD(const CAmount& target,
|
static util::Result<SelectionResult> SelectCoinsSRD(const CAmount& target,
|
||||||
const CoinSelectionParams& cs_params,
|
const CoinSelectionParams& cs_params,
|
||||||
const node::NodeContext& m_node,
|
const node::NodeContext& m_node,
|
||||||
int max_weight,
|
int max_selection_weight,
|
||||||
std::function<CoinsResult(CWallet&)> coin_setup)
|
std::function<CoinsResult(CWallet&)> coin_setup)
|
||||||
{
|
{
|
||||||
std::unique_ptr<CWallet> wallet = NewWallet(m_node);
|
std::unique_ptr<CWallet> wallet = NewWallet(m_node);
|
||||||
CoinEligibilityFilter filter(0, 0, 0); // accept all coins without ancestors
|
CoinEligibilityFilter filter(0, 0, 0); // accept all coins without ancestors
|
||||||
Groups group = GroupOutputs(*wallet, coin_setup(*wallet), cs_params, {{filter}})[filter].all_groups;
|
Groups group = GroupOutputs(*wallet, coin_setup(*wallet), cs_params, {{filter}})[filter].all_groups;
|
||||||
return SelectCoinsSRD(group.positive_group, target, cs_params.m_change_fee, cs_params.rng_fast, max_weight);
|
return SelectCoinsSRD(group.positive_group, target, cs_params.m_change_fee, cs_params.rng_fast, max_selection_weight);
|
||||||
}
|
}
|
||||||
|
|
||||||
BOOST_AUTO_TEST_CASE(srd_tests)
|
BOOST_AUTO_TEST_CASE(srd_tests)
|
||||||
@@ -1342,8 +1342,8 @@ BOOST_AUTO_TEST_CASE(srd_tests)
|
|||||||
// 1) Insufficient funds, select all provided coins and fail
|
// 1) Insufficient funds, select all provided coins and fail
|
||||||
// #########################################################
|
// #########################################################
|
||||||
CAmount target = 49.5L * COIN;
|
CAmount target = 49.5L * COIN;
|
||||||
int max_weight = 10000; // high enough to not fail for this reason.
|
int max_selection_weight = 10000; // high enough to not fail for this reason.
|
||||||
const auto& res = SelectCoinsSRD(target, dummy_params, m_node, max_weight, [&](CWallet& wallet) {
|
const auto& res = SelectCoinsSRD(target, dummy_params, m_node, max_selection_weight, [&](CWallet& wallet) {
|
||||||
CoinsResult available_coins;
|
CoinsResult available_coins;
|
||||||
for (int j = 0; j < 10; ++j) {
|
for (int j = 0; j < 10; ++j) {
|
||||||
add_coin(available_coins, wallet, CAmount(1 * COIN));
|
add_coin(available_coins, wallet, CAmount(1 * COIN));
|
||||||
@@ -1360,8 +1360,8 @@ BOOST_AUTO_TEST_CASE(srd_tests)
|
|||||||
// 2) Test max weight exceeded
|
// 2) Test max weight exceeded
|
||||||
// ###########################
|
// ###########################
|
||||||
CAmount target = 49.5L * COIN;
|
CAmount target = 49.5L * COIN;
|
||||||
int max_weight = 3000;
|
int max_selection_weight = 3000;
|
||||||
const auto& res = SelectCoinsSRD(target, dummy_params, m_node, max_weight, [&](CWallet& wallet) {
|
const auto& res = SelectCoinsSRD(target, dummy_params, m_node, max_selection_weight, [&](CWallet& wallet) {
|
||||||
CoinsResult available_coins;
|
CoinsResult available_coins;
|
||||||
for (int j = 0; j < 10; ++j) {
|
for (int j = 0; j < 10; ++j) {
|
||||||
/* 10 × 1 BTC + 10 × 2 BTC = 30 BTC. 20 × 272 WU = 5440 WU */
|
/* 10 × 1 BTC + 10 × 2 BTC = 30 BTC. 20 × 272 WU = 5440 WU */
|
||||||
@@ -1379,8 +1379,8 @@ BOOST_AUTO_TEST_CASE(srd_tests)
|
|||||||
// 3) Test selection when some coins surpass the max allowed weight while others not. --> must find a good solution
|
// 3) Test selection when some coins surpass the max allowed weight while others not. --> must find a good solution
|
||||||
// ################################################################################################################
|
// ################################################################################################################
|
||||||
CAmount target = 25.33L * COIN;
|
CAmount target = 25.33L * COIN;
|
||||||
int max_weight = 10000; // WU
|
int max_selection_weight = 10000; // WU
|
||||||
const auto& res = SelectCoinsSRD(target, dummy_params, m_node, max_weight, [&](CWallet& wallet) {
|
const auto& res = SelectCoinsSRD(target, dummy_params, m_node, max_selection_weight, [&](CWallet& wallet) {
|
||||||
CoinsResult available_coins;
|
CoinsResult available_coins;
|
||||||
for (int j = 0; j < 60; ++j) { // 60 UTXO --> 19,8 BTC total --> 60 × 272 WU = 16320 WU
|
for (int j = 0; j < 60; ++j) { // 60 UTXO --> 19,8 BTC total --> 60 × 272 WU = 16320 WU
|
||||||
add_coin(available_coins, wallet, CAmount(0.33 * COIN), CFeeRate(0), 144, false, 0, true);
|
add_coin(available_coins, wallet, CAmount(0.33 * COIN), CFeeRate(0), 144, false, 0, true);
|
||||||
@@ -1415,7 +1415,7 @@ static bool has_coin(const CoinSet& set, CAmount amount)
|
|||||||
return std::any_of(set.begin(), set.end(), [&](const auto& coin) { return coin->GetEffectiveValue() == amount; });
|
return std::any_of(set.begin(), set.end(), [&](const auto& coin) { return coin->GetEffectiveValue() == amount; });
|
||||||
}
|
}
|
||||||
|
|
||||||
BOOST_AUTO_TEST_CASE(check_max_weight)
|
BOOST_AUTO_TEST_CASE(check_max_selection_weight)
|
||||||
{
|
{
|
||||||
const CAmount target = 49.5L * COIN;
|
const CAmount target = 49.5L * COIN;
|
||||||
CCoinControl cc;
|
CCoinControl cc;
|
||||||
|
|||||||
@@ -195,11 +195,11 @@ FUZZ_TARGET(coin_grinder_is_optimal)
|
|||||||
|
|
||||||
if (best_weight < std::numeric_limits<int>::max()) {
|
if (best_weight < std::numeric_limits<int>::max()) {
|
||||||
// Sufficient funds and acceptable weight: CoinGrinder should find at least one solution
|
// Sufficient funds and acceptable weight: CoinGrinder should find at least one solution
|
||||||
int high_max_weight = fuzzed_data_provider.ConsumeIntegralInRange<int>(best_weight, std::numeric_limits<int>::max());
|
int high_max_selection_weight = fuzzed_data_provider.ConsumeIntegralInRange<int>(best_weight, std::numeric_limits<int>::max());
|
||||||
|
|
||||||
auto result_cg = CoinGrinder(group_pos, target, coin_params.m_min_change_target, high_max_weight);
|
auto result_cg = CoinGrinder(group_pos, target, coin_params.m_min_change_target, high_max_selection_weight);
|
||||||
assert(result_cg);
|
assert(result_cg);
|
||||||
assert(result_cg->GetWeight() <= high_max_weight);
|
assert(result_cg->GetWeight() <= high_max_selection_weight);
|
||||||
assert(result_cg->GetSelectedEffectiveValue() >= target + coin_params.m_min_change_target);
|
assert(result_cg->GetSelectedEffectiveValue() >= target + coin_params.m_min_change_target);
|
||||||
assert(best_weight < result_cg->GetWeight() || (best_weight == result_cg->GetWeight() && best_amount <= result_cg->GetSelectedEffectiveValue()));
|
assert(best_weight < result_cg->GetWeight() || (best_weight == result_cg->GetWeight() && best_amount <= result_cg->GetSelectedEffectiveValue()));
|
||||||
if (result_cg->GetAlgoCompleted()) {
|
if (result_cg->GetAlgoCompleted()) {
|
||||||
@@ -210,8 +210,8 @@ FUZZ_TARGET(coin_grinder_is_optimal)
|
|||||||
}
|
}
|
||||||
|
|
||||||
// CoinGrinder cannot ever find a better solution than the brute-forced best, or there is none in the first place
|
// CoinGrinder cannot ever find a better solution than the brute-forced best, or there is none in the first place
|
||||||
int low_max_weight = fuzzed_data_provider.ConsumeIntegralInRange<int>(0, best_weight - 1);
|
int low_max_selection_weight = fuzzed_data_provider.ConsumeIntegralInRange<int>(0, best_weight - 1);
|
||||||
auto result_cg = CoinGrinder(group_pos, target, coin_params.m_min_change_target, low_max_weight);
|
auto result_cg = CoinGrinder(group_pos, target, coin_params.m_min_change_target, low_max_selection_weight);
|
||||||
// Max_weight should have been exceeded, or there were insufficient funds
|
// Max_weight should have been exceeded, or there were insufficient funds
|
||||||
assert(!result_cg);
|
assert(!result_cg);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in new issue
Block a user