eth2.0-specs/sync/optimistic.md

340 lines
14 KiB
Markdown
Raw Normal View History

2021-12-05 23:07:54 +00:00
# Optimistic Sync
2022-01-12 05:49:08 +00:00
[`validate_merge_block`]: ../specs/bellatrix/fork-choice.md#validate_merge_block
2022-01-12 05:39:43 +00:00
2021-12-05 23:07:54 +00:00
## Introduction
2021-12-13 06:09:09 +00:00
In order to provide a syncing execution engine with a partial view of the head
of the chain, it may be desirable for a consensus engine to import beacon
blocks without verifying the execution payloads. This partial sync is called an
*optimistic sync*.
2021-12-05 23:07:54 +00:00
2021-12-19 03:44:21 +00:00
## Constants
|Name|Value|Unit
|---|---|---|
2021-12-21 05:49:43 +00:00
|`SAFE_SLOTS_TO_IMPORT_OPTIMISTICALLY`| `128` | slots
2021-12-19 23:15:51 +00:00
*Note: the `SAFE_SLOTS_TO_IMPORT_OPTIMISTICALLY` must be user-configurable. See
2021-12-19 23:32:45 +00:00
[Fork Choice Poisoning](#fork-choice-poisoning).*
2021-12-19 03:44:21 +00:00
## Helpers
2022-01-12 20:17:43 +00:00
Let `head: BeaconBlock` be the result of calling of the fork choice
2021-12-20 06:46:42 +00:00
algorithm at the time of block production.
2021-12-19 23:41:50 +00:00
Let `optimistic_roots: Set[Root]` be the set of `hash_tree_root(block)` for all
optimistically imported blocks which have yet to receive an `INVALID` or
`VALID` designation from an execution engine.
2022-01-12 04:57:27 +00:00
Let `optimistic_roots: Set[Root]` be the set of `hash_tree_root(block)` for all
optimistically imported blocks which have only received a `SYNCING` designation
from an execution engine (i.e., they are not known to be `INVALID` or `VALID`).
2021-12-19 03:44:21 +00:00
```python
@dataclass
class Store(object):
optimistic_roots: Set[Root]
head_block_root: Root
blocks: Dict[Root, BeaconBlock]
block_states: Dict[Root, BeaconState]
2021-12-19 03:44:21 +00:00
```
```python
def is_optimistic(store: Store, block: BeaconBlock) -> bool:
return hash_tree_root(block) in store.optimistic_roots
```
```python
def latest_verified_ancestor(store: Store, block: BeaconBlock) -> BeaconBlock:
2022-01-12 20:13:10 +00:00
# It is assumed that the `block` parameter is never an INVALID block.
2021-12-19 03:44:21 +00:00
while True:
if not is_optimistic(store, block) or block.parent_root == Root():
return block
2022-01-12 20:17:43 +00:00
block = store.blocks[block.parent_root]
2021-12-19 03:44:21 +00:00
```
```python
def is_execution_block(block: BeaconBlock) -> BeaconBlock:
return block.body.execution_payload != ExecutionPayload()
2021-12-19 03:44:21 +00:00
```
2021-12-19 03:56:46 +00:00
```python
def should_optimistically_import_block(store: Store, current_slot: Slot, block: BeaconBlock) -> bool:
2022-01-12 20:00:28 +00:00
justified_root = store.block_states[store.head_block_root].current_justified_checkpoint.root
justifed_is_verified = is_execution_block(store.blocks[justified_root])
block_is_deep = block.slot + SAFE_SLOTS_TO_IMPORT_OPTIMISTICALLY <= current_slot
return justified_is_verified or block_is_deep
2021-12-19 03:56:46 +00:00
```
2022-01-12 20:17:43 +00:00
Let only a node which returns `is_optimistic(store, head) is True` be an *optimistic
2021-12-19 03:44:21 +00:00
node*. Let only a validator on an optimistic node be an *optimistic validator*.
When this specification only defines behaviour for an optimistic
node/validator, but *not* for the non-optimistic case, assume default
behaviours without regard for optimistic sync.
2021-12-05 23:07:54 +00:00
## Mechanisms
### When to optimistically import blocks
2021-12-19 03:44:21 +00:00
2022-01-12 20:17:57 +00:00
A block MAY be optimistically imported when
`should_optimistically_import_block(store, current_slot, block)` returns
`True`. This ensures that blocks are only optimistically imported if either:
1. The justified checkpoint has execution enabled.
1. The current slot (as per the system clock) is at least
`SAFE_SLOTS_TO_IMPORT_OPTIMISTICALLY` ahead of the slot of the block being
imported.
2021-12-19 03:44:21 +00:00
2021-12-19 23:32:45 +00:00
*See [Fork Choice Poisoning](#fork-choice-poisoning) for the motivations behind
these conditions.*
### How to optimistically import blocks
2021-12-19 03:44:21 +00:00
To optimistically import a block:
2021-12-05 23:07:54 +00:00
2022-01-12 05:49:08 +00:00
- The [`execute_payload`](../specs/bellatrix/beacon-chain.md#execute_payload) function MUST return `True` if the execution
2021-12-13 06:09:09 +00:00
engine returns `SYNCING` or `VALID`. An `INVALID` response MUST return `False`.
2022-01-12 05:39:43 +00:00
- The [`validate_merge_block`][] function MUST NOT raise an assertion if both the
2021-12-20 06:55:10 +00:00
`pow_block` and `pow_parent` are unknown to the execution engine.
- The parent of the block MUST NOT have an INVALID execution payload.
2021-12-05 23:07:54 +00:00
2021-12-19 23:41:50 +00:00
In addition to this change to validation, the consensus engine MUST be able to
2021-12-20 00:12:11 +00:00
ascertain, after import, which blocks returned `SYNCING` and which returned
`VALID`.
2021-12-05 23:07:54 +00:00
2021-12-20 00:12:11 +00:00
Optimistically imported blocks MUST pass all verifications included in
`process_block` (withstanding the modifications to `execute_payload`).
2021-12-14 05:21:30 +00:00
2021-12-05 23:07:54 +00:00
A consensus engine MUST be able to retrospectively (i.e., after import) modify
2021-12-13 06:09:09 +00:00
the status of `SYNCING` blocks to be either `VALID` or `INVALID` based upon responses
2021-12-05 23:07:54 +00:00
from an execution engine. I.e., perform the following transitions:
2021-12-13 06:09:09 +00:00
- `SYNCING` -> `VALID`
- `SYNCING` -> `INVALID`
2021-12-05 23:07:54 +00:00
2021-12-20 00:12:11 +00:00
When a block transitions from `SYNCING` -> `VALID`, all *ancestors* of the
block MUST also transition from `SYNCING` -> `VALID`. Such a block is no longer
considered "optimistically imported".
2021-12-05 23:07:54 +00:00
2021-12-13 06:09:09 +00:00
When a block transitions from `SYNCING` -> `INVALID`, all *descendants* of the
block MUST also transition from `SYNCING` -> `INVALID`.
2021-12-20 06:46:42 +00:00
When a block transitions from the `SYNCING` state it is removed from the set of
`store.optimistic_roots`.
2021-12-13 06:09:09 +00:00
2022-01-12 05:39:43 +00:00
When a "merge block" (i.e. a block which enables execution) is declared to be
`VALID` by an execution engine (either directly or indirectly), the full
[`validate_merge_block`][] MUST be run against the merge block. If the block
fails [`validate_merge_block`][], the merge block MUST be treated the same as
an `INVALID` block (i.e., it and all its descendants are invalidated and
removed from the block tree).
### Execution Engine Errors
2021-12-19 03:34:43 +00:00
2021-12-20 00:12:11 +00:00
When an execution engine returns an error or fails to respond to a payload
2021-12-20 07:32:47 +00:00
validity request for some block, a consensus engine:
2021-12-20 00:12:11 +00:00
- MUST NOT optimistically import the block.
2021-12-20 03:04:33 +00:00
- MUST NOT apply the block to the fork choice store.
2021-12-20 00:12:11 +00:00
- MAY queue the block for later processing.
2021-12-13 06:09:09 +00:00
2021-12-14 04:44:15 +00:00
### Assumptions about Execution Engine Behaviour
This specification assumes execution engines will only return `SYNCING` when
there is insufficient information available to make a `VALID` or `INVALID`
determination on the given `ExecutionPayload` (e.g., the parent payload is
2021-12-20 06:46:42 +00:00
unknown). Specifically, `SYNCING` responses should be fork-specific, in that
the search for a block on one chain MUST NOT trigger a `SYNCING` response for
another chain.
2021-12-14 04:44:15 +00:00
2021-12-14 05:42:35 +00:00
### Re-Orgs
The consensus engine MUST support any chain reorganisation which does *not*
affect the justified checkpoint. The consensus engine MAY support re-orgs
beyond the justified checkpoint.
If the justified checkpoint transitions from `SYNCING` -> `INVALID`, a
consensus engine MAY choose to alert the user and force the application to
exit.
2021-12-19 23:32:45 +00:00
## Fork Choice
2021-12-20 06:46:42 +00:00
Consensus engines MUST support removing blocks from fork choice that transition
2021-12-19 23:32:45 +00:00
from `SYNCING` to `INVALID`. Specifically, a block deemed `INVALID` at any
point MUST NOT be included in the canonical chain and the weights from those
`INVALID` blocks MUST NOT be applied to any `VALID` or `SYNCING` ancestors.
### Fork Choice Poisoning
2021-12-19 23:15:51 +00:00
During the merge transition it is possible for an attacker to craft a
`BeaconBlock` with an execution payload that references an
2021-12-20 06:46:42 +00:00
eternally-unavailable `body.execution_payload.parent_hash` (i.e., the parent
hash is random bytes). In rare circumstances, it is possible that an attacker
can build atop such a block to trigger justification. If an optimistic node
imports this malicious chain, that node will have a "poisoned" fork choice
store, such that the node is unable to produce a block that descends from the
head (due to the invalid chain of payloads) and the node is unable to produce a
block that forks around the head (due to the justification of the malicious
2021-12-19 23:15:51 +00:00
chain).
2022-01-12 05:10:59 +00:00
If an honest chain exists which justifies a higher epoch than the malicious
2022-01-12 19:52:07 +00:00
chain, that chain will take precedence and revive any poisoned store. Such a
chain, if imported before the malicious chain, will prevent the store from
being poisoned. Therefore, the poisoning attack is temporary if >= 2/3rds of
the network is honest and non-faulty.
2021-12-19 23:15:51 +00:00
The `SAFE_SLOTS_TO_IMPORT_OPTIMISTICALLY` parameter assumes that the network
will justify a honest chain within some number of slots. With this assumption,
2021-12-20 00:12:11 +00:00
it is acceptable to optimistically import transition blocks during the sync
process. Since there is an assumption that an honest chain with a higher
2021-12-19 23:15:51 +00:00
justified checkpoint exists, any fork choice poisoning will be short-lived and
resolved before that node is required to produce a block.
However, the assumption that the honest, canonical chain will always justify
within `SAFE_SLOTS_TO_IMPORT_OPTIMISTICALLY` slots is dubious. Therefore,
clients MUST provide the following command line flag to assist with manual
disaster recovery:
2021-12-20 00:12:11 +00:00
- `--safe-slots-to-import-optimistically`: modifies the
2021-12-19 23:15:51 +00:00
`SAFE_SLOTS_TO_IMPORT_OPTIMISTICALLY`.
2021-12-14 05:10:19 +00:00
## Checkpoint Sync (Weak Subjectivity Sync)
2021-12-20 06:46:42 +00:00
A consensus engine MAY assume that the `ExecutionPayload` of a block used as an
anchor for checkpoint sync is `VALID` without necessarily providing that
payload to an execution engine.
2021-12-14 05:10:19 +00:00
2021-12-13 08:02:45 +00:00
## Validator assignments
2021-12-20 07:32:54 +00:00
An optimistic node is *not* a full node. It is unable to produce blocks, since
an execution engine cannot produce a payload upon an unknown parent. It cannot
faithfully attest to the head block of the chain, since it has not fully
verified that block.
2021-12-13 08:02:45 +00:00
2021-12-13 08:15:53 +00:00
### Block Production
2021-12-05 23:07:54 +00:00
An optimistic validator MUST NOT produce a block (i.e., sign across the
2021-12-20 07:32:54 +00:00
`DOMAIN_BEACON_PROPOSER` domain).
2021-12-05 23:07:54 +00:00
### Attesting
An optimistic validator MUST NOT participate in attestation (i.e., sign across the
`DOMAIN_BEACON_ATTESTER`, `DOMAIN_SELECTION_PROOF` or
2021-12-13 08:03:35 +00:00
`DOMAIN_AGGREGATE_AND_PROOF` domains).
2021-12-13 06:09:09 +00:00
### Participating in Sync Committees
An optimistic validator MUST NOT participate in sync committees (i.e., sign across the
`DOMAIN_SYNC_COMMITTEE`, `DOMAIN_SYNC_COMMITTEE_SELECTION_PROOF` or
`DOMAIN_CONTRIBUTION_AND_PROOF` domains).
2021-12-13 08:02:45 +00:00
2021-12-20 05:21:11 +00:00
## Ethereum Beacon APIs
2021-12-13 08:25:33 +00:00
2021-12-20 05:21:11 +00:00
Consensus engines which provide an implementation of the [Ethereum Beacon
APIs](https://github.com/ethereum/beacon-APIs) must take care to avoid
presenting optimistic blocks as fully-verified blocks.
2021-12-13 08:25:33 +00:00
2021-12-20 05:21:11 +00:00
### Helpers
2021-12-13 08:25:33 +00:00
2021-12-20 05:21:11 +00:00
Let the following response types be defined as any response with the
corresponding HTTP status code:
2021-12-13 08:25:33 +00:00
2021-12-20 05:21:11 +00:00
- "Success" Response: Status Codes 200-299.
- "Not Found" Response: Status Code 404.
- "Syncing" Response: Status Code 503.
2021-12-13 08:25:33 +00:00
2021-12-20 05:21:11 +00:00
### Requests for Optimistic Blocks
2021-12-13 08:25:33 +00:00
2021-12-20 05:21:11 +00:00
When information about an optimistic block is requested, the consensus engine:
2021-12-14 05:11:40 +00:00
2021-12-20 05:21:11 +00:00
- MUST NOT respond with success.
- MAY respond with not found.
- MAY respond with syncing.
2021-12-14 05:11:40 +00:00
2021-12-20 06:46:42 +00:00
### Requests for an Optimistic Head
2021-12-14 05:11:40 +00:00
2022-01-12 20:17:43 +00:00
When `is_optimistic(store, head) is True`, the consensus engine:
2021-12-14 05:11:40 +00:00
2021-12-20 06:46:42 +00:00
- MUST NOT return an optimistic `head`.
2022-01-12 05:41:24 +00:00
- MAY substitute the head block with `latest_verified_ancestor(block)`.
2021-12-20 05:21:11 +00:00
- MAY return syncing.
### Requests to Validators Endpoints
2021-12-14 05:11:40 +00:00
2022-01-12 20:17:43 +00:00
When `is_optimistic(store, head) is True`, the consensus engine MUST return syncing to
2021-12-20 06:46:42 +00:00
all endpoints which match the following pattern:
2021-12-14 05:11:40 +00:00
2021-12-20 06:46:42 +00:00
- `eth/*/validator/*`
2022-01-12 08:04:43 +00:00
## Design Decision Rationale
### Why `SAFE_SLOTS_TO_IMPORT_OPTIMISTICALLY`?
Nodes can only import an optimistic block if their justified checkpoint is
verified or the block is older than `SAFE_SLOTS_TO_IMPORT_OPTIMISTICALLY`.
These restraints are applied in order to mitigate an attack where a block which
enables execution (a *transition block*) can reference a junk parent hash. This
makes it impossible for honest nodes to build atop that block. If an attacker
exploits a nuance in fork choice `filter_block_tree`, they can, in some rare
cases, produce a junk block that out-competes all locally produced blocks for
the head. This prevents a node from producing a chain of blocks, therefore
breaking liveness.
Thankfully, if 2/3rds of validators are not poisoned, they can justify an
honest chain which will un-poison all other nodes.
Notably, this attack only exists for optimistic nodes. Nodes which fully verify
the transition block will reject a block with a junk parent hash.
Given all of this, we can say two things:
1. **BNs which are following the head during the transition shouldn't
optimistically import the transition block.** If 1/3rd of validators
optimistically import the poison block, there will be no remaining nodes to
justify an honest chain.
2. **BNs which are syncing can optimistically import transition blocks.** In
this case a justified chain already exists blocks. The poison block would be
quickly reverted and would have no affect on liveness.
Astute readers will notice that (2) contains a glaring assumption about network
liveness. This is necessary because a node cannot feasibly ascertain that the
transition block is justified without importing that block and risking
poisoning. Therefore, we use `SAFE_SLOTS_TO_IMPORT_OPTIMISTICALLY` to say
something along the lines of: *"if the transition block is sufficiently old
enough, then we can just assume that block is honest or there exists an honest
justified chain to out-compete it."*
Note the use of "feasibly" in the previous paragraph. One can imagine
mechanisms to check that a block is justified before importing it. For example,
just keep processing blocks without adding them to fork choice. However, there
are still edge-cases here (e.g., when to halt and declare there was no
2022-01-12 19:56:38 +00:00
justification?) and how to mitigate implementation complexity. At this point,
2022-01-12 08:04:43 +00:00
it's important to reflect on the attack and how likely it is to happen. It
2022-01-12 19:56:38 +00:00
requires some rather contrived circumstances and it seems very unlikely to
2022-01-12 08:04:43 +00:00
occur. Therefore, we need to consider if adding complexity to avoid an
unlikely attack increases or decreases our total risk. Presently, it appears
that `SAFE_SLOTS_TO_IMPORT_OPTIMISTICALLY` sits in a sweet spot for this
trade-off.
### Transitioning from VALID -> INVALID or INVALID -> VALID
These operations are purposefully omitted. It is outside of the scope of the
specification since it's only possible with a faulty EE.
Such a scenario requires manual intervention.
## What about Light Clients?
An alternative to optimistic sync is to run a light client inside/alongside
beacon nodes that mitigates the need for optimistic sync by providing
tip-of-chain blocks to the execution engine. However, light clients comes with
their own set of complexities. Relying on light clients may also restrict nodes
from syncing from genesis, if they so desire.
A notable thing about optimistic sync is that it's *optional*. Should an
implementation decide to go the light-client route, then they can just ignore
optimistic sync all together.