Bitcoin Core 31.0.0
P2P Digital Currency
Loading...
Searching...
No Matches
group_outputs_tests.cpp
Go to the documentation of this file.
1// Copyright (c) 2022-present The Bitcoin Core developers
2// Distributed under the MIT software license, see the accompanying
3// file COPYING or https://www.opensource.org/licenses/mit-license.php.
4
6
8#include <wallet/spend.h>
9#include <wallet/test/util.h>
10#include <wallet/wallet.h>
11
12#include <boost/test/unit_test.hpp>
13
14namespace wallet {
15BOOST_FIXTURE_TEST_SUITE(group_outputs_tests, TestingSetup)
16
17static int nextLockTime = 0;
18
19static std::shared_ptr<CWallet> NewWallet(const node::NodeContext& m_node)
20{
21 std::unique_ptr<CWallet> wallet = std::make_unique<CWallet>(m_node.chain.get(), "", CreateMockableWalletDatabase());
22 LOCK(wallet->cs_wallet);
23 wallet->SetWalletFlag(WALLET_FLAG_DESCRIPTORS);
24 wallet->SetupDescriptorScriptPubKeyMans();
25 return wallet;
26}
27
28static void addCoin(CoinsResult& coins,
30 const CTxDestination& dest,
31 const CAmount& nValue,
32 bool is_from_me,
33 CFeeRate fee_rate = CFeeRate(0),
34 int depth = 6)
35{
37 tx.nLockTime = nextLockTime++; // so all transactions get different hashes
38 tx.vout.resize(1);
39 tx.vout[0].nValue = nValue;
40 tx.vout[0].scriptPubKey = GetScriptForDestination(dest);
41
42 const auto txid{tx.GetHash()};
43 LOCK(wallet.cs_wallet);
44 auto ret = wallet.mapWallet.emplace(std::piecewise_construct, std::forward_as_tuple(txid), std::forward_as_tuple(MakeTransactionRef(std::move(tx)), TxStateInactive{}));
45 assert(ret.second);
46 CWalletTx& wtx = (*ret.first).second;
47 const auto& txout = wtx.tx->vout.at(0);
49 {COutPoint(wtx.GetHash(), 0),
50 txout,
51 depth,
52 CalculateMaximumSignedInputSize(txout, &wallet, /*coin_control=*/nullptr),
53 /*solvable=*/ true,
54 /*safe=*/ true,
55 wtx.GetTxTime(),
56 is_from_me,
57 fee_rate});
58}
59
61{
63 rand,
64 /*change_output_size=*/ 0,
65 /*change_spend_size=*/ 0,
66 /*min_change_target=*/ CENT,
67 /*effective_feerate=*/ CFeeRate(0),
68 /*long_term_feerate=*/ CFeeRate(0),
69 /*discard_feerate=*/ CFeeRate(0),
70 /*tx_noinputs_size=*/ 0,
71 /*avoid_partial=*/ avoid_partial_spends,
72 };
73}
74
76{
77public:
78 std::shared_ptr<CWallet> wallet{nullptr};
81
82 void GroupVerify(const OutputType type,
83 const CoinEligibilityFilter& filter,
84 bool avoid_partial_spends,
85 bool positive_only,
86 int expected_size)
87 {
88 OutputGroupTypeMap groups = GroupOutputs(*wallet, coins_pool, makeSelectionParams(rand, avoid_partial_spends), {{filter}})[filter];
89 std::vector<OutputGroup>& groups_out = positive_only ? groups.groups_by_type[type].positive_group :
90 groups.groups_by_type[type].mixed_group;
91 BOOST_CHECK_EQUAL(groups_out.size(), expected_size);
92 }
93
94 void GroupAndVerify(const OutputType type,
95 const CoinEligibilityFilter& filter,
96 int expected_with_partial_spends_size,
97 int expected_without_partial_spends_size,
98 bool positive_only)
99 {
100 // First avoid partial spends
101 GroupVerify(type, filter, /*avoid_partial_spends=*/false, positive_only, expected_with_partial_spends_size);
102 // Second don't avoid partial spends
103 GroupVerify(type, filter, /*avoid_partial_spends=*/true, positive_only, expected_without_partial_spends_size);
104 }
105};
106
107BOOST_AUTO_TEST_CASE(outputs_grouping_tests)
108{
109 const auto& wallet = NewWallet(m_node);
110 GroupVerifier group_verifier;
111 group_verifier.wallet = wallet;
112
113 const CoinEligibilityFilter& BASIC_FILTER{1, 6, 0};
114
115 // #################################################################################
116 // 10 outputs from different txs going to the same script
117 // 1) if partial spends is enabled --> must not be grouped
118 // 2) if partial spends is not enabled --> must be grouped into a single OutputGroup
119 // #################################################################################
120
121 unsigned long GROUP_SIZE = 10;
122 const CTxDestination dest = *Assert(wallet->GetNewDestination(OutputType::BECH32, ""));
123 for (unsigned long i = 0; i < GROUP_SIZE; i++) {
124 addCoin(group_verifier.coins_pool, *wallet, dest, 10 * COIN, /*is_from_me=*/true);
125 }
126
127 group_verifier.GroupAndVerify(OutputType::BECH32,
128 BASIC_FILTER,
129 /*expected_with_partial_spends_size=*/ GROUP_SIZE,
130 /*expected_without_partial_spends_size=*/ 1,
131 /*positive_only=*/ true);
132
133 // ####################################################################################
134 // 3) 10 more UTXO are added with a different script --> must be grouped into a single
135 // group for avoid partial spends and 10 different output groups for partial spends
136 // ####################################################################################
137
138 const CTxDestination dest2 = *Assert(wallet->GetNewDestination(OutputType::BECH32, ""));
139 for (unsigned long i = 0; i < GROUP_SIZE; i++) {
140 addCoin(group_verifier.coins_pool, *wallet, dest2, 5 * COIN, /*is_from_me=*/true);
141 }
142
143 group_verifier.GroupAndVerify(OutputType::BECH32,
144 BASIC_FILTER,
145 /*expected_with_partial_spends_size=*/ GROUP_SIZE * 2,
146 /*expected_without_partial_spends_size=*/ 2,
147 /*positive_only=*/ true);
148
149 // ################################################################################
150 // 4) Now add a negative output --> which will be skipped if "positive_only" is set
151 // ################################################################################
152
153 const CTxDestination dest3 = *Assert(wallet->GetNewDestination(OutputType::BECH32, ""));
154 addCoin(group_verifier.coins_pool, *wallet, dest3, 1, true, CFeeRate(100));
155 BOOST_CHECK(group_verifier.coins_pool.coins[OutputType::BECH32].back().GetEffectiveValue() <= 0);
156
157 // First expect no changes with "positive_only" enabled
158 group_verifier.GroupAndVerify(OutputType::BECH32,
159 BASIC_FILTER,
160 /*expected_with_partial_spends_size=*/ GROUP_SIZE * 2,
161 /*expected_without_partial_spends_size=*/ 2,
162 /*positive_only=*/ true);
163
164 // Then expect changes with "positive_only" disabled
165 group_verifier.GroupAndVerify(OutputType::BECH32,
166 BASIC_FILTER,
167 /*expected_with_partial_spends_size=*/ GROUP_SIZE * 2 + 1,
168 /*expected_without_partial_spends_size=*/ 3,
169 /*positive_only=*/ false);
170
171
172 // ##############################################################################
173 // 5) Try to add a non-eligible UTXO (due not fulfilling the min depth target for
174 // "not mine" UTXOs) --> it must not be added to any group
175 // ##############################################################################
176
177 const CTxDestination dest4 = *Assert(wallet->GetNewDestination(OutputType::BECH32, ""));
178 addCoin(group_verifier.coins_pool, *wallet, dest4, 6 * COIN,
179 /*is_from_me=*/false, CFeeRate(0), /*depth=*/5);
180
181 // Expect no changes from this round and the previous one (point 4)
182 group_verifier.GroupAndVerify(OutputType::BECH32,
183 BASIC_FILTER,
184 /*expected_with_partial_spends_size=*/ GROUP_SIZE * 2 + 1,
185 /*expected_without_partial_spends_size=*/ 3,
186 /*positive_only=*/ false);
187
188
189 // ##############################################################################
190 // 6) Try to add a non-eligible UTXO (due not fulfilling the min depth target for
191 // "mine" UTXOs) --> it must not be added to any group
192 // ##############################################################################
193
194 const CTxDestination dest5 = *Assert(wallet->GetNewDestination(OutputType::BECH32, ""));
195 addCoin(group_verifier.coins_pool, *wallet, dest5, 6 * COIN,
196 /*is_from_me=*/true, CFeeRate(0), /*depth=*/0);
197
198 // Expect no changes from this round and the previous one (point 5)
199 group_verifier.GroupAndVerify(OutputType::BECH32,
200 BASIC_FILTER,
201 /*expected_with_partial_spends_size=*/ GROUP_SIZE * 2 + 1,
202 /*expected_without_partial_spends_size=*/ 3,
203 /*positive_only=*/ false);
204
205 // ###########################################################################################
206 // 7) Surpass the OUTPUT_GROUP_MAX_ENTRIES and verify that a second partial group gets created
207 // ###########################################################################################
208
209 const CTxDestination dest7 = *Assert(wallet->GetNewDestination(OutputType::BECH32, ""));
210 uint16_t NUM_SINGLE_ENTRIES = 101;
211 for (unsigned long i = 0; i < NUM_SINGLE_ENTRIES; i++) { // OUTPUT_GROUP_MAX_ENTRIES{100}
212 addCoin(group_verifier.coins_pool, *wallet, dest7, 9 * COIN, /*is_from_me=*/true);
213 }
214
215 // Exclude partial groups only adds one more group to the previous test case (point 6)
216 int PREVIOUS_ROUND_COUNT = GROUP_SIZE * 2 + 1;
217 group_verifier.GroupAndVerify(OutputType::BECH32,
218 BASIC_FILTER,
219 /*expected_with_partial_spends_size=*/ PREVIOUS_ROUND_COUNT + NUM_SINGLE_ENTRIES,
220 /*expected_without_partial_spends_size=*/ 4,
221 /*positive_only=*/ false);
222
223 // Include partial groups should add one more group inside the "avoid partial spends" count
224 const CoinEligibilityFilter& avoid_partial_groups_filter{1, 6, 0, 0, /*include_partial=*/ true};
225 group_verifier.GroupAndVerify(OutputType::BECH32,
226 avoid_partial_groups_filter,
227 /*expected_with_partial_spends_size=*/ PREVIOUS_ROUND_COUNT + NUM_SINGLE_ENTRIES,
228 /*expected_without_partial_spends_size=*/ 5,
229 /*positive_only=*/ false);
230}
231
233} // end namespace wallet
CScript GetScriptForDestination(const CTxDestination &dest)
Generate a Bitcoin scriptPubKey for the given CTxDestination.
std::variant< CNoDestination, PubKeyDestination, PKHash, ScriptHash, WitnessV0ScriptHash, WitnessV0KeyHash, WitnessV1Taproot, PayToAnchor, WitnessUnknown > CTxDestination
A txout script categorized into standard templates.
int64_t CAmount
Amount in satoshis (Can be negative).
Definition amount.h:12
static constexpr CAmount COIN
The amount of satoshis in one BTC.
Definition amount.h:15
int ret
#define Assert(val)
Identity function.
Definition check.h:113
Fee rate in satoshis per virtualbyte: CAmount / vB the feerate is represented internally as FeeFrac.
Definition feerate.h:32
An outpoint - a combination of a transaction hash and an index n into its vout.
Definition transaction.h:29
Fast randomness source.
Definition random.h:386
A CWallet maintains a set of transactions and balances, and provides the ability to create new transa...
Definition wallet.h:310
A transaction with a bunch of additional info that only the owner cares about.
const Txid & GetHash() const LIFETIMEBOUND
CTransactionRef tx
int64_t GetTxTime() const
void GroupAndVerify(const OutputType type, const CoinEligibilityFilter &filter, int expected_with_partial_spends_size, int expected_without_partial_spends_size, bool positive_only)
void GroupVerify(const OutputType type, const CoinEligibilityFilter &filter, bool avoid_partial_spends, bool positive_only, int expected_size)
std::shared_ptr< CWallet > wallet
BOOST_FIXTURE_TEST_SUITE(cuckoocache_tests, BasicTestingSetup)
Test Suite for CuckooCache.
BOOST_AUTO_TEST_SUITE_END()
Definition common.h:29
FilteredOutputGroups GroupOutputs(const CWallet &wallet, const CoinsResult &coins, const CoinSelectionParams &coin_sel_params, const std::vector< SelectionFilter > &filters, std::vector< OutputGroup > &ret_discarded_groups)
Definition spend.cpp:572
static std::unique_ptr< CWallet > NewWallet(const node::NodeContext &m_node, const std::string &wallet_name="")
std::unique_ptr< WalletDatabase > CreateMockableWalletDatabase(MockableData records)
Definition util.cpp:211
BOOST_AUTO_TEST_CASE(bnb_test)
CoinSelectionParams makeSelectionParams(FastRandomContext &rand, bool avoid_partial_spends)
@ WALLET_FLAG_DESCRIPTORS
Indicate that this wallet supports DescriptorScriptPubKeyMan.
Definition walletutil.h:53
static int nextLockTime
int CalculateMaximumSignedInputSize(const CTxOut &txout, const COutPoint outpoint, const SigningProvider *provider, bool can_grind_r, const CCoinControl *coin_control)
Definition spend.cpp:92
static void addCoin(CoinsResult &coins, CWallet &wallet, const CTxDestination &dest, const CAmount &nValue, bool is_from_me, CFeeRate fee_rate=CFeeRate(0), int depth=6)
#define BOOST_CHECK_EQUAL(v1, v2)
Definition object.cpp:17
#define BOOST_CHECK(expr)
Definition object.cpp:16
std::optional< OutputType > OutputTypeFromDestination(const CTxDestination &dest)
Get the OutputType for a CTxDestination.
OutputType
Definition outputtype.h:18
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.
Parameters for filtering which OutputGroups we may use in coin selection.
Parameters for one iteration of Coin Selection.
COutputs available for spending, stored by OutputType.
Definition spend.h:46
void Add(OutputType type, const COutput &out)
Definition spend.cpp:240
std::map< OutputType, std::vector< COutput > > coins
Definition spend.h:47
Stores several 'Groups' whose were mapped by output type.
std::map< OutputType, Groups > groups_by_type
#define LOCK(cs)
Definition sync.h:258
assert(!tx.IsCoinBase())