Skip to content

Add quick slots (EIP-8198) - #5592

Merged
jtraglia merged 69 commits into
ethereum:masterfrom
barnabemonnot:eip8198-quick-slots
Sep 24, 2026
Merged

jtraglia merged 69 commits into
ethereum:masterfrom
barnabemonnot:eip8198-quick-slots

Conversation

@barnabemonnot

@barnabemonnot barnabemonnot commented Sep 2, 2026 •

Copy link
Copy Markdown
Contributor

EIP-8198 (quick slots) proposes shortening the slot duration. This PR adds the consensus-layer feature specification under specs/_features/eip8198, built on Heze, so the mechanism can be reviewed and prototyped.

Updates and design decisions

  • The slot duration becomes governed by a SLOT_DURATION_SCHEDULE configuration that changes it at a scheduled epoch (the intended mainnet entry is 10 seconds, epoch TBD), making the mapping between wall-clock time and slot number piecewise across the schedule's eras. The schedule does not intend to support BPO-like slot duration changes in-between upgrades: We make the strong assumption here that slot time reductions will always happen during non-BPO-like network upgrades. The schedule is simply a way to loop over historical slot durations in functions that require accumulating this data, such as computing the current slot number given a timestamp.
  • Issuance, penalties, and churn limits rescale with the active duration.
  • Intra-slot deadlines are expressed as basis points of the slot duration, but note that these may benefit from individual optimisation accounting for fixed overheads in certain slot moments. Choosing the deadlines is deferred until further testing.
  • The fork-choice store clock gains millisecond precision.
  • We have decided to make the blob retention constant in wall-clock time, so blobs are retained for more epochs following the fork, but the same amount of seconds. However we are leaving the block retention period and the subnet assignments constant in epochs, meaning that the block retention period becomes shorter and the subnet rotations become more frequent. This does not pose issues with the weak subjectivity period.
  • To keep throughput unchanged at the transition, we choose the approach of programmatically reducing the gas limit per block hours before the fork epoch, arriving at the gas limit per block that maintains throughput once the transition is performed, OR to simply make the block limit update consistent with the new slot time, if a throughput increase allows for this.
  • Beacon-chain, fork, fork-choice, p2p, validator documents are included. Unit and fork-choice tests deferred until CFI.

Execution layer considerations

We do not require tight coupling with the execution layer. In particular, the execution layer is not expected to consume the slot time from the consensus layer, keeping the separation of concerns. That said, two items require updating:

  • The base fee update rule needs a one-time constant update, to maintain the same rate of change per unit of time.
  • The blob schedules must be re-parameterised, to maintain constant blob target.

@github-actions github-actions Bot added the testing CI, actions, tests, testing infra label Sep 2, 2026
@jihoonsong

Copy link
Copy Markdown
Member

I would appreciate it if the PR authors would review and simplify AI-generated comments.

@jtraglia jtraglia changed the title Add EIP-8198 quick slots feature specification Add quick slots (EIP-8198) Sep 2, 2026
@jtraglia jtraglia added the eip8198 Quick Slots label Sep 2, 2026
Comment thread configs/mainnet.yaml Outdated
Comment thread configs/mainnet.yaml Outdated
Comment thread configs/mainnet.yaml Outdated
Comment thread configs/minimal.yaml Outdated
Comment thread configs/minimal.yaml Outdated
Comment thread specs/_features/eip8198/fork-choice.md Outdated
Comment thread specs/_features/eip8198/p2p-interface.md Outdated
Comment thread specs/_features/eip8198/p2p-interface.md Outdated
Comment thread specs/_features/eip8198/optimistic.md Outdated
Comment thread specs/_features/eip8198/beacon-chain.md Outdated
Comment thread specs/_features/eip8198/p2p-interface.md Outdated
Comment thread specs/_features/eip8198/validator.md Outdated
@jtraglia

jtraglia commented Sep 3, 2026

Copy link
Copy Markdown
Member

Nice updates @barnabemonnot! I'll re-review this again early next week.

@barnabemonnot

Copy link
Copy Markdown
Contributor Author

Nice updates @barnabemonnot! I'll re-review this again early next week.

