From 25f2efab198ee41dd31bcd4500eb5cc39dab4a70 Mon Sep 17 00:00:00 2001 From: vbuterin Date: Fri, 26 Nov 2021 15:11:19 -0600 Subject: [PATCH 01/46] Simplify sync protocol and update to calculate optimistic heads 1. Simplify `valid_updates` to `best_valid_update` so the `LightClientStore` only needs to store O(1) data 2. Track an optimistic head, by looking for the highest-slot header which passes a safety threshold --- specs/altair/sync-protocol.md | 88 ++++++++++++++++++++++++++++------- 1 file changed, 71 insertions(+), 17 deletions(-) diff --git a/specs/altair/sync-protocol.md b/specs/altair/sync-protocol.md index 24c35f891..b309b61f4 100644 --- a/specs/altair/sync-protocol.md +++ b/specs/altair/sync-protocol.md @@ -18,7 +18,10 @@ - [`LightClientStore`](#lightclientstore) - [Helper functions](#helper-functions) - [`get_subtree_index`](#get_subtree_index) + - [`get_signed_header`](#get_signed_header) + - [`get_safety_threshold`](#get_safety_threshold) - [Light client state updates](#light-client-state-updates) + - [`process_slot`](#process_slot) - [`validate_light_client_update`](#validate_light_client_update) - [`apply_light_client_update`](#apply_light_client_update) - [`process_light_client_update`](#process_light_client_update) @@ -47,9 +50,10 @@ uses sync committees introduced in [this beacon chain extension](./beacon-chain. ### Misc -| Name | Value | -| - | - | -| `MIN_SYNC_COMMITTEE_PARTICIPANTS` | `1` | +| Name | Value | Notes | +| - | - | - | +| `MIN_SYNC_COMMITTEE_PARTICIPANTS` | `1` | | +| `SAFETY_THRESHOLD_CALCULATION_PERIOD` | `4096` | ~13.6 hours | ## Containers @@ -86,10 +90,12 @@ class LightClientUpdate(Container): ### `LightClientStore` ```python -@dataclass class LightClientStore(object): snapshot: LightClientSnapshot - valid_updates: Set[LightClientUpdate] + best_valid_update: Optional[LightClientUpdate] + optimistic_header: BeaconBlockHeader + previous_period_max_attendance: uint64 + current_period_max_attendance: uint64 ``` ## Helper functions @@ -101,9 +107,38 @@ def get_subtree_index(generalized_index: GeneralizedIndex) -> uint64: return uint64(generalized_index % 2**(floorlog2(generalized_index))) ``` +### `get_signed_header` + +```python +def get_signed_header(update: LightClientUpdate): + if update.finality_header is None: + return update.header + else: + return update.finality_header +``` + +### `get_safety_threshold` + +```python +def get_safety_threshold(store: LightClientStore): + return max( + store.previous_period_max_attendance, + store.current_period_max_attendance + ) // 2 +``` + ## Light client state updates -A light client maintains its state in a `store` object of type `LightClientStore` and receives `update` objects of type `LightClientUpdate`. Every `update` triggers `process_light_client_update(store, update, current_slot)` where `current_slot` is the current slot based on some local clock. +A light client maintains its state in a `store` object of type `LightClientStore` and receives `update` objects of type `LightClientUpdate`. Every `update` triggers `process_light_client_update(store, update, current_slot)` where `current_slot` is the current slot based on some local clock. `process_slot` is processed every time the current slot increments. + +### `process_slot` + +```python +def process_slot(store: LightClientStore, current_slot: Slot): + if current_slot % SAFETY_THRESHOLD_CALCULATION_PERIOD == 0: + store.previous_period_max_attendance = store.current_period_max_attendance + store.current_period_max_attendance = 0 +``` #### `validate_light_client_update` @@ -172,24 +207,43 @@ def apply_light_client_update(snapshot: LightClientSnapshot, update: LightClient #### `process_light_client_update` ```python -def process_light_client_update(store: LightClientStore, update: LightClientUpdate, current_slot: Slot, +def process_light_client_update(store: LightClientStore, + update: LightClientUpdate, + current_slot: Slot, genesis_validators_root: Root) -> None: + validate_light_client_update(store.snapshot, update, genesis_validators_root) - store.valid_updates.add(update) - - update_timeout = SLOTS_PER_EPOCH * EPOCHS_PER_SYNC_COMMITTEE_PERIOD + + # Update the best update in case we have to force-update to it if the timeout elapses + if ( + sum(update.sync_committee_bits) > sum(store.best_finalization_update.sync_committee_bits) and + get_signed_header(update).slot > store.snapshot.header.slot + ): + store.best_finalization_update = update + + # Track the maximum attendance in the committee signatures + store.current_period_max_attendance = max( + store.current_period_max_attendance, + update.sync_committee_bits.count(1) + ) + + # Update the optimistic header + if ( + sum(update.sync_committee_bits) > get_safety_threshold(store) and + update.header.slot > store.optimistic_header.slot + ): + store.optimistic_header = update.header + + # Update finalized header if ( sum(update.sync_committee_bits) * 3 >= len(update.sync_committee_bits) * 2 and update.finality_header != BeaconBlockHeader() ): - # Apply update if (1) 2/3 quorum is reached and (2) we have a finality proof. - # Note that (2) means that the current light client design needs finality. - # It may be changed to re-organizable light client design. See the on-going issue consensus-specs#2182. + # Normal update through 2/3 threshold apply_light_client_update(store.snapshot, update) - store.valid_updates = set() + store.best_valid_update = None elif current_slot > store.snapshot.header.slot + update_timeout: # Forced best update when the update timeout has elapsed - apply_light_client_update(store.snapshot, - max(store.valid_updates, key=lambda update: sum(update.sync_committee_bits))) - store.valid_updates = set() + apply_light_client_update(store.snapshot, store.best_valid_update) + store.best_valid_update = None ``` From 013e814d2d2e0c9f46eb31e36f9f7e27c2cdf39e Mon Sep 17 00:00:00 2001 From: vbuterin Date: Fri, 26 Nov 2021 15:32:37 -0600 Subject: [PATCH 02/46] Update sync-protocol.md --- specs/altair/sync-protocol.md | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/specs/altair/sync-protocol.md b/specs/altair/sync-protocol.md index b309b61f4..b077d5021 100644 --- a/specs/altair/sync-protocol.md +++ b/specs/altair/sync-protocol.md @@ -156,10 +156,8 @@ def validate_light_client_update(snapshot: LightClientSnapshot, # Verify update header root is the finalized root of the finality header, if specified if update.finality_header == BeaconBlockHeader(): - signed_header = update.header assert update.finality_branch == [Bytes32() for _ in range(floorlog2(FINALIZED_ROOT_INDEX))] else: - signed_header = update.finality_header assert is_valid_merkle_branch( leaf=hash_tree_root(update.header), branch=update.finality_branch, @@ -188,7 +186,7 @@ def validate_light_client_update(snapshot: LightClientSnapshot, # Verify sync committee aggregate signature participant_pubkeys = [pubkey for (bit, pubkey) in zip(update.sync_committee_bits, sync_committee.pubkeys) if bit] domain = compute_domain(DOMAIN_SYNC_COMMITTEE, update.fork_version, genesis_validators_root) - signing_root = compute_signing_root(signed_header, domain) + signing_root = compute_signing_root(get_signed_header(update), domain) assert bls.FastAggregateVerify(participant_pubkeys, signing_root, update.sync_committee_signature) ``` From e1041649608be5fafe58f42a726948ca64064170 Mon Sep 17 00:00:00 2001 From: vbuterin Date: Sat, 27 Nov 2021 07:25:27 -0600 Subject: [PATCH 03/46] Rework data structures (#2747) 1. Replace `header` and `finality_header` with `attested_header` (always the header signed by the committee) and `finailzed_header` (always the header verified by the Merkle branch) 2. Remove `LightClientSnapshot`, fold its fields into `LightClientStore` for simplicity --- specs/altair/sync-protocol.md | 115 +++++++++++++++++----------------- 1 file changed, 56 insertions(+), 59 deletions(-) diff --git a/specs/altair/sync-protocol.md b/specs/altair/sync-protocol.md index b077d5021..f90469f98 100644 --- a/specs/altair/sync-protocol.md +++ b/specs/altair/sync-protocol.md @@ -13,7 +13,6 @@ - [Preset](#preset) - [Misc](#misc) - [Containers](#containers) - - [`LightClientSnapshot`](#lightclientsnapshot) - [`LightClientUpdate`](#lightclientupdate) - [`LightClientStore`](#lightclientstore) - [Helper functions](#helper-functions) @@ -22,6 +21,7 @@ - [`get_safety_threshold`](#get_safety_threshold) - [Light client state updates](#light-client-state-updates) - [`process_slot`](#process_slot) + - [`get_active_header`](#get_active_header) - [`validate_light_client_update`](#validate_light_client_update) - [`apply_light_client_update`](#apply_light_client_update) - [`process_light_client_update`](#process_light_client_update) @@ -57,28 +57,17 @@ uses sync committees introduced in [this beacon chain extension](./beacon-chain. ## Containers -### `LightClientSnapshot` - -```python -class LightClientSnapshot(Container): - # Beacon block header - header: BeaconBlockHeader - # Sync committees corresponding to the header - current_sync_committee: SyncCommittee - next_sync_committee: SyncCommittee -``` - ### `LightClientUpdate` ```python class LightClientUpdate(Container): - # Update beacon block header - header: BeaconBlockHeader + # The beacon block header that is attested to by the sync committee + attested_header: BeaconBlockHeader # Next sync committee corresponding to the header next_sync_committee: SyncCommittee next_sync_committee_branch: Vector[Bytes32, floorlog2(NEXT_SYNC_COMMITTEE_INDEX)] - # Finality proof for the update header - finality_header: BeaconBlockHeader + # The finalized beacon block header attested to by Merkle branch + finalized_header: BeaconBlockHeader finality_branch: Vector[Bytes32, floorlog2(FINALIZED_ROOT_INDEX)] # Sync committee aggregate signature sync_committee_bits: Bitvector[SYNC_COMMITTEE_SIZE] @@ -91,9 +80,16 @@ class LightClientUpdate(Container): ```python class LightClientStore(object): - snapshot: LightClientSnapshot + # Beacon block header that is finalized + finalized_header: BeaconBlockHeader + # Sync committees corresponding to the header + current_sync_committee: SyncCommittee + next_sync_committee: SyncCommittee + # Best available header to switch finalized head to if we see nothing else best_valid_update: Optional[LightClientUpdate] + # Most recent available reasonably-safe header optimistic_header: BeaconBlockHeader + # Max number of participants in a sync committee (used to calculate safety threshold) previous_period_max_attendance: uint64 current_period_max_attendance: uint64 ``` @@ -107,16 +103,6 @@ def get_subtree_index(generalized_index: GeneralizedIndex) -> uint64: return uint64(generalized_index % 2**(floorlog2(generalized_index))) ``` -### `get_signed_header` - -```python -def get_signed_header(update: LightClientUpdate): - if update.finality_header is None: - return update.header - else: - return update.finality_header -``` - ### `get_safety_threshold` ```python @@ -140,44 +126,57 @@ def process_slot(store: LightClientStore, current_slot: Slot): store.current_period_max_attendance = 0 ``` +### `get_active_header` + +```python +def get_active_header(update: LightClientUpdate) -> BeaconBlockHeader: + # Is the update trying to convince us of a finalized header or an optimistic header? + if update.finalized_header BeaconBlockHeader(): + return update.finalized_header + else: + return update.attested_header +``` + #### `validate_light_client_update` ```python -def validate_light_client_update(snapshot: LightClientSnapshot, +def validate_light_client_update(store: LightClientStore, update: LightClientUpdate, genesis_validators_root: Root) -> None: - # Verify update slot is larger than snapshot slot - assert update.header.slot > snapshot.header.slot + + # Verify update slot is larger than slot of current best finalized header + active_header = get_active_header(update) + assert active_header.slot > store.finalized_header.slot # Verify update does not skip a sync committee period - snapshot_period = compute_epoch_at_slot(snapshot.header.slot) // EPOCHS_PER_SYNC_COMMITTEE_PERIOD - update_period = compute_epoch_at_slot(update.header.slot) // EPOCHS_PER_SYNC_COMMITTEE_PERIOD - assert update_period in (snapshot_period, snapshot_period + 1) + finalized_period = compute_epoch_at_slot(store.finalized_header.slot) // EPOCHS_PER_SYNC_COMMITTEE_PERIOD + update_period = compute_epoch_at_slot(active_header.slot) // EPOCHS_PER_SYNC_COMMITTEE_PERIOD + assert update_period in (finalized_period, finalized_period + 1) # Verify update header root is the finalized root of the finality header, if specified - if update.finality_header == BeaconBlockHeader(): + if update.finalized_header == BeaconBlockHeader(): assert update.finality_branch == [Bytes32() for _ in range(floorlog2(FINALIZED_ROOT_INDEX))] else: assert is_valid_merkle_branch( - leaf=hash_tree_root(update.header), + leaf=hash_tree_root(update.finalized_header), branch=update.finality_branch, depth=floorlog2(FINALIZED_ROOT_INDEX), index=get_subtree_index(FINALIZED_ROOT_INDEX), - root=update.finality_header.state_root, + root=update.attested_header.state_root, ) # Verify update next sync committee if the update period incremented - if update_period == snapshot_period: - sync_committee = snapshot.current_sync_committee + if update_period == finalized_period: + sync_committee = store.current_sync_committee assert update.next_sync_committee_branch == [Bytes32() for _ in range(floorlog2(NEXT_SYNC_COMMITTEE_INDEX))] else: - sync_committee = snapshot.next_sync_committee + sync_committee = store.next_sync_committee assert is_valid_merkle_branch( leaf=hash_tree_root(update.next_sync_committee), branch=update.next_sync_committee_branch, depth=floorlog2(NEXT_SYNC_COMMITTEE_INDEX), index=get_subtree_index(NEXT_SYNC_COMMITTEE_INDEX), - root=update.header.state_root, + root=active_header.state_root, ) # Verify sync committee has sufficient participants @@ -186,20 +185,21 @@ def validate_light_client_update(snapshot: LightClientSnapshot, # Verify sync committee aggregate signature participant_pubkeys = [pubkey for (bit, pubkey) in zip(update.sync_committee_bits, sync_committee.pubkeys) if bit] domain = compute_domain(DOMAIN_SYNC_COMMITTEE, update.fork_version, genesis_validators_root) - signing_root = compute_signing_root(get_signed_header(update), domain) + signing_root = compute_signing_root(update.attested_header, domain) assert bls.FastAggregateVerify(participant_pubkeys, signing_root, update.sync_committee_signature) ``` #### `apply_light_client_update` ```python -def apply_light_client_update(snapshot: LightClientSnapshot, update: LightClientUpdate) -> None: - snapshot_period = compute_epoch_at_slot(snapshot.header.slot) // EPOCHS_PER_SYNC_COMMITTEE_PERIOD - update_period = compute_epoch_at_slot(update.header.slot) // EPOCHS_PER_SYNC_COMMITTEE_PERIOD - if update_period == snapshot_period + 1: - snapshot.current_sync_committee = snapshot.next_sync_committee - snapshot.next_sync_committee = update.next_sync_committee - snapshot.header = update.header +def apply_light_client_update(store: LightClientStore, update: LightClientUpdate) -> None: + active_header = get_active_header(update) + finalized_period = compute_epoch_at_slot(store.finalized_header.slot) // EPOCHS_PER_SYNC_COMMITTEE_PERIOD + update_period = compute_epoch_at_slot(active_header.slot) // EPOCHS_PER_SYNC_COMMITTEE_PERIOD + if update_period == finalized_period + 1: + store.current_sync_committee = store.next_sync_committee + store.next_sync_committee = update.next_sync_committee + store.finalized_header = active_header ``` #### `process_light_client_update` @@ -210,13 +210,10 @@ def process_light_client_update(store: LightClientStore, current_slot: Slot, genesis_validators_root: Root) -> None: - validate_light_client_update(store.snapshot, update, genesis_validators_root) + validate_light_client_update(store, update, genesis_validators_root) # Update the best update in case we have to force-update to it if the timeout elapses - if ( - sum(update.sync_committee_bits) > sum(store.best_finalization_update.sync_committee_bits) and - get_signed_header(update).slot > store.snapshot.header.slot - ): + if sum(update.sync_committee_bits) > sum(store.best_finalization_update.sync_committee_bits): store.best_finalization_update = update # Track the maximum attendance in the committee signatures @@ -228,20 +225,20 @@ def process_light_client_update(store: LightClientStore, # Update the optimistic header if ( sum(update.sync_committee_bits) > get_safety_threshold(store) and - update.header.slot > store.optimistic_header.slot + update.attested_header.slot > store.optimistic_header.slot ): - store.optimistic_header = update.header + store.optimistic_header = update.attested_header # Update finalized header if ( sum(update.sync_committee_bits) * 3 >= len(update.sync_committee_bits) * 2 - and update.finality_header != BeaconBlockHeader() + and update.finalized_header != BeaconBlockHeader() ): # Normal update through 2/3 threshold - apply_light_client_update(store.snapshot, update) + apply_light_client_update(store, update) store.best_valid_update = None - elif current_slot > store.snapshot.header.slot + update_timeout: + elif current_slot > store.finalized_header.slot + update_timeout: # Forced best update when the update timeout has elapsed - apply_light_client_update(store.snapshot, store.best_valid_update) + apply_light_client_update(store, store.best_valid_update) store.best_valid_update = None ``` From 77188726de0b71a89afe40e5e5c97c85a96a163c Mon Sep 17 00:00:00 2001 From: vbuterin Date: Sat, 27 Nov 2021 07:27:16 -0600 Subject: [PATCH 04/46] Fixed ToC and get_active_header positioninf --- specs/altair/sync-protocol.md | 25 ++++++++++++------------- 1 file changed, 12 insertions(+), 13 deletions(-) diff --git a/specs/altair/sync-protocol.md b/specs/altair/sync-protocol.md index f90469f98..ca7aa7451 100644 --- a/specs/altair/sync-protocol.md +++ b/specs/altair/sync-protocol.md @@ -17,11 +17,10 @@ - [`LightClientStore`](#lightclientstore) - [Helper functions](#helper-functions) - [`get_subtree_index`](#get_subtree_index) - - [`get_signed_header`](#get_signed_header) + - [`get_active_header`](#get_active_header) - [`get_safety_threshold`](#get_safety_threshold) - [Light client state updates](#light-client-state-updates) - [`process_slot`](#process_slot) - - [`get_active_header`](#get_active_header) - [`validate_light_client_update`](#validate_light_client_update) - [`apply_light_client_update`](#apply_light_client_update) - [`process_light_client_update`](#process_light_client_update) @@ -103,6 +102,17 @@ def get_subtree_index(generalized_index: GeneralizedIndex) -> uint64: return uint64(generalized_index % 2**(floorlog2(generalized_index))) ``` +### `get_active_header` + +```python +def get_active_header(update: LightClientUpdate) -> BeaconBlockHeader: + # Is the update trying to convince us of a finalized header or an optimistic header? + if update.finalized_header BeaconBlockHeader(): + return update.finalized_header + else: + return update.attested_header +``` + ### `get_safety_threshold` ```python @@ -126,17 +136,6 @@ def process_slot(store: LightClientStore, current_slot: Slot): store.current_period_max_attendance = 0 ``` -### `get_active_header` - -```python -def get_active_header(update: LightClientUpdate) -> BeaconBlockHeader: - # Is the update trying to convince us of a finalized header or an optimistic header? - if update.finalized_header BeaconBlockHeader(): - return update.finalized_header - else: - return update.attested_header -``` - #### `validate_light_client_update` ```python From c4f70970c41205b874c289f2f4f2e2554a18f78e Mon Sep 17 00:00:00 2001 From: vbuterin Date: Sun, 28 Nov 2021 08:31:48 -0600 Subject: [PATCH 05/46] Update specs/altair/sync-protocol.md Co-authored-by: terence tsao --- specs/altair/sync-protocol.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/specs/altair/sync-protocol.md b/specs/altair/sync-protocol.md index ca7aa7451..dd61b6545 100644 --- a/specs/altair/sync-protocol.md +++ b/specs/altair/sync-protocol.md @@ -107,7 +107,7 @@ def get_subtree_index(generalized_index: GeneralizedIndex) -> uint64: ```python def get_active_header(update: LightClientUpdate) -> BeaconBlockHeader: # Is the update trying to convince us of a finalized header or an optimistic header? - if update.finalized_header BeaconBlockHeader(): + if update.finalized_header != BeaconBlockHeader(): return update.finalized_header else: return update.attested_header From 06af6296c5727e4e0faedb54da0938ecfc75aae7 Mon Sep 17 00:00:00 2001 From: vbuterin Date: Mon, 29 Nov 2021 07:04:05 -0600 Subject: [PATCH 06/46] Updated in response to comments --- specs/altair/sync-protocol.md | 40 +++++++++++++++++++---------------- 1 file changed, 22 insertions(+), 18 deletions(-) diff --git a/specs/altair/sync-protocol.md b/specs/altair/sync-protocol.md index dd61b6545..6ce46bf6b 100644 --- a/specs/altair/sync-protocol.md +++ b/specs/altair/sync-protocol.md @@ -52,7 +52,8 @@ uses sync committees introduced in [this beacon chain extension](./beacon-chain. | Name | Value | Notes | | - | - | - | | `MIN_SYNC_COMMITTEE_PARTICIPANTS` | `1` | | -| `SAFETY_THRESHOLD_CALCULATION_PERIOD` | `4096` | ~13.6 hours | +| `SAFETY_THRESHOLD_PERIOD` | `SLOTS_PER_EPOCH * EPOCHS_PER_SYNC_COMMITTEE_PERIOD` | ~13.6 hours | +| `UPDATE_TIMEOUT` | `SLOTS_PER_EPOCH * EPOCHS_PER_SYNC_COMMITTEE_PERIOD` | ~27.3 hours | ## Containers @@ -88,9 +89,9 @@ class LightClientStore(object): best_valid_update: Optional[LightClientUpdate] # Most recent available reasonably-safe header optimistic_header: BeaconBlockHeader - # Max number of participants in a sync committee (used to calculate safety threshold) - previous_period_max_attendance: uint64 - current_period_max_attendance: uint64 + # Max number of active participants in a sync committee (used to calculate safety threshold) + previous_max_active_participants: uint64 + current_max_active_participants: uint64 ``` ## Helper functions @@ -106,7 +107,9 @@ def get_subtree_index(generalized_index: GeneralizedIndex) -> uint64: ```python def get_active_header(update: LightClientUpdate) -> BeaconBlockHeader: - # Is the update trying to convince us of a finalized header or an optimistic header? + # The "active header" is the header that the update is trying to convince us + # to accept. If a finalized header is present, it's the finalized header, + # otherwise it's the attested header if update.finalized_header != BeaconBlockHeader(): return update.finalized_header else: @@ -118,8 +121,8 @@ def get_active_header(update: LightClientUpdate) -> BeaconBlockHeader: ```python def get_safety_threshold(store: LightClientStore): return max( - store.previous_period_max_attendance, - store.current_period_max_attendance + store.previous_max_active_participants, + store.current_max_active_participants ) // 2 ``` @@ -131,9 +134,9 @@ A light client maintains its state in a `store` object of type `LightClientStore ```python def process_slot(store: LightClientStore, current_slot: Slot): - if current_slot % SAFETY_THRESHOLD_CALCULATION_PERIOD == 0: - store.previous_period_max_attendance = store.current_period_max_attendance - store.current_period_max_attendance = 0 + if current_slot % SAFETY_THRESHOLD_PERIOD == 0: + store.previous_max_active_participants = store.current_max_active_participants + store.current_max_active_participants = 0 ``` #### `validate_light_client_update` @@ -141,11 +144,12 @@ def process_slot(store: LightClientStore, current_slot: Slot): ```python def validate_light_client_update(store: LightClientStore, update: LightClientUpdate, + current_slot: Slot, genesis_validators_root: Root) -> None: # Verify update slot is larger than slot of current best finalized header active_header = get_active_header(update) - assert active_header.slot > store.finalized_header.slot + assert current_slot >= active_header.slot > store.finalized_header.slot # Verify update does not skip a sync committee period finalized_period = compute_epoch_at_slot(store.finalized_header.slot) // EPOCHS_PER_SYNC_COMMITTEE_PERIOD @@ -209,15 +213,15 @@ def process_light_client_update(store: LightClientStore, current_slot: Slot, genesis_validators_root: Root) -> None: - validate_light_client_update(store, update, genesis_validators_root) + validate_light_client_update(store, update, current_slot, genesis_validators_root) # Update the best update in case we have to force-update to it if the timeout elapses - if sum(update.sync_committee_bits) > sum(store.best_finalization_update.sync_committee_bits): - store.best_finalization_update = update + if sum(update.sync_committee_bits) > sum(store.best_valid_update.sync_committee_bits): + store.best_valid_update = update - # Track the maximum attendance in the committee signatures - store.current_period_max_attendance = max( - store.current_period_max_attendance, + # Track the maximum numebr of active participants in the committee signatures + store.current_max_active_participants = max( + store.current_max_active_participants, update.sync_committee_bits.count(1) ) @@ -236,7 +240,7 @@ def process_light_client_update(store: LightClientStore, # Normal update through 2/3 threshold apply_light_client_update(store, update) store.best_valid_update = None - elif current_slot > store.finalized_header.slot + update_timeout: + elif current_slot > store.finalized_header.slot + UPDATE_TIMEOUT: # Forced best update when the update timeout has elapsed apply_light_client_update(store, store.best_valid_update) store.best_valid_update = None From 6fa19705fb5f1414592d6f3cb66117a738285950 Mon Sep 17 00:00:00 2001 From: vbuterin Date: Mon, 29 Nov 2021 07:05:01 -0600 Subject: [PATCH 07/46] Clarified next sync committee comment --- specs/altair/sync-protocol.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/specs/altair/sync-protocol.md b/specs/altair/sync-protocol.md index 6ce46bf6b..0af20cbca 100644 --- a/specs/altair/sync-protocol.md +++ b/specs/altair/sync-protocol.md @@ -63,7 +63,7 @@ uses sync committees introduced in [this beacon chain extension](./beacon-chain. class LightClientUpdate(Container): # The beacon block header that is attested to by the sync committee attested_header: BeaconBlockHeader - # Next sync committee corresponding to the header + # Next sync committee corresponding to the active header next_sync_committee: SyncCommittee next_sync_committee_branch: Vector[Bytes32, floorlog2(NEXT_SYNC_COMMITTEE_INDEX)] # The finalized beacon block header attested to by Merkle branch From 7de1495a42f89a876fb84ac1701a4cbe3e3eab6b Mon Sep 17 00:00:00 2001 From: Hsiao-Wei Wang Date: Tue, 30 Nov 2021 20:38:42 +0800 Subject: [PATCH 08/46] Fix lint (#2750) --- presets/mainnet/altair.yaml | 4 ++++ presets/minimal/altair.yaml | 4 ++++ setup.py | 1 + specs/altair/sync-protocol.md | 25 ++++++++++++++++--------- 4 files changed, 25 insertions(+), 9 deletions(-) diff --git a/presets/mainnet/altair.yaml b/presets/mainnet/altair.yaml index 9a17b7803..21e3cc328 100644 --- a/presets/mainnet/altair.yaml +++ b/presets/mainnet/altair.yaml @@ -22,3 +22,7 @@ EPOCHS_PER_SYNC_COMMITTEE_PERIOD: 256 # --------------------------------------------------------------- # 1 MIN_SYNC_COMMITTEE_PARTICIPANTS: 1 +# SLOTS_PER_EPOCH * EPOCHS_PER_SYNC_COMMITTEE_PERIOD (= 32 * 256) +UPDATE_TIMEOUT: 8192 +# SLOTS_PER_EPOCH * EPOCHS_PER_SYNC_COMMITTEE_PERIOD // 2 (= 32 * 256 // 2) +SAFETY_THRESHOLD_PERIOD: 4096 diff --git a/presets/minimal/altair.yaml b/presets/minimal/altair.yaml index 88d78bea3..7cdbd58ea 100644 --- a/presets/minimal/altair.yaml +++ b/presets/minimal/altair.yaml @@ -22,3 +22,7 @@ EPOCHS_PER_SYNC_COMMITTEE_PERIOD: 8 # --------------------------------------------------------------- # 1 MIN_SYNC_COMMITTEE_PARTICIPANTS: 1 +# SLOTS_PER_EPOCH * EPOCHS_PER_SYNC_COMMITTEE_PERIOD (= 8 * 8) +UPDATE_TIMEOUT: 64 +# SLOTS_PER_EPOCH * EPOCHS_PER_SYNC_COMMITTEE_PERIOD // 2 (= 8 * 8 // 2) +SAFETY_THRESHOLD_PERIOD: 32 diff --git a/setup.py b/setup.py index 0ced87be2..7b74fc155 100644 --- a/setup.py +++ b/setup.py @@ -683,6 +683,7 @@ ignored_dependencies = [ 'uint8', 'uint16', 'uint32', 'uint64', 'uint128', 'uint256', 'bytes', 'byte', 'ByteList', 'ByteVector', 'Dict', 'dict', 'field', 'ceillog2', 'floorlog2', 'Set', + 'Optional', ] diff --git a/specs/altair/sync-protocol.md b/specs/altair/sync-protocol.md index 0af20cbca..ce7ae6252 100644 --- a/specs/altair/sync-protocol.md +++ b/specs/altair/sync-protocol.md @@ -52,8 +52,8 @@ uses sync committees introduced in [this beacon chain extension](./beacon-chain. | Name | Value | Notes | | - | - | - | | `MIN_SYNC_COMMITTEE_PARTICIPANTS` | `1` | | -| `SAFETY_THRESHOLD_PERIOD` | `SLOTS_PER_EPOCH * EPOCHS_PER_SYNC_COMMITTEE_PERIOD` | ~13.6 hours | | `UPDATE_TIMEOUT` | `SLOTS_PER_EPOCH * EPOCHS_PER_SYNC_COMMITTEE_PERIOD` | ~27.3 hours | +| `SAFETY_THRESHOLD_PERIOD` | `SLOTS_PER_EPOCH * EPOCHS_PER_SYNC_COMMITTEE_PERIOD // 2` | ~13.6 hours | ## Containers @@ -79,6 +79,7 @@ class LightClientUpdate(Container): ### `LightClientStore` ```python +@dataclass class LightClientStore(object): # Beacon block header that is finalized finalized_header: BeaconBlockHeader @@ -119,7 +120,7 @@ def get_active_header(update: LightClientUpdate) -> BeaconBlockHeader: ### `get_safety_threshold` ```python -def get_safety_threshold(store: LightClientStore): +def get_safety_threshold(store: LightClientStore) -> uint64: return max( store.previous_max_active_participants, store.current_max_active_participants @@ -130,10 +131,10 @@ def get_safety_threshold(store: LightClientStore): A light client maintains its state in a `store` object of type `LightClientStore` and receives `update` objects of type `LightClientUpdate`. Every `update` triggers `process_light_client_update(store, update, current_slot)` where `current_slot` is the current slot based on some local clock. `process_slot` is processed every time the current slot increments. -### `process_slot` +#### `process_slot` ```python -def process_slot(store: LightClientStore, current_slot: Slot): +def process_slot_for_light_client_store(store: LightClientStore, current_slot: Slot) -> None: if current_slot % SAFETY_THRESHOLD_PERIOD == 0: store.previous_max_active_participants = store.current_max_active_participants store.current_max_active_participants = 0 @@ -216,13 +217,16 @@ def process_light_client_update(store: LightClientStore, validate_light_client_update(store, update, current_slot, genesis_validators_root) # Update the best update in case we have to force-update to it if the timeout elapses - if sum(update.sync_committee_bits) > sum(store.best_valid_update.sync_committee_bits): + if ( + store.best_valid_update is None + or sum(update.sync_committee_bits) > sum(store.best_valid_update.sync_committee_bits) + ): store.best_valid_update = update - # Track the maximum numebr of active participants in the committee signatures + # Track the maximum number of active participants in the committee signatures store.current_max_active_participants = max( - store.current_max_active_participants, - update.sync_committee_bits.count(1) + store.current_max_active_participants, + update.sync_committee_bits.count(1), ) # Update the optimistic header @@ -240,7 +244,10 @@ def process_light_client_update(store: LightClientStore, # Normal update through 2/3 threshold apply_light_client_update(store, update) store.best_valid_update = None - elif current_slot > store.finalized_header.slot + UPDATE_TIMEOUT: + elif ( + current_slot > store.finalized_header.slot + UPDATE_TIMEOUT + and store.best_valid_update is not None + ): # Forced best update when the update timeout has elapsed apply_light_client_update(store, store.best_valid_update) store.best_valid_update = None From c30662b696e5ae3e8c4b0afb0bcfc736bbf547ed Mon Sep 17 00:00:00 2001 From: vbuterin Date: Tue, 30 Nov 2021 06:39:25 -0600 Subject: [PATCH 09/46] Consistently use sum instead of count(1) --- specs/altair/sync-protocol.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/specs/altair/sync-protocol.md b/specs/altair/sync-protocol.md index ce7ae6252..401effc16 100644 --- a/specs/altair/sync-protocol.md +++ b/specs/altair/sync-protocol.md @@ -226,7 +226,7 @@ def process_light_client_update(store: LightClientStore, # Track the maximum number of active participants in the committee signatures store.current_max_active_participants = max( store.current_max_active_participants, - update.sync_committee_bits.count(1), + sum(update.sync_committee_bits), ) # Update the optimistic header From 402c663b51805f725955356f5b9091502bb0505b Mon Sep 17 00:00:00 2001 From: Hsiao-Wei Wang Date: Tue, 30 Nov 2021 21:57:43 +0800 Subject: [PATCH 10/46] Fix function name leftover --- specs/altair/sync-protocol.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/specs/altair/sync-protocol.md b/specs/altair/sync-protocol.md index 401effc16..8da4a4d98 100644 --- a/specs/altair/sync-protocol.md +++ b/specs/altair/sync-protocol.md @@ -20,7 +20,7 @@ - [`get_active_header`](#get_active_header) - [`get_safety_threshold`](#get_safety_threshold) - [Light client state updates](#light-client-state-updates) - - [`process_slot`](#process_slot) + - [`process_slot_for_light_client_store`](#process_slot_for_light_client_store) - [`validate_light_client_update`](#validate_light_client_update) - [`apply_light_client_update`](#apply_light_client_update) - [`process_light_client_update`](#process_light_client_update) @@ -129,9 +129,9 @@ def get_safety_threshold(store: LightClientStore) -> uint64: ## Light client state updates -A light client maintains its state in a `store` object of type `LightClientStore` and receives `update` objects of type `LightClientUpdate`. Every `update` triggers `process_light_client_update(store, update, current_slot)` where `current_slot` is the current slot based on some local clock. `process_slot` is processed every time the current slot increments. +A light client maintains its state in a `store` object of type `LightClientStore` and receives `update` objects of type `LightClientUpdate`. Every `update` triggers `process_light_client_update(store, update, current_slot)` where `current_slot` is the current slot based on some local clock. `process_slot_for_light_client_store` is processed every time the current slot increments. -#### `process_slot` +#### `process_slot_for_light_client_store` ```python def process_slot_for_light_client_store(store: LightClientStore, current_slot: Slot) -> None: From d7f6a42729a7e734a51d3e5434ebe194f5edddaf Mon Sep 17 00:00:00 2001 From: Hsiao-Wei Wang Date: Tue, 30 Nov 2021 23:55:03 +0800 Subject: [PATCH 11/46] [WIP] Add ex-ante fork choice test cases --- specs/phase0/fork-choice.md | 1 + tests/core/pyspec/eth2spec/test/context.py | 5 +- .../eth2spec/test/helpers/fork_choice.py | 10 +- .../test/phase0/fork_choice/test_ex_ante.py | 290 ++++++++++++++++++ 4 files changed, 301 insertions(+), 5 deletions(-) create mode 100644 tests/core/pyspec/eth2spec/test/phase0/fork_choice/test_ex_ante.py diff --git a/specs/phase0/fork-choice.md b/specs/phase0/fork-choice.md index d082ede30..ca38926fc 100644 --- a/specs/phase0/fork-choice.md +++ b/specs/phase0/fork-choice.md @@ -263,6 +263,7 @@ def get_head(store: Store) -> Root: if len(children) == 0: return head # Sort by latest attesting balance with ties broken lexicographically + # Ties broken by favoring block with lexicographically higher root head = max(children, key=lambda root: (get_latest_attesting_balance(store, root), root)) ``` diff --git a/tests/core/pyspec/eth2spec/test/context.py b/tests/core/pyspec/eth2spec/test/context.py index 184c0d609..346d2fc70 100644 --- a/tests/core/pyspec/eth2spec/test/context.py +++ b/tests/core/pyspec/eth2spec/test/context.py @@ -484,8 +484,9 @@ def with_config_overrides(config_overrides): # Retain types of all config values test_config = {k: config_types[k](v) for k, v in tmp_config.items()} - # Output the config for test vectors (TODO: check config YAML encoding) - yield 'config', 'data', test_config + # FIXME: config YAML encoding issue + # Output the config for test vectors + # yield 'config', 'data', test_config spec.config = spec.Configuration(**test_config) diff --git a/tests/core/pyspec/eth2spec/test/helpers/fork_choice.py b/tests/core/pyspec/eth2spec/test/helpers/fork_choice.py index 0b06f283f..f056b9acd 100644 --- a/tests/core/pyspec/eth2spec/test/helpers/fork_choice.py +++ b/tests/core/pyspec/eth2spec/test/helpers/fork_choice.py @@ -42,6 +42,12 @@ def tick_and_add_block(spec, store, signed_block, test_steps, valid=True, return post_state +def add_attestation(spec, store, attestation, test_steps, is_from_block=False): + spec.on_attestation(store, attestation, is_from_block=is_from_block) + yield get_attestation_file_name(attestation), attestation + test_steps.append({'attestation': get_attestation_file_name(attestation)}) + + def tick_and_run_on_attestation(spec, store, attestation, test_steps, is_from_block=False): parent_block = store.blocks[attestation.data.beacon_block_root] pre_state = store.block_states[spec.hash_tree_root(parent_block)] @@ -52,9 +58,7 @@ def tick_and_run_on_attestation(spec, store, attestation, test_steps, is_from_bl spec.on_tick(store, next_epoch_time) test_steps.append({'tick': int(next_epoch_time)}) - spec.on_attestation(store, attestation, is_from_block=is_from_block) - yield get_attestation_file_name(attestation), attestation - test_steps.append({'attestation': get_attestation_file_name(attestation)}) + yield from add_attestation(spec, store, attestation, test_steps, is_from_block) def run_on_attestation(spec, store, attestation, is_from_block=False, valid=True): diff --git a/tests/core/pyspec/eth2spec/test/phase0/fork_choice/test_ex_ante.py b/tests/core/pyspec/eth2spec/test/phase0/fork_choice/test_ex_ante.py new file mode 100644 index 000000000..d19cb69b3 --- /dev/null +++ b/tests/core/pyspec/eth2spec/test/phase0/fork_choice/test_ex_ante.py @@ -0,0 +1,290 @@ +from eth2spec.test.context import ( + MAINNET, + spec_configured_state_test, + spec_state_test, + with_all_phases, + with_presets, +) +from eth2spec.test.helpers.attestations import ( + get_valid_attestation, + sign_attestation, +) +from eth2spec.test.helpers.block import ( + build_empty_block, +) +from eth2spec.test.helpers.fork_choice import ( + get_genesis_forkchoice_store_and_block, + on_tick_and_append_step, + add_attestation, + add_block, + tick_and_add_block, +) +from eth2spec.test.helpers.state import ( + state_transition_and_sign_block, +) + + +def _apply_base_block_a(spec, state, store, test_steps): + # On receiving block A at slot `N` + block = build_empty_block(spec, state, slot=state.slot + 1) + signed_block_a = state_transition_and_sign_block(spec, state, block) + yield from tick_and_add_block(spec, store, signed_block_a, test_steps) + assert spec.get_head(store) == signed_block_a.message.hash_tree_root() + + +@with_all_phases +@spec_state_test +def test_ex_ante_secnario_1_with_boost(spec, state): + """ + With a single adversarial attestation + + Block A - slot N + Block B (parent A) - slot N+1 + Block C (parent A) - slot N+2 + Attestation_1 (Block B) - slot N+1 – size 1 + """ + test_steps = [] + # Initialization + store, anchor_block = get_genesis_forkchoice_store_and_block(spec, state) + yield 'anchor_state', state + yield 'anchor_block', anchor_block + current_time = state.slot * spec.config.SECONDS_PER_SLOT + store.genesis_time + on_tick_and_append_step(spec, store, current_time, test_steps) + assert store.time == current_time + + # On receiving block A at slot `N` + yield from _apply_base_block_a(spec, state, store, test_steps) + state_a = state.copy() + + # Block B at slot `N + 1`, parent is A + state_b = state_a.copy() + block = build_empty_block(spec, state_a, slot=state_a.slot + 1) + signed_block_b = state_transition_and_sign_block(spec, state_b, block) + + # Block C at slot `N + 2`, parent is A + state_c = state_a.copy() + block = build_empty_block(spec, state_c, slot=state_a.slot + 2) + signed_block_c = state_transition_and_sign_block(spec, state_c, block) + + # Attestation_1 received at N+2 — B is head due to boost proposer + def _filter_participant_set(participants): + return [next(iter(participants))] + + attestation = get_valid_attestation( + spec, state_b, slot=state_b.slot, signed=False, filter_participant_set=_filter_participant_set + ) + attestation.data.beacon_block_root = signed_block_b.message.hash_tree_root() + assert len([i for i in attestation.aggregation_bits if i == 1]) == 1 + sign_attestation(spec, state_b, attestation) + + # Block C received at N+2 — C is head + time = state_c.slot * spec.config.SECONDS_PER_SLOT + store.genesis_time + on_tick_and_append_step(spec, store, time, test_steps) + yield from add_block(spec, store, signed_block_c, test_steps) + assert spec.get_head(store) == signed_block_c.message.hash_tree_root() + + # Block B received at N+2 — C is head that has higher proposer score boost + yield from add_block(spec, store, signed_block_b, test_steps) + assert spec.get_head(store) == signed_block_c.message.hash_tree_root() + + # Attestation_1 received at N+2 — C is head + yield from add_attestation(spec, store, attestation, test_steps) + assert spec.get_head(store) == signed_block_c.message.hash_tree_root() + + yield 'steps', test_steps + + +@with_all_phases +@spec_configured_state_test({ + 'PROPOSER_SCORE_BOOST': 0, +}) +def test_ex_ante_secnario_1_without_boost(spec, state): + """ + With a single adversarial attestation + + NOTE: this case disabled proposer score boost by setting config `PROPOSER_SCORE_BOOST` to `0` + + Block A - slot N + Block B (parent A) - slot N+1 + Block C (parent A) - slot N+2 + Attestation_1 (Block B) - slot N+1 – size 1 + """ + # For testing `PROPOSER_SCORE_BOOST = 0` case + yield 'PROPOSER_SCORE_BOOST', 'meta', 0 + + test_steps = [] + # Initialization + store, anchor_block = get_genesis_forkchoice_store_and_block(spec, state) + yield 'anchor_state', state + yield 'anchor_block', anchor_block + current_time = state.slot * spec.config.SECONDS_PER_SLOT + store.genesis_time + on_tick_and_append_step(spec, store, current_time, test_steps) + assert store.time == current_time + + # On receiving block A at slot `N` + yield from _apply_base_block_a(spec, state, store, test_steps) + state_a = state.copy() + + # Block B at slot `N + 1`, parent is A + state_b = state_a.copy() + block = build_empty_block(spec, state_a, slot=state_a.slot + 1) + signed_block_b = state_transition_and_sign_block(spec, state_b, block) + + # Block C at slot `N + 2`, parent is A + state_c = state_a.copy() + block = build_empty_block(spec, state_c, slot=state_a.slot + 2) + signed_block_c = state_transition_and_sign_block(spec, state_c, block) + + # Attestation_1 received at N+2 — B is head due to boost proposer + def _filter_participant_set(participants): + return [next(iter(participants))] + + attestation = get_valid_attestation( + spec, state_b, slot=state_b.slot, signed=False, filter_participant_set=_filter_participant_set + ) + attestation.data.beacon_block_root = signed_block_b.message.hash_tree_root() + assert len([i for i in attestation.aggregation_bits if i == 1]) == 1 + sign_attestation(spec, state_b, attestation) + + # Block C received at N+2 — C is head + time = state_c.slot * spec.config.SECONDS_PER_SLOT + store.genesis_time + on_tick_and_append_step(spec, store, time, test_steps) + yield from add_block(spec, store, signed_block_c, test_steps) + assert spec.get_head(store) == signed_block_c.message.hash_tree_root() + + # Block B received at N+2 + # Block B and C has the same score 0. Use a lexicographical order for tie-breaking. + yield from add_block(spec, store, signed_block_b, test_steps) + if signed_block_b.message.hash_tree_root() >= signed_block_c.message.hash_tree_root(): + assert spec.get_head(store) == signed_block_b.message.hash_tree_root() + else: + assert spec.get_head(store) == signed_block_c.message.hash_tree_root() + + # Attestation_1 received at N+2 — B is head + yield from add_attestation(spec, store, attestation, test_steps) + assert spec.get_head(store) == signed_block_b.message.hash_tree_root() + + yield 'steps', test_steps + + +@with_all_phases +@with_presets([MAINNET], reason="to create larger committee") +@spec_state_test +def test_ex_ante_attestations_is_greater_than_proposer_boost_with_boost(spec, state): + """ + Adversarial attestations > proposer boost + + Block A - slot N + Block B (parent A) - slot N+1 + Block C (parent A) - slot N+2 + Attestation_1 (Block B) - slot N+1 – size > proposer_boost + """ + test_steps = [] + # Initialization + store, anchor_block = get_genesis_forkchoice_store_and_block(spec, state) + yield 'anchor_state', state + yield 'anchor_block', anchor_block + current_time = state.slot * spec.config.SECONDS_PER_SLOT + store.genesis_time + on_tick_and_append_step(spec, store, current_time, test_steps) + assert store.time == current_time + + # On receiving block A at slot `N` + yield from _apply_base_block_a(spec, state, store, test_steps) + state_a = state.copy() + + # Block B at slot `N + 1`, parent is A + state_b = state_a.copy() + block = build_empty_block(spec, state_a, slot=state_a.slot + 1) + signed_block_b = state_transition_and_sign_block(spec, state_b, block) + + # Block C at slot `N + 2`, parent is A + state_c = state_a.copy() + block = build_empty_block(spec, state_c, slot=state_a.slot + 2) + signed_block_c = state_transition_and_sign_block(spec, state_c, block) + + # Full attestation received at N+2 — B is head due to boost proposer + attestation = get_valid_attestation(spec, state_b, slot=state_b.slot, signed=False) + attestation.data.beacon_block_root = signed_block_b.message.hash_tree_root() + assert len([i for i in attestation.aggregation_bits if i == 1]) > 1 + sign_attestation(spec, state_b, attestation) + + # Block C received at N+2 — C is head + time = state_c.slot * spec.config.SECONDS_PER_SLOT + store.genesis_time + on_tick_and_append_step(spec, store, time, test_steps) + yield from add_block(spec, store, signed_block_c, test_steps) + assert spec.get_head(store) == signed_block_c.message.hash_tree_root() + + # Block B received at N+2 — C is head that has higher proposer score boost + yield from add_block(spec, store, signed_block_b, test_steps) + assert spec.get_head(store) == signed_block_c.message.hash_tree_root() + + # Attestation_1 received at N+2 — B is head because B's attestation_score > C's proposer_score. + # (B's proposer_score = C's attestation_score = 0) + yield from add_attestation(spec, store, attestation, test_steps) + assert spec.get_head(store) == signed_block_b.message.hash_tree_root() + + yield 'steps', test_steps + + +@with_all_phases +@spec_configured_state_test({ + 'PROPOSER_SCORE_BOOST': 0, +}) +@with_presets([MAINNET], reason="to create larger committee") +def test_ex_ante_attestations_is_greater_than_proposer_boost_without_boost(spec, state): + """ + Adversarial attestations > proposer boost + + Block A - slot N + Block B (parent A) - slot N+1 + Block C (parent A) - slot N+2 + Attestation_1 (Block B) - slot N+1 – size > proposer_boost + """ + test_steps = [] + # Initialization + store, anchor_block = get_genesis_forkchoice_store_and_block(spec, state) + yield 'anchor_state', state + yield 'anchor_block', anchor_block + current_time = state.slot * spec.config.SECONDS_PER_SLOT + store.genesis_time + on_tick_and_append_step(spec, store, current_time, test_steps) + assert store.time == current_time + + # On receiving block A at slot `N` + yield from _apply_base_block_a(spec, state, store, test_steps) + state_a = state.copy() + + # Block B at slot `N + 1`, parent is A + state_b = state_a.copy() + block = build_empty_block(spec, state_a, slot=state_a.slot + 1) + signed_block_b = state_transition_and_sign_block(spec, state_b, block) + + # Block C at slot `N + 2`, parent is A + state_c = state_a.copy() + block = build_empty_block(spec, state_c, slot=state_a.slot + 2) + signed_block_c = state_transition_and_sign_block(spec, state_c, block) + + # Full attestation received at N+2 — B is head due to boost proposer + attestation = get_valid_attestation(spec, state_b, slot=state_b.slot, signed=False) + attestation.data.beacon_block_root = signed_block_b.message.hash_tree_root() + assert len([i for i in attestation.aggregation_bits if i == 1]) > 1 + sign_attestation(spec, state_b, attestation) + + # Block C received at N+2 — C is head + time = state_c.slot * spec.config.SECONDS_PER_SLOT + store.genesis_time + on_tick_and_append_step(spec, store, time, test_steps) + yield from add_block(spec, store, signed_block_c, test_steps) + assert spec.get_head(store) == signed_block_c.message.hash_tree_root() + + # Block B received at N+2 + # Block B and C has the same score 0. Use a lexicographical order for tie-breaking. + yield from add_block(spec, store, signed_block_b, test_steps) + if signed_block_b.message.hash_tree_root() >= signed_block_c.message.hash_tree_root(): + assert spec.get_head(store) == signed_block_b.message.hash_tree_root() + else: + assert spec.get_head(store) == signed_block_c.message.hash_tree_root() + + # Attestation_1 received at N+2 — B is head because B's attestation_score > C's attestation_score + yield from add_attestation(spec, store, attestation, test_steps) + assert spec.get_head(store) == signed_block_b.message.hash_tree_root() + + yield 'steps', test_steps From 0bc0769767287e33277b6e346463c6431249a761 Mon Sep 17 00:00:00 2001 From: realbigsean Date: Tue, 30 Nov 2021 15:44:56 -0500 Subject: [PATCH 12/46] Simplify logic in calculating proposer score --- specs/phase0/fork-choice.md | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/specs/phase0/fork-choice.md b/specs/phase0/fork-choice.md index d082ede30..279a08f8c 100644 --- a/specs/phase0/fork-choice.md +++ b/specs/phase0/fork-choice.md @@ -182,14 +182,12 @@ def get_latest_attesting_balance(store: Store, root: Root) -> Gwei: and get_ancestor(store, store.latest_messages[i].root, store.blocks[root].slot) == root) )) proposer_score = Gwei(0) - if store.proposer_boost_root != Root(): - block = store.blocks[root] - if get_ancestor(store, root, block.slot) == store.proposer_boost_root: - num_validators = len(get_active_validator_indices(state, get_current_epoch(state))) - avg_balance = get_total_active_balance(state) // num_validators - committee_size = num_validators // SLOTS_PER_EPOCH - committee_weight = committee_size * avg_balance - proposer_score = (committee_weight * PROPOSER_SCORE_BOOST) // 100 + if store.proposer_boost_root != Root() and root == store.proposer_boost_root: + num_validators = len(get_active_validator_indices(state, get_current_epoch(state))) + avg_balance = get_total_active_balance(state) // num_validators + committee_size = num_validators // SLOTS_PER_EPOCH + committee_weight = committee_size * avg_balance + proposer_score = (committee_weight * PROPOSER_SCORE_BOOST) // 100 return attestation_score + proposer_score ``` From ad588d79f6aa25e0974f7e47afff52311f1f188a Mon Sep 17 00:00:00 2001 From: dankrad Date: Tue, 30 Nov 2021 22:52:16 +0000 Subject: [PATCH 13/46] Update beacon-chain.md 5 is not a primitive root of the BLS field, it needs to be 7 --- specs/sharding/beacon-chain.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/specs/sharding/beacon-chain.md b/specs/sharding/beacon-chain.md index 8190951b4..cf3d7bea1 100644 --- a/specs/sharding/beacon-chain.md +++ b/specs/sharding/beacon-chain.md @@ -101,7 +101,7 @@ The following values are (non-configurable) constants used throughout the specif | Name | Value | Notes | | - | - | - | -| `PRIMITIVE_ROOT_OF_UNITY` | `5` | Primitive root of unity of the BLS12_381 (inner) modulus | +| `PRIMITIVE_ROOT_OF_UNITY` | `7` | Primitive root of unity of the BLS12_381 (inner) modulus | | `DATA_AVAILABILITY_INVERSE_CODING_RATE` | `2**1` (= 2) | Factor by which samples are extended for data availability encoding | | `POINTS_PER_SAMPLE` | `uint64(2**3)` (= 8) | 31 * 8 = 248 bytes | | `MODULUS` | `0x73eda753299d7d483339d80809a1d80553bda402fffe5bfeffffffff00000001` (curve order of BLS12_381) | From bb8168fd956d753ea5ff84f94d6f0b334fbad61d Mon Sep 17 00:00:00 2001 From: Hsiao-Wei Wang Date: Thu, 2 Dec 2021 00:46:19 +0800 Subject: [PATCH 14/46] Make config dict compatible with yaml.dump --- tests/core/pyspec/eth2spec/test/context.py | 24 +++++++++++++++++++--- 1 file changed, 21 insertions(+), 3 deletions(-) diff --git a/tests/core/pyspec/eth2spec/test/context.py b/tests/core/pyspec/eth2spec/test/context.py index 346d2fc70..2a6f2e324 100644 --- a/tests/core/pyspec/eth2spec/test/context.py +++ b/tests/core/pyspec/eth2spec/test/context.py @@ -1,6 +1,7 @@ import pytest from copy import deepcopy from dataclasses import dataclass +from eth_utils import encode_hex from eth2spec.phase0 import mainnet as spec_phase0_mainnet, minimal as spec_phase0_minimal from eth2spec.altair import mainnet as spec_altair_mainnet, minimal as spec_altair_minimal @@ -464,6 +465,22 @@ def with_presets(preset_bases, reason=None): return decorator +def _get_basic_dict(ssz_dict: Dict[str, Any]) -> Dict[str, Any]: + """ + Get dict of Python built-in types from a dict of SSZ objects. + """ + result = {} + for k, v in ssz_dict.items(): + if isinstance(v, int): + value = int(v) + elif isinstance(v, bytes): + value = encode_hex(v) + else: + value = str(v) + result[k] = value + return result + + def with_config_overrides(config_overrides): """ WARNING: the spec_test decorator must wrap this, to ensure the decorated test actually runs. @@ -484,9 +501,10 @@ def with_config_overrides(config_overrides): # Retain types of all config values test_config = {k: config_types[k](v) for k, v in tmp_config.items()} - # FIXME: config YAML encoding issue - # Output the config for test vectors - # yield 'config', 'data', test_config + # To output the changed config to could be serialized with yaml test vectors, + # the dict SSZ objects have to be converted into Python built-in types. + output_config = _get_basic_dict(test_config) + yield 'config', 'data', output_config spec.config = spec.Configuration(**test_config) From dc52b351457e11be14a40cf79873903b5d8ad368 Mon Sep 17 00:00:00 2001 From: Hsiao-Wei Wang Date: Thu, 2 Dec 2021 01:37:01 +0800 Subject: [PATCH 15/46] Add `PROPOSER_SCORE_BOOST` to meta.yaml and fix comments --- .../eth2spec/test/phase0/fork_choice/test_ex_ante.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/tests/core/pyspec/eth2spec/test/phase0/fork_choice/test_ex_ante.py b/tests/core/pyspec/eth2spec/test/phase0/fork_choice/test_ex_ante.py index d19cb69b3..806fe7ec4 100644 --- a/tests/core/pyspec/eth2spec/test/phase0/fork_choice/test_ex_ante.py +++ b/tests/core/pyspec/eth2spec/test/phase0/fork_choice/test_ex_ante.py @@ -153,7 +153,7 @@ def test_ex_ante_secnario_1_without_boost(spec, state): assert spec.get_head(store) == signed_block_c.message.hash_tree_root() # Block B received at N+2 - # Block B and C has the same score 0. Use a lexicographical order for tie-breaking. + # Block B and C have the same score 0. Use a lexicographical order for tie-breaking. yield from add_block(spec, store, signed_block_b, test_steps) if signed_block_b.message.hash_tree_root() >= signed_block_c.message.hash_tree_root(): assert spec.get_head(store) == signed_block_b.message.hash_tree_root() @@ -240,6 +240,9 @@ def test_ex_ante_attestations_is_greater_than_proposer_boost_without_boost(spec, Block C (parent A) - slot N+2 Attestation_1 (Block B) - slot N+1 – size > proposer_boost """ + # For testing `PROPOSER_SCORE_BOOST = 0` case + yield 'PROPOSER_SCORE_BOOST', 'meta', 0 + test_steps = [] # Initialization store, anchor_block = get_genesis_forkchoice_store_and_block(spec, state) @@ -276,7 +279,7 @@ def test_ex_ante_attestations_is_greater_than_proposer_boost_without_boost(spec, assert spec.get_head(store) == signed_block_c.message.hash_tree_root() # Block B received at N+2 - # Block B and C has the same score 0. Use a lexicographical order for tie-breaking. + # Block B and C have the same score 0. Use a lexicographical order for tie-breaking. yield from add_block(spec, store, signed_block_b, test_steps) if signed_block_b.message.hash_tree_root() >= signed_block_c.message.hash_tree_root(): assert spec.get_head(store) == signed_block_b.message.hash_tree_root() From 3e2890bb3e7b586f54d71b0958caef4347b0e644 Mon Sep 17 00:00:00 2001 From: Aditya Asgaonkar Date: Wed, 1 Dec 2021 18:03:40 -0800 Subject: [PATCH 16/46] Apply proposer boost to ancestors correctly --- specs/phase0/fork-choice.md | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/specs/phase0/fork-choice.md b/specs/phase0/fork-choice.md index 279a08f8c..281bf4e4d 100644 --- a/specs/phase0/fork-choice.md +++ b/specs/phase0/fork-choice.md @@ -182,12 +182,13 @@ def get_latest_attesting_balance(store: Store, root: Root) -> Gwei: and get_ancestor(store, store.latest_messages[i].root, store.blocks[root].slot) == root) )) proposer_score = Gwei(0) - if store.proposer_boost_root != Root() and root == store.proposer_boost_root: - num_validators = len(get_active_validator_indices(state, get_current_epoch(state))) - avg_balance = get_total_active_balance(state) // num_validators - committee_size = num_validators // SLOTS_PER_EPOCH - committee_weight = committee_size * avg_balance - proposer_score = (committee_weight * PROPOSER_SCORE_BOOST) // 100 + if store.proposer_boost_root != Root(): + if get_ancestor(store, store.proposer_boost_root, store.blocks[root].slot) == root: + num_validators = len(get_active_validator_indices(state, get_current_epoch(state))) + avg_balance = get_total_active_balance(state) // num_validators + committee_size = num_validators // SLOTS_PER_EPOCH + committee_weight = committee_size * avg_balance + proposer_score = (committee_weight * PROPOSER_SCORE_BOOST) // 100 return attestation_score + proposer_score ``` From 6f3379c7bee3da83caaa4da347e4ef8801e2a257 Mon Sep 17 00:00:00 2001 From: Aditya Asgaonkar Date: Wed, 1 Dec 2021 20:11:52 -0800 Subject: [PATCH 17/46] Apply HWW's suggestion Co-authored-by: Hsiao-Wei Wang --- specs/phase0/fork-choice.md | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/specs/phase0/fork-choice.md b/specs/phase0/fork-choice.md index 281bf4e4d..497c06dc6 100644 --- a/specs/phase0/fork-choice.md +++ b/specs/phase0/fork-choice.md @@ -182,13 +182,15 @@ def get_latest_attesting_balance(store: Store, root: Root) -> Gwei: and get_ancestor(store, store.latest_messages[i].root, store.blocks[root].slot) == root) )) proposer_score = Gwei(0) - if store.proposer_boost_root != Root(): - if get_ancestor(store, store.proposer_boost_root, store.blocks[root].slot) == root: - num_validators = len(get_active_validator_indices(state, get_current_epoch(state))) - avg_balance = get_total_active_balance(state) // num_validators - committee_size = num_validators // SLOTS_PER_EPOCH - committee_weight = committee_size * avg_balance - proposer_score = (committee_weight * PROPOSER_SCORE_BOOST) // 100 + if ( + store.proposer_boost_root != Root() + and get_ancestor(store, store.proposer_boost_root, store.blocks[root].slot) == root + ): + num_validators = len(get_active_validator_indices(state, get_current_epoch(state))) + avg_balance = get_total_active_balance(state) // num_validators + committee_size = num_validators // SLOTS_PER_EPOCH + committee_weight = committee_size * avg_balance + proposer_score = (committee_weight * PROPOSER_SCORE_BOOST) // 100 return attestation_score + proposer_score ``` From a399d953d3c2e32744c94eb6397a6454dbac7775 Mon Sep 17 00:00:00 2001 From: Hsiao-Wei Wang Date: Thu, 2 Dec 2021 17:21:42 +0800 Subject: [PATCH 18/46] Add assertion to `test_shorter_chain_but_heavier_weight` --- .../pyspec/eth2spec/test/phase0/fork_choice/test_get_head.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tests/core/pyspec/eth2spec/test/phase0/fork_choice/test_get_head.py b/tests/core/pyspec/eth2spec/test/phase0/fork_choice/test_get_head.py index d2c84fce7..a524cbd56 100644 --- a/tests/core/pyspec/eth2spec/test/phase0/fork_choice/test_get_head.py +++ b/tests/core/pyspec/eth2spec/test/phase0/fork_choice/test_get_head.py @@ -166,6 +166,9 @@ def test_shorter_chain_but_heavier_weight(spec, state): signed_short_block = state_transition_and_sign_block(spec, short_state, short_block) yield from tick_and_add_block(spec, store, signed_short_block, test_steps) + # Since the long chain has higher proposer_score at slot 1, the latest long block is the head + assert spec.get_head(store) == spec.hash_tree_root(long_block) + short_attestation = get_valid_attestation(spec, short_state, short_block.slot, signed=True) yield from tick_and_run_on_attestation(spec, store, short_attestation, test_steps) From 0d8fab3986e3338c76747e1582aa53b5707c345b Mon Sep 17 00:00:00 2001 From: Aditya Asgaonkar Date: Thu, 2 Dec 2021 09:57:28 -0800 Subject: [PATCH 19/46] Apply Danny's suggestion --- specs/phase0/fork-choice.md | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/specs/phase0/fork-choice.md b/specs/phase0/fork-choice.md index 497c06dc6..fe546142b 100644 --- a/specs/phase0/fork-choice.md +++ b/specs/phase0/fork-choice.md @@ -181,11 +181,13 @@ def get_latest_attesting_balance(store: Store, root: Root) -> Gwei: if (i in store.latest_messages and get_ancestor(store, store.latest_messages[i].root, store.blocks[root].slot) == root) )) + if store.proposer_boost_root == Root(): + # Return only attestation score if ``proposer_boost_root`` is not set + return attestation_score + # Calculate proposer score if ``proposer_boost_root`` is set proposer_score = Gwei(0) - if ( - store.proposer_boost_root != Root() - and get_ancestor(store, store.proposer_boost_root, store.blocks[root].slot) == root - ): + # Boost is applied if ``root`` is an ancestor of ``proposer_boost_root`` + if get_ancestor(store, store.proposer_boost_root, store.blocks[root].slot) == root: num_validators = len(get_active_validator_indices(state, get_current_epoch(state))) avg_balance = get_total_active_balance(state) // num_validators committee_size = num_validators // SLOTS_PER_EPOCH From 6308cee7a64d1820a38af0a3968481000a7a3081 Mon Sep 17 00:00:00 2001 From: Hsiao-Wei Wang Date: Fri, 3 Dec 2021 02:17:57 +0800 Subject: [PATCH 20/46] Fix typo --- .../pyspec/eth2spec/test/phase0/fork_choice/test_ex_ante.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/core/pyspec/eth2spec/test/phase0/fork_choice/test_ex_ante.py b/tests/core/pyspec/eth2spec/test/phase0/fork_choice/test_ex_ante.py index 806fe7ec4..c47ccefda 100644 --- a/tests/core/pyspec/eth2spec/test/phase0/fork_choice/test_ex_ante.py +++ b/tests/core/pyspec/eth2spec/test/phase0/fork_choice/test_ex_ante.py @@ -34,7 +34,7 @@ def _apply_base_block_a(spec, state, store, test_steps): @with_all_phases @spec_state_test -def test_ex_ante_secnario_1_with_boost(spec, state): +def test_ex_ante_scenario_1_with_boost(spec, state): """ With a single adversarial attestation @@ -98,7 +98,7 @@ def test_ex_ante_secnario_1_with_boost(spec, state): @spec_configured_state_test({ 'PROPOSER_SCORE_BOOST': 0, }) -def test_ex_ante_secnario_1_without_boost(spec, state): +def test_ex_ante_scenario_1_without_boost(spec, state): """ With a single adversarial attestation From 3a9777eebc0bc0aedf3f2286a5282785dad97341 Mon Sep 17 00:00:00 2001 From: Danny Ryan Date: Thu, 2 Dec 2021 14:00:06 -0700 Subject: [PATCH 21/46] Update specs/phase0/fork-choice.md --- specs/phase0/fork-choice.md | 1 + 1 file changed, 1 insertion(+) diff --git a/specs/phase0/fork-choice.md b/specs/phase0/fork-choice.md index fe546142b..d3b0e2dc5 100644 --- a/specs/phase0/fork-choice.md +++ b/specs/phase0/fork-choice.md @@ -184,6 +184,7 @@ def get_latest_attesting_balance(store: Store, root: Root) -> Gwei: if store.proposer_boost_root == Root(): # Return only attestation score if ``proposer_boost_root`` is not set return attestation_score + # Calculate proposer score if ``proposer_boost_root`` is set proposer_score = Gwei(0) # Boost is applied if ``root`` is an ancestor of ``proposer_boost_root`` From 9dc1b7af7e6f4a0b3fa79aa10a4deff71fb50c37 Mon Sep 17 00:00:00 2001 From: Hsiao-Wei Wang Date: Tue, 7 Dec 2021 00:23:01 +0800 Subject: [PATCH 22/46] Test with strict `proposer_boost + 1` attestation participants --- .../test/phase0/fork_choice/test_ex_ante.py | 64 +++++++++++++++---- 1 file changed, 50 insertions(+), 14 deletions(-) diff --git a/tests/core/pyspec/eth2spec/test/phase0/fork_choice/test_ex_ante.py b/tests/core/pyspec/eth2spec/test/phase0/fork_choice/test_ex_ante.py index c47ccefda..b3a8b38fb 100644 --- a/tests/core/pyspec/eth2spec/test/phase0/fork_choice/test_ex_ante.py +++ b/tests/core/pyspec/eth2spec/test/phase0/fork_choice/test_ex_ante.py @@ -167,6 +167,26 @@ def test_ex_ante_scenario_1_without_boost(spec, state): yield 'steps', test_steps +def _get_greater_than_proposer_boost_score(spec, store, state, proposer_boost_root, root): + """ + Return the minimum attestation participant count such that attestation_score > proposer_score + """ + # calculate proposer boost score + block = store.blocks[root] + proposer_score = 0 + if spec.get_ancestor(store, root, block.slot) == proposer_boost_root: + num_validators = len(spec.get_active_validator_indices(state, spec.get_current_epoch(state))) + avg_balance = spec.get_total_active_balance(state) // num_validators + committee_size = num_validators // spec.SLOTS_PER_EPOCH + committee_weight = committee_size * avg_balance + proposer_score = (committee_weight * spec.config.PROPOSER_SCORE_BOOST) // 100 + + # calculate minimum participant count such that attestation_score > proposer_score + base_effective_balance = state.validators[0].effective_balance + + return proposer_score // base_effective_balance + 1 + + @with_all_phases @with_presets([MAINNET], reason="to create larger committee") @spec_state_test @@ -177,7 +197,7 @@ def test_ex_ante_attestations_is_greater_than_proposer_boost_with_boost(spec, st Block A - slot N Block B (parent A) - slot N+1 Block C (parent A) - slot N+2 - Attestation_1 (Block B) - slot N+1 – size > proposer_boost + Attestation_1 (Block B) - slot N+1 – proposer_boost + 1 participants """ test_steps = [] # Initialization @@ -202,12 +222,6 @@ def test_ex_ante_attestations_is_greater_than_proposer_boost_with_boost(spec, st block = build_empty_block(spec, state_c, slot=state_a.slot + 2) signed_block_c = state_transition_and_sign_block(spec, state_c, block) - # Full attestation received at N+2 — B is head due to boost proposer - attestation = get_valid_attestation(spec, state_b, slot=state_b.slot, signed=False) - attestation.data.beacon_block_root = signed_block_b.message.hash_tree_root() - assert len([i for i in attestation.aggregation_bits if i == 1]) > 1 - sign_attestation(spec, state_b, attestation) - # Block C received at N+2 — C is head time = state_c.slot * spec.config.SECONDS_PER_SLOT + store.genesis_time on_tick_and_append_step(spec, store, time, test_steps) @@ -218,6 +232,20 @@ def test_ex_ante_attestations_is_greater_than_proposer_boost_with_boost(spec, st yield from add_block(spec, store, signed_block_b, test_steps) assert spec.get_head(store) == signed_block_c.message.hash_tree_root() + # Attestation of proposer_boost + 1 participants + proposer_boost_root = signed_block_b.message.hash_tree_root() + root = signed_block_b.message.hash_tree_root() + participant_num = _get_greater_than_proposer_boost_score(spec, store, state, proposer_boost_root, root) + + def _filter_participant_set(participants): + return [index for i, index in enumerate(participants) if i < participant_num] + + attestation = get_valid_attestation( + spec, state_b, slot=state_b.slot, signed=False, filter_participant_set=_filter_participant_set + ) + attestation.data.beacon_block_root = signed_block_b.message.hash_tree_root() + sign_attestation(spec, state_b, attestation) + # Attestation_1 received at N+2 — B is head because B's attestation_score > C's proposer_score. # (B's proposer_score = C's attestation_score = 0) yield from add_attestation(spec, store, attestation, test_steps) @@ -238,7 +266,7 @@ def test_ex_ante_attestations_is_greater_than_proposer_boost_without_boost(spec, Block A - slot N Block B (parent A) - slot N+1 Block C (parent A) - slot N+2 - Attestation_1 (Block B) - slot N+1 – size > proposer_boost + Attestation_1 (Block B) - slot N+1 – proposer_boost + 1 participants """ # For testing `PROPOSER_SCORE_BOOST = 0` case yield 'PROPOSER_SCORE_BOOST', 'meta', 0 @@ -266,12 +294,6 @@ def test_ex_ante_attestations_is_greater_than_proposer_boost_without_boost(spec, block = build_empty_block(spec, state_c, slot=state_a.slot + 2) signed_block_c = state_transition_and_sign_block(spec, state_c, block) - # Full attestation received at N+2 — B is head due to boost proposer - attestation = get_valid_attestation(spec, state_b, slot=state_b.slot, signed=False) - attestation.data.beacon_block_root = signed_block_b.message.hash_tree_root() - assert len([i for i in attestation.aggregation_bits if i == 1]) > 1 - sign_attestation(spec, state_b, attestation) - # Block C received at N+2 — C is head time = state_c.slot * spec.config.SECONDS_PER_SLOT + store.genesis_time on_tick_and_append_step(spec, store, time, test_steps) @@ -286,6 +308,20 @@ def test_ex_ante_attestations_is_greater_than_proposer_boost_without_boost(spec, else: assert spec.get_head(store) == signed_block_c.message.hash_tree_root() + # Attestation of proposer_boost + 1 participants + proposer_boost_root = signed_block_b.message.hash_tree_root() + root = signed_block_b.message.hash_tree_root() + participant_num = _get_greater_than_proposer_boost_score(spec, store, state, proposer_boost_root, root) + + def _filter_participant_set(participants): + return [index for i, index in enumerate(participants) if i < participant_num] + + attestation = get_valid_attestation( + spec, state_b, slot=state_b.slot, signed=False, filter_participant_set=_filter_participant_set + ) + attestation.data.beacon_block_root = signed_block_b.message.hash_tree_root() + sign_attestation(spec, state_b, attestation) + # Attestation_1 received at N+2 — B is head because B's attestation_score > C's attestation_score yield from add_attestation(spec, store, attestation, test_steps) assert spec.get_head(store) == signed_block_b.message.hash_tree_root() From 57be9d064f933626aaa0c3d6bbcd713ccc59289d Mon Sep 17 00:00:00 2001 From: Alex Stokes Date: Tue, 7 Dec 2021 16:51:11 +0100 Subject: [PATCH 23/46] update per-test config to be unique per-test --- tests/core/pyspec/eth2spec/test/context.py | 18 +++++------------- 1 file changed, 5 insertions(+), 13 deletions(-) diff --git a/tests/core/pyspec/eth2spec/test/context.py b/tests/core/pyspec/eth2spec/test/context.py index 2a6f2e324..4916e008c 100644 --- a/tests/core/pyspec/eth2spec/test/context.py +++ b/tests/core/pyspec/eth2spec/test/context.py @@ -491,22 +491,18 @@ def with_config_overrides(config_overrides): """ def decorator(fn): def wrapper(*args, spec: Spec, **kw): - # remember the old config - old_config = spec.config + spec = deepcopy(spec) # apply our overrides to a copy of it, and apply it to the spec - tmp_config = deepcopy(old_config._asdict()) # not a private method, there are multiple - tmp_config.update(config_overrides) - config_types = spec.Configuration.__annotations__ - # Retain types of all config values - test_config = {k: config_types[k](v) for k, v in tmp_config.items()} + spec.config.update(config_overrides) # To output the changed config to could be serialized with yaml test vectors, # the dict SSZ objects have to be converted into Python built-in types. - output_config = _get_basic_dict(test_config) + output_config = _get_basic_dict(spec.config) yield 'config', 'data', output_config - spec.config = spec.Configuration(**test_config) + # Output the config for test vectors (TODO: check config YAML encoding) + yield 'config', 'data', spec.config # Run the function out = fn(*args, spec=spec, **kw) @@ -514,10 +510,6 @@ def with_config_overrides(config_overrides): # it's generating things, and we need to complete it before setting back the config. if out is not None: yield from out - - # Restore the old config and apply it - spec.config = old_config - return wrapper return decorator From 733f37715e342823a0a9a93810051e573d62b0a4 Mon Sep 17 00:00:00 2001 From: Alex Stokes Date: Tue, 7 Dec 2021 17:51:32 +0100 Subject: [PATCH 24/46] use `importlib` to perform an actual spec copy --- tests/core/pyspec/eth2spec/test/context.py | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/tests/core/pyspec/eth2spec/test/context.py b/tests/core/pyspec/eth2spec/test/context.py index 4916e008c..0764bbe4a 100644 --- a/tests/core/pyspec/eth2spec/test/context.py +++ b/tests/core/pyspec/eth2spec/test/context.py @@ -1,6 +1,6 @@ import pytest -from copy import deepcopy from dataclasses import dataclass +import importlib from eth_utils import encode_hex from eth2spec.phase0 import mainnet as spec_phase0_mainnet, minimal as spec_phase0_minimal @@ -481,6 +481,16 @@ def _get_basic_dict(ssz_dict: Dict[str, Any]) -> Dict[str, Any]: return result +def _get_copy_of_spec(spec): + fork = spec.fork + preset = spec.config.PRESET_BASE + path = f"eth2spec.{fork}.{preset}" + + module_spec = importlib.util.find_spec(path) + module = importlib.util.module_from_spec(module_spec) + return module + + def with_config_overrides(config_overrides): """ WARNING: the spec_test decorator must wrap this, to ensure the decorated test actually runs. @@ -491,7 +501,7 @@ def with_config_overrides(config_overrides): """ def decorator(fn): def wrapper(*args, spec: Spec, **kw): - spec = deepcopy(spec) + spec = _get_copy_of_spec(spec) # apply our overrides to a copy of it, and apply it to the spec spec.config.update(config_overrides) @@ -501,9 +511,6 @@ def with_config_overrides(config_overrides): output_config = _get_basic_dict(spec.config) yield 'config', 'data', output_config - # Output the config for test vectors (TODO: check config YAML encoding) - yield 'config', 'data', spec.config - # Run the function out = fn(*args, spec=spec, **kw) # If it's not returning None like a normal test function, From 55c9c03f08696693af9f4e30e235d50e799f0bcd Mon Sep 17 00:00:00 2001 From: Alex Stokes Date: Tue, 7 Dec 2021 18:11:22 +0100 Subject: [PATCH 25/46] simply module import and fix config adjustment --- tests/core/pyspec/eth2spec/test/context.py | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/tests/core/pyspec/eth2spec/test/context.py b/tests/core/pyspec/eth2spec/test/context.py index 0764bbe4a..726723992 100644 --- a/tests/core/pyspec/eth2spec/test/context.py +++ b/tests/core/pyspec/eth2spec/test/context.py @@ -484,10 +484,10 @@ def _get_basic_dict(ssz_dict: Dict[str, Any]) -> Dict[str, Any]: def _get_copy_of_spec(spec): fork = spec.fork preset = spec.config.PRESET_BASE - path = f"eth2spec.{fork}.{preset}" - - module_spec = importlib.util.find_spec(path) + module_path = f"eth2spec.{fork}.{preset}" + module_spec = importlib.util.find_spec(module_path) module = importlib.util.module_from_spec(module_spec) + module_spec.loader.exec_module(module) return module @@ -504,13 +504,18 @@ def with_config_overrides(config_overrides): spec = _get_copy_of_spec(spec) # apply our overrides to a copy of it, and apply it to the spec - spec.config.update(config_overrides) + config = spec.config._asdict() + config.update(config_overrides) + config_types = spec.Configuration.__annotations__ + modified_config = {k: config_types[k](v) for k, v in config.items()} # To output the changed config to could be serialized with yaml test vectors, # the dict SSZ objects have to be converted into Python built-in types. - output_config = _get_basic_dict(spec.config) + output_config = _get_basic_dict(modified_config) yield 'config', 'data', output_config + spec.config = spec.Configuration(**modified_config) + # Run the function out = fn(*args, spec=spec, **kw) # If it's not returning None like a normal test function, From db2be42baaf763d44b409deff4c84700143d70cf Mon Sep 17 00:00:00 2001 From: Alex Stokes Date: Tue, 7 Dec 2021 18:50:00 +0100 Subject: [PATCH 26/46] use a specific `spec` rather than the pre-defined phases --- tests/core/pyspec/eth2spec/test/context.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/tests/core/pyspec/eth2spec/test/context.py b/tests/core/pyspec/eth2spec/test/context.py index 726723992..260cb4d7d 100644 --- a/tests/core/pyspec/eth2spec/test/context.py +++ b/tests/core/pyspec/eth2spec/test/context.py @@ -86,10 +86,9 @@ class SpecForks(TypedDict, total=False): def _prepare_state(balances_fn: Callable[[Any], Sequence[int]], threshold_fn: Callable[[Any], int], spec: Spec, phases: SpecForks): - phase = phases[spec.fork] - balances = balances_fn(phase) - activation_threshold = threshold_fn(phase) - state = create_genesis_state(spec=phase, validator_balances=balances, + balances = balances_fn(spec) + activation_threshold = threshold_fn(spec) + state = create_genesis_state(spec=spec, validator_balances=balances, activation_threshold=activation_threshold) return state From dc9db78a67e68e69503e29e20d66aa9c855546cb Mon Sep 17 00:00:00 2001 From: Caspar Schwarz-Schilling Date: Wed, 8 Dec 2021 17:05:44 +0100 Subject: [PATCH 27/46] Add sandwich test scenarios --- .../test/phase0/fork_choice/test_ex_ante.py | 391 +++++++++++++++++- 1 file changed, 387 insertions(+), 4 deletions(-) diff --git a/tests/core/pyspec/eth2spec/test/phase0/fork_choice/test_ex_ante.py b/tests/core/pyspec/eth2spec/test/phase0/fork_choice/test_ex_ante.py index b3a8b38fb..a200e10af 100644 --- a/tests/core/pyspec/eth2spec/test/phase0/fork_choice/test_ex_ante.py +++ b/tests/core/pyspec/eth2spec/test/phase0/fork_choice/test_ex_ante.py @@ -60,7 +60,7 @@ def test_ex_ante_scenario_1_with_boost(spec, state): state_b = state_a.copy() block = build_empty_block(spec, state_a, slot=state_a.slot + 1) signed_block_b = state_transition_and_sign_block(spec, state_b, block) - + # Block C at slot `N + 2`, parent is A state_c = state_a.copy() block = build_empty_block(spec, state_c, slot=state_a.slot + 2) @@ -89,7 +89,7 @@ def test_ex_ante_scenario_1_with_boost(spec, state): # Attestation_1 received at N+2 — C is head yield from add_attestation(spec, store, attestation, test_steps) - assert spec.get_head(store) == signed_block_c.message.hash_tree_root() + assert spec.get_head(store) == signed_block_c.message.hash_tree_root() yield 'steps', test_steps @@ -188,7 +188,7 @@ def _get_greater_than_proposer_boost_score(spec, store, state, proposer_boost_ro @with_all_phases -@with_presets([MAINNET], reason="to create larger committee") +@with_presets([MAINNET], reason="to create non-duplicate committee") @spec_state_test def test_ex_ante_attestations_is_greater_than_proposer_boost_with_boost(spec, state): """ @@ -258,7 +258,7 @@ def test_ex_ante_attestations_is_greater_than_proposer_boost_with_boost(spec, st @spec_configured_state_test({ 'PROPOSER_SCORE_BOOST': 0, }) -@with_presets([MAINNET], reason="to create larger committee") +@with_presets([MAINNET], reason="to create non-duplicate committee") def test_ex_ante_attestations_is_greater_than_proposer_boost_without_boost(spec, state): """ Adversarial attestations > proposer boost @@ -327,3 +327,386 @@ def test_ex_ante_attestations_is_greater_than_proposer_boost_without_boost(spec, assert spec.get_head(store) == signed_block_b.message.hash_tree_root() yield 'steps', test_steps + +@with_all_phases +@spec_state_test +def test_ex_ante_sandwich_without_attestations_with_boost(spec, state): + """ + Simple Sandwich test with boost and no attestations. + Obejcts: + Block A - slot N + Block B (parent A) - slot N+1 + Block C (parent A) - slot N+2 + Block D (parent B) - slot N+3 + Steps: + Block A received at N — A is head + Block C received at N+2 — C is head + Block B received at N+2 — C is head (with boost) + Block D received at N+3 — D is head (with boost) + """ + test_steps = [] + # Initialization + store, anchor_block = get_genesis_forkchoice_store_and_block(spec, state) + yield 'anchor_state', state + yield 'anchor_block', anchor_block + current_time = state.slot * spec.config.SECONDS_PER_SLOT + store.genesis_time + on_tick_and_append_step(spec, store, current_time, test_steps) + assert store.time == current_time + + # On receiving block A at slot `N` + yield from _apply_base_block_a(spec, state, store, test_steps) + state_a = state.copy() + + # Block B at slot `N + 1`, parent is A + state_b = state_a.copy() + block = build_empty_block(spec, state_a, slot=state_a.slot + 1) + signed_block_b = state_transition_and_sign_block(spec, state_b, block) + + # Block C at slot `N + 2`, parent is A + state_c = state_a.copy() + block = build_empty_block(spec, state_c, slot=state_a.slot + 2) + signed_block_c = state_transition_and_sign_block(spec, state_c, block) + + # Block D at slot `N + 3`, parent is B + state_d = state_b.copy() + block = build_empty_block(spec, state_d, slot=state_a.slot + 3) + signed_block_d = state_transition_and_sign_block(spec, state_d, block) + + # Block C received at N+2 — C is head + time = state_c.slot * spec.config.SECONDS_PER_SLOT + store.genesis_time + on_tick_and_append_step(spec, store, time, test_steps) + yield from add_block(spec, store, signed_block_c, test_steps) + assert spec.get_head(store) == signed_block_c.message.hash_tree_root() + + # Block B received at N+2 — C is head, it has proposer score boost + yield from add_block(spec, store, signed_block_b, test_steps) + assert spec.get_head(store) == signed_block_c.message.hash_tree_root() + + # Block D received at N+3 - D is head, it has proposer score boost + time = state_d.slot * spec.config.SECONDS_PER_SLOT + store.genesis_time + on_tick_and_append_step(spec, store, time, test_steps) + yield from add_block(spec, store, signed_block_d, test_steps) + assert spec.get_head(store) == signed_block_d.message.hash_tree_root() + + yield 'steps', test_steps + +@with_all_phases +@spec_configured_state_test({ + 'PROPOSER_SCORE_BOOST': 0, +}) +def test_ex_ante_sandwich_without_attestations_without_boost(spec, state): + """ + Simple Sandwich test with no boost and no attestations. + Obejcts: + Block A - slot N + Block B (parent A) - slot N+1 + Block C (parent A) - slot N+2 + Block D (parent B) - slot N+3 + Steps: + Block A received at N — A is head + Block C received at N+2 — C is head + Block B received at N+2 — B or C is head (chosen lexicographically; without boost) + Block D received at N+3 — D or C is head (chosen lexicographically; without boost) + """ + test_steps = [] + # Initialization + store, anchor_block = get_genesis_forkchoice_store_and_block(spec, state) + yield 'anchor_state', state + yield 'anchor_block', anchor_block + current_time = state.slot * spec.config.SECONDS_PER_SLOT + store.genesis_time + on_tick_and_append_step(spec, store, current_time, test_steps) + assert store.time == current_time + + # On receiving block A at slot `N` + yield from _apply_base_block_a(spec, state, store, test_steps) + state_a = state.copy() + + # Block B at slot `N + 1`, parent is A + state_b = state_a.copy() + block = build_empty_block(spec, state_a, slot=state_a.slot + 1) + signed_block_b = state_transition_and_sign_block(spec, state_b, block) + + # Block C at slot `N + 2`, parent is A + state_c = state_a.copy() + block = build_empty_block(spec, state_c, slot=state_a.slot + 2) + signed_block_c = state_transition_and_sign_block(spec, state_c, block) + + # Block D at slot `N + 3`, parent is B + state_d = state_b.copy() + block = build_empty_block(spec, state_d, slot=state_a.slot + 3) + signed_block_d = state_transition_and_sign_block(spec, state_d, block) + + # Block C received at N+2 — C is head + time = state_c.slot * spec.config.SECONDS_PER_SLOT + store.genesis_time + on_tick_and_append_step(spec, store, time, test_steps) + yield from add_block(spec, store, signed_block_c, test_steps) + assert spec.get_head(store) == signed_block_c.message.hash_tree_root() + + # Block B received at N+2 + # Block B and C have the same score 0. Use a lexicographical order for tie-breaking. + yield from add_block(spec, store, signed_block_b, test_steps) + if signed_block_b.message.hash_tree_root() >= signed_block_c.message.hash_tree_root(): + assert spec.get_head(store) == signed_block_b.message.hash_tree_root() + else: + assert spec.get_head(store) == signed_block_c.message.hash_tree_root() + + # Block D received at N+3 + # Block D and C have the same score 0. Use a lexicographical order for tie-breaking. + time = state_d.slot * spec.config.SECONDS_PER_SLOT + store.genesis_time + on_tick_and_append_step(spec, store, time, test_steps) + yield from add_block(spec, store, signed_block_d, test_steps) + if signed_block_d.message.hash_tree_root() >= signed_block_c.message.hash_tree_root(): + assert spec.get_head(store) == signed_block_d.message.hash_tree_root() + else: + assert spec.get_head(store) == signed_block_c.message.hash_tree_root() + + yield 'steps', test_steps + +@with_all_phases +@spec_state_test +def test_ex_ante_sandwich_with_honest_attestation_with_boost(spec, state): + """ + Boosting necessary to sandwich attack. + Objects: + Block A - slot N + Block B (parent A) - slot N+1 + Block C (parent A) - slot N+2 + Block D (parent B) - slot N+3 + Attestation_1 (Block C); size 1 - slot N+2 (honest) + Steps: + Block A received at N — A is head + Block C received at N+2 — C is head + Block B received at N+2 — C is head + Attestation_1 received at N+3 — C is head + Block D received at N+3 — D is head + + """ + test_steps = [] + # Initialization + store, anchor_block = get_genesis_forkchoice_store_and_block(spec, state) + yield 'anchor_state', state + yield 'anchor_block', anchor_block + current_time = state.slot * spec.config.SECONDS_PER_SLOT + store.genesis_time + on_tick_and_append_step(spec, store, current_time, test_steps) + assert store.time == current_time + + # On receiving block A at slot `N` + yield from _apply_base_block_a(spec, state, store, test_steps) + state_a = state.copy() + + # Block B at slot `N + 1`, parent is A + state_b = state_a.copy() + block = build_empty_block(spec, state_a, slot=state_a.slot + 1) + signed_block_b = state_transition_and_sign_block(spec, state_b, block) + + # Block C at slot `N + 2`, parent is A + state_c = state_a.copy() + block = build_empty_block(spec, state_c, slot=state_a.slot + 2) + signed_block_c = state_transition_and_sign_block(spec, state_c, block) + + # Attestation_1 at N+2 voting for block C + def _filter_participant_set(participants): + return [next(iter(participants))] + + attestation = get_valid_attestation( + spec, state_c, slot=state_c.slot, signed=False, filter_participant_set=_filter_participant_set + ) + attestation.data.beacon_block_root = signed_block_c.message.hash_tree_root() + assert len([i for i in attestation.aggregation_bits if i == 1]) == 1 + sign_attestation(spec, state_c, attestation) + + # Block D at slot `N + 3`, parent is B + state_d = state_b.copy() + block = build_empty_block(spec, state_d, slot=state_a.slot + 3) + signed_block_d = state_transition_and_sign_block(spec, state_d, block) + + # Block C received at N+2 — C is head + time = state_c.slot * spec.config.SECONDS_PER_SLOT + store.genesis_time + on_tick_and_append_step(spec, store, time, test_steps) + yield from add_block(spec, store, signed_block_c, test_steps) + assert spec.get_head(store) == signed_block_c.message.hash_tree_root() + + # Block B received at N+2 — C is head, it has proposer score boost + yield from add_block(spec, store, signed_block_b, test_steps) + assert spec.get_head(store) == signed_block_c.message.hash_tree_root() + + # Attestation_1 received at N+3 — C is head + time = state_d.slot * spec.config.SECONDS_PER_SLOT + store.genesis_time + on_tick_and_append_step(spec, store, time, test_steps) + yield from add_attestation(spec, store, attestation, test_steps) + assert spec.get_head(store) == signed_block_c.message.hash_tree_root() + + # Block D received at N+3 - D is head, it has proposer score boost + yield from add_block(spec, store, signed_block_d, test_steps) + assert spec.get_head(store) == signed_block_d.message.hash_tree_root() + + yield 'steps', test_steps + +@with_all_phases +@spec_configured_state_test({ + 'PROPOSER_SCORE_BOOST': 0, +}) +def test_ex_ante_sandwich_with_honest_attestation_without_boost(spec, state): + """ + Boost necessary to sandwich attack: no boost, so not successful here. + Objects: + Block A - slot N + Block B (parent A) - slot N+1 + Block C (parent A) - slot N+2 + Block D (parent B) - slot N+3 + Attestation_1 (Block C); size 1 - slot N+2 (honest) + Steps: + Block A received at N — A is head + Block C received at N+2 — C is head + Block B received at N+2 — B or C is head (chosen lexicographically) + Attestation_1 received at N+3 — C is head + Block D received at N+3 — C is head + """ + test_steps = [] + # Initialization + store, anchor_block = get_genesis_forkchoice_store_and_block(spec, state) + yield 'anchor_state', state + yield 'anchor_block', anchor_block + current_time = state.slot * spec.config.SECONDS_PER_SLOT + store.genesis_time + on_tick_and_append_step(spec, store, current_time, test_steps) + assert store.time == current_time + + # On receiving block A at slot `N` + yield from _apply_base_block_a(spec, state, store, test_steps) + state_a = state.copy() + + # Block B at slot `N + 1`, parent is A + state_b = state_a.copy() + block = build_empty_block(spec, state_a, slot=state_a.slot + 1) + signed_block_b = state_transition_and_sign_block(spec, state_b, block) + + # Block C at slot `N + 2`, parent is A + state_c = state_a.copy() + block = build_empty_block(spec, state_c, slot=state_a.slot + 2) + signed_block_c = state_transition_and_sign_block(spec, state_c, block) + + # Block D at slot `N + 3`, parent is B + state_d = state_b.copy() + block = build_empty_block(spec, state_d, slot=state_a.slot + 3) + signed_block_d = state_transition_and_sign_block(spec, state_d, block) + + # Block C received at N+2 — C is head + time = state_c.slot * spec.config.SECONDS_PER_SLOT + store.genesis_time + on_tick_and_append_step(spec, store, time, test_steps) + yield from add_block(spec, store, signed_block_c, test_steps) + assert spec.get_head(store) == signed_block_c.message.hash_tree_root() + + # Block B received at N+2 + # Block B and C have the same score, 0. Use a lexicographical order for tie-breaking. + yield from add_block(spec, store, signed_block_b, test_steps) + if signed_block_b.message.hash_tree_root() >= signed_block_c.message.hash_tree_root(): + assert spec.get_head(store) == signed_block_b.message.hash_tree_root() + else: + assert spec.get_head(store) == signed_block_c.message.hash_tree_root() + + # Attestation_1 at N+2 voting for block C + def _filter_participant_set(participants): + return [next(iter(participants))] + + attestation = get_valid_attestation( + spec, state_c, slot=state_c.slot, signed=False, filter_participant_set=_filter_participant_set + ) + attestation.data.beacon_block_root = signed_block_c.message.hash_tree_root() + assert len([i for i in attestation.aggregation_bits if i == 1]) == 1 + sign_attestation(spec, state_c, attestation) + + # Attestation_1 received at N+3 - C is head + time = state_d.slot * spec.config.SECONDS_PER_SLOT + store.genesis_time + on_tick_and_append_step(spec, store, time, test_steps) + yield from add_attestation(spec, store, attestation, test_steps) + assert spec.get_head(store) == signed_block_c.message.hash_tree_root() + + # Block D received at N+3 - C is head, because block D has no proposer boost + yield from add_block(spec, store, signed_block_d, test_steps) + assert spec.get_head(store) == signed_block_c.message.hash_tree_root() + + yield 'steps', test_steps + +@with_all_phases +@with_presets([MAINNET], reason="to create non-duplicate committee") +@spec_state_test +def test_ex_ante_sandwich_with_boost_not_sufficient(spec, state): + """ + Boost not sufficient to sandwich attack. + Objects: + Block A - slot N + Block B (parent A) - slot N+1 + Block C (parent A) - slot N+2 + Block D (parent B) - slot N+3 + Attestation_set_1 (Block C); size proposer_boost + 1 - slot N+2 + Steps: + Block A received at N — A is head + Block C received at N+2 — C is head + Block B received at N+2 — C is head + Attestation_set_1 received — C is head + Block D received at N+3 — C is head + """ + test_steps = [] + # Initialization + store, anchor_block = get_genesis_forkchoice_store_and_block(spec, state) + yield 'anchor_state', state + yield 'anchor_block', anchor_block + current_time = state.slot * spec.config.SECONDS_PER_SLOT + store.genesis_time + on_tick_and_append_step(spec, store, current_time, test_steps) + assert store.time == current_time + + # On receiving block A at slot `N` + yield from _apply_base_block_a(spec, state, store, test_steps) + state_a = state.copy() + + # Block B at slot `N + 1`, parent is A + state_b = state_a.copy() + block = build_empty_block(spec, state_a, slot=state_a.slot + 1) + signed_block_b = state_transition_and_sign_block(spec, state_b, block) + + # Block C at slot `N + 2`, parent is A + state_c = state_a.copy() + block = build_empty_block(spec, state_c, slot=state_a.slot + 2) + signed_block_c = state_transition_and_sign_block(spec, state_c, block) + + # Block D at slot `N + 3`, parent is B + state_d = state_b.copy() + block = build_empty_block(spec, state_d, slot=state_a.slot + 3) + signed_block_d = state_transition_and_sign_block(spec, state_d, block) + + # Block C received at N+2 — C is head + time = state_c.slot * spec.config.SECONDS_PER_SLOT + store.genesis_time + on_tick_and_append_step(spec, store, time, test_steps) + yield from add_block(spec, store, signed_block_c, test_steps) + assert spec.get_head(store) == signed_block_c.message.hash_tree_root() + + # Block B received at N+2 — C is head, it has proposer score boost + yield from add_block(spec, store, signed_block_b, test_steps) + assert spec.get_head(store) == signed_block_c.message.hash_tree_root() + + # Attestation_set_1 at N+2 voting for block C + proposer_boost_root = signed_block_c.message.hash_tree_root() + root = signed_block_c.message.hash_tree_root() + participant_num = _get_greater_than_proposer_boost_score(spec, store, state, proposer_boost_root, root) + + def _filter_participant_set(participants): + return [index for i, index in enumerate(participants) if i < participant_num] + + attestation = get_valid_attestation( + spec, state_c, slot=state_c.slot, signed=False, filter_participant_set=_filter_participant_set + ) + attestation.data.beacon_block_root = signed_block_c.message.hash_tree_root() + sign_attestation(spec, state_c, attestation) + + # Attestation_1 received at N+3 — B is head because B's attestation_score > C's proposer_score. + # (B's proposer_score = C's attestation_score = 0) + time = state_d.slot * spec.config.SECONDS_PER_SLOT + store.genesis_time + on_tick_and_append_step(spec, store, time, test_steps) + yield from add_attestation(spec, store, attestation, test_steps) + assert spec.get_head(store) == signed_block_c.message.hash_tree_root() + + # Block D received at N+3 - C is head, D's boost not sufficient! + yield from add_block(spec, store, signed_block_d, test_steps) + assert spec.get_head(store) == signed_block_c.message.hash_tree_root() + + yield 'steps', test_steps From 79e1d5e0216320ffef462a8f08839e56d0614106 Mon Sep 17 00:00:00 2001 From: Caspar Schwarz-Schilling Date: Wed, 8 Dec 2021 18:14:46 +0100 Subject: [PATCH 28/46] Fix head view comparison --- .../pyspec/eth2spec/test/phase0/fork_choice/test_ex_ante.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/core/pyspec/eth2spec/test/phase0/fork_choice/test_ex_ante.py b/tests/core/pyspec/eth2spec/test/phase0/fork_choice/test_ex_ante.py index a200e10af..bf526642a 100644 --- a/tests/core/pyspec/eth2spec/test/phase0/fork_choice/test_ex_ante.py +++ b/tests/core/pyspec/eth2spec/test/phase0/fork_choice/test_ex_ante.py @@ -455,7 +455,7 @@ def test_ex_ante_sandwich_without_attestations_without_boost(spec, state): time = state_d.slot * spec.config.SECONDS_PER_SLOT + store.genesis_time on_tick_and_append_step(spec, store, time, test_steps) yield from add_block(spec, store, signed_block_d, test_steps) - if signed_block_d.message.hash_tree_root() >= signed_block_c.message.hash_tree_root(): + if signed_block_b.message.hash_tree_root() >= signed_block_c.message.hash_tree_root(): assert spec.get_head(store) == signed_block_d.message.hash_tree_root() else: assert spec.get_head(store) == signed_block_c.message.hash_tree_root() From 9f614fca83b0e974f25488f9fc523702660b2a42 Mon Sep 17 00:00:00 2001 From: Caspar Schwarz-Schilling Date: Wed, 8 Dec 2021 18:31:14 +0100 Subject: [PATCH 29/46] Fix linting --- .../test/phase0/fork_choice/test_ex_ante.py | 21 ++++++++++++------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/tests/core/pyspec/eth2spec/test/phase0/fork_choice/test_ex_ante.py b/tests/core/pyspec/eth2spec/test/phase0/fork_choice/test_ex_ante.py index bf526642a..e85e9e937 100644 --- a/tests/core/pyspec/eth2spec/test/phase0/fork_choice/test_ex_ante.py +++ b/tests/core/pyspec/eth2spec/test/phase0/fork_choice/test_ex_ante.py @@ -60,7 +60,7 @@ def test_ex_ante_scenario_1_with_boost(spec, state): state_b = state_a.copy() block = build_empty_block(spec, state_a, slot=state_a.slot + 1) signed_block_b = state_transition_and_sign_block(spec, state_b, block) - + # Block C at slot `N + 2`, parent is A state_c = state_a.copy() block = build_empty_block(spec, state_c, slot=state_a.slot + 2) @@ -89,7 +89,7 @@ def test_ex_ante_scenario_1_with_boost(spec, state): # Attestation_1 received at N+2 — C is head yield from add_attestation(spec, store, attestation, test_steps) - assert spec.get_head(store) == signed_block_c.message.hash_tree_root() + assert spec.get_head(store) == signed_block_c.message.hash_tree_root() yield 'steps', test_steps @@ -328,6 +328,7 @@ def test_ex_ante_attestations_is_greater_than_proposer_boost_without_boost(spec, yield 'steps', test_steps + @with_all_phases @spec_state_test def test_ex_ante_sandwich_without_attestations_with_boost(spec, state): @@ -371,7 +372,7 @@ def test_ex_ante_sandwich_without_attestations_with_boost(spec, state): state_d = state_b.copy() block = build_empty_block(spec, state_d, slot=state_a.slot + 3) signed_block_d = state_transition_and_sign_block(spec, state_d, block) - + # Block C received at N+2 — C is head time = state_c.slot * spec.config.SECONDS_PER_SLOT + store.genesis_time on_tick_and_append_step(spec, store, time, test_steps) @@ -390,6 +391,7 @@ def test_ex_ante_sandwich_without_attestations_with_boost(spec, state): yield 'steps', test_steps + @with_all_phases @spec_configured_state_test({ 'PROPOSER_SCORE_BOOST': 0, @@ -435,7 +437,7 @@ def test_ex_ante_sandwich_without_attestations_without_boost(spec, state): state_d = state_b.copy() block = build_empty_block(spec, state_d, slot=state_a.slot + 3) signed_block_d = state_transition_and_sign_block(spec, state_d, block) - + # Block C received at N+2 — C is head time = state_c.slot * spec.config.SECONDS_PER_SLOT + store.genesis_time on_tick_and_append_step(spec, store, time, test_steps) @@ -462,6 +464,7 @@ def test_ex_ante_sandwich_without_attestations_without_boost(spec, state): yield 'steps', test_steps + @with_all_phases @spec_state_test def test_ex_ante_sandwich_with_honest_attestation_with_boost(spec, state): @@ -519,7 +522,7 @@ def test_ex_ante_sandwich_with_honest_attestation_with_boost(spec, state): state_d = state_b.copy() block = build_empty_block(spec, state_d, slot=state_a.slot + 3) signed_block_d = state_transition_and_sign_block(spec, state_d, block) - + # Block C received at N+2 — C is head time = state_c.slot * spec.config.SECONDS_PER_SLOT + store.genesis_time on_tick_and_append_step(spec, store, time, test_steps) @@ -542,6 +545,7 @@ def test_ex_ante_sandwich_with_honest_attestation_with_boost(spec, state): yield 'steps', test_steps + @with_all_phases @spec_configured_state_test({ 'PROPOSER_SCORE_BOOST': 0, @@ -589,7 +593,7 @@ def test_ex_ante_sandwich_with_honest_attestation_without_boost(spec, state): state_d = state_b.copy() block = build_empty_block(spec, state_d, slot=state_a.slot + 3) signed_block_d = state_transition_and_sign_block(spec, state_d, block) - + # Block C received at N+2 — C is head time = state_c.slot * spec.config.SECONDS_PER_SLOT + store.genesis_time on_tick_and_append_step(spec, store, time, test_steps) @@ -614,7 +618,7 @@ def test_ex_ante_sandwich_with_honest_attestation_without_boost(spec, state): attestation.data.beacon_block_root = signed_block_c.message.hash_tree_root() assert len([i for i in attestation.aggregation_bits if i == 1]) == 1 sign_attestation(spec, state_c, attestation) - + # Attestation_1 received at N+3 - C is head time = state_d.slot * spec.config.SECONDS_PER_SLOT + store.genesis_time on_tick_and_append_step(spec, store, time, test_steps) @@ -627,6 +631,7 @@ def test_ex_ante_sandwich_with_honest_attestation_without_boost(spec, state): yield 'steps', test_steps + @with_all_phases @with_presets([MAINNET], reason="to create non-duplicate committee") @spec_state_test @@ -673,7 +678,7 @@ def test_ex_ante_sandwich_with_boost_not_sufficient(spec, state): state_d = state_b.copy() block = build_empty_block(spec, state_d, slot=state_a.slot + 3) signed_block_d = state_transition_and_sign_block(spec, state_d, block) - + # Block C received at N+2 — C is head time = state_c.slot * spec.config.SECONDS_PER_SLOT + store.genesis_time on_tick_and_append_step(spec, store, time, test_steps) From addc03cb7c1111726fb838b67d8d9b69b97a0312 Mon Sep 17 00:00:00 2001 From: Hsiao-Wei Wang Date: Thu, 9 Dec 2021 17:47:20 +0800 Subject: [PATCH 30/46] Enable ex-ante tests in testgen and minor fixes --- .../test/phase0/fork_choice/test_ex_ante.py | 13 +++++++++++-- tests/generators/fork_choice/main.py | 1 + 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/tests/core/pyspec/eth2spec/test/phase0/fork_choice/test_ex_ante.py b/tests/core/pyspec/eth2spec/test/phase0/fork_choice/test_ex_ante.py index e85e9e937..b4013c18d 100644 --- a/tests/core/pyspec/eth2spec/test/phase0/fork_choice/test_ex_ante.py +++ b/tests/core/pyspec/eth2spec/test/phase0/fork_choice/test_ex_ante.py @@ -34,7 +34,7 @@ def _apply_base_block_a(spec, state, store, test_steps): @with_all_phases @spec_state_test -def test_ex_ante_scenario_1_with_boost(spec, state): +def test_ex_ante_vanilla_with_boost(spec, state): """ With a single adversarial attestation @@ -98,7 +98,7 @@ def test_ex_ante_scenario_1_with_boost(spec, state): @spec_configured_state_test({ 'PROPOSER_SCORE_BOOST': 0, }) -def test_ex_ante_scenario_1_without_boost(spec, state): +def test_ex_ante_vanilla_without_boost(spec, state): """ With a single adversarial attestation @@ -244,6 +244,7 @@ def test_ex_ante_attestations_is_greater_than_proposer_boost_with_boost(spec, st spec, state_b, slot=state_b.slot, signed=False, filter_participant_set=_filter_participant_set ) attestation.data.beacon_block_root = signed_block_b.message.hash_tree_root() + assert len([i for i in attestation.aggregation_bits if i == 1]) == participant_num sign_attestation(spec, state_b, attestation) # Attestation_1 received at N+2 — B is head because B's attestation_score > C's proposer_score. @@ -320,6 +321,7 @@ def test_ex_ante_attestations_is_greater_than_proposer_boost_without_boost(spec, spec, state_b, slot=state_b.slot, signed=False, filter_participant_set=_filter_participant_set ) attestation.data.beacon_block_root = signed_block_b.message.hash_tree_root() + assert len([i for i in attestation.aggregation_bits if i == 1]) == participant_num sign_attestation(spec, state_b, attestation) # Attestation_1 received at N+2 — B is head because B's attestation_score > C's attestation_score @@ -410,6 +412,9 @@ def test_ex_ante_sandwich_without_attestations_without_boost(spec, state): Block B received at N+2 — B or C is head (chosen lexicographically; without boost) Block D received at N+3 — D or C is head (chosen lexicographically; without boost) """ + # For testing `PROPOSER_SCORE_BOOST = 0` case + yield 'PROPOSER_SCORE_BOOST', 'meta', 0 + test_steps = [] # Initialization store, anchor_block = get_genesis_forkchoice_store_and_block(spec, state) @@ -566,6 +571,9 @@ def test_ex_ante_sandwich_with_honest_attestation_without_boost(spec, state): Attestation_1 received at N+3 — C is head Block D received at N+3 — C is head """ + # For testing `PROPOSER_SCORE_BOOST = 0` case + yield 'PROPOSER_SCORE_BOOST', 'meta', 0 + test_steps = [] # Initialization store, anchor_block = get_genesis_forkchoice_store_and_block(spec, state) @@ -701,6 +709,7 @@ def test_ex_ante_sandwich_with_boost_not_sufficient(spec, state): spec, state_c, slot=state_c.slot, signed=False, filter_participant_set=_filter_participant_set ) attestation.data.beacon_block_root = signed_block_c.message.hash_tree_root() + assert len([i for i in attestation.aggregation_bits if i == 1]) == participant_num sign_attestation(spec, state_c, attestation) # Attestation_1 received at N+3 — B is head because B's attestation_score > C's proposer_score. diff --git a/tests/generators/fork_choice/main.py b/tests/generators/fork_choice/main.py index 562f851d1..b194dc3bd 100644 --- a/tests/generators/fork_choice/main.py +++ b/tests/generators/fork_choice/main.py @@ -6,6 +6,7 @@ if __name__ == "__main__": phase_0_mods = {key: 'eth2spec.test.phase0.fork_choice.test_' + key for key in [ 'get_head', 'on_block', + 'ex_ante', ]} # No additional Altair specific finality tests, yet. altair_mods = phase_0_mods From 11a42f8d0661ddcc56831d1788b3066bd1d06372 Mon Sep 17 00:00:00 2001 From: Hsiao-Wei Wang Date: Thu, 9 Dec 2021 17:58:31 +0800 Subject: [PATCH 31/46] Remove `*_without_boost` tests --- .../test/phase0/fork_choice/test_ex_ante.py | 315 ------------------ 1 file changed, 315 deletions(-) diff --git a/tests/core/pyspec/eth2spec/test/phase0/fork_choice/test_ex_ante.py b/tests/core/pyspec/eth2spec/test/phase0/fork_choice/test_ex_ante.py index b4013c18d..00d58356f 100644 --- a/tests/core/pyspec/eth2spec/test/phase0/fork_choice/test_ex_ante.py +++ b/tests/core/pyspec/eth2spec/test/phase0/fork_choice/test_ex_ante.py @@ -1,6 +1,5 @@ from eth2spec.test.context import ( MAINNET, - spec_configured_state_test, spec_state_test, with_all_phases, with_presets, @@ -94,79 +93,6 @@ def test_ex_ante_vanilla_with_boost(spec, state): yield 'steps', test_steps -@with_all_phases -@spec_configured_state_test({ - 'PROPOSER_SCORE_BOOST': 0, -}) -def test_ex_ante_vanilla_without_boost(spec, state): - """ - With a single adversarial attestation - - NOTE: this case disabled proposer score boost by setting config `PROPOSER_SCORE_BOOST` to `0` - - Block A - slot N - Block B (parent A) - slot N+1 - Block C (parent A) - slot N+2 - Attestation_1 (Block B) - slot N+1 – size 1 - """ - # For testing `PROPOSER_SCORE_BOOST = 0` case - yield 'PROPOSER_SCORE_BOOST', 'meta', 0 - - test_steps = [] - # Initialization - store, anchor_block = get_genesis_forkchoice_store_and_block(spec, state) - yield 'anchor_state', state - yield 'anchor_block', anchor_block - current_time = state.slot * spec.config.SECONDS_PER_SLOT + store.genesis_time - on_tick_and_append_step(spec, store, current_time, test_steps) - assert store.time == current_time - - # On receiving block A at slot `N` - yield from _apply_base_block_a(spec, state, store, test_steps) - state_a = state.copy() - - # Block B at slot `N + 1`, parent is A - state_b = state_a.copy() - block = build_empty_block(spec, state_a, slot=state_a.slot + 1) - signed_block_b = state_transition_and_sign_block(spec, state_b, block) - - # Block C at slot `N + 2`, parent is A - state_c = state_a.copy() - block = build_empty_block(spec, state_c, slot=state_a.slot + 2) - signed_block_c = state_transition_and_sign_block(spec, state_c, block) - - # Attestation_1 received at N+2 — B is head due to boost proposer - def _filter_participant_set(participants): - return [next(iter(participants))] - - attestation = get_valid_attestation( - spec, state_b, slot=state_b.slot, signed=False, filter_participant_set=_filter_participant_set - ) - attestation.data.beacon_block_root = signed_block_b.message.hash_tree_root() - assert len([i for i in attestation.aggregation_bits if i == 1]) == 1 - sign_attestation(spec, state_b, attestation) - - # Block C received at N+2 — C is head - time = state_c.slot * spec.config.SECONDS_PER_SLOT + store.genesis_time - on_tick_and_append_step(spec, store, time, test_steps) - yield from add_block(spec, store, signed_block_c, test_steps) - assert spec.get_head(store) == signed_block_c.message.hash_tree_root() - - # Block B received at N+2 - # Block B and C have the same score 0. Use a lexicographical order for tie-breaking. - yield from add_block(spec, store, signed_block_b, test_steps) - if signed_block_b.message.hash_tree_root() >= signed_block_c.message.hash_tree_root(): - assert spec.get_head(store) == signed_block_b.message.hash_tree_root() - else: - assert spec.get_head(store) == signed_block_c.message.hash_tree_root() - - # Attestation_1 received at N+2 — B is head - yield from add_attestation(spec, store, attestation, test_steps) - assert spec.get_head(store) == signed_block_b.message.hash_tree_root() - - yield 'steps', test_steps - - def _get_greater_than_proposer_boost_score(spec, store, state, proposer_boost_root, root): """ Return the minimum attestation participant count such that attestation_score > proposer_score @@ -255,82 +181,6 @@ def test_ex_ante_attestations_is_greater_than_proposer_boost_with_boost(spec, st yield 'steps', test_steps -@with_all_phases -@spec_configured_state_test({ - 'PROPOSER_SCORE_BOOST': 0, -}) -@with_presets([MAINNET], reason="to create non-duplicate committee") -def test_ex_ante_attestations_is_greater_than_proposer_boost_without_boost(spec, state): - """ - Adversarial attestations > proposer boost - - Block A - slot N - Block B (parent A) - slot N+1 - Block C (parent A) - slot N+2 - Attestation_1 (Block B) - slot N+1 – proposer_boost + 1 participants - """ - # For testing `PROPOSER_SCORE_BOOST = 0` case - yield 'PROPOSER_SCORE_BOOST', 'meta', 0 - - test_steps = [] - # Initialization - store, anchor_block = get_genesis_forkchoice_store_and_block(spec, state) - yield 'anchor_state', state - yield 'anchor_block', anchor_block - current_time = state.slot * spec.config.SECONDS_PER_SLOT + store.genesis_time - on_tick_and_append_step(spec, store, current_time, test_steps) - assert store.time == current_time - - # On receiving block A at slot `N` - yield from _apply_base_block_a(spec, state, store, test_steps) - state_a = state.copy() - - # Block B at slot `N + 1`, parent is A - state_b = state_a.copy() - block = build_empty_block(spec, state_a, slot=state_a.slot + 1) - signed_block_b = state_transition_and_sign_block(spec, state_b, block) - - # Block C at slot `N + 2`, parent is A - state_c = state_a.copy() - block = build_empty_block(spec, state_c, slot=state_a.slot + 2) - signed_block_c = state_transition_and_sign_block(spec, state_c, block) - - # Block C received at N+2 — C is head - time = state_c.slot * spec.config.SECONDS_PER_SLOT + store.genesis_time - on_tick_and_append_step(spec, store, time, test_steps) - yield from add_block(spec, store, signed_block_c, test_steps) - assert spec.get_head(store) == signed_block_c.message.hash_tree_root() - - # Block B received at N+2 - # Block B and C have the same score 0. Use a lexicographical order for tie-breaking. - yield from add_block(spec, store, signed_block_b, test_steps) - if signed_block_b.message.hash_tree_root() >= signed_block_c.message.hash_tree_root(): - assert spec.get_head(store) == signed_block_b.message.hash_tree_root() - else: - assert spec.get_head(store) == signed_block_c.message.hash_tree_root() - - # Attestation of proposer_boost + 1 participants - proposer_boost_root = signed_block_b.message.hash_tree_root() - root = signed_block_b.message.hash_tree_root() - participant_num = _get_greater_than_proposer_boost_score(spec, store, state, proposer_boost_root, root) - - def _filter_participant_set(participants): - return [index for i, index in enumerate(participants) if i < participant_num] - - attestation = get_valid_attestation( - spec, state_b, slot=state_b.slot, signed=False, filter_participant_set=_filter_participant_set - ) - attestation.data.beacon_block_root = signed_block_b.message.hash_tree_root() - assert len([i for i in attestation.aggregation_bits if i == 1]) == participant_num - sign_attestation(spec, state_b, attestation) - - # Attestation_1 received at N+2 — B is head because B's attestation_score > C's attestation_score - yield from add_attestation(spec, store, attestation, test_steps) - assert spec.get_head(store) == signed_block_b.message.hash_tree_root() - - yield 'steps', test_steps - - @with_all_phases @spec_state_test def test_ex_ante_sandwich_without_attestations_with_boost(spec, state): @@ -394,82 +244,6 @@ def test_ex_ante_sandwich_without_attestations_with_boost(spec, state): yield 'steps', test_steps -@with_all_phases -@spec_configured_state_test({ - 'PROPOSER_SCORE_BOOST': 0, -}) -def test_ex_ante_sandwich_without_attestations_without_boost(spec, state): - """ - Simple Sandwich test with no boost and no attestations. - Obejcts: - Block A - slot N - Block B (parent A) - slot N+1 - Block C (parent A) - slot N+2 - Block D (parent B) - slot N+3 - Steps: - Block A received at N — A is head - Block C received at N+2 — C is head - Block B received at N+2 — B or C is head (chosen lexicographically; without boost) - Block D received at N+3 — D or C is head (chosen lexicographically; without boost) - """ - # For testing `PROPOSER_SCORE_BOOST = 0` case - yield 'PROPOSER_SCORE_BOOST', 'meta', 0 - - test_steps = [] - # Initialization - store, anchor_block = get_genesis_forkchoice_store_and_block(spec, state) - yield 'anchor_state', state - yield 'anchor_block', anchor_block - current_time = state.slot * spec.config.SECONDS_PER_SLOT + store.genesis_time - on_tick_and_append_step(spec, store, current_time, test_steps) - assert store.time == current_time - - # On receiving block A at slot `N` - yield from _apply_base_block_a(spec, state, store, test_steps) - state_a = state.copy() - - # Block B at slot `N + 1`, parent is A - state_b = state_a.copy() - block = build_empty_block(spec, state_a, slot=state_a.slot + 1) - signed_block_b = state_transition_and_sign_block(spec, state_b, block) - - # Block C at slot `N + 2`, parent is A - state_c = state_a.copy() - block = build_empty_block(spec, state_c, slot=state_a.slot + 2) - signed_block_c = state_transition_and_sign_block(spec, state_c, block) - - # Block D at slot `N + 3`, parent is B - state_d = state_b.copy() - block = build_empty_block(spec, state_d, slot=state_a.slot + 3) - signed_block_d = state_transition_and_sign_block(spec, state_d, block) - - # Block C received at N+2 — C is head - time = state_c.slot * spec.config.SECONDS_PER_SLOT + store.genesis_time - on_tick_and_append_step(spec, store, time, test_steps) - yield from add_block(spec, store, signed_block_c, test_steps) - assert spec.get_head(store) == signed_block_c.message.hash_tree_root() - - # Block B received at N+2 - # Block B and C have the same score 0. Use a lexicographical order for tie-breaking. - yield from add_block(spec, store, signed_block_b, test_steps) - if signed_block_b.message.hash_tree_root() >= signed_block_c.message.hash_tree_root(): - assert spec.get_head(store) == signed_block_b.message.hash_tree_root() - else: - assert spec.get_head(store) == signed_block_c.message.hash_tree_root() - - # Block D received at N+3 - # Block D and C have the same score 0. Use a lexicographical order for tie-breaking. - time = state_d.slot * spec.config.SECONDS_PER_SLOT + store.genesis_time - on_tick_and_append_step(spec, store, time, test_steps) - yield from add_block(spec, store, signed_block_d, test_steps) - if signed_block_b.message.hash_tree_root() >= signed_block_c.message.hash_tree_root(): - assert spec.get_head(store) == signed_block_d.message.hash_tree_root() - else: - assert spec.get_head(store) == signed_block_c.message.hash_tree_root() - - yield 'steps', test_steps - - @with_all_phases @spec_state_test def test_ex_ante_sandwich_with_honest_attestation_with_boost(spec, state): @@ -551,95 +325,6 @@ def test_ex_ante_sandwich_with_honest_attestation_with_boost(spec, state): yield 'steps', test_steps -@with_all_phases -@spec_configured_state_test({ - 'PROPOSER_SCORE_BOOST': 0, -}) -def test_ex_ante_sandwich_with_honest_attestation_without_boost(spec, state): - """ - Boost necessary to sandwich attack: no boost, so not successful here. - Objects: - Block A - slot N - Block B (parent A) - slot N+1 - Block C (parent A) - slot N+2 - Block D (parent B) - slot N+3 - Attestation_1 (Block C); size 1 - slot N+2 (honest) - Steps: - Block A received at N — A is head - Block C received at N+2 — C is head - Block B received at N+2 — B or C is head (chosen lexicographically) - Attestation_1 received at N+3 — C is head - Block D received at N+3 — C is head - """ - # For testing `PROPOSER_SCORE_BOOST = 0` case - yield 'PROPOSER_SCORE_BOOST', 'meta', 0 - - test_steps = [] - # Initialization - store, anchor_block = get_genesis_forkchoice_store_and_block(spec, state) - yield 'anchor_state', state - yield 'anchor_block', anchor_block - current_time = state.slot * spec.config.SECONDS_PER_SLOT + store.genesis_time - on_tick_and_append_step(spec, store, current_time, test_steps) - assert store.time == current_time - - # On receiving block A at slot `N` - yield from _apply_base_block_a(spec, state, store, test_steps) - state_a = state.copy() - - # Block B at slot `N + 1`, parent is A - state_b = state_a.copy() - block = build_empty_block(spec, state_a, slot=state_a.slot + 1) - signed_block_b = state_transition_and_sign_block(spec, state_b, block) - - # Block C at slot `N + 2`, parent is A - state_c = state_a.copy() - block = build_empty_block(spec, state_c, slot=state_a.slot + 2) - signed_block_c = state_transition_and_sign_block(spec, state_c, block) - - # Block D at slot `N + 3`, parent is B - state_d = state_b.copy() - block = build_empty_block(spec, state_d, slot=state_a.slot + 3) - signed_block_d = state_transition_and_sign_block(spec, state_d, block) - - # Block C received at N+2 — C is head - time = state_c.slot * spec.config.SECONDS_PER_SLOT + store.genesis_time - on_tick_and_append_step(spec, store, time, test_steps) - yield from add_block(spec, store, signed_block_c, test_steps) - assert spec.get_head(store) == signed_block_c.message.hash_tree_root() - - # Block B received at N+2 - # Block B and C have the same score, 0. Use a lexicographical order for tie-breaking. - yield from add_block(spec, store, signed_block_b, test_steps) - if signed_block_b.message.hash_tree_root() >= signed_block_c.message.hash_tree_root(): - assert spec.get_head(store) == signed_block_b.message.hash_tree_root() - else: - assert spec.get_head(store) == signed_block_c.message.hash_tree_root() - - # Attestation_1 at N+2 voting for block C - def _filter_participant_set(participants): - return [next(iter(participants))] - - attestation = get_valid_attestation( - spec, state_c, slot=state_c.slot, signed=False, filter_participant_set=_filter_participant_set - ) - attestation.data.beacon_block_root = signed_block_c.message.hash_tree_root() - assert len([i for i in attestation.aggregation_bits if i == 1]) == 1 - sign_attestation(spec, state_c, attestation) - - # Attestation_1 received at N+3 - C is head - time = state_d.slot * spec.config.SECONDS_PER_SLOT + store.genesis_time - on_tick_and_append_step(spec, store, time, test_steps) - yield from add_attestation(spec, store, attestation, test_steps) - assert spec.get_head(store) == signed_block_c.message.hash_tree_root() - - # Block D received at N+3 - C is head, because block D has no proposer boost - yield from add_block(spec, store, signed_block_d, test_steps) - assert spec.get_head(store) == signed_block_c.message.hash_tree_root() - - yield 'steps', test_steps - - @with_all_phases @with_presets([MAINNET], reason="to create non-duplicate committee") @spec_state_test From 916193bd69848ca80981458226acc2bb9f4840a6 Mon Sep 17 00:00:00 2001 From: vbuterin Date: Fri, 10 Dec 2021 07:48:14 -0600 Subject: [PATCH 32/46] Updates in response to comments --- specs/altair/sync-protocol.md | 31 ++++++++++++++++--------------- 1 file changed, 16 insertions(+), 15 deletions(-) diff --git a/specs/altair/sync-protocol.md b/specs/altair/sync-protocol.md index 8da4a4d98..a36bf782b 100644 --- a/specs/altair/sync-protocol.md +++ b/specs/altair/sync-protocol.md @@ -53,7 +53,6 @@ uses sync committees introduced in [this beacon chain extension](./beacon-chain. | - | - | - | | `MIN_SYNC_COMMITTEE_PARTICIPANTS` | `1` | | | `UPDATE_TIMEOUT` | `SLOTS_PER_EPOCH * EPOCHS_PER_SYNC_COMMITTEE_PERIOD` | ~27.3 hours | -| `SAFETY_THRESHOLD_PERIOD` | `SLOTS_PER_EPOCH * EPOCHS_PER_SYNC_COMMITTEE_PERIOD // 2` | ~13.6 hours | ## Containers @@ -70,8 +69,7 @@ class LightClientUpdate(Container): finalized_header: BeaconBlockHeader finality_branch: Vector[Bytes32, floorlog2(FINALIZED_ROOT_INDEX)] # Sync committee aggregate signature - sync_committee_bits: Bitvector[SYNC_COMMITTEE_SIZE] - sync_committee_signature: BLSSignature + sync_committee_aggregate: SyncAggregate # Fork version for the aggregate signature fork_version: Version ``` @@ -135,9 +133,16 @@ A light client maintains its state in a `store` object of type `LightClientStore ```python def process_slot_for_light_client_store(store: LightClientStore, current_slot: Slot) -> None: - if current_slot % SAFETY_THRESHOLD_PERIOD == 0: + if current_slot % UPDATE_TIMEOUT == 0: store.previous_max_active_participants = store.current_max_active_participants store.current_max_active_participants = 0 + if ( + current_slot > store.finalized_header.slot + UPDATE_TIMEOUT + and store.best_valid_update is not None + ): + # Forced best update when the update timeout has elapsed + apply_light_client_update(store, store.best_valid_update) + store.best_valid_update = None ``` #### `validate_light_client_update` @@ -157,7 +162,8 @@ def validate_light_client_update(store: LightClientStore, update_period = compute_epoch_at_slot(active_header.slot) // EPOCHS_PER_SYNC_COMMITTEE_PERIOD assert update_period in (finalized_period, finalized_period + 1) - # Verify update header root is the finalized root of the finality header, if specified + # Verify that the `finalized_header`, if present, actually is the finalized header saved in the + # state of the `attested header` if update.finalized_header == BeaconBlockHeader(): assert update.finality_branch == [Bytes32() for _ in range(floorlog2(FINALIZED_ROOT_INDEX))] else: @@ -182,15 +188,17 @@ def validate_light_client_update(store: LightClientStore, index=get_subtree_index(NEXT_SYNC_COMMITTEE_INDEX), root=active_header.state_root, ) + + sync_aggregate = update.sync_committee_aggregate # Verify sync committee has sufficient participants - assert sum(update.sync_committee_bits) >= MIN_SYNC_COMMITTEE_PARTICIPANTS + assert sum(sync_aggregate.sync_committee_bits) >= MIN_SYNC_COMMITTEE_PARTICIPANTS # Verify sync committee aggregate signature - participant_pubkeys = [pubkey for (bit, pubkey) in zip(update.sync_committee_bits, sync_committee.pubkeys) if bit] + participant_pubkeys = [pubkey for (bit, pubkey) in zip(sync_aggregate.sync_committee_bits, sync_committee.pubkeys) if bit] domain = compute_domain(DOMAIN_SYNC_COMMITTEE, update.fork_version, genesis_validators_root) signing_root = compute_signing_root(update.attested_header, domain) - assert bls.FastAggregateVerify(participant_pubkeys, signing_root, update.sync_committee_signature) + assert bls.FastAggregateVerify(participant_pubkeys, signing_root, sync_aggregate.sync_committee_signature) ``` #### `apply_light_client_update` @@ -244,11 +252,4 @@ def process_light_client_update(store: LightClientStore, # Normal update through 2/3 threshold apply_light_client_update(store, update) store.best_valid_update = None - elif ( - current_slot > store.finalized_header.slot + UPDATE_TIMEOUT - and store.best_valid_update is not None - ): - # Forced best update when the update timeout has elapsed - apply_light_client_update(store, store.best_valid_update) - store.best_valid_update = None ``` From 4cea972cc0f70e5f64e42d1832fdce3b0470af8a Mon Sep 17 00:00:00 2001 From: Hsiao-Wei Wang Date: Sat, 11 Dec 2021 09:01:16 +0800 Subject: [PATCH 33/46] Apply suggestions from code review Co-authored-by: Caspar Schwarz-Schilling <31305984+casparschwa@users.noreply.github.com> --- .../test/phase0/fork_choice/test_ex_ante.py | 46 +++++++++++-------- 1 file changed, 28 insertions(+), 18 deletions(-) diff --git a/tests/core/pyspec/eth2spec/test/phase0/fork_choice/test_ex_ante.py b/tests/core/pyspec/eth2spec/test/phase0/fork_choice/test_ex_ante.py index 00d58356f..660b00eb7 100644 --- a/tests/core/pyspec/eth2spec/test/phase0/fork_choice/test_ex_ante.py +++ b/tests/core/pyspec/eth2spec/test/phase0/fork_choice/test_ex_ante.py @@ -33,14 +33,19 @@ def _apply_base_block_a(spec, state, store, test_steps): @with_all_phases @spec_state_test -def test_ex_ante_vanilla_with_boost(spec, state): +def test_ex_ante_vanilla(spec, state): """ With a single adversarial attestation - - Block A - slot N - Block B (parent A) - slot N+1 - Block C (parent A) - slot N+2 - Attestation_1 (Block B) - slot N+1 – size 1 + Objects: + Block A - slot N + Block B (parent A) - slot N+1 + Block C (parent A) - slot N+2 + Attestation_1 (Block B); size `1` - slot N+1 + Steps: + Block A received at N — A is head + Block C received at N+2 — C is head + Block B received at N+2 — C is head + Attestation_1 received at N+2 — C is head """ test_steps = [] # Initialization @@ -65,7 +70,7 @@ def test_ex_ante_vanilla_with_boost(spec, state): block = build_empty_block(spec, state_c, slot=state_a.slot + 2) signed_block_c = state_transition_and_sign_block(spec, state_c, block) - # Attestation_1 received at N+2 — B is head due to boost proposer + # Attestation_1 at slot `N + 1` voting for block B def _filter_participant_set(participants): return [next(iter(participants))] @@ -82,7 +87,7 @@ def test_ex_ante_vanilla_with_boost(spec, state): yield from add_block(spec, store, signed_block_c, test_steps) assert spec.get_head(store) == signed_block_c.message.hash_tree_root() - # Block B received at N+2 — C is head that has higher proposer score boost + # Block B received at N+2 — C is head due to proposer score boost yield from add_block(spec, store, signed_block_b, test_steps) assert spec.get_head(store) == signed_block_c.message.hash_tree_root() @@ -119,11 +124,16 @@ def _get_greater_than_proposer_boost_score(spec, store, state, proposer_boost_ro def test_ex_ante_attestations_is_greater_than_proposer_boost_with_boost(spec, state): """ Adversarial attestations > proposer boost - - Block A - slot N - Block B (parent A) - slot N+1 - Block C (parent A) - slot N+2 - Attestation_1 (Block B) - slot N+1 – proposer_boost + 1 participants + Objects: + Block A - slot N + Block B (parent A) - slot N+1 + Block C (parent A) - slot N+2 + Attestation_set_1 (Block B); size `proposer_boost + 1` - slot N+1 + Steps: + Block A received at N — A is head + Block C received at N+2 — C is head + Block B received at N+2 — C is head + Attestation_1 received at N+2 — B is head """ test_steps = [] # Initialization @@ -154,11 +164,11 @@ def test_ex_ante_attestations_is_greater_than_proposer_boost_with_boost(spec, st yield from add_block(spec, store, signed_block_c, test_steps) assert spec.get_head(store) == signed_block_c.message.hash_tree_root() - # Block B received at N+2 — C is head that has higher proposer score boost + # Block B received at N+2 — C is head due to proposer score boost yield from add_block(spec, store, signed_block_b, test_steps) assert spec.get_head(store) == signed_block_c.message.hash_tree_root() - # Attestation of proposer_boost + 1 participants + # Attestation_set_1 at slot `N + 1` voting for block B proposer_boost_root = signed_block_b.message.hash_tree_root() root = signed_block_b.message.hash_tree_root() participant_num = _get_greater_than_proposer_boost_score(spec, store, state, proposer_boost_root, root) @@ -173,7 +183,7 @@ def test_ex_ante_attestations_is_greater_than_proposer_boost_with_boost(spec, st assert len([i for i in attestation.aggregation_bits if i == 1]) == participant_num sign_attestation(spec, state_b, attestation) - # Attestation_1 received at N+2 — B is head because B's attestation_score > C's proposer_score. + # Attestation_set_1 received at N+2 — B is head because B's attestation_score > C's proposer_score. # (B's proposer_score = C's attestation_score = 0) yield from add_attestation(spec, store, attestation, test_steps) assert spec.get_head(store) == signed_block_b.message.hash_tree_root() @@ -183,7 +193,7 @@ def test_ex_ante_attestations_is_greater_than_proposer_boost_with_boost(spec, st @with_all_phases @spec_state_test -def test_ex_ante_sandwich_without_attestations_with_boost(spec, state): +def test_ex_ante_sandwich_without_attestations(spec, state): """ Simple Sandwich test with boost and no attestations. Obejcts: @@ -246,7 +256,7 @@ def test_ex_ante_sandwich_without_attestations_with_boost(spec, state): @with_all_phases @spec_state_test -def test_ex_ante_sandwich_with_honest_attestation_with_boost(spec, state): +def test_ex_ante_sandwich_with_honest_attestation(spec, state): """ Boosting necessary to sandwich attack. Objects: From 6edf840992af06f1aac868fc3a3a1e28e09c8098 Mon Sep 17 00:00:00 2001 From: Hsiao-Wei Wang Date: Sat, 11 Dec 2021 09:06:56 +0800 Subject: [PATCH 34/46] fix lint --- .../pyspec/eth2spec/test/phase0/fork_choice/test_ex_ante.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/core/pyspec/eth2spec/test/phase0/fork_choice/test_ex_ante.py b/tests/core/pyspec/eth2spec/test/phase0/fork_choice/test_ex_ante.py index 660b00eb7..d93101156 100644 --- a/tests/core/pyspec/eth2spec/test/phase0/fork_choice/test_ex_ante.py +++ b/tests/core/pyspec/eth2spec/test/phase0/fork_choice/test_ex_ante.py @@ -36,7 +36,7 @@ def _apply_base_block_a(spec, state, store, test_steps): def test_ex_ante_vanilla(spec, state): """ With a single adversarial attestation - Objects: + Objects: Block A - slot N Block B (parent A) - slot N+1 Block C (parent A) - slot N+2 @@ -124,7 +124,7 @@ def _get_greater_than_proposer_boost_score(spec, store, state, proposer_boost_ro def test_ex_ante_attestations_is_greater_than_proposer_boost_with_boost(spec, state): """ Adversarial attestations > proposer boost - Objects: + Objects: Block A - slot N Block B (parent A) - slot N+1 Block C (parent A) - slot N+2 From 2f618f7b483310303525dfe592d176f3fc91f087 Mon Sep 17 00:00:00 2001 From: Hsiao-Wei Wang Date: Tue, 14 Dec 2021 21:38:58 +0800 Subject: [PATCH 35/46] Fix lint and presets --- presets/mainnet/altair.yaml | 2 -- presets/minimal/altair.yaml | 2 -- specs/altair/sync-protocol.md | 5 ++++- 3 files changed, 4 insertions(+), 5 deletions(-) diff --git a/presets/mainnet/altair.yaml b/presets/mainnet/altair.yaml index 21e3cc328..813ef7212 100644 --- a/presets/mainnet/altair.yaml +++ b/presets/mainnet/altair.yaml @@ -24,5 +24,3 @@ EPOCHS_PER_SYNC_COMMITTEE_PERIOD: 256 MIN_SYNC_COMMITTEE_PARTICIPANTS: 1 # SLOTS_PER_EPOCH * EPOCHS_PER_SYNC_COMMITTEE_PERIOD (= 32 * 256) UPDATE_TIMEOUT: 8192 -# SLOTS_PER_EPOCH * EPOCHS_PER_SYNC_COMMITTEE_PERIOD // 2 (= 32 * 256 // 2) -SAFETY_THRESHOLD_PERIOD: 4096 diff --git a/presets/minimal/altair.yaml b/presets/minimal/altair.yaml index 7cdbd58ea..5e472c49c 100644 --- a/presets/minimal/altair.yaml +++ b/presets/minimal/altair.yaml @@ -24,5 +24,3 @@ EPOCHS_PER_SYNC_COMMITTEE_PERIOD: 8 MIN_SYNC_COMMITTEE_PARTICIPANTS: 1 # SLOTS_PER_EPOCH * EPOCHS_PER_SYNC_COMMITTEE_PERIOD (= 8 * 8) UPDATE_TIMEOUT: 64 -# SLOTS_PER_EPOCH * EPOCHS_PER_SYNC_COMMITTEE_PERIOD // 2 (= 8 * 8 // 2) -SAFETY_THRESHOLD_PERIOD: 32 diff --git a/specs/altair/sync-protocol.md b/specs/altair/sync-protocol.md index a36bf782b..6dae145b9 100644 --- a/specs/altair/sync-protocol.md +++ b/specs/altair/sync-protocol.md @@ -195,7 +195,10 @@ def validate_light_client_update(store: LightClientStore, assert sum(sync_aggregate.sync_committee_bits) >= MIN_SYNC_COMMITTEE_PARTICIPANTS # Verify sync committee aggregate signature - participant_pubkeys = [pubkey for (bit, pubkey) in zip(sync_aggregate.sync_committee_bits, sync_committee.pubkeys) if bit] + participant_pubkeys = [ + pubkey for (bit, pubkey) in zip(sync_aggregate.sync_committee_bits, sync_committee.pubkeys) + if bit + ] domain = compute_domain(DOMAIN_SYNC_COMMITTEE, update.fork_version, genesis_validators_root) signing_root = compute_signing_root(update.attested_header, domain) assert bls.FastAggregateVerify(participant_pubkeys, signing_root, sync_aggregate.sync_committee_signature) From 25d88fee244cc687b0cd98a5a2b7cadb9d01f94a Mon Sep 17 00:00:00 2001 From: Hsiao-Wei Wang Date: Tue, 14 Dec 2021 22:05:09 +0800 Subject: [PATCH 36/46] Fix `process_light_client_update` --- specs/altair/sync-protocol.md | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/specs/altair/sync-protocol.md b/specs/altair/sync-protocol.md index 6dae145b9..fb86c2bf1 100644 --- a/specs/altair/sync-protocol.md +++ b/specs/altair/sync-protocol.md @@ -226,30 +226,32 @@ def process_light_client_update(store: LightClientStore, genesis_validators_root: Root) -> None: validate_light_client_update(store, update, current_slot, genesis_validators_root) - + + sync_committee_bits = update.sync_committee_aggregate.sync_committee_bits + # Update the best update in case we have to force-update to it if the timeout elapses if ( store.best_valid_update is None - or sum(update.sync_committee_bits) > sum(store.best_valid_update.sync_committee_bits) + or sum(sync_committee_bits) > sum(store.best_valid_update.sync_committee_aggregate.sync_committee_bits) ): store.best_valid_update = update # Track the maximum number of active participants in the committee signatures store.current_max_active_participants = max( store.current_max_active_participants, - sum(update.sync_committee_bits), + sum(sync_committee_bits), ) # Update the optimistic header if ( - sum(update.sync_committee_bits) > get_safety_threshold(store) and + sum(sync_committee_bits) > get_safety_threshold(store) and update.attested_header.slot > store.optimistic_header.slot ): store.optimistic_header = update.attested_header # Update finalized header if ( - sum(update.sync_committee_bits) * 3 >= len(update.sync_committee_bits) * 2 + sum(sync_committee_bits) * 3 >= len(sync_committee_bits) * 2 and update.finalized_header != BeaconBlockHeader() ): # Normal update through 2/3 threshold From 257c2413a57ec4b311f462931b47861be8ec9ada Mon Sep 17 00:00:00 2001 From: Hsiao-Wei Wang Date: Tue, 14 Dec 2021 22:06:25 +0800 Subject: [PATCH 37/46] Update test_sync_protocol.py per the new optimistic_header and data structure --- .../altair/unittests/test_sync_protocol.py | 116 +++++++++--------- 1 file changed, 61 insertions(+), 55 deletions(-) diff --git a/tests/core/pyspec/eth2spec/test/altair/unittests/test_sync_protocol.py b/tests/core/pyspec/eth2spec/test/altair/unittests/test_sync_protocol.py index 15444df81..30444c4ce 100644 --- a/tests/core/pyspec/eth2spec/test/altair/unittests/test_sync_protocol.py +++ b/tests/core/pyspec/eth2spec/test/altair/unittests/test_sync_protocol.py @@ -1,3 +1,5 @@ +from copy import deepcopy + from eth2spec.test.context import ( spec_state_test, with_presets, @@ -19,20 +21,24 @@ from eth2spec.test.helpers.sync_committee import ( from eth2spec.test.helpers.merkle import build_proof -@with_altair_and_later -@spec_state_test -def test_process_light_client_update_not_updated(spec, state): - pre_snapshot = spec.LightClientSnapshot( - header=spec.BeaconBlockHeader(), +def _initialize_light_client_store(spec, state): + return spec.LightClientStore( + finalized_header=spec.BeaconBlockHeader(), current_sync_committee=state.current_sync_committee, next_sync_committee=state.next_sync_committee, - ) - store = spec.LightClientStore( - snapshot=pre_snapshot, - valid_updates=set(), + best_valid_update=None, + optimistic_header=spec.BeaconBlockHeader(), + previous_max_active_participants=0, + current_max_active_participants=0, ) - # Block at slot 1 doesn't increase sync committee period, so it won't update snapshot + +@with_altair_and_later +@spec_state_test +def test_process_light_client_update_not_timeout(spec, state): + store = _initialize_light_client_store(spec, state) + + # Block at slot 1 doesn't increase sync committee period, so it won't force update store.finalized_header block = build_empty_block_for_next_slot(spec, state) signed_block = state_transition_and_sign_block(spec, state, block) block_header = spec.BeaconBlockHeader( @@ -52,6 +58,10 @@ def test_process_light_client_update_not_updated(spec, state): block_header.slot, committee, ) + sync_committee_aggregate = spec.SyncAggregate( + sync_committee_bits=sync_committee_bits, + sync_committee_signature=sync_committee_signature, + ) next_sync_committee_branch = [spec.Bytes32() for _ in range(spec.floorlog2(spec.NEXT_SYNC_COMMITTEE_INDEX))] # Ensure that finality checkpoint is genesis @@ -61,40 +71,34 @@ def test_process_light_client_update_not_updated(spec, state): finality_branch = [spec.Bytes32() for _ in range(spec.floorlog2(spec.FINALIZED_ROOT_INDEX))] update = spec.LightClientUpdate( - header=block_header, + attested_header=block_header, next_sync_committee=state.next_sync_committee, next_sync_committee_branch=next_sync_committee_branch, - finality_header=finality_header, + finalized_header=finality_header, finality_branch=finality_branch, - sync_committee_bits=sync_committee_bits, - sync_committee_signature=sync_committee_signature, + sync_committee_aggregate=sync_committee_aggregate, fork_version=state.fork.current_version, ) + pre_store = deepcopy(store) + spec.process_light_client_update(store, update, state.slot, state.genesis_validators_root) - assert len(store.valid_updates) == 1 - assert store.valid_updates.pop() == update - assert store.snapshot == pre_snapshot + assert store.current_max_active_participants > 0 + assert store.optimistic_header == update.attested_header + assert store.finalized_header == pre_store.finalized_header + assert store.best_valid_update == update @with_altair_and_later @spec_state_test @with_presets([MINIMAL], reason="too slow") def test_process_light_client_update_timeout(spec, state): - pre_snapshot = spec.LightClientSnapshot( - header=spec.BeaconBlockHeader(), - current_sync_committee=state.current_sync_committee, - next_sync_committee=state.next_sync_committee, - ) - store = spec.LightClientStore( - snapshot=pre_snapshot, - valid_updates=set(), - ) + store = _initialize_light_client_store(spec, state) # Forward to next sync committee period - next_slots(spec, state, spec.SLOTS_PER_EPOCH * (spec.EPOCHS_PER_SYNC_COMMITTEE_PERIOD)) - snapshot_period = spec.compute_epoch_at_slot(pre_snapshot.header.slot) // spec.EPOCHS_PER_SYNC_COMMITTEE_PERIOD + next_slots(spec, state, spec.UPDATE_TIMEOUT) + snapshot_period = spec.compute_epoch_at_slot(store.optimistic_header.slot) // spec.EPOCHS_PER_SYNC_COMMITTEE_PERIOD update_period = spec.compute_epoch_at_slot(state.slot) // spec.EPOCHS_PER_SYNC_COMMITTEE_PERIOD assert snapshot_period + 1 == update_period @@ -119,6 +123,10 @@ def test_process_light_client_update_timeout(spec, state): committee, block_root=spec.Root(block_header.hash_tree_root()), ) + sync_committee_aggregate = spec.SyncAggregate( + sync_committee_bits=sync_committee_bits, + sync_committee_signature=sync_committee_signature, + ) # Sync committee is updated next_sync_committee_branch = build_proof(state.get_backing(), spec.NEXT_SYNC_COMMITTEE_INDEX) @@ -127,36 +135,30 @@ def test_process_light_client_update_timeout(spec, state): finality_branch = [spec.Bytes32() for _ in range(spec.floorlog2(spec.FINALIZED_ROOT_INDEX))] update = spec.LightClientUpdate( - header=block_header, + attested_header=block_header, next_sync_committee=state.next_sync_committee, next_sync_committee_branch=next_sync_committee_branch, - finality_header=finality_header, + finalized_header=finality_header, finality_branch=finality_branch, - sync_committee_bits=sync_committee_bits, - sync_committee_signature=sync_committee_signature, + sync_committee_aggregate=sync_committee_aggregate, fork_version=state.fork.current_version, ) + pre_store = deepcopy(store) + spec.process_light_client_update(store, update, state.slot, state.genesis_validators_root) - # snapshot has been updated - assert len(store.valid_updates) == 0 - assert store.snapshot.header == update.header + assert store.current_max_active_participants > 0 + assert store.optimistic_header == update.attested_header + assert store.best_valid_update == update + assert store.finalized_header == pre_store.finalized_header @with_altair_and_later @spec_state_test @with_presets([MINIMAL], reason="too slow") def test_process_light_client_update_finality_updated(spec, state): - pre_snapshot = spec.LightClientSnapshot( - header=spec.BeaconBlockHeader(), - current_sync_committee=state.current_sync_committee, - next_sync_committee=state.next_sync_committee, - ) - store = spec.LightClientStore( - snapshot=pre_snapshot, - valid_updates=set(), - ) + store = _initialize_light_client_store(spec, state) # Change finality blocks = [] @@ -167,7 +169,7 @@ def test_process_light_client_update_finality_updated(spec, state): # Ensure that finality checkpoint has changed assert state.finalized_checkpoint.epoch == 3 # Ensure that it's same period - snapshot_period = spec.compute_epoch_at_slot(pre_snapshot.header.slot) // spec.EPOCHS_PER_SYNC_COMMITTEE_PERIOD + snapshot_period = spec.compute_epoch_at_slot(store.optimistic_header.slot) // spec.EPOCHS_PER_SYNC_COMMITTEE_PERIOD update_period = spec.compute_epoch_at_slot(state.slot) // spec.EPOCHS_PER_SYNC_COMMITTEE_PERIOD assert snapshot_period == update_period @@ -199,20 +201,24 @@ def test_process_light_client_update_finality_updated(spec, state): committee, block_root=spec.Root(block_header.hash_tree_root()), ) - - update = spec.LightClientUpdate( - header=finalized_block_header, - next_sync_committee=state.next_sync_committee, - next_sync_committee_branch=next_sync_committee_branch, - finality_header=block_header, # block_header is the signed header - finality_branch=finality_branch, + sync_committee_aggregate = spec.SyncAggregate( sync_committee_bits=sync_committee_bits, sync_committee_signature=sync_committee_signature, + ) + + update = spec.LightClientUpdate( + attested_header=block_header, + next_sync_committee=state.next_sync_committee, + next_sync_committee_branch=next_sync_committee_branch, + finalized_header=finalized_block_header, + finality_branch=finality_branch, + sync_committee_aggregate=sync_committee_aggregate, fork_version=state.fork.current_version, ) spec.process_light_client_update(store, update, state.slot, state.genesis_validators_root) - # snapshot has been updated - assert len(store.valid_updates) == 0 - assert store.snapshot.header == update.header + assert store.current_max_active_participants > 0 + assert store.optimistic_header == update.attested_header + assert store.finalized_header == update.finalized_header + assert store.best_valid_update is None From de892382db6135197c68dadb205e834a8b44ed51 Mon Sep 17 00:00:00 2001 From: Hsiao-Wei Wang Date: Wed, 15 Dec 2021 23:43:54 +0800 Subject: [PATCH 38/46] Minor style fixes --- specs/altair/sync-protocol.md | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/specs/altair/sync-protocol.md b/specs/altair/sync-protocol.md index fb86c2bf1..c8c7c3d4d 100644 --- a/specs/altair/sync-protocol.md +++ b/specs/altair/sync-protocol.md @@ -120,8 +120,8 @@ def get_active_header(update: LightClientUpdate) -> BeaconBlockHeader: ```python def get_safety_threshold(store: LightClientStore) -> uint64: return max( - store.previous_max_active_participants, - store.current_max_active_participants + store.previous_max_active_participants, + store.current_max_active_participants, ) // 2 ``` @@ -152,7 +152,6 @@ def validate_light_client_update(store: LightClientStore, update: LightClientUpdate, current_slot: Slot, genesis_validators_root: Root) -> None: - # Verify update slot is larger than slot of current best finalized header active_header = get_active_header(update) assert current_slot >= active_header.slot > store.finalized_header.slot @@ -224,7 +223,6 @@ def process_light_client_update(store: LightClientStore, update: LightClientUpdate, current_slot: Slot, genesis_validators_root: Root) -> None: - validate_light_client_update(store, update, current_slot, genesis_validators_root) sync_committee_bits = update.sync_committee_aggregate.sync_committee_bits @@ -235,20 +233,20 @@ def process_light_client_update(store: LightClientStore, or sum(sync_committee_bits) > sum(store.best_valid_update.sync_committee_aggregate.sync_committee_bits) ): store.best_valid_update = update - + # Track the maximum number of active participants in the committee signatures store.current_max_active_participants = max( store.current_max_active_participants, sum(sync_committee_bits), ) - + # Update the optimistic header if ( - sum(sync_committee_bits) > get_safety_threshold(store) and - update.attested_header.slot > store.optimistic_header.slot + sum(sync_committee_bits) > get_safety_threshold(store) + and update.attested_header.slot > store.optimistic_header.slot ): store.optimistic_header = update.attested_header - + # Update finalized header if ( sum(sync_committee_bits) * 3 >= len(sync_committee_bits) * 2 From cba3ddfc3de99e8dcc770a77e210c99693e47604 Mon Sep 17 00:00:00 2001 From: Hsiao-Wei Wang Date: Wed, 22 Dec 2021 15:47:38 +0800 Subject: [PATCH 39/46] bump VERSION.txt to 1.1.7 --- tests/core/pyspec/eth2spec/VERSION.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/core/pyspec/eth2spec/VERSION.txt b/tests/core/pyspec/eth2spec/VERSION.txt index ab679818c..a5ba93251 100644 --- a/tests/core/pyspec/eth2spec/VERSION.txt +++ b/tests/core/pyspec/eth2spec/VERSION.txt @@ -1 +1 @@ -1.1.6 \ No newline at end of file +1.1.7 \ No newline at end of file From 8facc0a095a3fca85dcec984bbbd29f61c763897 Mon Sep 17 00:00:00 2001 From: Hsiao-Wei Wang Date: Wed, 22 Dec 2021 15:46:18 +0800 Subject: [PATCH 40/46] Merge -> Bellatrix --- .circleci/config.yml | 6 +-- .gitignore | 2 +- Makefile | 12 ++--- README.md | 20 +++---- configs/mainnet.yaml | 6 +-- configs/minimal.yaml | 6 +-- .../mainnet/{merge.yaml => bellatrix.yaml} | 8 +-- .../minimal/{merge.yaml => bellatrix.yaml} | 6 +-- setup.py | 24 ++++----- specs/{merge => bellatrix}/beacon-chain.md | 44 +++++++-------- specs/{merge => bellatrix}/fork-choice.md | 4 +- specs/{merge => bellatrix}/fork.md | 24 ++++----- specs/{merge => bellatrix}/p2p-interface.md | 54 +++++++++---------- specs/{merge => bellatrix}/validator.md | 4 +- specs/custody_game/beacon-chain.md | 3 +- specs/sharding/beacon-chain.md | 6 +-- tests/README.md | 10 ++-- .../{helpers/merge => bellatrix}/__init__.py | 0 .../block_processing}/__init__.py | 0 .../test_process_execution_payload.py | 26 ++++----- .../fork}/__init__.py | 0 .../fork/test_merge_fork_basic.py | 48 ++++++++--------- .../fork/test_merge_fork_random.py | 48 ++++++++--------- .../fork_choice}/__init__.py | 0 .../fork_choice/test_on_merge_block.py | 10 ++-- .../genesis}/__init__.py | 0 .../genesis/test_initialization.py | 10 ++-- .../genesis => bellatrix/sanity}/__init__.py | 0 .../sanity/test_blocks.py | 6 +-- .../transition}/__init__.py | 0 .../transition/test_transition.py | 4 +- .../unittests}/__init__.py | 0 .../test_is_valid_terminal_pow_block.py | 8 +-- .../unittests/test_transition.py | 8 +-- .../unittests/test_validate_merge_block.py | 18 +++---- .../unittests/validator/test_validator.py | 6 +-- tests/core/pyspec/eth2spec/test/context.py | 20 +++---- .../bellatrix}/__init__.py | 0 .../test/helpers/{merge => bellatrix}/fork.py | 8 +-- .../pyspec/eth2spec/test/helpers/block.py | 4 +- .../eth2spec/test/helpers/block_processing.py | 2 +- .../pyspec/eth2spec/test/helpers/constants.py | 14 ++--- .../eth2spec/test/helpers/fork_transition.py | 10 ++-- .../pyspec/eth2spec/test/helpers/genesis.py | 10 ++-- .../test/helpers/proposer_slashings.py | 6 +-- .../pyspec/eth2spec/test/helpers/rewards.py | 6 +-- .../test_process_slashings.py | 6 +-- .../test/phase0/sanity/test_blocks.py | 6 +-- .../fork_choice/test_on_attestation.py | 4 +- .../unittests/test_config_invariants.py | 6 +-- tests/formats/forks/README.md | 2 +- tests/formats/genesis/initialization.md | 2 +- tests/formats/operations/README.md | 2 +- tests/generators/epoch_processing/main.py | 9 ++-- tests/generators/finality/main.py | 6 +-- tests/generators/fork_choice/main.py | 8 +-- tests/generators/genesis/main.py | 8 +-- tests/generators/merkle/main.py | 6 +-- tests/generators/operations/main.py | 8 +-- tests/generators/rewards/main.py | 10 ++-- tests/generators/sanity/main.py | 8 +-- tests/generators/transition/main.py | 10 ++-- 62 files changed, 307 insertions(+), 305 deletions(-) rename presets/mainnet/{merge.yaml => bellatrix.yaml} (71%) rename presets/minimal/{merge.yaml => bellatrix.yaml} (76%) rename specs/{merge => bellatrix}/beacon-chain.md (93%) rename specs/{merge => bellatrix}/fork-choice.md (99%) rename specs/{merge => bellatrix}/fork.md (79%) rename specs/{merge => bellatrix}/p2p-interface.md (75%) rename specs/{merge => bellatrix}/validator.md (98%) rename tests/core/pyspec/eth2spec/test/{helpers/merge => bellatrix}/__init__.py (100%) rename tests/core/pyspec/eth2spec/test/{merge => bellatrix/block_processing}/__init__.py (100%) rename tests/core/pyspec/eth2spec/test/{merge => bellatrix}/block_processing/test_process_execution_payload.py (95%) rename tests/core/pyspec/eth2spec/test/{merge/block_processing => bellatrix/fork}/__init__.py (100%) rename tests/core/pyspec/eth2spec/test/{merge => bellatrix}/fork/test_merge_fork_basic.py (55%) rename tests/core/pyspec/eth2spec/test/{merge => bellatrix}/fork/test_merge_fork_random.py (58%) rename tests/core/pyspec/eth2spec/test/{merge/fork => bellatrix/fork_choice}/__init__.py (100%) rename tests/core/pyspec/eth2spec/test/{merge => bellatrix}/fork_choice/test_on_merge_block.py (98%) rename tests/core/pyspec/eth2spec/test/{merge/fork_choice => bellatrix/genesis}/__init__.py (100%) rename tests/core/pyspec/eth2spec/test/{merge => bellatrix}/genesis/test_initialization.py (96%) rename tests/core/pyspec/eth2spec/test/{merge/genesis => bellatrix/sanity}/__init__.py (100%) rename tests/core/pyspec/eth2spec/test/{merge => bellatrix}/sanity/test_blocks.py (92%) rename tests/core/pyspec/eth2spec/test/{merge/sanity => bellatrix/transition}/__init__.py (100%) rename tests/core/pyspec/eth2spec/test/{merge => bellatrix}/transition/test_transition.py (91%) rename tests/core/pyspec/eth2spec/test/{merge/transition => bellatrix/unittests}/__init__.py (100%) rename tests/core/pyspec/eth2spec/test/{merge => bellatrix}/unittests/test_is_valid_terminal_pow_block.py (93%) rename tests/core/pyspec/eth2spec/test/{merge => bellatrix}/unittests/test_transition.py (93%) rename tests/core/pyspec/eth2spec/test/{merge => bellatrix}/unittests/test_validate_merge_block.py (96%) rename tests/core/pyspec/eth2spec/test/{merge => bellatrix}/unittests/validator/test_validator.py (98%) rename tests/core/pyspec/eth2spec/test/{merge/unittests => helpers/bellatrix}/__init__.py (100%) rename tests/core/pyspec/eth2spec/test/helpers/{merge => bellatrix}/fork.py (87%) diff --git a/.circleci/config.yml b/.circleci/config.yml index bcce74bd9..31da1db0f 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -116,7 +116,7 @@ jobs: command: make citest fork=altair - store_test_results: path: tests/core/pyspec/test-reports - test-merge: + test-bellatrix: docker: - image: circleci/python:3.8 working_directory: ~/specs-repo @@ -126,7 +126,7 @@ jobs: - restore_pyspec_cached_venv - run: name: Run py-tests - command: make citest fork=merge + command: make citest fork=bellatrix - store_test_results: path: tests/core/pyspec/test-reports table_of_contents: @@ -240,7 +240,7 @@ workflows: - test-altair: requires: - install_pyspec_test - - test-merge: + - test-bellatrix: requires: - install_pyspec_test - table_of_contents diff --git a/.gitignore b/.gitignore index 76fe21ddd..243d099bf 100644 --- a/.gitignore +++ b/.gitignore @@ -17,7 +17,7 @@ consensus-spec-tests/ # Dynamically built from Markdown spec tests/core/pyspec/eth2spec/phase0/ tests/core/pyspec/eth2spec/altair/ -tests/core/pyspec/eth2spec/merge/ +tests/core/pyspec/eth2spec/bellatrix/ # coverage reports .htmlcov diff --git a/Makefile b/Makefile index 7810ff19e..ca0096fb9 100644 --- a/Makefile +++ b/Makefile @@ -24,7 +24,7 @@ GENERATOR_VENVS = $(patsubst $(GENERATOR_DIR)/%, $(GENERATOR_DIR)/%venv, $(GENER #$(info $$GENERATOR_TARGETS is [${GENERATOR_TARGETS}]) MARKDOWN_FILES = $(wildcard $(SPEC_DIR)/phase0/*.md) $(wildcard $(SPEC_DIR)/altair/*.md) $(wildcard $(SSZ_DIR)/*.md) \ - $(wildcard $(SPEC_DIR)/merge/*.md) \ + $(wildcard $(SPEC_DIR)/bellatrix/*.md) \ $(wildcard $(SPEC_DIR)/custody/*.md) \ $(wildcard $(SPEC_DIR)/das/*.md) \ $(wildcard $(SPEC_DIR)/sharding/*.md) @@ -59,7 +59,7 @@ partial_clean: rm -rf $(DEPOSIT_CONTRACT_TESTER_DIR)/.pytest_cache rm -rf $(ETH2SPEC_MODULE_DIR)/phase0 rm -rf $(ETH2SPEC_MODULE_DIR)/altair - rm -rf $(ETH2SPEC_MODULE_DIR)/merge + rm -rf $(ETH2SPEC_MODULE_DIR)/bellatrix rm -rf $(COV_HTML_OUT_DIR) rm -rf $(TEST_REPORT_DIR) rm -rf eth2spec.egg-info dist build @@ -97,12 +97,12 @@ install_test: # Testing against `minimal` config by default test: pyspec . venv/bin/activate; cd $(PY_SPEC_DIR); \ - python3 -m pytest -n 4 --disable-bls --cov=eth2spec.phase0.minimal --cov=eth2spec.altair.minimal --cov=eth2spec.merge.minimal --cov-report="html:$(COV_HTML_OUT)" --cov-branch eth2spec + python3 -m pytest -n 4 --disable-bls --cov=eth2spec.phase0.minimal --cov=eth2spec.altair.minimal --cov=eth2spec.bellatrix.minimal --cov-report="html:$(COV_HTML_OUT)" --cov-branch eth2spec # Testing against `minimal` config by default find_test: pyspec . venv/bin/activate; cd $(PY_SPEC_DIR); \ - python3 -m pytest -k=$(K) --disable-bls --cov=eth2spec.phase0.minimal --cov=eth2spec.altair.minimal --cov=eth2spec.merge.minimal --cov-report="html:$(COV_HTML_OUT)" --cov-branch eth2spec + python3 -m pytest -k=$(K) --disable-bls --cov=eth2spec.phase0.minimal --cov=eth2spec.altair.minimal --cov=eth2spec.bellatrix.minimal --cov-report="html:$(COV_HTML_OUT)" --cov-branch eth2spec citest: pyspec mkdir -p tests/core/pyspec/test-reports/eth2spec; @@ -129,11 +129,11 @@ check_toc: $(MARKDOWN_FILES:=.toc) codespell: codespell . --skip ./.git -I .codespell-whitelist -# TODO: add future merge, sharding, etc. packages to linting. +# TODO: add future protocol upgrade patch packages to linting. lint: pyspec . venv/bin/activate; cd $(PY_SPEC_DIR); \ flake8 --config $(LINTER_CONFIG_FILE) ./eth2spec \ - && mypy --config-file $(LINTER_CONFIG_FILE) -p eth2spec.phase0 -p eth2spec.altair -p eth2spec.merge + && mypy --config-file $(LINTER_CONFIG_FILE) -p eth2spec.phase0 -p eth2spec.altair -p eth2spec.bellatrix lint_generators: pyspec . venv/bin/activate; cd $(TEST_GENERATORS_DIR); \ diff --git a/README.md b/README.md index 1ae37766f..60bac7f35 100644 --- a/README.md +++ b/README.md @@ -33,20 +33,20 @@ The current features are: * [Honest Validator guide changes](specs/altair/validator.md) * [P2P Networking](specs/altair/p2p-interface.md) -### Merge +### Bellatrix (as known as The Merge) -The merge is still actively in development. The exact specification has not been formally accepted as final and details are still subject to change. +Bellatrix protocol upgrade is still actively in development. The exact specification has not been formally accepted as final and details are still subject to change. * Background material: - * An [ethresear.ch](https://ethresear.ch) post [describing the basic mechanism](https://ethresear.ch/t/the-eth1-eth2-transition/6265) - * [ethereum.org](https://ethereum.org) high-level description of the merge [here](https://ethereum.org/en/eth2/docking/) + * An [ethresear.ch](https://ethresear.ch) post [describing the basic mechanism of the CL+EL merge](https://ethresear.ch/t/the-eth1-eth2-transition/6265) + * [ethereum.org](https://ethereum.org) high-level description of the CL+EL merge [here](https://ethereum.org/en/eth2/docking/) * Specifications: - * [Beacon Chain changes](specs/merge/beacon-chain.md) - * [Merge fork](specs/merge/fork.md) - * [Fork Choice changes](specs/merge/fork-choice.md) - * [Validator additions](specs/merge/validator.md) - * [Client settings](specs/merge/client-settings.md) - * [P2P Networking](specs/merge/p2p-interface.md) + * [Beacon Chain changes](specs/bellatrix/beacon-chain.md) + * [Bellatrix fork](specs/bellatrix/fork.md) + * [Fork Choice changes](specs/bellatrix/fork-choice.md) + * [Validator additions](specs/bellatrix/validator.md) + * [Client settings](specs/bellatrix/client-settings.md) + * [P2P Networking](specs/bellatrix/p2p-interface.md) ### Sharding diff --git a/configs/mainnet.yaml b/configs/mainnet.yaml index 395e8d717..6c6af6282 100644 --- a/configs/mainnet.yaml +++ b/configs/mainnet.yaml @@ -34,9 +34,9 @@ GENESIS_DELAY: 604800 # Altair ALTAIR_FORK_VERSION: 0x01000000 ALTAIR_FORK_EPOCH: 74240 # Oct 27, 2021, 10:56:23am UTC -# Merge -MERGE_FORK_VERSION: 0x02000000 -MERGE_FORK_EPOCH: 18446744073709551615 +# Bellatrix +BELLATRIX_FORK_VERSION: 0x02000000 +BELLATRIX_FORK_EPOCH: 18446744073709551615 # Sharding SHARDING_FORK_VERSION: 0x03000000 SHARDING_FORK_EPOCH: 18446744073709551615 diff --git a/configs/minimal.yaml b/configs/minimal.yaml index 1b5433ec3..4e48c470b 100644 --- a/configs/minimal.yaml +++ b/configs/minimal.yaml @@ -33,9 +33,9 @@ GENESIS_DELAY: 300 # Altair ALTAIR_FORK_VERSION: 0x01000001 ALTAIR_FORK_EPOCH: 18446744073709551615 -# Merge -MERGE_FORK_VERSION: 0x02000001 -MERGE_FORK_EPOCH: 18446744073709551615 +# Bellatrix +BELLATRIX_FORK_VERSION: 0x02000001 +BELLATRIX_FORK_EPOCH: 18446744073709551615 # Sharding SHARDING_FORK_VERSION: 0x03000001 SHARDING_FORK_EPOCH: 18446744073709551615 diff --git a/presets/mainnet/merge.yaml b/presets/mainnet/bellatrix.yaml similarity index 71% rename from presets/mainnet/merge.yaml rename to presets/mainnet/bellatrix.yaml index 5f2e27bfc..7ae61b732 100644 --- a/presets/mainnet/merge.yaml +++ b/presets/mainnet/bellatrix.yaml @@ -1,13 +1,13 @@ -# Mainnet preset - The Merge +# Mainnet preset - Bellatrix # Updated penalty values # --------------------------------------------------------------- # 2**24 (= 16,777,216) -INACTIVITY_PENALTY_QUOTIENT_MERGE: 16777216 +INACTIVITY_PENALTY_QUOTIENT_BELLATRIX: 16777216 # 2**5 (= 32) -MIN_SLASHING_PENALTY_QUOTIENT_MERGE: 32 +MIN_SLASHING_PENALTY_QUOTIENT_BELLATRIX: 32 # 3 -PROPORTIONAL_SLASHING_MULTIPLIER_MERGE: 3 +PROPORTIONAL_SLASHING_MULTIPLIER_BELLATRIX: 3 # Execution # --------------------------------------------------------------- diff --git a/presets/minimal/merge.yaml b/presets/minimal/bellatrix.yaml similarity index 76% rename from presets/minimal/merge.yaml rename to presets/minimal/bellatrix.yaml index 21f2f5929..d43e226fa 100644 --- a/presets/minimal/merge.yaml +++ b/presets/minimal/bellatrix.yaml @@ -3,11 +3,11 @@ # Updated penalty values # --------------------------------------------------------------- # 2**24 (= 16,777,216) -INACTIVITY_PENALTY_QUOTIENT_MERGE: 16777216 +INACTIVITY_PENALTY_QUOTIENT_BELLATRIX: 16777216 # 2**5 (= 32) -MIN_SLASHING_PENALTY_QUOTIENT_MERGE: 32 +MIN_SLASHING_PENALTY_QUOTIENT_BELLATRIX: 32 # 3 -PROPORTIONAL_SLASHING_MULTIPLIER_MERGE: 3 +PROPORTIONAL_SLASHING_MULTIPLIER_BELLATRIX: 3 # Execution # --------------------------------------------------------------- diff --git a/setup.py b/setup.py index 7b74fc155..81a629531 100644 --- a/setup.py +++ b/setup.py @@ -40,7 +40,7 @@ from marko.ext.gfm.elements import Table # Definitions in context.py PHASE0 = 'phase0' ALTAIR = 'altair' -MERGE = 'merge' +BELLATRIX = 'bellatrix' # The helper functions that are used when defining constants CONSTANT_DEP_SUNDRY_CONSTANTS_FUNCTIONS = ''' @@ -487,10 +487,10 @@ def get_generalized_index(ssz_class: Any, *path: Sequence[PyUnion[int, SSZVariab return super().implement_optimizations(functions) # -# MergeSpecBuilder +# BellatrixSpecBuilder # -class MergeSpecBuilder(AltairSpecBuilder): - fork: str = MERGE +class BellatrixSpecBuilder(AltairSpecBuilder): + fork: str = BELLATRIX @classmethod def imports(cls, preset_name: str): @@ -550,7 +550,7 @@ EXECUTION_ENGINE = NoopExecutionEngine()""" spec_builders = { builder.fork: builder - for builder in (Phase0SpecBuilder, AltairSpecBuilder, MergeSpecBuilder) + for builder in (Phase0SpecBuilder, AltairSpecBuilder, BellatrixSpecBuilder) } @@ -846,14 +846,14 @@ class PySpecCommand(Command): if len(self.md_doc_paths) == 0: print("no paths were specified, using default markdown file paths for pyspec" " build (spec fork: %s)" % self.spec_fork) - if self.spec_fork in (PHASE0, ALTAIR, MERGE): + if self.spec_fork in (PHASE0, ALTAIR, BELLATRIX): self.md_doc_paths = """ specs/phase0/beacon-chain.md specs/phase0/fork-choice.md specs/phase0/validator.md specs/phase0/weak-subjectivity.md """ - if self.spec_fork in (ALTAIR, MERGE): + if self.spec_fork in (ALTAIR, BELLATRIX): self.md_doc_paths += """ specs/altair/beacon-chain.md specs/altair/bls.md @@ -862,12 +862,12 @@ class PySpecCommand(Command): specs/altair/p2p-interface.md specs/altair/sync-protocol.md """ - if self.spec_fork == MERGE: + if self.spec_fork == BELLATRIX: self.md_doc_paths += """ - specs/merge/beacon-chain.md - specs/merge/fork.md - specs/merge/fork-choice.md - specs/merge/validator.md + specs/bellatrix/beacon-chain.md + specs/bellatrix/fork.md + specs/bellatrix/fork-choice.md + specs/bellatrix/validator.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/specs/merge/beacon-chain.md b/specs/bellatrix/beacon-chain.md similarity index 93% rename from specs/merge/beacon-chain.md rename to specs/bellatrix/beacon-chain.md index 5b818d5a2..364172b0d 100644 --- a/specs/merge/beacon-chain.md +++ b/specs/bellatrix/beacon-chain.md @@ -1,4 +1,4 @@ -# The Merge -- The Beacon Chain +# Bellatrix -- The Beacon Chain **Notice**: This document is a work-in-progress for researchers and implementers. @@ -48,7 +48,7 @@ ## Introduction -This upgrade adds transaction execution to the beacon chain as part of the Merge fork. +This upgrade adds transaction execution to the beacon chain as part of Bellatrix fork. Additionally, this upgrade introduces the following minor changes: * Penalty parameter updates to their planned maximally punitive values @@ -75,15 +75,15 @@ Additionally, this upgrade introduces the following minor changes: ### Updated penalty values -The Merge updates a few configuration values to move penalty parameters to their final, maximum security values. +Bellatrix updates a few configuration values to move penalty parameters to their final, maximum security values. *Note*: The spec does *not* override previous configuration values but instead creates new values and replaces usage throughout. | Name | Value | | - | - | -| `INACTIVITY_PENALTY_QUOTIENT_MERGE` | `uint64(2**24)` (= 16,777,216) | -| `MIN_SLASHING_PENALTY_QUOTIENT_MERGE` | `uint64(2**5)` (= 32) | -| `PROPORTIONAL_SLASHING_MULTIPLIER_MERGE` | `uint64(3)` | +| `INACTIVITY_PENALTY_QUOTIENT_BELLATRIX` | `uint64(2**24)` (= 16,777,216) | +| `MIN_SLASHING_PENALTY_QUOTIENT_BELLATRIX` | `uint64(2**5)` (= 32) | +| `PROPORTIONAL_SLASHING_MULTIPLIER_BELLATRIX` | `uint64(3)` | ## Configuration @@ -114,7 +114,7 @@ class BeaconBlockBody(Container): voluntary_exits: List[SignedVoluntaryExit, MAX_VOLUNTARY_EXITS] sync_aggregate: SyncAggregate # Execution - execution_payload: ExecutionPayload # [New in Merge] + execution_payload: ExecutionPayload # [New in Bellatrix] ``` #### `BeaconState` @@ -156,7 +156,7 @@ class BeaconState(Container): current_sync_committee: SyncCommittee next_sync_committee: SyncCommittee # Execution - latest_execution_payload_header: ExecutionPayloadHeader # [New in Merge] + latest_execution_payload_header: ExecutionPayloadHeader # [New in Bellatrix] ``` ### New containers @@ -246,7 +246,7 @@ def compute_timestamp_at_slot(state: BeaconState, slot: Slot) -> uint64: #### Modified `get_inactivity_penalty_deltas` -*Note*: The function `get_inactivity_penalty_deltas` is modified to use `INACTIVITY_PENALTY_QUOTIENT_MERGE`. +*Note*: The function `get_inactivity_penalty_deltas` is modified to use `INACTIVITY_PENALTY_QUOTIENT_BELLATRIX`. ```python def get_inactivity_penalty_deltas(state: BeaconState) -> Tuple[Sequence[Gwei], Sequence[Gwei]]: @@ -260,8 +260,8 @@ def get_inactivity_penalty_deltas(state: BeaconState) -> Tuple[Sequence[Gwei], S for index in get_eligible_validator_indices(state): if index not in matching_target_indices: penalty_numerator = state.validators[index].effective_balance * state.inactivity_scores[index] - # [Modified in Merge] - penalty_denominator = INACTIVITY_SCORE_BIAS * INACTIVITY_PENALTY_QUOTIENT_MERGE + # [Modified in Bellatrix] + penalty_denominator = INACTIVITY_SCORE_BIAS * INACTIVITY_PENALTY_QUOTIENT_BELLATRIX penalties[index] += Gwei(penalty_numerator // penalty_denominator) return rewards, penalties ``` @@ -270,7 +270,7 @@ def get_inactivity_penalty_deltas(state: BeaconState) -> Tuple[Sequence[Gwei], S #### Modified `slash_validator` -*Note*: The function `slash_validator` is modified to use `MIN_SLASHING_PENALTY_QUOTIENT_MERGE`. +*Note*: The function `slash_validator` is modified to use `MIN_SLASHING_PENALTY_QUOTIENT_BELLATRIX`. ```python def slash_validator(state: BeaconState, @@ -285,7 +285,7 @@ def slash_validator(state: BeaconState, validator.slashed = True validator.withdrawable_epoch = max(validator.withdrawable_epoch, Epoch(epoch + EPOCHS_PER_SLASHINGS_VECTOR)) state.slashings[epoch % EPOCHS_PER_SLASHINGS_VECTOR] += validator.effective_balance - slashing_penalty = validator.effective_balance // MIN_SLASHING_PENALTY_QUOTIENT_MERGE # [Modified in Merge] + slashing_penalty = validator.effective_balance // MIN_SLASHING_PENALTY_QUOTIENT_BELLATRIX # [Modified in Bellatrix] decrease_balance(state, slashed_index, slashing_penalty) # Apply proposer and whistleblower rewards @@ -332,7 +332,7 @@ def execute_payload(self: ExecutionEngine, execution_payload: ExecutionPayload) def process_block(state: BeaconState, block: BeaconBlock) -> None: process_block_header(state, block) if is_execution_enabled(state, block.body): - process_execution_payload(state, block.body.execution_payload, EXECUTION_ENGINE) # [New in Merge] + process_execution_payload(state, block.body.execution_payload, EXECUTION_ENGINE) # [New in Bellatrix] process_randao(state, block.body) process_eth1_data(state, block.body) process_operations(state, block.body) @@ -377,14 +377,14 @@ def process_execution_payload(state: BeaconState, payload: ExecutionPayload, exe #### Slashings -*Note*: The function `process_slashings` is modified to use `PROPORTIONAL_SLASHING_MULTIPLIER_MERGE`. +*Note*: The function `process_slashings` is modified to use `PROPORTIONAL_SLASHING_MULTIPLIER_BELLATRIX`. ```python def process_slashings(state: BeaconState) -> None: epoch = get_current_epoch(state) total_balance = get_total_active_balance(state) adjusted_total_slashing_balance = min( - sum(state.slashings) * PROPORTIONAL_SLASHING_MULTIPLIER_MERGE, # [Modified in Merge] + sum(state.slashings) * PROPORTIONAL_SLASHING_MULTIPLIER_BELLATRIX, # [Modified in Bellatrix] total_balance ) for index, validator in enumerate(state.validators): @@ -397,10 +397,10 @@ def process_slashings(state: BeaconState) -> None: ## Testing -*Note*: The function `initialize_beacon_state_from_eth1` is modified for pure Merge testing only. +*Note*: The function `initialize_beacon_state_from_eth1` is modified for pure Bellatrix testing only. Modifications include: -1. Use `MERGE_FORK_VERSION` as the current fork version. -2. Utilize the Merge `BeaconBlockBody` when constructing the initial `latest_block_header`. +1. Use `BELLATRIX_FORK_VERSION` as the current fork version. +2. Utilize the Bellatrix `BeaconBlockBody` when constructing the initial `latest_block_header`. 3. Initialize `latest_execution_payload_header`. If `execution_payload_header == ExecutionPayloadHeader()`, then the Merge has not yet occurred. Else, the Merge starts from genesis and the transition is incomplete. @@ -412,8 +412,8 @@ def initialize_beacon_state_from_eth1(eth1_block_hash: Hash32, execution_payload_header: ExecutionPayloadHeader=ExecutionPayloadHeader() ) -> BeaconState: fork = Fork( - previous_version=MERGE_FORK_VERSION, # [Modified in Merge] for testing only - current_version=MERGE_FORK_VERSION, # [Modified in Merge] + previous_version=BELLATRIX_FORK_VERSION, # [Modified in Bellatrix] for testing only + current_version=BELLATRIX_FORK_VERSION, # [Modified in Bellatrix] epoch=GENESIS_EPOCH, ) state = BeaconState( @@ -447,7 +447,7 @@ def initialize_beacon_state_from_eth1(eth1_block_hash: Hash32, state.current_sync_committee = get_next_sync_committee(state) state.next_sync_committee = get_next_sync_committee(state) - # [New in Merge] Initialize the execution payload header + # [New in Bellatrix] Initialize the execution payload header # If empty, will initialize a chain that has not yet gone through the Merge transition state.latest_execution_payload_header = execution_payload_header diff --git a/specs/merge/fork-choice.md b/specs/bellatrix/fork-choice.md similarity index 99% rename from specs/merge/fork-choice.md rename to specs/bellatrix/fork-choice.md index 95d613e9f..91fda88bc 100644 --- a/specs/merge/fork-choice.md +++ b/specs/bellatrix/fork-choice.md @@ -1,4 +1,4 @@ -# The Merge -- Fork Choice +# Bellatrix -- Fork Choice **Notice**: This document is a work-in-progress for researchers and implementers. @@ -170,7 +170,7 @@ def on_block(store: Store, signed_block: SignedBeaconBlock) -> None: state = pre_state.copy() state_transition(state, signed_block, True) - # [New in Merge] + # [New in Bellatrix] if is_merge_transition_block(pre_state, block.body): validate_merge_block(block) diff --git a/specs/merge/fork.md b/specs/bellatrix/fork.md similarity index 79% rename from specs/merge/fork.md rename to specs/bellatrix/fork.md index eb0ca91c6..8cb34099b 100644 --- a/specs/merge/fork.md +++ b/specs/bellatrix/fork.md @@ -1,4 +1,4 @@ -# The Merge -- Fork Logic +# Bellatrix -- Fork Logic **Notice**: This document is a work-in-progress for researchers and implementers. @@ -9,7 +9,7 @@ - [Introduction](#introduction) - [Configuration](#configuration) -- [Fork to Merge](#fork-to-merge) +- [Fork to Bellatrix](#fork-to-bellatrix) - [Fork trigger](#fork-trigger) - [Upgrading the state](#upgrading-the-state) @@ -17,7 +17,7 @@ ## Introduction -This document describes the process of the Merge upgrade. +This document describes the process of Bellatrix upgrade. ## Configuration @@ -25,30 +25,30 @@ Warning: this configuration is not definitive. | Name | Value | | - | - | -| `MERGE_FORK_VERSION` | `Version('0x02000000')` | -| `MERGE_FORK_EPOCH` | `Epoch(18446744073709551615)` **TBD** | +| `BELLATRIX_FORK_VERSION` | `Version('0x02000000')` | +| `BELLATRIX_FORK_EPOCH` | `Epoch(18446744073709551615)` **TBD** | -## Fork to Merge +## Fork to Bellatrix ### Fork trigger -TBD. Social consensus, along with state conditions such as epoch boundary, finality, deposits, active validator count, etc. may be part of the decision process to trigger the fork. For now we assume the condition will be triggered at epoch `MERGE_FORK_EPOCH`. +TBD. Social consensus, along with state conditions such as epoch boundary, finality, deposits, active validator count, etc. may be part of the decision process to trigger the fork. For now we assume the condition will be triggered at epoch `BELLATRIX_FORK_EPOCH`. -Note that for the pure Merge networks, we don't apply `upgrade_to_merge` since it starts with Merge version logic. +Note that for the pure Bellatrix networks, we don't apply `upgrade_to_bellatrix` since it starts with Bellatrix version logic. ### Upgrading the state As with the Phase0-to-Altair upgrade, the `state_transition` is modified to upgrade the `BeaconState`. The `BeaconState` upgrade runs as part of `process_slots`, slots with missing block proposals do not affect the upgrade time. -If `state.slot % SLOTS_PER_EPOCH == 0` and `compute_epoch_at_slot(state.slot) == MERGE_FORK_EPOCH`, an irregular state change is made to upgrade to Merge. -The upgrade occurs after the completion of the inner loop of `process_slots` that sets `state.slot` equal to `MERGE_FORK_EPOCH * SLOTS_PER_EPOCH`. +If `state.slot % SLOTS_PER_EPOCH == 0` and `compute_epoch_at_slot(state.slot) == BELLATRIX_FORK_EPOCH`, an irregular state change is made to upgrade to Bellatrix. +The upgrade occurs after the completion of the inner loop of `process_slots` that sets `state.slot` equal to `BELLATRIX_FORK_EPOCH * SLOTS_PER_EPOCH`. When multiple upgrades are scheduled for the same epoch (common for test-networks), all the upgrades run in sequence before resuming the regular state transition. ```python -def upgrade_to_merge(pre: altair.BeaconState) -> BeaconState: +def upgrade_to_bellatrix(pre: altair.BeaconState) -> BeaconState: epoch = altair.get_current_epoch(pre) post = BeaconState( # Versioning @@ -57,7 +57,7 @@ def upgrade_to_merge(pre: altair.BeaconState) -> BeaconState: slot=pre.slot, fork=Fork( previous_version=pre.fork.current_version, - current_version=MERGE_FORK_VERSION, + current_version=BELLATRIX_FORK_VERSION, epoch=epoch, ), # History diff --git a/specs/merge/p2p-interface.md b/specs/bellatrix/p2p-interface.md similarity index 75% rename from specs/merge/p2p-interface.md rename to specs/bellatrix/p2p-interface.md index 0ab3d0825..ea1ca8b5b 100644 --- a/specs/merge/p2p-interface.md +++ b/specs/bellatrix/p2p-interface.md @@ -1,6 +1,6 @@ -# The Merge -- Networking +# Bellatrix -- Networking -This document contains the networking specification for the Merge. +This document contains the networking specification for the Bellatrix. The specification of these changes continues in the same format as the network specifications of previous upgrades, and assumes them as pre-requisite. This document should be viewed as additive to the documents from [Phase 0](../phase0/p2p-interface.md) and from [Altair](../altair/p2p-interface.md) and will be referred to as the "Phase 0 document" and "Altair document" respectively, hereafter. @@ -13,7 +13,7 @@ Readers should understand the Phase 0 and Altair documents and use them as a bas - [Warning](#warning) -- [Modifications in the Merge](#modifications-in-the-merge) +- [Modifications in Bellatrix](#modifications-in-bellatrix) - [Configuration](#configuration) - [The gossip domain: gossipsub](#the-gossip-domain-gossipsub) - [Topics and messages](#topics-and-messages) @@ -26,19 +26,19 @@ Readers should understand the Phase 0 and Altair documents and use them as a bas - [BeaconBlocksByRoot v2](#beaconblocksbyroot-v2) - [Design decision rationale](#design-decision-rationale) - [Gossipsub](#gossipsub) - - [Why was the max gossip message size increased at the Merge?](#why-was-the-max-gossip-message-size-increased-at-the-merge) + - [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 the Merge?](#why-was-the-max-chunk-response-size-increased-at-the-merge) + - [Why was the max chunk response size increased at Bellatrix?](#why-was-the-max-chunk-response-size-increased-at-bellatrix) ## Warning -This document is currently illustrative for early Merge testnets and some parts are subject to change. +This document is currently illustrative for early Bellatrix testnets and some parts are subject to change. Refer to the note in the [validator guide](./validator.md) for further details. -# Modifications in the Merge +# Modifications in Bellatrix ## Configuration @@ -46,12 +46,12 @@ This section outlines modifications constants that are used in this spec. | Name | Value | Description | |---|---|---| -| `GOSSIP_MAX_SIZE_MERGE` | `10 * 2**20` (= 10,485,760, 10 MiB) | The maximum allowed size of uncompressed gossip messages starting at the Merge upgrade. | -| `MAX_CHUNK_SIZE_MERGE` | `10 * 2**20` (= 10,485,760, 10 MiB) | The maximum allowed size of uncompressed req/resp chunked responses starting at the Merge upgrade. | +| `GOSSIP_MAX_SIZE_BELLATRIX` | `10 * 2**20` (= 10,485,760, 10 MiB) | The maximum allowed size of uncompressed gossip messages starting at the Merge upgrade. | +| `MAX_CHUNK_SIZE_BELLATRIX` | `10 * 2**20` (= 10,485,760, 10 MiB) | The maximum allowed size of uncompressed req/resp chunked responses starting at Bellatrix upgrade. | ## The gossip domain: gossipsub -Some gossip meshes are upgraded in the Merge to support upgraded types. +Some gossip meshes are upgraded in Bellatrix to support upgraded types. ### Topics and messages @@ -60,8 +60,8 @@ All topics remain stable except the beacon block topic which is updated with the The specification around the creation, validation, and dissemination of messages has not changed from the Phase 0 and Altair documents unless explicitly noted here. -Starting at the Merge upgrade, each gossipsub [message](https://github.com/libp2p/go-libp2p-pubsub/blob/master/pb/rpc.proto#L17-L24) -has a maximum size of `GOSSIP_MAX_SIZE_MERGE`. +Starting at Bellatrix upgrade, each gossipsub [message](https://github.com/libp2p/go-libp2p-pubsub/blob/master/pb/rpc.proto#L17-L24) +has a maximum size of `GOSSIP_MAX_SIZE_BELLATRIX`. Clients MUST reject (fail validation) messages that are over this size limit. Likewise, clients MUST NOT emit or propagate messages larger than this limit. @@ -77,13 +77,13 @@ Note that the `ForkDigestValue` path segment of the topic separates the old and #### Global topics -The Merge changes the type of the global beacon block topic. +Bellatrix changes the type of the global beacon block topic. ##### `beacon_block` -The *type* of the payload of this topic changes to the (modified) `SignedBeaconBlock` found in the Merge. +The *type* of the payload of this topic changes to the (modified) `SignedBeaconBlock` found in Bellatrix. 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. +See Bellatrix [state transition document](./beacon-chain.md#beaconblockbody) for further details. 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. @@ -96,7 +96,7 @@ Alias `block = signed_beacon_block.message`, `execution_payload = block.body.exe ### Transitioning the gossip See gossip transition details found in the [Altair document](../altair/p2p-interface.md#transitioning-the-gossip) for -details on how to handle transitioning gossip topics for the Merge. +details on how to handle transitioning gossip topics for Bellatrix. ## The Req/Resp domain @@ -108,11 +108,11 @@ details on how to handle transitioning gossip topics for the Merge. Request and Response remain unchanged unless explicitly noted here. -Starting at the Merge upgrade, -a global maximum uncompressed byte size of `MAX_CHUNK_SIZE_MERGE` MUST be applied to all method response chunks +Starting at Bellatrix upgrade, +a global maximum uncompressed byte size of `MAX_CHUNK_SIZE_BELLATRIX` MUST be applied to all method response chunks regardless of type specific bounds that *MUST* also be respected. -The Merge fork-digest is introduced to the `context` enum to specify the Merge block type. +Bellatrix fork-digest is introduced to the `context` enum to specify Bellatrix block type. Per `context = compute_fork_digest(fork_version, genesis_validators_root)`: @@ -122,14 +122,14 @@ Per `context = compute_fork_digest(fork_version, genesis_validators_root)`: | ------------------------ | -------------------------- | | `GENESIS_FORK_VERSION` | `phase0.SignedBeaconBlock` | | `ALTAIR_FORK_VERSION` | `altair.SignedBeaconBlock` | -| `MERGE_FORK_VERSION` | `merge.SignedBeaconBlock` | +| `BELLATRIX_FORK_VERSION` | `bellatrix.SignedBeaconBlock` | #### BeaconBlocksByRoot v2 **Protocol ID:** `/eth2/beacon_chain/req/beacon_blocks_by_root/2/` Request and Response remain unchanged. -The Merge fork-digest is introduced to the `context` enum to specify the Merge block type. +Bellatrix fork-digest is introduced to the `context` enum to specify Bellatrix block type. Per `context = compute_fork_digest(fork_version, genesis_validators_root)`: @@ -139,13 +139,13 @@ Per `context = compute_fork_digest(fork_version, genesis_validators_root)`: | ------------------------ | -------------------------- | | `GENESIS_FORK_VERSION` | `phase0.SignedBeaconBlock` | | `ALTAIR_FORK_VERSION` | `altair.SignedBeaconBlock` | -| `MERGE_FORK_VERSION` | `merge.SignedBeaconBlock` | +| `BELLATRIX_FORK_VERSION` | `bellatrix.SignedBeaconBlock` | # Design decision rationale ## Gossipsub -### Why was the max gossip message size increased at the Merge? +### Why was the max gossip message size increased at Bellatrix? With the addition of `ExecutionPayload` to `BeaconBlock`s, there is a dynamic field -- `transactions` -- which can validly exceed the `GOSSIP_MAX_SIZE` limit (1 MiB) put in place in @@ -156,9 +156,9 @@ current mainnet conditions. Geth currently has a [max gossip message size](https://github.com/ethereum/go-ethereum/blob/3ce9f6d96f38712f5d6756e97b59ccc20cc403b3/eth/protocols/eth/protocol.go#L49) of 10 MiB. To support backward compatibility with this previously defined network limit, -we adopt `GOSSIP_MAX_SIZE_MERGE` of 10 MiB for maximum gossip sizes at the -point of the Merge and beyond. Note, that clients SHOULD still reject objects -that exceed their maximum theoretical bounds which in most cases is less than `GOSSIP_MAX_SIZE_MERGE`. +we adopt `GOSSIP_MAX_SIZE_BELLATRIX` of 10 MiB for maximum gossip sizes at the +point of Bellatrix and beyond. Note, that clients SHOULD still reject objects +that exceed their maximum theoretical bounds which in most cases is less than `GOSSIP_MAX_SIZE_BELLATRIX`. Note, that due to additional size induced by the `BeaconBlock` contents (e.g. proposer signature, operations lists, etc) this does reduce the @@ -170,7 +170,7 @@ impact on network functionality and security. ## Req/Resp -### Why was the max chunk response size increased at the Merge? +### Why was the max chunk response size increased at Bellatrix? Similar to the discussion about the maximum gossip size increase, the `ExecutionPayload` type can cause `BeaconBlock`s to exceed the 1 MiB bounds put diff --git a/specs/merge/validator.md b/specs/bellatrix/validator.md similarity index 98% rename from specs/merge/validator.md rename to specs/bellatrix/validator.md index 7065c9e85..7e0369a39 100644 --- a/specs/merge/validator.md +++ b/specs/bellatrix/validator.md @@ -1,4 +1,4 @@ -# The Merge -- Honest Validator +# Bellatrix -- Honest Validator **Notice**: This document is a work-in-progress for researchers and implementers. @@ -33,7 +33,7 @@ This document represents the changes to be made in the code of an "honest valida This document is an extension of the [Altair -- Honest Validator](../altair/validator.md) guide. All behaviors and definitions defined in this document, and documents it extends, carry over unless explicitly noted or overridden. -All terminology, constants, functions, and protocol mechanics defined in the updated Beacon Chain doc of [The Merge](./beacon-chain.md) are requisite for this document and used throughout. +All terminology, constants, functions, and protocol mechanics defined in the updated Beacon Chain doc of [Bellatrix](./beacon-chain.md) are requisite for this document and used throughout. Please see related Beacon Chain doc before continuing and use them as a reference throughout. ## Helpers diff --git a/specs/custody_game/beacon-chain.md b/specs/custody_game/beacon-chain.md index 6f9f61cf9..fd00dedda 100644 --- a/specs/custody_game/beacon-chain.md +++ b/specs/custody_game/beacon-chain.md @@ -11,7 +11,8 @@ - [Introduction](#introduction) - [Constants](#constants) - [Misc](#misc) -- [Configuration](#configuration) + - [Domain types](#domain-types) +- [Preset](#preset) - [Time parameters](#time-parameters) - [Max operations per block](#max-operations-per-block) - [Size parameters](#size-parameters) diff --git a/specs/sharding/beacon-chain.md b/specs/sharding/beacon-chain.md index cf3d7bea1..ede369b95 100644 --- a/specs/sharding/beacon-chain.md +++ b/specs/sharding/beacon-chain.md @@ -207,7 +207,7 @@ class AttestationData(Container): ### `BeaconBlockBody` ```python -class BeaconBlockBody(merge.BeaconBlockBody): # [extends The Merge block body] +class BeaconBlockBody(bellatrix.BeaconBlockBody): # [extends Bellatrix block body] shard_proposer_slashings: List[ShardProposerSlashing, MAX_SHARD_PROPOSER_SLASHINGS] shard_headers: List[SignedShardBlobHeader, MAX_SHARDS * MAX_SHARD_HEADERS_PER_SHARD] ``` @@ -215,7 +215,7 @@ class BeaconBlockBody(merge.BeaconBlockBody): # [extends The Merge block body] ### `BeaconState` ```python -class BeaconState(merge.BeaconState): +class BeaconState(bellatrix.BeaconState): # Blob builder registry. blob_builders: List[Builder, BLOB_BUILDER_REGISTRY_LIMIT] blob_builder_balances: List[Gwei, BLOB_BUILDER_REGISTRY_LIMIT] @@ -804,7 +804,7 @@ def process_shard_proposer_slashing(state: BeaconState, proposer_slashing: Shard ### Epoch transition -This epoch transition overrides the Merge epoch transition: +This epoch transition overrides Bellatrix epoch transition: ```python def process_epoch(state: BeaconState) -> None: diff --git a/tests/README.md b/tests/README.md index b45faef24..428574564 100644 --- a/tests/README.md +++ b/tests/README.md @@ -31,10 +31,10 @@ To read more about creating the environment, [see here](core/pyspec/README.md). cd ~/consensus-specs . venv/bin/activate ``` -1. Run a sanity check test: +1. Run a sanity check test against Altair fork: ```sh cd tests/core/pyspec - python -m pytest -k test_empty_block_transition --fork Merge eth2spec + python -m pytest -k test_empty_block_transition --fork altair eth2spec ``` 1. The output should be similar to: ``` @@ -44,7 +44,7 @@ To read more about creating the environment, [see here](core/pyspec/README.md). plugins: cov-2.12.1, forked-1.3.0, xdist-2.3.0 collected 629 items / 626 deselected / 3 selected - eth2spec/test/merge/sanity/test_blocks.py . [ 33%] + eth2spec/test/bellatrix/sanity/test_blocks.py . [ 33%] eth2spec/test/phase0/sanity/test_blocks.py .. [100%] =============================== warnings summary =============================== @@ -448,13 +448,13 @@ def test_almost_after_epoch_slots(spec, state): ``` Add this function to the file `consensus-specs/tests/core/pyspec/eth2spec/test/phase0/block_processing/test_process_attestation.py`, -and run the test: +and run the test against Altair fork: ```sh cd ~/consensus-specs . venv/bin/activate cd tests/core/pyspec -python -m pytest -k almost_after --fork Merge eth2spec +python -m pytest -k almost_after --fork altair eth2spec ``` You should see it ran successfully (although you might get a warning, you can ignore it) diff --git a/tests/core/pyspec/eth2spec/test/helpers/merge/__init__.py b/tests/core/pyspec/eth2spec/test/bellatrix/__init__.py similarity index 100% rename from tests/core/pyspec/eth2spec/test/helpers/merge/__init__.py rename to tests/core/pyspec/eth2spec/test/bellatrix/__init__.py diff --git a/tests/core/pyspec/eth2spec/test/merge/__init__.py b/tests/core/pyspec/eth2spec/test/bellatrix/block_processing/__init__.py similarity index 100% rename from tests/core/pyspec/eth2spec/test/merge/__init__.py rename to tests/core/pyspec/eth2spec/test/bellatrix/block_processing/__init__.py diff --git a/tests/core/pyspec/eth2spec/test/merge/block_processing/test_process_execution_payload.py b/tests/core/pyspec/eth2spec/test/bellatrix/block_processing/test_process_execution_payload.py similarity index 95% rename from tests/core/pyspec/eth2spec/test/merge/block_processing/test_process_execution_payload.py rename to tests/core/pyspec/eth2spec/test/bellatrix/block_processing/test_process_execution_payload.py index d12a68bf5..b8c5f55c3 100644 --- a/tests/core/pyspec/eth2spec/test/merge/block_processing/test_process_execution_payload.py +++ b/tests/core/pyspec/eth2spec/test/bellatrix/block_processing/test_process_execution_payload.py @@ -4,7 +4,7 @@ from eth2spec.test.helpers.execution_payload import ( build_state_with_incomplete_transition, build_state_with_complete_transition, ) -from eth2spec.test.context import spec_state_test, expect_assertion_error, with_merge_and_later +from eth2spec.test.context import spec_state_test, expect_assertion_error, with_bellatrix_and_later from eth2spec.test.helpers.state import next_slot @@ -46,7 +46,7 @@ def run_execution_payload_processing(spec, state, execution_payload, valid=True, assert state.latest_execution_payload_header == get_execution_payload_header(spec, execution_payload) -@with_merge_and_later +@with_bellatrix_and_later @spec_state_test def test_success_first_payload(spec, state): # pre-state @@ -59,7 +59,7 @@ def test_success_first_payload(spec, state): yield from run_execution_payload_processing(spec, state, execution_payload) -@with_merge_and_later +@with_bellatrix_and_later @spec_state_test def test_success_regular_payload(spec, state): # pre-state @@ -72,7 +72,7 @@ def test_success_regular_payload(spec, state): yield from run_execution_payload_processing(spec, state, execution_payload) -@with_merge_and_later +@with_bellatrix_and_later @spec_state_test def test_success_first_payload_with_gap_slot(spec, state): # pre-state @@ -86,7 +86,7 @@ def test_success_first_payload_with_gap_slot(spec, state): yield from run_execution_payload_processing(spec, state, execution_payload) -@with_merge_and_later +@with_bellatrix_and_later @spec_state_test def test_success_regular_payload_with_gap_slot(spec, state): # pre-state @@ -100,7 +100,7 @@ def test_success_regular_payload_with_gap_slot(spec, state): yield from run_execution_payload_processing(spec, state, execution_payload) -@with_merge_and_later +@with_bellatrix_and_later @spec_state_test def test_bad_execution_first_payload(spec, state): # completely valid payload, but execution itself fails (e.g. block exceeds gas limit) @@ -115,7 +115,7 @@ def test_bad_execution_first_payload(spec, state): yield from run_execution_payload_processing(spec, state, execution_payload, valid=False, execution_valid=False) -@with_merge_and_later +@with_bellatrix_and_later @spec_state_test def test_bad_execution_regular_payload(spec, state): # completely valid payload, but execution itself fails (e.g. block exceeds gas limit) @@ -130,7 +130,7 @@ def test_bad_execution_regular_payload(spec, state): yield from run_execution_payload_processing(spec, state, execution_payload, valid=False, execution_valid=False) -@with_merge_and_later +@with_bellatrix_and_later @spec_state_test def test_bad_parent_hash_regular_payload(spec, state): # pre-state @@ -144,7 +144,7 @@ def test_bad_parent_hash_regular_payload(spec, state): yield from run_execution_payload_processing(spec, state, execution_payload, valid=False) -@with_merge_and_later +@with_bellatrix_and_later @spec_state_test def test_bad_random_first_payload(spec, state): # pre-state @@ -158,7 +158,7 @@ def test_bad_random_first_payload(spec, state): yield from run_execution_payload_processing(spec, state, execution_payload, valid=False) -@with_merge_and_later +@with_bellatrix_and_later @spec_state_test def test_bad_random_regular_payload(spec, state): # pre-state @@ -172,7 +172,7 @@ def test_bad_random_regular_payload(spec, state): yield from run_execution_payload_processing(spec, state, execution_payload, valid=False) -@with_merge_and_later +@with_bellatrix_and_later @spec_state_test def test_bad_everything_regular_payload(spec, state): # pre-state @@ -188,7 +188,7 @@ def test_bad_everything_regular_payload(spec, state): yield from run_execution_payload_processing(spec, state, execution_payload, valid=False) -@with_merge_and_later +@with_bellatrix_and_later @spec_state_test def test_bad_timestamp_first_payload(spec, state): # pre-state @@ -202,7 +202,7 @@ def test_bad_timestamp_first_payload(spec, state): yield from run_execution_payload_processing(spec, state, execution_payload, valid=False) -@with_merge_and_later +@with_bellatrix_and_later @spec_state_test def test_bad_timestamp_regular_payload(spec, state): # pre-state diff --git a/tests/core/pyspec/eth2spec/test/merge/block_processing/__init__.py b/tests/core/pyspec/eth2spec/test/bellatrix/fork/__init__.py similarity index 100% rename from tests/core/pyspec/eth2spec/test/merge/block_processing/__init__.py rename to tests/core/pyspec/eth2spec/test/bellatrix/fork/__init__.py diff --git a/tests/core/pyspec/eth2spec/test/merge/fork/test_merge_fork_basic.py b/tests/core/pyspec/eth2spec/test/bellatrix/fork/test_merge_fork_basic.py similarity index 55% rename from tests/core/pyspec/eth2spec/test/merge/fork/test_merge_fork_basic.py rename to tests/core/pyspec/eth2spec/test/bellatrix/fork/test_merge_fork_basic.py index d92b0015c..6c0a56edc 100644 --- a/tests/core/pyspec/eth2spec/test/merge/fork/test_merge_fork_basic.py +++ b/tests/core/pyspec/eth2spec/test/bellatrix/fork/test_merge_fork_basic.py @@ -7,76 +7,76 @@ from eth2spec.test.context import ( ) from eth2spec.test.utils import with_meta_tags from eth2spec.test.helpers.constants import ( - ALTAIR, MERGE, + ALTAIR, BELLATRIX, MINIMAL, ) from eth2spec.test.helpers.state import ( next_epoch, next_epoch_via_block, ) -from eth2spec.test.helpers.merge.fork import ( - MERGE_FORK_TEST_META_TAGS, +from eth2spec.test.helpers.bellatrix.fork import ( + BELLATRIX_FORK_TEST_META_TAGS, run_fork_test, ) -@with_phases(phases=[ALTAIR], other_phases=[MERGE]) +@with_phases(phases=[ALTAIR], other_phases=[BELLATRIX]) @spec_test @with_state -@with_meta_tags(MERGE_FORK_TEST_META_TAGS) +@with_meta_tags(BELLATRIX_FORK_TEST_META_TAGS) def test_fork_base_state(spec, phases, state): - yield from run_fork_test(phases[MERGE], state) + yield from run_fork_test(phases[BELLATRIX], state) -@with_phases(phases=[ALTAIR], other_phases=[MERGE]) +@with_phases(phases=[ALTAIR], other_phases=[BELLATRIX]) @spec_test @with_state -@with_meta_tags(MERGE_FORK_TEST_META_TAGS) +@with_meta_tags(BELLATRIX_FORK_TEST_META_TAGS) def test_fork_next_epoch(spec, phases, state): next_epoch(spec, state) - yield from run_fork_test(phases[MERGE], state) + yield from run_fork_test(phases[BELLATRIX], state) -@with_phases(phases=[ALTAIR], other_phases=[MERGE]) +@with_phases(phases=[ALTAIR], other_phases=[BELLATRIX]) @spec_test @with_state -@with_meta_tags(MERGE_FORK_TEST_META_TAGS) +@with_meta_tags(BELLATRIX_FORK_TEST_META_TAGS) def test_fork_next_epoch_with_block(spec, phases, state): next_epoch_via_block(spec, state) - yield from run_fork_test(phases[MERGE], state) + yield from run_fork_test(phases[BELLATRIX], state) -@with_phases(phases=[ALTAIR], other_phases=[MERGE]) +@with_phases(phases=[ALTAIR], other_phases=[BELLATRIX]) @spec_test @with_state -@with_meta_tags(MERGE_FORK_TEST_META_TAGS) +@with_meta_tags(BELLATRIX_FORK_TEST_META_TAGS) def test_fork_many_next_epoch(spec, phases, state): for _ in range(3): next_epoch(spec, state) - yield from run_fork_test(phases[MERGE], state) + yield from run_fork_test(phases[BELLATRIX], state) -@with_phases(phases=[ALTAIR], other_phases=[MERGE]) +@with_phases(phases=[ALTAIR], other_phases=[BELLATRIX]) @with_custom_state(balances_fn=low_balances, threshold_fn=lambda spec: spec.config.EJECTION_BALANCE) @spec_test -@with_meta_tags(MERGE_FORK_TEST_META_TAGS) +@with_meta_tags(BELLATRIX_FORK_TEST_META_TAGS) def test_fork_random_low_balances(spec, phases, state): - yield from run_fork_test(phases[MERGE], state) + yield from run_fork_test(phases[BELLATRIX], state) -@with_phases(phases=[ALTAIR], other_phases=[MERGE]) +@with_phases(phases=[ALTAIR], other_phases=[BELLATRIX]) @with_custom_state(balances_fn=misc_balances, threshold_fn=lambda spec: spec.config.EJECTION_BALANCE) @spec_test -@with_meta_tags(MERGE_FORK_TEST_META_TAGS) +@with_meta_tags(BELLATRIX_FORK_TEST_META_TAGS) def test_fork_random_misc_balances(spec, phases, state): - yield from run_fork_test(phases[MERGE], state) + yield from run_fork_test(phases[BELLATRIX], state) -@with_phases(phases=[ALTAIR], other_phases=[MERGE]) +@with_phases(phases=[ALTAIR], other_phases=[BELLATRIX]) @with_presets([MINIMAL], reason="mainnet config leads to larger validator set than limit of public/private keys pre-generated") @with_custom_state(balances_fn=large_validator_set, threshold_fn=lambda spec: spec.config.EJECTION_BALANCE) @spec_test -@with_meta_tags(MERGE_FORK_TEST_META_TAGS) +@with_meta_tags(BELLATRIX_FORK_TEST_META_TAGS) def test_fork_random_large_validator_set(spec, phases, state): - yield from run_fork_test(phases[MERGE], state) + yield from run_fork_test(phases[BELLATRIX], state) diff --git a/tests/core/pyspec/eth2spec/test/merge/fork/test_merge_fork_random.py b/tests/core/pyspec/eth2spec/test/bellatrix/fork/test_merge_fork_random.py similarity index 58% rename from tests/core/pyspec/eth2spec/test/merge/fork/test_merge_fork_random.py rename to tests/core/pyspec/eth2spec/test/bellatrix/fork/test_merge_fork_random.py index 20101fac4..2479b1960 100644 --- a/tests/core/pyspec/eth2spec/test/merge/fork/test_merge_fork_random.py +++ b/tests/core/pyspec/eth2spec/test/bellatrix/fork/test_merge_fork_random.py @@ -9,76 +9,76 @@ from eth2spec.test.context import ( ) from eth2spec.test.utils import with_meta_tags from eth2spec.test.helpers.constants import ( - ALTAIR, MERGE, + ALTAIR, BELLATRIX, MINIMAL, ) -from eth2spec.test.helpers.merge.fork import ( - MERGE_FORK_TEST_META_TAGS, +from eth2spec.test.helpers.bellatrix.fork import ( + BELLATRIX_FORK_TEST_META_TAGS, run_fork_test, ) from eth2spec.test.helpers.random import randomize_state -@with_phases(phases=[ALTAIR], other_phases=[MERGE]) +@with_phases(phases=[ALTAIR], other_phases=[BELLATRIX]) @spec_test @with_state -@with_meta_tags(MERGE_FORK_TEST_META_TAGS) +@with_meta_tags(BELLATRIX_FORK_TEST_META_TAGS) def test_merge_fork_random_0(spec, phases, state): randomize_state(spec, state, rng=Random(1010)) - yield from run_fork_test(phases[MERGE], state) + yield from run_fork_test(phases[BELLATRIX], state) -@with_phases(phases=[ALTAIR], other_phases=[MERGE]) +@with_phases(phases=[ALTAIR], other_phases=[BELLATRIX]) @spec_test @with_state -@with_meta_tags(MERGE_FORK_TEST_META_TAGS) +@with_meta_tags(BELLATRIX_FORK_TEST_META_TAGS) def test_merge_fork_random_1(spec, phases, state): randomize_state(spec, state, rng=Random(2020)) - yield from run_fork_test(phases[MERGE], state) + yield from run_fork_test(phases[BELLATRIX], state) -@with_phases(phases=[ALTAIR], other_phases=[MERGE]) +@with_phases(phases=[ALTAIR], other_phases=[BELLATRIX]) @spec_test @with_state -@with_meta_tags(MERGE_FORK_TEST_META_TAGS) +@with_meta_tags(BELLATRIX_FORK_TEST_META_TAGS) def test_merge_fork_random_2(spec, phases, state): randomize_state(spec, state, rng=Random(3030)) - yield from run_fork_test(phases[MERGE], state) + yield from run_fork_test(phases[BELLATRIX], state) -@with_phases(phases=[ALTAIR], other_phases=[MERGE]) +@with_phases(phases=[ALTAIR], other_phases=[BELLATRIX]) @spec_test @with_state -@with_meta_tags(MERGE_FORK_TEST_META_TAGS) +@with_meta_tags(BELLATRIX_FORK_TEST_META_TAGS) def test_merge_fork_random_3(spec, phases, state): randomize_state(spec, state, rng=Random(4040)) - yield from run_fork_test(phases[MERGE], state) + yield from run_fork_test(phases[BELLATRIX], state) -@with_phases(phases=[ALTAIR], other_phases=[MERGE]) +@with_phases(phases=[ALTAIR], other_phases=[BELLATRIX]) @spec_test @with_custom_state(balances_fn=low_balances, threshold_fn=lambda spec: spec.config.EJECTION_BALANCE) -@with_meta_tags(MERGE_FORK_TEST_META_TAGS) +@with_meta_tags(BELLATRIX_FORK_TEST_META_TAGS) def test_merge_fork_random_low_balances(spec, phases, state): randomize_state(spec, state, rng=Random(5050)) - yield from run_fork_test(phases[MERGE], state) + yield from run_fork_test(phases[BELLATRIX], state) -@with_phases(phases=[ALTAIR], other_phases=[MERGE]) +@with_phases(phases=[ALTAIR], other_phases=[BELLATRIX]) @spec_test @with_custom_state(balances_fn=misc_balances, threshold_fn=lambda spec: spec.config.EJECTION_BALANCE) -@with_meta_tags(MERGE_FORK_TEST_META_TAGS) +@with_meta_tags(BELLATRIX_FORK_TEST_META_TAGS) def test_merge_fork_random_misc_balances(spec, phases, state): randomize_state(spec, state, rng=Random(6060)) - yield from run_fork_test(phases[MERGE], state) + yield from run_fork_test(phases[BELLATRIX], state) -@with_phases(phases=[ALTAIR], other_phases=[MERGE]) +@with_phases(phases=[ALTAIR], other_phases=[BELLATRIX]) @with_presets([MINIMAL], reason="mainnet config leads to larger validator set than limit of public/private keys pre-generated") @spec_test @with_custom_state(balances_fn=large_validator_set, threshold_fn=lambda spec: spec.config.EJECTION_BALANCE) -@with_meta_tags(MERGE_FORK_TEST_META_TAGS) +@with_meta_tags(BELLATRIX_FORK_TEST_META_TAGS) def test_merge_fork_random_large_validator_set(spec, phases, state): randomize_state(spec, state, rng=Random(7070)) - yield from run_fork_test(phases[MERGE], state) + yield from run_fork_test(phases[BELLATRIX], state) diff --git a/tests/core/pyspec/eth2spec/test/merge/fork/__init__.py b/tests/core/pyspec/eth2spec/test/bellatrix/fork_choice/__init__.py similarity index 100% rename from tests/core/pyspec/eth2spec/test/merge/fork/__init__.py rename to tests/core/pyspec/eth2spec/test/bellatrix/fork_choice/__init__.py diff --git a/tests/core/pyspec/eth2spec/test/merge/fork_choice/test_on_merge_block.py b/tests/core/pyspec/eth2spec/test/bellatrix/fork_choice/test_on_merge_block.py similarity index 98% rename from tests/core/pyspec/eth2spec/test/merge/fork_choice/test_on_merge_block.py rename to tests/core/pyspec/eth2spec/test/bellatrix/fork_choice/test_on_merge_block.py index e0703fdf7..3a935c061 100644 --- a/tests/core/pyspec/eth2spec/test/merge/fork_choice/test_on_merge_block.py +++ b/tests/core/pyspec/eth2spec/test/bellatrix/fork_choice/test_on_merge_block.py @@ -1,6 +1,6 @@ from eth2spec.utils.ssz.ssz_typing import uint256 from eth2spec.test.exceptions import BlockNotFoundException -from eth2spec.test.context import spec_state_test, with_phases, MERGE +from eth2spec.test.context import spec_state_test, with_phases, BELLATRIX from eth2spec.test.helpers.block import ( build_empty_block_for_next_slot, ) @@ -47,7 +47,7 @@ def with_pow_block_patch(spec, blocks, func): assert is_called.value -@with_phases([MERGE]) +@with_phases([BELLATRIX]) @spec_state_test def test_all_valid(spec, state): test_steps = [] @@ -81,7 +81,7 @@ def test_all_valid(spec, state): yield 'steps', test_steps -@with_phases([MERGE]) +@with_phases([BELLATRIX]) @spec_state_test def test_block_lookup_failed(spec, state): test_steps = [] @@ -111,7 +111,7 @@ def test_block_lookup_failed(spec, state): yield 'steps', test_steps -@with_phases([MERGE]) +@with_phases([BELLATRIX]) @spec_state_test def test_too_early_for_merge(spec, state): test_steps = [] @@ -143,7 +143,7 @@ def test_too_early_for_merge(spec, state): yield 'steps', test_steps -@with_phases([MERGE]) +@with_phases([BELLATRIX]) @spec_state_test def test_too_late_for_merge(spec, state): test_steps = [] diff --git a/tests/core/pyspec/eth2spec/test/merge/fork_choice/__init__.py b/tests/core/pyspec/eth2spec/test/bellatrix/genesis/__init__.py similarity index 100% rename from tests/core/pyspec/eth2spec/test/merge/fork_choice/__init__.py rename to tests/core/pyspec/eth2spec/test/bellatrix/genesis/__init__.py diff --git a/tests/core/pyspec/eth2spec/test/merge/genesis/test_initialization.py b/tests/core/pyspec/eth2spec/test/bellatrix/genesis/test_initialization.py similarity index 96% rename from tests/core/pyspec/eth2spec/test/merge/genesis/test_initialization.py rename to tests/core/pyspec/eth2spec/test/bellatrix/genesis/test_initialization.py index 9cd388698..140d8708c 100644 --- a/tests/core/pyspec/eth2spec/test/merge/genesis/test_initialization.py +++ b/tests/core/pyspec/eth2spec/test/bellatrix/genesis/test_initialization.py @@ -1,10 +1,10 @@ from eth2spec.test.context import ( - MERGE, + BELLATRIX, single_phase, spec_test, with_presets, with_phases, - with_merge_and_later, + with_bellatrix_and_later, ) from eth2spec.test.helpers.constants import MINIMAL from eth2spec.test.helpers.deposits import ( @@ -22,7 +22,7 @@ def eth1_init_data(eth1_block_hash, eth1_timestamp): } -@with_phases([MERGE]) +@with_phases([BELLATRIX]) @spec_test @single_phase @with_presets([MINIMAL], reason="too slow") @@ -50,7 +50,7 @@ def test_initialize_pre_transition_no_param(spec): yield 'state', state -@with_merge_and_later +@with_bellatrix_and_later @spec_test @single_phase @with_presets([MINIMAL], reason="too slow") @@ -86,7 +86,7 @@ def test_initialize_pre_transition_empty_payload(spec): yield 'state', state -@with_merge_and_later +@with_bellatrix_and_later @spec_test @single_phase @with_presets([MINIMAL], reason="too slow") diff --git a/tests/core/pyspec/eth2spec/test/merge/genesis/__init__.py b/tests/core/pyspec/eth2spec/test/bellatrix/sanity/__init__.py similarity index 100% rename from tests/core/pyspec/eth2spec/test/merge/genesis/__init__.py rename to tests/core/pyspec/eth2spec/test/bellatrix/sanity/__init__.py diff --git a/tests/core/pyspec/eth2spec/test/merge/sanity/test_blocks.py b/tests/core/pyspec/eth2spec/test/bellatrix/sanity/test_blocks.py similarity index 92% rename from tests/core/pyspec/eth2spec/test/merge/sanity/test_blocks.py rename to tests/core/pyspec/eth2spec/test/bellatrix/sanity/test_blocks.py index 1e8bb39a6..bad922d4f 100644 --- a/tests/core/pyspec/eth2spec/test/merge/sanity/test_blocks.py +++ b/tests/core/pyspec/eth2spec/test/bellatrix/sanity/test_blocks.py @@ -5,11 +5,11 @@ from eth2spec.test.helpers.block import ( build_empty_block_for_next_slot ) from eth2spec.test.context import ( - with_merge_and_later, spec_state_test + with_bellatrix_and_later, spec_state_test ) -@with_merge_and_later +@with_bellatrix_and_later @spec_state_test def test_empty_block_transition_no_tx(spec, state): yield 'pre', state @@ -25,7 +25,7 @@ def test_empty_block_transition_no_tx(spec, state): # TODO: tests with EVM, mock or replacement? -@with_merge_and_later +@with_bellatrix_and_later @spec_state_test def test_is_execution_enabled_false(spec, state): # Set `latest_execution_payload_header` to empty diff --git a/tests/core/pyspec/eth2spec/test/merge/sanity/__init__.py b/tests/core/pyspec/eth2spec/test/bellatrix/transition/__init__.py similarity index 100% rename from tests/core/pyspec/eth2spec/test/merge/sanity/__init__.py rename to tests/core/pyspec/eth2spec/test/bellatrix/transition/__init__.py diff --git a/tests/core/pyspec/eth2spec/test/merge/transition/test_transition.py b/tests/core/pyspec/eth2spec/test/bellatrix/transition/test_transition.py similarity index 91% rename from tests/core/pyspec/eth2spec/test/merge/transition/test_transition.py rename to tests/core/pyspec/eth2spec/test/bellatrix/transition/test_transition.py index d488d81dc..2382a3758 100644 --- a/tests/core/pyspec/eth2spec/test/merge/transition/test_transition.py +++ b/tests/core/pyspec/eth2spec/test/bellatrix/transition/test_transition.py @@ -3,7 +3,7 @@ from eth2spec.test.context import ( with_fork_metas, ) from eth2spec.test.helpers.constants import ( - AFTER_MERGE_PRE_POST_FORKS, + AFTER_BELLATRIX_PRE_POST_FORKS, ) from eth2spec.test.helpers.fork_transition import ( do_fork, @@ -13,7 +13,7 @@ from eth2spec.test.helpers.fork_transition import ( @with_fork_metas([ - ForkMeta(pre_fork_name=pre, post_fork_name=post, fork_epoch=2) for pre, post in AFTER_MERGE_PRE_POST_FORKS + ForkMeta(pre_fork_name=pre, post_fork_name=post, fork_epoch=2) for pre, post in AFTER_BELLATRIX_PRE_POST_FORKS ]) def test_sample_transition(state, fork_epoch, spec, post_spec, pre_tag, post_tag): transition_until_fork(spec, state, fork_epoch) diff --git a/tests/core/pyspec/eth2spec/test/merge/transition/__init__.py b/tests/core/pyspec/eth2spec/test/bellatrix/unittests/__init__.py similarity index 100% rename from tests/core/pyspec/eth2spec/test/merge/transition/__init__.py rename to tests/core/pyspec/eth2spec/test/bellatrix/unittests/__init__.py diff --git a/tests/core/pyspec/eth2spec/test/merge/unittests/test_is_valid_terminal_pow_block.py b/tests/core/pyspec/eth2spec/test/bellatrix/unittests/test_is_valid_terminal_pow_block.py similarity index 93% rename from tests/core/pyspec/eth2spec/test/merge/unittests/test_is_valid_terminal_pow_block.py rename to tests/core/pyspec/eth2spec/test/bellatrix/unittests/test_is_valid_terminal_pow_block.py index f20c15e35..8109d92d7 100644 --- a/tests/core/pyspec/eth2spec/test/merge/unittests/test_is_valid_terminal_pow_block.py +++ b/tests/core/pyspec/eth2spec/test/bellatrix/unittests/test_is_valid_terminal_pow_block.py @@ -4,11 +4,11 @@ from eth2spec.test.helpers.pow_block import ( ) from eth2spec.test.context import ( spec_state_test, - with_merge_and_later, + with_bellatrix_and_later, ) -@with_merge_and_later +@with_bellatrix_and_later @spec_state_test def test_is_valid_terminal_pow_block_success_valid(spec, state): parent_block = prepare_random_pow_block(spec) @@ -20,7 +20,7 @@ def test_is_valid_terminal_pow_block_success_valid(spec, state): assert spec.is_valid_terminal_pow_block(block, parent_block) -@with_merge_and_later +@with_bellatrix_and_later @spec_state_test def test_is_valid_terminal_pow_block_fail_before_terminal(spec, state): parent_block = prepare_random_pow_block(spec) @@ -32,7 +32,7 @@ def test_is_valid_terminal_pow_block_fail_before_terminal(spec, state): assert not spec.is_valid_terminal_pow_block(block, parent_block) -@with_merge_and_later +@with_bellatrix_and_later @spec_state_test def test_is_valid_terminal_pow_block_fail_just_after_terminal(spec, state): parent_block = prepare_random_pow_block(spec) diff --git a/tests/core/pyspec/eth2spec/test/merge/unittests/test_transition.py b/tests/core/pyspec/eth2spec/test/bellatrix/unittests/test_transition.py similarity index 93% rename from tests/core/pyspec/eth2spec/test/merge/unittests/test_transition.py rename to tests/core/pyspec/eth2spec/test/bellatrix/unittests/test_transition.py index 9c3b403ee..4a7c5d4a1 100644 --- a/tests/core/pyspec/eth2spec/test/merge/unittests/test_transition.py +++ b/tests/core/pyspec/eth2spec/test/bellatrix/unittests/test_transition.py @@ -5,18 +5,18 @@ from eth2spec.test.helpers.execution_payload import ( ) from eth2spec.test.context import ( spec_state_test, - with_merge_and_later + with_bellatrix_and_later ) -@with_merge_and_later +@with_bellatrix_and_later @spec_state_test def test_fail_merge_complete(spec, state): state = build_state_with_incomplete_transition(spec, state) assert not spec.is_merge_transition_complete(state) -@with_merge_and_later +@with_bellatrix_and_later @spec_state_test def test_success_merge_complete(spec, state): state = build_state_with_complete_transition(spec, state) @@ -32,7 +32,7 @@ expected_results = [ ] -@with_merge_and_later +@with_bellatrix_and_later @spec_state_test def test_is_merge_block_and_is_execution_enabled(spec, state): for result in expected_results: diff --git a/tests/core/pyspec/eth2spec/test/merge/unittests/test_validate_merge_block.py b/tests/core/pyspec/eth2spec/test/bellatrix/unittests/test_validate_merge_block.py similarity index 96% rename from tests/core/pyspec/eth2spec/test/merge/unittests/test_validate_merge_block.py rename to tests/core/pyspec/eth2spec/test/bellatrix/unittests/test_validate_merge_block.py index cf4c2234a..141ad5a27 100644 --- a/tests/core/pyspec/eth2spec/test/merge/unittests/test_validate_merge_block.py +++ b/tests/core/pyspec/eth2spec/test/bellatrix/unittests/test_validate_merge_block.py @@ -8,7 +8,7 @@ from eth2spec.test.helpers.pow_block import ( ) from eth2spec.test.context import ( spec_state_test, - with_merge_and_later, + with_bellatrix_and_later, spec_configured_state_test ) @@ -49,7 +49,7 @@ def run_validate_merge_block(spec, pow_chain, beacon_block, valid=True): assert assertion_error_caught -@with_merge_and_later +@with_bellatrix_and_later @spec_state_test def test_validate_merge_block_success(spec, state): pow_chain = prepare_random_pow_chain(spec, 2) @@ -60,7 +60,7 @@ def test_validate_merge_block_success(spec, state): run_validate_merge_block(spec, pow_chain, block) -@with_merge_and_later +@with_bellatrix_and_later @spec_state_test def test_validate_merge_block_fail_block_lookup(spec, state): pow_chain = prepare_random_pow_chain(spec, 2) @@ -70,7 +70,7 @@ def test_validate_merge_block_fail_block_lookup(spec, state): run_validate_merge_block(spec, pow_chain, block, valid=False) -@with_merge_and_later +@with_bellatrix_and_later @spec_state_test def test_validate_merge_block_fail_parent_block_lookup(spec, state): pow_chain = prepare_random_pow_chain(spec, 1) @@ -80,7 +80,7 @@ def test_validate_merge_block_fail_parent_block_lookup(spec, state): run_validate_merge_block(spec, pow_chain, block, valid=False) -@with_merge_and_later +@with_bellatrix_and_later @spec_state_test def test_validate_merge_block_fail_after_terminal(spec, state): pow_chain = prepare_random_pow_chain(spec, 2) @@ -91,7 +91,7 @@ def test_validate_merge_block_fail_after_terminal(spec, state): run_validate_merge_block(spec, pow_chain, block, valid=False) -@with_merge_and_later +@with_bellatrix_and_later @spec_configured_state_test({ 'TERMINAL_BLOCK_HASH': TERMINAL_BLOCK_HASH_CONFIG_VAR, 'TERMINAL_BLOCK_HASH_ACTIVATION_EPOCH': '0' @@ -107,7 +107,7 @@ def test_validate_merge_block_tbh_override_success(spec, state): run_validate_merge_block(spec, pow_chain, block) -@with_merge_and_later +@with_bellatrix_and_later @spec_configured_state_test({ 'TERMINAL_BLOCK_HASH': TERMINAL_BLOCK_HASH_CONFIG_VAR, 'TERMINAL_BLOCK_HASH_ACTIVATION_EPOCH': '0' @@ -122,7 +122,7 @@ def test_validate_merge_block_fail_parent_hash_is_not_tbh(spec, state): run_validate_merge_block(spec, pow_chain, block, valid=False) -@with_merge_and_later +@with_bellatrix_and_later @spec_configured_state_test({ 'TERMINAL_BLOCK_HASH': TERMINAL_BLOCK_HASH_CONFIG_VAR, 'TERMINAL_BLOCK_HASH_ACTIVATION_EPOCH': '1' @@ -138,7 +138,7 @@ def test_validate_merge_block_terminal_block_hash_fail_activation_not_reached(sp run_validate_merge_block(spec, pow_chain, block, valid=False) -@with_merge_and_later +@with_bellatrix_and_later @spec_configured_state_test({ 'TERMINAL_BLOCK_HASH': TERMINAL_BLOCK_HASH_CONFIG_VAR, 'TERMINAL_BLOCK_HASH_ACTIVATION_EPOCH': '1' diff --git a/tests/core/pyspec/eth2spec/test/merge/unittests/validator/test_validator.py b/tests/core/pyspec/eth2spec/test/bellatrix/unittests/validator/test_validator.py similarity index 98% rename from tests/core/pyspec/eth2spec/test/merge/unittests/validator/test_validator.py rename to tests/core/pyspec/eth2spec/test/bellatrix/unittests/validator/test_validator.py index d4acf04a6..5e767d2cd 100644 --- a/tests/core/pyspec/eth2spec/test/merge/unittests/validator/test_validator.py +++ b/tests/core/pyspec/eth2spec/test/bellatrix/unittests/validator/test_validator.py @@ -5,7 +5,7 @@ from eth2spec.test.helpers.pow_block import ( ) from eth2spec.test.context import ( spec_state_test, - with_merge_and_later, + with_bellatrix_and_later, ) @@ -29,7 +29,7 @@ expected_results = [ # it would return the first block (IS_HEAD_PARENT_BLOCK). -@with_merge_and_later +@with_bellatrix_and_later @spec_state_test def test_get_pow_block_at_terminal_total_difficulty(spec, state): for result in expected_results: @@ -89,7 +89,7 @@ prepare_execution_payload_expected_results = [ ] -@with_merge_and_later +@with_bellatrix_and_later @spec_state_test def test_prepare_execution_payload(spec, state): for result in prepare_execution_payload_expected_results: diff --git a/tests/core/pyspec/eth2spec/test/context.py b/tests/core/pyspec/eth2spec/test/context.py index 260cb4d7d..87ae2fc5c 100644 --- a/tests/core/pyspec/eth2spec/test/context.py +++ b/tests/core/pyspec/eth2spec/test/context.py @@ -5,13 +5,13 @@ from eth_utils import encode_hex from eth2spec.phase0 import mainnet as spec_phase0_mainnet, minimal as spec_phase0_minimal from eth2spec.altair import mainnet as spec_altair_mainnet, minimal as spec_altair_minimal -from eth2spec.merge import mainnet as spec_merge_mainnet, minimal as spec_merge_minimal +from eth2spec.bellatrix import mainnet as spec_bellatrix_mainnet, minimal as spec_bellatrix_minimal from eth2spec.utils import bls from .exceptions import SkippedTest from .helpers.constants import ( - PHASE0, ALTAIR, MERGE, MINIMAL, MAINNET, - ALL_PHASES, FORKS_BEFORE_ALTAIR, FORKS_BEFORE_MERGE, + PHASE0, ALTAIR, BELLATRIX, MINIMAL, MAINNET, + ALL_PHASES, FORKS_BEFORE_ALTAIR, FORKS_BEFORE_BELLATRIX, ALL_FORK_UPGRADES, ) from .helpers.typing import SpecForkName, PresetBaseName @@ -53,7 +53,7 @@ class SpecAltair(Spec): ... -class SpecMerge(Spec): +class SpecBellatrix(Spec): ... @@ -68,12 +68,12 @@ spec_targets: Dict[PresetBaseName, Dict[SpecForkName, Spec]] = { MINIMAL: { PHASE0: spec_phase0_minimal, ALTAIR: spec_altair_minimal, - MERGE: spec_merge_minimal, + BELLATRIX: spec_bellatrix_minimal, }, MAINNET: { PHASE0: spec_phase0_mainnet, ALTAIR: spec_altair_mainnet, - MERGE: spec_merge_mainnet, + BELLATRIX: spec_bellatrix_mainnet, }, } @@ -81,7 +81,7 @@ spec_targets: Dict[PresetBaseName, Dict[SpecForkName, Spec]] = { class SpecForks(TypedDict, total=False): PHASE0: SpecPhase0 ALTAIR: SpecAltair - MERGE: SpecMerge + BELLATRIX: SpecBellatrix def _prepare_state(balances_fn: Callable[[Any], Sequence[int]], threshold_fn: Callable[[Any], int], @@ -529,12 +529,12 @@ def is_post_altair(spec): return spec.fork not in FORKS_BEFORE_ALTAIR -def is_post_merge(spec): - return spec.fork not in FORKS_BEFORE_MERGE +def is_post_bellatrix(spec): + return spec.fork not in FORKS_BEFORE_BELLATRIX with_altair_and_later = with_all_phases_except([PHASE0]) -with_merge_and_later = with_all_phases_except([PHASE0, ALTAIR]) +with_bellatrix_and_later = with_all_phases_except([PHASE0, ALTAIR]) def only_generator(reason): diff --git a/tests/core/pyspec/eth2spec/test/merge/unittests/__init__.py b/tests/core/pyspec/eth2spec/test/helpers/bellatrix/__init__.py similarity index 100% rename from tests/core/pyspec/eth2spec/test/merge/unittests/__init__.py rename to tests/core/pyspec/eth2spec/test/helpers/bellatrix/__init__.py diff --git a/tests/core/pyspec/eth2spec/test/helpers/merge/fork.py b/tests/core/pyspec/eth2spec/test/helpers/bellatrix/fork.py similarity index 87% rename from tests/core/pyspec/eth2spec/test/helpers/merge/fork.py rename to tests/core/pyspec/eth2spec/test/helpers/bellatrix/fork.py index 5a45e8565..246404643 100644 --- a/tests/core/pyspec/eth2spec/test/helpers/merge/fork.py +++ b/tests/core/pyspec/eth2spec/test/helpers/bellatrix/fork.py @@ -1,12 +1,12 @@ -MERGE_FORK_TEST_META_TAGS = { - 'fork': 'merge', +BELLATRIX_FORK_TEST_META_TAGS = { + 'fork': 'bellatrix', } def run_fork_test(post_spec, pre_state): yield 'pre', pre_state - post_state = post_spec.upgrade_to_merge(pre_state) + post_state = post_spec.upgrade_to_bellatrix(pre_state) # Stable fields stable_fields = [ @@ -39,7 +39,7 @@ def run_fork_test(post_spec, pre_state): assert getattr(pre_state, field) != getattr(post_state, field) assert pre_state.fork.current_version == post_state.fork.previous_version - assert post_state.fork.current_version == post_spec.config.MERGE_FORK_VERSION + assert post_state.fork.current_version == post_spec.config.BELLATRIX_FORK_VERSION assert post_state.fork.epoch == post_spec.get_current_epoch(post_state) assert post_state.latest_execution_payload_header == post_spec.ExecutionPayloadHeader() diff --git a/tests/core/pyspec/eth2spec/test/helpers/block.py b/tests/core/pyspec/eth2spec/test/helpers/block.py index 78b90b165..facaa48b6 100644 --- a/tests/core/pyspec/eth2spec/test/helpers/block.py +++ b/tests/core/pyspec/eth2spec/test/helpers/block.py @@ -1,4 +1,4 @@ -from eth2spec.test.context import is_post_altair, is_post_merge +from eth2spec.test.context import is_post_altair, is_post_bellatrix from eth2spec.test.helpers.execution_payload import build_empty_execution_payload from eth2spec.test.helpers.keys import privkeys from eth2spec.utils import bls @@ -98,7 +98,7 @@ def build_empty_block(spec, state, slot=None): if is_post_altair(spec): empty_block.body.sync_aggregate.sync_committee_signature = spec.G2_POINT_AT_INFINITY - if is_post_merge(spec): + if is_post_bellatrix(spec): empty_block.body.execution_payload = build_empty_execution_payload(spec, state) return empty_block diff --git a/tests/core/pyspec/eth2spec/test/helpers/block_processing.py b/tests/core/pyspec/eth2spec/test/helpers/block_processing.py index 8721a772e..484b05164 100644 --- a/tests/core/pyspec/eth2spec/test/helpers/block_processing.py +++ b/tests/core/pyspec/eth2spec/test/helpers/block_processing.py @@ -27,7 +27,7 @@ def get_process_calls(spec): # Altair 'process_sync_aggregate': lambda state, block: spec.process_sync_aggregate(state, block.body.sync_aggregate), - # Merge + # Bellatrix 'process_application_payload': lambda state, block: spec.process_application_payload(state, block.body), # TODO: add sharding processing functions when spec stabilizes. diff --git a/tests/core/pyspec/eth2spec/test/helpers/constants.py b/tests/core/pyspec/eth2spec/test/helpers/constants.py index bb8f49cbc..ddd32b14a 100644 --- a/tests/core/pyspec/eth2spec/test/helpers/constants.py +++ b/tests/core/pyspec/eth2spec/test/helpers/constants.py @@ -7,7 +7,7 @@ from .typing import SpecForkName, PresetBaseName # Some of the Spec module functionality is exposed here to deal with phase-specific changes. PHASE0 = SpecForkName('phase0') ALTAIR = SpecForkName('altair') -MERGE = SpecForkName('merge') +BELLATRIX = SpecForkName('bellatrix') # Experimental phases (not included in default "ALL_PHASES"): SHARDING = SpecForkName('sharding') @@ -15,20 +15,20 @@ CUSTODY_GAME = SpecForkName('custody_game') DAS = SpecForkName('das') # The forks that pytest runs with. -ALL_PHASES = (PHASE0, ALTAIR, MERGE) +ALL_PHASES = (PHASE0, ALTAIR, BELLATRIX) # The forks that output to the test vectors. -TESTGEN_FORKS = (PHASE0, ALTAIR, MERGE) +TESTGEN_FORKS = (PHASE0, ALTAIR, BELLATRIX) FORKS_BEFORE_ALTAIR = (PHASE0,) -FORKS_BEFORE_MERGE = (PHASE0, ALTAIR) +FORKS_BEFORE_BELLATRIX = (PHASE0, ALTAIR) ALL_FORK_UPGRADES = { # pre_fork_name: post_fork_name PHASE0: ALTAIR, - ALTAIR: MERGE, + ALTAIR: BELLATRIX, } ALL_PRE_POST_FORKS = ALL_FORK_UPGRADES.items() -AFTER_MERGE_UPGRADES = {key: value for key, value in ALL_FORK_UPGRADES.items() if key not in FORKS_BEFORE_ALTAIR} -AFTER_MERGE_PRE_POST_FORKS = AFTER_MERGE_UPGRADES.items() +AFTER_BELLATRIX_UPGRADES = {key: value for key, value in ALL_FORK_UPGRADES.items() if key not in FORKS_BEFORE_ALTAIR} +AFTER_BELLATRIX_PRE_POST_FORKS = AFTER_BELLATRIX_UPGRADES.items() # # Config diff --git a/tests/core/pyspec/eth2spec/test/helpers/fork_transition.py b/tests/core/pyspec/eth2spec/test/helpers/fork_transition.py index 853863e51..01d297bd9 100644 --- a/tests/core/pyspec/eth2spec/test/helpers/fork_transition.py +++ b/tests/core/pyspec/eth2spec/test/helpers/fork_transition.py @@ -11,7 +11,7 @@ from eth2spec.test.helpers.block import ( ) from eth2spec.test.helpers.constants import ( ALTAIR, - MERGE, + BELLATRIX, ) from eth2spec.test.helpers.deposits import ( prepare_state_and_deposit, @@ -145,17 +145,17 @@ def do_fork(state, spec, post_spec, fork_epoch, with_block=True, operation_dict= if post_spec.fork == ALTAIR: state = post_spec.upgrade_to_altair(state) - elif post_spec.fork == MERGE: - state = post_spec.upgrade_to_merge(state) + elif post_spec.fork == BELLATRIX: + state = post_spec.upgrade_to_bellatrix(state) assert state.fork.epoch == fork_epoch if post_spec.fork == ALTAIR: assert state.fork.previous_version == post_spec.config.GENESIS_FORK_VERSION assert state.fork.current_version == post_spec.config.ALTAIR_FORK_VERSION - elif post_spec.fork == MERGE: + elif post_spec.fork == BELLATRIX: assert state.fork.previous_version == post_spec.config.ALTAIR_FORK_VERSION - assert state.fork.current_version == post_spec.config.MERGE_FORK_VERSION + assert state.fork.current_version == post_spec.config.BELLATRIX_FORK_VERSION if with_block: return state, _state_transition_and_sign_block_at_slot(post_spec, state, operation_dict=operation_dict) diff --git a/tests/core/pyspec/eth2spec/test/helpers/genesis.py b/tests/core/pyspec/eth2spec/test/helpers/genesis.py index ed90a7d4e..5ebe46c0e 100644 --- a/tests/core/pyspec/eth2spec/test/helpers/genesis.py +++ b/tests/core/pyspec/eth2spec/test/helpers/genesis.py @@ -1,6 +1,6 @@ from eth2spec.test.helpers.constants import ( - ALTAIR, MERGE, - FORKS_BEFORE_ALTAIR, FORKS_BEFORE_MERGE, + ALTAIR, BELLATRIX, + FORKS_BEFORE_ALTAIR, FORKS_BEFORE_BELLATRIX, ) from eth2spec.test.helpers.keys import pubkeys @@ -48,9 +48,9 @@ def create_genesis_state(spec, validator_balances, activation_threshold): if spec.fork == ALTAIR: current_version = spec.config.ALTAIR_FORK_VERSION - elif spec.fork == MERGE: + elif spec.fork == BELLATRIX: previous_version = spec.config.ALTAIR_FORK_VERSION - current_version = spec.config.MERGE_FORK_VERSION + current_version = spec.config.BELLATRIX_FORK_VERSION state = spec.BeaconState( genesis_time=0, @@ -93,7 +93,7 @@ def create_genesis_state(spec, validator_balances, activation_threshold): state.current_sync_committee = spec.get_next_sync_committee(state) state.next_sync_committee = spec.get_next_sync_committee(state) - if spec.fork not in FORKS_BEFORE_MERGE: + if spec.fork not in FORKS_BEFORE_BELLATRIX: # Initialize the execution payload header (with block number and genesis time set to 0) state.latest_execution_payload_header = get_sample_genesis_execution_payload_header( spec, diff --git a/tests/core/pyspec/eth2spec/test/helpers/proposer_slashings.py b/tests/core/pyspec/eth2spec/test/helpers/proposer_slashings.py index 6a8cd2dcb..faa4d4288 100644 --- a/tests/core/pyspec/eth2spec/test/helpers/proposer_slashings.py +++ b/tests/core/pyspec/eth2spec/test/helpers/proposer_slashings.py @@ -1,4 +1,4 @@ -from eth2spec.test.context import is_post_altair, is_post_merge +from eth2spec.test.context import is_post_altair, is_post_bellatrix from eth2spec.test.helpers.block_header import sign_block_header from eth2spec.test.helpers.keys import pubkey_to_privkey from eth2spec.test.helpers.state import get_balance @@ -9,8 +9,8 @@ from eth2spec.test.helpers.sync_committee import ( def get_min_slashing_penalty_quotient(spec): - if is_post_merge(spec): - return spec.MIN_SLASHING_PENALTY_QUOTIENT_MERGE + if is_post_bellatrix(spec): + return spec.MIN_SLASHING_PENALTY_QUOTIENT_BELLATRIX elif is_post_altair(spec): return spec.MIN_SLASHING_PENALTY_QUOTIENT_ALTAIR else: diff --git a/tests/core/pyspec/eth2spec/test/helpers/rewards.py b/tests/core/pyspec/eth2spec/test/helpers/rewards.py index 49a7b0cd1..ec7ff25e3 100644 --- a/tests/core/pyspec/eth2spec/test/helpers/rewards.py +++ b/tests/core/pyspec/eth2spec/test/helpers/rewards.py @@ -2,7 +2,7 @@ from random import Random from lru import LRU from eth2spec.phase0.mainnet import VALIDATOR_REGISTRY_LIMIT # equal everywhere, fine to import -from eth2spec.test.context import is_post_altair, is_post_merge +from eth2spec.test.context import is_post_altair, is_post_bellatrix from eth2spec.test.helpers.state import ( next_epoch, ) @@ -22,8 +22,8 @@ class Deltas(Container): def get_inactivity_penalty_quotient(spec): - if is_post_merge(spec): - return spec.INACTIVITY_PENALTY_QUOTIENT_MERGE + if is_post_bellatrix(spec): + return spec.INACTIVITY_PENALTY_QUOTIENT_BELLATRIX elif is_post_altair(spec): return spec.INACTIVITY_PENALTY_QUOTIENT_ALTAIR else: diff --git a/tests/core/pyspec/eth2spec/test/phase0/epoch_processing/test_process_slashings.py b/tests/core/pyspec/eth2spec/test/phase0/epoch_processing/test_process_slashings.py index d3593d9ac..e7ae96ecb 100644 --- a/tests/core/pyspec/eth2spec/test/phase0/epoch_processing/test_process_slashings.py +++ b/tests/core/pyspec/eth2spec/test/phase0/epoch_processing/test_process_slashings.py @@ -1,5 +1,5 @@ from random import Random -from eth2spec.test.context import spec_state_test, with_all_phases, is_post_altair, is_post_merge +from eth2spec.test.context import spec_state_test, with_all_phases, is_post_altair, is_post_bellatrix from eth2spec.test.helpers.epoch_processing import ( run_epoch_processing_with, run_epoch_processing_to ) @@ -31,8 +31,8 @@ def slash_validators(spec, state, indices, out_epochs): def get_slashing_multiplier(spec): - if is_post_merge(spec): - return spec.PROPORTIONAL_SLASHING_MULTIPLIER_MERGE + if is_post_bellatrix(spec): + return spec.PROPORTIONAL_SLASHING_MULTIPLIER_BELLATRIX elif is_post_altair(spec): return spec.PROPORTIONAL_SLASHING_MULTIPLIER_ALTAIR else: diff --git a/tests/core/pyspec/eth2spec/test/phase0/sanity/test_blocks.py b/tests/core/pyspec/eth2spec/test/phase0/sanity/test_blocks.py index 8ff6bd731..34a053c86 100644 --- a/tests/core/pyspec/eth2spec/test/phase0/sanity/test_blocks.py +++ b/tests/core/pyspec/eth2spec/test/phase0/sanity/test_blocks.py @@ -39,7 +39,7 @@ from eth2spec.test.context import ( with_custom_state, large_validator_set, is_post_altair, - is_post_merge, + is_post_bellatrix, ) @@ -143,7 +143,7 @@ def process_and_sign_block_without_header_validations(spec, state, block): state_root=spec.Bytes32(), body_root=block.body.hash_tree_root(), ) - if is_post_merge(spec): + if is_post_bellatrix(spec): if spec.is_execution_enabled(state, block.body): spec.process_execution_payload(state, block.body.execution_payload, spec.EXECUTION_ENGINE) @@ -195,7 +195,7 @@ def test_parent_from_same_slot(spec, state): child_block = parent_block.copy() child_block.parent_root = state.latest_block_header.hash_tree_root() - if is_post_merge(spec): + if is_post_bellatrix(spec): child_block.body.execution_payload = build_empty_execution_payload(spec, state) # Show that normal path through transition fails diff --git a/tests/core/pyspec/eth2spec/test/phase0/unittests/fork_choice/test_on_attestation.py b/tests/core/pyspec/eth2spec/test/phase0/unittests/fork_choice/test_on_attestation.py index 9007a778f..656c536a5 100644 --- a/tests/core/pyspec/eth2spec/test/phase0/unittests/fork_choice/test_on_attestation.py +++ b/tests/core/pyspec/eth2spec/test/phase0/unittests/fork_choice/test_on_attestation.py @@ -1,7 +1,7 @@ from eth2spec.test.context import with_all_phases, spec_state_test from eth2spec.test.helpers.block import build_empty_block_for_next_slot from eth2spec.test.helpers.attestations import get_valid_attestation, sign_attestation -from eth2spec.test.helpers.constants import PHASE0, ALTAIR, MERGE +from eth2spec.test.helpers.constants import PHASE0, ALTAIR, BELLATRIX from eth2spec.test.helpers.state import transition_to, state_transition_and_sign_block, next_epoch, next_slot from eth2spec.test.helpers.fork_choice import get_genesis_forkchoice_store @@ -19,7 +19,7 @@ def run_on_attestation(spec, state, store, attestation, valid=True): spec.on_attestation(store, attestation) sample_index = indexed_attestation.attesting_indices[0] - if spec.fork in (PHASE0, ALTAIR, MERGE): + if spec.fork in (PHASE0, ALTAIR, BELLATRIX): latest_message = spec.LatestMessage( epoch=attestation.data.target.epoch, root=attestation.data.beacon_block_root, diff --git a/tests/core/pyspec/eth2spec/test/phase0/unittests/test_config_invariants.py b/tests/core/pyspec/eth2spec/test/phase0/unittests/test_config_invariants.py index 8836d463e..7f0fd5d73 100644 --- a/tests/core/pyspec/eth2spec/test/phase0/unittests/test_config_invariants.py +++ b/tests/core/pyspec/eth2spec/test/phase0/unittests/test_config_invariants.py @@ -1,7 +1,7 @@ from eth2spec.test.context import ( spec_state_test, with_all_phases, - is_post_altair, is_post_merge, + is_post_altair, is_post_bellatrix, ) from eth2spec.test.helpers.constants import MAX_UINT_64 @@ -52,8 +52,8 @@ def test_hysteresis_quotient(spec, state): @spec_state_test def test_incentives(spec, state): # Ensure no ETH is minted in slash_validator - if is_post_merge(spec): - assert spec.MIN_SLASHING_PENALTY_QUOTIENT_MERGE <= spec.WHISTLEBLOWER_REWARD_QUOTIENT + if is_post_bellatrix(spec): + assert spec.MIN_SLASHING_PENALTY_QUOTIENT_BELLATRIX <= spec.WHISTLEBLOWER_REWARD_QUOTIENT elif is_post_altair(spec): assert spec.MIN_SLASHING_PENALTY_QUOTIENT_ALTAIR <= spec.WHISTLEBLOWER_REWARD_QUOTIENT else: diff --git a/tests/formats/forks/README.md b/tests/formats/forks/README.md index 1d3b18d0d..dfbaf2df0 100644 --- a/tests/formats/forks/README.md +++ b/tests/formats/forks/README.md @@ -23,7 +23,7 @@ Key of valid `fork` strings that might be found in `meta.yaml` | String ID | Pre-fork | Post-fork | Function | | - | - | - | - | | `altair` | Phase 0 | Altair | `upgrade_to_altair` | -| `merge` | Phase 0 | Merge | `upgrade_to_merge` | +| `bellatrix` | Altair | Bellatrix | `upgrade_to_bellatrix` | ### `pre.ssz_snappy` diff --git a/tests/formats/genesis/initialization.md b/tests/formats/genesis/initialization.md index d0d453e5b..9848e157d 100644 --- a/tests/formats/genesis/initialization.md +++ b/tests/formats/genesis/initialization.md @@ -29,7 +29,7 @@ Each file is a SSZ-snappy encoded `Deposit` object. ### `execution_payload_header.ssz_snappy` -*Note*: Param added only for the Merge and subsequent forks. +*Note*: Param added only for Bellatrix and subsequent forks. The execution payload header that state is initialized with. An SSZ-snappy encoded `BeaconState` object. diff --git a/tests/formats/operations/README.md b/tests/formats/operations/README.md index c69d798d7..e532c5047 100644 --- a/tests/formats/operations/README.md +++ b/tests/formats/operations/README.md @@ -42,7 +42,7 @@ Operations: | `proposer_slashing` | `ProposerSlashing` | `proposer_slashing` | `process_proposer_slashing(state, proposer_slashing)` | | `voluntary_exit` | `SignedVoluntaryExit` | `voluntary_exit` | `process_voluntary_exit(state, voluntary_exit)` | | `sync_aggregate` | `SyncAggregate` | `sync_aggregate` | `process_sync_aggregate(state, sync_aggregate)` (new in Altair) | -| `execution_payload` | `ExecutionPayload` | `execution_payload` | `process_execution_payload(state, execution_payload)` (new in Merge) | +| `execution_payload` | `ExecutionPayload` | `execution_payload` | `process_execution_payload(state, execution_payload)` (new in Bellatrix) | Note that `block_header` is not strictly an operation (and is a full `Block`), but processed in the same manner, and hence included here. diff --git a/tests/generators/epoch_processing/main.py b/tests/generators/epoch_processing/main.py index 10893fc7d..7ba270929 100644 --- a/tests/generators/epoch_processing/main.py +++ b/tests/generators/epoch_processing/main.py @@ -1,5 +1,5 @@ from eth2spec.gen_helpers.gen_from_tests.gen import run_state_test_generators, combine_mods -from eth2spec.test.helpers.constants import PHASE0, ALTAIR, MERGE +from eth2spec.test.helpers.constants import PHASE0, ALTAIR, BELLATRIX if __name__ == "__main__": @@ -23,8 +23,9 @@ if __name__ == "__main__": ]} altair_mods = combine_mods(_new_altair_mods, phase_0_mods) - # No epoch-processing changes in Merge and previous testing repeats with new types, so no additional tests required. - merge_mods = altair_mods + # No epoch-processing changes in Bellatrix and previous testing repeats with new types, + # so no additional tests required. + bellatrix_mods = altair_mods # TODO Custody Game testgen is disabled for now # custody_game_mods = {**{key: 'eth2spec.test.custody_game.epoch_processing.test_process_' + key for key in [ @@ -36,7 +37,7 @@ if __name__ == "__main__": all_mods = { PHASE0: phase_0_mods, ALTAIR: altair_mods, - MERGE: merge_mods, + BELLATRIX: bellatrix_mods, } run_state_test_generators(runner_name="epoch_processing", all_mods=all_mods) diff --git a/tests/generators/finality/main.py b/tests/generators/finality/main.py index dbc58a809..24c7d0c6e 100644 --- a/tests/generators/finality/main.py +++ b/tests/generators/finality/main.py @@ -1,16 +1,16 @@ from eth2spec.gen_helpers.gen_from_tests.gen import run_state_test_generators -from eth2spec.test.helpers.constants import PHASE0, ALTAIR, MERGE +from eth2spec.test.helpers.constants import PHASE0, ALTAIR, BELLATRIX if __name__ == "__main__": phase_0_mods = {'finality': 'eth2spec.test.phase0.finality.test_finality'} altair_mods = phase_0_mods # No additional Altair specific finality tests - merge_mods = altair_mods # No additional Merge specific finality tests + bellatrix_mods = altair_mods # No additional Bellatrix specific finality tests all_mods = { PHASE0: phase_0_mods, ALTAIR: altair_mods, - MERGE: merge_mods, + BELLATRIX: bellatrix_mods, } run_state_test_generators(runner_name="finality", all_mods=all_mods) diff --git a/tests/generators/fork_choice/main.py b/tests/generators/fork_choice/main.py index b194dc3bd..87d3a3fdf 100644 --- a/tests/generators/fork_choice/main.py +++ b/tests/generators/fork_choice/main.py @@ -1,5 +1,5 @@ from eth2spec.gen_helpers.gen_from_tests.gen import run_state_test_generators, combine_mods -from eth2spec.test.helpers.constants import PHASE0, ALTAIR, MERGE +from eth2spec.test.helpers.constants import PHASE0, ALTAIR, BELLATRIX if __name__ == "__main__": @@ -13,15 +13,15 @@ if __name__ == "__main__": # For merge `on_merge_block` test kind added with `pow_block_N.ssz` files with several # PowBlock's which should be resolved by `get_pow_block(hash: Hash32) -> PowBlock` function - _new_merge_mods = {key: 'eth2spec.test.merge.fork_choice.test_' + key for key in [ + _new_bellatrix_mods = {key: 'eth2spec.test.bellatrix.fork_choice.test_' + key for key in [ 'on_merge_block', ]} - merge_mods = combine_mods(_new_merge_mods, altair_mods) + bellatrix_mods = combine_mods(_new_bellatrix_mods, altair_mods) all_mods = { PHASE0: phase_0_mods, ALTAIR: altair_mods, - MERGE: merge_mods, + BELLATRIX: bellatrix_mods, } run_state_test_generators(runner_name="fork_choice", all_mods=all_mods) diff --git a/tests/generators/genesis/main.py b/tests/generators/genesis/main.py index 331381aa7..272eebb4e 100644 --- a/tests/generators/genesis/main.py +++ b/tests/generators/genesis/main.py @@ -1,5 +1,5 @@ from eth2spec.gen_helpers.gen_from_tests.gen import run_state_test_generators, combine_mods -from eth2spec.test.helpers.constants import PHASE0, ALTAIR, MERGE +from eth2spec.test.helpers.constants import PHASE0, ALTAIR, BELLATRIX if __name__ == "__main__": @@ -11,15 +11,15 @@ if __name__ == "__main__": # we have new unconditional lines in `initialize_beacon_state_from_eth1` and we want to test it altair_mods = phase_0_mods - _new_merge_mods = {key: 'eth2spec.test.merge.genesis.test_' + key for key in [ + _new_bellatrix_mods = {key: 'eth2spec.test.bellatrix.genesis.test_' + key for key in [ 'initialization', ]} - merge_mods = combine_mods(_new_merge_mods, altair_mods) + bellatrix_mods = combine_mods(_new_bellatrix_mods, altair_mods) all_mods = { PHASE0: phase_0_mods, ALTAIR: altair_mods, - MERGE: merge_mods, + BELLATRIX: bellatrix_mods, } run_state_test_generators(runner_name="genesis", all_mods=all_mods) diff --git a/tests/generators/merkle/main.py b/tests/generators/merkle/main.py index 44fbec10c..5d07264ef 100644 --- a/tests/generators/merkle/main.py +++ b/tests/generators/merkle/main.py @@ -1,4 +1,4 @@ -from eth2spec.test.helpers.constants import ALTAIR, MERGE +from eth2spec.test.helpers.constants import ALTAIR, BELLATRIX from eth2spec.gen_helpers.gen_from_tests.gen import run_state_test_generators @@ -6,11 +6,11 @@ if __name__ == "__main__": altair_mods = {key: 'eth2spec.test.altair.merkle.test_' + key for key in [ 'single_proof', ]} - merge_mods = altair_mods + bellatrix_mods = altair_mods all_mods = { ALTAIR: altair_mods, - MERGE: merge_mods, + BELLATRIX: bellatrix_mods, } run_state_test_generators(runner_name="merkle", all_mods=all_mods) diff --git a/tests/generators/operations/main.py b/tests/generators/operations/main.py index 4467b71b7..37e7cf094 100644 --- a/tests/generators/operations/main.py +++ b/tests/generators/operations/main.py @@ -1,5 +1,5 @@ from eth2spec.gen_helpers.gen_from_tests.gen import run_state_test_generators, combine_mods -from eth2spec.test.helpers.constants import PHASE0, ALTAIR, MERGE +from eth2spec.test.helpers.constants import PHASE0, ALTAIR, BELLATRIX if __name__ == "__main__": @@ -17,10 +17,10 @@ if __name__ == "__main__": ]} altair_mods = combine_mods(_new_altair_mods, phase_0_mods) - _new_merge_mods = {key: 'eth2spec.test.merge.block_processing.test_process_' + key for key in [ + _new_bellatrix_mods = {key: 'eth2spec.test.bellatrix.block_processing.test_process_' + key for key in [ 'execution_payload', ]} - merge_mods = combine_mods(_new_merge_mods, altair_mods) + bellatrix_mods = combine_mods(_new_bellatrix_mods, altair_mods) # TODO Custody Game testgen is disabled for now # _new_custody_game_mods = {key: 'eth2spec.test.custody_game.block_processing.test_process_' + key for key in [ @@ -35,7 +35,7 @@ if __name__ == "__main__": all_mods = { PHASE0: phase_0_mods, ALTAIR: altair_mods, - MERGE: merge_mods, + BELLATRIX: bellatrix_mods, } run_state_test_generators(runner_name="operations", all_mods=all_mods) diff --git a/tests/generators/rewards/main.py b/tests/generators/rewards/main.py index 6b6a7bc6f..22b4be5c3 100644 --- a/tests/generators/rewards/main.py +++ b/tests/generators/rewards/main.py @@ -1,5 +1,5 @@ from eth2spec.gen_helpers.gen_from_tests.gen import run_state_test_generators -from eth2spec.test.helpers.constants import PHASE0, ALTAIR, MERGE +from eth2spec.test.helpers.constants import PHASE0, ALTAIR, BELLATRIX if __name__ == "__main__": @@ -8,18 +8,18 @@ if __name__ == "__main__": 'leak', 'random', ]} - # No additional altair specific rewards tests, yet. + # No additional Altair specific rewards tests, yet. altair_mods = phase_0_mods - # No additional merge specific rewards tests, yet. + # No additional Bellatrix specific rewards tests, yet. # Note: Block rewards are non-epoch rewards and are tested as part of block processing tests. # Transaction fees are part of the execution-layer. - merge_mods = altair_mods + bellatrix_mods = altair_mods all_mods = { PHASE0: phase_0_mods, ALTAIR: altair_mods, - MERGE: merge_mods, + BELLATRIX: bellatrix_mods, } run_state_test_generators(runner_name="rewards", all_mods=all_mods) diff --git a/tests/generators/sanity/main.py b/tests/generators/sanity/main.py index 705456824..259e79059 100644 --- a/tests/generators/sanity/main.py +++ b/tests/generators/sanity/main.py @@ -1,4 +1,4 @@ -from eth2spec.test.helpers.constants import PHASE0, ALTAIR, MERGE +from eth2spec.test.helpers.constants import PHASE0, ALTAIR, BELLATRIX from eth2spec.gen_helpers.gen_from_tests.gen import run_state_test_generators, combine_mods @@ -13,15 +13,15 @@ if __name__ == "__main__": ]} altair_mods = combine_mods(_new_altair_mods, phase_0_mods) - _new_merge_mods = {key: 'eth2spec.test.merge.sanity.test_' + key for key in [ + _new_bellatrix_mods = {key: 'eth2spec.test.bellatrix.sanity.test_' + key for key in [ 'blocks', ]} - merge_mods = combine_mods(_new_merge_mods, altair_mods) + bellatrix_mods = combine_mods(_new_bellatrix_mods, altair_mods) all_mods = { PHASE0: phase_0_mods, ALTAIR: altair_mods, - MERGE: merge_mods, + BELLATRIX: bellatrix_mods, } run_state_test_generators(runner_name="sanity", all_mods=all_mods) diff --git a/tests/generators/transition/main.py b/tests/generators/transition/main.py index d6195de68..5c3b43b4f 100644 --- a/tests/generators/transition/main.py +++ b/tests/generators/transition/main.py @@ -16,8 +16,8 @@ from eth2spec.test.altair.transition import ( test_slashing as test_altair_slashing, test_operations as test_altair_operations, ) -from eth2spec.test.merge.transition import ( - test_transition as test_merge_transition, +from eth2spec.test.bellatrix.transition import ( + test_transition as test_bellatrix_transition, ) @@ -47,10 +47,10 @@ if __name__ == "__main__": test_altair_slashing, test_altair_operations, ) - merge_tests = ( - test_merge_transition, + bellatrix_tests = ( + test_bellatrix_transition, ) - all_tests = altair_tests + merge_tests + all_tests = altair_tests + bellatrix_tests for transition_test_module in all_tests: for pre_fork, post_fork in ALL_PRE_POST_FORKS: gen_runner.run_generator("transition", [ From 41af6395179d95dffbb6aa86e09efab3d28d6a83 Mon Sep 17 00:00:00 2001 From: Hsiao-Wei Wang Date: Thu, 23 Dec 2021 17:26:48 +0800 Subject: [PATCH 41/46] PR feedback from @mkalinin --- README.md | 2 +- presets/minimal/bellatrix.yaml | 2 +- specs/phase0/deposit-contract.md | 4 ++-- ..._fork_basic.py => test_bellatrix_fork_basic.py} | 0 ...ork_random.py => test_bellatrix_fork_random.py} | 14 +++++++------- 5 files changed, 11 insertions(+), 11 deletions(-) rename tests/core/pyspec/eth2spec/test/bellatrix/fork/{test_merge_fork_basic.py => test_bellatrix_fork_basic.py} (100%) rename tests/core/pyspec/eth2spec/test/bellatrix/fork/{test_merge_fork_random.py => test_bellatrix_fork_random.py} (85%) diff --git a/README.md b/README.md index 60bac7f35..4cac3d535 100644 --- a/README.md +++ b/README.md @@ -50,7 +50,7 @@ Bellatrix protocol upgrade is still actively in development. The exact specifica ### Sharding -Sharding follows the merge, and is divided into three parts: +Sharding follows Bellatrix, and is divided into three parts: * Sharding base functionality - In early engineering phase * [Beacon Chain changes](specs/sharding/beacon-chain.md) diff --git a/presets/minimal/bellatrix.yaml b/presets/minimal/bellatrix.yaml index d43e226fa..3417985fa 100644 --- a/presets/minimal/bellatrix.yaml +++ b/presets/minimal/bellatrix.yaml @@ -1,4 +1,4 @@ -# Minimal preset - The Merge +# Minimal preset - Bellatrix # Updated penalty values # --------------------------------------------------------------- diff --git a/specs/phase0/deposit-contract.md b/specs/phase0/deposit-contract.md index 51786129c..2f7d18ec4 100644 --- a/specs/phase0/deposit-contract.md +++ b/specs/phase0/deposit-contract.md @@ -44,7 +44,7 @@ These configurations are updated for releases and may be out of sync during `dev ## Staking deposit contract -The initial deployment phases of Ethereum proof-of-stake are implemented without consensus changes to the existing Ethereum proof-of-work chain. A deposit contract at address `DEPOSIT_CONTRACT_ADDRESS` is added to the Ethereum proof-of-work chain defined by the [chain-id](https://eips.ethereum.org/EIPS/eip-155) -- `DEPOSIT_CHAIN_ID` -- and the network-id -- `DEPOSIT_NETWORK_ID` -- for deposits of ETH to the beacon chain. Validator balances will be withdrawable to the execution-layer in a followup fork after the Merge. +The initial deployment phases of Ethereum proof-of-stake are implemented without consensus changes to the existing Ethereum proof-of-work chain. A deposit contract at address `DEPOSIT_CONTRACT_ADDRESS` is added to the Ethereum proof-of-work chain defined by the [chain-id](https://eips.ethereum.org/EIPS/eip-155) -- `DEPOSIT_CHAIN_ID` -- and the network-id -- `DEPOSIT_NETWORK_ID` -- for deposits of ETH to the beacon chain. Validator balances will be withdrawable to the execution-layer in a followup fork after Bellatrix fork. _Note_: See [here](https://chainid.network/) for a comprehensive list of public Ethereum chain chain-id's and network-id's. @@ -54,7 +54,7 @@ The deposit contract has a public `deposit` function to make deposits. It takes #### Deposit amount -The amount of ETH (rounded down to the closest Gwei) sent to the deposit contract is the deposit amount, which must be of size at least `MIN_DEPOSIT_AMOUNT` Gwei. Note that ETH consumed by the deposit contract is no longer usable on the execution-layer until sometime after the Merge. +The amount of ETH (rounded down to the closest Gwei) sent to the deposit contract is the deposit amount, which must be of size at least `MIN_DEPOSIT_AMOUNT` Gwei. Note that ETH consumed by the deposit contract is no longer usable on the execution-layer until sometime after Bellatrix fork. #### Withdrawal credentials diff --git a/tests/core/pyspec/eth2spec/test/bellatrix/fork/test_merge_fork_basic.py b/tests/core/pyspec/eth2spec/test/bellatrix/fork/test_bellatrix_fork_basic.py similarity index 100% rename from tests/core/pyspec/eth2spec/test/bellatrix/fork/test_merge_fork_basic.py rename to tests/core/pyspec/eth2spec/test/bellatrix/fork/test_bellatrix_fork_basic.py diff --git a/tests/core/pyspec/eth2spec/test/bellatrix/fork/test_merge_fork_random.py b/tests/core/pyspec/eth2spec/test/bellatrix/fork/test_bellatrix_fork_random.py similarity index 85% rename from tests/core/pyspec/eth2spec/test/bellatrix/fork/test_merge_fork_random.py rename to tests/core/pyspec/eth2spec/test/bellatrix/fork/test_bellatrix_fork_random.py index 2479b1960..9a39cc1aa 100644 --- a/tests/core/pyspec/eth2spec/test/bellatrix/fork/test_merge_fork_random.py +++ b/tests/core/pyspec/eth2spec/test/bellatrix/fork/test_bellatrix_fork_random.py @@ -23,7 +23,7 @@ from eth2spec.test.helpers.random import randomize_state @spec_test @with_state @with_meta_tags(BELLATRIX_FORK_TEST_META_TAGS) -def test_merge_fork_random_0(spec, phases, state): +def test_bellatrix_fork_random_0(spec, phases, state): randomize_state(spec, state, rng=Random(1010)) yield from run_fork_test(phases[BELLATRIX], state) @@ -32,7 +32,7 @@ def test_merge_fork_random_0(spec, phases, state): @spec_test @with_state @with_meta_tags(BELLATRIX_FORK_TEST_META_TAGS) -def test_merge_fork_random_1(spec, phases, state): +def test_bellatrix_fork_random_1(spec, phases, state): randomize_state(spec, state, rng=Random(2020)) yield from run_fork_test(phases[BELLATRIX], state) @@ -41,7 +41,7 @@ def test_merge_fork_random_1(spec, phases, state): @spec_test @with_state @with_meta_tags(BELLATRIX_FORK_TEST_META_TAGS) -def test_merge_fork_random_2(spec, phases, state): +def test_bellatrix_fork_random_2(spec, phases, state): randomize_state(spec, state, rng=Random(3030)) yield from run_fork_test(phases[BELLATRIX], state) @@ -50,7 +50,7 @@ def test_merge_fork_random_2(spec, phases, state): @spec_test @with_state @with_meta_tags(BELLATRIX_FORK_TEST_META_TAGS) -def test_merge_fork_random_3(spec, phases, state): +def test_bellatrix_fork_random_3(spec, phases, state): randomize_state(spec, state, rng=Random(4040)) yield from run_fork_test(phases[BELLATRIX], state) @@ -59,7 +59,7 @@ def test_merge_fork_random_3(spec, phases, state): @spec_test @with_custom_state(balances_fn=low_balances, threshold_fn=lambda spec: spec.config.EJECTION_BALANCE) @with_meta_tags(BELLATRIX_FORK_TEST_META_TAGS) -def test_merge_fork_random_low_balances(spec, phases, state): +def test_bellatrix_fork_random_low_balances(spec, phases, state): randomize_state(spec, state, rng=Random(5050)) yield from run_fork_test(phases[BELLATRIX], state) @@ -68,7 +68,7 @@ def test_merge_fork_random_low_balances(spec, phases, state): @spec_test @with_custom_state(balances_fn=misc_balances, threshold_fn=lambda spec: spec.config.EJECTION_BALANCE) @with_meta_tags(BELLATRIX_FORK_TEST_META_TAGS) -def test_merge_fork_random_misc_balances(spec, phases, state): +def test_bellatrix_fork_random_misc_balances(spec, phases, state): randomize_state(spec, state, rng=Random(6060)) yield from run_fork_test(phases[BELLATRIX], state) @@ -79,6 +79,6 @@ def test_merge_fork_random_misc_balances(spec, phases, state): @spec_test @with_custom_state(balances_fn=large_validator_set, threshold_fn=lambda spec: spec.config.EJECTION_BALANCE) @with_meta_tags(BELLATRIX_FORK_TEST_META_TAGS) -def test_merge_fork_random_large_validator_set(spec, phases, state): +def test_bellatrix_fork_random_large_validator_set(spec, phases, state): randomize_state(spec, state, rng=Random(7070)) yield from run_fork_test(phases[BELLATRIX], state) From 8c226013fd36c72ed17bd41b0a1ec8dd0093e5f3 Mon Sep 17 00:00:00 2001 From: Hsiao-Wei Wang Date: Thu, 23 Dec 2021 17:32:15 +0800 Subject: [PATCH 42/46] minor fix --- specs/bellatrix/beacon-chain.md | 2 +- specs/bellatrix/p2p-interface.md | 2 +- specs/phase0/deposit-contract.md | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/specs/bellatrix/beacon-chain.md b/specs/bellatrix/beacon-chain.md index 364172b0d..738f03556 100644 --- a/specs/bellatrix/beacon-chain.md +++ b/specs/bellatrix/beacon-chain.md @@ -48,7 +48,7 @@ ## Introduction -This upgrade adds transaction execution to the beacon chain as part of Bellatrix fork. +This upgrade adds transaction execution to the beacon chain as part of Bellatrix upgrade. Additionally, this upgrade introduces the following minor changes: * Penalty parameter updates to their planned maximally punitive values diff --git a/specs/bellatrix/p2p-interface.md b/specs/bellatrix/p2p-interface.md index ea1ca8b5b..aefbb54c7 100644 --- a/specs/bellatrix/p2p-interface.md +++ b/specs/bellatrix/p2p-interface.md @@ -46,7 +46,7 @@ This section outlines modifications constants that are used in this spec. | Name | Value | Description | |---|---|---| -| `GOSSIP_MAX_SIZE_BELLATRIX` | `10 * 2**20` (= 10,485,760, 10 MiB) | The maximum allowed size of uncompressed gossip messages starting at the Merge upgrade. | +| `GOSSIP_MAX_SIZE_BELLATRIX` | `10 * 2**20` (= 10,485,760, 10 MiB) | The maximum allowed size of uncompressed gossip messages starting at Bellatrix upgrade. | | `MAX_CHUNK_SIZE_BELLATRIX` | `10 * 2**20` (= 10,485,760, 10 MiB) | The maximum allowed size of uncompressed req/resp chunked responses starting at Bellatrix upgrade. | ## The gossip domain: gossipsub diff --git a/specs/phase0/deposit-contract.md b/specs/phase0/deposit-contract.md index 2f7d18ec4..039b5998b 100644 --- a/specs/phase0/deposit-contract.md +++ b/specs/phase0/deposit-contract.md @@ -44,7 +44,7 @@ These configurations are updated for releases and may be out of sync during `dev ## Staking deposit contract -The initial deployment phases of Ethereum proof-of-stake are implemented without consensus changes to the existing Ethereum proof-of-work chain. A deposit contract at address `DEPOSIT_CONTRACT_ADDRESS` is added to the Ethereum proof-of-work chain defined by the [chain-id](https://eips.ethereum.org/EIPS/eip-155) -- `DEPOSIT_CHAIN_ID` -- and the network-id -- `DEPOSIT_NETWORK_ID` -- for deposits of ETH to the beacon chain. Validator balances will be withdrawable to the execution-layer in a followup fork after Bellatrix fork. +The initial deployment phases of Ethereum proof-of-stake are implemented without consensus changes to the existing Ethereum proof-of-work chain. A deposit contract at address `DEPOSIT_CONTRACT_ADDRESS` is added to the Ethereum proof-of-work chain defined by the [chain-id](https://eips.ethereum.org/EIPS/eip-155) -- `DEPOSIT_CHAIN_ID` -- and the network-id -- `DEPOSIT_NETWORK_ID` -- for deposits of ETH to the beacon chain. Validator balances will be withdrawable to the execution-layer in a followup fork after Bellatrix upgrade. _Note_: See [here](https://chainid.network/) for a comprehensive list of public Ethereum chain chain-id's and network-id's. @@ -54,7 +54,7 @@ The deposit contract has a public `deposit` function to make deposits. It takes #### Deposit amount -The amount of ETH (rounded down to the closest Gwei) sent to the deposit contract is the deposit amount, which must be of size at least `MIN_DEPOSIT_AMOUNT` Gwei. Note that ETH consumed by the deposit contract is no longer usable on the execution-layer until sometime after Bellatrix fork. +The amount of ETH (rounded down to the closest Gwei) sent to the deposit contract is the deposit amount, which must be of size at least `MIN_DEPOSIT_AMOUNT` Gwei. Note that ETH consumed by the deposit contract is no longer usable on the execution-layer until sometime after Bellatrix upgrade. #### Withdrawal credentials From a7df9a39cc51d18abfcbc944c45dd76577cca4c4 Mon Sep 17 00:00:00 2001 From: Hsiao-Wei Wang Date: Thu, 23 Dec 2021 21:33:23 +0800 Subject: [PATCH 43/46] Set RNG seed --- .../pyspec/eth2spec/test/phase0/fork_choice/test_get_head.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/tests/core/pyspec/eth2spec/test/phase0/fork_choice/test_get_head.py b/tests/core/pyspec/eth2spec/test/phase0/fork_choice/test_get_head.py index a524cbd56..5e4d247e7 100644 --- a/tests/core/pyspec/eth2spec/test/phase0/fork_choice/test_get_head.py +++ b/tests/core/pyspec/eth2spec/test/phase0/fork_choice/test_get_head.py @@ -26,6 +26,9 @@ from eth2spec.test.helpers.state import ( ) +rng = random.Random(1001) + + @with_all_phases @spec_state_test def test_genesis(spec, state): @@ -303,7 +306,7 @@ def test_proposer_boost_correct_head(spec, state): block_2 = build_empty_block_for_next_slot(spec, state_2) signed_block_2 = state_transition_and_sign_block(spec, state_2.copy(), block_2) while spec.hash_tree_root(block_1) >= spec.hash_tree_root(block_2): - block_2.body.graffiti = spec.Bytes32(hex(random.getrandbits(8 * 32))[2:].zfill(64)) + block_2.body.graffiti = spec.Bytes32(hex(rng.getrandbits(8 * 32))[2:].zfill(64)) signed_block_2 = state_transition_and_sign_block(spec, state_2.copy(), block_2) assert spec.hash_tree_root(block_1) < spec.hash_tree_root(block_2) From 5910b90c43013bf386c8d1b6db3466fd5686afc2 Mon Sep 17 00:00:00 2001 From: Hsiao-Wei Wang Date: Fri, 24 Dec 2021 00:56:55 +0800 Subject: [PATCH 44/46] Enable Bellatrix fork tests in test generator --- tests/generators/forks/main.py | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/tests/generators/forks/main.py b/tests/generators/forks/main.py index 7be79847d..fbf75a221 100644 --- a/tests/generators/forks/main.py +++ b/tests/generators/forks/main.py @@ -1,8 +1,9 @@ from typing import Iterable -from eth2spec.test.helpers.constants import PHASE0, ALTAIR, MINIMAL, MAINNET +from eth2spec.test.helpers.constants import PHASE0, ALTAIR, BELLATRIX, MINIMAL, MAINNET from eth2spec.test.helpers.typing import SpecForkName, PresetBaseName from eth2spec.test.altair.fork import test_altair_fork_basic, test_altair_fork_random +from eth2spec.test.bellatrix.fork import test_bellatrix_fork_basic, test_bellatrix_fork_random from eth2spec.gen_helpers.gen_base import gen_runner, gen_typing from eth2spec.gen_helpers.gen_from_tests.gen import generate_from_tests @@ -26,10 +27,13 @@ def create_provider(tests_src, preset_name: PresetBaseName, return gen_typing.TestProvider(prepare=prepare_fn, make_cases=cases_fn) +def _get_fork_tests_providers(): + for preset in [MINIMAL, MAINNET]: + yield create_provider(test_altair_fork_basic, preset, PHASE0, ALTAIR) + yield create_provider(test_altair_fork_random, preset, PHASE0, ALTAIR) + yield create_provider(test_bellatrix_fork_basic, preset, ALTAIR, BELLATRIX) + yield create_provider(test_bellatrix_fork_random, preset, ALTAIR, BELLATRIX) + + if __name__ == "__main__": - gen_runner.run_generator("forks", [ - create_provider(test_altair_fork_basic, MINIMAL, PHASE0, ALTAIR), - create_provider(test_altair_fork_basic, MAINNET, PHASE0, ALTAIR), - create_provider(test_altair_fork_random, MINIMAL, PHASE0, ALTAIR), - create_provider(test_altair_fork_random, MAINNET, PHASE0, ALTAIR), - ]) + gen_runner.run_generator("forks", list(_get_fork_tests_providers())) From 3970fa7a751608e5a9596e09f1db54f49f5e0fa6 Mon Sep 17 00:00:00 2001 From: Hsiao-Wei Wang Date: Fri, 24 Dec 2021 01:38:33 +0800 Subject: [PATCH 45/46] PR feedback from @ralexstokes --- README.md | 2 +- specs/custody_game/beacon-chain.md | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 4cac3d535..738d43a05 100644 --- a/README.md +++ b/README.md @@ -35,7 +35,7 @@ The current features are: ### Bellatrix (as known as The Merge) -Bellatrix protocol upgrade is still actively in development. The exact specification has not been formally accepted as final and details are still subject to change. +The Bellatrix protocol upgrade is still actively in development. The exact specification has not been formally accepted as final and details are still subject to change. * Background material: * An [ethresear.ch](https://ethresear.ch) post [describing the basic mechanism of the CL+EL merge](https://ethresear.ch/t/the-eth1-eth2-transition/6265) diff --git a/specs/custody_game/beacon-chain.md b/specs/custody_game/beacon-chain.md index fd00dedda..6f9f61cf9 100644 --- a/specs/custody_game/beacon-chain.md +++ b/specs/custody_game/beacon-chain.md @@ -11,8 +11,7 @@ - [Introduction](#introduction) - [Constants](#constants) - [Misc](#misc) - - [Domain types](#domain-types) -- [Preset](#preset) +- [Configuration](#configuration) - [Time parameters](#time-parameters) - [Max operations per block](#max-operations-per-block) - [Size parameters](#size-parameters) From db642e05865f146419794f36bd2c5d65923db107 Mon Sep 17 00:00:00 2001 From: Hsiao-Wei Wang Date: Fri, 24 Dec 2021 02:23:17 +0800 Subject: [PATCH 46/46] bump VERSION.txt to 1.1.8 --- tests/core/pyspec/eth2spec/VERSION.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/core/pyspec/eth2spec/VERSION.txt b/tests/core/pyspec/eth2spec/VERSION.txt index a5ba93251..db1527897 100644 --- a/tests/core/pyspec/eth2spec/VERSION.txt +++ b/tests/core/pyspec/eth2spec/VERSION.txt @@ -1 +1 @@ -1.1.7 \ No newline at end of file +1.1.8 \ No newline at end of file