LCOV - code coverage report
Current view: top level - src/wallet/test - group_outputs_tests.cpp (source / functions) Coverage Total Hit
Test: test_bitcoin_coverage.info Lines: 100.0 % 75 75
Test Date: 2024-11-04 04:45:35 Functions: 100.0 % 7 7
Branches: 52.1 % 234 122

             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                 :             : BOOST_FIXTURE_TEST_SUITE(group_outputs_tests, TestingSetup)
      16                 :             : 
      17                 :             : static int nextLockTime = 0;
      18                 :             : 
      19                 :           1 : static std::shared_ptr<CWallet> NewWallet(const node::NodeContext& m_node)
      20                 :             : {
      21   [ +  -  +  - ]:           1 :     std::unique_ptr<CWallet> wallet = std::make_unique<CWallet>(m_node.chain.get(), "", CreateMockableWalletDatabase());
      22         [ +  - ]:           1 :     wallet->LoadWallet();
      23         [ +  - ]:           1 :     LOCK(wallet->cs_wallet);
      24         [ +  - ]:           1 :     wallet->SetWalletFlag(WALLET_FLAG_DESCRIPTORS);
      25         [ +  - ]:           1 :     wallet->SetupDescriptorScriptPubKeyMans();
      26   [ +  -  +  - ]:           1 :     return wallet;
      27         [ -  + ]:           1 : }
      28                 :             : 
      29                 :         124 : 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                 :             :                      int depth = 6)
      36                 :             : {
      37                 :         124 :     CMutableTransaction tx;
      38                 :         124 :     tx.nLockTime = nextLockTime++;        // so all transactions get different hashes
      39         [ +  - ]:         124 :     tx.vout.resize(1);
      40         [ +  - ]:         124 :     tx.vout[0].nValue = nValue;
      41         [ +  - ]:         124 :     tx.vout[0].scriptPubKey = GetScriptForDestination(dest);
      42                 :             : 
      43         [ +  - ]:         124 :     const auto txid{tx.GetHash().ToUint256()};
      44         [ +  - ]:         124 :     LOCK(wallet.cs_wallet);
      45   [ +  -  +  -  :         248 :     auto ret = wallet.mapWallet.emplace(std::piecewise_construct, std::forward_as_tuple(txid), std::forward_as_tuple(MakeTransactionRef(std::move(tx)), TxStateInactive{}));
                   -  + ]
      46         [ -  + ]:         124 :     assert(ret.second);
      47         [ +  - ]:         124 :     CWalletTx& wtx = (*ret.first).second;
      48         [ +  - ]:         124 :     const auto& txout = wtx.tx->vout.at(0);
      49   [ +  -  +  -  :         248 :     coins.Add(*Assert(OutputTypeFromDestination(dest)),
          +  -  +  -  +  
             -  +  -  +  
                      - ]
      50         [ +  - ]:         124 :               {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                 :         248 : }
      61                 :             : 
      62                 :          16 :  CoinSelectionParams makeSelectionParams(FastRandomContext& rand, bool avoid_partial_spends)
      63                 :             : {
      64                 :          16 :     return CoinSelectionParams{
      65                 :             :             rand,
      66                 :             :             /*change_output_size=*/ 0,
      67                 :             :             /*change_spend_size=*/ 0,
      68                 :             :             /*min_change_target=*/ CENT,
      69                 :          16 :             /*effective_feerate=*/ CFeeRate(0),
      70                 :          16 :             /*long_term_feerate=*/ CFeeRate(0),
      71                 :          16 :             /*discard_feerate=*/ CFeeRate(0),
      72                 :             :             /*tx_noinputs_size=*/ 0,
      73                 :             :             /*avoid_partial=*/ avoid_partial_spends,
      74                 :          16 :     };
      75                 :             : }
      76                 :             : 
      77                 :             : class GroupVerifier
      78                 :             : {
      79                 :             : public:
      80                 :             :     std::shared_ptr<CWallet> wallet{nullptr};
      81                 :             :     CoinsResult coins_pool;
      82                 :             :     FastRandomContext rand;
      83                 :             : 
      84                 :          16 :     void GroupVerify(const OutputType type,
      85                 :             :                      const CoinEligibilityFilter& filter,
      86                 :             :                      bool avoid_partial_spends,
      87                 :             :                      bool positive_only,
      88                 :             :                      int expected_size)
      89                 :             :     {
      90   [ +  -  +  -  :          32 :         OutputGroupTypeMap groups = GroupOutputs(*wallet, coins_pool, makeSelectionParams(rand, avoid_partial_spends), {{filter}})[filter];
             +  -  +  + ]
      91         [ +  - ]:           6 :         std::vector<OutputGroup>& groups_out = positive_only ? groups.groups_by_type[type].positive_group :
      92   [ +  +  +  - ]:          22 :                                                groups.groups_by_type[type].mixed_group;
      93   [ +  -  +  - ]:          16 :         BOOST_CHECK_EQUAL(groups_out.size(), expected_size);
      94                 :          16 :     }
      95                 :             : 
      96                 :           8 :     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                 :           8 :         GroupVerify(type, filter, /*avoid_partial_spends=*/false, positive_only,  expected_with_partial_spends_size);
     104                 :             :         // Second don't avoid partial spends
     105                 :           8 :         GroupVerify(type, filter, /*avoid_partial_spends=*/true, positive_only, expected_without_partial_spends_size);
     106                 :           8 :     }
     107                 :             : };
     108                 :             : 
     109   [ +  -  +  -  :           7 : BOOST_AUTO_TEST_CASE(outputs_grouping_tests)
          +  -  +  -  +  
          -  +  -  +  -  
          +  -  +  -  +  
          -  +  -  +  -  
          +  -  +  -  +  
          -  +  -  +  -  
          +  -  +  -  +  
          -  +  -  +  -  
          +  -  +  -  +  
          -  +  -  +  -  
          +  -  +  -  +  
                      - ]
     110                 :             : {
     111                 :           1 :     const auto& wallet = NewWallet(m_node);
     112                 :           1 :     GroupVerifier group_verifier;
     113                 :           1 :     group_verifier.wallet = wallet;
     114                 :             : 
     115         [ +  - ]:           1 :     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                 :           1 :     unsigned long GROUP_SIZE = 10;
     124   [ +  -  +  -  :           2 :     const CTxDestination dest = *Assert(wallet->GetNewDestination(OutputType::BECH32, ""));
             +  -  +  - ]
     125         [ +  + ]:          11 :     for (unsigned long i = 0; i < GROUP_SIZE; i++) {
     126         [ +  - ]:          10 :         addCoin(group_verifier.coins_pool, *wallet, dest, 10 * COIN, /*is_from_me=*/true);
     127                 :             :     }
     128                 :             : 
     129         [ +  - ]:           1 :     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   [ +  -  +  -  :           2 :     const CTxDestination dest2 = *Assert(wallet->GetNewDestination(OutputType::BECH32, ""));
             +  -  +  - ]
     141         [ +  + ]:          11 :     for (unsigned long i = 0; i < GROUP_SIZE; i++) {
     142         [ +  - ]:          10 :         addCoin(group_verifier.coins_pool, *wallet, dest2, 5 * COIN, /*is_from_me=*/true);
     143                 :             :     }
     144                 :             : 
     145         [ +  - ]:           1 :     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   [ +  -  +  -  :           2 :     const CTxDestination dest3 = *Assert(wallet->GetNewDestination(OutputType::BECH32, ""));
             +  -  +  - ]
     156         [ +  - ]:           1 :     addCoin(group_verifier.coins_pool, *wallet, dest3, 1, true, CFeeRate(100));
     157   [ +  -  +  -  :           2 :     BOOST_CHECK(group_verifier.coins_pool.coins[OutputType::BECH32].back().GetEffectiveValue() <= 0);
             +  -  +  - ]
     158                 :             : 
     159                 :             :     // First expect no changes with "positive_only" enabled
     160         [ +  - ]:           1 :     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         [ +  - ]:           1 :     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   [ +  -  +  -  :           2 :     const CTxDestination dest4 = *Assert(wallet->GetNewDestination(OutputType::BECH32, ""));
             +  -  +  - ]
     180         [ +  - ]:           1 :     addCoin(group_verifier.coins_pool, *wallet, dest4, 6 * COIN,
     181         [ +  - ]:           1 :             /*is_from_me=*/false, CFeeRate(0), /*depth=*/5);
     182                 :             : 
     183                 :             :     // Expect no changes from this round and the previous one (point 4)
     184         [ +  - ]:           1 :     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   [ +  -  +  -  :           2 :     const CTxDestination dest5 = *Assert(wallet->GetNewDestination(OutputType::BECH32, ""));
             +  -  +  - ]
     197         [ +  - ]:           1 :     addCoin(group_verifier.coins_pool, *wallet, dest5, 6 * COIN,
     198         [ +  - ]:           1 :             /*is_from_me=*/true, CFeeRate(0), /*depth=*/0);
     199                 :             : 
     200                 :             :     // Expect no changes from this round and the previous one (point 5)
     201         [ +  - ]:           1 :     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   [ +  -  +  -  :           2 :     const CTxDestination dest7 = *Assert(wallet->GetNewDestination(OutputType::BECH32, ""));
             +  -  +  - ]
     212                 :           1 :     uint16_t NUM_SINGLE_ENTRIES = 101;
     213         [ +  + ]:         102 :     for (unsigned long i = 0; i < NUM_SINGLE_ENTRIES; i++) { // OUTPUT_GROUP_MAX_ENTRIES{100}
     214         [ +  - ]:         101 :         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                 :           1 :     int PREVIOUS_ROUND_COUNT = GROUP_SIZE * 2 + 1;
     219         [ +  - ]:           1 :     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         [ +  - ]:           1 :     const CoinEligibilityFilter& avoid_partial_groups_filter{1, 6, 0, 0, /*include_partial=*/ true};
     227         [ +  - ]:           1 :     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         [ +  - ]:           2 : }
     233                 :             : 
     234                 :             : BOOST_AUTO_TEST_SUITE_END()
     235                 :             : } // end namespace wallet
        

Generated by: LCOV version 2.0-1