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.
GetTx()->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();
173 size_t expected_attempts;
191 coin_selection_params_bnb.m_change_fee = coin_selection_params_bnb.m_effective_feerate.
GetFee(coin_selection_params_bnb.change_output_size);
192 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;
193 coin_selection_params_bnb.min_viable_change = coin_selection_params_bnb.m_effective_feerate.GetFee(coin_selection_params_bnb.change_spend_size);
200 add_coin(available_coins, *
wallet, 1, coin_selection_params_bnb.m_effective_feerate);
201 available_coins.
All().at(0).input_bytes = 40;
205 available_coins = {};
206 add_coin(available_coins, *
wallet, 1 *
CENT, coin_selection_params_bnb.m_effective_feerate);
207 available_coins.
All().at(0).input_bytes = 40;
211 expected_attempts = 1;
212 BOOST_CHECK_MESSAGE(result9->GetSelectionsEvaluated() == expected_attempts,
strprintf(
"Expected %i attempts, but got %i", expected_attempts, result9->GetSelectionsEvaluated()));
220 coin_selection_params_bnb.m_effective_feerate =
CFeeRate(0);
221 add_coin(available_coins, *
wallet, 5 *
CENT, coin_selection_params_bnb.m_effective_feerate, 6 * 24,
false, 0,
true);
222 add_coin(available_coins, *
wallet, 3 *
CENT, coin_selection_params_bnb.m_effective_feerate, 6 * 24,
false, 0,
true);
223 add_coin(available_coins, *
wallet, 2 *
CENT, coin_selection_params_bnb.m_effective_feerate, 6 * 24,
false, 0,
true);
226 COutput select_coin = available_coins.
All().at(0);
233 const auto result10 =
SelectCoins(*
wallet, available_coins, selected_input, 10 *
CENT, coin_control, coin_selection_params_bnb);
235 expected_attempts = 3;
236 BOOST_CHECK_MESSAGE(result10->GetSelectionsEvaluated() == expected_attempts,
strprintf(
"Expected %i attempts, but got %i", expected_attempts, result10->GetSelectionsEvaluated()));
245 coin_selection_params_bnb.m_effective_feerate =
CFeeRate(5000);
246 coin_selection_params_bnb.m_long_term_feerate =
CFeeRate(3000);
249 CAmount input_fee = coin_selection_params_bnb.m_effective_feerate.GetFee(68);
250 add_coin(available_coins, *
wallet, 10 *
CENT + input_fee, coin_selection_params_bnb.m_effective_feerate, 6 * 24,
false, 0,
true);
251 add_coin(available_coins, *
wallet, 9 *
CENT + input_fee, coin_selection_params_bnb.m_effective_feerate, 6 * 24,
false, 0,
true);
252 add_coin(available_coins, *
wallet, 1 *
CENT + input_fee, coin_selection_params_bnb.m_effective_feerate, 6 * 24,
false, 0,
true);
259 COutput select_coin = available_coins.
All().at(1);
264 const auto result13 =
SelectCoins(*
wallet, available_coins, selected_input, 10 *
CENT, coin_control, coin_selection_params_bnb);
266 expected_attempts = 2;
267 BOOST_CHECK_MESSAGE(result13->GetSelectionsEvaluated() == expected_attempts,
strprintf(
"Expected %i attempts, but got %i", expected_attempts, result13->GetSelectionsEvaluated()));
277 add_coin(available_coins, *
wallet, 10 *
CENT, coin_selection_params_bnb.m_effective_feerate, 6 * 24,
false, 0,
true);
278 add_coin(available_coins, *
wallet, 9 *
CENT, coin_selection_params_bnb.m_effective_feerate, 6 * 24,
false, 0,
true);
279 add_coin(available_coins, *
wallet, 8 *
CENT, coin_selection_params_bnb.m_effective_feerate, 6 * 24,
false, 0,
true);
281 add_coin(available_coins, *
wallet, 3 *
CENT, coin_selection_params_bnb.m_effective_feerate, 6 * 24,
false, 0,
true);
282 add_coin(available_coins, *
wallet, 1 *
CENT, coin_selection_params_bnb.m_effective_feerate, 6 * 24,
false, 0,
true);
287 BOOST_REQUIRE(!no_res);
291 add_coin(available_coins, *
wallet, 5 *
CENT, coin_selection_params_bnb.m_effective_feerate, 6 * 24,
false, 0,
true);
299 expected_attempts = 22;
300 BOOST_CHECK_MESSAGE(res->GetSelectionsEvaluated() == expected_attempts,
strprintf(
"Expected %i attempts, but got %i", expected_attempts, res->GetSelectionsEvaluated()));
323 params.m_subtract_fee_outputs =
true;
324 params.m_change_fee = params.m_effective_feerate.
GetFee(params.change_output_size);
325 params.m_cost_of_change = params.m_discard_feerate.GetFee(params.change_spend_size) + params.m_change_fee;
326 params.m_min_change_target = params.m_cost_of_change + 1;
329 add_coin(available_coins, *
wallet,
COIN + params.m_cost_of_change, params.m_effective_feerate, 6,
true, 0,
true);
330 add_coin(available_coins, *
wallet, 0.5 *
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_effective_feerate, 6,
true, 0,
true);
354 available_coins = {};
423 available_coins = {};
481 available_coins = {};
513 available_coins = {};
514 for (
int j = 0; j < 20; j++)
526 available_coins = {};
537 available_coins = {};
548 available_coins = {};
568 available_coins = {};
570 for (uint16_t j = 0; j < 676; j++)
578 if (amt - 2000 <
CENT) {
580 uint16_t returnSize = std::ceil((2000.0 +
CENT)/amt);
581 CAmount returnValue = amt * returnSize;
594 available_coins = {};
595 for (
int i2 = 0; i2 < 100; i2++)
658 for (
int i = 0; i < 1000; i++)
675 std::default_random_engine generator;
676 std::exponential_distribution<double> distribution (100);
680 for (
int i = 0; i < 100; ++i)
686 for (
int j = 0; j < 1000; ++j)
688 CAmount val = distribution(generator)*10000000;
711 cs_params.m_cost_of_change = 1;
712 cs_params.min_viable_change = 1;
714 const auto result =
SelectCoins(*
wallet, available_coins, {}, target, cc, cs_params);
716 BOOST_CHECK_GE(result->GetSelectedValue(), target);
723 const CAmount min_viable_change{300};
724 const CAmount change_cost{125};
741 selection1.RecalculateWaste(min_viable_change, change_cost, change_fee);
748 selection2.RecalculateWaste(min_viable_change, change_cost, change_fee);
756 selection3.RecalculateWaste(min_viable_change, change_cost, change_fee);
758 BOOST_CHECK_LT(selection3.GetWaste(), selection1.GetWaste());
766 selection_nochange1.RecalculateWaste(min_viable_change, change_cost, change_fee);
774 selection_nochange2.RecalculateWaste(min_viable_change, change_cost, change_fee);
776 BOOST_CHECK_LT(selection_nochange2.GetWaste(), selection_nochange1.GetWaste());
784 selection.RecalculateWaste(min_viable_change, change_cost, change_fee);
793 selection.RecalculateWaste(min_viable_change, change_cost, change_fee);
802 selection.RecalculateWaste(min_viable_change, change_cost , change_fee);
811 selection.RecalculateWaste(min_viable_change, fee_diff * 2, change_fee);
817 const CAmount new_target{exact_target - fee_diff * 2};
821 selection.RecalculateWaste(min_viable_change, change_cost, change_fee);
828 const CAmount target_waste1{-2 * fee_diff};
831 selection.RecalculateWaste(min_viable_change, change_cost, change_fee);
838 const CAmount large_fee_diff{90};
839 const CAmount target_waste2{-2 * large_fee_diff + change_cost};
843 assert(target_waste2 == -55);
846 selection.RecalculateWaste(min_viable_change, change_cost, change_fee);
855 const CAmount min_viable_change{200};
856 const CAmount change_cost{125};
865 const std::vector<std::shared_ptr<COutput>> inputs = selection.GetShuffledInputVector();
867 for (
size_t i = 0; i < inputs.size(); ++i) {
868 inputs[i]->ApplyBumpFee(20*(i+1));
871 selection.RecalculateWaste(min_viable_change, change_cost, change_fee);
872 CAmount expected_waste = fee_diff * -2 + change_cost + 60;
875 selection.SetBumpFeeDiscount(30);
876 selection.RecalculateWaste(min_viable_change, change_cost, change_fee);
877 expected_waste = fee_diff * -2 + change_cost + 60 - 30;
891 const std::vector<std::shared_ptr<COutput>> inputs = selection.GetShuffledInputVector();
893 for (
size_t i = 0; i < inputs.size(); ++i) {
894 inputs[i]->ApplyBumpFee(20*(i+1));
897 selection.RecalculateWaste(min_viable_change, change_cost, change_fee);
898 CAmount expected_waste = fee_diff * -2 + 60 + 40;
901 selection.SetBumpFeeDiscount(30);
902 selection.RecalculateWaste(min_viable_change, change_cost, change_fee);
903 expected_waste = fee_diff * -2 + 60 - 30 + 70;
910 const int input_bytes = 148;
913 const int nInput = 0;
917 tx.
vout[nInput].nValue = nValue;
921 const CAmount expected_ev1 = 9852;
930 const CAmount expected_ev3 = -4800;
946 int max_selection_weight,
984 int max_selection_weight = 10'000;
987 for (
int j = 0; j < 10; ++j) {
991 return available_coins;
1002 int max_selection_weight = 3000;
1005 for (
int j = 0; j < 10; ++j) {
1009 return available_coins;
1020 int max_selection_weight = 10'000;
1023 for (
int j = 0; j < 60; ++j) {
1026 for (
int i = 0; i < 10; i++) {
1029 return available_coins;
1032 for (
int i = 0; i < 10; ++i) {
1035 for (
int j = 0; j < 17; ++j) {
1040 size_t expected_attempts = 37;
1041 BOOST_CHECK_MESSAGE(res->GetSelectionsEvaluated() == expected_attempts,
strprintf(
"Expected %i attempts, but got %i", expected_attempts, res->GetSelectionsEvaluated()));
1049 int max_selection_weight = 400'000;
1055 return available_coins;
1062 size_t expected_attempts = 3;
1063 BOOST_CHECK_MESSAGE(res->GetSelectionsEvaluated() == expected_attempts,
strprintf(
"Expected %i attempts, but got %i", expected_attempts, res->GetSelectionsEvaluated()));
1071 int max_selection_weight = 400'000;
1074 for (
int j = 0; j < 5; ++j) {
1082 return available_coins;
1091 size_t expected_attempts = 92;
1092 BOOST_CHECK_MESSAGE(res->GetSelectionsEvaluated() == expected_attempts,
strprintf(
"Expected %i attempts, but got %i", expected_attempts, res->GetSelectionsEvaluated()));
1100 int max_selection_weight = 400'000;
1109 for (
int j = 0; j < 100; ++j) {
1112 for (
int j = 0; j < 100; ++j) {
1115 for (
int j = 0; j < 100; ++j) {
1118 for (
int j = 0; j < 100; ++j) {
1121 return available_coins;
1130 size_t expected_attempts = 38;
1131 BOOST_CHECK_MESSAGE(res->GetSelectionsEvaluated() == expected_attempts,
strprintf(
"Expected %i attempts, but got %i", expected_attempts, res->GetSelectionsEvaluated()));
1139 int max_selection_weight = 40000;
1145 for (
int j = 0; j < 100; ++j) {
1149 return available_coins;
1156 size_t expected_attempts = 7;
1157 BOOST_CHECK_MESSAGE(res->GetSelectionsEvaluated() == expected_attempts,
strprintf(
"Expected %i attempts, but got %i", expected_attempts, res->GetSelectionsEvaluated()));
1165 int max_selection_weight = 3200;
1166 dummy_params.m_min_change_target = 0;
1169 for (
int i = 0; i < 18; ++i) {
1172 return doppelgangers;
1176 for (
int i = 0; i < 8; ++i) {
1181 size_t expected_attempts = 87'525;
1182 BOOST_CHECK_MESSAGE(result_a->GetSelectionsEvaluated() == expected_attempts,
strprintf(
"Expected %i attempts, but got %i", expected_attempts, result_a->GetSelectionsEvaluated()));
1187 for (
int i = 0; i < 19; ++i) {
1190 return doppelgangers;
1199 auto available_coins = coin_setup(*
wallet);
1204 const auto signedTxSize = 10 + 34 + 68 * result->GetInputSet().size();
1207 BOOST_CHECK_GE(result->GetSelectedValue(), target);
1214 return std::any_of(set.begin(), set.end(), [&](
const auto& coin) { return coin->GetEffectiveValue() == amount; });
1247 for (
int j = 0; j < 1515; ++j) {
1252 return available_coins;
1259 BOOST_CHECK_LE(result->GetWeight(), max_weight);
1273 for (
int j = 0; j < 400; ++j) {
1276 for (
int j = 0; j < 2000; ++j) {
1279 return available_coins;
1285 BOOST_CHECK_LE(result->GetWeight(), max_weight);
1298 for (
int j = 0; j < 1515; ++j) {
1301 return available_coins;
1323 add_coin(available_coins, *dummyWallet, 100000);
1342 COutput output = available_coins.
All().at(0);
1350 const auto result =
SelectCoins(*
wallet, available_coins, preset_inputs, target, cc, cs_params);
1362 for (
int i=0; i<10; i++) {
1370 std::unordered_set<COutPoint, SaltedOutpointHasher> outs_to_remove;
1371 const auto& coins = available_coins.
All();
1372 for (
int i = 0; i < 2; i++) {
1373 outs_to_remove.emplace(coins[i].outpoint);
1375 available_coins.
Erase(outs_to_remove);
1378 const auto& updated_coins = available_coins.
All();
1379 for (
const auto&
out: outs_to_remove) {
1380 auto it = std::find_if(updated_coins.begin(), updated_coins.end(), [&
out](
const COutput &coin) {
1381 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
CTransactionRef GetTx() const
static const int WITNESS_SCALE_FACTOR
BOOST_FIXTURE_TEST_SUITE(cuckoocache_tests, BasicTestingSetup)
Test Suite for CuckooCache.
BOOST_AUTO_TEST_SUITE_END()
BOOST_CHECK_GT(excessive_headers.size(), MAX_HEADERS_SIZE)
BOOST_CHECK_EQUAL(headers.FindFirst("key"), "value")
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(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.