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

          Line data    Source code
       1             : // Copyright (c) 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/llmq_tests.h>
       6             : #include <test/util/setup_common.h>
       7             : 
       8             : #include <chain.h>
       9             : #include <streams.h>
      10             : #include <univalue.h>
      11             : 
      12             : #include <llmq/params.h>
      13             : #include <llmq/snapshot.h>
      14             : 
      15             : #include <boost/test/unit_test.hpp>
      16             : 
      17             : #include <vector>
      18             : 
      19             : using namespace llmq;
      20             : using namespace llmq::testutils;
      21             : 
      22         146 : BOOST_FIXTURE_TEST_SUITE(llmq_snapshot_tests, BasicTestingSetup)
      23             : 
      24         149 : BOOST_AUTO_TEST_CASE(quorum_snapshot_construction_test)
      25             : {
      26             :     // Test default constructor
      27           1 :     CQuorumSnapshot snapshot1;
      28           1 :     BOOST_CHECK(snapshot1.activeQuorumMembers.empty());
      29           1 :     BOOST_CHECK_EQUAL(snapshot1.mnSkipListMode, SnapshotSkipMode::MODE_NO_SKIPPING);
      30           1 :     BOOST_CHECK(snapshot1.mnSkipList.empty());
      31             : 
      32             :     // Test parameterized constructor
      33           1 :     std::vector<bool> activeMembers = {true, false, true, true, false};
      34           1 :     auto skipMode = SnapshotSkipMode::MODE_SKIPPING_ENTRIES;
      35           1 :     std::vector<int> skipList = {1, 3, 5, 7};
      36             : 
      37           1 :     CQuorumSnapshot snapshot2(activeMembers, skipMode, skipList);
      38           1 :     BOOST_CHECK(snapshot2.activeQuorumMembers == activeMembers);
      39           1 :     BOOST_CHECK_EQUAL(snapshot2.mnSkipListMode, skipMode);
      40           1 :     BOOST_CHECK(snapshot2.mnSkipList == skipList);
      41             : 
      42             :     // Test move semantics
      43           1 :     std::vector<bool> activeMembersCopy = activeMembers;
      44           1 :     std::vector<int> skipListCopy = skipList;
      45           1 :     CQuorumSnapshot snapshot3(std::move(activeMembersCopy), skipMode, std::move(skipListCopy));
      46           1 :     BOOST_CHECK(snapshot3.activeQuorumMembers == activeMembers);
      47           1 :     BOOST_CHECK_EQUAL(snapshot3.mnSkipListMode, skipMode);
      48           1 :     BOOST_CHECK(snapshot3.mnSkipList == skipList);
      49           1 : }
      50             : 
      51         149 : BOOST_AUTO_TEST_CASE(quorum_snapshot_serialization_test)
      52             : {
      53             :     // Test with various configurations
      54           1 :     std::vector<bool> activeMembers = CreateBitVector(10, {0, 2, 4, 6, 8});
      55           1 :     auto skipMode = SnapshotSkipMode::MODE_SKIPPING_ENTRIES;
      56           1 :     std::vector<int> skipList = {10, 20, 30};
      57             : 
      58           1 :     CQuorumSnapshot snapshot(activeMembers, skipMode, skipList);
      59             : 
      60             :     // Test serialization roundtrip
      61           1 :     CDataStream ss(SER_NETWORK, PROTOCOL_VERSION);
      62           1 :     ss << snapshot;
      63             : 
      64           1 :     CQuorumSnapshot deserialized;
      65           1 :     ss >> deserialized;
      66             : 
      67           1 :     BOOST_CHECK(deserialized.activeQuorumMembers == snapshot.activeQuorumMembers);
      68           1 :     BOOST_CHECK_EQUAL(deserialized.mnSkipListMode, snapshot.mnSkipListMode);
      69           1 :     BOOST_CHECK(deserialized.mnSkipList == snapshot.mnSkipList);
      70           1 : }
      71             : 
      72         149 : BOOST_AUTO_TEST_CASE(quorum_snapshot_skip_modes_test)
      73             : {
      74             :     // Test all skip modes
      75           1 :     constexpr std::array<SnapshotSkipMode, 4> skipModes{
      76             :         SnapshotSkipMode::MODE_NO_SKIPPING,
      77             :         SnapshotSkipMode::MODE_SKIPPING_ENTRIES,
      78             :         SnapshotSkipMode::MODE_NO_SKIPPING_ENTRIES,
      79             :         SnapshotSkipMode::MODE_ALL_SKIPPED
      80             :     };
      81             : 
      82           5 :     for (auto mode : skipModes) {
      83           4 :         CQuorumSnapshot snapshot({true, false, true}, mode, {1, 2, 3});
      84             : 
      85           4 :         CDataStream ss(SER_NETWORK, PROTOCOL_VERSION);
      86           4 :         ss << snapshot;
      87             : 
      88           4 :         CQuorumSnapshot deserialized;
      89           4 :         ss >> deserialized;
      90             : 
      91           4 :         BOOST_CHECK_EQUAL(deserialized.mnSkipListMode, mode);
      92           4 :     }
      93           1 : }
      94             : 
      95         149 : BOOST_AUTO_TEST_CASE(quorum_snapshot_large_data_test)
      96             : {
      97             :     // Test with large quorum (400 members)
      98           1 :     std::vector<bool> largeActiveMembers(400);
      99             :     // Create pattern: every 3rd member is inactive
     100         401 :     for (size_t i = 0; i < largeActiveMembers.size(); i++) {
     101         400 :         largeActiveMembers[i] = (i % 3 != 0);
     102         400 :     }
     103             : 
     104             :     // Create large skip list
     105           1 :     std::vector<int> largeSkipList;
     106         101 :     for (int i = 0; i < 100; i++) {
     107         100 :         largeSkipList.push_back(i * 4);
     108         100 :     }
     109             : 
     110           1 :     CQuorumSnapshot snapshot(largeActiveMembers, SnapshotSkipMode::MODE_SKIPPING_ENTRIES, largeSkipList);
     111             : 
     112             :     // Test serialization with large data
     113             :     // Test serialization manually instead of using roundtrip helper
     114           1 :     CDataStream ss(SER_NETWORK, PROTOCOL_VERSION);
     115           1 :     ss << snapshot;
     116           1 :     CQuorumSnapshot deserialized;
     117           1 :     ss >> deserialized;
     118           1 :     BOOST_CHECK_EQUAL(deserialized.activeQuorumMembers.size(), 400);
     119           1 :     BOOST_CHECK_EQUAL(deserialized.mnSkipList.size(), 100);
     120           1 : }
     121             : 
     122         149 : BOOST_AUTO_TEST_CASE(quorum_snapshot_empty_data_test)
     123             : {
     124             :     // Test with empty data
     125           1 :     CQuorumSnapshot emptySnapshot({}, SnapshotSkipMode::MODE_NO_SKIPPING, {});
     126             : 
     127             :     // Test serialization roundtrip
     128           1 :     BOOST_CHECK(TestSerializationRoundtrip(emptySnapshot));
     129             : 
     130             :     // Test with empty active members but non-empty skip list
     131           1 :     CQuorumSnapshot snapshot1({}, SnapshotSkipMode::MODE_SKIPPING_ENTRIES, {1, 2, 3});
     132           1 :     BOOST_CHECK(TestSerializationRoundtrip(snapshot1));
     133             : 
     134             :     // Test with non-empty active members but empty skip list
     135           1 :     CQuorumSnapshot snapshot2({true, false, true}, SnapshotSkipMode::MODE_NO_SKIPPING, {});
     136           1 :     BOOST_CHECK(TestSerializationRoundtrip(snapshot2));
     137           1 : }
     138             : 
     139         149 : BOOST_AUTO_TEST_CASE(quorum_snapshot_bit_serialization_test)
     140             : {
     141             :     // Test bit vector serialization edge cases
     142             : 
     143             :     // Test single bit
     144           1 :     CQuorumSnapshot snapshot1({true}, SnapshotSkipMode::MODE_NO_SKIPPING, {});
     145           1 :     BOOST_CHECK(TestSerializationRoundtrip(snapshot1));
     146             : 
     147             :     // Test 8 bits (full byte)
     148           1 :     CQuorumSnapshot snapshot8(std::vector<bool>(8, true), SnapshotSkipMode::MODE_NO_SKIPPING, {});
     149           1 :     BOOST_CHECK(TestSerializationRoundtrip(snapshot8));
     150             : 
     151             :     // Test 9 bits (more than one byte)
     152           1 :     CQuorumSnapshot snapshot9(std::vector<bool>(9, false), SnapshotSkipMode::MODE_NO_SKIPPING, {});
     153           1 :     snapshot9.activeQuorumMembers[8] = true; // Set last bit
     154           1 :     BOOST_CHECK(TestSerializationRoundtrip(snapshot9));
     155             : 
     156             :     // Test alternating pattern
     157           1 :     std::vector<bool> alternating(16);
     158          17 :     for (size_t i = 0; i < alternating.size(); i++) {
     159          16 :         alternating[i] = (i % 2 == 0);
     160          16 :     }
     161           1 :     CQuorumSnapshot snapshotAlt(alternating, SnapshotSkipMode::MODE_NO_SKIPPING, {});
     162           1 :     BOOST_CHECK(TestSerializationRoundtrip(snapshotAlt));
     163           1 : }
     164             : 
     165         149 : BOOST_AUTO_TEST_CASE(quorum_rotation_info_construction_test)
     166             : {
     167           1 :     CQuorumRotationInfo rotInfo;
     168             : 
     169             :     // Test default state
     170           1 :     BOOST_CHECK(!rotInfo.extraShare);
     171           1 :     BOOST_CHECK(!rotInfo.cycleHMinus4C.has_value());
     172           1 :     BOOST_CHECK(rotInfo.lastCommitmentPerIndex.empty());
     173           1 :     BOOST_CHECK(rotInfo.quorumSnapshotList.empty());
     174           1 :     BOOST_CHECK(rotInfo.mnListDiffList.empty());
     175           1 : }
     176             : 
     177             : // Note: CQuorumRotationInfo serialization requires complex setup
     178             : // This is better tested in functional tests
     179             : 
     180         149 : BOOST_AUTO_TEST_CASE(get_last_base_block_hash_repeated_base_blocks_test)
     181             : {
     182           1 :     std::vector<CBlockIndex> blocks(4);
     183           1 :     std::vector<uint256> hashes{
     184           1 :         GetTestBlockHash(10),
     185           1 :         GetTestBlockHash(20),
     186           1 :         GetTestBlockHash(30),
     187           1 :         GetTestBlockHash(40),
     188             :     };
     189           5 :     for (size_t i{0}; i < blocks.size(); ++i) {
     190           4 :         blocks[i].nHeight = static_cast<int>((i + 1) * 10);
     191           4 :         blocks[i].phashBlock = &hashes[i];
     192           4 :     }
     193             : 
     194             :     // Non-legacy: sorts internally, so unsorted input with duplicates is fine.
     195           4 :     std::vector<const CBlockIndex*> unsorted_repeated_base_blocks{
     196           1 :         &blocks[2],
     197           1 :         &blocks[0],
     198           1 :         &blocks[1],
     199           1 :         &blocks[1],
     200             :     };
     201           1 :     BOOST_CHECK(GetLastBaseBlockHash(unsorted_repeated_base_blocks, &blocks[3], false) == hashes[2]);
     202           1 :     BOOST_CHECK(GetLastBaseBlockHash(unsorted_repeated_base_blocks, &blocks[1], false) == hashes[1]);
     203             : 
     204             :     // Legacy: relies on caller-supplied sort and tolerates duplicates as a no-op.
     205             :     // BuildQuorumRotationInfo deliberately does NOT deduplicate in the legacy path so
     206             :     // the wire response to older peers stays bit-for-bit identical; these checks
     207             :     // demonstrate that the duplicate is harmless to GetLastBaseBlockHash's output.
     208           4 :     std::vector<const CBlockIndex*> sorted_repeated_base_blocks{
     209           1 :         &blocks[0],
     210           1 :         &blocks[1],
     211           1 :         &blocks[1],
     212           1 :         &blocks[2],
     213             :     };
     214           3 :     std::vector<const CBlockIndex*> sorted_unique_base_blocks{
     215           1 :         &blocks[0],
     216           1 :         &blocks[1],
     217           1 :         &blocks[2],
     218             :     };
     219           1 :     BOOST_CHECK(GetLastBaseBlockHash(sorted_repeated_base_blocks, &blocks[3], true) == hashes[2]);
     220           1 :     BOOST_CHECK(GetLastBaseBlockHash(sorted_repeated_base_blocks, &blocks[1], true) == hashes[1]);
     221             :     // Legacy no-op proof: duplicate vs unique input produces the same hash.
     222           1 :     BOOST_CHECK(GetLastBaseBlockHash(sorted_repeated_base_blocks, &blocks[3], true) ==
     223             :                 GetLastBaseBlockHash(sorted_unique_base_blocks, &blocks[3], true));
     224           1 :     BOOST_CHECK(GetLastBaseBlockHash(sorted_repeated_base_blocks, &blocks[1], true) ==
     225             :                 GetLastBaseBlockHash(sorted_unique_base_blocks, &blocks[1], true));
     226           1 : }
     227             : 
     228         149 : BOOST_AUTO_TEST_CASE(get_quorum_rotation_info_serialization_test)
     229             : {
     230           1 :     CGetQuorumRotationInfo getInfo;
     231             : 
     232             :     // Test with multiple base block hashes
     233           1 :     getInfo.baseBlockHashes = {GetTestBlockHash(1), GetTestBlockHash(2), GetTestBlockHash(3)};
     234           1 :     getInfo.blockRequestHash = GetTestBlockHash(100);
     235           1 :     getInfo.extraShare = true;
     236             : 
     237             :     // Test serialization
     238           1 :     BOOST_CHECK(TestSerializationRoundtrip(getInfo));
     239             : 
     240             :     // Test with empty base block hashes
     241           1 :     CGetQuorumRotationInfo emptyInfo;
     242           1 :     emptyInfo.blockRequestHash = GetTestBlockHash(200);
     243           1 :     emptyInfo.extraShare = false;
     244             : 
     245           1 :     BOOST_CHECK(TestSerializationRoundtrip(emptyInfo));
     246           1 : }
     247             : 
     248         149 : BOOST_AUTO_TEST_CASE(quorum_rotation_info_serialization_test)
     249             : {
     250             :     // Note: mnListDiff{smth} testing requires proper CSimplifiedMNListDiff setup
     251             :     // which is complex and better tested in functional tests
     252             : 
     253             :     // Test CQuorumRotationInfo serialization with various optional field combinations
     254           1 :     CQuorumRotationInfo rotInfo;
     255             : 
     256             :     // Set up basic required fields
     257           1 :     rotInfo.cycleHMinusC.m_snap = CQuorumSnapshot({true, false, true}, SnapshotSkipMode::MODE_SKIPPING_ENTRIES, {1, 2});
     258           1 :     rotInfo.cycleHMinus2C.m_snap = CQuorumSnapshot({false, true, false}, SnapshotSkipMode::MODE_NO_SKIPPING, {});
     259           1 :     rotInfo.cycleHMinus3C.m_snap = CQuorumSnapshot({true, true, false}, SnapshotSkipMode::MODE_ALL_SKIPPED, {3});
     260             : 
     261             :     // Test without extraShare
     262           1 :     rotInfo.extraShare = false;
     263           1 :     BOOST_CHECK(TestSerializationRoundtrip(rotInfo));
     264             : 
     265             :     // Test with extraShare but uninitialized optional fields
     266           1 :     rotInfo.extraShare = true;
     267           1 :     BOOST_CHECK(TestSerializationRoundtrip(rotInfo));
     268             : 
     269             :     // Test with extraShare and initialized snapshot
     270           1 :     llmq::CycleData extra_cycle;
     271           1 :     extra_cycle.m_snap = CQuorumSnapshot({false, false, true}, SnapshotSkipMode::MODE_SKIPPING_ENTRIES, {4, 5, 6});
     272           1 :     rotInfo.cycleHMinus4C = extra_cycle;
     273           1 :     BOOST_CHECK(TestSerializationRoundtrip(rotInfo));
     274             : 
     275           1 :     CFinalCommitment commitment{GetLLMQParams(Consensus::LLMQType::LLMQ_TEST), uint256::ONE};
     276           1 :     rotInfo.lastCommitmentPerIndex.push_back(commitment);
     277           2 :     rotInfo.quorumSnapshotList.push_back(
     278           1 :         CQuorumSnapshot({false, false, true}, SnapshotSkipMode::MODE_SKIPPING_ENTRIES, {7, 8}));
     279           1 :     BOOST_CHECK(TestSerializationRoundtrip(rotInfo));
     280           1 : }
     281             : 
     282         149 : BOOST_AUTO_TEST_CASE(quorum_snapshot_json_test)
     283             : {
     284             :     // Create snapshot with test data
     285           1 :     std::vector<bool> activeMembers = {true, false, true, true, false, false, true};
     286           1 :     auto skipMode = SnapshotSkipMode::MODE_SKIPPING_ENTRIES;
     287           1 :     std::vector<int> skipList = {10, 20, 30, 40};
     288             : 
     289           1 :     CQuorumSnapshot snapshot(activeMembers, skipMode, skipList);
     290             : 
     291             :     // Test JSON conversion
     292           1 :     UniValue json = snapshot.ToJson();
     293             : 
     294             :     // Verify JSON structure
     295           1 :     BOOST_CHECK(json.isObject());
     296           1 :     BOOST_CHECK(json.exists("activeQuorumMembers"));
     297           1 :     BOOST_CHECK(json.exists("mnSkipListMode"));
     298           1 :     BOOST_CHECK(json.exists("mnSkipList"));
     299             : 
     300             :     // Verify skip list is array
     301           1 :     BOOST_CHECK(json["mnSkipList"].isArray());
     302           1 :     BOOST_CHECK_EQUAL(json["mnSkipList"].size(), skipList.size());
     303           1 : }
     304             : 
     305         149 : BOOST_AUTO_TEST_CASE(quorum_snapshot_malformed_data_test)
     306             : {
     307             :     // Create valid snapshot
     308           1 :     CQuorumSnapshot snapshot({true, false, true}, SnapshotSkipMode::MODE_SKIPPING_ENTRIES, {1, 2, 3});
     309             : 
     310           1 :     CDataStream ss(SER_NETWORK, PROTOCOL_VERSION);
     311           1 :     ss << snapshot;
     312             : 
     313             :     // Test truncated data deserialization
     314           1 :     std::string data = ss.str();
     315           5 :     for (size_t truncateAt = 1; truncateAt < data.size(); truncateAt += 5) {
     316           4 :         CDataStream truncated(std::vector<unsigned char>(data.begin(), data.begin() + truncateAt), SER_NETWORK,
     317             :                               PROTOCOL_VERSION);
     318             : 
     319           4 :         CQuorumSnapshot deserialized;
     320             :         try {
     321           4 :             truncated >> deserialized;
     322             :             // If no exception, it might be a valid partial deserialization
     323             :             // (though unlikely for complex structures)
     324           4 :         } catch (const std::exception&) {
     325             :             // Expected for most truncation points
     326           4 :         }
     327           4 :     }
     328           5 : }
     329             : 
     330         146 : BOOST_AUTO_TEST_SUITE_END()

Generated by: LCOV version 1.16