Thank you @jtraglia . For reference here is a quick digest of open items I'll try and address by next week:

  • Deprecate SLOT_DURATION_MS?
  • Prevent leaks with EL => Decide how to handle base fee update rule re-parameterisation
  • Handle block gas limit at the fork to maintain constant throughput: either reduce gas progressively before the fork itself, or as @fradamt mentioned, assume that gas will be increased post-fork given scaling gains, and adjust gas per block before the block.
  • Should fork digest depend on the slot duration? Only helpful if slot time decreases are done out-of-forks.
  • Resolve blob schedule question: keep throughput constant, or keep blob targets/limits integers?

barnabemonnot and others added 21 commits September 5, 2026 00:35
Introduces specs/_features/eip8198 as an experimental fork off Heze:
- fork.md: 0xe8198000 fork version + Heze-shaped state upgrade
- beacon-chain.md: rescaled issuance (BASE_REWARD_FACTOR 42), inactivity
  penalty quotient (x9/4), and churn limits (quotients x3/2, Gwei limits x2/3)
  overriding Gloas's activation/exit/consolidation churn functions
- fork-choice.md: override get_slot_component_duration_ms so all Heze intra-slot
  deadlines rescale to the 8s slot, plus 12->8 boundary slot-counting
- configs + build wiring (constants, md_doc_paths, spec_builders, Makefile, tests)

Blob x2/3 reduction and sidecar-window x3/2 documented as notes (not injected
into the live blob schedule). Builds cleanly via make _pyspec.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
get_inactivity_penalty_quotient now returns INACTIVITY_PENALTY_QUOTIENT_EIP8198
for post-EIP8198 specs, matching the rescaled inactivity penalty. Fixes the
phase0 rewards tests when run on the eip8198 fork.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- SLOT_DURATION_MS_EIP8198 12000->10000 (minimal 6000->5000)
- Replace pre-rounded *_EIP8198 constants with exact deferred-division ratio
  applied in-formula: base reward x r, inactivity penalty x r**2, churn x r
  where r = SLOT_DURATION_MS_EIP8198 / SLOT_DURATION_MS (= 5/6). This preserves
  the fractional base reward factor (effective 53.333, not rounded 53) and makes
  SLOT_DURATION_MS_EIP8198 the single source of truth.
- Remove the CHURN_*_EIP8198 config entries (now derived).
- Revert the inactivity-penalty test oracle (no EIP8198 quotient constant); the
  phase0 rewards inactivity test joins the churn tests as documented
  known-failures (oracle mismatch, not a spec bug).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Post-fork slot boundaries are no longer genesis-aligned modulo
SLOT_DURATION_MS, so the inherited `ms_since_genesis % SLOT_DURATION_MS`
pattern breaks every intra-slot timeliness measurement after the fork.

Add a piecewise get_time_into_slot_ms helper (mirroring
get_slot_from_time) and rebase the three inherited users on it:
record_block_timeliness (attestation/PTC timeliness, proposer boost),
is_proposing_on_time (proposer reorg cutoff), and on_inclusion_list
(inclusion list deadline).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ReTeMvAQpkkTH4k5wsqihM
compute_time_at_slot inherited genesis_time + slot * SLOT_DURATION_MS,
so post-fork EL payload timestamps (asserted in
process_execution_payload) would drift ahead of wall-clock time by 2s
per slot without bound. Override it with the piecewise mapping, and
rebase get_forkchoice_store's initial store time on it so a post-fork
anchor state (checkpoint sync) starts at the correct wall-clock slot.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ReTeMvAQpkkTH4k5wsqihM
Restore the intent of the reverted oracle fix, but mirror the spec's
in-formula slot-ratio scaling instead of a quotient constant: a shared
get_expected_inactivity_penalty helper reproduces the two interleaved
multiply-then-divide steps, and the consolidation churn expectation is
rescaled and re-rounded. Fixes the two tests failing on --fork eip8198
(phase0 test_full_random_without_leak_0, gloas
test_get_consolidation_churn_limit_independent).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ReTeMvAQpkkTH4k5wsqihM
Move EIP8198_FORK_EPOCH into the config files so it compiles as a
config variable rather than a constant: the state-transition and
fork-choice time functions branch on it, and with a compiled
FAR_FUTURE_EPOCH the fork-boundary branches were unreachable in tests.

