proposals: mev/commit-boost driven MEV flow - #2855
Conversation
Codecov Report❌ Patch coverage is ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
Greptile SummaryThis PR adds an opt-in MEV-optimized block-fetch path (
Confidence Score: 5/5Safe to merge: the new MEV-optimized path is purely additive and opt-in, and the legacy path is preserved bit-for-bit for all existing configurations. The change is well-structured with clear mutual exclusion between paths, comprehensive behavioral and config-layer tests, and correct context-cancellation handling throughout. The two flagged items are minor resource/observability concerns that do not affect correctness. No files require special attention; beacon/goclient/proposer.go carries the most logic but is well-tested by the new dispatch test file. Important Files Changed
Flowchart%%{init: {'theme': 'neutral'}}%%
flowchart TD
A[GetBeaconBlock called] --> B{len clients == 1?}
B -- yes --> C[fetchProposal direct]
C --> D{useSlotRelativeFetch?}
D -- yes --> E[waitUntilProposalSoftDeadline\nblock until slot_start + deadline]
D -- no --> F[return block immediately]
E --> F
B -- no --> G{useSlotRelativeFetch?}
G -- no --> H[getProposalParallelLegacy\nrace BNs, early-exit on blinded\nrelative proposalSoftTimeout]
G -- yes --> I[getProposalParallelByDeadline\nspawn per-BN fetchers]
I --> J{collect loop\nuntil softCtx deadline}
J -- res received --> K{all failed AND\nbestProposal==nil?}
K -- yes --> L[bail early\nwaitForFirstValidProposal]
K -- no --> M[update bestProposal\nif higher score]
M --> J
J -- softCtx.Done --> N{bestProposal != nil?}
N -- yes --> O[return bestProposal]
N -- no --> L
L --> P{ctx.Done?}
P -- yes --> Q[return ctx.Err]
P -- no --> R{first valid\nproposal received?}
R -- yes --> O
R -- no, all failed --> S[return all-clients-failed error]
Reviews (3): Last reviewed commit: "adjustmets" | Re-trigger Greptile |
Address review feedback on #2855: - cli/operator/node.go: ms-unit consistency in validateProposerDelayConfig error message and warn-log fields. The zap.Duration fields are renamed to proposer_delay_ms / max_safe_proposer_delay_ms (Int64) so the unit is explicit in structured log output. - docs/MEV_CONSIDERATIONS.md: - Configuration knobs: state enable_timing_games defaults to false. - Example A: parenthetical noting the 50ms overhead assumes BN and PBS co-located with SSV. - Example B: add second relay to match Example A's structure. - Appendix A: clarify that the 200ms MEVBoostRelayTimeout figure assumes legacy single-shot PBS behavior; not relevant when running timing-games-capable PBS.
Test_validateProposerDelayConfig was asserting on the old zap field names (proposer_delay, max_safe_proposer_delay) and time.Duration values. The previous follow-up commit renamed these to proposer_delay_ms / max_safe_proposer_delay_ms with int64 type. Update the assertions to match. Fixes the unit-test job on #2855.
|
@greptile pls re-review |
8e0c9df to
d0c5c0e
Compare
4c4bde2 to
35f1664
Compare
35f1664 to
2006437
Compare
72758bf to
f7fe8e6
Compare
f9eb2ee to
92fc1b2
Compare
… paths + rewrite MEV doc Config resolution + validation lives in cli/operator/config.go behind config.resolveAndValidate (consolidated in stage via #2866); this commit extends that resolution with block-fetch path selection and has goclient consume the resolved values. beacon/goclient: - New BlockFetchPath enum (safe / legacy / MEV-optimized) + dispatch in GetBeaconBlock. - getProposalParallelByDeadline (slot-relative deadline; safe early-exits on first blinded, MEV-optimized collects best-scored bid) + getProposalParallelLegacy (relative-timeout, preserved). Options gains ProposalSoftDeadline + BlockFetchPath. - New() applies the CommonTimeout/LongTimeout defaults directly; NewOptions removed. goclient consumes pre-resolved, pre-validated values — it owns mechanism, not config policy. cli/operator/config.go: - resolveAndValidate extended with block-fetch resolution (resolveBlockFetch): determine the path, validate the path-specific knob (legacy ProposerDelay / MEV ProposalSoftDeadline), resolve defaults onto cfg.ConsensusClient, set BlockFetchPath, and emit advisory warnings. - determineBlockFetchPath + validateProposalSoftDeadline + the bound consts moved here from goclient (business policy lives at the config boundary). cli/operator/node.go: drop the separate goclient.NewOptions step — resolveAndValidate (already run before goclient.New) resolves the block-fetch values onto cfg.ConsensusClient, which goclient.New now consumes directly. docs/MEV_CONSIDERATIONS.md + config.example.yaml: rewrite around PBS-side timing games, with ProposerDelay reframed as the legacy path.
- goclient.New: reject multi-BN clients whose selected path lacks its timing knob (safe/mev need ProposalSoftDeadline > 0, legacy needs ProposalSoftTimeout > 0). Such Options previously degraded silently to "return first valid response" via an already-expired collection window. - cli/operator: de-conflate resolveBlockFetch — snapshot the raw operator inputs up front so path selection never observes a value resolution produced; determineBlockFetchPath now takes the timing durations directly instead of the whole goclient.Options. Document the run-once invariant. - Fix the ProposerDelay env-description's dead doc anchor (the MEV-doc rewrite removed its target heading) and restore the 500ms-floor note on the ProposalSoftTimeout env-description. - Add end-to-end coverage for the legacy block-fetch path (early-exit-on-blinded + relative-soft-timeout fallback), which lost its behavioral test when TestGetProposalParallel_MultiClient was repointed to the safe path. - Assert the mev-optimized "exceeds safe-max" startup warning.
goclient no longer carries the operator-facing safe/legacy/MEV-optimized
block-fetch "path" enum or its three-way dispatch. The multi-BN block-fetch
strategy is now expressed as two mechanical Options knobs that cli/operator
resolves the path down to:
- ProposalCollectionSlotRelative: slot-relative deadline (true) vs legacy
relative timeout (false)
- EarlyExitOnBlinded: stop collecting on the first blinded (MEV) response
GetBeaconBlock dispatches on these via a 2-way branch instead of switching on
the enum, and New's multi-BN precondition validates the matching duration knob.
The path concept (and its String() label for the startup log line) moves into
cli/operator as policy vocabulary; resolveBlockFetch maps each path to the knob
pair. Runtime behavior is unchanged.
Multi-BN test clients now set the mechanical knobs explicitly; the path String()
test moves to cli/operator alongside the type.
4054c1d to
e40a5fe
Compare
…op safe path ProposalSoftDeadline (the MEV-optimized path) is now a slot-relative floor applied identically to single- and multi-BN setups: the fetched block is held until slot_start + ProposalSoftDeadline before QBFT starts, so every operator in the cluster starts QBFT at the same slot-relative time — aligning their proposer round timers and improving consensus convergence. Previously single-BN ignored the deadline entirely and multi-BN early-exited on the first blinded block, so neither delivered a cluster-aligned QBFT start. Multi-BN MEV-optimized no longer early-exits; it collects to the deadline and proposes the best-scored bid (bailing early only if every BN failed). Remove the `safe` block-fetch path: the default reverts to `legacy` (the original relative-timeout behavior). safe's early-exit meant it never aligned QBFT start, so it added a path and a knob without the benefit — operators who want MEV/alignment opt into ProposalSoftDeadline, the rest stay on legacy. Remove the now-redundant EarlyExitOnBlinded and ProposalCollectionSlotRelative Options knobs; goclient derives the path from ProposalSoftDeadline > 0 (useSlotRelativeFetch). Update tests (incl. new single-BN floor coverage) and docs/MEV_CONSIDERATIONS.md + config.example.yaml.
- simplify the per-request deadline to a single min() expression - explain the ~50ms BN→SSV transport margin and distinguish it from BlockSubmission - retarget 6 broken internal anchors to existing sections
…side links - remove timeout_get_payload_ms = 4000 (just restates both PBSes' default) - close unbalanced paren in Example A SSV-side caption - standardize #ssv-side-configuration link text to "SSV-side configuration"
…100ms - express the margin as a measured ~50–100ms range; note it's BN block-assembly + one-way network and to round up (under-shooting costs MEV, not slots) - set examples to the conservative 100ms end: ProposalSoftDeadline 1150ms / 1900ms, with header-arrival and slot-budget figures updated to match
…ngerousProposalSoftDeadline - ProposalSoftDeadline > 1450ms now fails startup unless AllowDangerousProposalSoftDeadline is set (then allowed up to the 3600ms hard max), mirroring AllowDangerousProposerDelay - add the flag to goclient.Options (eth2) + ALLOW_DANGEROUS_PROPOSAL_SOFT_DEADLINE env - keep the safe-max WARN (now reached only once the flag is set) - update tests + docs (Example B now requires the flag) + config.example.yaml
|
|
||
| fields := logs[0].ContextMap() | ||
| require.Equal(t, int64(1850), fields["proposal_soft_deadline_ms"]) | ||
| require.Equal(t, int64(1450), fields["safe_max_ms"]) |
There was a problem hiding this comment.
Seems that this assertion expects safe_max_ms, but resolveBlockFetch logs the field as safe_max_proposal_soft_deadline_ms (config.go:230), so ContextMap() returns nil here and this subtest fails:
go test -run Test_resolveBlockFetch ./cli/operator/
--- FAIL: .../mev-optimized_above_safe-max_with_flag_-_warns_with_ms_fields
expected: int64(1450) actual: <nil>
Maybe align the two — updating the assertion to safe_max_proposal_soft_deadline_ms would match the legacy branch's max_safe_proposer_delay_ms style?
A ~300ms BlockSubmission better reflects reality than the prior ~100ms. The +200ms shift propagates through the slot-budget math: - ProposalSoftDeadline safe-max 1450ms -> 1250ms (maxSafeProposalSoftDeadline) - legacy ProposerDelay theoretical max 1250ms -> 1050ms, headroom 550ms -> 350ms - worst-case 2-round QBFT budget note 2500ms -> 2700ms Mirror the new safe-max across env-descriptions, config.example.yaml, and the boundary tests. Also fix a pre-existing test that asserted the wrong WARN field key (safe_max_ms -> safe_max_proposal_soft_deadline_ms).
|
Recording a decision made during the ePBS work (#2901) that affects this PR, so it doesn't live only in ephemeral planning notes: Decision: hold this PR — merge ePBS (#2901) first, then reconsider its role. Rationale: ePBS removes the out-of-protocol apparatus this PR's flow configures (mev-boost/commit-boost relay polling is gone post-Gloas), but not the underlying "select the bid as late as the deadline allows" dynamic. Post-Gloas that lever relocates: for a non-self-building proposer it moves into SSV (the timing of the Gloas block-produce call) and/or the BN's own bid selection, while the larger late-MEV lever moves to the builder — or to SSV's §6 envelope timing when self-building. Consequences for this PR when re-evaluated:
|
The temporary in-branch scratch doc required removal once #2901 was in review; its still-open content now lives in durable homes — the devnet e2e runbook and verify items in #2920, the follow-ups and known limitations in the #2901 description, and the MEV-timing-games decision on #2855. The research/decision history stays retrievable from this branch's log.
The temporary in-branch scratch doc required removal once #2901 was in review; its still-open content now lives in durable homes — the devnet e2e runbook and verify items in #2920, the follow-ups and known limitations in the #2901 description, and the MEV-timing-games decision on #2855. The research/decision history stays retrievable from this branch's log.
The temporary in-branch scratch doc required removal once #2901 was in review; its still-open content now lives in durable homes — the devnet e2e runbook and verify items in #2920, the follow-ups and known limitations in the #2901 description, and the MEV-timing-games decision on #2855. The research/decision history stays retrievable from this branch's log.
The temporary in-branch scratch doc required removal once #2901 was in review; its still-open content now lives in durable homes — the devnet e2e runbook and verify items in #2920, the follow-ups and known limitations in the #2901 description, and the MEV-timing-games decision on #2855. The research/decision history stays retrievable from this branch's log.
The temporary in-branch scratch doc required removal once #2901 was in review; its still-open content now lives in durable homes — the devnet e2e runbook and verify items in #2920, the follow-ups and known limitations in the #2901 description, and the MEV-timing-games decision on #2855. The research/decision history stays retrievable from this branch's log.
|
This pull request has been marked as stale due to 60 days of inactivity. It will be closed in 30 days if there are no updates. Please comment if you would like to keep it open. |
The temporary in-branch scratch doc required removal once #2901 was in review; its still-open content now lives in durable homes — the devnet e2e runbook and verify items in #2920, the follow-ups and known limitations in the #2901 description, and the MEV-timing-games decision on #2855. The research/decision history stays retrievable from this branch's log.
The temporary in-branch scratch doc required removal once #2901 was in review; its still-open content now lives in durable homes — the devnet e2e runbook and verify items in #2920, the follow-ups and known limitations in the #2901 description, and the MEV-timing-games decision on #2855. The research/decision history stays retrievable from this branch's log.
Summary
Adds an opt-in MEV-optimized block-fetch path (selected by
ProposalSoftDeadline) alongside the existing legacy path, which stays the default. Config is resolved and validated once incli/operator;beacon/goclientconsumes the pre-resolved values as pure mechanism. Also rewritesdocs/MEV_CONSIDERATIONS.mdaround PBS-side timing games, reframingProposerDelayas the legacy lever.Additive — no behavior change for existing configs. Both nothing-set and the explicit legacy knobs resolve to the legacy path, bit-for-bit identical to
stage.Path selection
ProposalSoftTimeout(default 1800ms).ProposerDelay/ProposalSoftTimeoutset → legacy: same path, chosen explicitly.ProposalSoftDeadlineset → MEV-optimized: a slot-relative deadline that (a) collects multi-BN bids until the deadline without early-exiting, then proposes the best-scored bid, and (b) holds the block until the deadline before starting QBFT (single- and multi-BN alike), so every operator in the cluster starts QBFT at the same slot time (aligned round timers). Mutually exclusive with the legacy knobs — combining them is rejected at startup.New config (under
eth2)ProposalSoftDeadline— slot-relative deadline, range[1000ms, 3600ms]. Above the safe-max of 1250ms the node refuses to start unlessAllowDangerousProposalSoftDeadlineis also set (then allowed up to 3600ms, with a startup WARN) — mirrorsProposerDelay/AllowDangerousProposerDelay.AllowDangerousProposalSoftDeadline(envALLOW_DANGEROUS_PROPOSAL_SOFT_DEADLINE) — explicit acknowledgement of the above.Single-BN operators that set
ProposalSoftDeadlinenow get a slot-relative QBFT-start floor (previously single-BN ignored all block-fetch knobs and fetched directly). Worth a release note for the new knobs.Refactor:
goclient.NewOptionsis removed;goclient.Newapplies the network-timeout defaults directly.Docs
docs/MEV_CONSIDERATIONS.mdrewritten around PBS-side timing games (mev-boost v1.11+-config, or commit-boost);config.example.yamlupdated.Test plan
go build ./...,go vet,go test -race ./beacon/goclient/... ./cli/operator/...green.