Skip to content

Commit

Permalink
versionbits: simplify state transitions
Browse files Browse the repository at this point in the history
This removes the DEFINED->FAILED transition and changes the
STARTED->FAILED transition to only occur if signalling didn't pass the
threshold. This ensures that it is always possible for activation to
occur, no matter what settings are chosen, or the speed at which blocks
are found.
  • Loading branch information
ajtowns committed Apr 12, 2021
1 parent 55ac5f5 commit f054f6b
Show file tree
Hide file tree
Showing 3 changed files with 29 additions and 32 deletions.
17 changes: 7 additions & 10 deletions src/test/fuzz/versionbits.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -147,19 +147,14 @@ FUZZ_TARGET_INIT(versionbits, initialize)
// pick the timestamp to switch based on a block
// note states will change *after* these blocks because mediantime lags
int start_block = fuzzed_data_provider.ConsumeIntegralInRange<int>(0, period * (max_periods - 3));
int end_block = fuzzed_data_provider.ConsumeIntegralInRange<int>(start_block, period * (max_periods - 3));
int end_block = fuzzed_data_provider.ConsumeIntegralInRange<int>(0, period * (max_periods - 3));

start_time = block_start_time + start_block * interval;
timeout = block_start_time + end_block * interval;

assert(start_time <= timeout);

// allow for times to not exactly match a block
if (fuzzed_data_provider.ConsumeBool()) start_time += interval / 2;
if (fuzzed_data_provider.ConsumeBool()) timeout += interval / 2;