Add eip8198 tests: a fork transition suite modeled on Heze's (upgrade
covers every BeaconState field, only `fork` changes), and unittests
that override EIP8198_FORK_EPOCH to exercise the piecewise time math —
slot/time round trips, time-into-slot rebasing, compute_time_at_slot
consistency with the fork choice, post-fork anchor store init, and
on_tick catch-up across the boundary. Pass on both presets.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ReTeMvAQpkkTH4k5wsqihM
- Fix the base-reward note, which claimed the effective factor was
  exactly the rounded integer ratio -- the opposite of what the
  deferred division achieves.
- Reword the data-availability notes to prospective: the blob schedule
  entry and rescaled retention windows are scheduled together with the
  fork epoch, not yet present in the configs.
- Note that validator and p2p documents are still missing and inherit
  genesis-anchored slot math needing the same piecewise remap.
- mdformat reflow of the eip8198 docs and ruff cleanups in the new
  unittests.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ReTeMvAQpkkTH4k5wsqihM
New p2p-interface.md overriding compute_time_at_slot_ms, the millisecond
counterpart of compute_time_at_slot. The gossip validation gates
is_not_from_future_slot and is_within_slot_range are defined in terms of
it and inherit the corrected timeline; without the override, honest
post-fork messages would be rejected as from-the-future by a margin
growing 2s per slot. Boundary unittests cover the piecewise mapping and
both gates on both presets.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ReTeMvAQpkkTH4k5wsqihM
Add compute_fork_version with the EIP-8198 branch, the gossip topic
carry-over note (no new message types; topics re-key under the new fork
digest), and interpretation notes for time-sensitive constants:
ATTESTATION_PROPAGATION_SLOT_RANGE stays slot-denominated (a deliberate
wall-clock shrink), absolute allowances (clock disparity, req/resp
timeouts) must not be rescaled, and seen_ttl follows the new slot
duration through its defining formula. Cross-reference the DA retention
rescale from the Req/Resp side.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ReTeMvAQpkkTH4k5wsqihM
Prose-only: no new duty, deadline, or constant. Defines the two timing
rules — slot starts follow the remapped compute_time_at_slot timeline
(genesis-anchored derivation is incorrect post-fork), and intra-slot
deadlines rescale automatically through get_slot_component_duration_ms —
and records the behaviors inherited correctly (payload timestamp, eth1
voting period, epoch-denominated durations).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ReTeMvAQpkkTH4k5wsqihM
Replace the missing-documents note with an overview of the complete
document set and the shared piecewise time-remapping rule.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ReTeMvAQpkkTH4k5wsqihM
Address review feedback on the quick-slots specification. Drop stale
Gloas/Heze annotations from re-declared items, move the modified
compute_time_at_slot_ms back to the networking document where its base
lives, and restore the base documents' section grouping and ordering.

Remove content that does not belong: the behavior-neutral
validate_bls_to_execution_change_gossip override, the dead
whole-second helpers, the duplicated Data availability section, the
compute_seen_ttl helper (seen_ttl is a prose parameter in phase0), and
most restating notes. Replace Store.time with time_ms as the single
store clock and deprecate on_tick_per_slot.

Simplify the inactivity penalty to a single scaled-denominator
division (error is at most one Gwei per validator-epoch in realistic
states). Replace the pre-fork sidecar-retention ramp and its backfill
requirement with a lazy clamp that grows the window one epoch per
epoch from the fork, matching the Deneb retention idiom; a node
retaining the inherited window at the fork needs no pre-fork action.
Rename the fork-suffixed gas-limit helper to
is_gas_limit_transition_compatible and document that block-request
retention deliberately keeps its epoch count.
Replace the single SLOT_DURATION_MS_EIP8198 config value with a
SLOT_DURATION_SCHEDULE list-of-records config, following the blob
schedule mechanism: each entry activates a new slot duration at an
epoch, the timeline helpers accumulate the schedule's eras, and the
economics rescale by the ratio of the duration in effect to
SLOT_DURATION_MS. The schedule is empty until the fork is scheduled;
the intended first mainnet entry is 10000 ms at the fork epoch.

