Merge bitcoin/bitcoin#26720: wallet: coin selection, don't return results that exceed the max allowed weight
25ab14712brefactor: coinselector_tests, unify wallet creation code (furszy)ba9431c505test: coverage for bnb max weight (furszy)5a2bc45ee0wallet: clean post coin selection max weight filter (furszy)2d112584e3coin selection: BnB, don't return selection if exceeds max allowed tx weight (furszy)d3a1c098e4test: coin selection, add coverage for SRD (furszy)9d9689e5a6coin selection: heap-ify SRD, don't return selection if exceeds max tx weight (furszy)6107ec2229coin selection: knapsack, select closest UTXO above target if result exceeds max tx size (furszy)1284223691wallet: refactor coin selection algos to return util::Result (furszy) Pull request description: Coming from the following comment https://github.com/bitcoin/bitcoin/pull/25729#discussion_r1029324367. The reason why we are adding hundreds of UTXO from different sources when the target amount is covered only by one of them is because only SRD returns a usable result. Context: In the test, we create 1515 UTXOs with 0.033 BTC each, and 1 UTXO with 50 BTC. Then perform Coin Selection to fund 49.5 BTC. As the selection of the 1515 small UTXOs exceeds the max allowed tx size, the expectation here is to receive a selection result that only contain the big UTXO. Which is not happening for the following reason: Knapsack returns a result that exceeds the max allowed transaction size, when it should return the closest utxo above the target, so we fallback to SRD who selects coins randomly up until the target is met. So we end up with a selection result with lot more coins than what is needed. ACKs for top commit: S3RK: ACK25ab14712bachow101: ACK25ab14712bXekyo: reACK25ab14712btheStack: Code-review ACK25ab14712bTree-SHA512: 2425de4cc479b4db999b3b2e02eb522a2130a06379cca0418672a51c4076971a1d427191173820db76a0f85a8edfff100114e1c38fb3b5dc51598d07cabe1a60
This commit is contained in:
6 files changed
+288
-104
No files matched your search
@@ -68,7 +68,7 @@ static void add_coin(const CAmount& nValue, int nInput, CoinSet& set, CAmount fe
|
||||
set.insert(std::make_shared<COutput>(coin));
|
||||
}
|
||||
|
||||
static void add_coin(CoinsResult& available_coins, CWallet& wallet, const CAmount& nValue, CFeeRate feerate = CFeeRate(0), int nAge = 6*24, bool fIsFromMe = false, int nInput =0, bool spendable = false)
|
||||
static void add_coin(CoinsResult& available_coins, CWallet& wallet, const CAmount& nValue, CFeeRate feerate = CFeeRate(0), int nAge = 6*24, bool fIsFromMe = false, int nInput =0, bool spendable = false, int custom_size = 0)
|
||||
{
|
||||
CMutableTransaction tx;
|
||||
tx.nLockTime = nextLockTime++; // so all transactions get different hashes
|
||||
@@ -84,7 +84,21 @@ static void add_coin(CoinsResult& available_coins, CWallet& wallet, const CAmoun
|
||||
assert(ret.second);
|
||||
CWalletTx& wtx = (*ret.first).second;
|
||||
const auto& txout = wtx.tx->vout.at(nInput);
|
||||
available_coins.Add(OutputType::BECH32, {COutPoint(wtx.GetHash(), nInput), txout, nAge, CalculateMaximumSignedInputSize(txout, &wallet, /*coin_control=*/nullptr), /*spendable=*/ true, /*solvable=*/ true, /*safe=*/ true, wtx.GetTxTime(), fIsFromMe, feerate});
|
||||
available_coins.Add(OutputType::BECH32, {COutPoint(wtx.GetHash(), nInput), txout, nAge, custom_size == 0 ? CalculateMaximumSignedInputSize(txout, &wallet, /*coin_control=*/nullptr) : custom_size, /*spendable=*/ true, /*solvable=*/ true, /*safe=*/ true, wtx.GetTxTime(), fIsFromMe, feerate});
|
||||
}
|
||||
|
||||
// Helpers
|
||||
std::optional<SelectionResult> KnapsackSolver(std::vector<OutputGroup>& groups, const CAmount& nTargetValue,
|
||||
CAmount change_target, FastRandomContext& rng)
|
||||
{
|
||||
auto res{KnapsackSolver(groups, nTargetValue, change_target, rng, MAX_STANDARD_TX_WEIGHT)};
|
||||
return res ? std::optional<SelectionResult>(*res) : std::nullopt;
|
||||
}
|
||||
|
||||
std::optional<SelectionResult> SelectCoinsBnB(std::vector<OutputGroup>& utxo_pool, const CAmount& selection_target, const CAmount& cost_of_change)
|
||||
{
|
||||
auto res{SelectCoinsBnB(utxo_pool, selection_target, cost_of_change, MAX_STANDARD_TX_WEIGHT)};
|
||||
return res ? std::optional<SelectionResult>(*res) : std::nullopt;
|
||||
}
|
||||
|
||||
/** Check if SelectionResult a is equivalent to SelectionResult b.
|
||||
@@ -128,13 +142,15 @@ static CAmount make_hard_case(int utxos, std::vector<COutput>& utxo_pool)
|
||||
return target;
|
||||
}
|
||||
|
||||
inline std::vector<OutputGroup>& GroupCoins(const std::vector<COutput>& available_coins)
|
||||
inline std::vector<OutputGroup>& GroupCoins(const std::vector<COutput>& available_coins, bool subtract_fee_outputs = false)
|
||||
{
|
||||
static std::vector<OutputGroup> static_groups;
|
||||
static_groups.clear();
|
||||
for (auto& coin : available_coins) {
|
||||
static_groups.emplace_back();
|
||||
static_groups.back().Insert(std::make_shared<COutput>(coin), /*ancestors=*/ 0, /*descendants=*/ 0);
|
||||
OutputGroup& group = static_groups.back();
|
||||
group.Insert(std::make_shared<COutput>(coin), /*ancestors=*/ 0, /*descendants=*/ 0);
|
||||
group.m_subtract_fee_outputs = subtract_fee_outputs;
|
||||
}
|
||||
return static_groups;
|
||||
}
|
||||
@@ -158,6 +174,16 @@ inline std::vector<OutputGroup>& KnapsackGroupOutputs(const CoinsResult& availab
|
||||
return static_groups.all_groups.mixed_group;
|
||||
}
|
||||
|
||||
static std::unique_ptr<CWallet> NewWallet(const node::NodeContext& m_node, const std::string& wallet_name = "")
|
||||
{
|
||||
std::unique_ptr<CWallet> wallet = std::make_unique<CWallet>(m_node.chain.get(), wallet_name, CreateMockWalletDatabase());
|
||||
BOOST_CHECK(wallet->LoadWallet() == DBErrors::LOAD_OK);
|
||||
LOCK(wallet->cs_wallet);
|
||||
wallet->SetWalletFlag(WALLET_FLAG_DESCRIPTORS);
|
||||
wallet->SetupDescriptorScriptPubKeyMans();
|
||||
return wallet;
|
||||
}
|
||||
|
||||
// Branch and bound coin selection tests
|
||||
BOOST_AUTO_TEST_CASE(bnb_search_test)
|
||||
{
|
||||
@@ -294,11 +320,7 @@ BOOST_AUTO_TEST_CASE(bnb_search_test)
|
||||
coin_selection_params_bnb.m_subtract_fee_outputs = true;
|
||||
|
||||
{
|
||||
std::unique_ptr<CWallet> wallet = std::make_unique<CWallet>(m_node.chain.get(), "", CreateMockWalletDatabase());
|
||||
wallet->LoadWallet();
|
||||
LOCK(wallet->cs_wallet);
|
||||
wallet->SetWalletFlag(WALLET_FLAG_DESCRIPTORS);
|
||||
wallet->SetupDescriptorScriptPubKeyMans();
|
||||
std::unique_ptr<CWallet> wallet = NewWallet(m_node);
|
||||
|
||||
CoinsResult available_coins;
|
||||
|
||||
@@ -316,11 +338,7 @@ BOOST_AUTO_TEST_CASE(bnb_search_test)
|
||||
}
|
||||
|
||||
{
|
||||
std::unique_ptr<CWallet> wallet = std::make_unique<CWallet>(m_node.chain.get(), "", CreateMockWalletDatabase());
|
||||
wallet->LoadWallet();
|
||||
LOCK(wallet->cs_wallet);
|
||||
wallet->SetWalletFlag(WALLET_FLAG_DESCRIPTORS);
|
||||
wallet->SetupDescriptorScriptPubKeyMans();
|
||||
std::unique_ptr<CWallet> wallet = NewWallet(m_node);
|
||||
|
||||
CoinsResult available_coins;
|
||||
|
||||
@@ -335,15 +353,13 @@ BOOST_AUTO_TEST_CASE(bnb_search_test)
|
||||
selected_input.Insert(select_coin, coin_selection_params_bnb.m_subtract_fee_outputs);
|
||||
available_coins.Erase({available_coins.coins[OutputType::BECH32].begin()->outpoint});
|
||||
coin_selection_params_bnb.m_effective_feerate = CFeeRate(0);
|
||||
LOCK(wallet->cs_wallet);
|
||||
const auto result10 = SelectCoins(*wallet, available_coins, selected_input, 10 * CENT, coin_control, coin_selection_params_bnb);
|
||||
BOOST_CHECK(result10);
|
||||
}
|
||||
{
|
||||
std::unique_ptr<CWallet> wallet = std::make_unique<CWallet>(m_node.chain.get(), "", CreateMockWalletDatabase());
|
||||
wallet->LoadWallet();
|
||||
LOCK(wallet->cs_wallet);
|
||||
wallet->SetWalletFlag(WALLET_FLAG_DESCRIPTORS);
|
||||
wallet->SetupDescriptorScriptPubKeyMans();
|
||||
std::unique_ptr<CWallet> wallet = NewWallet(m_node);
|
||||
LOCK(wallet->cs_wallet); // Every 'SelectCoins' call requires it
|
||||
|
||||
CoinsResult available_coins;
|
||||
|
||||
@@ -397,6 +413,37 @@ BOOST_AUTO_TEST_CASE(bnb_search_test)
|
||||
const auto result13 = SelectCoins(*wallet, available_coins, selected_input, 10 * CENT, coin_control, coin_selection_params_bnb);
|
||||
BOOST_CHECK(EquivalentResult(expected_result, *result13));
|
||||
}
|
||||
|
||||
{
|
||||
// Test bnb max weight exceeded
|
||||
// Inputs set [10, 9, 8, 5, 3, 1], Selection Target = 16 and coin 5 exceeding the max weight.
|
||||
|
||||
std::unique_ptr<CWallet> wallet = NewWallet(m_node);
|
||||
|
||||
CoinsResult available_coins;
|
||||
add_coin(available_coins, *wallet, 10 * CENT, coin_selection_params_bnb.m_effective_feerate, 6 * 24, false, 0, true);
|
||||
add_coin(available_coins, *wallet, 9 * CENT, coin_selection_params_bnb.m_effective_feerate, 6 * 24, false, 0, true);
|
||||
add_coin(available_coins, *wallet, 8 * CENT, coin_selection_params_bnb.m_effective_feerate, 6 * 24, false, 0, true);
|
||||
add_coin(available_coins, *wallet, 5 * CENT, coin_selection_params_bnb.m_effective_feerate, 6 * 24, false, 0, true, /*custom_size=*/MAX_STANDARD_TX_WEIGHT);
|
||||
add_coin(available_coins, *wallet, 3 * CENT, coin_selection_params_bnb.m_effective_feerate, 6 * 24, false, 0, true);
|
||||
add_coin(available_coins, *wallet, 1 * CENT, coin_selection_params_bnb.m_effective_feerate, 6 * 24, false, 0, true);
|
||||
|
||||
CAmount selection_target = 16 * CENT;
|
||||
const auto& no_res = SelectCoinsBnB(GroupCoins(available_coins.All(), /*subtract_fee_outputs*/true),
|
||||
selection_target, /*cost_of_change=*/0, MAX_STANDARD_TX_WEIGHT);
|
||||
BOOST_ASSERT(!no_res);
|
||||
BOOST_CHECK(util::ErrorString(no_res).original.find("The inputs size exceeds the maximum weight") != std::string::npos);
|
||||
|
||||
// Now add same coin value with a good size and check that it gets selected
|
||||
add_coin(available_coins, *wallet, 5 * CENT, coin_selection_params_bnb.m_effective_feerate, 6 * 24, false, 0, true);
|
||||
const auto& res = SelectCoinsBnB(GroupCoins(available_coins.All(), /*subtract_fee_outputs*/true), selection_target, /*cost_of_change=*/0);
|
||||
|
||||
expected_result.Clear();
|
||||
add_coin(8 * CENT, 2, expected_result);
|
||||
add_coin(5 * CENT, 2, expected_result);
|
||||
add_coin(3 * CENT, 2, expected_result);
|
||||
BOOST_CHECK(EquivalentResult(expected_result, *res));
|
||||
}
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(knapsack_solver_test)
|
||||
@@ -404,11 +451,7 @@ BOOST_AUTO_TEST_CASE(knapsack_solver_test)
|
||||
FastRandomContext rand{};
|
||||
const auto temp1{[&rand](std::vector<OutputGroup>& g, const CAmount& v, CAmount c) { return KnapsackSolver(g, v, c, rand); }};
|
||||
const auto KnapsackSolver{temp1};
|
||||
std::unique_ptr<CWallet> wallet = std::make_unique<CWallet>(m_node.chain.get(), "", CreateMockWalletDatabase());
|
||||
wallet->LoadWallet();
|
||||
LOCK(wallet->cs_wallet);
|
||||
wallet->SetWalletFlag(WALLET_FLAG_DESCRIPTORS);
|
||||
wallet->SetupDescriptorScriptPubKeyMans();
|
||||
std::unique_ptr<CWallet> wallet = NewWallet(m_node);
|
||||
|
||||
CoinsResult available_coins;
|
||||
|
||||
@@ -714,11 +757,7 @@ BOOST_AUTO_TEST_CASE(knapsack_solver_test)
|
||||
BOOST_AUTO_TEST_CASE(ApproximateBestSubset)
|
||||
{
|
||||
FastRandomContext rand{};
|
||||
std::unique_ptr<CWallet> wallet = std::make_unique<CWallet>(m_node.chain.get(), "", CreateMockWalletDatabase());
|
||||
wallet->LoadWallet();
|
||||
LOCK(wallet->cs_wallet);
|
||||
wallet->SetWalletFlag(WALLET_FLAG_DESCRIPTORS);
|
||||
wallet->SetupDescriptorScriptPubKeyMans();
|
||||
std::unique_ptr<CWallet> wallet = NewWallet(m_node);
|
||||
|
||||
CoinsResult available_coins;
|
||||
|
||||
@@ -736,11 +775,8 @@ BOOST_AUTO_TEST_CASE(ApproximateBestSubset)
|
||||
// Tests that with the ideal conditions, the coin selector will always be able to find a solution that can pay the target value
|
||||
BOOST_AUTO_TEST_CASE(SelectCoins_test)
|
||||
{
|
||||
std::unique_ptr<CWallet> wallet = std::make_unique<CWallet>(m_node.chain.get(), "", CreateMockWalletDatabase());
|
||||
wallet->LoadWallet();
|
||||
LOCK(wallet->cs_wallet);
|
||||
wallet->SetWalletFlag(WALLET_FLAG_DESCRIPTORS);
|
||||
wallet->SetupDescriptorScriptPubKeyMans();
|
||||
std::unique_ptr<CWallet> wallet = NewWallet(m_node);
|
||||
LOCK(wallet->cs_wallet); // Every 'SelectCoins' call requires it
|
||||
|
||||
// Random generator stuff
|
||||
std::default_random_engine generator;
|
||||
@@ -921,16 +957,101 @@ BOOST_AUTO_TEST_CASE(effective_value_test)
|
||||
BOOST_CHECK_EQUAL(output5.GetEffectiveValue(), nValue); // The effective value should be equal to the absolute value if input_bytes is -1
|
||||
}
|
||||
|
||||
static util::Result<SelectionResult> select_coins(const CAmount& target, const CoinSelectionParams& cs_params, const CCoinControl& cc, std::function<CoinsResult(CWallet&)> coin_setup, interfaces::Chain* chain)
|
||||
{
|
||||
std::unique_ptr<CWallet> wallet = std::make_unique<CWallet>(chain, "", CreateMockWalletDatabase());
|
||||
wallet->LoadWallet();
|
||||
LOCK(wallet->cs_wallet);
|
||||
wallet->SetWalletFlag(WALLET_FLAG_DESCRIPTORS);
|
||||
wallet->SetupDescriptorScriptPubKeyMans();
|
||||
|
||||
static util::Result<SelectionResult> SelectCoinsSRD(const CAmount& target,
|
||||
const CoinSelectionParams& cs_params,
|
||||
const node::NodeContext& m_node,
|
||||
int max_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.rng_fast, max_weight);
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(srd_tests)
|
||||
{
|
||||
// Test SRD:
|
||||
// 1) Insufficient funds, select all provided coins and fail.
|
||||
// 2) Exceeded max weight, coin selection always surpasses the max allowed weight.
|
||||
// 3) Select coins without surpassing the max weight (some coins surpasses the max allowed weight, some others not)
|
||||
|
||||
FastRandomContext rand;
|
||||
CoinSelectionParams dummy_params{ // Only used to provide the 'avoid_partial' flag.
|
||||
rand,
|
||||
/*change_output_size=*/34,
|
||||
/*change_spend_size=*/68,
|
||||
/*min_change_target=*/CENT,
|
||||
/*effective_feerate=*/CFeeRate(0),
|
||||
/*long_term_feerate=*/CFeeRate(0),
|
||||
/*discard_feerate=*/CFeeRate(0),
|
||||
/*tx_noinputs_size=*/10 + 34, // static header size + output size
|
||||
/*avoid_partial=*/false,
|
||||
};
|
||||
|
||||
{
|
||||
// #########################################################
|
||||
// 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) {
|
||||
CoinsResult available_coins;
|
||||
for (int j = 0; j < 10; ++j) {
|
||||
add_coin(available_coins, wallet, CAmount(1 * COIN));
|
||||
add_coin(available_coins, wallet, CAmount(2 * COIN));
|
||||
}
|
||||
return available_coins;
|
||||
});
|
||||
BOOST_CHECK(!res);
|
||||
BOOST_CHECK(util::ErrorString(res).empty()); // empty means "insufficient funds"
|
||||
}
|
||||
|
||||
{
|
||||
// ###########################
|
||||
// 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) {
|
||||
CoinsResult available_coins;
|
||||
for (int j = 0; j < 10; ++j) {
|
||||
add_coin(available_coins, wallet, CAmount(1 * COIN), CFeeRate(0), 144, false, 0, true);
|
||||
add_coin(available_coins, wallet, CAmount(2 * COIN), CFeeRate(0), 144, false, 0, true);
|
||||
}
|
||||
return available_coins;
|
||||
});
|
||||
BOOST_CHECK(!res);
|
||||
BOOST_CHECK(util::ErrorString(res).original.find("The inputs size exceeds the maximum weight") != std::string::npos);
|
||||
}
|
||||
|
||||
{
|
||||
// ################################################################################################################
|
||||
// 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) {
|
||||
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);
|
||||
}
|
||||
for (int i = 0; i < 10; i++) { // 10 UTXO --> 20 BTC total --> 10 × 272 WU = 2720 WU
|
||||
add_coin(available_coins, wallet, CAmount(2 * COIN), CFeeRate(0), 144, false, 0, true);
|
||||
}
|
||||
return available_coins;
|
||||
});
|
||||
BOOST_CHECK(res);
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
std::unique_ptr<CWallet> wallet = NewWallet(m_node);
|
||||
auto available_coins = coin_setup(*wallet);
|
||||
|
||||
LOCK(wallet->cs_wallet);
|
||||
auto result = SelectCoins(*wallet, available_coins, /*pre_set_inputs=*/ {}, target, cc, cs_params);
|
||||
if (result) {
|
||||
const auto signedTxSize = 10 + 34 + 68 * result->GetInputSet().size(); // static header size + output size + inputs size (P2WPKH)
|
||||
@@ -964,8 +1085,6 @@ BOOST_AUTO_TEST_CASE(check_max_weight)
|
||||
/*avoid_partial=*/false,
|
||||
};
|
||||
|
||||
auto chain{m_node.chain.get()};
|
||||
|
||||
{
|
||||
// Scenario 1:
|
||||
// The actor starts with 1x 50.0 BTC and 1515x 0.033 BTC (~100.0 BTC total) unspent outputs
|
||||
@@ -984,10 +1103,13 @@ BOOST_AUTO_TEST_CASE(check_max_weight)
|
||||
add_coin(available_coins, wallet, CAmount(50 * COIN), CFeeRate(0), 144, false, 0, true);
|
||||
return available_coins;
|
||||
},
|
||||
chain);
|
||||
m_node);
|
||||
|
||||
BOOST_CHECK(result);
|
||||
BOOST_CHECK(has_coin(result->GetInputSet(), CAmount(50 * COIN)));
|
||||
// Verify that only the 50 BTC UTXO was selected
|
||||
const auto& selection_res = result->GetInputSet();
|
||||
BOOST_CHECK(selection_res.size() == 1);
|
||||
BOOST_CHECK((*selection_res.begin())->GetEffectiveValue() == 50 * COIN);
|
||||
}
|
||||
|
||||
{
|
||||
@@ -1009,7 +1131,7 @@ BOOST_AUTO_TEST_CASE(check_max_weight)
|
||||
}
|
||||
return available_coins;
|
||||
},
|
||||
chain);
|
||||
m_node);
|
||||
|
||||
BOOST_CHECK(has_coin(result->GetInputSet(), CAmount(0.0625 * COIN)));
|
||||
BOOST_CHECK(has_coin(result->GetInputSet(), CAmount(0.025 * COIN)));
|
||||
@@ -1030,7 +1152,7 @@ BOOST_AUTO_TEST_CASE(check_max_weight)
|
||||
}
|
||||
return available_coins;
|
||||
},
|
||||
chain);
|
||||
m_node);
|
||||
|
||||
// No results
|
||||
// 1515 inputs * 68 bytes = 103,020 bytes
|
||||
@@ -1045,20 +1167,11 @@ BOOST_AUTO_TEST_CASE(SelectCoins_effective_value_test)
|
||||
// This test creates a coin whose value is higher than the target but whose effective value is lower than the target.
|
||||
// The coin is selected using coin control, with m_allow_other_inputs = false. SelectCoins should fail due to insufficient funds.
|
||||
|
||||
std::unique_ptr<CWallet> wallet = std::make_unique<CWallet>(m_node.chain.get(), "", CreateMockWalletDatabase());
|
||||
wallet->LoadWallet();
|
||||
LOCK(wallet->cs_wallet);
|
||||
wallet->SetWalletFlag(WALLET_FLAG_DESCRIPTORS);
|
||||
wallet->SetupDescriptorScriptPubKeyMans();
|
||||
std::unique_ptr<CWallet> wallet = NewWallet(m_node);
|
||||
|
||||
CoinsResult available_coins;
|
||||
{
|
||||
std::unique_ptr<CWallet> dummyWallet = std::make_unique<CWallet>(m_node.chain.get(), "dummy", CreateMockWalletDatabase());
|
||||
dummyWallet->LoadWallet();
|
||||
LOCK(dummyWallet->cs_wallet);
|
||||
dummyWallet->SetWalletFlag(WALLET_FLAG_DESCRIPTORS);
|
||||
dummyWallet->SetupDescriptorScriptPubKeyMans();
|
||||
|
||||
std::unique_ptr<CWallet> dummyWallet = NewWallet(m_node, /*wallet_name=*/"dummy");
|
||||
add_coin(available_coins, *dummyWallet, 100000); // 0.001 BTC
|
||||
}
|
||||
|
||||
@@ -1082,6 +1195,7 @@ BOOST_AUTO_TEST_CASE(SelectCoins_effective_value_test)
|
||||
cc.SetInputWeight(output.outpoint, 148);
|
||||
cc.SelectExternal(output.outpoint, output.txout);
|
||||
|
||||
LOCK(wallet->cs_wallet);
|
||||
const auto preset_inputs = *Assert(FetchSelectedInputs(*wallet, cc, cs_params));
|
||||
available_coins.Erase({available_coins.coins[OutputType::BECH32].begin()->outpoint});
|
||||
|
||||
@@ -1094,11 +1208,7 @@ BOOST_FIXTURE_TEST_CASE(wallet_coinsresult_test, BasicTestingSetup)
|
||||
// Test case to verify CoinsResult object sanity.
|
||||
CoinsResult available_coins;
|
||||
{
|
||||
std::unique_ptr<CWallet> dummyWallet = std::make_unique<CWallet>(m_node.chain.get(), "dummy", CreateMockWalletDatabase());
|
||||
BOOST_CHECK_EQUAL(dummyWallet->LoadWallet(), DBErrors::LOAD_OK);
|
||||
LOCK(dummyWallet->cs_wallet);
|
||||
dummyWallet->SetWalletFlag(WALLET_FLAG_DESCRIPTORS);
|
||||
dummyWallet->SetupDescriptorScriptPubKeyMans();
|
||||
std::unique_ptr<CWallet> dummyWallet = NewWallet(m_node, /*wallet_name=*/"dummy");
|
||||
|
||||
// Add some coins to 'available_coins'
|
||||
for (int i=0; i<10; i++) {
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
|
||||
|
||||
#include <policy/feerate.h>
|
||||
#include <policy/policy.h>
|
||||
#include <primitives/transaction.h>
|
||||
#include <test/fuzz/FuzzedDataProvider.h>
|
||||
#include <test/fuzz/fuzz.h>
|
||||
@@ -44,6 +45,9 @@ static void GroupCoins(FuzzedDataProvider& fuzzed_data_provider, const std::vect
|
||||
if (valid_outputgroup) output_groups.push_back(output_group);
|
||||
}
|
||||
|
||||
// Returns true if the result contains an error and the message is not empty
|
||||
static bool HasErrorMsg(const util::Result<SelectionResult>& res) { return !util::ErrorString(res).empty(); }
|
||||
|
||||
FUZZ_TARGET(coinselection)
|
||||
{
|
||||
FuzzedDataProvider fuzzed_data_provider{buffer.data(), buffer.size()};
|
||||
@@ -84,18 +88,18 @@ FUZZ_TARGET(coinselection)
|
||||
GroupCoins(fuzzed_data_provider, utxo_pool, coin_params, /*positive_only=*/false, group_all);
|
||||
|
||||
// Run coinselection algorithms
|
||||
const auto result_bnb = SelectCoinsBnB(group_pos, target, cost_of_change);
|
||||
const auto result_bnb = SelectCoinsBnB(group_pos, target, cost_of_change, MAX_STANDARD_TX_WEIGHT);
|
||||
|
||||
auto result_srd = SelectCoinsSRD(group_pos, target, fast_random_context);
|
||||
auto result_srd = SelectCoinsSRD(group_pos, target, fast_random_context, MAX_STANDARD_TX_WEIGHT);
|
||||
if (result_srd) result_srd->ComputeAndSetWaste(cost_of_change, cost_of_change, 0);
|
||||
|
||||
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);
|
||||
auto result_knapsack = KnapsackSolver(group_all, target, change_target, fast_random_context, MAX_STANDARD_TX_WEIGHT);
|
||||
if (result_knapsack) result_knapsack->ComputeAndSetWaste(cost_of_change, cost_of_change, 0);
|
||||
|
||||
// If the total balance is sufficient for the target and we are not using
|
||||
// effective values, Knapsack should always find a solution.
|
||||
if (total_balance >= target && subtract_fee_outputs) {
|
||||
// effective values, Knapsack should always find a solution (unless the selection exceeded the max tx weight).
|
||||
if (total_balance >= target && subtract_fee_outputs && !HasErrorMsg(result_knapsack)) {
|
||||
assert(result_knapsack);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user