Add EIP: Builder Execution Requests - #11760
Conversation
Draft EIP for a builder-specific deposit predeploy that verifies BLS proof-of-possession signatures on chain via the EIP-2537 precompiles, serving the EIP-7732 builder population. A separate top_up entrypoint adds unverified stake to an already-registered builder.
Replace event-log delivery with the EIP-7685 request mechanism used by EIP-7002 (withdrawals) and EIP-7251 (consolidations): two single-type predeploys sharing a RequestQueue base, drained by a SYSTEM_ADDRESS end-of-block system call and committed via the block requests_hash. - BuilderDepositContract (request type 0x03): deposit() verifies the BLS proof-of-possession, then appends a record to its queue; no logs. - BuilderTopUpContract (request type 0x04): unverified top_up() appends a record to its queue. - No request fee: the staked value is the anti-spam gate. BLS verification and the prior audit fixes (domain separation, sign-bit binding, precompile gas caps) are unchanged. Dequeued records carry no signature, since the consensus layer trusts the execution-layer check. Tests rewritten for the queue / system-read model (14 passing). Adds requires 7685; request-type bytes and predeploy addresses are placeholders.
…fixes Request fee (like EIP-7002/7251): RequestQueue gains an excess/count fee market with fake_exponential; deposit/top_up require msg.value >= stake + fee. No EXCESS_INHIBITOR (predeploys install with empty storage, so excess starts at the minimum fee). Unsigned amount: the BLS proof-of-possession now commits only to the 2-field message (pubkey, withdrawal_credentials); amount_gwei is an explicit, unsigned parameter. Signing the amount added no security (the unverified top_up already adds unsigned stake) and would otherwise force a signed value derived from a fee unknown at signing time. The distinct 2-field message also reinforces cross-context replay protection. Round-2 audit fixes: - Queue storage is now a head/tail ring over a mapping that resets both pointers to 0 when emptied (EIP-7002 dequeue behavior), bounding storage to peak in-flight depth instead of leaking a slot per request forever. - fallback requires empty calldata, so only the system read-out / fee getter reach it. - Spec: a 0x03 record for an already-registered pubkey MUST be treated as a top-up (credit stake, never change withdrawal credentials), making the replayable deposit signature harmless. - Fixed stale entrypoint signatures in the contract header comment. Tests: 20 passing (17 without EIP-2537), incl. fee dynamics, queue reset, and fallback-calldata regressions. Vectors regenerated for the 2-field signing message.
Add the SSZ container definitions BuilderDepositRequest (pubkey, withdrawal_credentials, amount) and BuilderTopUpRequest (pubkey, amount) to the spec, matching the EIP-7002/7251 style and tying the 88/56-byte record serialization to what the contract appends; note the absence of a signature/index field versus the EIP-6110 DepositRequest. Reduce the "Consensus-layer processing of records" rules to normative statements that reference the new objects by name; the replayability rationale lives in Security Considerations.
Add BuilderWithdrawalContract (request type 0x05, EIP-7002-style) for builder partial withdrawals and full exits, and broaden the draft to the full builder lifecycle (renamed to "Builder Execution Requests").
Switch from fork-time installation to a presigned deployment transaction plus an EXCESS_INHIBITOR that blocks requests until the first end-of-block system call, matching EIP-7002/7251. Updates the spec, the RequestQueue reference contract, and the tests.
The builder deposit contract no longer verifies BLS; it carries the signature for the consensus layer to verify, bounded by the per-block cap. Top-up folds into the deposit request (0x03, register-or-credit) and the withdrawal/exit contract becomes exit-only (0x04), authorized by execution_address. Adds a normative "Changes to EIP-7732" section (remove the process_deposit_request and process_voluntary_exit builder branches; keep onboard_builders_from_pending_deposits for genesis). Updates the spec, contracts, and tests; deletes the BLS machinery and py_ecc fixtures.
…t precondition (review 2) Resolves the second adversarial review of the redesigned EIP. Builder deposits reuse DOMAIN_DEPOSIT (document the benign cross-class signature interchange instead of asserting a non-existent domain separation); give the exact process_deposit_request inert-return for a 0x03-prefix deposit plus a post-fork deposit-routing transition window so in-flight pre-fork deposits are not stranded; align the exit predicate to gloas is_active_builder and document consumed-not-retried; correct the PoP message to the 3-field DepositMessage; name the EIP-7804 0x03 collision; add Spam/state-growth and Locked-funds security notes. Spec-only; contracts and tests unchanged.
…ition window (review 3) Resolves the third adversarial review of the redesigned EIP. Require a builder first deposit to carry a 0x03-prefixed withdrawal_credentials (a consensus-layer check mirroring process_deposit_request), so a registered builder always has a well-formed execution_address and validator and builder deposits no longer cross-register; the cross-class Security note is rewritten accordingly and now records that DOMAIN_DEPOSIT is chain- and fork-agnostic. Drop the post-fork deposit-routing transition window in favour of a single deterministic cutover: the genesis snapshot onboards pending builder deposits, and from the fork onward every 0x03-credentialed validator-contract deposit is dropped (a late straggler is re-onboarded via the builder deposit contract). Document the exited-builder top-up (credited stake is non-reactivatable and sweeps to the execution_address) and the custodial-split exit standoff (a bidding operator can hold the pending balance non-zero and block the execution_address holder from exiting). Spec-only; contracts and tests unchanged.
|
✅ All reviewers have approved. |
…asm 7002/7251 contracts
The gloas bid and envelope carry an `ExecutionRequests` extended with `builder_deposits` (0x03) and `builder_exits` (0x04) per ethereum/consensus-specs#5359. Using `ssz.electra.ExecutionRequests.hashTreeRoot` in the gloas-only code paths produced a root over the electra-shaped container — wrong even when both builder lists are empty, because the gloas container has two additional zero-hash leaves. Switch the four gloas-only call sites to `ssz.gloas.ExecutionRequests`: * `produceBlockBody.ts` self-build bid construction * `verifyExecutionPayloadEnvelope.ts` bid↔envelope cross-check * `executionPayloadEnvelope.ts` gossip validation * `upgradeStateToGloas.ts` genesis bid default-value root Refs: ethereum/consensus-specs#5359, ethereum/EIPs#11760 🤖 Generated with AI assistance
|
I think the assets should be removed here. We should work on the contract in the sys-asm repo and then update the EIP later when it is final. This is also what we did for previous contracts. |
|
|
||
| ### Constants | ||
|
|
||
| All address and request-type values below are placeholders. The `0x03`/`0x04` request types MUST be unique across **all** active [EIP-7685](./eip-7685.md) request types, including in-flight proposals ([EIP-7804](./eip-7804.md), a Draft, also claims `0x03`); final allocation is coordinated in consensus-specs. |
There was a problem hiding this comment.
[...] a Draft [...]
Best not to mention the status of other proposals here. It's very likely that its status will change, making this sentence incorrect.
|
|
||
| ### Deployment | ||
|
|
||
| Each predeploy is deployed exactly as the [EIP-7002](./eip-7002.md) and [EIP-7251](./eip-7251.md) request contracts are: by a one-time presigned transaction from a single-use deployer account (the Nick's-method scheme), so that `BUILDER_DEPOSIT_CONTRACT_ADDRESS` and `BUILDER_EXIT_CONTRACT_ADDRESS` are the addresses cryptographically derived from those transactions. Each contract's init code sets its `excess` slot to `EXCESS_INHIBITOR`, so no request can be enqueued until the inhibitor is cleared (see [Request fee](#request-fee)). The concrete transactions — and therefore the final addresses — will be fixed once the runtime bytecode is audited and frozen (see [Reference Implementation](#reference-implementation)). |
There was a problem hiding this comment.
(the Nick's-method scheme)
If there's somewhere we can link to that defines this in detail (preferably in another EIP/ERC), we should.
| - **Deposit proof-of-possession at the consensus layer.** The consensus layer verifies the proof-of-possession over the `DepositMessage` `(pubkey, withdrawal_credentials, amount)` under `DOMAIN_BUILDER_DEPOSIT` on a builder's first registration, and ignores the signature for top-ups. The per-block cap bounds how many such verifications the consensus layer performs per block; see *Spam and state growth* below for the full anti-abuse picture. | ||
| - **Cross-class deposit signatures.** Builder deposits are signed under `DOMAIN_BUILDER_DEPOSIT`, a signing domain distinct from the validator deposit's `DOMAIN_DEPOSIT`. The separate domain prevents cross-contract replay between the two deposit classes: a *validator* deposit proof-of-possession cannot be resubmitted as a builder deposit, and a *builder* deposit proof-of-possession cannot be resubmitted to the validator deposit contract — each contract's consensus-layer handler verifies only its own domain, so the two classes cannot cross-register. The single exception is fork-transition onboarding: the initial builder set is seeded through the validator deposit contract *before* the fork, so those seed deposits are necessarily signed under `DOMAIN_DEPOSIT` and validated under it by `onboard_builders_from_pending_deposits`, with the `0x03` `BUILDER_WITHDRAWAL_PREFIX` on the credential distinguishing them for builder onboarding; after the fork the prefix is deprecated and steady-state builder deposits use `DOMAIN_BUILDER_DEPOSIT`. Like `DOMAIN_DEPOSIT`, the builder domain is chain- and fork-agnostic, so a builder's *own* public proof-of-possession remains replayable as a top-up (see *Replayable deposit records* below) — but only within the builder class, funding stake the original signer already authorized, and it can redirect nothing. | ||
| - **Exit authorization.** The exit contract records `msg.sender` as `source_address` and performs no further check. Because the request carries no signature, this is the sole authorization: the consensus layer MUST initiate an exit only when `source_address` equals the target builder's `execution_address`, or an arbitrary caller could exit a builder it does not control. A builder's only exit authorizer is therefore its `execution_address`; the voluntary-exit (BLS-key) path is removed for builders. | ||
| - **Custodial-split exit standoff.** A builder's exit precondition requires its pending balance to be zero (`get_pending_balance_to_withdraw_for_builder == 0`), every winning bid adds a pending payment, and the `execution_address` is the builder's sole exit authorizer (the BLS voluntary-exit path is removed). When the `execution_address` (the capital owner) and the BLS key (the bidding operator) are held by different parties — a custodial or staking-pool arrangement this design explicitly enables — the operator can keep the pending balance non-zero by continuing to win bids, so the capital owner cannot satisfy the exit precondition and the stake stays locked (a builder that never exits is never swept). The standoff is self-limiting, since the operator's bids must keep being included on-chain, but the protocol gives the `execution_address` holder no on-chain lever to halt bidding. Parties delegating builder operation SHOULD retain off-chain (contractual or operational) control over the operator's bidding, so a delegated builder can always be brought to a state in which it can exit. |
There was a problem hiding this comment.
Avoid creating requirements (defined with UPPERCASE keywords) outside of the Specification section. They're too easy to miss buried in this section. Instead, move the requirement itself to the Spec section, and discuss its security implications here.
Co-authored-by: Sam Wilson <57262657+SamWilsn@users.noreply.github.com>
SamWilsn
left a comment
There was a problem hiding this comment.
Please take care of the rest of these comments before moving into Review.
eth-bot
left a comment
There was a problem hiding this comment.
All Reviewers Have Approved; Performing Automatic Merge...
eth-bot
left a comment
There was a problem hiding this comment.
All Reviewers Have Approved; Performing Automatic Merge...
|
The commit 2d27c03 (as a parent of 944f5f0) contains errors. |
eth-bot
left a comment
There was a problem hiding this comment.
All Reviewers Have Approved; Performing Automatic Merge...
Co-authored-by: Jochem Brouwer <jochembrouwer96@gmail.com>
Head branch was pushed to by a user without write access
|
@eth-bot rerun |
jochem-brouwer
left a comment
There was a problem hiding this comment.
I am approving here to get this EIP merged, I have not done an editorial review, I have checked this extra commit 965f466 which looks fine to me. Since @SamWilsn has approved but the reviews were dismissed by a commit, will approve this here to push it through.
LGTM 😄 👍 Let's merge. @eth-bot do your thing 🎉
eth-bot
left a comment
There was a problem hiding this comment.
All Reviewers Have Approved; Performing Automatic Merge...
A new EIP that gives EIP-7732 (ePBS) builders their own
execution-layer request contracts on the EIP-7685 request bus, rather than routing
builder onboarding and exit through the validator flows. Two predeploys:
0x03pubkey ++ withdrawal_credentials ++ amount ++ signature(184 B)0x04pubkey(48 B)source_address ++ pubkey(68 B)Both contracts are line-level copies of the deployed EIP-7002/7251 system contracts
(written in geas; the
ethereum/sys-asmsource assembles byte-identically to themainnet code): same dispatch — no ABI, caller +
calldatasizeonly — EIP-1559-stylefee,
EXCESS_INHIBITOR, queue and storage layout, end-of-blockSYSTEM_ADDRESSdrain, and per-request
LOG0. The exit contract is the EIP-7002 contract minus theamount field; the deposit contract adds only two value checks (
amount >= 1 ETHingwei,
value - fee >= amount * 1 gwei). Neither performs on-chain cryptography.Addresses, request-type bytes, and runtime code are placeholders pending allocation
and audit.
Rationale
route by type instead of inspecting withdrawal-credential prefixes, keying the validator
and builder registries independently — which also lets a single public key be both a
validator and a builder (a restriction this EIP removes).
possession is verified by the consensus layer (as in EIP-7732). Delivering deposits
through a request bus capped at
MAX_REQUESTS_PER_BLOCKper block, plus the EIP-1559 feeon top of the 1-ETH stake, bounds the per-block verification work and the spam economics.
the builder's
execution_address, mirroring EIP-7002's rationale for0x01credentials.the audited, deployed EIP-7002/7251 code are kept to request-layout constants plus a
handful of comparison instructions — enumerated in each contract's header.