# Prysm vs Gloas spec: EIP-8282, EIP-7688, EIP-8045, EIP-8061 (item `prysm`, batch `cl-misc`)

This review only read code. Nothing was run against any network or node. Code came from shallow clones made on 2026-10-27.

## Pins

| Repo | Branch | Commit |
|---|---|---|
| OffchainLabs/prysm | develop | `eb681c1d13b57c2d0c5da27a63bb41ac9d071e1a` (2026-10-27 15:19 UTC) |
| ethereum/consensus-specs | master | `8a80ffa82bd49aac0dd6db17f9e8150aa257fbc2` (2026-10-27) |
| sigp/lighthouse | stable | `a376defa30470923556d91bdeb15a802e297f31f` (2026-10-23) |

- Short forms used below: P = `https://github.com/OffchainLabs/prysm/blob/eb681c1d13b57c2d0c5da27a63bb41ac9d071e1a/`, S = `https://github.com/ethereum/consensus-specs/blob/8a80ffa82bd49aac0dd6db17f9e8150aa257fbc2/`, L = `https://github.com/sigp/lighthouse/blob/a376defa30470923556d91bdeb15a802e297f31f/`.
- **Which spec Prysm targets:** v1.7.0-beta.4, not master. The evidence:
  - P`WORKSPACE#L285` reads `consensus_spec_version = "v1.7.0-beta.4"`.
  - P`.ethspecify.yml#L1` reads `version: v1.7.0-beta.4`.
- **Lighthouse targets the same release.** L`testing/ef_tests/Makefile#L3` reads `CONSENSUS_SPECS_TEST_VERSION ?= v1.7.0-beta.4`.
- **Beta.4 vs master.** I diffed `specs/gloas/fork.md` at tag v1.7.0-beta.4 against master. The only change is in `onboard_builders_from_pending_deposits` (builder onboarding). That matches the brief's description of spec PR 5736. I did not open the PR page itself.

## Candidates

### C1. Spec-version lag: builder onboarding at the Gloas fork follows beta.4, not master (EIP-8282 / EIP-7732 fork logic)

**(1) Spec.** S`specs/gloas/fork.md`, function `onboard_builders_from_pending_deposits`. Master reads:
```
        # Only builder-credential deposits can fund builders at the fork.
        if not is_builder_withdrawal_credential(deposit.withdrawal_credentials):
            pending_deposits.append(deposit)
            continue
        ...
        if deposit.pubkey not in builder_pubkeys:
            if not is_valid_deposit_signature(...):
                # Preserve validator top-ups when no builder can receive them.
                if deposit.pubkey in validator_pubkeys or is_pending_validator(
                    pending_deposits, deposit.pubkey
                ):
                    pending_deposits.append(deposit)
                continue
            add_builder_to_registry(...)
        else:
            ...
            state.builders[builder_index].balance += deposit.amount
```
Master's prose adds: "Other deposits stay queued, even if a builder uses the same public key." and "A valid builder registration can use a validator's public key."

Beta.4 (https://raw.githubusercontent.com/ethereum/consensus-specs/v1.7.0-beta.4/specs/gloas/fork.md) has these rules instead:
- It first runs "Deposits for existing validators stay in the pending queue / if deposit.pubkey in validator_pubkeys: … continue".
- It checks the 0xB0 prefix and `is_pending_validator` only for pubkeys that are not yet builders.

**(2) Prysm.** P`beacon-chain/state/state-native/setters_gloas.go#L795-L842`. The annotation at L743 is `<spec fn="onboard_builders_from_pending_deposits" fork="gloas" hash="e09a82dc">`.
```
		if _, ok := b.validatorIndexByPubkey(pubkey); ok {
			newPendingDeposits = append(newPendingDeposits, deposit)
			pendingIdx.Add(deposit)
			continue
		}
		builderIdx, isExistingBuilder := b.builderIndexByPubkey(pubkey)
		if isExistingBuilder {
			if err := b.increaseBuilderBalance(builderIdx, deposit.Amount); err != nil {
		...
		if !helpers.IsBuilderWithdrawalCredential(deposit.WithdrawalCredentials) {
		...
		isPending, err := pendingIdx.HasValid(deposit.PublicKey)
		...
		if isPending {
			newPendingDeposits = append(newPendingDeposits, deposit)
```
Prysm calls this from the upgrade at P`beacon-chain/core/gloas/upgrade.go#L132`.

**(3) When the results differ.** Prysm and master give different post-fork states (`builders`, `pending_deposits`, and so the state root) in three cases. Each needs a deposit still pending at the last Fulu epoch boundary:
- **(a) Builder deposit with an existing validator's pubkey.** The deposit has 0xB0 credentials and a valid signature, and its pubkey belongs to an existing validator. Master registers a new builder with that pubkey. Prysm leaves the deposit queued for the validator.
- **(b) Non-0xB0 deposit for a pubkey that just became a builder.** The deposit has non-0xB0 credentials (e.g. 0x01/0x02), and its pubkey was registered as a builder earlier in the same onboarding pass. Master keeps the deposit queued. Prysm credits it to the builder's balance and removes it from the queue.
- **(c) Valid builder deposit after a pending validator deposit.** The deposit has 0xB0 credentials and a valid signature, and an earlier pending deposit with a valid signature exists for the same new (not-yet-validator) pubkey with validator credentials. Master registers the builder, because only an *invalid* builder signature falls back to the pending-validator check. Prysm keeps the deposit queued as a validator top-up.

**(4) Lighthouse.** L`consensus/state_processing/src/upgrade/gloas.rs#L258-L304` gives the same results as Prysm. It follows beta.4:
- `// Deposits for existing validators stay in the pending queue.` then `if state.get_validator_index(&deposit.pubkey)?.is_some() { pending_deposits.push(...); continue; }`
- `Some(builder_index) => { ... builder.balance.safe_add_assign(deposit.amount)?; }`, which credits the builder whatever the deposit's credential prefix.

**(5) Confidence: high**, that this is a deviation from master.
- I tried to disprove it by diffing beta.4 against master. The diff touches exactly these lines, and Prysm's control flow matches beta.4 line for line.
- It is a lag behind master, not a bug against the release Prysm pins.
- It matters only if master's rule is what ships at the fork.

**(6) Severity:** consensus split at the Fulu→Gloas transition between master-conformant clients and beta.4 clients. It applies only when one of the deposit patterns above is pending at the fork epoch. Today Prysm and Lighthouse agree with each other.

### C2. Gloas change to `compute_min_epochs_for_block_requests` not implemented (EIP-8061)

**(1) Spec.** S`specs/gloas/p2p-interface.md`, function `compute_min_epochs_for_block_requests`:
```
    # [Modified in Gloas:EIP8061]
    numerator = 3 * CHURN_LIMIT_QUOTIENT_GLOAS * CONSOLIDATION_CHURN_LIMIT_QUOTIENT
    denominator = 2 * CONSOLIDATION_CHURN_LIMIT_QUOTIENT + 3 * CHURN_LIMIT_QUOTIENT_GLOAS
    return MIN_VALIDATOR_WITHDRAWABILITY_DELAY + (numerator // denominator) // 2
```
With the mainnet values (32768, 65536, 256), this is 256 + (6442450944 // 229376) // 2 = 256 + 14043 = **14299** epochs. I did this arithmetic myself.

**(2) Prysm.** P`config/params/mainnet_config.go#L403` reads `MinEpochsForBlockRequests: 33024, // MIN_VALIDATOR_WITHDRAWABILITY_DELAY + CHURN_LIMIT_QUOTIENT / 2`. This is a fixed value that does not change by fork. It is used for block retention and serving at:
- P`beacon-chain/db/pruner/pruner.go#L41`
- P`beacon-chain/p2p/custody.go#L142`
- P`beacon-chain/das/needs.go#L72`

**(3) When the results differ.** After Gloas, the spec's minimum serving window is 14299 epochs, but Prysm keeps serving and retaining 33024. Prysm serves *more* than the minimum, so it does not violate the "must serve" rule. It only differs if a peer relies on the shorter window, for example in its `earliest_available_slot` or status expectations.

**(4) Lighthouse.** L`consensus/types/src/core/chain_spec.rs#L1949` reads `min_epochs_for_block_requests: 33024`. It is also fixed, so Lighthouse matches Prysm, not the spec.

**(5) Confidence: medium.**
- I searched P for any Gloas-specific override: grep for `MinEpochsForBlockRequests` across the repo found only config uses.
- The deviation is real, but it is a superset of what the spec requires.

**(6) Severity:** cosmetic (resource use only).

### C3. Gloas `compute_weak_subjectivity_period` (EIP-8061)

Prysm, EIP-8061: **affects live code, details withheld.**

For contrast, Lighthouse has a separate Gloas function at L`consensus/types/src/state/beacon_state.rs#L4107-L4125`.

### No other differences found

I found no other place where Prysm's result differs from the spec for the rules below.

## Rules checked and found consistent with the spec (Prysm, at the pin)

| Rule | Prysm file#line | Note |
|---|---|---|
| `process_builder_deposit_request`: 0xB0 gate before anything else, for both new registration and top-up | P`beacon-chain/core/gloas/builder_deposit_request.go#L93-L96` (and batch pre-filter L29-L37) | Non-0xB0 deposits are dropped, as in the spec. |
| Signature only for new pubkey; top-up skips it | same file #L99-L119, #L121-L137 | Batch verification is used. If an earlier request in the same batch frees a pubkey's index, the request falls back to a single-signature check (L123-L133). |
| Top-up to an exited builder resets `withdrawable_epoch` only when `withdrawable_epoch != FAR_FUTURE` and balance == 0, checked before crediting | same file #L107-L116 | Uses `epoch + MIN_BUILDER_WITHDRAWABILITY_DELAY`. |
| `PAYLOAD_BUILDER_VERSION` on registration | P`beacon-chain/state/state-native/setters_gloas.go#L870` (onboarding passes `params.BeaconConfig().PayloadBuilderVersion`) | I did not open the inside of `AddBuilderFromDeposit` for request-time registration (P`builder_deposit_request.go#L139`). Version is assumed set there; unverified. |
| `process_builder_exit_request`: pubkey lookup, `is_active_builder`, `execution_address == source_address`, pending-withdraw == 0, then `initiate_builder_exit` | P`beacon-chain/core/gloas/builder_exit.go#L76-L112` | `InitiateBuilderExit` (L27-L43) also returns early if an exit was already started. That case can't be reached from the request path, because the builder would not be active. |
| Order of requests in `apply_parent_execution_payload`: length asserts, settle payment, deposits, withdrawals, consolidations, builder deposits, builder exits | P`beacon-chain/core/gloas/parent_payload.go#L70-L103`, #L124-L140 | Same order as the spec. Prysm takes `parent_slot` from `latest_execution_payload_bid.slot`, while the spec uses `latest_block_header.slot`. These are equal: bid slot equals block slot, and at the fork both are set to `pre.latest_block_header.slot` (P`beacon-chain/core/gloas/upgrade.go#L328`). |
| Settling a payment for a parent older than the previous epoch (appends a withdrawal only if value > 0) | P`beacon-chain/state/state-native/setters_gloas.go#L186-L211` | Matches. |
| Per-payload maximum checks on withdrawals, consolidations, builder deposits and builder exits in the state transition (EIP-7688 limits move to the consensus layer) | P`beacon-chain/core/gloas/parent_payload.go#L107-L122` | Four asserts, as in the spec. Deposits are deliberately unbounded. |
| `get_execution_requests` (engine → CL decoding) for types 0x03/0x04: strictly increasing type order, unknown type rejected, empty data rejected, length a multiple of item size | P`proto/engine/v1/electra.go#L35-L41` (types 0..4), #L96-L146, #L181-L208, #L351-L366 | Prysm also rejects lists over the per-payload maximum at decode time. The spec defers that to `apply_parent_execution_payload`, so the block-validity result is the same. Deposits use `MaxUint64/size` as their limit (L98). |
| Encoding of 0x03/0x04 requests (`EncodeExecutionRequestsGloas`) | P`proto/engine/v1/electra.go#L251-L313` | I read only the type bytes (L291, L300). I did not check the skip-empty rule line by line. |
| EIP-7916/7688 `merkleize_progressive`: subtrees with capacities 1, 4, 16, …, root = hash(subtree, successor) | P`encoding/ssz/progressive_merkleize.go#L20-L56` | Matches EIP-7916's `hash(a, b)`, where a is the first `num_leaves` chunks and b is the recursive remainder. |
| ProgressiveContainer root = mix_in_active_fields | same file #L106-L138 | Matches. |
| EIP-8045: slashed validators excluded when computing a new lookahead epoch | P`beacon-chain/core/gloas/proposer_lookahead.go#L17-L42`; P`beacon-chain/core/helpers/validators.go#L131-L142` | Called from the epoch transition at P`beacon-chain/core/transition/gloas.go#L206`. |
| EIP-8045 at the transition: lookahead copied from the pre-fork state | P`beacon-chain/core/gloas/upgrade.go` (the `upgrade_to_gloas` spec annotation at L20; `proposer_lookahead=pre.proposer_lookahead`) | Matches S`specs/gloas/fork.md` (`proposer_lookahead=pre.proposer_lookahead`). Pre-fork epochs that include slashed validators are kept, as the spec says. |
| EIP-8061 `get_activation_churn_limit` (floor to increment, capped at 256 ETH) | P`beacon-chain/core/helpers/validator_churn.go#L68-L73` | Matches. |
| EIP-8061 `get_exit_churn_limit` (uncapped) | same file #L86-L90 | Matches. |
| EIP-8061 `get_consolidation_churn_limit` (TAB // 65536, floored) | same file #L101-L105 | Matches. |
| Version dispatch for churn (`>= Gloas`) | same file #L108-L129 | Matches. |
| `compute_exit_epoch_and_update_churn` uses the exit churn | P`beacon-chain/state/state-native/setters_churn.go#L64-L95` (L69) | Matches. |
| Pending deposits use the activation churn | P`beacon-chain/core/electra/deposits.go#L278` | Matches. |
| Consolidations use the Gloas consolidation churn | P`beacon-chain/core/requests/consolidations.go#L151`, #L334; P`beacon-chain/core/electra/churn.go#L50` | Matches. |
| Constants | P`config/params/mainnet_config.go#L381-L385` | 32768, 65536, 256e9, 64, 16. These match S`configs/mainnet.yaml#L145-L149` and S`specs/gloas/beacon-chain.md#L612-L613`. |

Lighthouse references used for comparison:
- Builder deposits: L`consensus/state_processing/src/per_block_processing/process_operations.rs#L925-L972`. This agrees with the spec: 0xB0 gate at L930, signature check at L941, reset when FAR_FUTURE and balance == 0 at L960.
- Builder exits: same file #L986-L1012. Agrees with the spec.
- Exit and consolidation churn: L`consensus/types/src/state/beacon_state.rs#L3216-L3229`.

## Known-check

Searches used the GitHub search API against `repo:OffchainLabs/prysm`, covering issues and PRs, open and closed. I ran them on 2026-10-27. The API hit its unauthenticated rate limit partway through, so some queries returned nothing.

- **C1 (onboarding lag): not found.**
  - Queries: "onboard builders", "builder deposit", "v1.7.0-beta.5", "5736", "8282".
  - Related, not the same issue:
    - #16796 "Defer signature validation when onboarding builders at the Gloas fork" (closed PR)
    - #17440 "Pre-warm pending deposit signatures before the Gloas fork" (closed PR)
    - #17129 "Restrict builder deposits to payload builders" (closed PR)
    - #17147 cross-fork state-diff pending-deposits bug (closed issue)
  - These three queries hit the rate limit and returned no results: "beta.4 fork", "builder onboarding validator pubkey", "weak subjectivity".
- **C2 (MIN_EPOCHS_FOR_BLOCK_REQUESTS): not found.**
  - Queries: "MIN_EPOCHS_FOR_BLOCK_REQUESTS", "min epochs block requests gloas", "8061", "churn gloas".
  - Hits about retention/pruning that are unrelated: #17527 (open), #17675 (open).
- **C3: not reported in detail** (live code). "weak subjectivity gloas" returned 0 results.
- **Already indexed:** #17412, ProgressiveList limits on unmarshal (merged). Not re-reported.
- **EIP-8045:** #16857 "Implement EIP-8045: exclude slashed validators from proposer lookahead" (closed PR). It is the implementation, not a bug.

## Limits

- I did not check gossip validation of builder messages (`p2p-interface.md` builder checks). It was out of the reading budget.
- I did not check proposer duties served through RPC for future epochs.
- I did not check the SSZ unmarshal code generated for every Gloas container. It is covered by #17412 and the spec tests.
- I did not run any tests.

## URLs read

- https://api.github.com/repos/OffchainLabs/prysm/commits/develop (the pin; the commit was also confirmed from the clone)
- Shallow clones: https://github.com/OffchainLabs/prysm (develop), https://github.com/ethereum/consensus-specs (master), https://github.com/sigp/lighthouse (stable)
- https://raw.githubusercontent.com/ethereum/consensus-specs/v1.7.0-beta.4/specs/gloas/fork.md
- Spec files under master `specs/gloas/`: beacon-chain.md, validator.md, p2p-interface.md, weak-subjectivity.md, fork.md
- https://eips.ethereum.org/EIPS/eip-7916 (definition of `merkleize_progressive`)
- https://api.github.com/search/issues (the queries listed above)
- I did not fetch the EIP pages for 8282, 7688, 8045 and 8061. I compared against the consensus-specs text, which those EIPs defer to.
