Skip to content

Refine EIP-8025 ProofData type and gossip validation logic - #5593

Open
frisitano wants to merge 16 commits into
ethereum:masterfrom
frisitano:eip8025-clean-up
Open

Refine EIP-8025 ProofData type and gossip validation logic#5593
frisitano wants to merge 16 commits into
ethereum:masterfrom
frisitano:eip8025-clean-up

Conversation

@frisitano

@frisitano frisitano commented Sep 2, 2026

Copy link
Copy Markdown
Contributor

Description

This PR refines the EIP-8025 spec with the following changes:

  • Changes the ProofData type to be ByteList with a size limit
  • Remove the max size checks on gossiped execution proofs as they are implied and enforced by the size limit of ProofData and the container types
  • Remove the block root consistency check between the beacon block and the execution payload as this is already enforced by the execution payload handler outside the scope of eip-8025
  • Remove the explicit check on the block state associated with the block root. If a payload exists for the block root then the beacon block must be valid
  • Update tests to reflect these changes

@github-actions github-actions Bot added testing CI, actions, tests, testing infra eip8025 Optional Execution Proofs labels Sep 2, 2026
@frisitano frisitano changed the title Refine EIP-8025 ProofData typing and size limits and simply gossip validation logic Refine EIP-8025 ProofData typing and simply gossip validation logic Sep 2, 2026
@frisitano frisitano changed the title Refine EIP-8025 ProofData typing and simply gossip validation logic Refine EIP-8025 ProofData type and simply gossip validation logic Sep 2, 2026
@frisitano frisitano changed the title Refine EIP-8025 ProofData type and simply gossip validation logic Refine EIP-8025 ProofData type and gossip validation logic Sep 2, 2026
@frisitano
frisitano marked this pull request as ready for review September 2, 2026 15:48
@frisitano

Copy link
Copy Markdown
Contributor Author

Some additional changes to the spec cc: @dapplion

Comment on lines 128 to 141
verify_execution_proof_envelope(
state,
signed_proof_envelope,
payload_envelope,
)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should be able to make this a single line now, without wrapping.

@jtraglia

jtraglia commented Sep 9, 2026

Copy link
Copy Markdown
Member

Hey @frisitano I've been sort of waiting for @dapplion to review this before giving it a proper review myself. Is that a requirement or would this be ready to review/merge without him?

@frisitano

Copy link
Copy Markdown
Contributor Author

Hey @frisitano I've been sort of waiting for @dapplion to review this before giving it a proper review myself. Is that a requirement or would this be ready to review/merge without him?

I don't think it's a requirement, just a nice-to-have. I trust that @dapplion and @nalepae will provide review and feedback as and when they have capacity.

Comment thread specs/_features/eip8025/beacon-chain.md Outdated
Comment on lines +34 to +41
*Note*: `MAX_SIGNED_EXECUTION_PROOF_ENVELOPE_SIZE` is derived from
`MAX_PROOF_SIZE` plus 145 bytes of fixed SSZ overhead: two 4-byte offsets, a
1-byte `ProofType`, a 32-byte `Root`, an 8-byte `ValidatorIndex`, and a 96-byte
`BLSSignature`.

| Name | Value |
| ------------------------------------------ | -------------------------------------------------- |
| `MAX_SIGNED_EXECUTION_PROOF_ENVELOPE_SIZE` | `Uint64(MAX_PROOF_SIZE + 145)` (= 4,194,449 bytes) |

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Clients don't really need to know how this is computed. Let's delete the note, revert this change, then add a test like the following which ensures the constant matches the computed value.

@with_gloas_and_later
@spec_test
@single_phase
def test_max_signed_execution_payload_bid_size(spec):
encoded = build_max_size_signed_execution_payload_bid(spec).encode_bytes()
assert len(encoded) == get_max_signed_execution_payload_bid_size(spec)
assert len(encoded) <= spec.config.MAX_PAYLOAD_SIZE

Comment on lines +99 to +103
raise GossipReject("execution proof envelope is invalid")

# [REJECT] The proof type is supported
if proof_envelope.proof_type not in get_supported_proof_types():
raise GossipReject("execution proof envelope is invalid")

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These error messages must be different, otherwise we would be unable to test them properly (indistinguishable). Let's do something like "execution proof is empty" and "unexpected proof type".

Comment on lines 133 to 137
payload_envelope = store.payloads[beacon_block_root]

# [IGNORE] No valid proof is known for this beacon block and proof type
if proof_envelope.proof_type in store.execution_proofs.get(beacon_block_root, {}):
raise GossipIgnore("verified proof already known for this beacon block and proof type")

# [REJECT] The execution proof envelope passes validation
state = store.block_states[beacon_block_root]
try:

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's put the state definition above this check.

state = ...
payload_envelope = ...

# [REJECT] The execution proof envelope passes validation
try:

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm I just realized that these execution proof gossip tests are unit tests, not proper reference tests. This whole file needs to be re-written eventually; to be something more like this. But don't worry about this right now. We're planning to remove all tests for non-CFI'd EIPs. Updating the testing infrastructure for every proposed EIP is not sustainable; testing updates are way more invasive than a self-contained feature spec directory.


```python
class ProofData(ProgressiveList[Byte]):
class ProofData(ByteList):

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is the rationale behind this change?

Is it to ensure that a message is not too big (thanks to checking if proof size is not higher than (MAX_PROOF_SIZE)?

(If so, the message size is already bounded at the network layer by MAX_SIGNED_EXECUTION_PROOF_ENVELOPE_SIZE)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, it bounds the size of the gossiped message. The idea of this change is to provide the bound such that the limit is encoded into the type directly instead of needing an extra check elsewhere.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

MAX_SIGNED_EXECUTION_PROOF_ENVELOPE_SIZE is not actually enforced anywhere in code. It is this change that encodes the bound into the message.

raise GossipIgnore("verified proof already known for this beacon block and proof type")

# [REJECT] The execution proof envelope passes validation
state = store.block_states[beacon_block_root]

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The new version of the spec has no

if beacon_block_root not in store.block_states:

check any more.

So, if beacon_block_root is not in store.block_states, it will generate a KeyError. Before the change it was a clear GossipReject. It would probably be better to explicitly reject in that case.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The idea is that the payload envelope will reach the store only if the associated beacon block is valid, so checking for the payload envelope is implicit check that the beacon block is valid and as such it exists in block_states without having to explicitly do the check.

Co-authored-by: Justin Traglia <95511699+jtraglia@users.noreply.github.com>
if beacon_block_root not in store.payloads:
raise GossipIgnore("execution proof's payload is unavailable")

payload_envelope = store.payloads[beacon_block_root]

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So I disagree here. If those variables were only used within that gossip check, I would agree, but that's not the case. Please see https://github.com/ethereum/consensus-specs/pull/5593/changes#r3971468388 where I suggest something different.

Image

raise GossipIgnore("execution proof's beacon block has not been seen")

# [IGNORE] No valid proof is known for this beacon block and proof type
if proof_envelope.proof_type in store.execution_proofs.get(beacon_block_root, {}):

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

execution_proofs could be DefaultDict.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

eip8025 Optional Execution Proofs testing CI, actions, tests, testing infra

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants