Skip to content

WebUI builder exit button & exited-key safety - #144

Merged
pk910 merged 3 commits into
mainfrom
pk910/exit-state
Jul 22, 2026
Merged

WebUI builder exit button & exited-key safety#144
pk910 merged 3 commits into
mainfrom
pk910/exit-state

Conversation

@pk910

@pk910 pk910 commented Jul 22, 2026

Copy link
Copy Markdown
Member

WebUI builder exit button & exited-key safety

Adds a builder exit button to the WebUI and hardens the lifecycle around what an exit actually means on chain. While reviewing the exit flow against the Gloas consensus specs, it turned out that an exited builder key is effectively burned — and that buildoor's auto-topup would cycle funds through the exited entry forever. This PR ships the button together with the guards and UI flagging that make exiting safe.

Builder exit button

WebUI exit button (6528f88)

The Builder Info card gains an "Exit Builder" button (visible when lifecycle management is available, the operator is logged in, and the builder is registered/pending_finalization) with an inline confirm → submitting → done/error flow. It calls the existing POST /api/lifecycle/exit endpoint, which submits an EIP-8282 builder exit request through the exit system contract.

Also fixes Manager.InitiateExit gating: it rejected builder index 0, but 0 is a valid builder index — only the registration flag tells whether an exit can be submitted.

EIP-8282 predeploy address update + code guard (c10506c)

Updates the builder deposit/exit predeploy addresses to the current EIP-8282 draft (ethereum-genesis-generator#300). Since a stale address would silently accept requests as plain value transfers (an empty account reads storage slot 0 as zero, indistinguishable from an active contract with an empty queue), ReadQueueFee now verifies code exists at the contract address first and fails loudly with ErrContractNotDeployed otherwise. Before the Amsterdam fork this is the normal "not yet deployed" state and is treated as deferred, like a too-high queue fee.

Exit gas limit raise (c55b571)

The current EIP-8282 exit predeploy consumes ~590k gas for a request append (observed on glamsterdam-devnet-7), far above the ~100k an EIP-7002-style append would suggest. The exit gas limit now matches the deposit gas limit (1M).

Exited builder keys are burned

Checked against the Gloas consensus spec (process_builder_deposit_request): once initiate_builder_exit sets withdrawable_epoch (current + MIN_BUILDER_WITHDRAWABILITY_DELAY = 64 epochs), the builder permanently fails is_active_builder. A later deposit for the same pubkey never re-registers it — while the pubkey is still in state.builders it is treated as a top-up of the exited entry, and if the entry was already swept (balance 0), the deposit resets withdrawable_epoch to now+64, locking the funds for another 64 epochs before the sweep returns them to the wallet. The spec says it outright:

Exited builders cannot be reactivated, although a newly registered builder's public key may have previously appeared in the builder set.

The pubkey only becomes depositable again once its registry index is reused by a different builder's deposit (get_index_for_new_builder overwrites swept slots), removing the pubkey from the registry — on a devnet with few builders, potentially never. Once it's gone, GetBuilderByPubkey returns nil and the normal registration flow deposits fresh (a restart picks this up automatically).

Without guards this was a live money-cycling bug: after the sweep zeroes the balance, NeedsTopup sees 0 < threshold and the auto-topup deposits into the exited entry every cooldown — wallet → exited entry → (64 epochs locked) → wallet, burning gas + queue fee each round, forever.

Lifecycle guards

  • chain.HasBuilderExited(info): shared predicate for "exit initiated" (WithdrawableEpoch != FAR_FUTURE_EPOCH), with a table-driven test.
  • New lifecycle.ErrBuilderExited, refused by both DepositService.CreateDeposit (the single choke point for deposits and top-ups; a fresh registration with the pubkey absent from the registry still passes) and BalanceService.NeedsTopup. The balance monitor treats it as a quiet steady state (debug log); the manual /api/lifecycle/topup endpoint returns the error to the caller.
  • Manager.InitiateExit checks the live chain entry (not the 1-minute-stale cached state) and rejects:
    • already-exited builders — the beacon side ignores the request anyway, so a second exit only wastes the queue fee;
    • builders with pending payments — per spec, process_builder_exit_request silently no-ops while get_pending_balance_to_withdraw_for_builder != 0, so the transaction would confirm but the exit never happen.
  • One-time state_change warning event (noticeExitOnce) the first time an initiated exit is seen on chain — including at startup after a restart — re-armed if the key ever shows up freshly registered again.

WebUI exited-key flag

The Builder Info card shows an amber callout when the registration state is exiting/exited: the key cannot be reactivated, deposits to it are withdrawn back to the wallet, automatic top-ups are disabled, and the key only becomes usable again after its registry slot is reused by another builder's deposit. The exit button itself is already hidden in those states.

@pk910
pk910 merged commit c5ddf22 into main Jul 22, 2026
3 checks passed
@pk910
pk910 deleted the pk910/exit-state branch July 22, 2026 21:47
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.

1 participant