owned this note
owned this note
Published
Linked with GitHub
# Transition tests
[toc]
## `TERMINAL_TOTAL_DIFFICULTY`
### EL client tests
* [x] [[Hive](https://github.com/ethereum/hive/blob/32ff59c89c879972f00abe62137a566def81a080/simulators/ethereum/engine/clmock.go#L346)] Propose valid transition block
* `Genesis <- ... <- TB`, `TB` is a valid terminal block
* EL starts with `TB` as the head
* `forkchoiceUpdated(headBlockHash: TB.blockHash, payloadAttributes: mergeTransitionBlockAttributes)`
* EL's head is set to `headBlockHash`
* EL returns `{status: VALID, payloadId: mergeTransitionPayloadId}`
* `getPayload(mergeTransitionPayloadId)`
* EL returns merge transition payload
* `ommersHash == 0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347`
* `difficulty == 0`
* `mixHash == mergeTransitionBlockAttributes.prevRandao`
* `nonce == 0x0000000000000000`
* `newPayload(mergeTransitionPayload)`
* EL returns `VALID`
* [ ] Build atop of invalid terminal block
* `Genesis <- ... <- INV_TB`, `INV_TB` is a *valid PoW* but an *invalid terminal* block, i.e. `INV_TB.TD < TTD`; `INV_TB.parent.TD >= TTD` can't be checked easily as EL wouldn't process such block
* EL starts with `INV_TB` as the head
* `forkchoiceUpdated(headBlockHash: TB.blockHash, payloadAttributes: mergeTransitionBlockAttributes)`
* EL returns `{status: INVALID, latestValidHash: 0x00..00, payloadId: null}`
* EL's head points to `INV_TB` if `INV_TB.TD < TTD`, and to `INV_TB.parent` if `INV_TB.parent.TD >= TTD`
* [ ] Transition on a valid chain
* `Genesis <- ... <- TB <- P1 <- P2 <- P3`, `TB` is a valid terminal block
* EL starts with `TB` as the head
* `newPayload(P1)`
* EL returns `{status: VALID, latestValidHash: payload.blockHash}`
* EL's head points to `TB`
* `forkchoiceUpdated(head: P1, safe: 0x00..00, finalized: 0x00..00)`
* EL returns `{status: VALID, latestValidHash: forkchoiceState.headBlockHash}`
* EL's head updated to `P1`
* `eth_getBlockByNumber(safe)` returns `-39001: Unknown block` error
* `eth_getBlockByNumber(finalized)` returns `-39001: Unknown block` error
* `newPayload(P2) + forkchoiceUpdated(head: P2, safe: P1, finalized: 0x00..00)`
* EL's head updated to `P2`
* `eth_getBlockByNumber(safe)` returns `-39001: Unknown block` error
* `eth_getBlockByNumber(finalized)` returns `-39001: Unknown block` error
* `newPayload(P3) + forkchoiceUpdated(head: P3, safe: P2, finalized: P1)`
* EL's head updated to `P3`
* `eth_getBlockByNumber(safe)` returns `P2`
* `eth_getBlockByNumber(finalized)` returns `P1`
* [x] [[Hive](https://github.com/ethereum/hive/pull/553)] Transition on a valid chain with missed `fcU`
* `Genesis <- ... <- TB <- P1 <- P2 <- P3`, `TB` is a valid terminal block
* EL starts with `TB` as the head
* `newPayload(P1)`
* EL returns `{status: VALID, latestValidHash: payload.blockHash}`
* EL's head points to `TB`
* `newPayload(P2)`
* EL returns `{status: VALID, latestValidHash: payload.blockHash}`
* EL's head points to `TB`
* `newPayload(P3)`
* EL returns `{status: VALID, latestValidHash: payload.blockHash}`
* EL's head points to `TB`
* `forkchoiceUpdated(head: P3, safe: P2, finalized: P1)`
* EL's head updated to `P3`
* `eth_getBlockByNumber(safe)` returns `P2`
* `eth_getBlockByNumber(finalized)` returns `P1`
* [x] Transition on two valid chains
* `A: Genesis <- ... <- TB <- P1 <- P2`, `B: Genesis <- ... <- TB <- P1 <- P2`, `A.TB` and `B.TB` are valid terminal blocks
* EL starts with `A.TB` as the head
* `newPayload(A.P1) + newPayload(A.P2)`
* EL returns `{status: VALID, latestValidHash: payload.blockHash}`
* EL's head points to `A.TB`
* `forkchoiceUpdated(head: A.P2, safe: A.P1, finalized: 0x00..00)`
* EL's head updated to `A.P2`
* `newPayload(B.P1)`
* EL returns `{status: VALID/ACCEPTED, latestValidHash: payload.blockHash}`
* EL's head points to `A.P2`
* `forkchoiceUpdated(head: B.P1, safe: 0x00..00, finalized: 0x00..00)`
* EL's head points to `B.P1`
* `newPayload(B.P2)`
* EL returns `{status: VALID, latestValidHash: payload.blockHash}`
* `forkchoiceUpdated(head: B.P2, safe: B.P1, finalized: 0x00..00)`
* EL's head points to `B.P2`
* [ ] Transition on an invalid chain
* `Genesis <- ... <- INV_TB <- P1`, `INV_TB` is a *valid PoW* but an *invalid terminal* block:
* [ ] `INV_TB.TD < TTD`
* [ ] `INV_TB.parent.TD >= TTD` -- it might require a second EL with a higher `TTD` value
* EL starts with `INV_TB` as the head
* `newPayload(P1)`
* EL returns `{status: INVALID, latestValidHash: 0x00..00}`
* EL's head points to `INV_TB` if `INV_TB.TD < TTD`, and to `INV_TB.parent` if `INV_TB.parent.TD >= TTD`
* [ ] Re-org to chain with invalid transition block
* `A: Genesis <- ... <- TB <- P1 <- P2`, `B: Genesis <- ... <- TB <- INV_P1`, `A.TB` and `B.TB` are valid terminal blocks
* EL starts with `A.TB` as the head
* `newPayload(A.P1) + newPayload(A.P2)`
* EL returns `{status: VALID, latestValidHash: payload.blockHash}`
* EL's head points to `A.TB`
* `forkchoiceUpdated(head: A.P2, safe: A.P1, finalized: 0x00..00)`
* EL's head updated to `A.P2`
* `newPayload(B.INV_P1)`
* EL returns `{status: INVALID/ACCEPTED, latestValidHash: null/0x00..00}`
* EL's head points to `A.P2`
* `forkchoiceUpdated(head: B.INV_P1, safe: 0x00..00, finalized: 0x00..00)`
* EL returns `{status: INVALID, latestValidHash: 0x00..00}`
* EL's head points to `B.P1`
* [ ] Syncing with the chain having a valid transition
* `Genesis <- ... <- Bn <- TB <- P1`, `TB` is a valid terminal block
* EL starts with `Bn` as the head
* `newPayload(P1) + forkchoiceUpdated(P1)`
* EL returns `{status: SYNCING}`
* EL's head points to `Bn`
* EL should pull `TB <- P1` from the network
* poll `forkchoiceUpdated(P1)`
* EL returns `{status: VALID, latestValidHash: null}`
* EL's head points to `P1`
* [ ] Syncing with the chain having invalid *terminal* block
* `Genesis <- ... <- Bn <- INV_TB <- P1 <- P2`, `INV_TB` is a *valid PoW* but an *invalid terminal* block:
* [ ] `INV_TB.TD < TTD`
* [ ] `INV_TB.parent.TD >= TTD` -- it might require a second EL with a higher `TTD` value
* EL starts with `Bn` as the head
* `newPayload(P1) + forkchoiceUpdated(P1)`
* EL returns `{status: SYNCING}`
* EL's head points to `Bn`
* EL should pull `INV_TB <- P1` from the network
* poll `forkchoiceUpdated(P1)`
* EL returns `{status: INVALID, latestValidHash: 0x00.00}`
* EL's head points to `Bn`
* [ ] Syncing with the chain having invalid *transition* block
* `Genesis <- ... <- Bn <- TB <- INV_P1 <- P2`, `TB` is a valid terminal block, `INV_P1` is an invalid payload
* EL starts with `Bn` as the head
* `newPayload(P2) + forkchoiceUpdated(P2)`
* EL returns `{status: SYNCING}`
* EL's head points to `Bn`
* poll `forkchoiceUpdated(P2)`
* EL returns `{status: INVALID, latestValidHash: 0x00.00}`
* EL's head points to `TB`
* [ ] Syncing with the chain having invalid *post-transition* block
* `Genesis <- ... <- Bn <- TB <- P1 <- INV_P2 <- P3`, `TB` is a valid terminal block, `INV_P2` is an invalid payload
* EL starts with `Bn` as the head
* `newPayload(P3) + forkchoiceUpdated(P3)`
* EL returns `{status: SYNCING}`
* EL's head points to `Bn`
* poll `forkchoiceUpdated(P3)`
* EL returns `{status: INVALID, latestValidHash: P1.blockHash}`
* EL's head points to `TB` (in this case `fcU` won't be applied at all because it does point to a block on an invalid chain, thus, no update and PoS transition should happen; normally, a CL will send `fcU(P1)` when get `lvh = P1.blockHash`)
* [ ] Re-org and sync with the chain having invalid *terminal* block
* `A: Genesis <- ... <- TB <- P1 <- P2`, `B: Genesis <- ... <- INV_TB <- P1 <- P2`, `B.INV_TB` is a *valid PoW* but an *invalid terminal* block:
* [ ] `INV_TB.TD < TTD`
* [ ] `INV_TB.parent.TD >= TTD`
* This scenario may require second EL with a higher `TTD` value
* EL starts with `A.P2` as the head
* `newPayload(B.P2) + forkchoiceUpdated(P2)`
* EL returns `{status: SYNCING}`
* EL's head points to `A.P2`
* EL should pull `B: INV_TB <- P1 <- P2` from the network
* poll `forkchoiceUpdated(B.P2)`
* EL returns `{status: INVALID, latestValidHash: 0x00.00}`
* EL's head points to `A.P2`
* [ ] Re-org and sync with the chain having invalid *transition* block
* `A: Genesis <- ... <- TB <- P1 <- P2`, `B: Genesis <- ... <- TB <- INV_P1 <- P2`, `B.TB` is a valid terminal block, `B.INV_P1` is an invalid payload
* EL starts with `A.P2` as the head
* `newPayload(B.P2) + forkchoiceUpdated(B.P2)`
* EL returns `{status: SYNCING}`
* EL's head points to `A.P2`
* poll `forkchoiceUpdated(B.P2)`
* EL returns `{status: INVALID, latestValidHash: 0x00.00}`
* EL's head points to `A.P2`
* [ ] Re-org and sync with the chain having invalid *post-transition* block
* `A: Genesis <- ... <- TB <- P1 <- P2 <- P3`, `B: Genesis <- ... <- TB <- P1 <- INV_P2 <- P3`, `B.TB` is a valid terminal block, `B.INV_P2` is an invalid payload
* EL starts with `A.P2` as the head
* `newPayload(B.P3) + forkchoiceUpdated(B.P3)`
* EL returns `{status: SYNCING}`
* EL's head points to `A.P3`
* poll `forkchoiceUpdated(B.P3)`
* EL returns `{status: INVALID, latestValidHash: B.P1.blockHash}`
* EL's head points to `A.P3` (in this case `fcU` won't be applied at all because it does point to a block on an invalid chain, thus, no update of the forkchoice state should happen; normally, a CL will send `fcU(B.P1)` when get `lvh = B.P1.blockHash` and switch canonical chain to `B` with `B.P1` as the head)
* [ ] Stop processing gossiped PoW blocks
* PoW/Clique miner builds a chain and advertises it via gossip; the chain goes beyond a terminal PoW block
* EL is connected to the miner and does not process blocks after the terminal one, i.e. the head points to the terminal block
* [ ] Stop processing synced PoW blocks
* PoW client starts with `Genesis <- ... <- TB <- B1 <- B2 <- ... <- Bn` chain, where `TB` is a valid terminal block
* EL connects to the PoW client and syncs with the advertised chain
* EL's head points to `TB`
* [ ] Terminal blocks are gossiped
* Consider `PoW <-> EL1 <-> EL2` as a PoW client connected to EL client `EL1`, which in its turn connected to EL client `EL2`. There is no direct connection between `EL2` and `PoW` clients.
* `PoW` client gossips a terminal block `TB`
* Head of `EL2` client points to `TB`
* `PoW` client gossips a descendant of `TB`
* Head of `EL2` client points to `TB`
### CL client tests
* [ ] Transition on a valid chain
* `EL: Genesis <- ... <- TB`, `TB` is a valid terminal block
* `CL: Genesis <- ... <- Bellatrix`
* EL starts with `TB` as the head or mines a chain up to `TB`
* CL strats with `Genesis` and builds a chain up to `Bellatrix` and upgrades to `Bellatrix`
* CL drives EL through transition and finalizes it
* `eth_getBlockByNumber(latest)` returns the head
* `eth_getBlockByNumber(safe)` returns the most recent justified block
* `eth_getBlockByNumber(finalized)` returns the most recent finalized block
* [ ] Build atop of invalid terminal block
* `Genesis <- ... <- Bn`, `Bn` is a *valid PoW* but an *invalid terminal* block, i.e. `Bn.TD < EL.TTD`
* `Bn` is a valid terminal block to CL's observation, i.e. CL's `TERMINAL_TOTAL_DIFFICULTY` is configured in the following way:
* `Bn.TD >= CL.TTD && Bn.parent.TD < CL.TTD`
* EL starts with `Bn` as the head
* CL starts with `Genesis` and build a chain up to Bellatrix
* Transition never happens, i.e. CL's head block always contains zeroed payload (`ExecutionPayload()`)
* [ ] Transition on a chain with invalid *terminal* block
* `EL: Genesis <- ... <- Bn`
* Nodes: builder, importer; importer is connected to builder on both layers
* `Bn` is a valid terminal block to the builder's observation, i.e. builder's `TERMINAL_TOTAL_DIFFICULTY` is configured in the following way:
* `Bn.TD >= TTD && Bn.parent.TD < TTD`
* `Bn` is an *invalid* terminal block to the importer's observation, i.e. builder's `TERMINAL_TOTAL_DIFFICULTY` is configured in the following way:
* `Bn.TD < TTD`
* EL starts with `Bn` as the head
* The builder starts with `Genesis` and build a chain up to Bellatrix and beyond
* Importer does never imports a transition block and its head does always point to pre-transition block
* [ ] Transition on a chain with invalid *transition* block
* `EL: Genesis <- ... <- Bn`
* Nodes: builder, importer; importer is connected to builder on both layers
* `Bn` is a valid terminal block to builder and importer observation
* EL starts with `Bn` as the head
* CL starts with `Genesis` and the builder builds a chain up to Bellatrix and beyond
* EL mock of the importer node returns `INVALID` to the transition block's payload
* Importer does never imports a transition block and its head does always point to pre-transition block
* [ ] Syncing with the chain having a valid transition block
* `EL.A: Genesis <- ... <- Bn`, `EL.B: Genesis <- ... <- Bn`; `A` and `B` has equal `TD`
* Nodes: builder, importer; builder starts synced with `EL.A`, importer with `EL.B`, same `TD` value on both chains prevents importer from pulling builder's chain beforehand
* The builder starts with `CL: Genesis` and builds a chain up to Bellatrix and beyond
* Importer will have to pull `EL.A` from builder
* Importer block processing is paused and waits for [`SAFE_SLOTS_TO_IMPORT_OPTIMISTICALLY`](https://github.com/ethereum/consensus-specs/blob/dev/sync/optimistic.md#constants) to proceed -- this might worth checking
* After the timeout exceedes importer eventually syncs and passes through the transition
* [ ] Syncing with the chain having invalid *transition* block
* `EL.A: Genesis <- ... <- Bn`, `EL.B: Genesis <- ... <- Bn`; `A` and `B` has equal `TD`
* Nodes: builder, importer; builder starts synced with `EL.A`, importer with `EL.B`, same `TD` value on both chains prevents importer from pulling builder's chain beforehand
* `A.Bn` is a valid terminal block to the builder's observation
* `A.Bn` may be an *invalid* terminal block to the importer's observation, but it's not necessary as EL mock will emulate this invalidity
* The builder starts with `CL: Genesis` and builds a chain up to Bellatrix and beyond
* Importer will have to pull `EL.A` from builder
* Importer block processing is paused and waits for [`SAFE_SLOTS_TO_IMPORT_OPTIMISTICALLY`](https://github.com/ethereum/consensus-specs/blob/dev/sync/optimistic.md#constants) to proceed -- may not be checked in this scenario
* Importer's EL mock artificially keeps returning `SYNCING` to let importer's CL reach the point farther away from transition block
* After some time EL mock starts to respond with `{status: INVALID, latestValidHash: 0x00..00}` to every `newPayload` and `forkchoiceUpdated` call -- this would happen in real life if either terminal or transition block would be invalid
* The importer must invalidate blocks starting from a transition one and must never import a transition block again
* [ ] Syncing with the chain having invalid *post-transition* block
* `EL.A: Genesis <- ... <- Bn`, `EL.B: Genesis <- ... <- Bn`; `A` and `B` has equal `TD`
* Nodes: builder, importer; builder starts synced with `EL.A`, importer with `EL.B`, same `TD` value on both chains prevents importer from pulling builder's chain beforehand
* `A.Bn` is a valid terminal block to the builder's and importer's observation
* The builder starts with `CL: Genesis` and builds a chain up to Bellatrix and beyond
* Importer will have to pull `EL.A` from builder
* Importer block processing is paused and waits for [`SAFE_SLOTS_TO_IMPORT_OPTIMISTICALLY`](https://github.com/ethereum/consensus-specs/blob/dev/sync/optimistic.md#constants) to proceed -- may not be checked in this scenario
* Importer's EL mock artificially keeps returning `SYNCING` to let importer's CL reach the point farther away from *post-transition* block
* After some time EL mock starts to respond with `{status: INVALID, latestValidHash: transitionBlock.blockHash}` to every `newPayload` and `forkchoiceUpdated` call -- this would happen in real life if either terminal or transition block would be invalid
* The importer must invalidate blocks starting from a post-transition one and must never import a post-transition block again, and the head must point to a transition block
* [ ] Re-org and sync with the chain having invalid *terminal* block
* `EL.A: Genesis <- ... <- Bn`, `EL.B: Genesis <- ... <- Bn`; `A` and `B` has equal `TD`
* Nodes: valid builder, invalid builder, importer; valid builder and importer start synced with `EL.A`, invalid builder with `EL.B`, same `TD` value on both chains prevents importer and valid builder from pulling invalid builder's chain beforehand. Valid builder has `1/2n - x`, invalid builder has `1/2 + x` validators, where `x` is a small number
* `A.Bn` is a valid terminal block to valid builder's and importer's observation
* `B.Bn` is an *invalid* terminal block to the importer's observation, but valid to the invalid builder's observation
* Two builders start with `CL: Genesis` and builds a chain up to Bellatrix and beyond. They should stay in consensus up to Bellatrix and then fall apart starting with a transition block
* Importer's head eventually is coherent with a valid builder
* Ideally, the valid builder proposes a transition block *before* the invalid builder's does the same -- these can be achived by playing with validator distribution
* Then a transition block proposed by the invalid builder will have to be applied optimistically with [`SAFE_SLOTS_TO_IMPORT_OPTIMISTICALLY`](https://github.com/ethereum/consensus-specs/blob/dev/sync/optimistic.md#constants) timeout -- it will give a time for importer to follow the valid chain
* Invalid chain gets applied optimistically (because the invalid builder has more attesters and its chain is preferred by the fork choice rule) and importer's EL eventually responds with `{status: INVALID, latestValidHash: 0x00.00}` on this chain
* CL is expected to invalidate the invalid chain blocks and switch back to the minor but valid chain
## `TERMINAL_BLOCK_HASH`
Scenarios in this section are covering the case when a terminal PoW block is designated by specifying a certain `blockHash` value which is specified by `TERMINAL_BLOCK_HASH` parameter.
This scenario is also known as `TERMINAL_BLOCK_HASH` override and is an emergency case scenario. On EL side the override involves specifying the following parameters (as per [EIP-3675](https://eips.ethereum.org/EIPS/eip-3675#terminal-pow-block-overriding)):
* `TERMINAL_BLOCK_HASH` – set to the hash of a certain block to become the terminal PoW block.
* `TERMINAL_BLOCK_NUMBER` – set to the number of a block designated by `TERMINAL_BLOCK_HASH`.
* `TERMINAL_TOTAL_DIFFICULTY` – set to the total difficulty value of a block designated by `TERMINAL_BLOCK_HASH`.
On CL side the override has to be enabled via updating [the following parameters](https://github.com/ethereum/consensus-specs/blob/dev/specs/bellatrix/beacon-chain.md#transition-settings):
* `TERMINAL_BLOCK_HASH` – set to the hash of a certain block to become the terminal PoW block.
* `TERMINAL_BLOCK_HASH_ACTIVATION_EPOCH` - set to the epoch when the Merge transition will be activated.
### EL client tests
EL client tests for `TBH` (`TERMINAL_BLOCK_HASH`) override are design with the following *potential* implementation in mind:
* EL always triggers the Merge upgrade upon reaching `TERMINAL_TOTAL_DIFFICULTY`
* `TERMINAL_BLOCK_HASH` + `TERMINAL_BLOCK_NUMBER` whitelists the canonical chain
Effectively, this semantics gives the desired result. But if EL has two chains that have reached `TTD`, EL may not reject a transition payload built atop of a block which `blockHash != TBH`. But we have CL side to enforce the terminal block's `blockHash == TBH`. Which means that ommitting this check is OK on EL side. Tests in this section *doesn't* assume that `blockHash == TBH` is enforced by EL client implementations.
With the above thoughts in mind, the goal of this section is to check that `TBH` override doesn't affect `TTD` block condition.
* [ ] Propose valid transition block with enabled `TBH` override
* `Genesis <- ... <- TB`, `TB` is a valid terminal block, `TB.blockHash == TBH`
* `B: Genesis <- ... <- Bn`, `B` chain is heavier than `A` and would be canonical unless `TBH` override is activated
* EL starts with `A` and `B` imported
* `forkchoiceUpdated(headBlockHash: TB.blockHash, payloadAttributes: mergeTransitionBlockAttributes)`
* EL's head is set to `headBlockHash`
* EL returns `{status: VALID, payloadId: mergeTransitionPayloadId}`
* `getPayload(mergeTransitionPayloadId)`
* EL returns merge transition payload
* `ommersHash == 0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347`
* `difficulty == 0`
* `mixHash == mergeTransitionBlockAttributes.prevRandao`
* `nonce == 0x0000000000000000`
* `newPayload(mergeTransitionPayload)`
* EL returns `VALID`
* [ ] Build atop of invalid terminal block with enabled `TBH` override
* `Genesis <- ... <- INV_TB`, `INV_TB` is a *valid PoW* but an *invalid terminal* block, i.e. `INV_TB.TD < TTD`
* EL starts with `INV_TB` as the head
* `forkchoiceUpdated(headBlockHash: TB.blockHash, payloadAttributes: mergeTransitionBlockAttributes)`
* EL returns `{status: INVALID, latestValidHash: 0x00..00, payloadId: null}`
* EL's head points to `INV_TB` if `INV_TB.TD < TTD`, and to `INV_TB.parent` if `INV_TB.parent.TD >= TTD`
* [ ] Transition on a valid chain with enabled `TBH` override
* `A: Genesis <- ... <- TB <- P1 <- P2 <- P3`, `TB` is a valid terminal block, i.e. `TB.blockHash == TBH`
* `B: Genesis <- ... <- Bn`, `B` chain is heavier than `A` and would be canonical unless `TBH` override is activated
* EL starts with `A` and `B` imported
* `newPayload(P1)`
* EL returns `{status: VALID, latestValidHash: payload.blockHash}`
* EL's head points to `TB`
* `forkchoiceUpdated(head: P1, safe: 0x00..00, finalized: 0x00..00)`
* EL returns `{status: VALID, latestValidHash: forkchoiceState.headBlockHash}`
* EL's head updated to `P1`
* `eth_getBlockByNumber(safe)` returns `-39001: Unknown block` error
* `eth_getBlockByNumber(finalized)` returns `-39001: Unknown block` error
* `newPayload(P2) + forkchoiceUpdated(head: P2, safe: P1, finalized: 0x00..00)`
* EL's head updated to `P2`
* `eth_getBlockByNumber(safe)` returns `-39001: Unknown block` error
* `eth_getBlockByNumber(finalized)` returns `-39001: Unknown block` error
* `newPayload(P3) + forkchoiceUpdated(head: P3, safe: P2, finalized: P1)`
* EL's head updated to `P3`
* `eth_getBlockByNumber(safe)` returns `P2`
* `eth_getBlockByNumber(finalized)` returns `P1`
* [ ] Transition on an invalid chain with enabled `TBH` override
* `Genesis <- ... <- INV_TB <- P1`, `INV_TB` is a *valid PoW* but an *invalid terminal* block:
* [ ] `INV_TB.TD < TTD`
* [ ] `INV_TB.parent.TD >= TTD` -- it might require a second EL with a higher `TTD` value
* EL starts with `INV_TB` as the head
* `newPayload(P1)`
* EL returns `{status: INVALID, latestValidHash: 0x00..00}`
* EL's head points to `INV_TB` if `INV_TB.TD < TTD`, and to `INV_TB.parent` if `INV_TB.parent.TD >= TTD`
* [ ] Syncing with the chain having a valid transition, with enabled `TBH` override
* `A: Genesis <- ... <- Bn <- TB <- P1`, `TB` is a valid terminal block, i.e. `TB.blockHash == TBH`
* `B: Genesis <- ... <- Bn`, `B` chain is heavier than `A` and would be canonical unless `TBH` override is activated
* EL starts with `B` imported
* `newPayload(P1) + forkchoiceUpdated(P1)`
* EL returns `{status: SYNCING}`
* EL's head points to `Bn`
* EL should pull `TB <- P1` from the network
* poll `forkchoiceUpdated(P1)`
* EL returns `{status: VALID, latestValidHash: null}`
* EL's head points to `P1`
* [ ] Stop processing synced PoW blocks with `TBH` override enabled
* PoW client starts with `Genesis <- ... <- TB <- B1 <- B2 <- ... <- Bn` chain, where `TB` is a valid terminal block, i.e. `TB.blockHash == TBH`
* EL connects to the PoW client and syncs with the advertised chain
* EL's head points to `TB`
### CL client tests
* [ ] Transition on a valid chain with `TBH` override enabled
* `EL.A: Genesis <- ... <- TB`, `TB` is a valid terminal block. i.e. `TB.blockHash == TBH`
* `EL.B: Genesis <- ... <- Bn`, `B` chain is heavier than `A` and would be canonical unless `TBH` override is activated
* `CL: Genesis <- ... <- Bellatrix`
* EL starts with imported `A` and `B`
* `TERMINAL_BLOCK_HASH_ACTIVATION_EPOCH` is greater than `BELLATRIX_FORK_EPOCH`
* CL strats with `Genesis` and builds a chain up to `Bellatrix` and upgrades to `Bellatrix`
* Merger transition starts only when `TERMINAL_BLOCK_HASH_ACTIVATION_EPOCH` is reached
* CL drives EL through transition and finalizes it
* The terminal block is `EL.A.TB`, and the PoS chain is built atop of that block
* `eth_getBlockByNumber(latest)` returns the head
* `eth_getBlockByNumber(safe)` returns the most recent justified block
* `eth_getBlockByNumber(finalized)` returns the most recent finalized block
* [ ] Transition before `TERMINAL_BLOCK_HASH_ACTIVATION_EPOCH`
* `EL: Genesis <- ... <- Bn`
* Nodes: builder, importer; importer is connected to builder on both layers
* `Bn` is a valid terminal block to the builder's and importer's observation, i.e. `TBH == Bn.blockHash`
* `TERMINAL_BLOCK_HASH_ACTIVATION_EPOCH` on importer's side is much grater than on builder's side
* EL starts with `Bn` as the head
* The builder starts with `Genesis` and build a chain up to Bellatrix and beyond
* Importer does never imports a transition block and its head does always point to pre-transition block
* [ ] Transition with mismatched `TERMINAL_BLOCK_HASH`
* `EL: Genesis <- ... <- Bn`
* Nodes: builder, importer; importer is connected to builder on both layers
* `Bn` is a valid terminal block to the builder's observation, i.e. builder's `TBH == Bn.blockHash`
* `Bn` is an *invalid* terminal block to the importer's observation, i.e. builder's `TBH != Bn.blockHash`
* `TERMINAL_BLOCK_HASH_ACTIVATION_EPOCH` is the same on buidler's and importer's sides
* EL starts with `Bn` as the head
* The builder starts with `Genesis` and build a chain up to Bellatrix and beyond
* Importer does never imports a transition block and its head does always point to pre-transition block