Skip to content
6 changes: 6 additions & 0 deletions src/Makefile.test.include
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,12 @@ BITCOIN_TESTS =\
test/lcg.h \
test/limitedmap_tests.cpp \
test/llmq_dkg_tests.cpp \
test/llmq_chainlock_tests.cpp \
test/llmq_commitment_tests.cpp \
test/llmq_hash_tests.cpp \
test/llmq_params_tests.cpp \
test/llmq_snapshot_tests.cpp \
test/llmq_utils_tests.cpp \
test/logging_tests.cpp \
test/dbwrapper_tests.cpp \
test/validation_tests.cpp \
Expand Down
1 change: 1 addition & 0 deletions src/Makefile.test_util.include
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ TEST_UTIL_H = \
test/util/chainstate.h \
test/util/json.h \
test/util/index.h \
test/util/llmq_tests.h \
test/util/logging.h \
test/util/mining.h \
test/util/net.h \
Expand Down
168 changes: 168 additions & 0 deletions src/test/llmq_chainlock_tests.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,168 @@
// Copyright (c) 2025 The Dash Core developers
// Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.

#include <test/util/llmq_tests.h>
#include <test/util/setup_common.h>

#include <llmq/clsig.h>
#include <streams.h>
#include <util/strencodings.h>

#include <boost/test/unit_test.hpp>

using namespace llmq;
using namespace llmq::testutils;

BOOST_FIXTURE_TEST_SUITE(llmq_chainlock_tests, BasicTestingSetup)