Deadlines become era-dependent, so get_slot_component_duration_ms and
the deadline helpers gain a slot parameter, mirroring how Gloas
redefined the same family. Drop the modified get_blob_parameters: a
duration change must instead be accompanied by a manual BLOB_SCHEDULE
entry, matching how blob throughput changes are scheduled today.
Replace the epoch-count retention selectors with wall-clock
window-start helpers that preserve the retention windows' wall-clock
length across any number of duration changes. Key the one-time gas
limit scaling to the durations in effect at the parent payload and the
bid, and note that it mirrors the execution-layer rule. Add tests for
two-era schedules, alternate durations (including an increase), and
integer-second exactness of slot boundaries.
Drop the blob-sidecar retention-start helper: the blob sidecar
Req/Resp messages are already deprecated as of
FULU_FORK_EPOCH + MIN_EPOCHS_FOR_BLOB_SIDECARS_REQUESTS, so only the
data-column window needs schedule-aware treatment. Note explicitly
that the inherited bls_to_execution_change epoch gate keeps its
pre-schedule slot computation and is provably unaffected. Make the
modified compute_time_at_slot self-contained within the beacon chain
document, fix the misleading remark about per-entry basis-point
values, and define parent_gas_limit in the builder's step 8.
Ship the schedule with a single placeholder entry: a 10000 ms slot
duration at an epoch that is TBD and intended to be the fork epoch
(5000 ms in the minimal config). An entry whose epoch is
FAR_FUTURE_EPOCH is defined as not scheduled and has no effect, and
the timeline helpers skip such entries, so the placeholder documents
the target without activating anything.
Make the unscheduled-entry rule exact: get_slot_duration_ms now also
skips FAR_FUTURE_EPOCH entries, so the placeholder has no effect for
any input, and the inertness test covers that input. State in the
introduction that epoch- and slot-denominated quantities keep their
counts, so their wall-clock spans scale with the slot duration, and
note that exit and consolidation epochs assigned before a duration
change keep their assigned epochs and consumed quota. Clarify that the
retention-start helper replaces only the rolling window term and the
inherited FULU_FORK_EPOCH floor is unchanged. Extend the config
invariants to require a same-epoch BLOB_SCHEDULE entry for every
scheduled duration change.
The probe times fall strictly between the new and the old deadlines of
the first new-duration slot, so judging block timeliness or the
proposer reorg cutoff with the wrong slot's duration flips the
assertions. Also state in the validator document that the deadline
helpers take the duty's slot, whose era determines the duration.
@jihoonsong

Copy link
Copy Markdown
Member
  • Not an issue if there isn't a change in the number of epochs that a block is served for, e.g., if it would be 33,024 epochs before the fork, and remain 33,024 after the fork. Then blocks from before the fork are kept for the same number of epochs, but each successive block is served for a little less longer in wall-clock time (eg, the block that is 50 blocks before the fork, will be served 2 seconds less over its lifetime than the block that is 49 blocks before the fork).
  • If there is a jump, I believe this is an issue separately from the slot time reduction, that should be resolved independently. That said, the best policy then is probably that each object inherits the serving window that was assigned to it at the epoch it was created, with an interpolation starting right after the fork, from the previous window size to the next window size. If fork number is 100, then block 99 is kept 4 epochs, block 100 is kept 3 epochs, block 101 is kept 2 epochs. At block 103, block 99, 100 and 101 are served.
    • Note: This means the peak load is higher at some point, because old items stick around while new items that appear faster accumulate, but after some point you obtain the stable peak load of the new window.
  • In client implementation, timers are set in seconds, not epochs. 33,024-epochs period is translated into different duration across the fork. The timer issue would depend on software design and clients can choose to optimize this, of course. Serving objects for periods calculated before the slot reduction is spec-compliant though. Still, I think it's worth to mention this in the EIP and/or spec.

  • Any changes to retention period could come from different issue and could be orthogonal to EIP-8198. What's related to EIP-8198 is the fact that the duration of epoch changes at the fork transition.

Remind me, do we have any changes to retention period? In other words, will each period have the same number of epochs? @barnabemonnot

@barnabemonnot

Copy link
Copy Markdown
Contributor Author

In client implementation, timers are set in seconds, not epochs. 33,024-epochs period is translated into different duration across the fork. The timer issue would depend on software design and clients can choose to optimize this, of course. Serving objects for periods calculated before the slot reduction is spec-compliant though. Still, I think it's worth to mention this in the EIP and/or spec.

