Skip to content

Commit

Permalink
executable light client patch: beacon-chain.md (ethereum#2141)
Browse files Browse the repository at this point in the history
* 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ć <[email protected]>

* 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 <[email protected]>
Co-authored-by: Justin <[email protected]>
Co-authored-by: Marin Petrunić <[email protected]>
  • Loading branch information
4 people authored Dec 15, 2020
1 parent 1a30ea9 commit acfe49e
Show file tree
Hide file tree
Showing 27 changed files with 340 additions and 114 deletions.
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
6 changes: 3 additions & 3 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -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); \
Expand All @@ -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); \
Expand Down
21 changes: 21 additions & 0 deletions configs/mainnet/lightclient_patch.yaml
Original file line number Diff line number Diff line change
@@ -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
21 changes: 21 additions & 0 deletions configs/minimal/lightclient_patch.yaml
Original file line number Diff line number Diff line change
@@ -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
11 changes: 6 additions & 5 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -173,7 +173,7 @@ def get_spec(file_name: str) -> SpecObject:
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
Expand Down Expand Up @@ -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,
}


Expand Down Expand Up @@ -453,15 +453,16 @@ def finalize_options(self):
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)

Expand Down Expand Up @@ -584,7 +585,7 @@ def run(self):
"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"
]
Expand Down
95 changes: 71 additions & 24 deletions specs/lightclient/beacon-chain.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,10 @@
<!-- START doctoc generated TOC please keep comment here to allow auto update -->
<!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE -->


- [Introduction](#introduction)
- [Constants](#constants)
- [Configuration](#configuration)
- [Constants](#constants-1)
- [Misc](#misc)
- [Time parameters](#time-parameters)
- [Domain types](#domain-types)
Expand All @@ -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)

<!-- END doctoc generated TOC please keep comment here to allow auto update -->
Expand All @@ -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 |
Expand All @@ -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
Expand All @@ -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`
Expand All @@ -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]
Expand Down Expand Up @@ -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)
Expand Down
83 changes: 83 additions & 0 deletions specs/lightclient/lightclient-fork.md
Original file line number Diff line number Diff line change
@@ -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

<!-- START doctoc generated TOC please keep comment here to allow auto update -->
<!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE -->

- [Introduction](#introduction)
- [Configuration](#configuration)
- [Fork to Light-client patch](#fork-to-light-client-patch)
- [Fork trigger](#fork-trigger)
- [Upgrading the state](#upgrading-the-state)

<!-- END doctoc generated TOC please keep comment here to allow auto update -->

## 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
```
Loading

0 comments on commit acfe49e

Please sign in to comment.