Version-based soft-fork signaling stops working at block 2,000,000 — IsSuperMajority reads the raw nVersion, chain ID and all #40

Closed
opened 2026-09-15 01:51:25 +00:00 by btcbob · 0 comments
Owner

Found while verifying the AuxPoW fork on regtest (see qa/auxpow-smoke/, commit
47cb4fcc). Not a live bug — a mechanism that breaks on a known date.

The facts

CBlockIndex::IsSuperMajority (src/main.cpp:2846) counts a block as signaling
support with a raw comparison:

if (pstart->nVersion >= minVersion)
    ++nFound;

From HARDFORK_AUXPOW_MAIN (block 2,000,000) every block carries the chain ID in
the high half of the version field. Measured on regtest past the fork: blocks are
0x00b00003 = 11,534,339 (or 0x00b00103 when merge-mined).

CPureBlockHeader::GetBaseVersion() exists for exactly this problem — it returns
nVersion % VERSION_AUXPOW, i.e. 3 — and it is never called anywhere in
src/
. Every block-version comparison in main.cpp (lines 1799, 2716, 2724,
2745) uses the raw field.

So from block 2,000,000:

raw comparison says base version would say
IsSuperMajority(2, …) true true
IsSuperMajority(3, …) true true
IsSuperMajority(4, …) true false
IsSuperMajority(9, …) true false

Every post-fork block counts as signaling support for every version, forever.

Why this is not a bug today

Nothing currently deploys above v3. BIP34 (v2) and BIP66 (v3) are long since
active and their gates stay true, so their behaviour is unchanged. The
reject-outdated checks at 2716 and 2724 become unreachable post-fork, but the
chain-ID rule already rejects anything that would have tripped them.

There is no BIP9 / versionbits in this tree. IsSuperMajority is the only
soft-fork deployment mechanism we have.

Why it matters anyway

The next version-gated soft fork would activate spuriously and immediately at
block 2,000,000, with no miner having signaled anything.

That is not hypothetical. BIP65 / CHECKLOCKTIMEVERIFY — issue #34, the thing
atomic swaps need — is deployed exactly this way
, as IsSuperMajority(4, …).
Written the obvious way, copying the BIP66 line above it, it would be dead code
before 2M and unconditionally live after it.

Two ways out, and the cheap one is probably right

  1. Deploy CLTV by height instead of version supermajority — a flag day. This
    sidesteps IsSuperMajority entirely, needs no signaling, and is what the
    AuxPoW, LWMA-3 and emergency-difficulty forks in this tree already do. It costs
    nothing and can happen any time.
  2. Teach the version comparisons to use GetBaseVersion(). Correct, and makes
    the dead accessor do its job. But it changes when soft forks activate, so it is
    consensus-affecting — which means it wants to land at or before block
    2,000,000
    , while the upgrade audience is still people who care. After that it
    is a request foreign merge-miners can decline.

Option 1 alone is enough to unblock #34. Option 2 is about whether we ever want
version signaling to work again, and if we do, it is on the 2M clock with #36.

No decision proposed here. Filing because the deadline is real and was not
written down.

Found while verifying the AuxPoW fork on regtest (see `qa/auxpow-smoke/`, commit `47cb4fcc`). Not a live bug — a mechanism that breaks on a known date. ### The facts `CBlockIndex::IsSuperMajority` (`src/main.cpp:2846`) counts a block as signaling support with a raw comparison: ```cpp if (pstart->nVersion >= minVersion) ++nFound; ``` From `HARDFORK_AUXPOW_MAIN` (block 2,000,000) every block carries the chain ID in the high half of the version field. Measured on regtest past the fork: blocks are `0x00b00003` = **11,534,339** (or `0x00b00103` when merge-mined). `CPureBlockHeader::GetBaseVersion()` exists for exactly this problem — it returns `nVersion % VERSION_AUXPOW`, i.e. 3 — and **it is never called anywhere in `src/`**. Every block-version comparison in `main.cpp` (lines 1799, 2716, 2724, 2745) uses the raw field. So from block 2,000,000: | | raw comparison says | base version would say | |---|---|---| | `IsSuperMajority(2, …)` | true | true | | `IsSuperMajority(3, …)` | true | true | | **`IsSuperMajority(4, …)`** | **true** | **false** | | `IsSuperMajority(9, …)` | true | false | Every post-fork block counts as signaling support for every version, forever. ### Why this is not a bug today Nothing currently deploys above v3. BIP34 (v2) and BIP66 (v3) are long since active and their gates stay true, so their behaviour is unchanged. The reject-outdated checks at 2716 and 2724 become unreachable post-fork, but the chain-ID rule already rejects anything that would have tripped them. There is no BIP9 / versionbits in this tree. `IsSuperMajority` is the **only** soft-fork deployment mechanism we have. ### Why it matters anyway The next version-gated soft fork would activate **spuriously and immediately** at block 2,000,000, with no miner having signaled anything. That is not hypothetical. **BIP65 / CHECKLOCKTIMEVERIFY — issue #34, the thing atomic swaps need — is deployed exactly this way**, as `IsSuperMajority(4, …)`. Written the obvious way, copying the BIP66 line above it, it would be dead code before 2M and unconditionally live after it. ### Two ways out, and the cheap one is probably right 1. **Deploy CLTV by height instead of version supermajority** — a flag day. This sidesteps `IsSuperMajority` entirely, needs no signaling, and is what the AuxPoW, LWMA-3 and emergency-difficulty forks in this tree already do. It costs nothing and can happen any time. 2. **Teach the version comparisons to use `GetBaseVersion()`.** Correct, and makes the dead accessor do its job. But it changes when soft forks activate, so it is consensus-affecting — which means it wants to land **at or before block 2,000,000**, while the upgrade audience is still people who care. After that it is a request foreign merge-miners can decline. Option 1 alone is enough to unblock #34. Option 2 is about whether we ever want version signaling to work again, and if we do, it is on the 2M clock with #36. No decision proposed here. Filing because the deadline is real and was not written down.
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
SubGeniusFinance/dobbscoin-source#40
No description provided.