Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
57 commits
Select commit Hold shift + click to select a range
dfb3ef8
Update ssz_container.py
asanso Nov 24, 2021
00bede5
Remove client-settings.md
mkalinin Nov 24, 2021
6ece777
Bring more clarity to notify_forkchoice_updated calls
mkalinin Nov 24, 2021
b437733
Polish notify_forkchoice_updated description as per review
mkalinin Nov 25, 2021
7a8f236
Fix notify_forkchoice_updated description as per review
mkalinin Nov 25, 2021
96b856f
Merge pull request #2741 from mkalinin/remove-client-settings-doc
djrtwo Nov 26, 2021
d237491
minor typo
djrtwo Nov 26, 2021
e1356ae
Merge pull request #2745 from mkalinin/clarify-forkchoice-updated
djrtwo Nov 26, 2021
25f2efa
Simplify sync protocol and update to calculate optimistic heads
vbuterin Nov 26, 2021
013e814
Update sync-protocol.md
vbuterin Nov 26, 2021
e104164
Rework data structures (#2747)
vbuterin Nov 27, 2021
7718872
Fixed ToC and get_active_header positioninf
vbuterin Nov 27, 2021
c4f7097
Update specs/altair/sync-protocol.md
vbuterin Nov 28, 2021
06af629
Updated in response to comments
vbuterin Nov 29, 2021
6fa1970
Clarified next sync committee comment
vbuterin Nov 29, 2021
a20ed34
Merge pull request #2740 from asanso/patch-4
ralexstokes Nov 30, 2021
7de1495
Fix lint (#2750)
hwwhww Nov 30, 2021
c30662b
Consistently use sum instead of count(1)
vbuterin Nov 30, 2021
402c663
Fix function name leftover
hwwhww Nov 30, 2021
d7f6a42
[WIP] Add ex-ante fork choice test cases
hwwhww Nov 30, 2021
0bc0769
Simplify logic in calculating proposer score
realbigsean Nov 30, 2021
ad588d7
Update beacon-chain.md
dankrad Nov 30, 2021
37eae2a
Merge pull request #2753 from realbigsean/simplify-proposer-score-logic
djrtwo Dec 1, 2021
4d35748
Merge pull request #2754 from ethereum/dankrad-patch-1
djrtwo Dec 1, 2021
bb8168f
Make config dict compatible with yaml.dump
hwwhww Dec 1, 2021
dc52b35
Add `PROPOSER_SCORE_BOOST` to meta.yaml and fix comments
hwwhww Dec 1, 2021
3e2890b
Apply proposer boost to ancestors correctly
adiasg Dec 2, 2021
6f3379c
Apply HWW's suggestion
adiasg Dec 2, 2021
a399d95
Add assertion to `test_shorter_chain_but_heavier_weight`
hwwhww Dec 2, 2021
0d8fab3
Apply Danny's suggestion
adiasg Dec 2, 2021
6308cee
Fix typo
hwwhww Dec 2, 2021
3a9777e
Update specs/phase0/fork-choice.md
djrtwo Dec 2, 2021
9dc1b7a
Test with strict `proposer_boost + 1` attestation participants
hwwhww Dec 6, 2021
876c5b0
Merge pull request #2760 from ethereum/fix-proposer-boost-apply
hwwhww Dec 7, 2021
200ed58
Merge branch 'dev' into ex-ante-tests
hwwhww Dec 7, 2021
57be9d0
update per-test config to be unique per-test
ralexstokes Dec 7, 2021
733f377
use `importlib` to perform an actual spec copy
ralexstokes Dec 7, 2021
55c9c03
simply module import and fix config adjustment
ralexstokes Dec 7, 2021
db2be42
use a specific `spec` rather than the pre-defined phases
ralexstokes Dec 7, 2021
c3c24fb
Merge pull request #2765 from ralexstokes/fix-spec-config-concurrency…
hwwhww Dec 7, 2021
dc9db78
Add sandwich test scenarios
casparschwa Dec 8, 2021
79e1d5e
Fix head view comparison
casparschwa Dec 8, 2021
9f614fc
Fix linting
casparschwa Dec 8, 2021
addc03c
Enable ex-ante tests in testgen and minor fixes
hwwhww Dec 9, 2021
2226a16
Merge pull request #2766 from casparschwa/sandwich-tests
hwwhww Dec 9, 2021
11a42f8
Remove `*_without_boost` tests
hwwhww Dec 9, 2021
916193b
Updates in response to comments
vbuterin Dec 10, 2021
4cea972
Apply suggestions from code review
hwwhww Dec 11, 2021
6edf840
fix lint
hwwhww Dec 11, 2021
4cd2334
Merge pull request #2752 from ethereum/ex-ante-tests
hwwhww Dec 12, 2021
2f618f7
Fix lint and presets
hwwhww Dec 14, 2021
25d88fe
Fix `process_light_client_update`
hwwhww Dec 14, 2021
257c241
Update test_sync_protocol.py per the new optimistic_header and data s…
hwwhww Dec 14, 2021
de89238
Minor style fixes
hwwhww Dec 15, 2021
2fa396f
Merge pull request #2746 from ethereum/vbuterin-patch-12
hwwhww Dec 15, 2021
cba3ddf
bump VERSION.txt to 1.1.7
hwwhww Dec 22, 2021
67ece2a
Merge pull request #2772 from ethereum/bump-version
hwwhww Dec 22, 2021
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions presets/mainnet/altair.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -22,3 +22,5 @@ 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
2 changes: 2 additions & 0 deletions presets/minimal/altair.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -22,3 +22,5 @@ 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
1 change: 1 addition & 0 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -683,6 +683,7 @@ def combine_dicts(old_dict: Dict[str, T], new_dict: Dict[str, T]) -> Dict[str, T
'uint8', 'uint16', 'uint32', 'uint64', 'uint128', 'uint256',
'bytes', 'byte', 'ByteList', 'ByteVector',
'Dict', 'dict', 'field', 'ceillog2', 'floorlog2', 'Set',
'Optional',
]


Expand Down
199 changes: 131 additions & 68 deletions specs/altair/sync-protocol.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,14 @@
- [Preset](#preset)
- [Misc](#misc)
- [Containers](#containers)
- [`LightClientSnapshot`](#lightclientsnapshot)
- [`LightClientUpdate`](#lightclientupdate)
- [`LightClientStore`](#lightclientstore)
- [Helper functions](#helper-functions)
- [`get_subtree_index`](#get_subtree_index)
- [`get_active_header`](#get_active_header)
- [`get_safety_threshold`](#get_safety_threshold)
- [Light client state updates](#light-client-state-updates)
- [`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)
Expand Down Expand Up @@ -47,38 +49,27 @@ 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` | |
| `UPDATE_TIMEOUT` | `SLOTS_PER_EPOCH * EPOCHS_PER_SYNC_COMMITTEE_PERIOD` | ~27.3 hours |

## 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
# Next sync committee corresponding to the header
# The beacon block header that is attested to by the sync committee
attested_header: BeaconBlockHeader
# Next sync committee corresponding to the active 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]
sync_committee_signature: BLSSignature
sync_committee_aggregate: SyncAggregate
# Fork version for the aggregate signature
fork_version: Version
```
Expand All @@ -88,8 +79,18 @@ class LightClientUpdate(Container):
```python
@dataclass
class LightClientStore(object):
snapshot: LightClientSnapshot
valid_updates: Set[LightClientUpdate]
# 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 active participants in a sync committee (used to calculate safety threshold)
previous_max_active_participants: uint64
current_max_active_participants: uint64
```

## Helper functions
Expand All @@ -101,95 +102,157 @@ 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:
# 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:
return update.attested_header
```

### `get_safety_threshold`

```python
def get_safety_threshold(store: LightClientStore) -> uint64:
return max(
store.previous_max_active_participants,
store.current_max_active_participants,
) // 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_for_light_client_store` is processed every time the current slot increments.

#### `process_slot_for_light_client_store`

```python
def process_slot_for_light_client_store(store: LightClientStore, current_slot: Slot) -> None:
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`

```python
def validate_light_client_update(snapshot: LightClientSnapshot,
def validate_light_client_update(store: LightClientStore,
update: LightClientUpdate,
current_slot: Slot,
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 current_slot >= 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():
signed_header = update.header
# 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:
signed_header = update.finality_header
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,
)

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(signed_header, domain)
assert bls.FastAggregateVerify(participant_pubkeys, signing_root, update.sync_committee_signature)
signing_root = compute_signing_root(update.attested_header, domain)
assert bls.FastAggregateVerify(participant_pubkeys, signing_root, sync_aggregate.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`

```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)
validate_light_client_update(store, update, current_slot, genesis_validators_root)

update_timeout = SLOTS_PER_EPOCH * EPOCHS_PER_SYNC_COMMITTEE_PERIOD
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 (
sum(update.sync_committee_bits) * 3 >= len(update.sync_committee_bits) * 2
and update.finality_header != BeaconBlockHeader()
store.best_valid_update is None
or sum(sync_committee_bits) > sum(store.best_valid_update.sync_committee_aggregate.sync_committee_bits)
):
# 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.
apply_light_client_update(store.snapshot, update)
store.valid_updates = set()
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()
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
):
store.optimistic_header = update.attested_header

# Update finalized header
if (
sum(sync_committee_bits) * 3 >= len(sync_committee_bits) * 2
and update.finalized_header != BeaconBlockHeader()
):
# Normal update through 2/3 threshold
apply_light_client_update(store, update)
store.best_valid_update = None
```
29 changes: 0 additions & 29 deletions specs/merge/client-settings.md

This file was deleted.

6 changes: 5 additions & 1 deletion specs/merge/fork-choice.md
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,11 @@ def notify_forkchoice_updated(self: ExecutionEngine,
```

*Note*: The call of the `notify_forkchoice_updated` function maps on the `POS_FORKCHOICE_UPDATED` event defined in the [EIP-3675](https://eips.ethereum.org/EIPS/eip-3675#definitions).
As per EIP-3675, before a post-transition block is finalized, `notify_forkchoice_updated` must be called with `finalized_block_hash = Hash32()`.
As per EIP-3675, before a post-transition block is finalized, `notify_forkchoice_updated` MUST be called with `finalized_block_hash = Hash32()`.

*Note*: Client software MUST NOT call this function until the transition conditions are met on the PoW network, i.e. there exists a block for which `is_valid_terminal_pow_block` function returns `True`.

*Note*: Client software MUST call this function to initiate the payload build process to produce the merge transition block; the `head_block_hash` parameter MUST be set to the hash of a terminal PoW block in this case.

## Helpers

Expand Down
21 changes: 13 additions & 8 deletions specs/phase0/fork-choice.md
Original file line number Diff line number Diff line change
Expand Up @@ -181,15 +181,19 @@ 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():
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
# 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
committee_weight = committee_size * avg_balance
proposer_score = (committee_weight * PROPOSER_SCORE_BOOST) // 100
return attestation_score + proposer_score

```
Expand Down Expand Up @@ -263,6 +267,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))
```

Expand Down
2 changes: 1 addition & 1 deletion specs/sharding/beacon-chain.md
Original file line number Diff line number Diff line change
Expand Up @@ -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) |
Expand Down
Loading