Skip to content

fix(L1): retarget the AggregateVerifier cadence switch to Denim - #434

Merged
0x00101010 merged 1 commit into
mainfrom
feat/verifier-retarget-denim
Sep 9, 2026
Merged

fix(L1): retarget the AggregateVerifier cadence switch to Denim#434
0x00101010 merged 1 commit into
mainfrom
feat/verifier-retarget-denim

Conversation

@0xth4nh

@0xth4nh 0xth4nh commented Sep 9, 2026

Copy link
Copy Markdown
Contributor

Reverts the fork target introduced in #431 (merged). The 200ms block-time hardfork moved back to Denim.

What changed?

  • FAST_BLOCK_UPGRADE_INDEX in AggregateVerifier: 12 (Cobalt) → 13 (Denim).
  • Test-local FAST_BLOCK_UPGRADE_INDEX in AggregateVerifier.t.sol follows it. Every test references the constant symbolically, so no fixture arithmetic changed.
  • snapshots/semver-lock.json regenerated — only the AggregateVerifier entry moves.
  • Semver stays at 0.2.0. Nothing has consumed 0.2.0 yet — it was introduced by feat(L1): fork-gate AggregateVerifier proposal intervals for Cobalt #431 and no deployment reads it — so there is no version anyone could be pinned to and nothing to bump away from.

No machinery changed. _firstFastBlock(), _l2Timestamp(), and _intervalsAt() are unchanged; only the index they read is different.

Why?

The switch to Cobalt in #431 was made on the reasoning that the Base mainnet schedule is 13 entries long (indices 0–12) and could therefore never reach index 13. That is true today but it isn't a constraint — _timestamps is a dynamic array and registerUpgrade extends it, which is exactly how Denim gets its entry.

Both indices fail open in the same way and for the same duration:

  • Index 12 (Cobalt) exists today with a zero timestamp. _firstFastBlock() hits if (fastActivationTimestamp == 0) return type(uint256).max.
  • Index 13 (Denim) is one past the end. _firstFastBlock() hits if (schedule.length <= FAST_BLOCK_UPGRADE_INDEX) return type(uint256).max.

Either way the verifier reports "unscheduled" and every game gets the slow-block intervals until the entry is written. So this change is behaviour-neutral on every chain as of today, and the operational requirement is identical to what it was before: the speedup activation has to be registered on ProtocolVersions before the fork, or the verifier stays on the slow cadence silently.

How to test?

just build-go-ffi
forge test --match-contract AggregateVerifier

32 passed, 0 failed on this branch.

The two cases that pin the index behaviour directly:

forge test --match-test test_intervalsForStartingBlock_speedupUnscheduled_succeeds -vv
forge test --match-test test_initialize_unscheduledSpeedupUsesSlowTimestamp_succeeds -vv

_importSpeedupSchedule builds a FAST_BLOCK_UPGRADE_INDEX + 2-long schedule and writes the activation at the constant, so the scheduled-speedup cases (test_intervalsForStartingBlock_selectsOnFirstFastBlock_succeeds, test_initialize_scheduleChangesAtL2ActivationBoundary_succeeds, test_initialize_straddlingGame_usesSlowInterval_succeeds) now exercise a 15-entry registry with Denim at 13 rather than a 14-entry one with Cobalt at 12.

Lock check — should be a no-op after the commit:

FOUNDRY_PROFILE=ci forge build --force --skip "/**/test/**" --skip "/**/scripts/**"
go run scripts/autogen/generate-semver-lock/main.go
git diff --exit-code snapshots/semver-lock.json

Note

