Add quick slots (EIP-8198) - #5592
Conversation
|
I would appreciate it if the PR authors would review and simplify AI-generated comments. |
| ###### Modified `execution_payload_bid` | ||
|
|
||
| The gas-limit _[IGNORE]_ condition is replaced with the following, where | ||
| `parent_execution_payload_slot` is the slot of the beacon block associated with | ||
| the known execution payload identified by `bid.parent_block_hash`: | ||
|
|
||
| - _[IGNORE]_ `bid.parent_block_hash` is the block hash of a known execution | ||
| payload in fork choice and | ||
| `is_gas_limit_transition_compatible(parent_gas_limit, bid.gas_limit, proposer_preferences.target_gas_limit, parent_execution_payload_slot, bid.slot)` | ||
| is `True` where `parent_gas_limit` is the `gas_limit` of that execution | ||
| payload. |
There was a problem hiding this comment.
This change needs to exist inside an executable function. See:
consensus-specs/specs/gloas/p2p-interface.md
Lines 942 to 1065 in 566e1eb
There was a problem hiding this comment.
But again, not sure this is a good idea. If this depends on an update in the execution layer (does it?), this EIP becomes way more complicated.
| def is_gas_limit_transition_compatible( | ||
| parent_gas_limit: Uint64, | ||
| gas_limit: Uint64, | ||
| target_gas_limit: Uint64, | ||
| parent_execution_payload_slot: Slot, | ||
| bid_slot: Slot, | ||
| ) -> bool: | ||
| """ | ||
| Check the bid gas limit, including the one-time scaling at a slot | ||
| duration change. | ||
| """ | ||
| parent_duration_ms = get_slot_duration_ms(compute_epoch_at_slot(parent_execution_payload_slot)) | ||
| bid_duration_ms = get_slot_duration_ms(compute_epoch_at_slot(bid_slot)) | ||
| if parent_duration_ms != bid_duration_ms: | ||
| return gas_limit == parent_gas_limit * bid_duration_ms // parent_duration_ms | ||
| return is_gas_limit_target_compatible(parent_gas_limit, gas_limit, target_gas_limit) |
There was a problem hiding this comment.
I think this could exist as a separate check from is_gas_limit_target_compatible. Eg:
- IGNORE if
is_gas_limit_transition_compatible()is False. - IGNORE if
is_gas_limit_target_compatible()is False.
Also, it appears that this forces bids at the fork to use a gas limit based on parent_gas_limit which might not be what they want to do.
There was a problem hiding this comment.
Could make it a bound, within ± 1024 of that value. However, I am also not sure about the necessity for this. We will most likely do more scaling with Hegota, we could just adjust the target gas limit based on the slot time. E.g. if the plan would be to scale from 200M to 300M but slot time reduction implies a 25% reduction in per slot throughput, we could just target 225M instead.
Another reason to take this approach is that simply scaling by (new slot time / old slot time) isn't really accurate in general
|
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.
Rebasing onto master brought several upstream changes the EIP-8198 branch predates: the mypy-to-ty switch, `Store`-typed gossip time helpers, `Epoch`/`Slot`-typed config constants, the pydantic-based SSZ backend, and the extra validation in Heze's `on_inclusion_list`. Align the EIP-8198 documents and tests with these conventions: use plain `bool` in the `Store` timeliness fields, seed the anchor PTC votes in `get_forkchoice_store`, key `compute_time_at_slot_ms` on the store, and port the current `on_inclusion_list` body.
Trim docstrings that restate their function's signature, shorten notes to the one subtle point they exist to make, and condense multi-line test comments, per review feedback on the verbosity of generated comments.
Each `SLOT_DURATION_SCHEDULE` entry now carries explicit millisecond deadlines for its era instead of deriving them from the inherited basis points, so the intra-slot layout can be adjusted independently whenever the slot duration changes again. A new `get_slot_timing_parameters` helper returns the entry in effect at an epoch, with pre-schedule epochs falling back to the basis-point values, and the deadline helpers read from it. The default entries place every deadline at its inherited fraction of the slot duration, so behavior is unchanged.
Apply the review suggestions that need no design discussion: move `SLOT_DURATION_SCHEDULE` under the existing scheduling section and drop the extra config comments, remove the no-op `FAR_FUTURE_EPOCH` entry and the guard that existed only to serve it, annotate the new `slot` parameter inside the deadline helper signatures, drop the `field(default_factory=...)` defaults from `Store`, break the inactivity penalty denominator into named variables, delete the note about the unchanged blob sidecar retention window, and qualify "era" as "slot duration era".
Per review, the specs do not mention items that are unchanged.
The review asked for the whole section to go, not just its final paragraph: the specs do not describe what an upgrade leaves unchanged. The data-column retention window helper remains defined in the helpers section.
Rewards and penalties granted during epoch processing pay for participation in the previous epoch, but were priced at the current epoch's slot duration, so the first epoch after a slot duration change underpaid attestation rewards by the duration ratio and undersized inactivity penalties by its square. Introduce epoch-priced variants of the base reward helpers, point `get_flag_index_deltas` and `get_inactivity_penalty_deltas` at the previous epoch's duration, and keep the unmodified-signature wrappers pricing at the current epoch for in-block rewards.
The proposer reward for a newly included attestation was priced at the inclusion epoch, so an old-era attestation included after a slot duration change gave the proposer a smaller cut than the attesters' rewards for the same work. Override `process_attestation` to price the proposer's share at the attestation's target epoch.
Couple timing changes to network upgrades and retain normal gas-limit adjustment instead of forcing a transition jump. Use genesis timing for normalization, preserve wall-clock sidecar retention, and cover reward and deadline boundaries. Carry forward upstream gossip and inclusion-list validation changes.
Exclude deprecated config fields when generating fork specifications, while retaining the scalar in earlier forks. Derive shared test clocks from slot-start helpers and verify that EIP-8198 exports only the timing schedule, so legacy scalar dependencies cannot remain hidden.
6c5eabb to
a29567f
Compare
this only rescales the data column window.
Shorter slots at the same per-block gas limit raise throughput by |
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: