versionbits_tests.cpp raw
1 // Copyright (c) 2014-2022 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 <chain.h>
6 #include <chainparams.h>
7 #include <consensus/params.h>
8 #include <test/util/random.h>
9 #include <test/util/setup_common.h>
10 #include <util/chaintype.h>
11 #include <versionbits.h>
12
13 #include <boost/test/unit_test.hpp>
14
15 /* Define a virtual block time, one block per 10 minutes after Nov 14 2014, 0:55:36am */
16 static int32_t TestTime(int nHeight) { return 1415926536 + 600 * nHeight; }
17
18 static std::string StateName(ThresholdState state)
19 {
20 switch (state) {
21 case ThresholdState::DEFINED: return "DEFINED";
22 case ThresholdState::STARTED: return "STARTED";
23 case ThresholdState::LOCKED_IN: return "LOCKED_IN";
24 case ThresholdState::ACTIVE: return "ACTIVE";
25 case ThresholdState::FAILED: return "FAILED";
26 case ThresholdState::EXPIRED: return "EXPIRED";
27 } // no default case, so the compiler can warn about missing cases
28 return "";
29 }
30
31 static const Consensus::Params paramsDummy = Consensus::Params();
32
33 class TestConditionChecker : public AbstractThresholdConditionChecker
34 {
35 private:
36 mutable ThresholdConditionCache cache;
37
38 public:
39 int64_t BeginTime(const Consensus::Params& params) const override { return TestTime(10000); }
40 int64_t EndTime(const Consensus::Params& params) const override { return TestTime(20000); }
41 int Period(const Consensus::Params& params) const override { return 1000; }
42 int Threshold(const Consensus::Params& params) const override { return 900; }
43 bool Condition(const CBlockIndex* pindex, const Consensus::Params& params) const override { return (pindex->nVersion & 0x100); }
44
45 ThresholdState GetStateFor(const CBlockIndex* pindexPrev) const { return AbstractThresholdConditionChecker::GetStateFor(pindexPrev, paramsDummy, cache); }
46 int GetStateSinceHeightFor(const CBlockIndex* pindexPrev) const { return AbstractThresholdConditionChecker::GetStateSinceHeightFor(pindexPrev, paramsDummy, cache); }
47 };
48
49 class TestDelayedActivationConditionChecker : public TestConditionChecker
50 {
51 public:
52 int MinActivationHeight(const Consensus::Params& params) const override { return 15000; }
53 };
54
55 class TestAlwaysActiveConditionChecker : public TestConditionChecker
56 {
57 public:
58 int64_t BeginTime(const Consensus::Params& params) const override { return Consensus::BIP9Deployment::ALWAYS_ACTIVE; }
59 };
60
61 class TestNeverActiveConditionChecker : public TestConditionChecker
62 {
63 public:
64 int64_t BeginTime(const Consensus::Params& params) const override { return Consensus::BIP9Deployment::NEVER_ACTIVE; }
65 };
66
67 #define CHECKERS 6
68
69 class VersionBitsTester
70 {
71 FastRandomContext& m_rng;
72 // A fake blockchain
73 std::vector<CBlockIndex*> vpblock;
74
75 // 6 independent checkers for the same bit.
76 // The first one performs all checks, the second only 50%, the third only 25%, etc...
77 // This is to test whether lack of cached information leads to the same results.
78 TestConditionChecker checker[CHECKERS];
79 // Another 6 that assume delayed activation
80 TestDelayedActivationConditionChecker checker_delayed[CHECKERS];
81 // Another 6 that assume always active activation
82 TestAlwaysActiveConditionChecker checker_always[CHECKERS];
83 // Another 6 that assume never active activation
84 TestNeverActiveConditionChecker checker_never[CHECKERS];
85
86 // Test counter (to identify failures)
87 int num{1000};
88
89 public:
90 VersionBitsTester(FastRandomContext& rng) : m_rng{rng} {}
91
92 VersionBitsTester& Reset() {
93 // Have each group of tests be counted by the 1000s part, starting at 1000
94 num = num - (num % 1000) + 1000;
95
96 for (unsigned int i = 0; i < vpblock.size(); i++) {
97 delete vpblock[i];
98 }
99 for (unsigned int i = 0; i < CHECKERS; i++) {
100 checker[i] = TestConditionChecker();
101 checker_delayed[i] = TestDelayedActivationConditionChecker();
102 checker_always[i] = TestAlwaysActiveConditionChecker();
103 checker_never[i] = TestNeverActiveConditionChecker();
104 }
105 vpblock.clear();
106 return *this;
107 }
108
109 ~VersionBitsTester() {
110 Reset();
111 }
112
113 VersionBitsTester& Mine(unsigned int height, int32_t nTime, int32_t nVersion) {
114 while (vpblock.size() < height) {
115 CBlockIndex* pindex = new CBlockIndex();
116 pindex->nHeight = vpblock.size();
117 pindex->pprev = Tip();
118 pindex->nTime = nTime;
119 pindex->nVersion = nVersion;
120 pindex->BuildSkip();
121 vpblock.push_back(pindex);
122 }
123 return *this;
124 }
125
126 VersionBitsTester& TestStateSinceHeight(int height)
127 {
128 return TestStateSinceHeight(height, height);
129 }
130
131 VersionBitsTester& TestStateSinceHeight(int height, int height_delayed)
132 {
133 const CBlockIndex* tip = Tip();
134 for (int i = 0; i < CHECKERS; i++) {
135 if (m_rng.randbits(i) == 0) {
136 BOOST_CHECK_MESSAGE(checker[i].GetStateSinceHeightFor(tip) == height, strprintf("Test %i for StateSinceHeight", num));
137 BOOST_CHECK_MESSAGE(checker_delayed[i].GetStateSinceHeightFor(tip) == height_delayed, strprintf("Test %i for StateSinceHeight (delayed)", num));
138 BOOST_CHECK_MESSAGE(checker_always[i].GetStateSinceHeightFor(tip) == 0, strprintf("Test %i for StateSinceHeight (always active)", num));
139 BOOST_CHECK_MESSAGE(checker_never[i].GetStateSinceHeightFor(tip) == 0, strprintf("Test %i for StateSinceHeight (never active)", num));
140 }
141 }
142 num++;
143 return *this;
144 }
145
146 VersionBitsTester& TestState(ThresholdState exp)
147 {
148 return TestState(exp, exp);
149 }
150
151 VersionBitsTester& TestState(ThresholdState exp, ThresholdState exp_delayed)
152 {
153 if (exp != exp_delayed) {
154 // only expected differences are that delayed stays in locked_in longer
155 BOOST_CHECK_EQUAL(exp, ThresholdState::ACTIVE);
156 BOOST_CHECK_EQUAL(exp_delayed, ThresholdState::LOCKED_IN);
157 }
158
159 const CBlockIndex* pindex = Tip();
160 for (int i = 0; i < CHECKERS; i++) {
161 if (m_rng.randbits(i) == 0) {
162 ThresholdState got = checker[i].GetStateFor(pindex);
163 ThresholdState got_delayed = checker_delayed[i].GetStateFor(pindex);
164 ThresholdState got_always = checker_always[i].GetStateFor(pindex);
165 ThresholdState got_never = checker_never[i].GetStateFor(pindex);
166 // nHeight of the next block. If vpblock is empty, the next (ie first)
167 // block should be the genesis block with nHeight == 0.
168 int height = pindex == nullptr ? 0 : pindex->nHeight + 1;
169 BOOST_CHECK_MESSAGE(got == exp, strprintf("Test %i for %s height %d (got %s)", num, StateName(exp), height, StateName(got)));
170 BOOST_CHECK_MESSAGE(got_delayed == exp_delayed, strprintf("Test %i for %s height %d (got %s; delayed case)", num, StateName(exp_delayed), height, StateName(got_delayed)));
171 BOOST_CHECK_MESSAGE(got_always == ThresholdState::ACTIVE, strprintf("Test %i for ACTIVE height %d (got %s; always active case)", num, height, StateName(got_always)));
172 BOOST_CHECK_MESSAGE(got_never == ThresholdState::FAILED, strprintf("Test %i for FAILED height %d (got %s; never active case)", num, height, StateName(got_never)));
173 }
174 }
175 num++;
176 return *this;
177 }
178
179 VersionBitsTester& TestDefined() { return TestState(ThresholdState::DEFINED); }
180 VersionBitsTester& TestStarted() { return TestState(ThresholdState::STARTED); }
181 VersionBitsTester& TestLockedIn() { return TestState(ThresholdState::LOCKED_IN); }
182 VersionBitsTester& TestActive() { return TestState(ThresholdState::ACTIVE); }
183 VersionBitsTester& TestFailed() { return TestState(ThresholdState::FAILED); }
184
185 // non-delayed should be active; delayed should still be locked in
186 VersionBitsTester& TestActiveDelayed() { return TestState(ThresholdState::ACTIVE, ThresholdState::LOCKED_IN); }
187
188 CBlockIndex* Tip() { return vpblock.empty() ? nullptr : vpblock.back(); }
189 };
190
191 BOOST_FIXTURE_TEST_SUITE(versionbits_tests, BasicTestingSetup)
192
193 BOOST_AUTO_TEST_CASE(versionbits_test)
194 {
195 for (int i = 0; i < 64; i++) {
196 // DEFINED -> STARTED after timeout reached -> FAILED
197 VersionBitsTester(m_rng).TestDefined().TestStateSinceHeight(0)
198 .Mine(1, TestTime(1), 0x100).TestDefined().TestStateSinceHeight(0)
199 .Mine(11, TestTime(11), 0x100).TestDefined().TestStateSinceHeight(0)
200 .Mine(989, TestTime(989), 0x100).TestDefined().TestStateSinceHeight(0)
201 .Mine(999, TestTime(20000), 0x100).TestDefined().TestStateSinceHeight(0) // Timeout and start time reached simultaneously
202 .Mine(1000, TestTime(20000), 0).TestStarted().TestStateSinceHeight(1000) // Hit started, stop signalling
203 .Mine(1999, TestTime(30001), 0).TestStarted().TestStateSinceHeight(1000)
204 .Mine(2000, TestTime(30002), 0x100).TestFailed().TestStateSinceHeight(2000) // Hit failed, start signalling again
205 .Mine(2001, TestTime(30003), 0x100).TestFailed().TestStateSinceHeight(2000)
206 .Mine(2999, TestTime(30004), 0x100).TestFailed().TestStateSinceHeight(2000)
207 .Mine(3000, TestTime(30005), 0x100).TestFailed().TestStateSinceHeight(2000)
208 .Mine(4000, TestTime(30006), 0x100).TestFailed().TestStateSinceHeight(2000)
209
210 // DEFINED -> STARTED -> FAILED
211 .Reset().TestDefined().TestStateSinceHeight(0)
212 .Mine(1, TestTime(1), 0).TestDefined().TestStateSinceHeight(0)
213 .Mine(1000, TestTime(10000) - 1, 0x100).TestDefined().TestStateSinceHeight(0) // One second more and it would be defined
214 .Mine(2000, TestTime(10000), 0x100).TestStarted().TestStateSinceHeight(2000) // So that's what happens the next period
215 .Mine(2051, TestTime(10010), 0).TestStarted().TestStateSinceHeight(2000) // 51 old blocks
216 .Mine(2950, TestTime(10020), 0x100).TestStarted().TestStateSinceHeight(2000) // 899 new blocks
217 .Mine(3000, TestTime(20000), 0).TestFailed().TestStateSinceHeight(3000) // 50 old blocks (so 899 out of the past 1000)
218 .Mine(4000, TestTime(20010), 0x100).TestFailed().TestStateSinceHeight(3000)
219
220 // DEFINED -> STARTED -> LOCKEDIN after timeout reached -> ACTIVE
221 .Reset().TestDefined().TestStateSinceHeight(0)
222 .Mine(1, TestTime(1), 0).TestDefined().TestStateSinceHeight(0)
223 .Mine(1000, TestTime(10000) - 1, 0x101).TestDefined().TestStateSinceHeight(0) // One second more and it would be defined
224 .Mine(2000, TestTime(10000), 0x101).TestStarted().TestStateSinceHeight(2000) // So that's what happens the next period
225 .Mine(2999, TestTime(30000), 0x100).TestStarted().TestStateSinceHeight(2000) // 999 new blocks
226 .Mine(3000, TestTime(30000), 0x100).TestLockedIn().TestStateSinceHeight(3000) // 1 new block (so 1000 out of the past 1000 are new)
227 .Mine(3999, TestTime(30001), 0).TestLockedIn().TestStateSinceHeight(3000)
228 .Mine(4000, TestTime(30002), 0).TestActiveDelayed().TestStateSinceHeight(4000, 3000)
229 .Mine(14333, TestTime(30003), 0).TestActiveDelayed().TestStateSinceHeight(4000, 3000)
230 .Mine(24000, TestTime(40000), 0).TestActive().TestStateSinceHeight(4000, 15000)
231
232 // DEFINED -> STARTED -> LOCKEDIN before timeout -> ACTIVE
233 .Reset().TestDefined()
234 .Mine(1, TestTime(1), 0).TestDefined().TestStateSinceHeight(0)
235 .Mine(1000, TestTime(10000) - 1, 0x101).TestDefined().TestStateSinceHeight(0) // One second more and it would be defined
236 .Mine(2000, TestTime(10000), 0x101).TestStarted().TestStateSinceHeight(2000) // So that's what happens the next period
237 .Mine(2050, TestTime(10010), 0x200).TestStarted().TestStateSinceHeight(2000) // 50 old blocks
238 .Mine(2950, TestTime(10020), 0x100).TestStarted().TestStateSinceHeight(2000) // 900 new blocks
239 .Mine(2999, TestTime(19999), 0x200).TestStarted().TestStateSinceHeight(2000) // 49 old blocks
240 .Mine(3000, TestTime(29999), 0x200).TestLockedIn().TestStateSinceHeight(3000) // 1 old block (so 900 out of the past 1000)
241 .Mine(3999, TestTime(30001), 0).TestLockedIn().TestStateSinceHeight(3000)
242 .Mine(4000, TestTime(30002), 0).TestActiveDelayed().TestStateSinceHeight(4000, 3000) // delayed will not become active until height=15000
243 .Mine(14333, TestTime(30003), 0).TestActiveDelayed().TestStateSinceHeight(4000, 3000)
244 .Mine(15000, TestTime(40000), 0).TestActive().TestStateSinceHeight(4000, 15000)
245 .Mine(24000, TestTime(40000), 0).TestActive().TestStateSinceHeight(4000, 15000)
246
247 // DEFINED multiple periods -> STARTED multiple periods -> FAILED
248 .Reset().TestDefined().TestStateSinceHeight(0)
249 .Mine(999, TestTime(999), 0).TestDefined().TestStateSinceHeight(0)
250 .Mine(1000, TestTime(1000), 0).TestDefined().TestStateSinceHeight(0)
251 .Mine(2000, TestTime(2000), 0).TestDefined().TestStateSinceHeight(0)
252 .Mine(3000, TestTime(10000), 0).TestStarted().TestStateSinceHeight(3000)
253 .Mine(4000, TestTime(10000), 0).TestStarted().TestStateSinceHeight(3000)
254 .Mine(5000, TestTime(10000), 0).TestStarted().TestStateSinceHeight(3000)
255 .Mine(5999, TestTime(20000), 0).TestStarted().TestStateSinceHeight(3000)
256 .Mine(6000, TestTime(20000), 0).TestFailed().TestStateSinceHeight(6000)
257 .Mine(7000, TestTime(20000), 0x100).TestFailed().TestStateSinceHeight(6000)
258 .Mine(24000, TestTime(20000), 0x100).TestFailed().TestStateSinceHeight(6000) // stay in FAILED no matter how much we signal
259 ;
260 }
261 }
262
263 struct BlockVersionTest : BasicTestingSetup {
264 /** Check that ComputeBlockVersion will set the appropriate bit correctly */
265 void check_computeblockversion(VersionBitsCache& versionbitscache, const Consensus::Params& params, Consensus::DeploymentPos dep)
266 {
267 // Clear the cache every time
268 versionbitscache.Clear();
269
270 int64_t bit = params.vDeployments[dep].bit;
271 int64_t nStartTime = params.vDeployments[dep].nStartTime;
272 int64_t nTimeout = params.vDeployments[dep].nTimeout;
273 int min_activation_height = params.vDeployments[dep].min_activation_height;
274
275 // should not be any signalling for first block
276 BOOST_CHECK_EQUAL(versionbitscache.ComputeBlockVersion(nullptr, params), VERSIONBITS_TOP_BITS);
277
278 // always/never active deployments shouldn't need to be tested further
279 if (nStartTime == Consensus::BIP9Deployment::ALWAYS_ACTIVE ||
280 nStartTime == Consensus::BIP9Deployment::NEVER_ACTIVE)
281 {
282 BOOST_CHECK_EQUAL(min_activation_height, 0);
283 BOOST_CHECK_EQUAL(nTimeout, Consensus::BIP9Deployment::NO_TIMEOUT);
284 return;
285 }
286
287 BOOST_REQUIRE(nStartTime < nTimeout);
288 BOOST_REQUIRE(nStartTime >= 0);
289 BOOST_REQUIRE(nTimeout <= std::numeric_limits<uint32_t>::max() || nTimeout == Consensus::BIP9Deployment::NO_TIMEOUT);
290 BOOST_REQUIRE(0 <= bit && bit < 32);
291 // Make sure that no deployment tries to set an invalid bit.
292 BOOST_REQUIRE(((1 << bit) & VERSIONBITS_TOP_MASK) == 0);
293 BOOST_REQUIRE(min_activation_height >= 0);
294 // Check min_activation_height is on a retarget boundary
295 BOOST_REQUIRE_EQUAL(min_activation_height % params.nMinerConfirmationWindow, 0U);
296
297 const uint32_t bitmask{versionbitscache.Mask(params, dep)};
298 BOOST_CHECK_EQUAL(bitmask, uint32_t{1} << bit);
299
300 // In the first chain, test that the bit is set by CBV until it has failed.
301 // In the second chain, test the bit is set by CBV while STARTED and
302 // LOCKED-IN, and then no longer set while ACTIVE.
303 VersionBitsTester firstChain{m_rng}, secondChain{m_rng};
304
305 int64_t nTime = nStartTime;
306
307 const CBlockIndex *lastBlock = nullptr;
308
309 // Before MedianTimePast of the chain has crossed nStartTime, the bit
310 // should not be set.
311 if (nTime == 0) {
312 // since CBlockIndex::nTime is uint32_t we can't represent any
313 // earlier time, so will transition from DEFINED to STARTED at the
314 // end of the first period by mining blocks at nTime == 0
315 lastBlock = firstChain.Mine(params.nMinerConfirmationWindow - 1, nTime, VERSIONBITS_LAST_OLD_BLOCK_VERSION).Tip();
316 BOOST_CHECK_EQUAL(versionbitscache.ComputeBlockVersion(lastBlock, params) & (1 << bit), 0);
317 lastBlock = firstChain.Mine(params.nMinerConfirmationWindow, nTime, VERSIONBITS_LAST_OLD_BLOCK_VERSION).Tip();
318 BOOST_CHECK((versionbitscache.ComputeBlockVersion(lastBlock, params) & (1 << bit)) != 0);
319 // then we'll keep mining at nStartTime...
320 } else {
321 // use a time 1s earlier than start time to check we stay DEFINED
322 --nTime;
323
324 // Start generating blocks before nStartTime
325 lastBlock = firstChain.Mine(params.nMinerConfirmationWindow, nTime, VERSIONBITS_LAST_OLD_BLOCK_VERSION).Tip();
326 BOOST_CHECK_EQUAL(versionbitscache.ComputeBlockVersion(lastBlock, params) & (1 << bit), 0);
327
328 // Mine more blocks (4 less than the adjustment period) at the old time, and check that CBV isn't setting the bit yet.
329 for (uint32_t i = 1; i < params.nMinerConfirmationWindow - 4; i++) {
330 lastBlock = firstChain.Mine(params.nMinerConfirmationWindow + i, nTime, VERSIONBITS_LAST_OLD_BLOCK_VERSION).Tip();
331 BOOST_CHECK_EQUAL(versionbitscache.ComputeBlockVersion(lastBlock, params) & (1 << bit), 0);
332 }
333 // Now mine 5 more blocks at the start time -- MTP should not have passed yet, so
334 // CBV should still not yet set the bit.
335 nTime = nStartTime;
336 for (uint32_t i = params.nMinerConfirmationWindow - 4; i <= params.nMinerConfirmationWindow; i++) {
337 lastBlock = firstChain.Mine(params.nMinerConfirmationWindow + i, nTime, VERSIONBITS_LAST_OLD_BLOCK_VERSION).Tip();
338 BOOST_CHECK_EQUAL(versionbitscache.ComputeBlockVersion(lastBlock, params) & (1 << bit), 0);
339 }
340 // Next we will advance to the next period and transition to STARTED,
341 }
342
343 lastBlock = firstChain.Mine(params.nMinerConfirmationWindow * 3, nTime, VERSIONBITS_LAST_OLD_BLOCK_VERSION).Tip();
344 // so ComputeBlockVersion should now set the bit,
345 BOOST_CHECK((versionbitscache.ComputeBlockVersion(lastBlock, params) & (1 << bit)) != 0);
346 // and should also be using the VERSIONBITS_TOP_BITS.
347 BOOST_CHECK_EQUAL(versionbitscache.ComputeBlockVersion(lastBlock, params) & VERSIONBITS_TOP_MASK, VERSIONBITS_TOP_BITS);
348
349 // Check that ComputeBlockVersion will set the bit until nTimeout
350 nTime += 600;
351 uint32_t blocksToMine = params.nMinerConfirmationWindow * 2; // test blocks for up to 2 time periods
352 uint32_t nHeight = params.nMinerConfirmationWindow * 3;
353 // These blocks are all before nTimeout is reached.
354 while (nTime < nTimeout && blocksToMine > 0) {
355 lastBlock = firstChain.Mine(nHeight+1, nTime, VERSIONBITS_LAST_OLD_BLOCK_VERSION).Tip();
356 BOOST_CHECK((versionbitscache.ComputeBlockVersion(lastBlock, params) & (1 << bit)) != 0);
357 BOOST_CHECK_EQUAL(versionbitscache.ComputeBlockVersion(lastBlock, params) & VERSIONBITS_TOP_MASK, VERSIONBITS_TOP_BITS);
358 blocksToMine--;
359 nTime += 600;
360 nHeight += 1;
361 }
362
363 if (nTimeout != Consensus::BIP9Deployment::NO_TIMEOUT) {
364 // can reach any nTimeout other than NO_TIMEOUT due to earlier BOOST_REQUIRE
365
366 nTime = nTimeout;
367
368 // finish the last period before we start timing out
369 while (nHeight % params.nMinerConfirmationWindow != 0) {
370 lastBlock = firstChain.Mine(nHeight+1, nTime - 1, VERSIONBITS_LAST_OLD_BLOCK_VERSION).Tip();
371 BOOST_CHECK((versionbitscache.ComputeBlockVersion(lastBlock, params) & (1 << bit)) != 0);
372 nHeight += 1;
373 }
374
375 // FAILED is only triggered at the end of a period, so CBV should be setting
376 // the bit until the period transition.
377 for (uint32_t i = 0; i < params.nMinerConfirmationWindow - 1; i++) {
378 lastBlock = firstChain.Mine(nHeight+1, nTime, VERSIONBITS_LAST_OLD_BLOCK_VERSION).Tip();
379 BOOST_CHECK((versionbitscache.ComputeBlockVersion(lastBlock, params) & (1 << bit)) != 0);
380 nHeight += 1;
381 }
382 // The next block should trigger no longer setting the bit.
383 lastBlock = firstChain.Mine(nHeight+1, nTime, VERSIONBITS_LAST_OLD_BLOCK_VERSION).Tip();
384 BOOST_CHECK_EQUAL(versionbitscache.ComputeBlockVersion(lastBlock, params) & (1 << bit), 0);
385 }
386
387 // On a new chain:
388 // verify that the bit will be set after lock-in, and then stop being set
389 // after activation.
390 nTime = nStartTime;
391
392 // Mine one period worth of blocks, and check that the bit will be on for the
393 // next period.
394 lastBlock = secondChain.Mine(params.nMinerConfirmationWindow, nTime, VERSIONBITS_LAST_OLD_BLOCK_VERSION).Tip();
395 BOOST_CHECK((versionbitscache.ComputeBlockVersion(lastBlock, params) & (1 << bit)) != 0);
396
397 // Mine another period worth of blocks, signaling the new bit.
398 lastBlock = secondChain.Mine(params.nMinerConfirmationWindow * 2, nTime, VERSIONBITS_TOP_BITS | (1<<bit)).Tip();
399 // After one period of setting the bit on each block, it should have locked in.
400 // We keep setting the bit for one more period though, until activation.
401 BOOST_CHECK((versionbitscache.ComputeBlockVersion(lastBlock, params) & (1 << bit)) != 0);
402
403 // Now check that we keep mining the block until the end of this period, and
404 // then stop at the beginning of the next period.
405 lastBlock = secondChain.Mine((params.nMinerConfirmationWindow * 3) - 1, nTime, VERSIONBITS_LAST_OLD_BLOCK_VERSION).Tip();
406 BOOST_CHECK((versionbitscache.ComputeBlockVersion(lastBlock, params) & (1 << bit)) != 0);
407 lastBlock = secondChain.Mine(params.nMinerConfirmationWindow * 3, nTime, VERSIONBITS_LAST_OLD_BLOCK_VERSION).Tip();
408
409 if (lastBlock->nHeight + 1 < min_activation_height) {
410 // check signalling continues while min_activation_height is not reached
411 lastBlock = secondChain.Mine(min_activation_height - 1, nTime, VERSIONBITS_LAST_OLD_BLOCK_VERSION).Tip();
412 BOOST_CHECK((versionbitscache.ComputeBlockVersion(lastBlock, params) & (1 << bit)) != 0);
413 // then reach min_activation_height, which was already REQUIRE'd to start a new period
414 lastBlock = secondChain.Mine(min_activation_height, nTime, VERSIONBITS_LAST_OLD_BLOCK_VERSION).Tip();
415 }
416
417 // Check that we don't signal after activation
418 BOOST_CHECK_EQUAL(versionbitscache.ComputeBlockVersion(lastBlock, params) & (1 << bit), 0);
419 }
420 }; // struct BlockVersionTest
421
422 BOOST_FIXTURE_TEST_CASE(versionbits_computeblockversion, BlockVersionTest)
423 {
424 VersionBitsCache vbcache;
425
426 // check that any deployment on any chain can conceivably reach both
427 // ACTIVE and FAILED states in roughly the way we expect
428 for (const auto& chain_type: {ChainType::MAIN, ChainType::TESTNET, ChainType::TESTNET4, ChainType::SIGNET, ChainType::REGTEST}) {
429 const auto chainParams = CreateChainParams(*m_node.args, chain_type);
430 uint32_t chain_all_vbits{0};
431 for (int i = 0; i < (int)Consensus::MAX_VERSION_BITS_DEPLOYMENTS; ++i) {
432 const auto dep = static_cast<Consensus::DeploymentPos>(i);
433 // Check that no bits are reused (within the same chain). This is
434 // disallowed because the transition to FAILED (on timeout) does
435 // not take precedence over STARTED/LOCKED_IN. So all softforks on
436 // the same bit might overlap, even when non-overlapping start-end
437 // times are picked.
438 const uint32_t dep_mask{vbcache.Mask(chainParams->GetConsensus(), dep)};
439 BOOST_CHECK(!(chain_all_vbits & dep_mask));
440 chain_all_vbits |= dep_mask;
441 check_computeblockversion(vbcache, chainParams->GetConsensus(), dep);
442 }
443 }
444
445 {
446 // Use regtest/testdummy to ensure we always exercise some
447 // deployment that's not always/never active
448 ArgsManager args;
449 args.ForceSetArg("-vbparams", "testdummy:1199145601:1230767999"); // January 1, 2008 - December 31, 2008
450 const auto chainParams = CreateChainParams(args, ChainType::REGTEST);
451 check_computeblockversion(vbcache, chainParams->GetConsensus(), Consensus::DEPLOYMENT_TESTDUMMY);
452 }
453
454 {
455 // Use regtest/testdummy to ensure we always exercise the
456 // min_activation_height test, even if we're not using that in a
457 // live deployment
458 ArgsManager args;
459 args.ForceSetArg("-vbparams", "testdummy:1199145601:1230767999:403200"); // January 1, 2008 - December 31, 2008, min act height 403200
460 const auto chainParams = CreateChainParams(args, ChainType::REGTEST);
461 check_computeblockversion(vbcache, chainParams->GetConsensus(), Consensus::DEPLOYMENT_TESTDUMMY);
462 }
463 }
464
465 /**
466 * Test condition checker with max_activation_height for mandatory activation deadline.
467 * When max_activation_height is set, the deployment forces LOCKED_IN one period before
468 * max_activation_height, even if threshold signaling was not met.
469 */
470 class TestMaxActivationHeightConditionChecker : public AbstractThresholdConditionChecker
471 {
472 private:
473 mutable ThresholdConditionCache cache;
474 int m_max_activation_height;
475
476 public:
477 explicit TestMaxActivationHeightConditionChecker(int max_height) : m_max_activation_height(max_height) {}
478
479 int64_t BeginTime(const Consensus::Params& params) const override { return 0; } // Start immediately
480 int64_t EndTime(const Consensus::Params& params) const override { return Consensus::BIP9Deployment::NO_TIMEOUT; }
481 int Period(const Consensus::Params& params) const override { return 144; }
482 int Threshold(const Consensus::Params& params) const override { return 108; } // 75%
483 int MaxActivationHeight(const Consensus::Params& params) const override { return m_max_activation_height; }
484 bool Condition(const CBlockIndex* pindex, const Consensus::Params& params) const override { return (pindex->nVersion & 0x100); }
485
486 ThresholdState GetStateFor(const CBlockIndex* pindexPrev) const { return AbstractThresholdConditionChecker::GetStateFor(pindexPrev, paramsDummy, cache); }
487 int GetStateSinceHeightFor(const CBlockIndex* pindexPrev) const { return AbstractThresholdConditionChecker::GetStateSinceHeightFor(pindexPrev, paramsDummy, cache); }
488 void ClearCache() { cache.clear(); }
489 };
490
491 BOOST_AUTO_TEST_CASE(versionbits_max_activation_height)
492 {
493 // Test that max_activation_height forces LOCKED_IN one period before max_activation_height
494 // even without sufficient signaling.
495 //
496 // Timeline with period=144, max_activation_height=432:
497 // - Period 0 (0-143): DEFINED
498 // - Period 1 (144-287): STARTED (no signaling -> normally would stay STARTED)
499 // - Period 2 (288-431): LOCKED_IN (forced because 288 >= 432 - 144)
500 // - Period 3 (432+): ACTIVE
501
502 std::vector<CBlockIndex*> blocks;
503 auto cleanup = [&blocks]() {
504 for (auto* b : blocks) delete b;
505 blocks.clear();
506 };
507
508 // max_activation_height = 432 (period 3 start)
509 TestMaxActivationHeightConditionChecker checker(432);
510
511 // Helper to create blocks
512 auto mine_block = [&blocks](int32_t nVersion) -> CBlockIndex* {
513 CBlockIndex* pindex = new CBlockIndex();
514 pindex->nHeight = blocks.size();
515 pindex->pprev = blocks.empty() ? nullptr : blocks.back();
516 pindex->nTime = 1415926536 + 600 * pindex->nHeight;
517 pindex->nVersion = nVersion;
518 pindex->BuildSkip();
519 blocks.push_back(pindex);
520 return pindex;
521 };
522
523 // Mine through period 0 (DEFINED) - 144 blocks (0-143)
524 for (int i = 0; i < 144; i++) {
525 mine_block(0); // No signaling
526 }
527 BOOST_CHECK_EQUAL(blocks.back()->nHeight, 143);
528 // At tip 143, next block (144) would be STARTED
529 BOOST_CHECK(checker.GetStateFor(blocks.back()) == ThresholdState::STARTED);
530 BOOST_CHECK_EQUAL(checker.GetStateSinceHeightFor(blocks.back()), 144);
531
532 // Mine through period 1 (STARTED) without signaling - blocks 144-287
533 for (int i = 0; i < 144; i++) {
534 mine_block(0); // No signaling
535 }
536 BOOST_CHECK_EQUAL(blocks.back()->nHeight, 287);
537 // At tip 287, next block (288) would be LOCKED_IN due to max_activation_height
538 // 288 >= 432 - 144, so forced LOCKED_IN
539 BOOST_CHECK(checker.GetStateFor(blocks.back()) == ThresholdState::LOCKED_IN);
540 BOOST_CHECK_EQUAL(checker.GetStateSinceHeightFor(blocks.back()), 288);
541
542 // Mine through period 2 (LOCKED_IN) - blocks 288-431
543 for (int i = 0; i < 144; i++) {
544 mine_block(0);
545 }
546 BOOST_CHECK_EQUAL(blocks.back()->nHeight, 431);
547 // At tip 431, next block (432) would be ACTIVE
548 BOOST_CHECK(checker.GetStateFor(blocks.back()) == ThresholdState::ACTIVE);
549 BOOST_CHECK_EQUAL(checker.GetStateSinceHeightFor(blocks.back()), 432);
550
551 // Mine into period 3 (ACTIVE) - blocks 432+
552 for (int i = 0; i < 10; i++) {
553 mine_block(0);
554 }
555 BOOST_CHECK_EQUAL(blocks.back()->nHeight, 441);
556 BOOST_CHECK(checker.GetStateFor(blocks.back()) == ThresholdState::ACTIVE);
557 BOOST_CHECK_EQUAL(checker.GetStateSinceHeightFor(blocks.back()), 432);
558
559 cleanup();
560
561 // Test 2: Verify that signaling still works to activate earlier than max_activation_height
562 TestMaxActivationHeightConditionChecker checker2(1000); // max_activation_height far in future
563
564 // Period 0: DEFINED
565 for (int i = 0; i < 144; i++) {
566 mine_block(0);
567 }
568 BOOST_CHECK(checker2.GetStateFor(blocks.back()) == ThresholdState::STARTED);
569
570 // Period 1: Signal 108+ blocks (threshold)
571 for (int i = 0; i < 108; i++) {
572 mine_block(0x100); // Signal
573 }
574 for (int i = 0; i < 36; i++) {
575 mine_block(0); // No signal
576 }
577 BOOST_CHECK_EQUAL(blocks.back()->nHeight, 287);
578 // Should be LOCKED_IN via signaling, not via max_activation_height
579 BOOST_CHECK(checker2.GetStateFor(blocks.back()) == ThresholdState::LOCKED_IN);
580 BOOST_CHECK_EQUAL(checker2.GetStateSinceHeightFor(blocks.back()), 288);
581
582 // Period 2: LOCKED_IN -> ACTIVE
583 for (int i = 0; i < 144; i++) {
584 mine_block(0);
585 }
586 BOOST_CHECK(checker2.GetStateFor(blocks.back()) == ThresholdState::ACTIVE);
587 BOOST_CHECK_EQUAL(checker2.GetStateSinceHeightFor(blocks.back()), 432);
588
589 cleanup();
590 }
591
592 BOOST_AUTO_TEST_CASE(versionbits_max_activation_height_boundary)
593 {
594 // Test edge case: verify exact boundary where LOCKED_IN is forced
595 // With period=144 and max_activation_height=432:
596 // - At height 287, next block is 288, which is >= 432-144=288, so LOCKED_IN
597 // - At height 286, next block is 287, which is < 288, so would stay STARTED
598
599 std::vector<CBlockIndex*> blocks;
600 auto cleanup = [&blocks]() {
601 for (auto* b : blocks) delete b;
602 blocks.clear();
603 };
604
605 TestMaxActivationHeightConditionChecker checker(432);
606
607 auto mine_block = [&blocks](int32_t nVersion) -> CBlockIndex* {
608 CBlockIndex* pindex = new CBlockIndex();
609 pindex->nHeight = blocks.size();
610 pindex->pprev = blocks.empty() ? nullptr : blocks.back();
611 pindex->nTime = 1415926536 + 600 * pindex->nHeight;
612 pindex->nVersion = nVersion;
613 pindex->BuildSkip();
614 blocks.push_back(pindex);
615 return pindex;
616 };
617
618 // Mine to height 143 (end of period 0)
619 for (int i = 0; i < 144; i++) {
620 mine_block(0);
621 }
622 BOOST_CHECK_EQUAL(blocks.back()->nHeight, 143);
623 // State for block 144 is STARTED
624 BOOST_CHECK(checker.GetStateFor(blocks.back()) == ThresholdState::STARTED);
625
626 // Mine period 1 without signaling (blocks 144-287)
627 // But stop at block 286 first to check boundary
628 for (int i = 0; i < 143; i++) {
629 mine_block(0);
630 }
631 BOOST_CHECK_EQUAL(blocks.back()->nHeight, 286);
632 // At tip 286, next block 287 is still in STARTED period
633 // State is still STARTED
634 BOOST_CHECK(checker.GetStateFor(blocks.back()) == ThresholdState::STARTED);
635
636 // Mine block 287 (last block of period 1)
637 mine_block(0);
638 BOOST_CHECK_EQUAL(blocks.back()->nHeight, 287);
639 // At tip 287, state for next block (288) is computed
640 // 288 >= 432 - 144 = 288, so LOCKED_IN
641 BOOST_CHECK(checker.GetStateFor(blocks.back()) == ThresholdState::LOCKED_IN);
642 BOOST_CHECK_EQUAL(checker.GetStateSinceHeightFor(blocks.back()), 288);
643
644 cleanup();
645 }
646
647 BOOST_FIXTURE_TEST_CASE(versionbits_active_duration, BasicTestingSetup)
648 {
649 // Test active_duration parameter via -vbparams
650 // Format: deployment:start:timeout:min_activation_height:max_activation_height:active_duration
651 //
652 // This tests that the parameter is parsed correctly. The actual expiry logic
653 // is tested in DeploymentActiveAt/DeploymentActiveAfter which use active_duration.
654
655 {
656 ArgsManager args;
657 // start=0, timeout=never, min_height=0, max_height=INT_MAX (disabled), active_duration=144
658 args.ForceSetArg("-vbparams", "testdummy:0:999999999999:0:2147483647:144");
659 const auto chainParams = CreateChainParams(args, ChainType::REGTEST);
660 const auto& deployment = chainParams->GetConsensus().vDeployments[Consensus::DEPLOYMENT_TESTDUMMY];
661
662 BOOST_CHECK_EQUAL(deployment.nStartTime, 0);
663 BOOST_CHECK_EQUAL(deployment.nTimeout, 999999999999);
664 BOOST_CHECK_EQUAL(deployment.min_activation_height, 0);
665 BOOST_CHECK_EQUAL(deployment.max_activation_height, std::numeric_limits<int>::max());
666 BOOST_CHECK_EQUAL(deployment.active_duration, 144);
667 }
668
669 {
670 ArgsManager args;
671 // Test with max_activation_height set
672 // start=0, timeout=NO_TIMEOUT, min_height=288, max_height=432, active_duration=1008 (144*7)
673 // NO_TIMEOUT = INT64_MAX = 9223372036854775807
674 args.ForceSetArg("-vbparams", "testdummy:0:9223372036854775807:288:432:1008");
675 const auto chainParams = CreateChainParams(args, ChainType::REGTEST);
676 const auto& deployment = chainParams->GetConsensus().vDeployments[Consensus::DEPLOYMENT_TESTDUMMY];
677
678 BOOST_CHECK_EQUAL(deployment.min_activation_height, 288);
679 BOOST_CHECK_EQUAL(deployment.max_activation_height, 432);
680 BOOST_CHECK_EQUAL(deployment.active_duration, 1008);
681 }
682
683 {
684 ArgsManager args;
685 // Test permanent deployment (active_duration = INT_MAX)
686 args.ForceSetArg("-vbparams", "testdummy:0:999999999999:0:2147483647:2147483647");
687 const auto chainParams = CreateChainParams(args, ChainType::REGTEST);
688 const auto& deployment = chainParams->GetConsensus().vDeployments[Consensus::DEPLOYMENT_TESTDUMMY];
689
690 BOOST_CHECK_EQUAL(deployment.active_duration, std::numeric_limits<int>::max());
691 }
692 }
693
694 BOOST_FIXTURE_TEST_CASE(versionbits_max_activation_height_parsing, BasicTestingSetup)
695 {
696 // Test max_activation_height parameter via -vbparams
697
698 {
699 ArgsManager args;
700 // Test with max_activation_height=432 (mandatory activation deadline)
701 // NO_TIMEOUT = INT64_MAX = 9223372036854775807
702 args.ForceSetArg("-vbparams", "testdummy:0:9223372036854775807:0:432:2147483647");
703 const auto chainParams = CreateChainParams(args, ChainType::REGTEST);
704 const auto& deployment = chainParams->GetConsensus().vDeployments[Consensus::DEPLOYMENT_TESTDUMMY];
705
706 BOOST_CHECK_EQUAL(deployment.max_activation_height, 432);
707 // active_duration should be permanent when not specified differently
708 BOOST_CHECK_EQUAL(deployment.active_duration, std::numeric_limits<int>::max());
709 }
710
711 {
712 ArgsManager args;
713 // Test combined: max_activation_height + active_duration (RDTS)
714 // NO_TIMEOUT = INT64_MAX = 9223372036854775807
715 args.ForceSetArg("-vbparams", "testdummy:0:9223372036854775807:288:576:144");
716 const auto chainParams = CreateChainParams(args, ChainType::REGTEST);
717 const auto& deployment = chainParams->GetConsensus().vDeployments[Consensus::DEPLOYMENT_TESTDUMMY];
718
719 BOOST_CHECK_EQUAL(deployment.min_activation_height, 288);
720 BOOST_CHECK_EQUAL(deployment.max_activation_height, 576);
721 BOOST_CHECK_EQUAL(deployment.active_duration, 144);
722 }
723 }
724
725 /**
726 * Test condition checker for temporary deployments with active_duration.
727 * After active_duration blocks past activation, the state transitions to EXPIRED.
728 */
729 class TestTemporaryDeploymentConditionChecker : public AbstractThresholdConditionChecker
730 {
731 private:
732 mutable ThresholdConditionCache cache;
733 int m_active_duration;
734
735 public:
736 explicit TestTemporaryDeploymentConditionChecker(int active_duration) : m_active_duration(active_duration) {}
737
738 int64_t BeginTime(const Consensus::Params& params) const override { return 0; } // Start immediately
739 int64_t EndTime(const Consensus::Params& params) const override { return Consensus::BIP9Deployment::NO_TIMEOUT; }
740 int Period(const Consensus::Params& params) const override { return 144; }
741 int Threshold(const Consensus::Params& params) const override { return 108; } // 75%
742 int ActiveDuration(const Consensus::Params& params) const override { return m_active_duration; }
743 bool Condition(const CBlockIndex* pindex, const Consensus::Params& params) const override { return (pindex->nVersion & 0x100); }
744
745 ThresholdState GetStateFor(const CBlockIndex* pindexPrev) const { return AbstractThresholdConditionChecker::GetStateFor(pindexPrev, paramsDummy, cache); }
746 int GetStateSinceHeightFor(const CBlockIndex* pindexPrev) const { return AbstractThresholdConditionChecker::GetStateSinceHeightFor(pindexPrev, paramsDummy, cache); }
747 void ClearCache() { cache.clear(); }
748 };
749
750 BOOST_AUTO_TEST_CASE(versionbits_expired_state)
751 {
752 // Test that a temporary deployment transitions from ACTIVE to EXPIRED
753 // after active_duration blocks past activation.
754 //
755 // Timeline with period=144, active_duration=288:
756 // - Period 0 (0-143): DEFINED
757 // - Period 1 (144-287): STARTED, signal enough to lock in
758 // - Period 2 (288-431): LOCKED_IN
759 // - Period 3 (432-575): ACTIVE (activation_height=432)
760 // - Period 4 (576-719): ACTIVE (blocks in this period are ACTIVE)
761 // - At pindexPrev=719: EXPIRED for block 720+ (720 >= 432 + 288)
762
763 std::vector<CBlockIndex*> blocks;
764 auto cleanup = [&blocks]() {
765 for (auto* b : blocks) delete b;
766 blocks.clear();
767 };
768
769 TestTemporaryDeploymentConditionChecker checker(288);
770
771 auto mine_block = [&blocks](int32_t nVersion) -> CBlockIndex* {
772 CBlockIndex* pindex = new CBlockIndex();
773 pindex->nHeight = blocks.size();
774 pindex->pprev = blocks.empty() ? nullptr : blocks.back();
775 pindex->nTime = 1415926536 + 600 * pindex->nHeight;
776 pindex->nVersion = nVersion;
777 pindex->BuildSkip();
778 blocks.push_back(pindex);
779 return pindex;
780 };
781
782 // Period 0: DEFINED (blocks 0-143)
783 for (int i = 0; i < 144; i++) {
784 mine_block(0);
785 }
786 BOOST_CHECK(checker.GetStateFor(blocks.back()) == ThresholdState::STARTED);
787
788 // Period 1: STARTED, signal all blocks to lock in (blocks 144-287)
789 for (int i = 0; i < 144; i++) {
790 mine_block(0x100); // Signal
791 }
792 BOOST_CHECK(checker.GetStateFor(blocks.back()) == ThresholdState::LOCKED_IN);
793 BOOST_CHECK_EQUAL(checker.GetStateSinceHeightFor(blocks.back()), 288);
794
795 // Period 2: LOCKED_IN (blocks 288-431)
796 for (int i = 0; i < 144; i++) {
797 mine_block(0);
798 }
799 BOOST_CHECK(checker.GetStateFor(blocks.back()) == ThresholdState::ACTIVE);
800 BOOST_CHECK_EQUAL(checker.GetStateSinceHeightFor(blocks.back()), 432);
801
802 // Period 3: ACTIVE (blocks 432-575), activation_height = 432
803 for (int i = 0; i < 144; i++) {
804 mine_block(0);
805 }
806 BOOST_CHECK(checker.GetStateFor(blocks.back()) == ThresholdState::ACTIVE);
807 BOOST_CHECK_EQUAL(checker.GetStateSinceHeightFor(blocks.back()), 432);
808
809 // Period 4 (blocks 576-719): blocks in this period are ACTIVE,
810 // but at pindexPrev=719 the state for block 720+ is EXPIRED (720 >= 432 + 288)
811 for (int i = 0; i < 144; i++) {
812 mine_block(0);
813 }
814 BOOST_CHECK(checker.GetStateFor(blocks.back()) == ThresholdState::EXPIRED);
815 BOOST_CHECK_EQUAL(checker.GetStateSinceHeightFor(blocks.back()), 720);
816
817 // Verify EXPIRED is terminal
818 for (int i = 0; i < 144; i++) {
819 mine_block(0x100); // Signal shouldn't matter
820 }
821 BOOST_CHECK(checker.GetStateFor(blocks.back()) == ThresholdState::EXPIRED);
822 BOOST_CHECK_EQUAL(checker.GetStateSinceHeightFor(blocks.back()), 720);
823
824 cleanup();
825 }
826
827 BOOST_AUTO_TEST_CASE(versionbits_expired_unaligned_duration_rejected)
828 {
829 // Test that active_duration that is NOT a multiple of the period is rejected.
830 ArgsManager args;
831 args.ForceSetArg("-vbparams", "testdummy:0:9223372036854775807:0:432:200");
832 BOOST_CHECK_THROW(CreateChainParams(args, ChainType::REGTEST), std::runtime_error);
833 }
834
835 BOOST_AUTO_TEST_CASE(versionbits_expired_minimum_duration)
836 {
837 // Test active_duration = 1 period (144). Minimum useful duration.
838 // Activation at 432, so 432+144=576. At pindexPrev=575, state for 576+:
839 // 576 >= 576 -> EXPIRED. Only one period of ACTIVE.
840
841 std::vector<CBlockIndex*> blocks;
842 auto cleanup = [&blocks]() {
843 for (auto* b : blocks) delete b;
844 blocks.clear();
845 };
846
847 TestTemporaryDeploymentConditionChecker checker(144);
848
849 auto mine_block = [&blocks](int32_t nVersion) -> CBlockIndex* {
850 CBlockIndex* pindex = new CBlockIndex();
851 pindex->nHeight = blocks.size();
852 pindex->pprev = blocks.empty() ? nullptr : blocks.back();
853 pindex->nTime = 1415926536 + 600 * pindex->nHeight;
854 pindex->nVersion = nVersion;
855 pindex->BuildSkip();
856 blocks.push_back(pindex);
857 return pindex;
858 };
859
860 // Period 0: DEFINED (0-143)
861 for (int i = 0; i < 144; i++) mine_block(0);
862 BOOST_CHECK(checker.GetStateFor(blocks.back()) == ThresholdState::STARTED);
863
864 // Period 1: Signal (144-287)
865 for (int i = 0; i < 144; i++) mine_block(0x100);
866 BOOST_CHECK(checker.GetStateFor(blocks.back()) == ThresholdState::LOCKED_IN);
867
868 // Period 2: LOCKED_IN (288-431)
869 for (int i = 0; i < 144; i++) mine_block(0);
870 BOOST_CHECK(checker.GetStateFor(blocks.back()) == ThresholdState::ACTIVE);
871 BOOST_CHECK_EQUAL(checker.GetStateSinceHeightFor(blocks.back()), 432);
872
873 // Period 3 (432-575): ACTIVE for this period, but state for 576+:
874 // 576 >= 432 + 144 = 576 -> EXPIRED immediately at next boundary
875 for (int i = 0; i < 144; i++) mine_block(0);
876 BOOST_CHECK(checker.GetStateFor(blocks.back()) == ThresholdState::EXPIRED);
877 BOOST_CHECK_EQUAL(checker.GetStateSinceHeightFor(blocks.back()), 576);
878
879 cleanup();
880 }
881
882 BOOST_AUTO_TEST_CASE(versionbits_expired_cold_cache)
883 {
884 // Test that clearing the cache and re-querying correctly recovers
885 // the EXPIRED state. This exercises the activation_height recovery
886 // path in GetStateFor where it calls GetStateSinceHeightFor to find
887 // when ACTIVE started.
888
889 std::vector<CBlockIndex*> blocks;
890 auto cleanup = [&blocks]() {
891 for (auto* b : blocks) delete b;
892 blocks.clear();
893 };
894
895 TestTemporaryDeploymentConditionChecker checker(288);
896
897 auto mine_block = [&blocks](int32_t nVersion) -> CBlockIndex* {
898 CBlockIndex* pindex = new CBlockIndex();
899 pindex->nHeight = blocks.size();
900 pindex->pprev = blocks.empty() ? nullptr : blocks.back();
901 pindex->nTime = 1415926536 + 600 * pindex->nHeight;
902 pindex->nVersion = nVersion;
903 pindex->BuildSkip();
904 blocks.push_back(pindex);
905 return pindex;
906 };
907
908 // Build chain through to EXPIRED (same as basic test)
909 // Period 0: DEFINED (0-143)
910 for (int i = 0; i < 144; i++) mine_block(0);
911 // Period 1: Signal (144-287)
912 for (int i = 0; i < 144; i++) mine_block(0x100);
913 // Period 2: LOCKED_IN (288-431)
914 for (int i = 0; i < 144; i++) mine_block(0);
915 // Period 3: ACTIVE (432-575)
916 for (int i = 0; i < 144; i++) mine_block(0);
917 // Period 4: (576-719) -> EXPIRED at 720
918 for (int i = 0; i < 144; i++) mine_block(0);
919
920 // Verify EXPIRED with warm cache
921 BOOST_CHECK(checker.GetStateFor(blocks.back()) == ThresholdState::EXPIRED);
922 BOOST_CHECK_EQUAL(checker.GetStateSinceHeightFor(blocks.back()), 720);
923
924 // Clear cache completely — simulates node restart
925 checker.ClearCache();
926
927 // Re-query: must walk from genesis, recover activation_height, compute EXPIRED
928 BOOST_CHECK(checker.GetStateFor(blocks.back()) == ThresholdState::EXPIRED);
929 BOOST_CHECK_EQUAL(checker.GetStateSinceHeightFor(blocks.back()), 720);
930
931 // Also verify intermediate states are correct after cache rebuild
932 checker.ClearCache();
933 // Query at end of period 3 (pindexPrev=575) — should be ACTIVE
934 BOOST_CHECK(checker.GetStateFor(blocks[575]) == ThresholdState::ACTIVE);
935 BOOST_CHECK_EQUAL(checker.GetStateSinceHeightFor(blocks[575]), 432);
936
937 // Now query at end of period 4 with partially-populated cache
938 // (period 3 is cached as ACTIVE from the query above)
939 BOOST_CHECK(checker.GetStateFor(blocks.back()) == ThresholdState::EXPIRED);
940 BOOST_CHECK_EQUAL(checker.GetStateSinceHeightFor(blocks.back()), 720);
941
942 cleanup();
943 }
944
945 BOOST_AUTO_TEST_CASE(versionbits_expired_with_max_activation_height)
946 {
947 // Test interaction: deployment with BOTH max_activation_height AND active_duration.
948 // max_activation_height forces LOCKED_IN, then active_duration causes EXPIRED.
949 //
950 // period=144, max_activation_height=432, active_duration=288
951 // - Period 0 (0-143): DEFINED
952 // - Period 1 (144-287): STARTED (no signaling, but forced LOCKED_IN at boundary
953 // because 288 >= 432 - 144)
954 // - Period 2 (288-431): LOCKED_IN
955 // - Period 3 (432-575): ACTIVE (activation_height=432)
956 // - Period 4 (576-719): ACTIVE
957 // - At pindexPrev=719: EXPIRED (720 >= 432 + 288)
958
959 std::vector<CBlockIndex*> blocks;
960 auto cleanup = [&blocks]() {
961 for (auto* b : blocks) delete b;
962 blocks.clear();
963 };
964
965 // Need a checker that has both max_activation_height AND active_duration
966 class TestCombinedChecker : public AbstractThresholdConditionChecker
967 {
968 private:
969 mutable ThresholdConditionCache cache;
970 public:
971 int64_t BeginTime(const Consensus::Params& params) const override { return 0; }
972 int64_t EndTime(const Consensus::Params& params) const override { return Consensus::BIP9Deployment::NO_TIMEOUT; }
973 int Period(const Consensus::Params& params) const override { return 144; }
974 int Threshold(const Consensus::Params& params) const override { return 108; }
975 int MaxActivationHeight(const Consensus::Params& params) const override { return 432; }
976 int ActiveDuration(const Consensus::Params& params) const override { return 288; }
977 bool Condition(const CBlockIndex* pindex, const Consensus::Params& params) const override { return (pindex->nVersion & 0x100); }
978 ThresholdState GetStateFor(const CBlockIndex* pindexPrev) const { return AbstractThresholdConditionChecker::GetStateFor(pindexPrev, paramsDummy, cache); }
979 int GetStateSinceHeightFor(const CBlockIndex* pindexPrev) const { return AbstractThresholdConditionChecker::GetStateSinceHeightFor(pindexPrev, paramsDummy, cache); }
980 };
981
982 TestCombinedChecker checker;
983
984 auto mine_block = [&blocks](int32_t nVersion) -> CBlockIndex* {
985 CBlockIndex* pindex = new CBlockIndex();
986 pindex->nHeight = blocks.size();
987 pindex->pprev = blocks.empty() ? nullptr : blocks.back();
988 pindex->nTime = 1415926536 + 600 * pindex->nHeight;
989 pindex->nVersion = nVersion;
990 pindex->BuildSkip();
991 blocks.push_back(pindex);
992 return pindex;
993 };
994
995 // Period 0: DEFINED (0-143), no signaling
996 for (int i = 0; i < 144; i++) mine_block(0);
997 BOOST_CHECK(checker.GetStateFor(blocks.back()) == ThresholdState::STARTED);
998
999 // Period 1: STARTED (144-287), no signaling — forced LOCKED_IN by max_activation_height
1000 for (int i = 0; i < 144; i++) mine_block(0);
1001 BOOST_CHECK(checker.GetStateFor(blocks.back()) == ThresholdState::LOCKED_IN);
1002 BOOST_CHECK_EQUAL(checker.GetStateSinceHeightFor(blocks.back()), 288);
1003
1004 // Period 2: LOCKED_IN (288-431)
1005 for (int i = 0; i < 144; i++) mine_block(0);
1006 BOOST_CHECK(checker.GetStateFor(blocks.back()) == ThresholdState::ACTIVE);
1007 BOOST_CHECK_EQUAL(checker.GetStateSinceHeightFor(blocks.back()), 432);
1008
1009 // Period 3: ACTIVE (432-575)
1010 for (int i = 0; i < 144; i++) mine_block(0);
1011 BOOST_CHECK(checker.GetStateFor(blocks.back()) == ThresholdState::ACTIVE);
1012
1013 // Period 4: (576-719) -> EXPIRED at 720
1014 for (int i = 0; i < 144; i++) mine_block(0);
1015 BOOST_CHECK(checker.GetStateFor(blocks.back()) == ThresholdState::EXPIRED);
1016 BOOST_CHECK_EQUAL(checker.GetStateSinceHeightFor(blocks.back()), 720);
1017
1018 cleanup();
1019 }
1020
1021 BOOST_AUTO_TEST_CASE(versionbits_expired_zero_duration)
1022 {
1023 // Test active_duration = 0. This means the deployment expires immediately
1024 // after activation — zero blocks of enforcement.
1025 // Activation at 432, so 432 + 0 = 432. At pindexPrev=431 (end of LOCKED_IN),
1026 // state for 432+: LOCKED_IN transitions to ACTIVE, sets activation_height=432.
1027 // But that's computed for this period boundary. The ACTIVE->EXPIRED check
1028 // happens on the NEXT iteration of the walk-forward.
1029 // At pindexPrev=575 (end of period 3): 576 >= 432 + 0 = 432 -> EXPIRED.
1030 // So active_duration=0 gives exactly one period of ACTIVE, same as
1031 // active_duration=144 with period=144 (since transitions are per-period).
1032
1033 std::vector<CBlockIndex*> blocks;
1034 auto cleanup = [&blocks]() {
1035 for (auto* b : blocks) delete b;
1036 blocks.clear();
1037 };
1038
1039 TestTemporaryDeploymentConditionChecker checker(0);
1040
1041 auto mine_block = [&blocks](int32_t nVersion) -> CBlockIndex* {
1042 CBlockIndex* pindex = new CBlockIndex();
1043 pindex->nHeight = blocks.size();
1044 pindex->pprev = blocks.empty() ? nullptr : blocks.back();
1045 pindex->nTime = 1415926536 + 600 * pindex->nHeight;
1046 pindex->nVersion = nVersion;
1047 pindex->BuildSkip();
1048 blocks.push_back(pindex);
1049 return pindex;
1050 };
1051
1052 // Period 0: DEFINED (0-143)
1053 for (int i = 0; i < 144; i++) mine_block(0);
1054 // Period 1: Signal (144-287)
1055 for (int i = 0; i < 144; i++) mine_block(0x100);
1056 BOOST_CHECK(checker.GetStateFor(blocks.back()) == ThresholdState::LOCKED_IN);
1057
1058 // Period 2: LOCKED_IN (288-431) -> ACTIVE at 432
1059 for (int i = 0; i < 144; i++) mine_block(0);
1060 BOOST_CHECK(checker.GetStateFor(blocks.back()) == ThresholdState::ACTIVE);
1061 BOOST_CHECK_EQUAL(checker.GetStateSinceHeightFor(blocks.back()), 432);
1062
1063 // Period 3 (432-575): ACTIVE, but 576 >= 432+0 -> EXPIRED at next boundary
1064 for (int i = 0; i < 144; i++) mine_block(0);
1065 BOOST_CHECK(checker.GetStateFor(blocks.back()) == ThresholdState::EXPIRED);
1066 BOOST_CHECK_EQUAL(checker.GetStateSinceHeightFor(blocks.back()), 576);
1067
1068 cleanup();
1069 }
1070
1071 BOOST_AUTO_TEST_CASE(versionbits_no_signaling_after_expired)
1072 {
1073 // Test that ComputeBlockVersion does NOT set the deployment bit after EXPIRED.
1074 // Uses regtest with testdummy deployment configured with active_duration.
1075 //
1076 // ComputeBlockVersion only signals for STARTED and LOCKED_IN states.
1077 // After EXPIRED, the bit should not be set.
1078
1079 ArgsManager args;
1080 // testdummy: start=0, timeout=never, min_height=0, max_height=INT_MAX, active_duration=144
1081 args.ForceSetArg("-vbparams", "testdummy:0:9223372036854775807:0:2147483647:144");
1082 const auto chainParams = CreateChainParams(args, ChainType::REGTEST);
1083 const auto& params = chainParams->GetConsensus();
1084
1085 VersionBitsCache vbcache;
1086 const auto dep = Consensus::DEPLOYMENT_TESTDUMMY;
1087 const uint32_t bitmask = vbcache.Mask(params, dep);
1088 const int period = params.nMinerConfirmationWindow; // 144 for regtest
1089
1090 std::vector<CBlockIndex*> blocks;
1091 auto cleanup = [&blocks]() {
1092 for (auto* b : blocks) delete b;
1093 blocks.clear();
1094 };
1095
1096 auto mine_block = [&blocks](int32_t nVersion) -> CBlockIndex* {
1097 CBlockIndex* pindex = new CBlockIndex();
1098 pindex->nHeight = blocks.size();
1099 pindex->pprev = blocks.empty() ? nullptr : blocks.back();
1100 pindex->nTime = 1415926536 + 600 * pindex->nHeight;
1101 pindex->nVersion = nVersion;
1102 pindex->BuildSkip();
1103 blocks.push_back(pindex);
1104 return pindex;
1105 };
1106
1107 // Period 0: DEFINED (0-143) — bit should not be set
1108 for (int i = 0; i < period; i++) mine_block(VERSIONBITS_TOP_BITS);
1109 BOOST_CHECK_EQUAL(vbcache.ComputeBlockVersion(blocks.back(), params) & bitmask, bitmask); // STARTED, bit set
1110
1111 // Period 1: STARTED — signal to lock in (144-287), bit should be set
1112 for (int i = 0; i < period; i++) mine_block(VERSIONBITS_TOP_BITS | bitmask);
1113 BOOST_CHECK(vbcache.State(blocks.back(), params, dep) == ThresholdState::LOCKED_IN);
1114 BOOST_CHECK_EQUAL(vbcache.ComputeBlockVersion(blocks.back(), params) & bitmask, bitmask); // LOCKED_IN, bit set
1115
1116 // Period 2: LOCKED_IN (288-431), bit should be set
1117 for (int i = 0; i < period; i++) mine_block(VERSIONBITS_TOP_BITS | bitmask);
1118 BOOST_CHECK(vbcache.State(blocks.back(), params, dep) == ThresholdState::ACTIVE);
1119 // ACTIVE: bit should NOT be set
1120 BOOST_CHECK_EQUAL(vbcache.ComputeBlockVersion(blocks.back(), params) & bitmask, 0u);
1121
1122 // Period 3: ACTIVE (432-575)
1123 for (int i = 0; i < period; i++) mine_block(VERSIONBITS_TOP_BITS);
1124 BOOST_CHECK(vbcache.State(blocks.back(), params, dep) == ThresholdState::EXPIRED);
1125 // EXPIRED: bit should NOT be set
1126 BOOST_CHECK_EQUAL(vbcache.ComputeBlockVersion(blocks.back(), params) & bitmask, 0u);
1127
1128 // Period 4: EXPIRED (576+) — verify bit stays off
1129 for (int i = 0; i < period; i++) mine_block(VERSIONBITS_TOP_BITS);
1130 BOOST_CHECK(vbcache.State(blocks.back(), params, dep) == ThresholdState::EXPIRED);
1131 BOOST_CHECK_EQUAL(vbcache.ComputeBlockVersion(blocks.back(), params) & bitmask, 0u);
1132
1133 cleanup();
1134 }
1135
1136 BOOST_AUTO_TEST_SUITE_END()
1137