We can add this somewhere, but this feels more like an implementation concern?

  • If the spec says that an object must be kept for some constant number of epochs before and after the fork, then the client should compute the "time-to-live" in seconds for each object, but that computation is fairly transparent (remaining number of epochs before the fork * slot duration before the fork + number of epochs to keep after the fork * slot duration after the fork).
  • If the spec says that an object must be kept for some constant number of seconds, then it's even more transparent. Unless I'm missing something.

Remind me, do we have any changes to retention period? In other words, will each period have the same number of epochs?

If we decide to set the blob retention in seconds rather than in epochs, then that will change, but it is currently the only one I believe. Otherwise this feature would keep the number of epochs the same. However, proposed changes are being discussed to the block retention window, and those may independently change the number of epochs that a block is served for.

@jihoonsong

Copy link
Copy Markdown
Member

We can add this somewhere, but this feels more like an implementation concern?

We do have some note comments for implementors when it adds value. In this case, I believe it's worth to have because it reminds implementors of what they deserve to aware of.

@jihoonsong

jihoonsong commented Sep 24, 2026 •

Copy link
Copy Markdown
Member

Anyway, I can add that comment. I just wanted to make sure that the issue is addressed.

@jtraglia

Copy link
Copy Markdown
Member

@barnabemonnot yep, your recap above sounds accurate 👍

Not a strong opinion but regarding epoch-based versus seconds-based, I would prefer not to mix them, i.e. some are in epoch while others in seconds.

@jihoonsong regarding your comment above, I feel pretty strongly that the blob retention period should be defined in seconds & I do not believe it's straight-forward to define the block retention period (compute_min_epochs_for_block_requests) in seconds; it depends on other configs which are defined in epochs. So I'm not sure if it's practical for both to be the same unit. I haven't spent a lot of time thinking about this btw.

@jihoonsong

jihoonsong commented Sep 24, 2026 •

Copy link
Copy Markdown
Member

So just to confirm, do you prefer to set blob retention window in wall-clock time of 2 weeks? (Or equals to what was previously set in epochs.) @barnabemonnot

"""
window_ms = MIN_BLOB_DATA_RETENTION_MS
current_start_slot = compute_start_slot_at_epoch(current_epoch)
current_start_ms = compute_time_at_slot_ms(Uint64(0), current_start_slot)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I believe passing 0 as a genesis time here is not the right thing to do. It's an arbitrary value and additionally, it violates encapsulation, i.e. it relies on helpers' internal implementation that it will be canceled out.

I've spent some time to improve compute_time_at_slot_ms, compute_slot_at_time_ms and compute_blob_data_retention_start_epoch altogether but couldn't find anything I like. I'll leave it as-is but this should be improved later.

@jtraglia jtraglia left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is in a good enough state to merge! Thanks to everyone that contributed! Let's iterate on the specs with new PRs which are hopefully smaller 😄 And if/when this EIP is CFI'd, add tests.

@jtraglia
jtraglia merged commit 81749c8 into ethereum:master Sep 24, 2026
19 checks passed
@barnabemonnot

Copy link
Copy Markdown
Contributor Author

Thank you @jtraglia and @jihoonsong, for bearing with me here !!

And if/when this EIP is CFI'd, add tests.

Not if, when ;)

barnabemonnot added a commit to barnabemonnot/EIPs that referenced this pull request Sep 25, 2026
…utcomes

Incorporate the design changes from the upstream spec review
(ethereum/consensus-specs#5592):

- Schedule entries now carry explicit per-era millisecond deadlines
  instead of auto-scaling basis points, with ordering constraints;
  deadline values for the 10s era are deferred to phase 2 testing.
- SLOT_DURATION_MS is deprecated; the schedule begins with a genesis
  entry (12,000 ms + inherited deadlines) that anchors the ratio r,
  and subsequent entries must coincide with network upgrades (no
  BPO-like inter-upgrade changes; new rationale section).
- Price rewards and penalties at the epoch they pay for, avoiding a
  one-epoch issuance hole at the boundary (attester rewards, the
  inactivity penalty, and the proposer's inclusion reward).
- Drop the consensus-enforced gas limit rescale and CL bid rule in
  favor of coordinating the gas limit down ahead of the upgrade via
  the advisory GAS_LIMIT_SCHEDULE; add a transition-throughput
  security note.
- Decouple the EL: the base fee change becomes a one-time hard-coded
  constant update, and blob target/limit values are left to be chosen
  jointly with the EL (rounding does not preserve throughput exactly);
  remove the previously specified blob numbers.
- Formalize the DA retention bound in the ByRange/ByRoot messages and
  tie topic re-keying to upgrades.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01B9zwiCe5KJPCGkAyE8vwis
barnabemonnot added a commit to barnabemonnot/EIPs that referenced this pull request Sep 25, 2026
Align the EIP with the current state of ethereum/consensus-specs#5592.
Intra-slot deadlines stay in basis points, applied to the slot duration
at the fork epoch, so schedule entries carry only an epoch and a
duration. The data column sidecar retention window is redefined in
milliseconds (`MIN_BLOB_DATA_RETENTION_MS`) to keep it constant in
wall-clock time, while the block retention window and subnet
subscription period stay in epochs, with a note on retention timers
across a duration change. The fork choice store's millisecond clock is
now upstream, and the weak subjectivity claim is made precise.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
barnabasbusa added a commit to ethpandaops/ethereum-genesis-generator that referenced this pull request Sep 28, 2026
## Summary

Adds the config values introduced upstream by
[ethereum/consensus-specs#5592](ethereum/consensus-specs#5592)
(Add quick slots, EIP-8198), which the `Check consensus spec values` CI
job was reporting as missing for both the mainnet and minimal presets:

- `EIP8198_FORK_VERSION` / `EIP8198_FORK_EPOCH`
- `MIN_BLOB_DATA_RETENTION_MS`
- `SLOT_DURATION_SCHEDULE`

## Changes

- **`EIP8198_FORK_VERSION` / `EIP8198_FORK_EPOCH`**: configurable like
the other EIP forks. Defaults are `0xa3000000` and far future. Both are
excluded from the spec comparison, like the other testnet fork values.
- **`MIN_BLOB_DATA_RETENTION_MS`**: defaults to the spec values,
`1572864000` (mainnet) and `196608000` (minimal).
- **`SLOT_DURATION_SCHEDULE`**: new JSON env var, in the same style as
`GAS_LIMIT_SCHEDULE`:
  ```bash
export SLOT_DURATION_SCHEDULE='[{"epoch": 256, "slot_duration_ms":
10000}]'
  ```
  rendered as:
  ```yaml
  SLOT_DURATION_SCHEDULE:
    - EPOCH: 0
      SLOT_DURATION_MS: 12000
    - EPOCH: 256
      SLOT_DURATION_MS: 10000
  ```
The spec requires the schedule to start at genesis, so the epoch 0 entry
is always emitted from `SLOT_DURATION_MS`. User entries must have `epoch
> 0` and unique epochs. They are sorted by epoch, and invalid input
fails genesis generation.
- **EL genesis**: `genesis_get_activation_time` now sums time across
each slot duration era of `SLOT_DURATION_SCHEDULE`, so EL fork and BPO
timestamps are correct for forks after a slot time change. With no
schedule set, the result is unchanged.

## Testing

- Spec check (`.github/check-consensus-spec-values.py`) against upstream
`master`: mainnet and minimal both report `OK: all 66 comparable spec
fields match`.
- Custom schedule (12s slots, then 6s from epoch 2, Gloas at epoch 4):
the CL config renders correctly and `amsterdamTime` is `1700001212` as
expected.
- An invalid schedule (entry at epoch 0) is rejected with an error.
- `test/run-all-tests.sh` passes.

## Note

`EIP7928_*` and `EIP8321_*` have defaults in `defaults.env` but are not
in the `config-example/cl/config.yaml` template, so they never reach the
output. This PR leaves them unchanged.
barnabasbusa added a commit to ethpandaops/ethereum-package that referenced this pull request Oct 5, 2026
## Summary

Adds support for EIP-8198 (quick slots,
[ethereum/consensus-specs#5592](ethereum/consensus-specs#5592))
as a standalone feature fork on top of Heze.

The default `ethereum-genesis-generator` image is bumped to `6.2.3`,
which includes
[ethpandaops/ethereum-genesis-generator#321](ethpandaops/ethereum-genesis-generator#321)
and
[#322](ethpandaops/ethereum-genesis-generator#322).
Egg renders `SLOT_DURATION_SCHEDULE` exactly as given, starting at
genesis, and computes the EL fork times from it.

## New `network_params`

| Param | Default | Written as |
|---|---|---|
| `eip8198_fork_epoch` | `18446744073709551615` | `EIP8198_FORK_EPOCH`.
The fork version is `EIP8198_FORK_VERSION: 0xa3000038`, following the
package's `0x?0000038` pattern. |
| `slot_duration_schedule` | `[]` | `SLOT_DURATION_SCHEDULE`. List only
the changes after genesis; the package prepends the genesis entry
`{epoch: 0, slot_duration_ms: <slot_duration_ms>}`. |

```yaml
network_params:
  slot_duration_ms: 12000
  heze_fork_epoch: 2
  eip8198_fork_epoch: 3
  slot_duration_schedule:
    - epoch: 3
      slot_duration_ms: 10000
```

renders as

```yaml
SLOT_DURATION_SCHEDULE:
  - EPOCH: 0
    SLOT_DURATION_MS: 12000
  - EPOCH: 3
    SLOT_DURATION_MS: 10000
```

`MIN_BLOB_DATA_RETENTION_MS` is deliberately not a package param. Egg
sets it from the preset, matching the spec: `1572864000` (~18 days) for
mainnet and `196608000` (~2.3 days) for minimal. It can still be
overridden through `extra_env`.

## Sanity checks
Egg only renders what it's given, so `validate_slot_duration_schedule`
in this package checks:
- Every entry has exactly the `epoch` and `slot_duration_ms` keys, both
integers.
- `slot_duration_ms` is a positive multiple of `1000`, as EIP-8198
requires.
- Epochs are `> 0` and strictly increasing. The genesis entry always
comes from `slot_duration_ms`.
- The first entry is at `eip8198_fork_epoch`. Slot duration may only
change at a network upgrade, and a schedule without EIP-8198 enabled
also fails this check.
- `eip8198_fork_epoch >= heze_fork_epoch`, since EIP-8198 is built on
Heze.
- `eip8198_fork_epoch` can't be combined with `frames_enabled`, because
that disables the Heze fork on the CL side.

## Other changes
- The default egg image changes in `constants.star`,
`network_params.yaml` and `README.md`.
- `.github/check-consensus-spec-values.py` now excludes
`EIP8198_FORK_VERSION` / `EIP8198_FORK_EPOCH`, like the other testnet
fork values.

## Testing
- `kurtosis lint` is clean. `kurtosis run --dry-run` passes for the
valid configs, and each invalid case fails with its message.
- Devnets with consensoor
([ethpandaops/consensoor](https://github.com/ethpandaops/consensoor)
`main`) and a FOCIL EL (`focil-devnet-0`):
- Minimal preset, 6s → 4s with reth: finalizes every epoch across the
Heze and EIP-8198 forks.
  - Mainnet preset, 12s → 11s with reth: same result.
- Mainnet preset with ethrex: Fulu genesis, then Gloas at epoch 1, Heze
at epoch 2, and EIP-8198 at epoch 3 (12s → 10s).
- The generated config has the full schedule, and
`amsterdamTime`/`bogotaTime` match the fork epochs.
- Dora needs
[ethpandaops/dora#882](ethpandaops/dora#882)
(`dora_params.image: ethpandaops/dora:bbusa-eip8198-slot-schedule`) to
follow the chain past the EIP-8198 fork.

## Known limitations
- Tooling that reads `seconds_per_slot` / `slot_duration_ms` still
assumes the genesis slot duration after the EIP-8198 fork. That includes
the mev-relay `SEC_PER_SLOT`, Nethermind's `--Blocks.SecondsPerSlot` and
forky.
- The spec also requires later schedule entries to line up with later
network upgrades. The package doesn't model any forks after EIP-8198
yet, so only the first entry is checked against a fork epoch.

https://claude.ai/code/session_01FHGmKoch9p52UXRqqUd8mq
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants