Repository navigation
Add quick slots (EIP-8198) - #5592
Conversation
|
I would appreciate it if the PR authors would review and simplify AI-generated comments. |
|
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:
|
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.
Remind me, do we have any changes to retention period? In other words, will each period have the same number of epochs? @barnabemonnot |
We can add this somewhere, but this feels more like an implementation concern?
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. |
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. |
|
Anyway, I can add that comment. I just wanted to make sure that the issue is addressed. |
# Conflicts: # pysetup/helpers.py
|
@barnabemonnot yep, your recap above sounds accurate 👍
@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 ( |
|
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) |
There was a problem hiding this comment.
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.
faee331 to
58b8bf0
Compare
jtraglia
left a comment
There was a problem hiding this comment.
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.
|
Thank you @jtraglia and @jihoonsong, for bearing with me here !!
Not if, when ;) |
…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
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>
## 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.
## 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
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
SLOT_DURATION_SCHEDULEconfiguration 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.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: