Investigation in `SignedExecutionPayloadEnvelope` lifecycle from the perspective of a receiving peer:
1. receive `SignedExecutionPayloadEnvelope` over the wire
2. Run validations on received envelope per the spec
- if validations pass, the envelope can be forwarded
3. in fork choice, `on_execution_payload` is called when the node receives a `SignedExecutinPayloadEnvelope` to sync:
```python
def on_execution_payload(store: Store, signed_envelope: SignedExecutionPayloadEnvelope) -> None:
"""
Run ``on_execution_payload`` upon receiving a new execution payload.
"""
envelope = signed_envelope.message
# The corresponding beacon block root needs to be known
assert envelope.beacon_block_root in store.block_states
# Check if blob data is available
# If not, this payload MAY be queued and subsequently considered when blob data becomes available
assert is_data_available(envelope.beacon_block_root)
# Make a copy of the state to avoid mutability issues
state = copy(store.block_states[envelope.beacon_block_root])
# Process the execution payload
process_execution_payload(state, signed_envelope, EXECUTION_ENGINE)
# Add new state for this payload to the store
store.execution_payload_states[envelope.beacon_block_root] = state
```
- note that this function will call `process_execution_payload` in the BN and handle state transition. Then, the new state is stored in the fork choice store as well
- also note the `is_data_available` check, which will likely be handled with Mark's envelope processing pipeline
- need to decide when to trigger fork choice's `on_execution_payload`
- need to decide when to store a blinded envelope on disk for use when serving envelopes to peers that request it
in Prysm, it seems like the flow is:
```
// ReceiveExecutionPayloadEnvelope is a function that defines the operations (minus pubsub)
// that are performed on a received execution payload envelope. The operations consist of:
// 1. Validate the payload, apply state transition.
// 2. Apply fork choice to the processed payload
// 3. Save latest head info
```
this will run:
1. `validateExecutionOnEnvelope`
2. `savePostPayload` for blinded payload
3. `s.insertPayloadEnvelope` for use in fork choice
4. ```
if isValidPayload {
s.ForkChoicer().Lock()
if err := s.ForkChoicer().SetOptimisticToValid(ctx, root); err != nil {
s.ForkChoicer().Unlock()
return errors.Wrap(err, "could not set optimistic payload to valid")
}
s.ForkChoicer().Unlock()
}
```
What we've built:
`Network Receive → Router → NetworkBeaconProcessor → BeaconProcessor Queue → GossipMethods
↓
MessageAcceptance::Ignore`
```
graph TD
A[Network Receives Envelope] --> B[Router.handle_gossip]
B --> C[NetworkBeaconProcessor.send_gossip_execution_payload]
C --> D[BeaconProcessor Queue]
D --> E[process_gossip_execution_payload]
E --> F[MessageAcceptance::Ignore]
F --> G[No Processing - Dropped]
```
```
// 1. Network receives execution payload envelope
// 2. Router creates Work::GossipExecutionPayload work event
// 3. Manager task receives work event
// 4. Manager either:
// - Spawns new worker if available
// - Adds to gossip_execution_payload_queue if workers busy
// 5. When worker becomes free, manager pops from highest priority queue
// 6. Work gets processed by calling the function stored in the work event
```
The overall flow for adding a new global topic to lighthouse is:
1. `topics.rs` - add the new topic name like `execution_payload` and enable encoding and decoding from topic name to `GossipKind`
2. `pubsub.rs` - enable serialization and deserialization of the message itself
3. `router.rs` - add the new topic so that the message can be passed to the beacon processor so can be queued for validation and then gossiping to other peers/processing by beacon chain
### `beacon_block` modifications
The [spec](https://ethereum.github.io/consensus-specs/specs/gloas/p2p-interface/#beacon_block) notes the following rules that we need to interpret for better understanding:
```
If execution_payload verification of block's parent by an execution node is not complete:
[REJECT] The block's parent (defined by block.parent_root) passes all validation (excluding execution node verification of the block.body.execution_payload).
- Use this when your node has not finished EL verification of the parent. Require the parent to pass CL validation only. If that CL check fails, REJECT the incoming block. This is common while optimistic sync is in effect or whenever the parent’s EL status is still pending.
otherwise:
[IGNORE] The block's parent (defined by block.parent_root) passes all validation (including execution node verification of the block.body.execution_payload).
- Use this when your node has finished EL verification of the parent. Require the parent to pass CL + EL. If that bundled check fails, IGNORE the incoming block. Ignore means drop without penalizing the peer. Rationale, the network allows EL-invalid blocks to propagate, and once you know a parent is bad you stop forwarding descendants softly to avoid partitioning and down-scoring over EL outcomes.
- 
```
The background for this scenario is from the bellatrix spec [here](https://ethereum.github.io/consensus-specs/specs/bellatrix/p2p-interface/#beacon_block). In Bellatrix, the `execution_payload` was added to the `BeaconBlockBody`. They wanted blocks to still be able to be propagated regardless of the validity of the execution payload as to prevent network segregation between optimistic (sync) and non-optimistic nodes.
Note that optimistic sync is designed to be opt-in and backwards compatible (i.e., non-optimistic nodes can tolerate optimistic nodes on the network and vice versa).
```
Something to note was that the `REJECT` and `IGNORE` cases above should only be applied if `is_execution_enabled` is true, where:
```python
def is_merge_transition_complete(state: BeaconState) -> bool:
return state.latest_execution_payload_header != ExecutionPayloadHeader()
def is_merge_transition_block(state: BeaconState, body: BeaconBlockBody) -> bool:
return not is_merge_transition_complete(state) and body.execution_payload != ExecutionPayload()
def is_execution_enabled(state: BeaconState, body: BeaconBlockBody) -> bool:
return is_merge_transition_block(state, body) or is_merge_transition_complete(state)
```
This basically means the `REJECT` and `IGNORE` cases should only apply past-Bellatrix.
Before Bellatrix, i.e. `!is_Execution_enabled`, we apply this check:
```
[REJECT] The block's parent (defined by block.parent_root) passes validation.
```
And for `gloas`, we don't perform these pre and post-merge checks above and instead do:
```
And instead the following validations are set in place with the alias bid = signed_execution_payload_bid.message:
- If execution_payload verification of block's execution payload parent by an execution node is complete:
- [REJECT] The block's execution payload parent (defined by bid.parent_block_hash) passes all validation.
- note that this mesans to reject the block if your EL has a final **invalid** verdict for `bid.parent_block_hash`. In practice, this verdict comes once your EL has processed the **parent’s envelope** whose `payload.block_hash == bid.parent_block_hash` (via gossip or Req/Resp).
- [REJECT] The block's parent (defined by block.parent_root) passes validation.
- reject the block if the **beacon** parent is CL-invalid (pure CL gate; back to the pre-merge style check on `beacon_block`).
```