Skip to content

Add native proposer timing games support - #1

Open
ethermachine wants to merge 11 commits into
developfrom
feature/proposer-timing-games
Open

Add native proposer timing games support#1
ethermachine wants to merge 11 commits into
developfrom
feature/proposer-timing-games

Conversation

@ethermachine

Copy link
Copy Markdown
Owner

Summary

Adds opt-in, experimental support for proposer timing games to Prysm: the validator can delay its block proposal request within the slot so builders have more time to accrue MEV (higher proposer rewards), while safety rails prevent the block from being requested so late that it misses the attestation deadline (and the slot).

Today Prysm requests the block — and therefore the builder getHeader bid — immediately at the start of the slot (t=0), leaving MEV on the table. Other stacks expose this only through sidecars/relays (mev-boost target_first_request_ms, commit-boost, Vouch, relay-side headerDelay). This change makes it a first-class, native knob in the validator client, defaulting off so existing behavior is unchanged.

What's added

Validator client

  • --enable-proposer-timing-games — experimental gate. Off by default; behavior is untouched unless set.
  • --proposer-timing-game-delay — target time into the slot at which the block proposal request is released (default 1500ms). Only takes effect when the gate is on.

The delay is applied at the top of ProposeBlock, before the RANDAO signature and the block request. It is clamped so the request, the builder getHeader round-trip (~1s) and block propagation still finish before the attestation deadline (ATTESTATION_DUE_BPS, ~4s on mainnet) — so the delay can never, by itself, cause a missed slot. On mainnet the effective maximum is ~2.5s. A warning is logged when the configured value:

  • exceeds the safe maximum (the value is clamped), or
  • is beyond the honest-reorg-safe threshold (PROPOSER_REORG_CUTOFF_BPS, ~2s), where orphan risk rises.

The clamp is fork-aware (uses the Gloas attestation deadline once Gloas is active).

Beacon node

  • --builder-getheader-timeout — makes the previously hardcoded 1s builder getHeader timeout (BUILDER_PROPOSAL_DELAY_TOLERANCE) configurable. Falls back to 1s when unset. Useful to tighten the relay budget (mev-boost uses 950ms) when playing timing games.

How to use

Validator — release the proposal ~2s into the slot:

validator \
  --enable-proposer-timing-games \
  --proposer-timing-game-delay=2s \
  ...

Beacon node — tighten the relay getHeader budget (optional, complementary):

beacon-chain \
  --http-mev-relay=<relay-endpoint> \
  --builder-getheader-timeout=950ms \
  ...

Notes

  • Both flags are opt-in. With neither set, behavior is identical to before (proposal at t=0, 1s getHeader timeout).
  • The delay is most valuable with an external builder/relay (--http-mev-relay): it directly pushes back the getHeader bid request. With local block building it only slightly delays payload assembly.
  • Sensible range on mainnet is ~1.4–2.2s. The value is clamped to a safe maximum (~2.5s) regardless of what is configured.
  • Timing games increase the risk of orphaned/reorged blocks. The existing builder circuit breakers (--max-builder-consecutive-missed-slots, --max-builder-epoch-missed-slots) and the local-vs-builder value knobs (--local-block-value-boost, --min-builder-bid, --min-builder-to-local-difference) continue to apply and complement this feature.

Safety

  • Delay clamped to remain before the attestation deadline → the design invariant is "never miss a slot because of the delay".
  • Loud warnings for aggressive configuration.
  • Gated behind an experimental flag; default off.

Testing

  • proposalReleaseDelay: clamp and warning thresholds (mainnet, fork-aware).
  • waitUntilSlotOffset: returns when the offset has elapsed and on context cancellation.
  • ConfigureValidator: the gate reads the delay; without the gate the delay is ignored.
  • configureBuilderCircuitBreaker: --builder-getheader-timeout is applied to the active config.
  • Existing ProposeBlock tests pass unchanged (gate off by default).

Files

  • config/features/flags.go, config/features/config.go — flags + gate wiring
  • validator/client/wait_helpers.go, validator/client/propose.go — delay + clamp, applied in ProposeBlock
  • config/params/config.go, config/params/mainnet_config.go — configurable builder getHeader timeout (default 1s)
  • cmd/beacon-chain/flags/base.go, cmd/beacon-chain/main.go, cmd/beacon-chain/usage.go, beacon-chain/node/config.go, beacon-chain/rpc/prysm/v1alpha1/validator/proposer_bellatrix.go — beacon-side flag + wiring

