21#include <boost/test/unit_test.hpp>
32#define RANDOM_REPEATS 5
42 tx.
vout.resize(nInput + 1);
43 tx.
vout[nInput].nValue = nValue;
47 group.Insert(std::make_shared<COutput>(output), 0, 0);
54 tx.
vout.resize(nInput + 1);
55 tx.
vout[nInput].nValue = nValue;
57 std::shared_ptr<COutput> coin = std::make_shared<COutput>(
COutPoint(tx.
GetHash(), nInput), tx.
vout.at(nInput), 1, 148,
true,
true, 0,
false,
fee);
59 group.Insert(coin, 0, 0);
60 coin->long_term_fee = long_term_fee;
68 tx.
vout.resize(nInput + 1);
69 tx.
vout[nInput].nValue = nValue;
79 const auto& txout = wtx.
tx->vout.at(nInput);
80 available_coins.
Add(
OutputType::BECH32, {
COutPoint(wtx.
GetHash(), nInput), txout, nAge, custom_size == 0 ?
CalculateMaximumSignedInputSize(txout, &
wallet,
nullptr) : custom_size,
true,
true, wtx.
GetTxTime(), fIsFromMe, feerate});
88 return res ? std::optional<SelectionResult>(*res) : std::nullopt;
94 return res ? std::optional<SelectionResult>(*res) : std::nullopt;
101 std::vector<CAmount> a_amts;
102 std::vector<CAmount> b_amts;
104 a_amts.push_back(coin->txout.nValue);
107 b_amts.push_back(coin->txout.nValue);
109 std::sort(a_amts.begin(), a_amts.end());
110 std::sort(b_amts.begin(), b_amts.end());
112 std::pair<std::vector<CAmount>::iterator, std::vector<CAmount>::iterator>
ret = std::mismatch(a_amts.begin(), a_amts.end(), b_amts.begin());
113 return ret.first == a_amts.end() &&
ret.second == b_amts.end();
120 [](
const std::shared_ptr<COutput>& a,
const std::shared_ptr<COutput>& b) {
121 return a->outpoint == b->outpoint;
126inline std::vector<OutputGroup>&
GroupCoins(
const std::vector<COutput>& available_coins,
bool subtract_fee_outputs =
false)
128 static std::vector<OutputGroup> static_groups;
129 static_groups.clear();
130 for (
auto& coin : available_coins) {
131 static_groups.emplace_back();
133 group.Insert(std::make_shared<COutput>(coin), 0, 0);
134 group.m_subtract_fee_outputs = subtract_fee_outputs;
136 return static_groups;
154 static_groups =
GroupOutputs(
wallet, available_coins, coin_selection_params, {{filter}})[filter];
163 wallet->SetupDescriptorScriptPubKeyMans();
172 std::vector<COutput> utxo_pool;
174 size_t expected_attempts;
192 coin_selection_params_bnb.m_change_fee = coin_selection_params_bnb.m_effective_feerate.
GetFee(coin_selection_params_bnb.change_output_size);
193 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;
194 coin_selection_params_bnb.min_viable_change = coin_selection_params_bnb.m_effective_feerate.GetFee(coin_selection_params_bnb.change_spend_size);
201 add_coin(available_coins, *
wallet, 1, coin_selection_params_bnb.m_effective_feerate);
202 available_coins.
All().at(0).input_bytes = 40;
206 available_coins = {};
207 add_coin(available_coins, *
wallet, 1 *
CENT, coin_selection_params_bnb.m_effective_feerate);
208 available_coins.
All().at(0).input_bytes = 40;
212 expected_attempts = 1;
213 BOOST_CHECK_MESSAGE(result9->GetSelectionsEvaluated() == expected_attempts,
strprintf(
"Expected %i attempts, but got %i", expected_attempts, result9->GetSelectionsEvaluated()));
221 coin_selection_params_bnb.m_effective_feerate =
CFeeRate(0);
222 add_coin(available_coins, *
wallet, 5 *
CENT, coin_selection_params_bnb.m_effective_feerate, 6 * 24,
false, 0,
true);
223 add_coin(available_coins, *
wallet, 3 *
CENT, coin_selection_params_bnb.m_effective_feerate, 6 * 24,
false, 0,
true);
224 add_coin(available_coins, *
wallet, 2 *
CENT, coin_selection_params_bnb.m_effective_feerate, 6 * 24,
false, 0,
true);
227 COutput select_coin = available_coins.
All().at(0);
234 const auto result10 =
SelectCoins(*
wallet, available_coins, selected_input, 10 *
CENT, coin_control, coin_selection_params_bnb);
236 expected_attempts = 3;
237 BOOST_CHECK_MESSAGE(result10->GetSelectionsEvaluated() == expected_attempts,
strprintf(
"Expected %i attempts, but got %i", expected_attempts, result10->GetSelectionsEvaluated()));
246 coin_selection_params_bnb.m_effective_feerate =
CFeeRate(5000);
247 coin_selection_params_bnb.m_long_term_feerate =
CFeeRate(3000);
250 CAmount input_fee = coin_selection_params_bnb.m_effective_feerate.GetFee(68);
251 add_coin(available_coins, *
wallet, 10 *
CENT + input_fee, coin_selection_params_bnb.m_effective_feerate, 6 * 24,
false, 0,
true);
252 add_coin(available_coins, *
wallet, 9 *
CENT + input_fee, coin_selection_params_bnb.m_effective_feerate, 6 * 24,
false, 0,
true);
253 add_coin(available_coins, *
wallet, 1 *
CENT + input_fee, coin_selection_params_bnb.m_effective_feerate, 6 * 24,
false, 0,
true);
260 COutput select_coin = available_coins.
All().at(1);
265 const auto result13 =
SelectCoins(*
wallet, available_coins, selected_input, 10 *
CENT, coin_control, coin_selection_params_bnb);
267 expected_attempts = 2;
268 BOOST_CHECK_MESSAGE(result13->GetSelectionsEvaluated() == expected_attempts,
strprintf(
"Expected %i attempts, but got %i", expected_attempts, result13->GetSelectionsEvaluated()));
278 add_coin(available_coins, *
wallet, 10 *
CENT, coin_selection_params_bnb.m_effective_feerate, 6 * 24,
false, 0,
true);
279 add_coin(available_coins, *
wallet, 9 *
CENT, coin_selection_params_bnb.m_effective_feerate, 6 * 24,
false, 0,
true);
280 add_coin(available_coins, *
wallet, 8 *
CENT, coin_selection_params_bnb.m_effective_feerate, 6 * 24,
false, 0,
true);
282 add_coin(available_coins, *
wallet, 3 *
CENT, coin_selection_params_bnb.m_effective_feerate, 6 * 24,
false, 0,
true);
283 add_coin(available_coins, *
wallet, 1 *
CENT, coin_selection_params_bnb.m_effective_feerate, 6 * 24,
false, 0,
true);
288 BOOST_REQUIRE(!no_res);
292 add_coin(available_coins, *
wallet, 5 *
CENT, coin_selection_params_bnb.m_effective_feerate, 6 * 24,
false, 0,
true);
300 expected_attempts = 22;
301 BOOST_CHECK_MESSAGE(res->GetSelectionsEvaluated() == expected_attempts,
strprintf(
"Expected %i attempts, but got %i", expected_attempts, res->GetSelectionsEvaluated()));
324 params.m_subtract_fee_outputs =
true;
325 params.m_change_fee = params.m_effective_feerate.
GetFee(params.change_output_size);
326 params.m_cost_of_change = params.m_discard_feerate.GetFee(params.change_spend_size) + params.m_change_fee;
327 params.m_min_change_target = params.m_cost_of_change + 1;
330 add_coin(available_coins, *
wallet,
COIN + params.m_cost_of_change, params.m_effective_feerate, 6,
true, 0,
true);
331 add_coin(available_coins, *
wallet, 0.5 *
COIN + params.m_cost_of_change, params.m_effective_feerate, 6,
true, 0,
true);
332 add_coin(available_coins, *
wallet, 0.5 *
COIN, params.m_effective_feerate, 6,
true, 0,
true);
355 available_coins = {};
424 available_coins = {};
482 available_coins = {};
514 available_coins = {};
515 for (
int j = 0; j < 20; j++)
527 available_coins = {};
538 available_coins = {};
549 available_coins = {};
569 available_coins = {};
571 for (uint16_t j = 0; j < 676; j++)
579 if (amt - 2000 <
CENT) {
581 uint16_t returnSize = std::ceil((2000.0 +
CENT)/amt);
582 CAmount returnValue = amt * returnSize;
595 available_coins = {};
596 for (
int i2 = 0; i2 < 100; i2++)
659 for (
int i = 0; i < 1000; i++)
676 std::default_random_engine generator;
677 std::exponential_distribution<double> distribution (100);
681 for (
int i = 0; i < 100; ++i)
687 for (
int j = 0; j < 1000; ++j)
689 CAmount val = distribution(generator)*10000000;
712 cs_params.m_cost_of_change = 1;
713 cs_params.min_viable_change = 1;
715 const auto result =
SelectCoins(*
wallet, available_coins, {}, target, cc, cs_params);
717 BOOST_CHECK_GE(result->GetSelectedValue(), target);
724 const CAmount min_viable_change{300};
725 const CAmount change_cost{125};
742 selection1.RecalculateWaste(min_viable_change, change_cost, change_fee);
749 selection2.RecalculateWaste(min_viable_change, change_cost, change_fee);
750 BOOST_CHECK_GT(selection2.GetWaste(), selection1.GetWaste());
757 selection3.RecalculateWaste(min_viable_change, change_cost, change_fee);
759 BOOST_CHECK_LT(selection3.GetWaste(), selection1.GetWaste());
767 selection_nochange1.RecalculateWaste(min_viable_change, change_cost, change_fee);
775 selection_nochange2.RecalculateWaste(min_viable_change, change_cost, change_fee);
777 BOOST_CHECK_LT(selection_nochange2.GetWaste(), selection_nochange1.GetWaste());
785 selection.RecalculateWaste(min_viable_change, change_cost, change_fee);
794 selection.RecalculateWaste(min_viable_change, change_cost, change_fee);
803 selection.RecalculateWaste(min_viable_change, change_cost , change_fee);
812 selection.RecalculateWaste(min_viable_change, fee_diff * 2, change_fee);
818 const CAmount new_target{exact_target - fee_diff * 2};
822 selection.RecalculateWaste(min_viable_change, change_cost, change_fee);
829 const CAmount target_waste1{-2 * fee_diff};
832 selection.RecalculateWaste(min_viable_change, change_cost, change_fee);
839 const CAmount large_fee_diff{90};
840 const CAmount target_waste2{-2 * large_fee_diff + change_cost};
844 assert(target_waste2 == -55);
847 selection.RecalculateWaste(min_viable_change, change_cost, change_fee);
856 const CAmount min_viable_change{200};
857 const CAmount change_cost{125};
866 const std::vector<std::shared_ptr<COutput>> inputs = selection.GetShuffledInputVector();
868 for (
size_t i = 0; i < inputs.size(); ++i) {
869 inputs[i]->ApplyBumpFee(20*(i+1));
872 selection.RecalculateWaste(min_viable_change, change_cost, change_fee);
873 CAmount expected_waste = fee_diff * -2 + change_cost + 60;
876 selection.SetBumpFeeDiscount(30);
877 selection.RecalculateWaste(min_viable_change, change_cost, change_fee);
878 expected_waste = fee_diff * -2 + change_cost + 60 - 30;
892 const std::vector<std::shared_ptr<COutput>> inputs = selection.GetShuffledInputVector();
894 for (
size_t i = 0; i < inputs.size(); ++i) {
895 inputs[i]->ApplyBumpFee(20*(i+1));
898 selection.RecalculateWaste(min_viable_change, change_cost, change_fee);
899 CAmount expected_waste = fee_diff * -2 + 60 + 40;
902 selection.SetBumpFeeDiscount(30);
903 selection.RecalculateWaste(min_viable_change, change_cost, change_fee);
904 expected_waste = fee_diff * -2 + 60 - 30 + 70;
911 const int input_bytes = 148;
914 const int nInput = 0;
918 tx.
vout[nInput].nValue = nValue;
922 const CAmount expected_ev1 = 9852;
931 const CAmount expected_ev3 = -4800;
947 int max_selection_weight,
985 int max_selection_weight = 10'000;
988 for (
int j = 0; j < 10; ++j) {
992 return available_coins;
1003 int max_selection_weight = 3000;
1006 for (
int j = 0; j < 10; ++j) {
1010 return available_coins;
1021 int max_selection_weight = 10'000;
1024 for (
int j = 0; j < 60; ++j) {
1027 for (
int i = 0; i < 10; i++) {
1030 return available_coins;
1033 for (
int i = 0; i < 10; ++i) {
1036 for (
int j = 0; j < 17; ++j) {
1041 size_t expected_attempts = 37;
1042 BOOST_CHECK_MESSAGE(res->GetSelectionsEvaluated() == expected_attempts,
strprintf(
"Expected %i attempts, but got %i", expected_attempts, res->GetSelectionsEvaluated()));
1050 int max_selection_weight = 400'000;
1056 return available_coins;
1063 size_t expected_attempts = 3;
1064 BOOST_CHECK_MESSAGE(res->GetSelectionsEvaluated() == expected_attempts,
strprintf(
"Expected %i attempts, but got %i", expected_attempts, res->GetSelectionsEvaluated()));
1072 int max_selection_weight = 400'000;
1075 for (
int j = 0; j < 5; ++j) {
1083 return available_coins;
1092 size_t expected_attempts = 92;
1093 BOOST_CHECK_MESSAGE(res->GetSelectionsEvaluated() == expected_attempts,
strprintf(
"Expected %i attempts, but got %i", expected_attempts, res->GetSelectionsEvaluated()));
1101 int max_selection_weight = 400'000;
1110 for (
int j = 0; j < 100; ++j) {
1113 for (
int j = 0; j < 100; ++j) {
1116 for (
int j = 0; j < 100; ++j) {
1119 for (
int j = 0; j < 100; ++j) {
1122 return available_coins;
1131 size_t expected_attempts = 38;
1132 BOOST_CHECK_MESSAGE(res->GetSelectionsEvaluated() == expected_attempts,
strprintf(
"Expected %i attempts, but got %i", expected_attempts, res->GetSelectionsEvaluated()));
1140 int max_selection_weight = 40000;
1146 for (
int j = 0; j < 100; ++j) {
1150 return available_coins;
1157 size_t expected_attempts = 7;
1158 BOOST_CHECK_MESSAGE(res->GetSelectionsEvaluated() == expected_attempts,
strprintf(
"Expected %i attempts, but got %i", expected_attempts, res->GetSelectionsEvaluated()));
1166 int max_selection_weight = 3200;
1167 dummy_params.m_min_change_target = 0;
1170 for (
int i = 0; i < 18; ++i) {
1173 return doppelgangers;
1177 for (
int i = 0; i < 8; ++i) {
1182 size_t expected_attempts = 87'525;
1183 BOOST_CHECK_MESSAGE(result_a->GetSelectionsEvaluated() == expected_attempts,
strprintf(
"Expected %i attempts, but got %i", expected_attempts, result_a->GetSelectionsEvaluated()));
1188 for (
int i = 0; i < 19; ++i) {
1191 return doppelgangers;
1200 auto available_coins = coin_setup(*
wallet);
1205 const auto signedTxSize = 10 + 34 + 68 * result->GetInputSet().size();
1208 BOOST_CHECK_GE(result->GetSelectedValue(), target);
1215 return std::any_of(set.begin(), set.end(), [&](
const auto& coin) { return coin->GetEffectiveValue() == amount; });
1248 for (
int j = 0; j < 1515; ++j) {
1253 return available_coins;
1260 BOOST_CHECK_LE(result->GetWeight(), max_weight);
1274 for (
int j = 0; j < 400; ++j) {
1277 for (
int j = 0; j < 2000; ++j) {
1280 return available_coins;
1286 BOOST_CHECK_LE(result->GetWeight(), max_weight);
1299 for (
int j = 0; j < 1515; ++j) {
1302 return available_coins;
1324 add_coin(available_coins, *dummyWallet, 100000);
1343 COutput output = available_coins.
All().at(0);
1351 const auto result =
SelectCoins(*
wallet, available_coins, preset_inputs, target, cc, cs_params);
1363 for (
int i=0; i<10; i++) {
1371 std::unordered_set<COutPoint, SaltedOutpointHasher> outs_to_remove;
1372 const auto& coins = available_coins.
All();
1373 for (
int i = 0; i < 2; i++) {
1374 outs_to_remove.emplace(coins[i].outpoint);
1376 available_coins.
Erase(outs_to_remove);
1379 const auto& updated_coins = available_coins.
All();
1380 for (
const auto&
out: outs_to_remove) {
1381 auto it = std::find_if(updated_coins.begin(), updated_coins.end(), [&
out](
const COutput &coin) {
1382 return coin.outpoint == out;
CScript GetScriptForDestination(const CTxDestination &dest)
Generate a Bitcoin scriptPubKey for the given CTxDestination.
int64_t CAmount
Amount in satoshis (Can be negative)
static constexpr CAmount COIN
The amount of satoshis in one BTC.
#define Assert(val)
Identity function.
Fee rate in satoshis per virtualbyte: CAmount / vB the feerate is represented internally as FeeFrac.
CAmount GetFee(int32_t virtual_bytes) const
Return the fee in satoshis for the given vsize in vbytes.
An outpoint - a combination of a transaction hash and an index n into its vout.
I randrange(I range) noexcept
Generate a random integer in the range [0..range), with range > 0.
PreselectedInput & Select(const COutPoint &outpoint)
Lock-in the given output for spending.
bool m_allow_other_inputs
If true, the selection process can add extra unselected inputs from the wallet while requires all sel...
void SetInputWeight(const COutPoint &outpoint, int64_t weight)
Set an input's weight.
A CWallet maintains a set of transactions and balances, and provides the ability to create new transa...
A transaction with a bunch of additional info that only the owner cares about.
const Txid & GetHash() const LIFETIMEBOUND
int64_t GetTxTime() const
static const int WITNESS_SCALE_FACTOR
BOOST_FIXTURE_TEST_SUITE(cuckoocache_tests, BasicTestingSetup)
Test Suite for CuckooCache.
BOOST_AUTO_TEST_SUITE_END()
bilingual_str ErrorString(const Result< T > &result)
static const CoinEligibilityFilter filter_standard(1, 6, 0)
std::unique_ptr< WalletDatabase > CreateMockableWalletDatabase()
FilteredOutputGroups GroupOutputs(const CWallet &wallet, const CoinsResult &coins, const CoinSelectionParams &coin_sel_params, const std::vector< SelectionFilter > &filters, std::vector< OutputGroup > &ret_discarded_groups)
util::Result< CoinsResult > FetchSelectedInputs(const CWallet &wallet, const CCoinControl &coin_control, const CoinSelectionParams &coin_selection_params)
Fetch and validate coin control selected inputs.
BOOST_FIXTURE_TEST_CASE(wallet_coinsresult_test, BasicTestingSetup)
util::Result< SelectionResult > SelectCoinsBnB(std::vector< OutputGroup > &utxo_pool, const CAmount &selection_target, const CAmount &cost_of_change, int max_selection_weight)
static void add_coin(const CAmount &nValue, uint32_t nInput, std::vector< OutputGroup > &set)
static std::unique_ptr< CWallet > NewWallet(const node::NodeContext &m_node, const std::string &wallet_name="")
util::Result< SelectionResult > CoinGrinder(std::vector< OutputGroup > &utxo_pool, const CAmount &selection_target, CAmount change_target, int max_selection_weight)
std::vector< OutputGroup > & GroupCoins(const std::vector< COutput > &available_coins, bool subtract_fee_outputs=false)
std::vector< OutputGroup > & KnapsackGroupOutputs(const CoinsResult &available_coins, CWallet &wallet, const CoinEligibilityFilter &filter)
std::set< std::shared_ptr< COutput >, OutputPtrComparator > OutputSet
util::Result< SelectionResult > KnapsackSolver(std::vector< OutputGroup > &groups, const CAmount &nTargetValue, CAmount change_target, FastRandomContext &rng, int max_selection_weight)
util::Result< SelectionResult > SelectCoins(const CWallet &wallet, CoinsResult &available_coins, const CoinsResult &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',...
static bool has_coin(const OutputSet &set, CAmount amount)
static const CoinEligibilityFilter filter_standard_extra(6, 6, 0)
static bool EqualResult(const SelectionResult &a, const SelectionResult &b)
Check if this selection is equal to another one.
BOOST_AUTO_TEST_CASE(bnb_test)
@ WALLET_FLAG_DESCRIPTORS
Indicate that this wallet supports DescriptorScriptPubKeyMan.
static const CoinEligibilityFilter filter_confirmed(1, 1, 0)
int CalculateMaximumSignedInputSize(const CTxOut &txout, const COutPoint outpoint, const SigningProvider *provider, bool can_grind_r, const CCoinControl *coin_control)
static bool EquivalentResult(const SelectionResult &a, const SelectionResult &b)
Check if SelectionResult a is equivalent to SelectionResult b.
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 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)
#define BOOST_CHECK_EQUAL(v1, v2)
#define BOOST_CHECK(expr)
static constexpr int32_t MAX_STANDARD_TX_WEIGHT
The maximum weight for transactions we're willing to relay/mine.
static CTransactionRef MakeTransactionRef(Tx &&txIn)
static constexpr CAmount CENT
A mutable version of CTransaction.
std::vector< CTxOut > vout
Txid GetHash() const
Compute the hash of this CMutableTransaction.
NodeContext struct containing references to chain state and connection state.
std::unique_ptr< interfaces::Chain > chain
A UTXO under consideration for use in funding a new transaction.
COutPoint outpoint
The outpoint identifying this UTXO.
CTxOut txout
The output itself.
CAmount GetEffectiveValue() const
Parameters for filtering which OutputGroups we may use in coin selection.
Parameters for one iteration of Coin Selection.
CAmount m_min_change_target
Mininmum change to target in Knapsack solver and CoinGrinder: select coins to cover the payment and a...
int change_output_size
Size of a change output in bytes, determined by the output type.
int tx_noinputs_size
Size of the transaction before coin selection, consisting of the header and recipient output(s),...
COutputs available for spending, stored by OutputType.
void Add(OutputType type, const COutput &out)
std::vector< COutput > All() const
Concatenate and return all COutputs as one vector.
size_t Size() const
The following methods are provided so that CoinsResult can mimic a vector, i.e., methods can work wit...
std::map< OutputType, std::vector< COutput > > coins
void Erase(const std::unordered_set< COutPoint, SaltedOutpointHasher > &coins_to_remove)
std::vector< OutputGroup > mixed_group
A group of UTXOs paid to the same output script.
Stores several 'Groups' whose were mapped by output type.
void AddInput(const OutputGroup &group)
const OutputSet & GetInputSet() const
Get m_selected_inputs.
State of transaction not confirmed or conflicting with a known block and not in the mempool.
#define WITH_LOCK(cs, code)
Run code while locking a mutex.