Skip to content

Clarify pre-activation validator statuses and add deposit_inqueue for the post-Electra/Fulu deposit pipeline#618

Open
Zyra-V21 wants to merge 1 commit into
ethereum:masterfrom
migalabs:feat/clarify-pre-activation-statuses
Open

Clarify pre-activation validator statuses and add deposit_inqueue for the post-Electra/Fulu deposit pipeline#618
Zyra-V21 wants to merge 1 commit into
ethereum:masterfrom
migalabs:feat/clarify-pre-activation-statuses

Conversation

@Zyra-V21

Copy link
Copy Markdown
Contributor

Summary

The Beacon API ValidatorStatus enum (types/api.yaml) describes the two pre-activation states with Phase 0 semantics that no longer hold after Electra (EIP-6110 + EIP-7251) and Fulu.

This proposal:

  1. Renames pending_initializedpending_minBalance and clarifies its definition.
  2. Renames pending_queuedpending_lookahead and corrects its (now inaccurate) description.
  3. Adds a new deposit-level state, deposit_inqueue, for deposits that are in state.pending_deposits but not yet in state.validators.

Motivation

The current pending_queued description in types/api.yaml reads:

pending_queued — When validator is waiting to get activated, and have enough funds etc. while in the queue, validator activation epoch keeps changing until it gets to the front and make it through (finalization is a requirement here too).

This describes the Phase 0 activation mechanism, where process_registry_updates was the rate-limiting gate: validators competed for a churn-limited number of activation slots per epoch and their activation_epoch shifted as the queue drained.

That is no longer how activation works:

  • EIP-6110 moved deposit intake into state.pending_deposits, a per-epoch, churn-limited queue drained by process_pending_deposits.
  • EIP-7251 changed process_registry_updates to activate every eligible validator in a single pass (no churn, no per-epoch cap), and moved the churn gate upstream into process_pending_deposits (balance-based, ~256 ETH/epoch).

As a result, the two pre-activation states now mean:

  • The state currently called pending_initialized is really "the validator exists but its effective balance has not reached MIN_ACTIVATION_BALANCE (32 ETH), so it is not yet eligible." Its name conveys none of this.
  • The state currently called pending_queued is not a queue. Once activation_eligibility_epoch is finalized, activation_epoch is set to a fixed current_epoch + 1 + MAX_SEED_LOOKAHEAD and does not change regardless of how many other validators are activating. It is a fixed-duration lookahead, not a queue position.

The real churn-limited queue — the one that can hold a deposit for days or weeks — is state.pending_deposits, and it has no representation in the validator status enum at all, because the deposit is not yet a validator. This is the state operators actually ask about ("where is my deposit / how long until activation"), and the API cannot currently express it.

Proposed changes

1. New: deposit_inqueue (deposit-level, not a ValidatorStatus)

A deposit sits in state.pending_deposits and the pubkey is not yet in state.validators.

This is where the real churn-limited wait happens.

  • Condition: pubkey present in state.pending_deposits, absent from state.validators.
  • Observability: there is no validator object to attach a status to, so this is not returned by /eth/v1/beacon/states/{state_id}/validators. It is observable via the existing getPendingDeposits endpoint (/eth/v1/beacon/states/{state_id}/pending_deposits).

We recommend documenting deposit_inqueue as a deposit-pipeline state rather than adding it to the ValidatorStatus enum (which is, by construction, derived from registered-validator fields).

2. pending_initializedpending_minBalance

Validator exists in state.validators but its effective balance has not reached MIN_ACTIVATION_BALANCE (32 ETH), so it is not yet eligible for activation.

  • Condition: validator.activation_eligibility_epoch == FAR_FUTURE_EPOCH
  • Lasts while effective balance < 32 ETH (or, briefly, until the first epoch boundary after a full deposit stamps eligibility).

3. pending_queuedpending_lookahead

Validator is eligible (activation_eligibility_epoch set and finalized) and activation_epoch is assigned but in the future. This is a fixed 1 + MAX_SEED_LOOKAHEAD delay, not a queue.

  • Condition: (validator.activation_eligibility_epoch < FAR_FUTURE_EPOCH) and (validator.activation_epoch > current_epoch)

Status reference (pre-activation)