terencechain and others added 11 commits July 22, 2026 16:00
…s#17214)

- `forkchoice_ptc_vote_count` incremented on every `SetPTCVote` call, so
the same vote applied from gossip and again from a block aggregate
counted twice (~883/slot observed on devnet-7 vs PTC size 512)
- Only increment when the attester bit was previously unset
Introduce opt-in, experimental support for proposer timing games:
delaying the block proposal request within the slot so builders have
more time to accrue MEV, while staying safe from missed slots.

Validator client:
- --enable-proposer-timing-games: experimental gate, off by default.
- --proposer-timing-game-delay: target time into the slot at which the
  block proposal request is released (default 1500ms).
- The delay is applied at the top of ProposeBlock and clamped so the
  request plus the builder getHeader round-trip and block propagation
  still complete before the attestation deadline, never missing the slot.
  Warns when configured beyond the honest reorg-safe threshold, and
  clamps (with a warning) when it exceeds the safe maximum.

Beacon node:
- --builder-getheader-timeout makes the previously hardcoded 1s builder
  getHeader timeout configurable (falls back to 1s when unset).

Adds unit tests for the delay clamp, the slot-offset wait, the feature
gate wiring, and the builder getHeader timeout configuration.
…7221)

**What type of PR is this?**

Other

**What does this PR do? Why is it needed?**

This updates lighthouse version used in multiclient e2e tests. This
comment is taken from PR OffchainLabs#17134 with all credits to @nalepae.

**Which issue(s) does this PR fix?**

**Other notes for review**

**Acknowledgements**

- [x] I have read
[CONTRIBUTING.md](https://github.com/prysmaticlabs/prysm/blob/develop/CONTRIBUTING.md).
- [x] I have included a uniquely named [changelog fragment
file](https://github.com/prysmaticlabs/prysm/blob/develop/CONTRIBUTING.md#maintaining-changelogmd).
- [x] I have added a description with sufficient context for reviewers
to understand this PR.
- [x] I have tested that my changes work as expected and I added a
testing plan to the PR description (if applicable).

---------

Co-authored-by: Manu NALEPA <enalepa@offchainlabs.com>
… config dependency

- Extract the builder getHeader timeout wiring out of
  configureBuilderCircuitBreaker into its own
  configureBuilderGetHeaderTimeout step.
- Pin the mainnet config in TestProposalReleaseDelay so it does not
  depend on the globally active config left by other tests in the
  package (Gloas shifts the attestation deadline and broke the
  test's bounds when the whole package ran).
…ainLabs#17213)

- `new_payload_{valid,optimistic,invalid}_node_count` were only
incremented on the pre-Gloas block path, so they stay flat under Gloas
while the envelope path makes all the newPayload calls
- Increment them in `callNewPayload`
…17216)

- Defined but never recorded anywhere, permanently zero
- `data_columns_recovered_from_el_{attempts,total}` already cover this
Adding the progressive merkliezation functions for the SSZ package. 

- `MerkleizeProgressiveChunks`
- `MerkleizeVectorSSZProgressive`
- `MerkleizeListSSZProgressive`
- `SliceRootProgressive`
- `ByteSliceRootProgressive`
- `MixInActiveFields`
- Bid validation resolved the expected parent hash via
`ForkChoice.BlockHash`, which returns the hash committed in the parent
block's bid even when the payload was never revealed.
- Add `ForkChoice.HasPayloadBlockHash(root, hash)`
- Change `VerifyParentBlockHash` to take a `(root, hash) bool` lookup
and wire the new method into bid gossip validation and
`SubmitSignedExecutionPayloadBid`
…Labs#17222)

- `BlobDataAvailable` in payload attestation data was populated from
`HasFullNode`, which only flips after the envelope is fully imported
- Per spec, `blob_data_available` is
`is_data_available(beacon_block_root)`, independent of envelope import
- New `DataAvailable` getter checks the non-blocking column store status
first, and only reads the block's bid when no columns are stored, since
an empty column summary can't distinguish a blobless payload from
missing data and this order avoids a DB block read in the common case
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