// this may make timeout too early; if so, don't run the test
if (start_time > timeout) return;
} else {
if (fuzzed_data_provider.ConsumeBool()) {
start_time = Consensus::BIP9Deployment::ALWAYS_ACTIVE;
Expand Down Expand Up @@ -297,13 +292,12 @@ FUZZ_TARGET_INIT(versionbits, initialize)
assert(since == 0);
assert(exp_state == ThresholdState::DEFINED);
assert(current_block->GetMedianTimePast() < checker.m_begin);
assert(current_block->GetMedianTimePast() < checker.m_end);
break;
case ThresholdState::STARTED:
assert(current_block->GetMedianTimePast() >= checker.m_begin);
assert(current_block->GetMedianTimePast() < checker.m_end);
if (exp_state == ThresholdState::STARTED) {
assert(blocks_sig < threshold);
assert(current_block->GetMedianTimePast() < checker.m_end);
} else {
assert(exp_state == ThresholdState::DEFINED);
}
Expand All @@ -313,7 +307,6 @@ FUZZ_TARGET_INIT(versionbits, initialize)
assert(current_block->nHeight + 1 < min_activation);
} else {
assert(exp_state == ThresholdState::STARTED);
assert(current_block->GetMedianTimePast() < checker.m_end);
assert(blocks_sig >= threshold);
}
break;
Expand All @@ -323,7 +316,11 @@ FUZZ_TARGET_INIT(versionbits, initialize)
break;
case ThresholdState::FAILED:
assert(never_active_test || current_block->GetMedianTimePast() >= checker.m_end);
assert(exp_state != ThresholdState::LOCKED_IN && exp_state != ThresholdState::ACTIVE);
if (exp_state == ThresholdState::STARTED) {
assert(blocks_sig < threshold);
} else {
assert(exp_state == ThresholdState::FAILED);
}
break;
default:
assert(false);
Expand Down
34 changes: 19 additions & 15 deletions src/test/versionbits_tests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -190,18 +190,20 @@ BOOST_FIXTURE_TEST_SUITE(versionbits_tests, TestingSetup)
BOOST_AUTO_TEST_CASE(versionbits_test)
{
for (int i = 0; i < 64; i++) {
// DEFINED -> FAILED
// DEFINED -> STARTED after timeout reached -> FAILED
VersionBitsTester().TestDefined().TestStateSinceHeight(0)
.Mine(1, TestTime(1), 0x100).TestDefined().TestStateSinceHeight(0)
.Mine(11, TestTime(11), 0x100).TestDefined().TestStateSinceHeight(0)
.Mine(989, TestTime(989), 0x100).TestDefined().TestStateSinceHeight(0)
.Mine(999, TestTime(20000), 0x100).TestDefined().TestStateSinceHeight(0)
.Mine(1000, TestTime(20000), 0x100).TestFailed().TestStateSinceHeight(1000)
.Mine(1999, TestTime(30001), 0x100).TestFailed().TestStateSinceHeight(1000)
.Mine(2000, TestTime(30002), 0x100).TestFailed().TestStateSinceHeight(1000)
.Mine(2001, TestTime(30003), 0x100).TestFailed().TestStateSinceHeight(1000)
.Mine(2999, TestTime(30004), 0x100).TestFailed().TestStateSinceHeight(1000)
.Mine(3000, TestTime(30005), 0x100).TestFailed().TestStateSinceHeight(1000)
.Mine(999, TestTime(20000), 0x100).TestDefined().TestStateSinceHeight(0) // Timeout and start time reached simultaneously
.Mine(1000, TestTime(20000), 0).TestStarted().TestStateSinceHeight(1000) // Hit started, stop signalling
.Mine(1999, TestTime(30001), 0).TestStarted().TestStateSinceHeight(1000)
.Mine(2000, TestTime(30002), 0x100).TestFailed().TestStateSinceHeight(2000) // Hit failed, start signalling again
.Mine(2001, TestTime(30003), 0x100).TestFailed().TestStateSinceHeight(2000)
.Mine(2999, TestTime(30004), 0x100).TestFailed().TestStateSinceHeight(2000)
.Mine(3000, TestTime(30005), 0x100).TestFailed().TestStateSinceHeight(2000)
.Mine(4000, TestTime(30006), 0x100).TestFailed().TestStateSinceHeight(2000)

// DEFINED -> STARTED -> FAILED
.Reset().TestDefined().TestStateSinceHeight(0)
.Mine(1, TestTime(1), 0).TestDefined().TestStateSinceHeight(0)
Expand All @@ -212,19 +214,19 @@ BOOST_AUTO_TEST_CASE(versionbits_test)
.Mine(3000, TestTime(20000), 0).TestFailed().TestStateSinceHeight(3000) // 50 old blocks (so 899 out of the past 1000)
.Mine(4000, TestTime(20010), 0x100).TestFailed().TestStateSinceHeight(3000)

// DEFINED -> STARTED -> FAILED while threshold reached
// DEFINED -> STARTED -> LOCKEDIN after timeout reached -> ACTIVE
.Reset().TestDefined().TestStateSinceHeight(0)
.Mine(1, TestTime(1), 0).TestDefined().TestStateSinceHeight(0)
.Mine(1000, TestTime(10000) - 1, 0x101).TestDefined().TestStateSinceHeight(0) // One second more and it would be defined
.Mine(2000, TestTime(10000), 0x101).TestStarted().TestStateSinceHeight(2000) // So that's what happens the next period
.Mine(2999, TestTime(30000), 0x100).TestStarted().TestStateSinceHeight(2000) // 999 new blocks
.Mine(3000, TestTime(30000), 0x100).TestFailed().TestStateSinceHeight(3000) // 1 new block (so 1000 out of the past 1000 are new)
.Mine(3999, TestTime(30001), 0).TestFailed().TestStateSinceHeight(3000)
.Mine(4000, TestTime(30002), 0).TestFailed().TestStateSinceHeight(3000)
.Mine(14333, TestTime(30003), 0).TestFailed().TestStateSinceHeight(3000)
.Mine(24000, TestTime(40000), 0).TestFailed().TestStateSinceHeight(3000)
.Mine(3000, TestTime(30000), 0x100).TestLockedIn().TestStateSinceHeight(3000) // 1 new block (so 1000 out of the past 1000 are new)
.Mine(3999, TestTime(30001), 0).TestLockedIn().TestStateSinceHeight(3000)
.Mine(4000, TestTime(30002), 0).TestActiveDelayed().TestStateSinceHeight(4000, 3000)
.Mine(14333, TestTime(30003), 0).TestActiveDelayed().TestStateSinceHeight(4000, 3000)
.Mine(24000, TestTime(40000), 0).TestActive().TestStateSinceHeight(4000, 15000)

// DEFINED -> STARTED -> LOCKEDIN at the last minute -> ACTIVE
// DEFINED -> STARTED -> LOCKEDIN before timeout -> ACTIVE
.Reset().TestDefined()
.Mine(1, TestTime(1), 0).TestDefined().TestStateSinceHeight(0)
.Mine(1000, TestTime(10000) - 1, 0x101).TestDefined().TestStateSinceHeight(0) // One second more and it would be defined
Expand All @@ -247,8 +249,10 @@ BOOST_AUTO_TEST_CASE(versionbits_test)
.Mine(3000, TestTime(10000), 0).TestStarted().TestStateSinceHeight(3000)
.Mine(4000, TestTime(10000), 0).TestStarted().TestStateSinceHeight(3000)
.Mine(5000, TestTime(10000), 0).TestStarted().TestStateSinceHeight(3000)
.Mine(5999, TestTime(20000), 0).TestStarted().TestStateSinceHeight(3000)
.Mine(6000, TestTime(20000), 0).TestFailed().TestStateSinceHeight(6000)
.Mine(7000, TestTime(20000), 0x100).TestFailed().TestStateSinceHeight(6000)
.Mine(24000, TestTime(20000), 0x100).TestFailed().TestStateSinceHeight(6000) // stay in FAILED no matter how much we signal
;
}
}
Expand Down
10 changes: 3 additions & 7 deletions src/versionbits.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -57,18 +57,12 @@ ThresholdState AbstractThresholdConditionChecker::GetStateFor(const CBlockIndex*

switch (state) {
case ThresholdState::DEFINED: {
if (pindexPrev->GetMedianTimePast() >= nTimeTimeout) {
stateNext = ThresholdState::FAILED;
} else if (pindexPrev->GetMedianTimePast() >= nTimeStart) {
if (pindexPrev->GetMedianTimePast() >= nTimeStart) {
stateNext = ThresholdState::STARTED;
}
break;
}
case ThresholdState::STARTED: {
if (pindexPrev->GetMedianTimePast() >= nTimeTimeout) {
stateNext = ThresholdState::FAILED;
break;
}
// We need to count
const CBlockIndex* pindexCount = pindexPrev;
int count = 0;
Expand All @@ -80,6 +74,8 @@ ThresholdState AbstractThresholdConditionChecker::GetStateFor(const CBlockIndex*
}
if (count >= nThreshold) {
stateNext = ThresholdState::LOCKED_IN;
} else if (pindexPrev->GetMedianTimePast() >= nTimeTimeout) {
stateNext = ThresholdState::FAILED;
}
break;
}
Expand Down

0 comments on commit f054f6b

Please sign in to comment.