Skip to content

Commit f802805

Browse files
committed
versionbits: add LAST_CHANCE state
Instead of transitioning directly to FAILED when timeout is reached, transition to a final LAST_CHANCE signalling period instead (or, if the state was STARTED and the threshold was reached, directly to DELAYED/LOCKED_IN). This ensures that every defined deployment has at least one signalling period, and that it is always clear whether the current signalling period is the final signalling period or not. This allows BIP-91/BIP-148-style mandatory signalling to be deployed in a way that will ensure that a deployment activates (or the chain stops). Note that this behaviour is not equivalent to BIP 9, as it extends the signalling phase by one or two retarget periods -- the final STARTED period can now still result in activation, as can signalling in the following LAST_CHANCE period (which previously would have been FAILED), and if signalling would have been skipped entirely, there is now one LAST_CHANCE signalling period.
1 parent 8ecaf18 commit f802805

File tree

7 files changed

+69
-55
lines changed

7 files changed

+69
-55
lines changed

src/rpc/blockchain.cpp

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1233,20 +1233,21 @@ static void BIP9SoftForkDescPushBack(UniValue& softforks, const std::string &nam
12331233
switch (thresholdState) {
12341234
case ThresholdState::DEFINED: bip9.pushKV("status", "defined"); break;
12351235
case ThresholdState::STARTED: bip9.pushKV("status", "started"); break;
1236+
case ThresholdState::LAST_CHANCE: bip9.pushKV("status", "last_chance"); break;
12361237
case ThresholdState::DELAYED: bip9.pushKV("status", "delayed"); break;
12371238
case ThresholdState::LOCKED_IN: bip9.pushKV("status", "locked_in"); break;
12381239
case ThresholdState::ACTIVE: bip9.pushKV("status", "active"); break;
12391240
case ThresholdState::FAILED: bip9.pushKV("status", "failed"); break;
12401241
}
1241-
if (ThresholdState::STARTED == thresholdState)
1242+
if (ThresholdState::STARTED == thresholdState || ThresholdState::LAST_CHANCE == thresholdState)
12421243
{
12431244
bip9.pushKV("bit", consensusParams.vDeployments[id].bit);
12441245
}
12451246
bip9.pushKV("start_time", consensusParams.vDeployments[id].nStartTime);
12461247
bip9.pushKV("timeout", consensusParams.vDeployments[id].nTimeout);
12471248
int64_t since_height = VersionBitsStateSinceHeight(::ChainActive().Tip(), consensusParams, id, versionbitscache);
12481249
bip9.pushKV("since", since_height);
1249-
if (ThresholdState::STARTED == thresholdState)
1250+
if (ThresholdState::STARTED == thresholdState || ThresholdState::LAST_CHANCE == thresholdState)
12501251
{
12511252
UniValue statsUV(UniValue::VOBJ);
12521253
BIP9Stats statsStruct = VersionBitsStatistics(::ChainActive().Tip(), consensusParams, id);

src/rpc/mining.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -840,6 +840,7 @@ static RPCHelpMan getblocktemplate()
840840
// Ensure bit is set in block version
841841
pblock->nVersion |= VersionBitsMask(consensusParams, pos);
842842
// FALL THROUGH to get vbavailable set...
843+
case ThresholdState::LAST_CHANCE:
843844
case ThresholdState::STARTED:
844845
{
845846
const struct VBDeploymentInfo& vbinfo = VersionBitsDeploymentInfo[pos];

src/test/fuzz/versionbits.cpp

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -244,8 +244,8 @@ FUZZ_TARGET_INIT(versionbits, initialize)
244244
assert(state == exp_state);
245245
assert(since == exp_since);
246246

247-
// GetStateStatistics may crash when state is not STARTED
248-
if (state != ThresholdState::STARTED) continue;
247+
// GetStateStatistics may crash early, so only use it when it's sensible
248+
if (state != ThresholdState::STARTED && state != ThresholdState::LAST_CHANCE) continue;
249249

250250
// check that after mining this block stats change as expected
251251
const BIP9Stats stats = checker.GetStateStatisticsFor(current_block);
@@ -257,7 +257,7 @@ FUZZ_TARGET_INIT(versionbits, initialize)
257257
last_stats = stats;
258258
}
259259

260-
if (exp_state == ThresholdState::STARTED) {
260+
if (exp_state == ThresholdState::STARTED || exp_state == ThresholdState::LAST_CHANCE) {
261261
// double check that stats.possible is sane
262262
if (blocks_sig >= threshold - 1) assert(last_stats.possible);
263263
}
@@ -299,8 +299,13 @@ FUZZ_TARGET_INIT(versionbits, initialize)
299299
assert(current_block->GetMedianTimePast() < checker.m_end);
300300
break;
301301
case ThresholdState::STARTED:
302+
case ThresholdState::LAST_CHANCE:
303+
if (state == ThresholdState::STARTED) {
304+
assert(current_block->GetMedianTimePast() < checker.m_end);
305+
} else {
306+
assert(checker.m_end <= current_block->GetMedianTimePast());
307+
}
302308
assert(current_block->GetMedianTimePast() >= checker.m_begin);
303-
assert(current_block->GetMedianTimePast() < checker.m_end);
304309
if (exp_state == ThresholdState::STARTED) {
305310
assert(blocks_sig < threshold);
306311
} else {
@@ -316,7 +321,8 @@ FUZZ_TARGET_INIT(versionbits, initialize)
316321
}
317322
if (exp_state == ThresholdState::STARTED) {
318323
assert(blocks_sig >= threshold);
319-
assert(current_block->GetMedianTimePast() < checker.m_end);
324+
} else if (exp_state == ThresholdState::LAST_CHANCE) {
325+
assert(blocks_sig >= threshold);
320326
} else {
321327
assert(exp_state == ThresholdState::DELAYED);
322328
}
@@ -326,7 +332,11 @@ FUZZ_TARGET_INIT(versionbits, initialize)
326332
break;
327333
case ThresholdState::FAILED:
328334
assert(never_active_test || current_block->GetMedianTimePast() >= checker.m_end);
329-
assert(exp_state != ThresholdState::LOCKED_IN && exp_state != ThresholdState::ACTIVE);
335+
if (exp_state == ThresholdState::LAST_CHANCE) {
336+
assert(blocks_sig < threshold);
337+
} else {
338+
assert(exp_state == ThresholdState::FAILED);
339+
}
330340
break;
331341
default:
332342
assert(false);

src/test/versionbits_tests.cpp

Lines changed: 40 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ static const std::string StateName(ThresholdState state)
1919
switch (state) {
2020
case ThresholdState::DEFINED: return "DEFINED";
2121
case ThresholdState::STARTED: return "STARTED";
22+
case ThresholdState::LAST_CHANCE: return "LAST_CHANCE";
2223
case ThresholdState::DELAYED: return "DELAYED";
2324
case ThresholdState::LOCKED_IN: return "LOCKED_IN";
2425
case ThresholdState::ACTIVE: return "ACTIVE";
@@ -183,6 +184,7 @@ class VersionBitsTester
183184

184185
VersionBitsTester& TestDefined() { return TestState(ThresholdState::DEFINED); }
185186
VersionBitsTester& TestStarted() { return TestState(ThresholdState::STARTED); }
187+
VersionBitsTester& TestLastChance() { return TestState(ThresholdState::LAST_CHANCE); }
186188
VersionBitsTester& TestLockedIn() { return TestState(ThresholdState::LOCKED_IN); }
187189
VersionBitsTester& TestActive() { return TestState(ThresholdState::ACTIVE); }
188190
VersionBitsTester& TestFailed() { return TestState(ThresholdState::FAILED); }
@@ -200,40 +202,32 @@ BOOST_FIXTURE_TEST_SUITE(versionbits_tests, TestingSetup)
200202
BOOST_AUTO_TEST_CASE(versionbits_test)
201203
{
202204
for (int i = 0; i < 64; i++) {
203-
// DEFINED -> FAILED
205+
// DEFINED -> LAST_CHANCE -> FAILED
204206
VersionBitsTester().TestDefined().TestStateSinceHeight(0)
205207
.Mine(1, TestTime(1), 0x100).TestDefined().TestStateSinceHeight(0)
206208
.Mine(11, TestTime(11), 0x100).TestDefined().TestStateSinceHeight(0)
207209
.Mine(989, TestTime(989), 0x100).TestDefined().TestStateSinceHeight(0)
208210
.Mine(999, TestTime(20000), 0x100).TestDefined().TestStateSinceHeight(0)
209-
.Mine(1000, TestTime(20000), 0x100).TestFailed().TestStateSinceHeight(1000)
210-
.Mine(1999, TestTime(30001), 0x100).TestFailed().TestStateSinceHeight(1000)
211-
.Mine(2000, TestTime(30002), 0x100).TestFailed().TestStateSinceHeight(1000)
212-
.Mine(2001, TestTime(30003), 0x100).TestFailed().TestStateSinceHeight(1000)
213-
.Mine(2999, TestTime(30004), 0x100).TestFailed().TestStateSinceHeight(1000)
214-
.Mine(3000, TestTime(30005), 0x100).TestFailed().TestStateSinceHeight(1000)
215-
216-
// DEFINED -> STARTED -> FAILED
211+
.Mine(1000, TestTime(20000), 0x100).TestLastChance().TestStateSinceHeight(1000)
212+
.Mine(1999, TestTime(30001), 0).TestLastChance().TestStateSinceHeight(1000)
213+
.Mine(2000, TestTime(30002), 0x100).TestFailed().TestStateSinceHeight(2000)
214+
.Mine(2001, TestTime(30003), 0x100).TestFailed().TestStateSinceHeight(2000)
215+
.Mine(2999, TestTime(30004), 0x100).TestFailed().TestStateSinceHeight(2000)
216+
.Mine(3000, TestTime(30005), 0x100).TestFailed().TestStateSinceHeight(2000)
217+
.Mine(4000, TestTime(30006), 0x100).TestFailed().TestStateSinceHeight(2000)
218+
219+
// DEFINED -> STARTED -> LAST_CHANCE -> FAILED
217220
.Reset().TestDefined().TestStateSinceHeight(0)
218221
.Mine(1, TestTime(1), 0).TestDefined().TestStateSinceHeight(0)
219222
.Mine(1000, TestTime(10000) - 1, 0x100).TestDefined().TestStateSinceHeight(0) // One second more and it would be defined
220223
.Mine(2000, TestTime(10000), 0x100).TestStarted().TestStateSinceHeight(2000) // So that's what happens the next period
221224
.Mine(2051, TestTime(10010), 0).TestStarted().TestStateSinceHeight(2000) // 51 old blocks
222225
.Mine(2950, TestTime(10020), 0x100).TestStarted().TestStateSinceHeight(2000) // 899 new blocks
223-
.Mine(3000, TestTime(20000), 0).TestFailed().TestStateSinceHeight(3000) // 50 old blocks (so 899 out of the past 1000)
224-
.Mine(4000, TestTime(20010), 0x100).TestFailed().TestStateSinceHeight(3000)
225-
226-
// DEFINED -> STARTED -> FAILED while threshold reached
227-
.Reset().TestDefined().TestStateSinceHeight(0)
228-
.Mine(1, TestTime(1), 0).TestDefined().TestStateSinceHeight(0)
229-
.Mine(1000, TestTime(10000) - 1, 0x101).TestDefined().TestStateSinceHeight(0) // One second more and it would be defined
230-
.Mine(2000, TestTime(10000), 0x101).TestStarted().TestStateSinceHeight(2000) // So that's what happens the next period
231-
.Mine(2999, TestTime(30000), 0x100).TestStarted().TestStateSinceHeight(2000) // 999 new blocks
232-
.Mine(3000, TestTime(30000), 0x100).TestFailed().TestStateSinceHeight(3000) // 1 new block (so 1000 out of the past 1000 are new)
233-
.Mine(3999, TestTime(30001), 0).TestFailed().TestStateSinceHeight(3000)
234-
.Mine(4000, TestTime(30002), 0).TestFailed().TestStateSinceHeight(3000)
235-
.Mine(14333, TestTime(30003), 0).TestFailed().TestStateSinceHeight(3000)
236-
.Mine(24000, TestTime(40000), 0).TestFailed().TestStateSinceHeight(3000)
226+
.Mine(3000, TestTime(20000), 0).TestLastChance().TestStateSinceHeight(3000) // 50 old blocks (so 899 out of the past 1000)
227+
.Mine(3051, TestTime(20010), 0).TestLastChance().TestStateSinceHeight(3000) // 51 old blocks
228+
.Mine(3950, TestTime(20030), 0x100).TestLastChance().TestStateSinceHeight(3000) // 899 new blocks
229+
.Mine(4000, TestTime(30000), 0).TestFailed().TestStateSinceHeight(4000) // 50 old blocks (so 899 out of the past 1000)
230+
.Mine(5000, TestTime(30010), 0x100).TestFailed().TestStateSinceHeight(4000)
237231

238232
// DEFINED -> STARTED -> LOCKEDIN at the last minute -> ACTIVE
239233
.Reset().TestDefined()
@@ -251,16 +245,26 @@ BOOST_AUTO_TEST_CASE(versionbits_test)
251245
.Mine(6000, TestTime(40002), 0).TestActive().TestStateSinceHeight(4000, 6000)
252246
.Mine(15000, TestTime(40000), 0).TestActive().TestStateSinceHeight(4000, 6000)
253247

254-
// DEFINED multiple periods -> STARTED multiple periods -> FAILED
248+
// DEFINED -> LAST_CHANCE -> LOCKEDIN -> ACTIVE
249+
.Reset().TestDefined()
250+
.Mine(1, TestTime(1), 0).TestDefined().TestStateSinceHeight(0)
251+
.Mine(1000, TestTime(40000), 0).TestLastChance().TestStateSinceHeight(1000)
252+
.Mine(1999, TestTime(40010), 0x100).TestLastChance().TestStateSinceHeight(1000)
253+
.Mine(2000, TestTime(40020), 0).TestLockedIn().TestStateSinceHeight(2000)
254+
.Mine(3000, TestTime(40030), 0).TestActive().TestStateSinceHeight(3000)
255+
.Mine(5000, TestTime(40030), 0).TestActive().TestStateSinceHeight(3000)
256+
257+
// DEFINED multiple periods -> STARTED multiple periods -> LAST_CHANCE -> FAILED
255258
.Reset().TestDefined().TestStateSinceHeight(0)
256259
.Mine(999, TestTime(999), 0).TestDefined().TestStateSinceHeight(0)
257260
.Mine(1000, TestTime(1000), 0).TestDefined().TestStateSinceHeight(0)
258261
.Mine(2000, TestTime(2000), 0).TestDefined().TestStateSinceHeight(0)
259262
.Mine(3000, TestTime(10000), 0).TestStarted().TestStateSinceHeight(3000)
260263
.Mine(4000, TestTime(10000), 0).TestStarted().TestStateSinceHeight(3000)
261264
.Mine(5000, TestTime(10000), 0).TestStarted().TestStateSinceHeight(3000)
262-
.Mine(6000, TestTime(20000), 0).TestFailed().TestStateSinceHeight(6000)
263-
.Mine(7000, TestTime(20000), 0x100).TestFailed().TestStateSinceHeight(6000)
265+
.Mine(6000, TestTime(20000), 0).TestLastChance().TestStateSinceHeight(6000)
266+
.Mine(7000, TestTime(30000), 0).TestFailed().TestStateSinceHeight(7000)
267+
.Mine(8000, TestTime(40000), 0x100).TestFailed().TestStateSinceHeight(7000)
264268
;
265269
}
266270
}
@@ -282,18 +286,13 @@ BOOST_AUTO_TEST_CASE(versionbits_sanity)
282286
BOOST_CHECK_EQUAL(mainnetParams.vDeployments[i].min_lock_in_time, 0);
283287
}
284288

285-
// Verify that the deployment windows of different deployment using the
286-
// same bit are disjoint.
287-
// This test may need modification at such time as a new deployment
288-
// is proposed that reuses the bit of an activated soft fork, before the
289-
// end time of that soft fork. (Alternatively, the end time of that
290-
// activated soft fork could be later changed to be earlier to avoid
291-
// overlap.)
289+
// Verify that the different deployments do not use the same bit.
290+
// Because of LAST_CHANCE signalling it is not possible to
291+
// guarantee that future time frames are disjoint otherwise.
292+
// Past deployments should be deleted (if FAILED) or buried
293+
// (if ACTIVE) so that their bits may be reused.
292294
for (int j=i+1; j<(int) Consensus::MAX_VERSION_BITS_DEPLOYMENTS; j++) {
293-
if (VersionBitsMask(mainnetParams, static_cast<Consensus::DeploymentPos>(j)) == bitmask) {
294-
BOOST_CHECK(mainnetParams.vDeployments[j].nStartTime > mainnetParams.vDeployments[i].nTimeout ||
295-
mainnetParams.vDeployments[i].nStartTime > mainnetParams.vDeployments[j].nTimeout);
296-
}
295+
BOOST_CHECK(VersionBitsMask(mainnetParams, static_cast<Consensus::DeploymentPos>(j)) != bitmask);
297296
}
298297
}
299298
}
@@ -405,14 +404,14 @@ void check_computeblockversion(const Consensus::Params& params, Consensus::Deplo
405404
nHeight += 1;
406405
}
407406

408-
// FAILED is only triggered at the end of a period, so CBV should be setting
409-
// the bit until the period transition.
410-
for (uint32_t i = 0; i < params.nMinerConfirmationWindow - 1; i++) {
407+
// we do two periods: the last STARTED period, where
408+
// MTP hits nTimeout and the LAST_CHANCE period.
409+
for (uint32_t i = 0; i < 2 * params.nMinerConfirmationWindow - 1; i++) {
411410
lastBlock = firstChain.Mine(nHeight+1, nTime, VERSIONBITS_LAST_OLD_BLOCK_VERSION).Tip();
412411
BOOST_CHECK((ComputeBlockVersion(lastBlock, params) & (1<<bit)) != 0);
413412
nHeight += 1;
414413
}
415-
// The next block should trigger no longer setting the bit.
414+
// The next block should hit FAILED and no longer trigger setting the bit.
416415
lastBlock = firstChain.Mine(nHeight+1, nTime, VERSIONBITS_LAST_OLD_BLOCK_VERSION).Tip();
417416
BOOST_CHECK_EQUAL(ComputeBlockVersion(lastBlock, params) & (1<<bit), 0);
418417
}

src/validation.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1820,6 +1820,7 @@ int32_t ComputeBlockVersion(const CBlockIndex* pindexPrev, const Consensus::Para
18201820
ThresholdState state = VersionBitsState(pindexPrev, params, static_cast<Consensus::DeploymentPos>(i), versionbitscache);
18211821
switch (state) {
18221822
case ThresholdState::STARTED:
1823+
case ThresholdState::LAST_CHANCE:
18231824
case ThresholdState::DELAYED:
18241825
case ThresholdState::LOCKED_IN:
18251826
nVersion |= VersionBitsMask(params, static_cast<Consensus::DeploymentPos>(i));

src/versionbits.cpp

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -58,17 +58,14 @@ ThresholdState AbstractThresholdConditionChecker::GetStateFor(const CBlockIndex*
5858
switch (state) {
5959
case ThresholdState::DEFINED: {
6060
if (pindexPrev->GetMedianTimePast() >= nTimeTimeout) {
61-
stateNext = ThresholdState::FAILED;
61+
stateNext = ThresholdState::LAST_CHANCE;
6262
} else if (pindexPrev->GetMedianTimePast() >= nTimeStart) {
6363
stateNext = ThresholdState::STARTED;
6464
}
6565
break;
6666
}
67-
case ThresholdState::STARTED: {
68-
if (pindexPrev->GetMedianTimePast() >= nTimeTimeout) {
69-
stateNext = ThresholdState::FAILED;
70-
break;
71-
}
67+
case ThresholdState::STARTED:
68+
case ThresholdState::LAST_CHANCE: {
7269
// We need to count
7370
const CBlockIndex* pindexCount = pindexPrev;
7471
int count = 0;
@@ -80,6 +77,10 @@ ThresholdState AbstractThresholdConditionChecker::GetStateFor(const CBlockIndex*
8077
}
8178
if (count >= nThreshold) {
8279
stateNext = (pindexPrev->GetMedianTimePast() < min_lock_in_time ? ThresholdState::DELAYED : ThresholdState::LOCKED_IN);
80+
} else if (state == ThresholdState::LAST_CHANCE) {
81+
stateNext = ThresholdState::FAILED;
82+
} else if (pindexPrev->GetMedianTimePast() >= nTimeTimeout) {
83+
stateNext = ThresholdState::LAST_CHANCE;
8384
}
8485
break;
8586
}

src/versionbits.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ static const int32_t VERSIONBITS_NUM_BITS = 29;
2525
enum class ThresholdState {
2626
DEFINED, // First state that each softfork starts out as. The genesis block is by definition in this state for each deployment.
2727
STARTED, // For blocks past the starttime.
28+
LAST_CHANCE, // For first period past the timeout.
2829
DELAYED, // Between the first STARTED period where threshold is reached, until min_lockin_time is reached
2930
LOCKED_IN, // For one retarget period after both the first STARTED period where threshold is reached and min_lockin_time is reached
3031
ACTIVE, // For all blocks after the LOCKED_IN retarget period (final state)

0 commit comments

Comments
 (0)