From 7c6ede5eac9dee03817c78022874ff3b5c48a806 Mon Sep 17 00:00:00 2001 From: vbuterin Date: Thu, 12 Nov 2020 17:28:05 +0800 Subject: [PATCH 01/32] Added standalone light client patch --- specs/lightclient/beacon-chain.md | 208 ++++++++++++++++++++++++++++++ 1 file changed, 208 insertions(+) create mode 100644 specs/lightclient/beacon-chain.md diff --git a/specs/lightclient/beacon-chain.md b/specs/lightclient/beacon-chain.md new file mode 100644 index 000000000..0eb964266 --- /dev/null +++ b/specs/lightclient/beacon-chain.md @@ -0,0 +1,208 @@ +# Ethereum 2.0 Light Client Support: Beacon Chain Changes + +## Table of contents + +- [Introduction](#introduction) +- [Configuration](#configuration) + - [Domain types](#domain-types) + - [Misc](#misc) +- [Updated containers](#updated-containers) + - [Extended `BeaconBlockBody`](#extended-beaconblockbody) + - [Extended `BeaconState`](#extended-beaconstate) +- [New containers](#new-containers) + - [`CompactCommittee`](#compactcommittee) +- [Helper functions](#helper-functions) + - [Misc](#misc-1) + - [`pack_compact_validator`](#pack_compact_validator) + - [`unpack_compact_validator`](#unpack_compact_validator) + - [`committee_to_compact_committee`](#committee_to_compact_committee) + - [Beacon state accessors](#beacon-state-accessors) + - [`get_light_client_committee`](#get_light_client_committee) + - [Block processing](#block-processing) + - [Light client processing](#light-client-processing) + - [Epoch processing](#epoch-transition) + - [Light client committee updates](#light-client-committee-updates) + +## Introduction + +This is a standalone patch to the ethereum beacon chain that adds light client support. + +## Configuration + +### Misc + +| Name | Value | +| - | - | +| `LIGHT_CLIENT_COMMITTEE_SIZE` | `uint64(2**7)` (= 128) | +| `LIGHT_CLIENT_COMMITTEE_PERIOD` | `Epoch(2**8)` (= 256) | epochs | ~27 hours | +| `BASE_REWARDS_PER_EPOCH` | 5 | + +### Domain types + +| Name | Value | +| - | - | +| `DOMAIN_LIGHT_CLIENT` | `DomainType('0x82000000')` | + +## Updated containers + +### Extended `BeaconBlockBody` + +```python +class BeaconBlockBody(phase0.BeaconBlockBody): + # Bitfield of participants in this light client signature + light_client_bits: Bitvector[LIGHT_CLIENT_COMMITTEE_SIZE] + light_client_signature: BLSSignature +``` + +### Extended `BeaconState` + +```python +class BeaconState(phase0.BeaconState): + # Compact representations of the light client committee + current_light_committee: CompactCommittee + next_light_committee: CompactCommittee +``` + +## New containers + +### `CompactCommittee` + +```python +class CompactCommittee(Container): + pubkeys: List[BLSPubkey, MAX_VALIDATORS_PER_COMMITTEE] + compact_validators: List[uint64, MAX_VALIDATORS_PER_COMMITTEE] +``` + +## Helper functions + +### Misc + + +#### `pack_compact_validator` + +```python +def pack_compact_validator(index: ValidatorIndex, slashed: bool, balance_in_increments: uint64) -> uint64: + """ + Create a compact validator object representing index, slashed status, and compressed balance. + Takes as input balance-in-increments (// EFFECTIVE_BALANCE_INCREMENT) to preserve symmetry with + the unpacking function. + """ + return (index << 16) + (slashed << 15) + balance_in_increments +``` + +#### `unpack_compact_validator` + +```python +def unpack_compact_validator(compact_validator: uint64) -> Tuple[ValidatorIndex, bool, uint64]: + """ + Return validator index, slashed, balance // EFFECTIVE_BALANCE_INCREMENT + """ + return ( + ValidatorIndex(compact_validator >> 16), + bool((compact_validator >> 15) % 2), + compact_validator & (2**15 - 1), + ) +``` + +#### `committee_to_compact_committee` + +```python +def committee_to_compact_committee(state: BeaconState, committee: Sequence[ValidatorIndex]) -> CompactCommittee: + """ + Given a state and a list of validator indices, outputs the ``CompactCommittee`` representing them. + """ + validators = [state.validators[i] for i in committee] + compact_validators = [ + pack_compact_validator(i, v.slashed, v.effective_balance // EFFECTIVE_BALANCE_INCREMENT) + for i, v in zip(committee, validators) + ] + pubkeys = [v.pubkey for v in validators] + return CompactCommittee(pubkeys=pubkeys, compact_validators=compact_validators) +``` + +### Beacon state accessors + +#### `get_light_client_committee` + +```python +def get_light_client_committee(beacon_state: BeaconState, epoch: Epoch) -> Sequence[ValidatorIndex]: + """ + Return the light client committee of no more than ``LIGHT_CLIENT_COMMITTEE_SIZE`` validators. + """ + source_epoch = (max(epoch // LIGHT_CLIENT_COMMITTEE_PERIOD, 1) - 1) * LIGHT_CLIENT_COMMITTEE_PERIOD + active_validator_indices = get_active_validator_indices(beacon_state, source_epoch) + seed = get_seed(beacon_state, source_epoch, DOMAIN_LIGHT_CLIENT) + return [ + compute_shuffled_index(i, active_validator_indices, seed) + for i in range(min(active_validator_indices, LIGHT_CLIENT_COMMITTEE_SIZE)) + ] +``` + +### Block processing + +```python +def process_block(state: BeaconState, block: BeaconBlock) -> None: + phase0.process_block(state, block) + process_light_client_signature(state, block.body) +``` + +#### Light client processing + +```python +def process_light_client_signature(state: BeaconState, block_body: BeaconBlockBody) -> None: + committee = get_light_client_committee(state, get_current_epoch(state)) + previous_slot = max(state.slot, 1) - 1 + previous_block_root = get_block_root_at_slot(state, previous_slot) + + # Light clients sign over the previous block root + signing_root = compute_signing_root( + previous_block_root, + get_domain(state, DOMAIN_LIGHT_CLIENT, compute_epoch_at_slot(previous_slot)) + ) + + participants = [ + committee[i] for i in range(len(committee)) if block_body.light_client_bits[i] + ] + + signer_pubkeys = [ + state.validators[participant].pubkey for participant in participants + ] + + assert bls.FastAggregateVerify(signer_pubkeys, signing_root, block_body.light_client_signature) + + # Process rewards + total_reward = Gwei(0) + active_validator_count = len(get_active_validator_indices(beacon_state, get_current_epoch(state))) + for participant in participants: + reward = get_base_reward(state, participant) * active_validator_count // len(committee) + increase_balance(state, participant, reward) + total_reward += reward + + increase_balance(state, get_beacon_proposer_index(state), Gwei(total_reward // PROPOSER_REWARD_QUOTIENT)) +``` + +### Epoch processing + +This epoch transition overrides the phase0 epoch transition: + +```python +def process_epoch(state: BeaconState) -> None: + phase0.process_epoch(state) + process_light_client_committee_updates(state) +``` + +#### Light client committee updates + +```python +def process_light_client_committee_updates(state: BeaconState) -> None: + """ + Update light client committees. + """ + next_epoch = compute_epoch_at_slot(Slot(state.slot + 1)) + if next_epoch % LIGHT_CLIENT_COMMITTEE_PERIOD == 0: + state.current_light_committee = state.next_light_committee + new_committee = get_light_client_committee(state, next_epoch + LIGHT_CLIENT_COMMITTEE_PERIOD) + state.next_light_committee = committee_to_compact_committee(state, new_committee) +``` + + From 9e3690ad17adc2a087d39907c7a9830dcbfc020c Mon Sep 17 00:00:00 2001 From: vbuterin Date: Fri, 13 Nov 2020 10:19:37 +0800 Subject: [PATCH 02/32] Update specs/lightclient/beacon-chain.md Co-authored-by: Alex Stokes --- specs/lightclient/beacon-chain.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/specs/lightclient/beacon-chain.md b/specs/lightclient/beacon-chain.md index 0eb964266..77f0b8e0b 100644 --- a/specs/lightclient/beacon-chain.md +++ b/specs/lightclient/beacon-chain.md @@ -154,7 +154,7 @@ def process_light_client_signature(state: BeaconState, block_body: BeaconBlockBo previous_slot = max(state.slot, 1) - 1 previous_block_root = get_block_root_at_slot(state, previous_slot) - # Light clients sign over the previous block root + # Light client committees sign over the previous block root signing_root = compute_signing_root( previous_block_root, get_domain(state, DOMAIN_LIGHT_CLIENT, compute_epoch_at_slot(previous_slot)) @@ -205,4 +205,3 @@ def process_light_client_committee_updates(state: BeaconState) -> None: state.next_light_committee = committee_to_compact_committee(state, new_committee) ``` - From 620b812c2e5853bb3b737e736d8266eb522e2130 Mon Sep 17 00:00:00 2001 From: vbuterin Date: Fri, 13 Nov 2020 10:21:30 +0800 Subject: [PATCH 03/32] Reduce reward by SLOTS_PER_EPOCH --- specs/lightclient/beacon-chain.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/specs/lightclient/beacon-chain.md b/specs/lightclient/beacon-chain.md index 77f0b8e0b..fcf063c04 100644 --- a/specs/lightclient/beacon-chain.md +++ b/specs/lightclient/beacon-chain.md @@ -174,7 +174,7 @@ def process_light_client_signature(state: BeaconState, block_body: BeaconBlockBo total_reward = Gwei(0) active_validator_count = len(get_active_validator_indices(beacon_state, get_current_epoch(state))) for participant in participants: - reward = get_base_reward(state, participant) * active_validator_count // len(committee) + reward = get_base_reward(state, participant) * active_validator_count // len(committee) // SLOTS_PER_EPOCH increase_balance(state, participant, reward) total_reward += reward From 555e131e2c3826a8c90ed3fb4f38a0ff94bea9be Mon Sep 17 00:00:00 2001 From: vbuterin Date: Sat, 14 Nov 2020 13:56:00 +0800 Subject: [PATCH 04/32] Small changes to make Justin happy --- specs/lightclient/beacon-chain.md | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/specs/lightclient/beacon-chain.md b/specs/lightclient/beacon-chain.md index fcf063c04..5ba1b1f4b 100644 --- a/specs/lightclient/beacon-chain.md +++ b/specs/lightclient/beacon-chain.md @@ -33,7 +33,7 @@ This is a standalone patch to the ethereum beacon chain that adds light client s | Name | Value | | - | - | -| `LIGHT_CLIENT_COMMITTEE_SIZE` | `uint64(2**7)` (= 128) | +| `LIGHT_CLIENT_COMMITTEE_SIZE` | `uint64(2**8)` (= 256) | | `LIGHT_CLIENT_COMMITTEE_PERIOD` | `Epoch(2**8)` (= 256) | epochs | ~27 hours | | `BASE_REWARDS_PER_EPOCH` | 5 | @@ -70,6 +70,7 @@ class BeaconState(phase0.BeaconState): ```python class CompactCommittee(Container): pubkeys: List[BLSPubkey, MAX_VALIDATORS_PER_COMMITTEE] + sum_of_pubkeys: BLSPubkey compact_validators: List[uint64, MAX_VALIDATORS_PER_COMMITTEE] ``` @@ -117,7 +118,11 @@ def committee_to_compact_committee(state: BeaconState, committee: Sequence[Valid for i, v in zip(committee, validators) ] pubkeys = [v.pubkey for v in validators] - return CompactCommittee(pubkeys=pubkeys, compact_validators=compact_validators) + return CompactCommittee( + pubkeys=pubkeys, + sum_of_pubkeys=bls.AggregatePubkeys(pubkeys), + compact_validators=compact_validators + ) ``` ### Beacon state accessors From e7d52d9056ae06b3ee6c34882ea244aa4634535a Mon Sep 17 00:00:00 2001 From: Justin Date: Sun, 15 Nov 2020 10:56:24 +0000 Subject: [PATCH 05/32] Significant polishing and a few substantive fixes See discussion for further details. --- specs/lightclient/beacon-chain.md | 246 ++++++++++++++---------------- 1 file changed, 118 insertions(+), 128 deletions(-) diff --git a/specs/lightclient/beacon-chain.md b/specs/lightclient/beacon-chain.md index 5ba1b1f4b..4f6e07dd5 100644 --- a/specs/lightclient/beacon-chain.md +++ b/specs/lightclient/beacon-chain.md @@ -1,31 +1,47 @@ -# Ethereum 2.0 Light Client Support: Beacon Chain Changes +# Ethereum 2.0 Light Client Support ## Table of contents - [Introduction](#introduction) +- [Custom types](#custom-types) +- [Constants](#constants) - [Configuration](#configuration) - - [Domain types](#domain-types) - [Misc](#misc) -- [Updated containers](#updated-containers) - - [Extended `BeaconBlockBody`](#extended-beaconblockbody) - - [Extended `BeaconState`](#extended-beaconstate) -- [New containers](#new-containers) - - [`CompactCommittee`](#compactcommittee) + - [Time parameters](#time-parameters) + - [Domain types](#domain-types) +- [Containers](#containers) + - [Extended containers](#extended-containers) + - [`BeaconBlockBody`](#beaconblockbody) + - [`BeaconState`](#beaconstate) + - [New containers](#new-containers) + - [`SyncCommittee`](#synccommittee) - [Helper functions](#helper-functions) - [Misc](#misc-1) - - [`pack_compact_validator`](#pack_compact_validator) - - [`unpack_compact_validator`](#unpack_compact_validator) - - [`committee_to_compact_committee`](#committee_to_compact_committee) + - [`compactify_validator`](#compactify_validator) + - [`decompactify_validator`](#decompactify_validator) - [Beacon state accessors](#beacon-state-accessors) - - [`get_light_client_committee`](#get_light_client_committee) + - [`get_sync_committee_indices`](#get_sync_committee_indices) + - [`get_sync_committee`](#get_sync_committee) - [Block processing](#block-processing) - - [Light client processing](#light-client-processing) + - [Sync committee processing](#sync-committee-processing) - [Epoch processing](#epoch-transition) - - [Light client committee updates](#light-client-committee-updates) - + - [Final updates](#updates-updates) + ## Introduction -This is a standalone patch to the ethereum beacon chain that adds light client support. +This is a standalone beacon chain patch adding light client support via sync committees. + +## Custom types + +| Name | SSZ equivalent | Description | +| - | - | - | +| `CompactValidator` | `uint64` | a compact validator | + +## Constants + +| Name | Value | +| - | - | +| `BASE_REWARDS_PER_EPOCH` | `uint64(5)` | ## Configuration @@ -33,114 +49,106 @@ This is a standalone patch to the ethereum beacon chain that adds light client s | Name | Value | | - | - | -| `LIGHT_CLIENT_COMMITTEE_SIZE` | `uint64(2**8)` (= 256) | -| `LIGHT_CLIENT_COMMITTEE_PERIOD` | `Epoch(2**8)` (= 256) | epochs | ~27 hours | -| `BASE_REWARDS_PER_EPOCH` | 5 | +| `MAX_SYNC_COMMITTEE_SIZE` | `uint64(2**8)` (= 256) | + +### Time parameters + +| Name | Value | Unit | Duration | +| - | - | :-: | :-: | +| `EPOCHS_PER_SYNC_COMMITTEE_PERIOD` | `Epoch(2**8)` (= 256) | epochs | ~27 hours | ### Domain types | Name | Value | | - | - | -| `DOMAIN_LIGHT_CLIENT` | `DomainType('0x82000000')` | +| `DOMAIN_SYNC_COMMITTEE` | `DomainType('0x07000000')` | -## Updated containers +## Containers -### Extended `BeaconBlockBody` +### Extended containers + +#### `BeaconBlockBody` ```python class BeaconBlockBody(phase0.BeaconBlockBody): - # Bitfield of participants in this light client signature - light_client_bits: Bitvector[LIGHT_CLIENT_COMMITTEE_SIZE] - light_client_signature: BLSSignature + sync_committee_bits: Bitlist[MAX_SYNC_COMMITTEE_SIZE] + sync_committee_signature: BLSSignature ``` -### Extended `BeaconState` +#### `BeaconState` ```python class BeaconState(phase0.BeaconState): - # Compact representations of the light client committee - current_light_committee: CompactCommittee - next_light_committee: CompactCommittee + current_sync_committee: SyncCommittee + next_sync_committee: SyncCommittee ``` -## New containers +### New containers -### `CompactCommittee` +#### `SyncCommittee` ```python -class CompactCommittee(Container): - pubkeys: List[BLSPubkey, MAX_VALIDATORS_PER_COMMITTEE] - sum_of_pubkeys: BLSPubkey - compact_validators: List[uint64, MAX_VALIDATORS_PER_COMMITTEE] +class SyncCommittee(Container): + pubkeys: List[BLSPubkey, MAX_SYNC_COMMITTEE_SIZE] + pubkeys_aggregate: BLSPubkey + compact_validators: List[CompactValidator, MAX_SYNC_COMMITTEE_SIZE] ``` ## Helper functions ### Misc - -#### `pack_compact_validator` +#### `compactify_validator` ```python -def pack_compact_validator(index: ValidatorIndex, slashed: bool, balance_in_increments: uint64) -> uint64: +def compactify_validator(index: ValidatorIndex, slashed: bool, effective_balance: Gwei) -> CompactValidator: """ - Create a compact validator object representing index, slashed status, and compressed balance. - Takes as input balance-in-increments (// EFFECTIVE_BALANCE_INCREMENT) to preserve symmetry with - the unpacking function. + Return the compact validator for a given validator index, slashed status and effective balance. """ - return (index << 16) + (slashed << 15) + balance_in_increments + return CompactValidator((index << 16) + (slashed << 15) + uint64(effective_balance // EFFECTIVE_BALANCE_INCREMENT)) ``` -#### `unpack_compact_validator` +#### `decompactify_validator` ```python -def unpack_compact_validator(compact_validator: uint64) -> Tuple[ValidatorIndex, bool, uint64]: +def decompactify_validator(compact_validator: CompactValidator) -> Tuple[ValidatorIndex, bool, Gwei]: """ - Return validator index, slashed, balance // EFFECTIVE_BALANCE_INCREMENT + Return the validator index, slashed status and effective balance for a given compact validator. """ - return ( - ValidatorIndex(compact_validator >> 16), - bool((compact_validator >> 15) % 2), - compact_validator & (2**15 - 1), - ) -``` - -#### `committee_to_compact_committee` - -```python -def committee_to_compact_committee(state: BeaconState, committee: Sequence[ValidatorIndex]) -> CompactCommittee: - """ - Given a state and a list of validator indices, outputs the ``CompactCommittee`` representing them. - """ - validators = [state.validators[i] for i in committee] - compact_validators = [ - pack_compact_validator(i, v.slashed, v.effective_balance // EFFECTIVE_BALANCE_INCREMENT) - for i, v in zip(committee, validators) - ] - pubkeys = [v.pubkey for v in validators] - return CompactCommittee( - pubkeys=pubkeys, - sum_of_pubkeys=bls.AggregatePubkeys(pubkeys), - compact_validators=compact_validators - ) + index = ValidatorIndex(compact_validator >> 16) # from bits 16-63 + slashed = bool((compact_validator >> 15) % 2) # from bit 15 + effective_balance = Gwei(compact_validator & (2**15 - 1)) * EFFECTIVE_BALANCE_INCREMENT # from bits 0-14 + return (index, slashed, effective_balance) ``` ### Beacon state accessors -#### `get_light_client_committee` +#### `get_sync_committee_indices` ```python -def get_light_client_committee(beacon_state: BeaconState, epoch: Epoch) -> Sequence[ValidatorIndex]: +def get_sync_committee_indices(state: BeaconState, epoch: Epoch) -> Sequence[ValidatorIndex]: """ - Return the light client committee of no more than ``LIGHT_CLIENT_COMMITTEE_SIZE`` validators. + Return the sync committee indices for a given state and epoch. """ - source_epoch = (max(epoch // LIGHT_CLIENT_COMMITTEE_PERIOD, 1) - 1) * LIGHT_CLIENT_COMMITTEE_PERIOD - active_validator_indices = get_active_validator_indices(beacon_state, source_epoch) - seed = get_seed(beacon_state, source_epoch, DOMAIN_LIGHT_CLIENT) - return [ - compute_shuffled_index(i, active_validator_indices, seed) - for i in range(min(active_validator_indices, LIGHT_CLIENT_COMMITTEE_SIZE)) - ] + start_epoch = Epoch((max(epoch // EPOCHS_PER_SYNC_COMMITTEE_PERIOD, 1) - 1) * EPOCHS_PER_SYNC_COMMITTEE_PERIOD) + active_validator_count = uint64(len(get_active_validator_indices(state, start_epoch))) + sync_committee_size = min(active_validator_count, MAX_SYNC_COMMITTEE_SIZE) + seed = get_seed(state, base_epoch, DOMAIN_SYNC_COMMITTEE) + return [compute_shuffled_index(uint64(i), active_validator_count, seed) for i in range(sync_committee_size)] +``` + +### `get_sync_committee` + +```python +def get_sync_committee(state: BeaconState, epoch: Epoch) -> SyncCommittee: + """ + Return the sync committee for a given state and epoch. + """ + indices = get_sync_committee_indices(state, epoch) + validators = [state.validators[index] for index in indices] + pubkeys = [validator.pubkey for validator in validators] + compact_validators = [compactify_validator(i, v.slashed, v.effective_balance) for i, v in zip(indices, validators)] + return SyncCommittee(pubkeys, bls.AggregatePubkeys(pubkeys), compact_validators) ``` ### Block processing @@ -148,65 +156,47 @@ def get_light_client_committee(beacon_state: BeaconState, epoch: Epoch) -> Seque ```python def process_block(state: BeaconState, block: BeaconBlock) -> None: phase0.process_block(state, block) - process_light_client_signature(state, block.body) + process_sync_committee(state, block.body) ``` -#### Light client processing +#### Sync committee processing ```python -def process_light_client_signature(state: BeaconState, block_body: BeaconBlockBody) -> None: - committee = get_light_client_committee(state, get_current_epoch(state)) - previous_slot = max(state.slot, 1) - 1 - previous_block_root = get_block_root_at_slot(state, previous_slot) +def process_sync_committee(state: BeaconState, body: BeaconBlockBody) -> None: + # Verify sync committee bitfield length + committee_indices = get_sync_committee_indices(state, get_current_epoch(state)) + assert len(body.sync_committee_bits) == len(committee_indices) - # Light client committees sign over the previous block root - signing_root = compute_signing_root( - previous_block_root, - get_domain(state, DOMAIN_LIGHT_CLIENT, compute_epoch_at_slot(previous_slot)) - ) - - participants = [ - committee[i] for i in range(len(committee)) if block_body.light_client_bits[i] - ] - - signer_pubkeys = [ - state.validators[participant].pubkey for participant in participants - ] - - assert bls.FastAggregateVerify(signer_pubkeys, signing_root, block_body.light_client_signature) - - # Process rewards - total_reward = Gwei(0) - active_validator_count = len(get_active_validator_indices(beacon_state, get_current_epoch(state))) - for participant in participants: - reward = get_base_reward(state, participant) * active_validator_count // len(committee) // SLOTS_PER_EPOCH - increase_balance(state, participant, reward) - total_reward += reward + # Verify sync committee aggregate signature signing over the previous slot block root + previous_slot = max(state.slot, Slot(1)) - Slot(1) + participant_indices = [committee_indices[i] for i in range(len(committee_indices)) if body.sync_committee_bits[i]] + participant_pubkeys = [state.validators[participant_index].pubkey for participant_index in participant_indices] + domain = get_domain(state, DOMAIN_SYNC_COMMITTEE, compute_epoch_at_slot(previous_slot)) + signing_root = compute_signing_root(get_block_root_at_slot(state, previous_slot), domain) + assert bls.FastAggregateVerify(participant_pubkeys, signing_root, body.sync_committee_signature) - increase_balance(state, get_beacon_proposer_index(state), Gwei(total_reward // PROPOSER_REWARD_QUOTIENT)) + # Reward sync committee participants + participant_rewards = Gwei(0) + active_validator_count = uint64(len(get_active_validator_indices(state, get_current_epoch(state)))) + for participant_index in participant_indices: + base_reward = get_base_reward(state, participant_index) + reward = Gwei(base_reward * active_validator_count // len(committee_indices) // SLOTS_PER_EPOCH) + increase_balance(state, participant_index, reward) + participant_rewards += reward + + # Reward beacon proposer + increase_balance(state, get_beacon_proposer_index(state), Gwei(participant_rewards // PROPOSER_REWARD_QUOTIENT)) ``` ### Epoch processing -This epoch transition overrides the phase0 epoch transition: +#### Final updates ```python -def process_epoch(state: BeaconState) -> None: - phase0.process_epoch(state) - process_light_client_committee_updates(state) +def process_final_updates(state: BeaconState) -> None: + phase0.process_final_updates(state) + next_epoch = get_current_epoch(state) + Epoch(1) + if next_epoch % EPOCHS_PER_SYNC_COMMITTEE_PERIOD == 0: + state.current_sync_committee = state.next_sync_committee + state.next_sync_committee = get_sync_committee(state, next_epoch + EPOCHS_PER_SYNC_COMMITTEE_PERIOD) ``` - -#### Light client committee updates - -```python -def process_light_client_committee_updates(state: BeaconState) -> None: - """ - Update light client committees. - """ - next_epoch = compute_epoch_at_slot(Slot(state.slot + 1)) - if next_epoch % LIGHT_CLIENT_COMMITTEE_PERIOD == 0: - state.current_light_committee = state.next_light_committee - new_committee = get_light_client_committee(state, next_epoch + LIGHT_CLIENT_COMMITTEE_PERIOD) - state.next_light_committee = committee_to_compact_committee(state, new_committee) -``` - From 114e388d1230aed92987e8f12499bc14d18a0313 Mon Sep 17 00:00:00 2001 From: Justin Date: Sun, 15 Nov 2020 17:23:44 +0000 Subject: [PATCH 06/32] =?UTF-8?q?Fix=20bugs=E2=80=94thanks=20@hwwhww?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- specs/lightclient/beacon-chain.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/specs/lightclient/beacon-chain.md b/specs/lightclient/beacon-chain.md index 4f6e07dd5..f64dc1271 100644 --- a/specs/lightclient/beacon-chain.md +++ b/specs/lightclient/beacon-chain.md @@ -130,14 +130,14 @@ def get_sync_committee_indices(state: BeaconState, epoch: Epoch) -> Sequence[Val """ Return the sync committee indices for a given state and epoch. """ - start_epoch = Epoch((max(epoch // EPOCHS_PER_SYNC_COMMITTEE_PERIOD, 1) - 1) * EPOCHS_PER_SYNC_COMMITTEE_PERIOD) - active_validator_count = uint64(len(get_active_validator_indices(state, start_epoch))) + base_epoch = Epoch((max(epoch // EPOCHS_PER_SYNC_COMMITTEE_PERIOD, 1) - 1) * EPOCHS_PER_SYNC_COMMITTEE_PERIOD) + active_validator_count = uint64(len(get_active_validator_indices(state, base_epoch))) sync_committee_size = min(active_validator_count, MAX_SYNC_COMMITTEE_SIZE) seed = get_seed(state, base_epoch, DOMAIN_SYNC_COMMITTEE) return [compute_shuffled_index(uint64(i), active_validator_count, seed) for i in range(sync_committee_size)] ``` -### `get_sync_committee` +#### `get_sync_committee` ```python def get_sync_committee(state: BeaconState, epoch: Epoch) -> SyncCommittee: @@ -148,7 +148,7 @@ def get_sync_committee(state: BeaconState, epoch: Epoch) -> SyncCommittee: validators = [state.validators[index] for index in indices] pubkeys = [validator.pubkey for validator in validators] compact_validators = [compactify_validator(i, v.slashed, v.effective_balance) for i, v in zip(indices, validators)] - return SyncCommittee(pubkeys, bls.AggregatePubkeys(pubkeys), compact_validators) + return SyncCommittee(pubkeys, bls.AggregatePKs(pubkeys), compact_validators) ``` ### Block processing From 1f210fd1f84c00b7005915914f0ae8e94b18cd12 Mon Sep 17 00:00:00 2001 From: vbuterin Date: Mon, 16 Nov 2020 15:02:11 +0800 Subject: [PATCH 07/32] Added light client syncing protocol --- specs/lightclient/sync-protocol.md | 143 +++++++++++++++++++++++++++++ 1 file changed, 143 insertions(+) create mode 100644 specs/lightclient/sync-protocol.md diff --git a/specs/lightclient/sync-protocol.md b/specs/lightclient/sync-protocol.md new file mode 100644 index 000000000..ff9231365 --- /dev/null +++ b/specs/lightclient/sync-protocol.md @@ -0,0 +1,143 @@ +# Minimal Light Client Design + +**Notice**: This document is a work-in-progress for researchers and implementers. + +## Table of contents + + + + + + +- [Introduction](#introduction) +- [Custom types](#custom-types) +- [Constants](#constants) +- [Containers](#containers) + - [`LightClientUpdate`](#lightclientupdate) +- [Helpers](#helpers) + - [`LightClientMemory`](#lightclientmemory) +- [Light client state updates](#light-client-state-updates) + + + + +## Introduction + +Ethereum 2.0 is designed to be light client friendly. This allows low-resource clients such as mobile phones to access Ethereum 2.0 with reasonable safety and liveness. It also facilitates the development of "bridges" to external blockchains. This document suggests a minimal light client design for the beacon chain that uses the concept of "sync committees" introduced in [./beacon-chain.md](the the light-client-friendliness beacon chain extension). + +## Custom types + +We define the following Python custom types for type hinting and readability: + +| Name | SSZ equivalent | Description | +| - | - | - | + +## Constants + +| Name | Value | +| - | - | +| `SYNC_COMMITTEES_GENERALIZED_INDEX` | `GeneralizedIndexConcat(GeneralizedIndex(BeaconBlock, 'state_root'), GeneralizedIndex(BeaconState, 'current_sync_committee'))` | +| `FORK_GENERALIZED_INDEX` | `GeneralizedIndexConcat(GeneralizedIndex(BeaconBlock, 'state_root'), GeneralizedIndex(BeaconState, 'fork'))` | +| `BEACON_CHAIN_ROOT_IN_SHARD_BLOCK_HEADER_DEPTH` | `4` | +| `BEACON_CHAIN_ROOT_IN_SHARD_BLOCK_HEADER_INDEX` | **TBD** | +| `PERIOD_COMMITTEE_ROOT_IN_BEACON_STATE_DEPTH` | `5` | +| `PERIOD_COMMITTEE_ROOT_IN_BEACON_STATE_INDEX` | **TBD** | + +## Containers + +### `LightClientUpdate` + +```python +class LightClientUpdate(Container): + # Updated beacon header (and authenticating branch) + header: BeaconBlockHeader + # Sync committee signature to that header + aggregation_bits: Bitlist[MAX_SYNC_COMMITTEE_SIZE] + signature: BLSSignature + header_branch: Vector[Bytes32, BEACON_CHAIN_ROOT_IN_SHARD_BLOCK_HEADER_DEPTH] + # Updates fork version + new_fork: Fork + fork_branch: Vector[Bytes32, log_2(FORK_GENERALIZED_INDEX)] + # Updated period committee (and authenticating branch) + new_current_sync_committee: SyncCommittee + new_next_sync_committee: SyncCommittee + sync_committee_branch: Vector[Bytes32, log_2(SYNC_COMMITTEES_GENERALIZED_INDEX)] +``` + +## Helpers + +### `LightClientMemory` + +```python +class LightClientMemory(Container): + # Beacon header which is not expected to revert + header: BeaconBlockHeader + # Fork version data + fork_version: Version + # period committees corresponding to the beacon header + current_sync_committee: SyncCommittee + next_sync_committee: SyncCommittee +``` + +## Light client state updates + +The state of a light client is stored in a `memory` object of type `LightClientMemory`. To advance its state a light client requests an `update` object of type `LightClientUpdate` from the network by sending a request containing `(memory.shard, memory.header.slot, slot_range_end)`. It calls `validate_update(memory, update)` on each update that it receives in response. If `sum(update.aggregate_bits) * 3 > len(update.aggregate_bits) * 2` for any valid update, it accepts that update immediately; otherwise, it waits around for some time and then finally calls `update_memory(memory, update)` on the valid update with the highest `sum(update.aggregate_bits)`. + +#### `validate_update` + +```python +def validate_update(memory: LightClientMemory, update: LightClientUpdate) -> bool: + # Verify the update does not skip a period + current_period = compute_epoch_at_slot(memory.header.slot) // EPOCHS_PER_SYNC_COMMITTEE_PERIOD + new_epoch = compute_epoch_of_shard_slot(update.header.slot) + new_period = new_epoch // EPOCHS_PER_SYNC_COMMITTEE_PERIOD + assert new_period in (current_period, current_period + 1) + + # Verify that it actually updates to a newer slot + assert update.header.slot > memory.header.slot + + # Convenience as independent variable for convenience + committee = memory.current_sync_committee if new_period == current_period else memory.next_sync_committee + assert len(update.aggregation_bits) == len(committee) + + # Verify signature + active_pubkeys = [p for (bit, p) in zip(update.aggregation_bits, committee.pubkeys) if bit] + domain = compute_domain(DOMAIN_SYNC_COMMITTEE, memory.version) + signing_root = compute_signing_root(update.header, domain) + assert bls.FastAggregateVerify(pubkeys, signing_root, update.signature) + + # Verify Merkle branches of new info + assert is_valid_merkle_branch( + leaf=hash_tree_root(update.new_fork), + branch=update.fork_branch, + depth=log2(FORK_GENERALIZED_INDEX), + index=FORK_GENERALIZED_INDEX % 2**log2(FORK_GENERALIZED_INDEX), + root=hash_tree_root(update.header), + ) + assert is_valid_merkle_branch( + leaf=hash_tree_root(update.current_sync_committee), + branch=update.sync_committee_branch, + depth=log2(SYNC_COMMITTEES_GENERALIZED_INDEX), + index=SYNC_COMMITTEES_GENERALIZED_INDEX % 2**log2(SYNC_COMMITTEES_GENERALIZED_INDEX), + root=hash_tree_root(update.header), + ) + # Verify consistency of committees + if new_period == current_period: + assert update.new_current_sync_committee == memory.current_sync_committee + assert update.new_next_sync_committee == memory.next_sync_committee + else: + assert update.new_current_sync_committee == memory.next_sync_committee + + return True +``` + +#### `update_memory` + +``` +def update_memory(memory: LightClientMemory, update: LightClientUpdate) -> None: + memory.header = update.header + epoch = compute_epoch_at_slot(update.header.slot) + memory.fork_version = update.new_fork.previous_version if epoch < update.new_fork.epoch else update.new_fork.current_version + memory.current_sync_committee = update.new_current_sync_committee + memory.next_sync_committee == update.new_next_sync_committee +``` From 97dc916c9e0fbd48ac048d5212c5dca53b4b1e8c Mon Sep 17 00:00:00 2001 From: vbuterin Date: Mon, 16 Nov 2020 15:07:40 +0800 Subject: [PATCH 08/32] Python syntax highlighted updates --- specs/lightclient/sync-protocol.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/specs/lightclient/sync-protocol.md b/specs/lightclient/sync-protocol.md index ff9231365..aaff7c580 100644 --- a/specs/lightclient/sync-protocol.md +++ b/specs/lightclient/sync-protocol.md @@ -133,7 +133,7 @@ def validate_update(memory: LightClientMemory, update: LightClientUpdate) -> boo #### `update_memory` -``` +```python def update_memory(memory: LightClientMemory, update: LightClientUpdate) -> None: memory.header = update.header epoch = compute_epoch_at_slot(update.header.slot) From ca88dd6922ddba380754f20efd60ff6417f1631c Mon Sep 17 00:00:00 2001 From: vbuterin Date: Mon, 16 Nov 2020 15:08:43 +0800 Subject: [PATCH 09/32] Removed extraneous data --- specs/lightclient/sync-protocol.md | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/specs/lightclient/sync-protocol.md b/specs/lightclient/sync-protocol.md index aaff7c580..bbe53c87f 100644 --- a/specs/lightclient/sync-protocol.md +++ b/specs/lightclient/sync-protocol.md @@ -25,23 +25,12 @@ Ethereum 2.0 is designed to be light client friendly. This allows low-resource clients such as mobile phones to access Ethereum 2.0 with reasonable safety and liveness. It also facilitates the development of "bridges" to external blockchains. This document suggests a minimal light client design for the beacon chain that uses the concept of "sync committees" introduced in [./beacon-chain.md](the the light-client-friendliness beacon chain extension). -## Custom types - -We define the following Python custom types for type hinting and readability: - -| Name | SSZ equivalent | Description | -| - | - | - | - ## Constants | Name | Value | | - | - | | `SYNC_COMMITTEES_GENERALIZED_INDEX` | `GeneralizedIndexConcat(GeneralizedIndex(BeaconBlock, 'state_root'), GeneralizedIndex(BeaconState, 'current_sync_committee'))` | | `FORK_GENERALIZED_INDEX` | `GeneralizedIndexConcat(GeneralizedIndex(BeaconBlock, 'state_root'), GeneralizedIndex(BeaconState, 'fork'))` | -| `BEACON_CHAIN_ROOT_IN_SHARD_BLOCK_HEADER_DEPTH` | `4` | -| `BEACON_CHAIN_ROOT_IN_SHARD_BLOCK_HEADER_INDEX` | **TBD** | -| `PERIOD_COMMITTEE_ROOT_IN_BEACON_STATE_DEPTH` | `5` | -| `PERIOD_COMMITTEE_ROOT_IN_BEACON_STATE_INDEX` | **TBD** | ## Containers From cbb3856ab9756f829d6af4107b884327df9e606e Mon Sep 17 00:00:00 2001 From: Justin Date: Mon, 16 Nov 2020 09:09:04 +0000 Subject: [PATCH 10/32] Fix ToC (cherry-picked from @hwwhww's PR) --- specs/lightclient/beacon-chain.md | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/specs/lightclient/beacon-chain.md b/specs/lightclient/beacon-chain.md index f64dc1271..0c7ab2a3b 100644 --- a/specs/lightclient/beacon-chain.md +++ b/specs/lightclient/beacon-chain.md @@ -2,6 +2,10 @@ ## Table of contents + + + + - [Introduction](#introduction) - [Custom types](#custom-types) - [Constants](#constants) @@ -24,8 +28,11 @@ - [`get_sync_committee`](#get_sync_committee) - [Block processing](#block-processing) - [Sync committee processing](#sync-committee-processing) - - [Epoch processing](#epoch-transition) - - [Final updates](#updates-updates) + - [Epoch processing](#epoch-processing) + - [Final updates](#final-updates) + + + ## Introduction From 4df3547edfaf9703738dd1f517ae0583cc73534a Mon Sep 17 00:00:00 2001 From: Hsiao-Wei Wang Date: Tue, 17 Nov 2020 10:41:26 +0800 Subject: [PATCH 11/32] Make `lightclient` patch pass the linter (#2133) * Make `lightclient` an executable patch fork * fix conflicts * Fix ToC * Lightclient -> Light client * Try protolambda/remerkleable#8 * Fix sync-protocol.md ToC * Build lightclient/sync-protocol * Fix typo Co-authored-by: vbuterin Co-authored-by: vbuterin --- .gitignore | 1 + Makefile | 5 +-- setup.py | 49 ++++++++++++++++++++++++++++-- specs/lightclient/beacon-chain.md | 3 ++ specs/lightclient/sync-protocol.md | 3 +- 5 files changed, 56 insertions(+), 5 deletions(-) diff --git a/.gitignore b/.gitignore index bcd96f885..ed497112c 100644 --- a/.gitignore +++ b/.gitignore @@ -17,6 +17,7 @@ eth2.0-spec-tests/ # Dynamically built from Markdown spec tests/core/pyspec/eth2spec/phase0/ tests/core/pyspec/eth2spec/phase1/ +tests/core/pyspec/eth2spec/lightclient/ # coverage reports .htmlcov diff --git a/Makefile b/Makefile index 8fa104444..811abbad8 100644 --- a/Makefile +++ b/Makefile @@ -20,7 +20,7 @@ GENERATOR_VENVS = $(patsubst $(GENERATOR_DIR)/%, $(GENERATOR_DIR)/%venv, $(GENER # To check generator matching: #$(info $$GENERATOR_TARGETS is [${GENERATOR_TARGETS}]) -MARKDOWN_FILES = $(wildcard $(SPEC_DIR)/phase0/*.md) $(wildcard $(SPEC_DIR)/phase1/*.md) $(wildcard $(SSZ_DIR)/*.md) $(wildcard $(SPEC_DIR)/networking/*.md) $(wildcard $(SPEC_DIR)/validator/*.md) +MARKDOWN_FILES = $(wildcard $(SPEC_DIR)/phase0/*.md) $(wildcard $(SPEC_DIR)/phase1/*.md) $(wildcard $(SPEC_DIR)/lightclient/*.md) $(wildcard $(SSZ_DIR)/*.md) $(wildcard $(SPEC_DIR)/networking/*.md) $(wildcard $(SPEC_DIR)/validator/*.md) COV_HTML_OUT=.htmlcov COV_INDEX_FILE=$(PY_SPEC_DIR)/$(COV_HTML_OUT)/index.html @@ -49,6 +49,7 @@ partial_clean: rm -rf $(DEPOSIT_CONTRACT_TESTER_DIR)/.pytest_cache rm -rf $(PY_SPEC_DIR)/phase0 rm -rf $(PY_SPEC_DIR)/phase1 + rm -rf $(PY_SPEC_DIR)/lightclient rm -rf $(PY_SPEC_DIR)/$(COV_HTML_OUT) rm -rf $(PY_SPEC_DIR)/.coverage rm -rf $(PY_SPEC_DIR)/test-reports @@ -112,7 +113,7 @@ codespell: 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.phase1 + && mypy --config-file $(LINTER_CONFIG_FILE) -p eth2spec.phase0 -p eth2spec.phase1 -p eth2spec.lightclient lint_generators: pyspec . venv/bin/activate; cd $(TEST_GENERATORS_DIR); \ diff --git a/setup.py b/setup.py index 9c2de0751..6a2bf5707 100644 --- a/setup.py +++ b/setup.py @@ -52,8 +52,9 @@ def get_spec(file_name: str) -> SpecObject: else: # Handle function definitions & ssz_objects if pulling_from is not None: - if len(line) > 18 and line[:6] == 'class ' and line[-12:] == '(Container):': - name = line[6:-12] + if len(line) > 18 and line[:6] == 'class ' and (line[-12:] == '(Container):' or '(phase' in line): + end = -12 if line[-12:] == '(Container):' else line.find('(') + name = line[6:end] # Check consistency with markdown header assert name == current_name block_type = CodeBlockType.SSZ @@ -156,6 +157,40 @@ SSZObject = TypeVar('SSZObject', bound=View) CONFIG_NAME = 'mainnet' ''' +LIGHTCLIENT_IMPORT = '''from eth2spec.phase0 import spec as phase0 +from eth2spec.config.config_util import apply_constants_config +from typing import ( + Any, Dict, Set, Sequence, NewType, Tuple, TypeVar, Callable, Optional +) + +from dataclasses import ( + dataclass, + field, +) + +from lru import LRU + +from eth2spec.utils.ssz.ssz_impl import hash_tree_root, copy, uint_to_bytes +from eth2spec.utils.ssz.ssz_typing import ( + View, boolean, Container, List, Vector, uint8, uint32, uint64, + Bytes1, Bytes4, Bytes32, Bytes48, Bytes96, Bitlist, +) +from eth2spec.utils import bls + +from eth2spec.utils.hash_function import hash + +# Whenever lightclient is loaded, make sure we have the latest phase0 +from importlib import reload +reload(phase0) + + +SSZVariableName = str +GeneralizedIndex = NewType('GeneralizedIndex', int) +SSZObject = TypeVar('SSZObject', bound=View) + +CONFIG_NAME = 'mainnet' +''' + SUNDRY_CONSTANTS_FUNCTIONS = ''' def ceillog2(x: int) -> uint64: if x < 1: @@ -351,6 +386,7 @@ def combine_spec_objects(spec0: SpecObject, spec1: SpecObject) -> SpecObject: fork_imports = { 'phase0': PHASE0_IMPORTS, 'phase1': PHASE1_IMPORTS, + 'lightclient': LIGHTCLIENT_IMPORT, } @@ -417,6 +453,15 @@ class PySpecCommand(Command): specs/phase1/shard-fork-choice.md specs/phase1/validator.md """ + elif self.spec_fork == "lightclient": + self.md_doc_paths = """ + specs/phase0/beacon-chain.md + specs/phase0/fork-choice.md + specs/phase0/validator.md + specs/phase0/weak-subjectivity.md + specs/lightclient/beacon-chain.md + specs/lightclient/sync-protocol.md + """ else: raise Exception('no markdown files specified, and spec fork "%s" is unknown', self.spec_fork) diff --git a/specs/lightclient/beacon-chain.md b/specs/lightclient/beacon-chain.md index 0c7ab2a3b..2d00d5170 100644 --- a/specs/lightclient/beacon-chain.md +++ b/specs/lightclient/beacon-chain.md @@ -6,6 +6,7 @@ + - [Introduction](#introduction) - [Custom types](#custom-types) - [Constants](#constants) @@ -78,6 +79,7 @@ This is a standalone beacon chain patch adding light client support via sync com ```python class BeaconBlockBody(phase0.BeaconBlockBody): + # Light client sync_committee_bits: Bitlist[MAX_SYNC_COMMITTEE_SIZE] sync_committee_signature: BLSSignature ``` @@ -86,6 +88,7 @@ class BeaconBlockBody(phase0.BeaconBlockBody): ```python class BeaconState(phase0.BeaconState): + # Light client current_sync_committee: SyncCommittee next_sync_committee: SyncCommittee ``` diff --git a/specs/lightclient/sync-protocol.md b/specs/lightclient/sync-protocol.md index bbe53c87f..8a07271b2 100644 --- a/specs/lightclient/sync-protocol.md +++ b/specs/lightclient/sync-protocol.md @@ -10,13 +10,14 @@ - [Introduction](#introduction) -- [Custom types](#custom-types) - [Constants](#constants) - [Containers](#containers) - [`LightClientUpdate`](#lightclientupdate) - [Helpers](#helpers) - [`LightClientMemory`](#lightclientmemory) - [Light client state updates](#light-client-state-updates) + - [`validate_update`](#validate_update) + - [`update_memory`](#update_memory) From f9e9d7cabf8b45be493e1cab0c175a36e4cb088a Mon Sep 17 00:00:00 2001 From: vbuterin Date: Tue, 17 Nov 2020 10:45:02 +0800 Subject: [PATCH 12/32] Update specs/lightclient/sync-protocol.md Co-authored-by: Alex Stokes --- specs/lightclient/sync-protocol.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/specs/lightclient/sync-protocol.md b/specs/lightclient/sync-protocol.md index 8a07271b2..d247187cd 100644 --- a/specs/lightclient/sync-protocol.md +++ b/specs/lightclient/sync-protocol.md @@ -92,7 +92,7 @@ def validate_update(memory: LightClientMemory, update: LightClientUpdate) -> boo # Verify signature active_pubkeys = [p for (bit, p) in zip(update.aggregation_bits, committee.pubkeys) if bit] - domain = compute_domain(DOMAIN_SYNC_COMMITTEE, memory.version) + domain = compute_domain(DOMAIN_SYNC_COMMITTEE, memory.fork_version) signing_root = compute_signing_root(update.header, domain) assert bls.FastAggregateVerify(pubkeys, signing_root, update.signature) From 117d31985f3927d2ae9bf48a6439e8b6aad215d1 Mon Sep 17 00:00:00 2001 From: vbuterin Date: Tue, 17 Nov 2020 10:45:16 +0800 Subject: [PATCH 13/32] Update specs/lightclient/sync-protocol.md Co-authored-by: Alex Stokes --- specs/lightclient/sync-protocol.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/specs/lightclient/sync-protocol.md b/specs/lightclient/sync-protocol.md index d247187cd..e901f32c6 100644 --- a/specs/lightclient/sync-protocol.md +++ b/specs/lightclient/sync-protocol.md @@ -39,7 +39,7 @@ Ethereum 2.0 is designed to be light client friendly. This allows low-resource c ```python class LightClientUpdate(Container): - # Updated beacon header (and authenticating branch) + # Updated beacon header header: BeaconBlockHeader # Sync committee signature to that header aggregation_bits: Bitlist[MAX_SYNC_COMMITTEE_SIZE] From a5e6d77165f472d0fba756839fa79ef63a743df4 Mon Sep 17 00:00:00 2001 From: vbuterin Date: Tue, 17 Nov 2020 13:25:59 +0800 Subject: [PATCH 14/32] Update specs/lightclient/sync-protocol.md Co-authored-by: Alex Stokes --- specs/lightclient/sync-protocol.md | 1 - 1 file changed, 1 deletion(-) diff --git a/specs/lightclient/sync-protocol.md b/specs/lightclient/sync-protocol.md index e901f32c6..9b83d79e0 100644 --- a/specs/lightclient/sync-protocol.md +++ b/specs/lightclient/sync-protocol.md @@ -44,7 +44,6 @@ class LightClientUpdate(Container): # Sync committee signature to that header aggregation_bits: Bitlist[MAX_SYNC_COMMITTEE_SIZE] signature: BLSSignature - header_branch: Vector[Bytes32, BEACON_CHAIN_ROOT_IN_SHARD_BLOCK_HEADER_DEPTH] # Updates fork version new_fork: Fork fork_branch: Vector[Bytes32, log_2(FORK_GENERALIZED_INDEX)] From 1c146b2c031fc26902e2a6be4062f218ead262e5 Mon Sep 17 00:00:00 2001 From: vbuterin Date: Tue, 17 Nov 2020 13:26:08 +0800 Subject: [PATCH 15/32] Update specs/lightclient/sync-protocol.md Co-authored-by: Alex Stokes --- specs/lightclient/sync-protocol.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/specs/lightclient/sync-protocol.md b/specs/lightclient/sync-protocol.md index 9b83d79e0..e03821c9d 100644 --- a/specs/lightclient/sync-protocol.md +++ b/specs/lightclient/sync-protocol.md @@ -47,7 +47,7 @@ class LightClientUpdate(Container): # Updates fork version new_fork: Fork fork_branch: Vector[Bytes32, log_2(FORK_GENERALIZED_INDEX)] - # Updated period committee (and authenticating branch) + # Updated sync committee (and authenticating branch) new_current_sync_committee: SyncCommittee new_next_sync_committee: SyncCommittee sync_committee_branch: Vector[Bytes32, log_2(SYNC_COMMITTEES_GENERALIZED_INDEX)] From 5e5d03d56f73fe5311e9a75a57e0426692479d35 Mon Sep 17 00:00:00 2001 From: vbuterin Date: Tue, 17 Nov 2020 13:26:18 +0800 Subject: [PATCH 16/32] Update specs/lightclient/sync-protocol.md Co-authored-by: Alex Stokes --- specs/lightclient/sync-protocol.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/specs/lightclient/sync-protocol.md b/specs/lightclient/sync-protocol.md index e03821c9d..a24501426 100644 --- a/specs/lightclient/sync-protocol.md +++ b/specs/lightclient/sync-protocol.md @@ -63,7 +63,7 @@ class LightClientMemory(Container): header: BeaconBlockHeader # Fork version data fork_version: Version - # period committees corresponding to the beacon header + # sync committees corresponding to the beacon header current_sync_committee: SyncCommittee next_sync_committee: SyncCommittee ``` From 692a0aaaa5f599cbf0a0377e60346168cdc1fb26 Mon Sep 17 00:00:00 2001 From: vbuterin Date: Tue, 17 Nov 2020 13:26:26 +0800 Subject: [PATCH 17/32] Update specs/lightclient/sync-protocol.md Co-authored-by: Alex Stokes --- specs/lightclient/sync-protocol.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/specs/lightclient/sync-protocol.md b/specs/lightclient/sync-protocol.md index a24501426..935c59481 100644 --- a/specs/lightclient/sync-protocol.md +++ b/specs/lightclient/sync-protocol.md @@ -85,7 +85,7 @@ def validate_update(memory: LightClientMemory, update: LightClientUpdate) -> boo # Verify that it actually updates to a newer slot assert update.header.slot > memory.header.slot - # Convenience as independent variable for convenience + # Independent variable for convenience committee = memory.current_sync_committee if new_period == current_period else memory.next_sync_committee assert len(update.aggregation_bits) == len(committee) From 99219c874f05c8331540be9f2dfeb0168ed69af0 Mon Sep 17 00:00:00 2001 From: Justin Date: Tue, 17 Nov 2020 12:42:09 +0000 Subject: [PATCH 18/32] Revamp minimal light client (lots of polish and some bug fixes) --- specs/lightclient/sync-protocol.md | 197 +++++++++++++++++------------ 1 file changed, 113 insertions(+), 84 deletions(-) diff --git a/specs/lightclient/sync-protocol.md b/specs/lightclient/sync-protocol.md index 935c59481..8fb94bfa5 100644 --- a/specs/lightclient/sync-protocol.md +++ b/specs/lightclient/sync-protocol.md @@ -1,4 +1,4 @@ -# Minimal Light Client Design +# Minimal Light Client **Notice**: This document is a work-in-progress for researchers and implementers. @@ -11,122 +11,151 @@ - [Introduction](#introduction) - [Constants](#constants) +- [Configuration](#configuration) + - [Misc](#misc) + - [Time parameters](#time-parameters) - [Containers](#containers) - - [`LightClientUpdate`](#lightclientupdate) -- [Helpers](#helpers) - - [`LightClientMemory`](#lightclientmemory) + - [`LightClientSnapshot`](#lightclientsnapshot) + - [`LightClientUpdate`](#lightclientupdate) + - [`LightClientStore`](#lightclientstore) - [Light client state updates](#light-client-state-updates) - - [`validate_update`](#validate_update) - - [`update_memory`](#update_memory) + - [`is_valid_light_client_update`](#is_valid_light_client_update) + - [`process_light_client_update`](#process_light_client_update) ## Introduction -Ethereum 2.0 is designed to be light client friendly. This allows low-resource clients such as mobile phones to access Ethereum 2.0 with reasonable safety and liveness. It also facilitates the development of "bridges" to external blockchains. This document suggests a minimal light client design for the beacon chain that uses the concept of "sync committees" introduced in [./beacon-chain.md](the the light-client-friendliness beacon chain extension). +Eth2 is designed to be light client friendly for constrained environments to access Eth2 with reasonable satefy and liveness. Such environments include resource-constrained devices (e.g. phones for trust-minimised wallets) and metered VMs (e.g. blockchain VMs for cross-chain bridges). + +This document suggests a minimal light client design for the beacon chain that uses sync committees introduced in [this beacon chain extension](./beacon-chain.md). ## Constants | Name | Value | | - | - | -| `SYNC_COMMITTEES_GENERALIZED_INDEX` | `GeneralizedIndexConcat(GeneralizedIndex(BeaconBlock, 'state_root'), GeneralizedIndex(BeaconState, 'current_sync_committee'))` | -| `FORK_GENERALIZED_INDEX` | `GeneralizedIndexConcat(GeneralizedIndex(BeaconBlock, 'state_root'), GeneralizedIndex(BeaconState, 'fork'))` | +| `NEXT_SYNC_COMMITTEE_INDEX` | `IndexConcat(Index(BeaconBlock, 'state_root'), Index(BeaconState, 'next_sync_committee'))` | +| `FORK_INDEX` | `IndexConcat(Index(BeaconBlock, 'state_root'), Index(BeaconState, 'fork'))` | + +## Configuration + +### Misc + +| Name | Value | +| - | - | +| `MAX_VALID_LIGHT_CLIENT_UPDATES` | `uint64(2**64 - 1)` | + +### Time parameters + +| Name | Value | Unit | Duration | +| - | - | :-: | :-: | +| `LIGHT_CLIENT_UPDATE_TIMEOUT` | `Slot(2**13)` | slots | ~27 hours | ## Containers -### `LightClientUpdate` +#### `LightClientSnapshot` ```python -class LightClientUpdate(Container): - # Updated beacon header +class LightClientSnapshot(Container): + # Beacon block header header: BeaconBlockHeader - # Sync committee signature to that header - aggregation_bits: Bitlist[MAX_SYNC_COMMITTEE_SIZE] - signature: BLSSignature - # Updates fork version - new_fork: Fork - fork_branch: Vector[Bytes32, log_2(FORK_GENERALIZED_INDEX)] - # Updated sync committee (and authenticating branch) - new_current_sync_committee: SyncCommittee - new_next_sync_committee: SyncCommittee - sync_committee_branch: Vector[Bytes32, log_2(SYNC_COMMITTEES_GENERALIZED_INDEX)] -``` - -## Helpers - -### `LightClientMemory` - -```python -class LightClientMemory(Container): - # Beacon header which is not expected to revert - header: BeaconBlockHeader - # Fork version data - fork_version: Version - # sync committees corresponding to the beacon header + # Fork data corresponding to the header + fork: Fork + # Sync committees corresponding to the header current_sync_committee: SyncCommittee next_sync_committee: SyncCommittee ``` -## Light client state updates - -The state of a light client is stored in a `memory` object of type `LightClientMemory`. To advance its state a light client requests an `update` object of type `LightClientUpdate` from the network by sending a request containing `(memory.shard, memory.header.slot, slot_range_end)`. It calls `validate_update(memory, update)` on each update that it receives in response. If `sum(update.aggregate_bits) * 3 > len(update.aggregate_bits) * 2` for any valid update, it accepts that update immediately; otherwise, it waits around for some time and then finally calls `update_memory(memory, update)` on the valid update with the highest `sum(update.aggregate_bits)`. - -#### `validate_update` +#### `LightClientUpdate` ```python -def validate_update(memory: LightClientMemory, update: LightClientUpdate) -> bool: - # Verify the update does not skip a period - current_period = compute_epoch_at_slot(memory.header.slot) // EPOCHS_PER_SYNC_COMMITTEE_PERIOD - new_epoch = compute_epoch_of_shard_slot(update.header.slot) - new_period = new_epoch // EPOCHS_PER_SYNC_COMMITTEE_PERIOD - assert new_period in (current_period, current_period + 1) - - # Verify that it actually updates to a newer slot - assert update.header.slot > memory.header.slot - - # Independent variable for convenience - committee = memory.current_sync_committee if new_period == current_period else memory.next_sync_committee - assert len(update.aggregation_bits) == len(committee) - - # Verify signature - active_pubkeys = [p for (bit, p) in zip(update.aggregation_bits, committee.pubkeys) if bit] - domain = compute_domain(DOMAIN_SYNC_COMMITTEE, memory.fork_version) - signing_root = compute_signing_root(update.header, domain) - assert bls.FastAggregateVerify(pubkeys, signing_root, update.signature) +class LightClientUpdate(Container): + # Updated snapshot + snapshot: LightClientSnapshot + # Merkle branches for the updated snapshot + fork_branch: Vector[Bytes32, log_2(FORK_INDEX)] + next_sync_committee_branch: Vector[Bytes32, log_2(NEXT_SYNC_COMMITTEE_INDEX)] + # Sync committee aggregate signature + sync_committee_bits: Bitlist[MAX_SYNC_COMMITTEE_SIZE] + sync_committee_signature: BLSSignature +``` - # Verify Merkle branches of new info +#### `LightClientStore` + +```python +class LightClientStore(Container): + snapshot: LightClientSnapshot + valid_updates: List[LightClientUpdate, MAX_VALID_LIGHT_CLIENT_UPDATES] +``` + +## 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 currect slot based on some local clock. + +#### `is_valid_light_client_update` + +```python +def is_valid_light_client_update(store: LightClientStore, update: LightClientUpdate) -> bool: + # Verify new slot is larger than old slot + old_snapshot = store.snapshot + new_snapshot = update.snapshot + assert new_snapshot.header.slot > old_snapshot.header.slot + + # Verify update does not skip a sync committee period + old_period = compute_epoch_at_slot(old_snapshot.header.slot) // EPOCHS_PER_SYNC_COMMITTEE_PERIOD + new_period = compute_epoch_at_slot(new_snapshot.header.slot) // EPOCHS_PER_SYNC_COMMITTEE_PERIOD + assert new_period in (old_period, old_period + 1) + + # Verify new snapshot sync committees + if new_period == old_period: + assert new_snapshot.current_sync_committee == old_snapshot.current_sync_committee + assert new_snapshot.next_sync_committee == old_snapshot.next_sync_committee + else new_period == old_period + 1: + assert new_snapshot.current_sync_committee == old_snapshot.next_sync_committee + assert is_valid_merkle_branch( + leaf=hash_tree_root(new_snapshot.next_sync_committee), + branch=update.next_sync_committee_branch, + depth=log2(NEXT_SYNC_COMMITTEE_INDEX), + index=NEXT_SYNC_COMMITTEE_INDEX % 2**log2(NEXT_SYNC_COMMITTEE_INDEX), + root=hash_tree_root(new_snapshot.header), + ) + + # Verify new snapshot fork assert is_valid_merkle_branch( - leaf=hash_tree_root(update.new_fork), + leaf=hash_tree_root(new_snapshot.fork), branch=update.fork_branch, - depth=log2(FORK_GENERALIZED_INDEX), - index=FORK_GENERALIZED_INDEX % 2**log2(FORK_GENERALIZED_INDEX), - root=hash_tree_root(update.header), + depth=log2(FORK_INDEX), + index=FORK_INDEX % 2**log2(FORK_INDEX), + root=hash_tree_root(new_snapshot.header), ) - assert is_valid_merkle_branch( - leaf=hash_tree_root(update.current_sync_committee), - branch=update.sync_committee_branch, - depth=log2(SYNC_COMMITTEES_GENERALIZED_INDEX), - index=SYNC_COMMITTEES_GENERALIZED_INDEX % 2**log2(SYNC_COMMITTEES_GENERALIZED_INDEX), - root=hash_tree_root(update.header), - ) - # Verify consistency of committees - if new_period == current_period: - assert update.new_current_sync_committee == memory.current_sync_committee - assert update.new_next_sync_committee == memory.next_sync_committee - else: - assert update.new_current_sync_committee == memory.next_sync_committee + + # Verify sync committee bitfield length + sync_committee = new_snapshot.current_sync_committee + assert len(update.sync_committee_bits) == len(sync_committee) + + # 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, fork_version.current_version) + signing_root = compute_signing_root(new_snapshot.header, domain) + assert bls.FastAggregateVerify(participant_pubkeys, signing_root, update.sync_committee_signature) return True ``` -#### `update_memory` +#### `process_update` ```python -def update_memory(memory: LightClientMemory, update: LightClientUpdate) -> None: - memory.header = update.header - epoch = compute_epoch_at_slot(update.header.slot) - memory.fork_version = update.new_fork.previous_version if epoch < update.new_fork.epoch else update.new_fork.current_version - memory.current_sync_committee = update.new_current_sync_committee - memory.next_sync_committee == update.new_next_sync_committee +def process_light_client_update(store: LightClientStore, update: LightClientUpdate, current_slot: Slot) -> None: + assert is_valid_light_client_update(store, update) + if sum(update.sync_committee_bits) * 3 > len(update.sync_committee_bits) * 2: + store.snapshot = update.snapshot + valid_updates = [] + else: + valid_updates.append(update) + + # Force an update after the update timeout has elapsed + if current_slot > old_snapshot.header.slot + LIGHT_CLIENT_UPDATE_TIMEOUT: + best_update = max(valid_updates, key=lambda update: sum(update.sync_committee_bits)) + store.snapshot = best_update.new_snapshot ``` From 7ffc9c5bc0147203b765ee370c76e13a96fb9ce3 Mon Sep 17 00:00:00 2001 From: Justin Date: Tue, 17 Nov 2020 14:18:58 +0000 Subject: [PATCH 19/32] More polish and fixes to the sync protocol --- specs/lightclient/sync-protocol.md | 21 +++++---------------- 1 file changed, 5 insertions(+), 16 deletions(-) diff --git a/specs/lightclient/sync-protocol.md b/specs/lightclient/sync-protocol.md index 8fb94bfa5..6977d5862 100644 --- a/specs/lightclient/sync-protocol.md +++ b/specs/lightclient/sync-protocol.md @@ -36,7 +36,6 @@ This document suggests a minimal light client design for the beacon chain that u | Name | Value | | - | - | | `NEXT_SYNC_COMMITTEE_INDEX` | `IndexConcat(Index(BeaconBlock, 'state_root'), Index(BeaconState, 'next_sync_committee'))` | -| `FORK_INDEX` | `IndexConcat(Index(BeaconBlock, 'state_root'), Index(BeaconState, 'fork'))` | ## Configuration @@ -60,8 +59,6 @@ This document suggests a minimal light client design for the beacon chain that u class LightClientSnapshot(Container): # Beacon block header header: BeaconBlockHeader - # Fork data corresponding to the header - fork: Fork # Sync committees corresponding to the header current_sync_committee: SyncCommittee next_sync_committee: SyncCommittee @@ -73,12 +70,13 @@ class LightClientSnapshot(Container): class LightClientUpdate(Container): # Updated snapshot snapshot: LightClientSnapshot - # Merkle branches for the updated snapshot - fork_branch: Vector[Bytes32, log_2(FORK_INDEX)] - next_sync_committee_branch: Vector[Bytes32, log_2(NEXT_SYNC_COMMITTEE_INDEX)] + # Merkle branches for the next sync committee + next_sync_committee_branch: Vector[Bytes32, log2(NEXT_SYNC_COMMITTEE_INDEX)] # Sync committee aggregate signature sync_committee_bits: Bitlist[MAX_SYNC_COMMITTEE_SIZE] sync_committee_signature: BLSSignature + # Fork version corresponding to the aggregate signature + fork_version ``` #### `LightClientStore` @@ -121,22 +119,13 @@ def is_valid_light_client_update(store: LightClientStore, update: LightClientUpd root=hash_tree_root(new_snapshot.header), ) - # Verify new snapshot fork - assert is_valid_merkle_branch( - leaf=hash_tree_root(new_snapshot.fork), - branch=update.fork_branch, - depth=log2(FORK_INDEX), - index=FORK_INDEX % 2**log2(FORK_INDEX), - root=hash_tree_root(new_snapshot.header), - ) - # Verify sync committee bitfield length sync_committee = new_snapshot.current_sync_committee assert len(update.sync_committee_bits) == len(sync_committee) # 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, fork_version.current_version) + domain = compute_domain(DOMAIN_SYNC_COMMITTEE, update.fork_version) signing_root = compute_signing_root(new_snapshot.header, domain) assert bls.FastAggregateVerify(participant_pubkeys, signing_root, update.sync_committee_signature) From 5e717a456d3046433a4add9f40f38bef9a303342 Mon Sep 17 00:00:00 2001 From: Justin Date: Tue, 17 Nov 2020 15:30:09 +0000 Subject: [PATCH 20/32] More polish and fixes --- specs/lightclient/sync-protocol.md | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/specs/lightclient/sync-protocol.md b/specs/lightclient/sync-protocol.md index 6977d5862..99f25d875 100644 --- a/specs/lightclient/sync-protocol.md +++ b/specs/lightclient/sync-protocol.md @@ -43,6 +43,7 @@ This document suggests a minimal light client design for the beacon chain that u | Name | Value | | - | - | +| `MIN_SYNC_COMMITTEE_PARTICIPANTS` | `1` | | `MAX_VALID_LIGHT_CLIENT_UPDATES` | `uint64(2**64 - 1)` | ### Time parameters @@ -75,7 +76,7 @@ class LightClientUpdate(Container): # Sync committee aggregate signature sync_committee_bits: Bitlist[MAX_SYNC_COMMITTEE_SIZE] sync_committee_signature: BLSSignature - # Fork version corresponding to the aggregate signature + # Fork version for the aggregate signature fork_version ``` @@ -122,6 +123,7 @@ def is_valid_light_client_update(store: LightClientStore, update: LightClientUpd # Verify sync committee bitfield length sync_committee = new_snapshot.current_sync_committee assert len(update.sync_committee_bits) == len(sync_committee) + assert sum(update.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] @@ -136,15 +138,16 @@ def is_valid_light_client_update(store: LightClientStore, update: LightClientUpd ```python def process_light_client_update(store: LightClientStore, update: LightClientUpdate, current_slot: Slot) -> None: + # Validate update assert is_valid_light_client_update(store, update) + valid_updates.append(update) + if sum(update.sync_committee_bits) * 3 > len(update.sync_committee_bits) * 2: + # Immediate update when quorum is reached store.snapshot = update.snapshot valid_updates = [] - else: - valid_updates.append(update) - - # Force an update after the update timeout has elapsed - if current_slot > old_snapshot.header.slot + LIGHT_CLIENT_UPDATE_TIMEOUT: - best_update = max(valid_updates, key=lambda update: sum(update.sync_committee_bits)) - store.snapshot = best_update.new_snapshot + elif current_slot > old_snapshot.header.slot + LIGHT_CLIENT_UPDATE_TIMEOUT: + # Forced best update when the update timeout has elapsed + store.snapshot = max(valid_updates, key=lambda update: sum(update.sync_committee_bits)).new_snapshot + valid_updates = [] ``` From 830efa496a32835cce910fa4600547ddcc5ebd91 Mon Sep 17 00:00:00 2001 From: vbuterin Date: Wed, 18 Nov 2020 16:15:30 +0800 Subject: [PATCH 21/32] Removed compact validators, make committee balance-based --- specs/lightclient/beacon-chain.md | 40 ++++++++----------------------- 1 file changed, 10 insertions(+), 30 deletions(-) diff --git a/specs/lightclient/beacon-chain.md b/specs/lightclient/beacon-chain.md index 2d00d5170..fbca70071 100644 --- a/specs/lightclient/beacon-chain.md +++ b/specs/lightclient/beacon-chain.md @@ -22,8 +22,6 @@ - [`SyncCommittee`](#synccommittee) - [Helper functions](#helper-functions) - [Misc](#misc-1) - - [`compactify_validator`](#compactify_validator) - - [`decompactify_validator`](#decompactify_validator) - [Beacon state accessors](#beacon-state-accessors) - [`get_sync_committee_indices`](#get_sync_committee_indices) - [`get_sync_committee`](#get_sync_committee) @@ -43,7 +41,6 @@ This is a standalone beacon chain patch adding light client support via sync com | Name | SSZ equivalent | Description | | - | - | - | -| `CompactValidator` | `uint64` | a compact validator | ## Constants @@ -101,36 +98,12 @@ class BeaconState(phase0.BeaconState): class SyncCommittee(Container): pubkeys: List[BLSPubkey, MAX_SYNC_COMMITTEE_SIZE] pubkeys_aggregate: BLSPubkey - compact_validators: List[CompactValidator, MAX_SYNC_COMMITTEE_SIZE] ``` ## Helper functions ### Misc -#### `compactify_validator` - -```python -def compactify_validator(index: ValidatorIndex, slashed: bool, effective_balance: Gwei) -> CompactValidator: - """ - Return the compact validator for a given validator index, slashed status and effective balance. - """ - return CompactValidator((index << 16) + (slashed << 15) + uint64(effective_balance // EFFECTIVE_BALANCE_INCREMENT)) -``` - -#### `decompactify_validator` - -```python -def decompactify_validator(compact_validator: CompactValidator) -> Tuple[ValidatorIndex, bool, Gwei]: - """ - Return the validator index, slashed status and effective balance for a given compact validator. - """ - index = ValidatorIndex(compact_validator >> 16) # from bits 16-63 - slashed = bool((compact_validator >> 15) % 2) # from bit 15 - effective_balance = Gwei(compact_validator & (2**15 - 1)) * EFFECTIVE_BALANCE_INCREMENT # from bits 0-14 - return (index, slashed, effective_balance) -``` - ### Beacon state accessors #### `get_sync_committee_indices` @@ -144,7 +117,15 @@ def get_sync_committee_indices(state: BeaconState, epoch: Epoch) -> Sequence[Val active_validator_count = uint64(len(get_active_validator_indices(state, base_epoch))) sync_committee_size = min(active_validator_count, MAX_SYNC_COMMITTEE_SIZE) seed = get_seed(state, base_epoch, DOMAIN_SYNC_COMMITTEE) - return [compute_shuffled_index(uint64(i), active_validator_count, seed) for i in range(sync_committee_size)] + i, output = 0, [] + while i < active_validator_count and len(output) < sync_committee_size: + candidate_index = indices[compute_shuffled_index(uint64(i), active_validator_count, seed)] + random_byte = hash(seed + uint_to_bytes(uint64(i // 32)))[i % 32] + effective_balance = state.validators[candidate_index].effective_balance + if effective_balance * MAX_RANDOM_BYTE >= MAX_EFFECTIVE_BALANCE * random_byte: + output.append(candidate_index) + i += 1 + return output ``` #### `get_sync_committee` @@ -157,8 +138,7 @@ def get_sync_committee(state: BeaconState, epoch: Epoch) -> SyncCommittee: indices = get_sync_committee_indices(state, epoch) validators = [state.validators[index] for index in indices] pubkeys = [validator.pubkey for validator in validators] - compact_validators = [compactify_validator(i, v.slashed, v.effective_balance) for i, v in zip(indices, validators)] - return SyncCommittee(pubkeys, bls.AggregatePKs(pubkeys), compact_validators) + return SyncCommittee(pubkeys, bls.AggregatePKs(pubkeys)) ``` ### Block processing From 171b21301b73b9bad649199cd03e5ed58c757971 Mon Sep 17 00:00:00 2001 From: vbuterin Date: Wed, 18 Nov 2020 16:27:19 +0800 Subject: [PATCH 22/32] Added support for updates that point to finalized ancestors --- specs/lightclient/sync-protocol.md | 31 +++++++++++++++++++++++++----- 1 file changed, 26 insertions(+), 5 deletions(-) diff --git a/specs/lightclient/sync-protocol.md b/specs/lightclient/sync-protocol.md index 99f25d875..e460d86d9 100644 --- a/specs/lightclient/sync-protocol.md +++ b/specs/lightclient/sync-protocol.md @@ -35,7 +35,8 @@ This document suggests a minimal light client design for the beacon chain that u | Name | Value | | - | - | -| `NEXT_SYNC_COMMITTEE_INDEX` | `IndexConcat(Index(BeaconBlock, 'state_root'), Index(BeaconState, 'next_sync_committee'))` | +| `NEXT_SYNC_COMMITTEE_INDEX` | `Index(BeaconState, 'next_sync_committee')` | +| `FINALIZED_ROOT_INDEX` | `Index(BeaconState, 'finalized_checkpoint', 'root')` | ## Configuration @@ -71,13 +72,17 @@ class LightClientSnapshot(Container): class LightClientUpdate(Container): # Updated snapshot snapshot: LightClientSnapshot + # Header that the new snapshot is a finalized ancestor of + signed_header: BeaconBlockHeader + # Merkle branch proving ancestry of the header in the snapshot + ancestry_branch: Vector[Bytes32, log2(FINALIZED_ROOT_INDEX)] # Merkle branches for the next sync committee next_sync_committee_branch: Vector[Bytes32, log2(NEXT_SYNC_COMMITTEE_INDEX)] # Sync committee aggregate signature sync_committee_bits: Bitlist[MAX_SYNC_COMMITTEE_SIZE] sync_committee_signature: BLSSignature # Fork version for the aggregate signature - fork_version + fork_version: Version ``` #### `LightClientStore` @@ -105,6 +110,18 @@ def is_valid_light_client_update(store: LightClientStore, update: LightClientUpd old_period = compute_epoch_at_slot(old_snapshot.header.slot) // EPOCHS_PER_SYNC_COMMITTEE_PERIOD new_period = compute_epoch_at_slot(new_snapshot.header.slot) // EPOCHS_PER_SYNC_COMMITTEE_PERIOD assert new_period in (old_period, old_period + 1) + + # Verify relationship between signed header and ancestor header + if update.signed_header == new_snapshot.header: + assert update.ancestry_branch == [ZERO_HASH for _ in range(log2(FINALIZED_ROOT_INDEX))] + else: + assert is_valid_merkle_branch( + leaf=hash_tree_root(new_snapshot.header), + branch=update.ancestry_branch, + depth=log2(FINALIZED_ROOT_INDEX), + index=FINALIZED_ROOT_INDEX % 2**log2(FINALIZED_ROOT_INDEX), + root=update.signed_header.state_root, + ) # Verify new snapshot sync committees if new_period == old_period: @@ -117,7 +134,7 @@ def is_valid_light_client_update(store: LightClientStore, update: LightClientUpd branch=update.next_sync_committee_branch, depth=log2(NEXT_SYNC_COMMITTEE_INDEX), index=NEXT_SYNC_COMMITTEE_INDEX % 2**log2(NEXT_SYNC_COMMITTEE_INDEX), - root=hash_tree_root(new_snapshot.header), + root=new_snapshot.header.state_root, ) # Verify sync committee bitfield length @@ -128,7 +145,7 @@ def is_valid_light_client_update(store: LightClientStore, update: LightClientUpd # 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) - signing_root = compute_signing_root(new_snapshot.header, domain) + signing_root = compute_signing_root(update.signed_header, domain) assert bls.FastAggregateVerify(participant_pubkeys, signing_root, update.sync_committee_signature) return True @@ -142,7 +159,11 @@ def process_light_client_update(store: LightClientStore, update: LightClientUpda assert is_valid_light_client_update(store, update) valid_updates.append(update) - if sum(update.sync_committee_bits) * 3 > len(update.sync_committee_bits) * 2: + # Immediate update "happy path" requires: + # (i) 2/3 participation + # (ii) an update that refers to the finalized ancestor of a signed block, and not the signed block directly + + if sum(update.sync_committee_bits) * 3 > len(update.sync_committee_bits) * 2 and update.snapshot.header != update.signed_header: # Immediate update when quorum is reached store.snapshot = update.snapshot valid_updates = [] From 664bc4b42e0e2d43432c750039644745d8f1fb74 Mon Sep 17 00:00:00 2001 From: Justin Date: Wed, 18 Nov 2020 09:19:32 +0000 Subject: [PATCH 23/32] Polish and fixes including fixed-size sync committees --- specs/lightclient/beacon-chain.md | 35 +++++++++++-------------------- 1 file changed, 12 insertions(+), 23 deletions(-) diff --git a/specs/lightclient/beacon-chain.md b/specs/lightclient/beacon-chain.md index fbca70071..19ca9c85b 100644 --- a/specs/lightclient/beacon-chain.md +++ b/specs/lightclient/beacon-chain.md @@ -8,7 +8,6 @@ - [Introduction](#introduction) -- [Custom types](#custom-types) - [Constants](#constants) - [Configuration](#configuration) - [Misc](#misc) @@ -21,7 +20,6 @@ - [New containers](#new-containers) - [`SyncCommittee`](#synccommittee) - [Helper functions](#helper-functions) - - [Misc](#misc-1) - [Beacon state accessors](#beacon-state-accessors) - [`get_sync_committee_indices`](#get_sync_committee_indices) - [`get_sync_committee`](#get_sync_committee) @@ -37,11 +35,6 @@ This is a standalone beacon chain patch adding light client support via sync committees. -## Custom types - -| Name | SSZ equivalent | Description | -| - | - | - | - ## Constants | Name | Value | @@ -54,7 +47,7 @@ This is a standalone beacon chain patch adding light client support via sync com | Name | Value | | - | - | -| `MAX_SYNC_COMMITTEE_SIZE` | `uint64(2**8)` (= 256) | +| `SYNC_COMMITTEE_SIZE` | `uint64(2**8)` (= 256) | ### Time parameters @@ -77,7 +70,7 @@ This is a standalone beacon chain patch adding light client support via sync com ```python class BeaconBlockBody(phase0.BeaconBlockBody): # Light client - sync_committee_bits: Bitlist[MAX_SYNC_COMMITTEE_SIZE] + sync_committee_bits: Bitvector[SYNC_COMMITTEE_SIZE] sync_committee_signature: BLSSignature ``` @@ -96,14 +89,12 @@ class BeaconState(phase0.BeaconState): ```python class SyncCommittee(Container): - pubkeys: List[BLSPubkey, MAX_SYNC_COMMITTEE_SIZE] + pubkeys: Vector[BLSPubkey, SYNC_COMMITTEE_SIZE] pubkeys_aggregate: BLSPubkey ``` ## Helper functions -### Misc - ### Beacon state accessors #### `get_sync_committee_indices` @@ -114,18 +105,19 @@ def get_sync_committee_indices(state: BeaconState, epoch: Epoch) -> Sequence[Val Return the sync committee indices for a given state and epoch. """ base_epoch = Epoch((max(epoch // EPOCHS_PER_SYNC_COMMITTEE_PERIOD, 1) - 1) * EPOCHS_PER_SYNC_COMMITTEE_PERIOD) - active_validator_count = uint64(len(get_active_validator_indices(state, base_epoch))) - sync_committee_size = min(active_validator_count, MAX_SYNC_COMMITTEE_SIZE) + active_validator_indices = get_active_validator_indices(state, base_epoch) + active_validator_count = uint64(len(active_validator_indices)) seed = get_seed(state, base_epoch, DOMAIN_SYNC_COMMITTEE) - i, output = 0, [] - while i < active_validator_count and len(output) < sync_committee_size: - candidate_index = indices[compute_shuffled_index(uint64(i), active_validator_count, seed)] + i, sync_committee_indices = 0, [] + while len(sync_committee_indices) < SYNC_COMMITTEE_SIZE: + shuffled_index = compute_shuffled_index(uint64(i % active_validator_count), active_validator_count, seed) + candidate_index = active_validator_indices[shuffled_index] random_byte = hash(seed + uint_to_bytes(uint64(i // 32)))[i % 32] effective_balance = state.validators[candidate_index].effective_balance if effective_balance * MAX_RANDOM_BYTE >= MAX_EFFECTIVE_BALANCE * random_byte: - output.append(candidate_index) + sync_committee_indices.append(candidate_index) i += 1 - return output + return sync_committee_indices ``` #### `get_sync_committee` @@ -153,12 +145,9 @@ def process_block(state: BeaconState, block: BeaconBlock) -> None: ```python def process_sync_committee(state: BeaconState, body: BeaconBlockBody) -> None: - # Verify sync committee bitfield length - committee_indices = get_sync_committee_indices(state, get_current_epoch(state)) - assert len(body.sync_committee_bits) == len(committee_indices) - # Verify sync committee aggregate signature signing over the previous slot block root previous_slot = max(state.slot, Slot(1)) - Slot(1) + committee_indices = get_sync_committee_indices(state, get_current_epoch(state)) participant_indices = [committee_indices[i] for i in range(len(committee_indices)) if body.sync_committee_bits[i]] participant_pubkeys = [state.validators[participant_index].pubkey for participant_index in participant_indices] domain = get_domain(state, DOMAIN_SYNC_COMMITTEE, compute_epoch_at_slot(previous_slot)) From d16900a7532156991515e6267e9d22b70f02a31a Mon Sep 17 00:00:00 2001 From: Justin Date: Wed, 18 Nov 2020 09:31:41 +0000 Subject: [PATCH 24/32] Fixes from @ralexstokes --- specs/lightclient/sync-protocol.md | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/specs/lightclient/sync-protocol.md b/specs/lightclient/sync-protocol.md index e460d86d9..d35c9821d 100644 --- a/specs/lightclient/sync-protocol.md +++ b/specs/lightclient/sync-protocol.md @@ -76,10 +76,10 @@ class LightClientUpdate(Container): signed_header: BeaconBlockHeader # Merkle branch proving ancestry of the header in the snapshot ancestry_branch: Vector[Bytes32, log2(FINALIZED_ROOT_INDEX)] - # Merkle branches for the next sync committee + # Merkle branch for the next sync committee next_sync_committee_branch: Vector[Bytes32, log2(NEXT_SYNC_COMMITTEE_INDEX)] # Sync committee aggregate signature - sync_committee_bits: Bitlist[MAX_SYNC_COMMITTEE_SIZE] + sync_committee_bits: Bitvector[SYNC_COMMITTEE_SIZE] sync_committee_signature: BLSSignature # Fork version for the aggregate signature fork_version: Version @@ -127,7 +127,7 @@ def is_valid_light_client_update(store: LightClientStore, update: LightClientUpd if new_period == old_period: assert new_snapshot.current_sync_committee == old_snapshot.current_sync_committee assert new_snapshot.next_sync_committee == old_snapshot.next_sync_committee - else new_period == old_period + 1: + else: assert new_snapshot.current_sync_committee == old_snapshot.next_sync_committee assert is_valid_merkle_branch( leaf=hash_tree_root(new_snapshot.next_sync_committee), @@ -140,7 +140,7 @@ def is_valid_light_client_update(store: LightClientStore, update: LightClientUpd # Verify sync committee bitfield length sync_committee = new_snapshot.current_sync_committee assert len(update.sync_committee_bits) == len(sync_committee) - assert sum(update.sync_committee_bits) > MIN_SYNC_COMMITTEE_PARTICIPANTS + assert sum(update.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] @@ -166,9 +166,9 @@ def process_light_client_update(store: LightClientStore, update: LightClientUpda if sum(update.sync_committee_bits) * 3 > len(update.sync_committee_bits) * 2 and update.snapshot.header != update.signed_header: # Immediate update when quorum is reached store.snapshot = update.snapshot - valid_updates = [] + store.valid_updates = [] elif current_slot > old_snapshot.header.slot + LIGHT_CLIENT_UPDATE_TIMEOUT: # Forced best update when the update timeout has elapsed - store.snapshot = max(valid_updates, key=lambda update: sum(update.sync_committee_bits)).new_snapshot - valid_updates = [] + store.snapshot = max(store.valid_updates, key=lambda update: sum(update.sync_committee_bits)).snapshot + store.valid_updates = [] ``` From 09ec58131d41792ac4479881ad89573331adcb38 Mon Sep 17 00:00:00 2001 From: Justin Date: Wed, 18 Nov 2020 10:33:42 +0000 Subject: [PATCH 25/32] Optimised updates as suggested by @vbuterin --- specs/lightclient/sync-protocol.md | 101 +++++++++++++++-------------- 1 file changed, 54 insertions(+), 47 deletions(-) diff --git a/specs/lightclient/sync-protocol.md b/specs/lightclient/sync-protocol.md index d35c9821d..f78e6f090 100644 --- a/specs/lightclient/sync-protocol.md +++ b/specs/lightclient/sync-protocol.md @@ -20,6 +20,7 @@ - [`LightClientStore`](#lightclientstore) - [Light client state updates](#light-client-state-updates) - [`is_valid_light_client_update`](#is_valid_light_client_update) + - [`apply_light_client_update`](#apply_light_client_update) - [`process_light_client_update`](#process_light_client_update) @@ -35,8 +36,8 @@ This document suggests a minimal light client design for the beacon chain that u | Name | Value | | - | - | -| `NEXT_SYNC_COMMITTEE_INDEX` | `Index(BeaconState, 'next_sync_committee')` | | `FINALIZED_ROOT_INDEX` | `Index(BeaconState, 'finalized_checkpoint', 'root')` | +| `NEXT_SYNC_COMMITTEE_INDEX` | `Index(BeaconState, 'next_sync_committee')` | ## Configuration @@ -70,14 +71,14 @@ class LightClientSnapshot(Container): ```python class LightClientUpdate(Container): - # Updated snapshot - snapshot: LightClientSnapshot - # Header that the new snapshot is a finalized ancestor of - signed_header: BeaconBlockHeader - # Merkle branch proving ancestry of the header in the snapshot - ancestry_branch: Vector[Bytes32, log2(FINALIZED_ROOT_INDEX)] - # Merkle branch for the next sync committee + # Update beacon block header + header: BeaconBlockHeader + # Next sync committee corresponding to the header + next_sync_committee: SyncCommittee next_sync_committee_branch: Vector[Bytes32, log2(NEXT_SYNC_COMMITTEE_INDEX)] + # Finality proof for the update header + finality_header: BeaconBlockHeader + finality_branch: Vector[Bytes32, log2(FINALIZED_ROOT_INDEX)] # Sync committee aggregate signature sync_committee_bits: Bitvector[SYNC_COMMITTEE_SIZE] sync_committee_signature: BLSSignature @@ -100,75 +101,81 @@ A light client maintains its state in a `store` object of type `LightClientStore #### `is_valid_light_client_update` ```python -def is_valid_light_client_update(store: LightClientStore, update: LightClientUpdate) -> bool: - # Verify new slot is larger than old slot - old_snapshot = store.snapshot - new_snapshot = update.snapshot - assert new_snapshot.header.slot > old_snapshot.header.slot +def is_valid_light_client_update(snapshot: LightClientSnapshot, update: LightClientUpdate) -> bool: + # Verify update slot is larger than snapshot slot + assert update.header.slot > snapshot.header.slot # Verify update does not skip a sync committee period - old_period = compute_epoch_at_slot(old_snapshot.header.slot) // EPOCHS_PER_SYNC_COMMITTEE_PERIOD - new_period = compute_epoch_at_slot(new_snapshot.header.slot) // EPOCHS_PER_SYNC_COMMITTEE_PERIOD - assert new_period in (old_period, old_period + 1) - - # Verify relationship between signed header and ancestor header - if update.signed_header == new_snapshot.header: - assert update.ancestry_branch == [ZERO_HASH for _ in range(log2(FINALIZED_ROOT_INDEX))] + 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) + + # 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 == [ZERO_HASH for _ in range(log2(FINALIZED_ROOT_INDEX))] else: + signed_header = update.finality_header assert is_valid_merkle_branch( - leaf=hash_tree_root(new_snapshot.header), - branch=update.ancestry_branch, + leaf=hash_tree_root(update.header), + branch=update.finality_branch, depth=log2(FINALIZED_ROOT_INDEX), index=FINALIZED_ROOT_INDEX % 2**log2(FINALIZED_ROOT_INDEX), - root=update.signed_header.state_root, - ) + root=update.finality_header.state_root, + ) - # Verify new snapshot sync committees - if new_period == old_period: - assert new_snapshot.current_sync_committee == old_snapshot.current_sync_committee - assert new_snapshot.next_sync_committee == old_snapshot.next_sync_committee + # Verify update next sync committee if the update period incremented + if update_period == snapshot_period: + sync_committee = snapshot.current_sync_committee + assert update.next_sync_committee_branch == [ZERO_HASH for _ in range(log2(NEXT_SYNC_COMMITTEE_INDEX))] else: - assert new_snapshot.current_sync_committee == old_snapshot.next_sync_committee + sync_committee = snapshot.next_sync_committee assert is_valid_merkle_branch( - leaf=hash_tree_root(new_snapshot.next_sync_committee), + leaf=hash_tree_root(update.next_sync_committee), branch=update.next_sync_committee_branch, depth=log2(NEXT_SYNC_COMMITTEE_INDEX), index=NEXT_SYNC_COMMITTEE_INDEX % 2**log2(NEXT_SYNC_COMMITTEE_INDEX), - root=new_snapshot.header.state_root, + root=update.header.state_root, ) - # Verify sync committee bitfield length - sync_committee = new_snapshot.current_sync_committee - assert len(update.sync_committee_bits) == len(sync_committee) + # Verify sync committee has sufficient participants assert sum(update.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] domain = compute_domain(DOMAIN_SYNC_COMMITTEE, update.fork_version) - signing_root = compute_signing_root(update.signed_header, domain) + signing_root = compute_signing_root(signed_header, domain) assert bls.FastAggregateVerify(participant_pubkeys, signing_root, update.sync_committee_signature) return True ``` -#### `process_update` +#### `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 +``` + +#### `process_light_client_update` ```python def process_light_client_update(store: LightClientStore, update: LightClientUpdate, current_slot: Slot) -> None: # Validate update - assert is_valid_light_client_update(store, update) - valid_updates.append(update) + assert is_valid_light_client_update(store.snapshot, update) + store.valid_updates.append(update) - # Immediate update "happy path" requires: - # (i) 2/3 participation - # (ii) an update that refers to the finalized ancestor of a signed block, and not the signed block directly - - if sum(update.sync_committee_bits) * 3 > len(update.sync_committee_bits) * 2 and update.snapshot.header != update.signed_header: - # Immediate update when quorum is reached - store.snapshot = update.snapshot + if sum(update.sync_committee_bits) * 3 > len(update.sync_committee_bits) * 2 and update.header != update.finality_header: + # Apply update if 2/3 quorum is reached and we have a finality proof + apply_light_client_update(store, update) store.valid_updates = [] - elif current_slot > old_snapshot.header.slot + LIGHT_CLIENT_UPDATE_TIMEOUT: + elif current_slot > snapshot.header.slot + LIGHT_CLIENT_UPDATE_TIMEOUT: # Forced best update when the update timeout has elapsed - store.snapshot = max(store.valid_updates, key=lambda update: sum(update.sync_committee_bits)).snapshot + apply_light_client_update(store, max(store.valid_updates, key=lambda update: sum(update.sync_committee_bits))) store.valid_updates = [] ``` From 5bfe61f86567511ddb8e07179b7cb390c1f99357 Mon Sep 17 00:00:00 2001 From: vbuterin Date: Wed, 25 Nov 2020 19:38:45 +0800 Subject: [PATCH 26/32] Embiggened sync committee size and added sub-aggregates --- specs/lightclient/beacon-chain.md | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/specs/lightclient/beacon-chain.md b/specs/lightclient/beacon-chain.md index 19ca9c85b..c218a09bd 100644 --- a/specs/lightclient/beacon-chain.md +++ b/specs/lightclient/beacon-chain.md @@ -47,7 +47,8 @@ This is a standalone beacon chain patch adding light client support via sync com | Name | Value | | - | - | -| `SYNC_COMMITTEE_SIZE` | `uint64(2**8)` (= 256) | +| `SYNC_COMMITTEE_SIZE` | `uint64(2**10)` (= 1024) | +| `SYNC_PUBKEY_AGGREGATION_INTERVAL` | `uint64(2**6)` (= 64) | ### Time parameters @@ -90,7 +91,7 @@ class BeaconState(phase0.BeaconState): ```python class SyncCommittee(Container): pubkeys: Vector[BLSPubkey, SYNC_COMMITTEE_SIZE] - pubkeys_aggregate: BLSPubkey + pubkey_aggregates: Vector[BLSPubkey, SYNC_COMMITTEE_SIZE // SYNC_PUBKEY_AGGREGATION_INTERVAL] ``` ## Helper functions @@ -130,7 +131,11 @@ def get_sync_committee(state: BeaconState, epoch: Epoch) -> SyncCommittee: indices = get_sync_committee_indices(state, epoch) validators = [state.validators[index] for index in indices] pubkeys = [validator.pubkey for validator in validators] - return SyncCommittee(pubkeys, bls.AggregatePKs(pubkeys)) + aggregates = [ + bls.AggregatePKs(pubkeys[i:i+SYNC_PUBKEY_AGGREGATION_INTERVAL]) + for i in range(0, len(pubkeys), SYNC_PUBKEY_AGGREGATION_INTERVAL) + ] + return SyncCommittee(pubkeys, aggregates) ``` ### Block processing From b2d25f745493260d73648e92e5e7c79bb30d98cb Mon Sep 17 00:00:00 2001 From: Justin Date: Wed, 25 Nov 2020 11:59:01 +0000 Subject: [PATCH 27/32] Nitpicks --- specs/lightclient/beacon-chain.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/specs/lightclient/beacon-chain.md b/specs/lightclient/beacon-chain.md index c218a09bd..e8beb13a3 100644 --- a/specs/lightclient/beacon-chain.md +++ b/specs/lightclient/beacon-chain.md @@ -48,7 +48,7 @@ This is a standalone beacon chain patch adding light client support via sync com | Name | Value | | - | - | | `SYNC_COMMITTEE_SIZE` | `uint64(2**10)` (= 1024) | -| `SYNC_PUBKEY_AGGREGATION_INTERVAL` | `uint64(2**6)` (= 64) | +| `SYNC_COMMITTEE_PUBKEY_AGGREGATES_SIZE` | `uint64(2**6)` (= 64) | ### Time parameters @@ -70,7 +70,7 @@ This is a standalone beacon chain patch adding light client support via sync com ```python class BeaconBlockBody(phase0.BeaconBlockBody): - # Light client + # Sync committee aggregate signature sync_committee_bits: Bitvector[SYNC_COMMITTEE_SIZE] sync_committee_signature: BLSSignature ``` @@ -79,7 +79,7 @@ class BeaconBlockBody(phase0.BeaconBlockBody): ```python class BeaconState(phase0.BeaconState): - # Light client + # Sync committees current_sync_committee: SyncCommittee next_sync_committee: SyncCommittee ``` @@ -91,7 +91,7 @@ class BeaconState(phase0.BeaconState): ```python class SyncCommittee(Container): pubkeys: Vector[BLSPubkey, SYNC_COMMITTEE_SIZE] - pubkey_aggregates: Vector[BLSPubkey, SYNC_COMMITTEE_SIZE // SYNC_PUBKEY_AGGREGATION_INTERVAL] + pubkey_aggregates: Vector[BLSPubkey, SYNC_COMMITTEE_SIZE // SYNC_COMMITTEE_PUBKEY_AGGREGATES_SIZE] ``` ## Helper functions @@ -132,8 +132,8 @@ def get_sync_committee(state: BeaconState, epoch: Epoch) -> SyncCommittee: validators = [state.validators[index] for index in indices] pubkeys = [validator.pubkey for validator in validators] aggregates = [ - bls.AggregatePKs(pubkeys[i:i+SYNC_PUBKEY_AGGREGATION_INTERVAL]) - for i in range(0, len(pubkeys), SYNC_PUBKEY_AGGREGATION_INTERVAL) + bls.AggregatePKs(pubkeys[i:i + SYNC_COMMITTEE_PUBKEY_AGGREGATES_SIZE]) + for i in range(0, len(pubkeys), SYNC_COMMITTEE_PUBKEY_AGGREGATES_SIZE) ] return SyncCommittee(pubkeys, aggregates) ``` From 3b7c02514b9c855acdddbe2918bd0e02eeb02c9b Mon Sep 17 00:00:00 2001 From: Danny Ryan Date: Mon, 7 Dec 2020 08:10:39 -0700 Subject: [PATCH 28/32] straightforward light client edits --- specs/lightclient/beacon-chain.md | 31 ++++++++++++++++++++++-------- specs/lightclient/sync-protocol.md | 8 ++++++-- 2 files changed, 29 insertions(+), 10 deletions(-) diff --git a/specs/lightclient/beacon-chain.md b/specs/lightclient/beacon-chain.md index e8beb13a3..25952599d 100644 --- a/specs/lightclient/beacon-chain.md +++ b/specs/lightclient/beacon-chain.md @@ -66,15 +66,28 @@ This is a standalone beacon chain patch adding light client support via sync com ### Extended containers -#### `BeaconBlockBody` +*Note*: Extended SSZ containers inherit all fields from the parent in the original +order and append any additional fields to the end. + +#### `BeaconBlock` ```python -class BeaconBlockBody(phase0.BeaconBlockBody): +class BeaconBlock(phase0.BeaconBlock): # Sync committee aggregate signature sync_committee_bits: Bitvector[SYNC_COMMITTEE_SIZE] sync_committee_signature: BLSSignature ``` +#### `BeaconBlockHeader` + +```python +class BeaconBlockHeader(phase0.BeaconBlockHeader): + # Sync committee aggregate signature + sync_committee_bits: Bitvector[SYNC_COMMITTEE_SIZE] + sync_committee_signature: BLSSignature +``` + + #### `BeaconState` ```python @@ -105,6 +118,7 @@ def get_sync_committee_indices(state: BeaconState, epoch: Epoch) -> Sequence[Val """ Return the sync committee indices for a given state and epoch. """ + MAX_RANDOM_BYTE = 2**8 - 1 base_epoch = Epoch((max(epoch // EPOCHS_PER_SYNC_COMMITTEE_PERIOD, 1) - 1) * EPOCHS_PER_SYNC_COMMITTEE_PERIOD) active_validator_indices = get_active_validator_indices(state, base_epoch) active_validator_count = uint64(len(active_validator_indices)) @@ -143,21 +157,22 @@ def get_sync_committee(state: BeaconState, epoch: Epoch) -> SyncCommittee: ```python def process_block(state: BeaconState, block: BeaconBlock) -> None: phase0.process_block(state, block) - process_sync_committee(state, block.body) + process_sync_committee(state, block) ``` #### Sync committee processing ```python -def process_sync_committee(state: BeaconState, body: BeaconBlockBody) -> None: +def process_sync_committee(state: BeaconState, block: BeaconBlock) -> None: # Verify sync committee aggregate signature signing over the previous slot block root - previous_slot = max(state.slot, Slot(1)) - Slot(1) + previous_slot = Slot(max(state.slot, 1) - 1) committee_indices = get_sync_committee_indices(state, get_current_epoch(state)) - participant_indices = [committee_indices[i] for i in range(len(committee_indices)) if body.sync_committee_bits[i]] - participant_pubkeys = [state.validators[participant_index].pubkey for participant_index in participant_indices] + participant_indices = [index for index, bit in zip(committee_indices, body.sync_committee_bits) if bit] + committee_pubkeys = state.current_sync_committee.pubkeys + participant_pubkeys = [pubkey for pubkey, bit in zip(committee_pubkeys, body.sync_committee_bits) if bit] domain = get_domain(state, DOMAIN_SYNC_COMMITTEE, compute_epoch_at_slot(previous_slot)) signing_root = compute_signing_root(get_block_root_at_slot(state, previous_slot), domain) - assert bls.FastAggregateVerify(participant_pubkeys, signing_root, body.sync_committee_signature) + assert bls.FastAggregateVerify(participant_pubkeys, signing_root, block.sync_committee_signature) # Reward sync committee participants participant_rewards = Gwei(0) diff --git a/specs/lightclient/sync-protocol.md b/specs/lightclient/sync-protocol.md index f78e6f090..84b6f6fd7 100644 --- a/specs/lightclient/sync-protocol.md +++ b/specs/lightclient/sync-protocol.md @@ -28,9 +28,13 @@ ## Introduction -Eth2 is designed to be light client friendly for constrained environments to access Eth2 with reasonable satefy and liveness. Such environments include resource-constrained devices (e.g. phones for trust-minimised wallets) and metered VMs (e.g. blockchain VMs for cross-chain bridges). +Eth2 is designed to be light client friendly for constrained environments to +access Eth2 with reasonable safety and liveness. +Such environments include resource-constrained devices (e.g. phones for trust-minimised wallets) +and metered VMs (e.g. blockchain VMs for cross-chain bridges). -This document suggests a minimal light client design for the beacon chain that uses sync committees introduced in [this beacon chain extension](./beacon-chain.md). +This document suggests a minimal light client design for the beacon chain that +uses sync committees introduced in [this beacon chain extension](./beacon-chain.md). ## Constants From acfe49e3f311b02e127a31d0a453a07bb810bd49 Mon Sep 17 00:00:00 2001 From: Hsiao-Wei Wang Date: Tue, 15 Dec 2020 13:18:20 +0800 Subject: [PATCH 29/32] executable light client patch: beacon-chain.md (#2141) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Bump remerkleable to 0.1.18 * Disable `sync-protocol.md` for now. Make linter pass * Enable lightclient tests * Use *new* `optional_fast_aggregate_verify` * Fix ToC and codespell * Do not run phase1 tests with Lightclient patch * Fix the Eth1Data casting bug. Add a workaround. * Fix `run_on_attestation` testing helper * Revert * Rename `optional_fast_aggregate_verify` to `eth2_fast_aggregate_verify` * Apply Proto's suggestion * Apply Danny's suggestion * Fixing tests * Fix after rebasing * Rename `LIGHTCLIENT` -> `LIGHTCLIENT_PATCH` * New doctoc * Add lightclient patch configs * fix gitignore light client patch generator output * Upgrade state for light client patch * Add `lightclient-fork.md` to deal the fork boundary and fix `process_block_header` * Misc cleanups 1) Add a summary note for every function that is changed. 2) Avoid changing `process_block` (instead only change `process_block_header`). 3) Rename `G2_INFINITY_POINT_SIG` to `G2_POINT_AT_INFINITY` to avoid `SIG` contraction. 4) Misc cleanups * Update block.py * Update beacon-chain.md * Fix typo "minimal" -> "mainnet" Co-authored-by: Marin Petrunić * Use the new `BeaconBlockHeader` instead of phase 0 version * Update config files * Move `sync_committee_bits` and `sync_committee_signature` back to `BeaconBlockBody` Co-authored-by: protolambda Co-authored-by: Justin Co-authored-by: Marin Petrunić --- .gitignore | 2 +- Makefile | 6 +- configs/mainnet/lightclient_patch.yaml | 21 ++++ configs/minimal/lightclient_patch.yaml | 21 ++++ setup.py | 11 ++- specs/lightclient/beacon-chain.md | 95 ++++++++++++++----- specs/lightclient/lightclient-fork.md | 83 ++++++++++++++++ specs/lightclient/sync-protocol.md | 3 +- specs/phase0/validator.md | 4 +- tests/core/pyspec/eth2spec/test/context.py | 20 +++- .../pyspec/eth2spec/test/helpers/block.py | 5 + .../pyspec/eth2spec/test/helpers/rewards.py | 7 +- .../test_process_rewards_and_penalties.py | 12 ++- .../fork_choice/test_on_attestation.py | 6 +- .../test_process_attestation.py | 5 +- .../test_process_chunk_challenge.py | 23 ++--- .../test_process_custody_key_reveal.py | 11 ++- .../test_process_custody_slashing.py | 11 ++- ...est_process_early_derived_secret_reveal.py | 17 ++-- .../test_process_shard_transition.py | 9 +- .../test_process_challenge_deadlines.py | 3 +- .../test_process_custody_final_updates.py | 9 +- .../test_process_reveal_deadlines.py | 5 +- .../test/phase1/sanity/test_blocks.py | 16 ++-- .../test/phase1/sanity/test_shard_blocks.py | 23 ++--- .../fork_choice/test_on_shard_block.py | 15 ++- .../phase1/unittests/test_get_start_shard.py | 11 ++- 27 files changed, 340 insertions(+), 114 deletions(-) create mode 100644 configs/mainnet/lightclient_patch.yaml create mode 100644 configs/minimal/lightclient_patch.yaml create mode 100644 specs/lightclient/lightclient-fork.md diff --git a/.gitignore b/.gitignore index ed497112c..17d058225 100644 --- a/.gitignore +++ b/.gitignore @@ -17,7 +17,7 @@ eth2.0-spec-tests/ # Dynamically built from Markdown spec tests/core/pyspec/eth2spec/phase0/ tests/core/pyspec/eth2spec/phase1/ -tests/core/pyspec/eth2spec/lightclient/ +tests/core/pyspec/eth2spec/lightclient_patch/ # coverage reports .htmlcov diff --git a/Makefile b/Makefile index 811abbad8..987650948 100644 --- a/Makefile +++ b/Makefile @@ -86,11 +86,11 @@ install_test: test: pyspec . venv/bin/activate; cd $(PY_SPEC_DIR); \ - python -m pytest -n 4 --disable-bls --cov=eth2spec.phase0.spec --cov=eth2spec.phase1.spec --cov-report="html:$(COV_HTML_OUT)" --cov-branch eth2spec + python -m pytest -n 4 --disable-bls --cov=eth2spec.phase0.spec --cov=eth2spec.phase1.spec --cov=eth2spec.lightclient_patch.spec -cov-report="html:$(COV_HTML_OUT)" --cov-branch eth2spec find_test: pyspec . venv/bin/activate; cd $(PY_SPEC_DIR); \ - python -m pytest -k=$(K) --disable-bls --cov=eth2spec.phase0.spec --cov=eth2spec.phase1.spec --cov-report="html:$(COV_HTML_OUT)" --cov-branch eth2spec + python -m pytest -k=$(K) --disable-bls --cov=eth2spec.phase0.spec --cov=eth2spec.phase1.spec --cov=eth2spec.lightclient_patch.spec --cov-report="html:$(COV_HTML_OUT)" --cov-branch eth2spec citest: pyspec mkdir -p tests/core/pyspec/test-reports/eth2spec; . venv/bin/activate; cd $(PY_SPEC_DIR); \ @@ -113,7 +113,7 @@ codespell: 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.phase1 -p eth2spec.lightclient + && mypy --config-file $(LINTER_CONFIG_FILE) -p eth2spec.phase0 -p eth2spec.phase1 -p eth2spec.lightclient_patch lint_generators: pyspec . venv/bin/activate; cd $(TEST_GENERATORS_DIR); \ diff --git a/configs/mainnet/lightclient_patch.yaml b/configs/mainnet/lightclient_patch.yaml new file mode 100644 index 000000000..64c05a720 --- /dev/null +++ b/configs/mainnet/lightclient_patch.yaml @@ -0,0 +1,21 @@ +# Mainnet preset - lightclient patch + +CONFIG_NAME: "mainnet" + +# Misc +# --------------------------------------------------------------- +# 2**10 (=1,024) +SYNC_COMMITTEE_SIZE: 1024 +# 2**6 (=64) +SYNC_COMMITTEE_PUBKEY_AGGREGATES_SIZE: 64 + + +# Time parameters +# --------------------------------------------------------------- +# 2**8 (= 256) +EPOCHS_PER_SYNC_COMMITTEE_PERIOD: 256 + + +# Signature domains +# --------------------------------------------------------------- +DOMAIN_SYNC_COMMITTEE: 0x07000000 diff --git a/configs/minimal/lightclient_patch.yaml b/configs/minimal/lightclient_patch.yaml new file mode 100644 index 000000000..ba1179a2b --- /dev/null +++ b/configs/minimal/lightclient_patch.yaml @@ -0,0 +1,21 @@ +# Minimal preset - lightclient patch + +CONFIG_NAME: "minimal" + +# Misc +# --------------------------------------------------------------- +# [customized] +SYNC_COMMITTEE_SIZE: 64 +# [customized] +SYNC_COMMITTEE_PUBKEY_AGGREGATES_SIZE: 16 + + +# Time parameters +# --------------------------------------------------------------- +# 2**8 (= 256) +EPOCHS_PER_SYNC_COMMITTEE_PERIOD: 256 + + +# Signature domains +# --------------------------------------------------------------- +DOMAIN_SYNC_COMMITTEE: 0x07000000 diff --git a/setup.py b/setup.py index 6a2bf5707..bd043dccc 100644 --- a/setup.py +++ b/setup.py @@ -173,7 +173,7 @@ from lru import LRU from eth2spec.utils.ssz.ssz_impl import hash_tree_root, copy, uint_to_bytes from eth2spec.utils.ssz.ssz_typing import ( View, boolean, Container, List, Vector, uint8, uint32, uint64, - Bytes1, Bytes4, Bytes32, Bytes48, Bytes96, Bitlist, + Bytes1, Bytes4, Bytes32, Bytes48, Bytes96, Bitlist, Bitvector, ) from eth2spec.utils import bls @@ -386,7 +386,7 @@ def combine_spec_objects(spec0: SpecObject, spec1: SpecObject) -> SpecObject: fork_imports = { 'phase0': PHASE0_IMPORTS, 'phase1': PHASE1_IMPORTS, - 'lightclient': LIGHTCLIENT_IMPORT, + 'lightclient_patch': LIGHTCLIENT_IMPORT, } @@ -453,15 +453,16 @@ class PySpecCommand(Command): specs/phase1/shard-fork-choice.md specs/phase1/validator.md """ - elif self.spec_fork == "lightclient": + elif self.spec_fork == "lightclient_patch": self.md_doc_paths = """ specs/phase0/beacon-chain.md specs/phase0/fork-choice.md specs/phase0/validator.md specs/phase0/weak-subjectivity.md specs/lightclient/beacon-chain.md - specs/lightclient/sync-protocol.md + specs/lightclient/lightclient-fork.md """ + # TODO: add specs/lightclient/sync-protocol.md back when the GeneralizedIndex helpers are included. else: raise Exception('no markdown files specified, and spec fork "%s" is unknown', self.spec_fork) @@ -584,7 +585,7 @@ setup( "py_ecc==5.0.0", "milagro_bls_binding==1.5.0", "dataclasses==0.6", - "remerkleable==0.1.17", + "remerkleable==0.1.18", "ruamel.yaml==0.16.5", "lru-dict==1.1.6" ] diff --git a/specs/lightclient/beacon-chain.md b/specs/lightclient/beacon-chain.md index 25952599d..4eb1f24ab 100644 --- a/specs/lightclient/beacon-chain.md +++ b/specs/lightclient/beacon-chain.md @@ -6,10 +6,10 @@ - - [Introduction](#introduction) - [Constants](#constants) - [Configuration](#configuration) + - [Constants](#constants-1) - [Misc](#misc) - [Time parameters](#time-parameters) - [Domain types](#domain-types) @@ -20,12 +20,15 @@ - [New containers](#new-containers) - [`SyncCommittee`](#synccommittee) - [Helper functions](#helper-functions) + - [`Predicates`](#predicates) + - [`eth2_fast_aggregate_verify`](#eth2_fast_aggregate_verify) - [Beacon state accessors](#beacon-state-accessors) - [`get_sync_committee_indices`](#get_sync_committee_indices) - [`get_sync_committee`](#get_sync_committee) - [Block processing](#block-processing) - [Sync committee processing](#sync-committee-processing) - [Epoch processing](#epoch-processing) + - [Components of attestation deltas](#components-of-attestation-deltas) - [Final updates](#final-updates) @@ -43,6 +46,12 @@ This is a standalone beacon chain patch adding light client support via sync com ## Configuration +### Constants + +| Name | Value | +| - | - | +| `G2_POINT_AT_INFINITY` | `BLSSignature(b'\xc0' + b'\x00' * 95)` | + ### Misc | Name | Value | @@ -69,25 +78,15 @@ This is a standalone beacon chain patch adding light client support via sync com *Note*: Extended SSZ containers inherit all fields from the parent in the original order and append any additional fields to the end. -#### `BeaconBlock` +#### `BeaconBlockBody` ```python -class BeaconBlock(phase0.BeaconBlock): +class BeaconBlockBody(phase0.BeaconBlockBody): # Sync committee aggregate signature sync_committee_bits: Bitvector[SYNC_COMMITTEE_SIZE] sync_committee_signature: BLSSignature ``` -#### `BeaconBlockHeader` - -```python -class BeaconBlockHeader(phase0.BeaconBlockHeader): - # Sync committee aggregate signature - sync_committee_bits: Bitvector[SYNC_COMMITTEE_SIZE] - sync_committee_signature: BLSSignature -``` - - #### `BeaconState` ```python @@ -109,6 +108,20 @@ class SyncCommittee(Container): ## Helper functions +### `Predicates` + +#### `eth2_fast_aggregate_verify` + +```python +def eth2_fast_aggregate_verify(pubkeys: Sequence[BLSPubkey], message: Bytes32, signature: BLSSignature) -> bool: + """ + Wrapper to ``bls.FastAggregateVerify`` accepting the ``G2_POINT_AT_INFINITY`` signature when ``pubkeys`` is empty. + """ + if len(pubkeys) == 0 and signature == G2_POINT_AT_INFINITY: + return True + return bls.FastAggregateVerify(pubkeys, message, signature) +``` + ### Beacon state accessors #### `get_sync_committee_indices` @@ -117,13 +130,14 @@ class SyncCommittee(Container): def get_sync_committee_indices(state: BeaconState, epoch: Epoch) -> Sequence[ValidatorIndex]: """ Return the sync committee indices for a given state and epoch. - """ + """ MAX_RANDOM_BYTE = 2**8 - 1 base_epoch = Epoch((max(epoch // EPOCHS_PER_SYNC_COMMITTEE_PERIOD, 1) - 1) * EPOCHS_PER_SYNC_COMMITTEE_PERIOD) active_validator_indices = get_active_validator_indices(state, base_epoch) active_validator_count = uint64(len(active_validator_indices)) seed = get_seed(state, base_epoch, DOMAIN_SYNC_COMMITTEE) - i, sync_committee_indices = 0, [] + i = 0 + sync_committee_indices: List[ValidatorIndex] = [] while len(sync_committee_indices) < SYNC_COMMITTEE_SIZE: shuffled_index = compute_shuffled_index(uint64(i % active_validator_count), active_validator_count, seed) candidate_index = active_validator_indices[shuffled_index] @@ -156,41 +170,74 @@ def get_sync_committee(state: BeaconState, epoch: Epoch) -> SyncCommittee: ```python def process_block(state: BeaconState, block: BeaconBlock) -> None: - phase0.process_block(state, block) - process_sync_committee(state, block) + process_block_header(state, block) + process_randao(state, block.body) + process_eth1_data(state, block.body) + process_operations(state, block.body) + # Light client support + process_sync_committee(state, block.body) ``` #### Sync committee processing ```python -def process_sync_committee(state: BeaconState, block: BeaconBlock) -> None: +def process_sync_committee(state: BeaconState, body: BeaconBlockBody) -> None: # Verify sync committee aggregate signature signing over the previous slot block root - previous_slot = Slot(max(state.slot, 1) - 1) + previous_slot = Slot(max(int(state.slot), 1) - 1) committee_indices = get_sync_committee_indices(state, get_current_epoch(state)) participant_indices = [index for index, bit in zip(committee_indices, body.sync_committee_bits) if bit] committee_pubkeys = state.current_sync_committee.pubkeys participant_pubkeys = [pubkey for pubkey, bit in zip(committee_pubkeys, body.sync_committee_bits) if bit] domain = get_domain(state, DOMAIN_SYNC_COMMITTEE, compute_epoch_at_slot(previous_slot)) signing_root = compute_signing_root(get_block_root_at_slot(state, previous_slot), domain) - assert bls.FastAggregateVerify(participant_pubkeys, signing_root, block.sync_committee_signature) + assert eth2_fast_aggregate_verify(participant_pubkeys, signing_root, body.sync_committee_signature) # Reward sync committee participants - participant_rewards = Gwei(0) + proposer_reward = Gwei(0) active_validator_count = uint64(len(get_active_validator_indices(state, get_current_epoch(state)))) for participant_index in participant_indices: base_reward = get_base_reward(state, participant_index) - reward = Gwei(base_reward * active_validator_count // len(committee_indices) // SLOTS_PER_EPOCH) + max_participant_reward = base_reward - base_reward // PROPOSER_REWARD_QUOTIENT + reward = Gwei(max_participant_reward * active_validator_count // len(committee_indices) // SLOTS_PER_EPOCH) increase_balance(state, participant_index, reward) - participant_rewards += reward + proposer_reward += base_reward // PROPOSER_REWARD_QUOTIENT # Reward beacon proposer - increase_balance(state, get_beacon_proposer_index(state), Gwei(participant_rewards // PROPOSER_REWARD_QUOTIENT)) + increase_balance(state, get_beacon_proposer_index(state), proposer_reward) ``` ### Epoch processing +#### Components of attestation deltas + +*Note*: The function `get_inactivity_penalty_deltas` is modified with `BASE_REWARDS_PER_EPOCH` replaced by `BASE_REWARDS_PER_EPOCH - 1`. + +```python +def get_inactivity_penalty_deltas(state: BeaconState) -> Tuple[Sequence[Gwei], Sequence[Gwei]]: + """ + Return inactivity reward/penalty deltas for each validator. + """ + penalties = [Gwei(0) for _ in range(len(state.validators))] + if is_in_inactivity_leak(state): + matching_target_attestations = get_matching_target_attestations(state, get_previous_epoch(state)) + matching_target_attesting_indices = get_unslashed_attesting_indices(state, matching_target_attestations) + for index in get_eligible_validator_indices(state): + # Penalize validator so that optimal attestation performance is rewarded with one base reward per epoch + base_reward = get_base_reward(state, index) + penalties[index] += Gwei((BASE_REWARDS_PER_EPOCH - 1) * base_reward - get_proposer_reward(state, index)) + if index not in matching_target_attesting_indices: + effective_balance = state.validators[index].effective_balance + penalties[index] += Gwei(effective_balance * get_finality_delay(state) // INACTIVITY_PENALTY_QUOTIENT) + + # No rewards associated with inactivity penalties + rewards = [Gwei(0) for _ in range(len(state.validators))] + return rewards, penalties +``` + #### Final updates +*Note*: The function `process_final_updates` is modified to handle sync committee updates. + ```python def process_final_updates(state: BeaconState) -> None: phase0.process_final_updates(state) diff --git a/specs/lightclient/lightclient-fork.md b/specs/lightclient/lightclient-fork.md new file mode 100644 index 000000000..bb67fa54a --- /dev/null +++ b/specs/lightclient/lightclient-fork.md @@ -0,0 +1,83 @@ +# Ethereum 2.0 Light Client Support -- From Phase 0 to Light Client Patch + +**Notice**: This document is a work-in-progress for researchers and implementers. + +## Table of contents + + + + +- [Introduction](#introduction) +- [Configuration](#configuration) +- [Fork to Light-client patch](#fork-to-light-client-patch) + - [Fork trigger](#fork-trigger) + - [Upgrading the state](#upgrading-the-state) + + + +## Introduction + +This document describes the process of moving from Phase 0 to Phase 1 of Ethereum 2.0. + +## Configuration + +Warning: this configuration is not definitive. + +| Name | Value | +| - | - | +| `LIGHTCLIENT_PATCH_FORK_VERSION` | `Version('0x01000000')` | +| `LIGHTCLIENT_PATCH_FORK_SLOT` | `Slot(0)` **TBD** | + +## Fork to Light-client patch + +### 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 slot `LIGHTCLIENT_PATCH_FORK_SLOT`, where `LIGHTCLIENT_PATCH_FORK_SLOT % SLOTS_PER_EPOCH == 0`. + +### Upgrading the state + +After `process_slots` of Phase 0 finishes, if `state.slot == LIGHTCLIENT_PATCH_FORK_SLOT`, an irregular state change is made to upgrade to light-client patch. + +```python +def upgrade_to_lightclient_patch(pre: phase0.BeaconState) -> BeaconState: + epoch = get_current_epoch(pre) + post = BeaconState( + genesis_time=pre.genesis_time, + slot=pre.slot, + fork=Fork( + previous_version=pre.fork.current_version, + current_version=LIGHTCLIENT_PATCH_FORK_VERSION, + epoch=epoch, + ), + # History + latest_block_header=pre.latest_block_header, + block_roots=pre.block_roots, + state_roots=pre.state_roots, + historical_roots=pre.historical_roots, + # Eth1 + eth1_data=pre.eth1_data, + eth1_data_votes=pre.eth1_data_votes, + eth1_deposit_index=pre.eth1_deposit_index, + # Registry + validators=pre.validators, + balances=pre.balances, + # Randomness + randao_mixes=pre.randao_mixes, + # Slashings + slashings=pre.slashings, + # Attestations + # previous_epoch_attestations is cleared on upgrade. + previous_epoch_attestations=List[PendingAttestation, MAX_ATTESTATIONS * SLOTS_PER_EPOCH](), + # empty in pre state, since the upgrade is performed just after an epoch boundary. + current_epoch_attestations=List[PendingAttestation, MAX_ATTESTATIONS * SLOTS_PER_EPOCH](), + # Finality + justification_bits=pre.justification_bits, + previous_justified_checkpoint=pre.previous_justified_checkpoint, + current_justified_checkpoint=pre.current_justified_checkpoint, + finalized_checkpoint=pre.finalized_checkpoint, + # Light-client + current_sync_committee=SyncCommittee(), + next_sync_committee=SyncCommittee(), + ) + return post +``` diff --git a/specs/lightclient/sync-protocol.md b/specs/lightclient/sync-protocol.md index 84b6f6fd7..310aad2df 100644 --- a/specs/lightclient/sync-protocol.md +++ b/specs/lightclient/sync-protocol.md @@ -8,7 +8,6 @@ - - [Introduction](#introduction) - [Constants](#constants) - [Configuration](#configuration) @@ -100,7 +99,7 @@ class LightClientStore(Container): ## 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 currect 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. #### `is_valid_light_client_update` diff --git a/specs/phase0/validator.md b/specs/phase0/validator.md index cb45e65e8..97b1acd92 100644 --- a/specs/phase0/validator.md +++ b/specs/phase0/validator.md @@ -327,7 +327,9 @@ def get_eth1_vote(state: BeaconState, eth1_chain: Sequence[Eth1Block]) -> Eth1Da valid_votes = [vote for vote in state.eth1_data_votes if vote in votes_to_consider] # Default vote on latest eth1 block data in the period range unless eth1 chain is not live - default_vote = votes_to_consider[len(votes_to_consider) - 1] if any(votes_to_consider) else state.eth1_data + # Non-substantive casting for linter + state_eth1_data: Eth1Data = state.eth1_data + default_vote = votes_to_consider[len(votes_to_consider) - 1] if any(votes_to_consider) else state_eth1_data return max( valid_votes, diff --git a/tests/core/pyspec/eth2spec/test/context.py b/tests/core/pyspec/eth2spec/test/context.py index cfd6724ed..d19547477 100644 --- a/tests/core/pyspec/eth2spec/test/context.py +++ b/tests/core/pyspec/eth2spec/test/context.py @@ -2,6 +2,7 @@ import pytest from eth2spec.phase0 import spec as spec_phase0 from eth2spec.phase1 import spec as spec_phase1 +from eth2spec.lightclient_patch import spec as spec_lightclient_patch from eth2spec.utils import bls from .exceptions import SkippedTest @@ -19,6 +20,7 @@ from importlib import reload def reload_specs(): reload(spec_phase0) reload(spec_phase1) + reload(spec_lightclient_patch) # Some of the Spec module functionality is exposed here to deal with phase-specific changes. @@ -28,7 +30,9 @@ ConfigName = NewType("ConfigName", str) PHASE0 = SpecForkName('phase0') PHASE1 = SpecForkName('phase1') -ALL_PHASES = (PHASE0, PHASE1) +LIGHTCLIENT_PATCH = SpecForkName('lightclient_patch') + +ALL_PHASES = (PHASE0, PHASE1, LIGHTCLIENT_PATCH) MAINNET = ConfigName('mainnet') MINIMAL = ConfigName('minimal') @@ -47,14 +51,18 @@ class SpecPhase0(Spec): class SpecPhase1(Spec): - def upgrade_to_phase1(self, state: spec_phase0.BeaconState) -> spec_phase1.BeaconState: - ... + ... + + +class SpecLightclient(Spec): + ... # add transfer, bridge, etc. as the spec evolves class SpecForks(TypedDict, total=False): PHASE0: SpecPhase0 PHASE1: SpecPhase1 + LIGHTCLIENT_PATCH: SpecLightclient def _prepare_state(balances_fn: Callable[[Any], Sequence[int]], threshold_fn: Callable[[Any], int], @@ -70,6 +78,8 @@ def _prepare_state(balances_fn: Callable[[Any], Sequence[int]], threshold_fn: Ca # TODO: instead of upgrading a test phase0 genesis state we can also write a phase1 state helper. # Decide based on performance/consistency results later. state = phases[PHASE1].upgrade_to_phase1(state) + elif spec.fork == LIGHTCLIENT_PATCH: # not generalizing this just yet, unclear final spec fork/patch order. + state = phases[LIGHTCLIENT_PATCH].upgrade_to_lightclient_patch(state) return state @@ -337,12 +347,16 @@ def with_phases(phases, other_phases=None): phase_dir[PHASE0] = spec_phase0 if PHASE1 in available_phases: phase_dir[PHASE1] = spec_phase1 + if LIGHTCLIENT_PATCH in available_phases: + phase_dir[LIGHTCLIENT_PATCH] = spec_lightclient_patch # return is ignored whenever multiple phases are ran. If if PHASE0 in run_phases: ret = fn(spec=spec_phase0, phases=phase_dir, *args, **kw) if PHASE1 in run_phases: ret = fn(spec=spec_phase1, phases=phase_dir, *args, **kw) + if LIGHTCLIENT_PATCH in run_phases: + ret = fn(spec=spec_lightclient_patch, phases=phase_dir, *args, **kw) return ret return wrapper return decorator diff --git a/tests/core/pyspec/eth2spec/test/helpers/block.py b/tests/core/pyspec/eth2spec/test/helpers/block.py index 69cb59021..c4d8f1931 100644 --- a/tests/core/pyspec/eth2spec/test/helpers/block.py +++ b/tests/core/pyspec/eth2spec/test/helpers/block.py @@ -1,3 +1,4 @@ +from eth2spec.test.context import LIGHTCLIENT_PATCH from eth2spec.test.helpers.keys import privkeys from eth2spec.utils import bls from eth2spec.utils.bls import only_with_bls @@ -89,6 +90,10 @@ def build_empty_block(spec, state, slot=None): empty_block.proposer_index = spec.get_beacon_proposer_index(state) empty_block.body.eth1_data.deposit_count = state.eth1_deposit_index empty_block.parent_root = parent_block_root + + if spec.fork == LIGHTCLIENT_PATCH: + empty_block.body.sync_committee_signature = spec.G2_POINT_AT_INFINITY + apply_randao_reveal(spec, state, empty_block) return empty_block diff --git a/tests/core/pyspec/eth2spec/test/helpers/rewards.py b/tests/core/pyspec/eth2spec/test/helpers/rewards.py index c11ba1ec1..dc355972f 100644 --- a/tests/core/pyspec/eth2spec/test/helpers/rewards.py +++ b/tests/core/pyspec/eth2spec/test/helpers/rewards.py @@ -2,6 +2,7 @@ from random import Random from lru import LRU from eth2spec.phase0 import spec as spec_phase0 +from eth2spec.test.context import LIGHTCLIENT_PATCH from eth2spec.test.helpers.attestations import cached_prepare_state_with_attestations from eth2spec.test.helpers.deposits import mock_deposit from eth2spec.test.helpers.state import next_epoch @@ -159,8 +160,12 @@ def run_get_inactivity_penalty_deltas(spec, state): continue if spec.is_in_inactivity_leak(state): + if spec.fork == LIGHTCLIENT_PATCH: + cancel_base_rewards_per_epoch = spec.BASE_REWARDS_PER_EPOCH - 1 + else: + cancel_base_rewards_per_epoch = spec.BASE_REWARDS_PER_EPOCH base_reward = spec.get_base_reward(state, index) - base_penalty = spec.BASE_REWARDS_PER_EPOCH * base_reward - spec.get_proposer_reward(state, index) + base_penalty = cancel_base_rewards_per_epoch * base_reward - spec.get_proposer_reward(state, index) if not has_enough_for_reward(spec, state, index): assert penalties[index] == 0 elif index in matching_attesting_indices: diff --git a/tests/core/pyspec/eth2spec/test/phase0/epoch_processing/test_process_rewards_and_penalties.py b/tests/core/pyspec/eth2spec/test/phase0/epoch_processing/test_process_rewards_and_penalties.py index 661a00014..dd3cbb791 100644 --- a/tests/core/pyspec/eth2spec/test/phase0/epoch_processing/test_process_rewards_and_penalties.py +++ b/tests/core/pyspec/eth2spec/test/phase0/epoch_processing/test_process_rewards_and_penalties.py @@ -1,4 +1,5 @@ from eth2spec.test.context import ( + LIGHTCLIENT_PATCH, spec_state_test, spec_test, with_all_phases, single_phase, with_phases, PHASE0, @@ -162,6 +163,9 @@ def run_with_participation(spec, state, participation_fn): pre_state = state.copy() + if spec.fork == LIGHTCLIENT_PATCH: + sync_committee_indices = spec.get_sync_committee_indices(state, spec.get_current_epoch(state)) + yield from run_process_rewards_and_penalties(spec, state) attesting_indices = spec.get_unslashed_attesting_indices(state, attestations) @@ -172,9 +176,13 @@ def run_with_participation(spec, state, participation_fn): # Proposers can still make money during a leak if index in proposer_indices and index in participated: assert state.balances[index] > pre_state.balances[index] - # If not proposer but participated optimally, should have exactly neutral balance elif index in attesting_indices: - assert state.balances[index] == pre_state.balances[index] + if spec.fork == LIGHTCLIENT_PATCH and index in sync_committee_indices: + # The sync committee reward has not been canceled, so the sync committee participants still earn it + assert state.balances[index] >= pre_state.balances[index] + else: + # If not proposer but participated optimally, should have exactly neutral balance + assert state.balances[index] == pre_state.balances[index] else: assert state.balances[index] < pre_state.balances[index] else: 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 63b0572b1..05f0fb051 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,4 +1,4 @@ -from eth2spec.test.context import PHASE0, with_all_phases, spec_state_test +from eth2spec.test.context import PHASE0, PHASE1, LIGHTCLIENT_PATCH, 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.state import transition_to, state_transition_and_sign_block, next_epoch, next_slot @@ -18,12 +18,12 @@ 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 == PHASE0: + if spec.fork in (PHASE0, LIGHTCLIENT_PATCH): latest_message = spec.LatestMessage( epoch=attestation.data.target.epoch, root=attestation.data.beacon_block_root, ) - else: + elif spec.fork == PHASE1: latest_message = spec.LatestMessage( epoch=attestation.data.target.epoch, root=attestation.data.beacon_block_root, diff --git a/tests/core/pyspec/eth2spec/test/phase1/block_processing/test_process_attestation.py b/tests/core/pyspec/eth2spec/test/phase1/block_processing/test_process_attestation.py index a0cf7472f..5b2f952ae 100644 --- a/tests/core/pyspec/eth2spec/test/phase1/block_processing/test_process_attestation.py +++ b/tests/core/pyspec/eth2spec/test/phase1/block_processing/test_process_attestation.py @@ -1,5 +1,6 @@ from eth2spec.test.context import ( PHASE0, + LIGHTCLIENT_PATCH, with_all_phases_except, spec_state_test, always_bls, @@ -12,7 +13,7 @@ from eth2spec.test.helpers.attestations import ( ) -@with_all_phases_except([PHASE0]) +@with_all_phases_except([PHASE0, LIGHTCLIENT_PATCH]) @spec_state_test @always_bls def test_on_time_success(spec, state): @@ -23,7 +24,7 @@ def test_on_time_success(spec, state): yield from run_attestation_processing(spec, state, attestation) -@with_all_phases_except([PHASE0]) +@with_all_phases_except([PHASE0, LIGHTCLIENT_PATCH]) @spec_state_test @always_bls def test_late_success(spec, state): diff --git a/tests/core/pyspec/eth2spec/test/phase1/block_processing/test_process_chunk_challenge.py b/tests/core/pyspec/eth2spec/test/phase1/block_processing/test_process_chunk_challenge.py index e916010b2..27829e4a0 100644 --- a/tests/core/pyspec/eth2spec/test/phase1/block_processing/test_process_chunk_challenge.py +++ b/tests/core/pyspec/eth2spec/test/phase1/block_processing/test_process_chunk_challenge.py @@ -9,6 +9,7 @@ from eth2spec.test.helpers.attestations import ( from eth2spec.test.helpers.state import transition_to, transition_to_valid_shard_slot from eth2spec.test.context import ( PHASE0, + LIGHTCLIENT_PATCH, MINIMAL, expect_assertion_error, disable_process_reveal_deadlines, @@ -68,7 +69,7 @@ def run_custody_chunk_response_processing(spec, state, custody_response, valid=T yield 'post', state -@with_all_phases_except([PHASE0]) +@with_all_phases_except([PHASE0, LIGHTCLIENT_PATCH]) @spec_state_test @with_configs([MINIMAL], reason="too slow") @disable_process_reveal_deadlines @@ -92,7 +93,7 @@ def test_challenge_appended(spec, state): yield from run_chunk_challenge_processing(spec, state, challenge) -@with_all_phases_except([PHASE0]) +@with_all_phases_except([PHASE0, LIGHTCLIENT_PATCH]) @spec_state_test @disable_process_reveal_deadlines @with_configs([MINIMAL], reason="too slow") @@ -118,7 +119,7 @@ def test_challenge_empty_element_replaced(spec, state): yield from run_chunk_challenge_processing(spec, state, challenge) -@with_all_phases_except([PHASE0]) +@with_all_phases_except([PHASE0, LIGHTCLIENT_PATCH]) @spec_state_test @disable_process_reveal_deadlines @with_configs([MINIMAL], reason="too slow") @@ -144,7 +145,7 @@ def test_duplicate_challenge(spec, state): yield from run_chunk_challenge_processing(spec, state, challenge, valid=False) -@with_all_phases_except([PHASE0]) +@with_all_phases_except([PHASE0, LIGHTCLIENT_PATCH]) @spec_state_test @disable_process_reveal_deadlines @with_configs([MINIMAL], reason="too slow") @@ -172,7 +173,7 @@ def test_second_challenge(spec, state): yield from run_chunk_challenge_processing(spec, state, challenge1) -@with_all_phases_except([PHASE0]) +@with_all_phases_except([PHASE0, LIGHTCLIENT_PATCH]) @spec_state_test @disable_process_reveal_deadlines @with_configs([MINIMAL], reason="too slow") @@ -197,7 +198,7 @@ def test_multiple_epochs_custody(spec, state): yield from run_chunk_challenge_processing(spec, state, challenge) -@with_all_phases_except([PHASE0]) +@with_all_phases_except([PHASE0, LIGHTCLIENT_PATCH]) @spec_state_test @disable_process_reveal_deadlines @with_configs([MINIMAL], reason="too slow") @@ -222,7 +223,7 @@ def test_many_epochs_custody(spec, state): yield from run_chunk_challenge_processing(spec, state, challenge) -@with_all_phases_except([PHASE0]) +@with_all_phases_except([PHASE0, LIGHTCLIENT_PATCH]) @spec_state_test @disable_process_reveal_deadlines @with_configs([MINIMAL], reason="too slow") @@ -243,7 +244,7 @@ def test_off_chain_attestation(spec, state): yield from run_chunk_challenge_processing(spec, state, challenge) -@with_all_phases_except([PHASE0]) +@with_all_phases_except([PHASE0, LIGHTCLIENT_PATCH]) @spec_state_test @disable_process_reveal_deadlines @with_configs([MINIMAL], reason="too slow") @@ -275,7 +276,7 @@ def test_custody_response(spec, state): yield from run_custody_chunk_response_processing(spec, state, custody_response) -@with_all_phases_except([PHASE0]) +@with_all_phases_except([PHASE0, LIGHTCLIENT_PATCH]) @spec_state_test @disable_process_reveal_deadlines @with_configs([MINIMAL], reason="too slow") @@ -306,7 +307,7 @@ def test_custody_response_chunk_index_2(spec, state): yield from run_custody_chunk_response_processing(spec, state, custody_response) -@with_all_phases_except([PHASE0]) +@with_all_phases_except([PHASE0, LIGHTCLIENT_PATCH]) @spec_state_test @disable_process_reveal_deadlines @with_configs([MINIMAL], reason="too slow") @@ -338,7 +339,7 @@ def test_custody_response_multiple_epochs(spec, state): yield from run_custody_chunk_response_processing(spec, state, custody_response) -@with_all_phases_except([PHASE0]) +@with_all_phases_except([PHASE0, LIGHTCLIENT_PATCH]) @spec_state_test @disable_process_reveal_deadlines @with_configs([MINIMAL], reason="too slow") diff --git a/tests/core/pyspec/eth2spec/test/phase1/block_processing/test_process_custody_key_reveal.py b/tests/core/pyspec/eth2spec/test/phase1/block_processing/test_process_custody_key_reveal.py index cb96c97e1..00a6112bf 100644 --- a/tests/core/pyspec/eth2spec/test/phase1/block_processing/test_process_custody_key_reveal.py +++ b/tests/core/pyspec/eth2spec/test/phase1/block_processing/test_process_custody_key_reveal.py @@ -1,6 +1,7 @@ from eth2spec.test.helpers.custody import get_valid_custody_key_reveal from eth2spec.test.context import ( PHASE0, + LIGHTCLIENT_PATCH, with_all_phases_except, spec_state_test, expect_assertion_error, @@ -39,7 +40,7 @@ def run_custody_key_reveal_processing(spec, state, custody_key_reveal, valid=Tru yield 'post', state -@with_all_phases_except([PHASE0]) +@with_all_phases_except([PHASE0, LIGHTCLIENT_PATCH]) @spec_state_test @always_bls def test_success(spec, state): @@ -49,7 +50,7 @@ def test_success(spec, state): yield from run_custody_key_reveal_processing(spec, state, custody_key_reveal) -@with_all_phases_except([PHASE0]) +@with_all_phases_except([PHASE0, LIGHTCLIENT_PATCH]) @spec_state_test @always_bls def test_reveal_too_early(spec, state): @@ -58,7 +59,7 @@ def test_reveal_too_early(spec, state): yield from run_custody_key_reveal_processing(spec, state, custody_key_reveal, False) -@with_all_phases_except([PHASE0]) +@with_all_phases_except([PHASE0, LIGHTCLIENT_PATCH]) @spec_state_test @always_bls def test_wrong_period(spec, state): @@ -67,7 +68,7 @@ def test_wrong_period(spec, state): yield from run_custody_key_reveal_processing(spec, state, custody_key_reveal, False) -@with_all_phases_except([PHASE0]) +@with_all_phases_except([PHASE0, LIGHTCLIENT_PATCH]) @spec_state_test @always_bls def test_late_reveal(spec, state): @@ -77,7 +78,7 @@ def test_late_reveal(spec, state): yield from run_custody_key_reveal_processing(spec, state, custody_key_reveal) -@with_all_phases_except([PHASE0]) +@with_all_phases_except([PHASE0, LIGHTCLIENT_PATCH]) @spec_state_test @always_bls def test_double_reveal(spec, state): diff --git a/tests/core/pyspec/eth2spec/test/phase1/block_processing/test_process_custody_slashing.py b/tests/core/pyspec/eth2spec/test/phase1/block_processing/test_process_custody_slashing.py index fc7efa5bc..1f46bcf05 100644 --- a/tests/core/pyspec/eth2spec/test/phase1/block_processing/test_process_custody_slashing.py +++ b/tests/core/pyspec/eth2spec/test/phase1/block_processing/test_process_custody_slashing.py @@ -11,6 +11,7 @@ from eth2spec.test.helpers.state import get_balance, transition_to from eth2spec.test.context import ( PHASE0, MINIMAL, + LIGHTCLIENT_PATCH, with_all_phases_except, spec_state_test, expect_assertion_error, @@ -112,7 +113,7 @@ def run_standard_custody_slashing_test(spec, yield from run_custody_slashing_processing(spec, state, slashing, valid=valid, correct=correct) -@with_all_phases_except([PHASE0]) +@with_all_phases_except([PHASE0, LIGHTCLIENT_PATCH]) @spec_state_test @disable_process_reveal_deadlines @with_configs([MINIMAL], reason="too slow") @@ -120,7 +121,7 @@ def test_custody_slashing(spec, state): yield from run_standard_custody_slashing_test(spec, state) -@with_all_phases_except([PHASE0]) +@with_all_phases_except([PHASE0, LIGHTCLIENT_PATCH]) @spec_state_test @disable_process_reveal_deadlines @with_configs([MINIMAL], reason="too slow") @@ -128,7 +129,7 @@ def test_incorrect_custody_slashing(spec, state): yield from run_standard_custody_slashing_test(spec, state, correct=False) -@with_all_phases_except([PHASE0]) +@with_all_phases_except([PHASE0, LIGHTCLIENT_PATCH]) @spec_state_test @disable_process_reveal_deadlines @with_configs([MINIMAL], reason="too slow") @@ -136,7 +137,7 @@ def test_multiple_epochs_custody(spec, state): yield from run_standard_custody_slashing_test(spec, state, shard_lateness=spec.SLOTS_PER_EPOCH * 3) -@with_all_phases_except([PHASE0]) +@with_all_phases_except([PHASE0, LIGHTCLIENT_PATCH]) @spec_state_test @disable_process_reveal_deadlines @with_configs([MINIMAL], reason="too slow") @@ -144,7 +145,7 @@ def test_many_epochs_custody(spec, state): yield from run_standard_custody_slashing_test(spec, state, shard_lateness=spec.SLOTS_PER_EPOCH * 5) -@with_all_phases_except([PHASE0]) +@with_all_phases_except([PHASE0, LIGHTCLIENT_PATCH]) @spec_state_test @disable_process_reveal_deadlines @with_configs([MINIMAL], reason="too slow") diff --git a/tests/core/pyspec/eth2spec/test/phase1/block_processing/test_process_early_derived_secret_reveal.py b/tests/core/pyspec/eth2spec/test/phase1/block_processing/test_process_early_derived_secret_reveal.py index 668561261..3094f795b 100644 --- a/tests/core/pyspec/eth2spec/test/phase1/block_processing/test_process_early_derived_secret_reveal.py +++ b/tests/core/pyspec/eth2spec/test/phase1/block_processing/test_process_early_derived_secret_reveal.py @@ -2,6 +2,7 @@ from eth2spec.test.helpers.custody import get_valid_early_derived_secret_reveal from eth2spec.test.helpers.state import next_epoch_via_block, get_balance from eth2spec.test.context import ( PHASE0, + LIGHTCLIENT_PATCH, with_all_phases_except, spec_state_test, expect_assertion_error, @@ -41,7 +42,7 @@ def run_early_derived_secret_reveal_processing(spec, state, randao_key_reveal, v yield 'post', state -@with_all_phases_except([PHASE0]) +@with_all_phases_except([PHASE0, LIGHTCLIENT_PATCH]) @spec_state_test @always_bls def test_success(spec, state): @@ -50,7 +51,7 @@ def test_success(spec, state): yield from run_early_derived_secret_reveal_processing(spec, state, randao_key_reveal) -@with_all_phases_except([PHASE0]) +@with_all_phases_except([PHASE0, LIGHTCLIENT_PATCH]) @spec_state_test @never_bls def test_reveal_from_current_epoch(spec, state): @@ -59,7 +60,7 @@ def test_reveal_from_current_epoch(spec, state): yield from run_early_derived_secret_reveal_processing(spec, state, randao_key_reveal, False) -@with_all_phases_except([PHASE0]) +@with_all_phases_except([PHASE0, LIGHTCLIENT_PATCH]) @spec_state_test @never_bls def test_reveal_from_past_epoch(spec, state): @@ -69,7 +70,7 @@ def test_reveal_from_past_epoch(spec, state): yield from run_early_derived_secret_reveal_processing(spec, state, randao_key_reveal, False) -@with_all_phases_except([PHASE0]) +@with_all_phases_except([PHASE0, LIGHTCLIENT_PATCH]) @spec_state_test @always_bls def test_reveal_with_custody_padding(spec, state): @@ -81,7 +82,7 @@ def test_reveal_with_custody_padding(spec, state): yield from run_early_derived_secret_reveal_processing(spec, state, randao_key_reveal, True) -@with_all_phases_except([PHASE0]) +@with_all_phases_except([PHASE0, LIGHTCLIENT_PATCH]) @spec_state_test @always_bls def test_reveal_with_custody_padding_minus_one(spec, state): @@ -93,7 +94,7 @@ def test_reveal_with_custody_padding_minus_one(spec, state): yield from run_early_derived_secret_reveal_processing(spec, state, randao_key_reveal, True) -@with_all_phases_except([PHASE0]) +@with_all_phases_except([PHASE0, LIGHTCLIENT_PATCH]) @spec_state_test @never_bls def test_double_reveal(spec, state): @@ -114,7 +115,7 @@ def test_double_reveal(spec, state): yield from run_early_derived_secret_reveal_processing(spec, state, randao_key_reveal2, False) -@with_all_phases_except([PHASE0]) +@with_all_phases_except([PHASE0, LIGHTCLIENT_PATCH]) @spec_state_test @never_bls def test_revealer_is_slashed(spec, state): @@ -124,7 +125,7 @@ def test_revealer_is_slashed(spec, state): yield from run_early_derived_secret_reveal_processing(spec, state, randao_key_reveal, False) -@with_all_phases_except([PHASE0]) +@with_all_phases_except([PHASE0, LIGHTCLIENT_PATCH]) @spec_state_test @never_bls def test_far_future_epoch(spec, state): diff --git a/tests/core/pyspec/eth2spec/test/phase1/block_processing/test_process_shard_transition.py b/tests/core/pyspec/eth2spec/test/phase1/block_processing/test_process_shard_transition.py index b0a51557a..d2b7962b6 100644 --- a/tests/core/pyspec/eth2spec/test/phase1/block_processing/test_process_shard_transition.py +++ b/tests/core/pyspec/eth2spec/test/phase1/block_processing/test_process_shard_transition.py @@ -1,5 +1,6 @@ from eth2spec.test.context import ( PHASE0, + LIGHTCLIENT_PATCH, with_all_phases_except, only_full_crosslink, spec_state_test, @@ -90,21 +91,21 @@ def run_successful_crosslink_tests(spec, state, target_len_offset_slot): assert bool(pending_attestation.crosslink_success) is True -@with_all_phases_except([PHASE0]) +@with_all_phases_except([PHASE0, LIGHTCLIENT_PATCH]) @spec_state_test @only_full_crosslink def test_basic_crosslinks(spec, state): yield from run_successful_crosslink_tests(spec, state, target_len_offset_slot=1) -@with_all_phases_except([PHASE0]) +@with_all_phases_except([PHASE0, LIGHTCLIENT_PATCH]) @spec_state_test @only_full_crosslink def test_multiple_offset_slots(spec, state): yield from run_successful_crosslink_tests(spec, state, target_len_offset_slot=2) -@with_all_phases_except([PHASE0]) +@with_all_phases_except([PHASE0, LIGHTCLIENT_PATCH]) @spec_state_test @only_full_crosslink def test_no_winning_root(spec, state): @@ -152,7 +153,7 @@ def test_no_winning_root(spec, state): assert state.shard_states == pre_shard_states -@with_all_phases_except([PHASE0]) +@with_all_phases_except([PHASE0, LIGHTCLIENT_PATCH]) @spec_state_test @only_full_crosslink def test_wrong_shard_transition_root(spec, state): diff --git a/tests/core/pyspec/eth2spec/test/phase1/epoch_processing/test_process_challenge_deadlines.py b/tests/core/pyspec/eth2spec/test/phase1/epoch_processing/test_process_challenge_deadlines.py index e270ff615..0350324a5 100644 --- a/tests/core/pyspec/eth2spec/test/phase1/epoch_processing/test_process_challenge_deadlines.py +++ b/tests/core/pyspec/eth2spec/test/phase1/epoch_processing/test_process_challenge_deadlines.py @@ -8,6 +8,7 @@ from eth2spec.test.helpers.attestations import ( from eth2spec.test.helpers.state import transition_to, transition_to_valid_shard_slot from eth2spec.test.context import ( PHASE0, + LIGHTCLIENT_PATCH, MINIMAL, spec_state_test, with_all_phases_except, @@ -25,7 +26,7 @@ def run_process_challenge_deadlines(spec, state): yield from run_epoch_processing_with(spec, state, 'process_challenge_deadlines') -@with_all_phases_except([PHASE0]) +@with_all_phases_except([PHASE0, LIGHTCLIENT_PATCH]) @spec_state_test @with_configs([MINIMAL], reason="too slow") def test_validator_slashed_after_chunk_challenge(spec, state): diff --git a/tests/core/pyspec/eth2spec/test/phase1/epoch_processing/test_process_custody_final_updates.py b/tests/core/pyspec/eth2spec/test/phase1/epoch_processing/test_process_custody_final_updates.py index 0541411da..5994306d9 100644 --- a/tests/core/pyspec/eth2spec/test/phase1/epoch_processing/test_process_custody_final_updates.py +++ b/tests/core/pyspec/eth2spec/test/phase1/epoch_processing/test_process_custody_final_updates.py @@ -1,5 +1,6 @@ from eth2spec.test.context import ( PHASE0, + LIGHTCLIENT_PATCH, ) from eth2spec.test.helpers.custody import ( get_valid_chunk_challenge, @@ -29,7 +30,7 @@ def run_process_custody_final_updates(spec, state): yield from run_epoch_processing_with(spec, state, 'process_custody_final_updates') -@with_all_phases_except([PHASE0]) +@with_all_phases_except([PHASE0, LIGHTCLIENT_PATCH]) @spec_state_test def test_validator_withdrawal_delay(spec, state): transition_to_valid_shard_slot(spec, state) @@ -42,7 +43,7 @@ def test_validator_withdrawal_delay(spec, state): assert state.validators[0].withdrawable_epoch == spec.FAR_FUTURE_EPOCH -@with_all_phases_except([PHASE0]) +@with_all_phases_except([PHASE0, LIGHTCLIENT_PATCH]) @spec_state_test def test_validator_withdrawal_reenable_after_custody_reveal(spec, state): transition_to_valid_shard_slot(spec, state) @@ -67,7 +68,7 @@ def test_validator_withdrawal_reenable_after_custody_reveal(spec, state): assert state.validators[0].withdrawable_epoch < spec.FAR_FUTURE_EPOCH -@with_all_phases_except([PHASE0]) +@with_all_phases_except([PHASE0, LIGHTCLIENT_PATCH]) @spec_state_test def test_validator_withdrawal_suspend_after_chunk_challenge(spec, state): transition_to_valid_shard_slot(spec, state) @@ -116,7 +117,7 @@ def test_validator_withdrawal_suspend_after_chunk_challenge(spec, state): assert state.validators[validator_index].withdrawable_epoch == spec.FAR_FUTURE_EPOCH -@with_all_phases_except([PHASE0]) +@with_all_phases_except([PHASE0, LIGHTCLIENT_PATCH]) @spec_state_test def test_validator_withdrawal_resume_after_chunk_challenge_response(spec, state): transition_to_valid_shard_slot(spec, state) diff --git a/tests/core/pyspec/eth2spec/test/phase1/epoch_processing/test_process_reveal_deadlines.py b/tests/core/pyspec/eth2spec/test/phase1/epoch_processing/test_process_reveal_deadlines.py index 5777e184a..3c2060ba5 100644 --- a/tests/core/pyspec/eth2spec/test/phase1/epoch_processing/test_process_reveal_deadlines.py +++ b/tests/core/pyspec/eth2spec/test/phase1/epoch_processing/test_process_reveal_deadlines.py @@ -4,6 +4,7 @@ from eth2spec.test.helpers.custody import ( from eth2spec.test.helpers.state import transition_to from eth2spec.test.context import ( PHASE0, + LIGHTCLIENT_PATCH, MINIMAL, with_all_phases_except, with_configs, @@ -17,7 +18,7 @@ def run_process_challenge_deadlines(spec, state): yield from run_epoch_processing_with(spec, state, 'process_challenge_deadlines') -@with_all_phases_except([PHASE0]) +@with_all_phases_except([PHASE0, LIGHTCLIENT_PATCH]) @spec_state_test @with_configs([MINIMAL], reason="too slow") def test_validator_slashed_after_reveal_deadline(spec, state): @@ -37,7 +38,7 @@ def test_validator_slashed_after_reveal_deadline(spec, state): assert state.validators[0].slashed == 1 -@with_all_phases_except([PHASE0]) +@with_all_phases_except([PHASE0, LIGHTCLIENT_PATCH]) @spec_state_test @with_configs([MINIMAL], reason="too slow") def test_validator_not_slashed_after_reveal(spec, state): diff --git a/tests/core/pyspec/eth2spec/test/phase1/sanity/test_blocks.py b/tests/core/pyspec/eth2spec/test/phase1/sanity/test_blocks.py index 922b604ad..ba47adde9 100644 --- a/tests/core/pyspec/eth2spec/test/phase1/sanity/test_blocks.py +++ b/tests/core/pyspec/eth2spec/test/phase1/sanity/test_blocks.py @@ -1,7 +1,9 @@ from typing import Dict, Sequence from eth2spec.test.context import ( - PHASE0, MINIMAL, + PHASE0, + LIGHTCLIENT_PATCH, + MINIMAL, with_all_phases_except, spec_state_test, only_full_crosslink, @@ -98,7 +100,7 @@ def run_beacon_block_with_shard_blocks(spec, state, target_len_offset_slot, comm assert post_shard_state.gasprice > pre_gasprice -@with_all_phases_except([PHASE0]) +@with_all_phases_except([PHASE0, LIGHTCLIENT_PATCH]) @spec_state_test @only_full_crosslink def test_process_beacon_block_with_normal_shard_transition(spec, state): @@ -112,7 +114,7 @@ def test_process_beacon_block_with_normal_shard_transition(spec, state): yield from run_beacon_block_with_shard_blocks(spec, state, target_len_offset_slot, committee_index, shard) -@with_all_phases_except([PHASE0]) +@with_all_phases_except([PHASE0, LIGHTCLIENT_PATCH]) @spec_state_test @only_full_crosslink def test_process_beacon_block_with_empty_proposal_transition(spec, state): @@ -131,7 +133,7 @@ def test_process_beacon_block_with_empty_proposal_transition(spec, state): # -@with_all_phases_except([PHASE0]) +@with_all_phases_except([PHASE0, LIGHTCLIENT_PATCH]) @spec_state_test @only_full_crosslink def test_with_shard_transition_with_custody_challenge_and_response(spec, state): @@ -165,7 +167,7 @@ def test_with_shard_transition_with_custody_challenge_and_response(spec, state): yield from run_beacon_block(spec, state, block) -@with_all_phases_except([PHASE0]) +@with_all_phases_except([PHASE0, LIGHTCLIENT_PATCH]) @spec_state_test @with_configs([MINIMAL]) def test_custody_key_reveal(spec, state): @@ -179,7 +181,7 @@ def test_custody_key_reveal(spec, state): yield from run_beacon_block(spec, state, block) -@with_all_phases_except([PHASE0]) +@with_all_phases_except([PHASE0, LIGHTCLIENT_PATCH]) @spec_state_test def test_early_derived_secret_reveal(spec, state): transition_to_valid_shard_slot(spec, state) @@ -190,7 +192,7 @@ def test_early_derived_secret_reveal(spec, state): yield from run_beacon_block(spec, state, block) -@with_all_phases_except([PHASE0]) +@with_all_phases_except([PHASE0, LIGHTCLIENT_PATCH]) @spec_state_test @only_full_crosslink def test_custody_slashing(spec, state): diff --git a/tests/core/pyspec/eth2spec/test/phase1/sanity/test_shard_blocks.py b/tests/core/pyspec/eth2spec/test/phase1/sanity/test_shard_blocks.py index ab66314e5..1590d2a6e 100644 --- a/tests/core/pyspec/eth2spec/test/phase1/sanity/test_shard_blocks.py +++ b/tests/core/pyspec/eth2spec/test/phase1/sanity/test_shard_blocks.py @@ -1,5 +1,6 @@ from eth2spec.test.context import ( PHASE0, + LIGHTCLIENT_PATCH, always_bls, expect_assertion_error, spec_state_test, @@ -43,7 +44,7 @@ def run_shard_blocks(spec, shard_state, signed_shard_block, beacon_parent_state, shard_state.latest_block_root == pre_shard_state.latest_block_root -@with_all_phases_except([PHASE0]) +@with_all_phases_except([PHASE0, LIGHTCLIENT_PATCH]) @spec_state_test @always_bls @only_full_crosslink @@ -63,7 +64,7 @@ def test_valid_shard_block(spec, state): # -@with_all_phases_except([PHASE0]) +@with_all_phases_except([PHASE0, LIGHTCLIENT_PATCH]) @spec_state_test @only_full_crosslink def test_invalid_shard_parent_root(spec, state): @@ -79,7 +80,7 @@ def test_invalid_shard_parent_root(spec, state): yield from run_shard_blocks(spec, shard_state, signed_shard_block, beacon_state, valid=False) -@with_all_phases_except([PHASE0]) +@with_all_phases_except([PHASE0, LIGHTCLIENT_PATCH]) @spec_state_test @only_full_crosslink def test_invalid_beacon_parent_root(spec, state): @@ -94,7 +95,7 @@ def test_invalid_beacon_parent_root(spec, state): yield from run_shard_blocks(spec, shard_state, signed_shard_block, beacon_state, valid=False) -@with_all_phases_except([PHASE0]) +@with_all_phases_except([PHASE0, LIGHTCLIENT_PATCH]) @spec_state_test @only_full_crosslink def test_invalid_slot(spec, state): @@ -110,7 +111,7 @@ def test_invalid_slot(spec, state): yield from run_shard_blocks(spec, shard_state, signed_shard_block, beacon_state, valid=False) -@with_all_phases_except([PHASE0]) +@with_all_phases_except([PHASE0, LIGHTCLIENT_PATCH]) @spec_state_test @only_full_crosslink def test_invalid_proposer_index(spec, state): @@ -130,7 +131,7 @@ def test_invalid_proposer_index(spec, state): yield from run_shard_blocks(spec, shard_state, signed_shard_block, beacon_state, valid=False) -@with_all_phases_except([PHASE0]) +@with_all_phases_except([PHASE0, LIGHTCLIENT_PATCH]) @spec_state_test @always_bls @only_full_crosslink @@ -151,7 +152,7 @@ def test_out_of_bound_offset(spec, state): yield from run_shard_blocks(spec, shard_state, signed_shard_block, beacon_state, valid=False) -@with_all_phases_except([PHASE0]) +@with_all_phases_except([PHASE0, LIGHTCLIENT_PATCH]) @spec_state_test @always_bls @only_full_crosslink @@ -170,7 +171,7 @@ def test_invalid_offset(spec, state): yield from run_shard_blocks(spec, shard_state, signed_shard_block, beacon_state, valid=False) -@with_all_phases_except([PHASE0]) +@with_all_phases_except([PHASE0, LIGHTCLIENT_PATCH]) @spec_state_test @always_bls @only_full_crosslink @@ -189,7 +190,7 @@ def test_empty_block_body(spec, state): # -@with_all_phases_except([PHASE0]) +@with_all_phases_except([PHASE0, LIGHTCLIENT_PATCH]) @spec_state_test @always_bls @only_full_crosslink @@ -208,7 +209,7 @@ def test_invalid_signature(spec, state): # -@with_all_phases_except([PHASE0]) +@with_all_phases_except([PHASE0, LIGHTCLIENT_PATCH]) @spec_state_test @always_bls @only_full_crosslink @@ -225,7 +226,7 @@ def test_max_offset(spec, state): yield from run_shard_blocks(spec, shard_state, signed_shard_block, beacon_state) -@with_all_phases_except([PHASE0]) +@with_all_phases_except([PHASE0, LIGHTCLIENT_PATCH]) @spec_state_test @always_bls @only_full_crosslink diff --git a/tests/core/pyspec/eth2spec/test/phase1/unittests/fork_choice/test_on_shard_block.py b/tests/core/pyspec/eth2spec/test/phase1/unittests/fork_choice/test_on_shard_block.py index 30eaa8d80..66d254ed1 100644 --- a/tests/core/pyspec/eth2spec/test/phase1/unittests/fork_choice/test_on_shard_block.py +++ b/tests/core/pyspec/eth2spec/test/phase1/unittests/fork_choice/test_on_shard_block.py @@ -1,6 +1,13 @@ from eth2spec.utils.ssz.ssz_impl import hash_tree_root -from eth2spec.test.context import PHASE0, spec_state_test, with_all_phases_except, never_bls, only_full_crosslink +from eth2spec.test.context import ( + PHASE0, + LIGHTCLIENT_PATCH, + spec_state_test, + with_all_phases_except, + never_bls, + only_full_crosslink, +) from eth2spec.test.helpers.attestations import get_valid_on_time_attestation from eth2spec.test.helpers.shard_block import ( build_shard_block, @@ -145,7 +152,7 @@ def create_and_apply_beacon_and_shard_blocks(spec, state, store, shard, shard_bl return has_shard_committee -@with_all_phases_except([PHASE0]) +@with_all_phases_except([PHASE0, LIGHTCLIENT_PATCH]) @spec_state_test @never_bls # Set to never_bls for testing `check_pending_shard_blocks` def test_basic(spec, state): @@ -206,7 +213,7 @@ def create_simple_fork(spec, state, store, shard): return head_block, forking_block -@with_all_phases_except([PHASE0]) +@with_all_phases_except([PHASE0, LIGHTCLIENT_PATCH]) @spec_state_test @only_full_crosslink def test_shard_simple_fork(spec, state): @@ -231,7 +238,7 @@ def test_shard_simple_fork(spec, state): assert spec.get_shard_head(store, shard) == forking_block.message.hash_tree_root() -@with_all_phases_except([PHASE0]) +@with_all_phases_except([PHASE0, LIGHTCLIENT_PATCH]) @spec_state_test @only_full_crosslink def test_shard_latest_messages_for_different_shards(spec, state): diff --git a/tests/core/pyspec/eth2spec/test/phase1/unittests/test_get_start_shard.py b/tests/core/pyspec/eth2spec/test/phase1/unittests/test_get_start_shard.py index a802d6c3c..030357655 100644 --- a/tests/core/pyspec/eth2spec/test/phase1/unittests/test_get_start_shard.py +++ b/tests/core/pyspec/eth2spec/test/phase1/unittests/test_get_start_shard.py @@ -1,12 +1,13 @@ from eth2spec.test.context import ( PHASE0, + LIGHTCLIENT_PATCH, with_all_phases_except, spec_state_test, ) from eth2spec.test.helpers.state import next_epoch -@with_all_phases_except([PHASE0]) +@with_all_phases_except([PHASE0, LIGHTCLIENT_PATCH]) @spec_state_test def test_get_committee_count_delta(spec, state): assert spec.get_committee_count_delta(state, 0, 0) == 0 @@ -23,7 +24,7 @@ def test_get_committee_count_delta(spec, state): ) -@with_all_phases_except([PHASE0]) +@with_all_phases_except([PHASE0, LIGHTCLIENT_PATCH]) @spec_state_test def test_get_start_shard_current_epoch_start(spec, state): assert state.current_epoch_start_shard == 0 @@ -39,7 +40,7 @@ def test_get_start_shard_current_epoch_start(spec, state): assert start_shard == state.current_epoch_start_shard -@with_all_phases_except([PHASE0]) +@with_all_phases_except([PHASE0, LIGHTCLIENT_PATCH]) @spec_state_test def test_get_start_shard_next_slot(spec, state): next_epoch(spec, state) @@ -57,7 +58,7 @@ def test_get_start_shard_next_slot(spec, state): assert start_shard == expected_start_shard -@with_all_phases_except([PHASE0]) +@with_all_phases_except([PHASE0, LIGHTCLIENT_PATCH]) @spec_state_test def test_get_start_shard_previous_slot(spec, state): next_epoch(spec, state) @@ -76,7 +77,7 @@ def test_get_start_shard_previous_slot(spec, state): assert start_shard == expected_start_shard -@with_all_phases_except([PHASE0]) +@with_all_phases_except([PHASE0, LIGHTCLIENT_PATCH]) @spec_state_test def test_get_start_shard_far_past_epoch(spec, state): initial_epoch = spec.get_current_epoch(state) From e63c96416a16109a7984603cb46dbef506c4f1a9 Mon Sep 17 00:00:00 2001 From: Hsiao-Wei Wang Date: Wed, 16 Dec 2020 15:10:54 +0800 Subject: [PATCH 30/32] Add a FIXME comment. --- specs/lightclient/beacon-chain.md | 1 + 1 file changed, 1 insertion(+) diff --git a/specs/lightclient/beacon-chain.md b/specs/lightclient/beacon-chain.md index 4eb1f24ab..eabcb3a86 100644 --- a/specs/lightclient/beacon-chain.md +++ b/specs/lightclient/beacon-chain.md @@ -240,6 +240,7 @@ def get_inactivity_penalty_deltas(state: BeaconState) -> Tuple[Sequence[Gwei], S ```python def process_final_updates(state: BeaconState) -> None: + # FIXME: unfold the full `process_final_updates` to avoid side effects. phase0.process_final_updates(state) next_epoch = get_current_epoch(state) + Epoch(1) if next_epoch % EPOCHS_PER_SYNC_COMMITTEE_PERIOD == 0: From cc9a4cdc46131b6d320eb4ac4bbd663db16bf917 Mon Sep 17 00:00:00 2001 From: Danny Ryan Date: Wed, 16 Dec 2020 17:12:51 -0700 Subject: [PATCH 31/32] add base sanity light client tests --- specs/lightclient/beacon-chain.md | 2 +- specs/lightclient/lightclient-fork.md | 6 +- .../lightclient_patch/sanity/test_blocks.py | 102 ++++++++++++++++++ 3 files changed, 106 insertions(+), 4 deletions(-) create mode 100644 tests/core/pyspec/eth2spec/test/lightclient_patch/sanity/test_blocks.py diff --git a/specs/lightclient/beacon-chain.md b/specs/lightclient/beacon-chain.md index eabcb3a86..3b03ad853 100644 --- a/specs/lightclient/beacon-chain.md +++ b/specs/lightclient/beacon-chain.md @@ -163,7 +163,7 @@ def get_sync_committee(state: BeaconState, epoch: Epoch) -> SyncCommittee: bls.AggregatePKs(pubkeys[i:i + SYNC_COMMITTEE_PUBKEY_AGGREGATES_SIZE]) for i in range(0, len(pubkeys), SYNC_COMMITTEE_PUBKEY_AGGREGATES_SIZE) ] - return SyncCommittee(pubkeys, aggregates) + return SyncCommittee(pubkeys=pubkeys, pubkey_aggregates=aggregates) ``` ### Block processing diff --git a/specs/lightclient/lightclient-fork.md b/specs/lightclient/lightclient-fork.md index bb67fa54a..568a9793b 100644 --- a/specs/lightclient/lightclient-fork.md +++ b/specs/lightclient/lightclient-fork.md @@ -75,9 +75,9 @@ def upgrade_to_lightclient_patch(pre: phase0.BeaconState) -> BeaconState: previous_justified_checkpoint=pre.previous_justified_checkpoint, current_justified_checkpoint=pre.current_justified_checkpoint, finalized_checkpoint=pre.finalized_checkpoint, - # Light-client - current_sync_committee=SyncCommittee(), - next_sync_committee=SyncCommittee(), ) + # Fill in sync committees + post.current_sync_committee = get_sync_committee(post, get_current_epoch(post)) + post.next_sync_committee = get_sync_committee(post, get_current_epoch(post) + EPOCHS_PER_SYNC_COMMITTEE_PERIOD) return post ``` diff --git a/tests/core/pyspec/eth2spec/test/lightclient_patch/sanity/test_blocks.py b/tests/core/pyspec/eth2spec/test/lightclient_patch/sanity/test_blocks.py new file mode 100644 index 000000000..df8e2545c --- /dev/null +++ b/tests/core/pyspec/eth2spec/test/lightclient_patch/sanity/test_blocks.py @@ -0,0 +1,102 @@ +import random +from eth2spec.test.helpers.keys import privkeys, pubkeys +from eth2spec.utils import bls +from eth2spec.test.helpers.state import ( + state_transition_and_sign_block, + next_epoch, +) +from eth2spec.test.helpers.block import ( + build_empty_block_for_next_slot, +) +from eth2spec.test.context import ( + PHASE0, PHASE1, + with_all_phases_except, + spec_state_test, +) + + +def compute_light_client_signature(spec, state, slot, privkey): + domain = spec.get_domain(state, spec.DOMAIN_SYNC_COMMITTEE, spec.compute_epoch_at_slot(slot)) + if slot == state.slot: + block_root = build_empty_block_for_next_slot(spec, state).parent_root + else: + block_root = spec.get_block_root_at_slot(state, slot) + signing_root = spec.compute_signing_root(block_root, domain) + return bls.Sign(privkey, signing_root) + + +def compute_aggregate_light_client_signature(spec, state, slot, participants): + if len(participants) == 0: + return spec.G2_POINT_AT_INFINITY + + signatures = [] + for validator_index in participants: + privkey = privkeys[validator_index] + signatures.append( + compute_light_client_signature( + spec, + state, + slot, + privkey, + ) + ) + return bls.Aggregate(signatures) + + +def run_light_client_sanity_test(spec, state, fraction_full=1.0): + committee = spec.get_sync_committee_indices(state, spec.get_current_epoch(state)) + participants = random.sample(committee, int(len(committee) * fraction_full)) + + yield 'pre', state + + block = build_empty_block_for_next_slot(spec, state) + block.body.sync_committee_bits = [index in participants for index in committee] + block.body.sync_committee_signature = compute_aggregate_light_client_signature( + spec, + state, + block.slot - 1, + participants, + ) + signed_block = state_transition_and_sign_block(spec, state, block) + + yield 'blocks', [signed_block] + yield 'post', state + + +@with_all_phases_except([PHASE0, PHASE1]) +@spec_state_test +def test_full_light_client_committee(spec, state): + next_epoch(spec, state) + yield from run_light_client_sanity_test(spec, state, fraction_full=1.0) + + +@with_all_phases_except([PHASE0, PHASE1]) +@spec_state_test +def test_half_light_client_committee(spec, state): + next_epoch(spec, state) + yield from run_light_client_sanity_test(spec, state, fraction_full=0.5) + + +@with_all_phases_except([PHASE0, PHASE1]) +@spec_state_test +def test_empty_light_client_committee(spec, state): + next_epoch(spec, state) + yield from run_light_client_sanity_test(spec, state, fraction_full=0.0) + + +@with_all_phases_except([PHASE0, PHASE1]) +@spec_state_test +def test_full_light_client_committee_genesis(spec, state): + yield from run_light_client_sanity_test(spec, state, fraction_full=1.0) + + +@with_all_phases_except([PHASE0, PHASE1]) +@spec_state_test +def test_half_light_client_committee_genesis(spec, state): + yield from run_light_client_sanity_test(spec, state, fraction_full=0.5) + + +@with_all_phases_except([PHASE0, PHASE1]) +@spec_state_test +def test_empty_light_client_committee_genesis(spec, state): + yield from run_light_client_sanity_test(spec, state, fraction_full=0.0) From 89c5ca6bcd2fa9a1f57d342038bb08effa969fe7 Mon Sep 17 00:00:00 2001 From: Danny Ryan Date: Thu, 17 Dec 2020 06:25:58 -0700 Subject: [PATCH 32/32] 'light_client' -> 'sync_committee' --- .../lightclient_patch/sanity/test_blocks.py | 36 +++++++++---------- 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/tests/core/pyspec/eth2spec/test/lightclient_patch/sanity/test_blocks.py b/tests/core/pyspec/eth2spec/test/lightclient_patch/sanity/test_blocks.py index df8e2545c..4fbdfc371 100644 --- a/tests/core/pyspec/eth2spec/test/lightclient_patch/sanity/test_blocks.py +++ b/tests/core/pyspec/eth2spec/test/lightclient_patch/sanity/test_blocks.py @@ -1,5 +1,5 @@ import random -from eth2spec.test.helpers.keys import privkeys, pubkeys +from eth2spec.test.helpers.keys import privkeys from eth2spec.utils import bls from eth2spec.test.helpers.state import ( state_transition_and_sign_block, @@ -15,7 +15,7 @@ from eth2spec.test.context import ( ) -def compute_light_client_signature(spec, state, slot, privkey): +def compute_sync_committee_signature(spec, state, slot, privkey): domain = spec.get_domain(state, spec.DOMAIN_SYNC_COMMITTEE, spec.compute_epoch_at_slot(slot)) if slot == state.slot: block_root = build_empty_block_for_next_slot(spec, state).parent_root @@ -25,7 +25,7 @@ def compute_light_client_signature(spec, state, slot, privkey): return bls.Sign(privkey, signing_root) -def compute_aggregate_light_client_signature(spec, state, slot, participants): +def compute_aggregate_sync_committee_signature(spec, state, slot, participants): if len(participants) == 0: return spec.G2_POINT_AT_INFINITY @@ -33,7 +33,7 @@ def compute_aggregate_light_client_signature(spec, state, slot, participants): for validator_index in participants: privkey = privkeys[validator_index] signatures.append( - compute_light_client_signature( + compute_sync_committee_signature( spec, state, slot, @@ -43,7 +43,7 @@ def compute_aggregate_light_client_signature(spec, state, slot, participants): return bls.Aggregate(signatures) -def run_light_client_sanity_test(spec, state, fraction_full=1.0): +def run_sync_committee_sanity_test(spec, state, fraction_full=1.0): committee = spec.get_sync_committee_indices(state, spec.get_current_epoch(state)) participants = random.sample(committee, int(len(committee) * fraction_full)) @@ -51,7 +51,7 @@ def run_light_client_sanity_test(spec, state, fraction_full=1.0): block = build_empty_block_for_next_slot(spec, state) block.body.sync_committee_bits = [index in participants for index in committee] - block.body.sync_committee_signature = compute_aggregate_light_client_signature( + block.body.sync_committee_signature = compute_aggregate_sync_committee_signature( spec, state, block.slot - 1, @@ -65,38 +65,38 @@ def run_light_client_sanity_test(spec, state, fraction_full=1.0): @with_all_phases_except([PHASE0, PHASE1]) @spec_state_test -def test_full_light_client_committee(spec, state): +def test_full_sync_committee_committee(spec, state): next_epoch(spec, state) - yield from run_light_client_sanity_test(spec, state, fraction_full=1.0) + yield from run_sync_committee_sanity_test(spec, state, fraction_full=1.0) @with_all_phases_except([PHASE0, PHASE1]) @spec_state_test -def test_half_light_client_committee(spec, state): +def test_half_sync_committee_committee(spec, state): next_epoch(spec, state) - yield from run_light_client_sanity_test(spec, state, fraction_full=0.5) + yield from run_sync_committee_sanity_test(spec, state, fraction_full=0.5) @with_all_phases_except([PHASE0, PHASE1]) @spec_state_test -def test_empty_light_client_committee(spec, state): +def test_empty_sync_committee_committee(spec, state): next_epoch(spec, state) - yield from run_light_client_sanity_test(spec, state, fraction_full=0.0) + yield from run_sync_committee_sanity_test(spec, state, fraction_full=0.0) @with_all_phases_except([PHASE0, PHASE1]) @spec_state_test -def test_full_light_client_committee_genesis(spec, state): - yield from run_light_client_sanity_test(spec, state, fraction_full=1.0) +def test_full_sync_committee_committee_genesis(spec, state): + yield from run_sync_committee_sanity_test(spec, state, fraction_full=1.0) @with_all_phases_except([PHASE0, PHASE1]) @spec_state_test -def test_half_light_client_committee_genesis(spec, state): - yield from run_light_client_sanity_test(spec, state, fraction_full=0.5) +def test_half_sync_committee_committee_genesis(spec, state): + yield from run_sync_committee_sanity_test(spec, state, fraction_full=0.5) @with_all_phases_except([PHASE0, PHASE1]) @spec_state_test -def test_empty_light_client_committee_genesis(spec, state): - yield from run_light_client_sanity_test(spec, state, fraction_full=0.0) +def test_empty_sync_committee_committee_genesis(spec, state): + yield from run_sync_committee_sanity_test(spec, state, fraction_full=0.0)