forked from bitcoin/bitcoin
-
Notifications
You must be signed in to change notification settings - Fork 1.2k
Expand file tree
/
Copy pathcoinjoin_inouts_tests.cpp
More file actions
179 lines (155 loc) · 6.54 KB
/
Copy pathcoinjoin_inouts_tests.cpp
File metadata and controls
179 lines (155 loc) · 6.54 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
// Copyright (c) 2025 The Dash Core developers
// Distributed under the MIT/X11 software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
#include <test/util/setup_common.h>
#include <algorithm>
#include <array>
#include <cstdint>
#include <vector>
#include <chain.h>
#include <coinjoin/coinjoin.h>
#include <coinjoin/common.h>
#include <evo/chainhelper.h>
#include <script/script.h>
#include <uint256.h>
#include <util/check.h>
#include <util/time.h>
#include <boost/test/unit_test.hpp>
BOOST_FIXTURE_TEST_SUITE(coinjoin_inouts_tests, TestingSetup)
static CScript P2PKHScript(uint8_t tag = 0x01)
{
// OP_DUP OP_HASH160 <20-byte-tag> OP_EQUALVERIFY OP_CHECKSIG
std::vector<unsigned char> hash(20, tag);
return CScript{} << OP_DUP << OP_HASH160 << hash << OP_EQUALVERIFY << OP_CHECKSIG;
}
BOOST_AUTO_TEST_CASE(broadcasttx_isvalidstructure_good_and_bad)
{
// Good: equal vin/vout sizes, vin count >= min participants, <= max*entry_size, P2PKH outputs with standard denominations
CCoinJoinBroadcastTx good;
{
CMutableTransaction mtx;
// Use min pool participants (e.g. 3). Build 3 inputs and 3 denominated outputs
const int participants = std::max(3, CoinJoin::GetMinPoolParticipants());
for (int i = 0; i < participants; ++i) {
CTxIn in;
in.prevout = COutPoint(uint256::ONE, static_cast<uint32_t>(i));
mtx.vin.push_back(in);
// Pick the smallest denomination
CTxOut out{CoinJoin::GetSmallestDenomination(), P2PKHScript(static_cast<uint8_t>(i))};
mtx.vout.push_back(out);
}
good.tx = MakeTransactionRef(mtx);
good.m_protxHash = uint256::ONE; // at least one of (outpoint, protxhash) must be set
}
BOOST_CHECK(good.IsValidStructure());
// Bad: both identifiers null
CCoinJoinBroadcastTx bad_ids = good;
bad_ids.m_protxHash = uint256{};
bad_ids.masternodeOutpoint.SetNull();
BOOST_CHECK(!bad_ids.IsValidStructure());
// Bad: vin/vout size mismatch
CCoinJoinBroadcastTx bad_sizes = good;
{
CMutableTransaction mtx(*good.tx);
mtx.vout.pop_back();
bad_sizes.tx = MakeTransactionRef(mtx);
}
BOOST_CHECK(!bad_sizes.IsValidStructure());
// Bad: non-P2PKH output
CCoinJoinBroadcastTx bad_script = good;
{
CMutableTransaction mtx(*good.tx);
mtx.vout[0].scriptPubKey = CScript() << OP_RETURN << std::vector<unsigned char>{'x'};
bad_script.tx = MakeTransactionRef(mtx);
}
BOOST_CHECK(!bad_script.IsValidStructure());
// Bad: non-denominated amount
CCoinJoinBroadcastTx bad_amount = good;
{
CMutableTransaction mtx(*good.tx);
mtx.vout[0].nValue = 42; // not a valid denom
bad_amount.tx = MakeTransactionRef(mtx);
}
BOOST_CHECK(!bad_amount.IsValidStructure());
}
BOOST_AUTO_TEST_CASE(entry_addscriptsig_matches_and_rejects)
{
// Build an entry with two distinct inputs so we can check both the match
// and the isolation (only the matching input mutates).
const COutPoint op0(uint256::ONE, 0);
const COutPoint op1(uint256::ONE, 1);
const uint32_t seq0 = 0xfffffffeU;
const uint32_t seq1 = 0xfffffffdU;
auto make_dsin = [](const COutPoint& op, uint32_t seq) {
CTxIn in(op);
in.nSequence = seq;
return CTxDSIn(in, P2PKHScript(0x10), /*nRounds=*/0);
};
std::vector<CTxDSIn> dsins{make_dsin(op0, seq0), make_dsin(op1, seq1)};
CCoinJoinEntry entry(dsins, /*vecTxOut=*/{}, CTransaction{CMutableTransaction{}});
// The scriptSig we expect to be copied across on a successful match.
const CScript scriptSig0 = CScript() << std::vector<unsigned char>{0xde, 0xad} << std::vector<unsigned char>{0xbe, 0xef};
// Matching prevout + matching sequence -> copies scriptSig, sets fHasSig.
{
CTxIn signed_in(op0, scriptSig0, seq0);
BOOST_CHECK(entry.AddScriptSig(signed_in));
BOOST_CHECK(entry.vecTxDSIn[0].fHasSig);
BOOST_CHECK(entry.vecTxDSIn[0].scriptSig == scriptSig0);
// Other input is untouched.
BOOST_CHECK(!entry.vecTxDSIn[1].fHasSig);
BOOST_CHECK(entry.vecTxDSIn[1].scriptSig.empty());
}
// Duplicate signature for the already-signed input -> rejected, no overwrite.
{
const CScript scriptSig_other = CScript() << std::vector<unsigned char>{0x01};
CTxIn dup_in(op0, scriptSig_other, seq0);
BOOST_CHECK(!entry.AddScriptSig(dup_in));
// Still holds the original signature.
BOOST_CHECK(entry.vecTxDSIn[0].scriptSig == scriptSig0);
BOOST_CHECK(entry.vecTxDSIn[0].fHasSig);
}
// Wrong prevout (sequence matches an existing input) -> rejected.
{
const COutPoint op_wrong(uint256S("ff"), 9);
CTxIn wrong_in(op_wrong, scriptSig0, seq1);
BOOST_CHECK(!entry.AddScriptSig(wrong_in));
BOOST_CHECK(!entry.vecTxDSIn[1].fHasSig);
BOOST_CHECK(entry.vecTxDSIn[1].scriptSig.empty());
}
// Right prevout but wrong sequence -> rejected (guards against malleated nSequence).
{
CTxIn badseq_in(op1, scriptSig0, /*nSequence=*/seq1 ^ 0xffU);
BOOST_CHECK(!entry.AddScriptSig(badseq_in));
BOOST_CHECK(!entry.vecTxDSIn[1].fHasSig);
BOOST_CHECK(entry.vecTxDSIn[1].scriptSig.empty());
}
// Correct prevout + correct sequence on the second input -> succeeds, doesn't disturb the first.
{
const CScript scriptSig1 = CScript() << std::vector<unsigned char>{0xca, 0xfe};
CTxIn signed_in(op1, scriptSig1, seq1);
BOOST_CHECK(entry.AddScriptSig(signed_in));
BOOST_CHECK(entry.vecTxDSIn[1].fHasSig);
BOOST_CHECK(entry.vecTxDSIn[1].scriptSig == scriptSig1);
// First input unchanged.
BOOST_CHECK(entry.vecTxDSIn[0].scriptSig == scriptSig0);
}
}
BOOST_AUTO_TEST_CASE(queue_timeout_bounds)
{
CCoinJoinQueue dsq;
dsq.nDenom = CoinJoin::AmountToDenomination(CoinJoin::GetSmallestDenomination());
dsq.m_protxHash = uint256::ONE;
dsq.nTime = GetAdjustedTime();
// current time -> not out of bounds
BOOST_CHECK(!dsq.IsTimeOutOfBounds());
// Too old (beyond COINJOIN_QUEUE_TIMEOUT)
SetMockTime(GetTime() + (COINJOIN_QUEUE_TIMEOUT + 1));
BOOST_CHECK(dsq.IsTimeOutOfBounds());
// Too far in the future
SetMockTime(GetTime() - 2 * (COINJOIN_QUEUE_TIMEOUT + 1)); // move back to anchor baseline
dsq.nTime = GetAdjustedTime() + (COINJOIN_QUEUE_TIMEOUT + 1);
BOOST_CHECK(dsq.IsTimeOutOfBounds());
// Reset mock time
SetMockTime(0);
}
BOOST_AUTO_TEST_SUITE_END()