Merge branch 'dev' into pr3757

This commit is contained in:
Hsiao-Wei Wang 2024-06-05 00:42:37 +08:00
commit 12d95da0da
No known key found for this signature in database
GPG Key ID: AE3D6B174F971DE4
40 changed files with 1513 additions and 963 deletions

1
.gitignore vendored
View File

@ -24,6 +24,7 @@ tests/core/pyspec/eth2spec/deneb/
tests/core/pyspec/eth2spec/electra/
tests/core/pyspec/eth2spec/whisk/
tests/core/pyspec/eth2spec/eip7594/
tests/core/pyspec/eth2spec/eip6800/
# coverage reports
.htmlcov

View File

@ -35,7 +35,7 @@ MARKDOWN_FILES = $(wildcard $(SPEC_DIR)/*/*.md) \
$(wildcard $(SPEC_DIR)/_features/*/*/*.md) \
$(wildcard $(SSZ_DIR)/*.md)
ALL_EXECUTABLE_SPEC_NAMES = phase0 altair bellatrix capella deneb electra whisk
ALL_EXECUTABLE_SPEC_NAMES = phase0 altair bellatrix capella deneb electra whisk eip6800
# The parameters for commands. Use `foreach` to avoid listing specs again.
COVERAGE_SCOPE := $(foreach S,$(ALL_EXECUTABLE_SPEC_NAMES), --cov=eth2spec.$S.$(TEST_PRESET_TYPE))
PYLINT_SCOPE := $(foreach S,$(ALL_EXECUTABLE_SPEC_NAMES), ./eth2spec/$S)

View File

@ -159,6 +159,9 @@ NUMBER_OF_COLUMNS: 128
MAX_CELLS_IN_EXTENDED_MATRIX: 768
DATA_COLUMN_SIDECAR_SUBNET_COUNT: 32
MAX_REQUEST_DATA_COLUMN_SIDECARS: 16384
SAMPLES_PER_SLOT: 8
CUSTODY_REQUIREMENT: 1
TARGET_NUMBER_OF_PEERS: 70
# [New in Electra:EIP7251]
MIN_PER_EPOCH_CHURN_LIMIT_ELECTRA: 128000000000 # 2**7 * 10**9 (= 128,000,000,000)

View File

@ -158,6 +158,9 @@ NUMBER_OF_COLUMNS: 128
MAX_CELLS_IN_EXTENDED_MATRIX: 768
DATA_COLUMN_SIDECAR_SUBNET_COUNT: 32
MAX_REQUEST_DATA_COLUMN_SIDECARS: 16384
SAMPLES_PER_SLOT: 8
CUSTODY_REQUIREMENT: 1
TARGET_NUMBER_OF_PEERS: 70
# [New in Electra:EIP7251]
MIN_PER_EPOCH_CHURN_LIMIT_ELECTRA: 64000000000 # 2**6 * 10**9 (= 64,000,000,000)

View File

@ -54,10 +54,10 @@ You can refer to the previous fork's `fork.md` file.
### 5. Make it executable
- Update Pyspec [`constants.py`](https://github.com/ethereum/consensus-specs/blob/dev/tests/core/pyspec/eth2spec/test/helpers/constants.py) with the new feature name.
- Update helpers for [`setup.py`](https://github.com/ethereum/consensus-specs/blob/dev/setup.py) for building the spec:
- Update [`pysetup/constants.py`](https://github.com/ethereum/consensus-specs/blob/dev/constants.py) with the new feature name as Pyspec `constants.py` defined.
- Update [`pysetup/constants.py`](https://github.com/ethereum/consensus-specs/blob/dev/pysetup/constants.py) with the new feature name as Pyspec `constants.py` defined.
- Update [`pysetup/spec_builders/__init__.py`](https://github.com/ethereum/consensus-specs/blob/dev/pysetup/spec_builders/__init__.py). Implement a new `<FEATURE_NAME>SpecBuilder` in `pysetup/spec_builders/<FEATURE_NAME>.py` with the new feature name. e.g., `EIP9999SpecBuilder`. Append it to the `spec_builders` list.
- Update [`pysetup/md_doc_paths.py`](https://github.com/ethereum/consensus-specs/blob/dev/pysetup/md_doc_paths.py): add the path of the new markdown files in `get_md_doc_paths` function if needed.
- Update `PREVIOUS_FORK_OF` setting in both [`test/helpers/constants.py`](https://github.com/ethereum/consensus-specs/blob/dev/constants.py) and [`pysetup/md_doc_paths.py`](https://github.com/ethereum/consensus-specs/blob/dev/pysetup/md_doc_paths.py).
- Update `PREVIOUS_FORK_OF` setting in both [`test/helpers/constants.py`](https://github.com/ethereum/consensus-specs/blob/dev/tests/core/pyspec/eth2spec/test/helpers/constants.py) and [`pysetup/md_doc_paths.py`](https://github.com/ethereum/consensus-specs/blob/dev/pysetup/md_doc_paths.py).
- NOTE: since these two modules (the pyspec itself and the spec builder tool) must be separate, the fork sequence setting has to be defined again.
## B: Make it executable for pytest and test generator

View File

@ -0,0 +1,12 @@
# Mainnet preset - EIP6800
# Misc
# ---------------------------------------------------------------
# `uint64(2**16)` (= 65,536)
MAX_STEMS: 65536
# `uint64(33)`
MAX_COMMITMENTS_PER_STEM: 33
# `uint64(2**8)` (= 256)
VERKLE_WIDTH: 256
# `uint64(2**3)` (= 8)
IPA_PROOF_DEPTH: 8

View File

@ -30,7 +30,7 @@ MAX_ATTESTER_SLASHINGS_ELECTRA: 1
# `uint64(2**3)` (= 8)
MAX_ATTESTATIONS_ELECTRA: 8
# `uint64(2**0)` (= 1)
MAX_CONSOLIDATIONS: 1
MAX_CONSOLIDATION_REQUESTS_PER_PAYLOAD: 1
# Execution
# ---------------------------------------------------------------

View File

@ -0,0 +1,12 @@
# Minimal preset - EIP6800
# Execution
# ---------------------------------------------------------------
# `uint64(2**16)` (= 65,536)
MAX_STEMS: 65536
# `uint64(33)`
MAX_COMMITMENTS_PER_STEM: 33
# `uint64(2**8)` (= 256)
VERKLE_WIDTH: 256
# `uint64(2**3)` (= 8)
IPA_PROOF_DEPTH: 8

View File

@ -30,7 +30,7 @@ MAX_ATTESTER_SLASHINGS_ELECTRA: 1
# `uint64(2**3)` (= 8)
MAX_ATTESTATIONS_ELECTRA: 8
# `uint64(2**0)` (= 1)
MAX_CONSOLIDATIONS: 1
MAX_CONSOLIDATION_REQUESTS_PER_PAYLOAD: 1
# Execution
# ---------------------------------------------------------------

View File

@ -6,10 +6,10 @@ CAPELLA = 'capella'
DENEB = 'deneb'
ELECTRA = 'electra'
EIP7594 = 'eip7594'
EIP6800 = 'eip6800'
WHISK = 'whisk'
# The helper functions that are used when defining constants
CONSTANT_DEP_SUNDRY_CONSTANTS_FUNCTIONS = '''
def ceillog2(x: int) -> uint64:

View File

@ -178,7 +178,7 @@ def combine_dicts(old_dict: Dict[str, T], new_dict: Dict[str, T]) -> Dict[str, T
ignored_dependencies = [
'bit', 'boolean', 'Vector', 'List', 'Container', 'BLSPubkey', 'BLSSignature',
'Bytes1', 'Bytes4', 'Bytes8', 'Bytes20', 'Bytes32', 'Bytes48', 'Bytes96', 'Bitlist', 'Bitvector',
'Bytes1', 'Bytes4', 'Bytes8', 'Bytes20', 'Bytes31', 'Bytes32', 'Bytes48', 'Bytes96', 'Bitlist', 'Bitvector',
'uint8', 'uint16', 'uint32', 'uint64', 'uint128', 'uint256',
'bytes', 'byte', 'ByteList', 'ByteVector',
'Dict', 'dict', 'field', 'ceillog2', 'floorlog2', 'Set',

View File

@ -9,6 +9,7 @@ from .constants import (
ELECTRA,
WHISK,
EIP7594,
EIP6800,
)
@ -21,6 +22,7 @@ PREVIOUS_FORK_OF = {
ELECTRA: DENEB,
WHISK: CAPELLA,
EIP7594: DENEB,
EIP6800: DENEB,
}
ALL_FORKS = list(PREVIOUS_FORK_OF.keys())

View File

@ -6,12 +6,13 @@ from .deneb import DenebSpecBuilder
from .electra import ElectraSpecBuilder
from .whisk import WhiskSpecBuilder
from .eip7594 import EIP7594SpecBuilder
from .eip6800 import EIP6800SpecBuilder
spec_builders = {
builder.fork: builder
for builder in (
Phase0SpecBuilder, AltairSpecBuilder, BellatrixSpecBuilder, CapellaSpecBuilder, DenebSpecBuilder,
ElectraSpecBuilder, WhiskSpecBuilder, EIP7594SpecBuilder,
ElectraSpecBuilder, WhiskSpecBuilder, EIP7594SpecBuilder, EIP6800SpecBuilder,
)
}

View File

@ -0,0 +1,21 @@
from typing import Dict
from .base import BaseSpecBuilder
from ..constants import EIP6800
class EIP6800SpecBuilder(BaseSpecBuilder):
fork: str = EIP6800
@classmethod
def imports(cls, preset_name: str):
return f'''
from eth2spec.deneb import {preset_name} as deneb
from eth2spec.utils.ssz.ssz_typing import Bytes31
'''
@classmethod
def hardcoded_custom_type_dep_constants(cls, spec_object) -> str:
return {
'MAX_STEMS': spec_object.preset_vars['MAX_STEMS'].value,
}

View File

@ -219,7 +219,13 @@ def get_spec(file_name: Path, preset: Dict[str, str], config: Dict[str, str], pr
elif source.startswith("class"):
class_name, parent_class = _get_class_info_from_source(source)
# check consistency with spec
assert class_name == current_name
try:
assert class_name == current_name
except Exception:
print('class_name', class_name)
print('current_name', current_name)
raise
if parent_class:
assert parent_class == "Container"
# NOTE: trim whitespace from spec

View File

@ -0,0 +1,221 @@
# EIP6800 -- 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 -->
- [Introduction](#introduction)
- [Custom types](#custom-types)
- [Preset](#preset)
- [Execution](#execution)
- [Containers](#containers)
- [Extended containers](#extended-containers)
- [`ExecutionPayload`](#executionpayload)
- [`ExecutionPayloadHeader`](#executionpayloadheader)
- [New containers](#new-containers)
- [`SuffixStateDiff`](#suffixstatediff)
- [`StemStateDiff`](#stemstatediff)
- [`IPAProof`](#ipaproof)
- [`VerkleProof`](#verkleproof)
- [`ExecutionWitness`](#executionwitness)
- [Beacon chain state transition function](#beacon-chain-state-transition-function)
- [Block processing](#block-processing)
- [Execution payload](#execution-payload)
- [`process_execution_payload`](#process_execution_payload)
- [Testing](#testing)
<!-- END doctoc generated TOC please keep comment here to allow auto update -->
<!-- /TOC -->
## Introduction
This upgrade adds transaction execution to the beacon chain as part of the eip6800 upgrade.
## Custom types
| Name | SSZ equivalent | Description |
| - | - | - |
| `BanderwagonGroupElement` | `Bytes32` | |
| `BanderwagonFieldElement` | `Bytes32` | |
| `Stem` | `Bytes31` | |
## Preset
### Execution
| Name | Value |
| - | - |
| `MAX_STEMS` | `uint64(2**16)` (= 65,536) |
| `MAX_COMMITMENTS_PER_STEM` | `uint64(33)` |
| `VERKLE_WIDTH` | `uint64(2**8)` (= 256) |
| `IPA_PROOF_DEPTH` | `uint64(2**3)` (= 8) |
## Containers
### Extended containers
#### `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
execution_witness: ExecutionWitness # [New in EIP6800]
```
#### `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_data_gas: uint64
execution_witness_root: Root # [New in EIP6800]
```
### New containers
#### `SuffixStateDiff`
```python
class SuffixStateDiff(Container):
suffix: Bytes1
# Null means not currently present
current_value: Optional[Bytes32]
# Null means value not updated
new_value: Optional[Bytes32]
```
*Note*: on the Kaustinen testnet, `new_value` is omitted from the container.
#### `StemStateDiff`
```python
class StemStateDiff(Container):
stem: Stem
# Valid only if list is sorted by suffixes
suffix_diffs: List[SuffixStateDiff, VERKLE_WIDTH]
```
#### `IPAProof`
```python
class IPAProof(Container):
cl: Vector[BanderwagonGroupElement, IPA_PROOF_DEPTH]
cr: Vector[BanderwagonGroupElement, IPA_PROOF_DEPTH]
final_evaluation = BanderwagonFieldElement
```
#### `VerkleProof`
```python
class VerkleProof(Container):
other_stems: List[Bytes31, MAX_STEMS]
depth_extension_present: ByteList[MAX_STEMS]
commitments_by_path: List[BanderwagonGroupElement, MAX_STEMS * MAX_COMMITMENTS_PER_STEM]
d: BanderwagonGroupElement
ipa_proof: IPAProof
```
#### `ExecutionWitness`
```python
class ExecutionWitness(Container):
state_diff: List[StemStateDiff, MAX_STEMS]
verkle_proof: VerkleProof
```
## Beacon chain state transition function
### Block processing
#### Execution payload
##### `process_execution_payload`
```python
def process_execution_payload(state: BeaconState, body: BeaconBlockBody, execution_engine: ExecutionEngine) -> None:
payload = body.execution_payload
# Verify consistency of the parent hash with respect to the previous execution payload header
assert payload.parent_hash == state.latest_execution_payload_header.block_hash
# Verify prev_randao
assert payload.prev_randao == get_randao_mix(state, get_current_epoch(state))
# Verify timestamp
assert payload.timestamp == compute_timestamp_at_slot(state, state.slot)
# Verify commitments are under limit
assert len(body.blob_kzg_commitments) <= MAX_BLOBS_PER_BLOCK
# Verify the execution payload is valid
# Pass `versioned_hashes` to Execution Engine
# Pass `parent_beacon_block_root` to Execution Engine
versioned_hashes = [kzg_commitment_to_versioned_hash(commitment) for commitment in body.blob_kzg_commitments]
assert execution_engine.verify_and_notify_new_payload(
NewPayloadRequest(
execution_payload=payload,
versioned_hashes=versioned_hashes,
parent_beacon_block_root=state.latest_block_header.parent_root,
)
)
# Cache execution payload header
state.latest_execution_payload_header = ExecutionPayloadHeader(
parent_hash=payload.parent_hash,
fee_recipient=payload.fee_recipient,
state_root=payload.state_root,
receipts_root=payload.receipts_root,
logs_bloom=payload.logs_bloom,
prev_randao=payload.prev_randao,
block_number=payload.block_number,
gas_limit=payload.gas_limit,
gas_used=payload.gas_used,
timestamp=payload.timestamp,
extra_data=payload.extra_data,
base_fee_per_gas=payload.base_fee_per_gas,
block_hash=payload.block_hash,
transactions_root=hash_tree_root(payload.transactions),
withdrawals_root=hash_tree_root(payload.withdrawals),
excess_data_gas=payload.excess_data_gas,
execution_witness_root=hash_tree_root(payload.execution_witness), # [New in EIP6800]
)
```
## Testing
TBD

View File

@ -0,0 +1,145 @@
# EIP-6800 -- Fork Logic
## 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 eip6800](#fork-to-eip6800)
- [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 the eip6800 upgrade.
## Configuration
Warning: this configuration is not definitive.
| Name | Value |
| - | - |
| `EIP6800_FORK_VERSION` | `Version('0x05000000')` |
| `EIP6800_FORK_EPOCH` | `Epoch(18446744073709551615)` **TBD** |
## 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 >= EIP6800_FORK_EPOCH:
return EIP6800_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 eip6800
### Fork trigger
The fork is triggered at epoch `EIP6800_FORK_EPOCH`.
Note that for the pure eip6800 networks, we don't apply `upgrade_to_eip6800` since it starts with the eip6800 version logic.
### Upgrading the state
If `state.slot % SLOTS_PER_EPOCH == 0` and `compute_epoch_at_slot(state.slot) == EIP6800_FORK_EPOCH`,
an irregular state change is made to upgrade to eip6800.
The upgrade occurs after the completion of the inner loop of `process_slots` that sets `state.slot` equal to `EIP6800_FORK_EPOCH * SLOTS_PER_EPOCH`.
Care must be taken when transitioning through the fork boundary as implementations will need a modified [state transition function](../phase0/beacon-chain.md#beacon-chain-state-transition-function) that deviates from the Phase 0 document.
In particular, the outer `state_transition` function defined in the Phase 0 document will not expose the precise fork slot to execute the upgrade in the presence of skipped slots at the fork boundary. Instead, the logic must be within `process_slots`.
```python
def upgrade_to_eip6800(pre: deneb.BeaconState) -> BeaconState:
epoch = capella.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,
excess_data_gas=uint256(0),
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,
execution_witness_root=hash_tree_root(ExecutionWitness([], [])) # New in eip6800
)
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=EIP6800_FORK_VERSION, # [Modified in eip6800]
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,
)
return post
```

View File

@ -106,18 +106,19 @@ def get_custody_columns(node_id: NodeID, custody_subnet_count: uint64) -> Sequen
assert custody_subnet_count <= DATA_COLUMN_SIDECAR_SUBNET_COUNT
subnet_ids: List[uint64] = []
i = 0
current_id = uint256(node_id)
while len(subnet_ids) < custody_subnet_count:
if node_id == UINT256_MAX:
node_id = NodeID(0)
subnet_id = (
bytes_to_uint64(hash(uint_to_bytes(uint256(node_id + i)))[0:8])
bytes_to_uint64(hash(uint_to_bytes(uint256(current_id)))[0:8])
% DATA_COLUMN_SIDECAR_SUBNET_COUNT
)
if subnet_id not in subnet_ids:
subnet_ids.append(subnet_id)
i += 1
if current_id == UINT256_MAX:
# Overflow prevention
current_id = NodeID(0)
current_id += 1
assert len(subnet_ids) == len(set(subnet_ids))
columns_per_subnet = NUMBER_OF_COLUMNS // DATA_COLUMN_SIDECAR_SUBNET_COUNT

View File

@ -25,6 +25,7 @@
- [The Req/Resp domain](#the-reqresp-domain)
- [Messages](#messages)
- [DataColumnSidecarsByRoot v1](#datacolumnsidecarsbyroot-v1)
- [DataColumnSidecarsByRange v1](#datacolumnsidecarsbyrange-v1)
- [The discovery domain: discv5](#the-discovery-domain-discv5)
- [ENR structure](#enr-structure)
- [`custody_subnet_count`](#custody_subnet_count)
@ -200,6 +201,85 @@ Clients SHOULD include a sidecar in the response as soon as it passes the gossip
Clients SHOULD NOT respond with sidecars related to blocks that fail gossip validation rules.
Clients SHOULD NOT respond with sidecars related to blocks that fail the beacon chain state transition
##### DataColumnSidecarsByRange v1
**Protocol ID:** `/eth2/beacon_chain/req/data_column_sidecars_by_range/1/`
The `<context-bytes>` field is calculated as `context = compute_fork_digest(fork_version, genesis_validators_root)`:
[1]: # (eth2spec: skip)
| `fork_version` | Chunk SSZ type |
|--------------------------|-------------------------------|
| `EIP7594_FORK_VERSION` | `eip7594.DataColumnSidecar` |
Request Content:
```
(
start_slot: Slot
count: uint64
columns: List[ColumnIndex, NUMBER_OF_COLUMNS]
)
```
Response Content:
```
(
List[DataColumnSidecar, MAX_REQUEST_DATA_COLUMN_SIDECARS]
)
```
Requests data column sidecars in the slot range `[start_slot, start_slot + count)` of the given `columns`, leading up to the current head block as selected by fork choice.
Before consuming the next response chunk, the response reader SHOULD verify the data column sidecar is well-formatted, has valid inclusion proof, and is correct w.r.t. the expected KZG commitments through `verify_data_column_sidecar_kzg_proofs`.
`DataColumnSidecarsByRange` is primarily used to sync data columns that may have been missed on gossip and to sync within the `MIN_EPOCHS_FOR_DATA_COLUMN_SIDECARS_REQUESTS` window.
The request MUST be encoded as an SSZ-container.
The response MUST consist of zero or more `response_chunk`.
Each _successful_ `response_chunk` MUST contain a single `DataColumnSidecar` payload.
Let `data_column_serve_range` be `[max(current_epoch - MIN_EPOCHS_FOR_DATA_COLUMN_SIDECARS_REQUESTS, EIP7594_FORK_EPOCH), current_epoch]`.
Clients MUST keep a record of data column sidecars seen on the epoch range `data_column_serve_range`
where `current_epoch` is defined by the current wall-clock time,
and clients MUST support serving requests of data columns on this range.
Peers that are unable to reply to data column sidecar requests within the
range `data_column_serve_range` SHOULD respond with error code `3: ResourceUnavailable`.
Such peers that are unable to successfully reply to this range of requests MAY get descored
or disconnected at any time.
*Note*: The above requirement implies that nodes that start from a recent weak subjectivity checkpoint
MUST backfill the local data columns database to at least the range `data_column_serve_range`
to be fully compliant with `DataColumnSidecarsByRange` requests.
*Note*: Although clients that bootstrap from a weak subjectivity checkpoint can begin
participating in the networking immediately, other peers MAY
disconnect and/or temporarily ban such an un-synced or semi-synced client.
Clients MUST respond with at least the data column sidecars of the first blob-carrying block that exists in the range, if they have it, and no more than `MAX_REQUEST_DATA_COLUMN_SIDECARS` sidecars.
Clients MUST include all data column sidecars of each block from which they include data column sidecars.
The following data column sidecars, where they exist, MUST be sent in `(slot, column_index)` order.
Slots that do not contain known data columns MUST be skipped, mimicking the behaviour
of the `BlocksByRange` request. Only response chunks with known data columns should
therefore be sent.
Clients MAY limit the number of data column sidecars in the response.
The response MUST contain no more than `count * NUMBER_OF_COLUMNS` data column sidecars.
Clients MUST respond with data columns sidecars from their view of the current fork choice
-- that is, data column sidecars as included by blocks from the single chain defined by the current head.
Of note, blocks from slots before the finalization MUST lead to the finalized block reported in the `Status` handshake.
Clients MUST respond with data column sidecars that are consistent from a single chain within the context of the request.
After the initial data column sidecar, clients MAY stop in the process of responding if their fork choice changes the view of the chain in the context of the request.
### The discovery domain: discv5
#### ENR structure

View File

@ -683,6 +683,9 @@ def recover_all_cells(cell_ids: Sequence[CellID], cells: Sequence[Cell]) -> Sequ
# Check that each cell is the correct length
for cell in cells:
assert len(cell) == BYTES_PER_CELL
# Check that the cell ids are within bounds
for cell_id in cell_ids:
assert cell_id < CELLS_PER_EXT_BLOB
# Get the extended domain
roots_of_unity_extended = compute_roots_of_unity(FIELD_ELEMENTS_PER_EXT_BLOB)

View File

@ -28,8 +28,7 @@
- [`PendingBalanceDeposit`](#pendingbalancedeposit)
- [`PendingPartialWithdrawal`](#pendingpartialwithdrawal)
- [`ExecutionLayerWithdrawalRequest`](#executionlayerwithdrawalrequest)
- [`Consolidation`](#consolidation)
- [`SignedConsolidation`](#signedconsolidation)
- [`ExecutionLayerConsolidationRequest`](#executionlayerconsolidationrequest)
- [`PendingConsolidation`](#pendingconsolidation)
- [Modified Containers](#modified-containers)
- [`AttesterSlashing`](#attesterslashing)
@ -42,6 +41,7 @@
- [`BeaconState`](#beaconstate)
- [Helper functions](#helper-functions)
- [Predicates](#predicates)
- [Updated `compute_proposer_index`](#updated-compute_proposer_index)
- [Updated `is_eligible_for_activation_queue`](#updated-is_eligible_for_activation_queue)
- [New `is_compounding_withdrawal_credential`](#new-is_compounding_withdrawal_credential)
- [New `has_compounding_withdrawal_credential`](#new-has_compounding_withdrawal_credential)
@ -94,8 +94,8 @@
- [New `process_execution_layer_withdrawal_request`](#new-process_execution_layer_withdrawal_request)
- [Deposit requests](#deposit-requests)
- [New `process_deposit_request`](#new-process_deposit_request)
- [Consolidations](#consolidations)
- [New `process_consolidation`](#new-process_consolidation)
- [Execution layer consolidation requests](#execution-layer-consolidation-requests)
- [New `process_execution_layer_consolidation_request`](#new-process_execution_layer_consolidation_request)
- [Testing](#testing)
<!-- END doctoc generated TOC please keep comment here to allow auto update -->
@ -164,16 +164,16 @@ The following values are (non-configurable) constants used throughout the specif
| Name | Value |
| - | - |
| `MAX_CONSOLIDATIONS` | `uint64(1)` |
| `MAX_ATTESTER_SLASHINGS_ELECTRA` | `2**0` (= 1) | *[New in Electra:EIP7549]* |
| `MAX_ATTESTATIONS_ELECTRA` | `2**3` (= 8) | *[New in Electra:EIP7549]* |
### Execution
| Name | Value | Description |
| - | - | - |
| `MAX_DEPOSIT_REQUESTS_PER_PAYLOAD` | `uint64(2**13)` (= 8,192) | *[New in Electra:EIP6110]* Maximum number of deposit requests allowed in each payload |
| `MAX_ATTESTER_SLASHINGS_ELECTRA` | `2**0` (= 1) | *[New in Electra:EIP7549]* |
| `MAX_ATTESTATIONS_ELECTRA` | `2**3` (= 8) | *[New in Electra:EIP7549]* |
| `MAX_DEPOSIT_REQUESTS_PER_PAYLOAD` | `uint64(2**13)` (= 8,192) | *[New in Electra:EIP6110]* Maximum number of deposit receipts allowed in each payload |
| `MAX_WITHDRAWAL_REQUESTS_PER_PAYLOAD` | `uint64(2**4)` (= 16)| *[New in Electra:EIP7002]* Maximum number of execution layer withdrawal requests in each payload |
| `MAX_CONSOLIDATION_REQUESTS_PER_PAYLOAD` | `uint64(1)` (= 1) | *[New in Electra:EIP7002]* Maximum number of execution layer consolidation requests in each payload |
### Withdrawals processing
@ -238,25 +238,15 @@ class ExecutionLayerWithdrawalRequest(Container):
amount: Gwei
```
#### `Consolidation`
#### `ExecutionLayerConsolidationRequest`
*Note*: The container is new in EIP7251.
```python
class Consolidation(Container):
source_index: ValidatorIndex
target_index: ValidatorIndex
epoch: Epoch
```
#### `SignedConsolidation`
*Note*: The container is new in EIP7251.
```python
class SignedConsolidation(Container):
message: Consolidation
signature: BLSSignature
class ExecutionLayerConsolidationRequest(Container):
source_address: ExecutionAddress
source_pubkey: BLSPubkey
target_pubkey: BLSPubkey
```
#### `PendingConsolidation`
@ -319,7 +309,6 @@ class BeaconBlockBody(Container):
execution_payload: ExecutionPayload # [Modified in Electra:EIP6110:EIP7002]
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 Electra:EIP7251]
```
#### `ExecutionPayload`
@ -348,6 +337,8 @@ class ExecutionPayload(Container):
deposit_requests: List[DepositRequest, MAX_DEPOSIT_REQUESTS_PER_PAYLOAD] # [New in Electra:EIP6110]
# [New in Electra:EIP7002:EIP7251]
withdrawal_requests: List[ExecutionLayerWithdrawalRequest, MAX_WITHDRAWAL_REQUESTS_PER_PAYLOAD]
# [New in Electra:EIP7251]
consolidation_requests: List[ExecutionLayerConsolidationRequest, MAX_CONSOLIDATION_REQUESTS_PER_PAYLOAD]
```
#### `ExecutionPayloadHeader`
@ -375,6 +366,7 @@ class ExecutionPayloadHeader(Container):
excess_blob_gas: uint64
deposit_requests_root: Root # [New in Electra:EIP6110]
withdrawal_requests_root: Root # [New in Electra:EIP7002:EIP7251]
consolidation_requests_root: Root # [New in Electra:EIP7251]
```
#### `BeaconState`
@ -438,6 +430,27 @@ class BeaconState(Container):
### Predicates
#### Updated `compute_proposer_index`
```python
def compute_proposer_index(state: BeaconState, indices: Sequence[ValidatorIndex], seed: Bytes32) -> ValidatorIndex:
"""
Return from ``indices`` a random index sampled by effective balance.
"""
assert len(indices) > 0
MAX_RANDOM_BYTE = 2**8 - 1
i = uint64(0)
total = uint64(len(indices))
while True:
candidate_index = indices[compute_shuffled_index(i % total, total, seed)]
random_byte = hash(seed + uint_to_bytes(uint64(i // 32)))[i % 32]
effective_balance = state.validators[candidate_index].effective_balance
# [Modified in Electra:EIP7251]
if effective_balance * MAX_RANDOM_BYTE >= MAX_EFFECTIVE_BALANCE_ELECTRA * random_byte:
return candidate_index
i += 1
```
#### Updated `is_eligible_for_activation_queue`
```python
@ -1013,6 +1026,7 @@ def process_execution_payload(state: BeaconState, body: BeaconBlockBody, executi
excess_blob_gas=payload.excess_blob_gas,
deposit_requests_root=hash_tree_root(payload.deposit_requests), # [New in Electra:EIP6110]
withdrawal_requests_root=hash_tree_root(payload.withdrawal_requests), # [New in Electra:EIP7002:EIP7251]
consolidation_requests_root=hash_tree_root(payload.consolidation_requests), # [New in Electra:EIP7251]
)
```
@ -1042,10 +1056,11 @@ def process_operations(state: BeaconState, body: BeaconBlockBody) -> None:
for_ops(body.deposits, process_deposit) # [Modified in Electra:EIP7251]
for_ops(body.voluntary_exits, process_voluntary_exit) # [Modified in Electra:EIP7251]
for_ops(body.bls_to_execution_changes, process_bls_to_execution_change)
for_ops(body.execution_payload.deposit_requests, process_deposit_request) # [New in Electra:EIP6110]
# [New in Electra:EIP7002:EIP7251]
for_ops(body.execution_payload.withdrawal_requests, process_execution_layer_withdrawal_request)
for_ops(body.execution_payload.deposit_requests, process_deposit_request) # [New in Electra:EIP6110]
for_ops(body.consolidations, process_consolidation) # [New in Electra:EIP7251]
# [New in Electra:EIP7251]
for_ops(body.execution_payload.consolidation_requests, process_execution_layer_consolidation_request)
```
##### Attestations
@ -1292,43 +1307,62 @@ def process_deposit_request(state: BeaconState, deposit_request: DepositRequest)
)
```
##### Consolidations
##### Execution layer consolidation requests
###### New `process_consolidation`
###### New `process_execution_layer_consolidation_request`
```python
def process_consolidation(state: BeaconState, signed_consolidation: SignedConsolidation) -> None:
# If the pending consolidations queue is full, no consolidations are allowed in the block
assert len(state.pending_consolidations) < PENDING_CONSOLIDATIONS_LIMIT
# If there is too little available consolidation churn limit, no consolidations are allowed in the block
assert get_consolidation_churn_limit(state) > MIN_ACTIVATION_BALANCE
consolidation = signed_consolidation.message
# Verify that source != target, so a consolidation cannot be used as an exit.
assert consolidation.source_index != consolidation.target_index
def process_execution_layer_consolidation_request(
state: BeaconState,
execution_layer_consolidation_request: ExecutionLayerConsolidationRequest
) -> None:
# If the pending consolidations queue is full, consolidation requests are ignored
if len(state.pending_consolidations) == PENDING_CONSOLIDATIONS_LIMIT:
return
# If there is too little available consolidation churn limit, consolidation requests are ignored
if get_consolidation_churn_limit(state) <= MIN_ACTIVATION_BALANCE:
return
validator_pubkeys = [v.pubkey for v in state.validators]
# Verify pubkeys exists
request_source_pubkey = execution_layer_consolidation_request.source_pubkey
request_target_pubkey = execution_layer_consolidation_request.target_pubkey
if request_source_pubkey not in validator_pubkeys:
return
if request_target_pubkey not in validator_pubkeys:
return
source_index = ValidatorIndex(validator_pubkeys.index(request_source_pubkey))
target_index = ValidatorIndex(validator_pubkeys.index(request_target_pubkey))
source_validator = state.validators[source_index]
target_validator = state.validators[target_index]
# Verify that source != target, so a consolidation cannot be used as an exit.
if source_index == target_index:
return
# Verify source withdrawal credentials
has_correct_credential = has_execution_withdrawal_credential(source_validator)
is_correct_source_address = (
source_validator.withdrawal_credentials[12:] == execution_layer_consolidation_request.source_address
)
if not (has_correct_credential and is_correct_source_address):
return
# Verify that target has execution withdrawal credentials
if not has_execution_withdrawal_credential(target_validator):
return
source_validator = state.validators[consolidation.source_index]
target_validator = state.validators[consolidation.target_index]
# Verify the source and the target are active
current_epoch = get_current_epoch(state)
assert is_active_validator(source_validator, current_epoch)
assert is_active_validator(target_validator, current_epoch)
if not is_active_validator(source_validator, current_epoch):
return
if not is_active_validator(target_validator, current_epoch):
return
# Verify exits for source and target have not been initiated
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
# Verify the source and the target have Execution layer withdrawal credentials
assert has_execution_withdrawal_credential(source_validator)
assert has_execution_withdrawal_credential(target_validator)
# Verify the same withdrawal address
assert source_validator.withdrawal_credentials[12:] == target_validator.withdrawal_credentials[12:]
# Verify consolidation is signed by the source and the target
domain = compute_domain(DOMAIN_CONSOLIDATION, genesis_validators_root=state.genesis_validators_root)
signing_root = compute_signing_root(consolidation, domain)
pubkeys = [source_validator.pubkey, target_validator.pubkey]
assert bls.FastAggregateVerify(pubkeys, signing_root, signed_consolidation.signature)
if source_validator.exit_epoch != FAR_FUTURE_EPOCH:
return
if target_validator.exit_epoch != FAR_FUTURE_EPOCH:
return
# Initiate source validator exit and append pending consolidation
source_validator.exit_epoch = compute_consolidation_epoch_and_update_churn(
@ -1338,8 +1372,8 @@ def process_consolidation(state: BeaconState, signed_consolidation: SignedConsol
source_validator.exit_epoch + MIN_VALIDATOR_WITHDRAWABILITY_DELAY
)
state.pending_consolidations.append(PendingConsolidation(
source_index=consolidation.source_index,
target_index=consolidation.target_index
source_index=source_index,
target_index=target_index
))
```

View File

@ -91,7 +91,8 @@ def upgrade_to_electra(pre: deneb.BeaconState) -> BeaconState:
blob_gas_used=pre.latest_execution_payload_header.blob_gas_used,
excess_blob_gas=pre.latest_execution_payload_header.excess_blob_gas,
deposit_requests_root=Root(), # [New in Electra:EIP6110]
withdrawal_requests_root=Root(), # [New in Electra:EIP7002],
withdrawal_requests_root=Root(), # [New in Electra:EIP7002]
consolidation_requests_root=Root(), # [New in Electra:EIP7251]
)
exit_epochs = [v.exit_epoch for v in pre.validators if v.exit_epoch != FAR_FUTURE_EPOCH]

View File

@ -8,6 +8,10 @@
- [Introduction](#introduction)
- [Prerequisites](#prerequisites)
- [Containers](#containers)
- [Modified Containers](#modified-containers)
- [`AggregateAndProof`](#aggregateandproof)
- [`SignedAggregateAndProof`](#signedaggregateandproof)
- [Block proposal](#block-proposal)
- [Constructing the `BeaconBlockBody`](#constructing-the-beaconblockbody)
- [Attester slashings](#attester-slashings)
@ -34,6 +38,27 @@ All behaviors and definitions defined in this document, and documents it extends
All terminology, constants, functions, and protocol mechanics defined in the updated Beacon Chain doc of [Electra](./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.
## Containers
### Modified Containers
#### `AggregateAndProof`
```python
class AggregateAndProof(Container):
aggregator_index: ValidatorIndex
aggregate: Attestation # [Modified in Electra:EIP7549]
selection_proof: BLSSignature
```
#### `SignedAggregateAndProof`
```python
class SignedAggregateAndProof(Container):
message: AggregateAndProof # [Modified in Electra:EIP7549]
signature: BLSSignature
```
## Block proposal
### Constructing the `BeaconBlockBody`

View File

@ -356,7 +356,7 @@ to subscribing nodes (typically validators) to be included in future blocks.
We define the following variables for convenience:
- `aggregate_and_proof = signed_aggregate_and_proof.message`
- `aggregate = aggregate_and_proof.aggregate`
- `index = aggregate.index`
- `index = aggregate.data.index`
- `aggregation_bits = attestation.aggregation_bits`
The following validations MUST pass before forwarding the `signed_aggregate_and_proof` on the network.
@ -436,7 +436,7 @@ The `beacon_attestation_{subnet_id}` topics are used to propagate unaggregated a
to the subnet `subnet_id` (typically beacon and persistent committees) to be aggregated before being gossiped to `beacon_aggregate_and_proof`.
We define the following variables for convenience:
- `index = attestation.index`
- `index = attestation.data.index`
- `aggregation_bits = attestation.aggregation_bits`
The following validations MUST pass before forwarding the `attestation` on the subnet.

View File

@ -1 +1 @@
1.5.0-alpha.1
1.5.0-alpha.2

View File

@ -65,6 +65,25 @@ def test_get_custody_columns__max_node_id_max_custody_subnet_count(spec):
)
@with_eip7594_and_later
@spec_test
@single_phase
def test_get_custody_columns__max_node_id_max_custody_subnet_count_minus_1(spec):
rng = random.Random(1111)
yield from _run_get_custody_columns(
spec, rng, node_id=2**256 - 2,
custody_subnet_count=spec.config.DATA_COLUMN_SIDECAR_SUBNET_COUNT,
)
@with_eip7594_and_later
@spec_test
@single_phase
def test_get_custody_columns__short_node_id(spec):
rng = random.Random(1111)
yield from _run_get_custody_columns(spec, rng, node_id=1048576, custody_subnet_count=1)
@with_eip7594_and_later
@spec_test
@single_phase

View File

@ -11,8 +11,8 @@ from eth2spec.test.context import (
def test_invariants(spec):
assert spec.FIELD_ELEMENTS_PER_BLOB % spec.FIELD_ELEMENTS_PER_CELL == 0
assert spec.FIELD_ELEMENTS_PER_EXT_BLOB % spec.config.NUMBER_OF_COLUMNS == 0
assert spec.SAMPLES_PER_SLOT <= spec.config.NUMBER_OF_COLUMNS
assert spec.CUSTODY_REQUIREMENT <= spec.config.DATA_COLUMN_SIDECAR_SUBNET_COUNT
assert spec.config.SAMPLES_PER_SLOT <= spec.config.NUMBER_OF_COLUMNS
assert spec.config.CUSTODY_REQUIREMENT <= spec.config.DATA_COLUMN_SIDECAR_SUBNET_COUNT
assert spec.config.DATA_COLUMN_SIDECAR_SUBNET_COUNT <= spec.config.NUMBER_OF_COLUMNS
assert spec.config.NUMBER_OF_COLUMNS % spec.config.DATA_COLUMN_SIDECAR_SUBNET_COUNT == 0
assert spec.config.MAX_REQUEST_DATA_COLUMN_SIDECARS == (

View File

@ -9,7 +9,7 @@ from eth2spec.test.context import (
def run_get_custody_columns(spec, peer_count, custody_subnet_count):
assignments = [spec.get_custody_columns(node_id, custody_subnet_count) for node_id in range(peer_count)]
columns_per_subnet = spec.NUMBER_OF_COLUMNS // spec.config.DATA_COLUMN_SIDECAR_SUBNET_COUNT
columns_per_subnet = spec.config.NUMBER_OF_COLUMNS // spec.config.DATA_COLUMN_SIDECAR_SUBNET_COUNT
for assignment in assignments:
assert len(assignment) == custody_subnet_count * columns_per_subnet
assert len(assignment) == len(set(assignment))
@ -20,8 +20,8 @@ def run_get_custody_columns(spec, peer_count, custody_subnet_count):
@single_phase
def test_get_custody_columns_peers_within_number_of_columns(spec):
peer_count = 10
custody_subnet_count = spec.CUSTODY_REQUIREMENT
assert spec.NUMBER_OF_COLUMNS > peer_count
custody_subnet_count = spec.config.CUSTODY_REQUIREMENT
assert spec.config.NUMBER_OF_COLUMNS > peer_count
run_get_custody_columns(spec, peer_count, custody_subnet_count)
@ -30,8 +30,8 @@ def test_get_custody_columns_peers_within_number_of_columns(spec):
@single_phase
def test_get_custody_columns_peers_more_than_number_of_columns(spec):
peer_count = 200
custody_subnet_count = spec.CUSTODY_REQUIREMENT
assert spec.NUMBER_OF_COLUMNS < peer_count
custody_subnet_count = spec.config.CUSTODY_REQUIREMENT
assert spec.config.NUMBER_OF_COLUMNS < peer_count
run_get_custody_columns(spec, peer_count, custody_subnet_count)

View File

@ -1,811 +0,0 @@
from eth2spec.test.helpers.constants import MINIMAL
from eth2spec.test.context import (
with_electra_and_later,
with_presets,
always_bls,
spec_test,
single_phase,
with_custom_state,
scaled_churn_balances_exceed_activation_exit_churn_limit,
default_activation_threshold,
)
from eth2spec.test.helpers.keys import pubkey_to_privkey
from eth2spec.test.helpers.consolidations import (
run_consolidation_processing,
sign_consolidation,
)
from eth2spec.test.helpers.withdrawals import (
set_eth1_withdrawal_credential_with_balance,
set_compounding_withdrawal_credential,
)
# ***********************
# * CONSOLIDATION TESTS *
# ***********************
@with_electra_and_later
@with_presets([MINIMAL], "need sufficient consolidation churn limit")
@with_custom_state(
balances_fn=scaled_churn_balances_exceed_activation_exit_churn_limit,
threshold_fn=default_activation_threshold,
)
@spec_test
@single_phase
def test_basic_consolidation_in_current_consolidation_epoch(spec, state):
# This state has 256 validators each with 32 ETH in MINIMAL preset, 128 ETH consolidation churn
current_epoch = spec.get_current_epoch(state)
source_index = spec.get_active_validator_indices(state, current_epoch)[0]
target_index = spec.get_active_validator_indices(state, current_epoch)[1]
source_privkey = pubkey_to_privkey[state.validators[source_index].pubkey]
target_privkey = pubkey_to_privkey[state.validators[target_index].pubkey]
# Set source and target withdrawal credentials to the same eth1 credential
set_eth1_withdrawal_credential_with_balance(spec, state, source_index)
set_eth1_withdrawal_credential_with_balance(spec, state, target_index)
signed_consolidation = sign_consolidation(
spec,
state,
spec.Consolidation(
epoch=current_epoch, source_index=source_index, target_index=target_index
),
source_privkey,
target_privkey,
)
# Set earliest consolidation epoch to the expected exit epoch
expected_exit_epoch = spec.compute_activation_exit_epoch(current_epoch)
state.earliest_consolidation_epoch = expected_exit_epoch
consolidation_churn_limit = spec.get_consolidation_churn_limit(state)
# Set the consolidation balance to consume equal to churn limit
state.consolidation_balance_to_consume = consolidation_churn_limit
yield from run_consolidation_processing(spec, state, signed_consolidation)
# Check consolidation churn is decremented correctly
assert (
state.consolidation_balance_to_consume
== consolidation_churn_limit - spec.MIN_ACTIVATION_BALANCE
)
# Check exit epoch
assert state.validators[0].exit_epoch == expected_exit_epoch
@with_electra_and_later
@with_presets([MINIMAL], "need sufficient consolidation churn limit")
@with_custom_state(
balances_fn=scaled_churn_balances_exceed_activation_exit_churn_limit,
threshold_fn=default_activation_threshold,
)
@spec_test
@single_phase
def test_basic_consolidation_in_new_consolidation_epoch(spec, state):
# This state has 256 validators each with 32 ETH in MINIMAL preset, 128 ETH consolidation churn
# Set consolidation balance to consume to some arbitrary nonzero value below the churn limit
state.consolidation_balance_to_consume = spec.EFFECTIVE_BALANCE_INCREMENT
current_epoch = spec.get_current_epoch(state)
source_index = spec.get_active_validator_indices(state, current_epoch)[0]
target_index = spec.get_active_validator_indices(state, current_epoch)[1]
source_privkey = pubkey_to_privkey[state.validators[source_index].pubkey]
target_privkey = pubkey_to_privkey[state.validators[target_index].pubkey]
# Set source and target withdrawal credentials to the same eth1 credential
set_eth1_withdrawal_credential_with_balance(spec, state, source_index)
set_eth1_withdrawal_credential_with_balance(spec, state, target_index)
signed_consolidation = sign_consolidation(
spec,
state,
spec.Consolidation(
epoch=current_epoch, source_index=source_index, target_index=target_index
),
source_privkey,
target_privkey,
)
yield from run_consolidation_processing(spec, state, signed_consolidation)
expected_exit_epoch = spec.compute_activation_exit_epoch(current_epoch)
# Check consolidation churn is decremented correctly
# consolidation_balance_to_consume is replenished to the churn limit since we move to a new consolidation epoch
consolidation_churn_limit = spec.get_consolidation_churn_limit(state)
assert (
state.consolidation_balance_to_consume
== consolidation_churn_limit - spec.MIN_ACTIVATION_BALANCE
)
# Check exit epochs
assert state.validators[0].exit_epoch == expected_exit_epoch
@with_electra_and_later
@with_presets([MINIMAL], "need sufficient consolidation churn limit")
@with_custom_state(
balances_fn=scaled_churn_balances_exceed_activation_exit_churn_limit,
threshold_fn=default_activation_threshold,
)
@spec_test
@single_phase
def test_basic_consolidation_with_preexisting_churn(spec, state):
# This state has 256 validators each with 32 ETH in MINIMAL preset, 128 ETH consolidation churn
current_epoch = spec.get_current_epoch(state)
source_index = spec.get_active_validator_indices(state, current_epoch)[0]
target_index = spec.get_active_validator_indices(state, current_epoch)[1]
source_privkey = pubkey_to_privkey[state.validators[source_index].pubkey]
target_privkey = pubkey_to_privkey[state.validators[target_index].pubkey]
# Set source and target withdrawal credentials to the same eth1 credential
set_eth1_withdrawal_credential_with_balance(spec, state, source_index)
set_eth1_withdrawal_credential_with_balance(spec, state, target_index)
signed_consolidation = sign_consolidation(
spec,
state,
spec.Consolidation(
epoch=current_epoch, source_index=source_index, target_index=target_index
),
source_privkey,
target_privkey,
)
# Set earliest consolidation epoch to the expected exit epoch
expected_exit_epoch = spec.compute_activation_exit_epoch(current_epoch)
state.earliest_consolidation_epoch = expected_exit_epoch
# Set some nonzero preexisting churn lower than churn limit and sufficient to process the consolidation
preexisting_churn = 2 * spec.MIN_ACTIVATION_BALANCE
state.consolidation_balance_to_consume = preexisting_churn
yield from run_consolidation_processing(spec, state, signed_consolidation)
# Check consolidation churn is decremented correctly
assert (
state.consolidation_balance_to_consume
== preexisting_churn - spec.MIN_ACTIVATION_BALANCE
)
# Check exit epoch
assert state.validators[0].exit_epoch == expected_exit_epoch
@with_electra_and_later
@with_presets([MINIMAL], "need sufficient consolidation churn limit")
@with_custom_state(
balances_fn=scaled_churn_balances_exceed_activation_exit_churn_limit,
threshold_fn=default_activation_threshold,
)
@spec_test
@single_phase
def test_basic_consolidation_with_insufficient_preexisting_churn(spec, state):
# This state has 256 validators each with 32 ETH in MINIMAL preset, 128 ETH consolidation churn
current_epoch = spec.get_current_epoch(state)
source_index = spec.get_active_validator_indices(state, current_epoch)[0]
target_index = spec.get_active_validator_indices(state, current_epoch)[1]
source_privkey = pubkey_to_privkey[state.validators[source_index].pubkey]
target_privkey = pubkey_to_privkey[state.validators[target_index].pubkey]
# Set source and target withdrawal credentials to the same eth1 credential
set_eth1_withdrawal_credential_with_balance(spec, state, source_index)
set_eth1_withdrawal_credential_with_balance(spec, state, target_index)
signed_consolidation = sign_consolidation(
spec,
state,
spec.Consolidation(
epoch=current_epoch, source_index=source_index, target_index=target_index
),
source_privkey,
target_privkey,
)
# Set earliest consolidation epoch to the first available epoch
state.earliest_consolidation_epoch = spec.compute_activation_exit_epoch(
current_epoch
)
# Set preexisting churn lower than required to process the consolidation
preexisting_churn = spec.MIN_ACTIVATION_BALANCE - spec.EFFECTIVE_BALANCE_INCREMENT
state.consolidation_balance_to_consume = preexisting_churn
yield from run_consolidation_processing(spec, state, signed_consolidation)
# It takes one more epoch to process the consolidation due to insufficient churn
expected_exit_epoch = spec.compute_activation_exit_epoch(current_epoch) + 1
# Check consolidation churn is decremented correctly
consolidation_churn_limit = spec.get_consolidation_churn_limit(state)
remainder = spec.MIN_ACTIVATION_BALANCE % preexisting_churn
assert (
state.consolidation_balance_to_consume == consolidation_churn_limit - remainder
)
# Check exit epoch
assert state.validators[0].exit_epoch == expected_exit_epoch
@with_electra_and_later
@with_presets([MINIMAL], "need sufficient consolidation churn limit")
@with_custom_state(
balances_fn=scaled_churn_balances_exceed_activation_exit_churn_limit,
threshold_fn=default_activation_threshold,
)
@spec_test
@single_phase
def test_basic_consolidation_with_compounding_credential(spec, state):
# This state has 256 validators each with 32 ETH in MINIMAL preset, 128 ETH consolidation churn
consolidation_churn_limit = spec.get_consolidation_churn_limit(state)
# Set the consolidation balance to consume equal to churn limit
state.consolidation_balance_to_consume = consolidation_churn_limit
current_epoch = spec.get_current_epoch(state)
source_index = spec.get_active_validator_indices(state, current_epoch)[0]
target_index = spec.get_active_validator_indices(state, current_epoch)[1]
source_privkey = pubkey_to_privkey[state.validators[source_index].pubkey]
target_privkey = pubkey_to_privkey[state.validators[target_index].pubkey]
# Set source and target withdrawal credentials to the same eth1 credential
set_compounding_withdrawal_credential(spec, state, source_index)
set_compounding_withdrawal_credential(spec, state, target_index)
signed_consolidation = sign_consolidation(
spec,
state,
spec.Consolidation(
epoch=current_epoch, source_index=source_index, target_index=target_index
),
source_privkey,
target_privkey,
)
yield from run_consolidation_processing(spec, state, signed_consolidation)
expected_exit_epoch = spec.compute_activation_exit_epoch(current_epoch)
# Check consolidation churn is decremented correctly
assert (
state.consolidation_balance_to_consume
== consolidation_churn_limit - spec.MIN_ACTIVATION_BALANCE
)
# Check exit epoch
assert state.validators[0].exit_epoch == expected_exit_epoch
@with_electra_and_later
@with_presets([MINIMAL], "need sufficient consolidation churn limit")
@with_custom_state(
balances_fn=scaled_churn_balances_exceed_activation_exit_churn_limit,
threshold_fn=default_activation_threshold,
)
@spec_test
@single_phase
def test_consolidation_churn_limit_balance(spec, state):
# This state has 256 validators each with 32 ETH in MINIMAL preset, 128 ETH consolidation churn
consolidation_churn_limit = spec.get_consolidation_churn_limit(state)
current_epoch = spec.get_current_epoch(state)
source_index = spec.get_active_validator_indices(state, current_epoch)[0]
source_validator = state.validators[source_index]
source_validator.effective_balance = consolidation_churn_limit
# Churn limit increases due to higher total balance
updated_consolidation_churn_limit = spec.get_consolidation_churn_limit(state)
target_index = spec.get_active_validator_indices(state, current_epoch)[1]
source_privkey = pubkey_to_privkey[state.validators[source_index].pubkey]
target_privkey = pubkey_to_privkey[state.validators[target_index].pubkey]
# Set source and target withdrawal credentials to the same eth1 credential
set_compounding_withdrawal_credential(spec, state, source_index)
set_compounding_withdrawal_credential(spec, state, target_index)
signed_consolidation = sign_consolidation(
spec,
state,
spec.Consolidation(
epoch=current_epoch, source_index=source_index, target_index=target_index
),
source_privkey,
target_privkey,
)
yield from run_consolidation_processing(spec, state, signed_consolidation)
# validator's effective balance fits into the churn, exit as soon as possible
expected_exit_epoch = spec.compute_activation_exit_epoch(current_epoch)
# Check consolidation churn is decremented correctly
assert (
state.consolidation_balance_to_consume
== updated_consolidation_churn_limit - consolidation_churn_limit
)
# Check exit epoch
assert state.validators[0].exit_epoch == expected_exit_epoch
@with_electra_and_later
@with_presets([MINIMAL], "need sufficient consolidation churn limit")
@with_custom_state(
balances_fn=scaled_churn_balances_exceed_activation_exit_churn_limit,
threshold_fn=default_activation_threshold,
)
@spec_test
@single_phase
def test_consolidation_balance_larger_than_churn_limit(spec, state):
# This state has 256 validators each with 32 ETH in MINIMAL preset, 128 ETH consolidation churn
consolidation_churn_limit = spec.get_consolidation_churn_limit(state)
current_epoch = spec.get_current_epoch(state)
source_index = spec.get_active_validator_indices(state, current_epoch)[0]
# Set source balance higher than consolidation churn limit
state.validators[source_index].effective_balance = 2 * consolidation_churn_limit
target_index = spec.get_active_validator_indices(state, current_epoch)[1]
source_privkey = pubkey_to_privkey[state.validators[source_index].pubkey]
target_privkey = pubkey_to_privkey[state.validators[target_index].pubkey]
# Set source and target withdrawal credentials to the same eth1 credential
set_compounding_withdrawal_credential(spec, state, source_index)
set_compounding_withdrawal_credential(spec, state, target_index)
# Consolidation churn limit increases due to higher total balance
new_churn_limit = spec.get_consolidation_churn_limit(state)
remainder = state.validators[source_index].effective_balance % new_churn_limit
expected_balance = new_churn_limit - remainder
signed_consolidation = sign_consolidation(
spec,
state,
spec.Consolidation(
epoch=current_epoch, source_index=source_index, target_index=target_index
),
source_privkey,
target_privkey,
)
yield from run_consolidation_processing(spec, state, signed_consolidation)
expected_exit_epoch = spec.compute_activation_exit_epoch(current_epoch) + 1
# Check consolidation churn is decremented correctly
assert state.consolidation_balance_to_consume == expected_balance
# Check exit epoch
assert state.validators[0].exit_epoch == expected_exit_epoch
@with_electra_and_later
@with_presets([MINIMAL], "need sufficient consolidation churn limit")
@with_custom_state(
balances_fn=scaled_churn_balances_exceed_activation_exit_churn_limit,
threshold_fn=default_activation_threshold,
)
@spec_test
@single_phase
def test_consolidation_balance_through_two_churn_epochs(spec, state):
# This state has 256 validators each with 32 ETH in MINIMAL preset, 128 ETH consolidation churn
consolidation_churn_limit = spec.get_consolidation_churn_limit(state)
current_epoch = spec.get_current_epoch(state)
source_index = spec.get_active_validator_indices(state, current_epoch)[0]
target_index = spec.get_active_validator_indices(state, current_epoch)[1]
source_privkey = pubkey_to_privkey[state.validators[source_index].pubkey]
target_privkey = pubkey_to_privkey[state.validators[target_index].pubkey]
# Set source and target withdrawal credentials to the same eth1 credential
set_compounding_withdrawal_credential(spec, state, source_index)
set_compounding_withdrawal_credential(spec, state, target_index)
# Set source balance higher than consolidation churn limit
state.validators[source_index].effective_balance = 3 * consolidation_churn_limit
new_churn_limit = spec.get_consolidation_churn_limit(state)
remainder = state.validators[source_index].effective_balance % new_churn_limit
expected_balance = new_churn_limit - remainder
signed_consolidation = sign_consolidation(
spec,
state,
spec.Consolidation(
epoch=current_epoch, source_index=source_index, target_index=target_index
),
source_privkey,
target_privkey,
)
yield from run_consolidation_processing(spec, state, signed_consolidation)
# when exiting a multiple of the churn limit greater than 1, an extra exit epoch is added
expected_exit_epoch = spec.compute_activation_exit_epoch(current_epoch) + 2
assert state.validators[0].exit_epoch == expected_exit_epoch
# since the earliest exit epoch moves to a new one, consolidation balance is back to full
assert state.consolidation_balance_to_consume == expected_balance
# Failing tests
@with_electra_and_later
@with_presets([MINIMAL], "need sufficient consolidation churn limit")
@with_custom_state(
balances_fn=scaled_churn_balances_exceed_activation_exit_churn_limit,
threshold_fn=default_activation_threshold,
)
@spec_test
@single_phase
def test_invalid_source_equals_target(spec, state):
current_epoch = spec.get_current_epoch(state)
validator_index = spec.get_active_validator_indices(state, current_epoch)[0]
validator_privkey = pubkey_to_privkey[state.validators[validator_index].pubkey]
# Set withdrawal credentials to eth1
set_eth1_withdrawal_credential_with_balance(spec, state, validator_index)
signed_consolidation = sign_consolidation(
spec,
state,
spec.Consolidation(
epoch=current_epoch,
source_index=validator_index,
target_index=validator_index,
),
validator_privkey,
validator_privkey,
)
yield from run_consolidation_processing(
spec, state, signed_consolidation, valid=False
)
@with_electra_and_later
@with_presets([MINIMAL], "need sufficient consolidation churn limit")
@with_custom_state(
balances_fn=scaled_churn_balances_exceed_activation_exit_churn_limit,
threshold_fn=default_activation_threshold,
)
@spec_test
@single_phase
def test_invalid_exceed_pending_consolidations_limit(spec, state):
state.pending_consolidations = [
spec.PendingConsolidation(source_index=0, target_index=1)
] * spec.PENDING_CONSOLIDATIONS_LIMIT
current_epoch = spec.get_current_epoch(state)
source_privkey = pubkey_to_privkey[state.validators[0].pubkey]
target_privkey = pubkey_to_privkey[state.validators[1].pubkey]
# Set source and target withdrawal credentials to the same eth1 credential
set_eth1_withdrawal_credential_with_balance(spec, state, 0)
set_eth1_withdrawal_credential_with_balance(spec, state, 1)
signed_consolidation = sign_consolidation(
spec,
state,
spec.Consolidation(epoch=current_epoch, source_index=0, target_index=1),
source_privkey,
target_privkey,
)
yield from run_consolidation_processing(
spec, state, signed_consolidation, valid=False
)
@with_electra_and_later
@with_presets([MINIMAL], "need sufficient consolidation churn limit")
@with_custom_state(
balances_fn=scaled_churn_balances_exceed_activation_exit_churn_limit,
threshold_fn=default_activation_threshold,
)
@spec_test
@single_phase
def test_invalid_not_enough_consolidation_churn_available(spec, state):
state.validators = state.validators[0:2]
state.pending_consolidations = [
spec.PendingConsolidation(source_index=0, target_index=1)
]
current_epoch = spec.get_current_epoch(state)
source_privkey = pubkey_to_privkey[state.validators[0].pubkey]
target_privkey = pubkey_to_privkey[state.validators[1].pubkey]
# Set source and target withdrawal credentials to the same eth1 credential
set_eth1_withdrawal_credential_with_balance(spec, state, 0)
set_eth1_withdrawal_credential_with_balance(spec, state, 1)
signed_consolidation = sign_consolidation(
spec,
state,
spec.Consolidation(epoch=current_epoch, source_index=0, target_index=1),
source_privkey,
target_privkey,
)
yield from run_consolidation_processing(
spec, state, signed_consolidation, valid=False
)
@with_electra_and_later
@with_presets([MINIMAL], "need sufficient consolidation churn limit")
@with_custom_state(
balances_fn=scaled_churn_balances_exceed_activation_exit_churn_limit,
threshold_fn=default_activation_threshold,
)
@spec_test
@single_phase
def test_invalid_exited_source(spec, state):
current_epoch = spec.get_current_epoch(state)
source_privkey = pubkey_to_privkey[state.validators[0].pubkey]
target_privkey = pubkey_to_privkey[state.validators[1].pubkey]
set_eth1_withdrawal_credential_with_balance(spec, state, 0)
set_eth1_withdrawal_credential_with_balance(spec, state, 1)
signed_consolidation = sign_consolidation(
spec,
state,
spec.Consolidation(epoch=current_epoch, source_index=0, target_index=1),
source_privkey,
target_privkey,
)
# exit source
spec.initiate_validator_exit(state, 0)
yield from run_consolidation_processing(
spec, state, signed_consolidation, valid=False
)
@with_electra_and_later
@with_presets([MINIMAL], "need sufficient consolidation churn limit")
@with_custom_state(
balances_fn=scaled_churn_balances_exceed_activation_exit_churn_limit,
threshold_fn=default_activation_threshold,
)
@spec_test
@single_phase
def test_invalid_exited_target(spec, state):
current_epoch = spec.get_current_epoch(state)
source_privkey = pubkey_to_privkey[state.validators[0].pubkey]
target_privkey = pubkey_to_privkey[state.validators[1].pubkey]
# Set source and target withdrawal credentials to the same eth1 credential
set_eth1_withdrawal_credential_with_balance(spec, state, 0)
set_eth1_withdrawal_credential_with_balance(spec, state, 1)
signed_consolidation = sign_consolidation(
spec,
state,
spec.Consolidation(epoch=current_epoch, source_index=0, target_index=1),
source_privkey,
target_privkey,
)
# exit target
spec.initiate_validator_exit(state, 1)
yield from run_consolidation_processing(
spec, state, signed_consolidation, valid=False
)
@with_electra_and_later
@with_presets([MINIMAL], "need sufficient consolidation churn limit")
@with_custom_state(
balances_fn=scaled_churn_balances_exceed_activation_exit_churn_limit,
threshold_fn=default_activation_threshold,
)
@spec_test
@single_phase
def test_invalid_inactive_source(spec, state):
current_epoch = spec.get_current_epoch(state)
source_privkey = pubkey_to_privkey[state.validators[0].pubkey]
target_privkey = pubkey_to_privkey[state.validators[1].pubkey]
set_eth1_withdrawal_credential_with_balance(spec, state, 0)
set_eth1_withdrawal_credential_with_balance(spec, state, 1)
signed_consolidation = sign_consolidation(
spec,
state,
spec.Consolidation(epoch=current_epoch, source_index=0, target_index=1),
source_privkey,
target_privkey,
)
# set source validator as not yet activated
state.validators[0].activation_epoch = spec.FAR_FUTURE_EPOCH
yield from run_consolidation_processing(
spec, state, signed_consolidation, valid=False
)
@with_electra_and_later
@with_presets([MINIMAL], "need sufficient consolidation churn limit")
@with_custom_state(
balances_fn=scaled_churn_balances_exceed_activation_exit_churn_limit,
threshold_fn=default_activation_threshold,
)
@spec_test
@single_phase
def test_invalid_inactive_target(spec, state):
current_epoch = spec.get_current_epoch(state)
source_privkey = pubkey_to_privkey[state.validators[0].pubkey]
target_privkey = pubkey_to_privkey[state.validators[1].pubkey]
# Set source and target withdrawal credentials to the same eth1 credential
set_eth1_withdrawal_credential_with_balance(spec, state, 0)
set_eth1_withdrawal_credential_with_balance(spec, state, 1)
signed_consolidation = sign_consolidation(
spec,
state,
spec.Consolidation(epoch=current_epoch, source_index=0, target_index=1),
source_privkey,
target_privkey,
)
# set target validator as not yet activated
state.validators[1].activation_epoch = spec.FAR_FUTURE_EPOCH
yield from run_consolidation_processing(
spec, state, signed_consolidation, valid=False
)
@with_electra_and_later
@with_presets([MINIMAL], "need sufficient consolidation churn limit")
@with_custom_state(
balances_fn=scaled_churn_balances_exceed_activation_exit_churn_limit,
threshold_fn=default_activation_threshold,
)
@spec_test
@single_phase
def test_invalid_no_execution_withdrawal_credential(spec, state):
current_epoch = spec.get_current_epoch(state)
source_privkey = pubkey_to_privkey[state.validators[0].pubkey]
target_privkey = pubkey_to_privkey[state.validators[1].pubkey]
signed_consolidation = sign_consolidation(
spec,
state,
spec.Consolidation(epoch=current_epoch, source_index=0, target_index=1),
source_privkey,
target_privkey,
)
yield from run_consolidation_processing(
spec, state, signed_consolidation, valid=False
)
@with_electra_and_later
@with_presets([MINIMAL], "need sufficient consolidation churn limit")
@with_custom_state(
balances_fn=scaled_churn_balances_exceed_activation_exit_churn_limit,
threshold_fn=default_activation_threshold,
)
@spec_test
@single_phase
def test_invalid_different_credentials(spec, state):
current_epoch = spec.get_current_epoch(state)
source_privkey = pubkey_to_privkey[state.validators[0].pubkey]
target_privkey = pubkey_to_privkey[state.validators[1].pubkey]
signed_consolidation = sign_consolidation(
spec,
state,
spec.Consolidation(epoch=current_epoch, source_index=0, target_index=1),
source_privkey,
target_privkey,
)
# Set source and target withdrawal credentials to different eth1 credentials
set_eth1_withdrawal_credential_with_balance(spec, state, 0)
set_eth1_withdrawal_credential_with_balance(spec, state, 1, address=b"\x10" * 20)
yield from run_consolidation_processing(
spec, state, signed_consolidation, valid=False
)
@with_electra_and_later
@with_presets([MINIMAL], "need sufficient consolidation churn limit")
@with_custom_state(
balances_fn=scaled_churn_balances_exceed_activation_exit_churn_limit,
threshold_fn=default_activation_threshold,
)
@spec_test
@single_phase
@always_bls
def test_invalid_source_signature(spec, state):
# This state has 256 validators each with 32 ETH in MINIMAL preset, 128 ETH consolidation churn
current_epoch = spec.get_current_epoch(state)
source_index = spec.get_active_validator_indices(state, current_epoch)[0]
target_index = spec.get_active_validator_indices(state, current_epoch)[1]
source_privkey = pubkey_to_privkey[state.validators[source_index].pubkey]
target_privkey = pubkey_to_privkey[state.validators[target_index].pubkey]
# Set source and target withdrawal credentials to the same eth1 credential
set_eth1_withdrawal_credential_with_balance(spec, state, source_index)
set_eth1_withdrawal_credential_with_balance(spec, state, target_index)
signed_consolidation = sign_consolidation(
spec,
state,
spec.Consolidation(
epoch=current_epoch, source_index=source_index, target_index=target_index
),
source_privkey,
target_privkey,
)
# Set earliest consolidation epoch to the expected exit epoch
expected_exit_epoch = spec.compute_activation_exit_epoch(current_epoch)
state.earliest_consolidation_epoch = expected_exit_epoch
consolidation_churn_limit = spec.get_consolidation_churn_limit(state)
# Set the consolidation balance to consume equal to churn limit
state.consolidation_balance_to_consume = consolidation_churn_limit
current_epoch = spec.get_current_epoch(state)
source_privkey = pubkey_to_privkey[state.validators[0].pubkey]
target_privkey = pubkey_to_privkey[state.validators[1].pubkey]
# Set source and target withdrawal credentials to the same eth1 credential
set_eth1_withdrawal_credential_with_balance(spec, state, 0)
set_eth1_withdrawal_credential_with_balance(spec, state, 1)
signed_consolidation = sign_consolidation(
spec,
state,
spec.Consolidation(epoch=current_epoch, source_index=0, target_index=1),
source_privkey,
target_privkey,
)
# Change the pubkey of the source validator, invalidating its signature
state.validators[0].pubkey = state.validators[1].pubkey
yield from run_consolidation_processing(
spec, state, signed_consolidation, valid=False
)
@with_electra_and_later
@with_presets([MINIMAL], "need sufficient consolidation churn limit")
@with_custom_state(
balances_fn=scaled_churn_balances_exceed_activation_exit_churn_limit,
threshold_fn=default_activation_threshold,
)
@spec_test
@single_phase
@always_bls
def test_invalid_target_signature(spec, state):
# This state has 256 validators each with 32 ETH in MINIMAL preset, 128 ETH consolidation churn
current_epoch = spec.get_current_epoch(state)
source_index = spec.get_active_validator_indices(state, current_epoch)[0]
target_index = spec.get_active_validator_indices(state, current_epoch)[1]
source_privkey = pubkey_to_privkey[state.validators[source_index].pubkey]
target_privkey = pubkey_to_privkey[state.validators[target_index].pubkey]
# Set source and target withdrawal credentials to the same eth1 credential
set_eth1_withdrawal_credential_with_balance(spec, state, source_index)
set_eth1_withdrawal_credential_with_balance(spec, state, target_index)
signed_consolidation = sign_consolidation(
spec,
state,
spec.Consolidation(
epoch=current_epoch, source_index=source_index, target_index=target_index
),
source_privkey,
target_privkey,
)
# Set earliest consolidation epoch to the expected exit epoch
expected_exit_epoch = spec.compute_activation_exit_epoch(current_epoch)
state.earliest_consolidation_epoch = expected_exit_epoch
consolidation_churn_limit = spec.get_consolidation_churn_limit(state)
# Set the consolidation balance to consume equal to churn limit
state.consolidation_balance_to_consume = consolidation_churn_limit
current_epoch = spec.get_current_epoch(state)
source_privkey = pubkey_to_privkey[state.validators[0].pubkey]
target_privkey = pubkey_to_privkey[state.validators[1].pubkey]
# Set source and target withdrawal credentials to the same eth1 credential
set_eth1_withdrawal_credential_with_balance(spec, state, 0)
set_eth1_withdrawal_credential_with_balance(spec, state, 1)
signed_consolidation = sign_consolidation(
spec,
state,
spec.Consolidation(epoch=current_epoch, source_index=0, target_index=1),
source_privkey,
target_privkey,
)
# Change the pubkey of the target validator, invalidating its signature
state.validators[1].pubkey = state.validators[2].pubkey
yield from run_consolidation_processing(
spec, state, signed_consolidation, valid=False
)
@with_electra_and_later
@with_presets([MINIMAL], "need sufficient consolidation churn limit")
@with_custom_state(
balances_fn=scaled_churn_balances_exceed_activation_exit_churn_limit,
threshold_fn=default_activation_threshold,
)
@spec_test
@single_phase
def test_invalid_before_specified_epoch(spec, state):
current_epoch = spec.get_current_epoch(state)
source_privkey = pubkey_to_privkey[state.validators[0].pubkey]
target_privkey = pubkey_to_privkey[state.validators[1].pubkey]
# Set source and target withdrawal credentials to the same eth1 credential
set_eth1_withdrawal_credential_with_balance(spec, state, 0)
set_eth1_withdrawal_credential_with_balance(spec, state, 1)
# set epoch=current_epoch + 1, so it's too early to process it
signed_consolidation = sign_consolidation(
spec,
state,
spec.Consolidation(epoch=current_epoch + 1, source_index=0, target_index=1),
source_privkey,
target_privkey,
)
yield from run_consolidation_processing(
spec, state, signed_consolidation, valid=False
)

View File

@ -0,0 +1,809 @@
from eth2spec.test.helpers.constants import MINIMAL
from eth2spec.test.context import (
with_electra_and_later,
with_presets,
spec_test,
single_phase,
with_custom_state,
scaled_churn_balances_exceed_activation_exit_churn_limit,
default_activation_threshold,
spec_state_test,
)
from eth2spec.test.helpers.withdrawals import (
set_eth1_withdrawal_credential_with_balance,
set_compounding_withdrawal_credential,
)
# ***********************
# * CONSOLIDATION TESTS *
# ***********************
@with_electra_and_later
@with_presets([MINIMAL], "need sufficient consolidation churn limit")
@with_custom_state(
balances_fn=scaled_churn_balances_exceed_activation_exit_churn_limit,
threshold_fn=default_activation_threshold,
)
@spec_test
@single_phase
def test_basic_consolidation_in_current_consolidation_epoch(spec, state):
# This state has 256 validators each with 32 ETH in MINIMAL preset, 128 ETH consolidation churn
current_epoch = spec.get_current_epoch(state)
source_index = spec.get_active_validator_indices(state, current_epoch)[0]
target_index = spec.get_active_validator_indices(state, current_epoch)[1]
# Set source to eth1 credentials
source_address = b"\x22" * 20
set_eth1_withdrawal_credential_with_balance(
spec, state, source_index, address=source_address
)
# Make consolidation with source address
consolidation = spec.ExecutionLayerConsolidationRequest(
source_address=source_address,
source_pubkey=state.validators[source_index].pubkey,
target_pubkey=state.validators[target_index].pubkey,
)
# Set target to eth1 credentials
set_eth1_withdrawal_credential_with_balance(spec, state, target_index)
# Set earliest consolidation epoch to the expected exit epoch
expected_exit_epoch = spec.compute_activation_exit_epoch(current_epoch)
state.earliest_consolidation_epoch = expected_exit_epoch
consolidation_churn_limit = spec.get_consolidation_churn_limit(state)
# Set the consolidation balance to consume equal to churn limit
state.consolidation_balance_to_consume = consolidation_churn_limit
yield from run_consolidation_processing(spec, state, consolidation)
# Check consolidation churn is decremented correctly
assert (
state.consolidation_balance_to_consume
== consolidation_churn_limit - spec.MIN_ACTIVATION_BALANCE
)
# Check exit epoch
assert state.validators[source_index].exit_epoch == expected_exit_epoch
@with_electra_and_later
@with_presets([MINIMAL], "need sufficient consolidation churn limit")
@with_custom_state(
balances_fn=scaled_churn_balances_exceed_activation_exit_churn_limit,
threshold_fn=default_activation_threshold,
)
@spec_test
@single_phase
def test_basic_consolidation_in_new_consolidation_epoch(spec, state):
# This state has 256 validators each with 32 ETH in MINIMAL preset, 128 ETH consolidation churn
# Set consolidation balance to consume to some arbitrary nonzero value below the churn limit
state.consolidation_balance_to_consume = spec.EFFECTIVE_BALANCE_INCREMENT
current_epoch = spec.get_current_epoch(state)
source_index = spec.get_active_validator_indices(state, current_epoch)[0]
target_index = spec.get_active_validator_indices(state, current_epoch)[1]
# Set source to eth1 credentials
source_address = b"\x22" * 20
set_eth1_withdrawal_credential_with_balance(
spec, state, source_index, address=source_address
)
# Make consolidation with source address
consolidation = spec.ExecutionLayerConsolidationRequest(
source_address=source_address,
source_pubkey=state.validators[source_index].pubkey,
target_pubkey=state.validators[target_index].pubkey,
)
# Set target to eth1 credentials
set_eth1_withdrawal_credential_with_balance(spec, state, target_index)
yield from run_consolidation_processing(spec, state, consolidation)
expected_exit_epoch = spec.compute_activation_exit_epoch(current_epoch)
# Check consolidation churn is decremented correctly
# consolidation_balance_to_consume is replenished to the churn limit since we move to a new consolidation epoch
consolidation_churn_limit = spec.get_consolidation_churn_limit(state)
assert (
state.consolidation_balance_to_consume
== consolidation_churn_limit - spec.MIN_ACTIVATION_BALANCE
)
# Check exit epochs
assert state.validators[source_index].exit_epoch == expected_exit_epoch
@with_electra_and_later
@with_presets([MINIMAL], "need sufficient consolidation churn limit")
@with_custom_state(
balances_fn=scaled_churn_balances_exceed_activation_exit_churn_limit,
threshold_fn=default_activation_threshold,
)
@spec_test
@single_phase
def test_basic_consolidation_with_preexisting_churn(spec, state):
# This state has 256 validators each with 32 ETH in MINIMAL preset, 128 ETH consolidation churn
current_epoch = spec.get_current_epoch(state)
source_index = spec.get_active_validator_indices(state, current_epoch)[0]
target_index = spec.get_active_validator_indices(state, current_epoch)[1]
# Set source to eth1 credentials
source_address = b"\x22" * 20
set_eth1_withdrawal_credential_with_balance(
spec, state, source_index, address=source_address
)
# Make consolidation with source address
consolidation = spec.ExecutionLayerConsolidationRequest(
source_address=source_address,
source_pubkey=state.validators[source_index].pubkey,
target_pubkey=state.validators[target_index].pubkey,
)
# Set target to eth1 credentials
set_eth1_withdrawal_credential_with_balance(spec, state, target_index)
# Set earliest consolidation epoch to the expected exit epoch
expected_exit_epoch = spec.compute_activation_exit_epoch(current_epoch)
state.earliest_consolidation_epoch = expected_exit_epoch
# Set some nonzero preexisting churn lower than churn limit and sufficient to process the consolidation
preexisting_churn = 2 * spec.MIN_ACTIVATION_BALANCE
state.consolidation_balance_to_consume = preexisting_churn
yield from run_consolidation_processing(spec, state, consolidation)
# Check consolidation churn is decremented correctly
assert (
state.consolidation_balance_to_consume
== preexisting_churn - spec.MIN_ACTIVATION_BALANCE
)
# Check exit epoch
assert state.validators[source_index].exit_epoch == expected_exit_epoch
@with_electra_and_later
@with_presets([MINIMAL], "need sufficient consolidation churn limit")
@with_custom_state(
balances_fn=scaled_churn_balances_exceed_activation_exit_churn_limit,
threshold_fn=default_activation_threshold,
)
@spec_test
@single_phase
def test_basic_consolidation_with_insufficient_preexisting_churn(spec, state):
# This state has 256 validators each with 32 ETH in MINIMAL preset, 128 ETH consolidation churn
current_epoch = spec.get_current_epoch(state)
source_index = spec.get_active_validator_indices(state, current_epoch)[0]
target_index = spec.get_active_validator_indices(state, current_epoch)[1]
# Set source to eth1 credentials
source_address = b"\x22" * 20
set_eth1_withdrawal_credential_with_balance(
spec, state, source_index, address=source_address
)
# Make consolidation with source address
consolidation = spec.ExecutionLayerConsolidationRequest(
source_address=source_address,
source_pubkey=state.validators[source_index].pubkey,
target_pubkey=state.validators[target_index].pubkey,
)
# Set target to eth1 credentials
set_eth1_withdrawal_credential_with_balance(spec, state, target_index)
# Set earliest consolidation epoch to the first available epoch
state.earliest_consolidation_epoch = spec.compute_activation_exit_epoch(
current_epoch
)
# Set preexisting churn lower than required to process the consolidation
preexisting_churn = spec.MIN_ACTIVATION_BALANCE - spec.EFFECTIVE_BALANCE_INCREMENT
state.consolidation_balance_to_consume = preexisting_churn
yield from run_consolidation_processing(spec, state, consolidation)
# It takes one more epoch to process the consolidation due to insufficient churn
expected_exit_epoch = spec.compute_activation_exit_epoch(current_epoch) + 1
# Check consolidation churn is decremented correctly
consolidation_churn_limit = spec.get_consolidation_churn_limit(state)
remainder = spec.MIN_ACTIVATION_BALANCE % preexisting_churn
assert (
state.consolidation_balance_to_consume == consolidation_churn_limit - remainder
)
# Check exit epoch
assert state.validators[source_index].exit_epoch == expected_exit_epoch
@with_electra_and_later
@with_presets([MINIMAL], "need sufficient consolidation churn limit")
@with_custom_state(
balances_fn=scaled_churn_balances_exceed_activation_exit_churn_limit,
threshold_fn=default_activation_threshold,
)
@spec_test
@single_phase
def test_basic_consolidation_with_compounding_credentials(spec, state):
# This state has 256 validators each with 32 ETH in MINIMAL preset, 128 ETH consolidation churn
current_epoch = spec.get_current_epoch(state)
source_index = spec.get_active_validator_indices(state, current_epoch)[0]
target_index = spec.get_active_validator_indices(state, current_epoch)[1]
# Set source to eth1 credentials
source_address = b"\x22" * 20
set_compounding_withdrawal_credential(
spec, state, source_index, address=source_address
)
# Make consolidation with source address
consolidation = spec.ExecutionLayerConsolidationRequest(
source_address=source_address,
source_pubkey=state.validators[source_index].pubkey,
target_pubkey=state.validators[target_index].pubkey,
)
# Set target to eth1 credentials
set_compounding_withdrawal_credential(spec, state, target_index)
# Set the consolidation balance to consume equal to churn limit
consolidation_churn_limit = spec.get_consolidation_churn_limit(state)
state.consolidation_balance_to_consume = consolidation_churn_limit
yield from run_consolidation_processing(spec, state, consolidation)
expected_exit_epoch = spec.compute_activation_exit_epoch(current_epoch)
# Check consolidation churn is decremented correctly
assert (
state.consolidation_balance_to_consume
== consolidation_churn_limit - spec.MIN_ACTIVATION_BALANCE
)
# Check exit epoch
assert state.validators[source_index].exit_epoch == expected_exit_epoch
@with_electra_and_later
@with_presets([MINIMAL], "need sufficient consolidation churn limit")
@with_custom_state(
balances_fn=scaled_churn_balances_exceed_activation_exit_churn_limit,
threshold_fn=default_activation_threshold,
)
@spec_test
@single_phase
def test_consolidation_churn_limit_balance(spec, state):
# This state has 256 validators each with 32 ETH in MINIMAL preset, 128 ETH consolidation churn
current_epoch = spec.get_current_epoch(state)
source_index = spec.get_active_validator_indices(state, current_epoch)[0]
target_index = spec.get_active_validator_indices(state, current_epoch)[1]
# Set source to eth1 credentials
source_address = b"\x22" * 20
set_eth1_withdrawal_credential_with_balance(
spec, state, source_index, address=source_address
)
# Make consolidation with source address
consolidation = spec.ExecutionLayerConsolidationRequest(
source_address=source_address,
source_pubkey=state.validators[source_index].pubkey,
target_pubkey=state.validators[target_index].pubkey,
)
# Set target to eth1 credentials
set_eth1_withdrawal_credential_with_balance(spec, state, target_index)
# Set source effective balance to consolidation churn limit
consolidation_churn_limit = spec.get_consolidation_churn_limit(state)
state.validators[source_index].effective_balance = consolidation_churn_limit
# Churn limit increases due to higher total balance
updated_consolidation_churn_limit = spec.get_consolidation_churn_limit(state)
yield from run_consolidation_processing(spec, state, consolidation)
# validator's effective balance fits into the churn, exit as soon as possible
expected_exit_epoch = spec.compute_activation_exit_epoch(current_epoch)
# Check consolidation churn is decremented correctly
assert (
state.consolidation_balance_to_consume
== updated_consolidation_churn_limit - consolidation_churn_limit
)
# Check exit epoch
assert state.validators[source_index].exit_epoch == expected_exit_epoch
@with_electra_and_later
@with_presets([MINIMAL], "need sufficient consolidation churn limit")
@with_custom_state(
balances_fn=scaled_churn_balances_exceed_activation_exit_churn_limit,
threshold_fn=default_activation_threshold,
)
@spec_test
@single_phase
def test_consolidation_balance_larger_than_churn_limit(spec, state):
# This state has 256 validators each with 32 ETH in MINIMAL preset, 128 ETH consolidation churn
current_epoch = spec.get_current_epoch(state)
source_index = spec.get_active_validator_indices(state, current_epoch)[0]
target_index = spec.get_active_validator_indices(state, current_epoch)[1]
# Set source to eth1 credentials
source_address = b"\x22" * 20
set_eth1_withdrawal_credential_with_balance(
spec, state, source_index, address=source_address
)
# Make consolidation with source address
consolidation = spec.ExecutionLayerConsolidationRequest(
source_address=source_address,
source_pubkey=state.validators[source_index].pubkey,
target_pubkey=state.validators[target_index].pubkey,
)
# Set target to eth1 credentials
set_eth1_withdrawal_credential_with_balance(spec, state, target_index)
# Set source effective balance to 2 * consolidation churn limit
consolidation_churn_limit = spec.get_consolidation_churn_limit(state)
state.validators[source_index].effective_balance = 2 * consolidation_churn_limit
# Consolidation churn limit increases due to higher total balance
updated_consolidation_churn_limit = spec.get_consolidation_churn_limit(state)
remainder = state.validators[source_index].effective_balance % updated_consolidation_churn_limit
expected_balance = updated_consolidation_churn_limit - remainder
yield from run_consolidation_processing(spec, state, consolidation)
expected_exit_epoch = spec.compute_activation_exit_epoch(current_epoch) + 1
# Check consolidation churn is decremented correctly
assert state.consolidation_balance_to_consume == expected_balance
# Check exit epoch
assert state.validators[source_index].exit_epoch == expected_exit_epoch
@with_electra_and_later
@with_presets([MINIMAL], "need sufficient consolidation churn limit")
@with_custom_state(
balances_fn=scaled_churn_balances_exceed_activation_exit_churn_limit,
threshold_fn=default_activation_threshold,
)
@spec_test
@single_phase
def test_consolidation_balance_through_two_churn_epochs(spec, state):
# This state has 256 validators each with 32 ETH in MINIMAL preset, 128 ETH consolidation churn
current_epoch = spec.get_current_epoch(state)
source_index = spec.get_active_validator_indices(state, current_epoch)[0]
target_index = spec.get_active_validator_indices(state, current_epoch)[1]
# Set source to eth1 credentials
source_address = b"\x22" * 20
set_eth1_withdrawal_credential_with_balance(
spec, state, source_index, address=source_address
)
# Make consolidation with source address
consolidation = spec.ExecutionLayerConsolidationRequest(
source_address=source_address,
source_pubkey=state.validators[source_index].pubkey,
target_pubkey=state.validators[target_index].pubkey,
)
# Set target to eth1 credentials
set_eth1_withdrawal_credential_with_balance(spec, state, target_index)
# Set source balance higher to 3 * consolidation churn limit
consolidation_churn_limit = spec.get_consolidation_churn_limit(state)
state.validators[source_index].effective_balance = 3 * consolidation_churn_limit
new_churn_limit = spec.get_consolidation_churn_limit(state)
remainder = state.validators[source_index].effective_balance % new_churn_limit
expected_balance = new_churn_limit - remainder
yield from run_consolidation_processing(spec, state, consolidation)
# when exiting a multiple of the churn limit greater than 1, an extra exit epoch is added
expected_exit_epoch = spec.compute_activation_exit_epoch(current_epoch) + 2
assert state.validators[0].exit_epoch == expected_exit_epoch
# since the earliest exit epoch moves to a new one, consolidation balance is back to full
assert state.consolidation_balance_to_consume == expected_balance
# Failing tests
@with_electra_and_later
@with_presets([MINIMAL], "need sufficient consolidation churn limit")
@with_custom_state(
balances_fn=scaled_churn_balances_exceed_activation_exit_churn_limit,
threshold_fn=default_activation_threshold,
)
@spec_test
@single_phase
def test_incorrect_source_equals_target(spec, state):
current_epoch = spec.get_current_epoch(state)
source_index = spec.get_active_validator_indices(state, current_epoch)[0]
# Set source to eth1 credentials
source_address = b"\x22" * 20
set_eth1_withdrawal_credential_with_balance(
spec, state, source_index, address=source_address
)
# Make consolidation from source to source
consolidation = spec.ExecutionLayerConsolidationRequest(
source_address=source_address,
source_pubkey=state.validators[source_index].pubkey,
target_pubkey=state.validators[source_index].pubkey,
)
yield from run_consolidation_processing(
spec, state, consolidation, success=False
)
@with_electra_and_later
@with_presets([MINIMAL], "need sufficient consolidation churn limit")
@with_custom_state(
balances_fn=scaled_churn_balances_exceed_activation_exit_churn_limit,
threshold_fn=default_activation_threshold,
)
@spec_test
@single_phase
def test_incorrect_exceed_pending_consolidations_limit(spec, state):
state.pending_consolidations = [
spec.PendingConsolidation(source_index=0, target_index=1)
] * spec.PENDING_CONSOLIDATIONS_LIMIT
# Set up an otherwise correct consolidation
current_epoch = spec.get_current_epoch(state)
source_index = spec.get_active_validator_indices(state, current_epoch)[0]
target_index = spec.get_active_validator_indices(state, current_epoch)[1]
source_address = b"\x22" * 20
set_eth1_withdrawal_credential_with_balance(
spec, state, source_index, address=source_address
)
consolidation = spec.ExecutionLayerConsolidationRequest(
source_address=source_address,
source_pubkey=state.validators[source_index].pubkey,
target_pubkey=state.validators[target_index].pubkey,
)
set_eth1_withdrawal_credential_with_balance(spec, state, target_index)
yield from run_consolidation_processing(
spec, state, consolidation, success=False
)
@with_electra_and_later
@spec_state_test
@single_phase
def test_incorrect_not_enough_consolidation_churn_available(spec, state):
state.validators = state.validators[0:2]
state.pending_consolidations = [
spec.PendingConsolidation(source_index=0, target_index=1)
]
# Set up an otherwise correct consolidation
current_epoch = spec.get_current_epoch(state)
source_index = spec.get_active_validator_indices(state, current_epoch)[0]
target_index = spec.get_active_validator_indices(state, current_epoch)[1]
source_address = b"\x22" * 20
set_eth1_withdrawal_credential_with_balance(
spec, state, source_index, address=source_address
)
consolidation = spec.ExecutionLayerConsolidationRequest(
source_address=source_address,
source_pubkey=state.validators[source_index].pubkey,
target_pubkey=state.validators[target_index].pubkey,
)
set_eth1_withdrawal_credential_with_balance(spec, state, target_index)
yield from run_consolidation_processing(
spec, state, consolidation, success=False
)
@with_electra_and_later
@with_presets([MINIMAL], "need sufficient consolidation churn limit")
@with_custom_state(
balances_fn=scaled_churn_balances_exceed_activation_exit_churn_limit,
threshold_fn=default_activation_threshold,
)
@spec_test
@single_phase
def test_incorrect_exited_source(spec, state):
# Set up an otherwise correct consolidation
current_epoch = spec.get_current_epoch(state)
source_index = spec.get_active_validator_indices(state, current_epoch)[0]
target_index = spec.get_active_validator_indices(state, current_epoch)[1]
source_address = b"\x22" * 20
set_eth1_withdrawal_credential_with_balance(
spec, state, source_index, address=source_address
)
consolidation = spec.ExecutionLayerConsolidationRequest(
source_address=source_address,
source_pubkey=state.validators[source_index].pubkey,
target_pubkey=state.validators[target_index].pubkey,
)
set_eth1_withdrawal_credential_with_balance(spec, state, target_index)
# exit source
spec.initiate_validator_exit(state, source_index)
yield from run_consolidation_processing(
spec, state, consolidation, success=False
)
@with_electra_and_later
@with_presets([MINIMAL], "need sufficient consolidation churn limit")
@with_custom_state(
balances_fn=scaled_churn_balances_exceed_activation_exit_churn_limit,
threshold_fn=default_activation_threshold,
)
@spec_test
@single_phase
def test_incorrect_exited_target(spec, state):
# Set up an otherwise correct consolidation
current_epoch = spec.get_current_epoch(state)
source_index = spec.get_active_validator_indices(state, current_epoch)[0]
target_index = spec.get_active_validator_indices(state, current_epoch)[1]
source_address = b"\x22" * 20
set_eth1_withdrawal_credential_with_balance(
spec, state, source_index, address=source_address
)
consolidation = spec.ExecutionLayerConsolidationRequest(
source_address=source_address,
source_pubkey=state.validators[source_index].pubkey,
target_pubkey=state.validators[target_index].pubkey,
)
set_eth1_withdrawal_credential_with_balance(spec, state, target_index)
# exit target
spec.initiate_validator_exit(state, 1)
yield from run_consolidation_processing(
spec, state, consolidation, success=False
)
@with_electra_and_later
@with_presets([MINIMAL], "need sufficient consolidation churn limit")
@with_custom_state(
balances_fn=scaled_churn_balances_exceed_activation_exit_churn_limit,
threshold_fn=default_activation_threshold,
)
@spec_test
@single_phase
def test_incorrect_inactive_source(spec, state):
# Set up an otherwise correct consolidation
current_epoch = spec.get_current_epoch(state)
source_index = spec.get_active_validator_indices(state, current_epoch)[0]
target_index = spec.get_active_validator_indices(state, current_epoch)[1]
source_address = b"\x22" * 20
set_eth1_withdrawal_credential_with_balance(
spec, state, source_index, address=source_address
)
consolidation = spec.ExecutionLayerConsolidationRequest(
source_address=source_address,
source_pubkey=state.validators[source_index].pubkey,
target_pubkey=state.validators[target_index].pubkey,
)
set_eth1_withdrawal_credential_with_balance(spec, state, target_index)
# set source validator as not yet activated
state.validators[source_index].activation_epoch = spec.FAR_FUTURE_EPOCH
yield from run_consolidation_processing(
spec, state, consolidation, success=False
)
@with_electra_and_later
@with_presets([MINIMAL], "need sufficient consolidation churn limit")
@with_custom_state(
balances_fn=scaled_churn_balances_exceed_activation_exit_churn_limit,
threshold_fn=default_activation_threshold,
)
@spec_test
@single_phase
def test_incorrect_inactive_target(spec, state):
# Set up an otherwise correct consolidation
current_epoch = spec.get_current_epoch(state)
source_index = spec.get_active_validator_indices(state, current_epoch)[0]
target_index = spec.get_active_validator_indices(state, current_epoch)[1]
source_address = b"\x22" * 20
set_eth1_withdrawal_credential_with_balance(
spec, state, source_index, address=source_address
)
consolidation = spec.ExecutionLayerConsolidationRequest(
source_address=source_address,
source_pubkey=state.validators[source_index].pubkey,
target_pubkey=state.validators[target_index].pubkey,
)
set_eth1_withdrawal_credential_with_balance(spec, state, target_index)
# set target validator as not yet activated
state.validators[1].activation_epoch = spec.FAR_FUTURE_EPOCH
yield from run_consolidation_processing(
spec, state, consolidation, success=False
)
@with_electra_and_later
@with_presets([MINIMAL], "need sufficient consolidation churn limit")
@with_custom_state(
balances_fn=scaled_churn_balances_exceed_activation_exit_churn_limit,
threshold_fn=default_activation_threshold,
)
@spec_test
@single_phase
def test_incorrect_no_source_execution_withdrawal_credential(spec, state):
# Set up a correct consolidation, but source does not have
# an execution withdrawal credential
current_epoch = spec.get_current_epoch(state)
source_index = spec.get_active_validator_indices(state, current_epoch)[0]
target_index = spec.get_active_validator_indices(state, current_epoch)[1]
source_address = b"\x22" * 20
consolidation = spec.ExecutionLayerConsolidationRequest(
source_address=source_address,
source_pubkey=state.validators[source_index].pubkey,
target_pubkey=state.validators[target_index].pubkey,
)
set_eth1_withdrawal_credential_with_balance(spec, state, target_index)
yield from run_consolidation_processing(
spec, state, consolidation, success=False
)
@with_electra_and_later
@with_presets([MINIMAL], "need sufficient consolidation churn limit")
@with_custom_state(
balances_fn=scaled_churn_balances_exceed_activation_exit_churn_limit,
threshold_fn=default_activation_threshold,
)
@spec_test
@single_phase
def test_incorrect_no_target_execution_withdrawal_credential(spec, state):
# Set up a correct consolidation, but target does not have
# an execution withdrawal credential
current_epoch = spec.get_current_epoch(state)
source_index = spec.get_active_validator_indices(state, current_epoch)[0]
target_index = spec.get_active_validator_indices(state, current_epoch)[1]
source_address = b"\x22" * 20
set_eth1_withdrawal_credential_with_balance(
spec, state, source_index, address=source_address
)
consolidation = spec.ExecutionLayerConsolidationRequest(
source_address=source_address,
source_pubkey=state.validators[source_index].pubkey,
target_pubkey=state.validators[target_index].pubkey,
)
yield from run_consolidation_processing(
spec, state, consolidation, success=False
)
@with_electra_and_later
@with_presets([MINIMAL], "need sufficient consolidation churn limit")
@with_custom_state(
balances_fn=scaled_churn_balances_exceed_activation_exit_churn_limit,
threshold_fn=default_activation_threshold,
)
@spec_test
@single_phase
def test_incorrect_incorrect_source_address(spec, state):
# Set up an otherwise correct consolidation
current_epoch = spec.get_current_epoch(state)
source_index = spec.get_active_validator_indices(state, current_epoch)[0]
target_index = spec.get_active_validator_indices(state, current_epoch)[1]
source_address = b"\x22" * 20
set_eth1_withdrawal_credential_with_balance(
spec, state, source_index, address=source_address
)
# Make consolidation with different source address
consolidation = spec.ExecutionLayerConsolidationRequest(
source_address=b"\x33" * 20,
source_pubkey=state.validators[source_index].pubkey,
target_pubkey=state.validators[target_index].pubkey,
)
set_eth1_withdrawal_credential_with_balance(spec, state, target_index)
yield from run_consolidation_processing(
spec, state, consolidation, success=False
)
@with_electra_and_later
@with_presets([MINIMAL], "need sufficient consolidation churn limit")
@with_custom_state(
balances_fn=scaled_churn_balances_exceed_activation_exit_churn_limit,
threshold_fn=default_activation_threshold,
)
@spec_test
@single_phase
def test_incorrect_unknown_source_pubkey(spec, state):
# Set up an otherwise correct consolidation
current_epoch = spec.get_current_epoch(state)
source_index = spec.get_active_validator_indices(state, current_epoch)[0]
target_index = spec.get_active_validator_indices(state, current_epoch)[1]
source_address = b"\x22" * 20
set_eth1_withdrawal_credential_with_balance(
spec, state, source_index, address=source_address
)
# Make consolidation with different source pubkey
consolidation = spec.ExecutionLayerConsolidationRequest(
source_address=source_address,
source_pubkey=b"\x00" * 48,
target_pubkey=state.validators[target_index].pubkey,
)
set_eth1_withdrawal_credential_with_balance(spec, state, target_index)
yield from run_consolidation_processing(
spec, state, consolidation, success=False
)
@with_electra_and_later
@with_presets([MINIMAL], "need sufficient consolidation churn limit")
@with_custom_state(
balances_fn=scaled_churn_balances_exceed_activation_exit_churn_limit,
threshold_fn=default_activation_threshold,
)
@spec_test
@single_phase
def test_incorrect_unknown_target_pubkey(spec, state):
# Set up an otherwise correct consolidation
current_epoch = spec.get_current_epoch(state)
source_index = spec.get_active_validator_indices(state, current_epoch)[0]
target_index = spec.get_active_validator_indices(state, current_epoch)[1]
source_address = b"\x22" * 20
set_eth1_withdrawal_credential_with_balance(
spec, state, source_index, address=source_address
)
# Make consolidation with different target pubkey
consolidation = spec.ExecutionLayerConsolidationRequest(
source_address=b"\x33" * 20,
source_pubkey=state.validators[source_index].pubkey,
target_pubkey=b"\x00" * 48,
)
set_eth1_withdrawal_credential_with_balance(spec, state, target_index)
yield from run_consolidation_processing(
spec, state, consolidation, success=False
)
def run_consolidation_processing(spec, state, consolidation, success=True):
"""
Run ``process_consolidation``, yielding:
- pre-state ('pre')
- execution_layer_consolidation_request ('execution_layer_consolidation_request')
- post-state ('post').
If ``valid == False``, run expecting ``AssertionError``
"""
if success:
validator_pubkeys = [v.pubkey for v in state.validators]
source_index = spec.ValidatorIndex(validator_pubkeys.index(consolidation.source_pubkey))
target_index = spec.ValidatorIndex(validator_pubkeys.index(consolidation.target_pubkey))
source_validator = state.validators[source_index]
target_validator = state.validators[target_index]
pre_exit_epoch_source = source_validator.exit_epoch
pre_exit_epoch_target = target_validator.exit_epoch
pre_pending_consolidations = state.pending_consolidations.copy()
else:
pre_state = state.copy()
yield 'pre', state
yield 'execution_layer_consolidation_request', consolidation
spec.process_execution_layer_consolidation_request(state, consolidation)
yield 'post', state
if success:
# Check source and target have execution credentials
assert spec.has_execution_withdrawal_credential(source_validator)
assert spec.has_execution_withdrawal_credential(target_validator)
# Check source address in the consolidation fits the withdrawal credentials
assert source_validator.withdrawal_credentials[12:] == consolidation.source_address
# Check source and target are not the same
assert source_index != target_index
# Check source and target were not exiting
assert pre_exit_epoch_source == spec.FAR_FUTURE_EPOCH
assert pre_exit_epoch_target == spec.FAR_FUTURE_EPOCH
# Check source is now exiting
assert state.validators[source_index].exit_epoch < spec.FAR_FUTURE_EPOCH
# Check that the exit epoch matches earliest_consolidation_epoch
assert state.validators[source_index].exit_epoch == state.earliest_consolidation_epoch
# Check that the correct consolidation has been appended
expected_new_pending_consolidation = spec.PendingConsolidation(
source_index=source_index,
target_index=target_index,
)
assert state.pending_consolidations == pre_pending_consolidations + [expected_new_pending_consolidation]
else:
assert pre_state == state

View File

@ -1,61 +0,0 @@
from eth2spec.utils import bls
from eth2spec.test.context import expect_assertion_error
from eth2spec.test.helpers.keys import privkeys
def prepare_signed_consolidations(spec, state, index_pairs, fork_version=None):
def create_signed_consolidation(source_index, target_index):
consolidation = spec.Consolidation(
epoch=spec.get_current_epoch(state),
source_index=source_index,
target_index=target_index,
)
return sign_consolidation(spec, state, consolidation, privkeys[source_index], privkeys[target_index],
fork_version=fork_version)
return [create_signed_consolidation(source_index, target_index) for (source_index, target_index) in index_pairs]
def sign_consolidation(spec, state, consolidation, source_privkey, target_privkey, fork_version=None):
domain = spec.compute_domain(spec.DOMAIN_CONSOLIDATION, genesis_validators_root=state.genesis_validators_root)
signing_root = spec.compute_signing_root(consolidation, domain)
return spec.SignedConsolidation(
message=consolidation,
signature=bls.Aggregate([bls.Sign(source_privkey, signing_root), bls.Sign(target_privkey, signing_root)])
)
def run_consolidation_processing(spec, state, signed_consolidation, valid=True):
"""
Run ``process_consolidation``, yielding:
- pre-state ('pre')
- consolidation ('consolidation')
- post-state ('post').
If ``valid == False``, run expecting ``AssertionError``
"""
source_validator = state.validators[signed_consolidation.message.source_index]
target_validator = state.validators[signed_consolidation.message.target_index]
yield 'pre', state
yield 'consolidation', signed_consolidation
if not valid:
expect_assertion_error(lambda: spec.process_consolidation(state, signed_consolidation))
yield 'post', None
return
pre_exit_epoch = source_validator.exit_epoch
spec.process_consolidation(state, signed_consolidation)
yield 'post', state
assert source_validator.withdrawal_credentials[1:] == target_validator.withdrawal_credentials[1:]
assert pre_exit_epoch == spec.FAR_FUTURE_EPOCH
assert state.validators[signed_consolidation.message.source_index].exit_epoch < spec.FAR_FUTURE_EPOCH
assert state.validators[signed_consolidation.message.source_index].exit_epoch == state.earliest_consolidation_epoch
assert state.pending_consolidations[len(state.pending_consolidations) - 1] == spec.PendingConsolidation(
source_index=signed_consolidation.message.source_index,
target_index=signed_consolidation.message.target_index
)

View File

@ -34,7 +34,6 @@ LATEST_FORK = MAINNET_FORKS[-1]
ALL_PHASES = (
# Formal forks
*MAINNET_FORKS,
DENEB,
ELECTRA,
# Experimental patches
EIP7594,

View File

@ -37,6 +37,7 @@ def get_execution_payload_header(spec, execution_payload):
if is_post_electra(spec):
payload_header.deposit_requests_root = spec.hash_tree_root(execution_payload.deposit_requests)
payload_header.withdrawal_requests_root = spec.hash_tree_root(execution_payload.withdrawal_requests)
payload_header.consolidation_requests_root = spec.hash_tree_root(execution_payload.consolidation_requests)
return payload_header
@ -59,7 +60,8 @@ def compute_el_header_block_hash(spec,
transactions_trie_root,
withdrawals_trie_root=None,
deposit_requests_trie_root=None,
withdrawal_requests_root=None):
withdrawal_requests_root=None,
consolidation_requests_root=None):
"""
Computes the RLP execution block hash described by an `ExecutionPayloadHeader`.
"""

View File

@ -1,5 +1,5 @@
from py_ecc.bls import G2ProofOfPossession as py_ecc_bls
from py_ecc.bls.g2_primatives import signature_to_G2 as _signature_to_G2
from py_ecc.bls.g2_primitives import signature_to_G2 as _signature_to_G2
from py_ecc.optimized_bls12_381 import ( # noqa: F401
G1 as py_ecc_G1,
G2 as py_ecc_G2,

View File

@ -10,3 +10,4 @@ from remerkleable.core import BasicView, View, Path
Bytes20 = ByteVector[20] # type: ignore
Bytes31 = ByteVector[31] # type: ignore

View File

@ -8,7 +8,7 @@
```yaml
description: string -- optional: description of test case, purely for debugging purposes.
node_id: int -- argument: the NodeId input.
node_id: int -- argument: the NodeID input.
custody_subnet_count: int -- argument: the count of custody subnets.
result: list of int -- output: the list of resulting column indices.
```

View File

@ -46,7 +46,8 @@ Operations:
| `withdrawals` | `ExecutionPayload` | `execution_payload` | `process_withdrawals(state, execution_payload)` (new in Capella) |
| `bls_to_execution_change` | `SignedBLSToExecutionChange` | `address_change` | `process_bls_to_execution_change(state, address_change)` (new in Capella) |
| `deposit_request` | `DepositRequest` | `deposit_request` | `process_deposit_request(state, deposit_request)` (new in Electra) |
| `exits` | `ExecutionLayerExit` | `execution_layer_exit` | `process_execution_layer_exit(state, execution_layer_exit)` (new in Electra) |
| `execution_layer_withdrawal_request` | `ExecutionLayerWithdrawalRequest` | `execution_layer_withdrawal_request` | `process_execution_layer_withdrawal_request(state, execution_layer_withdrawal_request)` (new in Electra) |
| `execution_layer_consolidation_request` | `ExecutionLayerConsolidationRequest` | `execution_layer_consolidation_request` | `process_execution_layer_consolidation_request(state, execution_layer_consolidation_request)` (new in Electra) |
Note that `block_header` is not strictly an operation (and is a full `Block`), but processed in the same manner, and hence included here.

View File

@ -710,6 +710,21 @@ def case05_recover_all_cells():
'output': None
}
# Edge case: More cells provided than CELLS_PER_EXT_BLOB
blob = BLOB_RANDOM_VALID2
cells = spec.compute_cells(blob)
cell_ids = list(range(spec.CELLS_PER_EXT_BLOB)) + [0]
partial_cells = [cells[cell_id] for cell_id in cell_ids]
expect_exception(spec.recover_all_cells, cell_ids, partial_cells)
identifier = make_id(cell_ids, partial_cells)
yield f'recover_all_cells_case_invalid_more_cells_than_cells_per_ext_blob_{identifier}', {
'input': {
'cell_ids': cell_ids,
'cells': encode_hex_list(partial_cells),
},
'output': None
}
# Edge case: Invalid cell_id
blob = BLOB_RANDOM_VALID1
cells = spec.compute_cells(blob)
@ -782,7 +797,12 @@ def case05_recover_all_cells():
# Edge case: Duplicate cell_id
blob = BLOB_RANDOM_VALID2
cells = spec.compute_cells(blob)
cell_ids = list(range(spec.CELLS_PER_EXT_BLOB // 2))
# There will be 65 cells, where 64 are unique and 1 is a duplicate.
# Depending on the implementation, 63 & 1 might not fail for the right
# reason. For example, if the implementation assigns cells in an array
# via index, this would result in 63 cells and the test would fail due
# to insufficient cell count, not because of a duplicate cell.
cell_ids = list(range(spec.CELLS_PER_EXT_BLOB // 2 + 1))
partial_cells = [cells[cell_id] for cell_id in cell_ids]
# Replace first cell_id with the second cell_id
cell_ids[0] = cell_ids[1]

View File

@ -45,7 +45,7 @@ if __name__ == "__main__":
_new_electra_mods = {key: 'eth2spec.test.electra.block_processing.test_process_' + key for key in [
'attestation',
'consolidation',
'execution_layer_consolidation_request',
'deposit_request',
'execution_layer_withdrawal_request',
'voluntary_exit'