| Client / source | Branch | Commit | Spec commit |
|---|---|---|---|
| Prysm ([pin API](https://api.github.com/repos/OffchainLabs/prysm/commits/develop)) | `develop` | `eb681c1d13b57c2d0c5da27a63bb41ac9d071e1a` | `8a80ffa82bd49aac0dd6db17f9e8150aa257fbc2` |
| Teku ([pin API](https://api.github.com/repos/Consensys-Incorporated/teku/commits/master)) | `master` | `2c35574b3373cbe8ad121a768d98f066aec05b73` | `8a80ffa82bd49aac0dd6db17f9e8150aa257fbc2` |
| Nimbus ([pin API](https://api.github.com/repos/status-im/nimbus-eth2/commits/stable)) | `stable` | `0abc0dd0fc0750a8875c49089d074f7d7197b62c` | `8a80ffa82bd49aac0dd6db17f9e8150aa257fbc2` |
| Nimbus comparison ([pin API](https://api.github.com/repos/status-im/nimbus-eth2/commits/unstable)) | `unstable` | `5babd698516450d8b8e60c2af610a5853baaa9a9` | `8a80ffa82bd49aac0dd6db17f9e8150aa257fbc2` |
| Lighthouse comparison ([pin API](https://api.github.com/repos/sigp/lighthouse/commits/stable)) | `stable` | `a376defa30470923556d91bdeb15a802e297f31f` | `8a80ffa82bd49aac0dd6db17f9e8150aa257fbc2` |
| [ethereum/consensus-specs pin](https://api.github.com/repos/ethereum/consensus-specs/commits/master) | `master` | `8a80ffa82bd49aac0dd6db17f9e8150aa257fbc2` | — |
| [ethereum/consensus-specs pin](https://api.github.com/repos/ethereum/consensus-specs/commits/v1.7.0-beta.4) | `v1.7.0-beta.4` | `aa16bb4c156184e9548a997d53efaf2a228a6304` | — |
| [ethereum/EIPs pin](https://api.github.com/repos/ethereum/EIPs/commits/master) | `master` | `d4c575521d99ec881a444ab3af604e684b5f0743` | — |
| [ethereum/execution-specs pin](https://api.github.com/repos/ethereum/execution-specs/commits/forks/amsterdam) | `forks/amsterdam` | `16898000911c1e3caa57be08509536c10ae92d3e` | — |

# cl-state merge 1: Prysm, Teku, Nimbus

Merged solely from supplied item-prysm, item-teku and item-nimbus reviews dated 2026-10-27. Four candidates are retained verbatim below except their heading identifiers. No new research or findings were added. “Not found” records search limits, not novelty or a clean test result. All candidate code was reported as Gloas-only, not active mainnet code.

Citation keys retain each item's notation. Prysm: `P`, `S`, `L`, `B`, `E`, `A` mean Prysm, spec master, Lighthouse, spec beta.4, EIPs and execution-specs respectively; `X:path#Lx-Ly` expands to `https://github.com/REPO/blob/COMMIT/path#Lx-Ly` using the pins above. Nimbus: `NB` and `PY` use the Nimbus stable and Prysm blob prefixes; `SP` uses the spec master blob prefix plus `specs/gloas/`. Teku: `T` uses its pinned raw.githubusercontent.com prefix, `G/` expands to `ethereum/spec/src/main/java/tech/pegasys/teku/spec/logic/versions/gloas/`, and `S` uses the spec master raw prefix plus `specs/gloas/`. These local notations are scoped to their client sections.

Teku reports its line ranges as ±3 lines (repeat raw fetches differed by up to two); its quotes are exact, but spec citations identify pinned files/functions without line numbers. Those source limitations are preserved, not repaired by guessing. Nimbus reports equivalent reviewed behavior on stable and unstable; its citations use stable. All three report beta.4 onboarding rather than master/PR 5736 semantics. The source reports do not establish a divergent queue on any public devnet.

### Fork scope and versions

The exact **Scheduled for Inclusion** set in the pinned meta EIP is: **2780, 7688, 7708, 7732, 7778, 7843, 7928, 7954, 7976, 7981, 7997, 8024, 8037, 8038, 8045, 8061, 8246, 8282**. Networking EIPs **7975, 8070, 8136, 8159, 8189** and informational/optional **7904, 8261** are separately listed under Other EIPs, not added to that scheduled set. Source: `E:EIPS/eip-7773.md#L24-L62`.

The pins above define current EIP, EL and CL spec snapshots for the scheduled set. The EL Amsterdam change inventory contains 2780, 7708, 7778, 7843, 7928, 7954, 7976, 7981, 7997, 8024, 8037, 8038, 8246, 8282: `A:src/ethereum/forks/amsterdam/__init__.py#L1-L23`. The CL inventory names 7688, 7732, 7843, 8070, 8045, 8061, 8261, 8282 and explicitly pins historical EIP revisions: `S:specs/gloas/beacon-chain.md#L164-L186`. Other clients/EIPs were not reviewed.

The meta EIP leaves Gloas mainnet activation blank: `E:EIPS/eip-7773.md#L69-L79`. Candidates are Gloas-specific.

## Retained candidates

### Prysm-1 — spec-version lag: fork onboarding still uses beta.4 registry/queue separation

**1. Spec, verbatim.** Master says “Only deposits with builder withdrawal credentials can fund builders” and “A valid builder registration can use a validator's public key.” Code:

```python
if not is_builder_withdrawal_credential(deposit.withdrawal_credentials):
    pending_deposits.append(deposit)
    continue
```

For a builder-credential deposit whose pubkey is not yet a builder, master tests the deposit signature before registration; existing-validator or pending-validator membership only preserves the deposit when that signature is invalid. Source: `S:specs/gloas/fork.md#L65-L127`. Changed by [PR 5736](https://github.com/ethereum/consensus-specs/pull/5736).

**2. Prysm, verbatim at P.** `P:beacon-chain/state/state-native/setters_gloas.go#L808-L843` instead first executes:

```go
if _, ok := b.validatorIndexByPubkey(pubkey); ok {
    newPendingDeposits = append(newPendingDeposits, deposit)
    pendingIdx.Add(deposit)
    continue
}
```

It next looks up the builder and executes `b.increaseBuilderBalance(builderIdx, deposit.Amount)` with a `continue` **before** the withdrawal-prefix test. Finally, `if isPending {` retains a builder-credential deposit before its own signature is checked. Registration follows in `P:beacon-chain/state/state-native/setters_gloas.go#L852-L876`. Fork caller: `P:beacon-chain/core/gloas/upgrade.go#L120-L136`.

**3. Differing conditions.** Three pending-queue shapes distinguish this version lag:

- A valid builder-credential deposit uses an existing validator's key. Master registers a builder and consumes the deposit; Prysm retains it for validator processing and leaves the builder absent.
- A valid ordinary-validator deposit precedes a valid builder-credential deposit with the same new key. Master leaves the first deposit pending and registers the builder from the second; Prysm leaves both pending.
- A valid builder registration is followed by an ordinary-credential deposit for that key. Master preserves the latter in the pending queue; Prysm consumes it as a builder top-up, regardless of its credentials.

These queue shapes change the upgrade state root. Public-devnet occurrence is unconfirmed. Pre-fork validator-contract onboarding is permitted: `S:specs/gloas/builder.md#L62-L95`.

**4. Lighthouse at L.** Agrees with Prysm/beta.4. `L:consensus/state_processing/src/upgrade/gloas.rs#L258-L304` first retains existing-validator deposits, next branches on builder membership, checks credentials only for non-builders, and blocks a builder registration when `valid_pending_validator_pubkeys.contains(&deposit.pubkey)`. Its existing-builder arm unconditionally executes `builder.balance.safe_add_assign(deposit.amount)?;`. Both clients lag master.

**5. Confidence and attempted disproof.** **High**, as version lag. Compared both complete spec functions, the actual fork caller, pending-validator signature handling, registration validation and Lighthouse. Registration examples require valid signatures. The early top-up cannot reach the later prefix check. Both clients conform to beta.4 here; a network uniformly targeting beta.4 does not split.

**6. Severity.** **Consensus split**, if some participants implement master/PR 5736 while others retain this onboarding behavior and a distinguishing queue exists at activation. 

**Known-check:** **not found as this Prysm bug**, with the queries recorded below. Spec change **known**, [PR 5736](https://github.com/ethereum/consensus-specs/pull/5736), merged. Prysm [17387](https://github.com/OffchainLabs/prysm/pull/17387), merged, addresses quadratic signature work, not these new semantics; [17447](https://github.com/OffchainLabs/prysm/issues/17447), closed, addresses older bid-field initialization, not this queue rule. No matching Hive/EST result found (limits below).

### Prysm-2 — bid seen-cache written before all gossip checks succeed

**1. Spec, verbatim.** The gossip rule checks gas-limit compatibility and other conditions before writing:

```python
seen.execution_payload_bids.add(bid_key)
seen.best_execution_payload_bid[best_bid_key] = bid.value
```

Gas-limit failure raises `GossipIgnore`. Sources: `S:specs/gloas/p2p-interface.md#L968-L983`, `S:specs/gloas/p2p-interface.md#L1033-L1090`.

**2. Prysm, verbatim at P.** `P:beacon-chain/sync/validate_execution_payload_bid.go#L115-L165` executes:

```go
if err := v.VerifySignature(st); err != nil {
    return pubsub.ValidationReject, err
}
s.setSeenExecutionPayloadBid(bid.Slot(), tupleKey)
```

Later checks include coverage, exit, execution parent, gas limit, branch and descendant slot:

```go
if err := v.VerifyGasLimitTargetCompatible(parentGasLimit, pref.TargetGasLimit); err != nil {
    return pubsub.ValidationIgnore, err
}
```

The key omits gas limit/value; matching tuples are ignored: `P:beacon-chain/sync/validate_execution_payload_bid.go#L69-L74`, `P:beacon-chain/sync/validate_execution_payload_bid.go#L189-L206`. No rollback follows.

**3. Differing condition, in words.** An active funded builder sends a correctly signed bid passing early checks but failing gas-limit compatibility. Prysm ignores it and marks the tuple seen. A newly signed correction with compatible gas limit and the same slot, parent hash/root and builder index is then ignored as seen; the spec can accept it because its first attempt never updated the cache. Assume available parent/state/preferences, no higher bid, blacklist or exit, and compatible head. 

**4. Lighthouse at L.** Agrees with the spec on cache-write timing. Gas-limit checks precede success; state coverage, exit checks and signature verification precede `observe_bid`: `L:beacon_node/beacon_chain/src/payload_bid_verification/gossip_verified_bid.rs#L416-L479`. Its cache records the seen tuple in `observe_bid`: `L:beacon_node/beacon_chain/src/payload_bid_verification/payload_bid_cache.rs#L83-L115`.

**5. Confidence and attempted disproof.** **Medium** for conformance, high for ordering. Traced subscriber, key construction and error returns: the highest-bid cache updates only after acceptance and cannot undo the separate seen-cache write. BLS-before-write disproves unauthenticated poisoning; a builder must sign both attempts. The spec allows anti-spam measures: irrevocable signed-tuple dedup may be intentional policy, although it differs from the executable rules. Requires maintainer/spec clarification.

**6. Severity.** **Cosmetic**: gossip **ACCEPT→IGNORE** and bounded propagation/liveness loss. No invalid block acceptance, consensus split, or fork-choice divergence established.

**Known-check:** **not found as this exact bug**. Related known changes: [16832](https://github.com/OffchainLabs/prysm/pull/16832), merged, moves the dedup *read* early; [16539](https://github.com/OffchainLabs/prysm/pull/16539), merged, introduces bid gossip/dedup; [17237](https://github.com/OffchainLabs/prysm/pull/17237), closed/unmerged, discusses tuple changes; [17405](https://github.com/OffchainLabs/prysm/pull/17405), merged, fixes which parent's gas limit to read. Their descriptions do not identify this write-before-failure issue; no corroborating Hive/EST result found.


### Teku-1 — spec-version lag: builder onboarding at the Gloas fork follows beta.4, not master (PR 5736)

**(1) Spec (master), `S` + `fork.md`, `onboard_builders_from_pending_deposits`.** Verbatim lines, in order:
- `# Only builder-credential deposits can fund builders at the fork.`
- `if not is_builder_withdrawal_credential(deposit.withdrawal_credentials):` → `pending_deposits.append(deposit)` → `continue`
- `builder_pubkeys = [builder.pubkey for builder in state.builders]`
- `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.append(deposit)`
    - `continue`
  - `add_builder_to_registry(` … `PAYLOAD_BUILDER_VERSION,` …
- `else:` → `state.builders[builder_index].balance += deposit.amount`

Beta.4 (https://raw.githubusercontent.com/ethereum/consensus-specs/v1.7.0-beta.4/specs/gloas/fork.md) checks in a different order:
1. `# Deposits for existing validators stay in the pending queue` / `if deposit.pubkey in validator_pubkeys:` → keep, `continue`.
2. Then, under `if deposit.pubkey not in builder_pubkeys:`:
   - `if not is_builder_withdrawal_credential(...)` → keep
   - `if is_pending_validator(pending_deposits, deposit.pubkey):` → keep
   - `if not is_valid_deposit_signature(...)` → `continue` (drop)
   - otherwise `add_builder_to_registry`
3. `else:` → top up the builder.

PR #5736 (https://api.github.com/repos/ethereum/consensus-specs/pulls/5736) summarises the change:
- "Validator deposits stay in the validator queue, even if a builder uses the same key."
- A valid builder deposit registers or tops up a builder even if a validator uses the same key.

**(2) Teku**, `T` + `G/forktransition/GloasStateUpgrade.java`. `upgrade` calls `onboardBuildersFromPendingDeposits(state)` at #L205. The method body is #L209-L318:
- #L222: the loop walks `state.getPendingDeposits()` in queue order.
- #L224-L228: if `getValidatorIndex` finds the pubkey, the deposit is kept and the loop continues. This happens before any credential check, which is the beta.4 order.
- #L229-L237: if `getBuilderIndex` finds the pubkey, the builder's balance is increased by the amount and the deposit is consumed. The deposit's credentials are not checked on this path.
- #L246-L249: `predicates.isBuilderWithdrawalCredential(...)` false → kept, and recorded in `PendingValidatorTracker`.
- #L251-L253: if the pubkey is a pending validator → kept.
- #L255-L268:
  - `isValidDepositSignature` false → dropped (#L259-L260).
  - Otherwise `addBuilderToRegistry(... PAYLOAD_BUILDER_VERSION ...)` is called.
- #L272-L273: the surviving deposits are written back.

**(3) When results differ.** Each case requires the fork-epoch state's `pending_deposits` to contain the deposits described. The resulting post-fork state root differs in every case:
- **(a)** A pending deposit has the 0xB0 builder credential and a **valid** signature, and its pubkey already belongs to a validator.
  - Master: a builder is registered with that pubkey and the deposit is consumed.
  - Teku: the deposit stays in `pending_deposits` and no builder is created.
- **(b)** A pending deposit has a non-0xB0 credential (0x00/0x01/0x02), and its pubkey belongs to a builder created earlier in the same onboarding pass by an earlier 0xB0 deposit.
  - Master: the deposit stays in the validator queue.
  - Teku: the builder's balance is credited with the deposit and the deposit is removed.
- **(c)** A pending deposit has the 0xB0 credential and a valid signature, and an earlier deposit in the queue with a non-builder credential and a valid signature already made the pubkey a pending validator.
  - Master: a builder is registered.
  - Teku: the deposit stays queued.

Cases that agree with master:
- An invalid-signature 0xB0 deposit for a known or pending validator: kept in both.
- An invalid-signature 0xB0 deposit for an unknown pubkey: dropped in both.
- A 0xB0 deposit for an existing builder: credited in both, with no signature check in either.

**(4) Lighthouse stable** `a376defa`, `consensus/state_processing/src/upgrade/gloas.rs#L235-L318`. Lighthouse **agrees with Teku (beta.4)**:
- #L260 `if state.get_validator_index(&deposit.pubkey)?.is_some() {` and #L261 `pending_deposits.push(deposit.clone());` come first.
- The builder top-up is at #L297-L302.
- `!is_builder_withdrawal_credential` is at #L268, inside the not-a-builder arm.
- The pending-validator check is #L278; the signature checks are #L270 and #L283.

The brief says Prysm also follows beta.4; Prysm was not read for this item.

**(5) Confidence: high** that Teku's code differs from master. **Medium** on practical reach: it needs a deposit of the shapes above in the pending queue at the fork epoch.

Disproof attempts:
- (i) I fetched beta.4 and master `fork.md` and compared them line by line. Teku matches beta.4 in branch order and outcomes.
- (ii) I checked whether Teku reorders the checks elsewhere. `upgrade` calls only this method (#L205).
- (iii) I checked whether master's change might be test-only. It changes the executable spec, and Teku's PR #11582 says the fork reference tests change with it.

The difference stands at the pinned commit.

**(6) Severity: consensus split** at the Gloas fork epoch, between clients on master (spec) and clients on beta.4 (Teku, Lighthouse, Prysm). Under the brief's taxonomy it is a **spec-version lag**. It disappears once #11582 or an equivalent fix is merged and clients converge.

**Known-check:** **known**.
- https://github.com/Consensys-Incorporated/teku/pull/11582: "Route pending deposits by credentials at Gloas builder onboarding". **Open** at the time of reading; created 2026-10-27, head `40935962690daedd83ac5ed53a96557d15641153`. It implements consensus-specs #5736.
- Tracking issue https://github.com/Consensys-Incorporated/teku/issues/11510 ("look into consensus specs -builder deposits at upgrade", links PR 5736): **closed** 2026-10-19.


### Nimbus-1. Spec-version lag: `onboard_builders_from_pending_deposits` follows beta.4, not master (PR 5736)

**(1) Spec (master).** `SP fork.md`, section "New `onboard_builders_from_pending_deposits`":

> "Only deposits with builder withdrawal credentials can fund builders. Other deposits stay queued, even if a builder uses the same public key. A valid builder registration can use a validator's public key."

```python
for deposit in state.pending_deposits:
    # Only builder-credential deposits can fund builders at the fork.
    if not is_builder_withdrawal_credential(deposit.withdrawal_credentials):
        pending_deposits.append(deposit)
        continue
    builder_pubkeys = [builder.pubkey for builder in state.builders]
    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:
        ...balance += deposit.amount
```

beta.4 (https://github.com/ethereum/consensus-specs/blob/v1.7.0-beta.4/specs/gloas/fork.md) works differently. It first keeps every deposit whose pubkey belongs to an existing validator. It then tops up any existing builder whatever the credential. For a new pubkey, it keeps the deposit if the pubkey is a pending validator and drops it if the signature is invalid.

**(2) Client.** `NB beacon_chain/spec/beaconstate.nim#L2308-L2405` (the code cites "v1.7.0-alpha.12" at L2308):

```nim
L2360  for deposit in state.pending_deposits:
L2361    # Deposits for existing validators stay in the pending queue
L2362    if findValidatorIndex(
L2363        state.validators.asSeq, bucket_sorted_validators[],
L2364        deposit.pubkey).isSome:
L2365      pending_deposits.add(deposit)
L2366      continue
...
L2375    if opt_builder_index.isNone:
...
L2378      if not is_builder_withdrawal_credential(deposit.withdrawal_credentials):
L2379        add_candidate_validator_deposit(deposit)
L2380        continue
L2382      if is_pending_validator(deposit.pubkey):
L2383        pending_deposits.add(deposit)
L2384        continue
L2386      if not verify_deposit_signature_cached(
...                continue
...    else:
L2401      # Top up the balance of the existing builder
L2402      state.builders.mitem(opt_builder_index.get).balance += deposit.amount
```

It is called from `upgrade_to_next(fulu→gloas)` at `NB beacon_chain/spec/beaconstate.nim#L3104`.

**(3) When results differ.** The post-fork state (`builders`, `pending_deposits`, and therefore the state root of the first Gloas state) differs from master when the Fulu `pending_deposits` queue holds any of the following at the fork:
- (a) A deposit with builder (0xB0) credentials whose pubkey is already a validator. Master onboards it as a builder (valid signature) or keeps it queued (invalid signature). Nimbus always keeps it queued for the validator.
- (b) A deposit with builder credentials and a valid signature whose pubkey also has a valid pending validator deposit earlier in the queue. Master creates the builder. Nimbus keeps the deposit queued.
- (c) A deposit with builder credentials and an invalid signature whose pubkey is a validator or a pending validator. **Disproof:** master keeps it queued, and Nimbus also keeps it (via the existing-validator branch or `is_pending_validator`). Both versions agree, so this is not a difference.
- (d) A deposit with non-builder credentials (0x00/0x01/0x02) whose pubkey became a builder earlier in the same onboarding pass. Master leaves it queued. Nimbus adds its amount to the builder's balance.

These differences are against master. Against beta.4, Nimbus matches.

**(4) Prysm.** `PY beacon-chain/state/state-native/setters_gloas.go#L795-L850` (`OnboardBuildersFromPendingDeposits`) has the same beta.4 structure:
- an existing validator pubkey → keep;
- an existing builder → `increaseBuilderBalance` whatever the credential;
- non-builder credentials → keep;
- `pendingIdx.HasValid` → keep;
- otherwise check the signature and add the builder.

It is called from `PY beacon-chain/core/gloas/upgrade.go#L132`. Prysm therefore **agrees with Nimbus (beta.4)**, not with master.

**(5) Confidence: high** that the code is beta.4 and differs from master. I tried to disprove this in four ways:
- I checked that the client's `SPEC_VERSION` is beta.4 on both branches.
- I diffed beta.4 and master `beacon-chain.md`: only a comment changed, so the lag is confined to `fork.md`.
- I compared the Nimbus loop line by line with both versions of the spec.
- I confirmed that a reachable divergent queue exists. Cases (a), (b) and (d) need only ordinary deposit-contract deposits made before the fork, with no special privilege. The pre-fork caching in `NB beacon_chain/spec/state_transition_block.nim#L1800-L1805` (`cacheBuilderDepositSigs`) does not change the verdict.

What I could not establish is whether any public devnet or testnet's pending queue currently contains such a deposit.

**(6) Severity: consensus split at the Gloas fork epoch** between clients on master semantics and clients on beta.4 semantics (Nimbus, Prysm and, per the brief, Lighthouse), whenever such a deposit is queued. Until a master-based release is cut it is a spec-version lag, not a bug against the release the client targets.

**Known-check: not found** as an open tracking item. Related closed PRs implemented earlier versions of this logic: #8965 "fix Gloas fork transition builder deposit handling", #8955 "fix same-pubkey builder deposits after slot reuse", #9137 "fix O(n^2) gloas builder deposit handling…", #8686 "avoid verifying BLS sigs on non-builders during Gloas fork builder onboarding", and #8887 "pre-verify gloas builder deposits received before gloas fork". All are closed and predate PR 5736 (2026-10-26).

## Cross-client comparison

Cells describe only the checked rule, with evidence in the client-specific inventories below and candidates above. “Not checked” includes partial or unverified checks; it is not a finding of disagreement. Related rules are grouped where the inventories check them together. Lighthouse is included only for the two comparisons actually read; all other clients in the goal were outside these items.

| Rule | Prysm | Teku | Nimbus | Lighthouse |
|---|---|---|---|---|
| Fork onboarding / credential routing | differs (see Prysm-1) | differs (see Teku-1) | differs (see Nimbus-1) | differs (see Prysm-1) |
| Bid gossip seen-cache timing | differs (see Prysm-2) | not checked | not checked | agrees with spec |
| Bid self-build value/signature | agrees with spec | agrees with spec | agrees with spec | not checked |
| Bid active builder, version, signature | agrees with spec | agrees with spec | agrees with spec | not checked |
| Bid coverage reserve, pending balances and inclusive boundary | agrees with spec | agrees with spec | agrees with spec | not checked |
| Bid blob count maximum | agrees with spec | agrees with spec | agrees with spec | not checked |
| Bid slot, parent hashes/root, non-genesis, RANDAO | agrees with spec | agrees with spec | agrees with spec | not checked |
| Pending-payment slot index, zero weight and proposer | agrees with spec | agrees with spec | agrees with spec | not checked |
| Parent EMPTY requests / FULL requests-root commitment | agrees with spec | agrees with spec | agrees with spec | not checked |
| Parent payment settlement: current, previous and older epoch | agrees with spec | agrees with spec | agrees with spec | not checked |
| Parent request application order | agrees with spec | agrees with spec | agrees with spec | not checked |
| Withdrawal early return | agrees with spec | agrees with spec | agrees with spec | not checked |
| Payload-attestation parent root and previous slot | agrees with spec | agrees with spec | agrees with spec | not checked |
| Indexed payload-attestation sorted indices, duplicates and signature domain | agrees with spec | agrees with spec | agrees with spec | not checked |
| PTC concatenation, seed and repeated traversal | agrees with spec | agrees with spec | agrees with spec | not checked |
| PTC window bounds and pre-fork rejection | agrees with spec | agrees with spec | agrees with spec | not checked |
| PTC initialization at fork | agrees with spec | agrees with spec | agrees with spec | not checked |
| PTC window shift/lookahead | agrees with spec | agrees with spec | agrees with spec | not checked |
| Payment quorum rounding | agrees with spec | agrees with spec | agrees with spec | not checked |
| Pending-payment threshold, rotation and clearing | agrees with spec | agrees with spec | agrees with spec | not checked |
| Attestation builder-payment weight conditions | agrees with spec | agrees with spec | agrees with spec | not checked |
| Envelope consistency and signer | agrees with spec | agrees with spec | agrees with spec | not checked |
| Upgrade defaults excluding onboarding | agrees with spec | agrees with spec | agrees with spec | not checked |
| SSZ BeaconState field order | agrees with spec | agrees with spec | agrees with spec | not checked |
| SSZ ExecutionPayloadBid field order | agrees with spec | agrees with spec | agrees with spec | not checked |
| SSZ ExecutionPayloadEnvelope field order | agrees with spec | agrees with spec | agrees with spec | not checked |
| SSZ ExecutionRequests field order | agrees with spec | agrees with spec | agrees with spec | not checked |
| SSZ Builder field order | agrees with spec | agrees with spec | agrees with spec | not checked |
| Builder constants, domains and mainnet preset | agrees with spec | agrees with spec | agrees with spec | not checked |
| PTC 16-bit weighted selection (constant not read by Teku) | agrees with spec | not checked | agrees with spec | not checked |
| Parent request per-type limits / decoder enforcement | agrees with spec | not checked | agrees with spec | not checked |
| Withdrawal priority and reserved validator slot | agrees with spec | agrees with spec | not checked | not checked |
| Builder sweep netting, saturating debit and cursor | agrees with spec | agrees with spec | not checked | not checked |
| Partial-withdrawal and validator-sweep internals | agrees with spec | not checked | not checked | not checked |
| Epoch processing order | not checked | not checked | agrees with spec | not checked |
| Builder deposit registration/top-up and dedicated domain | agrees with spec | not checked | agrees with spec | not checked |
| Builder exit guards | agrees with spec | not checked | agrees with spec | not checked |
| Reusable builder registry index (Teku comparison not quoted) | agrees with spec | not checked | agrees with spec | not checked |
| Slashing clears proposer payments | not checked | not checked | agrees with spec | not checked |
| Attestation data.index < 2 | not checked | agrees with spec | not checked | not checked |
| Builder-index flag conversion | agrees with spec | agrees with spec | not checked | not checked |
| BuilderPendingPayment / BuilderPendingWithdrawal SSZ | agrees with spec | not checked | agrees with spec | not checked |
| BuilderDepositRequest / BuilderExitRequest SSZ | agrees with spec | not checked | agrees with spec | not checked |
| PayloadAttestationData / PayloadAttestation / PayloadAttestationMessage SSZ | agrees with spec | not checked | agrees with spec | not checked |
| IndexedPayloadAttestation field order | agrees with spec | not checked | agrees with spec | not checked |
| ExecutionPayload field order | agrees with spec | not checked | agrees with spec | not checked |
| BeaconBlockBody field order | agrees with spec | not checked | agrees with spec | not checked |
| Signed bid / signed envelope wrapper SSZ | agrees with spec | not checked | not checked | not checked |
| Attestation / IndexedAttestation / AttesterSlashing SSZ | agrees with spec | not checked | not checked | not checked |
| BeaconBlock / SignedBeaconBlock SSZ | agrees with spec | not checked | not checked | not checked |
| DataColumnSidecar / proposer-preferences containers SSZ | agrees with spec | not checked | not checked | not checked |
| Partial-column sidecar / metadata declarations | agrees with spec | not checked | not checked | not checked |
| Light-client header and wrapper field order | agrees with spec | not checked | not checked | not checked |
| PayloadTimelinessCommittee vector wrapper | agrees with spec | not checked | not checked | not checked |
| Standalone metadata / NewPayloadRequest codecs | not checked | not checked | not checked | not checked |

Geth, Nethermind, Besu, Erigon, Reth, Lodestar and Grandine: **not checked** for every row; no supplied review covers them. Nimbus gossip and withdrawal-sweep internals were explicitly not reviewed. Teku did not separately read builder.md or the unlisted SSZ schemas.

## Known-check summary

| Candidate | Supplied known-check result |
|---|---|
| Prysm-1 | Not found as the exact Prysm bug; spec change known via merged consensus-specs PR 5736. |
| Prysm-2 | Not found as the exact write-before-failure bug; related bid-gossip PRs retained above. Medium conformance confidence, cosmetic severity; anti-spam interpretation remains open. |
| Teku-1 | Known: open [PR 11582](https://github.com/Consensys-Incorporated/teku/pull/11582), closed [issue 11510](https://github.com/Consensys-Incorporated/teku/issues/11510). |
| Nimbus-1 | No matching open tracking item found; related older closed PRs retained above. |

These statuses differ by client, not by the underlying spec rule. No supplied item establishes a candidate-specific public failing Hive/EST run.

### Prysm known-check evidence

The Prysm review searched open and closed issue/PR APIs for onboarding, PR 5736, pending deposits, Gloas/ePBS and bid-cache/gas-limit/signature terms. Broad Gloas search returned 492 results (latest 100 inspected); focused searches had fewer than 100 each, all inspected. Literal cache-symbol search returned zero. Related PTC PRs 16753 and 16797 were closed/merged; no PTC arithmetic candidate was retained.

Indexed items checked:

- [17447](https://github.com/OffchainLabs/prysm/issues/17447): **closed** issue, reports devnet-9 fork state-root failure from alpha.12 versus alpha.14 bid initialization. Current code initializes those fields; not re-reported as a new bug.
- [17387](https://github.com/OffchainLabs/prysm/pull/17387): **merged** 2026-08-20, quadratic onboarding BLS work. Does not implement the subsequently merged spec PR 5736.
- [16706](https://github.com/OffchainLabs/prysm/pull/16706): **closed, unmerged** (`merged_at` null), historical getPayloadV6 slotNumber JSON omission. Does not establish a current bug or eventual fix; no duplicate asserted.

**Public test results:** [Hive discovery](https://hive.ethpandaops.io/discovery.json) lists a `glamsterdam-quick` suite. The [Glamsterdam listing](https://hive.ethpandaops.io/glamsterdam-quick/listing.jsonl) and [generic listing](https://hive.ethpandaops.io/generic/listing.jsonl) both returned **HTTP 403** during this review; no individual run/result could therefore be checked. [Hive CL-spec-runner PR 1537](https://github.com/ethereum/hive/pull/1537) was **open/unmerged** when fetched; it describes native CL fixtures, not a published pass. [Execution-specs tests@v21.0.0 milestone](https://github.com/ethereum/execution-specs/milestone/42) is closed/100% complete, but is a test-production milestone, not evidence that Prysm passes these CL queue/gossip cases. Neither candidate was tied to a public failing run. Status is “not found,” not “tested clean”; #17447 corroborates only the old bug.

### Teku known-check evidence

| Topic | Result | Queries / URLs |
|---|---|---|
| C1 onboarding vs PR 5736 | **known**: PR #11582 open; issue #11510 closed | `api.github.com/search/issues?q=repo:Consensys-Incorporated/teku+onboard` (12 hits); `...+5736` (5 hits); `...+gloas+builder+deposits` (18 hits, only #11582 open); https://api.github.com/repos/Consensys-Incorporated/teku/pulls/11582 ; https://api.github.com/repos/Consensys-Incorporated/teku/issues/11510 |
| Onboarding O(K²) | already indexed: #11154 issue closed 2026-08-24; fix PR #11155 merged 2026-08-24; related #11511 merged 2026-10-18, #10729 merged 2026-05-29 | same searches |
| Payment order | already indexed by the brief: #11408 merged (not re-fetched) | — |
| 10682 | already indexed by the brief as closed (not re-fetched) | — |

No other candidate needed a known-check. Hive and execution-spec-tests results were not consulted for this item, because no candidate besides C1 came up and C1 is already confirmed by Teku's own PR.

### Nimbus known-check evidence (status-im/nimbus-eth2 issues and PRs, open and closed, via `api.github.com/search/issues`)

Queries: `onboard`, `5736`, `gloas fork deposits`, `builder deposit`, `beta.5`, `epbs`, `separate builder validator deposits`, `consensus-specs bump is:open`, `EIP-7732 is:open`, `pending_deposits gloas`.

- C1: **not found** (no issue or PR for PR 5736 or the master onboarding). Related closed PRs: #8965, #8955, #9137, #8686, #8887. The "5736" query matched only an unrelated Nimbus PR #5736 from 2024.
- Already indexed per the brief: #8383 (EIP-7732 fixes, merged) and #8334 (requests and slot number, merged).
- Related open items I saw, outside this item's state-transition scope: #9203 (issue, open) "Gloas: head with an EL-unvalidated payload is treated as execution-valid…" and #9334 (PR, open) "Fix envelope backfilling".

Hive and EEST: I consulted no public Glamsterdam hive or execution-spec-tests result pages for this item. Whether the master fork-transition tests (PR 5736) are already in a reference-test release that Nimbus runs is **not established**.


## Checked-rule evidence and disproofs

The following source inventories supply citations for the comparison table. Local C1/C2 references within each inventory mean that client’s retained candidates. Nimbus section references refer to its original numbered review; the retained content follows here. Teku’s redundant “Spec says” table column is omitted to fit the merge limit; its pinned spec function names and implementation citations remain.

### Prysm inventory

## Rules checked without a retained discrepancy

“Matches” means static agreement, not executed tests or proof.

| Rule / boundary checked | Specification | Prysm at P / finding |
|---|---|---|
| Self-build value zero and exact point-at-infinity signature; non-self active builder and payload-builder version | `S:specs/gloas/beacon-chain.md#L2162-L2184` | `P:beacon-chain/core/gloas/bid.go#L76-L131` — matches; self-build bypasses registry lookup. |
| Bid coverage includes pending withdrawals AND payments; below reserve fails, exact reserve plus bid succeeds | `S:specs/gloas/beacon-chain.md#L1168-L1193` | `P:beacon-chain/state/state-native/getters_gloas.go#L152-L207` — matches subtraction order and inclusive comparison. No reachable overflow counterexample established. |
| Bid blob count, current slot, non-genesis slot, parent hash, unequal own hash, parent beacon root, RANDAO | `S:specs/gloas/beacon-chain.md#L2186-L2203` | `P:beacon-chain/core/gloas/bid.go#L133-L198` — matches, inclusive blob maximum. RANDAO follows the bid. |
| Pending-payment slot index, zero initial weight, proposer | `S:specs/gloas/beacon-chain.md#L2205-L2220` | `P:beacon-chain/core/gloas/bid.go#L144-L160` — current-epoch half plus slot modulo epoch. Header processing validates proposer. |
| Weight only for first participation, newly set flag, same-slot attestation, positive payment | `S:specs/gloas/beacon-chain.md#L2436-L2480` | `P:beacon-chain/state/state-native/setters_gloas.go#L518-L604` — matches conditions and current/previous-epoch payment selection. |
| Parent FULL vs EMPTY; EMPTY requires all five request lists empty; FULL root commitment | `S:specs/gloas/beacon-chain.md#L1841-L1857` | `P:beacon-chain/core/gloas/parent_payload.go#L19-L58` — matches; empty-parent branch returns before mutations. |
| Parent settlement before requests; same/previous/older epoch payment; request ordering and limits | `S:specs/gloas/beacon-chain.md#L1784-L1830` | `P:beacon-chain/core/gloas/parent_payload.go#L70-L140`; `P:beacon-chain/state/state-native/setters_gloas.go#L186-L244` — matches: deposits, withdrawals, consolidations, builder deposits, builder exits. Older parent reconstructs positive bid payment. |
| Withdrawal early return and priority; reserve one validator withdrawal slot | `S:specs/gloas/beacon-chain.md#L1861-L1989` | `P:beacon-chain/core/gloas/withdrawals.go#L44-L105`; `P:beacon-chain/state/state-native/getters_gloas.go#L432-L472` — preserves cache on early return; payments → partials → builder sweep → validator sweep; builder cap max−1. |
| Builder sweep nets earlier payments; saturating debit; next builder cursor | `S:specs/gloas/beacon-chain.md#L1895-L1950` | `P:beacon-chain/state/state-native/getters_gloas.go#L575-L634` — matches; nets prior committed withdrawals. |
| Envelope consistency and self-build signer | `S:specs/gloas/fork-choice.md#L683-L730` | `P:beacon-chain/core/gloas/payload.go#L113-L196`; `P:beacon-chain/core/gloas/payload.go#L258-L324` — compares slot, parent beacon root, builder, requests, RANDAO, withdrawals, gas, hash, execution parent, timestamp; signer proposer for self-build, builder otherwise. Caller checks below. |
| Payload-attestation root, previous slot, nonempty sorted indices, duplicates and BLS domain | `S:specs/gloas/beacon-chain.md#L1094-L1111`; `S:specs/gloas/beacon-chain.md#L2488-L2500` | `P:beacon-chain/core/gloas/payload_attestation.go#L47-L107`; `P:beacon-chain/core/gloas/payload_attestation.go#L327-L366` — matches; sorting retains duplicate PTC selections, not a set. Slot addition checked for overflow. |
| PTC selection committee concatenation, repeated passes, seed, 16-bit little-endian sampling, inclusive acceptance | `S:specs/gloas/beacon-chain.md#L1198-L1229`; `S:specs/gloas/beacon-chain.md#L1266-L1283` | `P:beacon-chain/core/gloas/payload_attestation.go#L131-L168`; `P:beacon-chain/core/gloas/payload_attestation.go#L214-L220`; `P:beacon-chain/core/gloas/payload_attestation.go#L259-L304` — matches: counter crosses committees, 16 candidates/hash, weight multiplier 65535. Final iteration increment is unused. Lighthouse: `L:consensus/types/src/state/beacon_state.rs#L3639-L3662`. |
| Payments quorum rounding and rotation | `S:specs/gloas/beacon-chain.md#L1433-L1441`; `S:specs/gloas/beacon-chain.md#L1719-L1731` | `P:beacon-chain/core/gloas/pending_payment.go#L31-L88` — floor active balance/slots first, then multiply 6/divide 10; equality qualifies; previous half processed, current half shifted and cleared. |
| process_ptc_window lookahead and shift; reject pre-fork committee lookup | `S:specs/gloas/beacon-chain.md#L1392-L1405`; `S:specs/gloas/beacon-chain.md#L1737-L1750` | `P:beacon-chain/core/gloas/payload_attestation.go#L383-L404`; `P:beacon-chain/state/state-native/setters_gloas.go#L966-L1003`; `P:beacon-chain/state/state-native/getters_gloas.go#L740-L784` — current epoch + lookahead + one, shift one epoch; explicit Gloas lower bound matches spec. |
| upgrade_to_gloas defaults excluding C1 | `S:specs/gloas/fork.md#L139-L257` | `P:beacon-chain/core/gloas/upgrade.go#L270-L353` — all-one availability; empty builders/withdrawals; two-epoch zero payments; latest hash/full bid from header; self-build sentinel, zero value/payment and empty commitments; previous-epoch PTC zeros/current/lookahead computed at `P:beacon-chain/core/gloas/upgrade.go#L162-L188`. |
| Builder request prefix, dedicated signature domain, reused index, exited zero-balance top-up delay and exit guards | `S:specs/gloas/beacon-chain.md#L2270-L2386` | `P:beacon-chain/core/gloas/builder_deposit_request.go#L88-L144`; `P:beacon-chain/core/gloas/builder_exit.go#L76-L112` — matches sequential application, registration versus top-up, active/address/pending-balance guards; request limits are 64 deposits/16 exits, not the older 256 deposit cap. |
| Constants: self-build uint64 max; builder flag 2^40; 0xB0; domains 0x0B–0x0E; mainnet PTC 512 | `S:specs/gloas/beacon-chain.md#L550-L613` | `P:config/params/mainnet_config.go#L100-L104`; `P:config/params/mainnet_config.go#L202-L206`; `P:consensus-types/primitives/validator.go#L13-L38`; `P:config/fieldparams/mainnet.go#L57-L60` — matches. Minimal PTC is separately 16, not a mainnet discrepancy. |

## SSZ field-order inventory

Compared spec sequences against generated marshal/root routines, not outdated proto comments. Progressive overrides: `P:proto/prysm/v1alpha1/gloas.yaml#L8-L73`. Core generated field sequences also matched by local text comparison. SSZ-library correctness/decoder limits were not exhaustively verified.

| Container(s) | Spec order source | Prysm order source / result |
|---|---|---|
| Builder; BuilderPendingPayment; BuilderPendingWithdrawal | `S:specs/gloas/beacon-chain.md#L660-L686` | `P:proto/prysm/v1alpha1/gloas.ssz.go#L3754-L3780`; `P:proto/prysm/v1alpha1/gloas.ssz.go#L3862-L3880`; `P:proto/prysm/v1alpha1/gloas.ssz.go#L3951-L3971` |
| BuilderDepositRequest; BuilderExitRequest | `S:specs/gloas/beacon-chain.md#L690-L703` | `P:proto/engine/v1/engine.ssz.go#L511-L537`; `P:proto/engine/v1/engine.ssz.go#L594-L607` |
| PayloadAttestationData; PayloadAttestation; PayloadAttestationMessage | `S:specs/gloas/beacon-chain.md#L708-L734` | `P:proto/prysm/v1alpha1/gloas.ssz.go#L5837-L5860`; `P:proto/prysm/v1alpha1/gloas.ssz.go#L5721-L5736`; `P:proto/prysm/v1alpha1/gloas.ssz.go#L5931-L5948` |
| IndexedPayloadAttestation | `S:specs/gloas/beacon-chain.md#L738-L743` | `P:consensus-types/indexed_payload_attestation.go#L9-L13` — indices/data/signature. Internal helper only; no standalone wire encoder found. Its data signature path is checked above. |
| ExecutionPayloadBid; SignedExecutionPayloadBid | `S:specs/gloas/beacon-chain.md#L749-L772` | `P:proto/prysm/v1alpha1/gloas.ssz.go#L5027-L5091`; `P:proto/prysm/v1alpha1/gloas.ssz.go#L6505-L6522` |
| ExecutionPayloadEnvelope; SignedExecutionPayloadEnvelope | `S:specs/gloas/beacon-chain.md#L777-L793` | `P:proto/prysm/v1alpha1/gloas.ssz.go#L5230-L5252`; `P:proto/prysm/v1alpha1/gloas.ssz.go#L6599-L6616` |
| Attestation; IndexedAttestation; AttesterSlashing | `S:specs/gloas/beacon-chain.md#L801-L822` | `P:proto/prysm/v1alpha1/gloas.ssz.go#L825-L847`; `P:proto/prysm/v1alpha1/gloas.ssz.go#L5369-L5390`; `P:proto/prysm/v1alpha1/gloas.ssz.go#L1147-L1161` |
| BeaconBlockBody; BeaconState | `S:specs/gloas/beacon-chain.md#L829-L933` | `P:proto/prysm/v1alpha1/gloas.ssz.go#L1668-L1776`; `P:proto/prysm/v1alpha1/gloas.ssz.go#L3146-L3473` |
| ExecutionPayload; ExecutionRequests | `S:specs/gloas/beacon-chain.md#L940-L983` | `P:proto/engine/v1/engine.ssz.go#L2265-L2355`; `P:proto/engine/v1/engine.ssz.go#L3844-L3893` |
| BeaconBlock; SignedBeaconBlock (inherited wrappers) | `S:specs/gloas/beacon-chain.md#L1754-L1772` | `P:proto/prysm/v1alpha1/gloas.ssz.go#L2100-L2135`; `P:proto/prysm/v1alpha1/gloas.ssz.go#L6317-L6334` |
| DataColumnSidecar; ProposerPreferences; SignedProposerPreferences | `S:specs/gloas/p2p-interface.md#L145-L184` | `P:proto/prysm/v1alpha1/gloas.ssz.go#L4782-L4818`; `P:proto/prysm/v1alpha1/gloas.ssz.go#L6039-L6062`; `P:proto/prysm/v1alpha1/gloas.ssz.go#L6865-L6882` |
| PartialDataColumnSidecar; PartialDataColumnPartsMetadata; PartialDataColumnGroupID | `S:specs/gloas/partial-columns/p2p-interface.md#L50-L74` | `P:proto/prysm/v1alpha1/gloas.ssz.go#L5603-L5636`; `P:proto/prysm/v1alpha1/partial_data_columns.proto#L48-L68` |
| LightClientHeader; Bootstrap; Update; FinalityUpdate; OptimisticUpdate | `S:specs/gloas/light-client/sync-protocol.md#L120-L127`; `S:specs/altair/light-client/sync-protocol.md#L132-L180` | `P:proto/prysm/v1alpha1/gloas.ssz.go#L103-L131`; `P:proto/prysm/v1alpha1/gloas.ssz.go#L225-L250`; `P:proto/prysm/v1alpha1/gloas.ssz.go#L412-L465`; `P:proto/prysm/v1alpha1/gloas.ssz.go#L585-L619`; `P:proto/prysm/v1alpha1/gloas.ssz.go#L700-L717` |

`PayloadTimelinessCommittee` is a vector, not an extra spec container; Prysm's one-field PTCs wrapper serializes its vector: `P:proto/prysm/v1alpha1/gloas.ssz.go#L6076-L6140`. `NewPayloadRequest` is an execution-engine helper, not a beacon wire object; a standalone Prysm Gloas SSZ codec was not found. [PR 17664](https://github.com/OffchainLabs/prysm/pull/17664) explicitly notes skipping its static-SSZ test because it is not a consensus type. API/storage-only containers excluded.


### Teku inventory

## Rules checked and found consistent with the spec

Spec text is from `S` + `beacon-chain.md` unless another file is named. Teku paths are under `T`. Lines are ±3, as explained above.

| # | Rule (spec function) | Teku file#lines (behaviour) | Verdict |
|---|---|---|---|
| 1 | `process_execution_payload_bid`: self-build | `G/block/BlockProcessorGloas.java#L245-L252`: `builderIndex.equals(BUILDER_INDEX_SELF_BUILD)`, `!amount.isZero()`, `!...isInfinity()` | match |
| 2 | bid: active builder, version, cover, signature | `BlockProcessorGloas.java#L254-L270` (same order, else branch only) | match |
| 3 | bid: blob count | `BlockProcessorGloas.java#L275-L281` (blob parameters for the current epoch, `>` → reject) | match |
| 4 | bid: slot / parent hash / block hash ≠ parent / parent root / prev_randao | `BlockProcessorGloas.java#L284-L306`. Teku uses `state.getSlot().minusMinZero(1)` and has no explicit `slot > GENESIS_SLOT` check. That is equivalent, because a block is never processed at slot 0 (`process_block_header` needs `block.slot > latest_block_header.slot`). | match (cosmetic only) |
| 5 | pending payment index and fields | `BlockProcessorGloas.java#L307-L329`: `bid.getSlot().mod(SPE).plus(SPE)`, weight `UInt64.ZERO`, `create(bid.getFeeRecipient(), amount, builderIndex)`, `getBeaconProposerIndex(state)`; only when `amount > 0`; bid cached | match |
| 6 | `can_builder_cover_bid` boundaries | `G/helpers/BeaconStateAccessorsGloas.java#L161-L172`: `builderBalance.isLessThan(minBalance)`; `minusMinZero(minBalance).isGreaterThanOrEqualTo(bidAmount)` | match (equality accepted in both) |
| 7 | `get_pending_balance_to_withdraw_for_builder` | `BeaconStateAccessorsGloas.java#L144-L159` (both lists) | match |
| 8 | `is_active_builder` | `G/helpers/PredicatesGloas.java#L50-L60`. An out-of-range index returns false, where the spec would raise an IndexError; the block is rejected either way. | match |
| 9 | `process_parent_execution_payload`: empty parent | `BlockProcessorGloas.java#L134-L151`: `!isBidBuildingOnFullParent` (`MiscHelpersGloas.java#L268-L271`: `bid.getParentBlockHash().equals(state.getLatestExecutionPayloadBid().getBlockHash())`), `!requests.isDefault()` → reject | match |
| 10 | full parent: requests root | `BlockProcessorGloas.java#L154-L157`, `MiscHelpersGloas.java#L302-L307` | match |
| 11 | `apply_parent_execution_payload`: payment settle index | `BlockProcessorGloas.java#L168-L190` (parentSlot from `getLatestBlockHeader().getSlot()`, same three branches) | match |
| 12 | apply: request order | `BlockProcessorGloas.java#L197-L210` (same order) | match |
| 13 | apply: availability bit and latest_block_hash | `BlockProcessorGloas.java#L214-L218` (`parentSlot.mod(SLOTS_PER_HISTORICAL_ROOT)`) | match |
| 14 | apply: per-type request limits | No asserts in apply. Limits sit on the SSZ types: `ethereum/spec/src/main/java/tech/pegasys/teku/spec/schemas/registry/SchemaRegistryBuilder.java` ~#L475 (deposits: `SszProgressiveListSchema.create(...)` with no max), ~#L496-L498, #L519-L521, #L732-L734, #L749-L751 (progressive lists with `getMax*RequestsPerPayload()`) | **partial**: equivalent only if Teku's progressive-list max is enforced when decoding. Not verified (the SSZ library code was not read). |
| 15 | Gloas `process_withdrawals`: early return | `G/withdrawals/WithdrawalsHelpersGloas.java#L58-L62` (`!isParentBlockFull`); `MiscHelpersGloas.java#L278-L280`: `getLatestExecutionPayloadBid().getBlockHash().equals(getLatestBlockHash())` | match |
| 16 | withdrawals: order and limits | `WithdrawalsHelpersGloas.java#L67-L131` (limits at #L72, #L112, #L116-L117; condition at #L126-L127; flag conversion at #L87, #L131); pending-withdrawals sublist at #L196-L197; next builder index at #L204-L208 (guarded for an empty list) | match. **Partial** for the partial-withdrawals and validator-sweep steps, which are inherited from `WithdrawalsHelpersElectra` (shared with live code, not reviewed). |
| 17 | `process_payload_attestation` | `BlockProcessorGloas.java#L426-L455` | match |
| 18 | `get_indexed_payload_attestation` | `BeaconStateAccessorsGloas.java#L179-L195` (ascending sort, no dedup) | match |
| 19 | `is_valid_indexed_payload_attestation` | `G/util/AttestationUtilGloas.java#L83-L104` (`indices.isEmpty() \|\| !Comparators.isInOrder(...)`, `Domain.PTC_ATTESTER`, epoch from `data.getSlot()`) | match |
| 20 | `compute_ptc` | `BeaconStateAccessorsGloas.java#L202-L227` | match |
| 21 | `compute_balance_weighted_selection` (16-bit) | `G/helpers/MiscHelpersGloas.java#L157-L190` (`(i % 16) * 2`, `slice(offset, 2)`, `isGreaterThanOrEqualTo`, `nextIndex = i % total`, shuffled only if the flag is set) | match (the value of the `MAX_RANDOM_VALUE` constant was not read) |
| 22 | `get_ptc` window indexing | `BeaconStateAccessorsGloas.java#L235-L267` | match |
| 23 | `initialize_ptc_window` (fork.md) | `BeaconStateAccessorsGloas.java#L275-L302` | match |
| 24 | `process_ptc_window` | `G/statetransition/epoch/EpochProcessorGloas.java#L113-L135` | match |
| 25 | `get_builder_payment_quorum_threshold` | `BeaconStateAccessorsGloas.java#L309-L313` (same integer order) | match |
| 26 | `process_builder_pending_payments` | `EpochProcessorGloas.java#L83-L106` | match. Where this runs in the epoch order (after `process_pending_consolidations`, before effective-balance updates) was **not verified**: the epoch sequencer is in a parent class that was not read. |
| 27 | `process_attestation` builder weight | `BlockProcessorGloas.java#L344-L373` (index and conditions). The new-flag gate is in the caller `versions/altair/block/BlockProcessorAltair.java#L201-L222` (`if (newParticipationFlags != 0)`); that caller is shared Altair code, but this hook does nothing before Gloas (#L247-L256) | match |
| 28 | `data.index < 2` | `G/operations/validation/AttestationDataValidatorGloas.java#L41-L46` | match |
| 29 | envelope (`fork-choice.md`, `verify_execution_payload_envelope`) | `G/execution/ExecutionPayloadVerifierGloas.java#L94-L180`: all checks present, at #L104-L168; the slot check at #L119 uses `envelope.getSlot()`, which returns the payload's `getSlotNumber()` (`.../epbs/versions/gloas/ExecutionPayloadEnvelope.java#L80-L82`); parent root at #L116; the engine call at #L170-L177 | match. The check order differs, which does not change the verdict. |
| 30 | `upgrade_to_gloas` defaults | `G/forktransition/GloasStateUpgrade.java#L128-L205` (every field as in the spec; availability uses `ofBits(range(0, SLOTS_PER_HISTORICAL_ROOT))`) | match (all-ones bitvector assumed from the `ofBits` call; the schema was not read) |
| 31 | SSZ: `BeaconState` | `.../state/beaconstate/versions/gloas/BeaconStateSchemaGloas.java#L73-L74` (`allActive` 46), #L106-L163 (header index replaced by `LATEST_BLOCK_HASH`; new fields at indices 38-45 in spec order) | match |
| 32 | SSZ: `ExecutionPayloadBid` (12 fields, progressive) | `.../epbs/versions/gloas/ExecutionPayloadBidSchema.java#L65`, #L71-L82 | match |
| 33 | SSZ: `ExecutionPayloadEnvelope` (5 fields, progressive) | `.../epbs/versions/gloas/ExecutionPayloadEnvelopeSchema.java#L41`, #L48-L55 | match |
| 34 | SSZ: `ExecutionRequests` (5 fields, progressive) | `.../execution/versions/gloas/ExecutionRequestsSchemaGloas.java#L42-L49`, #L68-L84 | match |
| 35 | SSZ: `Builder` | `.../state/versions/gloas/Builder.java#L43-L48` | match |
| 36 | SSZ: other containers | schema files located in the tree but **not read** | not verified |
| 37 | constants | `ethereum/spec/src/main/java/tech/pegasys/teku/spec/config/SpecConfigGloas.java#L22-L27`; `.../spec/constants/Domain.java#L36-L39`; `.../spec/constants/WithdrawalPrefixes.java#L26-L27` (`(byte) 0xB0`); `ethereum/spec/src/main/resources/tech/pegasys/teku/spec/config/presets/mainnet/gloas.yaml` L6, L11, L19, L21, L33 | match |
| 38 | builder-index flag conversion | `MiscHelpersGloas.java#L97-L103` (`\| BUILDER_INDEX_FLAG`, `& ~BUILDER_INDEX_FLAG`); `PredicatesGloas.java#L45-L47` | match |
| 39 | `get_index_for_new_builder` | `BeaconStateAccessorsGloas.java#L372-L382` (described by the fetch tool as "first builder that is withdrawable with zero balance") | likely match (exact comparison not quoted) |

**Shared with live (Fulu) code.** These were not reviewed, and nothing about them is reported beyond this note:
- the Electra/Fulu withdrawal sweeps that Gloas inherits;
- `BlockProcessorAltair.processAttestation`, apart from the Gloas hook.


### Nimbus inventory

## 4. Rules checked and found consistent with master

| Rule | Spec | Nimbus (`NB` …) | Note |
|---|---|---|---|
| `process_block` order: parent payload → header → withdrawals → bid → randao → eth1 → operations(parent_slot) → sync | `SP beacon-chain.md` "Block processing" | `beacon_chain/spec/state_transition_block.nim#L1877-L1906` | `parent_slot` comes from `latest_execution_payload_bid.slot`, read before the bid is overwritten (L1349-L1354). It equals `latest_block_header.slot` (see 5.1). |
| Bid: self-build requires amount 0 and the infinity signature | `process_execution_payload_bid` | `state_transition_block.nim#L1257-L1262` | |
| Bid: builder is active, version is `PAYLOAD_BUILDER_VERSION`, `can_builder_cover_bid`, signature | same | `#L1263-L1278`; `is_active_builder` at `beaconstate.nim#L3399-L3409` (adds a bounds check, which also rejects); `can_builder_cover_bid` at `beaconstate.nim#L3427-L3437` | Same boundaries: `balance < MIN_DEPOSIT_AMOUNT+pending` → false; equality → true. |
| Bid: blob count ≤ `get_blob_parameters(epoch).max_blobs_per_block` | same | `#L1280-L1283` | |
| Bid: `slot == state.slot`, `state.slot > GENESIS_SLOT`, parent_block_hash == `latest_block_hash`, block_hash ≠ parent hash, parent_block_root, prev_randao | same | `#L1285-L1298` | `proposal_slot = state.slot` in block processing (L1332). |
| Pending payment recorded at index `SLOTS_PER_EPOCH + slot % SLOTS_PER_EPOCH`, weight 0, proposer index | same | `#L1334-L1347` | |
| `get_pending_balance_to_withdraw_for_builder` | spec | `beaconstate.nim#L3412-L3424` | |
| `process_withdrawals`: early return when `latest_block_hash != latest_execution_payload_bid.block_hash`; update order | `process_withdrawals` | `state_transition_block.nim#L1555-L1578` | Same order of the update calls. I did not re-derive `get_expected_withdrawals` or the builder sweep internals (`beaconstate.nim#L1841-L2000`) in this pass. |
| `process_parent_execution_payload`: an empty parent needs requests equal to empty; a full parent needs `hash_tree_root(requests) == execution_requests_root` | spec | `state_transition_block.nim#L1374-L1412` | Nimbus checks the per-type limits before the root check, whereas the spec checks them inside `apply_parent_execution_payload`. Both reject. |
| `apply_parent_execution_payload`: settle the payment (current, previous, or older: append) before requests; request order deposits→withdrawals→consolidations→builder_deposits→builder_exits; set availability bit; `latest_block_hash` | spec | `state_transition_block.nim#L1178-L1238` | |
| `settle_builder_payment` | spec | `beaconstate.nim#L267-L279` | |
| `process_builder_deposit_request` (EIP-8282): 0xB0 prefix; new pubkey with a valid signature → add at `state.slot`; existing builder → reset withdrawable if exited and swept, then top up | spec | `state_transition_block.nim#L1122-L1154`; domain `DOMAIN_BUILDER_DEPOSIT` with `GENESIS_FORK_VERSION` at `signatures.nim#L222-L227` | |
| `process_builder_exit_request` | spec | `state_transition_block.nim#L1157-L1176` | |
| `get_index_for_new_builder` / `add_builder_to_registry` | spec | `beaconstate.nim#L2265-L2306` | Nimbus resumes the scan from a cursor that persists within one payload. Balances only grow before the exit requests run, so no index below the cursor can become reusable within the payload. Equivalent. |
| Payment weight in `process_attestation` (`will_set_new_flag && had_no_participation && is_attestation_same_slot && amount>0`) | spec `process_attestation` | `beaconstate.nim#L1392-L1460`; `is_attestation_same_slot` at `#L960-L972` | |
| `slash_validator` clears payments whose `proposer_index` is the slashed validator | spec | `beaconstate.nim#L527-L533` | |
| `process_payload_attestation`: root equals the parent root; `slot+1 == state.slot`; indexed attestation is valid | spec | `state_transition_block.nim#L707-L728` | |
| `get_indexed_payload_attestation` (sorted) / `is_valid_indexed_payload_attestation` (non-empty, sorted, `DOMAIN_PTC_ATTESTER` at the data epoch) | spec | `beaconstate.nim#L3347-L3397` | |
| `get_ptc` window offset `(epoch - state_epoch + 1)*SLOTS_PER_EPOCH + slot%SLOTS_PER_EPOCH` | spec | `beaconstate.nim#L2434-L2456` | See 5.2. |
| `compute_ptc`: seed = sha256(get_seed(PTC)+slot), concatenated committees, `compute_balance_weighted_selection(size=PTC_SIZE, shuffle=False)` | spec | `beaconstate.nim#L2410-L2431` | |
| 16-bit weighted selection: `MAX_RANDOM_VALUE=2**16-1`, offset `i%16*2`, hash `seed+uint_to_bytes(i//16)`, `weight >= MAX_EB_ELECTRA*rv` | spec | `validator.nim#L466-L505` | |
| Quorum = `total_active // SLOTS_PER_EPOCH * 6 // 10` | spec | `state_transition_epoch.nim#L1386-L1392` | |
| `process_builder_pending_payments` (≥ quorum → withdrawal; shift; clear) | spec | `state_transition_epoch.nim#L1395-L1415` | |
| `process_ptc_window` (shift by one epoch, fill `current+MIN_SEED_LOOKAHEAD+1`) | spec | `state_transition_epoch.nim#L1418-L1438` | |
| `process_epoch` order (payments after pending consolidations, before effective-balance updates; PTC window last) | spec | `state_transition_epoch.nim#L1681-L1690` | |
| Envelope checks: signature, beacon_block_root via header with state_root, parent root, builder_index, prev_randao, gas_limit, block_hash, requests root, slot_number, parent_hash, timestamp, withdrawals root | `SP fork-choice.md` `verify_execution_payload_envelope` | `state_transition_block.nim#L1809-L1871` | See 5.3. |
| `upgrade_to_gloas` defaults: fork versions/epoch; progressive wrappers; `latest_execution_payload_bid` (parent hash/root, block hash, randao, gas limit, `BUILDER_INDEX_SELF_BUILD`, header slot, value 0, empty requests root); all-ones availability; `latest_block_hash`; empty builders, payments and withdrawals; `ptc_window` initialised | `SP fork.md` | `beaconstate.nim#L3006-L3106` | Nimbus calls onboarding *before* `initialize_ptc_window`, while the spec does the reverse. Onboarding touches only `builders` and `pending_deposits`, and `compute_ptc` reads neither, so the result is equal. |
| `initialize_ptc_window` (zeroed previous epoch, then current..+lookahead) | `SP fork.md` | `beaconstate.nim#L2487-L2505` | |
| SSZ: `BeaconState` 46 active fields, same order as the spec | `SP beacon-chain.md` | `datatypes/gloas.nim#L340-L455` | |
| SSZ: `ExecutionPayload` (19), `ExecutionPayloadBid` (12), `ExecutionPayloadEnvelope` (5), `BeaconBlockBody` (13), `ExecutionRequests` (5), `PayloadAttestation`/`IndexedPayloadAttestation` (3), `PayloadAttestationData`, `PayloadAttestationMessage`, `Builder`, `BuilderPendingPayment`, `BuilderPendingWithdrawal`, `BuilderDepositRequest`, `BuilderExitRequest` | same | `datatypes/gloas.nim#L120-L560` (`sszActiveFields` pragmas) | Field order matches. |
| Constants: `DOMAIN_BEACON_BUILDER 0x0B`, `PTC_ATTESTER 0x0C`, `PROPOSER_PREFERENCES 0x0D`, `BUILDER_DEPOSIT 0x0E`; `BUILDER_WITHDRAWAL_PREFIX 0xB0`; `BUILDER_INDEX_SELF_BUILD = UINT64_MAX`; `BUILDER_INDEX_FLAG = 2**40`; threshold 6/10; `PAYLOAD_BUILDER_VERSION 0` | `SP beacon-chain.md` tables | `datatypes/constants.nim#L58-L105` | |
| Preset: `PTC_SIZE 512`, `MAX_PAYLOAD_ATTESTATIONS 4`, builder deposit/exit request maxima 64/16, `MAX_BUILDERS_PER_WITHDRAWALS_SWEEP 16384`; config `MIN_BUILDER_WITHDRAWABILITY_DELAY 64` | spec / `configs/mainnet.yaml` | `presets/mainnet/gloas_preset.nim#L16-L33`, `presets.nim#L349` | |

Not reviewed in this pass, so none of these is claimed correct: gossip validation (p2p-interface), fork-choice handlers other than `verify_execution_payload_envelope`, the internals of `get_expected_withdrawals` and the builder sweep, and the Prysm equivalents of the table rows (Prysm functions located: `PY beacon-chain/core/gloas/bid.go#L76`, `parent_payload.go#L19`, `withdrawals.go#L44`, `payload_attestation.go#L47`, `#L131`, `#L383`, `pending_payment.go#L31`).


## 5. Near-misses checked and disproved

1. **Parent slot source.** The spec uses `state.latest_block_header.slot`. Nimbus uses `latest_execution_payload_bid.slot` (`state_transition_block.nim#L1184-L1186`, `#L1349`). The bid in the parent block is asserted to have `bid.slot == state.slot`, so the two are equal. At the fork, `upgrade_to_gloas` sets `bid.slot = latest_block_header.slot`. Equal in all reachable states.
2. **`get_ptc` out of range.** The spec `assert`s. Nimbus yields an empty iterator (`beaconstate.nim#L2445-L2449`). The only caller in block processing pins `data.slot+1 == state.slot`, and a pre-Gloas epoch gives empty indices, which `is_valid_indexed_payload_attestation` rejects. Same verdict (invalid).
3. **Envelope signature domain epoch.** The spec uses `get_domain(state, DOMAIN_BEACON_BUILDER)`, i.e. the state's current epoch. Nimbus uses `payload.slot_number.epoch` (`state_transition_block.nim#L1833-L1837`). They differ only when `slot_number ≠ state.slot`, and Nimbus then rejects at L1863-L1864. Same verdict.
4. **Onboarding vs. PTC window order.** Equal (section 4).

