You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
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_urlabove
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
Step 1: refuse Gloas on the SSZ simulator path, on both sides -- the
relay must not dispatch a fork it cannot route, and the simulation role
must reject a fork it does not understand rather than validating it as
Fulu (tests: a Gloas SSZ request is refused with a clear error; Fulu is
unaffected) (PR: Refuse an unsupported fork on the SSZ validation path #564)
Step 2: carry the block access list on the Gloas submission, and
populate it in to_lighthouse_gloas_payload, removing the TODO (tests:
SSZ round-trip of a Gloas submission; the converted Gloas payload
reproduces the BAL; non-Gloas forks are byte-identical to before) (PR: Carry the builder's block access list on a Gloas submission #570)
Step 3: simulation role validates a Gloas/Amsterdam block -- payload to
ethrex block including the BAL hash and slot number, the recomputed BAL
must match the submitted one, and the relay routes Gloas to SSZ
simulators again (tests: a valid Amsterdam block passes; a tampered BAL
is rejected; the block hash covers the BAL hash) (PR: Validate a Gloas/Amsterdam block in the simulation role #571)
Step 4: building role builds and submits a Gloas/Amsterdam block
(tests: the built block carries a BAL and a slot number; the submission
round-trips; our own simulation role accepts it) (PR: Build and submit a Gloas/Amsterdam block #575)
Step 5: merging role handles a Gloas payload over the merging
protocol -- moved to Gloas (ePBS) support in the ethrex builder's merging role #576. It needs a merging-protocol wire-format
change, unlike the additive work in steps 3 and 4, and the testnet does
not depend on it: the merging role already declines Amsterdam bases by
name rather than merging them wrongly. Merged block building is therefore
unavailable on Gloas, not broken.
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.
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.
Status
Two stacks exist and neither can be exercised on a Gloas testnet alone:
getExecutionPayloadBid/submitBuilderPreferences/submitSignedBeaconBlock.roles, all Fulu-shaped.
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.
ExecutionPayloadGloascarries a fullBlockAccessList(EIP-7928) and a
slot_number(EIP-7843); Gloas pairs with Amsterdam on theexecution layer.
helix_types::ExecutionPayloadhas no field for it, so#514's conversion does
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_bundleandexecution_requestson the Gloas submission. The builder computes it whilebuilding, 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:363short-circuits onssz_urlabovethat 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_namereachesvalidation/server.rsand is discarded.What already works in our favour
ethrex at the pinned rev has Amsterdam: BAL recording,
block_access_list_hashon the header,
engine_newPayloadV5/engine_getPayloadV6, andslot_numberinBuildPayloadArgs. This is exactly what reth's tagged releaselacks, and why #518 is stuck.
The Gloas stack does not touch
crates/relay/src/api/builderor thehousekeeper, so
get_validatorsstill serves proposer duties and the buildingrole'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
crates/relay/src/simulator/tile.rscrates/builder/src/validation/server.rsdecoder_params.fork_name, not ignore itcrates/types/src/bid_submission.rscrates/types/src/execution_payload.rsto_lighthouse_gloas_payloadpopulates the BALcrates/builder/src/engine/convert.rscrates/builder/src/validation/crates/builder/src/building/crates/tcp-types/src/merging/ExecutionPayloadV3onlySteps (each becomes one PR)
Gloas stack (Serve real Gloas bids and envelopes from the auctioneer's existing submissions #515); resolve the one
hydration.rstest conflict(tests: the existing suites of all three crates pass) (PR: Merge the ethrex builder stack into the Gloas stack #562)
relay must not dispatch a fork it cannot route, and the simulation role
must reject a fork it does not understand rather than validating it as
Fulu (tests: a Gloas SSZ request is refused with a clear error; Fulu is
unaffected) (PR: Refuse an unsupported fork on the SSZ validation path #564)
populate it in
to_lighthouse_gloas_payload, removing the TODO (tests:SSZ round-trip of a Gloas submission; the converted Gloas payload
reproduces the BAL; non-Gloas forks are byte-identical to before) (PR: Carry the builder's block access list on a Gloas submission #570)
ethrex block including the BAL hash and slot number, the recomputed BAL
must match the submitted one, and the relay routes Gloas to SSZ
simulators again (tests: a valid Amsterdam block passes; a tampered BAL
is rejected; the block hash covers the BAL hash) (PR: Validate a Gloas/Amsterdam block in the simulation role #571)
(tests: the built block carries a BAL and a slot number; the submission
round-trips; our own simulation role accepts it) (PR: Build and submit a Gloas/Amsterdam block #575)
Step 5: merging role handles a Gloas payload over the merging-- moved to Gloas (ePBS) support in the ethrex builder's merging role #576. It needs a merging-protocol wire-formatprotocol
change, unlike the additive work in steps 3 and 4, and the testnet does
not depend on it: the merging role already declines Amsterdam bases by
name rather than merging them wrongly. Merged block building is therefore
unavailable on Gloas, not broken.
covering relay, simulator and builder (PR: Document Gloas support, add a testnet runbook, and publish a helix-builder image #578)
Notes from step 3
to those exact bytes. This is what ethrex's own
engine_newPayloadV5does.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.
decode_dehydratedanddecode_mergeable to decode a Gloassubmission into a Fulu shape and drop the BAL. Step 3 refuses both
combinations.
travels in the Gloas wire shape with
decoder_paramsnaming it. No new fieldon
SszValidationRequest, so a mixed-version deploy keeps working.validateandvalidate_mergedare now at eight and nine arguments. Arequest 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:
(
0x0000884d2AA32eAa155F59A2f24eFa73D9008282builder deposit,0x000014574A74c805590AFF9499fc7A690f008282builder exit). Without them everyAmsterdam block is invalid:
SystemContractCallFailed ... has no code after deployment.ssz_urlset. Dispatch is per simulator,and
sim_request_builderstill returnsNonefor 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 blockshape 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.
and traffic-generating tooling needs a higher limit. See the step 4 note.
overflows tokio's default worker stack in debug.
Notes from step 4
fee recipient does not yet exist. EIP-8037 charges state gas for the account
the payment creates:
STATE_BYTES_PER_NEW_ACCOUNT(120) xcost_per_state_byte(1530) = 183600, on top of the transfer. With the oldfixed reserve every such block failed with
PayoutReverted, and a freshtestnet 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.21000-gas transfer to a fresh address also fails. Such txs are included with
failed receipts.
is_amsterdam_activated(timestamp)) andthe submission shape the CL fork (
chain_info.fork_at_slot). The relaydecodes 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.rsnow 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:
recipients produced a 29.7 KB list.
gas_limit / 2000, so the list cannot exceed roughly 0.4-0.9 MB at a 25Mgas 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 savenothing worth the round trip, and would mean trusting a list nobody committed
to.
Open questions
with Amsterdam EL.