fork_block_tests.cpp raw
1 // Copyright (c) 2025 The Limenka 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/fork_util.h>
6
7 #include <chain.h>
8 #include <chainparams.h>
9 #include <consensus/merkle.h>
10 #include <consensus/params.h>
11 #include <consensus/validation.h>
12 #include <consensus/delay.h>
13 #include <pow.h>
14 #include <pow_fork.h>
15 #include <primitives/block.h>
16 #include <script/interpreter.h>
17 #include <script/script.h>
18 #include <validation.h>
19
20 #include <boost/test/unit_test.hpp>
21
22 BOOST_FIXTURE_TEST_SUITE(fork_block_tests, ForkTestingSetup)
23
24 // ---------------------------------------------------------------------------
25 // Single-lane DAA: target after block
26 // ---------------------------------------------------------------------------
27
28 BOOST_AUTO_TEST_CASE(fork_block_daa_single_target)
29 {
30 const auto& p = ForkConsensus();
31 const arith_uint256 seed = UintToArith256(p.powLimit);
32
33 CBlockHeader hdr;
34 hdr.nVersion = 0;
35 hdr.nBits = seed.GetCompact();
36 hdr.nTime = 1632; // 632s after first block (near 600s target)
37
38 CBlockIndex idx(hdr);
39 InitForkDAAState(&idx);
40 idx.nForkLastBlockTime = 1000;
41
42 // Mine one block near target cadence (error ≈ 0).
43 uint32_t nbits = CalculateForkTarget(&idx, &hdr, p);
44
45 BOOST_CHECK_GT(nbits, 0u);
46 BOOST_CHECK_GT(idx.nForkTarget, arith_uint256(0));
47 }
48
49 // ---------------------------------------------------------------------------
50 // Witness discount removal
51 // ---------------------------------------------------------------------------
52
53 BOOST_AUTO_TEST_CASE(fork_block_witness_weight)
54 {
55 // Build a block with witness data and compare weight with/without
56 // fork active flag.
57 CMutableTransaction cb;
58 cb.vin.resize(1);
59 cb.vin[0].prevout.SetNull();
60 cb.vout.resize(1);
61 cb.vout[0].scriptPubKey = CScript() << OP_TRUE;
62 cb.vout[0].nValue = 50 * COIN;
63 cb.vin[0].scriptWitness.stack.push_back(std::vector<uint8_t>(32, 0x42));
64
65 CBlock block;
66 block.vtx.push_back(MakeTransactionRef(cb));
67 block.nVersion = 0;
68 block.nTime = 1000;
69 block.hashMerkleRoot = BlockMerkleRoot(block);
70
71 int64_t w_fork = GetBlockWeight(block, true);
72 int64_t w_std = GetBlockWeight(block, false);
73 // With witness, fork weight should be SMALLER (raw size, no 3x discount).
74 // Standard weight: stripped*3 + total. Fork weight: just total.
75 BOOST_CHECK_LE(w_fork, w_std);
76 }
77
78 // ---------------------------------------------------------------------------
79 // P2BPCT tier rejection: script recognition
80 // ---------------------------------------------------------------------------
81
82 BOOST_AUTO_TEST_CASE(fork_block_p2bpct_script_recognition)
83 {
84 // P2BPCT output is witness v4 (OP_4) + 33-byte Pedersen commitment.
85 CScript p2bpct;
86 p2bpct << OP_4 << std::vector<uint8_t>(WITNESS_V4_BPCT_SIZE, 0x42);
87
88 int witver; std::vector<uint8_t> witprog;
89 BOOST_CHECK(p2bpct.IsWitnessProgram(witver, witprog));
90 BOOST_CHECK_EQUAL(witver, 4);
91 BOOST_CHECK_EQUAL(witprog.size(), size_t(WITNESS_V4_BPCT_SIZE));
92 }
93
94 // ---------------------------------------------------------------------------
95 // Replay protection constants
96 // ---------------------------------------------------------------------------
97
98 BOOST_AUTO_TEST_CASE(fork_block_replay_constants)
99 {
100 }
101
102 // ---------------------------------------------------------------------------
103 // Fork reward calculation
104 // ---------------------------------------------------------------------------
105
106 BOOST_AUTO_TEST_CASE(fork_block_reward_calculation)
107 {
108 // Time-proportional reward: R(e) = R_full * e / 600.
109 // At e = 600 the block pays the full pre-halving subsidy.
110 CAmount r = GetForkBlockSubsidy(600, 0, 210000);
111 BOOST_CHECK_EQUAL(r, 50 * COIN);
112 // Early block: half the interval, half the reward.
113 BOOST_CHECK_EQUAL(GetForkBlockSubsidy(300, 0, 210000), 25 * COIN);
114 // Late block: uncapped catch-up.
115 BOOST_CHECK_EQUAL(GetForkBlockSubsidy(1200, 0, 210000), 100 * COIN);
116
117 // Post-halving: after 4 years of aggregate seconds.
118 int64_t agg = 600LL * 210000 + 1;
119 CAmount r_pre = GetForkBlockSubsidy(600, 0, 210000);
120 CAmount r_post = GetForkBlockSubsidy(600, agg, 210000);
121 BOOST_CHECK_EQUAL(r_post, r_pre / 2);
122 }
123
124 // ---------------------------------------------------------------------------
125 // Delay output coinbase parser
126 // ---------------------------------------------------------------------------
127
128 BOOST_AUTO_TEST_CASE(fork_block_delay_output_parser)
129 {
130 // Create a coinbase with the delay OP_RETURN and verify parsing.
131 CMutableTransaction cb;
132 cb.vin.resize(1);
133 cb.vin[0].prevout.SetNull();
134
135 // Delay output: OP_RETURN <"LD"> <8-byte LE value>
136 CTxOut delay_out;
137 delay_out.nValue = 0;
138 std::vector<uint8_t> delay_magic = {DELAY_MAGIC_BYTE0, DELAY_MAGIC_BYTE1};
139 std::vector<uint8_t> delay_bytes = {0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
140 delay_out.scriptPubKey << OP_RETURN << delay_magic << delay_bytes;
141 cb.vout.push_back(delay_out);
142
143 // Add a regular output after the delay commitment.
144 cb.vout.push_back(CTxOut(50 * COIN, CScript() << OP_TRUE));
145
146 CTransaction tx(cb);
147 int idx = GetDelayOutputIndex(tx);
148 BOOST_CHECK_GE(idx, 0);
149 BOOST_CHECK_EQUAL(idx, 0);
150 auto val = GetDelayOutputValue(tx.vout[idx]);
151 BOOST_CHECK(val.has_value());
152 BOOST_CHECK_EQUAL(*val, uint64_t(1));
153 }
154
155 // ---------------------------------------------------------------------------
156 // Fork block weight: time-proportional payload, no fixed cap
157 // ---------------------------------------------------------------------------
158
159 BOOST_AUTO_TEST_CASE(fork_block_weight_limit)
160 {
161 // Fork blocks have no fixed weight cap (decision 5): the payload
162 // limit is time-proportional. The parent 4MW constant remains as
163 // the base of the payload formula.
164 BOOST_CHECK_EQUAL(MAX_BLOCK_WEIGHT, 4'000'000);
165 BOOST_CHECK_EQUAL(GetForkPayloadWeightLimit(600, MAX_BLOCK_WEIGHT), 4'000'000);
166 BOOST_CHECK_EQUAL(GetForkPayloadWeightLimit(300, MAX_BLOCK_WEIGHT), 2'000'000);
167 BOOST_CHECK_EQUAL(GetForkPayloadWeightLimit(1200, MAX_BLOCK_WEIGHT), 8'000'000);
168 }
169
170 // ---------------------------------------------------------------------------
171 BOOST_AUTO_TEST_CASE(fork_block_p2bpct_coinbase_exclusion)
172 {
173 // A P2BPCT output is witness v4 with a 32-byte program.
174 CScript p2bpct;
175 p2bpct << OP_4 << std::vector<uint8_t>(WITNESS_V4_BPCT_SIZE, 0x42);
176
177 int witver; std::vector<uint8_t> witprog;
178 BOOST_CHECK(p2bpct.IsWitnessProgram(witver, witprog));
179 BOOST_CHECK_EQUAL(witver, 4);
180 BOOST_CHECK_EQUAL(witprog.size(), WITNESS_V4_BPCT_SIZE);
181
182 // A delay OP_RETURN output is NOT a witness program, so a coinbase
183 // carrying the delay commitment + plain outputs is clean.
184 CTxOut delay_out;
185 delay_out.nValue = 0;
186 std::vector<uint8_t> magic = {DELAY_MAGIC_BYTE0, DELAY_MAGIC_BYTE1};
187 delay_out.scriptPubKey << OP_RETURN << magic << std::vector<uint8_t>(8, 0);
188 BOOST_CHECK(!delay_out.scriptPubKey.IsWitnessProgram(witver, witprog));
189 }
190
191 // ---------------------------------------------------------------------------
192 // Delay computation correctness (reference values computed with Python
193 // bigints from the same construction)
194 // ---------------------------------------------------------------------------
195
196 BOOST_AUTO_TEST_CASE(fork_block_delay_computation)
197 {
198 // prev_hash = 00 01 02 ... 1f (in-memory bytes 00..1f)
199 uint256 prev_hash;
200 for (int i = 0; i < 32; i++) prev_hash.begin()[i] = (uint8_t)i;
201
202 BOOST_CHECK_EQUAL(ComputeDelay(prev_hash, 1), uint64_t(0x0407618d7e9d02f7ULL));
203 BOOST_CHECK_EQUAL(ComputeDelay(prev_hash, 10), uint64_t(0x026edfffac915932ULL));
204 BOOST_CHECK_EQUAL(ComputeDelay(prev_hash, 100), uint64_t(0x0525b3de31e9a6d4ULL));
205 BOOST_CHECK_EQUAL(ComputeDelay(prev_hash, 1024), uint64_t(0x01eb57199ada2befULL));
206 }
207
208 // ---------------------------------------------------------------------------
209 // Activation-boundary epoch seeding: the fork continues the parent's
210 // halving schedule instead of restarting at 50 BTC
211 // ---------------------------------------------------------------------------
212
213 BOOST_AUTO_TEST_CASE(fork_block_epoch_seeding)
214 {
215 // At the activation boundary, ConnectBlock seeds the aggregate
216 // seconds from chain_height * 600. At height 966501 (post-4th
217 // halving) the full-interval subsidy must be 3.125 BTC, matching
218 // the parent chain's schedule.
219 const int halving_interval = 210000;
220 int64_t agg_base = int64_t(966501) * 600;
221 CAmount sub = GetForkBlockSubsidy(600, agg_base, halving_interval);
222 BOOST_CHECK_EQUAL(sub, CAmount(312500000)); // 3.125 BTC
223 // Early block at that height: half interval, half reward.
224 BOOST_CHECK_EQUAL(GetForkBlockSubsidy(300, agg_base, halving_interval), CAmount(156250000));
225 // Pre-activation restart (agg=0) would pay 50 BTC - the seed
226 // prevents exactly this.
227 BOOST_CHECK_EQUAL(GetForkBlockSubsidy(600, 0, halving_interval), 50 * COIN);
228 }
229
230 BOOST_AUTO_TEST_SUITE_END()
231