Current name Proposed name Condition Real meaning post-Electra
(none) deposit_inqueue in state.pending_deposits, not in state.validators The actual churn-limited queue (≤ ~8 validators/epoch at mainnet scale)
pending_initialized pending_minBalance activation_eligibility_epoch == FAR_FUTURE_EPOCH Validator created, effective balance < 32 ETH
pending_queued pending_lookahead activation_eligibility_epoch < FAR_FUTURE_EPOCH and activation_epoch > current_epoch Eligible; fixed 1 + MAX_SEED_LOOKAHEAD delay (not a queue)

All active_*, exited_*, and withdrawal_* statuses are unchanged.

Backward compatibility

deposit_inqueue is additive: documenting a deposit-pipeline state and pointing consumers at the existing getPendingDeposits endpoint breaks nothing.

The renames (pending_initializedpending_minBalance, pending_queuedpending_lookahead) are breaking for any consumer that matches on the string values (clients, explorers, dashboards, the ?status= query filter on /validators). Options for discussion:

  • A — Clarify only, do not rename. Keep pending_initialized / pending_queued, fix their descriptions, and add deposit_inqueue. Zero breakage; lowest friction to merge.
  • B — Rename with deprecation window. Introduce the new names, keep the old ones as accepted aliases (request and response) for a defined number of releases, then remove. Highest clarity, needs ecosystem coordination.

This PR is written for option B; if reviewers prefer to avoid the breaking change, it degrades cleanly to option A (the description fixes and deposit_inqueue stand on their own).

References

  • types/api.yaml — current ValidatorStatus enum and descriptions
  • EIP-6110 — Supply validator deposits on chain
  • EIP-7251 — Increase the MAX_EFFECTIVE_BALANCE
  • consensus-specs electra/beacon-chain.mdprocess_pending_deposits, process_registry_updates
  • apis/beacon/states/pending_deposits.yaml — existing getPendingDeposits endpoint

Proposal by MigaLabs, coordinated by @leobago.

Rename pending_initialized -> pending_minBalance and pending_queued ->
pending_lookahead in the ValidatorStatus enum, correcting their descriptions
for the post-Electra (EIP-6110/EIP-7251) deposit pipeline: pending_minBalance is
the pre-eligibility state (effective balance < MIN_ACTIVATION_BALANCE) and
pending_lookahead is the fixed 1 + MAX_SEED_LOOKAHEAD delay (no longer a
churn-limited queue). Document deposit_inqueue, the deposit-pipeline state for
deposits in state.pending_deposits that are not yet validators, observable via
getPendingDeposits.
Comment thread types/api.yaml
Comment on lines -57 to +59
enum: ["pending_initialized", "pending_queued", "active_ongoing", "active_exiting", "active_slashed", "exited_unslashed", "exited_slashed", "withdrawal_possible", "withdrawal_done"]
enum: ["pending_minBalance", "pending_lookahead", "active_ongoing", "active_exiting", "active_slashed", "exited_unslashed", "exited_slashed", "withdrawal_possible", "withdrawal_done"]

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this would be breaking in this format.

The 2 ideas that may be better would be either

  • v2, seems a simple option but can be annoying for something fairly trivial
  • rather than rename, add 2 new entries to the enum, and document the fact that the flow changes (eg pending_minBalance (post electra) pending_queued(pre-electra)) with an extra statement like your note to cover the flow pre / post electra.

Maybe the extra doc with new enums is adequate in this case - thoughts @nflaig ?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks @rolfyone, agreed. Renaming the existing values outright would break anything matching on the strings (and the ?status= filter); that's the breaking trade-off I called out under Backward compatibility in the description (option A vs B).

Going additive is basically option B there: add pending_minBalance and pending_lookahead as the post-Electra values, keep pending_initialized / pending_queued in the enum as deprecated aliases, and document the pre/post-Electra flow in the description. Nothing breaks for current consumers and we get a clean deprecation window, with the old names removable in a future major once everyone has moved over.

Does that work for you and @nflaig? If you're both good with it I'll update the PR accordingly.

@nflaig nflaig Jun 25, 2026

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

renaming existing values seems definitely like it requires a v2, adding new value might be debatable, but if we want to allow filtering by these new values then we would also need a v2

this api is heavily used by many consumers outside of clients so it worries me to do any such change without a v2

I also haven't looked into the actual new values in detail, maybe depending on how they work this could be different, a tldr of what this PR does would be helpful and why we need it, the PR description is way to verbose and seems AI generated to me

