diff --git a/.circleci/config.yml b/.circleci/config.yml index ba9e79b98b..62e584be5a 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -36,13 +36,13 @@ commands: steps: - restore_cached_venv: venv_name: v33-pyspec - reqs_checksum: cache-{{ checksum "setup.py" }}-{{ checksum "pyproject.toml" }} + reqs_checksum: cache-{{ checksum "setup.py" }}-{{ checksum "pyproject.toml" }}-{{ python3 --version }} save_pyspec_cached_venv: description: Save a venv into a cache with pyspec keys" steps: - save_cached_venv: venv_name: v33-pyspec - reqs_checksum: cache-{{ checksum "setup.py" }}-{{ checksum "pyproject.toml" }} + reqs_checksum: cache-{{ checksum "setup.py" }}-{{ checksum "pyproject.toml" }}-{{ python3 --version }} venv_path: ./venv jobs: checkout_specs: diff --git a/.github/workflows/generate_vectors.yml b/.github/workflows/generate_vectors.yml index 128d402776..7e13f5f837 100644 --- a/.github/workflows/generate_vectors.yml +++ b/.github/workflows/generate_vectors.yml @@ -17,6 +17,7 @@ on: jobs: generate-tests: + timeout-minutes: 720 # 12 hours runs-on: [self-hosted-ghr-custom, size-xl-x64, profile-consensusSpecs] steps: - name: Checkout repository @@ -25,12 +26,6 @@ jobs: repository: 'ethereum/consensus-specs' path: 'consensus-specs' ref: ${{ inputs.ref || 'dev' }} - - name: Checkout consensus-spec-tests repository - uses: actions/checkout@v4 - with: - repository: 'ethereum/consensus-spec-tests' - path: 'consensus-spec-tests' - fetch-depth: 1 - name: Setup Python uses: actions/setup-python@v5 with: @@ -39,7 +34,7 @@ jobs: - name: Generate tests run: | cd consensus-specs - make -j 16 gen_all 2>&1 | tee ../consensustestgen.log + make -j$(nproc) gen_all 2>&1 | tee ../consensustestgen.log cp -r presets/ ../consensus-spec-tests/presets cp -r configs/ ../consensus-spec-tests/configs find . -type d -empty -delete @@ -78,4 +73,4 @@ jobs: uses: actions/upload-artifact@v4 with: name: consensustestgen.log - path: consensustestgen.log \ No newline at end of file + path: consensustestgen.log diff --git a/Makefile b/Makefile index dc9406d9f6..9bb5e5fa48 100644 --- a/Makefile +++ b/Makefile @@ -74,7 +74,7 @@ PYSPEC_DIR = $(TEST_LIBS_DIR)/pyspec # Create the pyspec for all phases. pyspec: $(VENV) setup.py pyproject.toml @echo "Building eth2spec" - @$(PYTHON_VENV) -m uv pip install .[docs,lint,test,generator] + @$(PYTHON_VENV) -m uv pip install --reinstall-package=eth2spec .[docs,lint,test,generator] @echo "Building all pyspecs" @$(PYTHON_VENV) setup.py pyspecdev diff --git a/README.md b/README.md index 5749a89506..b823d82ae2 100644 --- a/README.md +++ b/README.md @@ -6,11 +6,11 @@ To learn more about proof-of-stake and sharding, see the [PoS documentation](htt This repository hosts the current Ethereum proof-of-stake specifications. Discussions about design rationale and proposed changes can be brought up and discussed as issues. Solidified, agreed-upon changes to the spec can be made through pull requests. -## Specs +## Specifications [![GitHub release](https://img.shields.io/github/v/release/ethereum/consensus-specs)](https://github.com/ethereum/consensus-specs/releases/) [![PyPI version](https://badge.fury.io/py/eth2spec.svg)](https://badge.fury.io/py/eth2spec) [![testgen](https://github.com/ethereum/consensus-specs/actions/workflows/generate_vectors.yml/badge.svg?branch=dev&event=schedule)](https://github.com/ethereum/consensus-specs/actions/workflows/generate_vectors.yml) -Core specifications for Ethereum proof-of-stake clients can be found in [specs](./specs). These are divided into features. +Core specifications for Ethereum proof-of-stake clients can be found in [specs](specs). These are divided into features. Features are researched and developed in parallel, and then consolidated into sequential upgrades when ready. ### Stable Specifications @@ -30,14 +30,6 @@ Features are researched and developed in parallel, and then consolidated into se | 5 | **Electra** | TBD | | | 6 | **Fulu** | TBD | | -### Outdated Specifications - -| Code Name or Topic | Specs | Notes | -| - | - | - | -| Sharding | | -| Custody Game | | Dependent on sharding | -| Data Availability Sampling | | | - ### Accompanying documents can be found in [specs](specs) and include: * [SimpleSerialize (SSZ) spec](ssz/simple-serialize.md) @@ -50,7 +42,7 @@ Additional specifications and standards outside of requisite client functionalit * [Beacon APIs](https://github.com/ethereum/beacon-apis) * [Engine APIs](https://github.com/ethereum/execution-apis/tree/main/src/engine) -* [Beacon Metrics](https://github.com/ethereum/beacon-metrics/) +* [Beacon Metrics](https://github.com/ethereum/beacon-metrics) ## Design goals @@ -81,7 +73,8 @@ Documentation on the different components used during spec writing can be found Conformance tests built from the executable python spec are available in the [Ethereum Proof-of-Stake Consensus Spec Tests](https://github.com/ethereum/consensus-spec-tests) repo. Compressed tarballs are available in [releases](https://github.com/ethereum/consensus-spec-tests/releases). -## Installation and Usage +## Installation and usage + The consensus-specs repo can be used by running the tests locally or inside a docker container. To run the tests locally: diff --git a/configs/mainnet.yaml b/configs/mainnet.yaml index 7f96d087c9..a441ce5f25 100644 --- a/configs/mainnet.yaml +++ b/configs/mainnet.yaml @@ -170,6 +170,8 @@ DATA_COLUMN_SIDECAR_SUBNET_COUNT: 128 MAX_REQUEST_DATA_COLUMN_SIDECARS: 16384 SAMPLES_PER_SLOT: 8 CUSTODY_REQUIREMENT: 4 +VALIDATOR_CUSTODY_REQUIREMENT: 8 +BALANCE_PER_ADDITIONAL_CUSTODY_GROUP: 32000000000 MAX_BLOBS_PER_BLOCK_FULU: 12 MIN_EPOCHS_FOR_DATA_COLUMN_SIDECARS_REQUESTS: 4096 diff --git a/configs/minimal.yaml b/configs/minimal.yaml index 559e04d70e..ddbc181e88 100644 --- a/configs/minimal.yaml +++ b/configs/minimal.yaml @@ -171,6 +171,8 @@ DATA_COLUMN_SIDECAR_SUBNET_COUNT: 128 MAX_REQUEST_DATA_COLUMN_SIDECARS: 16384 SAMPLES_PER_SLOT: 8 CUSTODY_REQUIREMENT: 4 +VALIDATOR_CUSTODY_REQUIREMENT: 8 +BALANCE_PER_ADDITIONAL_CUSTODY_GROUP: 32000000000 MAX_BLOBS_PER_BLOCK_FULU: 12 MIN_EPOCHS_FOR_DATA_COLUMN_SIDECARS_REQUESTS: 4096 diff --git a/specs/electra/beacon-chain.md b/specs/electra/beacon-chain.md index d6854e5e7a..0c01f5204f 100644 --- a/specs/electra/beacon-chain.md +++ b/specs/electra/beacon-chain.md @@ -830,11 +830,9 @@ def process_registry_updates(state: BeaconState) -> None: for index, validator in enumerate(state.validators): if is_eligible_for_activation_queue(validator): # [Modified in Electra:EIP7251] validator.activation_eligibility_epoch = current_epoch + 1 - - if is_active_validator(validator, current_epoch) and validator.effective_balance <= EJECTION_BALANCE: + elif is_active_validator(validator, current_epoch) and validator.effective_balance <= EJECTION_BALANCE: initiate_validator_exit(state, ValidatorIndex(index)) # [Modified in Electra:EIP7251] - - if is_eligible_for_activation(state, validator): + elif is_eligible_for_activation(state, validator): validator.activation_epoch = activation_epoch ``` diff --git a/specs/electra/validator.md b/specs/electra/validator.md index ee59a278cd..f94bf9e77c 100644 --- a/specs/electra/validator.md +++ b/specs/electra/validator.md @@ -153,6 +153,40 @@ def get_eth1_pending_deposit_count(state: BeaconState) -> uint64: return uint64(0) ``` +*Note*: Clients will be able to remove the `Eth1Data` polling mechanism in an uncoordinated fashion once the transition period is finished. The transition period is considered finished when a network reaches the point where `state.eth1_deposit_index == state.deposit_requests_start_index`. + +```python +def get_eth1_vote(state: BeaconState, eth1_chain: Sequence[Eth1Block]) -> Eth1Data: + # [New in Electra:EIP6110] + if state.eth1_deposit_index == state.deposit_requests_start_index: + return state.eth1_data + + period_start = voting_period_start_time(state) + # `eth1_chain` abstractly represents all blocks in the eth1 chain sorted by ascending block height + votes_to_consider = [ + get_eth1_data(block) for block in eth1_chain + if ( + is_candidate_block(block, period_start) + # Ensure cannot move back to earlier deposit contract states + and get_eth1_data(block).deposit_count >= state.eth1_data.deposit_count + ) + ] + + # Valid votes already cast during this period + valid_votes = [vote for vote in state.eth1_data_votes if vote in votes_to_consider] + + # Default vote on latest eth1 block data in the period range unless eth1 chain is not live + # Non-substantive casting for linter + state_eth1_data: Eth1Data = state.eth1_data + default_vote = votes_to_consider[len(votes_to_consider) - 1] if any(votes_to_consider) else state_eth1_data + + return max( + valid_votes, + key=lambda v: (valid_votes.count(v), -valid_votes.index(v)), # Tiebreak by smallest distance + default=default_vote + ) +``` + #### Execution payload `prepare_execution_payload` is updated from the Deneb specs. diff --git a/specs/fulu/das-core.md b/specs/fulu/das-core.md index 846f6b206e..36cb501660 100644 --- a/specs/fulu/das-core.md +++ b/specs/fulu/das-core.md @@ -25,6 +25,7 @@ - [`get_data_column_sidecars`](#get_data_column_sidecars) - [Custody](#custody) - [Custody requirement](#custody-requirement) + - [Validator custody](#validator-custody) - [Public, deterministic selection](#public-deterministic-selection) - [Custody sampling](#custody-sampling) - [Extended data](#extended-data) @@ -72,6 +73,8 @@ The following values are (non-configurable) constants used throughout the specif | `SAMPLES_PER_SLOT` | `8` | Number of `DataColumnSidecar` random samples a node queries per slot | | `NUMBER_OF_CUSTODY_GROUPS` | `128` | Number of custody groups available for nodes to custody | | `CUSTODY_REQUIREMENT` | `4` | Minimum number of custody groups an honest node custodies and serves samples from | +| `VALIDATOR_CUSTODY_REQUIREMENT` | `8` | Minimum number of custody groups an honest node with validators attached custodies and serves samples from | +| `BALANCE_PER_ADDITIONAL_CUSTODY_GROUP` | `Gwei(32 * 10**9)` | Balance increment corresponding to one additional group to custody | ### Containers @@ -224,12 +227,25 @@ def get_data_column_sidecars(signed_block: SignedBeaconBlock, ### Custody requirement -Columns are grouped into custody groups. Nodes custodying a custody group MUST custody all the columns in that group. +Columns are grouped into custody groups. Nodes custodying a custody group MUST custody all the columns in that group. When syncing, a node MUST backfill columns from all of its custody groups. A node *may* choose to custody and serve more than the minimum honesty requirement. Such a node explicitly advertises a number greater than `CUSTODY_REQUIREMENT` through the peer discovery mechanism, specifically by setting a higher value in the `custody_group_count` field within its ENR. This value can be increased up to `NUMBER_OF_CUSTODY_GROUPS`, indicating a super-full node. A node stores the custodied columns for the duration of the pruning period and responds to peer requests for samples on those columns. +### Validator custody + +A node with validators attached downloads and custodies a higher minimum of custody groups per slot, determined by `get_validators_custody_requirement(state, validator_indices)`. Here, `state` is the current `BeaconState` and `validator_indices` is the list of indices corresponding to validators attached to the node. Any node with at least one validator attached, and with the sum of the balances of all attached validators being `total_node_balance`, downloads and custodies `total_node_balance // BALANCE_PER_ADDITIONAL_CUSTODY_GROUP` custody groups per slot, with a minimum of `VALIDATOR_CUSTODY_REQUIREMENT` and of course a maximum of `NUMBER_OF_CUSTODY_GROUPS`. + +```python +def get_validators_custody_requirement(state: BeaconState, validator_indices: Sequence[ValidatorIndex]) -> uint64: + total_node_balance = sum(state.balances[index] for index in validator_indices) + count = total_node_balance // BALANCE_PER_ADDITIONAL_CUSTODY_GROUP + return min(max(count, VALIDATOR_CUSTODY_REQUIREMENT), NUMBER_OF_CUSTODY_GROUPS) +``` + +This higher custody is advertised in the node's Metadata by setting a higher `custody_group_count` and in the node's ENR by setting a higher `cgc`. As with the regular custody requirement, a node with validators *may* still choose to custody, advertise and serve more than this minimum. As with the regular custody requirement, a node MUST backfill columns when syncing. In addition, when the validator custody requirement increases, due to an increase in the total balance of the attached validators, a node MUST backfill columns from the new custody groups. However, a node *may* wait to advertise a higher custody in its Metadata and ENR until backfilling is complete. + ### Public, deterministic selection The particular columns/groups that a node custodies are selected pseudo-randomly as a function (`get_custody_groups`) of the node-id and custody size -- importantly this function can be run by any party as the inputs are all public. diff --git a/specs/fulu/fork.md b/specs/fulu/fork.md index 009bb6c943..5f97c265b3 100644 --- a/specs/fulu/fork.md +++ b/specs/fulu/fork.md @@ -130,7 +130,7 @@ def upgrade_to_fulu(pre: electra.BeaconState) -> BeaconState: earliest_exit_epoch=pre.earliest_exit_epoch, consolidation_balance_to_consume=pre.consolidation_balance_to_consume, earliest_consolidation_epoch=pre.earliest_consolidation_epoch, - pending_balance_deposits=pre.pending_balance_deposits, + pending_deposits=pre.pending_deposits, pending_partial_withdrawals=pre.pending_partial_withdrawals, pending_consolidations=pre.pending_consolidations, ) diff --git a/tests/core/pyspec/eth2spec/VERSION.txt b/tests/core/pyspec/eth2spec/VERSION.txt index 591650acef..5b500643da 100644 --- a/tests/core/pyspec/eth2spec/VERSION.txt +++ b/tests/core/pyspec/eth2spec/VERSION.txt @@ -1 +1 @@ -1.5.0-beta.1 +1.5.0-beta.2 diff --git a/tests/core/pyspec/eth2spec/test/bellatrix/unittests/test_execution_engine_interface.py b/tests/core/pyspec/eth2spec/test/bellatrix/unittests/test_execution_engine_interface.py new file mode 100644 index 0000000000..1f62f46e50 --- /dev/null +++ b/tests/core/pyspec/eth2spec/test/bellatrix/unittests/test_execution_engine_interface.py @@ -0,0 +1,99 @@ +from eth2spec.test.context import ( + BELLATRIX, + CAPELLA, + spec_state_test, + with_bellatrix_and_later, + with_phases, +) +from eth2spec.test.helpers.execution_payload import ( + build_empty_execution_payload, +) +from eth2spec.test.helpers.state import next_slot +from eth2spec.utils.ssz.ssz_typing import Bytes32 + + +@with_bellatrix_and_later +@spec_state_test +def test_noop_execution_engine_notify_forkchoice_updated(spec, state): + """ + Test NoopExecutionEngine.notify_forkchoice_updated returns None and doesn't modify state + """ + engine = spec.NoopExecutionEngine() + pre_state = state.copy() + + # Test notify_forkchoice_updated + result = engine.notify_forkchoice_updated( + head_block_hash=Bytes32(), + safe_block_hash=Bytes32(), + finalized_block_hash=Bytes32(), + payload_attributes=None + ) + + # Verify behavior + assert result is None + assert state == pre_state + + +@with_bellatrix_and_later +@spec_state_test +def test_noop_execution_engine_get_payload(spec, state): + """ + Test NoopExecutionEngine.get_payload raises NotImplementedError + """ + engine = spec.NoopExecutionEngine() + pre_state = state.copy() + + # Test get_payload raises NotImplementedError + try: + engine.get_payload(payload_id=None) + raise AssertionError("get_payload should raise NotImplementedError") + except NotImplementedError: + pass + + # Verify state wasn't modified + assert state == pre_state + + +@with_bellatrix_and_later +@spec_state_test +def test_noop_execution_engine_verify_and_notify_new_payload(spec, state): + """ + Test NoopExecutionEngine.verify_and_notify_new_payload returns True and doesn't modify state + """ + engine = spec.NoopExecutionEngine() + pre_state = state.copy() + + result = engine.verify_and_notify_new_payload(new_payload_request=None) + + assert result is True + assert state == pre_state + + +@with_phases([BELLATRIX, CAPELLA]) +@spec_state_test +def test_noop_execution_engine_notify_new_payload_bellatrix_capella(spec, state): + """ + Test NoopExecutionEngine.notify_new_payload returns True and doesn't modify state + """ + engine = spec.NoopExecutionEngine() + + next_slot(spec, state) + payload = build_empty_execution_payload(spec, state) + result = engine.notify_new_payload(execution_payload=payload) + + assert result is True + + +@with_phases([BELLATRIX, CAPELLA]) +@spec_state_test +def test_noop_execution_engine_is_valid_block_hash_bellatrix_capella(spec, state): + """ + Test NoopExecutionEngine.is_valid_block_hash returns True and doesn't modify state + """ + engine = spec.NoopExecutionEngine() + + next_slot(spec, state) + payload = build_empty_execution_payload(spec, state) + result = engine.is_valid_block_hash(execution_payload=payload) + + assert result is True diff --git a/tests/core/pyspec/eth2spec/test/capella/block_processing/test_process_withdrawals.py b/tests/core/pyspec/eth2spec/test/capella/block_processing/test_process_withdrawals.py index 5d3407e956..26691bd9b5 100644 --- a/tests/core/pyspec/eth2spec/test/capella/block_processing/test_process_withdrawals.py +++ b/tests/core/pyspec/eth2spec/test/capella/block_processing/test_process_withdrawals.py @@ -812,3 +812,77 @@ def test_random_partial_withdrawals_4(spec, state): @spec_state_test def test_random_partial_withdrawals_5(spec, state): yield from run_random_partial_withdrawals_test(spec, state, random.Random(5)) + + +@with_capella_and_later +@spec_state_test +def test_partially_withdrawable_validator_legacy_max_plus_one(spec, state): + """Test legacy validator with balance just above MAX_EFFECTIVE_BALANCE""" + validator_index = 0 + set_eth1_withdrawal_credential_with_balance( + spec, state, + validator_index, + balance=spec.MAX_EFFECTIVE_BALANCE + 1 + ) + assert spec.is_partially_withdrawable_validator( + state.validators[validator_index], + state.balances[validator_index] + ) + + next_slot(spec, state) + execution_payload = build_empty_execution_payload(spec, state) + yield from run_withdrawals_processing( + spec, state, + execution_payload, + fully_withdrawable_indices=[], + partial_withdrawals_indices=[validator_index] + ) + + +@with_capella_and_later +@spec_state_test +def test_partially_withdrawable_validator_legacy_exact_max(spec, state): + """Test legacy validator whose balance is exactly MAX_EFFECTIVE_BALANCE""" + validator_index = 0 + set_eth1_withdrawal_credential_with_balance( + spec, state, + validator_index + ) + assert not spec.is_partially_withdrawable_validator( + state.validators[validator_index], + state.balances[validator_index] + ) + + next_slot(spec, state) + execution_payload = build_empty_execution_payload(spec, state) + yield from run_withdrawals_processing( + spec, state, + execution_payload, + fully_withdrawable_indices=[], + partial_withdrawals_indices=[] + ) + + +@with_capella_and_later +@spec_state_test +def test_partially_withdrawable_validator_legacy_max_minus_one(spec, state): + """Test legacy validator whose balance is below MAX_EFFECTIVE_BALANCE""" + validator_index = 0 + set_eth1_withdrawal_credential_with_balance( + spec, state, + validator_index, + balance=spec.MAX_EFFECTIVE_BALANCE - 1 + ) + assert not spec.is_partially_withdrawable_validator( + state.validators[validator_index], + state.balances[validator_index] + ) + + next_slot(spec, state) + execution_payload = build_empty_execution_payload(spec, state) + yield from run_withdrawals_processing( + spec, state, + execution_payload, + fully_withdrawable_indices=[], + partial_withdrawals_indices=[] + ) diff --git a/tests/core/pyspec/eth2spec/test/context.py b/tests/core/pyspec/eth2spec/test/context.py index 33209f17e2..1b8a3057c0 100644 --- a/tests/core/pyspec/eth2spec/test/context.py +++ b/tests/core/pyspec/eth2spec/test/context.py @@ -154,7 +154,7 @@ def scaled_churn_balances_exceed_activation_churn_limit(spec: Spec): def scaled_churn_balances_exceed_activation_exit_churn_limit(spec: Spec): """ Helper method to create enough validators to scale the churn limit. - (The number of validators is double the amount need for the max activation/exit churn limit) + (The number of validators is double the amount need for the max activation/exit churn limit) Usage: `@with_custom_state(balances_fn=scaled_churn_balances_exceed_activation_churn_limit, ...)` """ num_validators = ( diff --git a/tests/core/pyspec/eth2spec/test/deneb/unittests/test_execution_engine_interface.py b/tests/core/pyspec/eth2spec/test/deneb/unittests/test_execution_engine_interface.py new file mode 100644 index 0000000000..b2ec5a6adf --- /dev/null +++ b/tests/core/pyspec/eth2spec/test/deneb/unittests/test_execution_engine_interface.py @@ -0,0 +1,63 @@ +from eth2spec.test.context import ( + DENEB, + spec_state_test, + with_phases, + with_deneb_and_later, +) +from eth2spec.test.helpers.execution_payload import ( + build_empty_execution_payload, +) +from eth2spec.test.helpers.state import next_slot + + +@with_deneb_and_later +@spec_state_test +def test_noop_execution_engine_is_valid_versioned_hashes(spec, state): + """ + Test NoopExecutionEngine.is_valid_versioned_hashes returns True and doesn't modify state + """ + engine = spec.NoopExecutionEngine() + pre_state = state.copy() + + # Test is_valid_versioned_hashes + result = engine.is_valid_versioned_hashes(new_payload_request=None) + + # Verify behavior + assert result is True + assert state == pre_state + + +@with_phases([DENEB]) +@spec_state_test +def test_noop_execution_engine_notify_new_payload_deneb(spec, state): + """ + Test NoopExecutionEngine.notify_new_payload returns True and doesn't modify state + """ + engine = spec.NoopExecutionEngine() + + next_slot(spec, state) + payload = build_empty_execution_payload(spec, state) + result = engine.notify_new_payload( + execution_payload=payload, + parent_beacon_block_root=state.latest_block_header.parent_root, + ) + + assert result is True + + +@with_phases([DENEB]) +@spec_state_test +def test_noop_execution_engine_is_valid_block_hash_deneb(spec, state): + """ + Test NoopExecutionEngine.is_valid_block_hash returns True and doesn't modify state + """ + engine = spec.NoopExecutionEngine() + + next_slot(spec, state) + payload = build_empty_execution_payload(spec, state) + result = engine.is_valid_block_hash( + execution_payload=payload, + parent_beacon_block_root=state.latest_block_header.parent_root, + ) + + assert result is True diff --git a/tests/core/pyspec/eth2spec/test/electra/block_processing/test_process_consolidation_request.py b/tests/core/pyspec/eth2spec/test/electra/block_processing/test_process_consolidation_request.py index 3703b9f0f5..12ee03ae2c 100644 --- a/tests/core/pyspec/eth2spec/test/electra/block_processing/test_process_consolidation_request.py +++ b/tests/core/pyspec/eth2spec/test/electra/block_processing/test_process_consolidation_request.py @@ -367,6 +367,61 @@ def test_consolidation_churn_limit_balance(spec, state): 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_source_has_less_than_max_effective_balance(spec, state): + # Move state forward SHARD_COMMITTEE_PERIOD epochs to allow for consolidation + state.slot += spec.config.SHARD_COMMITTEE_PERIOD * spec.SLOTS_PER_EPOCH + + # 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 + ) + + # Lower the source validator's effective balance + source_effective_balance = spec.MAX_EFFECTIVE_BALANCE - spec.EFFECTIVE_BALANCE_INCREMENT + state.validators[source_index].effective_balance = source_effective_balance + + # Make consolidation with source address + consolidation = spec.ConsolidationRequest( + source_address=source_address, + source_pubkey=state.validators[source_index].pubkey, + target_pubkey=state.validators[target_index].pubkey, + ) + + # Set target to compounding credentials + set_compounding_withdrawal_credential(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 - source_effective_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( diff --git a/tests/core/pyspec/eth2spec/test/electra/block_processing/test_process_withdrawal_request.py b/tests/core/pyspec/eth2spec/test/electra/block_processing/test_process_withdrawal_request.py index aba6a29332..8406b0e0a3 100644 --- a/tests/core/pyspec/eth2spec/test/electra/block_processing/test_process_withdrawal_request.py +++ b/tests/core/pyspec/eth2spec/test/electra/block_processing/test_process_withdrawal_request.py @@ -271,7 +271,7 @@ def test_incorrect_withdrawal_credential_prefix(spec, state): @with_electra_and_later @spec_state_test -def test_on_withdrawal_request_initiated_validator(spec, state): +def test_on_withdrawal_request_initiated_exit_validator(spec, state): rng = random.Random(1342) # move state forward SHARD_COMMITTEE_PERIOD epochs to allow for exit state.slot += spec.config.SHARD_COMMITTEE_PERIOD * spec.SLOTS_PER_EPOCH @@ -766,9 +766,7 @@ def test_insufficient_effective_balance(spec, state): address = b"\x22" * 20 amount = spec.EFFECTIVE_BALANCE_INCREMENT # Make effective balance insufficient - state.validators[ - validator_index - ].effective_balance -= spec.EFFECTIVE_BALANCE_INCREMENT + state.validators[validator_index].effective_balance -= spec.EFFECTIVE_BALANCE_INCREMENT # Make sure validator has enough balance to withdraw state.balances[validator_index] += spec.EFFECTIVE_BALANCE_INCREMENT diff --git a/tests/core/pyspec/eth2spec/test/electra/block_processing/test_process_withdrawals.py b/tests/core/pyspec/eth2spec/test/electra/block_processing/test_process_withdrawals.py index ed3d914154..0c2248b929 100644 --- a/tests/core/pyspec/eth2spec/test/electra/block_processing/test_process_withdrawals.py +++ b/tests/core/pyspec/eth2spec/test/electra/block_processing/test_process_withdrawals.py @@ -442,3 +442,162 @@ def test_pending_withdrawals_at_max_mixed_with_sweep_and_fully_withdrawable(spec withdrawals_exceeding_max = pending_withdrawal_requests[spec.MAX_PENDING_PARTIALS_PER_WITHDRAWALS_SWEEP:] assert state.pending_partial_withdrawals == withdrawals_exceeding_max + + +@with_electra_and_later +@spec_state_test +def test_partially_withdrawable_validator_compounding_max_plus_one(spec, state): + """Test compounding validator with balance just above MAX_EFFECTIVE_BALANCE_ELECTRA""" + validator_index = 0 + set_compounding_withdrawal_credential_with_balance( + spec, state, + validator_index, + balance=spec.MAX_EFFECTIVE_BALANCE_ELECTRA + 1 + ) + assert spec.is_partially_withdrawable_validator( + state.validators[validator_index], + state.balances[validator_index] + ) + + next_slot(spec, state) + execution_payload = build_empty_execution_payload(spec, state) + yield from run_withdrawals_processing( + spec, state, + execution_payload, + fully_withdrawable_indices=[], + partial_withdrawals_indices=[validator_index] + ) + assert state.pending_partial_withdrawals == [] + + +@with_electra_and_later +@spec_state_test +def test_partially_withdrawable_validator_compounding_exact_max(spec, state): + """Test compounding validator with balance exactly equal to MAX_EFFECTIVE_BALANCE_ELECTRA""" + validator_index = 0 + set_compounding_withdrawal_credential_with_balance( + spec, state, + validator_index + ) + assert not spec.is_partially_withdrawable_validator( + state.validators[validator_index], + state.balances[validator_index] + ) + + next_slot(spec, state) + execution_payload = build_empty_execution_payload(spec, state) + yield from run_withdrawals_processing( + spec, state, + execution_payload, + fully_withdrawable_indices=[], + partial_withdrawals_indices=[] + ) + assert state.pending_partial_withdrawals == [] + + +@with_electra_and_later +@spec_state_test +def test_partially_withdrawable_validator_compounding_max_minus_one(spec, state): + """Test compounding validator whose balance is just below MAX_EFFECTIVE_BALANCE_ELECTRA""" + validator_index = 0 + set_compounding_withdrawal_credential_with_balance( + spec, state, + validator_index, + effective_balance=spec.MAX_EFFECTIVE_BALANCE_ELECTRA - spec.EFFECTIVE_BALANCE_INCREMENT, + balance=spec.MAX_EFFECTIVE_BALANCE_ELECTRA - 1 + ) + assert not spec.is_partially_withdrawable_validator( + state.validators[validator_index], + state.balances[validator_index] + ) + + next_slot(spec, state) + execution_payload = build_empty_execution_payload(spec, state) + yield from run_withdrawals_processing( + spec, state, + execution_payload, + fully_withdrawable_indices=[], + partial_withdrawals_indices=[] + ) + assert state.pending_partial_withdrawals == [] + + +@with_electra_and_later +@spec_state_test +def test_partially_withdrawable_validator_compounding_min_plus_one(spec, state): + """Test compounding validator just above MIN_ACTIVATION_BALANCE""" + validator_index = 0 + set_compounding_withdrawal_credential_with_balance( + spec, state, + validator_index, + effective_balance=spec.MIN_ACTIVATION_BALANCE, + balance=spec.MIN_ACTIVATION_BALANCE + 1 + ) + assert not spec.is_partially_withdrawable_validator( + state.validators[validator_index], + state.balances[validator_index] + ) + + next_slot(spec, state) + execution_payload = build_empty_execution_payload(spec, state) + yield from run_withdrawals_processing( + spec, state, + execution_payload, + fully_withdrawable_indices=[], + partial_withdrawals_indices=[] + ) + assert state.pending_partial_withdrawals == [] + + +@with_electra_and_later +@spec_state_test +def test_partially_withdrawable_validator_compounding_exact_min(spec, state): + """Test compounding validator with balance exactly equal to MIN_ACTIVATION_BALANCE""" + validator_index = 0 + set_compounding_withdrawal_credential_with_balance( + spec, state, + validator_index, + effective_balance=spec.MIN_ACTIVATION_BALANCE, + balance=spec.MIN_ACTIVATION_BALANCE + ) + assert not spec.is_partially_withdrawable_validator( + state.validators[validator_index], + state.balances[validator_index] + ) + + next_slot(spec, state) + execution_payload = build_empty_execution_payload(spec, state) + yield from run_withdrawals_processing( + spec, state, + execution_payload, + fully_withdrawable_indices=[], + partial_withdrawals_indices=[] + ) + assert state.pending_partial_withdrawals == [] + + +@with_electra_and_later +@spec_state_test +def test_partially_withdrawable_validator_compounding_min_minus_one(spec, state): + """Test compounding validator below MIN_ACTIVATION_BALANCE""" + validator_index = 0 + set_compounding_withdrawal_credential_with_balance( + spec, state, + validator_index, + effective_balance=spec.MIN_ACTIVATION_BALANCE - spec.EFFECTIVE_BALANCE_INCREMENT, + balance=spec.MIN_ACTIVATION_BALANCE - 1 + ) + assert not spec.is_partially_withdrawable_validator( + state.validators[validator_index], + state.balances[validator_index] + ) + + next_slot(spec, state) + execution_payload = build_empty_execution_payload(spec, state) + yield from run_withdrawals_processing( + spec, state, + execution_payload, + fully_withdrawable_indices=[], + partial_withdrawals_indices=[] + ) + assert state.pending_partial_withdrawals == [] diff --git a/tests/core/pyspec/eth2spec/test/electra/epoch_processing/pending_deposits/test_apply_pending_deposit.py b/tests/core/pyspec/eth2spec/test/electra/epoch_processing/pending_deposits/test_apply_pending_deposit.py index ccbfbc13a3..2a60ec8eab 100644 --- a/tests/core/pyspec/eth2spec/test/electra/epoch_processing/pending_deposits/test_apply_pending_deposit.py +++ b/tests/core/pyspec/eth2spec/test/electra/epoch_processing/pending_deposits/test_apply_pending_deposit.py @@ -16,7 +16,7 @@ def test_apply_pending_deposit_under_min_activation(spec, state): # fresh deposit = next validator index = validator appended to registry validator_index = len(state.validators) - # effective balance will be 1 EFFECTIVE_BALANCE_INCREMENT smaller because of this small decrement. + # effective balance will be 1 EFFECTIVE_BALANCE_INCREMENT smaller because of this small decrement amount = spec.MIN_ACTIVATION_BALANCE - 1 pending_deposit = prepare_pending_deposit(spec, validator_index, amount, signed=True) @@ -47,6 +47,22 @@ def test_apply_pending_deposit_over_min_activation(spec, state): yield from run_pending_deposit_applying(spec, state, pending_deposit, validator_index) +@with_electra_and_later +@spec_state_test +def test_apply_pending_deposit_over_min_activation_next_increment(spec, state): + # fresh deposit = next validator index = validator appended to registry + validator_index = len(state.validators) + # set deposit amount to the next effective balance increment over the limit + # the validator's effective balance should be set to pre-electra MAX_EFFECTIVE_BALANCE + amount = spec.MAX_EFFECTIVE_BALANCE + spec.EFFECTIVE_BALANCE_INCREMENT + pending_deposit = prepare_pending_deposit(spec, validator_index, amount, signed=True) + + yield from run_pending_deposit_applying(spec, state, pending_deposit, validator_index) + + # check validator's effective balance + assert state.validators[validator_index].effective_balance == spec.MAX_EFFECTIVE_BALANCE + + @with_electra_and_later @spec_state_test def test_apply_pending_deposit_eth1_withdrawal_credentials(spec, state): @@ -79,7 +95,7 @@ def test_apply_pending_deposit_compounding_withdrawal_credentials_under_max(spec + b'\x00' * 11 # specified 0s + b'\x59' * 20 # a 20-byte eth1 address ) - # effective balance will be 1 EFFECTIVE_BALANCE_INCREMENT smaller because of this small decrement. + # effective balance will be 1 EFFECTIVE_BALANCE_INCREMENT smaller because of this small decrement amount = spec.MAX_EFFECTIVE_BALANCE_ELECTRA - 1 pending_deposit = prepare_pending_deposit( spec, @@ -102,7 +118,7 @@ def test_apply_pending_deposit_compounding_withdrawal_credentials_max(spec, stat + b'\x00' * 11 # specified 0s + b'\x59' * 20 # a 20-byte eth1 address ) - # effective balance will be exactly the same as balance. + # effective balance will be exactly the same as balance amount = spec.MAX_EFFECTIVE_BALANCE_ELECTRA pending_deposit = prepare_pending_deposit( spec, @@ -138,6 +154,33 @@ def test_apply_pending_deposit_compounding_withdrawal_credentials_over_max(spec, yield from run_pending_deposit_applying(spec, state, pending_deposit, validator_index) +@with_electra_and_later +@spec_state_test +def test_apply_pending_deposit_compounding_withdrawal_credentials_over_max_next_increment(spec, state): + # fresh deposit = next validator index = validator appended to registry + validator_index = len(state.validators) + withdrawal_credentials = ( + spec.COMPOUNDING_WITHDRAWAL_PREFIX + + b'\x00' * 11 # specified 0s + + b'\x59' * 20 # a 20-byte eth1 address + ) + # set deposit amount to the next effective balance increment over the limit + # the validator's effective balance should be set to MAX_EFFECTIVE_BALANCE_ELECTRA + amount = spec.MAX_EFFECTIVE_BALANCE_ELECTRA + spec.EFFECTIVE_BALANCE_INCREMENT + pending_deposit = prepare_pending_deposit( + spec, + validator_index, + amount, + withdrawal_credentials=withdrawal_credentials, + signed=True, + ) + + yield from run_pending_deposit_applying(spec, state, pending_deposit, validator_index) + + # check validator's effective balance + assert state.validators[validator_index].effective_balance == spec.MAX_EFFECTIVE_BALANCE_ELECTRA + + @with_electra_and_later @spec_state_test def test_apply_pending_deposit_non_versioned_withdrawal_credentials(spec, state): @@ -347,7 +390,7 @@ def test_apply_pending_deposit_key_validate_invalid_subgroup(spec, state): validator_index = len(state.validators) amount = spec.MIN_ACTIVATION_BALANCE - # All-zero pubkey would not pass `bls.KeyValidate`, but `apply_pending_deposit` would not throw exception. + # All-zero pubkey would not pass `bls.KeyValidate`, but `apply_pending_deposit` would not throw exception pubkey = b'\x00' * 48 pending_deposit = prepare_pending_deposit(spec, validator_index, amount, pubkey=pubkey, signed=True) @@ -362,8 +405,8 @@ def test_apply_pending_deposit_key_validate_invalid_decompression(spec, state): validator_index = len(state.validators) amount = spec.MIN_ACTIVATION_BALANCE - # `deserialization_fails_infinity_with_true_b_flag` BLS G1 deserialization test case. - # This pubkey would not pass `bls.KeyValidate`, but `apply_pending_deposit` would not throw exception. + # `deserialization_fails_infinity_with_true_b_flag` BLS G1 deserialization test case + # This pubkey would not pass `bls.KeyValidate`, but `apply_pending_deposit` would not throw exception pubkey_hex = 'c01000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000' pubkey = bytes.fromhex(pubkey_hex) @@ -393,9 +436,9 @@ def test_apply_pending_deposit_ineffective_deposit_with_bad_fork_version(spec, s @spec_state_test @always_bls def test_apply_pending_deposit_with_previous_fork_version(spec, state): - # Since deposits are valid across forks, the domain is always set with `GENESIS_FORK_VERSION`. - # It's an ineffective deposit because it fails at BLS sig verification. - # NOTE: it was effective in Altair. + # Since deposits are valid across forks, the domain is always set with `GENESIS_FORK_VERSION` + # It's an ineffective deposit because it fails at BLS sig verification + # NOTE: it was effective in Altair assert state.fork.previous_version != state.fork.current_version validator_index = len(state.validators) diff --git a/tests/core/pyspec/eth2spec/test/electra/epoch_processing/pending_deposits/test_process_pending_deposits.py b/tests/core/pyspec/eth2spec/test/electra/epoch_processing/pending_deposits/test_process_pending_deposits.py index ee9ceccee7..2b2afb2071 100644 --- a/tests/core/pyspec/eth2spec/test/electra/epoch_processing/pending_deposits/test_process_pending_deposits.py +++ b/tests/core/pyspec/eth2spec/test/electra/epoch_processing/pending_deposits/test_process_pending_deposits.py @@ -8,6 +8,7 @@ with_custom_state, scaled_churn_balances_exceed_activation_exit_churn_limit, default_activation_threshold, + always_bls, ) from eth2spec.test.helpers.deposits import prepare_pending_deposit from eth2spec.test.helpers.state import ( @@ -333,6 +334,35 @@ def test_process_pending_deposits_multiple_pending_deposits_above_churn(spec, st ] +@with_electra_and_later +@spec_state_test +@always_bls +def test_process_pending_deposits_multiple_for_new_validator(spec, state): + """ + - There are three pending deposits in the state, all pointing to the same public key. + - The public key does not exist in the beacon state. + - The first pending deposit has an invalid signature and should be ignored. + - The second pending deposit has a valid signature and the validator should be created. + - The third pending deposit has a valid signature and should be applied. + """ + # A new validator, pubkey doesn't exist in the state + validator_index = len(state.validators) + amount = spec.EFFECTIVE_BALANCE_INCREMENT + + # Add pending deposits to the state + # Provide different amounts so we can tell which were applied + state.pending_deposits.append(prepare_pending_deposit(spec, validator_index, amount * 1, signed=False)) + state.pending_deposits.append(prepare_pending_deposit(spec, validator_index, amount * 2, signed=True)) + state.pending_deposits.append(prepare_pending_deposit(spec, validator_index, amount * 4, signed=True)) + + yield from run_process_pending_deposits(spec, state) + + # The second and third deposits were applied + assert state.balances[validator_index] == amount * 6 + # No more pending deposits + assert state.pending_deposits == [] + + @with_electra_and_later @spec_state_test def test_process_pending_deposits_skipped_deposit_exiting_validator(spec, state): @@ -410,12 +440,12 @@ def test_process_pending_deposits_multiple_pending_one_skipped(spec, state): @with_electra_and_later @spec_state_test def test_process_pending_deposits_mixture_of_skipped_and_above_churn(spec, state): - amount01 = spec.EFFECTIVE_BALANCE_INCREMENT + amount1 = spec.EFFECTIVE_BALANCE_INCREMENT amount2 = spec.MAX_EFFECTIVE_BALANCE_ELECTRA # First two validators have small deposit, third validators a large one for i in [0, 1]: state.pending_deposits.append( - prepare_pending_deposit(spec, validator_index=i, amount=amount01) + prepare_pending_deposit(spec, validator_index=i, amount=amount1) ) state.pending_deposits.append( prepare_pending_deposit(spec, validator_index=2, amount=amount2) @@ -427,18 +457,18 @@ def test_process_pending_deposits_mixture_of_skipped_and_above_churn(spec, state yield from run_process_pending_deposits(spec, state) # First deposit is processed - assert state.balances[0] == pre_balances[0] + amount01 + assert state.balances[0] == pre_balances[0] + amount1 # Second deposit is postponed, third is above churn for i in [1, 2]: assert state.balances[i] == pre_balances[i] # First deposit consumes some deposit balance # Deposit is not processed - wanted_balance = spec.get_activation_exit_churn_limit(state) - amount01 + wanted_balance = spec.get_activation_exit_churn_limit(state) - amount1 assert state.deposit_balance_to_consume == wanted_balance # second and third deposit still in the queue assert state.pending_deposits == [ prepare_pending_deposit(spec, validator_index=2, amount=amount2), - prepare_pending_deposit(spec, validator_index=1, amount=amount01) + prepare_pending_deposit(spec, validator_index=1, amount=amount1) ] diff --git a/tests/core/pyspec/eth2spec/test/electra/fork_choice/__init__.py b/tests/core/pyspec/eth2spec/test/electra/fork_choice/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/core/pyspec/eth2spec/test/electra/fork_choice/test_deposit_with_reorg.py b/tests/core/pyspec/eth2spec/test/electra/fork_choice/test_deposit_with_reorg.py new file mode 100644 index 0000000000..74c418ad60 --- /dev/null +++ b/tests/core/pyspec/eth2spec/test/electra/fork_choice/test_deposit_with_reorg.py @@ -0,0 +1,94 @@ +from eth2spec.test.helpers.block import ( + build_empty_block_for_next_slot, +) +from eth2spec.test.context import ( + with_presets, + spec_state_test, + with_electra_and_later, +) +from eth2spec.test.helpers.execution_payload import ( + compute_el_block_hash_for_block, +) +from eth2spec.test.helpers.state import ( + state_transition_and_sign_block, + next_slot, +) +from eth2spec.test.helpers.deposits import ( + prepare_deposit_request, +) +from eth2spec.test.helpers.fork_choice import ( + get_genesis_forkchoice_store_and_block, + tick_and_add_block, + apply_next_slots_with_attestations, +) +from eth2spec.test.helpers.constants import ( + MINIMAL, +) + + +@with_electra_and_later +@spec_state_test +@with_presets([MINIMAL], reason="too slow") +def test_new_validator_deposit_with_multiple_epoch_transitions(spec, state): + # signify the eth1 bridge deprecation + state.deposit_requests_start_index = state.eth1_deposit_index + + # yield anchor state and block + store, anchor_block = get_genesis_forkchoice_store_and_block(spec, state) + yield 'anchor_state', state + yield 'anchor_block', anchor_block + + test_steps = [] + + # (1) create deposit request for a new validator + deposit_request = prepare_deposit_request( + spec, len(state.validators), spec.MIN_ACTIVATION_BALANCE, signed=True) + deposit_block = build_empty_block_for_next_slot(spec, state) + deposit_block.body.execution_requests.deposits = [deposit_request] + deposit_block.body.execution_payload.block_hash = compute_el_block_hash_for_block(spec, deposit_block) + signed_deposit_block = state_transition_and_sign_block(spec, state, deposit_block) + + pending_deposit = spec.PendingDeposit( + pubkey=deposit_request.pubkey, + withdrawal_credentials=deposit_request.withdrawal_credentials, + amount=deposit_request.amount, + signature=deposit_request.signature, + slot=deposit_block.slot + ) + + assert state.pending_deposits == [pending_deposit] + + yield from tick_and_add_block(spec, store, signed_deposit_block, test_steps) + + # (2) finalize and process pending deposit on one fork + slots = 4 * spec.SLOTS_PER_EPOCH - state.slot + post_state, _, latest_block = yield from apply_next_slots_with_attestations( + spec, state, store, slots, True, True, test_steps) + + # check new validator has been created + assert post_state.pending_deposits == [] + new_validator = post_state.validators[len(post_state.validators) - 1] + assert new_validator.pubkey == pending_deposit.pubkey + assert new_validator.withdrawal_credentials == pending_deposit.withdrawal_credentials + + # (3) create a conflicting block that triggers deposit processing on another fork + prev_epoch_ancestor = store.blocks[latest_block.message.parent_root] + # important to skip last block of the epoch to make client do the epoch processing + # otherwise, client can read the post-epoch from cache + prev_epoch_ancestor = store.blocks[prev_epoch_ancestor.parent_root] + another_fork_state = store.block_states[prev_epoch_ancestor.hash_tree_root()].copy() + + assert another_fork_state.pending_deposits == [pending_deposit] + + # skip a slot to create and process a fork block + next_slot(spec, another_fork_state) + post_state, _, _ = yield from apply_next_slots_with_attestations( + spec, another_fork_state, store, 1, True, True, test_steps) + + # check new validator has been created on another fork + assert post_state.pending_deposits == [] + new_validator = post_state.validators[len(post_state.validators) - 1] + assert new_validator.pubkey == pending_deposit.pubkey + assert new_validator.withdrawal_credentials == pending_deposit.withdrawal_credentials + + yield 'steps', test_steps diff --git a/tests/core/pyspec/eth2spec/test/electra/sanity/blocks/test_blocks.py b/tests/core/pyspec/eth2spec/test/electra/sanity/blocks/test_blocks.py index e5b07cd3dd..bad569b4b7 100644 --- a/tests/core/pyspec/eth2spec/test/electra/sanity/blocks/test_blocks.py +++ b/tests/core/pyspec/eth2spec/test/electra/sanity/blocks/test_blocks.py @@ -1,5 +1,5 @@ from eth2spec.test.helpers.block import ( - build_empty_block_for_next_slot + build_empty_block_for_next_slot, ) from eth2spec.test.context import ( spec_state_test, @@ -21,6 +21,9 @@ set_eth1_withdrawal_credential_with_balance, set_compounding_withdrawal_credential_with_balance, ) +from eth2spec.test.helpers.deposits import ( + prepare_deposit_request, +) @with_electra_and_later @@ -251,3 +254,119 @@ def test_multiple_el_partial_withdrawal_requests_different_validator(spec, state assert len(state.pending_partial_withdrawals) == 2 for validator_index in validator_indices: assert state.validators[validator_index].exit_epoch == spec.FAR_FUTURE_EPOCH + + +@with_electra_and_later +@spec_state_test +def test_withdrawal_and_withdrawal_request_same_validator(spec, state): + # Give a validator an excess balance + validator_index = 0 + excess_balance = 200000 + balance = spec.MAX_EFFECTIVE_BALANCE + excess_balance + address = b'\x22' * 20 + set_eth1_withdrawal_credential_with_balance(spec, state, validator_index, balance, address) + + # Ensure the validator has an upcoming withdrawal + # This will happen before the withdrawal request + expected_withdrawals, _ = spec.get_expected_withdrawals(state) + assert len(expected_withdrawals) == 1 + assert expected_withdrawals[0].validator_index == validator_index + + yield 'pre', state + + # Create a 1 gwei withdrawal request for the same validator + withdrawal_request = spec.WithdrawalRequest( + source_address=address, + validator_pubkey=state.validators[validator_index].pubkey, + amount=1, + ) + + block = build_empty_block_for_next_slot(spec, state) + block.body.execution_requests.withdrawals = [withdrawal_request] + block.body.execution_payload.block_hash = compute_el_block_hash_for_block(spec, block) + signed_block = state_transition_and_sign_block(spec, state, block) + + yield 'blocks', [signed_block] + yield 'post', state + + # Ensure the withdrawal request was unsuccessful + assert len(state.pending_partial_withdrawals) == 0 + + +@with_electra_and_later +@spec_state_test +def test_withdrawal_and_switch_to_compounding_request_same_validator(spec, state): + # Give a validator an excess balance + validator_index = 0 + excess_balance = 200000 + balance = spec.MAX_EFFECTIVE_BALANCE + excess_balance + address = b'\x22' * 20 + set_eth1_withdrawal_credential_with_balance(spec, state, validator_index, balance, address) + + # Ensure the validator has an upcoming withdrawal + # This will happen before the withdrawal request + expected_withdrawals, _ = spec.get_expected_withdrawals(state) + assert len(expected_withdrawals) == 1 + assert expected_withdrawals[0].validator_index == validator_index + + yield 'pre', state + + # Create a switch to compounding validator request for the same validator + consolidation_request = spec.ConsolidationRequest( + source_address=address, + source_pubkey=state.validators[validator_index].pubkey, + target_pubkey=state.validators[validator_index].pubkey, + ) + + block = build_empty_block_for_next_slot(spec, state) + block.body.execution_requests.consolidations = [consolidation_request] + block.body.execution_payload.block_hash = compute_el_block_hash_for_block(spec, block) + signed_block = state_transition_and_sign_block(spec, state, block) + + yield 'blocks', [signed_block] + yield 'post', state + + # Ensure the validator has compounding credentials now + assert spec.is_compounding_withdrawal_credential(state.validators[validator_index].withdrawal_credentials) + # Ensure there was no excess balance pending deposit + assert len(state.pending_deposits) == 0 + + +@with_electra_and_later +@spec_state_test +def test_deposit_request_with_same_pubkey_different_withdrawal_credentials(spec, state): + # signify the eth1 bridge deprecation + state.deposit_requests_start_index = state.eth1_deposit_index + + # prepare three deposit requests, where + # 1st and 3rd have the same pubkey but different withdrawal credentials + deposit_request_0 = prepare_deposit_request( + spec, len(state.validators), spec.MIN_ACTIVATION_BALANCE, state.eth1_deposit_index, signed=True) + deposit_request_1 = prepare_deposit_request( + spec, len(state.validators) + 1, spec.MIN_ACTIVATION_BALANCE, state.eth1_deposit_index + 1, signed=True) + deposit_request_2 = prepare_deposit_request( + spec, len(state.validators), spec.MIN_ACTIVATION_BALANCE, state.eth1_deposit_index + 2, signed=True, + withdrawal_credentials=(spec.ETH1_ADDRESS_WITHDRAWAL_PREFIX + b'\x00' * 11 + b'\x11' * 20) + ) + + # build a block with deposit requests + block = build_empty_block_for_next_slot(spec, state) + block.body.execution_requests.deposits = [deposit_request_0, deposit_request_1, deposit_request_2] + block.body.execution_payload.block_hash = compute_el_block_hash_for_block(spec, block) + + yield 'pre', state + + signed_block = state_transition_and_sign_block(spec, state, block) + + yield 'blocks', [signed_block] + yield 'post', state + + # check deposit requests are processed correctly + for i, deposit_request in enumerate(block.body.execution_requests.deposits): + assert state.pending_deposits[i] == spec.PendingDeposit( + pubkey=deposit_request.pubkey, + withdrawal_credentials=deposit_request.withdrawal_credentials, + amount=deposit_request.amount, + signature=deposit_request.signature, + slot=signed_block.message.slot, + ) diff --git a/tests/core/pyspec/eth2spec/test/electra/sanity/blocks/test_deposit_transition.py b/tests/core/pyspec/eth2spec/test/electra/sanity/blocks/test_deposit_transition.py index f2d9a6f11a..b021eebe05 100644 --- a/tests/core/pyspec/eth2spec/test/electra/sanity/blocks/test_deposit_transition.py +++ b/tests/core/pyspec/eth2spec/test/electra/sanity/blocks/test_deposit_transition.py @@ -93,8 +93,8 @@ def prepare_state_and_block(spec, deposit_data = build_deposit_data(spec, pubkeys[keypair_index], privkeys[keypair_index], - # use max effective balance - spec.MAX_EFFECTIVE_BALANCE, + # use min activation balance + spec.MIN_ACTIVATION_BALANCE, # insecurely use pubkey as withdrawal key spec.BLS_WITHDRAWAL_PREFIX + spec.hash(pubkeys[keypair_index])[1:], signed=True) @@ -118,8 +118,8 @@ def prepare_state_and_block(spec, for offset in range(deposit_request_cnt): deposit_request = prepare_deposit_request(spec, keypair_index, - # use max effective balance - spec.MAX_EFFECTIVE_BALANCE, + # use min activation balance + spec.MIN_ACTIVATION_BALANCE, first_deposit_request_index + offset, signed=True) deposit_requests.append(deposit_request) diff --git a/tests/core/pyspec/eth2spec/test/electra/unittests/test_execution_engine_interface.py b/tests/core/pyspec/eth2spec/test/electra/unittests/test_execution_engine_interface.py new file mode 100644 index 0000000000..86c1e5f580 --- /dev/null +++ b/tests/core/pyspec/eth2spec/test/electra/unittests/test_execution_engine_interface.py @@ -0,0 +1,46 @@ +from eth2spec.test.context import ( + ELECTRA, + spec_state_test, + with_phases, +) +from eth2spec.test.helpers.execution_payload import ( + build_empty_execution_payload, +) +from eth2spec.test.helpers.state import next_slot + + +@with_phases([ELECTRA]) +@spec_state_test +def test_noop_execution_engine_notify_new_payload_electra(spec, state): + """ + Test NoopExecutionEngine.notify_new_payload returns True and doesn't modify state + """ + engine = spec.NoopExecutionEngine() + + next_slot(spec, state) + payload = build_empty_execution_payload(spec, state) + result = engine.notify_new_payload( + execution_payload=payload, + parent_beacon_block_root=state.latest_block_header.parent_root, + execution_requests_list=[] + ) + assert result is True + + +@with_phases([ELECTRA]) +@spec_state_test +def test_noop_execution_engine_is_valid_block_hash_electra(spec, state): + """ + Test NoopExecutionEngine.is_valid_block_hash returns True and doesn't modify state + """ + engine = spec.NoopExecutionEngine() + + next_slot(spec, state) + payload = build_empty_execution_payload(spec, state) + result = engine.is_valid_block_hash( + execution_payload=payload, + parent_beacon_block_root=state.latest_block_header.parent_root, + execution_requests_list=[] + ) + + assert result is True diff --git a/tests/core/pyspec/eth2spec/test/fulu/fork/__init__.py b/tests/core/pyspec/eth2spec/test/fulu/fork/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/core/pyspec/eth2spec/test/fulu/fork/test_fulu_fork_basic.py b/tests/core/pyspec/eth2spec/test/fulu/fork/test_fulu_fork_basic.py new file mode 100644 index 0000000000..196202a0af --- /dev/null +++ b/tests/core/pyspec/eth2spec/test/fulu/fork/test_fulu_fork_basic.py @@ -0,0 +1,82 @@ +from eth2spec.test.context import ( + with_phases, + with_custom_state, + with_presets, + spec_test, with_state, + low_balances, misc_balances, large_validator_set, +) +from eth2spec.test.utils import with_meta_tags +from eth2spec.test.helpers.constants import ( + ELECTRA, FULU, + MINIMAL, +) +from eth2spec.test.helpers.state import ( + next_epoch, + next_epoch_via_block, +) +from eth2spec.test.helpers.fulu.fork import ( + FULU_FORK_TEST_META_TAGS, + run_fork_test, +) + + +@with_phases(phases=[ELECTRA], other_phases=[FULU]) +@spec_test +@with_state +@with_meta_tags(FULU_FORK_TEST_META_TAGS) +def test_fork_base_state(spec, phases, state): + yield from run_fork_test(phases[FULU], state) + + +@with_phases(phases=[ELECTRA], other_phases=[FULU]) +@spec_test +@with_state +@with_meta_tags(FULU_FORK_TEST_META_TAGS) +def test_fork_next_epoch(spec, phases, state): + next_epoch(spec, state) + yield from run_fork_test(phases[FULU], state) + + +@with_phases(phases=[ELECTRA], other_phases=[FULU]) +@spec_test +@with_state +@with_meta_tags(FULU_FORK_TEST_META_TAGS) +def test_fork_next_epoch_with_block(spec, phases, state): + next_epoch_via_block(spec, state) + yield from run_fork_test(phases[FULU], state) + + +@with_phases(phases=[ELECTRA], other_phases=[FULU]) +@spec_test +@with_state +@with_meta_tags(FULU_FORK_TEST_META_TAGS) +def test_fork_many_next_epoch(spec, phases, state): + for _ in range(3): + next_epoch(spec, state) + yield from run_fork_test(phases[FULU], state) + + +@with_phases(phases=[ELECTRA], other_phases=[FULU]) +@with_custom_state(balances_fn=low_balances, threshold_fn=lambda spec: spec.config.EJECTION_BALANCE) +@spec_test +@with_meta_tags(FULU_FORK_TEST_META_TAGS) +def test_fork_random_low_balances(spec, phases, state): + yield from run_fork_test(phases[FULU], state) + + +@with_phases(phases=[ELECTRA], other_phases=[FULU]) +@with_custom_state(balances_fn=misc_balances, threshold_fn=lambda spec: spec.config.EJECTION_BALANCE) +@spec_test +@with_meta_tags(FULU_FORK_TEST_META_TAGS) +def test_fork_random_misc_balances(spec, phases, state): + yield from run_fork_test(phases[FULU], state) + + +@with_phases(phases=[ELECTRA], other_phases=[FULU]) +@with_presets([MINIMAL], + reason="mainnet config leads to larger validator set than limit of public/private keys pre-generated") +@with_custom_state(balances_fn=large_validator_set, threshold_fn=lambda spec: spec.config.EJECTION_BALANCE) +@spec_test +@with_meta_tags(FULU_FORK_TEST_META_TAGS) +def test_fork_random_large_validator_set(spec, phases, state): + yield from run_fork_test(phases[FULU], state) diff --git a/tests/core/pyspec/eth2spec/test/fulu/fork/test_fulu_fork_random.py b/tests/core/pyspec/eth2spec/test/fulu/fork/test_fulu_fork_random.py new file mode 100644 index 0000000000..5e289a23a5 --- /dev/null +++ b/tests/core/pyspec/eth2spec/test/fulu/fork/test_fulu_fork_random.py @@ -0,0 +1,84 @@ +from random import Random + +from eth2spec.test.context import ( + with_phases, + with_custom_state, + with_presets, + spec_test, with_state, + low_balances, misc_balances, large_validator_set, +) +from eth2spec.test.utils import with_meta_tags +from eth2spec.test.helpers.constants import ( + ELECTRA, FULU, + MINIMAL, +) +from eth2spec.test.helpers.fulu.fork import ( + FULU_FORK_TEST_META_TAGS, + run_fork_test, +) +from eth2spec.test.helpers.random import randomize_state + + +@with_phases(phases=[ELECTRA], other_phases=[FULU]) +@spec_test +@with_state +@with_meta_tags(FULU_FORK_TEST_META_TAGS) +def test_fulu_fork_random_0(spec, phases, state): + randomize_state(spec, state, rng=Random(1010)) + yield from run_fork_test(phases[FULU], state) + + +@with_phases(phases=[ELECTRA], other_phases=[FULU]) +@spec_test +@with_state +@with_meta_tags(FULU_FORK_TEST_META_TAGS) +def test_fulu_fork_random_1(spec, phases, state): + randomize_state(spec, state, rng=Random(2020)) + yield from run_fork_test(phases[FULU], state) + + +@with_phases(phases=[ELECTRA], other_phases=[FULU]) +@spec_test +@with_state +@with_meta_tags(FULU_FORK_TEST_META_TAGS) +def test_fulu_fork_random_2(spec, phases, state): + randomize_state(spec, state, rng=Random(3030)) + yield from run_fork_test(phases[FULU], state) + + +@with_phases(phases=[ELECTRA], other_phases=[FULU]) +@spec_test +@with_state +@with_meta_tags(FULU_FORK_TEST_META_TAGS) +def test_fulu_fork_random_3(spec, phases, state): + randomize_state(spec, state, rng=Random(4040)) + yield from run_fork_test(phases[FULU], state) + + +@with_phases(phases=[ELECTRA], other_phases=[FULU]) +@spec_test +@with_custom_state(balances_fn=low_balances, threshold_fn=lambda spec: spec.config.EJECTION_BALANCE) +@with_meta_tags(FULU_FORK_TEST_META_TAGS) +def test_fulu_fork_random_low_balances(spec, phases, state): + randomize_state(spec, state, rng=Random(5050)) + yield from run_fork_test(phases[FULU], state) + + +@with_phases(phases=[ELECTRA], other_phases=[FULU]) +@spec_test +@with_custom_state(balances_fn=misc_balances, threshold_fn=lambda spec: spec.config.EJECTION_BALANCE) +@with_meta_tags(FULU_FORK_TEST_META_TAGS) +def test_fulu_fork_random_misc_balances(spec, phases, state): + randomize_state(spec, state, rng=Random(6060)) + yield from run_fork_test(phases[FULU], state) + + +@with_phases(phases=[ELECTRA], other_phases=[FULU]) +@with_presets([MINIMAL], + reason="mainnet config leads to larger validator set than limit of public/private keys pre-generated") +@spec_test +@with_custom_state(balances_fn=large_validator_set, threshold_fn=lambda spec: spec.config.EJECTION_BALANCE) +@with_meta_tags(FULU_FORK_TEST_META_TAGS) +def test_fulu_fork_random_large_validator_set(spec, phases, state): + randomize_state(spec, state, rng=Random(7070)) + yield from run_fork_test(phases[FULU], state) diff --git a/tests/core/pyspec/eth2spec/test/fulu/fork_choice/__init__.py b/tests/core/pyspec/eth2spec/test/fulu/fork_choice/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/core/pyspec/eth2spec/test/fulu/fork_choice/test_on_block.py b/tests/core/pyspec/eth2spec/test/fulu/fork_choice/test_on_block.py new file mode 100644 index 0000000000..fdead7e728 --- /dev/null +++ b/tests/core/pyspec/eth2spec/test/fulu/fork_choice/test_on_block.py @@ -0,0 +1 @@ +# TODO: add new data availability tests. diff --git a/tests/core/pyspec/eth2spec/test/fulu/random/__init__.py b/tests/core/pyspec/eth2spec/test/fulu/random/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/core/pyspec/eth2spec/test/fulu/random/test_random.py b/tests/core/pyspec/eth2spec/test/fulu/random/test_random.py new file mode 100644 index 0000000000..0f245b57eb --- /dev/null +++ b/tests/core/pyspec/eth2spec/test/fulu/random/test_random.py @@ -0,0 +1,438 @@ +""" +This module is generated from the ``random`` test generator. +Please do not edit this file manually. +See the README for that generator for more information. +""" + +from eth2spec.test.helpers.constants import FULU +from eth2spec.test.context import ( + misc_balances_in_default_range_with_many_validators, + with_phases, + zero_activation_threshold, + only_generator, +) +from eth2spec.test.context import ( + always_bls, + spec_test, + with_custom_state, + single_phase, +) +from eth2spec.test.utils.randomized_block_tests import ( + run_generated_randomized_test, +) + + +@only_generator("randomized test for broad coverage, not point-to-point CI") +@with_phases([FULU]) +@with_custom_state( + balances_fn=misc_balances_in_default_range_with_many_validators, + threshold_fn=zero_activation_threshold +) +@spec_test +@single_phase +@always_bls +def test_randomized_0(spec, state): + # scenario as high-level, informal text: + # epochs:0,slots:0,with-block:no_block + # epochs:0,slots:0,with-block:no_block + # epochs:0,slots:0,with-block:no_block + # epochs:0,slots:0,with-block:random_block_fulu + # epochs:1,slots:0,with-block:no_block + # epochs:0,slots:random_slot_in_epoch,with-block:no_block + # epochs:0,slots:0,with-block:random_block_fulu + scenario = {'transitions': [{'validation': 'validate_is_not_leaking', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'block_producer': 'no_block'}, {'epochs_to_skip': 0, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 0, 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block_fulu', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}, {'epochs_to_skip': 1, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 'random_slot_in_epoch', 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block_fulu', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}], 'state_randomizer': 'randomize_state_fulu'} # noqa: E501 + yield from run_generated_randomized_test( + spec, + state, + scenario, + ) + + +@only_generator("randomized test for broad coverage, not point-to-point CI") +@with_phases([FULU]) +@with_custom_state( + balances_fn=misc_balances_in_default_range_with_many_validators, + threshold_fn=zero_activation_threshold +) +@spec_test +@single_phase +@always_bls +def test_randomized_1(spec, state): + # scenario as high-level, informal text: + # epochs:0,slots:0,with-block:no_block + # epochs:1,slots:0,with-block:no_block + # epochs:0,slots:random_slot_in_epoch,with-block:no_block + # epochs:0,slots:0,with-block:random_block_fulu + # epochs:0,slots:0,with-block:no_block + # epochs:0,slots:0,with-block:no_block + # epochs:0,slots:0,with-block:random_block_fulu + scenario = {'transitions': [{'validation': 'validate_is_not_leaking', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'block_producer': 'no_block'}, {'epochs_to_skip': 1, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 'random_slot_in_epoch', 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block_fulu', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}, {'epochs_to_skip': 0, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 0, 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block_fulu', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}], 'state_randomizer': 'randomize_state_fulu'} # noqa: E501 + yield from run_generated_randomized_test( + spec, + state, + scenario, + ) + + +@only_generator("randomized test for broad coverage, not point-to-point CI") +@with_phases([FULU]) +@with_custom_state( + balances_fn=misc_balances_in_default_range_with_many_validators, + threshold_fn=zero_activation_threshold +) +@spec_test +@single_phase +@always_bls +def test_randomized_2(spec, state): + # scenario as high-level, informal text: + # epochs:0,slots:0,with-block:no_block + # epochs:0,slots:0,with-block:no_block + # epochs:0,slots:penultimate_slot_in_epoch,with-block:no_block + # epochs:0,slots:0,with-block:random_block_fulu + # epochs:0,slots:0,with-block:no_block + # epochs:0,slots:last_slot_in_epoch,with-block:no_block + # epochs:0,slots:0,with-block:random_block_fulu + scenario = {'transitions': [{'validation': 'validate_is_not_leaking', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'block_producer': 'no_block'}, {'epochs_to_skip': 0, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 'penultimate_slot_in_epoch', 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block_fulu', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}, {'epochs_to_skip': 0, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 'last_slot_in_epoch', 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block_fulu', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}], 'state_randomizer': 'randomize_state_fulu'} # noqa: E501 + yield from run_generated_randomized_test( + spec, + state, + scenario, + ) + + +@only_generator("randomized test for broad coverage, not point-to-point CI") +@with_phases([FULU]) +@with_custom_state( + balances_fn=misc_balances_in_default_range_with_many_validators, + threshold_fn=zero_activation_threshold +) +@spec_test +@single_phase +@always_bls +def test_randomized_3(spec, state): + # scenario as high-level, informal text: + # epochs:0,slots:0,with-block:no_block + # epochs:0,slots:0,with-block:no_block + # epochs:0,slots:last_slot_in_epoch,with-block:no_block + # epochs:0,slots:0,with-block:random_block_fulu + # epochs:1,slots:0,with-block:no_block + # epochs:0,slots:last_slot_in_epoch,with-block:no_block + # epochs:0,slots:0,with-block:random_block_fulu + scenario = {'transitions': [{'validation': 'validate_is_not_leaking', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'block_producer': 'no_block'}, {'epochs_to_skip': 0, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 'last_slot_in_epoch', 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block_fulu', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}, {'epochs_to_skip': 1, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 'last_slot_in_epoch', 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block_fulu', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}], 'state_randomizer': 'randomize_state_fulu'} # noqa: E501 + yield from run_generated_randomized_test( + spec, + state, + scenario, + ) + + +@only_generator("randomized test for broad coverage, not point-to-point CI") +@with_phases([FULU]) +@with_custom_state( + balances_fn=misc_balances_in_default_range_with_many_validators, + threshold_fn=zero_activation_threshold +) +@spec_test +@single_phase +@always_bls +def test_randomized_4(spec, state): + # scenario as high-level, informal text: + # epochs:0,slots:0,with-block:no_block + # epochs:1,slots:0,with-block:no_block + # epochs:0,slots:last_slot_in_epoch,with-block:no_block + # epochs:0,slots:0,with-block:random_block_fulu + # epochs:1,slots:0,with-block:no_block + # epochs:0,slots:penultimate_slot_in_epoch,with-block:no_block + # epochs:0,slots:0,with-block:random_block_fulu + scenario = {'transitions': [{'validation': 'validate_is_not_leaking', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'block_producer': 'no_block'}, {'epochs_to_skip': 1, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 'last_slot_in_epoch', 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block_fulu', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}, {'epochs_to_skip': 1, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 'penultimate_slot_in_epoch', 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block_fulu', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}], 'state_randomizer': 'randomize_state_fulu'} # noqa: E501 + yield from run_generated_randomized_test( + spec, + state, + scenario, + ) + + +@only_generator("randomized test for broad coverage, not point-to-point CI") +@with_phases([FULU]) +@with_custom_state( + balances_fn=misc_balances_in_default_range_with_many_validators, + threshold_fn=zero_activation_threshold +) +@spec_test +@single_phase +@always_bls +def test_randomized_5(spec, state): + # scenario as high-level, informal text: + # epochs:0,slots:0,with-block:no_block + # epochs:0,slots:0,with-block:no_block + # epochs:0,slots:random_slot_in_epoch,with-block:no_block + # epochs:0,slots:0,with-block:random_block_fulu + # epochs:0,slots:0,with-block:no_block + # epochs:0,slots:random_slot_in_epoch,with-block:no_block + # epochs:0,slots:0,with-block:random_block_fulu + scenario = {'transitions': [{'validation': 'validate_is_not_leaking', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'block_producer': 'no_block'}, {'epochs_to_skip': 0, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 'random_slot_in_epoch', 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block_fulu', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}, {'epochs_to_skip': 0, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 'random_slot_in_epoch', 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block_fulu', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}], 'state_randomizer': 'randomize_state_fulu'} # noqa: E501 + yield from run_generated_randomized_test( + spec, + state, + scenario, + ) + + +@only_generator("randomized test for broad coverage, not point-to-point CI") +@with_phases([FULU]) +@with_custom_state( + balances_fn=misc_balances_in_default_range_with_many_validators, + threshold_fn=zero_activation_threshold +) +@spec_test +@single_phase +@always_bls +def test_randomized_6(spec, state): + # scenario as high-level, informal text: + # epochs:0,slots:0,with-block:no_block + # epochs:1,slots:0,with-block:no_block + # epochs:0,slots:penultimate_slot_in_epoch,with-block:no_block + # epochs:0,slots:0,with-block:random_block_fulu + # epochs:0,slots:0,with-block:no_block + # epochs:0,slots:penultimate_slot_in_epoch,with-block:no_block + # epochs:0,slots:0,with-block:random_block_fulu + scenario = {'transitions': [{'validation': 'validate_is_not_leaking', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'block_producer': 'no_block'}, {'epochs_to_skip': 1, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 'penultimate_slot_in_epoch', 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block_fulu', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}, {'epochs_to_skip': 0, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 'penultimate_slot_in_epoch', 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block_fulu', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}], 'state_randomizer': 'randomize_state_fulu'} # noqa: E501 + yield from run_generated_randomized_test( + spec, + state, + scenario, + ) + + +@only_generator("randomized test for broad coverage, not point-to-point CI") +@with_phases([FULU]) +@with_custom_state( + balances_fn=misc_balances_in_default_range_with_many_validators, + threshold_fn=zero_activation_threshold +) +@spec_test +@single_phase +@always_bls +def test_randomized_7(spec, state): + # scenario as high-level, informal text: + # epochs:0,slots:0,with-block:no_block + # epochs:1,slots:0,with-block:no_block + # epochs:0,slots:0,with-block:no_block + # epochs:0,slots:0,with-block:random_block_fulu + # epochs:1,slots:0,with-block:no_block + # epochs:0,slots:0,with-block:no_block + # epochs:0,slots:0,with-block:random_block_fulu + scenario = {'transitions': [{'validation': 'validate_is_not_leaking', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'block_producer': 'no_block'}, {'epochs_to_skip': 1, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 0, 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block_fulu', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}, {'epochs_to_skip': 1, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 0, 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block_fulu', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}], 'state_randomizer': 'randomize_state_fulu'} # noqa: E501 + yield from run_generated_randomized_test( + spec, + state, + scenario, + ) + + +@only_generator("randomized test for broad coverage, not point-to-point CI") +@with_phases([FULU]) +@with_custom_state( + balances_fn=misc_balances_in_default_range_with_many_validators, + threshold_fn=zero_activation_threshold +) +@spec_test +@single_phase +@always_bls +def test_randomized_8(spec, state): + # scenario as high-level, informal text: + # epochs:epochs_until_leak,slots:0,with-block:no_block + # epochs:0,slots:0,with-block:no_block + # epochs:0,slots:0,with-block:no_block + # epochs:0,slots:0,with-block:random_block_fulu + # epochs:1,slots:0,with-block:no_block + # epochs:0,slots:random_slot_in_epoch,with-block:no_block + # epochs:0,slots:0,with-block:random_block_fulu + scenario = {'transitions': [{'epochs_to_skip': 'epochs_until_leak', 'validation': 'validate_is_leaking', 'slots_to_skip': 0, 'block_producer': 'no_block'}, {'epochs_to_skip': 0, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 0, 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block_fulu', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}, {'epochs_to_skip': 1, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 'random_slot_in_epoch', 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block_fulu', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}], 'state_randomizer': 'randomize_state_fulu'} # noqa: E501 + yield from run_generated_randomized_test( + spec, + state, + scenario, + ) + + +@only_generator("randomized test for broad coverage, not point-to-point CI") +@with_phases([FULU]) +@with_custom_state( + balances_fn=misc_balances_in_default_range_with_many_validators, + threshold_fn=zero_activation_threshold +) +@spec_test +@single_phase +@always_bls +def test_randomized_9(spec, state): + # scenario as high-level, informal text: + # epochs:epochs_until_leak,slots:0,with-block:no_block + # epochs:1,slots:0,with-block:no_block + # epochs:0,slots:random_slot_in_epoch,with-block:no_block + # epochs:0,slots:0,with-block:random_block_fulu + # epochs:0,slots:0,with-block:no_block + # epochs:0,slots:0,with-block:no_block + # epochs:0,slots:0,with-block:random_block_fulu + scenario = {'transitions': [{'epochs_to_skip': 'epochs_until_leak', 'validation': 'validate_is_leaking', 'slots_to_skip': 0, 'block_producer': 'no_block'}, {'epochs_to_skip': 1, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 'random_slot_in_epoch', 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block_fulu', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}, {'epochs_to_skip': 0, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 0, 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block_fulu', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}], 'state_randomizer': 'randomize_state_fulu'} # noqa: E501 + yield from run_generated_randomized_test( + spec, + state, + scenario, + ) + + +@only_generator("randomized test for broad coverage, not point-to-point CI") +@with_phases([FULU]) +@with_custom_state( + balances_fn=misc_balances_in_default_range_with_many_validators, + threshold_fn=zero_activation_threshold +) +@spec_test +@single_phase +@always_bls +def test_randomized_10(spec, state): + # scenario as high-level, informal text: + # epochs:epochs_until_leak,slots:0,with-block:no_block + # epochs:0,slots:0,with-block:no_block + # epochs:0,slots:penultimate_slot_in_epoch,with-block:no_block + # epochs:0,slots:0,with-block:random_block_fulu + # epochs:0,slots:0,with-block:no_block + # epochs:0,slots:last_slot_in_epoch,with-block:no_block + # epochs:0,slots:0,with-block:random_block_fulu + scenario = {'transitions': [{'epochs_to_skip': 'epochs_until_leak', 'validation': 'validate_is_leaking', 'slots_to_skip': 0, 'block_producer': 'no_block'}, {'epochs_to_skip': 0, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 'penultimate_slot_in_epoch', 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block_fulu', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}, {'epochs_to_skip': 0, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 'last_slot_in_epoch', 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block_fulu', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}], 'state_randomizer': 'randomize_state_fulu'} # noqa: E501 + yield from run_generated_randomized_test( + spec, + state, + scenario, + ) + + +@only_generator("randomized test for broad coverage, not point-to-point CI") +@with_phases([FULU]) +@with_custom_state( + balances_fn=misc_balances_in_default_range_with_many_validators, + threshold_fn=zero_activation_threshold +) +@spec_test +@single_phase +@always_bls +def test_randomized_11(spec, state): + # scenario as high-level, informal text: + # epochs:epochs_until_leak,slots:0,with-block:no_block + # epochs:0,slots:0,with-block:no_block + # epochs:0,slots:last_slot_in_epoch,with-block:no_block + # epochs:0,slots:0,with-block:random_block_fulu + # epochs:1,slots:0,with-block:no_block + # epochs:0,slots:last_slot_in_epoch,with-block:no_block + # epochs:0,slots:0,with-block:random_block_fulu + scenario = {'transitions': [{'epochs_to_skip': 'epochs_until_leak', 'validation': 'validate_is_leaking', 'slots_to_skip': 0, 'block_producer': 'no_block'}, {'epochs_to_skip': 0, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 'last_slot_in_epoch', 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block_fulu', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}, {'epochs_to_skip': 1, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 'last_slot_in_epoch', 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block_fulu', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}], 'state_randomizer': 'randomize_state_fulu'} # noqa: E501 + yield from run_generated_randomized_test( + spec, + state, + scenario, + ) + + +@only_generator("randomized test for broad coverage, not point-to-point CI") +@with_phases([FULU]) +@with_custom_state( + balances_fn=misc_balances_in_default_range_with_many_validators, + threshold_fn=zero_activation_threshold +) +@spec_test +@single_phase +@always_bls +def test_randomized_12(spec, state): + # scenario as high-level, informal text: + # epochs:epochs_until_leak,slots:0,with-block:no_block + # epochs:1,slots:0,with-block:no_block + # epochs:0,slots:last_slot_in_epoch,with-block:no_block + # epochs:0,slots:0,with-block:random_block_fulu + # epochs:1,slots:0,with-block:no_block + # epochs:0,slots:penultimate_slot_in_epoch,with-block:no_block + # epochs:0,slots:0,with-block:random_block_fulu + scenario = {'transitions': [{'epochs_to_skip': 'epochs_until_leak', 'validation': 'validate_is_leaking', 'slots_to_skip': 0, 'block_producer': 'no_block'}, {'epochs_to_skip': 1, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 'last_slot_in_epoch', 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block_fulu', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}, {'epochs_to_skip': 1, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 'penultimate_slot_in_epoch', 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block_fulu', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}], 'state_randomizer': 'randomize_state_fulu'} # noqa: E501 + yield from run_generated_randomized_test( + spec, + state, + scenario, + ) + + +@only_generator("randomized test for broad coverage, not point-to-point CI") +@with_phases([FULU]) +@with_custom_state( + balances_fn=misc_balances_in_default_range_with_many_validators, + threshold_fn=zero_activation_threshold +) +@spec_test +@single_phase +@always_bls +def test_randomized_13(spec, state): + # scenario as high-level, informal text: + # epochs:epochs_until_leak,slots:0,with-block:no_block + # epochs:0,slots:0,with-block:no_block + # epochs:0,slots:random_slot_in_epoch,with-block:no_block + # epochs:0,slots:0,with-block:random_block_fulu + # epochs:0,slots:0,with-block:no_block + # epochs:0,slots:random_slot_in_epoch,with-block:no_block + # epochs:0,slots:0,with-block:random_block_fulu + scenario = {'transitions': [{'epochs_to_skip': 'epochs_until_leak', 'validation': 'validate_is_leaking', 'slots_to_skip': 0, 'block_producer': 'no_block'}, {'epochs_to_skip': 0, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 'random_slot_in_epoch', 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block_fulu', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}, {'epochs_to_skip': 0, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 'random_slot_in_epoch', 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block_fulu', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}], 'state_randomizer': 'randomize_state_fulu'} # noqa: E501 + yield from run_generated_randomized_test( + spec, + state, + scenario, + ) + + +@only_generator("randomized test for broad coverage, not point-to-point CI") +@with_phases([FULU]) +@with_custom_state( + balances_fn=misc_balances_in_default_range_with_many_validators, + threshold_fn=zero_activation_threshold +) +@spec_test +@single_phase +@always_bls +def test_randomized_14(spec, state): + # scenario as high-level, informal text: + # epochs:epochs_until_leak,slots:0,with-block:no_block + # epochs:1,slots:0,with-block:no_block + # epochs:0,slots:penultimate_slot_in_epoch,with-block:no_block + # epochs:0,slots:0,with-block:random_block_fulu + # epochs:0,slots:0,with-block:no_block + # epochs:0,slots:penultimate_slot_in_epoch,with-block:no_block + # epochs:0,slots:0,with-block:random_block_fulu + scenario = {'transitions': [{'epochs_to_skip': 'epochs_until_leak', 'validation': 'validate_is_leaking', 'slots_to_skip': 0, 'block_producer': 'no_block'}, {'epochs_to_skip': 1, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 'penultimate_slot_in_epoch', 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block_fulu', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}, {'epochs_to_skip': 0, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 'penultimate_slot_in_epoch', 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block_fulu', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}], 'state_randomizer': 'randomize_state_fulu'} # noqa: E501 + yield from run_generated_randomized_test( + spec, + state, + scenario, + ) + + +@only_generator("randomized test for broad coverage, not point-to-point CI") +@with_phases([FULU]) +@with_custom_state( + balances_fn=misc_balances_in_default_range_with_many_validators, + threshold_fn=zero_activation_threshold +) +@spec_test +@single_phase +@always_bls +def test_randomized_15(spec, state): + # scenario as high-level, informal text: + # epochs:epochs_until_leak,slots:0,with-block:no_block + # epochs:1,slots:0,with-block:no_block + # epochs:0,slots:0,with-block:no_block + # epochs:0,slots:0,with-block:random_block_fulu + # epochs:1,slots:0,with-block:no_block + # epochs:0,slots:0,with-block:no_block + # epochs:0,slots:0,with-block:random_block_fulu + scenario = {'transitions': [{'epochs_to_skip': 'epochs_until_leak', 'validation': 'validate_is_leaking', 'slots_to_skip': 0, 'block_producer': 'no_block'}, {'epochs_to_skip': 1, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 0, 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block_fulu', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}, {'epochs_to_skip': 1, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 0, 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block_fulu', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}], 'state_randomizer': 'randomize_state_fulu'} # noqa: E501 + yield from run_generated_randomized_test( + spec, + state, + scenario, + ) diff --git a/tests/core/pyspec/eth2spec/test/helpers/fulu/__init__.py b/tests/core/pyspec/eth2spec/test/helpers/fulu/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/core/pyspec/eth2spec/test/helpers/fulu/fork.py b/tests/core/pyspec/eth2spec/test/helpers/fulu/fork.py new file mode 100644 index 0000000000..f6f94e840f --- /dev/null +++ b/tests/core/pyspec/eth2spec/test/helpers/fulu/fork.py @@ -0,0 +1,68 @@ +from eth2spec.test.helpers.constants import ( + FULU, +) + + +FULU_FORK_TEST_META_TAGS = { + 'fork': FULU, +} + + +def run_fork_test(post_spec, pre_state): + yield 'pre', pre_state + + post_state = post_spec.upgrade_to_fulu(pre_state) + + # Stable fields + stable_fields = [ + 'genesis_time', 'genesis_validators_root', 'slot', + # History + 'latest_block_header', 'block_roots', 'state_roots', 'historical_roots', + # Eth1 + 'eth1_data', 'eth1_data_votes', 'eth1_deposit_index', + # Registry + # NOTE: 'validators', 'balances' could be changed. + # Randomness + 'randao_mixes', + # Slashings + 'slashings', + # Participation + 'previous_epoch_participation', 'current_epoch_participation', + # Finality + 'justification_bits', 'previous_justified_checkpoint', 'current_justified_checkpoint', 'finalized_checkpoint', + # Inactivity + 'inactivity_scores', + # Sync + 'current_sync_committee', 'next_sync_committee', + # Withdrawals + 'next_withdrawal_index', 'next_withdrawal_validator_index', + # Deep history valid from Capella onwards + 'historical_summaries', + 'latest_execution_payload_header' + + ] + for field in stable_fields: + assert getattr(pre_state, field) == getattr(post_state, field) + + # Modified fields + modified_fields = ['fork'] + for field in modified_fields: + assert getattr(pre_state, field) != getattr(post_state, field) + + assert len(pre_state.validators) == len(post_state.validators) + for pre_validator, post_validator in zip(pre_state.validators, post_state.validators): + stable_validator_fields = [ + 'pubkey', 'withdrawal_credentials', + 'slashed', + 'activation_epoch', 'exit_epoch', 'withdrawable_epoch', + ] + for field in stable_validator_fields: + assert getattr(pre_validator, field) == getattr(post_validator, field) + + assert pre_state.fork.current_version == post_state.fork.previous_version + assert post_state.fork.current_version == post_spec.config.FULU_FORK_VERSION + assert post_state.fork.epoch == post_spec.get_current_epoch(post_state) + + yield 'post', post_state + + return post_state diff --git a/tests/core/pyspec/eth2spec/test/phase0/epoch_processing/test_process_registry_updates.py b/tests/core/pyspec/eth2spec/test/phase0/epoch_processing/test_process_registry_updates.py index 7d7d79a178..dcc90a7672 100644 --- a/tests/core/pyspec/eth2spec/test/phase0/epoch_processing/test_process_registry_updates.py +++ b/tests/core/pyspec/eth2spec/test/phase0/epoch_processing/test_process_registry_updates.py @@ -31,6 +31,7 @@ def test_add_to_activation_queue(spec, state): assert state.validators[index].activation_eligibility_epoch != spec.FAR_FUTURE_EPOCH assert state.validators[index].activation_epoch == spec.FAR_FUTURE_EPOCH assert not spec.is_active_validator(state.validators[index], spec.get_current_epoch(state)) + assert spec.get_committee_assignment(state, spec.get_current_epoch(state), index) is None @with_all_phases diff --git a/tests/core/pyspec/eth2spec/test/utils/randomized_block_tests.py b/tests/core/pyspec/eth2spec/test/utils/randomized_block_tests.py index 3dae15c694..6e39ea3ff2 100644 --- a/tests/core/pyspec/eth2spec/test/utils/randomized_block_tests.py +++ b/tests/core/pyspec/eth2spec/test/utils/randomized_block_tests.py @@ -111,6 +111,17 @@ def randomize_state_electra(spec, state, stats, exit_fraction=0.1, slash_fractio return scenario_state +def randomize_state_fulu(spec, state, stats, exit_fraction=0.1, slash_fraction=0.1): + scenario_state = randomize_state_electra( + spec, + state, + stats, + exit_fraction=exit_fraction, + slash_fraction=slash_fraction, + ) + return scenario_state + + # epochs def epochs_until_leak(spec): @@ -269,6 +280,12 @@ def random_block_electra(spec, state, signed_blocks, scenario_state, rng=Random( return block +def random_block_fulu(spec, state, signed_blocks, scenario_state, rng=Random(3456)): + block = random_block_electra(spec, state, signed_blocks, scenario_state, rng=rng) + + return block + + # validations def no_op_validation(_spec, _state): diff --git a/tests/generators/epoch_processing/main.py b/tests/generators/epoch_processing/main.py index 62653e2fe8..14793bb8bc 100644 --- a/tests/generators/epoch_processing/main.py +++ b/tests/generators/epoch_processing/main.py @@ -1,5 +1,5 @@ from eth2spec.gen_helpers.gen_from_tests.gen import run_state_test_generators, combine_mods, check_mods -from eth2spec.test.helpers.constants import PHASE0, ALTAIR, BELLATRIX, CAPELLA, DENEB, ELECTRA +from eth2spec.test.helpers.constants import PHASE0, ALTAIR, BELLATRIX, CAPELLA, DENEB, ELECTRA, FULU if __name__ == "__main__": @@ -49,6 +49,9 @@ _new_electra_mods = {**_new_electra_mods_1, **_new_electra_mods_2} electra_mods = combine_mods(_new_electra_mods, deneb_mods) + # No additional Fulu specific epoch processing tests + fulu_mods = electra_mods + # TODO Custody Game testgen is disabled for now # custody_game_mods = {**{key: 'eth2spec.test.custody_game.epoch_processing.test_process_' + key for key in [ # 'reveal_deadlines', @@ -63,6 +66,7 @@ CAPELLA: capella_mods, DENEB: deneb_mods, ELECTRA: electra_mods, + FULU: fulu_mods, } check_mods(all_mods, "epoch_processing") diff --git a/tests/generators/finality/main.py b/tests/generators/finality/main.py index b31e949421..a13ff528da 100644 --- a/tests/generators/finality/main.py +++ b/tests/generators/finality/main.py @@ -1,5 +1,5 @@ from eth2spec.gen_helpers.gen_from_tests.gen import run_state_test_generators, check_mods -from eth2spec.test.helpers.constants import PHASE0, ALTAIR, BELLATRIX, CAPELLA, DENEB, ELECTRA +from eth2spec.test.helpers.constants import PHASE0, ALTAIR, BELLATRIX, CAPELLA, DENEB, ELECTRA, FULU if __name__ == "__main__": @@ -9,6 +9,7 @@ capella_mods = bellatrix_mods # No additional Capella specific finality tests deneb_mods = capella_mods # No additional Deneb specific finality tests electra_mods = deneb_mods # No additional Electra specific finality tests + fulu_mods = electra_mods # No additional Fulu specific finality tests all_mods = { PHASE0: phase_0_mods, @@ -17,6 +18,7 @@ CAPELLA: capella_mods, DENEB: deneb_mods, ELECTRA: electra_mods, + FULU: fulu_mods, } check_mods(all_mods, "finality") diff --git a/tests/generators/fork_choice/main.py b/tests/generators/fork_choice/main.py index 10a52fb954..37a1d66f06 100644 --- a/tests/generators/fork_choice/main.py +++ b/tests/generators/fork_choice/main.py @@ -1,5 +1,5 @@ from eth2spec.gen_helpers.gen_from_tests.gen import run_state_test_generators, combine_mods, check_mods -from eth2spec.test.helpers.constants import ALTAIR, BELLATRIX, CAPELLA, DENEB, ELECTRA +from eth2spec.test.helpers.constants import ALTAIR, BELLATRIX, CAPELLA, DENEB, ELECTRA, FULU if __name__ == "__main__": @@ -28,7 +28,16 @@ ]} deneb_mods = combine_mods(_new_deneb_mods, capella_mods) - electra_mods = deneb_mods # No additional Electra specific fork choice tests + _new_electra_mods = {key: 'eth2spec.test.electra.fork_choice.test_' + key for key in [ + 'deposit_with_reorg', + ]} + electra_mods = combine_mods(_new_electra_mods, deneb_mods) + + # Fulu adds new `is_data_available` tests + _new_fulu_mods = {key: 'eth2spec.test.fulu.fork_choice.test_' + key for key in [ + 'on_block', + ]} + fulu_mods = combine_mods(_new_fulu_mods, electra_mods) all_mods = { ALTAIR: altair_mods, @@ -36,6 +45,7 @@ CAPELLA: capella_mods, DENEB: deneb_mods, ELECTRA: electra_mods, + FULU: fulu_mods, } check_mods(all_mods, "fork_choice") diff --git a/tests/generators/forks/main.py b/tests/generators/forks/main.py index 91078c8dae..e56e619c0e 100644 --- a/tests/generators/forks/main.py +++ b/tests/generators/forks/main.py @@ -1,7 +1,7 @@ from typing import Iterable from eth2spec.test.helpers.constants import ( - PHASE0, ALTAIR, BELLATRIX, CAPELLA, DENEB, ELECTRA, + PHASE0, ALTAIR, BELLATRIX, CAPELLA, DENEB, ELECTRA, FULU, MINIMAL, MAINNET, ) from eth2spec.test.helpers.typing import SpecForkName, PresetBaseName @@ -10,6 +10,7 @@ from eth2spec.test.capella.fork import test_capella_fork_basic, test_capella_fork_random from eth2spec.test.deneb.fork import test_deneb_fork_basic, test_deneb_fork_random from eth2spec.test.electra.fork import test_electra_fork_basic, test_electra_fork_random +from eth2spec.test.fulu.fork import test_fulu_fork_basic, test_fulu_fork_random from eth2spec.gen_helpers.gen_base import gen_runner, gen_typing from eth2spec.gen_helpers.gen_from_tests.gen import generate_from_tests @@ -45,6 +46,8 @@ def _get_fork_tests_providers(): yield create_provider(test_deneb_fork_random, preset, CAPELLA, DENEB) yield create_provider(test_electra_fork_basic, preset, DENEB, ELECTRA) yield create_provider(test_electra_fork_random, preset, DENEB, ELECTRA) + yield create_provider(test_fulu_fork_basic, preset, ELECTRA, FULU) + yield create_provider(test_fulu_fork_random, preset, ELECTRA, FULU) if __name__ == "__main__": diff --git a/tests/generators/genesis/main.py b/tests/generators/genesis/main.py index 12907ab7de..b58553e203 100644 --- a/tests/generators/genesis/main.py +++ b/tests/generators/genesis/main.py @@ -1,5 +1,5 @@ from eth2spec.gen_helpers.gen_from_tests.gen import run_state_test_generators, check_mods -from eth2spec.test.helpers.constants import PHASE0, ALTAIR, BELLATRIX, CAPELLA, DENEB, ELECTRA +from eth2spec.test.helpers.constants import PHASE0, ALTAIR, BELLATRIX, CAPELLA, DENEB, ELECTRA, FULU if __name__ == "__main__": @@ -13,6 +13,8 @@ capella_mods = bellatrix_mods # No additional Capella specific genesis tests deneb_mods = capella_mods # No additional Deneb specific genesis tests electra_mods = deneb_mods # No additional Electra specific genesis tests + fulu_mods = electra_mods # No additional Fulu specific genesis tests + all_mods = { PHASE0: phase_0_mods, ALTAIR: altair_mods, @@ -20,6 +22,7 @@ CAPELLA: capella_mods, DENEB: deneb_mods, ELECTRA: electra_mods, + FULU: fulu_mods, } check_mods(all_mods, "genesis") diff --git a/tests/generators/light_client/main.py b/tests/generators/light_client/main.py index 6420382240..52a1800bff 100644 --- a/tests/generators/light_client/main.py +++ b/tests/generators/light_client/main.py @@ -1,4 +1,4 @@ -from eth2spec.test.helpers.constants import ALTAIR, BELLATRIX, CAPELLA, DENEB, ELECTRA +from eth2spec.test.helpers.constants import ALTAIR, BELLATRIX, CAPELLA, DENEB, ELECTRA, FULU from eth2spec.gen_helpers.gen_from_tests.gen import combine_mods, run_state_test_generators, check_mods @@ -27,14 +27,20 @@ 'sync', ]} deneb_mods = combine_mods(_new_deneb_mods, capella_mods) + + # No additional Electra specific light client tests electra_mods = deneb_mods + # No additional Electra specific light client tests + fulu_mods = electra_mods + all_mods = { ALTAIR: altair_mods, BELLATRIX: bellatrix_mods, CAPELLA: capella_mods, DENEB: deneb_mods, ELECTRA: electra_mods, + FULU: fulu_mods, } check_mods(all_mods, "light_client") diff --git a/tests/generators/operations/main.py b/tests/generators/operations/main.py index 9e3a7c21a4..3755cf6aef 100644 --- a/tests/generators/operations/main.py +++ b/tests/generators/operations/main.py @@ -1,5 +1,5 @@ from eth2spec.gen_helpers.gen_from_tests.gen import run_state_test_generators, combine_mods, check_mods -from eth2spec.test.helpers.constants import PHASE0, ALTAIR, BELLATRIX, CAPELLA, DENEB, ELECTRA +from eth2spec.test.helpers.constants import PHASE0, ALTAIR, BELLATRIX, CAPELLA, DENEB, ELECTRA, FULU if __name__ == "__main__": @@ -53,6 +53,9 @@ ]} electra_mods = combine_mods(_new_electra_mods, deneb_mods) + # No additional Fulu specific block processing tests + fulu_mods = electra_mods + all_mods = { PHASE0: phase_0_mods, ALTAIR: altair_mods, @@ -60,6 +63,7 @@ CAPELLA: capella_mods, DENEB: deneb_mods, ELECTRA: electra_mods, + FULU: fulu_mods, } check_mods(all_mods, "block_processing") diff --git a/tests/generators/random/generate.py b/tests/generators/random/generate.py index 1def843ba9..74972fff51 100644 --- a/tests/generators/random/generate.py +++ b/tests/generators/random/generate.py @@ -23,12 +23,14 @@ randomize_state_capella, randomize_state_deneb, randomize_state_electra, + randomize_state_fulu, random_block, random_block_altair_with_cycling_sync_committee_participation, random_block_bellatrix, random_block_capella, random_block_deneb, random_block_electra, + random_block_fulu, last_slot_in_epoch, random_slot_in_epoch, penultimate_slot_in_epoch, @@ -38,7 +40,7 @@ transition_to_leaking, transition_without_leak, ) -from eth2spec.test.helpers.constants import PHASE0, ALTAIR, BELLATRIX, CAPELLA, DENEB, ELECTRA +from eth2spec.test.helpers.constants import PHASE0, ALTAIR, BELLATRIX, CAPELLA, DENEB, ELECTRA, FULU # Ensure this many blocks are present in *each* randomized scenario @@ -290,5 +292,12 @@ def run_generate_tests_to_std_out(phase, state_randomizer, block_randomizer): state_randomizer=randomize_state_electra, block_randomizer=random_block_electra, ) + if FULU in sys.argv: + did_generate = True + run_generate_tests_to_std_out( + FULU, + state_randomizer=randomize_state_fulu, + block_randomizer=random_block_fulu, + ) if not did_generate: warnings.warn("no phase given for test generation") diff --git a/tests/generators/random/main.py b/tests/generators/random/main.py index 1d176c03ca..66ffa2eb51 100644 --- a/tests/generators/random/main.py +++ b/tests/generators/random/main.py @@ -1,5 +1,5 @@ from eth2spec.test.helpers.constants import ( - PHASE0, ALTAIR, BELLATRIX, CAPELLA, DENEB, ELECTRA, + PHASE0, ALTAIR, BELLATRIX, CAPELLA, DENEB, ELECTRA, FULU ) from eth2spec.gen_helpers.gen_from_tests.gen import run_state_test_generators, check_mods @@ -23,6 +23,9 @@ electra_mods = {key: 'eth2spec.test.electra.random.test_' + key for key in [ 'random', ]} + fulu_mods = {key: 'eth2spec.test.fulu.random.test_' + key for key in [ + 'random', + ]} all_mods = { PHASE0: phase_0_mods, @@ -31,6 +34,7 @@ CAPELLA: capella_mods, DENEB: deneb_mods, ELECTRA: electra_mods, + FULU: fulu_mods, } check_mods(all_mods, "random") diff --git a/tests/generators/rewards/main.py b/tests/generators/rewards/main.py index f1b27133a9..3e397e5a1b 100644 --- a/tests/generators/rewards/main.py +++ b/tests/generators/rewards/main.py @@ -1,5 +1,5 @@ from eth2spec.gen_helpers.gen_from_tests.gen import run_state_test_generators, check_mods -from eth2spec.test.helpers.constants import PHASE0, ALTAIR, BELLATRIX, CAPELLA, DENEB, ELECTRA +from eth2spec.test.helpers.constants import PHASE0, ALTAIR, BELLATRIX, CAPELLA, DENEB, ELECTRA, FULU if __name__ == "__main__": @@ -18,6 +18,7 @@ capella_mods = bellatrix_mods deneb_mods = capella_mods electra_mods = deneb_mods + fulu_mods = electra_mods all_mods = { PHASE0: phase_0_mods, @@ -26,6 +27,7 @@ CAPELLA: capella_mods, DENEB: deneb_mods, ELECTRA: electra_mods, + FULU: fulu_mods, } check_mods(all_mods, "rewards") diff --git a/tests/generators/sanity/main.py b/tests/generators/sanity/main.py index 2101894d9c..7526fc935a 100644 --- a/tests/generators/sanity/main.py +++ b/tests/generators/sanity/main.py @@ -1,4 +1,4 @@ -from eth2spec.test.helpers.constants import PHASE0, ALTAIR, BELLATRIX, CAPELLA, DENEB, ELECTRA +from eth2spec.test.helpers.constants import PHASE0, ALTAIR, BELLATRIX, CAPELLA, DENEB, ELECTRA, FULU from eth2spec.gen_helpers.gen_from_tests.gen import run_state_test_generators, combine_mods, check_mods @@ -40,6 +40,9 @@ _new_electra_mods = {**_new_electra_mods_1, **_new_electra_mods_2} electra_mods = combine_mods(_new_electra_mods, deneb_mods) + # No additional Fulu specific sanity tests + fulu_mods = electra_mods + all_mods = { PHASE0: phase_0_mods, ALTAIR: altair_mods, @@ -47,6 +50,7 @@ CAPELLA: capella_mods, DENEB: deneb_mods, ELECTRA: electra_mods, + FULU: fulu_mods, } check_mods(all_mods, "sanity") diff --git a/tests/generators/sync/main.py b/tests/generators/sync/main.py index fd2f3f0209..68b2d5fd1d 100644 --- a/tests/generators/sync/main.py +++ b/tests/generators/sync/main.py @@ -1,5 +1,5 @@ from eth2spec.gen_helpers.gen_from_tests.gen import run_state_test_generators, check_mods -from eth2spec.test.helpers.constants import BELLATRIX, CAPELLA, DENEB, ELECTRA +from eth2spec.test.helpers.constants import BELLATRIX, CAPELLA, DENEB, ELECTRA, FULU if __name__ == "__main__": @@ -9,12 +9,14 @@ capella_mods = bellatrix_mods deneb_mods = capella_mods electra_mods = deneb_mods + fulu_mods = electra_mods all_mods = { BELLATRIX: bellatrix_mods, CAPELLA: capella_mods, DENEB: deneb_mods, ELECTRA: electra_mods, + FULU: fulu_mods, } check_mods(all_mods, "sync")