Line data Source code
1 : // Copyright (c) 2025 The Dash Core developers 2 : // Distributed under the MIT/X11 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 <algorithm> 8 : #include <array> 9 : #include <cstdint> 10 : #include <vector> 11 : 12 : #include <chain.h> 13 : #include <coinjoin/coinjoin.h> 14 : #include <coinjoin/common.h> 15 : #include <evo/chainhelper.h> 16 : #include <script/script.h> 17 : #include <uint256.h> 18 : #include <util/check.h> 19 : #include <util/time.h> 20 : 21 : #include <boost/test/unit_test.hpp> 22 : 23 146 : BOOST_FIXTURE_TEST_SUITE(coinjoin_inouts_tests, TestingSetup) 24 : 25 3 : static CScript P2PKHScript(uint8_t tag = 0x01) 26 : { 27 : // OP_DUP OP_HASH160 <20-byte-tag> OP_EQUALVERIFY OP_CHECKSIG 28 3 : std::vector<unsigned char> hash(20, tag); 29 3 : return CScript{} << OP_DUP << OP_HASH160 << hash << OP_EQUALVERIFY << OP_CHECKSIG; 30 3 : } 31 : 32 149 : BOOST_AUTO_TEST_CASE(broadcasttx_isvalidstructure_good_and_bad) 33 : { 34 : // Good: equal vin/vout sizes, vin count >= min participants, <= max*entry_size, P2PKH outputs with standard denominations 35 1 : CCoinJoinBroadcastTx good; 36 : { 37 1 : CMutableTransaction mtx; 38 : // Use min pool participants (e.g. 3). Build 3 inputs and 3 denominated outputs 39 1 : const int participants = std::max(3, CoinJoin::GetMinPoolParticipants()); 40 4 : for (int i = 0; i < participants; ++i) { 41 3 : CTxIn in; 42 3 : in.prevout = COutPoint(uint256::ONE, static_cast<uint32_t>(i)); 43 3 : mtx.vin.push_back(in); 44 : // Pick the smallest denomination 45 3 : CTxOut out{CoinJoin::GetSmallestDenomination(), P2PKHScript(static_cast<uint8_t>(i))}; 46 3 : mtx.vout.push_back(out); 47 3 : } 48 1 : good.tx = MakeTransactionRef(mtx); 49 1 : good.m_protxHash = uint256::ONE; // at least one of (outpoint, protxhash) must be set 50 1 : } 51 1 : BOOST_CHECK(good.IsValidStructure()); 52 : 53 : // Bad: both identifiers null 54 1 : CCoinJoinBroadcastTx bad_ids = good; 55 1 : bad_ids.m_protxHash = uint256{}; 56 1 : bad_ids.masternodeOutpoint.SetNull(); 57 1 : BOOST_CHECK(!bad_ids.IsValidStructure()); 58 : 59 : // Bad: vin/vout size mismatch 60 1 : CCoinJoinBroadcastTx bad_sizes = good; 61 : { 62 1 : CMutableTransaction mtx(*good.tx); 63 1 : mtx.vout.pop_back(); 64 1 : bad_sizes.tx = MakeTransactionRef(mtx); 65 1 : } 66 1 : BOOST_CHECK(!bad_sizes.IsValidStructure()); 67 : 68 : // Bad: non-P2PKH output 69 1 : CCoinJoinBroadcastTx bad_script = good; 70 : { 71 1 : CMutableTransaction mtx(*good.tx); 72 1 : mtx.vout[0].scriptPubKey = CScript() << OP_RETURN << std::vector<unsigned char>{'x'}; 73 1 : bad_script.tx = MakeTransactionRef(mtx); 74 1 : } 75 1 : BOOST_CHECK(!bad_script.IsValidStructure()); 76 : 77 : // Bad: non-denominated amount 78 1 : CCoinJoinBroadcastTx bad_amount = good; 79 : { 80 1 : CMutableTransaction mtx(*good.tx); 81 1 : mtx.vout[0].nValue = 42; // not a valid denom 82 1 : bad_amount.tx = MakeTransactionRef(mtx); 83 1 : } 84 1 : BOOST_CHECK(!bad_amount.IsValidStructure()); 85 1 : } 86 : 87 149 : BOOST_AUTO_TEST_CASE(queue_timeout_bounds) 88 : { 89 1 : CCoinJoinQueue dsq; 90 1 : dsq.nDenom = CoinJoin::AmountToDenomination(CoinJoin::GetSmallestDenomination()); 91 1 : dsq.m_protxHash = uint256::ONE; 92 1 : dsq.nTime = GetAdjustedTime(); 93 : // current time -> not out of bounds 94 1 : BOOST_CHECK(!dsq.IsTimeOutOfBounds()); 95 : 96 : // Too old (beyond COINJOIN_QUEUE_TIMEOUT) 97 1 : SetMockTime(GetTime() + (COINJOIN_QUEUE_TIMEOUT + 1)); 98 1 : BOOST_CHECK(dsq.IsTimeOutOfBounds()); 99 : 100 : // Too far in the future 101 1 : SetMockTime(GetTime() - 2 * (COINJOIN_QUEUE_TIMEOUT + 1)); // move back to anchor baseline 102 1 : dsq.nTime = GetAdjustedTime() + (COINJOIN_QUEUE_TIMEOUT + 1); 103 1 : BOOST_CHECK(dsq.IsTimeOutOfBounds()); 104 : 105 : // Reset mock time 106 1 : SetMockTime(0); 107 1 : } 108 146 : BOOST_AUTO_TEST_SUITE_END()