diff --git a/.circleci/config.yml b/.circleci/config.yml index 157c56ca5f..f7c42542d0 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -168,6 +168,19 @@ jobs: command: make citest fork=eip6110 - store_test_results: path: tests/core/pyspec/test-reports + test-eip7002: + docker: + - image: circleci/python:3.9 + working_directory: ~/specs-repo + steps: + - restore_cache: + key: v3-specs-repo-{{ .Branch }}-{{ .Revision }} + - restore_pyspec_cached_venv + - run: + name: Run py-tests + command: make citest fork=eip7002 + - store_test_results: + path: tests/core/pyspec/test-reports table_of_contents: docker: - image: circleci/node:10.16.3 @@ -291,6 +304,9 @@ workflows: - test-eip6110: requires: - install_pyspec_test + - test-eip7002: + requires: + - install_pyspec_test - table_of_contents - codespell - lint: diff --git a/.github/workflows/run-tests.yml b/.github/workflows/run-tests.yml index 6b24ef5eb1..998a1e0fc4 100644 --- a/.github/workflows/run-tests.yml +++ b/.github/workflows/run-tests.yml @@ -71,7 +71,7 @@ jobs: needs: [preclear,lint,codespell,table_of_contents] strategy: matrix: - version: ["phase0", "altair", "bellatrix", "capella", "deneb", "eip6110"] + version: ["phase0", "altair", "bellatrix", "capella", "deneb", "eip6110", "eip7002"] steps: - name: Checkout this repo uses: actions/checkout@v3.2.0 diff --git a/.gitignore b/.gitignore index 2ff10cf099..cdfddfb0c3 100644 --- a/.gitignore +++ b/.gitignore @@ -22,6 +22,7 @@ tests/core/pyspec/eth2spec/bellatrix/ tests/core/pyspec/eth2spec/capella/ tests/core/pyspec/eth2spec/deneb/ tests/core/pyspec/eth2spec/eip6110/ +tests/core/pyspec/eth2spec/eip7002/ tests/core/pyspec/eth2spec/whisk/ # coverage reports diff --git a/configs/mainnet.yaml b/configs/mainnet.yaml index 9206ab77da..ed018aab1b 100644 --- a/configs/mainnet.yaml +++ b/configs/mainnet.yaml @@ -53,6 +53,9 @@ DENEB_FORK_EPOCH: 18446744073709551615 # EIP6110 EIP6110_FORK_VERSION: 0x05000000 # temporary stub EIP6110_FORK_EPOCH: 18446744073709551615 +# EIP7002 +EIP7002_FORK_VERSION: 0x05000000 # temporary stub +EIP7002_FORK_EPOCH: 18446744073709551615 # WHISK WHISK_FORK_VERSION: 0x06000000 # temporary stub WHISK_FORK_EPOCH: 18446744073709551615 diff --git a/configs/minimal.yaml b/configs/minimal.yaml index 256a39d1c1..238598b0e0 100644 --- a/configs/minimal.yaml +++ b/configs/minimal.yaml @@ -52,6 +52,9 @@ DENEB_FORK_EPOCH: 18446744073709551615 # EIP6110 EIP6110_FORK_VERSION: 0x05000001 EIP6110_FORK_EPOCH: 18446744073709551615 +# EIP7002 +EIP7002_FORK_VERSION: 0x05000001 +EIP7002_FORK_EPOCH: 18446744073709551615 # WHISK WHISK_FORK_VERSION: 0x06000001 WHISK_FORK_EPOCH: 18446744073709551615 diff --git a/pysetup/constants.py b/pysetup/constants.py index 1f29117f7f..8d53455634 100644 --- a/pysetup/constants.py +++ b/pysetup/constants.py @@ -5,9 +5,11 @@ CAPELLA = 'capella' DENEB = 'deneb' EIP6110 = 'eip6110' +EIP7002 = 'eip7002' WHISK = 'whisk' + # The helper functions that are used when defining constants CONSTANT_DEP_SUNDRY_CONSTANTS_FUNCTIONS = ''' def ceillog2(x: int) -> uint64: diff --git a/pysetup/md_doc_paths.py b/pysetup/md_doc_paths.py index d250223240..781ae41db3 100644 --- a/pysetup/md_doc_paths.py +++ b/pysetup/md_doc_paths.py @@ -8,6 +8,7 @@ DENEB, EIP6110, WHISK, + EIP7002, ) @@ -19,6 +20,7 @@ DENEB: CAPELLA, EIP6110: DENEB, WHISK: CAPELLA, + EIP7002: CAPELLA, } ALL_FORKS = list(PREVIOUS_FORK_OF.keys()) diff --git a/pysetup/spec_builders/__init__.py b/pysetup/spec_builders/__init__.py index 123939b98c..794ae50d29 100644 --- a/pysetup/spec_builders/__init__.py +++ b/pysetup/spec_builders/__init__.py @@ -4,6 +4,7 @@ from .capella import CapellaSpecBuilder from .deneb import DenebSpecBuilder from .eip6110 import EIP6110SpecBuilder +from .eip7002 import EIP7002SpecBuilder from .whisk import WhiskSpecBuilder @@ -11,6 +12,6 @@ builder.fork: builder for builder in ( Phase0SpecBuilder, AltairSpecBuilder, BellatrixSpecBuilder, CapellaSpecBuilder, DenebSpecBuilder, - EIP6110SpecBuilder, WhiskSpecBuilder, + EIP6110SpecBuilder, EIP7002SpecBuilder, WhiskSpecBuilder, ) } diff --git a/pysetup/spec_builders/eip7002.py b/pysetup/spec_builders/eip7002.py new file mode 100644 index 0000000000..a9f6ebc9b1 --- /dev/null +++ b/pysetup/spec_builders/eip7002.py @@ -0,0 +1,12 @@ +from .base import BaseSpecBuilder +from ..constants import EIP7002 + + +class EIP7002SpecBuilder(BaseSpecBuilder): + fork: str = EIP7002 + + @classmethod + def imports(cls, preset_name: str): + return super().imports(preset_name) + f''' +from eth2spec.capella import {preset_name} as capella +''' diff --git a/specs/_features/eip7002/beacon-chain.md b/specs/_features/eip7002/beacon-chain.md new file mode 100644 index 0000000000..39b03e878a --- /dev/null +++ b/specs/_features/eip7002/beacon-chain.md @@ -0,0 +1,300 @@ +# EIP-7002 -- The Beacon Chain + +## Table of contents + + + + + +- [Introduction](#introduction) +- [Preset](#preset) + - [Max operations per block](#max-operations-per-block) +- [Containers](#containers) + - [New containers](#new-containers) + - [`ExecutionLayerExit`](#executionlayerexit) + - [Extended Containers](#extended-containers) + - [`ExecutionPayload`](#executionpayload) + - [`ExecutionPayloadHeader`](#executionpayloadheader) + - [`BeaconState`](#beaconstate) +- [Beacon chain state transition function](#beacon-chain-state-transition-function) + - [Block processing](#block-processing) + - [Execution payload](#execution-payload) + - [Modified `process_execution_payload`](#modified-process_execution_payload) + - [Operations](#operations) + - [Modified `process_operations`](#modified-process_operations) + - [New `process_execution_layer_exit`](#new-process_execution_layer_exit) +- [Testing](#testing) + + + + +## Introduction + +This is the beacon chain specification of the execution layer triggerable exits feature. + +This mechanism relies on the changes proposed by [EIP-7002](http://eips.ethereum.org/EIPS/eip-7002). + +*Note:* This specification is built upon [Capella](../../capella/beacon-chain.md) and is under active development. + +## Preset + +### Max operations per block + +| Name | Value | +| - | - | +| `MAX_EXECUTION_LAYER_EXITS` | `2**4` (= 16) | + +## Containers + +### New containers + +#### `ExecutionLayerExit` + +```python +class ExecutionLayerExit(Container): + source_address: ExecutionAddress + validator_pubkey: BLSPubkey +``` + +### Extended Containers + +#### `ExecutionPayload` + +```python +class ExecutionPayload(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 + transactions: List[Transaction, MAX_TRANSACTIONS_PER_PAYLOAD] + withdrawals: List[Withdrawal, MAX_WITHDRAWALS_PER_PAYLOAD] + exits: List[ExecutionLayerExit, MAX_EXECUTION_LAYER_EXITS] # [New in EIP7002] +``` + +#### `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 + transactions_root: Root + withdrawals_root: Root + exits_root: Root # [New in EIP7002] +``` + +#### `BeaconState` + +```python +class BeaconState(Container): + # Versioning + genesis_time: uint64 + genesis_validators_root: Root + slot: Slot + fork: Fork + # History + latest_block_header: BeaconBlockHeader + block_roots: Vector[Root, SLOTS_PER_HISTORICAL_ROOT] + state_roots: Vector[Root, SLOTS_PER_HISTORICAL_ROOT] + historical_roots: List[Root, HISTORICAL_ROOTS_LIMIT] + # Eth1 + eth1_data: Eth1Data + eth1_data_votes: List[Eth1Data, EPOCHS_PER_ETH1_VOTING_PERIOD * SLOTS_PER_EPOCH] + eth1_deposit_index: uint64 + # Registry + validators: List[Validator, VALIDATOR_REGISTRY_LIMIT] + balances: List[Gwei, VALIDATOR_REGISTRY_LIMIT] + # Randomness + randao_mixes: Vector[Bytes32, EPOCHS_PER_HISTORICAL_VECTOR] + # Slashings + slashings: Vector[Gwei, EPOCHS_PER_SLASHINGS_VECTOR] # Per-epoch sums of slashed effective balances + # Participation + previous_epoch_participation: List[ParticipationFlags, VALIDATOR_REGISTRY_LIMIT] + current_epoch_participation: List[ParticipationFlags, VALIDATOR_REGISTRY_LIMIT] + # Finality + justification_bits: Bitvector[JUSTIFICATION_BITS_LENGTH] # Bit set for every recent justified epoch + previous_justified_checkpoint: Checkpoint + current_justified_checkpoint: Checkpoint + finalized_checkpoint: Checkpoint + # Inactivity + inactivity_scores: List[uint64, VALIDATOR_REGISTRY_LIMIT] + # Sync + current_sync_committee: SyncCommittee + next_sync_committee: SyncCommittee + # Execution + latest_execution_payload_header: ExecutionPayloadHeader # [Modified in EIP7002] + # Withdrawals + next_withdrawal_index: WithdrawalIndex + next_withdrawal_validator_index: ValidatorIndex + # Deep history valid from Capella onwards + historical_summaries: List[HistoricalSummary, HISTORICAL_ROOTS_LIMIT] +``` + +## Beacon chain state transition function + +### Block processing + +#### Execution payload + +##### Modified `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 the execution payload is valid + assert execution_engine.verify_and_notify_new_payload(NewPayloadRequest(execution_payload=payload)) + # 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), + exits_root=hash_tree_root(payload.exits), # [New in EIP7002] + ) +``` + +#### Operations + +##### Modified `process_operations` + +*Note*: The function `process_operations` is modified to process `ExecutionLayerExit` operations included in the block. + +```python +def process_operations(state: BeaconState, body: BeaconBlockBody) -> None: + # Verify that outstanding deposits are processed up to the maximum number of deposits + assert len(body.deposits) == min(MAX_DEPOSITS, state.eth1_data.deposit_count - state.eth1_deposit_index) + + def for_ops(operations: Sequence[Any], fn: Callable[[BeaconState, Any], None]) -> None: + for operation in operations: + fn(state, operation) + + for_ops(body.proposer_slashings, process_proposer_slashing) + for_ops(body.attester_slashings, process_attester_slashing) + for_ops(body.attestations, process_attestation) + for_ops(body.deposits, process_deposit) + for_ops(body.voluntary_exits, process_voluntary_exit) + for_ops(body.execution_payload.exits, process_execution_layer_exit) # [New in EIP7002] + for_ops(body.bls_to_execution_changes, process_bls_to_execution_change) +``` + +##### New `process_execution_layer_exit` + +```python +def process_execution_layer_exit(state: BeaconState, execution_layer_exit: ExecutionLayerExit) -> None: + validator_pubkeys = [v.pubkey for v in state.validators] + validator_index = ValidatorIndex(validator_pubkeys.index(execution_layer_exit.validator_pubkey)) + validator = state.validators[validator_index] + + # Verify withdrawal credentials + is_execution_address = validator.withdrawal_credentials[:1] == ETH1_ADDRESS_WITHDRAWAL_PREFIX + is_correct_source_address = validator.withdrawal_credentials[12:] == execution_layer_exit.source_address + if not (is_execution_address and is_correct_source_address): + return + # Verify the validator is active + if not is_active_validator(validator, get_current_epoch(state)): + return + # Verify exit has not been initiated + if validator.exit_epoch != FAR_FUTURE_EPOCH: + return + # Verify the validator has been active long enough + if get_current_epoch(state) < validator.activation_epoch + SHARD_COMMITTEE_PERIOD: + return + + # Initiate exit + initiate_validator_exit(state, validator_index) +``` + +## Testing + +*Note*: The function `initialize_beacon_state_from_eth1` is modified for pure EIP-7002 testing only. +Modifications include: +1. Use `EIP7002_FORK_VERSION` as the previous and current fork version. +2. Utilize the EIP-7002 `BeaconBlockBody` when constructing the initial `latest_block_header`. + +```python +def initialize_beacon_state_from_eth1(eth1_block_hash: Hash32, + eth1_timestamp: uint64, + deposits: Sequence[Deposit], + execution_payload_header: ExecutionPayloadHeader=ExecutionPayloadHeader() + ) -> BeaconState: + fork = Fork( + previous_version=EIP7002_FORK_VERSION, # [Modified in EIP7002] for testing only + current_version=EIP7002_FORK_VERSION, # [Modified in EIP7002] + epoch=GENESIS_EPOCH, + ) + state = BeaconState( + genesis_time=eth1_timestamp + GENESIS_DELAY, + fork=fork, + eth1_data=Eth1Data(block_hash=eth1_block_hash, deposit_count=uint64(len(deposits))), + latest_block_header=BeaconBlockHeader(body_root=hash_tree_root(BeaconBlockBody())), + randao_mixes=[eth1_block_hash] * EPOCHS_PER_HISTORICAL_VECTOR, # Seed RANDAO with Eth1 entropy + ) + + # Process deposits + leaves = list(map(lambda deposit: deposit.data, deposits)) + for index, deposit in enumerate(deposits): + deposit_data_list = List[DepositData, 2**DEPOSIT_CONTRACT_TREE_DEPTH](*leaves[:index + 1]) + state.eth1_data.deposit_root = hash_tree_root(deposit_data_list) + process_deposit(state, deposit) + + # Process activations + for index, validator in enumerate(state.validators): + balance = state.balances[index] + validator.effective_balance = min(balance - balance % EFFECTIVE_BALANCE_INCREMENT, MAX_EFFECTIVE_BALANCE) + if validator.effective_balance == MAX_EFFECTIVE_BALANCE: + validator.activation_eligibility_epoch = GENESIS_EPOCH + validator.activation_epoch = GENESIS_EPOCH + + # Set genesis validators root for domain separation and chain versioning + state.genesis_validators_root = hash_tree_root(state.validators) + + # Fill in sync committees + # Note: A duplicate committee is assigned for the current and next committee at genesis + state.current_sync_committee = get_next_sync_committee(state) + state.next_sync_committee = get_next_sync_committee(state) + + # Initialize the execution payload header + state.latest_execution_payload_header = execution_payload_header + + return state +``` diff --git a/specs/_features/eip7002/fork.md b/specs/_features/eip7002/fork.md new file mode 100644 index 0000000000..87008de77b --- /dev/null +++ b/specs/_features/eip7002/fork.md @@ -0,0 +1,140 @@ +# EIP-7002 -- Fork Logic + +**Notice**: This document is a work-in-progress for researchers and implementers. + +## Table of contents + + + + +- [Introduction](#introduction) +- [Configuration](#configuration) +- [Helper functions](#helper-functions) + - [Misc](#misc) + - [Modified `compute_fork_version`](#modified-compute_fork_version) +- [Fork to EIP-7002](#fork-to-eip-7002) + - [Fork trigger](#fork-trigger) + - [Upgrading the state](#upgrading-the-state) + + + +## Introduction + +This document describes the process of EIP-7002 upgrade. + +## Configuration + +Warning: this configuration is not definitive. + +| Name | Value | +| - | - | +| `EIP7002_FORK_VERSION` | `Version('0x05000000')` | +| `EIP7002_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 >= EIP7002_FORK_EPOCH: + return EIP7002_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 EIP-7002 + +### Fork trigger + +TBD. This fork is defined for testing purposes, the EIP may be combined with other consensus-layer upgrade. +For now, we assume the condition will be triggered at epoch `EIP7002_FORK_EPOCH`. + +Note that for the pure EIP-7002 networks, we don't apply `upgrade_to_eip7002` since it starts with EIP-7002 version logic. + +### Upgrading the state + +If `state.slot % SLOTS_PER_EPOCH == 0` and `compute_epoch_at_slot(state.slot) == EIP7002_FORK_EPOCH`, +an irregular state change is made to upgrade to EIP-7002. + +```python +def upgrade_to_eip7002(pre: capella.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, + 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, + exits_root=Root(), # [New in EIP-7002] + ) + 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=EIP7002_FORK_VERSION, # [Modified in EIP-7002] + 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, # [Modified in EIP-7002] + # 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 +``` diff --git a/tests/core/pyspec/eth2spec/test/context.py b/tests/core/pyspec/eth2spec/test/context.py index 48f6857f64..0c9d4a1ec5 100644 --- a/tests/core/pyspec/eth2spec/test/context.py +++ b/tests/core/pyspec/eth2spec/test/context.py @@ -9,12 +9,13 @@ from eth2spec.capella import mainnet as spec_capella_mainnet, minimal as spec_capella_minimal from eth2spec.deneb import mainnet as spec_deneb_mainnet, minimal as spec_deneb_minimal from eth2spec.eip6110 import mainnet as spec_eip6110_mainnet, minimal as spec_eip6110_minimal +from eth2spec.eip7002 import mainnet as spec_eip7002_mainnet, minimal as spec_eip7002_minimal from eth2spec.utils import bls from .exceptions import SkippedTest from .helpers.constants import ( PHASE0, ALTAIR, BELLATRIX, CAPELLA, DENEB, - EIP6110, + EIP6110, EIP7002, MINIMAL, MAINNET, ALL_PHASES, ALL_FORK_UPGRADES, @@ -83,6 +84,7 @@ class ForkMeta: CAPELLA: spec_capella_minimal, DENEB: spec_deneb_minimal, EIP6110: spec_eip6110_minimal, + EIP7002: spec_eip7002_minimal, }, MAINNET: { PHASE0: spec_phase0_mainnet, @@ -91,6 +93,7 @@ class ForkMeta: CAPELLA: spec_capella_mainnet, DENEB: spec_deneb_mainnet, EIP6110: spec_eip6110_mainnet, + EIP7002: spec_eip7002_mainnet, }, } @@ -541,6 +544,7 @@ def wrapper(*args, spec: Spec, **kw): with_capella_and_later = with_all_phases_from(CAPELLA) with_deneb_and_later = with_all_phases_from(DENEB) with_eip6110_and_later = with_all_phases_from(EIP6110) +with_eip7002_and_later = with_all_phases_from(EIP7002) class quoted_str(str): diff --git a/tests/core/pyspec/eth2spec/test/eip7002/__init__.py b/tests/core/pyspec/eth2spec/test/eip7002/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/core/pyspec/eth2spec/test/eip7002/block_processing/__init__.py b/tests/core/pyspec/eth2spec/test/eip7002/block_processing/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/core/pyspec/eth2spec/test/eip7002/block_processing/test_process_execution_layer_exit.py b/tests/core/pyspec/eth2spec/test/eip7002/block_processing/test_process_execution_layer_exit.py new file mode 100644 index 0000000000..bd944a1fa3 --- /dev/null +++ b/tests/core/pyspec/eth2spec/test/eip7002/block_processing/test_process_execution_layer_exit.py @@ -0,0 +1,107 @@ +from eth2spec.test.context import spec_state_test, with_eip7002_and_later +from eth2spec.test.helpers.execution_layer_exits import run_execution_layer_exit_processing +from eth2spec.test.helpers.withdrawals import set_eth1_withdrawal_credential_with_balance + + +@with_eip7002_and_later +@spec_state_test +def test_basic_exit(spec, state): + # move state forward SHARD_COMMITTEE_PERIOD epochs to allow for exit + state.slot += spec.config.SHARD_COMMITTEE_PERIOD * spec.SLOTS_PER_EPOCH + + current_epoch = spec.get_current_epoch(state) + validator_index = spec.get_active_validator_indices(state, current_epoch)[0] + validator_pubkey = state.validators[validator_index].pubkey + address = b'\x22' * 20 + set_eth1_withdrawal_credential_with_balance(spec, state, validator_index, address=address) + execution_layer_exit = spec.ExecutionLayerExit( + source_address=address, + validator_pubkey=validator_pubkey, + ) + + yield from run_execution_layer_exit_processing(spec, state, execution_layer_exit) + + +@with_eip7002_and_later +@spec_state_test +def test_incorrect_source_address(spec, state): + # move state forward SHARD_COMMITTEE_PERIOD epochs to allow for exit + state.slot += spec.config.SHARD_COMMITTEE_PERIOD * spec.SLOTS_PER_EPOCH + + current_epoch = spec.get_current_epoch(state) + validator_index = spec.get_active_validator_indices(state, current_epoch)[0] + validator_pubkey = state.validators[validator_index].pubkey + address = b'\x22' * 20 + incorrect_address = b'\x33' * 20 + set_eth1_withdrawal_credential_with_balance(spec, state, validator_index, address=address) + execution_layer_exit = spec.ExecutionLayerExit( + source_address=incorrect_address, + validator_pubkey=validator_pubkey, + ) + + yield from run_execution_layer_exit_processing(spec, state, execution_layer_exit, success=False) + + +@with_eip7002_and_later +@spec_state_test +def test_incorrect_withdrawal_credential_prefix(spec, state): + # move state forward SHARD_COMMITTEE_PERIOD epochs to allow for exit + state.slot += spec.config.SHARD_COMMITTEE_PERIOD * spec.SLOTS_PER_EPOCH + + current_epoch = spec.get_current_epoch(state) + validator_index = spec.get_active_validator_indices(state, current_epoch)[0] + validator_pubkey = state.validators[validator_index].pubkey + address = b'\x22' * 20 + set_eth1_withdrawal_credential_with_balance(spec, state, validator_index, address=address) + # Set incorrect prefix + state.validators[validator_index].withdrawal_credentials = ( + spec.BLS_WITHDRAWAL_PREFIX + + state.validators[validator_index].withdrawal_credentials[1:] + ) + execution_layer_exit = spec.ExecutionLayerExit( + source_address=address, + validator_pubkey=validator_pubkey, + ) + + yield from run_execution_layer_exit_processing(spec, state, execution_layer_exit, success=False) + + +@with_eip7002_and_later +@spec_state_test +def test_on_exit_initiated_validator(spec, state): + # move state forward SHARD_COMMITTEE_PERIOD epochs to allow for exit + state.slot += spec.config.SHARD_COMMITTEE_PERIOD * spec.SLOTS_PER_EPOCH + + current_epoch = spec.get_current_epoch(state) + validator_index = spec.get_active_validator_indices(state, current_epoch)[0] + validator_pubkey = state.validators[validator_index].pubkey + address = b'\x22' * 20 + set_eth1_withdrawal_credential_with_balance(spec, state, validator_index, address=address) + # Initiate exit earlier + spec.initiate_validator_exit(state, validator_index) + execution_layer_exit = spec.ExecutionLayerExit( + source_address=address, + validator_pubkey=validator_pubkey, + ) + + yield from run_execution_layer_exit_processing(spec, state, execution_layer_exit, success=False) + + +@with_eip7002_and_later +@spec_state_test +def test_activation_epoch_less_than_shard_committee_period(spec, state): + current_epoch = spec.get_current_epoch(state) + validator_index = spec.get_active_validator_indices(state, current_epoch)[0] + validator_pubkey = state.validators[validator_index].pubkey + address = b'\x22' * 20 + set_eth1_withdrawal_credential_with_balance(spec, state, validator_index, address=address) + execution_layer_exit = spec.ExecutionLayerExit( + source_address=address, + validator_pubkey=validator_pubkey, + ) + + assert spec.get_current_epoch(state) < ( + state.validators[validator_index].activation_epoch + spec.config.SHARD_COMMITTEE_PERIOD + ) + + yield from run_execution_layer_exit_processing(spec, state, execution_layer_exit, success=False) diff --git a/tests/core/pyspec/eth2spec/test/eip7002/sanity/__init__.py b/tests/core/pyspec/eth2spec/test/eip7002/sanity/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/core/pyspec/eth2spec/test/eip7002/sanity/test_blocks.py b/tests/core/pyspec/eth2spec/test/eip7002/sanity/test_blocks.py new file mode 100644 index 0000000000..29a03fee01 --- /dev/null +++ b/tests/core/pyspec/eth2spec/test/eip7002/sanity/test_blocks.py @@ -0,0 +1,174 @@ +from eth2spec.test.helpers.block import ( + build_empty_block_for_next_slot +) +from eth2spec.test.context import ( + spec_state_test, + with_eip7002_and_later, +) +from eth2spec.test.helpers.bls_to_execution_changes import ( + get_signed_address_change, +) +from eth2spec.test.helpers.execution_payload import ( + compute_el_block_hash, +) +from eth2spec.test.helpers.voluntary_exits import ( + prepare_signed_exits, +) +from eth2spec.test.helpers.state import ( + state_transition_and_sign_block, +) +from eth2spec.test.helpers.withdrawals import ( + set_eth1_withdrawal_credential_with_balance, +) + + +@with_eip7002_and_later +@spec_state_test +def test_basic_el_exit(spec, state): + # move state forward SHARD_COMMITTEE_PERIOD epochs to allow for exit + state.slot += spec.config.SHARD_COMMITTEE_PERIOD * spec.SLOTS_PER_EPOCH + + yield 'pre', state + + validator_index = 0 + address = b'\x22' * 20 + set_eth1_withdrawal_credential_with_balance(spec, state, validator_index, address=address) + assert state.validators[validator_index].exit_epoch == spec.FAR_FUTURE_EPOCH + + validator_pubkey = state.validators[validator_index].pubkey + execution_layer_exit = spec.ExecutionLayerExit( + source_address=address, + validator_pubkey=validator_pubkey, + ) + block = build_empty_block_for_next_slot(spec, state) + block.body.execution_payload.exits = [execution_layer_exit] + block.body.execution_payload.block_hash = compute_el_block_hash(spec, block.body.execution_payload) + signed_block = state_transition_and_sign_block(spec, state, block) + + yield 'blocks', [signed_block] + yield 'post', state + + assert state.validators[validator_index].exit_epoch < spec.FAR_FUTURE_EPOCH + + +@with_eip7002_and_later +@spec_state_test +def test_basic_btec_and_el_exit_in_same_block(spec, state): + # move state forward SHARD_COMMITTEE_PERIOD epochs to allow for exit + state.slot += spec.config.SHARD_COMMITTEE_PERIOD * spec.SLOTS_PER_EPOCH + + yield 'pre', state + validator_index = 0 + assert state.validators[validator_index].exit_epoch == spec.FAR_FUTURE_EPOCH + + block = build_empty_block_for_next_slot(spec, state) + + address = b'\x22' * 20 + signed_address_change = get_signed_address_change( + spec, + state, + validator_index=validator_index, + to_execution_address=address, + ) + block.body.bls_to_execution_changes = [signed_address_change] + + validator_pubkey = state.validators[validator_index].pubkey + execution_layer_exit = spec.ExecutionLayerExit( + source_address=address, + validator_pubkey=validator_pubkey, + ) + block.body.execution_payload.exits = [execution_layer_exit] + + block.body.execution_payload.block_hash = compute_el_block_hash(spec, block.body.execution_payload) + signed_block = state_transition_and_sign_block(spec, state, block) + + yield 'blocks', [signed_block] + yield 'post', state + + # BTEC is executed after EL-Exit, so it doesn't take effect. `initiate_validator_exit` is not called. + validator = state.validators[validator_index] + assert validator.exit_epoch == spec.FAR_FUTURE_EPOCH + # Check if BTEC is effect + is_execution_address = validator.withdrawal_credentials[:1] == spec.ETH1_ADDRESS_WITHDRAWAL_PREFIX + is_correct_source_address = validator.withdrawal_credentials[12:] == address + assert is_execution_address and is_correct_source_address + + +@with_eip7002_and_later +@spec_state_test +def test_basic_btec_before_el_exit(spec, state): + # move state forward SHARD_COMMITTEE_PERIOD epochs to allow for exit + state.slot += spec.config.SHARD_COMMITTEE_PERIOD * spec.SLOTS_PER_EPOCH + + yield 'pre', state + + validator_index = 0 + assert state.validators[validator_index].exit_epoch == spec.FAR_FUTURE_EPOCH + + # block_1 contains a BTEC operation of the given validator + address = b'\x22' * 20 + signed_address_change = get_signed_address_change( + spec, + state, + validator_index=validator_index, + to_execution_address=address, + ) + block_1 = build_empty_block_for_next_slot(spec, state) + block_1.body.bls_to_execution_changes = [signed_address_change] + signed_block_1 = state_transition_and_sign_block(spec, state, block_1) + + validator = state.validators[validator_index] + assert validator.exit_epoch == spec.FAR_FUTURE_EPOCH + # Check if BTEC is effect + is_execution_address = validator.withdrawal_credentials[:1] == spec.ETH1_ADDRESS_WITHDRAWAL_PREFIX + is_correct_source_address = validator.withdrawal_credentials[12:] == address + assert is_execution_address and is_correct_source_address + + # block_2 contains an EL-Exit operation of the given validator + validator_pubkey = state.validators[validator_index].pubkey + execution_layer_exit = spec.ExecutionLayerExit( + source_address=address, + validator_pubkey=validator_pubkey, + ) + block_2 = build_empty_block_for_next_slot(spec, state) + block_2.body.execution_payload.exits = [execution_layer_exit] + block_2.body.execution_payload.block_hash = compute_el_block_hash(spec, block_2.body.execution_payload) + signed_block_2 = state_transition_and_sign_block(spec, state, block_2) + + yield 'blocks', [signed_block_1, signed_block_2] + yield 'post', state + + assert state.validators[validator_index].exit_epoch < spec.FAR_FUTURE_EPOCH + + +@with_eip7002_and_later +@spec_state_test +def test_cl_exit_and_el_exit_in_same_block(spec, state): + # move state forward SHARD_COMMITTEE_PERIOD epochs to allow for exit + state.slot += spec.config.SHARD_COMMITTEE_PERIOD * spec.SLOTS_PER_EPOCH + + yield 'pre', state + + validator_index = 0 + address = b'\x22' * 20 + set_eth1_withdrawal_credential_with_balance(spec, state, validator_index, address=address) + assert state.validators[validator_index].exit_epoch == spec.FAR_FUTURE_EPOCH + + # CL-Exit + signed_voluntary_exits = prepare_signed_exits(spec, state, indices=[validator_index]) + # EL-Exit + validator_pubkey = state.validators[validator_index].pubkey + execution_layer_exit = spec.ExecutionLayerExit( + source_address=address, + validator_pubkey=validator_pubkey, + ) + block = build_empty_block_for_next_slot(spec, state) + block.body.voluntary_exits = signed_voluntary_exits + block.body.execution_payload.exits = [execution_layer_exit] + block.body.execution_payload.block_hash = compute_el_block_hash(spec, block.body.execution_payload) + signed_block = state_transition_and_sign_block(spec, state, block) + + yield 'blocks', [signed_block] + yield 'post', state + + assert state.validators[validator_index].exit_epoch < spec.FAR_FUTURE_EPOCH diff --git a/tests/core/pyspec/eth2spec/test/helpers/constants.py b/tests/core/pyspec/eth2spec/test/helpers/constants.py index 049c354caf..82e4f9d0a5 100644 --- a/tests/core/pyspec/eth2spec/test/helpers/constants.py +++ b/tests/core/pyspec/eth2spec/test/helpers/constants.py @@ -17,6 +17,7 @@ CUSTODY_GAME = SpecForkName('custody_game') DAS = SpecForkName('das') EIP6110 = SpecForkName('eip6110') +EIP7002 = SpecForkName('eip7002') # # SpecFork settings @@ -32,6 +33,7 @@ DENEB, # Experimental patches EIP6110, + EIP7002, ) # The forks that have light client specs LIGHT_CLIENT_TESTING_FORKS = (*[item for item in MAINNET_FORKS if item != PHASE0], DENEB) diff --git a/tests/core/pyspec/eth2spec/test/helpers/execution_layer_exits.py b/tests/core/pyspec/eth2spec/test/helpers/execution_layer_exits.py new file mode 100644 index 0000000000..e0dda75d1d --- /dev/null +++ b/tests/core/pyspec/eth2spec/test/helpers/execution_layer_exits.py @@ -0,0 +1,39 @@ +from eth2spec.test.context import expect_assertion_error +from eth2spec.test.helpers.state import get_validator_index_by_pubkey + + +# +# Run processing +# + + +def run_execution_layer_exit_processing(spec, state, execution_layer_exit, valid=True, success=True): + """ + Run ``process_execution_layer_exit``, yielding: + - pre-state ('pre') + - execution_layer_exit ('execution_layer_exit') + - post-state ('post'). + If ``valid == False``, run expecting ``AssertionError`` + If ``success == False``, it doesn't initiate exit successfully + """ + validator_index = get_validator_index_by_pubkey(state, execution_layer_exit.validator_pubkey) + + yield 'pre', state + yield 'execution_layer_exit', execution_layer_exit + + if not valid: + expect_assertion_error(lambda: spec.process_execution_layer_exit(state, execution_layer_exit)) + yield 'post', None + return + + pre_exit_epoch = state.validators[validator_index].exit_epoch + + spec.process_execution_layer_exit(state, execution_layer_exit) + + yield 'post', state + + if success: + assert pre_exit_epoch == spec.FAR_FUTURE_EPOCH + assert state.validators[validator_index].exit_epoch < spec.FAR_FUTURE_EPOCH + else: + assert state.validators[validator_index].exit_epoch == pre_exit_epoch diff --git a/tests/core/pyspec/eth2spec/test/helpers/execution_payload.py b/tests/core/pyspec/eth2spec/test/helpers/execution_payload.py index b08b1975eb..2f9c0e0a46 100644 --- a/tests/core/pyspec/eth2spec/test/helpers/execution_payload.py +++ b/tests/core/pyspec/eth2spec/test/helpers/execution_payload.py @@ -8,6 +8,7 @@ is_post_capella, is_post_deneb, is_post_eip6110, + is_post_eip7002, ) @@ -35,6 +36,8 @@ def get_execution_payload_header(spec, execution_payload): payload_header.excess_data_gas = execution_payload.excess_data_gas if is_post_eip6110(spec): payload_header.deposit_receipts_root = spec.hash_tree_root(execution_payload.deposit_receipts) + if is_post_eip7002(spec): + payload_header.exits_root = spec.hash_tree_root(execution_payload.exits) return payload_header @@ -56,7 +59,8 @@ def compute_el_header_block_hash(spec, payload_header, transactions_trie_root, withdrawals_trie_root=None, - deposit_receipts_trie_root=None): + deposit_receipts_trie_root=None, + exits_trie_root=None): """ Computes the RLP execution block hash described by an `ExecutionPayloadHeader`. """ @@ -105,6 +109,9 @@ def compute_el_header_block_hash(spec, # deposit_receipts_root assert deposit_receipts_trie_root is not None execution_payload_header_rlp.append((Binary(32, 32), deposit_receipts_trie_root)) + if is_post_eip7002(spec): + # exits_trie_root + execution_payload_header_rlp.append((Binary(32, 32), exits_trie_root)) sedes = List([schema for schema, _ in execution_payload_header_rlp]) values = [value for _, value in execution_payload_header_rlp] @@ -114,7 +121,7 @@ def compute_el_header_block_hash(spec, # https://eips.ethereum.org/EIPS/eip-4895 -def get_withdrawal_rlp(spec, withdrawal): +def get_withdrawal_rlp(withdrawal): withdrawal_rlp = [ # index (big_endian_int, withdrawal.index), @@ -131,6 +138,20 @@ def get_withdrawal_rlp(spec, withdrawal): return encode(values, sedes) +# https://eips.ethereum.org/EIPS/eip-7002 +def get_exit_rlp(exit): + exit_rlp = [ + # source_address + (Binary(20, 20), exit.source_address), + # validator_pubkey + (Binary(48, 48), exit.validator_pubkey), + ] + + sedes = List([schema for schema, _ in exit_rlp]) + values = [value for _, value in exit_rlp] + return encode(values, sedes) + + def get_deposit_receipt_rlp(spec, deposit_receipt): deposit_receipt_rlp = [ # pubkey @@ -155,13 +176,17 @@ def compute_el_block_hash(spec, payload): withdrawals_trie_root = None deposit_receipts_trie_root = None + exits_trie_root = None if is_post_capella(spec): - withdrawals_encoded = [get_withdrawal_rlp(spec, withdrawal) for withdrawal in payload.withdrawals] + withdrawals_encoded = [get_withdrawal_rlp(withdrawal) for withdrawal in payload.withdrawals] withdrawals_trie_root = compute_trie_root_from_indexed_data(withdrawals_encoded) if is_post_eip6110(spec): deposit_receipts_encoded = [get_deposit_receipt_rlp(spec, receipt) for receipt in payload.deposit_receipts] deposit_receipts_trie_root = compute_trie_root_from_indexed_data(deposit_receipts_encoded) + if is_post_eip7002(spec): + exits_encoded = [get_exit_rlp(exit) for exit in payload.exits] + exits_trie_root = compute_trie_root_from_indexed_data(exits_encoded) payload_header = get_execution_payload_header(spec, payload) @@ -171,6 +196,7 @@ def compute_el_block_hash(spec, payload): transactions_trie_root, withdrawals_trie_root, deposit_receipts_trie_root, + exits_trie_root, ) diff --git a/tests/core/pyspec/eth2spec/test/helpers/fork_transition.py b/tests/core/pyspec/eth2spec/test/helpers/fork_transition.py index 68444c4726..80c54d9c1f 100644 --- a/tests/core/pyspec/eth2spec/test/helpers/fork_transition.py +++ b/tests/core/pyspec/eth2spec/test/helpers/fork_transition.py @@ -16,6 +16,7 @@ CAPELLA, DENEB, EIP6110, + EIP7002, ) from eth2spec.test.helpers.deposits import ( prepare_state_and_deposit, @@ -161,6 +162,8 @@ def do_fork(state, spec, post_spec, fork_epoch, with_block=True, sync_aggregate= state = post_spec.upgrade_to_deneb(state) elif post_spec.fork == EIP6110: state = post_spec.upgrade_to_eip6110(state) + elif post_spec.fork == EIP7002: + state = post_spec.upgrade_to_eip7002(state) assert state.fork.epoch == fork_epoch @@ -179,6 +182,9 @@ def do_fork(state, spec, post_spec, fork_epoch, with_block=True, sync_aggregate= elif post_spec.fork == EIP6110: assert state.fork.previous_version == post_spec.config.DENEB_FORK_VERSION assert state.fork.current_version == post_spec.config.EIP6110_FORK_VERSION + elif post_spec.fork == EIP7002: + assert state.fork.previous_version == post_spec.config.CAPELLA_FORK_VERSION + assert state.fork.current_version == post_spec.config.EIP7002_FORK_VERSION if with_block: return state, _state_transition_and_sign_block_at_slot( diff --git a/tests/core/pyspec/eth2spec/test/helpers/forks.py b/tests/core/pyspec/eth2spec/test/helpers/forks.py index 5e97522dbb..492af47fe3 100644 --- a/tests/core/pyspec/eth2spec/test/helpers/forks.py +++ b/tests/core/pyspec/eth2spec/test/helpers/forks.py @@ -1,10 +1,12 @@ from .constants import ( PHASE0, ALTAIR, BELLATRIX, CAPELLA, DENEB, - EIP6110, + EIP6110, EIP7002, ) def is_post_fork(a, b): + if a == EIP7002: + return b in [PHASE0, ALTAIR, BELLATRIX, CAPELLA, EIP7002] if a == EIP6110: return b in [PHASE0, ALTAIR, BELLATRIX, CAPELLA, DENEB, EIP6110] if a == DENEB: @@ -38,3 +40,7 @@ def is_post_deneb(spec): def is_post_eip6110(spec): return is_post_fork(spec.fork, EIP6110) + + +def is_post_eip7002(spec): + return is_post_fork(spec.fork, EIP7002) diff --git a/tests/core/pyspec/eth2spec/test/helpers/genesis.py b/tests/core/pyspec/eth2spec/test/helpers/genesis.py index fea259013b..e55bdef5ce 100644 --- a/tests/core/pyspec/eth2spec/test/helpers/genesis.py +++ b/tests/core/pyspec/eth2spec/test/helpers/genesis.py @@ -1,11 +1,11 @@ from eth2spec.test.helpers.constants import ( - ALTAIR, BELLATRIX, CAPELLA, DENEB, EIP6110, + ALTAIR, BELLATRIX, CAPELLA, DENEB, EIP6110, EIP7002, ) from eth2spec.test.helpers.execution_payload import ( compute_el_header_block_hash, ) from eth2spec.test.helpers.forks import ( - is_post_altair, is_post_bellatrix, is_post_capella, is_post_eip6110, + is_post_altair, is_post_bellatrix, is_post_capella, is_post_eip6110, is_post_eip7002, ) from eth2spec.test.helpers.keys import pubkeys @@ -49,11 +49,14 @@ def get_sample_genesis_execution_payload_header(spec, transactions_trie_root = bytes.fromhex("56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421") withdrawals_trie_root = None deposit_receipts_trie_root = None + exits_trie_root = None if is_post_capella(spec): withdrawals_trie_root = bytes.fromhex("56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421") if is_post_eip6110(spec): deposit_receipts_trie_root = bytes.fromhex("56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421") + if is_post_eip7002(spec): + exits_trie_root = bytes.fromhex("56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421") payload_header.block_hash = compute_el_header_block_hash( spec, @@ -61,6 +64,7 @@ def get_sample_genesis_execution_payload_header(spec, transactions_trie_root, withdrawals_trie_root, deposit_receipts_trie_root, + exits_trie_root, ) return payload_header @@ -86,6 +90,9 @@ def create_genesis_state(spec, validator_balances, activation_threshold): elif spec.fork == EIP6110: previous_version = spec.config.DENEB_FORK_VERSION current_version = spec.config.EIP6110_FORK_VERSION + elif spec.fork == EIP7002: + previous_version = spec.config.CAPELLA_FORK_VERSION + current_version = spec.config.EIP7002_FORK_VERSION state = spec.BeaconState( genesis_time=0, diff --git a/tests/core/pyspec/eth2spec/test/helpers/state.py b/tests/core/pyspec/eth2spec/test/helpers/state.py index 0dc17b00f1..1e64bd4db2 100644 --- a/tests/core/pyspec/eth2spec/test/helpers/state.py +++ b/tests/core/pyspec/eth2spec/test/helpers/state.py @@ -166,3 +166,8 @@ def has_active_balance_differential(spec, state): active_balance = spec.get_total_active_balance(state) total_balance = spec.get_total_balance(state, set(range(len(state.validators)))) return active_balance // spec.EFFECTIVE_BALANCE_INCREMENT != total_balance // spec.EFFECTIVE_BALANCE_INCREMENT + + +def get_validator_index_by_pubkey(state, pubkey): + index = next((i for i, validator in enumerate(state.validators) if validator.pubkey == pubkey), None) + return index diff --git a/tests/core/pyspec/eth2spec/test/helpers/withdrawals.py b/tests/core/pyspec/eth2spec/test/helpers/withdrawals.py index aebe49f26e..9ebb05ba0e 100644 --- a/tests/core/pyspec/eth2spec/test/helpers/withdrawals.py +++ b/tests/core/pyspec/eth2spec/test/helpers/withdrawals.py @@ -20,9 +20,14 @@ def set_validator_fully_withdrawable(spec, state, index, withdrawable_epoch=None assert spec.is_fully_withdrawable_validator(validator, state.balances[index], withdrawable_epoch) -def set_eth1_withdrawal_credential_with_balance(spec, state, index, balance): +def set_eth1_withdrawal_credential_with_balance(spec, state, index, balance=None, address=None): + if balance is None: + balance = spec.MAX_EFFECTIVE_BALANCE + if address is None: + address = b'\x11' * 20 + validator = state.validators[index] - validator.withdrawal_credentials = spec.ETH1_ADDRESS_WITHDRAWAL_PREFIX + validator.withdrawal_credentials[1:] + validator.withdrawal_credentials = spec.ETH1_ADDRESS_WITHDRAWAL_PREFIX + b'\x00' * 11 + address validator.effective_balance = min(balance, spec.MAX_EFFECTIVE_BALANCE) state.balances[index] = balance