test/L1/ProtocolVersions.t.sol still names Cobalt at index 12 (schedule[12] = 0; // Cobalt is unscheduled on Base mainnet.). That is a factual statement about contract registration order, not a targeting choice, and is unrelated to this change — left alone.

@cb-heimdall

cb-heimdall commented Sep 9, 2026

Copy link
Copy Markdown
Collaborator

✅ Heimdall Review Status

Requirement Status More Info
Reviews 1/1
Denominator calculation
Show calculation
1 if user is bot 0
1 if user is external 0
2 if repo is sensitive 0
From .codeflow.yml 1
Additional review requirements
Show calculation
Max 0
0
From CODEOWNERS 0
Global minimum 0
Max 1
1
1 if commit is unverified 0
Sum 1

0xth4nh added a commit to base/base that referenced this pull request Sep 9, 2026
The 200ms block-time hardfork moved back to Denim, so the prose and the one
remaining fixture name follow base/contracts#434. No behaviour change: the
proposer reads the interval pair from the verifier per game and never names a
fork itself.

Co-Authored-By: Claude <noreply@anthropic.com>
0xth4nh added a commit to base/base that referenced this pull request Sep 9, 2026
The 200ms block-time hardfork moved back to Denim, so the prose and the anchor
test fixture names follow base/contracts#434. No behaviour change: the anchor
updater and scanner resolve the interval pair from the verifier per game and
never name a fork themselves.

Co-Authored-By: Claude <noreply@anthropic.com>
Comment thread src/L1/proofs/AggregateVerifier.sol Outdated
The 200ms block-time hardfork moved back to Denim, so `FAST_BLOCK_UPGRADE_INDEX`
goes from 12 (Cobalt) to 13 (Denim).

Index 13 is one past the end of the current Base mainnet schedule, so
`getSchedule()` returns a shorter array until Denim is registered.
`_firstFastBlock()` already treats a short array as unscheduled, which is the
same state the zero timestamp at index 12 produced, so behaviour is unchanged
until the entry is written.

Semver stays at 0.2.0 — no deployment has consumed it yet.

Co-Authored-By: Claude <noreply@anthropic.com>
@0xth4nh
0xth4nh force-pushed the feat/verifier-retarget-denim branch from 26bb55f to 3d3e683 Compare September 9, 2026 17:27
@0x00101010
0x00101010 merged commit a879bfe into main Sep 9, 2026
6 checks passed
@0x00101010
0x00101010 deleted the feat/verifier-retarget-denim branch September 9, 2026 22:26
0xth4nh added a commit to base/base that referenced this pull request Sep 10, 2026
The 200ms block-time hardfork moved back to Denim, so the prose and the one
remaining fixture name follow base/contracts#434. No behaviour change: the
proposer reads the interval pair from the verifier per game and never names a
fork itself.

Co-Authored-By: Claude <noreply@anthropic.com>
0xth4nh added a commit to base/base that referenced this pull request Sep 10, 2026
The 200ms block-time hardfork moved back to Denim, so the prose and the anchor
test fixture names follow base/contracts#434. No behaviour change: the anchor
updater and scanner resolve the interval pair from the verifier per game and
never name a fork themselves.

Co-Authored-By: Claude <noreply@anthropic.com>
mablr pushed a commit to foundry-rs/base that referenced this pull request Sep 10, 2026
)

* feat(proposer): resolve proposal intervals per game for Denim

The proposer read BLOCK_INTERVAL and INTERMEDIATE_BLOCK_INTERVAL from the
AggregateVerifier implementation once at startup and copied them into four
config structs that were never refreshed. Denim switches the verifier to a
shorter cadence at a fixed L2 block, so those startup values are wrong for
every game on the far side of the boundary: the recovery forward walk builds
the next game's UUID with the stale interval, gets Address::ZERO back, and
re-proposes over games that already exist.

Add intervalsForStartingBlock(uint256) to the AggregateVerifier bindings and
an IntervalResolver that reads it — along with the factory's current
gameImpls entry — per lookup, keyed on the starting block of the game being
created, reconstructed, or looked up. The dispatcher, collector, submitter
and recovery walk all resolve through it instead of carrying cached copies.

ProofSubmitter::submit now takes the starting block from the caller rather
than deriving it as target_block - block_interval, and rejects a target that
contradicts the interval resolved for that starting block.

Co-Authored-By: Claude <noreply@anthropic.com>

* fix(proposer): target Cobalt for the cadence switch

