Skip to content

Commit 4b00a0e

Browse files
test: add CoinJoin DSTX and inouts tests
This commit introduces two new test files: `coinjoin_dstxmanager_tests.cpp` and `coinjoin_inouts_tests.cpp`. The `coinjoin_dstxmanager_tests` file includes tests for managing CoinJoin broadcast transactions, while the `coinjoin_inouts_tests` file focuses on validating the structure and behavior of CoinJoin transactions. Both files are added to the test suite in the Makefile for comprehensive testing coverage.
1 parent d4202b5 commit 4b00a0e

File tree

3 files changed

+216
-0
lines changed

3 files changed

+216
-0
lines changed

src/Makefile.test.include

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,8 @@ BITCOIN_TESTS =\
120120
test/fs_tests.cpp \
121121
test/getarg_tests.cpp \
122122
test/governance_validators_tests.cpp \
123+
test/coinjoin_inouts_tests.cpp \
124+
test/coinjoin_dstxmanager_tests.cpp \
123125
test/hash_tests.cpp \
124126
test/httpserver_tests.cpp \
125127
test/i2p_tests.cpp \
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
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 <array>
8+
#include <algorithm>
9+
#include <vector>
10+
11+
#include <coinjoin/coinjoin.h>
12+
#include <coinjoin/common.h>
13+
#include <coinjoin/context.h>
14+
#include <chain.h>
15+
#include <script/script.h>
16+
#include <uint256.h>
17+
18+
#include <boost/test/unit_test.hpp>
19+
20+
BOOST_FIXTURE_TEST_SUITE(coinjoin_dstxmanager_tests, TestingSetup)
21+
22+
static CCoinJoinBroadcastTx MakeDSTX(int vin_vout_count = 3)
23+
{
24+
CCoinJoinBroadcastTx dstx;
25+
CMutableTransaction mtx;
26+
const int count = std::max(vin_vout_count, 1);
27+
for (int i = 0; i < count; ++i) {
28+
mtx.vin.emplace_back(COutPoint(uint256S("0a"), i));
29+
// Use denominated P2PKH outputs
30+
std::array<unsigned char, 20> h{}; h.fill(static_cast<unsigned char>(i));
31+
CScript spk; spk << OP_DUP << OP_HASH160 << std::vector<unsigned char>(h.begin(), h.end()) << OP_EQUALVERIFY << OP_CHECKSIG;
32+
mtx.vout.emplace_back(CoinJoin::GetSmallestDenomination(), spk);
33+
}
34+
dstx.tx = MakeTransactionRef(mtx);
35+
dstx.m_protxHash = uint256::ONE;
36+
return dstx;
37+
}
38+
39+
BOOST_AUTO_TEST_CASE(add_get_dstx)
40+
{
41+
CCoinJoinBroadcastTx dstx = MakeDSTX();
42+
auto& man = *Assert(Assert(m_node.cj_ctx)->dstxman);
43+
// Not present initially
44+
BOOST_CHECK(!man.GetDSTX(dstx.tx->GetHash()));
45+
// Add
46+
man.AddDSTX(dstx);
47+
// Fetch back
48+
auto got = man.GetDSTX(dstx.tx->GetHash());
49+
BOOST_CHECK(static_cast<bool>(got));
50+
BOOST_CHECK_EQUAL(got.tx->GetHash().ToString(), dstx.tx->GetHash().ToString());
51+
}
52+
53+
BOOST_AUTO_TEST_CASE(update_heights_block_connect_disconnect)
54+
{
55+
CCoinJoinBroadcastTx dstx = MakeDSTX();
56+
auto& man = *Assert(Assert(m_node.cj_ctx)->dstxman);
57+
man.AddDSTX(dstx);
58+
59+
// Create a fake block containing the tx
60+
auto block = std::make_shared<CBlock>();
61+
block->vtx.push_back(dstx.tx);
62+
CBlockIndex index; index.nHeight = 100; uint256 bh = uint256S("0b"); index.phashBlock = &bh;
63+
64+
// Height should set to 100 on connect
65+
man.BlockConnected(block, &index);
66+
67+
// Height should clear on disconnect
68+
man.BlockDisconnected(block, nullptr);
69+
}
70+
71+
BOOST_AUTO_TEST_SUITE_END()
72+
73+

src/test/coinjoin_inouts_tests.cpp

