# ePBS Fork Choice Payload Extension and Reorgs
This document outlines `get_head` scenarios under ePBS fork choice. The goal is to cover how payload extension, LMD-GHOST weight, proposer boost, and reorgs interact
## Notation
* `n` is the current slot. `n-1`, `n-2`, `n-3` are previous slots.
* `B@k` denotes beacon block `B` proposed at slot `k`.
* Fork-choice node states:
* `(B, P)` = PENDING
* `(B, E)` = EMPTY
* `(B, F)` = FULL
* `EP[B]` means `B ∈ execution_payload_states`
* `PTC[B]=T/F` means `sum(ptc_vote[B]) > PAYLOAD_TIMELY_THRESHOLD`
* `PB=X` means `proposer_boost_root == X` (or `0` if unset)
* `PB.parent` means `blocks[PB].parent_root`
* `PB extends FULL` means `is_parent_node_full(blocks[PB]) == true`
## Invariants
A list of invariants we plan cover in the following test cases
- Payload ambiguity exists only `n-1` and does not drive weight. (`(B, E)` and `(B, F)` at `n-1` have weight `0`)
- Payload semantics at `>=n-2` is determined by LMD weight via `LatestMessage.payload_present`
- PENDING nodes always receive LMD weight
- PB can only force EMPTY if PTC threshold is not met and on the relevant branch
- PB can still force FULL if PTC threshold is not met and on the relevant branch
- Children enumeration alternates: `PENDING → {EMPTY,(FULL if EP)} → PENDING → ...` (although implementation detail)
## Test cases
All `get_head` tests assume:
* There's a justified checkpoint `J`
* Every block mentioned descends from `J`
* `get_head` is called after all blocks in the setup are processed
### A. `n-1`, payload extension cases
These cases isolate `should_extend_payload` and payload tie-breaking at `n-1`. This runs at start of `n`
* A1. No execution payload
* Setup: `B@(n-1)`, `EP[B]=false`
* Expect: from `(B, P)` only child is `(B, E)`
* A2. Execution payload exists, PTC timely
* Setup: `B@(n-1)`, `EP[B]=true`, `PTC[B]=true`
* Expect: `(B, F)` beats `(B, E)`
* A3. Execution payload exists, PTC untimely, PB unset
* Setup: `B@(n-1)`, `EP[B]=true`, `PTC[B]=false`, `PB=0`
* Expect: `(B, F)` beats `(B, E)`
* A4. Execution payload exists, PTC untimely, PB on another branch
* Setup: `B@(n-1)`, `EP[B]=true`, `PTC[B]=false`, `PB=A`, `PB.parent ≠ B`
* Expect: `(B, F)` beats `(B, E)`
* A5. Execution payload exists, PTC untimely, PB extends FULL
* Setup: `B@(n-1)`, `EP[B]=true`, `PTC[B]=false`, `PB=A`, `PB.parent == B`, `PB extends FULL`
* Expect: `(B, F)` beats `(B, E)`
* A6. Execution payload exists, PTC untimely, PB extends EMPTY
* Setup: `B@(n-1)`, `EP[B]=true`, `PTC[B]=false`, `PB=A`, `PB.parent == B`, `PB extends EMPTY`
* Expect: `(B, E)` beats `(B, F)`
* A7. PTC timely but execution payload missing
* Setup: `B@(n-1)`, `EP[B]=false`, `PTC[B]=true`
* Expect: `(B, E)` beats `(B, F)`
### B. `n-2` with `n-1` skipped, lmd-weight cases
These cases validate that payload semantics at `n-2` flow through LMD weight without `n-1`
* B1. No execution payload
* Setup: `J@(n-3) → B@(n-2)`, `EP[B]=false`
* Expect: only `(B, E)` exists, `get_head = (B, E)`
* B2. Execution payload exists, no attestations
* Setup: `J@(n-3) → B@(n-2)`, `EP[B]=true`, `latest_messages = {}`
* Expect: `(B, F)` wins via payload tiebreaker
* B3. Execution payload missing, votes claim payload_present
* Setup: `J@(n-3) → B@(n-2)`, `EP[B]=false`,
`latest_messages = {root=B, slot>B.slot, payload_present=true}`
* Expect: Cant be `FULL` without local execution state, `get_head = (B, E)`
* B4. Execution payload exists, all votes payload_present true
* Setup: `EP[B]=true`, `latest_messages's payload_present=true`
* Expect: `get_head = (B, F)`
* B5. Execution payload exists, all votes payload_present false
* Setup: same as B4, but `payload_present=false`
* Expect: `get_head = (B, E)`
* B6. Two competing n-2 blocks, different weight
* Setup: `B@(n-2)`, `B'@(n-2)`, `weight[B] > weight[B']`, `EP[B]=true`
* Expect: `get_head = (B, F)`
* B7. Two competing n-2 blocks, higher weight favors EMPTY
* Setup: `weight[B'] > weight[B]`, `EP[B]=true`
* Expect: `get_head = (B, E)`
* B8. Equal weight, lexicographic tie break
* Setup: equal weight, `B.root > B'.root`, `EP[B]=false`
* Expect: `(B, E)` wins lexicographically
### C. `n` with `n-1` and `n-2`, reorg cases
These cases cover block at `n` gets reorg by block in `n-1` or `n-2`
`A@(n-2)` resolved as FULL or EMPTY
`B@(n-1)` resolved as FULL or EMPTY, contending with `A`
`C@(n)` is processed, but `get_head` reorgs `C` out and selects a head from `A||B` and `EMPTY||FULL`
- C1.
- Setup: `get_weight(A, FULL)` is highest after processing `C`
- Expect: `get_head = (A, FULL)`, `C` is reorged while building on `B`
- C2.
- Setup: `get_weight(A, EMPTY)` is highest after processing `C`
- Expect: `get_head = (A, EMPTY)`, `C` is reorged while building on `B`
- C3.
- Setup: `get_weight(B, FULL)` is highest after processing `C`
- Expect: `get_head = (B, FULL)`, `C` is reorged while building on `A`
- C4.
- Setup: `get_weight(B, EMPTY)` is highest after processing `C`
- Expect: `get_head = (B, EMPTY)`, `C` is reorged while building on `A`
Here is the same set, renamed cleanly to section **D** and case labels **D1–D4**, with no other changes.
### D. proposer boost and equivocation cases
These cases assume blocks exist at `n-2`, `n-1`, and `n`, and fork choice is evaluated after processing the block at `n`
* D1. Equivocation at n-2 does not suppress proposer boost
* Setup:
* Two equivocating blocks at `n-2`, `n-1` block contends with the `n-2` blocks
* Block `n` builds on top of one `n-2` block
* Conditions: Equivocations are at `n-2`, not the previous slot
* Expect: `should_apply_proposer_boost = true`, `get_head = n`
* D2. Equivocation at n-1, head is not weak
* Setup:
* Two equivocating blocks at `n-1`, `n-2` block contends with `n-1`
* Block `n` builds on top of `n-1`
* Conditions: `n-1` head weight is not weak (> 20%)
* Expect: `should_apply_proposer_boost = true`, `get_head = n`
* D3. Equivocation at n-1, head is weak but not PTC timely
* Setup:
* Two equivocating blocks at `n-1`, `n-2` block contends with `n-1`
* Block `n` builds on top of `n-1`
* Conditions: `n-1` is weak (< 20%) and is not PTC timely
* Expect: `should_apply_proposer_boost = true`, `get_head = n`
* D4. Equivocation at n-1, head is weak and PTC timely
* Setup:
* Two equivocating blocks at `n-1`, `n-2` block contends with `n-1`
* Block `n` builds on top of `n-1`
* Conditions:`n-1` is weak (< 20%) and is PTC timely
* Expect: `should_apply_proposer_boost = false`, `get_head = n-2`