Retargets the switchover from Denim (`ProtocolVersions` index 13, which
the Base mainnet schedule does not reach) to Cobalt (index 12), matching
base/contracts#431. The mock verifier's fields follow the contract's
slow/fast vocabulary so a later cadence change needs no rename.

Also drops the stray whitespace `cargo fmt` left in the
`INTERMEDIATE_BLOCK_INTERVAL` divisibility error, flagged in review.

Co-Authored-By: Claude <noreply@anthropic.com>

* fix(proposer): fail loudly on target block overflow

`saturating_add` clamps to `u64::MAX`, which would let a saturated sum match a
`target_block` of `u64::MAX` and pass the guard. `checked_add` cannot.

Co-Authored-By: Claude <noreply@anthropic.com>

* refactor(proposer): trim interval plumbing

Co-authored-by: Codex <codex-noreply@coinbase.com>

* fix(proposer): validate interval resolver at startup

Co-authored-by: Codex <codex-noreply@coinbase.com>

* fix(basectl): resolve game intervals by starting block

Co-authored-by: Codex <codex-noreply@coinbase.com>

* docs(proposer): retarget the cadence switch back to Denim

The 200ms block-time hardfork moved back to Denim, so the prose and the one
remaining fixture name follow base/contracts#434. No behaviour change: the
proposer reads the interval pair from the verifier per game and never names a
fork itself.

Co-Authored-By: Claude <noreply@anthropic.com>

---------

Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: Codex <codex-noreply@coinbase.com>
huklaa pushed a commit to huklaa/base that referenced this pull request Sep 11, 2026
…#4931)

* feat(challenger): resolve proposal intervals per game for Denim

`AnchorUpdater` built the next game's UUID from the `BLOCK_INTERVAL` /
`INTERMEDIATE_BLOCK_INTERVAL` pair read once at startup, and
`GameScanner` cached the intermediate interval per implementation
address. Denim switches the `AggregateVerifier` to a shorter cadence at a
fixed L2 block, so a single implementation serves both sides of the
boundary and neither cache can be right for every game.

Both now resolve the pair from the starting block of the game they are
handling, via the shared `resolve_intervals` helper in
`base-proof-contracts`. The proposer's `IntervalResolver` delegates to
the same helper instead of duplicating the `gameImpls` lookup.

Co-Authored-By: Claude <noreply@anthropic.com>

* fix(challenger): target Cobalt for the cadence switch

Follows base/contracts#431 and the proposer branch: the switchover is
Cobalt (`ProtocolVersions` index 12), not Denim (index 13). Test
fixtures and mock builders use the contract's slow/fast vocabulary.

Co-Authored-By: Claude <noreply@anthropic.com>

* fix(challenger): read existing game intervals from proxy

Co-authored-by: Codex <codex-noreply@coinbase.com>

* docs(challenger): retarget the cadence switch back to Denim

The 200ms block-time hardfork moved back to Denim, so the prose and the anchor
test fixture names follow base/contracts#434. No behaviour change: the anchor
updater and scanner resolve the interval pair from the verifier per game and
never name a fork themselves.

Co-Authored-By: Claude <noreply@anthropic.com>

* docs(challenger): address review on per-game interval resolution

- resolve_intervals: drop the stale "legacy games have no
  intervalsForStartingBlock" justification; existing games are read through
  their own proxy and fall back to the fixed getters there.
- read_intervals_for_starting_block: rename the parameter to
  verifier_address (it takes an implementation or a game proxy) and spell
  out that a game proxy is a CWIA clone, not an upgradeable proxy.
- scanner: note the dependency on the missing-method fallback for
  pre-upgrade games.
- anchor: record the setImplementation limitation and the factory-scan fix.
- service: log what happened, not the intent.
- scanner test: sort the recorded reads; scan runs evaluate_game through
  buffer_unordered, so the order is incidental.

Co-Authored-By: Claude <noreply@anthropic.com>

---------

Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: Codex <codex-noreply@coinbase.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants