From c99e48c60cc06a93e86cc61214012741c9b58cf5 Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Mon, 6 Dec 2021 10:07:54 +1100 Subject: [PATCH 01/75] Add first efforts --- sync/optimistic.md | 105 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 105 insertions(+) create mode 100644 sync/optimistic.md diff --git a/sync/optimistic.md b/sync/optimistic.md new file mode 100644 index 000000000..b3220b96f --- /dev/null +++ b/sync/optimistic.md @@ -0,0 +1,105 @@ +# Optimistic Sync + +## Introduction + +In order to provide a syncing execution engine with a (partially-verified) 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*. + +## Mechanisms + +To perform an optimistic sync: + +- The `execute_payload` function MUST return `True` if the execution + engine returns SYNCING or VALID. An INVALID response MUST return `False`. +- The `validate_merge_block` function MUST NOT raise an assertion if both the + `pow_block` and `pow_parent` are unknown to the execution engine. + +In addition to these changes to validation, the consensus engine MUST be able +to ascertain, after import, which blocks returned SYNCING and which returned +VALID. This document will assume consensus engines store the following sets: + +- `valid_roots: Set[Root]`: `hash_tree_root(block)` where + `block.body.execution_payload` is known to be VALID. +- `optimistic_roots: Set[Root]`: `hash_tree_root(block)` where + `block.body.execution_payload` is known to be SYNCING. + +Notably, `optimistic_roots` only includes blocks which have execution enabled +whilst `valid_roots` contains blocks *with or without* execution enabled (i.e., +all blocks). + +A consensus engine MUST be able to retrospectively (i.e., after import) modify +the status of SYNCING blocks to be either VALID or INVALID based upon responses +from an execution engine. I.e., perform the following transitions: + +- SYNCING -> VALID +- SYNCING -> INVALID + +When a block transitions from SYNCING -> VALID, all *ancestors* of the block MUST +also transition from SYNCING -> VALID. + +When a block transitions from SYNCING -> INVALID, all *descendants* of the +block MUST also transition from SYNCING -> INVALID. + +## Fork Choice + +Consensus engines MUST support removing blocks that transition from SYNCING to +INVALID. Specifically, an INVALID block MUST NOT be included in the canonical +chain and the weights from INVALID blocks MUST NOT be applied to any VALID or +SYNCING ancestors. + +## Validator assignments + +An entirely optimistically synced 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. + +Let `head_block: BeaconBlock` be the result of calling of the fork choice +algorithm at the time of block production. Let `justified_block: BeaconBlock` +be the latest current justified ancestor ancestor of the `head_block`. + +```python +def is_optimistic(block: BeaconBlock) -> bool: + hash_tree_root(block) in optimistic_roots +``` + +```python +def latest_valid_ancestor(block: BeaconBlock) -> Optional[BeaconBlock]: + while block.parent_root != Root(): + if not is_optimistic(block): + return block + block = get_block(block.parent_root) + + return None +``` + +Let a node which returns `is_optimistic(head) == True` be an *optimistic +node*. Let a validator on an optimistic node be an *optimistic validator*. + +### Block production + +A optimistic validator MUST NOT produce a block (i.e., sign across the +`DOMAIN_BEACON_PROPOSER` domain), unless one of the follow exceptions are met: + +#### Exception 1. + +If the justified block is fully verified (i.e., `not +is_optimistic(justified_block)`, the validator MUST produce a block upon +`latest_valid_ancestor(head)`. + +### Attesting + +An optimistic validator MUST NOT participate in attestation (i.e., sign across the +`DOMAIN_BEACON_ATTESTER`, `DOMAIN_SELECTION_PROOF` or +`DOMAIN_AGGREGATE_AND_PROOF` domains), unless one of the follow exceptions are +met: + +#### Exception + +#### Exception 1. + +If a validator *does not* have an optimistic head (i.e., `not +is_optimistic(head_block)`), the node is *fully synced*. +The validator may produce an attestation. From 38fffd3e2f5d7f32d85e0b317fc5a0202534b2e9 Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Mon, 13 Dec 2021 17:09:09 +1100 Subject: [PATCH 02/75] Tidy, finish duties --- sync/optimistic.md | 105 ++++++++++++++++++++++++++++++++------------- 1 file changed, 74 insertions(+), 31 deletions(-) diff --git a/sync/optimistic.md b/sync/optimistic.md index b3220b96f..5837a1349 100644 --- a/sync/optimistic.md +++ b/sync/optimistic.md @@ -2,52 +2,71 @@ ## Introduction -In order to provide a syncing execution engine with a (partially-verified) 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*. +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*. ## Mechanisms To perform an optimistic sync: - The `execute_payload` function MUST return `True` if the execution - engine returns SYNCING or VALID. An INVALID response MUST return `False`. + engine returns `SYNCING` or `VALID`. An `INVALID` response MUST return `False`. - The `validate_merge_block` function MUST NOT raise an assertion if both the `pow_block` and `pow_parent` are unknown to the execution engine. In addition to these changes to validation, the consensus engine MUST be able -to ascertain, after import, which blocks returned SYNCING and which returned -VALID. This document will assume consensus engines store the following sets: +to ascertain, after import, which blocks returned `SYNCING` and which returned +`VALID`. This document will assume that consensus engines store the following +sets: - `valid_roots: Set[Root]`: `hash_tree_root(block)` where - `block.body.execution_payload` is known to be VALID. + `block.body.execution_payload` is known to be `VALID`. - `optimistic_roots: Set[Root]`: `hash_tree_root(block)` where - `block.body.execution_payload` is known to be SYNCING. + `block.body.execution_payload` is known to be `SYNCING`. -Notably, `optimistic_roots` only includes blocks which have execution enabled -whilst `valid_roots` contains blocks *with or without* execution enabled (i.e., -all blocks). +Notably, `optimistic_roots` only includes blocks which have execution enabled. +On the other hand, `valid_roots` contains blocks *with or without* execution +enabled (i.e., all blocks). A consensus engine MUST be able to retrospectively (i.e., after import) modify -the status of SYNCING blocks to be either VALID or INVALID based upon responses +the status of `SYNCING` blocks to be either `VALID` or `INVALID` based upon responses from an execution engine. I.e., perform the following transitions: -- SYNCING -> VALID -- SYNCING -> INVALID +- `SYNCING` -> `VALID` +- `SYNCING` -> `INVALID` -When a block transitions from SYNCING -> VALID, all *ancestors* of the block MUST -also transition from SYNCING -> VALID. +When a block transitions from `SYNCING` -> `VALID`, all *ancestors* of the block MUST +also transition from `SYNCING` -> `VALID`. -When a block transitions from SYNCING -> INVALID, all *descendants* of the -block MUST also transition from SYNCING -> INVALID. +When a block transitions from `SYNCING` -> `INVALID`, all *descendants* of the +block MUST also transition from `SYNCING` -> `INVALID`. + +### Execution Engine Errors + +A consensus engine MUST NOT interpret an error or failure to respond to a +message as a `SYNCING`, `VALID` or `INVALID` response. A consensus engine MAY +queue such a message for later processing. + +## Merge Transition + +To protect against attacks during the transition from empty `ExecutionPayload` +values to those which include the terminal PoW block, a consensus engine MUST +NOT perform an optimistic sync unless the `finalized_checkpoint.root` of the head +block references a block for which +`is_execution_enabled(head_state, head_block.body) == True`. + +TODO: this restriction is very onerous, however it is the best known remedy for +the attack described in https://hackmd.io/S5ZEVhsNTqqfJirTAkBPlg I hope we can +do better. ## Fork Choice -Consensus engines MUST support removing blocks that transition from SYNCING to -INVALID. Specifically, an INVALID block MUST NOT be included in the canonical -chain and the weights from INVALID blocks MUST NOT be applied to any VALID or -SYNCING ancestors. +Consensus engines MUST support removing from fork choice blocks that transition +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. ## Validator assignments @@ -56,6 +75,8 @@ 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. +### Helpers + Let `head_block: BeaconBlock` be the result of calling of the fork choice algorithm at the time of block production. Let `justified_block: BeaconBlock` be the latest current justified ancestor ancestor of the `head_block`. @@ -75,8 +96,18 @@ def latest_valid_ancestor(block: BeaconBlock) -> Optional[BeaconBlock]: return None ``` -Let a node which returns `is_optimistic(head) == True` be an *optimistic -node*. Let a validator on an optimistic node be an *optimistic validator*. +```python +def recent_valid_ancestor(block: BeaconBlock) -> Optional[BeaconBlock]: + ancestor = latest_valid_ancestor(block) + + if ancestor is None or ancestor.slot + SLOTS_PER_EPOCH >= block.slot: + return None + + return ancestor +``` + +Let only a node which returns `is_optimistic(head) == True` be an *optimistic +node*. Let only a validator on an optimistic node be an *optimistic validator*. ### Block production @@ -86,9 +117,11 @@ A optimistic validator MUST NOT produce a block (i.e., sign across the #### Exception 1. If the justified block is fully verified (i.e., `not -is_optimistic(justified_block)`, the validator MUST produce a block upon +is_optimistic(justified_block)`, the validator MAY produce a block upon `latest_valid_ancestor(head)`. +If the latest valid ancestor is `None`, the validator MUST NOT produce a block. + ### Attesting An optimistic validator MUST NOT participate in attestation (i.e., sign across the @@ -96,10 +129,20 @@ An optimistic validator MUST NOT participate in attestation (i.e., sign across t `DOMAIN_AGGREGATE_AND_PROOF` domains), unless one of the follow exceptions are met: -#### Exception - #### Exception 1. -If a validator *does not* have an optimistic head (i.e., `not -is_optimistic(head_block)`), the node is *fully synced*. -The validator may produce an attestation. +If the justified block is fully verified (i.e., `not +is_optimistic(justified_block)`, the validator MAY sign across the following +domains: + +- `DOMAIN_BEACON_ATTESTER`: where `attestation.data.beacon_block_root == hash_tree_root(recent_valid_ancestor(head))`. +- `DOMAIN_AGGREGATE_AND_PROOF` and `DOMAIN_SELECTION_PROOF`: where `aggregate.message.aggregate.data.beacon_block_root == hash_tree_root(recent_valid_ancestor(head))` + +If the recent valid ancestor is `None`, the validator MUST NOT participate in +attestation. + +### 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). From 891882307120eff346a8768aab3f3ce6d2497cd3 Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Mon, 13 Dec 2021 19:02:45 +1100 Subject: [PATCH 03/75] Start adding p2p components --- sync/optimistic.md | 53 ++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 46 insertions(+), 7 deletions(-) diff --git a/sync/optimistic.md b/sync/optimistic.md index 5837a1349..0089abbd3 100644 --- a/sync/optimistic.md +++ b/sync/optimistic.md @@ -68,13 +68,6 @@ 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. -## Validator assignments - -An entirely optimistically synced 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. - ### Helpers Let `head_block: BeaconBlock` be the result of calling of the fork choice @@ -109,6 +102,17 @@ def recent_valid_ancestor(block: BeaconBlock) -> Optional[BeaconBlock]: Let only a node which returns `is_optimistic(head) == True` be an *optimistic 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. + +## Validator assignments + +An entirely optimistically synced 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. + ### Block production A optimistic validator MUST NOT produce a block (i.e., sign across the @@ -146,3 +150,38 @@ attestation. 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). + +## P2P Networking + +### Topics and Messages on Gossipsub + +#### `beacon_block` + +An optimistic validator MUST subscribe to the `beacon_block` topic. Propagation +validation conditions are modified as such: + +Do not apply the existing condition: + +- [REJECT] The block's parent (defined by block.parent_root) passes validation. + +Instead, apply the new condition: + +- [REJECT] The block's parent (defined by block.parent_root) passes validation, + *and* `block.parent root not in optimistic_roots`. + +#### `beacon_aggregate_and_proof` + +An optimistic validator MUST NOT subscribe to the `beacon_aggregate_and_proof` +topic. + +#### `voluntary_exit` + +An optimistic validator MUST NOT subscribe to the `voluntary_exit` topic. + +#### `proposer_slashing` + +An optimistic validator MUST NOT subscribe to the `proposer_slashing` topic. + +#### `attester_slashing` + +An optimistic validator MUST NOT subscribe to the `attester_slashing` topic. From 7f5b7d1535fa36708472f8a2811c3a760a65dc06 Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Mon, 13 Dec 2021 19:03:35 +1100 Subject: [PATCH 04/75] Remove attesting during opt sync --- sync/optimistic.md | 15 +-------------- 1 file changed, 1 insertion(+), 14 deletions(-) diff --git a/sync/optimistic.md b/sync/optimistic.md index 0089abbd3..0b8036b21 100644 --- a/sync/optimistic.md +++ b/sync/optimistic.md @@ -130,20 +130,7 @@ If the latest valid ancestor is `None`, the validator MUST NOT produce a block. An optimistic validator MUST NOT participate in attestation (i.e., sign across the `DOMAIN_BEACON_ATTESTER`, `DOMAIN_SELECTION_PROOF` or -`DOMAIN_AGGREGATE_AND_PROOF` domains), unless one of the follow exceptions are -met: - -#### Exception 1. - -If the justified block is fully verified (i.e., `not -is_optimistic(justified_block)`, the validator MAY sign across the following -domains: - -- `DOMAIN_BEACON_ATTESTER`: where `attestation.data.beacon_block_root == hash_tree_root(recent_valid_ancestor(head))`. -- `DOMAIN_AGGREGATE_AND_PROOF` and `DOMAIN_SELECTION_PROOF`: where `aggregate.message.aggregate.data.beacon_block_root == hash_tree_root(recent_valid_ancestor(head))` - -If the recent valid ancestor is `None`, the validator MUST NOT participate in -attestation. +`DOMAIN_AGGREGATE_AND_PROOF` domains). ### Participating in Sync Committees From 1a89d167da53de19f1f02fd95c5b49f143157c50 Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Mon, 13 Dec 2021 19:15:53 +1100 Subject: [PATCH 05/75] Remove recent valid ancestor --- sync/optimistic.md | 37 ++++++++++++------------------------- 1 file changed, 12 insertions(+), 25 deletions(-) diff --git a/sync/optimistic.md b/sync/optimistic.md index 0b8036b21..a59c0ef87 100644 --- a/sync/optimistic.md +++ b/sync/optimistic.md @@ -76,27 +76,15 @@ be the latest current justified ancestor ancestor of the `head_block`. ```python def is_optimistic(block: BeaconBlock) -> bool: - hash_tree_root(block) in optimistic_roots + hash_tree_root(block) in optimistic_roots ``` ```python -def latest_valid_ancestor(block: BeaconBlock) -> Optional[BeaconBlock]: - while block.parent_root != Root(): - if not is_optimistic(block): - return block - block = get_block(block.parent_root) - - return None -``` - -```python -def recent_valid_ancestor(block: BeaconBlock) -> Optional[BeaconBlock]: - ancestor = latest_valid_ancestor(block) - - if ancestor is None or ancestor.slot + SLOTS_PER_EPOCH >= block.slot: - return None - - return ancestor +def latest_valid_ancestor(block: BeaconBlock) -> BeaconBlock: + while block.parent_root != Root(): + if not is_optimistic(block) or block.parent_root == Root(): + return block + block = get_block(block.parent_root) ``` Let only a node which returns `is_optimistic(head) == True` be an *optimistic @@ -113,19 +101,18 @@ 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. -### Block production +### Block Production A optimistic validator MUST NOT produce a block (i.e., sign across the -`DOMAIN_BEACON_PROPOSER` domain), unless one of the follow exceptions are met: +`DOMAIN_BEACON_PROPOSER` domain), unless one of the following exceptions are +met: -#### Exception 1. +#### Block Production Exception 1. If the justified block is fully verified (i.e., `not is_optimistic(justified_block)`, the validator MAY produce a block upon `latest_valid_ancestor(head)`. -If the latest valid ancestor is `None`, the validator MUST NOT produce a block. - ### Attesting An optimistic validator MUST NOT participate in attestation (i.e., sign across the @@ -140,11 +127,11 @@ An optimistic validator MUST NOT participate in sync committees (i.e., sign acro ## P2P Networking -### Topics and Messages on Gossipsub +### The Gossip Domain (gossipsub) #### `beacon_block` -An optimistic validator MUST subscribe to the `beacon_block` topic. Propagation +An optimistic validator MAY subscribe to the `beacon_block` topic. Propagation validation conditions are modified as such: Do not apply the existing condition: From 5a5f980fca98c54f3b83b84130d9c9644b7c7b41 Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Mon, 13 Dec 2021 19:25:33 +1100 Subject: [PATCH 06/75] Add RPC responses --- sync/optimistic.md | 42 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) diff --git a/sync/optimistic.md b/sync/optimistic.md index a59c0ef87..149fdd391 100644 --- a/sync/optimistic.md +++ b/sync/optimistic.md @@ -159,3 +159,45 @@ An optimistic validator MUST NOT subscribe to the `proposer_slashing` topic. #### `attester_slashing` An optimistic validator MUST NOT subscribe to the `attester_slashing` topic. + +#### `beacon_attestation_{subnet_id}` + +An optimistic validator MUST NOT subscribe to the +`beacon_attestation_{subnet_id}` (attestation subnets) topics. + +#### `sync_committee_contribution_and_proof` + +An optimistic validator MUST NOT subscribe to the +`sync_committee_contribution_and_proof` topic. + +#### `sync_committee_{subnet_id}` + +An optimistic validator MUST NOT subscribe to the `sync_committee_{subnet_id}` +(sync committee subnets) topics. + +### The Req/Resp Domain + +#### BeaconBlocksByRange (v1, v2) + +Consensus engines MUST NOT include any block in a response where +`is_optimistic(block) == False`. + +#### BeaconBlocksByRoot (v1, v2) + +Consensus engines MUST NOT include any block in a response where +`is_optimistic(block) == False`. + +#### Status + +An optimistic node MUST use the `latest_valid_ancestor(head)` block to form +responses, rather than the head block. Specifically, an optimistic node must +form a `Status` message as so: + +The fields are, as seen by the client at the time of sending the message: + +- `fork_digest`: As previously defined. +- `finalized_root`: `state.finalized_checkpoint.root` for the state corresponding to the latest valid ancestor block + (Note this defaults to `Root(b'\x00' * 32)` for the genesis finalized checkpoint). +- `finalized_epoch`: `state.finalized_checkpoint.epoch` for the state corresponding to the latest valid ancestor block. +- `head_root`: The `hash_tree_root` root of the current latest valid ancestor block (`BeaconBlock`). +- `head_slot`: The slot of the block corresponding to `latest_valid_ancestor(head)`. From 2c62ed3b77bf08e71f8345d35657c99a114e8c88 Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Tue, 14 Dec 2021 14:53:04 +1100 Subject: [PATCH 07/75] Flip bool --- sync/optimistic.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sync/optimistic.md b/sync/optimistic.md index 149fdd391..be5626bf1 100644 --- a/sync/optimistic.md +++ b/sync/optimistic.md @@ -185,7 +185,7 @@ Consensus engines MUST NOT include any block in a response where #### BeaconBlocksByRoot (v1, v2) Consensus engines MUST NOT include any block in a response where -`is_optimistic(block) == False`. +`is_optimistic(block) == True`. #### Status From 497b5f8cbb1552f694f1ccf133e68beb87837b42 Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Tue, 14 Dec 2021 15:44:15 +1100 Subject: [PATCH 08/75] Add EE assumptions --- sync/optimistic.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/sync/optimistic.md b/sync/optimistic.md index be5626bf1..63c060009 100644 --- a/sync/optimistic.md +++ b/sync/optimistic.md @@ -49,6 +49,15 @@ A consensus engine MUST NOT interpret an error or failure to respond to a message as a `SYNCING`, `VALID` or `INVALID` response. A consensus engine MAY queue such a message for later processing. +### 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 +unknown). Specifically, `SYNCING` responses should be fork-specific; the search +for a block on one chain MUST NOT trigger a `SYNCING` response for another +chain. + ## Merge Transition To protect against attacks during the transition from empty `ExecutionPayload` From 0ad6025b488ff690e87c270a061cd3d736de76e6 Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Tue, 14 Dec 2021 15:51:02 +1100 Subject: [PATCH 09/75] Remove valid roots set --- sync/optimistic.md | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/sync/optimistic.md b/sync/optimistic.md index 63c060009..7092a69ee 100644 --- a/sync/optimistic.md +++ b/sync/optimistic.md @@ -13,23 +13,16 @@ To perform an optimistic sync: - The `execute_payload` function MUST return `True` if the execution engine returns `SYNCING` or `VALID`. An `INVALID` response MUST return `False`. -- The `validate_merge_block` function MUST NOT raise an assertion if both the - `pow_block` and `pow_parent` are unknown to the execution engine. +- The `validate_merge_block` function MUST NOT raise an assertion if both the `pow_block` and `pow_parent` are unknown to the execution engine. In addition to these changes to validation, the consensus engine MUST be able to ascertain, after import, which blocks returned `SYNCING` and which returned `VALID`. This document will assume that consensus engines store the following -sets: +set: -- `valid_roots: Set[Root]`: `hash_tree_root(block)` where - `block.body.execution_payload` is known to be `VALID`. - `optimistic_roots: Set[Root]`: `hash_tree_root(block)` where `block.body.execution_payload` is known to be `SYNCING`. -Notably, `optimistic_roots` only includes blocks which have execution enabled. -On the other hand, `valid_roots` contains blocks *with or without* execution -enabled (i.e., all blocks). - A consensus engine MUST be able to retrospectively (i.e., after import) modify the status of `SYNCING` blocks to be either `VALID` or `INVALID` based upon responses from an execution engine. I.e., perform the following transitions: From 3b67c334e4903092bdaa68bee62479f5eb000dd1 Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Tue, 14 Dec 2021 15:51:15 +1100 Subject: [PATCH 10/75] Add note about removal from optimistic_roots --- sync/optimistic.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/sync/optimistic.md b/sync/optimistic.md index 7092a69ee..3abc5c1c6 100644 --- a/sync/optimistic.md +++ b/sync/optimistic.md @@ -36,8 +36,10 @@ also transition from `SYNCING` -> `VALID`. When a block transitions from `SYNCING` -> `INVALID`, all *descendants* of the block MUST also transition from `SYNCING` -> `INVALID`. -### Execution Engine Errors +When a node transitions from the `SYNCING` state is is removed from the set of +`optimistic_roots`. +### Execution Engine Errors A consensus engine MUST NOT interpret an error or failure to respond to a message as a `SYNCING`, `VALID` or `INVALID` response. A consensus engine MAY queue such a message for later processing. From fb520a83563570f2dddfb2cf0f9d0a1a00d424aa Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Tue, 14 Dec 2021 15:51:26 +1100 Subject: [PATCH 11/75] Condense gossip topics --- sync/optimistic.md | 39 +++++++++++---------------------------- 1 file changed, 11 insertions(+), 28 deletions(-) diff --git a/sync/optimistic.md b/sync/optimistic.md index 3abc5c1c6..70319503d 100644 --- a/sync/optimistic.md +++ b/sync/optimistic.md @@ -147,37 +147,20 @@ Instead, apply the new condition: - [REJECT] The block's parent (defined by block.parent_root) passes validation, *and* `block.parent root not in optimistic_roots`. -#### `beacon_aggregate_and_proof` +#### Other Topics -An optimistic validator MUST NOT subscribe to the `beacon_aggregate_and_proof` -topic. +An optimistic node MUST NOT subscribe to the following topics: -#### `voluntary_exit` +- `beacon_aggregate_and_proof` +- `voluntary_exit` +- `proposer_slashing` +- `attester_slashing` +- `beacon_attestation_{subnet_id}` +- `sync_committee_contribution_and_proof` +- `sync_committee_{subnet_id}` -An optimistic validator MUST NOT subscribe to the `voluntary_exit` topic. - -#### `proposer_slashing` - -An optimistic validator MUST NOT subscribe to the `proposer_slashing` topic. - -#### `attester_slashing` - -An optimistic validator MUST NOT subscribe to the `attester_slashing` topic. - -#### `beacon_attestation_{subnet_id}` - -An optimistic validator MUST NOT subscribe to the -`beacon_attestation_{subnet_id}` (attestation subnets) topics. - -#### `sync_committee_contribution_and_proof` - -An optimistic validator MUST NOT subscribe to the -`sync_committee_contribution_and_proof` topic. - -#### `sync_committee_{subnet_id}` - -An optimistic validator MUST NOT subscribe to the `sync_committee_{subnet_id}` -(sync committee subnets) topics. +Once the node ceases to be optimistic, it MAY re-subscribe to the aformentioned +topics. ### The Req/Resp Domain From d1851dce21f7fb89eb65f248eca477d44f41598e Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Tue, 14 Dec 2021 15:53:03 +1100 Subject: [PATCH 12/75] Tidy tab formatting --- sync/optimistic.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sync/optimistic.md b/sync/optimistic.md index 70319503d..edb54ab27 100644 --- a/sync/optimistic.md +++ b/sync/optimistic.md @@ -86,8 +86,8 @@ def is_optimistic(block: BeaconBlock) -> bool: ```python def latest_valid_ancestor(block: BeaconBlock) -> BeaconBlock: while block.parent_root != Root(): - if not is_optimistic(block) or block.parent_root == Root(): - return block + if not is_optimistic(block) or block.parent_root == Root(): + return block block = get_block(block.parent_root) ``` From 4c4ffe71b4c20634ddeb238fe50e304fb1b239f1 Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Tue, 14 Dec 2021 15:54:33 +1100 Subject: [PATCH 13/75] Fix latest_valid_ancestor --- sync/optimistic.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sync/optimistic.md b/sync/optimistic.md index edb54ab27..ccae7d093 100644 --- a/sync/optimistic.md +++ b/sync/optimistic.md @@ -85,7 +85,7 @@ def is_optimistic(block: BeaconBlock) -> bool: ```python def latest_valid_ancestor(block: BeaconBlock) -> BeaconBlock: - while block.parent_root != Root(): + while True: if not is_optimistic(block) or block.parent_root == Root(): return block block = get_block(block.parent_root) From 3f6e5b9c3ad5c453bcb7ef19f342801751f92c58 Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Tue, 14 Dec 2021 16:10:19 +1100 Subject: [PATCH 14/75] Add checkpoint sync --- sync/optimistic.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/sync/optimistic.md b/sync/optimistic.md index ccae7d093..07293bde0 100644 --- a/sync/optimistic.md +++ b/sync/optimistic.md @@ -72,6 +72,11 @@ 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. +## Checkpoint Sync (Weak Subjectivity Sync) + +A consensus engine MAY assume that the `ExecutionPayload` of a block used for +checkpoint sync is `VALID`. + ### Helpers Let `head_block: BeaconBlock` be the result of calling of the fork choice From ffc2c405c41396528eac9b8fa6ed528773655ff6 Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Tue, 14 Dec 2021 16:11:40 +1100 Subject: [PATCH 15/75] Add section about API --- sync/optimistic.md | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/sync/optimistic.md b/sync/optimistic.md index 07293bde0..c67855710 100644 --- a/sync/optimistic.md +++ b/sync/optimistic.md @@ -193,3 +193,20 @@ The fields are, as seen by the client at the time of sending the message: - `finalized_epoch`: `state.finalized_checkpoint.epoch` for the state corresponding to the latest valid ancestor block. - `head_root`: The `hash_tree_root` root of the current latest valid ancestor block (`BeaconBlock`). - `head_slot`: The slot of the block corresponding to `latest_valid_ancestor(head)`. + +## Ethereum Beacon APIs + +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. + +When information about an optimistic block is requested, the consensus engine: + +- MUST NOT return a "success"-type response (e.g., 2xx). +- MAY return an "empty"-type response (e.g., 404). +- MAY return a "beacon node is currently syncing"-type response (e.g., 503). + +When `is_optimistic(head) == True`, the consensus engine: + +- MAY substitute the head block with `latest_valid_ancestor(block)`. +- MAY return a "beacon node is currently syncing"-type response (e.g., 503). From 9d7d4d086939ccc68ef8619a3cd66e2df4fbe891 Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Tue, 14 Dec 2021 16:21:23 +1100 Subject: [PATCH 16/75] Remove validate_merge_block check --- sync/optimistic.md | 1 - 1 file changed, 1 deletion(-) diff --git a/sync/optimistic.md b/sync/optimistic.md index c67855710..4b0c381a7 100644 --- a/sync/optimistic.md +++ b/sync/optimistic.md @@ -13,7 +13,6 @@ To perform an optimistic sync: - The `execute_payload` function MUST return `True` if the execution engine returns `SYNCING` or `VALID`. An `INVALID` response MUST return `False`. -- The `validate_merge_block` function MUST NOT raise an assertion if both the `pow_block` and `pow_parent` are unknown to the execution engine. In addition to these changes to validation, the consensus engine MUST be able to ascertain, after import, which blocks returned `SYNCING` and which returned From 4f1d8152d5b0d5a3eed26435bbe036c153ad5bdb Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Tue, 14 Dec 2021 16:21:30 +1100 Subject: [PATCH 17/75] Add note about full verification --- sync/optimistic.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/sync/optimistic.md b/sync/optimistic.md index 4b0c381a7..696a954ac 100644 --- a/sync/optimistic.md +++ b/sync/optimistic.md @@ -22,6 +22,11 @@ set: - `optimistic_roots: Set[Root]`: `hash_tree_root(block)` where `block.body.execution_payload` is known to be `SYNCING`. +Notably, blocks included in `optimistic_roots` have passed all verifications +included in `process_block` (noting the modifications to the +`execute_payload`). I.e., the blocks are fully verified but awaiting execution +of the `ExecutionPayload`. + A consensus engine MUST be able to retrospectively (i.e., after import) modify the status of `SYNCING` blocks to be either `VALID` or `INVALID` based upon responses from an execution engine. I.e., perform the following transitions: From eb32e141d29a18208b0e0f9b2447f17416934f27 Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Tue, 14 Dec 2021 16:22:52 +1100 Subject: [PATCH 18/75] Fix typo --- sync/optimistic.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sync/optimistic.md b/sync/optimistic.md index 696a954ac..37e7d4447 100644 --- a/sync/optimistic.md +++ b/sync/optimistic.md @@ -40,7 +40,7 @@ also transition from `SYNCING` -> `VALID`. When a block transitions from `SYNCING` -> `INVALID`, all *descendants* of the block MUST also transition from `SYNCING` -> `INVALID`. -When a node transitions from the `SYNCING` state is is removed from the set of +When a node transitions from the `SYNCING` state it is removed from the set of `optimistic_roots`. ### Execution Engine Errors From 084255465da0c96e7cb94e3c2d1ac3a4a9d02652 Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Tue, 14 Dec 2021 16:42:35 +1100 Subject: [PATCH 19/75] Add section about re-orgs --- sync/optimistic.md | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/sync/optimistic.md b/sync/optimistic.md index 37e7d4447..49a5b3c88 100644 --- a/sync/optimistic.md +++ b/sync/optimistic.md @@ -57,6 +57,16 @@ unknown). Specifically, `SYNCING` responses should be fork-specific; the search for a block on one chain MUST NOT trigger a `SYNCING` response for another chain. +### 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. + ## Merge Transition To protect against attacks during the transition from empty `ExecutionPayload` From d9a0d16cc682e88e052c50be2bf712c54ee0b643 Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Tue, 14 Dec 2021 16:46:31 +1100 Subject: [PATCH 20/75] Tidy --- sync/optimistic.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/sync/optimistic.md b/sync/optimistic.md index 49a5b3c88..fa04d5da7 100644 --- a/sync/optimistic.md +++ b/sync/optimistic.md @@ -75,9 +75,9 @@ NOT perform an optimistic sync unless the `finalized_checkpoint.root` of the hea block references a block for which `is_execution_enabled(head_state, head_block.body) == True`. -TODO: this restriction is very onerous, however it is the best known remedy for -the attack described in https://hackmd.io/S5ZEVhsNTqqfJirTAkBPlg I hope we can -do better. +> TODO: this restriction is very onerous, however it is the best known remedy for +> the attack described in https://hackmd.io/S5ZEVhsNTqqfJirTAkBPlg I hope we can +> do better. ## Fork Choice From 538cc816814f1c95b88c7fed0078467101797578 Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Wed, 15 Dec 2021 09:02:39 +1100 Subject: [PATCH 21/75] Flip bool --- sync/optimistic.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sync/optimistic.md b/sync/optimistic.md index fa04d5da7..298d0c276 100644 --- a/sync/optimistic.md +++ b/sync/optimistic.md @@ -186,7 +186,7 @@ topics. #### BeaconBlocksByRange (v1, v2) Consensus engines MUST NOT include any block in a response where -`is_optimistic(block) == False`. +`is_optimistic(block) == True`. #### BeaconBlocksByRoot (v1, v2) From 5c1fcaf3e7bb535113753e78f1a6c738be9dd7b3 Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Sun, 19 Dec 2021 14:34:43 +1100 Subject: [PATCH 22/75] Add qualification for errors --- sync/optimistic.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/sync/optimistic.md b/sync/optimistic.md index 298d0c276..e92647007 100644 --- a/sync/optimistic.md +++ b/sync/optimistic.md @@ -44,9 +44,11 @@ When a node transitions from the `SYNCING` state it is removed from the set of `optimistic_roots`. ### Execution Engine Errors + A consensus engine MUST NOT interpret an error or failure to respond to a -message as a `SYNCING`, `VALID` or `INVALID` response. A consensus engine MAY -queue such a message for later processing. +message as a `SYNCING`, `VALID` or `INVALID` response. A message which receives +and error or no response MUST NOT be permitted to modify the fork choice +`Store`. A consensus engine MAY queue such a message for later processing. ### Assumptions about Execution Engine Behaviour From 2aa4edf933a4be8178db0a49bb6a750e68223486 Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Sun, 19 Dec 2021 14:44:21 +1100 Subject: [PATCH 23/75] Move helpers --- sync/optimistic.md | 75 ++++++++++++++++++++++++++++------------------ 1 file changed, 46 insertions(+), 29 deletions(-) diff --git a/sync/optimistic.md b/sync/optimistic.md index e92647007..d633f25ac 100644 --- a/sync/optimistic.md +++ b/sync/optimistic.md @@ -7,14 +7,57 @@ 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*. +## Constants + +|Name|Value|Unit +|---|---|---| +|`SAFE_SLOTS_TO_IMPORT_OPTIMISTICALLY`| `64` | slots + +## Helpers + +Let `head_block: BeaconBlock` be the result of calling of the fork choice +algorithm at the time of block production. Let `justified_block: BeaconBlock` +be the latest current justified ancestor ancestor of the `head_block`. + +```python +def is_optimistic(block: BeaconBlock) -> bool: + hash_tree_root(block) in optimistic_roots +``` + +```python +def latest_valid_ancestor(block: BeaconBlock) -> BeaconBlock: + while True: + if not is_optimistic(block) or block.parent_root == Root(): + return block + block = get_block(block.parent_root) +``` + +```python +def is_execution_block(block: BeaconBlock) -> BeaconBlock: + block.execution_payload != ExecutionPayload() +``` + +Let only a node which returns `is_optimistic(head) == True` be an *optimistic +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. + ## Mechanisms -To perform an optimistic sync: +## When to optimistically import blocks + +TODO + +## How to optimistically import blocks + +To optimistically import a block: - The `execute_payload` function MUST return `True` if the execution engine returns `SYNCING` or `VALID`. An `INVALID` response MUST return `False`. -In addition to these changes to validation, the consensus engine MUST be able +In addition to this change to validation, the consensus engine MUST be able to ascertain, after import, which blocks returned `SYNCING` and which returned `VALID`. This document will assume that consensus engines store the following set: @@ -75,7 +118,7 @@ To protect against attacks during the transition from empty `ExecutionPayload` values to those which include the terminal PoW block, a consensus engine MUST NOT perform an optimistic sync unless the `finalized_checkpoint.root` of the head block references a block for which -`is_execution_enabled(head_state, head_block.body) == True`. +`is_execution_block(head_block) == True`. > TODO: this restriction is very onerous, however it is the best known remedy for > the attack described in https://hackmd.io/S5ZEVhsNTqqfJirTAkBPlg I hope we can @@ -93,32 +136,6 @@ point MUST NOT be included in the canonical chain and the weights from those A consensus engine MAY assume that the `ExecutionPayload` of a block used for checkpoint sync is `VALID`. -### Helpers - -Let `head_block: BeaconBlock` be the result of calling of the fork choice -algorithm at the time of block production. Let `justified_block: BeaconBlock` -be the latest current justified ancestor ancestor of the `head_block`. - -```python -def is_optimistic(block: BeaconBlock) -> bool: - hash_tree_root(block) in optimistic_roots -``` - -```python -def latest_valid_ancestor(block: BeaconBlock) -> BeaconBlock: - while True: - if not is_optimistic(block) or block.parent_root == Root(): - return block - block = get_block(block.parent_root) -``` - -Let only a node which returns `is_optimistic(head) == True` be an *optimistic -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. - ## Validator assignments An entirely optimistically synced node is *not* a full node. It is unable to From e49685eed5cc2c4f95b83798a6e90650f60d837b Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Sun, 19 Dec 2021 14:56:46 +1100 Subject: [PATCH 24/75] Add section for enabling opt sync --- sync/optimistic.md | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/sync/optimistic.md b/sync/optimistic.md index d633f25ac..ff249dcbf 100644 --- a/sync/optimistic.md +++ b/sync/optimistic.md @@ -37,6 +37,11 @@ def is_execution_block(block: BeaconBlock) -> BeaconBlock: block.execution_payload != ExecutionPayload() ``` +```python +def should_optimistically_import_block(current_slot: Slot, block: BeaconBlock) -> bool: + block.slot + SAFE_SLOTS_TO_IMPORT_OPTIMISTICALLY <= current_slot +``` + Let only a node which returns `is_optimistic(head) == True` be an *optimistic node*. Let only a validator on an optimistic node be an *optimistic validator*. @@ -48,7 +53,13 @@ behaviours without regard for optimistic sync. ## When to optimistically import blocks -TODO +A block MUST NOT be optimistically imported, unless either of the following +conditions are met: + +1. The justified checkpoint has execution enabled. I.e., + `is_execution_block(get_block(get_state(head_block).finalized_checkpoint.root))` +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. I.e., `should_optimistically_import_block(current_slot) == True`. ## How to optimistically import blocks From ffba24f03edf3a7a896fd96adb5d398696e17d9f Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Mon, 20 Dec 2021 10:15:51 +1100 Subject: [PATCH 25/75] Add failure recovery --- sync/optimistic.md | 37 ++++++++++++++++++++++++++++++++++++- 1 file changed, 36 insertions(+), 1 deletion(-) diff --git a/sync/optimistic.md b/sync/optimistic.md index ff249dcbf..65391332c 100644 --- a/sync/optimistic.md +++ b/sync/optimistic.md @@ -11,7 +11,10 @@ blocks without verifying the execution payloads. This partial sync is called an |Name|Value|Unit |---|---|---| -|`SAFE_SLOTS_TO_IMPORT_OPTIMISTICALLY`| `64` | slots +|`SAFE_SLOTS_TO_IMPORT_OPTIMISTICALLY`| `96` | slots + +*Note: the `SAFE_SLOTS_TO_IMPORT_OPTIMISTICALLY` must be user-configurable. See +[Failure Recovery](#failure-recovery). ## Helpers @@ -123,6 +126,38 @@ If the justified checkpoint transitions from `SYNCING` -> `INVALID`, a consensus engine MAY choose to alert the user and force the application to exit. +## Failure Recovery + +During the merge transition it is possible for an attacker to craft a +`BeaconBlock` with an execution payload that references an +eternally-unavailable `body.execution_payload.parent_hash` value. In some 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 child of the head (due to the invalid chain of payloads) and the node +is unable to fork around the head (due to the justification of the malicious +chain). + +The fork choice poisoning attack is temporary for an individual node, assuming +there exists an honest chain. An honest chain which justifies a higher epoch +than the malicious chain will take precedence and revive any poisoned store +once imported. + +The `SAFE_SLOTS_TO_IMPORT_OPTIMISTICALLY` parameter assumes that the network +will justify a honest chain within some number of slots. With this assumption, +it is therefore "safe" to optimistically import transition blocks during the +sync process. Since there is an assumption that an honest chain with a higher +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: + +- `--safe_slots_to_import_optimistically`: modifies the + `SAFE_SLOTS_TO_IMPORT_OPTIMISTICALLY`. + ## Merge Transition To protect against attacks during the transition from empty `ExecutionPayload` From 26e934b1e1d3fd2e3f4d92cd273626eabf344d14 Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Mon, 20 Dec 2021 10:16:09 +1100 Subject: [PATCH 26/75] Remove merge transition section --- sync/optimistic.md | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/sync/optimistic.md b/sync/optimistic.md index 65391332c..4a89e5118 100644 --- a/sync/optimistic.md +++ b/sync/optimistic.md @@ -158,18 +158,6 @@ disaster recovery: - `--safe_slots_to_import_optimistically`: modifies the `SAFE_SLOTS_TO_IMPORT_OPTIMISTICALLY`. -## Merge Transition - -To protect against attacks during the transition from empty `ExecutionPayload` -values to those which include the terminal PoW block, a consensus engine MUST -NOT perform an optimistic sync unless the `finalized_checkpoint.root` of the head -block references a block for which -`is_execution_block(head_block) == True`. - -> TODO: this restriction is very onerous, however it is the best known remedy for -> the attack described in https://hackmd.io/S5ZEVhsNTqqfJirTAkBPlg I hope we can -> do better. - ## Fork Choice Consensus engines MUST support removing from fork choice blocks that transition From da6cad8767c43b72ee3380b97b4ae3f3356f1255 Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Mon, 20 Dec 2021 10:32:45 +1100 Subject: [PATCH 27/75] Tidy --- sync/optimistic.md | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/sync/optimistic.md b/sync/optimistic.md index 4a89e5118..15f94f636 100644 --- a/sync/optimistic.md +++ b/sync/optimistic.md @@ -14,7 +14,7 @@ blocks without verifying the execution payloads. This partial sync is called an |`SAFE_SLOTS_TO_IMPORT_OPTIMISTICALLY`| `96` | slots *Note: the `SAFE_SLOTS_TO_IMPORT_OPTIMISTICALLY` must be user-configurable. See -[Failure Recovery](#failure-recovery). +[Fork Choice Poisoning](#fork-choice-poisoning).* ## Helpers @@ -64,6 +64,9 @@ conditions are met: 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. I.e., `should_optimistically_import_block(current_slot) == True`. +*See [Fork Choice Poisoning](#fork-choice-poisoning) for the motivations behind +these conditions.* + ## How to optimistically import blocks To optimistically import a block: @@ -126,7 +129,14 @@ If the justified checkpoint transitions from `SYNCING` -> `INVALID`, a consensus engine MAY choose to alert the user and force the application to exit. -## Failure Recovery +## Fork Choice + +Consensus engines MUST support removing from fork choice blocks that transition +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 During the merge transition it is possible for an attacker to craft a `BeaconBlock` with an execution payload that references an @@ -158,13 +168,6 @@ disaster recovery: - `--safe_slots_to_import_optimistically`: modifies the `SAFE_SLOTS_TO_IMPORT_OPTIMISTICALLY`. -## Fork Choice - -Consensus engines MUST support removing from fork choice blocks that transition -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. - ## Checkpoint Sync (Weak Subjectivity Sync) A consensus engine MAY assume that the `ExecutionPayload` of a block used for From 9901cb38f7f5b562d4a46dc1da78d2138a177c8b Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Mon, 20 Dec 2021 10:41:50 +1100 Subject: [PATCH 28/75] Move optimsitic_roots definition --- sync/optimistic.md | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/sync/optimistic.md b/sync/optimistic.md index 15f94f636..6b52b98a2 100644 --- a/sync/optimistic.md +++ b/sync/optimistic.md @@ -22,6 +22,10 @@ Let `head_block: BeaconBlock` be the result of calling of the fork choice algorithm at the time of block production. Let `justified_block: BeaconBlock` be the latest current justified ancestor ancestor of the `head_block`. +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. + ```python def is_optimistic(block: BeaconBlock) -> bool: hash_tree_root(block) in optimistic_roots @@ -74,15 +78,11 @@ To optimistically import a block: - The `execute_payload` function MUST return `True` if the execution engine returns `SYNCING` or `VALID`. An `INVALID` response MUST return `False`. -In addition to this change to validation, the consensus engine MUST be able -to ascertain, after import, which blocks returned `SYNCING` and which returned -`VALID`. This document will assume that consensus engines store the following -set: +In addition to this change to validation, the consensus engine MUST be able to +ascertain, after import, which blocks returned `SYNCING` (`optimistic_roots`) +and which returned `VALID`. -- `optimistic_roots: Set[Root]`: `hash_tree_root(block)` where - `block.body.execution_payload` is known to be `SYNCING`. - -Notably, blocks included in `optimistic_roots` have passed all verifications +Notably, optimistically imported blocks MUST have passed all verifications included in `process_block` (noting the modifications to the `execute_payload`). I.e., the blocks are fully verified but awaiting execution of the `ExecutionPayload`. From 26431b762d92f7e4e813179d0cb11b2386f8e324 Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Mon, 20 Dec 2021 11:12:11 +1100 Subject: [PATCH 29/75] Tidy --- sync/optimistic.md | 42 +++++++++++++++++++++--------------------- 1 file changed, 21 insertions(+), 21 deletions(-) diff --git a/sync/optimistic.md b/sync/optimistic.md index 6b52b98a2..553c3e487 100644 --- a/sync/optimistic.md +++ b/sync/optimistic.md @@ -64,7 +64,7 @@ A block MUST NOT be optimistically imported, unless either of the following conditions are met: 1. The justified checkpoint has execution enabled. I.e., - `is_execution_block(get_block(get_state(head_block).finalized_checkpoint.root))` + `is_execution_block(get_block(get_state(head_block).current_justified_checkpoint.root))` 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. I.e., `should_optimistically_import_block(current_slot) == True`. @@ -79,13 +79,11 @@ To optimistically import a block: engine returns `SYNCING` or `VALID`. An `INVALID` response MUST return `False`. In addition to this change to validation, the consensus engine MUST be able to -ascertain, after import, which blocks returned `SYNCING` (`optimistic_roots`) -and which returned `VALID`. +ascertain, after import, which blocks returned `SYNCING` and which returned +`VALID`. -Notably, optimistically imported blocks MUST have passed all verifications -included in `process_block` (noting the modifications to the -`execute_payload`). I.e., the blocks are fully verified but awaiting execution -of the `ExecutionPayload`. +Optimistically imported blocks MUST pass all verifications included in +`process_block` (withstanding the modifications to `execute_payload`). A consensus engine MUST be able to retrospectively (i.e., after import) modify the status of `SYNCING` blocks to be either `VALID` or `INVALID` based upon responses @@ -94,8 +92,9 @@ from an execution engine. I.e., perform the following transitions: - `SYNCING` -> `VALID` - `SYNCING` -> `INVALID` -When a block transitions from `SYNCING` -> `VALID`, all *ancestors* of the block MUST -also transition from `SYNCING` -> `VALID`. +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". When a block transitions from `SYNCING` -> `INVALID`, all *descendants* of the block MUST also transition from `SYNCING` -> `INVALID`. @@ -105,10 +104,11 @@ When a node transitions from the `SYNCING` state it is removed from the set of ### Execution Engine Errors -A consensus engine MUST NOT interpret an error or failure to respond to a -message as a `SYNCING`, `VALID` or `INVALID` response. A message which receives -and error or no response MUST NOT be permitted to modify the fork choice -`Store`. A consensus engine MAY queue such a message for later processing. +When an execution engine returns an error or fails to respond to a payload +validity request some block, a consensus engine: + +- MUST NOT optimistically import the block. +- MAY queue the block for later processing. ### Assumptions about Execution Engine Behaviour @@ -140,7 +140,7 @@ point MUST NOT be included in the canonical chain and the weights from those During the merge transition it is possible for an attacker to craft a `BeaconBlock` with an execution payload that references an -eternally-unavailable `body.execution_payload.parent_hash` value. In some rare +eternally-unavailable `body.execution_payload.parent_hash` value. 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 @@ -149,14 +149,13 @@ is unable to fork around the head (due to the justification of the malicious chain). The fork choice poisoning attack is temporary for an individual node, assuming -there exists an honest chain. An honest chain which justifies a higher epoch -than the malicious chain will take precedence and revive any poisoned store -once imported. +there exists an honest chain which justifies a higher epoch than the malicious +chain. Such an honest chain will take precedence and revive any poisoned store. The `SAFE_SLOTS_TO_IMPORT_OPTIMISTICALLY` parameter assumes that the network will justify a honest chain within some number of slots. With this assumption, -it is therefore "safe" to optimistically import transition blocks during the -sync process. Since there is an assumption that an honest chain with a higher +it is acceptable to optimistically import transition blocks during the sync +process. Since there is an assumption that an honest chain with a higher justified checkpoint exists, any fork choice poisoning will be short-lived and resolved before that node is required to produce a block. @@ -165,13 +164,14 @@ within `SAFE_SLOTS_TO_IMPORT_OPTIMISTICALLY` slots is dubious. Therefore, clients MUST provide the following command line flag to assist with manual disaster recovery: -- `--safe_slots_to_import_optimistically`: modifies the +- `--safe-slots-to-import-optimistically`: modifies the `SAFE_SLOTS_TO_IMPORT_OPTIMISTICALLY`. ## Checkpoint Sync (Weak Subjectivity Sync) A consensus engine MAY assume that the `ExecutionPayload` of a block used for -checkpoint sync is `VALID`. +checkpoint sync is `VALID` without providing that payload to an execution +engine. ## Validator assignments From a797ae406bbdc29f01fc9533ef230e7f613e21e2 Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Mon, 20 Dec 2021 14:04:33 +1100 Subject: [PATCH 30/75] Add qualification about fc store --- sync/optimistic.md | 1 + 1 file changed, 1 insertion(+) diff --git a/sync/optimistic.md b/sync/optimistic.md index 553c3e487..c1841d2dd 100644 --- a/sync/optimistic.md +++ b/sync/optimistic.md @@ -108,6 +108,7 @@ When an execution engine returns an error or fails to respond to a payload validity request some block, a consensus engine: - MUST NOT optimistically import the block. +- MUST NOT apply the block to the fork choice store. - MAY queue the block for later processing. ### Assumptions about Execution Engine Behaviour From b287f65dc9e9cc4e8988825f0fe6d7776589bab5 Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Mon, 20 Dec 2021 16:21:11 +1100 Subject: [PATCH 31/75] Allow RPC blocks --- sync/optimistic.md | 55 +++++++++++++++++++++++----------------------- 1 file changed, 27 insertions(+), 28 deletions(-) diff --git a/sync/optimistic.md b/sync/optimistic.md index c1841d2dd..b59c5b99b 100644 --- a/sync/optimistic.md +++ b/sync/optimistic.md @@ -240,30 +240,10 @@ topics. ### The Req/Resp Domain -#### BeaconBlocksByRange (v1, v2) - -Consensus engines MUST NOT include any block in a response where -`is_optimistic(block) == True`. - -#### BeaconBlocksByRoot (v1, v2) - -Consensus engines MUST NOT include any block in a response where -`is_optimistic(block) == True`. - -#### Status - -An optimistic node MUST use the `latest_valid_ancestor(head)` block to form -responses, rather than the head block. Specifically, an optimistic node must -form a `Status` message as so: - -The fields are, as seen by the client at the time of sending the message: - -- `fork_digest`: As previously defined. -- `finalized_root`: `state.finalized_checkpoint.root` for the state corresponding to the latest valid ancestor block - (Note this defaults to `Root(b'\x00' * 32)` for the genesis finalized checkpoint). -- `finalized_epoch`: `state.finalized_checkpoint.epoch` for the state corresponding to the latest valid ancestor block. -- `head_root`: The `hash_tree_root` root of the current latest valid ancestor block (`BeaconBlock`). -- `head_slot`: The slot of the block corresponding to `latest_valid_ancestor(head)`. +Non-faulty, optimistic nodes may send blocks which result in an INVALID +response from an execution engine. To prevent network segregation between +optimistic and non-optimistic nodes, transmission of an INVALID payload SHOULD +NOT cause a node to be down-scored or disconnected. ## Ethereum Beacon APIs @@ -271,13 +251,32 @@ 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. +### Helpers + +Let the following response types be defined as any response with the +corresponding HTTP status code: + +- "Success" Response: Status Codes 200-299. +- "Not Found" Response: Status Code 404. +- "Syncing" Response: Status Code 503. + +### Requests for Optimistic Blocks + When information about an optimistic block is requested, the consensus engine: -- MUST NOT return a "success"-type response (e.g., 2xx). -- MAY return an "empty"-type response (e.g., 404). -- MAY return a "beacon node is currently syncing"-type response (e.g., 503). +- MUST NOT respond with success. +- MAY respond with not found. +- MAY respond with syncing. + +### Requests for the Head When `is_optimistic(head) == True`, the consensus engine: - MAY substitute the head block with `latest_valid_ancestor(block)`. -- MAY return a "beacon node is currently syncing"-type response (e.g., 503). +- MAY return syncing. + +### Requests to Validators Endpoints + +When `is_optimistic(head) == True`, the consensus engine: + +MUST respon From 91cad9b19b873b66a260ae47bb20aba903d1a2aa Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Mon, 20 Dec 2021 17:19:32 +1100 Subject: [PATCH 32/75] Improve gossip wording --- sync/optimistic.md | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/sync/optimistic.md b/sync/optimistic.md index b59c5b99b..cf9bc2d54 100644 --- a/sync/optimistic.md +++ b/sync/optimistic.md @@ -220,8 +220,14 @@ Do not apply the existing condition: Instead, apply the new condition: -- [REJECT] The block's parent (defined by block.parent_root) passes validation, - *and* `block.parent root not in optimistic_roots`. +- [IGNORE] The block's parent (defined by block.parent_root) passes validation + except the block.body.execution_payload was deemed INVALID. +- [REJECT] The block's parent (defined by block.parent_root) passes all + validation, excluding verification of the block.body.execution_payload. + +The effect of these modifications is that invalid payloads may be propagated +across the network, but only when contained inside a block that is valid in *all +other aspects*. #### Other Topics From aa9a2967aa398e9c9bddac542644462907c395cf Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Mon, 20 Dec 2021 17:27:02 +1100 Subject: [PATCH 33/75] Clarify API head condition --- sync/optimistic.md | 1 + 1 file changed, 1 insertion(+) diff --git a/sync/optimistic.md b/sync/optimistic.md index cf9bc2d54..e36378119 100644 --- a/sync/optimistic.md +++ b/sync/optimistic.md @@ -278,6 +278,7 @@ When information about an optimistic block is requested, the consensus engine: When `is_optimistic(head) == True`, the consensus engine: +- MUST NOT return `head`. - MAY substitute the head block with `latest_valid_ancestor(block)`. - MAY return syncing. From 7837dc74bff92576c868d3c4f97bb921102d5dd6 Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Mon, 20 Dec 2021 17:46:42 +1100 Subject: [PATCH 34/75] Tidy, add validator endpoints --- sync/optimistic.md | 53 ++++++++++++++++++++++++---------------------- 1 file changed, 28 insertions(+), 25 deletions(-) diff --git a/sync/optimistic.md b/sync/optimistic.md index e36378119..fcb6d981a 100644 --- a/sync/optimistic.md +++ b/sync/optimistic.md @@ -19,8 +19,10 @@ blocks without verifying the execution payloads. This partial sync is called an ## Helpers Let `head_block: BeaconBlock` be the result of calling of the fork choice -algorithm at the time of block production. Let `justified_block: BeaconBlock` -be the latest current justified ancestor ancestor of the `head_block`. +algorithm at the time of block production. + +Let `justified_block: BeaconBlock` be the latest current justified ancestor +ancestor of the `head_block`. 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 @@ -99,7 +101,7 @@ considered "optimistically imported". When a block transitions from `SYNCING` -> `INVALID`, all *descendants* of the block MUST also transition from `SYNCING` -> `INVALID`. -When a node transitions from the `SYNCING` state it is removed from the set of +When a block transitions from the `SYNCING` state it is removed from the set of `optimistic_roots`. ### Execution Engine Errors @@ -116,9 +118,9 @@ validity request some block, a consensus engine: 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 -unknown). Specifically, `SYNCING` responses should be fork-specific; the search -for a block on one chain MUST NOT trigger a `SYNCING` response for another -chain. +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. ### Re-Orgs @@ -132,7 +134,7 @@ exit. ## Fork Choice -Consensus engines MUST support removing from fork choice blocks that transition +Consensus engines MUST support removing blocks from fork choice that transition 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. @@ -141,17 +143,18 @@ point MUST NOT be included in the canonical chain and the weights from those During the merge transition it is possible for an attacker to craft a `BeaconBlock` with an execution payload that references an -eternally-unavailable `body.execution_payload.parent_hash` value. 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 child of the head (due to the invalid chain of payloads) and the node -is unable to fork around the head (due to the justification of the malicious +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 chain). -The fork choice poisoning attack is temporary for an individual node, assuming -there exists an honest chain which justifies a higher epoch than the malicious -chain. Such an honest chain will take precedence and revive any poisoned store. +The fork choice poisoning attack is temporary for an individual node when that +an honest chain exists which justifies a higher epoch than the malicious chain. +Such an honest chain will take precedence and revive any poisoned store. The `SAFE_SLOTS_TO_IMPORT_OPTIMISTICALLY` parameter assumes that the network will justify a honest chain within some number of slots. With this assumption, @@ -170,9 +173,9 @@ disaster recovery: ## Checkpoint Sync (Weak Subjectivity Sync) -A consensus engine MAY assume that the `ExecutionPayload` of a block used for -checkpoint sync is `VALID` without providing that payload to an execution -engine. +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. ## Validator assignments @@ -220,8 +223,7 @@ Do not apply the existing condition: Instead, apply the new condition: -- [IGNORE] The block's parent (defined by block.parent_root) passes validation - except the block.body.execution_payload was deemed INVALID. +- [IGNORE] The block's parent was imported optimistically. - [REJECT] The block's parent (defined by block.parent_root) passes all validation, excluding verification of the block.body.execution_payload. @@ -274,16 +276,17 @@ When information about an optimistic block is requested, the consensus engine: - MAY respond with not found. - MAY respond with syncing. -### Requests for the Head +### Requests for an Optimistic Head When `is_optimistic(head) == True`, the consensus engine: -- MUST NOT return `head`. +- MUST NOT return an optimistic `head`. - MAY substitute the head block with `latest_valid_ancestor(block)`. - MAY return syncing. ### Requests to Validators Endpoints -When `is_optimistic(head) == True`, the consensus engine: +When `is_optimistic(head) == True`, the consensus engine MUST return syncing to +all endpoints which match the following pattern: -MUST respon +- `eth/*/validator/*` From 451ae293ce7cc9ff4ee9230dfb932698d979e884 Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Mon, 20 Dec 2021 17:55:10 +1100 Subject: [PATCH 35/75] Specify no invalid parents --- sync/optimistic.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/sync/optimistic.md b/sync/optimistic.md index fcb6d981a..515e340cc 100644 --- a/sync/optimistic.md +++ b/sync/optimistic.md @@ -79,6 +79,9 @@ To optimistically import a block: - The `execute_payload` function MUST return `True` if the execution engine returns `SYNCING` or `VALID`. An `INVALID` response MUST return `False`. +- The `validate_merge_block` function MUST NOT raise an assertion if both the +`pow_block` and `pow_parent` are unknown to the execution engine. +- The parent of the block MUST NOT have an INVALID execution payload. In addition to this change to validation, the consensus engine MUST be able to ascertain, after import, which blocks returned `SYNCING` and which returned From 9421bf3dfd7ea5a4658e419eca66c63f75a388fc Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Mon, 20 Dec 2021 18:32:47 +1100 Subject: [PATCH 36/75] Tidy --- sync/optimistic.md | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/sync/optimistic.md b/sync/optimistic.md index 515e340cc..665bea63d 100644 --- a/sync/optimistic.md +++ b/sync/optimistic.md @@ -43,7 +43,7 @@ def latest_valid_ancestor(block: BeaconBlock) -> BeaconBlock: ```python def is_execution_block(block: BeaconBlock) -> BeaconBlock: - block.execution_payload != ExecutionPayload() + block.body.execution_payload != ExecutionPayload() ``` ```python @@ -110,7 +110,7 @@ When a block transitions from the `SYNCING` state it is removed from the set of ### Execution Engine Errors When an execution engine returns an error or fails to respond to a payload -validity request some block, a consensus engine: +validity request for some block, a consensus engine: - MUST NOT optimistically import the block. - MUST NOT apply the block to the fork choice store. @@ -155,9 +155,10 @@ 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 chain). -The fork choice poisoning attack is temporary for an individual node when that -an honest chain exists which justifies a higher epoch than the malicious chain. -Such an honest chain will take precedence and revive any poisoned store. +If honest chain exists which justifies a higher epoch than the malicious chain, +that chain will take precedence and revive any poisoned store. Therefore, the +poisoning attack is temporary if >= 2/3rds of the network is honest and +non-faulty. The `SAFE_SLOTS_TO_IMPORT_OPTIMISTICALLY` parameter assumes that the network will justify a honest chain within some number of slots. With this assumption, From ff50bfe6e8695a6fbe92b6964ba454b6e225e562 Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Mon, 20 Dec 2021 18:32:54 +1100 Subject: [PATCH 37/75] Remove block production exception --- sync/optimistic.md | 17 +++++------------ 1 file changed, 5 insertions(+), 12 deletions(-) diff --git a/sync/optimistic.md b/sync/optimistic.md index 665bea63d..ac805be0e 100644 --- a/sync/optimistic.md +++ b/sync/optimistic.md @@ -183,22 +183,15 @@ payload to an execution engine. ## Validator assignments -An entirely optimistically synced 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. +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. ### Block Production A optimistic validator MUST NOT produce a block (i.e., sign across the -`DOMAIN_BEACON_PROPOSER` domain), unless one of the following exceptions are -met: - -#### Block Production Exception 1. - -If the justified block is fully verified (i.e., `not -is_optimistic(justified_block)`, the validator MAY produce a block upon -`latest_valid_ancestor(head)`. +`DOMAIN_BEACON_PROPOSER` domain). ### Attesting From e696d1103c4efec9d6d8432953fcee0c06ae5cac Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Mon, 20 Dec 2021 18:36:03 +1100 Subject: [PATCH 38/75] Update gossip conditions --- sync/optimistic.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/sync/optimistic.md b/sync/optimistic.md index ac805be0e..e45d6bbb2 100644 --- a/sync/optimistic.md +++ b/sync/optimistic.md @@ -220,9 +220,10 @@ Do not apply the existing condition: Instead, apply the new condition: -- [IGNORE] The block's parent was imported optimistically. - [REJECT] The block's parent (defined by block.parent_root) passes all validation, excluding verification of the block.body.execution_payload. +- [IGNORE] The block's parent (defined by block.parent_root) passes all + validation, including verification of the block.body.execution_payload. The effect of these modifications is that invalid payloads may be propagated across the network, but only when contained inside a block that is valid in *all From 941531c8784a960c5bbb56a417658fcbc11150d7 Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Wed, 22 Dec 2021 09:24:14 +1100 Subject: [PATCH 39/75] Update sync/optimistic.md Co-authored-by: terence tsao --- sync/optimistic.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sync/optimistic.md b/sync/optimistic.md index e45d6bbb2..abf352d07 100644 --- a/sync/optimistic.md +++ b/sync/optimistic.md @@ -68,7 +68,7 @@ conditions are met: 1. The justified checkpoint has execution enabled. I.e., `is_execution_block(get_block(get_state(head_block).current_justified_checkpoint.root))` 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. I.e., `should_optimistically_import_block(current_slot) == True`. + the slot of the block being imported. I.e., `should_optimistically_import_block(current_slot, block) == True`. *See [Fork Choice Poisoning](#fork-choice-poisoning) for the motivations behind these conditions.* From 12293c999aa3ead27934d01e0887c6d48cd6693f Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Tue, 21 Dec 2021 16:49:43 +1100 Subject: [PATCH 40/75] Bump safe slots --- sync/optimistic.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sync/optimistic.md b/sync/optimistic.md index abf352d07..b67cf85d4 100644 --- a/sync/optimistic.md +++ b/sync/optimistic.md @@ -11,7 +11,7 @@ blocks without verifying the execution payloads. This partial sync is called an |Name|Value|Unit |---|---|---| -|`SAFE_SLOTS_TO_IMPORT_OPTIMISTICALLY`| `96` | slots +|`SAFE_SLOTS_TO_IMPORT_OPTIMISTICALLY`| `128` | slots *Note: the `SAFE_SLOTS_TO_IMPORT_OPTIMISTICALLY` must be user-configurable. See [Fork Choice Poisoning](#fork-choice-poisoning).* From 50f526e418cb4115937271b49371d09502fbb820 Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Wed, 22 Dec 2021 09:32:33 +1100 Subject: [PATCH 41/75] Fix typo --- sync/optimistic.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sync/optimistic.md b/sync/optimistic.md index b67cf85d4..33ae71e9a 100644 --- a/sync/optimistic.md +++ b/sync/optimistic.md @@ -241,8 +241,8 @@ An optimistic node MUST NOT subscribe to the following topics: - `sync_committee_contribution_and_proof` - `sync_committee_{subnet_id}` -Once the node ceases to be optimistic, it MAY re-subscribe to the aformentioned -topics. +Once the node ceases to be optimistic, it MAY re-subscribe to the +aforementioned topics. ### The Req/Resp Domain From 9e619f88b81041cb2b38887789b0fa830a5e71e6 Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Tue, 11 Jan 2022 16:42:20 +1100 Subject: [PATCH 42/75] Apply suggestions from code review Co-authored-by: Hsiao-Wei Wang Co-authored-by: Mikhail Kalinin Co-authored-by: terence tsao --- sync/optimistic.md | 38 +++++++++++++++++++------------------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/sync/optimistic.md b/sync/optimistic.md index 33ae71e9a..964413e25 100644 --- a/sync/optimistic.md +++ b/sync/optimistic.md @@ -29,29 +29,29 @@ optimistically imported blocks which have yet to receive an `INVALID` or `VALID` designation from an execution engine. ```python -def is_optimistic(block: BeaconBlock) -> bool: - hash_tree_root(block) in optimistic_roots +def is_optimistic(block: BeaconBlock, optimistic_roots: Set[Root]) -> bool: + return hash_tree_root(block) in optimistic_roots ``` ```python -def latest_valid_ancestor(block: BeaconBlock) -> BeaconBlock: +def latest_verified_ancestor(block: BeaconBlock) -> BeaconBlock: while True: if not is_optimistic(block) or block.parent_root == Root(): - return block + return block block = get_block(block.parent_root) ``` ```python def is_execution_block(block: BeaconBlock) -> BeaconBlock: - block.body.execution_payload != ExecutionPayload() + return block.body.execution_payload != ExecutionPayload() ``` ```python def should_optimistically_import_block(current_slot: Slot, block: BeaconBlock) -> bool: - block.slot + SAFE_SLOTS_TO_IMPORT_OPTIMISTICALLY <= current_slot + return block.slot + SAFE_SLOTS_TO_IMPORT_OPTIMISTICALLY <= current_slot ``` -Let only a node which returns `is_optimistic(head) == True` be an *optimistic +Let only a node which returns `is_optimistic(head) is True` be an *optimistic node*. Let only a validator on an optimistic node be an *optimistic validator*. When this specification only defines behaviour for an optimistic @@ -60,7 +60,7 @@ behaviours without regard for optimistic sync. ## Mechanisms -## When to optimistically import blocks +### When to optimistically import blocks A block MUST NOT be optimistically imported, unless either of the following conditions are met: @@ -73,13 +73,13 @@ conditions are met: *See [Fork Choice Poisoning](#fork-choice-poisoning) for the motivations behind these conditions.* -## How to optimistically import blocks +### How to optimistically import blocks To optimistically import a block: -- The `execute_payload` function MUST return `True` if the execution +- The [`execute_payload`](../specs/merge/beacon-chain.md#execute_payload) function MUST return `True` if the execution engine returns `SYNCING` or `VALID`. An `INVALID` response MUST return `False`. -- The `validate_merge_block` function MUST NOT raise an assertion if both the +- The [`validate_merge_block`](../specs/merge/fork-choice.md#validate_merge_block) function MUST NOT raise an assertion if both the `pow_block` and `pow_parent` are unknown to the execution engine. - The parent of the block MUST NOT have an INVALID execution payload. @@ -190,7 +190,7 @@ verified that block. ### Block Production -A optimistic validator MUST NOT produce a block (i.e., sign across the +An optimistic validator MUST NOT produce a block (i.e., sign across the `DOMAIN_BEACON_PROPOSER` domain). ### Attesting @@ -216,14 +216,14 @@ validation conditions are modified as such: Do not apply the existing condition: -- [REJECT] The block's parent (defined by block.parent_root) passes validation. +- [REJECT] The block's parent (defined by `block.parent_root`) passes validation. Instead, apply the new condition: -- [REJECT] The block's parent (defined by block.parent_root) passes all - validation, excluding verification of the block.body.execution_payload. -- [IGNORE] The block's parent (defined by block.parent_root) passes all - validation, including verification of the block.body.execution_payload. +- [REJECT] The block's parent (defined by `block.parent_root`) passes all + validation, excluding verification of the `block.body.execution_payload`. +- [IGNORE] The block's parent (defined by `block.parent_root`) passes all + validation, including verification of the `block.body.execution_payload`. The effect of these modifications is that invalid payloads may be propagated across the network, but only when contained inside a block that is valid in *all @@ -276,7 +276,7 @@ When information about an optimistic block is requested, the consensus engine: ### Requests for an Optimistic Head -When `is_optimistic(head) == True`, the consensus engine: +When `is_optimistic(head) is True`, the consensus engine: - MUST NOT return an optimistic `head`. - MAY substitute the head block with `latest_valid_ancestor(block)`. @@ -284,7 +284,7 @@ When `is_optimistic(head) == True`, the consensus engine: ### Requests to Validators Endpoints -When `is_optimistic(head) == True`, the consensus engine MUST return syncing to +When `is_optimistic(head) is True`, the consensus engine MUST return syncing to all endpoints which match the following pattern: - `eth/*/validator/*` From 6eba269e9ba772f40451716290e9e030fdd6a34f Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Tue, 11 Jan 2022 19:02:40 +1100 Subject: [PATCH 43/75] Update sync/optimistic.md Co-authored-by: Raul Jordan --- sync/optimistic.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sync/optimistic.md b/sync/optimistic.md index 964413e25..3b96ae9a8 100644 --- a/sync/optimistic.md +++ b/sync/optimistic.md @@ -62,7 +62,7 @@ behaviours without regard for optimistic sync. ### When to optimistically import blocks -A block MUST NOT be optimistically imported, unless either of the following +A block MAY be optimistically imported when either of the following conditions are met: 1. The justified checkpoint has execution enabled. I.e., From 6c13e2ecdbe2fbb2262e5570788dec1664d90803 Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Wed, 12 Jan 2022 15:54:11 +1100 Subject: [PATCH 44/75] Expand `should_optimistically_import_block` --- sync/optimistic.md | 26 +++++++++++++++++++------- 1 file changed, 19 insertions(+), 7 deletions(-) diff --git a/sync/optimistic.md b/sync/optimistic.md index 3b96ae9a8..6137579cb 100644 --- a/sync/optimistic.md +++ b/sync/optimistic.md @@ -29,14 +29,23 @@ optimistically imported blocks which have yet to receive an `INVALID` or `VALID` designation from an execution engine. ```python -def is_optimistic(block: BeaconBlock, optimistic_roots: Set[Root]) -> bool: - return hash_tree_root(block) in optimistic_roots +@dataclass +class Store(object): + optimistic_roots: Set[Root] + head_block_root: Root + blocks: Dict[Root, BeaconBlock] + block_states: Dict[Root, BeaconState] ``` ```python -def latest_verified_ancestor(block: BeaconBlock) -> BeaconBlock: +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: while True: - if not is_optimistic(block) or block.parent_root == Root(): + if not is_optimistic(store, block) or block.parent_root == Root(): return block block = get_block(block.parent_root) ``` @@ -47,8 +56,11 @@ def is_execution_block(block: BeaconBlock) -> BeaconBlock: ``` ```python -def should_optimistically_import_block(current_slot: Slot, block: BeaconBlock) -> bool: - return block.slot + SAFE_SLOTS_TO_IMPORT_OPTIMISTICALLY <= current_slot +def should_optimistically_import_block(store: Store, current_slot: Slot, block: BeaconBlock) -> bool: + 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 ``` Let only a node which returns `is_optimistic(head) is True` be an *optimistic @@ -105,7 +117,7 @@ When a block transitions from `SYNCING` -> `INVALID`, all *descendants* of the block MUST also transition from `SYNCING` -> `INVALID`. When a block transitions from the `SYNCING` state it is removed from the set of -`optimistic_roots`. +`store.optimistic_roots`. ### Execution Engine Errors From 2ce2aac0c89eac7ff6098cc2d0a4ad7f700f1d06 Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Wed, 12 Jan 2022 15:57:27 +1100 Subject: [PATCH 45/75] Address "yet to" paragraph --- sync/optimistic.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/sync/optimistic.md b/sync/optimistic.md index 6137579cb..f3f70ee2f 100644 --- a/sync/optimistic.md +++ b/sync/optimistic.md @@ -28,6 +28,10 @@ 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. +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`). + ```python @dataclass class Store(object): From 736f3cec3f1d5c5a167c8d74b33a4d73b3f6b07b Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Wed, 12 Jan 2022 16:09:24 +1100 Subject: [PATCH 46/75] Remove `justified_block` --- sync/optimistic.md | 3 --- 1 file changed, 3 deletions(-) diff --git a/sync/optimistic.md b/sync/optimistic.md index f3f70ee2f..c511e9781 100644 --- a/sync/optimistic.md +++ b/sync/optimistic.md @@ -21,9 +21,6 @@ blocks without verifying the execution payloads. This partial sync is called an Let `head_block: BeaconBlock` be the result of calling of the fork choice algorithm at the time of block production. -Let `justified_block: BeaconBlock` be the latest current justified ancestor -ancestor of the `head_block`. - 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. From 55d92cee34842be718d4580619fdd16812a5f9d2 Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Wed, 12 Jan 2022 16:10:59 +1100 Subject: [PATCH 47/75] Fix typo --- sync/optimistic.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/sync/optimistic.md b/sync/optimistic.md index c511e9781..959fa9ffb 100644 --- a/sync/optimistic.md +++ b/sync/optimistic.md @@ -168,10 +168,10 @@ 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 chain). -If honest chain exists which justifies a higher epoch than the malicious chain, -that chain will take precedence and revive any poisoned store. Therefore, the -poisoning attack is temporary if >= 2/3rds of the network is honest and -non-faulty. +If an honest chain exists which justifies a higher epoch than the malicious +chain, that chain will take precedence and revive any poisoned store. +Therefore, the poisoning attack is temporary if >= 2/3rds of the network is +honest and non-faulty. The `SAFE_SLOTS_TO_IMPORT_OPTIMISTICALLY` parameter assumes that the network will justify a honest chain within some number of slots. With this assumption, From 1228e01883685ae90aeef8b05676879ed3d8bb13 Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Wed, 12 Jan 2022 16:18:53 +1100 Subject: [PATCH 48/75] Update p2p-networking --- specs/merge/p2p-interface.md | 17 +++++++++++++ sync/optimistic.md | 46 ------------------------------------ 2 files changed, 17 insertions(+), 46 deletions(-) diff --git a/specs/merge/p2p-interface.md b/specs/merge/p2p-interface.md index 0ab3d0825..9646eae4d 100644 --- a/specs/merge/p2p-interface.md +++ b/specs/merge/p2p-interface.md @@ -85,6 +85,10 @@ The *type* of the payload of this topic changes to the (modified) `SignedBeaconB Specifically, this type changes with the addition of `execution_payload` to the inner `BeaconBlockBody`. See the Merge [state transition document](./beacon-chain.md#beaconblockbody) for further details. +Blocks with execution enabled will be permitted to propagate regardless of the +validity of the execution payload. This prevents network segregation between +[optimistic](/sync/optimistic.md) and non-optimistic nodes. + In addition to the gossip validations for this topic from prior specifications, the following validations MUST pass before forwarding the `signed_beacon_block` on the network. Alias `block = signed_beacon_block.message`, `execution_payload = block.body.execution_payload`. @@ -92,6 +96,13 @@ Alias `block = signed_beacon_block.message`, `execution_payload = block.body.exe then validate the following: - _[REJECT]_ The block's execution payload timestamp is correct with respect to the slot -- i.e. `execution_payload.timestamp == compute_timestamp_at_slot(state, block.slot)`. + - [REJECT] The block's parent (defined by `block.parent_root`) passes all + validation, excluding verification of the `block.body.execution_payload`. + - [IGNORE] The block's parent (defined by `block.parent_root`) passes all + validation, including verification of the `block.body.execution_payload`. + +The following gossip validation from prior specifications MUST NOT be applied if the execution is enabled for the block -- i.e. `is_execution_enabled(state, block.body)`: + - [REJECT] The block's parent (defined by `block.parent_root`) passes validation. ### Transitioning the gossip @@ -100,6 +111,12 @@ details on how to handle transitioning gossip topics for the Merge. ## The Req/Resp domain +Non-faulty, [optimistic](/sync/optimistic.md) nodes may send blocks which +result in an INVALID response from an execution engine. To prevent network +segregation between optimistic and non-optimistic nodes, transmission of an +INVALID payload via the Req/Resp domain SHOULD NOT cause a node to be +down-scored or disconnected. + ### Messages #### BeaconBlocksByRange v2 diff --git a/sync/optimistic.md b/sync/optimistic.md index 959fa9ffb..84175d06c 100644 --- a/sync/optimistic.md +++ b/sync/optimistic.md @@ -218,52 +218,6 @@ An optimistic validator MUST NOT participate in sync committees (i.e., sign acro `DOMAIN_SYNC_COMMITTEE`, `DOMAIN_SYNC_COMMITTEE_SELECTION_PROOF` or `DOMAIN_CONTRIBUTION_AND_PROOF` domains). -## P2P Networking - -### The Gossip Domain (gossipsub) - -#### `beacon_block` - -An optimistic validator MAY subscribe to the `beacon_block` topic. Propagation -validation conditions are modified as such: - -Do not apply the existing condition: - -- [REJECT] The block's parent (defined by `block.parent_root`) passes validation. - -Instead, apply the new condition: - -- [REJECT] The block's parent (defined by `block.parent_root`) passes all - validation, excluding verification of the `block.body.execution_payload`. -- [IGNORE] The block's parent (defined by `block.parent_root`) passes all - validation, including verification of the `block.body.execution_payload`. - -The effect of these modifications is that invalid payloads may be propagated -across the network, but only when contained inside a block that is valid in *all -other aspects*. - -#### Other Topics - -An optimistic node MUST NOT subscribe to the following topics: - -- `beacon_aggregate_and_proof` -- `voluntary_exit` -- `proposer_slashing` -- `attester_slashing` -- `beacon_attestation_{subnet_id}` -- `sync_committee_contribution_and_proof` -- `sync_committee_{subnet_id}` - -Once the node ceases to be optimistic, it MAY re-subscribe to the -aforementioned topics. - -### The Req/Resp Domain - -Non-faulty, optimistic nodes may send blocks which result in an INVALID -response from an execution engine. To prevent network segregation between -optimistic and non-optimistic nodes, transmission of an INVALID payload SHOULD -NOT cause a node to be down-scored or disconnected. - ## Ethereum Beacon APIs Consensus engines which provide an implementation of the [Ethereum Beacon From 60eab25774e6282c2531b02921b4172780e8fb04 Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Wed, 12 Jan 2022 16:39:43 +1100 Subject: [PATCH 49/75] Add section about merge block --- sync/optimistic.md | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/sync/optimistic.md b/sync/optimistic.md index 84175d06c..d90880310 100644 --- a/sync/optimistic.md +++ b/sync/optimistic.md @@ -1,5 +1,7 @@ # Optimistic Sync +[`validate_merge_block`]: ../specs/merge/fork-choice.md#validate_merge_block + ## Introduction In order to provide a syncing execution engine with a partial view of the head @@ -92,7 +94,7 @@ To optimistically import a block: - The [`execute_payload`](../specs/merge/beacon-chain.md#execute_payload) function MUST return `True` if the execution engine returns `SYNCING` or `VALID`. An `INVALID` response MUST return `False`. -- The [`validate_merge_block`](../specs/merge/fork-choice.md#validate_merge_block) function MUST NOT raise an assertion if both the +- The [`validate_merge_block`][] function MUST NOT raise an assertion if both the `pow_block` and `pow_parent` are unknown to the execution engine. - The parent of the block MUST NOT have an INVALID execution payload. @@ -120,6 +122,13 @@ block MUST also transition from `SYNCING` -> `INVALID`. When a block transitions from the `SYNCING` state it is removed from the set of `store.optimistic_roots`. +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 When an execution engine returns an error or fails to respond to a payload From 0ae80d93826c1dcf0051ebd7afc49febe46d4bb5 Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Wed, 12 Jan 2022 16:41:24 +1100 Subject: [PATCH 50/75] Fix typo --- sync/optimistic.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sync/optimistic.md b/sync/optimistic.md index d90880310..05ab5076f 100644 --- a/sync/optimistic.md +++ b/sync/optimistic.md @@ -255,7 +255,7 @@ When information about an optimistic block is requested, the consensus engine: When `is_optimistic(head) is True`, the consensus engine: - MUST NOT return an optimistic `head`. -- MAY substitute the head block with `latest_valid_ancestor(block)`. +- MAY substitute the head block with `latest_verified_ancestor(block)`. - MAY return syncing. ### Requests to Validators Endpoints From de1a6caa5dec08fa6358f2e2506e5c4e50548814 Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Wed, 12 Jan 2022 16:43:36 +1100 Subject: [PATCH 51/75] Add comment about INVALID block --- sync/optimistic.md | 1 + 1 file changed, 1 insertion(+) diff --git a/sync/optimistic.md b/sync/optimistic.md index 05ab5076f..72037dc3a 100644 --- a/sync/optimistic.md +++ b/sync/optimistic.md @@ -47,6 +47,7 @@ def is_optimistic(store: Store, block: BeaconBlock) -> bool: ```python def latest_verified_ancestor(store: Store, block: BeaconBlock) -> BeaconBlock: + # It is assumed that the `block` parameter is never an INVALID block. while True: if not is_optimistic(store, block) or block.parent_root == Root(): return block From ad7e92433a05b0fd6c2b74fddae3c5cf154b1533 Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Wed, 12 Jan 2022 16:49:08 +1100 Subject: [PATCH 52/75] Update links to bellatrix --- sync/optimistic.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sync/optimistic.md b/sync/optimistic.md index 72037dc3a..656a328b1 100644 --- a/sync/optimistic.md +++ b/sync/optimistic.md @@ -1,6 +1,6 @@ # Optimistic Sync -[`validate_merge_block`]: ../specs/merge/fork-choice.md#validate_merge_block +[`validate_merge_block`]: ../specs/bellatrix/fork-choice.md#validate_merge_block ## Introduction @@ -93,7 +93,7 @@ these conditions.* To optimistically import a block: -- The [`execute_payload`](../specs/merge/beacon-chain.md#execute_payload) function MUST return `True` if the execution +- The [`execute_payload`](../specs/bellatrix/beacon-chain.md#execute_payload) function MUST return `True` if the execution engine returns `SYNCING` or `VALID`. An `INVALID` response MUST return `False`. - The [`validate_merge_block`][] function MUST NOT raise an assertion if both the `pow_block` and `pow_parent` are unknown to the execution engine. From 90fb7f68719b9bae2889c505e892114ee22540e8 Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Wed, 12 Jan 2022 19:04:43 +1100 Subject: [PATCH 53/75] Add rationale --- specs/bellatrix/p2p-interface.md | 23 +++++++++++ sync/optimistic.md | 70 ++++++++++++++++++++++++++++++++ 2 files changed, 93 insertions(+) diff --git a/specs/bellatrix/p2p-interface.md b/specs/bellatrix/p2p-interface.md index 75c70a18a..5a260d982 100644 --- a/specs/bellatrix/p2p-interface.md +++ b/specs/bellatrix/p2p-interface.md @@ -198,3 +198,26 @@ valid block sizes in the range of gas limits expected in the medium term. As with both gossip and req/rsp maximum values, type-specific limits should always by simultaneously respected. + +### Why allow invalid payloads on the P2P network? + +The specification allows blocks with invalid payloads to propagate across +gossip and via RPC calls. The reasoning for this is as follows: + +1. Optimistic nodes must listen to block gossip to obtain a view of the head of + the chain. +2. Therefore, optimistic nodes must propagate gossip blocks. Otherwise, they'd + be censoring. +3. If optimistic nodes will propose blocks via gossip, then they must respond + to requests for the parent via RPC. +4. Therefore, optimistic nodes must send optimistic blocks via RPC. + +So, to prevent network segregation from optimistic nodes accidentally sending +invalid payloads, nodes should never downscore/disconnect nodes due to invalid +payloads. This does open the network to some DoS attacks from invalid execution +payloads, but the scope of actors is limited to validators who can put those +payloads in valid (and slashable) beacon blocks. Therefore, it is argued that +the DoS risk introduced in tolerable. + +More complicated schemes are possible that could restrict invalid payloads from +RPC. However, it's not clear that complexity is warranted. diff --git a/sync/optimistic.md b/sync/optimistic.md index 656a328b1..6793b972d 100644 --- a/sync/optimistic.md +++ b/sync/optimistic.md @@ -265,3 +265,73 @@ When `is_optimistic(head) is True`, the consensus engine MUST return syncing to all endpoints which match the following pattern: - `eth/*/validator/*` + +## 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 +justification?) and how to mitigate implemenation complexity. At this point, +it's important to reflect on the attack and how likely it is to happen. It +requires some rather contrived circumstances and it seems very unlikley to +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. From 6d73b0a4ac30a8fae1ba909d1ebf628234cb41ca Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Thu, 13 Jan 2022 06:52:07 +1100 Subject: [PATCH 54/75] Add poisoning prevention --- sync/optimistic.md | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/sync/optimistic.md b/sync/optimistic.md index 6793b972d..217b8bfd1 100644 --- a/sync/optimistic.md +++ b/sync/optimistic.md @@ -179,9 +179,10 @@ block that forks around the head (due to the justification of the malicious chain). If an honest chain exists which justifies a higher epoch than the malicious -chain, that chain will take precedence and revive any poisoned store. -Therefore, the poisoning attack is temporary if >= 2/3rds of the network is -honest and non-faulty. +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. The `SAFE_SLOTS_TO_IMPORT_OPTIMISTICALLY` parameter assumes that the network will justify a honest chain within some number of slots. With this assumption, From 0c2e416a6e9cc9a7fb8c8e30b07ea0ece77832ff Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Thu, 13 Jan 2022 06:56:32 +1100 Subject: [PATCH 55/75] Run doctoc --- specs/bellatrix/p2p-interface.md | 1 + 1 file changed, 1 insertion(+) diff --git a/specs/bellatrix/p2p-interface.md b/specs/bellatrix/p2p-interface.md index 5a260d982..964a77464 100644 --- a/specs/bellatrix/p2p-interface.md +++ b/specs/bellatrix/p2p-interface.md @@ -29,6 +29,7 @@ Readers should understand the Phase 0 and Altair documents and use them as a bas - [Why was the max gossip message size increased at Bellatrix?](#why-was-the-max-gossip-message-size-increased-at-bellatrix) - [Req/Resp](#reqresp) - [Why was the max chunk response size increased at Bellatrix?](#why-was-the-max-chunk-response-size-increased-at-bellatrix) + - [Why allow invalid payloads on the P2P network?](#why-allow-invalid-payloads-on-the-p2p-network) From 6d72038f12583de772e6972f4f9b834b2508353e Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Thu, 13 Jan 2022 06:56:38 +1100 Subject: [PATCH 56/75] Fix spelling mistakes --- sync/optimistic.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sync/optimistic.md b/sync/optimistic.md index 217b8bfd1..1b413ea71 100644 --- a/sync/optimistic.md +++ b/sync/optimistic.md @@ -310,9 +310,9 @@ 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 -justification?) and how to mitigate implemenation complexity. At this point, +justification?) and how to mitigate implementation complexity. At this point, it's important to reflect on the attack and how likely it is to happen. It -requires some rather contrived circumstances and it seems very unlikley to +requires some rather contrived circumstances and it seems very unlikely to 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 From 18c32e0a5e10e4b2c7170139ea1a7ba1acdf08a3 Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Thu, 13 Jan 2022 07:00:28 +1100 Subject: [PATCH 57/75] Fix indents --- sync/optimistic.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sync/optimistic.md b/sync/optimistic.md index 1b413ea71..826396a41 100644 --- a/sync/optimistic.md +++ b/sync/optimistic.md @@ -61,8 +61,8 @@ def is_execution_block(block: BeaconBlock) -> BeaconBlock: ```python def should_optimistically_import_block(store: Store, current_slot: Slot, block: BeaconBlock) -> bool: - justified_root = store.block_states[store.head_block_root].current_justified_checkpoint.root - justifed_is_verified = is_execution_block(store.blocks[justified_root]) + 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 ``` From 6af3d4cbfc5b2e075e0441172277d08736e45b72 Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Thu, 13 Jan 2022 07:13:10 +1100 Subject: [PATCH 58/75] Fix comment indents --- sync/optimistic.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sync/optimistic.md b/sync/optimistic.md index 826396a41..ab539a64d 100644 --- a/sync/optimistic.md +++ b/sync/optimistic.md @@ -47,7 +47,7 @@ def is_optimistic(store: Store, block: BeaconBlock) -> bool: ```python def latest_verified_ancestor(store: Store, block: BeaconBlock) -> BeaconBlock: - # It is assumed that the `block` parameter is never an INVALID block. + # It is assumed that the `block` parameter is never an INVALID block. while True: if not is_optimistic(store, block) or block.parent_root == Root(): return block From b7c332f0ee836da702c7a9e74afa8b8c285f8a20 Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Thu, 13 Jan 2022 07:17:43 +1100 Subject: [PATCH 59/75] Tidy --- sync/optimistic.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/sync/optimistic.md b/sync/optimistic.md index ab539a64d..31f182ac4 100644 --- a/sync/optimistic.md +++ b/sync/optimistic.md @@ -20,7 +20,7 @@ blocks without verifying the execution payloads. This partial sync is called an ## Helpers -Let `head_block: BeaconBlock` be the result of calling of the fork choice +Let `head: BeaconBlock` be the result of calling of the fork choice algorithm at the time of block production. Let `optimistic_roots: Set[Root]` be the set of `hash_tree_root(block)` for all @@ -51,7 +51,7 @@ def latest_verified_ancestor(store: Store, block: BeaconBlock) -> BeaconBlock: while True: if not is_optimistic(store, block) or block.parent_root == Root(): return block - block = get_block(block.parent_root) + block = store.blocks[block.parent_root] ``` ```python @@ -67,7 +67,7 @@ def should_optimistically_import_block(store: Store, current_slot: Slot, block: return justified_is_verified or block_is_deep ``` -Let only a node which returns `is_optimistic(head) is True` be an *optimistic +Let only a node which returns `is_optimistic(store, head) is True` be an *optimistic node*. Let only a validator on an optimistic node be an *optimistic validator*. When this specification only defines behaviour for an optimistic @@ -254,7 +254,7 @@ When information about an optimistic block is requested, the consensus engine: ### Requests for an Optimistic Head -When `is_optimistic(head) is True`, the consensus engine: +When `is_optimistic(store, head) is True`, the consensus engine: - MUST NOT return an optimistic `head`. - MAY substitute the head block with `latest_verified_ancestor(block)`. @@ -262,7 +262,7 @@ When `is_optimistic(head) is True`, the consensus engine: ### Requests to Validators Endpoints -When `is_optimistic(head) is True`, the consensus engine MUST return syncing to +When `is_optimistic(store, head) is True`, the consensus engine MUST return syncing to all endpoints which match the following pattern: - `eth/*/validator/*` From 8522f27b8ecfcffd067f873c957f824808ab53e9 Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Thu, 13 Jan 2022 07:17:57 +1100 Subject: [PATCH 60/75] Modify "when" section --- sync/optimistic.md | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/sync/optimistic.md b/sync/optimistic.md index 31f182ac4..5d7f0d1f4 100644 --- a/sync/optimistic.md +++ b/sync/optimistic.md @@ -78,13 +78,14 @@ behaviours without regard for optimistic sync. ### When to optimistically import blocks -A block MAY be optimistically imported when either of the following -conditions are met: +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. I.e., - `is_execution_block(get_block(get_state(head_block).current_justified_checkpoint.root))` -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. I.e., `should_optimistically_import_block(current_slot, block) == True`. +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. *See [Fork Choice Poisoning](#fork-choice-poisoning) for the motivations behind these conditions.* From 856eea4986f9b20d98132fc0d91898d68b31aac3 Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Thu, 13 Jan 2022 07:23:12 +1100 Subject: [PATCH 61/75] Describe all fields in store --- sync/optimistic.md | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/sync/optimistic.md b/sync/optimistic.md index 5d7f0d1f4..cb45b7036 100644 --- a/sync/optimistic.md +++ b/sync/optimistic.md @@ -21,11 +21,12 @@ blocks without verifying the execution payloads. This partial sync is called an ## Helpers Let `head: BeaconBlock` be the result of calling of the fork choice -algorithm at the time of block production. +algorithm at the time of block production. Let `head_block_root: Root` be the +root of that block. -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. +Let `blocks: Dict[Root, BeaconBlock]` and `block_states: Dict[Root, +BeaconState]` be the blocks (and accompanying states) that have been verified +either completely or optimistically. 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 15ef2f30d2dfd7a19f30d99df76c646ce07085ac Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Tue, 18 Jan 2022 09:30:01 +1100 Subject: [PATCH 62/75] Apply suggestions from @djrtwo review Co-authored-by: Danny Ryan --- specs/bellatrix/p2p-interface.md | 6 +++--- sync/optimistic.md | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/specs/bellatrix/p2p-interface.md b/specs/bellatrix/p2p-interface.md index 964a77464..2f07068fe 100644 --- a/specs/bellatrix/p2p-interface.md +++ b/specs/bellatrix/p2p-interface.md @@ -202,7 +202,7 @@ always by simultaneously respected. ### Why allow invalid payloads on the P2P network? -The specification allows blocks with invalid payloads to propagate across +The specification allows blocks with invalid execution payloads to propagate across gossip and via RPC calls. The reasoning for this is as follows: 1. Optimistic nodes must listen to block gossip to obtain a view of the head of @@ -213,8 +213,8 @@ gossip and via RPC calls. The reasoning for this is as follows: to requests for the parent via RPC. 4. Therefore, optimistic nodes must send optimistic blocks via RPC. -So, to prevent network segregation from optimistic nodes accidentally sending -invalid payloads, nodes should never downscore/disconnect nodes due to invalid +So, to prevent network segregation from optimistic nodes inadvertently sending +invalid execution payloads, nodes should never downscore/disconnect nodes due to such invalid payloads. This does open the network to some DoS attacks from invalid execution payloads, but the scope of actors is limited to validators who can put those payloads in valid (and slashable) beacon blocks. Therefore, it is argued that diff --git a/sync/optimistic.md b/sync/optimistic.md index cb45b7036..88dc28444 100644 --- a/sync/optimistic.md +++ b/sync/optimistic.md @@ -116,16 +116,16 @@ from an execution engine. I.e., perform the following transitions: - `SYNCING` -> `INVALID` When a block transitions from `SYNCING` -> `VALID`, all *ancestors* of the -block MUST also transition from `SYNCING` -> `VALID`. Such a block is no longer +block MUST also transition from `SYNCING` -> `VALID`. Such a block and any previously `SYNCING` ancestors are no longer considered "optimistically imported". When a block transitions from `SYNCING` -> `INVALID`, all *descendants* of the block MUST also transition from `SYNCING` -> `INVALID`. -When a block transitions from the `SYNCING` state it is removed from the set of +When a block transitions from the `SYNCING` state, it is removed from the set of `store.optimistic_roots`. -When a "merge block" (i.e. a block which enables execution) is declared to be +When a "merge block" (i.e. the first block which enables execution in a chain) 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 From b1ec9bcfbc395549f3871a4d80960a5ddd27322d Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Tue, 18 Jan 2022 11:12:55 +1100 Subject: [PATCH 63/75] Update gossip conditions --- specs/bellatrix/p2p-interface.md | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/specs/bellatrix/p2p-interface.md b/specs/bellatrix/p2p-interface.md index 2f07068fe..1d790bce7 100644 --- a/specs/bellatrix/p2p-interface.md +++ b/specs/bellatrix/p2p-interface.md @@ -95,12 +95,14 @@ the following validations MUST pass before forwarding the `signed_beacon_block` Alias `block = signed_beacon_block.message`, `execution_payload = block.body.execution_payload`. - If the execution is enabled for the block -- i.e. `is_execution_enabled(state, block.body)` then validate the following: - - _[REJECT]_ The block's execution payload timestamp is correct with respect to the slot - -- i.e. `execution_payload.timestamp == compute_timestamp_at_slot(state, block.slot)`. - - [REJECT] The block's parent (defined by `block.parent_root`) passes all - validation, excluding verification of the `block.body.execution_payload`. - - [IGNORE] The block's parent (defined by `block.parent_root`) passes all - validation, including verification of the `block.body.execution_payload`. + - _[REJECT]_ The block's execution payload timestamp is correct with respect to the slot + -- i.e. `execution_payload.timestamp == compute_timestamp_at_slot(state, block.slot)`. + - If `exection_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`). + - otherwise: + - [IGNORE] The block's parent (defined by `block.parent_root`) passes all + validation (including execution node verification of the `block.body.execution_payload`). The following gossip validation from prior specifications MUST NOT be applied if the execution is enabled for the block -- i.e. `is_execution_enabled(state, block.body)`: - [REJECT] The block's parent (defined by `block.parent_root`) passes validation. From 6225236a52df6017f139127deec806a069cbabad Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Tue, 18 Jan 2022 11:20:52 +1100 Subject: [PATCH 64/75] Specify about EL/CL scoring rules --- specs/bellatrix/p2p-interface.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/specs/bellatrix/p2p-interface.md b/specs/bellatrix/p2p-interface.md index 1d790bce7..4f3a70990 100644 --- a/specs/bellatrix/p2p-interface.md +++ b/specs/bellatrix/p2p-interface.md @@ -117,8 +117,10 @@ details on how to handle transitioning gossip topics for Bellatrix. Non-faulty, [optimistic](/sync/optimistic.md) nodes may send blocks which result in an INVALID response from an execution engine. To prevent network segregation between optimistic and non-optimistic nodes, transmission of an -INVALID payload via the Req/Resp domain SHOULD NOT cause a node to be -down-scored or disconnected. +INVALID execution payload via the Req/Resp domain SHOULD NOT cause a node to be +down-scored or disconnected. Transmission of a block which is invalid due to +any consensus layer rules (i.e., *not* execution layer rules) MAY result in +down-scoring or disconnection. ### Messages From 092f3e0b167d6ad65d7e83ea75956c276206700a Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Tue, 18 Jan 2022 11:22:07 +1100 Subject: [PATCH 65/75] Propose -> Propagate --- specs/bellatrix/p2p-interface.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/specs/bellatrix/p2p-interface.md b/specs/bellatrix/p2p-interface.md index 4f3a70990..60a9be774 100644 --- a/specs/bellatrix/p2p-interface.md +++ b/specs/bellatrix/p2p-interface.md @@ -213,7 +213,7 @@ gossip and via RPC calls. The reasoning for this is as follows: the chain. 2. Therefore, optimistic nodes must propagate gossip blocks. Otherwise, they'd be censoring. -3. If optimistic nodes will propose blocks via gossip, then they must respond +3. If optimistic nodes will propagate blocks via gossip, then they must respond to requests for the parent via RPC. 4. Therefore, optimistic nodes must send optimistic blocks via RPC. From 52caba6eae98169549b78b3e5f5219f79c0db740 Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Tue, 18 Jan 2022 11:28:08 +1100 Subject: [PATCH 66/75] Add section about backwards compat --- sync/optimistic.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/sync/optimistic.md b/sync/optimistic.md index 88dc28444..d794269a2 100644 --- a/sync/optimistic.md +++ b/sync/optimistic.md @@ -9,6 +9,13 @@ 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*. +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). Optimistic sync is not a fundamental requirement for consensus nodes. +Rather, it's a stop-gap measure to allow execution nodes to sync via +established methods until future Ethereum roadmap items are implemented (e.g., +statelessness). + ## Constants |Name|Value|Unit From b50bee16f6ca45ed535c608f556983622cb2c567 Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Tue, 18 Jan 2022 11:30:28 +1100 Subject: [PATCH 67/75] Rename Store -> OptimisticStore --- sync/optimistic.md | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/sync/optimistic.md b/sync/optimistic.md index d794269a2..3aaa2688b 100644 --- a/sync/optimistic.md +++ b/sync/optimistic.md @@ -41,7 +41,7 @@ from an execution engine (i.e., they are not known to be `INVALID` or `VALID`). ```python @dataclass -class Store(object): +class OptimisticStore(object): optimistic_roots: Set[Root] head_block_root: Root blocks: Dict[Root, BeaconBlock] @@ -49,17 +49,17 @@ class Store(object): ``` ```python -def is_optimistic(store: Store, block: BeaconBlock) -> bool: - return hash_tree_root(block) in store.optimistic_roots +def is_optimistic(opt_store: OptimisticStore, block: BeaconBlock) -> bool: + return hash_tree_root(block) in opt_store.optimistic_roots ``` ```python -def latest_verified_ancestor(store: Store, block: BeaconBlock) -> BeaconBlock: +def latest_verified_ancestor(opt_store: OptimisticStore, block: BeaconBlock) -> BeaconBlock: # It is assumed that the `block` parameter is never an INVALID block. while True: - if not is_optimistic(store, block) or block.parent_root == Root(): + if not is_optimistic(opt_store, block) or block.parent_root == Root(): return block - block = store.blocks[block.parent_root] + block = opt_store.blocks[block.parent_root] ``` ```python @@ -68,14 +68,14 @@ def is_execution_block(block: BeaconBlock) -> BeaconBlock: ``` ```python -def should_optimistically_import_block(store: Store, current_slot: Slot, block: BeaconBlock) -> bool: - justified_root = store.block_states[store.head_block_root].current_justified_checkpoint.root - justifed_is_verified = is_execution_block(store.blocks[justified_root]) +def should_optimistically_import_block(opt_store: OptimisticStore, current_slot: Slot, block: BeaconBlock) -> bool: + justified_root = opt_store.block_states[opt_store.head_block_root].current_justified_checkpoint.root + justifed_is_verified = is_execution_block(opt_store.blocks[justified_root]) block_is_deep = block.slot + SAFE_SLOTS_TO_IMPORT_OPTIMISTICALLY <= current_slot return justified_is_verified or block_is_deep ``` -Let only a node which returns `is_optimistic(store, head) is True` be an *optimistic +Let only a node which returns `is_optimistic(opt_store, head) is True` be an *optimistic node*. Let only a validator on an optimistic node be an *optimistic validator*. When this specification only defines behaviour for an optimistic @@ -87,7 +87,7 @@ behaviours without regard for optimistic sync. ### When to optimistically import blocks A block MAY be optimistically imported when -`should_optimistically_import_block(store, current_slot, block)` returns +`should_optimistically_import_block(opt_store, current_slot, block)` returns `True`. This ensures that blocks are only optimistically imported if either: 1. The justified checkpoint has execution enabled. @@ -130,7 +130,7 @@ When a block transitions from `SYNCING` -> `INVALID`, all *descendants* of the block MUST also transition from `SYNCING` -> `INVALID`. When a block transitions from the `SYNCING` state, it is removed from the set of -`store.optimistic_roots`. +`opt_store.optimistic_roots`. When a "merge block" (i.e. the first block which enables execution in a chain) is declared to be `VALID` by an execution engine (either directly or indirectly), the full @@ -263,7 +263,7 @@ When information about an optimistic block is requested, the consensus engine: ### Requests for an Optimistic Head -When `is_optimistic(store, head) is True`, the consensus engine: +When `is_optimistic(opt_store, head) is True`, the consensus engine: - MUST NOT return an optimistic `head`. - MAY substitute the head block with `latest_verified_ancestor(block)`. @@ -271,7 +271,7 @@ When `is_optimistic(store, head) is True`, the consensus engine: ### Requests to Validators Endpoints -When `is_optimistic(store, head) is True`, the consensus engine MUST return syncing to +When `is_optimistic(opt_store, head) is True`, the consensus engine MUST return syncing to all endpoints which match the following pattern: - `eth/*/validator/*` From 4ccd38b54e3a2a19b159ff4c1383bd2f820156c9 Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Tue, 18 Jan 2022 11:32:47 +1100 Subject: [PATCH 68/75] Tidy tracking note --- sync/optimistic.md | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/sync/optimistic.md b/sync/optimistic.md index 3aaa2688b..dd9d428d4 100644 --- a/sync/optimistic.md +++ b/sync/optimistic.md @@ -108,9 +108,8 @@ To optimistically import a block: `pow_block` and `pow_parent` are unknown to the execution engine. - The parent of the block MUST NOT have an INVALID execution payload. -In addition to this change to validation, the consensus engine MUST be able to -ascertain, after import, which blocks returned `SYNCING` and which returned -`VALID`. +In addition to this change in validation, the consensus engine MUST track which +blocks returned `SYNCING` and which returned `VALID` for subsequent processing. Optimistically imported blocks MUST pass all verifications included in `process_block` (withstanding the modifications to `execute_payload`). From f4a125c89aaf1523322ff175ba32c169e0dbf493 Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Tue, 18 Jan 2022 11:36:36 +1100 Subject: [PATCH 69/75] Remove reorg section --- sync/optimistic.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/sync/optimistic.md b/sync/optimistic.md index dd9d428d4..67f2e3ee5 100644 --- a/sync/optimistic.md +++ b/sync/optimistic.md @@ -159,8 +159,7 @@ another chain. ### 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. +affect 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 From 0ec61bd195a308a81e2bf221346394284801c42a Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Tue, 18 Jan 2022 12:21:34 +1100 Subject: [PATCH 70/75] Clarify liveness --- sync/optimistic.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/sync/optimistic.md b/sync/optimistic.md index 67f2e3ee5..66ec8c312 100644 --- a/sync/optimistic.md +++ b/sync/optimistic.md @@ -293,7 +293,9 @@ 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. +the transition block will reject a block with a junk parent hash. Therefore, +liveness is unaffected if a vast majority of nodes have fully synced execution +and consensus clients before and during the transition. Given all of this, we can say two things: From bfe4172584b35a881020b01e7f781ad0f91e8011 Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Tue, 18 Jan 2022 12:26:30 +1100 Subject: [PATCH 71/75] Define `current_slot` --- sync/optimistic.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/sync/optimistic.md b/sync/optimistic.md index 66ec8c312..a66408b93 100644 --- a/sync/optimistic.md +++ b/sync/optimistic.md @@ -39,6 +39,9 @@ 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`). +Let `current_slot: Slot` be `(time - genesis_time) // SECONDS_PER_SLOT` where +`time` is the UNIX time according to the local system clock. + ```python @dataclass class OptimisticStore(object): From 24947be7c3446c142aa767fdbbb57b412a008b86 Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Tue, 18 Jan 2022 12:29:20 +1100 Subject: [PATCH 72/75] Rename should_optimistically_import... --- sync/optimistic.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sync/optimistic.md b/sync/optimistic.md index a66408b93..257574a62 100644 --- a/sync/optimistic.md +++ b/sync/optimistic.md @@ -71,7 +71,7 @@ def is_execution_block(block: BeaconBlock) -> BeaconBlock: ``` ```python -def should_optimistically_import_block(opt_store: OptimisticStore, current_slot: Slot, block: BeaconBlock) -> bool: +def is_optimistic_candidate_block(opt_store: OptimisticStore, current_slot: Slot, block: BeaconBlock) -> bool: justified_root = opt_store.block_states[opt_store.head_block_root].current_justified_checkpoint.root justifed_is_verified = is_execution_block(opt_store.blocks[justified_root]) block_is_deep = block.slot + SAFE_SLOTS_TO_IMPORT_OPTIMISTICALLY <= current_slot @@ -90,7 +90,7 @@ behaviours without regard for optimistic sync. ### When to optimistically import blocks A block MAY be optimistically imported when -`should_optimistically_import_block(opt_store, current_slot, block)` returns +`is_optimistic_candidate_block(opt_store, current_slot, block)` returns `True`. This ensures that blocks are only optimistically imported if either: 1. The justified checkpoint has execution enabled. From be4319a048926aeecb53d707488612b971bccde7 Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Tue, 18 Jan 2022 12:31:36 +1100 Subject: [PATCH 73/75] Rename `justified_is_verified` --- sync/optimistic.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sync/optimistic.md b/sync/optimistic.md index 257574a62..44d1fb616 100644 --- a/sync/optimistic.md +++ b/sync/optimistic.md @@ -73,9 +73,9 @@ def is_execution_block(block: BeaconBlock) -> BeaconBlock: ```python def is_optimistic_candidate_block(opt_store: OptimisticStore, current_slot: Slot, block: BeaconBlock) -> bool: justified_root = opt_store.block_states[opt_store.head_block_root].current_justified_checkpoint.root - justifed_is_verified = is_execution_block(opt_store.blocks[justified_root]) + justifed_is_execution_block = is_execution_block(opt_store.blocks[justified_root]) block_is_deep = block.slot + SAFE_SLOTS_TO_IMPORT_OPTIMISTICALLY <= current_slot - return justified_is_verified or block_is_deep + return justifed_is_execution_block or block_is_deep ``` Let only a node which returns `is_optimistic(opt_store, head) is True` be an *optimistic From 50b236e5ebbae2eecf60b867442a3df83a88d056 Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Tue, 18 Jan 2022 14:55:10 +1100 Subject: [PATCH 74/75] Address TERMINAL_BLOCK_HASH --- sync/optimistic.md | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/sync/optimistic.md b/sync/optimistic.md index 44d1fb616..01765112c 100644 --- a/sync/optimistic.md +++ b/sync/optimistic.md @@ -109,6 +109,7 @@ To optimistically import a block: engine returns `SYNCING` or `VALID`. An `INVALID` response MUST return `False`. - The [`validate_merge_block`][] function MUST NOT raise an assertion if both the `pow_block` and `pow_parent` are unknown to the execution engine. + - All other assertions in [`validate_merge_block`][] (e.g., `TERMINAL_BLOCK_HASH`) MUST prevent an optimistic import. - The parent of the block MUST NOT have an INVALID execution payload. In addition to this change in validation, the consensus engine MUST track which @@ -348,3 +349,13 @@ 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. + +## What if `TERMINAL_BLOCK_HASH` is used? + +If the terminal block hash override is used (i.e., `TERMINAL_BLOCK_HASH != +Hash32()`), the [`validate_merge_block`][] function will deterministically +return `True` or `False`. Whilst it's not *technically* required +retrospectively call [`validate_merge_block`][] on a transition block that +matches `TERMINAL_BLOCK_HASH` after an optimistic sync, doing so will have no +effect. For simplicity, the optimistic sync specification does not define +edge-case behaviour for when `TERMINAL_BLOCK_HASH` is used. From a5b3c91f250a8d6a057c3f4021c41199d41e4c21 Mon Sep 17 00:00:00 2001 From: Danny Ryan Date: Thu, 20 Jan 2022 09:25:02 -0700 Subject: [PATCH 75/75] build opimistic sync file and fix a minor lint/typing issue --- setup.py | 1 + sync/optimistic.md | 25 ++++++++++++++++--------- 2 files changed, 17 insertions(+), 9 deletions(-) diff --git a/setup.py b/setup.py index aa4157b29..888c16142 100644 --- a/setup.py +++ b/setup.py @@ -868,6 +868,7 @@ class PySpecCommand(Command): specs/bellatrix/fork.md specs/bellatrix/fork-choice.md specs/bellatrix/validator.md + sync/optimistic.md """ if len(self.md_doc_paths) == 0: raise Exception('no markdown files specified, and spec fork "%s" is unknown', self.spec_fork) diff --git a/sync/optimistic.md b/sync/optimistic.md index 01765112c..80e9de3e9 100644 --- a/sync/optimistic.md +++ b/sync/optimistic.md @@ -1,7 +1,8 @@ # Optimistic Sync -[`validate_merge_block`]: ../specs/bellatrix/fork-choice.md#validate_merge_block + +../specs/bellatrix/fork-choice.md#validate_merge_block ## Introduction In order to provide a syncing execution engine with a partial view of the head @@ -66,7 +67,7 @@ def latest_verified_ancestor(opt_store: OptimisticStore, block: BeaconBlock) -> ``` ```python -def is_execution_block(block: BeaconBlock) -> BeaconBlock: +def is_execution_block(block: BeaconBlock) -> bool: return block.body.execution_payload != ExecutionPayload() ``` @@ -106,10 +107,12 @@ these conditions.* To optimistically import a block: - The [`execute_payload`](../specs/bellatrix/beacon-chain.md#execute_payload) function MUST return `True` if the execution - engine returns `SYNCING` or `VALID`. An `INVALID` response MUST return `False`. -- The [`validate_merge_block`][] function MUST NOT raise an assertion if both the + engine returns `SYNCING` or `VALID`. An `INVALID` response MUST return `False`. +- The [`validate_merge_block`](../specs/bellatrix/fork-choice.md#validate_merge_block) + function MUST NOT raise an assertion if both the `pow_block` and `pow_parent` are unknown to the execution engine. - - All other assertions in [`validate_merge_block`][] (e.g., `TERMINAL_BLOCK_HASH`) MUST prevent an optimistic import. + - All other assertions in [`validate_merge_block`](../specs/bellatrix/fork-choice.md#validate_merge_block) + (e.g., `TERMINAL_BLOCK_HASH`) MUST prevent an optimistic import. - The parent of the block MUST NOT have an INVALID execution payload. In addition to this change in validation, the consensus engine MUST track which @@ -137,8 +140,10 @@ When a block transitions from the `SYNCING` state, it is removed from the set of When a "merge block" (i.e. the first block which enables execution in a chain) 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 +[`validate_merge_block`](../specs/bellatrix/fork-choice.md#validate_merge_block) +MUST be run against the merge block. If the block +fails [`validate_merge_block`](../specs/bellatrix/fork-choice.md#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). @@ -353,9 +358,11 @@ optimistic sync all together. ## What if `TERMINAL_BLOCK_HASH` is used? If the terminal block hash override is used (i.e., `TERMINAL_BLOCK_HASH != -Hash32()`), the [`validate_merge_block`][] function will deterministically +Hash32()`), the [`validate_merge_block`](../specs/bellatrix/fork-choice.md#validate_merge_block) +function will deterministically return `True` or `False`. Whilst it's not *technically* required -retrospectively call [`validate_merge_block`][] on a transition block that +retrospectively call [`validate_merge_block`](../specs/bellatrix/fork-choice.md#validate_merge_block) +on a transition block that matches `TERMINAL_BLOCK_HASH` after an optimistic sync, doing so will have no effect. For simplicity, the optimistic sync specification does not define edge-case behaviour for when `TERMINAL_BLOCK_HASH` is used.