@nflaig nflaig Jun 25, 2026

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe the extra doc with new enums is adequate in this case - thoughts @nflaig ?

could be warranted, or we add the details to the spec itself, it's not great to rely on an external source (like a hackmd) for more details

@jshufro

jshufro commented Jun 16, 2026

Copy link
Copy Markdown
Contributor

This seems hairy to me-
apply_pending_deposit contains the is_valid_deposit_signature call, which implies that an invalid deposit frontrunning a valid one with distinct withdrawal credentials could cause the validator api to show a record in deposit_inqueue with credentials that will never be used- unless we also specify that BNs must validate the credentials at query time.

I think this is a big enough departure from an API that simply _GET_s a list of validators from the state that it should be a new endpoint, personally, if accepted at all.

E.g. /eth/v1/beacon/states/{state_id}/validator_deposits could return a list of containers that have an optional validator_info for validators that have already had deposits applied, as well as a list of pending deposits for any deposit in pending_deposits with a matching pubkey (valid or not). You would still need to admonish people against assuming the deposit will be successfully processed, or add some sort of valid_only argument to the request

@Zyra-V21

Copy link
Copy Markdown
Contributor Author

This seems hairy to me- apply_pending_deposit contains the is_valid_deposit_signature call, which implies that an invalid deposit frontrunning a valid one with distinct withdrawal credentials could cause the validator api to show a record in deposit_inqueue with credentials that will never be used- unless we also specify that BNs must validate the credentials at query time.

I think this is a big enough departure from an API that simply _GET_s a list of validators from the state that it should be a new endpoint, personally, if accepted at all.

E.g. /eth/v1/beacon/states/{state_id}/validator_deposits could return a list of containers that have an optional validator_info for validators that have already had deposits applied, as well as a list of pending deposits for any deposit in pending_deposits with a matching pubkey (valid or not). You would still need to admonish people against assuming the deposit will be successfully processed, or add some sort of valid_only argument to the request

This PR keeps deposit_inqueue as documentation only (not a ValidatorStatus, no new endpoint), just pointing at the existing getPendingDeposits. I'll add an explicit caveat to that note that pending deposits are unvalidated until applied (presence != guaranteed activation), which I think covers the concern for now.

Comment thread apis/beacon/states/pending_deposits.yaml
Comment thread types/api.yaml
Possible statuses:
- **pending_initialized** - When the first deposit is processed, but not enough funds are available (or not yet the end of the first epoch) to get validator into the activation queue.
- **pending_queued** - When validator is waiting to get activated, and have enough funds etc. while in the queue, validator activation epoch keeps changing until it gets to the front and make it through (finalization is a requirement here too).
- **pending_minBalance** - The validator exists in `state.validators` but its effective balance has not reached `MIN_ACTIVATION_BALANCE` (32 ETH), so it is not yet eligible for activation. Condition: `activation_eligibility_epoch == FAR_FUTURE_EPOCH`. (Formerly `pending_initialized`.)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what status do we return in the case that the validator has deposited 32 ETH but was not activated yet (before next epoch boundary)? There will be one epoch where no valid status exists now. Returning that the validator has not reached the min balance is wrong.

as a side note, we probably wanna follow snake_case naming, ie. rename this to pending_min_balance

Comment thread types/api.yaml
description: |
Possible statuses:
- **pending_initialized** - When the first deposit is processed, but not enough funds are available (or not yet the end of the first epoch) to get validator into the activation queue.
- **pending_queued** - When validator is waiting to get activated, and have enough funds etc. while in the queue, validator activation epoch keeps changing until it gets to the front and make it through (finalization is a requirement here too).

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what status do we use now if activation_epoch == FAR_FUTURE (eligible but not finalized), below for pending_lookahead it notes "this is a fixed 1 + MAX_SEED_LOOKAHEAD delay" but during non-finality this doesn't seem true?

I will probably need @rolfyone to chime in here he knows the Electra changes much better than I do

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

pending_queued im not sure is strictly accurate, i don't think there's an activation epoch computed until there are sufficient funds to activate, and until theres sufficient funds it's FAR_FUTURE_EPOCH
The function in question is basically is_eligible_for_activation_queue which requires that your activation balance is sufficient and that you have a activation_epoch of FAR_FUTURE_EPOCH

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.

4 participants