Skip to content

simulators/lean: correctly decode devnet5 blocks - #1542

Merged
KolbyML merged 2 commits into
ethereum:masterfrom
MegaRedHand:lean-sim-merged-block-proof
Jun 15, 2026
Merged

simulators/lean: correctly decode devnet5 blocks#1542
KolbyML merged 2 commits into
ethereum:masterfrom
MegaRedHand:lean-sim-merged-block-proof

Conversation

@MegaRedHand

@MegaRedHand MegaRedHand commented Jun 12, 2026

Copy link
Copy Markdown
Contributor

Problem

The lean simulator decodes every SignedBlock with the devnet4 wire shape:

LeanSignedBlock { block, signature: LeanBlockSignatures { attestation_signatures, proposer_signature } }

Devnet5 (leanSpec main, since leanEthereum/leanSpec#717) replaced the per-attestation signature list with a single merged multi-message aggregate proof:

class SignedBlock(Container):
    block: Block
    proof: MultiMessageAggregate  # { proof: ByteList512KiB }

Against a devnet5 client the reqresp/blocks_by_root/* tests therefore fail with:

returned block should decode from SSZ: OffsetIntoFixedPortion(4)

The fingerprint is exact: the merged-proof container opens with SSZ offset 4 (its only field is variable), which lands inside the devnet4 signatures container's fixed portion (4-byte offset + 2536-byte proposer signature). Observed against ethlambda_devnet5 in this run (reqresp/blocks_by_root/multiple_known_blocks). The rpc-compat finalized-block decoder (/lean/v0/blocks/finalized) has the same mismatch.

Note the simulator was internally inconsistent: its own devnet5 helper builds leanSpec from main, which already produces the merged-proof shape the mock types could not decode.

Fix

Keep both envelope shapes as wire-only types and select between them from the devnet under test, mirroring the existing devnet4/devnet5 split for states (LeanStateDevnet4):

  • LeanSignedBlock is the merged-proof envelope; the devnet4 envelope moves to LeanSignedBlockDevnet4
  • Scenario code works with the inner LeanBlock only. LeanBlock::from_signed_wire_ssz_bytes decodes the devnet-appropriate envelope (validating the signature payload structurally) and returns the block; no placeholder signature or proof values are stored, since no test inspects them
  • LeanBlock::to_signed_wire_ssz_bytes / encode_gossip_block wrap a block in the devnet-appropriate envelope for gossip publishes (encode_gossip_data was only ever used for blocks, so it became encode_gossip_block)
  • rpc-compat gets the same split (LeanRpcSignedBlockDevnet4 / LeanRpcSignedBlock) and decode_finalized_block returns the inner LeanRpcBlock

Working with the inner block is sound because the decode and encode paths never meet. Decode sites (reqresp, rpc-compat) only assert on block fields and never re-send what they received. Encode sites (gossip, validation) publish synthetic blocks built from scratch: the mock has no validator keys, so it has always published default (unverifiable) signatures, and the empty envelope produces the same bytes as before. There is no decode-then-re-encode path that would lose signature data; if a future test needs to relay a received signed block verbatim, both envelope types stay pub for decoding in full (or the raw payload bytes can be forwarded unchanged).

Devnet4 runs are unaffected: they keep encoding and decoding the old shape, bit-identical to before.

Testing

  • cargo test in simulators/lean passes (client_interop::two_subnet_node_builder_accepts_all_devnet4_lean_clients fails identically on master in my environment)
  • cargo clippy introduces no new warnings; cargo fmt clean

Related: leanEthereum/leanSpec#974 restores the /lean/v0/blocks/finalized helper endpoint this suite's checkpoint-sync setup probes; the two fixes are independent.

Second commit: helper crashes against leanSpec main

While verifying the fix locally, a second devnet5 breakage surfaced: the lean-spec helper is built from leanSpec main, but leanSpec restructured its packages (subspecs/typesnode/spec, leanEthereum/leanSpec#788 and follow-ups). A freshly built devnet5 helper now crashes on startup with ModuleNotFoundError: No module named 'lean_spec.subspecs', taking down every helper-dependent devnet5 test at setup. (Production results on hive.leanroadmap.org still show the helper running, which means the deployed sim image carries a pre-restructure leanSpec snapshot; a genuine rebuild would hit this.)

The second commit makes the helper runner work with both layouts: new import locations are tried first, the old ones remain as fallbacks for the pinned devnet4 helper. Verified by importing the runner in venvs of both leanSpec main (1b02fd1) and the devnet4 pin (db21cc24).

Verification

Ran hive --sim lean --sim.limit 'reqresp/blocks_by_root/multiple_known_blocks$' locally against an ethlambda devnet5 client with the helper built from leanSpec main (1b02fd1): the previously failing test passes. The devnet4 helper path is covered by import smoke tests against the pinned checkout (db21cc24).

One caveat surfaced during verification: the currently published ghcr.io/lambdaclass/ethlambda:devnet5 image (built from ethlambda's devnet5-bump-leanmultisig branch) serves the genesis anchor block with a zero-byte proof, which is invalid SSZ under MultiMessageAggregate (its offset alone needs 4 bytes). The simulator now correctly rejects that with InvalidLengthPrefix { len: 0, expected: 4 }. ethlambda main already encodes the genesis proof correctly (verified passing with an image built from ethlambda main), so this resolves itself when the published devnet5 image is rebuilt.

Not addressed here: the simulator Dockerfile's metadata ADD lines hit GitHub's unauthenticated API rate limit on local builds; worked around locally, left untouched since the hosted builder presumably authenticates.

@MegaRedHand
MegaRedHand force-pushed the lean-sim-merged-block-proof branch from 20ef450 to 1b34979 Compare June 12, 2026 18:35
Devnet5 replaced the per-attestation signature list on the signed-block
envelope with a single merged multi-message aggregate proof, matching
leanSpec's SignedBlock { block, proof: MultiMessageAggregate }. The
simulator still decoded every signed block with the devnet4 shape
SignedBlock { block, signature: BlockSignatures }, so reqresp
blocks_by_root tests failed against devnet5 clients with
OffsetIntoFixedPortion(4): the merged-proof container opens with offset
4, which lands inside the devnet4 signatures container's fixed portion.
The rpc-compat finalized-block decoder had the same mismatch.

Keep both envelope shapes as wire-only types and select between them
from the devnet under test, mirroring how the state decoder already
splits devnet formats. Scenario code now works with the inner block
only: decoding validates the envelope structurally, then returns the
block, since no test inspects signature or proof contents.
@MegaRedHand
MegaRedHand force-pushed the lean-sim-merged-block-proof branch from 1b34979 to 1770660 Compare June 12, 2026 18:42
@MegaRedHand
MegaRedHand marked this pull request as ready for review June 12, 2026 18:43
@MegaRedHand MegaRedHand changed the title simulators/lean: decode devnet5 blocks with the merged-proof shape simulators/lean: correctly decode devnet5 blocks Jun 12, 2026
@MegaRedHand
MegaRedHand force-pushed the lean-sim-merged-block-proof branch from 2f0209a to 6480eb4 Compare June 12, 2026 19:22
…e helper

leanSpec moved its packages from subspecs/types to node/spec (leanSpec
PR ethereum#788 and follow-ups), so a devnet5 helper built from leanSpec main
crashes on startup with ModuleNotFoundError and every helper-dependent
devnet5 test fails at setup. The devnet4 helper stays pinned on the old
layout, so the runner now tries the new import locations first and falls
back to the old ones, both for static imports and the dynamically
imported modules used by the compatibility patches.

The trusted gossip attestation override additionally follows two API
renames on the new layout: the voter field (validator_id ->
validator_index) and the registry-bounds predicate (is_valid ->
is_within_registry).
@MegaRedHand
MegaRedHand force-pushed the lean-sim-merged-block-proof branch from 6480eb4 to 4ab4cfd Compare June 12, 2026 19:48

@KolbyML KolbyML left a comment

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.

:shipit: looks good

@KolbyML
KolbyML merged commit 4db8f99 into ethereum:master Jun 15, 2026
6 checks passed
@MegaRedHand
MegaRedHand deleted the lean-sim-merged-block-proof branch June 16, 2026 14:44
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.

2 participants