Conversation
This was referenced Sep 25, 2026
0w3n-d
force-pushed
the
od/gloas-step4-builder-preferences
branch
from
September 28, 2026 15:31
425994b to
d22a3ce
Compare
0w3n-d
marked this pull request as draft
September 28, 2026 15:36
0w3n-d
force-pushed
the
od/gloas-step4-builder-preferences
branch
2 times, most recently
from
September 28, 2026 17:14
42f845e to
7ba9ebd
Compare
Base automatically changed from
od/gloas-step4-builder-preferences
to
develop
September 28, 2026 17:15
helix's ExecutionPayload/SignedBidSubmission is its own bounded-list builder<->relay wire shape, shared unchanged across Bellatrix-Fulu; it is not a mirror of the real per-fork consensus SSZ types. Route the Gloas fork through the same decode path Fulu already uses instead of erroring, and add conversion functions producing the real, progressive-list Gloas consensus types (ExecutionPayloadGloas, ExecutionRequestsGloas) for use at the outbound bid/envelope boundary. block_access_list, builder_deposits, and builder_exits are left empty (TODO(gloas): EIP-7928/EIP-8282, no producer path yet).
…bmissions getExecutionPayloadBid now reads the winning submission out of the same bid-sorter/payloads map get_header already reads, converts it via the prior step's Gloas conversion functions, and signs a real SignedExecutionPayloadBid under helix's own configured builder identity. submitSignedBeaconBlock's GloasPayloadStore placeholder is replaced by a real auctioneer round trip (Event::TakeHeldGloasPayload) that looks up the same payload by block hash and converts it for envelope construction. GloasBuilderIdentity is now constructed once in main.rs and shared between the proposer API and the auctioneer. execution_payment is set equal to value (no payment-split product need yet, per #489).
The SSZ dispatch short-circuited above the fork gate #517 added, so a Gloas submission reaching an SSZ simulator was validated as Fulu. Gate both dispatch kinds, and have the simulation role refuse a fork it does not understand instead of discarding `decoder_params.fork_name`. Answer 501, not 400. The relay maps a 400 body to a demotable error, and this is helix's own limitation.
`to_lighthouse_gloas_payload` left the EIP-7928 list empty, so the envelope the relay broadcasts could never be valid. Only an execution client can produce the list, so it travels on the submission. A separate `SignedBidSubmissionGloas` rather than a fork-gated field: `Encode` is derived on `SignedBidSubmission`, so an extra field would change the bytes for every fork. Refuse Gloas with bid adjustments. Combining every extension with Gloas multiplies the wire shapes, and a testnet does not need adjustments.
Set the header's EIP-7928 block access list hash and EIP-7843 slot number from the submission, and compare the list against the one execution recomputes. Route Gloas to SSZ simulators again.
Set the header's slot number from the proposal slot and carry the block access list ethrex records, then submit it in the Gloas shape. Size the payout reserve for EIP-8037's account-creation state gas, without which a fee recipient's first payment runs out of gas.
Records the genesis, relay and builder configuration a Gloas testnet needs, including the requirements that are not discoverable from the code: the EIP-8282 predeploys, ssz_url on every simulator, and the Amsterdam gas cost of a first payment.
`rayon` is a default feature of ethrex-blockchain, ethrex-common and ethrex-vm. The pins here set `default-features = false`, and only the `ethrex` cmd crate (deliberately not a dependency) adds it back, so the embedded node has never had it. It is not optional. ethrex gates the parallel BAL execution path on it (`crates/vm/backends/levm/mod.rs:485`) but not the caller that decides whether to create the merkleizer channel (`crates/blockchain/blockchain.rs:1026`). With a BAL supplied and both `bal_parallel_*` options at their defaults, the caller skips the channel and LEVM then drops the BAL and takes the sequential path, which needs that Sender: Error executing block: sequential execution path called without a merkleizer Sender So every `newPayload` that carries a BAL fails and the node cannot follow the head. P2P sync is unaffected because it passes no BAL. Also restores the mempool prewarm and the block warmer, both of which log or no-op when the feature is off.
Gloas drops `parent_block_number` from the event, so every event failed to parse and the relay never got a slot's attributes. The field is now optional, and stays quoted on the wire for the forks that still send it. Nothing reads it yet; a future consumer must take the number from the execution layer, because Gloas will never supply it.
An empty duty list built `VALUES ON CONFLICT`, which Postgres refused, so a relay with no registered proposer logged a failure every slot. The aborted transaction changed nothing, so returning early keeps the same result without the error.
The building role's HTTP client panicked on the main thread with "Could not automatically determine the process-level CryptoProvider", which killed the process and took the simulation role and the embedded node with it. The relay and the data API already install the provider at startup; the builder now does the same.
The bid's `value` and `execution_payment` are gwei, checked against the builder's on-chain balance, but the relay put a wei figure in both. A 0.001 ETH bid claimed a million ETH, and anything above 18.4 ETH pinned to u64::MAX. The proposer is paid in-block, so `value` is now 0 and only `execution_payment` carries the amount, converted to gwei.
Lodestar types `getExecutionPayloadBid` with `VersionMeta` and parses the
JSON body as `{version, data}`, so a bare `SignedExecutionPayloadBid`
fails with "expected key version is undefined". `getHeader` already
returns `ForkVersionedResponse`; the Gloas endpoint now does the same.
The SSZ path is unchanged: it already sets `Eth-Consensus-Version`.
Helix revealed the payload the moment the proposer handed back its block, which is at the top of the slot. An equivocating proposer can build a competing block on a payload revealed that early, before any attestation weighs behind the honest block. Gloas puts the attestation deadline at 25% of the slot (ATTESTATION_DUE_BPS_GLOAS) and the payload deadline at 50%, so hold the reveal until the attestation deadline and let the proposer have its 202 straight away. Also publish the proposer's block to our own beacon nodes. Post-Gloas publishBlockV2 takes a bare SignedBeaconBlock, not SignedBlockContents. This is what makes the block root known by the time we reveal, instead of waiting for the block to come back to us over gossip.
Two ways a proposer could redeem two blocks against one bid, neither of which helix noticed. First, it could send helix two different signed blocks. `get_for_redemption` is a lookup, not a take, so the second one built and revealed a second envelope. Remember the block root committed to per slot and refuse a different one. Because the reveal is now held until the attestation deadline, an equivocation seen before then aborts the pending reveal, so the payload is withheld rather than merely logged. Registration happens only after the block is known to redeem our bid: this endpoint does not verify the proposer's signature, so an unvalidated block must not be able to withhold a legitimate reveal. Second, it could gossip a competing block helix never sees. Publish the envelope with broadcast_validation=consensus_and_equivocation so the beacon node refuses to broadcast a payload for a block it has seen a competing version of. The default is `gossip`, which skips that check.
submitSignedBeaconBlock authenticated nothing: no signature check, and no auth middleware on the route. The bid's block hash becomes public as soon as the proposer gossips its block, so anyone could redeem a bid on the proposer's behalf. That was survivable while a forged block only produced an envelope the beacon node would reject. With the equivocation guard it is not: a forged block with a different root reads as an equivocation and aborts the real reveal, so any observer could make helix withhold a legitimate payload. Carry the proposer helix bid to alongside the held payload and check the block's signature under DOMAIN_BEACON_PROPOSER before anything else acts on it. A gossiped payload carries no bid trace and so no proposer, and is refused rather than redeemed unverified.
`BuilderRequestAuth.data` is opaque and nothing reads it, so we do not know what clients put there. Log its length and a bounded prefix, on both getExecutionPayloadBid and submitBuilderPreferences, along with the slot the auth is signed for. The field runs to 4096 bytes, so the rendering is truncated and says so while still reporting the real length.
0w3n-d
force-pushed
the
od/gloas-stack-collapsed
branch
from
September 30, 2026 11:12
c8d89f5 to
0e47b77
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
This PR puts the 12 Gloas stack PRs that had no reviews into one PR, rebased onto develop. It replaces #514, #515, #562, #564, #570, #571, #575, #578, #580, #584, #587 and #629. It is stacked on #509.
With these changes, helix can run a full Gloas (ePBS) auction end to end on a devnet:
Differences from the original PRs
The rebase is not purely mechanical, because develop changed under the stack:
simulator/mod.rs.execute_block, which returns the block access list. As a result, the validation response for a Gloas block has an empty per-transaction list (tx_details).What this deliberately does not do
🤖 Generated with Claude Code