Skip to content

Gloas support across the ethrex builder's three roles #561

Description

@0w3n-d

Status

Two stacks exist and neither can be exercised on a Gloas testnet alone:

This issue joins them and makes the three ethrex roles Gloas-ready, so relay,
simulator, builder and merge builder can be tested together on a Gloas
testnet.

The reth simulator is explicitly out of scope; #518 stays open for it.

Why

Nothing today can produce or validate a Gloas block, so the relay stack cannot
be tested end to end.

Two findings that shape the work

1. The block access list is missing end to end, and it blocks a valid
envelope.
ExecutionPayloadGloas carries a full BlockAccessList
(EIP-7928) and a slot_number (EIP-7843); Gloas pairs with Amsterdam on the
execution layer. helix_types::ExecutionPayload has no field for it, so
#514's conversion does

block_access_list: Default::default(),   // TODO(gloas): populate once EIP-7928 tracking exists

and a test asserts the result is empty. An envelope built from that is invalid
on a real network. #489's "the wire shape needs no change" note holds for
progressive lists, which are shape-compatible; it does not cover the BAL,
which is a new field.

Resolved: the external builder keeps paying the proposer in-block, as
it does today. ePBS pays the proposer from the builder's on-chain balance, and
helix signs the bid under its own identity, so the relay pays and recoups from
the submission's in-block transfer. The building role's trailing payout needs
no change for step 4.

Resolved: the builder sends the BAL, as a sibling of blobs_bundle and
execution_requests on the Gloas submission. The builder computes it while
building, and the simulator recomputes it during validation, so a wrong one is
caught rather than trusted.

2. The SSZ simulator path has no fork gate. #517 stopped Gloas
submissions being mis-routed to the Electra-shaped RPC, but
crates/relay/src/simulator/tile.rs:363 short-circuits on ssz_url above
that guard. An SSZ simulator -- which is how the ethrex simulation role is
reached -- therefore receives Gloas submissions and validates them as Fulu.
The builder side ignores the fork too: decoder_params.fork_name reaches
validation/server.rs and is discarded.

What already works in our favour

ethrex at the pinned rev has Amsterdam: BAL recording, block_access_list_hash
on the header, engine_newPayloadV5 / engine_getPayloadV6, and
slot_number in BuildPayloadArgs. This is exactly what reth's tagged release
lacks, and why #518 is stuck.

The Gloas stack does not touch crates/relay/src/api/builder or the
housekeeper, so get_validators still serves proposer duties and the building
role's slot context needs no change.

The two stacks merge with one conflict -- two adjacent test functions in
crates/types/src/hydration.rs -- after which helix-types (35), helix-builder
(147) and helix-relay (125) all pass.

Affected surface

File / area Gloas impact
crates/relay/src/simulator/tile.rs SSZ dispatch must respect the fork gate
crates/builder/src/validation/server.rs must honour decoder_params.fork_name, not ignore it
crates/types/src/bid_submission.rs Gloas submission carries a block access list
crates/types/src/execution_payload.rs to_lighthouse_gloas_payload populates the BAL
crates/builder/src/engine/convert.rs Amsterdam-shaped payload <-> ethrex block, with BAL hash and slot number
crates/builder/src/validation/ validate an Amsterdam block
crates/builder/src/building/ build and submit one
crates/tcp-types/src/merging/ the merging protocol carries ExecutionPayloadV3 only

Steps (each becomes one PR)

Notes from step 3

  • The BAL is hashed as received, never re-encoded: the block hash commits
    to those exact bytes. This is what ethrex's own engine_newPayloadV5 does.
  • An Amsterdam block needs the two EIP-8282 predeploys (builder deposit and
    exit) in genesis, or every block is invalid. Step 3 added them to the test
    fixture; a testnet genesis needs them too, which is a step 6 runbook item.
  • Step 2 left decode_dehydrated and decode_merge able to decode a Gloas
    submission into a Fulu shape and drop the BAL. Step 3 refuses both
    combinations.
  • The relay's SSZ path always re-encodes the submission, so a Gloas one now
    travels in the Gloas wire shape with decoder_params naming it. No new field
    on SszValidationRequest, so a mixed-version deploy keeps working.
  • validate and validate_merged are now at eight and nine arguments. A
    request struct would read better and is worth its own change.

What the runbook has to cover (step 5)

Found the hard way while building steps 3 and 4:

  • The two EIP-8282 predeploys must be in the EL genesis
    (0x0000884d2AA32eAa155F59A2f24eFa73D9008282 builder deposit,
    0x000014574A74c805590AFF9499fc7A690f008282 builder exit). Without them every
    Amsterdam block is invalid: SystemContractCallFailed ... has no code after deployment.
  • Every relay simulator must have ssz_url set. Dispatch is per simulator,
    and sim_request_builder still returns None for Gloas, so any JSON-only
    (reth) simulator in the pool silently drops Gloas submissions as
    UnsupportedFork. That is Gloas (ePBS) support in helix-simulator #518 surfacing as a config requirement.
  • amsterdamTime (EL) and the Gloas fork epoch (CL) must line up. The block
    shape follows the EL fork and the submission shape the CL fork; a submission
    whose shape cannot carry the block's list is refused rather than sent.
  • Flat 21000-gas transfers to a fresh address fail under Amsterdam. Funding
    and traffic-generating tooling needs a higher limit. See the step 4 note.
  • Block merging must be off, or expect a decline per base block (Gloas (ePBS) support in the ethrex builder's merging role #576).
  • Release builds for the simulation and building roles: a 128 KiB blob
    overflows tokio's default worker stack in debug.

Notes from step 4

  • The payout needs 204600 gas under Amsterdam, not 21000, when the proposer's
    fee recipient does not yet exist.
    EIP-8037 charges state gas for the account
    the payment creates: STATE_BYTES_PER_NEW_ACCOUNT (120) x
    cost_per_state_byte (1530) = 183600, on top of the transfer. With the old
    fixed reserve every such block failed with PayoutReverted, and a fresh
    testnet is exactly the case where fee recipients are absent. The reserve is
    now read from in-block state and the constants come from ethrex-levm.
  • Related, and not something the builder can fix: under Amsterdam an ordinary
    21000-gas transfer to a fresh address also fails. Such txs are included with
    failed receipts.
  • The block shape follows the EL fork (is_amsterdam_activated(timestamp)) and
    the submission shape the CL fork (chain_info.fork_at_slot). The relay
    decodes by current_fork_name(), so the builder must read the same spec.
    A submission whose shape cannot carry the block's list is refused rather than
    sent, because it would die as a block hash mismatch.
  • crates/builder/src/building/assemble/tests.rs now runs the full loop --
    build, sign, then validate with our own simulation role -- for both forks.
    That is the only test that catches a step 3 / step 4 disagreement.

Measured: the block access list's size

Measured against the Amsterdam fixture, so these are ethrex's own encoding at
the pinned revision. Per-item costs, counted the way EIP-7928's cap counts:

item bytes
account (address + balance/nonce change) ~70
storage read (slot only) ~33
storage change (slot + value + index) ~71
  • A realistic busy block is small. 400 transfers across 400 distinct
    recipients produced a 29.7 KB list.
  • The worst case is bounded by the EIP itself. EIP-7928 caps items at
    gas_limit / 2000, so the list cannot exceed roughly 0.4-0.9 MB at a 25M
    gas limit
    , 0.7-1.6 MB at 45M, and 1.0-2.1 MB at 60M -- the range
    spanning an all-reads and an all-changes list.

Conclusion: the builder sends it, as decided. A typical block costs tens of
KB, and even the adversarial maximum stays inside the relay's existing
MAX_PAYLOAD_LENGTH. Having the simulator return the list instead would save
nothing worth the round trip, and would mean trusting a list nobody committed
to.

Open questions

  • Which EL/CL pair the target testnet actually runs. The work assumes Gloas CL
    with Amsterdam EL.

Activity

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions