Skip to content

Commit a9fb42d

Browse files
authored
docs: add execution section in architecture document (#1203)
1 parent 756ccca commit a9fb42d

File tree

1 file changed

+92
-78
lines changed

1 file changed

+92
-78
lines changed

Diff for: docs/architecture.md

+92-78
Original file line numberDiff line numberDiff line change
@@ -2,44 +2,40 @@
22

33
## Processes summary
44

5-
### Supervision tree
6-
75
This is our complete supervision tree.
86

97
```mermaid
108
graph LR
119
Application[Application <br> <:one_for_one>]
1210
BeaconNode[BeaconNode <br> <:one_for_all>]
13-
P2P.IncomingRequests[P2P.IncomingRequests <br> <:one_for_one>]
1411
ValidatorManager[ValidatorManager <br> <:one_for_one>]
1512
Telemetry[Telemetry <br> <:one_for_one>]
1613
1714
Application --> Telemetry
1815
Application --> DB
1916
Application --> Blocks
2017
Application --> BlockStates
21-
Application --> Metadata
2218
Application --> BeaconNode
2319
Application --> BeaconApi.Endpoint
2420
25-
BeaconNode -->|genesis_time,<br>genesis_validators_root,<br> fork_choice_data, time| BeaconChain
26-
BeaconNode -->|store, head_slot, time| ForkChoice
21+
subgraph Basic infrastructure
22+
DB
23+
BeaconApi.Endpoint
24+
Telemetry
25+
:telemetry_poller
26+
TelemetryMetricsPrometheus
27+
end
28+
29+
subgraph Caches
30+
Blocks
31+
BlockStates
32+
end
33+
2734
BeaconNode -->|listen_addr, <br>enable_discovery, <br> discovery_addr, <br>bootnodes| P2P.Libp2pPort
28-
BeaconNode --> P2P.Peerbook
29-
BeaconNode --> P2P.IncomingRequests
30-
BeaconNode --> PendingBlocks
3135
BeaconNode --> SyncBlocks
32-
BeaconNode --> Attestation
33-
BeaconNode --> BeaconBlock
34-
BeaconNode --> BlobSideCar
35-
BeaconNode --> OperationsCollector
3636
BeaconNode -->|slot, head_root| ValidatorManager
37-
BeaconNode -->|genesis_time, snapshot, votes| ExecutionChain
3837
ValidatorManager --> ValidatorN
3938
40-
P2P.IncomingRequests --> IncomingRequests.Handler
41-
P2P.IncomingRequests --> IncomingRequests.Receiver
42-
4339
Telemetry --> :telemetry_poller
4440
Telemetry --> TelemetryMetricsPrometheus
4541
```
@@ -48,63 +44,6 @@ Each box is a process. If it has children, it's a supervisor, with it's restart
4844

4945
If it's a leaf in the tree, it's a GenServer, task, or other non-supervisor process. The tags in the edges/arrows are the init args passed on children init (start or restart after crash).
5046

51-
### High level interaction
52-
53-
This is the high level interaction between the processes.
54-
55-
```mermaid
56-
graph LR
57-
58-
ExecutionChain
59-
60-
BlobDb
61-
BlockDb
62-
63-
subgraph "P2P"
64-
Libp2pPort
65-
Peerbook
66-
IncomingRequests
67-
Attestation
68-
BeaconBlock
69-
BlobSideCar
70-
Metadata
71-
end
72-
73-
subgraph "Node"
74-
Validator
75-
BeaconChain
76-
ForkChoice
77-
PendingBlocks
78-
OperationsCollector
79-
end
80-
81-
BeaconChain <-->|on_tick <br> get_fork_digest, get_| Validator
82-
BeaconChain -->|on_tick| BeaconBlock
83-
BeaconChain <-->|on_tick <br> update_fork_choice_cache| ForkChoice
84-
BeaconBlock -->|add_block| PendingBlocks
85-
Validator -->|get_eth1_data <br>to build blocks| ExecutionChain
86-
Validator -->|publish block| Libp2pPort
87-
Validator -->|collect, stop_collecting| Attestation
88-
Validator -->|get slashings, <br>attestations,<br> voluntary exits|OperationsCollector
89-
Validator -->|store_blob| BlobDb
90-
ForkChoice -->|notify new block|Validator
91-
ForkChoice <-->|notify new block <br> on_attestation|OperationsCollector
92-
ForkChoice -->|notify new block|ExecutionChain
93-
ForkChoice -->|store_block| BlockDb
94-
PendingBlocks -->|on_block| ForkChoice
95-
PendingBlocks -->|get_blob_sidecar|BlobDb
96-
Libp2pPort <-->|gosipsub <br> validate_message| BlobSideCar
97-
Libp2pPort <-->|gossipsub <br> validate_message<br> subscribe_to_topic| BeaconBlock
98-
Libp2pPort <-->|gossipsub <br> validate_message<br> subscribe_to_topic| Attestation
99-
Libp2pPort -->|store_blob| BlobDb
100-
Libp2pPort -->|new_peer| Peerbook
101-
BlobSideCar -->|store_blob| BlobDb
102-
Attestation -->|set_attnet|Metadata
103-
IncomingRequests -->|get seq_number|Metadata
104-
PendingBlocks -->|penalize/get<br>on downloading|Peerbook
105-
Libp2pPort -->|new_request| IncomingRequests
106-
```
107-
10847
## P2P Events
10948

11049
This section contains sequence diagrams representing the interaction of processes through time in response to a stimulus. The main entry point for new events is through gossip and request-response protocols, which is how nodes communicates between each other.
@@ -296,10 +235,6 @@ Explained, a process that wants to request something from Libp2pPort sends a req
296235

297236
The specific kind of command (a request) is specified, but there's nothing identifying this is a response vs any other kind of result, or the specific kind of response (e.g. a block download vs a blob download). Currently the only way this is handled differentially is because the pid is waiting for a specific kind of response and for nothing else at a time.
298237

299-
## Checkpoint sync
300-
301-
**TO DO**: document checkpoint sync.
302-
303238
## Validators
304239

305240
Validators are separate processes. They react to:
@@ -351,6 +286,85 @@ In the proposing slot:
351286
- The deposits and eth1_vote are fetched from `ExecutionChain`.
352287
- The block is signed.
353288

289+
## Execution Chain
290+
291+
The consensus node needs to communicate with the execution client for three different reasons:
292+
293+
- Fork choice updates: the execution clients needs notifications when the head is updated, and payloads need validation. For these goals, `engineAPI` is used.
294+
- Deposit contract tracking: accounts that wish to become validators need to deposit 32ETH in the deposit contract. This happens in the execution chain, but the information needs to arrive to consensus for the validator set to be updated.
295+
- Eth 1 votes: consensus nodes agree on a summary of the execution state and a voting mechanism is built for this.
296+
297+
Let's go to this communication sections one by one.
298+
299+
### Engine API: fork choice updates
300+
301+
The consensus node does not live in isolation. It communicates to the execution client. We implemented, in the `ExecutionClient` module, the following primitives:
302+
303+
- `notify_forkchoice_updated(fork_choice_state)`: first message sent to the execution client right after exchanging capabilities. It returns if the client is syncing or valid.
304+
- `notify_forkchoice_updated(fork_choice_state, payload_attributes)`: sent to update the fork choice state in the execution client (finalized and head payload hash). This starts the execution payload build process. Returns a `payload_id` that will be used at block building time to get the actual execution payload. It's sent in the slot prior to proposing. It might return a null id if the execution client is still syncing.
305+
- `get_payload(payload_id)`: returns an `{ExecutionPayload.t(), BlobsBundle.t()}` tuple that started building when `notify_forkchoice_updated` was called with payload attributes.
306+
- `notify_new_payload(execution_payload, versioned_hashes, parent_beacon_block_root)`: when the execution client gets a new block, it needs to check if the execution payload is valid. This method is used to send that payload for verification. It may return valid, invalid, or syncing, in the case where the execution client is not yet synced.
307+
308+
### Deposit contract
309+
310+
Each time there's a deposit, a log is included in the execution block that can be read by the consensus layer using the `get_deposit_logs(range)` function for this.
311+
312+
Each deposit has the following form:
313+
314+
```elixir
315+
%DepositData {
316+
# BLS Credentials publicly identifying the validator.
317+
pubkey: Types.bls_pubkey(),
318+
# Public address where the stake rewards will be sent.
319+
withdrawal_credentials: Types.bytes32(),
320+
# Amount of eth deposited.
321+
amount: Types.gwei(),
322+
# Signature over the other fields.
323+
signature: Types.bls_signature()
324+
}
325+
```
326+
327+
These deposits are aggregated into a merkle trie where each deposit is a leaf. After aggregation we can obtain:
328+
329+
- A `deposit_root`: root of the merkle trie.
330+
- A `deposit_count`: the amount of deposits that were processed in the execution layer.
331+
332+
This aggregation structure is useful to send snapshots cheaply, when performing checkpoint sync. See [EIP-4881](https://eips.ethereum.org/EIPS/eip-4881). It can also be used to send cheap merkle proofs that show a deposit is part of the current deposit set.
333+
334+
### Eth 1 voting
335+
336+
Validators have a summarized view of the execution chain, stored in a struct called `Eth1Data`:
337+
338+
```elixir
339+
%Eth1Data{
340+
deposit_root: Types.root(),
341+
deposit_count: Types.uint64(),
342+
block_hash: Types.hash32()
343+
}
344+
```
345+
346+
This is the full process of how this data is included in the consensus state:
347+
348+
- There's a voting period each 64 epochs. That is, 2048 slots, almost 7 hours. There's a period change when `rem(next_epoch, epochs_per_eth1_voting_period) == 0`.
349+
- Each proposer include their view of the chain (`Eth1Data`) in the block they propose (`block.body.eth1_data`). They obtain this data from their own execution client. This is sometimes called their "eth 1 vote".
350+
- When state transition is performed using that block, the vote is included in the `beacon_state.eth1_data_votes` array, which contains the votes for the whole voting period. If a single `eth1_data_vote` is present in more than half of the period slots, then it's now considered the current `eth1_data`, which means it's assigned as `beacon_state.eth1_data` in that state transition.
351+
- After an eth1 data vote period finishes, the `beacon_state.eth1_data_votes` are reset (the list is assigned as empty), regardless of there being a winner (new eth 1 data after the period) or not.
352+
353+
Everything related to state transition is performed by beacon nodes even if they're not validators, and need no interaction with the execution chain, as they only apply blocks to states as a pure function.
354+
355+
However, validators that include their view of the execution chain in a block being built, do need access to the latest blocks, as described in the [eth1 data section](https://github.com/ethereum/consensus-specs/blob/v1.3.0/specs/phase0/validator.md#eth1-data) of the validator specs. To summarize:
356+
357+
- The consensus node gets blocks from the execution client and builds `Eth1Data` structs.
358+
- A proposer at a slot `N` will need to build its eth1 vote for the voting period that started at slot `s = div(N, EPOCHS_PER_ETH1_VOTING_PERIOD)`. It will take into account the execution blocks that are `ETH1_FOLLOW_DISTANCE` behind the current voting period.
359+
- The vote is selected using the following priority:
360+
- An `eth1_data` that is present in the execution client and also in the beacon state `eth1_data_votes` list. If more than one match, the most frequent will be selected. That ways it tries to match a vote by a different node if present.
361+
- If no matches are found in the beacon state votes, it will default to the latest eth1 data available in the local execution client (within the expected range).
362+
- If nothing is found in the execution chain for the expected range, the last default is voting for the current `beacon_state.eth1_data`.
363+
364+
## Checkpoint sync
365+
366+
**TO DO**: document checkpoint sync.
367+
354368
## Next document
355369

356370
Let's go over [Fork Choice](fork_choice.md) to see a theoretical explanation of LMD GHOST.

0 commit comments

Comments
 (0)