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 <active/masternode.h> 8 : #include <bls/bls.h> 9 : #include <coinjoin/coinjoin.h> 10 : #include <coinjoin/common.h> 11 : #include <consensus/amount.h> 12 : 13 : #include <uint256.h> 14 : 15 : #include <climits> 16 : #include <cstdint> 17 : 18 : #include <boost/test/unit_test.hpp> 19 : 20 146 : BOOST_FIXTURE_TEST_SUITE(coinjoin_queue_tests, TestingSetup) 21 : 22 1 : static CBLSSecretKey MakeSecretKey() 23 : { 24 : // Generate a dummy operator key pair for signing 25 1 : CBLSSecretKey sk; 26 1 : sk.MakeNewKey(); 27 1 : return sk; 28 1 : } 29 : 30 149 : BOOST_AUTO_TEST_CASE(queue_sign_and_verify) 31 : { 32 : // Build active MN manager with operator key using node context wiring 33 1 : CActiveMasternodeManager mn_activeman(*Assert(m_node.connman), *Assert(m_node.dmnman), MakeSecretKey()); 34 : 35 1 : CCoinJoinQueue q; 36 1 : q.nDenom = CoinJoin::AmountToDenomination(CoinJoin::GetSmallestDenomination()); 37 1 : q.masternodeOutpoint = COutPoint(uint256S("aa"), 1); 38 1 : q.m_protxHash = uint256::ONE; 39 1 : q.nTime = GetAdjustedTime(); 40 1 : q.fReady = false; 41 : 42 : // Sign and verify with corresponding pubkey 43 1 : q.vchSig = mn_activeman.SignBasic(q.GetSignatureHash()); 44 1 : const CBLSPublicKey pub = mn_activeman.GetPubKey(); 45 1 : BOOST_CHECK(q.CheckSignature(pub)); 46 1 : } 47 : 48 149 : BOOST_AUTO_TEST_CASE(queue_hashes_and_equality) 49 : { 50 1 : CCoinJoinQueue a, b; 51 1 : a.nDenom = b.nDenom = CoinJoin::AmountToDenomination(CoinJoin::GetSmallestDenomination()); 52 1 : a.masternodeOutpoint = b.masternodeOutpoint = COutPoint(uint256S("bb"), 2); 53 1 : a.m_protxHash = b.m_protxHash = uint256::ONE; 54 1 : a.nTime = b.nTime = GetAdjustedTime(); 55 1 : a.fReady = b.fReady = true; 56 : 57 1 : BOOST_CHECK(a == b); 58 1 : BOOST_CHECK(a.GetHash() == b.GetHash()); 59 1 : BOOST_CHECK(a.GetSignatureHash() == b.GetSignatureHash()); 60 1 : } 61 : 62 149 : BOOST_AUTO_TEST_CASE(queue_denomination_validation) 63 : { 64 : // Test that valid denominations pass 65 1 : int validDenom = CoinJoin::AmountToDenomination(CoinJoin::GetSmallestDenomination()); 66 1 : BOOST_CHECK(CoinJoin::IsValidDenomination(validDenom)); 67 : 68 : // Test that invalid denominations fail 69 1 : BOOST_CHECK(!CoinJoin::IsValidDenomination(0)); // Zero 70 1 : BOOST_CHECK(!CoinJoin::IsValidDenomination(-1)); // Negative 71 1 : BOOST_CHECK(!CoinJoin::IsValidDenomination(999)); // Invalid value 72 1 : } 73 : 74 149 : BOOST_AUTO_TEST_CASE(queue_timestamp_validation) 75 : { 76 1 : CCoinJoinQueue q; 77 1 : q.nDenom = CoinJoin::AmountToDenomination(CoinJoin::GetSmallestDenomination()); 78 1 : q.masternodeOutpoint = COutPoint(uint256S("cc"), 3); 79 1 : q.m_protxHash = uint256::ONE; 80 : 81 1 : int64_t current_time = GetAdjustedTime(); 82 : 83 : // Test valid timestamp (current time) 84 1 : q.nTime = current_time; 85 1 : BOOST_CHECK(!q.IsTimeOutOfBounds(current_time)); 86 : 87 : // Test timestamp slightly in future (within COINJOIN_QUEUE_TIMEOUT = 30) 88 1 : q.nTime = current_time + 15; // 15 seconds in future 89 1 : BOOST_CHECK(!q.IsTimeOutOfBounds(current_time)); 90 : 91 : // Test timestamp slightly in past (within COINJOIN_QUEUE_TIMEOUT = 30) 92 1 : q.nTime = current_time - 15; // 15 seconds ago 93 1 : BOOST_CHECK(!q.IsTimeOutOfBounds(current_time)); 94 : 95 : // Test timestamp too far in future (outside COINJOIN_QUEUE_TIMEOUT = 30) 96 1 : q.nTime = current_time + 60; // 60 seconds in future 97 1 : BOOST_CHECK(q.IsTimeOutOfBounds(current_time)); 98 : 99 : // Test timestamp too far in past (outside COINJOIN_QUEUE_TIMEOUT = 30) 100 1 : q.nTime = current_time - 60; // 60 seconds ago 101 1 : BOOST_CHECK(q.IsTimeOutOfBounds(current_time)); 102 1 : } 103 : 104 149 : BOOST_AUTO_TEST_CASE(queue_timestamp_extreme_values) 105 : { 106 1 : CCoinJoinQueue q; 107 1 : q.nDenom = CoinJoin::AmountToDenomination(CoinJoin::GetSmallestDenomination()); 108 1 : q.m_protxHash = uint256::ONE; 109 : 110 : // Negative timestamps are rejected by the guard 111 1 : q.nTime = INT64_MIN; 112 1 : BOOST_CHECK(q.IsTimeOutOfBounds(INT64_MAX)); 113 : 114 1 : q.nTime = INT64_MAX; 115 1 : BOOST_CHECK(q.IsTimeOutOfBounds(INT64_MIN)); 116 : 117 1 : q.nTime = INT64_MIN; 118 1 : BOOST_CHECK(q.IsTimeOutOfBounds(INT64_MIN)); 119 : 120 : // Large positive timestamp with same value: zero diff, in bounds 121 1 : q.nTime = INT64_MAX; 122 1 : BOOST_CHECK(!q.IsTimeOutOfBounds(INT64_MAX)); 123 : 124 : // Zero vs extreme positive: huge gap, out of bounds 125 1 : q.nTime = 0; 126 1 : BOOST_CHECK(q.IsTimeOutOfBounds(INT64_MAX)); 127 : 128 : // Zero vs negative: rejected by guard 129 1 : q.nTime = 0; 130 1 : BOOST_CHECK(q.IsTimeOutOfBounds(INT64_MIN)); 131 1 : } 132 : 133 : static_assert(CoinJoin::CalculateAmountPriority(MAX_MONEY) == -(MAX_MONEY / COIN)); 134 : static_assert(CoinJoin::CalculateAmountPriority(static_cast<CAmount>(INT64_MAX)) == 0); 135 : static_assert(CoinJoin::CalculateAmountPriority(static_cast<CAmount>(-1)) == 0); 136 : 137 149 : BOOST_AUTO_TEST_CASE(calculate_amount_priority_guard) 138 : { 139 : // Realistic amount: MAX_MONEY (21 million DASH) 140 1 : BOOST_CHECK_EQUAL(CoinJoin::CalculateAmountPriority(MAX_MONEY), -(MAX_MONEY / COIN)); 141 : 142 : // Out-of-range amounts return 0 143 1 : BOOST_CHECK_EQUAL(CoinJoin::CalculateAmountPriority(static_cast<CAmount>(INT64_MAX)), 0); 144 1 : BOOST_CHECK_EQUAL(CoinJoin::CalculateAmountPriority(static_cast<CAmount>(-1)), 0); 145 1 : BOOST_CHECK_EQUAL(CoinJoin::CalculateAmountPriority(MAX_MONEY + 1), 0); 146 1 : } 147 : 148 3 : static CCoinJoinQueue MakeQueue(int denom, int64_t nTime, bool fReady, const COutPoint& outpoint) 149 : { 150 3 : CCoinJoinQueue q; 151 3 : q.nDenom = denom; 152 3 : q.masternodeOutpoint = outpoint; 153 3 : q.m_protxHash = uint256::ONE; 154 3 : q.nTime = nTime; 155 3 : q.fReady = fReady; 156 3 : return q; 157 3 : } 158 : 159 149 : BOOST_AUTO_TEST_CASE(queuemanager_checkqueue_removes_timeouts) 160 : { 161 1 : CoinJoinQueueManager man; 162 1 : const int denom = CoinJoin::AmountToDenomination(CoinJoin::GetSmallestDenomination()); 163 1 : const int64_t now = GetAdjustedTime(); 164 : // Non-expired 165 1 : man.AddQueue(MakeQueue(denom, now, false, COutPoint(uint256S("11"), 0))); 166 : // Expired (too old) 167 1 : man.AddQueue(MakeQueue(denom, now - COINJOIN_QUEUE_TIMEOUT - 1, false, COutPoint(uint256S("12"), 0))); 168 : 169 1 : BOOST_CHECK_EQUAL(man.GetQueueSize(), 2); 170 1 : man.CheckQueue(); 171 : // One should be removed 172 1 : BOOST_CHECK_EQUAL(man.GetQueueSize(), 1); 173 1 : } 174 : 175 149 : BOOST_AUTO_TEST_CASE(queuemanager_getqueueitem_marks_tried_once) 176 : { 177 1 : CoinJoinQueueManager man; 178 1 : const int denom = CoinJoin::AmountToDenomination(CoinJoin::GetSmallestDenomination()); 179 1 : const int64_t now = GetAdjustedTime(); 180 1 : CCoinJoinQueue dsq = MakeQueue(denom, now, false, COutPoint(uint256S("21"), 0)); 181 1 : man.AddQueue(dsq); 182 : 183 1 : CCoinJoinQueue picked; 184 : // First retrieval should succeed 185 1 : BOOST_CHECK(man.GetQueueItemAndTry(picked)); 186 : // No other items left to try (picked is marked tried inside) 187 1 : CCoinJoinQueue picked2; 188 1 : BOOST_CHECK(!man.GetQueueItemAndTry(picked2)); 189 1 : } 190 : 191 146 : BOOST_AUTO_TEST_SUITE_END()