Merge bitcoin/bitcoin#29523: Wallet: Add max_tx_weight to transaction funding options (take 2)
734076c6de[wallet, rpc]: add `max_tx_weight` to tx funding options (ismaelsadeeq)b6fc5043c1[wallet]: update the data type of `change_output_size`, `change_spend_size` and `tx_noinputs_size` to `int` (ismaelsadeeq)baab0d2d43[doc]: update reason for deducting change output weight (ismaelsadeeq)7f61d31a5c[refactor]: update coin selection algorithms input parameter `max_weight` name (ismaelsadeeq) Pull request description: This PR taken over from #29264 The PR added an option `max_tx_weight` to transaction funding RPC's that ensures the resulting transaction weight does not exceed the specified `max_tx_weight` limit. If `max_tx_weight` is not given `MAX_STANDARD_TX_WEIGHT` is used as the max threshold. This PR addressed outstanding review comments in #29264 For more context and rationale behind this PR see https://delvingbitcoin.org/t/lightning-transactions-with-v3-and-ephemeral-anchors/418/11?u=instagibbs ACKs for top commit: achow101: ACK734076c6defurszy: utACK734076c6derkrux: reACK [734076c](https://github.com/bitcoin/bitcoin/pull/29523/commits/734076c6de1781f957c8bc3bf7ed6951920cfcf6) Tree-SHA512: 013501aa443d239ee2ac01bccfc5296490c27b4edebe5cfca6b96c842375e895e5cfeb5424e82e359be581460f8be92095855763a62779a18ccd5bdfdd7ddce7
This commit is contained in:
10 files changed
+178
-76
No files matched your search
@@ -1097,13 +1097,13 @@ BOOST_AUTO_TEST_CASE(effective_value_test)
|
||||
static util::Result<SelectionResult> CoinGrinder(const CAmount& target,
|
||||
const CoinSelectionParams& cs_params,
|
||||
const node::NodeContext& m_node,
|
||||
int max_weight,
|
||||
int max_selection_weight,
|
||||
std::function<CoinsResult(CWallet&)> coin_setup)
|
||||
{
|
||||
std::unique_ptr<CWallet> wallet = NewWallet(m_node);
|
||||
CoinEligibilityFilter filter(0, 0, 0); // accept all coins without ancestors
|
||||
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)
|
||||
@@ -1135,8 +1135,8 @@ BOOST_AUTO_TEST_CASE(coin_grinder_tests)
|
||||
// 1) Insufficient funds, select all provided coins and fail
|
||||
// #########################################################
|
||||
CAmount target = 49.5L * COIN;
|
||||
int max_weight = 10'000; // high enough to not fail for this reason.
|
||||
const auto& res = CoinGrinder(target, dummy_params, m_node, max_weight, [&](CWallet& wallet) {
|
||||
int max_selection_weight = 10'000; // high enough to not fail for this reason.
|
||||
const auto& res = CoinGrinder(target, dummy_params, m_node, max_selection_weight, [&](CWallet& wallet) {
|
||||
CoinsResult available_coins;
|
||||
for (int j = 0; j < 10; ++j) {
|
||||
add_coin(available_coins, wallet, CAmount(1 * COIN));
|
||||
@@ -1153,8 +1153,8 @@ BOOST_AUTO_TEST_CASE(coin_grinder_tests)
|
||||
// 2) Test max weight exceeded
|
||||
// ###########################
|
||||
CAmount target = 29.5L * COIN;
|
||||
int max_weight = 3000;
|
||||
const auto& res = CoinGrinder(target, dummy_params, m_node, max_weight, [&](CWallet& wallet) {
|
||||
int max_selection_weight = 3000;
|
||||
const auto& res = CoinGrinder(target, dummy_params, m_node, max_selection_weight, [&](CWallet& wallet) {
|
||||
CoinsResult available_coins;
|
||||
for (int j = 0; j < 10; ++j) {
|
||||
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
|
||||
// ################################################################################################################
|
||||
CAmount target = 25.33L * COIN;
|
||||
int max_weight = 10'000; // WU
|
||||
const auto& res = CoinGrinder(target, dummy_params, m_node, max_weight, [&](CWallet& wallet) {
|
||||
int max_selection_weight = 10'000; // WU
|
||||
const auto& res = CoinGrinder(target, dummy_params, m_node, max_selection_weight, [&](CWallet& wallet) {
|
||||
CoinsResult available_coins;
|
||||
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);
|
||||
@@ -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
|
||||
// #################################################################################################################
|
||||
CAmount target = 1.9L * COIN;
|
||||
int max_weight = 400'000; // WU
|
||||
const auto& res = CoinGrinder(target, dummy_params, m_node, max_weight, [&](CWallet& wallet) {
|
||||
int max_selection_weight = 400'000; // WU
|
||||
const auto& res = CoinGrinder(target, dummy_params, m_node, max_selection_weight, [&](CWallet& wallet) {
|
||||
CoinsResult available_coins;
|
||||
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);
|
||||
@@ -1215,8 +1215,8 @@ BOOST_AUTO_TEST_CASE(coin_grinder_tests)
|
||||
// 5) Test finding a solution in a UTXO pool with mixed weights
|
||||
// ################################################################################################################
|
||||
CAmount target = 30L * COIN;
|
||||
int max_weight = 400'000; // WU
|
||||
const auto& res = CoinGrinder(target, dummy_params, m_node, max_weight, [&](CWallet& wallet) {
|
||||
int max_selection_weight = 400'000; // WU
|
||||
const auto& res = CoinGrinder(target, dummy_params, m_node, max_selection_weight, [&](CWallet& wallet) {
|
||||
CoinsResult available_coins;
|
||||
for (int j = 0; j < 5; ++j) {
|
||||
// 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
|
||||
// #################################################################################################################
|
||||
CAmount target = 9.9L * COIN;
|
||||
int max_weight = 400'000; // WU
|
||||
const auto& res = CoinGrinder(target, dummy_params, m_node, max_weight, [&](CWallet& wallet) {
|
||||
int max_selection_weight = 400'000; // WU
|
||||
const auto& res = CoinGrinder(target, dummy_params, m_node, max_selection_weight, [&](CWallet& wallet) {
|
||||
CoinsResult available_coins;
|
||||
// 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);
|
||||
@@ -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
|
||||
// #################################################################################################################
|
||||
CAmount target = 1.9L * COIN;
|
||||
int max_weight = 40000; // WU
|
||||
const auto& res = CoinGrinder(target, dummy_params, m_node, max_weight, [&](CWallet& wallet) {
|
||||
int max_selection_weight = 40000; // WU
|
||||
const auto& res = CoinGrinder(target, dummy_params, m_node, max_selection_weight, [&](CWallet& wallet) {
|
||||
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 * 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,
|
||||
const CoinSelectionParams& cs_params,
|
||||
const node::NodeContext& m_node,
|
||||
int max_weight,
|
||||
int max_selection_weight,
|
||||
std::function<CoinsResult(CWallet&)> coin_setup)
|
||||
{
|
||||
std::unique_ptr<CWallet> wallet = NewWallet(m_node);
|
||||
CoinEligibilityFilter filter(0, 0, 0); // accept all coins without ancestors
|
||||
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)
|
||||
@@ -1342,8 +1342,8 @@ BOOST_AUTO_TEST_CASE(srd_tests)
|
||||
// 1) Insufficient funds, select all provided coins and fail
|
||||
// #########################################################
|
||||
CAmount target = 49.5L * COIN;
|
||||
int max_weight = 10000; // high enough to not fail for this reason.
|
||||
const auto& res = SelectCoinsSRD(target, dummy_params, m_node, max_weight, [&](CWallet& wallet) {
|
||||
int max_selection_weight = 10000; // high enough to not fail for this reason.
|
||||
const auto& res = SelectCoinsSRD(target, dummy_params, m_node, max_selection_weight, [&](CWallet& wallet) {
|
||||
CoinsResult available_coins;
|
||||
for (int j = 0; j < 10; ++j) {
|
||||
add_coin(available_coins, wallet, CAmount(1 * COIN));
|
||||
@@ -1360,8 +1360,8 @@ BOOST_AUTO_TEST_CASE(srd_tests)
|
||||
// 2) Test max weight exceeded
|
||||
// ###########################
|
||||
CAmount target = 49.5L * COIN;
|
||||
int max_weight = 3000;
|
||||
const auto& res = SelectCoinsSRD(target, dummy_params, m_node, max_weight, [&](CWallet& wallet) {
|
||||
int max_selection_weight = 3000;
|
||||
const auto& res = SelectCoinsSRD(target, dummy_params, m_node, max_selection_weight, [&](CWallet& wallet) {
|
||||
CoinsResult available_coins;
|
||||
for (int j = 0; j < 10; ++j) {
|
||||
/* 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
|
||||
// ################################################################################################################
|
||||
CAmount target = 25.33L * COIN;
|
||||
int max_weight = 10000; // WU
|
||||
const auto& res = SelectCoinsSRD(target, dummy_params, m_node, max_weight, [&](CWallet& wallet) {
|
||||
int max_selection_weight = 10000; // WU
|
||||
const auto& res = SelectCoinsSRD(target, dummy_params, m_node, max_selection_weight, [&](CWallet& wallet) {
|
||||
CoinsResult available_coins;
|
||||
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);
|
||||
@@ -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; });
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(check_max_weight)
|
||||
BOOST_AUTO_TEST_CASE(check_max_selection_weight)
|
||||
{
|
||||
const CAmount target = 49.5L * COIN;
|
||||
CCoinControl cc;
|
||||
|
||||
@@ -195,11 +195,11 @@ FUZZ_TARGET(coin_grinder_is_optimal)
|
||||
|
||||
if (best_weight < std::numeric_limits<int>::max()) {
|
||||
// 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->GetWeight() <= high_max_weight);
|
||||
assert(result_cg->GetWeight() <= high_max_selection_weight);
|
||||
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()));
|
||||
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
|
||||
int low_max_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);
|
||||
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_selection_weight);
|
||||
// Max_weight should have been exceeded, or there were insufficient funds
|
||||
assert(!result_cg);
|
||||
}
|
||||
@@ -256,29 +256,34 @@ FUZZ_TARGET(coinselection)
|
||||
(void)group.EligibleForSpending(filter);
|
||||
}
|
||||
|
||||
int max_selection_weight = fuzzed_data_provider.ConsumeIntegralInRange<int>(0, std::numeric_limits<int>::max());
|
||||
|
||||
// Run coinselection algorithms
|
||||
auto result_bnb = coin_params.m_subtract_fee_outputs ? util::Error{Untranslated("BnB disabled when SFFO is enabled")} :
|
||||
SelectCoinsBnB(group_pos, target, coin_params.m_cost_of_change, MAX_STANDARD_TX_WEIGHT);
|
||||
SelectCoinsBnB(group_pos, target, coin_params.m_cost_of_change, max_selection_weight);
|
||||
if (result_bnb) {
|
||||
assert(result_bnb->GetChange(coin_params.min_viable_change, coin_params.m_change_fee) == 0);
|
||||
assert(result_bnb->GetSelectedValue() >= target);
|
||||
assert(result_bnb->GetWeight() <= max_selection_weight);
|
||||
(void)result_bnb->GetShuffledInputVector();
|
||||
(void)result_bnb->GetInputSet();
|
||||
}
|
||||
|
||||
auto result_srd = SelectCoinsSRD(group_pos, target, coin_params.m_change_fee, fast_random_context, MAX_STANDARD_TX_WEIGHT);
|
||||
auto result_srd = SelectCoinsSRD(group_pos, target, coin_params.m_change_fee, fast_random_context, max_selection_weight);
|
||||
if (result_srd) {
|
||||
assert(result_srd->GetSelectedValue() >= target);
|
||||
assert(result_srd->GetChange(CHANGE_LOWER, coin_params.m_change_fee) > 0); // Demonstrate that SRD creates change of at least CHANGE_LOWER
|
||||
assert(result_srd->GetWeight() <= max_selection_weight);
|
||||
result_srd->RecalculateWaste(coin_params.min_viable_change, coin_params.m_cost_of_change, coin_params.m_change_fee);
|
||||
(void)result_srd->GetShuffledInputVector();
|
||||
(void)result_srd->GetInputSet();
|
||||
}
|
||||
|
||||
CAmount change_target{GenerateChangeTarget(target, coin_params.m_change_fee, fast_random_context)};
|
||||
auto result_knapsack = KnapsackSolver(group_all, target, change_target, fast_random_context, MAX_STANDARD_TX_WEIGHT);
|
||||
auto result_knapsack = KnapsackSolver(group_all, target, change_target, fast_random_context, max_selection_weight);
|
||||
if (result_knapsack) {
|
||||
assert(result_knapsack->GetSelectedValue() >= target);
|
||||
assert(result_knapsack->GetWeight() <= max_selection_weight);
|
||||
result_knapsack->RecalculateWaste(coin_params.min_viable_change, coin_params.m_cost_of_change, coin_params.m_change_fee);
|
||||
(void)result_knapsack->GetShuffledInputVector();
|
||||
(void)result_knapsack->GetInputSet();
|
||||
|
||||
Reference in new issue
Block a user