18 #include <boost/test/unit_test.hpp> 22 BOOST_FIXTURE_TEST_SUITE(coinselector_tests, WalletTestingSetup)
29 #define RANDOM_REPEATS 5 38 static void add_coin(
const CAmount& nValue,
int nInput, std::vector<COutput>&
set)
41 tx.
vout.resize(nInput + 1);
42 tx.
vout[nInput].nValue = nValue;
44 set.emplace_back(
COutPoint(tx.
GetHash(), nInput), tx.
vout.at(nInput), 1, -1,
true,
true,
true, 0,
false, 0);
50 tx.
vout.resize(nInput + 1);
51 tx.
vout[nInput].nValue = nValue;
53 COutput output(
COutPoint(tx.
GetHash(), nInput), tx.
vout.at(nInput), 1, -1,
true,
true,
true, 0,
false, 0);
55 group.
Insert(output, 0, 0,
true);
62 tx.
vout.resize(nInput + 1);
63 tx.
vout[nInput].nValue = nValue;
65 COutput coin(
COutPoint(tx.
GetHash(), nInput), tx.
vout.at(nInput), 1, 148,
true,
true,
true, 0,
false, fee);
74 tx.
vout.resize(nInput + 1);
75 tx.
vout[nInput].nValue = nValue;
85 const auto& txout = wtx.
tx->vout.at(nInput);
86 available_coins.
coins[
OutputType::BECH32].emplace_back(
COutPoint(wtx.
GetHash(), nInput), txout, nAge,
CalculateMaximumSignedInputSize(txout, &
wallet,
nullptr),
true,
true,
true, wtx.
GetTxTime(), fIsFromMe, feerate);
93 std::vector<CAmount> a_amts;
94 std::vector<CAmount> b_amts;
96 a_amts.push_back(coin.txout.nValue);
99 b_amts.push_back(coin.txout.nValue);
101 std::sort(a_amts.begin(), a_amts.end());
102 std::sort(b_amts.begin(), b_amts.end());
104 std::pair<std::vector<CAmount>::iterator, std::vector<CAmount>::iterator>
ret = std::mismatch(a_amts.begin(), a_amts.end(), b_amts.begin());
105 return ret.first == a_amts.end() &&
ret.second == b_amts.end();
115 return ret.first == a.GetInputSet().end() &&
ret.second == b.
GetInputSet().end();
122 for (
int i = 0; i < utxos; ++i) {
123 target += (
CAmount)1 << (utxos+i);
130 inline std::vector<OutputGroup>&
GroupCoins(
const std::vector<COutput>& available_coins)
132 static std::vector<OutputGroup> static_groups;
133 static_groups.clear();
134 for (
auto& coin : available_coins) {
135 static_groups.emplace_back();
136 static_groups.back().Insert(coin, 0, 0,
false);
138 return static_groups;
155 static std::vector<OutputGroup> static_groups;
156 static_groups =
GroupOutputs(
wallet, available_coins, coin_selection_params, filter,
false);
157 return static_groups;
165 std::vector<COutput> utxo_pool;
187 expected_result.
Clear();
195 expected_result.
Clear();
204 expected_result.
Clear();
208 expected_result.
Clear();
216 expected_result.
Clear();
220 expected_result.
Clear();
232 expected_result.
Clear();
247 expected_result.
Clear();
268 for (
int i = 0; i < 50000; ++i) {
281 for (
int i = 5; i <= 20; ++i) {
285 for (
int i = 0; i < 100; ++i) {
301 coin_selection_params_bnb.m_change_fee = coin_selection_params_bnb.m_effective_feerate.
GetFee(coin_selection_params_bnb.change_output_size);
302 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;
303 coin_selection_params_bnb.min_viable_change = coin_selection_params_bnb.m_effective_feerate.GetFee(coin_selection_params_bnb.change_spend_size);
309 wallet->SetupDescriptorScriptPubKeyMans();
313 add_coin(available_coins, *
wallet, 1, coin_selection_params_bnb.m_effective_feerate);
314 available_coins.
All().at(0).input_bytes = 40;
318 available_coins.
Clear();
319 add_coin(available_coins, *
wallet, 1 *
CENT, coin_selection_params_bnb.m_effective_feerate);
320 available_coins.
All().at(0).input_bytes = 40;
321 coin_selection_params_bnb.m_subtract_fee_outputs =
true;
332 wallet->SetupDescriptorScriptPubKeyMans();
336 add_coin(available_coins, *
wallet, 5 *
CENT, coin_selection_params_bnb.m_effective_feerate, 6 * 24,
false, 0,
true);
337 add_coin(available_coins, *
wallet, 3 *
CENT, coin_selection_params_bnb.m_effective_feerate, 6 * 24,
false, 0,
true);
338 add_coin(available_coins, *
wallet, 2 *
CENT, coin_selection_params_bnb.m_effective_feerate, 6 * 24,
false, 0,
true);
341 coin_control.
Select(available_coins.
All().at(0).outpoint);
342 coin_selection_params_bnb.m_effective_feerate =
CFeeRate(0);
343 const auto result10 =
SelectCoins(*
wallet, available_coins, 10 *
CENT, coin_control, coin_selection_params_bnb);
351 wallet->SetupDescriptorScriptPubKeyMans();
356 coin_selection_params_bnb.m_effective_feerate =
CFeeRate(5000);
357 coin_selection_params_bnb.m_long_term_feerate =
CFeeRate(3000);
359 add_coin(available_coins, *
wallet, 10 *
CENT, coin_selection_params_bnb.m_effective_feerate, 6 * 24,
false, 0,
true);
360 add_coin(available_coins, *
wallet, 9 *
CENT, coin_selection_params_bnb.m_effective_feerate, 6 * 24,
false, 0,
true);
361 add_coin(available_coins, *
wallet, 1 *
CENT, coin_selection_params_bnb.m_effective_feerate, 6 * 24,
false, 0,
true);
363 expected_result.
Clear();
366 const auto result11 =
SelectCoins(*
wallet, available_coins, 10 *
CENT, coin_control, coin_selection_params_bnb);
368 available_coins.
Clear();
371 coin_selection_params_bnb.m_effective_feerate =
CFeeRate(3000);
372 coin_selection_params_bnb.m_long_term_feerate =
CFeeRate(5000);
374 add_coin(available_coins, *
wallet, 10 *
CENT, coin_selection_params_bnb.m_effective_feerate, 6 * 24,
false, 0,
true);
375 add_coin(available_coins, *
wallet, 9 *
CENT, coin_selection_params_bnb.m_effective_feerate, 6 * 24,
false, 0,
true);
376 add_coin(available_coins, *
wallet, 1 *
CENT, coin_selection_params_bnb.m_effective_feerate, 6 * 24,
false, 0,
true);
378 expected_result.
Clear();
381 const auto result12 =
SelectCoins(*
wallet, available_coins, 10 *
CENT, coin_control, coin_selection_params_bnb);
383 available_coins.
Clear();
386 coin_selection_params_bnb.m_effective_feerate =
CFeeRate(5000);
387 coin_selection_params_bnb.m_long_term_feerate =
CFeeRate(3000);
389 add_coin(available_coins, *
wallet, 10 *
CENT, coin_selection_params_bnb.m_effective_feerate, 6 * 24,
false, 0,
true);
390 add_coin(available_coins, *
wallet, 9 *
CENT, coin_selection_params_bnb.m_effective_feerate, 6 * 24,
false, 0,
true);
391 add_coin(available_coins, *
wallet, 1 *
CENT, coin_selection_params_bnb.m_effective_feerate, 6 * 24,
false, 0,
true);
393 expected_result.
Clear();
397 coin_control.
Select(available_coins.
All().at(1).outpoint);
398 const auto result13 =
SelectCoins(*
wallet, available_coins, 10 *
CENT, coin_control, coin_selection_params_bnb);
412 wallet->SetupDescriptorScriptPubKeyMans();
419 available_coins.
Clear();
488 available_coins.
Clear();
546 available_coins.
Clear();
578 available_coins.
Clear();
579 for (
int j = 0; j < 20; j++)
591 available_coins.
Clear();
602 available_coins.
Clear();
613 available_coins.
Clear();
633 available_coins.
Clear();
635 for (uint16_t j = 0; j < 676; j++)
643 if (amt - 2000 <
CENT) {
645 uint16_t returnSize = std::ceil((2000.0 +
CENT)/amt);
646 CAmount returnValue = amt * returnSize;
659 available_coins.
Clear();
660 for (
int i2 = 0; i2 < 100; i2++)
722 wallet->SetupDescriptorScriptPubKeyMans();
727 for (
int i = 0; i < 1000; i++)
744 wallet->SetupDescriptorScriptPubKeyMans();
747 std::default_random_engine generator;
748 std::exponential_distribution<double> distribution (100);
752 for (
int i = 0; i < 100; ++i)
758 for (
int j = 0; j < 1000; ++j)
760 CAmount val = distribution(generator)*10000000;
783 cs_params.m_cost_of_change = 1;
784 cs_params.min_viable_change = 1;
786 const auto result =
SelectCoins(*
wallet, available_coins, target, cc, cs_params);
788 BOOST_CHECK_GE(result->GetSelectedValue(), target);
796 const CAmount change_cost{125};
800 const CAmount excess{in_amt - fee * 2 - target};
829 add_coin(1 *
COIN, 1, selection, fee * 2, fee - fee_diff);
830 add_coin(2 *
COIN, 2, selection, fee * 2, fee - fee_diff);
832 BOOST_CHECK_GT(waste2, waste1);
841 BOOST_CHECK_LT(waste3, waste1);
850 BOOST_CHECK_LT(waste_nochange2, waste_nochange1);
856 const CAmount exact_target{in_amt - fee * 2};
861 const CAmount new_change_cost{fee_diff * 2};
868 const CAmount new_target{in_amt - fee * 2 - fee_diff * 2};
876 const CAmount target_waste1{-2 * fee_diff};
883 const CAmount large_fee_diff{90};
884 const CAmount target_waste2{-2 * large_fee_diff + change_cost};
885 add_coin(1 *
COIN, 1, selection, fee, fee + large_fee_diff);
886 add_coin(2 *
COIN, 2, selection, fee, fee + large_fee_diff);
892 const int input_bytes = 148;
895 const int nInput = 0;
899 tx.
vout[nInput].nValue = nValue;
902 COutput output1(
COutPoint(tx.
GetHash(), nInput), tx.
vout.at(nInput), 1, input_bytes,
true,
true,
true, 0,
false, feerate);
903 const CAmount expected_ev1 = 9852;
907 COutput output2(
COutPoint(tx.
GetHash(), nInput), tx.
vout.at(nInput), 1, -1,
true,
true,
true, 0,
false, feerate);
911 COutput output3(
COutPoint(tx.
GetHash(), nInput), tx.
vout.at(nInput), 1, input_bytes,
true,
true,
true, 0,
false,
CFeeRate(100000));
912 const CAmount expected_ev3 = -4800;
917 COutput output4(
COutPoint(tx.
GetHash(), nInput), tx.
vout.at(nInput), 1, input_bytes,
true,
true,
true, 0,
false, fees);
921 COutput output5(
COutPoint(tx.
GetHash(), nInput), tx.
vout.at(nInput), 1, -1,
true,
true,
true, 0,
false, 0);
935 wallet->SetupDescriptorScriptPubKeyMans();
940 dummyWallet->LoadWallet();
941 LOCK(dummyWallet->cs_wallet);
943 dummyWallet->SetupDescriptorScriptPubKeyMans();
945 add_coin(available_coins, *dummyWallet, 100000);
968 const auto result =
SelectCoins(*
wallet, available_coins, target, cc, cs_params);
979 LOCK(dummyWallet->cs_wallet);
981 dummyWallet->SetupDescriptorScriptPubKeyMans();
984 for (
int i=0; i<10; i++) {
992 std::set<COutPoint> outs_to_remove;
993 const auto& coins = available_coins.
All();
994 for (
int i = 0; i < 2; i++) {
995 outs_to_remove.emplace(coins[i].outpoint);
997 available_coins.
Erase(outs_to_remove);
1000 const auto& updated_coins = available_coins.
All();
1001 for (
const auto& out: outs_to_remove) {
1002 auto it = std::find_if(updated_coins.begin(), updated_coins.end(), [&out](
const COutput &coin) {
1003 return coin.outpoint == out;
COutPoint outpoint
The outpoint identifying this UTXO.
std::unique_ptr< interfaces::Chain > chain
CAmount GetSelectionWaste(const std::set< COutput > &inputs, CAmount change_cost, CAmount target, bool use_effective_value)
Compute the waste for this result given the cost of change and the opportunity cost of spending these...
static bool EquivalentResult(const SelectionResult &a, const SelectionResult &b)
Check if SelectionResult a is equivalent to SelectionResult b.
size_t Size() const
The following methods are provided so that CoinsResult can mimic a vector, i.e., methods can work wit...
std::vector< COutput > All() const
Concatenate and return all COutputs as one vector.
void AddInput(const OutputGroup &group)
void Insert(const COutput &output, size_t ancestors, size_t descendants, bool positive_only)
State of transaction not confirmed or conflicting with a known block and not in the mempool...
std::optional< SelectionResult > SelectCoinsBnB(std::vector< OutputGroup > &utxo_pool, const CAmount &selection_target, const CAmount &cost_of_change)
void SelectExternal(const COutPoint &outpoint, const CTxOut &txout)
int64_t GetTxTime() const
static bool EqualResult(const SelectionResult &a, const SelectionResult &b)
Check if this selection is equal to another one.
CTxOut txout
The output itself.
void Select(const COutPoint &output)
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.
CAmount long_term_fee
The fee required to spend this output at the consolidation feerate.
static void add_coin(const CAmount &nValue, int nInput, std::vector< COutput > &set)
std::unique_ptr< WalletDatabase > CreateMockWalletDatabase(DatabaseOptions &options)
Return object for accessing temporary in-memory database.
BOOST_FIXTURE_TEST_CASE(BasicOutputTypesTest, AvailableCoinsTestingSetup)
BOOST_AUTO_TEST_SUITE_END()
Indicate that this wallet supports DescriptorScriptPubKeyMan.
static void ApproximateBestSubset(FastRandomContext &insecure_rand, const std::vector< OutputGroup > &groups, const CAmount &nTotalLower, const CAmount &nTargetValue, std::vector< char > &vfBest, CAmount &nBest, int iterations=1000)
Find a subset of the OutputGroups that is at least as large as, but as close as possible to...
void SetInputWeight(const COutPoint &outpoint, int64_t weight)
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.
CScript GetScriptForDestination(const CTxDestination &dest)
Generate a Bitcoin scriptPubKey for the given CTxDestination.
std::set< COutput > CoinSet
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
std::vector< OutputGroup > & GroupCoins(const std::vector< COutput > &available_coins)
Parameters for one iteration of Coin Selection.
const std::set< COutput > & GetInputSet() const
Get m_selected_inputs.
static CTransactionRef MakeTransactionRef(Tx &&txIn)
Parameters for filtering which OutputGroups we may use in coin selection.
static const CoinEligibilityFilter filter_standard(1, 6, 0)
int CalculateMaximumSignedInputSize(const CTxOut &txout, const COutPoint outpoint, const SigningProvider *provider, const CCoinControl *coin_control)
uint256 GetHash() const
Compute the hash of this CMutableTransaction.
#define BOOST_CHECK_EQUAL(v1, v2)
const uint256 & GetHash() const
std::optional< SelectionResult > SelectCoins(const CWallet &wallet, CoinsResult &available_coins, const CAmount &nTargetValue, const CCoinControl &coin_control, const CoinSelectionParams &coin_selection_params)
Select a set of coins such that nTargetValue is met and at least all coins from coin_control are sele...
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.
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
std::vector< OutputGroup > GroupOutputs(const CWallet &wallet, const std::vector< COutput > &outputs, const CoinSelectionParams &coin_sel_params, const CoinEligibilityFilter &filter, bool positive_only)
std::shared_ptr< CWallet > wallet
BOOST_AUTO_TEST_CASE(bnb_search_test)
std::optional< SelectionResult > KnapsackSolver(std::vector< OutputGroup > &groups, const CAmount &nTargetValue, CAmount change_target, FastRandomContext &rng)
A UTXO under consideration for use in funding a new transaction.
uint64_t randrange(uint64_t range) noexcept
Generate a random integer in the range [0..range).
void Erase(const std::set< COutPoint > &coins_to_remove)
#define Assert(val)
Identity function.
std::vector< OutputGroup > & KnapsackGroupOutputs(const std::vector< COutput > &available_coins, CWallet &wallet, const CoinEligibilityFilter &filter)
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.