Bitcoin Core  31.0.0
P2P Digital Currency
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 
7 #include <wallet/coinselection.h>
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 
14 namespace wallet {
15 BOOST_FIXTURE_TEST_SUITE(group_outputs_tests, TestingSetup)
16 
17 static int nextLockTime = 0;
18 
19 static 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 
28 static void addCoin(CoinsResult& coins,
29  CWallet& wallet,
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);
48  coins.Add(*Assert(OutputTypeFromDestination(dest)),
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 
60  CoinSelectionParams makeSelectionParams(FastRandomContext& rand, bool avoid_partial_spends)
61 {
62  return CoinSelectionParams{
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 {
77 public:
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 
107 BOOST_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
std::unique_ptr< interfaces::Chain > chain
Definition: context.h:76
int ret
std::map< OutputType, Groups > groups_by_type
Stores several &#39;Groups&#39; whose were mapped by output type.
assert(!tx.IsCoinBase())
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)
node::NodeContext m_node
Definition: bitcoin-gui.cpp:43
static std::unique_ptr< CWallet > NewWallet(const node::NodeContext &m_node, const std::string &wallet_name="")
State of transaction not confirmed or conflicting with a known block and not in the mempool...
Definition: transaction.h:59
Definition: common.h:29
static int nextLockTime
OutputType
Definition: outputtype.h:18
int64_t GetTxTime() const
Definition: transaction.cpp:32
std::shared_ptr< CWallet > wallet
std::map< OutputType, std::vector< COutput > > coins
Definition: spend.h:47
int64_t CAmount
Amount in satoshis (Can be negative)
Definition: amount.h:12
COutputs available for spending, stored by OutputType.
Definition: spend.h:46
A transaction with a bunch of additional info that only the owner cares about.
Definition: transaction.h:194
CoinSelectionParams makeSelectionParams(FastRandomContext &rand, bool avoid_partial_spends)
BOOST_FIXTURE_TEST_SUITE(cuckoocache_tests, BasicTestingSetup)
Test Suite for CuckooCache.
void GroupVerify(const OutputType type, const CoinEligibilityFilter &filter, bool avoid_partial_spends, bool positive_only, int expected_size)
#define LOCK(cs)
Definition: sync.h:258
int CalculateMaximumSignedInputSize(const CTxOut &txout, const COutPoint outpoint, const SigningProvider *provider, bool can_grind_r, const CCoinControl *coin_control)
Definition: spend.cpp:92
Fast randomness source.
Definition: random.h:385
BOOST_AUTO_TEST_SUITE_END()
Indicate that this wallet supports DescriptorScriptPubKeyMan.
Definition: walletutil.h:53
A CWallet maintains a set of transactions and balances, and provides the ability to create new transa...
Definition: wallet.h:309
Txid GetHash() const
Compute the hash of this CMutableTransaction.
Definition: transaction.cpp:69
An outpoint - a combination of a transaction hash and an index n into its vout.
Definition: transaction.h:28
std::vector< CTxOut > vout
Definition: transaction.h:360
Parameters for one iteration of Coin Selection.
CScript GetScriptForDestination(const CTxDestination &dest)
Generate a Bitcoin scriptPubKey for the given CTxDestination.
static CTransactionRef MakeTransactionRef(Tx &&txIn)
Definition: transaction.h:404
Definition: messages.h:21
Parameters for filtering which OutputGroups we may use in coin selection.
const Txid & GetHash() const LIFETIMEBOUND
Definition: transaction.h:367
BOOST_AUTO_TEST_CASE(bnb_test)
std::unique_ptr< WalletDatabase > CreateMockableWalletDatabase(MockableData records)
Definition: util.cpp:211
#define BOOST_CHECK_EQUAL(v1, v2)
Definition: object.cpp:17
std::variant< CNoDestination, PubKeyDestination, PKHash, ScriptHash, WitnessV0ScriptHash, WitnessV0KeyHash, WitnessV1Taproot, PayToAnchor, WitnessUnknown > CTxDestination
A txout script categorized into standard templates.
Definition: addresstype.h:143
Fee rate in satoshis per virtualbyte: CAmount / vB the feerate is represented internally as FeeFrac...
Definition: feerate.h:31
A mutable version of CTransaction.
Definition: transaction.h:357
static constexpr CAmount CENT
Definition: setup_common.h:47
void Add(OutputType type, const COutput &out)
Definition: spend.cpp:240
std::optional< OutputType > OutputTypeFromDestination(const CTxDestination &dest)
Get the OutputType for a CTxDestination.
Definition: outputtype.cpp:80
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
CTransactionRef tx
Definition: transaction.h:269
Testing setup that configures a complete environment.
Definition: setup_common.h:121
void GroupAndVerify(const OutputType type, const CoinEligibilityFilter &filter, int expected_with_partial_spends_size, int expected_without_partial_spends_size, bool positive_only)
#define Assert(val)
Identity function.
Definition: check.h:113
#define BOOST_CHECK(expr)
Definition: object.cpp:16
static constexpr CAmount COIN
The amount of satoshis in one BTC.
Definition: amount.h:15