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

Various spec fixes for EIP-7251 #3657

Merged
merged 5 commits into from
Apr 8, 2024
Merged
Show file tree
Hide file tree
Changes from all 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
154 changes: 139 additions & 15 deletions specs/_features/eip7251/beacon-chain.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@
- [Extended Containers](#extended-containers)
- [`BeaconState`](#beaconstate)
- [`BeaconBlockBody`](#beaconblockbody)
- [`ExecutionPayload`](#executionpayload)
- [`ExecutionPayloadHeader`](#executionpayloadheader)
- [Helpers](#helpers)
- [Predicates](#predicates)
- [Updated `is_eligible_for_activation_queue`](#updated-is_eligible_for_activation_queue)
Expand Down Expand Up @@ -75,13 +77,14 @@
- [New `process_consolidation`](#new-process_consolidation)
- [Voluntary exits](#voluntary-exits)
- [Updated `process_voluntary_exit`](#updated-process_voluntary_exit)
- [Testing](#testing)

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

## Introduction

See [a modest proposal](https://notes.ethereum.org/@mikeneuder/increase-maxeb), the [diff view](https://github.com/michaelneuder/consensus-specs/pull/3/files) and
See [a modest proposal](https://notes.ethereum.org/@mikeneuder/increase-maxeb), the [diff view](https://github.com/michaelneuder/consensus-specs/pull/3/files) and
[security considerations](https://notes.ethereum.org/@fradamt/meb-increase-security).

*Note:* This specification is built upon [Deneb](../../deneb/beacon-chain.md).
Expand Down Expand Up @@ -136,6 +139,7 @@ The following values are (non-configurable) constants used throughout the specif

| Name | Value | Description |
| - | - | - |
| `MAX_WITHDRAWAL_REQUESTS_PER_PAYLOAD` | `uint64(16)` |
| `MAX_PARTIAL_WITHDRAWALS_PER_PAYLOAD` | `uint64(2**3)` (= 8) | Maximum amount of partial withdrawals allowed in each payload |

### State list lengths
Expand Down Expand Up @@ -282,12 +286,64 @@ class BeaconBlockBody(Container):
voluntary_exits: List[SignedVoluntaryExit, MAX_VOLUNTARY_EXITS]
sync_aggregate: SyncAggregate
# Execution
execution_payload: ExecutionPayload
execution_payload: ExecutionPayload
bls_to_execution_changes: List[SignedBLSToExecutionChange, MAX_BLS_TO_EXECUTION_CHANGES]
blob_kzg_commitments: List[KZGCommitment, MAX_BLOB_COMMITMENTS_PER_BLOCK]
consolidations: List[SignedConsolidation, MAX_CONSOLIDATIONS] # [New in EIP-7251]
```

#### `ExecutionPayload`

```python
class ExecutionPayload(Container):
# Execution block header fields
parent_hash: Hash32
fee_recipient: ExecutionAddress # 'beneficiary' in the yellow paper
state_root: Bytes32
receipts_root: Bytes32
logs_bloom: ByteVector[BYTES_PER_LOGS_BLOOM]
prev_randao: Bytes32 # 'difficulty' in the yellow paper
block_number: uint64 # 'number' in the yellow paper
gas_limit: uint64
gas_used: uint64
timestamp: uint64
extra_data: ByteList[MAX_EXTRA_DATA_BYTES]
base_fee_per_gas: uint256
# Extra payload fields
block_hash: Hash32 # Hash of execution block
transactions: List[Transaction, MAX_TRANSACTIONS_PER_PAYLOAD]
withdrawals: List[Withdrawal, MAX_WITHDRAWALS_PER_PAYLOAD]
blob_gas_used: uint64
excess_blob_gas: uint64
withdraw_requests: List[ExecutionLayerWithdrawRequest, MAX_WITHDRAWAL_REQUESTS_PER_PAYLOAD] # [New in EIP-7251]
```

#### `ExecutionPayloadHeader`

```python
class ExecutionPayloadHeader(Container):
# Execution block header fields
parent_hash: Hash32
fee_recipient: ExecutionAddress
state_root: Bytes32
receipts_root: Bytes32
logs_bloom: ByteVector[BYTES_PER_LOGS_BLOOM]
prev_randao: Bytes32
block_number: uint64
gas_limit: uint64
gas_used: uint64
timestamp: uint64
extra_data: ByteList[MAX_EXTRA_DATA_BYTES]
base_fee_per_gas: uint256
# Extra payload fields
block_hash: Hash32 # Hash of execution block
transactions_root: Root
withdrawals_root: Root
blob_gas_used: uint64
excess_blob_gas: uint64
withdraw_requests_root: Root # [New in EIP-7251]
```

## Helpers

### Predicates
Expand Down Expand Up @@ -386,7 +442,7 @@ def get_churn_limit(state: BeaconState) -> Gwei:
Return the churn limit for the current epoch.
"""
churn = max(
MIN_PER_EPOCH_CHURN_LIMIT_EIP7251,
MIN_PER_EPOCH_CHURN_LIMIT_EIP7251,
get_total_active_balance(state) // CHURN_LIMIT_QUOTIENT
)
return churn - churn % EFFECTIVE_BALANCE_INCREMENT
Expand Down Expand Up @@ -472,7 +528,6 @@ def queue_excess_active_balance(state: BeaconState, index: ValidatorIndex) -> No

#### New `compute_exit_epoch_and_update_churn`


```python
def compute_exit_epoch_and_update_churn(state: BeaconState, exit_balance: Gwei) -> Epoch:
earliest_exit_epoch = compute_activation_exit_epoch(get_current_epoch(state))
Expand Down Expand Up @@ -566,16 +621,23 @@ def process_epoch(state: BeaconState) -> None:
process_effective_balance_updates(state) # [Modified in EIP7251]
process_slashings_reset(state)
process_randao_mixes_reset(state)
process_historical_summaries_update(state)
process_participation_flag_updates(state)
process_sync_committee_updates(state)
```

#### Updated `process_registry_updates`

`process_registry_updates` uses the updated definition of `initiate_validator_exit`
and changes how the activation epochs are computed for eligible validators.

```python
def process_registry_updates(state: BeaconState) -> None:
# Process activation eligibility and ejections
for index, validator in enumerate(state.validators):
if is_eligible_for_activation_queue(validator):
validator.activation_eligibility_epoch = get_current_epoch(state) + 1

if (
is_active_validator(validator, get_current_epoch(state))
and validator.effective_balance <= EJECTION_BALANCE
Expand Down Expand Up @@ -607,7 +669,7 @@ def process_pending_balance_deposits(state: BeaconState) -> None:
state.pending_balance_deposits = state.pending_balance_deposits[next_deposit_index:]

if len(state.pending_balance_deposits) == 0:
state.deposit_balance_to_consume = 0
state.deposit_balance_to_consume = Gwei(0)
else:
state.deposit_balance_to_consume = available_for_processing - processed_amount
```
Expand Down Expand Up @@ -638,6 +700,8 @@ def process_pending_consolidations(state: BeaconState) -> None:

#### Updated `process_effective_balance_updates`

`process_effective_balance_updates` is updated with a new limit for the maximum effective balance.

```python
def process_effective_balance_updates(state: BeaconState) -> None:
# Update effective balances with hysteresis
Expand Down Expand Up @@ -760,7 +824,7 @@ def process_withdrawals(state: BeaconState, payload: ExecutionPayload) -> None:
state.next_withdrawal_validator_index = next_validator_index
```

#### Operations
#### Operations

##### Updated `process_operations`

Expand All @@ -779,14 +843,16 @@ def process_operations(state: BeaconState, body: BeaconBlockBody) -> None:
for_ops(body.deposits, process_deposit) # [Modified in EIP7251]
for_ops(body.voluntary_exits, process_voluntary_exit) # [Modified in EIP7251]
for_ops(body.bls_to_execution_changes, process_bls_to_execution_change)
for_ops(body.execution_payload.withdraw_requests, process_execution_layer_withdraw_request) # New in EIP7251
for_ops(body.consolidations, process_consolidation) # New in EIP7251
for_ops(body.execution_payload.withdraw_requests, process_execution_layer_withdraw_request) # [New in EIP7251]
for_ops(body.consolidations, process_consolidation) # [New in EIP7251]
```

##### Deposits

###### Updated `apply_deposit`

*NOTE*: `process_deposit` is updated with a new definition of `apply_deposit`.

```python
def apply_deposit(state: BeaconState,
pubkey: BLSPubkey,
Expand Down Expand Up @@ -819,7 +885,7 @@ def apply_deposit(state: BeaconState,
def is_valid_deposit_signature(pubkey: BLSPubkey,
withdrawal_credentials: Bytes32,
amount: uint64,
signature: BLSSignature) -> None:
signature: BLSSignature) -> bool:
deposit_message = DepositMessage(
pubkey=pubkey,
withdrawal_credentials=withdrawal_credentials,
Expand Down Expand Up @@ -862,7 +928,7 @@ def get_validator_from_deposit(pubkey: BLSPubkey, withdrawal_credentials: Bytes3
)
```

##### Withdrawals
##### Withdrawals

###### New `process_execution_layer_withdraw_request`

Expand All @@ -874,7 +940,7 @@ def process_execution_layer_withdraw_request(
amount = execution_layer_withdraw_request.amount
is_full_exit_request = amount == FULL_EXIT_REQUEST_AMOUNT

# If partial withdrawal queue is full, only full exits are processed
# If partial withdrawal queue is full, only full exits are processed
if len(state.pending_partial_withdrawals) >= PENDING_PARTIAL_WITHDRAWALS_LIMIT and not is_full_exit_request:
return

Expand Down Expand Up @@ -947,7 +1013,7 @@ def process_consolidation(state: BeaconState, signed_consolidation: SignedConsol
assert source_validator.exit_epoch == FAR_FUTURE_EPOCH
assert target_validator.exit_epoch == FAR_FUTURE_EPOCH
# Consolidations must specify an epoch when they become valid; they are not valid before then
assert current_epoch >= consolidation.epoch
assert current_epoch >= consolidation.epoch

# Verify the source and the target have Execution layer withdrawal credentials
assert has_execution_withdrawal_credential(source_validator)
Expand Down Expand Up @@ -989,12 +1055,70 @@ def process_voluntary_exit(state: BeaconState, signed_voluntary_exit: SignedVolu
assert get_current_epoch(state) >= voluntary_exit.epoch
# Verify the validator has been active long enough
assert get_current_epoch(state) >= validator.activation_epoch + SHARD_COMMITTEE_PERIOD
# Only exit validator if it has no pending withdrawals in the queue
assert get_pending_balance_to_withdraw(state, voluntary_exit.validator_index) == 0 # [New in EIP7251]
# Verify signature
domain = get_domain(state, DOMAIN_VOLUNTARY_EXIT, voluntary_exit.epoch)
domain = compute_domain(DOMAIN_VOLUNTARY_EXIT, CAPELLA_FORK_VERSION, state.genesis_validators_root)
signing_root = compute_signing_root(voluntary_exit, domain)
assert bls.Verify(validator.pubkey, signing_root, signed_voluntary_exit.signature)
# Only exit validator if it has no pending withdrawals in the queue
assert get_pending_balance_to_withdraw(state, voluntary_exit.validator_index) == 0 # [New in EIP7251]
# Initiate exit
initiate_validator_exit(state, voluntary_exit.validator_index)
```

## Testing

*Note*: The function `initialize_beacon_state_from_eth1` is modified for pure EIP-7251 testing only.

```python
def initialize_beacon_state_from_eth1(eth1_block_hash: Hash32,
eth1_timestamp: uint64,
deposits: Sequence[Deposit],
execution_payload_header: ExecutionPayloadHeader=ExecutionPayloadHeader()
) -> BeaconState:
fork = Fork(
previous_version=EIP7251_FORK_VERSION, # [Modified in EIP-7251] for testing only
current_version=EIP7251_FORK_VERSION, # [Modified in EIP-7251]
epoch=GENESIS_EPOCH,
)
state = BeaconState(
genesis_time=eth1_timestamp + GENESIS_DELAY,
fork=fork,
eth1_data=Eth1Data(block_hash=eth1_block_hash, deposit_count=uint64(len(deposits))),
latest_block_header=BeaconBlockHeader(body_root=hash_tree_root(BeaconBlockBody())),
randao_mixes=[eth1_block_hash] * EPOCHS_PER_HISTORICAL_VECTOR, # Seed RANDAO with Eth1 entropy
)

# Process deposits
leaves = list(map(lambda deposit: deposit.data, deposits))
for index, deposit in enumerate(deposits):
deposit_data_list = List[DepositData, 2**DEPOSIT_CONTRACT_TREE_DEPTH](*leaves[:index + 1])
state.eth1_data.deposit_root = hash_tree_root(deposit_data_list)
process_deposit(state, deposit)

# Process deposit balance updates
for deposit in state.pending_balance_deposits:
increase_balance(state, deposit.index, deposit.amount)
state.pending_balance_deposits = []

# Process activations
for index, validator in enumerate(state.validators):
balance = state.balances[index]
validator.effective_balance = min(balance - balance % EFFECTIVE_BALANCE_INCREMENT, MAX_EFFECTIVE_BALANCE)
if validator.effective_balance == MAX_EFFECTIVE_BALANCE:
validator.activation_eligibility_epoch = GENESIS_EPOCH
validator.activation_epoch = GENESIS_EPOCH

# Set genesis validators root for domain separation and chain versioning
state.genesis_validators_root = hash_tree_root(state.validators)

# Fill in sync committees
# Note: A duplicate committee is assigned for the current and next committee at genesis
state.current_sync_committee = get_next_sync_committee(state)
state.next_sync_committee = get_next_sync_committee(state)

# Initialize the execution payload header
# If empty, will initialize a chain that has not yet gone through the Merge transition
state.latest_execution_payload_header = execution_payload_header

return state
Copy link
Collaborator

Choose a reason for hiding this comment

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

@hwwhww are we still merging these? state bumps can be computed from the upgrade_to_* function

Copy link
Member Author

Choose a reason for hiding this comment

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

they do serve distinct purposes

for testing, we will usually start at genesis, whereas at the fork boundary there is likely state we want to bring forward/translate from the old fork to the new

Copy link
Contributor

Choose a reason for hiding this comment

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

I think @dapplion was pointing to an old discussion. I just opened an issue: #3663 (a good first issue!)

It's better to clean up them in other PRs altogether. 👍

```
3 changes: 1 addition & 2 deletions specs/_features/eip7251/fork.md
Original file line number Diff line number Diff line change
Expand Up @@ -131,8 +131,7 @@ def upgrade_to_eip7251(pre: deneb.BeaconState) -> BeaconState:
# Ensure early adopters of compounding credentials go through the activation churn
for index, validator in enumerate(post.validators):
if has_compounding_withdrawal_credential(validator):
queue_excess_active_balance(post, index)
queue_excess_active_balance(post, ValidatorIndex(index))

return post
```

73 changes: 73 additions & 0 deletions specs/_features/eip7251/validator.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
# EIP-7251 -- Honest Validator

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

- [Introduction](#introduction)
- [Prerequisites](#prerequisites)
- [Beacon chain responsibilities](#beacon-chain-responsibilities)
- [Block and sidecar proposal](#block-and-sidecar-proposal)
- [Constructing the `BeaconBlockBody`](#constructing-the-beaconblockbody)
- [ExecutionPayload](#executionpayload)

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

## Introduction

This document represents the changes to be made in the code of an "honest validator".

## Prerequisites

This document is an extension of the [Deneb -- Honest Validator](../deneb/validator.md) guide.
All behaviors and definitions defined in this document, and documents it extends, carry over unless explicitly noted or overridden.

All terminology, constants, functions, and protocol mechanics defined in the updated [Beacon Chain doc of EIP-7251](./beacon-chain.md) are requisite for this document and used throughout.
Please see related Beacon Chain doc before continuing and use them as a reference throughout.

## Beacon chain responsibilities

All validator responsibilities remain unchanged other than those noted below.

### Block and sidecar proposal

#### Constructing the `BeaconBlockBody`

##### ExecutionPayload

`prepare_execution_payload` is updated from the Deneb specs.

*Note*: In this section, `state` is the state of the slot for the block proposal _without_ the block yet applied.
That is, `state` is the `previous_state` processed through any empty slots up to the assigned slot using `process_slots(previous_state, slot)`.

*Note*: The only change to `prepare_execution_payload` is the new definition of `get_expected_withdrawals`.

```python
def prepare_execution_payload(state: BeaconState,
safe_block_hash: Hash32,
finalized_block_hash: Hash32,
suggested_fee_recipient: ExecutionAddress,
execution_engine: ExecutionEngine) -> Optional[PayloadId]:
# Verify consistency of the parent hash with respect to the previous execution payload header
parent_hash = state.latest_execution_payload_header.block_hash

# Set the forkchoice head and initiate the payload build process
withdrawals, _ = get_expected_withdrawals(state) # [Modified in EIP-7251]

payload_attributes = PayloadAttributes(
timestamp=compute_timestamp_at_slot(state, state.slot),
prev_randao=get_randao_mix(state, get_current_epoch(state)),
suggested_fee_recipient=suggested_fee_recipient,
withdrawals=withdrawals,
parent_beacon_block_root=hash_tree_root(state.latest_block_header),
)
return execution_engine.notify_forkchoice_updated(
head_block_hash=parent_hash,
safe_block_hash=safe_block_hash,
finalized_block_hash=finalized_block_hash,
payload_attributes=payload_attributes,
)
```
Loading