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;
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.
Clear();
206 add_coin(available_coins, *
wallet, 1 *
CENT, coin_selection_params_bnb.m_effective_feerate);
207 available_coins.
All().at(0).input_bytes = 40;
218 coin_selection_params_bnb.m_effective_feerate =
CFeeRate(0);
219 add_coin(available_coins, *
wallet, 5 *
CENT, coin_selection_params_bnb.m_effective_feerate, 6 * 24,
false, 0,
true);
220 add_coin(available_coins, *
wallet, 3 *
CENT, coin_selection_params_bnb.m_effective_feerate, 6 * 24,
false, 0,
true);
221 add_coin(available_coins, *
wallet, 2 *
CENT, coin_selection_params_bnb.m_effective_feerate, 6 * 24,
false, 0,
true);
224 COutput select_coin = available_coins.
All().at(0);
231 const auto result10 =
SelectCoins(*
wallet, available_coins, selected_input, 10 *
CENT, coin_control, coin_selection_params_bnb);
241 coin_selection_params_bnb.m_effective_feerate =
CFeeRate(5000);
242 coin_selection_params_bnb.m_long_term_feerate =
CFeeRate(3000);
245 CAmount input_fee = coin_selection_params_bnb.m_effective_feerate.GetFee(68);
246 add_coin(available_coins, *
wallet, 10 *
CENT + input_fee, coin_selection_params_bnb.m_effective_feerate, 6 * 24,
false, 0,
true);
247 add_coin(available_coins, *
wallet, 9 *
CENT + input_fee, coin_selection_params_bnb.m_effective_feerate, 6 * 24,
false, 0,
true);
248 add_coin(available_coins, *
wallet, 1 *
CENT + input_fee, coin_selection_params_bnb.m_effective_feerate, 6 * 24,
false, 0,
true);
255 COutput select_coin = available_coins.
All().at(1);
260 const auto result13 =
SelectCoins(*
wallet, available_coins, selected_input, 10 *
CENT, coin_control, coin_selection_params_bnb);
271 add_coin(available_coins, *
wallet, 10 *
CENT, coin_selection_params_bnb.m_effective_feerate, 6 * 24,
false, 0,
true);
272 add_coin(available_coins, *
wallet, 9 *
CENT, coin_selection_params_bnb.m_effective_feerate, 6 * 24,
false, 0,
true);
273 add_coin(available_coins, *
wallet, 8 *
CENT, coin_selection_params_bnb.m_effective_feerate, 6 * 24,
false, 0,
true);
275 add_coin(available_coins, *
wallet, 3 *
CENT, coin_selection_params_bnb.m_effective_feerate, 6 * 24,
false, 0,
true);
276 add_coin(available_coins, *
wallet, 1 *
CENT, coin_selection_params_bnb.m_effective_feerate, 6 * 24,
false, 0,
true);
281 BOOST_REQUIRE(!no_res);
285 add_coin(available_coins, *
wallet, 5 *
CENT, coin_selection_params_bnb.m_effective_feerate, 6 * 24,
false, 0,
true);
315 params.m_subtract_fee_outputs =
true;
316 params.m_change_fee = params.m_effective_feerate.
GetFee(params.change_output_size);
317 params.m_cost_of_change = params.m_discard_feerate.GetFee(params.change_spend_size) + params.m_change_fee;
318 params.m_min_change_target = params.m_cost_of_change + 1;
321 add_coin(available_coins, *
wallet,
COIN + params.m_cost_of_change, params.m_effective_feerate, 6,
true, 0,
true);
322 add_coin(available_coins, *
wallet, 0.5 *
COIN + params.m_cost_of_change, params.m_effective_feerate, 6,
true, 0,
true);
323 add_coin(available_coins, *
wallet, 0.5 *
COIN, params.m_effective_feerate, 6,
true, 0,
true);
346 available_coins.
Clear();
415 available_coins.
Clear();
473 available_coins.
Clear();
505 available_coins.
Clear();
506 for (
int j = 0; j < 20; j++)
518 available_coins.
Clear();
529 available_coins.
Clear();
540 available_coins.
Clear();
560 available_coins.
Clear();
562 for (uint16_t j = 0; j < 676; j++)
570 if (amt - 2000 <
CENT) {
572 uint16_t returnSize = std::ceil((2000.0 +
CENT)/amt);
573 CAmount returnValue = amt * returnSize;
586 available_coins.
Clear();
587 for (
int i2 = 0; i2 < 100; i2++)
650 for (
int i = 0; i < 1000; i++)
667 std::default_random_engine generator;
668 std::exponential_distribution<double> distribution (100);
672 for (
int i = 0; i < 100; ++i)
678 for (
int j = 0; j < 1000; ++j)
680 CAmount val = distribution(generator)*10000000;
703 cs_params.m_cost_of_change = 1;
704 cs_params.min_viable_change = 1;
706 const auto result =
SelectCoins(*
wallet, available_coins, {}, target, cc, cs_params);
708 BOOST_CHECK_GE(result->GetSelectedValue(), target);
715 const CAmount min_viable_change{300};
716 const CAmount change_cost{125};
733 selection1.RecalculateWaste(min_viable_change, change_cost, change_fee);
740 selection2.RecalculateWaste(min_viable_change, change_cost, change_fee);
741 BOOST_CHECK_GT(selection2.GetWaste(), selection1.GetWaste());
748 selection3.RecalculateWaste(min_viable_change, change_cost, change_fee);
750 BOOST_CHECK_LT(selection3.GetWaste(), selection1.GetWaste());
758 selection_nochange1.RecalculateWaste(min_viable_change, change_cost, change_fee);
766 selection_nochange2.RecalculateWaste(min_viable_change, change_cost, change_fee);
768 BOOST_CHECK_LT(selection_nochange2.GetWaste(), selection_nochange1.GetWaste());
776 selection.RecalculateWaste(min_viable_change, change_cost, change_fee);
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, fee_diff * 2, change_fee);
809 const CAmount new_target{exact_target - fee_diff * 2};
813 selection.RecalculateWaste(min_viable_change, change_cost, change_fee);
820 const CAmount target_waste1{-2 * fee_diff};
823 selection.RecalculateWaste(min_viable_change, change_cost, change_fee);
830 const CAmount large_fee_diff{90};
831 const CAmount target_waste2{-2 * large_fee_diff + change_cost};
835 assert(target_waste2 == -55);
838 selection.RecalculateWaste(min_viable_change, change_cost, change_fee);
847 const CAmount min_viable_change{200};
848 const CAmount change_cost{125};
857 const std::vector<std::shared_ptr<COutput>> inputs = selection.GetShuffledInputVector();
859 for (
size_t i = 0; i < inputs.size(); ++i) {
860 inputs[i]->ApplyBumpFee(20*(i+1));
863 selection.RecalculateWaste(min_viable_change, change_cost, change_fee);
864 CAmount expected_waste = fee_diff * -2 + change_cost + 60;
867 selection.SetBumpFeeDiscount(30);
868 selection.RecalculateWaste(min_viable_change, change_cost, change_fee);
869 expected_waste = fee_diff * -2 + change_cost + 60 - 30;
883 const std::vector<std::shared_ptr<COutput>> inputs = selection.GetShuffledInputVector();
885 for (
size_t i = 0; i < inputs.size(); ++i) {
886 inputs[i]->ApplyBumpFee(20*(i+1));
889 selection.RecalculateWaste(min_viable_change, change_cost, change_fee);
890 CAmount expected_waste = fee_diff * -2 + 60 + 40;
893 selection.SetBumpFeeDiscount(30);
894 selection.RecalculateWaste(min_viable_change, change_cost, change_fee);
895 expected_waste = fee_diff * -2 + 60 - 30 + 70;
902 const int input_bytes = 148;
905 const int nInput = 0;
909 tx.
vout[nInput].nValue = nValue;
913 const CAmount expected_ev1 = 9852;
922 const CAmount expected_ev3 = -4800;
938 int max_selection_weight,
976 int max_selection_weight = 10'000;
979 for (
int j = 0; j < 10; ++j) {
983 return available_coins;
994 int max_selection_weight = 3000;
997 for (
int j = 0; j < 10; ++j) {
1001 return available_coins;
1012 int max_selection_weight = 10'000;
1015 for (
int j = 0; j < 60; ++j) {
1018 for (
int i = 0; i < 10; i++) {
1021 return available_coins;
1024 for (
int i = 0; i < 10; ++i) {
1027 for (
int j = 0; j < 17; ++j) {
1032 size_t expected_attempts = 37;
1033 BOOST_CHECK_MESSAGE(res->GetSelectionsEvaluated() == expected_attempts,
strprintf(
"Expected %i attempts, but got %i", expected_attempts, res->GetSelectionsEvaluated()));
1041 int max_selection_weight = 400'000;
1047 return available_coins;
1054 size_t expected_attempts = 3;
1055 BOOST_CHECK_MESSAGE(res->GetSelectionsEvaluated() == expected_attempts,
strprintf(
"Expected %i attempts, but got %i", expected_attempts, res->GetSelectionsEvaluated()));
1063 int max_selection_weight = 400'000;
1066 for (
int j = 0; j < 5; ++j) {
1074 return available_coins;
1083 size_t expected_attempts = 92;
1084 BOOST_CHECK_MESSAGE(res->GetSelectionsEvaluated() == expected_attempts,
strprintf(
"Expected %i attempts, but got %i", expected_attempts, res->GetSelectionsEvaluated()));
1092 int max_selection_weight = 400'000;
1101 for (
int j = 0; j < 100; ++j) {
1104 for (
int j = 0; j < 100; ++j) {
1107 for (
int j = 0; j < 100; ++j) {
1110 for (
int j = 0; j < 100; ++j) {
1113 return available_coins;
1122 size_t expected_attempts = 38;
1123 BOOST_CHECK_MESSAGE(res->GetSelectionsEvaluated() == expected_attempts,
strprintf(
"Expected %i attempts, but got %i", expected_attempts, res->GetSelectionsEvaluated()));
1131 int max_selection_weight = 40000;
1137 for (
int j = 0; j < 100; ++j) {
1141 return available_coins;
1148 size_t expected_attempts = 7;
1149 BOOST_CHECK_MESSAGE(res->GetSelectionsEvaluated() == expected_attempts,
strprintf(
"Expected %i attempts, but got %i", expected_attempts, res->GetSelectionsEvaluated()));
1157 int max_selection_weight = 3200;
1158 dummy_params.m_min_change_target = 0;
1161 for (
int i = 0; i < 18; ++i) {
1164 return doppelgangers;
1168 for (
int i = 0; i < 8; ++i) {
1173 size_t expected_attempts = 87'525;
1174 BOOST_CHECK_MESSAGE(result_a->GetSelectionsEvaluated() == expected_attempts,
strprintf(
"Expected %i attempts, but got %i", expected_attempts, result_a->GetSelectionsEvaluated()));
1179 for (
int i = 0; i < 19; ++i) {
1182 return doppelgangers;
1191 int max_selection_weight,
1225 int max_selection_weight = 10000;
1228 for (
int j = 0; j < 10; ++j) {
1232 return available_coins;
1243 int max_selection_weight = 3000;
1246 for (
int j = 0; j < 10; ++j) {
1251 return available_coins;
1262 int max_selection_weight = 10000;
1265 for (
int j = 0; j < 60; ++j) {
1268 for (
int i = 0; i < 10; i++) {
1271 return available_coins;
1274 BOOST_CHECK(res->GetWeight() <= max_selection_weight);
1281 auto available_coins = coin_setup(*
wallet);
1286 const auto signedTxSize = 10 + 34 + 68 * result->GetInputSet().size();
1289 BOOST_CHECK_GE(result->GetSelectedValue(), target);
1296 return std::any_of(set.begin(), set.end(), [&](
const auto& coin) { return coin->GetEffectiveValue() == amount; });
1329 for (
int j = 0; j < 1515; ++j) {
1334 return available_coins;
1341 BOOST_CHECK_LE(result->GetWeight(), max_weight);
1355 for (
int j = 0; j < 400; ++j) {
1358 for (
int j = 0; j < 2000; ++j) {
1361 return available_coins;
1367 BOOST_CHECK_LE(result->GetWeight(), max_weight);
1380 for (
int j = 0; j < 1515; ++j) {
1383 return available_coins;
1405 add_coin(available_coins, *dummyWallet, 100000);
1424 COutput output = available_coins.
All().at(0);
1432 const auto result =
SelectCoins(*
wallet, available_coins, preset_inputs, target, cc, cs_params);
1444 for (
int i=0; i<10; i++) {
1452 std::unordered_set<COutPoint, SaltedOutpointHasher> outs_to_remove;
1453 const auto& coins = available_coins.
All();
1454 for (
int i = 0; i < 2; i++) {
1455 outs_to_remove.emplace(coins[i].outpoint);
1457 available_coins.
Erase(outs_to_remove);
1460 const auto& updated_coins = available_coins.
All();
1461 for (
const auto&
out: outs_to_remove) {
1462 auto it = std::find_if(updated_coins.begin(), updated_coins.end(), [&
out](
const COutput &coin) {
1463 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)
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 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.
std::unique_ptr< WalletDatabase > CreateMockableWalletDatabase(MockableData records)
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 void add_coin(const CAmount &nValue, int nInput, SelectionResult &result)
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,...
util::Result< SelectionResult > SelectCoinsSRD(const std::vector< OutputGroup > &utxo_pool, CAmount target_value, CAmount change_fee, FastRandomContext &rng, int max_selection_weight)
Select coins by Single Random Draw (SRD).
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.
FastRandomContext & rng_fast
Randomness to use in the context 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...
CAmount m_change_fee
Cost of creating the change output.
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.