Bitcoin Core 28.99.0
P2P Digital Currency
group_outputs_tests.cpp
Go to the documentation of this file.
1// Copyright (c) 2022 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 wallet->LoadWallet();
23 LOCK(wallet->cs_wallet);
24 wallet->SetWalletFlag(WALLET_FLAG_DESCRIPTORS);
25 wallet->SetupDescriptorScriptPubKeyMans();
26 return wallet;
27}
28
29static void addCoin(CoinsResult& coins,
31 const CTxDestination& dest,
32 const CAmount& nValue,
33 bool is_from_me,
34 CFeeRate fee_rate = CFeeRate(0),
35 int depth = 6)
36{
38 tx.nLockTime = nextLockTime++; // so all transactions get different hashes
39 tx.vout.resize(1);
40 tx.vout[0].nValue = nValue;
41 tx.vout[0].scriptPubKey = GetScriptForDestination(dest);
42
43 const auto txid{tx.GetHash().ToUint256()};
44 LOCK(wallet.cs_wallet);
45 auto ret = wallet.mapWallet.emplace(std::piecewise_construct, std::forward_as_tuple(txid), std::forward_as_tuple(MakeTransactionRef(std::move(tx)), TxStateInactive{}));
46 assert(ret.second);
47 CWalletTx& wtx = (*ret.first).second;
48 const auto& txout = wtx.tx->vout.at(0);
50 {COutPoint(wtx.GetHash(), 0),
51 txout,
52 depth,
53 CalculateMaximumSignedInputSize(txout, &wallet, /*coin_control=*/nullptr),
54 /*spendable=*/ true,
55 /*solvable=*/ true,
56 /*safe=*/ true,
57 wtx.GetTxTime(),
58 is_from_me,
59 fee_rate});
60}
61
63{
65 rand,
66 /*change_output_size=*/ 0,
67 /*change_spend_size=*/ 0,
68 /*min_change_target=*/ CENT,
69 /*effective_feerate=*/ CFeeRate(0),
70 /*long_term_feerate=*/ CFeeRate(0),
71 /*discard_feerate=*/ CFeeRate(0),
72 /*tx_noinputs_size=*/ 0,
73 /*avoid_partial=*/ avoid_partial_spends,
74 };
75}
76
78{
79public:
80 std::shared_ptr<CWallet> wallet{nullptr};
83
84 void GroupVerify(const OutputType type,
85 const CoinEligibilityFilter& filter,
86 bool avoid_partial_spends,
87 bool positive_only,
88 int expected_size)
89 {
90 OutputGroupTypeMap groups = GroupOutputs(*wallet, coins_pool, makeSelectionParams(rand, avoid_partial_spends), {{filter}})[filter];
91 std::vector<OutputGroup>& groups_out = positive_only ? groups.groups_by_type[type].positive_group :
92 groups.groups_by_type[type].mixed_group;
93 BOOST_CHECK_EQUAL(groups_out.size(), expected_size);
94 }
95
96 void GroupAndVerify(const OutputType type,
97 const CoinEligibilityFilter& filter,
98 int expected_with_partial_spends_size,
99 int expected_without_partial_spends_size,
100 bool positive_only)
101 {
102 // First avoid partial spends
103 GroupVerify(type, filter, /*avoid_partial_spends=*/false, positive_only, expected_with_partial_spends_size);
104 // Second don't avoid partial spends
105 GroupVerify(type, filter, /*avoid_partial_spends=*/true, positive_only, expected_without_partial_spends_size);
106 }
107};
108
109BOOST_AUTO_TEST_CASE(outputs_grouping_tests)
110{
111 const auto& wallet = NewWallet(m_node);
112 GroupVerifier group_verifier;
113 group_verifier.wallet = wallet;
114
115 const CoinEligibilityFilter& BASIC_FILTER{1, 6, 0};
116
117 // #################################################################################
118 // 10 outputs from different txs going to the same script
119 // 1) if partial spends is enabled --> must not be grouped
120 // 2) if partial spends is not enabled --> must be grouped into a single OutputGroup
121 // #################################################################################
122
123 unsigned long GROUP_SIZE = 10;
124 const CTxDestination dest = *Assert(wallet->GetNewDestination(OutputType::BECH32, ""));
125 for (unsigned long i = 0; i < GROUP_SIZE; i++) {
126 addCoin(group_verifier.coins_pool, *wallet, dest, 10 * COIN, /*is_from_me=*/true);
127 }
128
129 group_verifier.GroupAndVerify(OutputType::BECH32,
130 BASIC_FILTER,
131 /*expected_with_partial_spends_size=*/ GROUP_SIZE,
132 /*expected_without_partial_spends_size=*/ 1,
133 /*positive_only=*/ true);
134
135 // ####################################################################################
136 // 3) 10 more UTXO are added with a different script --> must be grouped into a single
137 // group for avoid partial spends and 10 different output groups for partial spends
138 // ####################################################################################
139
140 const CTxDestination dest2 = *Assert(wallet->GetNewDestination(OutputType::BECH32, ""));
141 for (unsigned long i = 0; i < GROUP_SIZE; i++) {
142 addCoin(group_verifier.coins_pool, *wallet, dest2, 5 * COIN, /*is_from_me=*/true);
143 }
144
145 group_verifier.GroupAndVerify(OutputType::BECH32,
146 BASIC_FILTER,
147 /*expected_with_partial_spends_size=*/ GROUP_SIZE * 2,
148 /*expected_without_partial_spends_size=*/ 2,
149 /*positive_only=*/ true);
150
151 // ################################################################################
152 // 4) Now add a negative output --> which will be skipped if "positive_only" is set
153 // ################################################################################
154
155 const CTxDestination dest3 = *Assert(wallet->GetNewDestination(OutputType::BECH32, ""));
156 addCoin(group_verifier.coins_pool, *wallet, dest3, 1, true, CFeeRate(100));
157 BOOST_CHECK(group_verifier.coins_pool.coins[OutputType::BECH32].back().GetEffectiveValue() <= 0);
158
159 // First expect no changes with "positive_only" enabled
160 group_verifier.GroupAndVerify(OutputType::BECH32,
161 BASIC_FILTER,
162 /*expected_with_partial_spends_size=*/ GROUP_SIZE * 2,
163 /*expected_without_partial_spends_size=*/ 2,
164 /*positive_only=*/ true);
165
166 // Then expect changes with "positive_only" disabled
167 group_verifier.GroupAndVerify(OutputType::BECH32,
168 BASIC_FILTER,
169 /*expected_with_partial_spends_size=*/ GROUP_SIZE * 2 + 1,
170 /*expected_without_partial_spends_size=*/ 3,
171 /*positive_only=*/ false);
172
173
174 // ##############################################################################
175 // 5) Try to add a non-eligible UTXO (due not fulfilling the min depth target for
176 // "not mine" UTXOs) --> it must not be added to any group
177 // ##############################################################################
178
179 const CTxDestination dest4 = *Assert(wallet->GetNewDestination(OutputType::BECH32, ""));
180 addCoin(group_verifier.coins_pool, *wallet, dest4, 6 * COIN,
181 /*is_from_me=*/false, CFeeRate(0), /*depth=*/5);
182
183 // Expect no changes from this round and the previous one (point 4)
184 group_verifier.GroupAndVerify(OutputType::BECH32,
185 BASIC_FILTER,
186 /*expected_with_partial_spends_size=*/ GROUP_SIZE * 2 + 1,
187 /*expected_without_partial_spends_size=*/ 3,
188 /*positive_only=*/ false);
189
190
191 // ##############################################################################
192 // 6) Try to add a non-eligible UTXO (due not fulfilling the min depth target for
193 // "mine" UTXOs) --> it must not be added to any group
194 // ##############################################################################
195
196 const CTxDestination dest5 = *Assert(wallet->GetNewDestination(OutputType::BECH32, ""));
197 addCoin(group_verifier.coins_pool, *wallet, dest5, 6 * COIN,
198 /*is_from_me=*/true, CFeeRate(0), /*depth=*/0);
199
200 // Expect no changes from this round and the previous one (point 5)
201 group_verifier.GroupAndVerify(OutputType::BECH32,
202 BASIC_FILTER,
203 /*expected_with_partial_spends_size=*/ GROUP_SIZE * 2 + 1,
204 /*expected_without_partial_spends_size=*/ 3,
205 /*positive_only=*/ false);
206
207 // ###########################################################################################
208 // 7) Surpass the OUTPUT_GROUP_MAX_ENTRIES and verify that a second partial group gets created
209 // ###########################################################################################
210
211 const CTxDestination dest7 = *Assert(wallet->GetNewDestination(OutputType::BECH32, ""));
212 uint16_t NUM_SINGLE_ENTRIES = 101;
213 for (unsigned long i = 0; i < NUM_SINGLE_ENTRIES; i++) { // OUTPUT_GROUP_MAX_ENTRIES{100}
214 addCoin(group_verifier.coins_pool, *wallet, dest7, 9 * COIN, /*is_from_me=*/true);
215 }
216
217 // Exclude partial groups only adds one more group to the previous test case (point 6)
218 int PREVIOUS_ROUND_COUNT = GROUP_SIZE * 2 + 1;
219 group_verifier.GroupAndVerify(OutputType::BECH32,
220 BASIC_FILTER,
221 /*expected_with_partial_spends_size=*/ PREVIOUS_ROUND_COUNT + NUM_SINGLE_ENTRIES,
222 /*expected_without_partial_spends_size=*/ 4,
223 /*positive_only=*/ false);
224
225 // Include partial groups should add one more group inside the "avoid partial spends" count
226 const CoinEligibilityFilter& avoid_partial_groups_filter{1, 6, 0, 0, /*include_partial=*/ true};
227 group_verifier.GroupAndVerify(OutputType::BECH32,
228 avoid_partial_groups_filter,
229 /*expected_with_partial_spends_size=*/ PREVIOUS_ROUND_COUNT + NUM_SINGLE_ENTRIES,
230 /*expected_without_partial_spends_size=*/ 5,
231 /*positive_only=*/ false);
232}
233
235} // 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.
Definition: addresstype.h:140
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
node::NodeContext m_node
Definition: bitcoin-gui.cpp:42
#define Assert(val)
Identity function.
Definition: check.h:85
Fee rate in satoshis per kilovirtualbyte: CAmount / kvB.
Definition: feerate.h:33
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:377
const uint256 & ToUint256() const LIFETIMEBOUND
A CWallet maintains a set of transactions and balances, and provides the ability to create new transa...
Definition: wallet.h:300
A transaction with a bunch of additional info that only the owner cares about.
Definition: transaction.h:177
const Txid & GetHash() const LIFETIMEBOUND
Definition: transaction.h:351
CTransactionRef tx
Definition: transaction.h:258
int64_t GetTxTime() const
Definition: transaction.cpp:26
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: messages.h:20
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:533
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:186
BOOST_AUTO_TEST_CASE(bnb_search_test)
CoinSelectionParams makeSelectionParams(FastRandomContext &rand, bool avoid_partial_spends)
@ WALLET_FLAG_DESCRIPTORS
Indicate that this wallet supports DescriptorScriptPubKeyMan.
Definition: walletutil.h:74
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:90
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:18
#define BOOST_CHECK(expr)
Definition: object.cpp:17
std::optional< OutputType > OutputTypeFromDestination(const CTxDestination &dest)
Get the OutputType for a CTxDestination.
Definition: outputtype.cpp:110
OutputType
Definition: outputtype.h:17
static CTransactionRef MakeTransactionRef(Tx &&txIn)
Definition: transaction.h:424
static constexpr CAmount CENT
Definition: setup_common.h:46
A mutable version of CTransaction.
Definition: transaction.h:378
std::vector< CTxOut > vout
Definition: transaction.h:380
Txid GetHash() const
Compute the hash of this CMutableTransaction.
Definition: transaction.cpp:69
Testing setup that configures a complete environment.
Definition: setup_common.h:120
std::unique_ptr< interfaces::Chain > chain
Definition: context.h:76
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:40
void Add(OutputType type, const COutput &out)
Definition: spend.cpp:238
std::map< OutputType, std::vector< COutput > > coins
Definition: spend.h:41
Stores several 'Groups' whose were mapped by output type.
std::map< OutputType, Groups > groups_by_type
State of transaction not confirmed or conflicting with a known block and not in the mempool.
Definition: transaction.h:58
#define LOCK(cs)
Definition: sync.h:257
assert(!tx.IsCoinBase())