Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Enable light client data backfill by tracking best SyncAggregate #3614

Draft
wants to merge 8 commits into
base: dev
Choose a base branch
from
180 changes: 180 additions & 0 deletions specs/electra/beacon-chain.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,180 @@
# Electra -- The Beacon Chain

**Notice**: This document is a work-in-progress for researchers and implementers.

## Table of contents

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

- [Containers](#containers)
- [New containers](#new-containers)
- [`SyncData`](#syncdata)
- [Extended containers](#extended-containers)
- [`BeaconState`](#beaconstate)
- [Helper functions](#helper-functions)
- [`default_sync_data`](#default_sync_data)
- [Beacon chain state transition function](#beacon-chain-state-transition-function)
- [Epoch processing](#epoch-processing)
- [Modified `process_sync_committee_updates`](#modified-process_sync_committee_updates)
- [Block processing](#block-processing)
- [New `process_best_sync_data`](#new-process_best_sync_data)

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

## Containers

### New containers

#### `SyncData`

```python
class SyncData(Container):
# Sync committee aggregate signature
sync_aggregate: SyncAggregate
# Slot at which the aggregate signature was created
signature_slot: Slot
```

### Extended containers

#### `BeaconState`

```python
class BeaconState(Container):
# Versioning
genesis_time: uint64
genesis_validators_root: Root
slot: Slot
fork: Fork
# History
latest_block_header: BeaconBlockHeader
block_roots: Vector[Root, SLOTS_PER_HISTORICAL_ROOT]
state_roots: Vector[Root, SLOTS_PER_HISTORICAL_ROOT]
historical_roots: List[Root, HISTORICAL_ROOTS_LIMIT] # Frozen in Capella, replaced by historical_summaries
# Eth1
eth1_data: Eth1Data
eth1_data_votes: List[Eth1Data, EPOCHS_PER_ETH1_VOTING_PERIOD * SLOTS_PER_EPOCH]
eth1_deposit_index: uint64
# Registry
validators: List[Validator, VALIDATOR_REGISTRY_LIMIT]
balances: List[Gwei, VALIDATOR_REGISTRY_LIMIT]
# Randomness
randao_mixes: Vector[Bytes32, EPOCHS_PER_HISTORICAL_VECTOR]
# Slashings
slashings: Vector[Gwei, EPOCHS_PER_SLASHINGS_VECTOR] # Per-epoch sums of slashed effective balances
# Participation
previous_epoch_participation: List[ParticipationFlags, VALIDATOR_REGISTRY_LIMIT]
current_epoch_participation: List[ParticipationFlags, VALIDATOR_REGISTRY_LIMIT]
# Finality
justification_bits: Bitvector[JUSTIFICATION_BITS_LENGTH] # Bit set for every recent justified epoch
previous_justified_checkpoint: Checkpoint
current_justified_checkpoint: Checkpoint
finalized_checkpoint: Checkpoint
# Inactivity
inactivity_scores: List[uint64, VALIDATOR_REGISTRY_LIMIT]
# Sync
current_sync_committee: SyncCommittee
next_sync_committee: SyncCommittee
# Execution
latest_execution_payload_header: ExecutionPayloadHeader
# Withdrawals
next_withdrawal_index: WithdrawalIndex
next_withdrawal_validator_index: ValidatorIndex
# Deep history valid from Capella onwards
historical_summaries: List[HistoricalSummary, HISTORICAL_ROOTS_LIMIT]
# Sync history
previous_best_sync_data: SyncData # [New in Electra]
current_best_sync_data: SyncData # [New in Electra]
parent_block_has_sync_committee_finality: bool # [New in Electra]
```

## Helper functions

### `default_sync_data`

```python
def default_sync_data() -> SyncData:
return SyncData(
sync_aggregate=SyncAggregate(
sync_committee_bits=Bitvector[SYNC_COMMITTEE_SIZE]()
sync_committee_signature=G2_POINT_AT_INFINITY,
),
signature_slot=GENESIS_SLOT,
)
```

## Beacon chain state transition function

### Epoch processing

#### Modified `process_sync_committee_updates`

```python
def process_sync_committee_updates(state: BeaconState) -> None:
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_next_sync_committee(state)

# [New in Electra]
state.previous_best_sync_data = state.current_best_sync_data
state.current_best_sync_data = default_sync_data()
state.parent_block_has_sync_committee_finality = False
```

### Block processing

```python
def process_block(state: BeaconState, block: BeaconBlock) -> None:
process_best_sync_data(state, block) # [New in Electra]
process_block_header(state, block)
process_withdrawals(state, block.body.execution_payload)
process_execution_payload(state, block.body, EXECUTION_ENGINE)
process_randao(state, block.body)
process_eth1_data(state, block.body)
process_operations(state, block.body)
process_sync_aggregate(state, block.body.sync_aggregate)
```

#### New `process_best_sync_data`

```python
def process_best_sync_data(state: BeaconState, block: BeaconBlock) -> None:
signature_period = compute_sync_committee_period_at_slot(block.slot)
attested_period = compute_sync_committee_period_at_slot(state.latest_block_header.slot)

# Track sync committee finality
old_has_sync_committee_finality = state.parent_block_has_sync_committee_finality
if state.parent_block_has_sync_committee_finality:
new_has_sync_committee_finality = True
elif state.finalized_checkpoint.epoch < ALTAIR_FORK_EPOCH:
new_has_sync_committee_finality = False
else:
finalized_period = compute_sync_committee_period(state.finalized_checkpoint.epoch)
new_has_sync_committee_finality = (finalized_period == attested_period)
state.parent_block_has_sync_committee_finality = new_has_sync_committee_finality

# Track best sync data
if attested_period == signature_period:
max_active_participants = len(block.body.sync_aggregate.sync_committee_bits)
new_num_active_participants = sum(block.body.sync_aggregate.sync_committee_bits)
old_num_active_participants = sum(state.current_best_sync_data.sync_aggregate.sync_committee_bits)
new_has_supermajority = new_num_active_participants * 3 >= max_active_participants * 2
old_has_supermajority = old_num_active_participants * 3 >= max_active_participants * 2
if new_has_supermajority != old_has_supermajority:
is_better_sync_data = new_has_supermajority
elif not new_has_supermajority and new_num_active_participants != old_num_active_participants:
is_better_sync_data = new_num_active_participants > old_num_active_participants
elif new_has_sync_committee_finality != old_has_sync_committee_finality:
is_better_sync_data = new_has_sync_committee_finality
else:
is_better_sync_data = new_num_active_participants > old_num_active_participants
if is_better_sync_data:
state.current_best_sync_data = SyncData(
sync_aggregate=block.body.sync_aggregate,
signature_slot=block.slot,
)
```
144 changes: 144 additions & 0 deletions specs/electra/fork.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
# Electra -- Fork Logic

**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)
- [Helper functions](#helper-functions)
- [Misc](#misc)
- [Modified `compute_fork_version`](#modified-compute_fork_version)
- [Fork to Electra](#fork-to-electra)
- [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 Electra upgrade.

## Configuration

Warning: this configuration is not definitive.

| Name | Value |
| - | - |
| `ELECTRA_FORK_VERSION` | `Version('0x05000000')` |
| `ELECTRA_FORK_EPOCH` | `Epoch(FAR_FUTURE_EPOCH)` |

## Helper functions

### Misc

#### Modified `compute_fork_version`

```python
def compute_fork_version(epoch: Epoch) -> Version:
"""
Return the fork version at the given ``epoch``.
"""
if epoch >= ELECTRA_FORK_EPOCH:
return ELECTRA_FORK_VERSION
if epoch >= DENEB_FORK_EPOCH:
return DENEB_FORK_VERSION
if epoch >= CAPELLA_FORK_EPOCH:
return CAPELLA_FORK_VERSION
if epoch >= BELLATRIX_FORK_EPOCH:
return BELLATRIX_FORK_VERSION
if epoch >= ALTAIR_FORK_EPOCH:
return ALTAIR_FORK_VERSION
return GENESIS_FORK_VERSION
```

## Fork to Electra

### Fork trigger

TBD. This fork is defined for testing purposes.
For now, we assume the condition will be triggered at epoch `ELECTRA_FORK_EPOCH`.

Note that for the pure Electra networks, we don't apply `upgrade_to_electra` since it starts with Electra version logic.

### Upgrading the state

```python
def upgrade_to_electra(pre: deneb.BeaconState) -> BeaconState:
epoch = deneb.get_current_epoch(pre)
latest_execution_payload_header = ExecutionPayloadHeader(
parent_hash=pre.latest_execution_payload_header.parent_hash,
fee_recipient=pre.latest_execution_payload_header.fee_recipient,
state_root=pre.latest_execution_payload_header.state_root,
receipts_root=pre.latest_execution_payload_header.receipts_root,
logs_bloom=pre.latest_execution_payload_header.logs_bloom,
prev_randao=pre.latest_execution_payload_header.prev_randao,
block_number=pre.latest_execution_payload_header.block_number,
gas_limit=pre.latest_execution_payload_header.gas_limit,
gas_used=pre.latest_execution_payload_header.gas_used,
timestamp=pre.latest_execution_payload_header.timestamp,
extra_data=pre.latest_execution_payload_header.extra_data,
base_fee_per_gas=pre.latest_execution_payload_header.base_fee_per_gas,
block_hash=pre.latest_execution_payload_header.block_hash,
transactions_root=pre.latest_execution_payload_header.transactions_root,
withdrawals_root=pre.latest_execution_payload_header.withdrawals_root,
blob_gas_used=pre.latest_execution_payload_header.blob_gas_used, # [Modified in Electra]
excess_blob_gas=pre.latest_execution_payload_header.excess_blob_gas, # [Modified in Electra]
)
post = BeaconState(
# Versioning
genesis_time=pre.genesis_time,
genesis_validators_root=pre.genesis_validators_root,
slot=pre.slot,
fork=Fork(
previous_version=pre.fork.current_version,
current_version=ELECTRA_FORK_VERSION, # [Modified in Electra]
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,
# Participation
previous_epoch_participation=pre.previous_epoch_participation,
current_epoch_participation=pre.current_epoch_participation,
# 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,
# Inactivity
inactivity_scores=pre.inactivity_scores,
# Sync
current_sync_committee=pre.current_sync_committee,
next_sync_committee=pre.next_sync_committee,
# Execution-layer
latest_execution_payload_header=latest_execution_payload_header,
# Withdrawals
next_withdrawal_index=pre.next_withdrawal_index,
next_withdrawal_validator_index=pre.next_withdrawal_validator_index,
# Deep history valid from Capella onwards
historical_summaries=pre.historical_summaries,
# Sync history
previous_best_sync_data=default_sync_data(), # [New in Electra]
current_best_sync_data=default_sync_data(), # [New in Electra]
parent_block_has_sync_committee_finality=(pre.slot == GENESIS_SLOT), # [New in Electra]
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@hwwhww the idea here is that, if a genesis state is produced, it should be True. If the fork is applied later, it should be False. Not sure how to best express that.

There are only two situations how the sync committees can already be finalized at the time the fork is activated:

  • Genesis at >= Electra
  • Electra not scheduled at historical roots boundary, which is not expected to happen on the relevant networks. If it would be the case, there would be a tiny de-rank for the blocks building on the initial Electra block, but nothing impactful.

)

return post
```