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

[WIP] Withdrawals pull. #2759

Closed
wants to merge 19 commits into from
Closed
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions .circleci/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,20 @@ jobs:
command: make citest fork=merge
- store_test_results:
path: tests/core/pyspec/test-reports
test-capella:
djrtwo marked this conversation as resolved.
Show resolved Hide resolved
docker:
- image: circleci/python:3.8
working_directory: ~/specs-repo
steps:
- restore_cache:
key: v3-specs-repo-{{ .Branch }}-{{ .Revision }}
- restore_pyspec_cached_venv
- run:
name: Run py-tests
command: make citest fork=capella
- store_test_results:
path: tests/core/pyspec/test-reports

table_of_contents:
docker:
- image: circleci/node:10.16.3
Expand Down
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ consensus-spec-tests/
tests/core/pyspec/eth2spec/phase0/
tests/core/pyspec/eth2spec/altair/
tests/core/pyspec/eth2spec/merge/
tests/core/pyspec/eth2spec/capella/

# coverage reports
.htmlcov
Expand Down
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -133,7 +133,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.altair -p eth2spec.merge
&& mypy --config-file $(LINTER_CONFIG_FILE) -p eth2spec.phase0 -p eth2spec.altair -p eth2spec.merge -p eth2spec.capella

lint_generators: pyspec
. venv/bin/activate; cd $(TEST_GENERATORS_DIR); \
Expand Down
7 changes: 6 additions & 1 deletion configs/mainnet.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -37,11 +37,16 @@ ALTAIR_FORK_EPOCH: 74240 # Oct 27, 2021, 10:56:23am UTC
# Merge
MERGE_FORK_VERSION: 0x02000000
MERGE_FORK_EPOCH: 18446744073709551615
# Capella
CAPELLA_FORK_VERSION: 0x03000000
CAPELLA_FORK_EPOCH: 18446744073709551615
# Sharding
SHARDING_FORK_VERSION: 0x03000000
SHARDING_FORK_VERSION: 0x04000000
SHARDING_FORK_EPOCH: 18446744073709551615




# Time parameters
# ---------------------------------------------------------------
# 12 seconds
Expand Down
5 changes: 4 additions & 1 deletion configs/minimal.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,11 @@ ALTAIR_FORK_EPOCH: 18446744073709551615
# Merge
MERGE_FORK_VERSION: 0x02000001
MERGE_FORK_EPOCH: 18446744073709551615
# Capella
CAPELLA_FORK_VERSION: 0x03000001
CAPELLA_FORK_EPOCH: 18446744073709551615
# Sharding
SHARDING_FORK_VERSION: 0x03000001
SHARDING_FORK_VERSION: 0x04000001
SHARDING_FORK_EPOCH: 18446744073709551615


Expand Down
1 change: 1 addition & 0 deletions presets/mainnet/capella.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
# Minimal preset - Sharding
1 change: 1 addition & 0 deletions presets/minimal/capella.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
# Minimal preset - Sharding
45 changes: 41 additions & 4 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ def installPackage(package: str):
PHASE0 = 'phase0'
ALTAIR = 'altair'
MERGE = 'merge'
CAPELLA = 'capella'

