LCOV - code coverage report
Current view: top level - src/wallet/test - coinjoin_tests.cpp (source / functions) Hit Total Coverage
Test: total_coverage.info Lines: 216 218 99.1 %
Date: 2026-06-25 07:23:43 Functions: 66 66 100.0 %

          Line data    Source code
       1             : // Copyright (c) 2020-2025 The Dash Core developers
       2             : // Distributed under the MIT software license, see the accompanying
       3             : // file COPYING or http://www.opensource.org/licenses/mit-license.php.
       4             : 
       5             : #include <test/util/setup_common.h>
       6             : 
       7             : #include <coinjoin/client.h>
       8             : #include <coinjoin/coinjoin.h>
       9             : #include <coinjoin/walletman.h>
      10             : #include <coinjoin/options.h>
      11             : #include <coinjoin/util.h>
      12             : #include <consensus/amount.h>
      13             : #include <node/context.h>
      14             : #include <util/translation.h>
      15             : #include <policy/settings.h>
      16             : #include <validation.h>
      17             : #include <wallet/context.h>
      18             : #include <wallet/spend.h>
      19             : #include <wallet/wallet.h>
      20             : 
      21             : #include <boost/test/unit_test.hpp>
      22             : 
      23             : namespace wallet {
      24         146 : BOOST_FIXTURE_TEST_SUITE(coinjoin_tests, BasicTestingSetup)
      25             : 
      26         149 : BOOST_AUTO_TEST_CASE(coinjoin_options_tests)
      27             : {
      28           1 :     BOOST_CHECK_EQUAL(CCoinJoinClientOptions::GetSessions(), DEFAULT_COINJOIN_SESSIONS);
      29           1 :     BOOST_CHECK_EQUAL(CCoinJoinClientOptions::GetRounds(), DEFAULT_COINJOIN_ROUNDS);
      30           1 :     BOOST_CHECK_EQUAL(CCoinJoinClientOptions::GetRandomRounds(), COINJOIN_RANDOM_ROUNDS);
      31           1 :     BOOST_CHECK_EQUAL(CCoinJoinClientOptions::GetAmount(), DEFAULT_COINJOIN_AMOUNT);
      32           1 :     BOOST_CHECK_EQUAL(CCoinJoinClientOptions::GetDenomsGoal(), DEFAULT_COINJOIN_DENOMS_GOAL);
      33           1 :     BOOST_CHECK_EQUAL(CCoinJoinClientOptions::GetDenomsHardCap(), DEFAULT_COINJOIN_DENOMS_HARDCAP);
      34             : 
      35           1 :     BOOST_CHECK_EQUAL(CCoinJoinClientOptions::IsEnabled(), false);
      36           1 :     BOOST_CHECK_EQUAL(CCoinJoinClientOptions::IsMultiSessionEnabled(), DEFAULT_COINJOIN_MULTISESSION);
      37             : 
      38           1 :     CCoinJoinClientOptions::SetEnabled(true);
      39           1 :     BOOST_CHECK_EQUAL(CCoinJoinClientOptions::IsEnabled(), true);
      40           1 :     CCoinJoinClientOptions::SetEnabled(false);
      41           1 :     BOOST_CHECK_EQUAL(CCoinJoinClientOptions::IsEnabled(), false);
      42             : 
      43           1 :     CCoinJoinClientOptions::SetMultiSessionEnabled(!DEFAULT_COINJOIN_MULTISESSION);
      44           1 :     BOOST_CHECK_EQUAL(CCoinJoinClientOptions::IsMultiSessionEnabled(), !DEFAULT_COINJOIN_MULTISESSION);
      45           1 :     CCoinJoinClientOptions::SetMultiSessionEnabled(DEFAULT_COINJOIN_MULTISESSION);
      46           1 :     BOOST_CHECK_EQUAL(CCoinJoinClientOptions::IsMultiSessionEnabled(), DEFAULT_COINJOIN_MULTISESSION);
      47             : 
      48           1 :     CCoinJoinClientOptions::SetRounds(DEFAULT_COINJOIN_ROUNDS + 10);
      49           1 :     BOOST_CHECK_EQUAL(CCoinJoinClientOptions::GetRounds(), DEFAULT_COINJOIN_ROUNDS + 10);
      50           1 :     CCoinJoinClientOptions::SetAmount(DEFAULT_COINJOIN_AMOUNT + 50);
      51           1 :     BOOST_CHECK_EQUAL(CCoinJoinClientOptions::GetAmount(), DEFAULT_COINJOIN_AMOUNT + 50);
      52           1 : }
      53             : 
      54         149 : BOOST_AUTO_TEST_CASE(coinjoin_collateral_tests)
      55             : {
      56             :     // Good collateral values
      57             :     static_assert(CoinJoin::IsCollateralAmount(0.00010000 * COIN));
      58             :     static_assert(CoinJoin::IsCollateralAmount(0.00012345 * COIN));
      59             :     static_assert(CoinJoin::IsCollateralAmount(0.00032123 * COIN));
      60             :     static_assert(CoinJoin::IsCollateralAmount(0.00019000 * COIN));
      61             : 
      62             :     // Bad collateral values
      63             :     static_assert(!CoinJoin::IsCollateralAmount(0.00009999 * COIN));
      64             :     static_assert(!CoinJoin::IsCollateralAmount(0.00040001 * COIN));
      65             :     static_assert(!CoinJoin::IsCollateralAmount(0.00100000 * COIN));
      66             :     static_assert(!CoinJoin::IsCollateralAmount(0.00100001 * COIN));
      67           1 : }
      68             : 
      69         149 : BOOST_AUTO_TEST_CASE(coinjoin_pending_dsa_request_tests)
      70             : {
      71           1 :     CPendingDsaRequest dsa_request;
      72           1 :     BOOST_CHECK(dsa_request.GetProTxHash() == uint256());
      73           1 :     BOOST_CHECK(dsa_request.GetDSA() == CCoinJoinAccept());
      74           1 :     BOOST_CHECK_EQUAL(dsa_request.IsExpired(), true);
      75           1 :     CPendingDsaRequest dsa_request_2;
      76           1 :     BOOST_CHECK(dsa_request == dsa_request_2);
      77           1 :     CCoinJoinAccept cja;
      78           1 :     cja.nDenom = 4;
      79           1 :     uint256 proTxHash{uint256::ONE};
      80           1 :     CPendingDsaRequest custom_request(proTxHash, cja);
      81           1 :     BOOST_CHECK(custom_request.GetProTxHash() == proTxHash);
      82           1 :     BOOST_CHECK(custom_request.GetDSA() == cja);
      83           1 :     BOOST_CHECK_EQUAL(custom_request.IsExpired(), false);
      84           1 :     SetMockTime(GetTime() + 15);
      85           1 :     BOOST_CHECK_EQUAL(custom_request.IsExpired(), false);
      86           1 :     SetMockTime(GetTime() + 1);
      87           1 :     BOOST_CHECK_EQUAL(custom_request.IsExpired(), true);
      88             : 
      89           1 :     BOOST_CHECK(dsa_request != custom_request);
      90           1 :     BOOST_CHECK(!(dsa_request == custom_request));
      91           1 :     BOOST_CHECK(!dsa_request);
      92           1 :     BOOST_CHECK(custom_request);
      93           1 : }
      94             : 
      95         149 : BOOST_AUTO_TEST_CASE(coinjoin_dstxin_tests)
      96             : {
      97           1 :     CTxDSIn txin;
      98           1 :     BOOST_CHECK(txin.prevPubKey == CScript());
      99           1 :     BOOST_CHECK_EQUAL(txin.fHasSig, false);
     100           1 :     BOOST_CHECK_EQUAL(txin.nRounds, -10);
     101           1 :     CTxDSIn custom_txin(txin, CScript(4), -9);
     102           1 :     BOOST_CHECK(custom_txin.prevPubKey == CScript(4));
     103           1 :     BOOST_CHECK_EQUAL(custom_txin.fHasSig, false);
     104           1 :     BOOST_CHECK_EQUAL(custom_txin.nRounds, -9);
     105           1 : }
     106             : 
     107         149 : BOOST_AUTO_TEST_CASE(coinjoin_status_update_tests)
     108             : {
     109           1 :     CCoinJoinStatusUpdate cjsu;
     110           1 :     BOOST_CHECK_EQUAL(cjsu.nSessionID, 0);
     111           1 :     BOOST_CHECK_EQUAL(cjsu.nState, POOL_STATE_IDLE);
     112           1 :     BOOST_CHECK_EQUAL(cjsu.nEntriesCount, 0);
     113           1 :     BOOST_CHECK_EQUAL(cjsu.nStatusUpdate, STATUS_ACCEPTED);
     114           1 :     BOOST_CHECK_EQUAL(cjsu.nMessageID, MSG_NOERR);
     115           1 :     CCoinJoinStatusUpdate custom_cjsu(1, POOL_STATE_QUEUE, 1, STATUS_REJECTED, ERR_QUEUE_FULL);
     116           1 :     BOOST_CHECK_EQUAL(custom_cjsu.nSessionID, 1);
     117           1 :     BOOST_CHECK_EQUAL(custom_cjsu.nState, POOL_STATE_QUEUE);
     118           1 :     BOOST_CHECK_EQUAL(custom_cjsu.nEntriesCount, 1);
     119           1 :     BOOST_CHECK_EQUAL(custom_cjsu.nStatusUpdate, STATUS_REJECTED);
     120           1 :     BOOST_CHECK_EQUAL(custom_cjsu.nMessageID, ERR_QUEUE_FULL);
     121           1 : }
     122             : 
     123         149 : BOOST_AUTO_TEST_CASE(coinjoin_accept_tests)
     124             : {
     125           1 :     CCoinJoinAccept cja;
     126           1 :     BOOST_CHECK_EQUAL(cja.nDenom, 0);
     127           1 :     BOOST_CHECK_EQUAL(cja.txCollateral.GetHash(), CMutableTransaction().GetHash());
     128             :     // CMutableTransaction custom_cmt()
     129           1 : }
     130             : 
     131             : class CTransactionBuilderTestSetup : public TestChain100Setup
     132             : {
     133             : public:
     134           2 :     CTransactionBuilderTestSetup() :
     135           2 :         wallet{std::make_unique<CWallet>(m_node.chain.get(), m_node.coinjoin_loader.get(), "", m_args, CreateMockWalletDatabase())}
     136             :     {
     137           2 :         context.args = &m_args;
     138           2 :         context.chain = m_node.chain.get();
     139           2 :         context.coinjoin_loader = m_node.coinjoin_loader.get();
     140           2 :         CreateAndProcessBlock({}, GetScriptForRawPubKey(coinbaseKey.GetPubKey()));
     141           2 :         wallet->SetupLegacyScriptPubKeyMan();
     142           2 :         wallet->LoadWallet();
     143           2 :         AddWallet(context, wallet);
     144             :         {
     145           2 :             LOCK2(wallet->cs_wallet, ::cs_main);
     146           2 :             wallet->GetLegacyScriptPubKeyMan()->AddKeyPubKey(coinbaseKey, coinbaseKey.GetPubKey());
     147           2 :             wallet->SetLastBlockProcessed(m_node.chainman->ActiveChain().Height(), m_node.chainman->ActiveChain().Tip()->GetBlockHash());
     148           2 :         }
     149           2 :         WalletRescanReserver reserver(*wallet);
     150           2 :         reserver.reserve();
     151           2 :         CWallet::ScanResult result = wallet->ScanForWalletTransactions(/*start_block=*/wallet->chain().getBlockHash(0),
     152           2 :                                                                        /*start_height=*/0, /*max_height=*/{}, reserver,
     153             :                                                                        /*fUpdate=*/true, /*save_progress=*/false);
     154           2 :         BOOST_CHECK_EQUAL(result.status, CWallet::ScanResult::SUCCESS);
     155           2 :     }
     156             : 
     157           2 :     ~CTransactionBuilderTestSetup()
     158             :     {
     159           2 :         RemoveWallet(context, wallet, /*load_on_start=*/std::nullopt);
     160           2 :     }
     161             : 
     162             :     WalletContext context;
     163             :     const std::shared_ptr<CWallet> wallet;
     164             : 
     165           8 :     CWalletTx& AddTxToChain(uint256 nTxHash)
     166             :     {
     167           8 :         decltype(wallet->mapWallet)::iterator it;
     168           8 :         CMutableTransaction blocktx;
     169             :         {
     170           8 :             LOCK(wallet->cs_wallet);
     171           8 :             it = wallet->mapWallet.find(nTxHash);
     172           8 :             BOOST_REQUIRE(it != wallet->mapWallet.end());
     173           8 :             blocktx = CMutableTransaction(*it->second.tx);
     174           8 :         }
     175           8 :         CreateAndProcessBlock({blocktx}, GetScriptForRawPubKey(coinbaseKey.GetPubKey()));
     176           8 :         LOCK2(wallet->cs_wallet, ::cs_main);
     177           8 :         wallet->SetLastBlockProcessed(m_node.chainman->ActiveChain().Height(), m_node.chainman->ActiveChain().Tip()->GetBlockHash());
     178           8 :         it->second.m_state = TxStateConfirmed{m_node.chainman->ActiveChain().Tip()->GetBlockHash(), m_node.chainman->ActiveChain().Height(), /*index=*/1};
     179           8 :         return it->second;
     180           8 :     }
     181           2 :     CompactTallyItem GetTallyItem(const std::vector<CAmount>& vecAmounts)
     182             :     {
     183           2 :         CompactTallyItem tallyItem;
     184           2 :         ReserveDestination reserveDest(wallet.get());
     185           2 :         int nChangePosRet{RANDOM_CHANGE_POSITION};
     186           2 :         CCoinControl coinControl;
     187           2 :         coinControl.m_feerate = CFeeRate(1000);
     188             :         {
     189           2 :             LOCK(wallet->cs_wallet);
     190           2 :             auto dest_opt = reserveDest.GetReservedDestination(false);
     191           2 :             BOOST_REQUIRE(dest_opt);
     192           2 :             tallyItem.txdest = *dest_opt;
     193           2 :         }
     194           8 :         for (CAmount nAmount : vecAmounts) {
     195           6 :             CTransactionRef tx;
     196             :             {
     197           6 :                 auto res = CreateTransaction(*wallet, {{GetScriptForDestination(tallyItem.txdest), nAmount, false}}, nChangePosRet, coinControl);
     198           6 :                 BOOST_REQUIRE(res);
     199           6 :                 tx = res->tx;
     200           6 :                 nChangePosRet = res->change_pos;
     201           6 :             }
     202             :             {
     203           6 :                 LOCK2(wallet->cs_wallet, ::cs_main);
     204           6 :                 wallet->CommitTransaction(tx, {}, {});
     205           6 :             }
     206           6 :             AddTxToChain(tx->GetHash());
     207          18 :             for (uint32_t n = 0; n < tx->vout.size(); ++n) {
     208          12 :                 if (nChangePosRet != RANDOM_CHANGE_POSITION && int(n) == nChangePosRet) {
     209             :                     // Skip the change output to only return the requested coins
     210           6 :                     continue;
     211             :                 }
     212           6 :                 tallyItem.outpoints.emplace_back(COutPoint{tx->GetHash(), n});
     213           6 :                 tallyItem.nAmount += tx->vout[n].nValue;
     214           6 :             }
     215           6 :         }
     216           2 :         BOOST_REQUIRE_EQUAL(tallyItem.outpoints.size(), vecAmounts.size());
     217           2 :         reserveDest.KeepDestination();
     218           2 :         return tallyItem;
     219           2 :     }
     220             : };
     221             : 
     222         148 : BOOST_FIXTURE_TEST_CASE(coinjoin_manager_start_stop_tests, CTransactionBuilderTestSetup)
     223             : {
     224           1 :     auto& cj_man = *Assert(m_node.cj_walletman->getClient(""));
     225           1 :     BOOST_CHECK_EQUAL(cj_man.IsMixing(), false);
     226           1 :     BOOST_CHECK_EQUAL(cj_man.StartMixing(), true);
     227           1 :     BOOST_CHECK_EQUAL(cj_man.IsMixing(), true);
     228           1 :     BOOST_CHECK_EQUAL(cj_man.StartMixing(), false);
     229           1 :     cj_man.StopMixing();
     230           1 :     BOOST_CHECK_EQUAL(cj_man.IsMixing(), false);
     231           1 : }
     232             : 
     233         148 : BOOST_FIXTURE_TEST_CASE(CTransactionBuilderTest, CTransactionBuilderTestSetup)
     234             : {
     235             :     // NOTE: Mock wallet version is FEATURE_BASE which means that it uses uncompressed pubkeys
     236             :     // (65 bytes instead of 33 bytes) and we use Low R signatures, so CTxIn size is 179 bytes.
     237             :     // Each output is 34 bytes, vin and vout compact sizes are 1 byte each.
     238             :     // Therefore base size (i.e. for a tx with 1 input, 0 outputs) is expected to be
     239             :     // 4(n32bitVersion) + 1(vin size) + 179(vin[0]) + 1(vout size) + 4(nLockTime) = 189 bytes.
     240             : 
     241           1 :     minRelayTxFee = CFeeRate(DEFAULT_MIN_RELAY_TX_FEE);
     242             :     // Tests with single outpoint tallyItem
     243             :     {
     244           1 :         CompactTallyItem tallyItem = GetTallyItem({4999});
     245           1 :         CTransactionBuilder txBuilder(*wallet, tallyItem);
     246             : 
     247           1 :         BOOST_CHECK_EQUAL(txBuilder.CountOutputs(), 0);
     248           1 :         BOOST_CHECK_EQUAL(txBuilder.GetAmountInitial(), tallyItem.nAmount);
     249           1 :         BOOST_CHECK_EQUAL(txBuilder.GetAmountLeft(), 4810);         // 4999 - 189
     250             : 
     251           1 :         BOOST_CHECK(txBuilder.CouldAddOutput(4776));                // 4810 - 34
     252           1 :         BOOST_CHECK(!txBuilder.CouldAddOutput(4777));
     253             : 
     254           1 :         BOOST_CHECK(txBuilder.CouldAddOutput(0));
     255           1 :         BOOST_CHECK(!txBuilder.CouldAddOutput(-1));
     256             : 
     257           1 :         BOOST_CHECK(txBuilder.CouldAddOutputs({1000, 1000, 2708})); // (4810 - 34 * 3) split in 3 outputs
     258           1 :         BOOST_CHECK(!txBuilder.CouldAddOutputs({1000, 1000, 2709}));
     259             : 
     260           1 :         BOOST_CHECK_EQUAL(txBuilder.AddOutput(4999), nullptr);
     261           1 :         BOOST_CHECK_EQUAL(txBuilder.AddOutput(-1), nullptr);
     262             : 
     263           1 :         CTransactionBuilderOutput* output = txBuilder.AddOutput();
     264           1 :         BOOST_CHECK(output->UpdateAmount(txBuilder.GetAmountLeft()));
     265           1 :         BOOST_CHECK(output->UpdateAmount(1));
     266           1 :         BOOST_CHECK(output->UpdateAmount(output->GetAmount() + txBuilder.GetAmountLeft()));
     267           1 :         BOOST_CHECK(!output->UpdateAmount(output->GetAmount() + 1));
     268           1 :         BOOST_CHECK(!output->UpdateAmount(0));
     269           1 :         BOOST_CHECK(!output->UpdateAmount(-1));
     270           1 :         BOOST_CHECK_EQUAL(txBuilder.CountOutputs(), 1);
     271             : 
     272           1 :         bilingual_str strResult;
     273           1 :         BOOST_REQUIRE(txBuilder.Commit(strResult));
     274           1 :         CWalletTx& wtx = AddTxToChain(uint256S(strResult.original));
     275           1 :         BOOST_CHECK_EQUAL(wtx.tx->vout.size(), txBuilder.CountOutputs()); // should have no change output
     276           1 :         BOOST_CHECK_EQUAL(wtx.tx->vout[0].nValue, output->GetAmount());
     277           1 :         BOOST_CHECK(wtx.tx->vout[0].scriptPubKey == output->GetScript());
     278           1 :     }
     279             :     // Tests with multiple outpoint tallyItem
     280             :     {
     281           1 :         CompactTallyItem tallyItem = GetTallyItem({10000, 20000, 30000, 40000, 50000});
     282           1 :         CTransactionBuilder txBuilder(*wallet, tallyItem);
     283           1 :         std::vector<CTransactionBuilderOutput*> vecOutputs;
     284           1 :         bilingual_str strResult;
     285             : 
     286           1 :         auto output = txBuilder.AddOutput(100);
     287           1 :         BOOST_CHECK(output != nullptr);
     288           1 :         BOOST_CHECK(!txBuilder.Commit(strResult));
     289             : 
     290           1 :         if (output != nullptr) {
     291           1 :             output->UpdateAmount(1000);
     292           1 :             vecOutputs.push_back(output);
     293           1 :         }
     294         100 :         while (vecOutputs.size() < 100) {
     295          99 :             output = txBuilder.AddOutput(1000 + vecOutputs.size());
     296          99 :             if (output == nullptr) {
     297           0 :                 break;
     298             :             }
     299          99 :             vecOutputs.push_back(output);
     300             :         }
     301           1 :         BOOST_CHECK_EQUAL(vecOutputs.size(), 100);
     302           1 :         BOOST_CHECK_EQUAL(txBuilder.CountOutputs(), vecOutputs.size());
     303           1 :         BOOST_REQUIRE(txBuilder.Commit(strResult));
     304           1 :         CWalletTx& wtx = AddTxToChain(uint256S(strResult.original));
     305           1 :         BOOST_CHECK_EQUAL(wtx.tx->vout.size(), txBuilder.CountOutputs() + 1); // should have change output
     306         102 :         for (const auto& out : wtx.tx->vout) {
     307         201 :             auto it = std::find_if(vecOutputs.begin(), vecOutputs.end(), [&](CTransactionBuilderOutput* output) -> bool {
     308         200 :                 return output->GetAmount() == out.nValue && output->GetScript() == out.scriptPubKey;
     309           0 :             });
     310         101 :             if (it != vecOutputs.end()) {
     311         100 :                 vecOutputs.erase(it);
     312         100 :             } else {
     313             :                 // change output
     314           1 :                 BOOST_CHECK_EQUAL(txBuilder.GetAmountLeft() - 34, out.nValue);
     315             :             }
     316             :         }
     317           1 :         BOOST_CHECK(vecOutputs.size() == 0);
     318           1 :     }
     319           1 : }
     320             : 
     321         146 : BOOST_AUTO_TEST_SUITE_END()
     322             : } // namespace wallet

Generated by: LCOV version 1.16