Lines changed: 141 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,141 @@
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 <coinjoin/coinjoin.h>
13+
#include <coinjoin/common.h>
14+
#include <chainlock/chainlock.h>
15+
#include <chain.h>
16+
#include <llmq/context.h>
17+
#include <script/script.h>
18+
#include <uint256.h>
19+
#include <util/check.h>
20+
#include <util/time.h>
21+
22+
#include <boost/test/unit_test.hpp>
23+
24+
BOOST_FIXTURE_TEST_SUITE(coinjoin_inouts_tests, TestingSetup)
25+
26+
static CScript P2PKHScript(uint8_t tag = 0x01)
27+
{
28+
// OP_DUP OP_HASH160 <20-byte-tag> OP_EQUALVERIFY OP_CHECKSIG
29+
std::array<unsigned char, 20> hash{};
30+
hash.fill(tag);
31+
CScript spk;
32+
spk << OP_DUP << OP_HASH160 << std::vector<unsigned char>(hash.begin(), hash.end()) << OP_EQUALVERIFY << OP_CHECKSIG;
33+
return spk;
34+
}
35+
36+
BOOST_AUTO_TEST_CASE(broadcasttx_isvalidstructure_good_and_bad)
37+
{
38+
// Good: equal vin/vout sizes, vin count >= min participants, <= max*entry_size, P2PKH outputs with standard denominations
39+
CCoinJoinBroadcastTx good;
40+
{
41+
CMutableTransaction mtx;
42+
// Use min pool participants (e.g. 3). Build 3 inputs and 3 denominated outputs
43+
const int participants = std::max(3, CoinJoin::GetMinPoolParticipants());
44+
for (int i = 0; i < participants; ++i) {
45+
CTxIn in;
46+
in.prevout = COutPoint(uint256S("01"), static_cast<uint32_t>(i));
47+
mtx.vin.push_back(in);
48+
// Pick the smallest denomination
49+
CTxOut out{CoinJoin::GetSmallestDenomination(), P2PKHScript(static_cast<uint8_t>(i))};
50+
mtx.vout.push_back(out);
51+
}
52+
good.tx = MakeTransactionRef(mtx);
53+
good.m_protxHash = uint256::ONE; // at least one of (outpoint, protxhash) must be set
54+
}
55+
BOOST_CHECK(good.IsValidStructure());
56+
57+
// Bad: both identifiers null
58+
CCoinJoinBroadcastTx bad_ids = good;
59+
bad_ids.m_protxHash = uint256();
60+
bad_ids.masternodeOutpoint.SetNull();
61+
BOOST_CHECK(!bad_ids.IsValidStructure());
62+
63+
// Bad: vin/vout size mismatch
64+
CCoinJoinBroadcastTx bad_sizes = good;
65+
{
66+
CMutableTransaction mtx(*good.tx);
67+
mtx.vout.pop_back();
68+
bad_sizes.tx = MakeTransactionRef(mtx);
69+
}
70+
BOOST_CHECK(!bad_sizes.IsValidStructure());
71+
72+
// Bad: non-P2PKH output
73+
CCoinJoinBroadcastTx bad_script = good;
74+
{
75+
CMutableTransaction mtx(*good.tx);
76+
mtx.vout[0].scriptPubKey = CScript() << OP_RETURN << std::vector<unsigned char>{'x'};
77+
bad_script.tx = MakeTransactionRef(mtx);
78+
}
79+
BOOST_CHECK(!bad_script.IsValidStructure());
80+
81+
// Bad: non-denominated amount
82+
CCoinJoinBroadcastTx bad_amount = good;
83+
{
84+
CMutableTransaction mtx(*good.tx);
85+
mtx.vout[0].nValue = 42; // not a valid denom
86+
bad_amount.tx = MakeTransactionRef(mtx);
87+
}
88+
BOOST_CHECK(!bad_amount.IsValidStructure());
89+
}
90+
91+
BOOST_AUTO_TEST_CASE(queue_timeout_bounds)
92+
{
93+
CCoinJoinQueue dsq;
94+
dsq.nDenom = CoinJoin::AmountToDenomination(CoinJoin::GetSmallestDenomination());
95+
dsq.m_protxHash = uint256::ONE;
96+
dsq.nTime = GetAdjustedTime();
97+
// current time -> not out of bounds
98+
BOOST_CHECK(!dsq.IsTimeOutOfBounds());
99+
100+
// Too old (beyond COINJOIN_QUEUE_TIMEOUT)
101+
SetMockTime(GetTime() + (COINJOIN_QUEUE_TIMEOUT + 1));
102+
BOOST_CHECK(dsq.IsTimeOutOfBounds());
103+
104+
// Too far in the future
105+
SetMockTime(GetTime() - 2 * (COINJOIN_QUEUE_TIMEOUT + 1)); // move back to anchor baseline
106+
dsq.nTime = GetAdjustedTime() + (COINJOIN_QUEUE_TIMEOUT + 1);
107+
BOOST_CHECK(dsq.IsTimeOutOfBounds());
108+
109+
// Reset mock time
110+
SetMockTime(0);
111+
}
112+
113+
BOOST_AUTO_TEST_CASE(broadcasttx_expiry_height_logic)
114+
{
115+
// Build a valid-looking CCoinJoinBroadcastTx with confirmed height
116+
CCoinJoinBroadcastTx dstx;
117+
{
118+
CMutableTransaction mtx;
119+
const int participants = std::max(3, CoinJoin::GetMinPoolParticipants());
120+
for (int i = 0; i < participants; ++i) {
121+
mtx.vin.emplace_back(COutPoint(uint256S("02"), i));
122+
mtx.vout.emplace_back(CoinJoin::GetSmallestDenomination(), P2PKHScript(static_cast<uint8_t>(i)));
123+
}
124+
dstx.tx = MakeTransactionRef(mtx);
125+
dstx.m_protxHash = uint256::ONE;
126+
// mark as confirmed at height 100
127+
dstx.SetConfirmedHeight(100);
128+
}
129+
130+
// Minimal CBlockIndex with required fields
131+
// Create a minimal block index to satisfy the interface
132+
CBlockIndex index;
133+
uint256 blk_hash = uint256S("03");
134+
index.nHeight = 125; // 125 - 100 == 25 > 24 → expired by height
135+
index.phashBlock = &blk_hash;
136+
BOOST_CHECK(dstx.IsExpired(&index, *Assert(m_node.llmq_ctx->clhandler)));
137+
}
138+
139+
BOOST_AUTO_TEST_SUITE_END()
140+
141+

0 commit comments

Comments
 (0)