# The helper functions that are used when defining constants
CONSTANT_DEP_SUNDRY_CONSTANTS_FUNCTIONS = '''
Expand Down Expand Up @@ -548,9 +549,38 @@ def hardcoded_custom_type_dep_constants(cls) -> str:
return {**super().hardcoded_custom_type_dep_constants(), **constants}


#
# CapellaSpecBuilder
#
class CapellaSpecBuilder(MergeSpecBuilder):
fork: str = CAPELLA

@classmethod
def imports(cls, preset_name: str):
return super().imports(preset_name) + f'''
from eth2spec.merge import {preset_name} as merge
'''

@classmethod
def preparations(cls):
return super().preparations()

@classmethod
def sundry_functions(cls) -> str:
return super().sundry_functions()

@classmethod
def hardcoded_ssz_dep_constants(cls) -> Dict[str, str]:
constants = {
'FINALIZED_ROOT_INDEX': 'GeneralizedIndex(105)',
'NEXT_SYNC_COMMITTEE_INDEX': 'GeneralizedIndex(55)',
}
return {**super().hardcoded_ssz_dep_constants(), **constants}
djrtwo marked this conversation as resolved.
Show resolved Hide resolved


spec_builders = {
builder.fork: builder
for builder in (Phase0SpecBuilder, AltairSpecBuilder, MergeSpecBuilder)
for builder in (Phase0SpecBuilder, AltairSpecBuilder, MergeSpecBuilder, CapellaSpecBuilder)
}


Expand Down Expand Up @@ -845,14 +875,14 @@ def finalize_options(self):
if len(self.md_doc_paths) == 0:
print("no paths were specified, using default markdown file paths for pyspec"
" build (spec fork: %s)" % self.spec_fork)
if self.spec_fork in (PHASE0, ALTAIR, MERGE):
if self.spec_fork in (PHASE0, ALTAIR, MERGE, CAPELLA):
self.md_doc_paths = """
specs/phase0/beacon-chain.md
specs/phase0/fork-choice.md
specs/phase0/validator.md
specs/phase0/weak-subjectivity.md
"""
if self.spec_fork in (ALTAIR, MERGE):
if self.spec_fork in (ALTAIR, MERGE, CAPELLA):
self.md_doc_paths += """
specs/altair/beacon-chain.md
specs/altair/bls.md
Expand All @@ -861,13 +891,20 @@ def finalize_options(self):
specs/altair/p2p-interface.md
specs/altair/sync-protocol.md
"""
if self.spec_fork == MERGE:
if self.spec_fork in (MERGE, CAPELLA):
self.md_doc_paths += """
specs/merge/beacon-chain.md
specs/merge/fork.md
specs/merge/fork-choice.md
specs/merge/validator.md
"""
if self.spec_fork == CAPELLA:
self.md_doc_paths += """
specs/capella/beacon-chain.md
specs/capella/fork.md
specs/capella/validator.md
specs/capella/p2p-interface.md
"""
if len(self.md_doc_paths) == 0:
raise Exception('no markdown files specified, and spec fork "%s" is unknown', self.spec_fork)

Expand Down
163 changes: 163 additions & 0 deletions specs/capella/beacon-chain.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,163 @@
# Cappela -- The Beacon Chain

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

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

## Introduction

Cappela is a consensus-layer upgrade containin a number of features related
to validator withdrawals. Including:
* Automatic withdrawals of `withdrawable` validators
* Partial withdrawals during block proposal
* Operation to change from `BLS_WITHDRAWAL_PREFIX` to
`ETH1_ADDRESS_WITHDRAWAL_PREFIX` versioned withdrawal credentials to enable withdrawals for a validator

## Custom types

| Name | SSZ equivalent | Description |
| - | - | - |
| `WithdrawalReceiptIndex` | `uint64` | a withdrawal receipt index |

## Constants

## Preset

### State list lengths

| Name | Value | Unit | Duration |
| - | - | :-: | :-: |
Comment on lines +56 to +57
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
| Name | Value | Unit | Duration |
| - | - | :-: | :-: |
| Name | Value | Unit |
| - | - | :-: |

| `WITHDRAWAL_RECEIPT_LIMIT` | `uint64(2**40)` (= 1,099,511,627,776) | withdrawal receipts|
Copy link
Contributor

Choose a reason for hiding this comment

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

Need to be added to preset files.

Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
| `WITHDRAWAL_RECEIPT_LIMIT` | `uint64(2**40)` (= 1,099,511,627,776) | withdrawal receipts|
| `WITHDRAWAL_RECEIPT_LIMIT` | `uint64(2**40)` (= 1,099,511,627,776) | withdrawal receipts |


## Configuration

## Containers

### 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]
# 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
withdrawal_receipts: List[WithdrawalReceipt, WITHDRAWAL_RECEIPT_LIMIT] # [New in Cappela]
```

### New containers

#### `WithdrawalReceipt`

```python
class WithdrawalReceipt(Container):
index: WithdrawalReceiptIndex
address: ExecutionAddress
Copy link
Member

Choose a reason for hiding this comment

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

I wonder if it would make sense making this Bytes32 preparing for the possibility of "address space extension"?

It has some downsides:

  1. The receipt is now packed, given 64 + 160 + 256 bits of values.
  2. The deposit mechanism is still locked in to 160-bit addresses anyway, and likely many other places on the beacon chain too.

amount: Gwei
```

## Helpers

### Beacon state mutators

#### `withdraw`

```python
def withdraw(state: BeaconState, index: ValidatorIndex, amount: Gwei) -> None:
# Decrease the validator's balance
decrease_balance(state, index, amount)
# Create a corresponding withdrawal receipt
receipt = WithdrawalReceipt(
index=WithdrawalReceiptIndex(len(state.withdrawal_receipts)),
address=state.validators[index].withdrawal_credentials[12:],
amount=amount,
)
state.withdrawal_receipts.append(receipt)
```

### Predicates

#### `is_withdrawable_validator`

```python
def is_withdrawable_validator(validator: Validator, epoch: Epoch) -> bool:
"""
Check if ``validator`` is withdrawable.
"""
return validator.withdrawable_epoch <= epoch
mkalinin marked this conversation as resolved.
Show resolved Hide resolved
```

## Beacon chain state transition function

### Epoch processing

```python
def process_epoch(state: BeaconState) -> None:
process_justification_and_finalization(state)
process_inactivity_updates(state)
process_rewards_and_penalties(state)
process_registry_updates(state)
process_slashings(state)
process_eth1_data_reset(state)
process_effective_balance_updates(state)
process_slashings_reset(state)
process_randao_mixes_reset(state)
process_historical_roots_update(state)
process_participation_flag_updates(state)
process_sync_committee_updates(state)
process_withdrawals(state) # [New in Cappela]
```

#### Withdrawals

*Note*: The function `process_inactivity_updates` is new.
djrtwo marked this conversation as resolved.
Show resolved Hide resolved

```python
def process_withdrawals(state: BeaconState) -> None:
current_epoch = get_current_epoch(state)
for index, validator in enumerate(state.validators):
balance = state.balances[index]
is_balance_nonzero = state.balances[index] == 0
is_eth1_withdrawal_prefix = validator.withdrawal_credentials[0] != ETH1_ADDRESS_WITHDRAWAL_PREFIX
djrtwo marked this conversation as resolved.
Show resolved Hide resolved
if is_balance_nonzero and is_eth1_withdrawal_prefix and is_withdrawable_validator(validator, current_epoch):
djrtwo marked this conversation as resolved.
Show resolved Hide resolved
withdraw(state, ValidatorIndex(index), balance)
```
Loading