LCOV - code coverage report
Current view: top level - src/wallet/test - group_outputs_tests.cpp (source / functions) Hit Total Coverage
Test: fuzz_coverage.info Lines: 0 116 0.0 %
Date: 2023-10-05 12:38:51 Functions: 0 25 0.0 %
Branches: 0 0 -

           Branch data     Line data    Source code
       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                 :            : 
       5                 :            : #include <test/util/setup_common.h>
       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                 :          0 : BOOST_FIXTURE_TEST_SUITE(group_outputs_tests, TestingSetup)
      16                 :            : 
      17                 :          0 : static int nextLockTime = 0;
      18                 :          0 : 
      19                 :          0 : static std::shared_ptr<CWallet> NewWallet(const node::NodeContext& m_node)
      20                 :            : {
      21                 :          0 :     std::unique_ptr<CWallet> wallet = std::make_unique<CWallet>(m_node.chain.get(), "", CreateMockableWalletDatabase());
      22                 :          0 :     wallet->LoadWallet();
      23                 :          0 :     LOCK(wallet->cs_wallet);
      24                 :          0 :     wallet->SetWalletFlag(WALLET_FLAG_DESCRIPTORS);
      25                 :          0 :     wallet->SetupDescriptorScriptPubKeyMans();
      26                 :          0 :     return wallet;
      27                 :          0 : }
      28                 :            : 
      29                 :          0 : static void addCoin(CoinsResult& coins,
      30                 :            :                      CWallet& wallet,
      31                 :            :                      const CTxDestination& dest,
      32                 :            :                      const CAmount& nValue,
      33                 :            :                      bool is_from_me,
      34                 :            :                      CFeeRate fee_rate = CFeeRate(0),
      35                 :          0 :                      int depth = 6)
      36                 :            : {
      37                 :          0 :     CMutableTransaction tx;
      38                 :          0 :     tx.nLockTime = nextLockTime++;        // so all transactions get different hashes
      39                 :          0 :     tx.vout.resize(1);
      40                 :          0 :     tx.vout[0].nValue = nValue;
      41                 :          0 :     tx.vout[0].scriptPubKey = GetScriptForDestination(dest);
      42                 :            : 
      43                 :          0 :     const uint256& txid = tx.GetHash();
      44                 :          0 :     LOCK(wallet.cs_wallet);
      45                 :          0 :     auto ret = wallet.mapWallet.emplace(std::piecewise_construct, std::forward_as_tuple(txid), std::forward_as_tuple(MakeTransactionRef(std::move(tx)), TxStateInactive{}));
      46                 :          0 :     assert(ret.second);
      47                 :          0 :     CWalletTx& wtx = (*ret.first).second;
      48                 :          0 :     const auto& txout = wtx.tx->vout.at(0);
      49                 :          0 :     coins.Add(*Assert(OutputTypeFromDestination(dest)),
      50                 :          0 :               {COutPoint(wtx.GetHash(), 0),
      51                 :          0 :                    txout,
      52                 :          0 :                    depth,
      53                 :          0 :                    CalculateMaximumSignedInputSize(txout, &wallet, /*coin_control=*/nullptr),
      54                 :            :                    /*spendable=*/ true,
      55                 :            :                    /*solvable=*/ true,
      56                 :            :                    /*safe=*/ true,
      57                 :          0 :                    wtx.GetTxTime(),
      58                 :          0 :                    is_from_me,
      59                 :          0 :                    fee_rate});
      60                 :          0 : }
      61                 :            : 
      62                 :          0 :  CoinSelectionParams makeSelectionParams(FastRandomContext& rand, bool avoid_partial_spends)
      63                 :            : {
      64                 :          0 :     return CoinSelectionParams{
      65                 :          0 :             rand,
      66                 :            :             /*change_output_size=*/ 0,
      67                 :            :             /*change_spend_size=*/ 0,
      68                 :            :             /*min_change_target=*/ CENT,
      69                 :          0 :             /*effective_feerate=*/ CFeeRate(0),
      70                 :          0 :             /*long_term_feerate=*/ CFeeRate(0),
      71                 :          0 :             /*discard_feerate=*/ CFeeRate(0),
      72                 :            :             /*tx_noinputs_size=*/ 0,
      73                 :          0 :             /*avoid_partial=*/ avoid_partial_spends,
      74                 :          0 :     };
      75                 :            : }
      76                 :            : 
      77                 :          0 : class GroupVerifier
      78                 :            : {
      79                 :            : public:
      80                 :          0 :     std::shared_ptr<CWallet> wallet{nullptr};
      81                 :            :     CoinsResult coins_pool;
      82                 :            :     FastRandomContext rand;
      83                 :            : 
      84                 :          0 :     void GroupVerify(const OutputType type,
      85                 :            :                      const CoinEligibilityFilter& filter,
      86                 :            :                      bool avoid_partial_spends,
      87                 :            :                      bool positive_only,
      88                 :            :                      int expected_size)
      89                 :            :     {
      90                 :          0 :         OutputGroupTypeMap groups = GroupOutputs(*wallet, coins_pool, makeSelectionParams(rand, avoid_partial_spends), {{filter}})[filter];
      91                 :          0 :         std::vector<OutputGroup>& groups_out = positive_only ? groups.groups_by_type[type].positive_group :
      92                 :          0 :                                                groups.groups_by_type[type].mixed_group;
      93                 :          0 :         BOOST_CHECK_EQUAL(groups_out.size(), expected_size);
      94                 :          0 :     }
      95                 :            : 
      96                 :          0 :     void GroupAndVerify(const OutputType type,
      97                 :            :                         const CoinEligibilityFilter& filter,
      98                 :            :                         int expected_with_partial_spends_size,
      99                 :          0 :                         int expected_without_partial_spends_size,
     100                 :            :                         bool positive_only)
     101                 :            :     {
     102                 :            :         // First avoid partial spends
     103                 :          0 :         GroupVerify(type, filter, /*avoid_partial_spends=*/false, positive_only,  expected_with_partial_spends_size);
     104                 :            :         // Second don't avoid partial spends
     105                 :          0 :         GroupVerify(type, filter, /*avoid_partial_spends=*/true, positive_only, expected_without_partial_spends_size);
     106                 :          0 :     }
     107                 :            : };
     108                 :            : 
     109                 :          0 : BOOST_AUTO_TEST_CASE(outputs_grouping_tests)
     110                 :            : {
     111                 :          0 :     const auto& wallet = NewWallet(m_node);
     112                 :          0 :     GroupVerifier group_verifier;
     113                 :          0 :     group_verifier.wallet = wallet;
     114                 :            : 
     115                 :          0 :     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                 :          0 :     unsigned long GROUP_SIZE = 10;
     124                 :          0 :     const CTxDestination dest = *Assert(wallet->GetNewDestination(OutputType::BECH32, ""));
     125                 :          0 :     for (unsigned long i = 0; i < GROUP_SIZE; i++) {
     126                 :          0 :         addCoin(group_verifier.coins_pool, *wallet, dest, 10 * COIN, /*is_from_me=*/true);
     127                 :          0 :     }
     128                 :            : 
     129                 :          0 :     group_verifier.GroupAndVerify(OutputType::BECH32,
     130                 :          0 :                                   BASIC_FILTER,
     131                 :          0 :                                   /*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                 :          0 :     const CTxDestination dest2 = *Assert(wallet->GetNewDestination(OutputType::BECH32, ""));
     141                 :          0 :     for (unsigned long i = 0; i < GROUP_SIZE; i++) {
     142                 :          0 :         addCoin(group_verifier.coins_pool, *wallet, dest2, 5 * COIN, /*is_from_me=*/true);
     143                 :          0 :     }
     144                 :            : 
     145                 :          0 :     group_verifier.GroupAndVerify(OutputType::BECH32,
     146                 :          0 :             BASIC_FILTER,
     147                 :          0 :             /*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                 :          0 :     const CTxDestination dest3 = *Assert(wallet->GetNewDestination(OutputType::BECH32, ""));
     156                 :          0 :     addCoin(group_verifier.coins_pool, *wallet, dest3, 1, true, CFeeRate(100));
     157                 :          0 :     BOOST_CHECK(group_verifier.coins_pool.coins[OutputType::BECH32].back().GetEffectiveValue() <= 0);
     158                 :            : 
     159                 :            :     // First expect no changes with "positive_only" enabled
     160                 :          0 :     group_verifier.GroupAndVerify(OutputType::BECH32,
     161                 :          0 :             BASIC_FILTER,
     162                 :          0 :             /*expected_with_partial_spends_size=*/ GROUP_SIZE * 2,
     163                 :          0 :             /*expected_without_partial_spends_size=*/ 2,
     164                 :          0 :             /*positive_only=*/ true);
     165                 :          0 : 
     166                 :          0 :     // Then expect changes with "positive_only" disabled
     167                 :          0 :     group_verifier.GroupAndVerify(OutputType::BECH32,
     168                 :          0 :             BASIC_FILTER,
     169                 :          0 :             /*expected_with_partial_spends_size=*/ GROUP_SIZE * 2 + 1,
     170                 :          0 :             /*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                 :          0 :     const CTxDestination dest4 = *Assert(wallet->GetNewDestination(OutputType::BECH32, ""));
     180                 :          0 :     addCoin(group_verifier.coins_pool, *wallet, dest4, 6 * COIN,
     181                 :          0 :             /*is_from_me=*/false, CFeeRate(0), /*depth=*/5);
     182                 :            : 
     183                 :            :     // Expect no changes from this round and the previous one (point 4)
     184                 :          0 :     group_verifier.GroupAndVerify(OutputType::BECH32,
     185                 :          0 :             BASIC_FILTER,
     186                 :          0 :             /*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                 :          0 :     const CTxDestination dest5 = *Assert(wallet->GetNewDestination(OutputType::BECH32, ""));
     197                 :          0 :     addCoin(group_verifier.coins_pool, *wallet, dest5, 6 * COIN,
     198                 :          0 :             /*is_from_me=*/true, CFeeRate(0), /*depth=*/0);
     199                 :            : 
     200                 :            :     // Expect no changes from this round and the previous one (point 5)
     201                 :          0 :     group_verifier.GroupAndVerify(OutputType::BECH32,
     202                 :          0 :             BASIC_FILTER,
     203                 :          0 :             /*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                 :          0 :     const CTxDestination dest7 = *Assert(wallet->GetNewDestination(OutputType::BECH32, ""));
     212                 :          0 :     uint16_t NUM_SINGLE_ENTRIES = 101;
     213                 :          0 :     for (unsigned long i = 0; i < NUM_SINGLE_ENTRIES; i++) { // OUTPUT_GROUP_MAX_ENTRIES{100}
     214                 :          0 :         addCoin(group_verifier.coins_pool, *wallet, dest7, 9 * COIN, /*is_from_me=*/true);
     215                 :          0 :     }
     216                 :            : 
     217                 :            :     // Exclude partial groups only adds one more group to the previous test case (point 6)
     218                 :          0 :     int PREVIOUS_ROUND_COUNT = GROUP_SIZE * 2 + 1;
     219                 :          0 :     group_verifier.GroupAndVerify(OutputType::BECH32,
     220                 :          0 :             BASIC_FILTER,
     221                 :          0 :             /*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                 :          0 :     const CoinEligibilityFilter& avoid_partial_groups_filter{1, 6, 0, 0, /*include_partial=*/ true};
     227                 :          0 :     group_verifier.GroupAndVerify(OutputType::BECH32,
     228                 :          0 :             avoid_partial_groups_filter,
     229                 :          0 :             /*expected_with_partial_spends_size=*/ PREVIOUS_ROUND_COUNT + NUM_SINGLE_ENTRIES,
     230                 :            :             /*expected_without_partial_spends_size=*/ 5,
     231                 :            :             /*positive_only=*/ false);
     232                 :          0 : }
     233                 :            : 
     234                 :          0 : BOOST_AUTO_TEST_SUITE_END()
     235                 :            : } // end namespace wallet

Generated by: LCOV version 1.14