20#include <boost/test/unit_test.hpp>
31#define RANDOM_REPEATS 5
41 tx.
vout.resize(nInput + 1);
42 tx.
vout[nInput].nValue = nValue;
46 group.Insert(std::make_shared<COutput>(output), 0, 0);
53 tx.
vout.resize(nInput + 1);
54 tx.
vout[nInput].nValue = nValue;
56 std::shared_ptr<COutput> coin = std::make_shared<COutput>(
COutPoint(tx.
GetHash(), nInput), tx.
vout.at(nInput), 1, 148,
true,
true, 0,
false,
fee);
58 group.Insert(coin, 0, 0);
59 coin->long_term_fee = long_term_fee;
67 tx.
vout.resize(nInput + 1);
68 tx.
vout[nInput].nValue = nValue;
78 const auto& txout = wtx.
tx->vout.at(nInput);
79 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});
87 return res ? std::optional<SelectionResult>(*res) : std::nullopt;
93 return res ? std::optional<SelectionResult>(*res) : std::nullopt;
100 std::vector<CAmount> a_amts;
101 std::vector<CAmount> b_amts;
103 a_amts.push_back(coin->txout.nValue);
106 b_amts.push_back(coin->txout.nValue);
108 std::sort(a_amts.begin(), a_amts.end());
109 std::sort(b_amts.begin(), b_amts.end());
111 std::pair<std::vector<CAmount>::iterator, std::vector<CAmount>::iterator>
ret = std::mismatch(a_amts.begin(), a_amts.end(), b_amts.begin());
112 return ret.first == a_amts.end() &&
ret.second == b_amts.end();
119 [](
const std::shared_ptr<COutput>& a,
const std::shared_ptr<COutput>& b) {
120 return a->outpoint == b->outpoint;
125inline std::vector<OutputGroup>&
GroupCoins(
const std::vector<COutput>& available_coins,
bool subtract_fee_outputs =
false)
127 static std::vector<OutputGroup> static_groups;
128 static_groups.clear();
129 for (
auto& coin : available_coins) {
130 static_groups.emplace_back();
132 group.Insert(std::make_shared<COutput>(coin), 0, 0);
133 group.m_subtract_fee_outputs = subtract_fee_outputs;
135 return static_groups;
153 static_groups =
GroupOutputs(
wallet, available_coins, coin_selection_params, {{filter}})[filter];
162 wallet->SetupDescriptorScriptPubKeyMans();
171 std::vector<COutput> utxo_pool;
190 coin_selection_params_bnb.m_change_fee = coin_selection_params_bnb.m_effective_feerate.
GetFee(coin_selection_params_bnb.change_output_size);
191 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;
192 coin_selection_params_bnb.min_viable_change = coin_selection_params_bnb.m_effective_feerate.GetFee(coin_selection_params_bnb.change_spend_size);
199 add_coin(available_coins, *
wallet, 1, coin_selection_params_bnb.m_effective_feerate);
200 available_coins.
All().at(0).input_bytes = 40;
204 available_coins.
Clear();
205 add_coin(available_coins, *
wallet, 1 *
CENT, coin_selection_params_bnb.m_effective_feerate);
206 available_coins.
All().at(0).input_bytes = 40;
217 coin_selection_params_bnb.m_effective_feerate =
CFeeRate(0);
218 add_coin(available_coins, *
wallet, 5 *
CENT, coin_selection_params_bnb.m_effective_feerate, 6 * 24,
false, 0,
true);
219 add_coin(available_coins, *
wallet, 3 *
CENT, coin_selection_params_bnb.m_effective_feerate, 6 * 24,
false, 0,
true);
220 add_coin(available_coins, *
wallet, 2 *
CENT, coin_selection_params_bnb.m_effective_feerate, 6 * 24,
false, 0,
true);
223 COutput select_coin = available_coins.
All().at(0);
230 const auto result10 =
SelectCoins(*
wallet, available_coins, selected_input, 10 *
CENT, coin_control, coin_selection_params_bnb);
240 coin_selection_params_bnb.m_effective_feerate =
CFeeRate(5000);
241 coin_selection_params_bnb.m_long_term_feerate =
CFeeRate(3000);
244 CAmount input_fee = coin_selection_params_bnb.m_effective_feerate.GetFee(68);
245 add_coin(available_coins, *
wallet, 10 *
CENT + input_fee, coin_selection_params_bnb.m_effective_feerate, 6 * 24,
false, 0,
true);
246 add_coin(available_coins, *
wallet, 9 *
CENT + input_fee, coin_selection_params_bnb.m_effective_feerate, 6 * 24,
false, 0,
true);
247 add_coin(available_coins, *
wallet, 1 *
CENT + input_fee, coin_selection_params_bnb.m_effective_feerate, 6 * 24,
false, 0,
true);
254 COutput select_coin = available_coins.
All().at(1);
259 const auto result13 =
SelectCoins(*
wallet, available_coins, selected_input, 10 *
CENT, coin_control, coin_selection_params_bnb);
270 add_coin(available_coins, *
wallet, 10 *
CENT, coin_selection_params_bnb.m_effective_feerate, 6 * 24,
false, 0,
true);
271 add_coin(available_coins, *
wallet, 9 *
CENT, coin_selection_params_bnb.m_effective_feerate, 6 * 24,
false, 0,
true);
272 add_coin(available_coins, *
wallet, 8 *
CENT, coin_selection_params_bnb.m_effective_feerate, 6 * 24,
false, 0,
true);
274 add_coin(available_coins, *
wallet, 3 *
CENT, coin_selection_params_bnb.m_effective_feerate, 6 * 24,
false, 0,
true);
275 add_coin(available_coins, *
wallet, 1 *
CENT, coin_selection_params_bnb.m_effective_feerate, 6 * 24,
false, 0,
true);
280 BOOST_REQUIRE(!no_res);
284 add_coin(available_coins, *
wallet, 5 *
CENT, coin_selection_params_bnb.m_effective_feerate, 6 * 24,
false, 0,
true);
314 params.m_subtract_fee_outputs =
true;
315 params.m_change_fee = params.m_effective_feerate.
GetFee(params.change_output_size);
316 params.m_cost_of_change = params.m_discard_feerate.GetFee(params.change_spend_size) + params.m_change_fee;
317 params.m_min_change_target = params.m_cost_of_change + 1;
320 add_coin(available_coins, *
wallet,
COIN + params.m_cost_of_change, params.m_effective_feerate, 6,
true, 0,
true);
321 add_coin(available_coins, *
wallet, 0.5 *
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_effective_feerate, 6,
true, 0,
true);
345 available_coins.
Clear();
414 available_coins.
Clear();
472 available_coins.
Clear();
504 available_coins.
Clear();
505 for (
int j = 0; j < 20; j++)
517 available_coins.
Clear();
528 available_coins.
Clear();
539 available_coins.
Clear();
559 available_coins.
Clear();
561 for (uint16_t j = 0; j < 676; j++)
569 if (amt - 2000 <
CENT) {
571 uint16_t returnSize = std::ceil((2000.0 +
CENT)/amt);
572 CAmount returnValue = amt * returnSize;
585 available_coins.
Clear();
586 for (
int i2 = 0; i2 < 100; i2++)
649 for (
int i = 0; i < 1000; i++)
666 std::default_random_engine generator;
667 std::exponential_distribution<double> distribution (100);
671 for (
int i = 0; i < 100; ++i)
677 for (
int j = 0; j < 1000; ++j)
679 CAmount val = distribution(generator)*10000000;
702 cs_params.m_cost_of_change = 1;
703 cs_params.min_viable_change = 1;
705 const auto result =
SelectCoins(*
wallet, available_coins, {}, target, cc, cs_params);
707 BOOST_CHECK_GE(result->GetSelectedValue(), target);
714 const CAmount min_viable_change{300};
715 const CAmount change_cost{125};
732 selection1.RecalculateWaste(min_viable_change, change_cost, change_fee);
739 selection2.RecalculateWaste(min_viable_change, change_cost, change_fee);
740 BOOST_CHECK_GT(selection2.GetWaste(), selection1.GetWaste());
747 selection3.RecalculateWaste(min_viable_change, change_cost, change_fee);
749 BOOST_CHECK_LT(selection3.GetWaste(), selection1.GetWaste());
757 selection_nochange1.RecalculateWaste(min_viable_change, change_cost, change_fee);
765 selection_nochange2.RecalculateWaste(min_viable_change, change_cost, change_fee);
767 BOOST_CHECK_LT(selection_nochange2.GetWaste(), selection_nochange1.GetWaste());
775 selection.RecalculateWaste(min_viable_change, change_cost, change_fee);
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, fee_diff * 2, change_fee);
808 const CAmount new_target{exact_target - fee_diff * 2};
812 selection.RecalculateWaste(min_viable_change, change_cost, change_fee);
819 const CAmount target_waste1{-2 * fee_diff};
822 selection.RecalculateWaste(min_viable_change, change_cost, change_fee);
829 const CAmount large_fee_diff{90};
830 const CAmount target_waste2{-2 * large_fee_diff + change_cost};
834 assert(target_waste2 == -55);
837 selection.RecalculateWaste(min_viable_change, change_cost, change_fee);
846 const CAmount min_viable_change{200};
847 const CAmount change_cost{125};
856 const std::vector<std::shared_ptr<COutput>> inputs = selection.GetShuffledInputVector();
858 for (
size_t i = 0; i < inputs.size(); ++i) {
859 inputs[i]->ApplyBumpFee(20*(i+1));
862 selection.RecalculateWaste(min_viable_change, change_cost, change_fee);
863 CAmount expected_waste = fee_diff * -2 + change_cost + 60;
866 selection.SetBumpFeeDiscount(30);
867 selection.RecalculateWaste(min_viable_change, change_cost, change_fee);
868 expected_waste = fee_diff * -2 + change_cost + 60 - 30;
882 const std::vector<std::shared_ptr<COutput>> inputs = selection.GetShuffledInputVector();
884 for (
size_t i = 0; i < inputs.size(); ++i) {
885 inputs[i]->ApplyBumpFee(20*(i+1));
888 selection.RecalculateWaste(min_viable_change, change_cost, change_fee);
889 CAmount expected_waste = fee_diff * -2 + 60 + 40;
892 selection.SetBumpFeeDiscount(30);
893 selection.RecalculateWaste(min_viable_change, change_cost, change_fee);
894 expected_waste = fee_diff * -2 + 60 - 30 + 70;
901 const int input_bytes = 148;
904 const int nInput = 0;
908 tx.
vout[nInput].nValue = nValue;
912 const CAmount expected_ev1 = 9852;
921 const CAmount expected_ev3 = -4800;
937 int max_selection_weight,
975 int max_selection_weight = 10'000;
978 for (
int j = 0; j < 10; ++j) {
982 return available_coins;
993 int max_selection_weight = 3000;
996 for (
int j = 0; j < 10; ++j) {
1000 return available_coins;
1011 int max_selection_weight = 10'000;
1014 for (
int j = 0; j < 60; ++j) {
1017 for (
int i = 0; i < 10; i++) {
1020 return available_coins;
1023 for (
int i = 0; i < 10; ++i) {
1026 for (
int j = 0; j < 17; ++j) {
1031 size_t expected_attempts = 37;
1032 BOOST_CHECK_MESSAGE(res->GetSelectionsEvaluated() == expected_attempts,
strprintf(
"Expected %i attempts, but got %i", expected_attempts, res->GetSelectionsEvaluated()));
1040 int max_selection_weight = 400'000;
1046 return available_coins;
1053 size_t expected_attempts = 3;
1054 BOOST_CHECK_MESSAGE(res->GetSelectionsEvaluated() == expected_attempts,
strprintf(
"Expected %i attempts, but got %i", expected_attempts, res->GetSelectionsEvaluated()));
1062 int max_selection_weight = 400'000;
1065 for (
int j = 0; j < 5; ++j) {
1073 return available_coins;
1082 size_t expected_attempts = 92;
1083 BOOST_CHECK_MESSAGE(res->GetSelectionsEvaluated() == expected_attempts,
strprintf(
"Expected %i attempts, but got %i", expected_attempts, res->GetSelectionsEvaluated()));
1091 int max_selection_weight = 400'000;
1100 for (
int j = 0; j < 100; ++j) {
1103 for (
int j = 0; j < 100; ++j) {
1106 for (
int j = 0; j < 100; ++j) {
1109 for (
int j = 0; j < 100; ++j) {
1112 return available_coins;
1121 size_t expected_attempts = 38;
1122 BOOST_CHECK_MESSAGE(res->GetSelectionsEvaluated() == expected_attempts,
strprintf(
"Expected %i attempts, but got %i", expected_attempts, res->GetSelectionsEvaluated()));
1130 int max_selection_weight = 40000;
1136 for (
int j = 0; j < 100; ++j) {
1140 return available_coins;
1147 size_t expected_attempts = 7;
1148 BOOST_CHECK_MESSAGE(res->GetSelectionsEvaluated() == expected_attempts,
strprintf(
"Expected %i attempts, but got %i", expected_attempts, res->GetSelectionsEvaluated()));
1156 int max_selection_weight = 3200;
1157 dummy_params.m_min_change_target = 0;
1160 for (
int i = 0; i < 18; ++i) {
1163 return doppelgangers;
1167 for (
int i = 0; i < 8; ++i) {
1172 size_t expected_attempts = 87'525;
1173 BOOST_CHECK_MESSAGE(result_a->GetSelectionsEvaluated() == expected_attempts,
strprintf(
"Expected %i attempts, but got %i", expected_attempts, result_a->GetSelectionsEvaluated()));
1178 for (
int i = 0; i < 19; ++i) {
1181 return doppelgangers;
1190 int max_selection_weight,
1224 int max_selection_weight = 10000;
1227 for (
int j = 0; j < 10; ++j) {
1231 return available_coins;
1242 int max_selection_weight = 3000;
1245 for (
int j = 0; j < 10; ++j) {
1250 return available_coins;
1261 int max_selection_weight = 10000;
1264 for (
int j = 0; j < 60; ++j) {
1267 for (
int i = 0; i < 10; i++) {
1270 return available_coins;
1273 BOOST_CHECK(res->GetWeight() <= max_selection_weight);
1280 auto available_coins = coin_setup(*
wallet);
1285 const auto signedTxSize = 10 + 34 + 68 * result->GetInputSet().size();
1288 BOOST_CHECK_GE(result->GetSelectedValue(), target);
1295 return std::any_of(set.begin(), set.end(), [&](
const auto& coin) { return coin->GetEffectiveValue() == amount; });
1328 for (
int j = 0; j < 1515; ++j) {
1333 return available_coins;
1340 BOOST_CHECK_LE(result->GetWeight(), max_weight);
1354 for (
int j = 0; j < 400; ++j) {
1357 for (
int j = 0; j < 2000; ++j) {
1360 return available_coins;
1366 BOOST_CHECK_LE(result->GetWeight(), max_weight);
1379 for (
int j = 0; j < 1515; ++j) {
1382 return available_coins;
1404 add_coin(available_coins, *dummyWallet, 100000);
1423 COutput output = available_coins.
All().at(0);
1431 const auto result =
SelectCoins(*
wallet, available_coins, preset_inputs, target, cc, cs_params);
1443 for (
int i=0; i<10; i++) {
1451 std::unordered_set<COutPoint, SaltedOutpointHasher> outs_to_remove;
1452 const auto& coins = available_coins.
All();
1453 for (
int i = 0; i < 2; i++) {
1454 outs_to_remove.emplace(coins[i].outpoint);
1456 available_coins.
Erase(outs_to_remove);
1459 const auto& updated_coins = available_coins.
All();
1460 for (
const auto&
out: outs_to_remove) {
1461 auto it = std::find_if(updated_coins.begin(), updated_coins.end(), [&
out](
const COutput &coin) {
1462 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.