BOOST_AUTO_TEST_CASE(chainlock_construction_test)
{
// Test default constructor
CChainLockSig clsig1;
BOOST_CHECK(clsig1.IsNull());
BOOST_CHECK_EQUAL(clsig1.getHeight(), -1);
BOOST_CHECK(clsig1.getBlockHash().IsNull());
BOOST_CHECK(!clsig1.getSig().IsValid());

// Test parameterized constructor
int32_t height = 12345;
uint256 blockHash = GetTestBlockHash(1);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: names should be named in snake_case for new code; for example: block_hash instead blockHash.
nullClsig -> null_clsig (or clsig_null)
nonRotatedParams -> params_non_rotated (or non_rotated_params)
truncateAt -> truncate_at
hashDiffQuorum -> hash_diff_quorum
hashDiffPubKey -> hash_diff_pubkey
hashDiffVvec -> hash_diff_vvec
hashEmpty -> hash_empty
hashNoneValid -> hash_none_valid
hashAllValid -> hash_all_valid
proTxHash1 -> protx_hash1

also, hashNullQuorum, hashInvalidKey, hashNullVvec, largeValidMembers, hashLarge, hashLargeDiff, nonRotated, getInfo, activeMembers, skipMode, skipList, nullHash, validHash

See doc/developer-notes.md:

- **Symbol naming conventions**. These are preferred in new code, but are not
required when doing so would need changes to significant pieces of existing
code.
  - Variable (including function arguments) and namespace names are all lowercase and may use `_` to
    separate words (snake_case).
    - Class member variables have a `m_` prefix.
    - Global variables have a `g_` prefix.
  - Constant names are all uppercase, and use `_` to separate words.
  - Enumerator constants may be `snake_case`, `PascalCase` or `ALL_CAPS`.
    This is a more tolerant policy than the [C++ Core
    Guidelines](https://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines#Renum-caps),
    which recommend using `snake_case`.  Please use what seems appropriate.
  - Class names, function names, and method names are UpperCamelCase
    (PascalCase). Do not prefix class names with `C`. See [Internal interface
    naming style](#internal-interface-naming-style) for an exception to this
    convention.

CBLSSignature sig = CreateRandomBLSSignature();

CChainLockSig clsig2(height, blockHash, sig);
BOOST_CHECK(!clsig2.IsNull());
BOOST_CHECK_EQUAL(clsig2.getHeight(), height);
BOOST_CHECK(clsig2.getBlockHash() == blockHash);
BOOST_CHECK(clsig2.getSig() == sig);
}

BOOST_AUTO_TEST_CASE(chainlock_null_test)
{
CChainLockSig clsig;

// Default constructed should be null
BOOST_CHECK(clsig.IsNull());

// With height set but null hash, should not be null
clsig = CChainLockSig(100, uint256(), CBLSSignature());
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit use {}:

    clsig = CChainLockSig(100, uint256{}, CBLSSignature{});

BOOST_CHECK(!clsig.IsNull());

// With valid data should not be null
clsig = CreateChainLock(100, GetTestBlockHash(1));
BOOST_CHECK(!clsig.IsNull());
}

BOOST_AUTO_TEST_CASE(chainlock_serialization_test)
{
// Test with valid chainlock
CChainLockSig clsig = CreateChainLock(67890, GetTestBlockHash(42));

// Test serialization preserves all fields
CDataStream ss(SER_NETWORK, PROTOCOL_VERSION);
ss << clsig;

CChainLockSig deserialized;
ss >> deserialized;

BOOST_CHECK_EQUAL(clsig.getHeight(), deserialized.getHeight());
BOOST_CHECK(clsig.getBlockHash() == deserialized.getBlockHash());
BOOST_CHECK(clsig.getSig() == deserialized.getSig());
BOOST_CHECK_EQUAL(clsig.IsNull(), deserialized.IsNull());
}

BOOST_AUTO_TEST_CASE(chainlock_tostring_test)
{
// Test null chainlock
CChainLockSig nullClsig;
std::string nullStr = nullClsig.ToString();
BOOST_CHECK(!nullStr.empty());

// Test valid chainlock
int32_t height = 123456;
uint256 blockHash = GetTestBlockHash(789);
CChainLockSig clsig = CreateChainLock(height, blockHash);

std::string str = clsig.ToString();
BOOST_CHECK(!str.empty());

// ToString should contain height and hash info
BOOST_CHECK(str.find(strprintf("%d", height)) != std::string::npos);
BOOST_CHECK(str.find(blockHash.ToString().substr(0, 10)) != std::string::npos);
}

BOOST_AUTO_TEST_CASE(chainlock_edge_cases_test)
{
// Test with edge case heights
CChainLockSig clsig1 = CreateChainLock(0, GetTestBlockHash(1));
BOOST_CHECK_EQUAL(clsig1.getHeight(), 0);
BOOST_CHECK(!clsig1.IsNull());

CChainLockSig clsig2 = CreateChainLock(std::numeric_limits<int32_t>::max(), GetTestBlockHash(2));
BOOST_CHECK_EQUAL(clsig2.getHeight(), std::numeric_limits<int32_t>::max());

// Test serialization with extreme values
CDataStream ss1(SER_NETWORK, PROTOCOL_VERSION);
ss1 << clsig1;
CChainLockSig clsig1_deserialized;
ss1 >> clsig1_deserialized;
BOOST_CHECK_EQUAL(clsig1.getHeight(), clsig1_deserialized.getHeight());

CDataStream ss2(SER_NETWORK, PROTOCOL_VERSION);
ss2 << clsig2;
CChainLockSig clsig2_deserialized;
ss2 >> clsig2_deserialized;
BOOST_CHECK_EQUAL(clsig2.getHeight(), clsig2_deserialized.getHeight());
}

BOOST_AUTO_TEST_CASE(chainlock_comparison_test)
{
// Create identical chainlocks
int32_t height = 5000;
uint256 blockHash = GetTestBlockHash(10);
CBLSSignature sig = CreateRandomBLSSignature();

CChainLockSig clsig1(height, blockHash, sig);
CChainLockSig clsig2(height, blockHash, sig);

// Verify getters return same values
BOOST_CHECK_EQUAL(clsig1.getHeight(), clsig2.getHeight());
BOOST_CHECK(clsig1.getBlockHash() == clsig2.getBlockHash());
BOOST_CHECK(clsig1.getSig() == clsig2.getSig());

// Different chainlocks
CChainLockSig clsig3(height + 1, blockHash, sig);
BOOST_CHECK(clsig1.getHeight() != clsig3.getHeight());

CChainLockSig clsig4(height, GetTestBlockHash(11), sig);
BOOST_CHECK(clsig1.getBlockHash() != clsig4.getBlockHash());
}

BOOST_AUTO_TEST_CASE(chainlock_malformed_data_test)
{
// Test deserialization of truncated data
CChainLockSig clsig = CreateChainLock(1000, GetTestBlockHash(5));

CDataStream ss(SER_NETWORK, PROTOCOL_VERSION);
ss << clsig;

// Truncate the stream
std::string data = ss.str();
for (size_t truncateAt = 1; truncateAt < data.size(); truncateAt += 10) {
CDataStream truncated(std::vector<unsigned char>(data.begin(), data.begin() + truncateAt), SER_NETWORK,
PROTOCOL_VERSION);

CChainLockSig deserialized;
try {
truncated >> deserialized;
// If no exception, verify it's either complete or default
if (truncateAt < sizeof(int32_t)) {
BOOST_CHECK(deserialized.IsNull());
}
} catch (const std::exception&) {
// Expected for most truncation points
}
}
}

BOOST_AUTO_TEST_SUITE_END()
Loading
Loading