Make ExecutionEngine highly recommended but optional#3
Make ExecutionEngine highly recommended but optional#3barnabasbusa wants to merge 3 commits intofrisitano:feat/eip8025-refactorfrom
Conversation
Mirror the fire-and-forget pattern used for ProofEngine by dropping the assert on `execution_engine.verify_and_notify_new_payload`. With EIP-8025 execution proofs, consensus correctness is established via the proof engine path, so gating consensus on the execution engine's response is no longer strictly required. Running an execution engine remains highly recommended for defense-in-depth.
…ted by proofs The previous phrasing "consensus correctness is established via the proof engine path" was misleading -- `process_execution_proof` runs during gossip validation, not as part of block state-transition, and proofs are explicitly optional in this EIP. With the execution-engine assert removed, consensus no longer verifies execution validity at all; validity becomes an out-of-band concern handled by a locally-run execution engine and/or by asynchronously gossiped execution proofs. State this explicitly so the trade-off is visible to readers.
Introduce a validator SHOULD-rule that gates attestation on having an independent execution-validity signal: either a local ExecutionEngine VALID response or a verified SignedExecutionProof for the corresponding NewPayloadRequest. This restores the missed-attestation penalty signal for validators that opt out of running a local execution engine, so that opt-out validators who also fail to verify a proof before the attestation deadline self-correct via standard inactivity penalties rather than silently attesting to invalid blocks. Addresses reviewer concern that merely removing the consensus-level assert left validators free to attest to any syntactically valid block without any execution-validity verification.
frisitano
left a comment
There was a problem hiding this comment.
I agree with the sentiment. Uncertain on how best to capture this in the spec. Added some points for discussion inline.
| **highly recommended** for defense-in-depth, but is no longer mandatory. A | ||
| node doing neither accepts execution payloads without execution-validity | ||
| verification. |
There was a problem hiding this comment.
Some wording here may need tightening: "accepts execution payloads". The node will optimistically import the execution payloads in an optimistic (NOT_VALIDATED) state. I would argue that this is not equal to accepting the execution payload. This point is a little pedantic, but I think it is important for interpretation.
| # [Modified in EIP8025] | ||
| # Notify ExecutionEngine of the new execution payload. Running an execution | ||
| # engine is highly recommended for defense-in-depth but is no longer | ||
| # mandatory -- consensus is not gated on the execution engine's response. | ||
| # Execution validity becomes an out-of-band concern: it is verified by | ||
| # the locally-run execution engine (if present) and/or by asynchronously | ||
| # gossiped execution proofs processed via `process_execution_proof`. | ||
| execution_engine.verify_and_notify_new_payload( |
There was a problem hiding this comment.
I'm not sure if we should remove the assertion here or if we should modify the implementation of verify_and_notify_new_payload to note that it should return a NOT_VALIDATED response if the external execution_engine has not been configured.
Maybe it's better to do it as you have here, as it's higher in the call stack and therefore "propagates" the implications to the caller. Just thinking out loud.
There was a problem hiding this comment.
IMO, we should keep the assertion and modify verify_and_notify_new_payload to return True only if either holds:
- ExecutionEngine returned
VALID - There is a
VALIDproof received from p2p
In practice, both verification modes can turn a node to optimistic state:
- EE returned
SYNCING | ACCEPTED— resolved by the signal mechanism of the Optimistic sync (subsequentnewPayloadorforkchoiceUpdatedresponding withVALID | INVALIDwhen EE pulls missing blocks and completes verification makes a node non-optimistic again) - There is no proof yet received — this is new, but can be handled in a similar way relying on a different signal mechanism. In this case the signal will be upon receiving gossiped proof or pulling it of a remote peer and validating it
Today a validator client can already recognize when a node it connects to is in optimistic state and skip attesting if this is the case.
| ### Attestation execution-validity gating | ||
|
|
||
| *[New in EIP8025]* | ||
|
|
||
| With `process_execution_payload` no longer gating consensus on execution | ||
| validity, an honest validator SHOULD NOT attest to a block whose | ||
| `execution_payload` has not been independently validated. Specifically, a | ||
| validator SHOULD withhold its attestation for a block until at least one of | ||
| the following conditions holds by the attestation deadline: | ||
|
|
||
| 1. A locally-run `ExecutionEngine` has returned `VALID` for the block's | ||
| corresponding `NewPayloadRequest` (i.e. | ||
| `execution_engine.verify_and_notify_new_payload(new_payload_request)` | ||
| returned `True`). | ||
| 2. A `SignedExecutionProof` has been received on the `execution_proof` | ||
| gossip topic for the block, and: | ||
| - `proof.public_input.new_payload_request_root` equals | ||
| `hash_tree_root(new_payload_request)` for the block's | ||
| `NewPayloadRequest`, and | ||
| - `proof_engine.verify_execution_proof(proof)` returned `True` (i.e. the | ||
| proof passed `process_execution_proof`). | ||
|
|
||
| If neither signal is available by the attestation deadline, the validator | ||
| SHOULD withhold its attestation for that block. This restores a | ||
| defense-in-depth penalty signal against attesting to blocks with invalid | ||
| execution payloads after the removal of the consensus-level assert on the | ||
| execution engine: validators that opt out of both running a local execution | ||
| engine and subscribing to execution proofs will miss attestations for blocks | ||
| whose validity they cannot independently establish, and will therefore | ||
| incur the standard missed-attestation penalty. | ||
|
|
||
| *Note*: This guidance applies only to validators performing attestation | ||
| duties. Follower or non-attesting nodes MAY accept blocks through | ||
| state-transition without any execution-validity signal; they do not put | ||
| network safety at risk by doing so. | ||
|
|
||
| *Note*: Validators running a local execution engine are unaffected in | ||
| practice — condition (1) will be satisfied via the existing | ||
| `verify_and_notify_new_payload` call in `process_execution_payload`. This | ||
| rule only changes behavior for validators that have opted out of running an | ||
| execution engine. | ||
|
|
There was a problem hiding this comment.
I think this adds clarity, but it may already be implied by the rest of the specs / optimistic block import handling. I see some overlap with this. Essentially, if a block has not been validated by the attestation deadline, then it will remain in the optimistic state and not be attested by the validator.
| proof passed `process_execution_proof`). | ||
|
|
||
| If neither signal is available by the attestation deadline, the validator | ||
| SHOULD withhold its attestation for that block. This restores a |
There was a problem hiding this comment.
| SHOULD withhold its attestation for that block. This restores a | |
| MUST NOT attest to that block. This restores a |
This must be a MUST, otherwise, it reads as if neither of the listed verifications complete a client MAY attest and remain spec compliant.
|
|
||
| *Note*: This guidance applies only to validators performing attestation | ||
| duties. Follower or non-attesting nodes MAY accept blocks through | ||
| state-transition without any execution-validity signal; they do not put |
There was a problem hiding this comment.
Then these nodes exposes their consumers to rely on unverified data. Each node MUST NOT import a block or MUST signal that it is “optimistic” until block execution is verified.
|
This PR has been reopened against upstream |
Summary
This PR targets ethereum#5055 (
feat/eip8025-refactor) and proposes extending thefire-and-forget pattern you introduced for
ProofEngineto theExecutionEngine, while adding a validator-level rule that preserves thepenalty signal for validators who opt out of running a local EL.
Proposal
Two changes:
assertonexecution_engine.verify_and_notify_new_payloadin
process_execution_payload, matching the fire-and-forgetProofEnginetreatment. A node that chooses not to run a local execution engine (e.g.
a stateless client consuming proofs) should not be forced to by the
consensus spec.
validator.md(SHOULD-level): a validator SHOULD NOT attest to a block whose
execution_payloadhas not been independently validated — either by alocal EL returning
VALID, or by a verifiedSignedExecutionProofwhosepublic_input.new_payload_request_rootmatches the block'sNewPayloadRequest, received by the attestation deadline. If neithersignal is available, the validator SHOULD withhold attestation.
Why both changes are needed
Removing the consensus-level
assertalone would be unsafe during theoptional-proof phase:
proof_engine.notify_new_payloadon L166 is fire-and-forget; it performsno verification.
process_execution_proof(which does verify proofs) is only invoked viaexecution_proofgossip validation — it is not part of blockstate-transition.
available before the 4-second attestation deadline.
still happily attest to invalid blocks, and there would be no
missed-attestation penalty to self-correct the behavior.
Change (2) restores the penalty signal. Validators that opt out of the EL
and also fail to receive a valid proof in time simply miss their
attestation and pay the standard inactivity penalty — making the opt-out
economically self-regulating rather than a silent safety risk.
What changed
specs/_features/eip8025/beacon-chain.mdassertwrapper onexecution_engine.verify_and_notify_new_payload(...).*Note*aboveprocess_execution_payloadto describeboth engines as fire-and-forget notifications and to be explicit that
execution-validity verification becomes out-of-band.
specs/_features/eip8025/validator.mdchain responsibilities encoding the SHOULD-rule described above, plus
non-normative notes clarifying (a) follower/non-attesting nodes are
unaffected and (b) validators running an EL see no behavioral change
in practice.
Why "highly recommended" rather than "removed"
Running an execution engine remains the right default for defense-in-depth:
independent check against proof-engine bugs/malicious provers, needed for
local block building and fast attesting, useful fallback while proof
availability/latency matures. Validators who want to opt out now pay the
cost of either running a proof subscription or missing attestations —
which seems like the right incentive shape for an optional-proof phase.
Open questions for discussion
execution_engine.notify_new_payload(returningNone) to match theproof engine signature exactly? That is more invasive since
verify_and_notify_new_payloadis defined up the fork hierarchy(Bellatrix onwards), so I kept this PR to the minimal semantic change.
phase, or is SHOULD the right level given it's guidance for honest
validators and we cannot punish deviation on-chain?
rule, or is the attestation penalty signal sufficient?
Test plan
eip8025/beacon-chain.md(no syntacticchange to the Python block, only
assertremoval).validator.mdsection for correctness of theproof-matching condition (
public_input.new_payload_request_rootagainst
hash_tree_root(new_payload_request)).interaction with fork choice.