Merge branch 'dev' into tests/merge

This commit is contained in:
Dmitrii Shmatko 2021-09-10 17:43:48 +03:00
commit f38750ae61
50 changed files with 2777 additions and 260 deletions

View File

@ -45,6 +45,7 @@ The merge is still actively in development. The exact specification has not been
* [Merge fork](specs/merge/fork.md)
* [Fork Choice changes](specs/merge/fork-choice.md)
* [Validator additions](specs/merge/validator.md)
* [Client settings](specs/merge/client_settings.md)
### Sharding
@ -53,7 +54,7 @@ Sharding follows the merge, and is divided into three parts:
* Sharding base functionality - In early engineering phase
* [Beacon Chain changes](specs/sharding/beacon-chain.md)
* [P2P Network changes](specs/sharding/p2p-interface.md)
* Custody Game - Ready, dependent on sharding
* Custody Game - Ready, dependent on sharding
* [Beacon Chain changes](specs/custody_game/beacon-chain.md)
* [Validator custody work](specs/custody_game/validator.md)
* Data Availability Sampling - In active R&D

11
SECURITY.md Normal file
View File

@ -0,0 +1,11 @@
# Security Policy
## Supported Versions
Please see [Releases](https://github.com/ethereum/consensus-specs/releases/). We recommend using the [most recently released version](https://github.com/ethereum/consensus-specs/releases/latest).
## Reporting a Vulnerability
**Please do not file a public ticket** mentioning the vulnerability.
To find out how to disclose a vulnerability in the Ethereum Consensus Layer visit [https://eth2bounty.ethereum.org](https://eth2bounty.ethereum.org) or email eth2bounty@ethereum.org. Please read the [disclosure page](https://eth2bounty.ethereum.org) for more information about publicly disclosed security vulnerabilities.

View File

@ -58,8 +58,8 @@ INACTIVITY_SCORE_RECOVERY_RATE: 16
EJECTION_BALANCE: 16000000000
# 2**2 (= 4)
MIN_PER_EPOCH_CHURN_LIMIT: 4
# 2**16 (= 65,536)
CHURN_LIMIT_QUOTIENT: 65536
# [customized] scale queue churn at much lower validator counts for testing
CHURN_LIMIT_QUOTIENT: 32
# Deposit contract

View File

@ -223,7 +223,7 @@ def get_spec(file_name: Path, preset: Dict[str, str], config: Dict[str, str]) ->
if not _is_constant_id(name):
# Check for short type declarations
if value.startswith("uint") or value.startswith("Bytes") or value.startswith("ByteList") or value.startswith("Union"):
if value.startswith(("uint", "Bytes", "ByteList", "Union")):
custom_types[name] = value
continue

View File

@ -0,0 +1,26 @@
<!-- START doctoc generated TOC please keep comment here to allow auto update -->
<!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE -->
**Table of Contents** *generated with [DocToc](https://github.com/thlorenz/doctoc)*
- [The Merge -- Client Settings](#the-merge----client-settings)
- [Override terminal total difficulty](#override-terminal-total-difficulty)
<!-- END doctoc generated TOC please keep comment here to allow auto update -->
# The Merge -- Client Settings
**Notice**: This document is a work-in-progress for researchers and implementers.
This document specifies configurable settings that clients must implement for the Merge.
### Override terminal total difficulty
To coordinate manual overrides to [`terminal_total_difficulty`](fork-choice.md#transitionstore), clients
must provide `--terminal-total-difficulty-override` as a configurable setting.
If `TransitionStore` has already [been initialized](./fork.md#initializing-transition-store), this alters the previously initialized value of
`TransitionStore.terminal_total_difficulty`, otherwise this setting initializes `TransitionStore` with the specified, bypassing `compute_terminal_total_difficulty` and the use of an `anchor_pow_block`.
`terminal_total_difficulty`.
Except under exceptional scenarios, this setting is expected to not be used, and `terminal_total_difficulty` will operate with [default functionality](./fork.md#initializing-transition-store). Sufficient warning to the user about this exceptional configurable setting should be provided.
[here](fork.md#initializing-transition-store).

View File

@ -73,7 +73,7 @@ def finalize_block(self: ExecutionEngine, block_hash: Hash32) -> bool:
```python
@dataclass
class TransitionStore(object):
transition_total_difficulty: uint256
terminal_total_difficulty: uint256
```
### `PowBlock`
@ -101,8 +101,8 @@ Used by fork-choice handler, `on_block`.
```python
def is_valid_terminal_pow_block(transition_store: TransitionStore, block: PowBlock, parent: PowBlock) -> bool:
is_total_difficulty_reached = block.total_difficulty >= transition_store.transition_total_difficulty
is_parent_total_difficulty_valid = parent.total_difficulty < transition_store.transition_total_difficulty
is_total_difficulty_reached = block.total_difficulty >= transition_store.terminal_total_difficulty
is_parent_total_difficulty_valid = parent.total_difficulty < transition_store.terminal_total_difficulty
return block.is_valid and is_total_difficulty_reached and is_parent_total_difficulty_valid
```
@ -129,7 +129,7 @@ def on_block(store: Store, signed_block: SignedBeaconBlock, transition_store: Tr
assert get_ancestor(store, block.parent_root, finalized_slot) == store.finalized_checkpoint.root
# [New in Merge]
if (transition_store is not None) and is_merge_block(pre_state, block):
if (transition_store is not None) and is_merge_block(pre_state, block.body):
# Delay consideration of block until PoW block is processed by the PoW node
pow_block = get_pow_block(block.body.execution_payload.parent_hash)
pow_parent = get_pow_block(pow_block.parent_hash)

View File

@ -97,18 +97,18 @@ def upgrade_to_merge(pre: altair.BeaconState) -> BeaconState:
# Execution-layer
latest_execution_payload_header=ExecutionPayloadHeader(),
)
return post
```
### Initializing transition store
If `state.slot % SLOTS_PER_EPOCH == 0` and `compute_epoch_at_slot(state.slot) == MERGE_FORK_EPOCH`, a transition store is initialized to be further utilized by the transition process of the Merge.
If `state.slot % SLOTS_PER_EPOCH == 0`, `compute_epoch_at_slot(state.slot) == MERGE_FORK_EPOCH`, and the transition store has not already been initialized, a transition store is initialized to be further utilized by the transition process of the Merge.
Transition store initialization occurs after the state has been modified by corresponding `upgrade_to_merge` function.
```python
def compute_transition_total_difficulty(anchor_pow_block: PowBlock) -> uint256:
def compute_terminal_total_difficulty(anchor_pow_block: PowBlock) -> uint256:
seconds_per_voting_period = EPOCHS_PER_ETH1_VOTING_PERIOD * SLOTS_PER_EPOCH * SECONDS_PER_SLOT
pow_blocks_per_voting_period = seconds_per_voting_period // SECONDS_PER_ETH1_BLOCK
pow_blocks_to_merge = TARGET_SECONDS_TO_MERGE // SECONDS_PER_ETH1_BLOCK
@ -119,11 +119,14 @@ def compute_transition_total_difficulty(anchor_pow_block: PowBlock) -> uint256:
def get_transition_store(anchor_pow_block: PowBlock) -> TransitionStore:
transition_total_difficulty = compute_transition_total_difficulty(anchor_pow_block)
return TransitionStore(transition_total_difficulty=transition_total_difficulty)
terminal_total_difficulty = compute_terminal_total_difficulty(anchor_pow_block)
return TransitionStore(terminal_total_difficulty=terminal_total_difficulty)
def initialize_transition_store(state: BeaconState) -> TransitionStore:
pow_block = get_pow_block(state.eth1_data.block_hash)
return get_transition_store(pow_block)
```
*Note*: Transition store can also be initialized at client startup by [overriding terminal total
difficulty](client_settings.md#override-terminal-total-difficulty).

View File

@ -95,7 +95,7 @@ def get_execution_payload(state: BeaconState,
execution_engine: ExecutionEngine,
pow_chain: Sequence[PowBlock]) -> ExecutionPayload:
if not is_merge_complete(state):
terminal_pow_block = get_pow_block_at_total_difficulty(transition_store.transition_total_difficulty, pow_chain)
terminal_pow_block = get_pow_block_at_total_difficulty(transition_store.terminal_total_difficulty, pow_chain)
if terminal_pow_block is None:
# Pre-merge, empty payload
return ExecutionPayload()

View File

@ -327,9 +327,13 @@ def on_tick(store: Store, time: uint64) -> None:
# Not a new epoch, return
if not (current_slot > previous_slot and compute_slots_since_epoch_start(current_slot) == 0):
return
# Update store.justified_checkpoint if a better checkpoint is known
# Update store.justified_checkpoint if a better checkpoint on the store.finalized_checkpoint chain
if store.best_justified_checkpoint.epoch > store.justified_checkpoint.epoch:
store.justified_checkpoint = store.best_justified_checkpoint
finalized_slot = compute_start_slot_at_epoch(store.finalized_checkpoint.epoch)
ancestor_at_finalized_slot = get_ancestor(store, store.best_justified_checkpoint.root, finalized_slot)
if ancestor_at_finalized_slot == store.finalized_checkpoint.root:
store.justified_checkpoint = store.best_justified_checkpoint
```
#### `on_block`

View File

@ -32,7 +32,7 @@
- [`Builder`](#builder)
- [`DataCommitment`](#datacommitment)
- [`AttestedDataCommitment`](#attesteddatacommitment)
- [ShardBlobBody](#shardblobbody)
- [`ShardBlobBody`](#shardblobbody)
- [`ShardBlobBodySummary`](#shardblobbodysummary)
- [`ShardBlob`](#shardblob)
- [`ShardBlobHeader`](#shardblobheader)
@ -257,7 +257,7 @@ class AttestedDataCommitment(Container):
includer_index: ValidatorIndex
```
### ShardBlobBody
### `ShardBlobBody`
Unsigned shard data, bundled by a shard-builder.
Unique, signing different bodies as shard proposer for the same `(slot, shard)` is slashable.
@ -503,14 +503,14 @@ def get_active_shard_count(state: BeaconState, epoch: Epoch) -> uint64:
#### `get_shard_proposer_index`
```python
def get_shard_proposer_index(beacon_state: BeaconState, slot: Slot, shard: Shard) -> ValidatorIndex:
def get_shard_proposer_index(state: BeaconState, slot: Slot, shard: Shard) -> ValidatorIndex:
"""
Return the proposer's index of shard block at ``slot``.
"""
epoch = compute_epoch_at_slot(slot)
seed = hash(get_seed(beacon_state, epoch, DOMAIN_SHARD_BLOB) + uint_to_bytes(slot) + uint_to_bytes(shard))
seed = hash(get_seed(state, epoch, DOMAIN_SHARD_BLOB) + uint_to_bytes(slot) + uint_to_bytes(shard))
indices = get_active_validator_indices(state, epoch)
return compute_proposer_index(beacon_state, indices, seed)
return compute_proposer_index(state, indices, seed)
```
#### `get_start_shard`
@ -520,7 +520,7 @@ def get_start_shard(state: BeaconState, slot: Slot) -> Shard:
"""
Return the start shard at ``slot``.
"""
epoch = compute_epoch_at_slot(Slot(_slot))
epoch = compute_epoch_at_slot(Slot(slot))
committee_count = get_committee_count_per_slot(state, epoch)
active_shard_count = get_active_shard_count(state, epoch)
return committee_count * slot % active_shard_count
@ -759,7 +759,7 @@ def process_shard_header(state: BeaconState, signed_header: SignedShardBlobHeade
commitment=body_summary.commitment,
root=header_root,
includer_index=get_beacon_proposer_index(state),
)
),
votes=initial_votes,
weight=0,
update_slot=state.slot,
@ -885,7 +885,7 @@ def reset_pending_shard_work(state: BeaconState) -> None:
selector=SHARD_WORK_PENDING,
value=List[PendingShardHeader, MAX_SHARD_HEADERS_PER_SHARD](
PendingShardHeader(
attested=AttestedDataCommitment()
attested=AttestedDataCommitment(),
votes=Bitlist[MAX_VALIDATORS_PER_COMMITTEE]([0] * committee_length),
weight=0,
update_slot=slot,

View File

@ -1 +1 @@
1.1.0-beta.3
1.1.0-beta.4

View File

@ -1,4 +1,5 @@
import os
import time
import shutil
import argparse
from pathlib import Path
@ -22,6 +23,9 @@ from .gen_typing import TestProvider
context.is_pytest = False
TIME_THRESHOLD_TO_PRINT = 1.0 # seconds
def validate_output_dir(path_str):
path = Path(path_str)
@ -73,6 +77,13 @@ def run_generator(generator_name, test_providers: Iterable[TestProvider]):
required=False,
help="specify presets to run with. Allows all if no preset names are specified.",
)
parser.add_argument(
"-c",
"--collect-only",
action="store_true",
default=False,
help="if set only print tests to generate, do not actually run the test and dump the target data",
)
args = parser.parse_args()
output_dir = args.output_dir
@ -96,11 +107,15 @@ def run_generator(generator_name, test_providers: Iterable[TestProvider]):
if len(presets) != 0:
print(f"Filtering test-generator runs to only include presets: {', '.join(presets)}")
collect_only = args.collect_only
collected_test_count = 0
generated_test_count = 0
skipped_test_count = 0
provider_start = time.time()
for tprov in test_providers:
# runs anything that we don't want to repeat for every test case.
tprov.prepare()
if not collect_only:
# runs anything that we don't want to repeat for every test case.
tprov.prepare()
for test_case in tprov.make_cases():
case_dir = (
@ -110,8 +125,14 @@ def run_generator(generator_name, test_providers: Iterable[TestProvider]):
)
incomplete_tag_file = case_dir / "INCOMPLETE"
collected_test_count += 1
if collect_only:
print(f"Collected test at: {case_dir}")
continue
if case_dir.exists():
if not args.force and not incomplete_tag_file.exists():
skipped_test_count += 1
print(f'Skipping already existing test: {case_dir}')
continue
else:
@ -121,6 +142,7 @@ def run_generator(generator_name, test_providers: Iterable[TestProvider]):
shutil.rmtree(case_dir)
print(f'Generating test: {case_dir}')
test_start = time.time()
written_part = False
@ -178,10 +200,22 @@ def run_generator(generator_name, test_providers: Iterable[TestProvider]):
generated_test_count += 1
# Only remove `INCOMPLETE` tag file
os.remove(incomplete_tag_file)
test_end = time.time()
span = round(test_end - test_start, 2)
if span > TIME_THRESHOLD_TO_PRINT:
print(f' - generated in {span} seconds')
summary_message = f"completed generation of {generator_name} with {generated_test_count} tests"
summary_message += f" ({skipped_test_count} skipped tests)"
print(summary_message)
provider_end = time.time()
span = round(provider_end - provider_start, 2)
if collect_only:
print(f"Collected {collected_test_count} tests in total")
else:
summary_message = f"completed generation of {generator_name} with {generated_test_count} tests"
summary_message += f" ({skipped_test_count} skipped tests)"
if span > TIME_THRESHOLD_TO_PRINT:
summary_message += f" in {span} seconds"
print(summary_message)
def dump_yaml_fn(data: Any, name: str, file_mode: str, yaml_encoder: YAML):

View File

@ -1,6 +1,6 @@
from importlib import import_module
from inspect import getmembers, isfunction
from typing import Any, Callable, Dict, Iterable, Optional
from typing import Any, Callable, Dict, Iterable, Optional, List, Union
from eth2spec.utils import bls
from eth2spec.test.helpers.constants import ALL_PRESETS, TESTGEN_FORKS
@ -59,8 +59,10 @@ def generate_from_tests(runner_name: str, handler_name: str, src: Any,
def get_provider(create_provider_fn: Callable[[SpecForkName, PresetBaseName, str, str], TestProvider],
fork_name: SpecForkName,
preset_name: PresetBaseName,
all_mods: Dict[str, Dict[str, str]]) -> Iterable[TestProvider]:
all_mods: Dict[str, Dict[str, Union[List[str], str]]]) -> Iterable[TestProvider]:
for key, mod_name in all_mods[fork_name].items():
if not isinstance(mod_name, List):
mod_name = [mod_name]
yield create_provider_fn(
fork_name=fork_name,
preset_name=preset_name,
@ -75,16 +77,17 @@ def get_create_provider_fn(runner_name: str) -> Callable[[SpecForkName, str, str
return
def create_provider(fork_name: SpecForkName, preset_name: PresetBaseName,
handler_name: str, tests_src_mod_name: str) -> TestProvider:
handler_name: str, tests_src_mod_name: List[str]) -> TestProvider:
def cases_fn() -> Iterable[TestCase]:
tests_src = import_module(tests_src_mod_name)
return generate_from_tests(
runner_name=runner_name,
handler_name=handler_name,
src=tests_src,
fork_name=fork_name,
preset_name=preset_name,
)
for mod_name in tests_src_mod_name:
tests_src = import_module(mod_name)
yield from generate_from_tests(
runner_name=runner_name,
handler_name=handler_name,
src=tests_src,
fork_name=fork_name,
preset_name=preset_name,
)
return TestProvider(prepare=prepare_fn, make_cases=cases_fn)
return create_provider

View File

@ -5,6 +5,7 @@ from eth2spec.test.helpers.block import (
from eth2spec.test.helpers.state import (
state_transition_and_sign_block,
transition_to,
next_epoch_via_block,
)
from eth2spec.test.helpers.constants import (
MAINNET, MINIMAL,
@ -12,10 +13,12 @@ from eth2spec.test.helpers.constants import (
from eth2spec.test.helpers.sync_committee import (
compute_aggregate_sync_committee_signature,
compute_committee_indices,
get_committee_indices,
run_sync_committee_processing,
run_successful_sync_committee_test,
)
from eth2spec.test.helpers.voluntary_exits import (
get_unslashed_exited_validators,
)
from eth2spec.test.context import (
with_altair_and_later,
with_presets,
@ -28,19 +31,17 @@ from eth2spec.test.context import (
@spec_state_test
@always_bls
def test_invalid_signature_bad_domain(spec, state):
committee_indices = compute_committee_indices(spec, state, state.current_sync_committee)
rng = random.Random(2020)
random_participant = rng.choice(committee_indices)
committee_indices = compute_committee_indices(spec, state)
block = build_empty_block_for_next_slot(spec, state)
# Exclude one participant whose signature was included.
block.body.sync_aggregate = spec.SyncAggregate(
sync_committee_bits=[index != random_participant for index in committee_indices],
sync_committee_bits=[True] * len(committee_indices),
sync_committee_signature=compute_aggregate_sync_committee_signature(
spec,
state,
block.slot - 1,
committee_indices, # full committee signs
block_root=block.parent_root,
domain_type=spec.DOMAIN_BEACON_ATTESTER, # Incorrect domain
)
)
@ -51,7 +52,7 @@ def test_invalid_signature_bad_domain(spec, state):
@spec_state_test
@always_bls
def test_invalid_signature_missing_participant(spec, state):
committee_indices = compute_committee_indices(spec, state, state.current_sync_committee)
committee_indices = compute_committee_indices(spec, state)
rng = random.Random(2020)
random_participant = rng.choice(committee_indices)
@ -64,6 +65,7 @@ def test_invalid_signature_missing_participant(spec, state):
state,
block.slot - 1,
committee_indices, # full committee signs
block_root=block.parent_root,
)
)
yield from run_sync_committee_processing(spec, state, block, expect_exception=True)
@ -114,7 +116,7 @@ def test_invalid_signature_infinite_signature_with_single_participant(spec, stat
@spec_state_test
@always_bls
def test_invalid_signature_extra_participant(spec, state):
committee_indices = compute_committee_indices(spec, state, state.current_sync_committee)
committee_indices = compute_committee_indices(spec, state)
rng = random.Random(3030)
random_participant = rng.choice(committee_indices)
@ -127,6 +129,7 @@ def test_invalid_signature_extra_participant(spec, state):
state,
block.slot - 1,
[index for index in committee_indices if index != random_participant],
block_root=block.parent_root,
)
)
@ -137,7 +140,7 @@ def test_invalid_signature_extra_participant(spec, state):
@with_presets([MINIMAL], reason="to create nonduplicate committee")
@spec_state_test
def test_sync_committee_rewards_nonduplicate_committee(spec, state):
committee_indices = get_committee_indices(spec, state, duplicates=False)
committee_indices = compute_committee_indices(spec, state)
committee_size = len(committee_indices)
committee_bits = [True] * committee_size
active_validator_count = len(spec.get_active_validator_indices(state, spec.get_current_epoch(state)))
@ -153,7 +156,7 @@ def test_sync_committee_rewards_nonduplicate_committee(spec, state):
@with_presets([MAINNET], reason="to create duplicate committee")
@spec_state_test
def test_sync_committee_rewards_duplicate_committee_no_participation(spec, state):
committee_indices = get_committee_indices(spec, state, duplicates=True)
committee_indices = compute_committee_indices(spec, state)
committee_size = len(committee_indices)
committee_bits = [False] * committee_size
active_validator_count = len(spec.get_active_validator_indices(state, spec.get_current_epoch(state)))
@ -169,7 +172,7 @@ def test_sync_committee_rewards_duplicate_committee_no_participation(spec, state
@with_presets([MAINNET], reason="to create duplicate committee")
@spec_state_test
def test_sync_committee_rewards_duplicate_committee_half_participation(spec, state):
committee_indices = get_committee_indices(spec, state, duplicates=True)
committee_indices = compute_committee_indices(spec, state)
committee_size = len(committee_indices)
committee_bits = [True] * (committee_size // 2) + [False] * (committee_size // 2)
assert len(committee_bits) == committee_size
@ -186,7 +189,7 @@ def test_sync_committee_rewards_duplicate_committee_half_participation(spec, sta
@with_presets([MAINNET], reason="to create duplicate committee")
@spec_state_test
def test_sync_committee_rewards_duplicate_committee_full_participation(spec, state):
committee_indices = get_committee_indices(spec, state, duplicates=True)
committee_indices = compute_committee_indices(spec, state)
committee_size = len(committee_indices)
committee_bits = [True] * committee_size
active_validator_count = len(spec.get_active_validator_indices(state, spec.get_current_epoch(state)))
@ -202,7 +205,7 @@ def test_sync_committee_rewards_duplicate_committee_full_participation(spec, sta
@spec_state_test
@always_bls
def test_sync_committee_rewards_not_full_participants(spec, state):
committee_indices = compute_committee_indices(spec, state, state.current_sync_committee)
committee_indices = compute_committee_indices(spec, state)
rng = random.Random(1010)
committee_bits = [rng.choice([True, False]) for _ in committee_indices]
@ -213,7 +216,7 @@ def test_sync_committee_rewards_not_full_participants(spec, state):
@spec_state_test
@always_bls
def test_sync_committee_rewards_empty_participants(spec, state):
committee_indices = compute_committee_indices(spec, state, state.current_sync_committee)
committee_indices = compute_committee_indices(spec, state)
committee_bits = [False for _ in committee_indices]
yield from run_successful_sync_committee_test(spec, state, committee_indices, committee_bits)
@ -223,7 +226,7 @@ def test_sync_committee_rewards_empty_participants(spec, state):
@spec_state_test
@always_bls
def test_invalid_signature_past_block(spec, state):
committee_indices = compute_committee_indices(spec, state, state.current_sync_committee)
committee_indices = compute_committee_indices(spec, state)
for _ in range(2):
# NOTE: need to transition twice to move beyond the degenerate case at genesis
@ -236,6 +239,7 @@ def test_invalid_signature_past_block(spec, state):
state,
block.slot - 1,
committee_indices,
block_root=block.parent_root,
)
)
@ -286,6 +290,7 @@ def test_invalid_signature_previous_committee(spec, state):
state,
block.slot - 1,
committee_indices,
block_root=block.parent_root,
)
)
@ -327,6 +332,7 @@ def test_valid_signature_future_committee(spec, state):
state,
block.slot - 1,
committee_indices,
block_root=block.parent_root,
)
)
@ -360,6 +366,7 @@ def test_proposer_in_committee_without_participation(spec, state):
state,
block.slot - 1,
participants,
block_root=block.parent_root,
)
)
@ -396,6 +403,7 @@ def test_proposer_in_committee_with_participation(spec, state):
state,
block.slot - 1,
committee_indices,
block_root=block.parent_root,
)
)
@ -406,3 +414,191 @@ def test_proposer_in_committee_with_participation(spec, state):
else:
state_transition_and_sign_block(spec, state, block)
raise AssertionError("failed to find a proposer in the sync committee set; check test setup")
def _exit_validator_from_committee_and_transition_state(spec,
state,
committee_indices,
rng,
target_epoch_provider,
withdrawable_offset=1):
exited_validator_index = rng.sample(committee_indices, 1)[0]
validator = state.validators[exited_validator_index]
current_epoch = spec.get_current_epoch(state)
validator.exit_epoch = current_epoch
validator.withdrawable_epoch = validator.exit_epoch + withdrawable_offset
target_epoch = target_epoch_provider(state.validators[exited_validator_index])
target_slot = target_epoch * spec.SLOTS_PER_EPOCH
transition_to(spec, state, target_slot)
exited_validator_indices = get_unslashed_exited_validators(spec, state)
assert exited_validator_index in exited_validator_indices
exited_pubkey = state.validators[exited_validator_index].pubkey
assert exited_pubkey in state.current_sync_committee.pubkeys
return exited_validator_index
@with_altair_and_later
@spec_state_test
@always_bls
def test_sync_committee_with_participating_exited_member(spec, state):
# move state forward SHARD_COMMITTEE_PERIOD epochs to allow for exit
state.slot += spec.config.SHARD_COMMITTEE_PERIOD * spec.SLOTS_PER_EPOCH
# move forward via some blocks
for _ in range(3):
next_epoch_via_block(spec, state)
committee_indices = compute_committee_indices(spec, state)
rng = random.Random(1010)
exited_index = _exit_validator_from_committee_and_transition_state(
spec,
state,
committee_indices,
rng,
lambda v: v.exit_epoch,
)
current_epoch = spec.get_current_epoch(state)
assert current_epoch < state.validators[exited_index].withdrawable_epoch
block = build_empty_block_for_next_slot(spec, state)
block.body.sync_aggregate = spec.SyncAggregate(
sync_committee_bits=[True] * len(committee_indices),
sync_committee_signature=compute_aggregate_sync_committee_signature(
spec,
state,
block.slot - 1,
committee_indices, # full committee signs
block_root=block.parent_root,
)
)
yield from run_sync_committee_processing(spec, state, block)
@with_altair_and_later
@spec_state_test
@always_bls
def test_sync_committee_with_nonparticipating_exited_member(spec, state):
# move state forward SHARD_COMMITTEE_PERIOD epochs to allow for exit
state.slot += spec.config.SHARD_COMMITTEE_PERIOD * spec.SLOTS_PER_EPOCH
# move forward via some blocks
for _ in range(3):
next_epoch_via_block(spec, state)
committee_indices = compute_committee_indices(spec, state)
rng = random.Random(1010)
exited_index = _exit_validator_from_committee_and_transition_state(
spec,
state,
committee_indices,
rng,
lambda v: v.exit_epoch,
)
exited_pubkey = state.validators[exited_index].pubkey
current_epoch = spec.get_current_epoch(state)
assert current_epoch < state.validators[exited_index].withdrawable_epoch
exited_committee_index = state.current_sync_committee.pubkeys.index(exited_pubkey)
block = build_empty_block_for_next_slot(spec, state)
committee_bits = [i != exited_committee_index for i in committee_indices]
committee_indices = [index for index in committee_indices if index != exited_committee_index]
block.body.sync_aggregate = spec.SyncAggregate(
sync_committee_bits=committee_bits,
sync_committee_signature=compute_aggregate_sync_committee_signature(
spec,
state,
block.slot - 1,
committee_indices, # with exited validator removed
block_root=block.parent_root,
)
)
yield from run_sync_committee_processing(spec, state, block)
@with_altair_and_later
@spec_state_test
@always_bls
def test_sync_committee_with_participating_withdrawable_member(spec, state):
# move state forward SHARD_COMMITTEE_PERIOD epochs to allow for exit
state.slot += spec.config.SHARD_COMMITTEE_PERIOD * spec.SLOTS_PER_EPOCH
# move forward via some blocks
for _ in range(3):
next_epoch_via_block(spec, state)
committee_indices = compute_committee_indices(spec, state)
rng = random.Random(1010)
exited_index = _exit_validator_from_committee_and_transition_state(
spec,
state,
committee_indices,
rng,
lambda v: v.withdrawable_epoch + 1,
)
current_epoch = spec.get_current_epoch(state)
assert current_epoch > state.validators[exited_index].withdrawable_epoch
block = build_empty_block_for_next_slot(spec, state)
block.body.sync_aggregate = spec.SyncAggregate(
sync_committee_bits=[True] * len(committee_indices),
sync_committee_signature=compute_aggregate_sync_committee_signature(
spec,
state,
block.slot - 1,
committee_indices, # full committee signs
block_root=block.parent_root,
)
)
yield from run_sync_committee_processing(spec, state, block)
@with_altair_and_later
@spec_state_test
@always_bls
def test_sync_committee_with_nonparticipating_withdrawable_member(spec, state):
# move state forward SHARD_COMMITTEE_PERIOD epochs to allow for exit
state.slot += spec.config.SHARD_COMMITTEE_PERIOD * spec.SLOTS_PER_EPOCH
# move forward via some blocks
for _ in range(3):
next_epoch_via_block(spec, state)
committee_indices = compute_committee_indices(spec, state)
rng = random.Random(1010)
exited_index = _exit_validator_from_committee_and_transition_state(
spec,
state,
committee_indices,
rng,
lambda v: v.withdrawable_epoch + 1,
)
exited_pubkey = state.validators[exited_index].pubkey
current_epoch = spec.get_current_epoch(state)
assert current_epoch > state.validators[exited_index].withdrawable_epoch
target_committee_index = state.current_sync_committee.pubkeys.index(exited_pubkey)
block = build_empty_block_for_next_slot(spec, state)
committee_bits = [i != target_committee_index for i in committee_indices]
committee_indices = [index for index in committee_indices if index != target_committee_index]
block.body.sync_aggregate = spec.SyncAggregate(
sync_committee_bits=committee_bits,
sync_committee_signature=compute_aggregate_sync_committee_signature(
spec,
state,
block.slot - 1,
committee_indices, # with withdrawable validator removed
block_root=block.parent_root,
)
)
yield from run_sync_committee_processing(spec, state, block)

View File

@ -2,10 +2,19 @@ import random
from eth2spec.test.helpers.constants import (
MAINNET, MINIMAL,
)
from eth2spec.test.helpers.random import (
randomize_state,
)
from eth2spec.test.helpers.state import (
has_active_balance_differential,
)
from eth2spec.test.helpers.sync_committee import (
get_committee_indices,
compute_committee_indices,
run_successful_sync_committee_test,
)
from eth2spec.test.helpers.voluntary_exits import (
get_unslashed_exited_validators,
)
from eth2spec.test.context import (
with_altair_and_later,
spec_state_test,
@ -18,8 +27,8 @@ from eth2spec.test.context import (
)
def _test_harness_for_randomized_test_case(spec, state, duplicates=False, participation_fn=None):
committee_indices = get_committee_indices(spec, state, duplicates=duplicates)
def _test_harness_for_randomized_test_case(spec, state, expect_duplicates=False, participation_fn=None):
committee_indices = compute_committee_indices(spec, state)
if participation_fn:
participating_indices = participation_fn(committee_indices)
@ -28,7 +37,7 @@ def _test_harness_for_randomized_test_case(spec, state, duplicates=False, partic
committee_bits = [index in participating_indices for index in committee_indices]
committee_size = len(committee_indices)
if duplicates:
if expect_duplicates:
assert committee_size > len(set(committee_indices))
else:
assert committee_size == len(set(committee_indices))
@ -44,7 +53,7 @@ def test_random_only_one_participant_with_duplicates(spec, state):
yield from _test_harness_for_randomized_test_case(
spec,
state,
duplicates=True,
expect_duplicates=True,
participation_fn=lambda comm: [rng.choice(comm)],
)
@ -57,7 +66,7 @@ def test_random_low_participation_with_duplicates(spec, state):
yield from _test_harness_for_randomized_test_case(
spec,
state,
duplicates=True,
expect_duplicates=True,
participation_fn=lambda comm: rng.sample(comm, int(len(comm) * 0.25)),
)
@ -70,7 +79,7 @@ def test_random_high_participation_with_duplicates(spec, state):
yield from _test_harness_for_randomized_test_case(
spec,
state,
duplicates=True,
expect_duplicates=True,
participation_fn=lambda comm: rng.sample(comm, int(len(comm) * 0.75)),
)
@ -83,7 +92,7 @@ def test_random_all_but_one_participating_with_duplicates(spec, state):
yield from _test_harness_for_randomized_test_case(
spec,
state,
duplicates=True,
expect_duplicates=True,
participation_fn=lambda comm: rng.sample(comm, len(comm) - 1),
)
@ -98,7 +107,25 @@ def test_random_misc_balances_and_half_participation_with_duplicates(spec, state
yield from _test_harness_for_randomized_test_case(
spec,
state,
duplicates=True,
expect_duplicates=True,
participation_fn=lambda comm: rng.sample(comm, len(comm) // 2),
)
@with_altair_and_later
@with_presets([MAINNET], reason="to create duplicate committee")
@spec_state_test
@single_phase
def test_random_with_exits_with_duplicates(spec, state):
rng = random.Random(1402)
randomize_state(spec, state, rng=rng, exit_fraction=0.1, slash_fraction=0.0)
target_validators = get_unslashed_exited_validators(spec, state)
assert len(target_validators) != 0
assert has_active_balance_differential(spec, state)
yield from _test_harness_for_randomized_test_case(
spec,
state,
expect_duplicates=True,
participation_fn=lambda comm: rng.sample(comm, len(comm) // 2),
)
@ -163,3 +190,20 @@ def test_random_misc_balances_and_half_participation_without_duplicates(spec, st
state,
participation_fn=lambda comm: rng.sample(comm, len(comm) // 2),
)
@with_altair_and_later
@with_presets([MINIMAL], reason="to create nonduplicate committee")
@spec_state_test
@single_phase
def test_random_with_exits_without_duplicates(spec, state):
rng = random.Random(1502)
randomize_state(spec, state, rng=rng, exit_fraction=0.1, slash_fraction=0.0)
target_validators = get_unslashed_exited_validators(spec, state)
assert len(target_validators) != 0
assert has_active_balance_differential(spec, state)
yield from _test_harness_for_randomized_test_case(
spec,
state,
participation_fn=lambda comm: rng.sample(comm, len(comm) // 2),
)

View File

@ -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 ALTAIR
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([ALTAIR])
@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_altair_with_cycling_sync_committee_participation
# 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_altair_with_cycling_sync_committee_participation
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_altair_with_cycling_sync_committee_participation', '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_altair_with_cycling_sync_committee_participation', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}], 'state_randomizer': 'randomize_state_altair'} # 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([ALTAIR])
@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_altair_with_cycling_sync_committee_participation
# epochs:0,slots:0,with-block:no_block
# epochs:0,slots:0,with-block:no_block
# epochs:0,slots:0,with-block:random_block_altair_with_cycling_sync_committee_participation
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_altair_with_cycling_sync_committee_participation', '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_altair_with_cycling_sync_committee_participation', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}], 'state_randomizer': 'randomize_state_altair'} # 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([ALTAIR])
@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_altair_with_cycling_sync_committee_participation
# 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_altair_with_cycling_sync_committee_participation
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_altair_with_cycling_sync_committee_participation', '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_altair_with_cycling_sync_committee_participation', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}], 'state_randomizer': 'randomize_state_altair'} # 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([ALTAIR])
@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_altair_with_cycling_sync_committee_participation
# 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_altair_with_cycling_sync_committee_participation
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_altair_with_cycling_sync_committee_participation', '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_altair_with_cycling_sync_committee_participation', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}], 'state_randomizer': 'randomize_state_altair'} # 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([ALTAIR])
@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_altair_with_cycling_sync_committee_participation
# 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_altair_with_cycling_sync_committee_participation
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_altair_with_cycling_sync_committee_participation', '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_altair_with_cycling_sync_committee_participation', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}], 'state_randomizer': 'randomize_state_altair'} # 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([ALTAIR])
@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_altair_with_cycling_sync_committee_participation
# 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_altair_with_cycling_sync_committee_participation
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_altair_with_cycling_sync_committee_participation', '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_altair_with_cycling_sync_committee_participation', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}], 'state_randomizer': 'randomize_state_altair'} # 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([ALTAIR])
@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_altair_with_cycling_sync_committee_participation
# 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_altair_with_cycling_sync_committee_participation
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_altair_with_cycling_sync_committee_participation', '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_altair_with_cycling_sync_committee_participation', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}], 'state_randomizer': 'randomize_state_altair'} # 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([ALTAIR])
@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_altair_with_cycling_sync_committee_participation
# epochs:1,slots:0,with-block:no_block
# epochs:0,slots:0,with-block:no_block
# epochs:0,slots:0,with-block:random_block_altair_with_cycling_sync_committee_participation
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_altair_with_cycling_sync_committee_participation', '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_altair_with_cycling_sync_committee_participation', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}], 'state_randomizer': 'randomize_state_altair'} # 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([ALTAIR])
@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_altair_with_cycling_sync_committee_participation
# 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_altair_with_cycling_sync_committee_participation
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_altair_with_cycling_sync_committee_participation', '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_altair_with_cycling_sync_committee_participation', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}], 'state_randomizer': 'randomize_state_altair'} # 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([ALTAIR])
@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_altair_with_cycling_sync_committee_participation
# epochs:0,slots:0,with-block:no_block
# epochs:0,slots:0,with-block:no_block
# epochs:0,slots:0,with-block:random_block_altair_with_cycling_sync_committee_participation
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_altair_with_cycling_sync_committee_participation', '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_altair_with_cycling_sync_committee_participation', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}], 'state_randomizer': 'randomize_state_altair'} # 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([ALTAIR])
@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_altair_with_cycling_sync_committee_participation
# 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_altair_with_cycling_sync_committee_participation
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_altair_with_cycling_sync_committee_participation', '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_altair_with_cycling_sync_committee_participation', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}], 'state_randomizer': 'randomize_state_altair'} # 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([ALTAIR])
@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_altair_with_cycling_sync_committee_participation
# 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_altair_with_cycling_sync_committee_participation
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_altair_with_cycling_sync_committee_participation', '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_altair_with_cycling_sync_committee_participation', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}], 'state_randomizer': 'randomize_state_altair'} # 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([ALTAIR])
@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_altair_with_cycling_sync_committee_participation
# 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_altair_with_cycling_sync_committee_participation
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_altair_with_cycling_sync_committee_participation', '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_altair_with_cycling_sync_committee_participation', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}], 'state_randomizer': 'randomize_state_altair'} # 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([ALTAIR])
@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_altair_with_cycling_sync_committee_participation
# 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_altair_with_cycling_sync_committee_participation
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_altair_with_cycling_sync_committee_participation', '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_altair_with_cycling_sync_committee_participation', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}], 'state_randomizer': 'randomize_state_altair'} # 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([ALTAIR])
@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_altair_with_cycling_sync_committee_participation
# 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_altair_with_cycling_sync_committee_participation
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_altair_with_cycling_sync_committee_participation', '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_altair_with_cycling_sync_committee_participation', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}], 'state_randomizer': 'randomize_state_altair'} # 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([ALTAIR])
@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_altair_with_cycling_sync_committee_participation
# epochs:1,slots:0,with-block:no_block
# epochs:0,slots:0,with-block:no_block
# epochs:0,slots:0,with-block:random_block_altair_with_cycling_sync_committee_participation
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_altair_with_cycling_sync_committee_participation', '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_altair_with_cycling_sync_committee_participation', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}], 'state_randomizer': 'randomize_state_altair'} # noqa: E501
yield from run_generated_randomized_test(
spec,
state,
scenario,
)

View File

@ -126,6 +126,17 @@ def default_balances(spec):
return [spec.MAX_EFFECTIVE_BALANCE] * num_validators
def scaled_churn_balances(spec):
"""
Helper method to create enough validators to scale the churn limit.
(This is *firmly* over the churn limit -- thus the +2 instead of just +1)
See the second argument of ``max`` in ``get_validator_churn_limit``.
Usage: `@with_custom_state(balances_fn=scaled_churn_balances, ...)`
"""
num_validators = spec.config.CHURN_LIMIT_QUOTIENT * (2 + spec.config.MIN_PER_EPOCH_CHURN_LIMIT)
return [spec.MAX_EFFECTIVE_BALANCE] * num_validators
with_state = with_custom_state(default_balances, default_activation_threshold)
@ -152,6 +163,22 @@ def misc_balances(spec):
return balances
def misc_balances_in_default_range_with_many_validators(spec):
"""
Helper method to create a series of balances that includes some misc. balances but
none that are below the ``EJECTION_BALANCE``.
"""
# Double validators to facilitate randomized testing
num_validators = spec.SLOTS_PER_EPOCH * 8 * 2
floor = spec.config.EJECTION_BALANCE + spec.EFFECTIVE_BALANCE_INCREMENT
balances = [
max(spec.MAX_EFFECTIVE_BALANCE * 2 * i // num_validators, floor) for i in range(num_validators)
]
rng = Random(1234)
rng.shuffle(balances)
return balances
def low_single_balance(spec):
"""
Helper method to create a single of balance of 1 Gwei.
@ -440,6 +467,17 @@ with_altair_and_later = with_phases([ALTAIR, MERGE])
with_merge_and_later = with_phases([MERGE]) # TODO: include sharding when spec stabilizes.
def only_generator(reason):
def _decorator(inner):
def _wrapper(*args, **kwargs):
if is_pytest:
dump_skipping_message(reason)
return None
return inner(*args, **kwargs)
return _wrapper
return _decorator
def fork_transition_test(pre_fork_name, post_fork_name, fork_epoch=None):
"""
A decorator to construct a "transition" test from one fork of the eth2 spec

View File

@ -156,9 +156,18 @@ def add_block(spec, store, signed_block, test_steps, valid=True, allow_invalid_a
'checks': {
'time': int(store.time),
'head': get_formatted_head_output(spec, store),
'justified_checkpoint_root': encode_hex(store.justified_checkpoint.root),
'finalized_checkpoint_root': encode_hex(store.finalized_checkpoint.root),
'best_justified_checkpoint': encode_hex(store.best_justified_checkpoint.root),
'justified_checkpoint': {
'epoch': int(store.justified_checkpoint.epoch),
'root': encode_hex(store.justified_checkpoint.root),
},
'finalized_checkpoint': {
'epoch': int(store.finalized_checkpoint.epoch),
'root': encode_hex(store.finalized_checkpoint.root),
},
'best_justified_checkpoint': {
'epoch': int(store.best_justified_checkpoint.epoch),
'root': encode_hex(store.best_justified_checkpoint.root),
},
}
})

View File

@ -7,6 +7,10 @@ from eth2spec.test.helpers.state import (
from eth2spec.test.helpers.block import (
build_empty_block_for_next_slot,
)
from eth2spec.test.helpers.sync_committee import (
compute_committee_indices,
compute_aggregate_sync_committee_signature,
)
from eth2spec.test.helpers.proposer_slashings import get_valid_proposer_slashing
from eth2spec.test.helpers.attester_slashings import get_valid_attester_slashing_by_indices
from eth2spec.test.helpers.attestations import get_valid_attestation
@ -44,8 +48,12 @@ def run_slash_and_exit(spec, state, slash_index, exit_index, valid=True):
def get_random_proposer_slashings(spec, state, rng):
num_slashings = rng.randrange(spec.MAX_PROPOSER_SLASHINGS)
indices = spec.get_active_validator_indices(state, spec.get_current_epoch(state)).copy()
num_slashings = rng.randrange(1, spec.MAX_PROPOSER_SLASHINGS)
active_indices = spec.get_active_validator_indices(state, spec.get_current_epoch(state)).copy()
indices = [
index for index in active_indices
if not state.validators[index].slashed
]
slashings = [
get_valid_proposer_slashing(
spec, state,
@ -56,14 +64,33 @@ def get_random_proposer_slashings(spec, state, rng):
return slashings
def get_random_attester_slashings(spec, state, rng):
num_slashings = rng.randrange(spec.MAX_ATTESTER_SLASHINGS)
indices = spec.get_active_validator_indices(state, spec.get_current_epoch(state)).copy()
def get_random_attester_slashings(spec, state, rng, slashed_indices=[]):
"""
Caller can supply ``slashed_indices`` if they are aware of other indices
that will be slashed by other operations in the same block as the one that
contains the output of this function.
"""
# ensure at least one attester slashing, the max count
# is small so not much room for random inclusion
num_slashings = rng.randrange(1, spec.MAX_ATTESTER_SLASHINGS)
active_indices = spec.get_active_validator_indices(state, spec.get_current_epoch(state)).copy()
indices = [
index for index in active_indices
if (
not state.validators[index].slashed
and index not in slashed_indices
)
]
sample_upper_bound = 4
max_slashed_count = num_slashings * sample_upper_bound - 1
if len(indices) < max_slashed_count:
return []
slot_range = list(range(state.slot - spec.SLOTS_PER_HISTORICAL_ROOT + 1, state.slot))
slashings = [
get_valid_attester_slashing_by_indices(
spec, state,
sorted([indices.pop(rng.randrange(len(indices))) for _ in range(rng.randrange(1, 4))]),
sorted([indices.pop(rng.randrange(len(indices))) for _ in range(rng.randrange(1, sample_upper_bound))]),
slot=slot_range.pop(rng.randrange(len(slot_range))),
signed_1=True, signed_2=True,
)
@ -73,7 +100,7 @@ def get_random_attester_slashings(spec, state, rng):
def get_random_attestations(spec, state, rng):
num_attestations = rng.randrange(spec.MAX_ATTESTATIONS)
num_attestations = rng.randrange(1, spec.MAX_ATTESTATIONS)
attestations = [
get_valid_attestation(
@ -86,8 +113,12 @@ def get_random_attestations(spec, state, rng):
return attestations
def prepare_state_and_get_random_deposits(spec, state, rng):
num_deposits = rng.randrange(spec.MAX_DEPOSITS)
def get_random_deposits(spec, state, rng, num_deposits=None):
if not num_deposits:
num_deposits = rng.randrange(1, spec.MAX_DEPOSITS)
if num_deposits == 0:
return [], b"\x00" * 32
deposit_data_leaves = [spec.DepositData() for _ in range(len(state.validators))]
deposits = []
@ -105,38 +136,82 @@ def prepare_state_and_get_random_deposits(spec, state, rng):
signed=True,
)
state.eth1_data.deposit_root = root
state.eth1_data.deposit_count += num_deposits
# Then for that context, build deposits/proofs
for i in range(num_deposits):
index = len(state.validators) + i
deposit, _, _ = deposit_from_context(spec, deposit_data_leaves, index)
deposits.append(deposit)
return deposits, root
def prepare_state_and_get_random_deposits(spec, state, rng, num_deposits=None):
deposits, root = get_random_deposits(spec, state, rng, num_deposits=num_deposits)
state.eth1_data.deposit_root = root
state.eth1_data.deposit_count += len(deposits)
return deposits
def _eligible_for_exit(spec, state, index):
validator = state.validators[index]
not_slashed = not validator.slashed
current_epoch = spec.get_current_epoch(state)
activation_epoch = validator.activation_epoch
active_for_long_enough = current_epoch >= activation_epoch + spec.config.SHARD_COMMITTEE_PERIOD
not_exited = validator.exit_epoch == spec.FAR_FUTURE_EPOCH
return not_slashed and active_for_long_enough and not_exited
def get_random_voluntary_exits(spec, state, to_be_slashed_indices, rng):
num_exits = rng.randrange(spec.MAX_VOLUNTARY_EXITS)
indices = set(spec.get_active_validator_indices(state, spec.get_current_epoch(state)).copy())
num_exits = rng.randrange(1, spec.MAX_VOLUNTARY_EXITS)
active_indices = set(spec.get_active_validator_indices(state, spec.get_current_epoch(state)).copy())
indices = set(
index for index in active_indices
if _eligible_for_exit(spec, state, index)
)
eligible_indices = indices - to_be_slashed_indices
exit_indices = [eligible_indices.pop() for _ in range(num_exits)]
indices_count = min(num_exits, len(eligible_indices))
exit_indices = [eligible_indices.pop() for _ in range(indices_count)]
return prepare_signed_exits(spec, state, exit_indices)
def run_test_full_random_operations(spec, state, rng=Random(2080)):
# move state forward SHARD_COMMITTEE_PERIOD epochs to allow for exit
state.slot += spec.config.SHARD_COMMITTEE_PERIOD * spec.SLOTS_PER_EPOCH
def get_random_sync_aggregate(spec, state, slot, block_root=None, fraction_participated=1.0, rng=Random(2099)):
committee_indices = compute_committee_indices(spec, state, state.current_sync_committee)
participant_count = int(len(committee_indices) * fraction_participated)
participant_indices = rng.sample(range(len(committee_indices)), participant_count)
participants = [
committee_indices[index]
for index in participant_indices
]
signature = compute_aggregate_sync_committee_signature(
spec,
state,
slot,
participants,
block_root=block_root,
)
return spec.SyncAggregate(
sync_committee_bits=[index in participant_indices for index in range(len(committee_indices))],
sync_committee_signature=signature,
)
# prepare state for deposits before building block
deposits = prepare_state_and_get_random_deposits(spec, state, rng)
def build_random_block_from_state_for_next_slot(spec, state, rng=Random(2188), deposits=None):
block = build_empty_block_for_next_slot(spec, state)
block.body.proposer_slashings = get_random_proposer_slashings(spec, state, rng)
block.body.attester_slashings = get_random_attester_slashings(spec, state, rng)
proposer_slashings = get_random_proposer_slashings(spec, state, rng)
block.body.proposer_slashings = proposer_slashings
slashed_indices = [
slashing.signed_header_1.message.proposer_index
for slashing in proposer_slashings
]
block.body.attester_slashings = get_random_attester_slashings(spec, state, rng, slashed_indices)
block.body.attestations = get_random_attestations(spec, state, rng)
block.body.deposits = deposits
if deposits:
block.body.deposits = deposits
# cannot include to be slashed indices as exits
slashed_indices = set([
@ -148,6 +223,17 @@ def run_test_full_random_operations(spec, state, rng=Random(2080)):
slashed_indices = slashed_indices.union(attester_slashing.attestation_2.attesting_indices)
block.body.voluntary_exits = get_random_voluntary_exits(spec, state, slashed_indices, rng)
return block
def run_test_full_random_operations(spec, state, rng=Random(2080)):
# move state forward SHARD_COMMITTEE_PERIOD epochs to allow for exit
state.slot += spec.config.SHARD_COMMITTEE_PERIOD * spec.SLOTS_PER_EPOCH
# prepare state for deposits before building block
deposits = prepare_state_and_get_random_deposits(spec, state, rng)
block = build_random_block_from_state_for_next_slot(spec, state, rng, deposits=deposits)
yield 'pre', state
signed_block = state_transition_and_sign_block(spec, state, block)

View File

@ -20,32 +20,40 @@ def set_some_new_deposits(spec, state, rng):
state.validators[index].activation_eligibility_epoch = spec.get_current_epoch(state)
def exit_random_validators(spec, state, rng):
def exit_random_validators(spec, state, rng, fraction=None):
if fraction is None:
# Exit ~1/2
fraction = 0.5
if spec.get_current_epoch(state) < 5:
# Move epochs forward to allow for some validators already exited/withdrawable
for _ in range(5):
next_epoch(spec, state)
current_epoch = spec.get_current_epoch(state)
# Exit ~1/2 of validators
for index in spec.get_active_validator_indices(state, current_epoch):
if rng.choice([True, False]):
sampled = rng.random() < fraction
if not sampled:
continue
validator = state.validators[index]
validator.exit_epoch = rng.choice([current_epoch - 1, current_epoch - 2, current_epoch - 3])
# ~1/2 are withdrawable
validator.exit_epoch = rng.choice([current_epoch, current_epoch - 1, current_epoch - 2, current_epoch - 3])
# ~1/2 are withdrawable (note, unnatural span between exit epoch and withdrawable epoch)
if rng.choice([True, False]):
validator.withdrawable_epoch = current_epoch
else:
validator.withdrawable_epoch = current_epoch + 1
def slash_random_validators(spec, state, rng):
# Slash ~1/2 of validators
def slash_random_validators(spec, state, rng, fraction=None):
if fraction is None:
# Slash ~1/2 of validators
fraction = 0.5
for index in range(len(state.validators)):
# slash at least one validator
if index == 0 or rng.choice([True, False]):
sampled = rng.random() < fraction
if index == 0 or sampled:
spec.slash_validator(state, index)
@ -115,8 +123,39 @@ def randomize_attestation_participation(spec, state, rng=Random(8020)):
randomize_epoch_participation(spec, state, spec.get_current_epoch(state), rng)
def randomize_state(spec, state, rng=Random(8020)):
def randomize_state(spec, state, rng=Random(8020), exit_fraction=None, slash_fraction=None):
set_some_new_deposits(spec, state, rng)
exit_random_validators(spec, state, rng)
slash_random_validators(spec, state, rng)
exit_random_validators(spec, state, rng, fraction=exit_fraction)
slash_random_validators(spec, state, rng, fraction=slash_fraction)
randomize_attestation_participation(spec, state, rng)
def patch_state_to_non_leaking(spec, state):
"""
This function performs an irregular state transition so that:
1. the current justified checkpoint references the previous epoch
2. the previous justified checkpoint references the epoch before previous
3. the finalized checkpoint matches the previous justified checkpoint
The effects of this function are intended to offset randomization side effects
performed by other functionality in this module so that if the ``state`` was leaking,
then the ``state`` is not leaking after.
"""
state.justification_bits[0] = True
state.justification_bits[1] = True
previous_epoch = spec.get_previous_epoch(state)
previous_root = spec.get_block_root(state, previous_epoch)
previous_previous_epoch = max(spec.GENESIS_EPOCH, spec.Epoch(previous_epoch - 1))
previous_previous_root = spec.get_block_root(state, previous_previous_epoch)
state.previous_justified_checkpoint = spec.Checkpoint(
epoch=previous_previous_epoch,
root=previous_previous_root,
)
state.current_justified_checkpoint = spec.Checkpoint(
epoch=previous_epoch,
root=previous_root,
)
state.finalized_checkpoint = spec.Checkpoint(
epoch=previous_previous_epoch,
root=previous_previous_root,
)

View File

@ -255,7 +255,19 @@ def run_get_inactivity_penalty_deltas(spec, state):
else:
assert penalties[index] > base_penalty
else:
assert penalties[index] == 0
if not is_post_altair(spec):
assert penalties[index] == 0
continue
else:
# post altair, this penalty is derived from the inactivity score
# regardless if the state is leaking or not...
if index in matching_attesting_indices:
assert penalties[index] == 0
else:
# copied from spec:
penalty_numerator = state.validators[index].effective_balance * state.inactivity_scores[index]
penalty_denominator = spec.config.INACTIVITY_SCORE_BIAS * spec.INACTIVITY_PENALTY_QUOTIENT_ALTAIR
assert penalties[index] == penalty_numerator // penalty_denominator
def transition_state_to_leak(spec, state, epochs=None):

View File

@ -1,5 +1,6 @@
from eth2spec.test.context import expect_assertion_error, is_post_altair
from eth2spec.test.helpers.block import apply_empty_block, sign_block, transition_unsigned_block
from eth2spec.test.helpers.voluntary_exits import get_unslashed_exited_validators
def get_balance(state, index):
@ -133,3 +134,34 @@ def _set_empty_participation(spec, state, current=True, previous=True):
def set_empty_participation(spec, state, rng=None):
_set_empty_participation(spec, state)
def ensure_state_has_validators_across_lifecycle(spec, state):
"""
Scan the validator registry to ensure there is at least 1 validator
for each of the following lifecycle states:
1. Pending / deposited
2. Active
3. Exited (but not slashed)
4. Slashed
"""
has_pending = any(filter(spec.is_eligible_for_activation_queue, state.validators))
current_epoch = spec.get_current_epoch(state)
has_active = any(filter(lambda v: spec.is_active_validator(v, current_epoch), state.validators))
has_exited = any(get_unslashed_exited_validators(spec, state))
has_slashed = any(filter(lambda v: v.slashed, state.validators))
return has_pending and has_active and has_exited and has_slashed
def has_active_balance_differential(spec, state):
"""
Ensure there is a difference between the total balance of
all _active_ validators and _all_ validators.
"""
active_balance = spec.get_total_active_balance(state)
total_balance = spec.get_total_balance(state, set(range(len(state.validators))))
return active_balance // spec.EFFECTIVE_BALANCE_INCREMENT != total_balance // spec.EFFECTIVE_BALANCE_INCREMENT

View File

@ -9,7 +9,6 @@ from eth2spec.test.helpers.block import (
)
from eth2spec.test.helpers.block_processing import run_block_processing_to
from eth2spec.utils import bls
from eth2spec.utils.hash_function import hash
def compute_sync_committee_signature(spec, state, slot, privkey, block_root=None, domain_type=None):
@ -75,10 +74,12 @@ def compute_sync_committee_proposer_reward(spec, state, committee_indices, commi
return spec.Gwei(participant_reward * participant_number)
def compute_committee_indices(spec, state, committee):
def compute_committee_indices(spec, state, committee=None):
"""
Given a ``committee``, calculate and return the related indices
"""
if committee is None:
committee = state.current_sync_committee
all_pubkeys = [v.pubkey for v in state.validators]
return [all_pubkeys.index(pubkey) for pubkey in committee.pubkeys]
@ -153,6 +154,7 @@ def _build_block_for_next_slot_with_sync_participation(spec, state, committee_in
state,
block.slot - 1,
[index for index, bit in zip(committee_indices, committee_bits) if bit],
block_root=block.parent_root,
)
)
return block
@ -161,23 +163,3 @@ def _build_block_for_next_slot_with_sync_participation(spec, state, committee_in
def run_successful_sync_committee_test(spec, state, committee_indices, committee_bits):
block = _build_block_for_next_slot_with_sync_participation(spec, state, committee_indices, committee_bits)
yield from run_sync_committee_processing(spec, state, block)
def get_committee_indices(spec, state, duplicates=False):
"""
This utility function allows the caller to ensure there are or are not
duplicate validator indices in the returned committee based on
the boolean ``duplicates``.
"""
state = state.copy()
current_epoch = spec.get_current_epoch(state)
randao_index = (current_epoch + 1) % spec.EPOCHS_PER_HISTORICAL_VECTOR
while True:
committee = spec.get_next_sync_committee_indices(state)
if duplicates:
if len(committee) != len(set(committee)):
return committee
else:
if len(committee) == len(set(committee)):
return committee
state.randao_mixes[randao_index] = hash(state.randao_mixes[randao_index])

View File

@ -34,6 +34,13 @@ def get_exited_validators(spec, state):
return [index for (index, validator) in enumerate(state.validators) if validator.exit_epoch <= current_epoch]
def get_unslashed_exited_validators(spec, state):
return [
index for index in get_exited_validators(spec, state)
if not state.validators[index].slashed
]
def exit_validators(spec, state, validator_count, rng=None):
if rng is None:
rng = Random(1337)

View File

@ -1,4 +1,10 @@
from eth2spec.test.context import spec_state_test, expect_assertion_error, always_bls, with_all_phases
from eth2spec.test.helpers.constants import MINIMAL
from eth2spec.test.context import (
spec_state_test, expect_assertion_error,
always_bls, with_all_phases, with_presets,
spec_test, single_phase,
with_custom_state, scaled_churn_balances,
)
from eth2spec.test.helpers.keys import pubkey_to_privkey
from eth2spec.test.helpers.voluntary_exits import sign_voluntary_exit
@ -68,9 +74,7 @@ def test_invalid_signature(spec, state):
yield from run_voluntary_exit_processing(spec, state, signed_voluntary_exit, False)
@with_all_phases
@spec_state_test
def test_success_exit_queue(spec, state):
def run_test_success_exit_queue(spec, state):
# move state forward SHARD_COMMITTEE_PERIOD epochs to allow for exit
state.slot += spec.config.SHARD_COMMITTEE_PERIOD * spec.SLOTS_PER_EPOCH
@ -106,10 +110,29 @@ def test_success_exit_queue(spec, state):
# when processing an additional exit, it results in an exit in a later epoch
yield from run_voluntary_exit_processing(spec, state, signed_voluntary_exit)
assert (
state.validators[validator_index].exit_epoch ==
state.validators[initial_indices[0]].exit_epoch + 1
)
for index in initial_indices:
assert (
state.validators[validator_index].exit_epoch ==
state.validators[index].exit_epoch + 1
)
@with_all_phases
@spec_state_test
def test_success_exit_queue__min_churn(spec, state):
yield from run_test_success_exit_queue(spec, state)
@with_all_phases
@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=scaled_churn_balances, threshold_fn=lambda spec: spec.config.EJECTION_BALANCE)
@single_phase
def test_success_exit_queue__scaled_churn(spec, state):
churn_limit = spec.get_validator_churn_limit(state)
assert churn_limit > spec.config.MIN_PER_EPOCH_CHURN_LIMIT
yield from run_test_success_exit_queue(spec, state)
@with_all_phases

View File

@ -1,8 +1,10 @@
from random import Random
from eth2spec.test.context import is_post_altair, spec_state_test, with_all_phases
from eth2spec.test.helpers.epoch_processing import (
run_epoch_processing_with,
)
from eth2spec.test.helpers.state import transition_to
from eth2spec.test.helpers.state import transition_to, next_epoch_via_block, next_slot
from eth2spec.test.helpers.voluntary_exits import get_unslashed_exited_validators
def run_process_just_and_fin(spec, state):
@ -300,3 +302,76 @@ def test_12_ok_support_messed_target(spec, state):
@spec_state_test
def test_12_poor_support(spec, state):
yield from finalize_on_12(spec, state, 3, False, False)
@with_all_phases
@spec_state_test
def test_balance_threshold_with_exited_validators(spec, state):
"""
This test exercises a very specific failure mode where
exited validators are incorrectly included in the total active balance
when weighing justification.
"""
rng = Random(133333)
# move past genesis conditions
for _ in range(3):
next_epoch_via_block(spec, state)
# mock attestation helper requires last slot of epoch
for _ in range(spec.SLOTS_PER_EPOCH - 1):
next_slot(spec, state)
# Step 1: Exit ~1/2 vals in current epoch
epoch = spec.get_current_epoch(state)
for index in spec.get_active_validator_indices(state, epoch):
if rng.choice([True, False]):
continue
validator = state.validators[index]
validator.exit_epoch = epoch
validator.withdrawable_epoch = epoch + 1
validator.withdrawable_epoch = validator.exit_epoch + spec.config.MIN_VALIDATOR_WITHDRAWABILITY_DELAY
exited_validators = get_unslashed_exited_validators(spec, state)
assert len(exited_validators) != 0
source = state.current_justified_checkpoint
target = spec.Checkpoint(
epoch=epoch,
root=spec.get_block_root(state, epoch)
)
add_mock_attestations(
spec,
state,
epoch,
source,
target,
sufficient_support=False,
)
if not is_post_altair(spec):
current_attestations = spec.get_matching_target_attestations(state, epoch)
total_active_balance = spec.get_total_active_balance(state)
current_target_balance = spec.get_attesting_balance(state, current_attestations)
# Check we will not justify the current checkpoint
does_justify = current_target_balance * 3 >= total_active_balance * 2
assert not does_justify
# Ensure we would have justified the current checkpoint w/ the exited validators
current_exited_balance = spec.get_total_balance(state, exited_validators)
does_justify = (current_target_balance + current_exited_balance) * 3 >= total_active_balance * 2
assert does_justify
else:
current_indices = spec.get_unslashed_participating_indices(state, spec.TIMELY_TARGET_FLAG_INDEX, epoch)
total_active_balance = spec.get_total_active_balance(state)
current_target_balance = spec.get_total_balance(state, current_indices)
# Check we will not justify the current checkpoint
does_justify = current_target_balance * 3 >= total_active_balance * 2
assert not does_justify
# Ensure we would have justified the current checkpoint w/ the exited validators
current_exited_balance = spec.get_total_balance(state, exited_validators)
does_justify = (current_target_balance + current_exited_balance) * 3 >= total_active_balance * 2
assert does_justify
yield from run_process_just_and_fin(spec, state)
assert state.current_justified_checkpoint.epoch != epoch

View File

@ -1,6 +1,12 @@
from eth2spec.test.helpers.deposits import mock_deposit
from eth2spec.test.helpers.state import next_epoch, next_slots
from eth2spec.test.context import spec_state_test, with_all_phases
from eth2spec.test.helpers.constants import MINIMAL
from eth2spec.test.context import (
spec_test, spec_state_test,
with_all_phases, single_phase,
with_custom_state, with_presets,
scaled_churn_balances,
)
from eth2spec.test.helpers.epoch_processing import run_epoch_processing_with
@ -112,9 +118,7 @@ def test_activation_queue_sorting(spec, state):
assert state.validators[churn_limit - 1].activation_epoch != spec.FAR_FUTURE_EPOCH
@with_all_phases
@spec_state_test
def test_activation_queue_efficiency(spec, state):
def run_test_activation_queue_efficiency(spec, state):
churn_limit = spec.get_validator_churn_limit(state)
mock_activations = churn_limit * 2
@ -128,23 +132,45 @@ def test_activation_queue_efficiency(spec, state):
state.finalized_checkpoint.epoch = epoch + 1
# Churn limit could have changed given the active vals removed via `mock_deposit`
churn_limit_0 = spec.get_validator_churn_limit(state)
# Run first registry update. Do not yield test vectors
for _ in run_process_registry_updates(spec, state):
pass
# Half should churn in first run of registry update
for i in range(mock_activations):
if i < mock_activations // 2:
if i < churn_limit_0:
assert state.validators[i].activation_epoch < spec.FAR_FUTURE_EPOCH
else:
assert state.validators[i].activation_epoch == spec.FAR_FUTURE_EPOCH
# Second half should churn in second run of registry update
churn_limit_1 = spec.get_validator_churn_limit(state)
yield from run_process_registry_updates(spec, state)
for i in range(mock_activations):
for i in range(churn_limit_0 + churn_limit_1):
assert state.validators[i].activation_epoch < spec.FAR_FUTURE_EPOCH
@with_all_phases
@spec_state_test
def test_activation_queue_efficiency_min(spec, state):
assert spec.get_validator_churn_limit(state) == spec.config.MIN_PER_EPOCH_CHURN_LIMIT
yield from run_test_activation_queue_efficiency(spec, state)
@with_all_phases
@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=scaled_churn_balances, threshold_fn=lambda spec: spec.config.EJECTION_BALANCE)
@single_phase
def test_activation_queue_efficiency_scaled(spec, state):
assert spec.get_validator_churn_limit(state) > spec.config.MIN_PER_EPOCH_CHURN_LIMIT
yield from run_test_activation_queue_efficiency(spec, state)
@with_all_phases
@spec_state_test
def test_ejection(spec, state):
@ -165,9 +191,7 @@ def test_ejection(spec, state):
)
@with_all_phases
@spec_state_test
def test_ejection_past_churn_limit(spec, state):
def run_test_ejection_past_churn_limit(spec, state):
churn_limit = spec.get_validator_churn_limit(state)
# try to eject more than per-epoch churn limit
@ -184,58 +208,137 @@ def test_ejection_past_churn_limit(spec, state):
# first third ejected in normal speed
if i < mock_ejections // 3:
assert state.validators[i].exit_epoch == expected_ejection_epoch
# second thirdgets delayed by 1 epoch
# second third gets delayed by 1 epoch
elif mock_ejections // 3 <= i < mock_ejections * 2 // 3:
assert state.validators[i].exit_epoch == expected_ejection_epoch + 1
# second thirdgets delayed by 2 epochs
# final third gets delayed by 2 epochs
else:
assert state.validators[i].exit_epoch == expected_ejection_epoch + 2
@with_all_phases
@spec_state_test
def test_activation_queue_activation_and_ejection(spec, state):
def test_ejection_past_churn_limit_min(spec, state):
assert spec.get_validator_churn_limit(state) == spec.config.MIN_PER_EPOCH_CHURN_LIMIT
yield from run_test_ejection_past_churn_limit(spec, state)
@with_all_phases
@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=scaled_churn_balances, threshold_fn=lambda spec: spec.config.EJECTION_BALANCE)
@single_phase
def test_ejection_past_churn_limit_scaled(spec, state):
assert spec.get_validator_churn_limit(state) > spec.config.MIN_PER_EPOCH_CHURN_LIMIT
yield from run_test_ejection_past_churn_limit(spec, state)
def run_test_activation_queue_activation_and_ejection(spec, state, num_per_status):
# move past first two irregular epochs wrt finality
next_epoch(spec, state)
next_epoch(spec, state)
# ready for entrance into activation queue
activation_queue_index = 0
mock_deposit(spec, state, activation_queue_index)
activation_queue_start_index = 0
activation_queue_indices = list(range(activation_queue_start_index, activation_queue_start_index + num_per_status))
for validator_index in activation_queue_indices:
mock_deposit(spec, state, validator_index)
# ready for activation
activation_index = 1
mock_deposit(spec, state, activation_index)
state.finalized_checkpoint.epoch = spec.get_current_epoch(state) - 1
state.validators[activation_index].activation_eligibility_epoch = state.finalized_checkpoint.epoch
activation_start_index = num_per_status
activation_indices = list(range(activation_start_index, activation_start_index + num_per_status))
for validator_index in activation_indices:
mock_deposit(spec, state, validator_index)
state.validators[validator_index].activation_eligibility_epoch = state.finalized_checkpoint.epoch
# ready for ejection
ejection_index = 2
state.validators[ejection_index].effective_balance = spec.config.EJECTION_BALANCE
ejection_start_index = num_per_status * 2
ejection_indices = list(range(ejection_start_index, ejection_start_index + num_per_status))
for validator_index in ejection_indices:
state.validators[validator_index].effective_balance = spec.config.EJECTION_BALANCE
churn_limit = spec.get_validator_churn_limit(state)
yield from run_process_registry_updates(spec, state)
# validator moved into activation queue
validator = state.validators[activation_queue_index]
assert validator.activation_eligibility_epoch != spec.FAR_FUTURE_EPOCH
assert validator.activation_epoch == spec.FAR_FUTURE_EPOCH
assert not spec.is_active_validator(validator, spec.get_current_epoch(state))
# all eligible validators moved into activation queue
for validator_index in activation_queue_indices:
validator = state.validators[validator_index]
assert validator.activation_eligibility_epoch != spec.FAR_FUTURE_EPOCH
assert validator.activation_epoch == spec.FAR_FUTURE_EPOCH
assert not spec.is_active_validator(validator, spec.get_current_epoch(state))
# validator activated for future epoch
validator = state.validators[activation_index]
assert validator.activation_eligibility_epoch != spec.FAR_FUTURE_EPOCH
assert validator.activation_epoch != spec.FAR_FUTURE_EPOCH
assert not spec.is_active_validator(validator, spec.get_current_epoch(state))
assert spec.is_active_validator(
validator,
spec.compute_activation_exit_epoch(spec.get_current_epoch(state))
)
# up to churn limit validators get activated for future epoch from the queue
for validator_index in activation_indices[:churn_limit]:
validator = state.validators[validator_index]
assert validator.activation_eligibility_epoch != spec.FAR_FUTURE_EPOCH
assert validator.activation_epoch != spec.FAR_FUTURE_EPOCH
assert not spec.is_active_validator(validator, spec.get_current_epoch(state))
assert spec.is_active_validator(
validator,
spec.compute_activation_exit_epoch(spec.get_current_epoch(state))
)
# validator ejected for future epoch
validator = state.validators[ejection_index]
assert validator.exit_epoch != spec.FAR_FUTURE_EPOCH
assert spec.is_active_validator(validator, spec.get_current_epoch(state))
assert not spec.is_active_validator(
validator,
spec.compute_activation_exit_epoch(spec.get_current_epoch(state))
)
# any remaining validators do not exit the activation queue
for validator_index in activation_indices[churn_limit:]:
validator = state.validators[validator_index]
assert validator.activation_eligibility_epoch != spec.FAR_FUTURE_EPOCH
assert validator.activation_epoch == spec.FAR_FUTURE_EPOCH
# all ejection balance validators ejected for a future epoch
for i, validator_index in enumerate(ejection_indices):
validator = state.validators[validator_index]
assert validator.exit_epoch != spec.FAR_FUTURE_EPOCH
assert spec.is_active_validator(validator, spec.get_current_epoch(state))
queue_offset = i // churn_limit
assert not spec.is_active_validator(
validator,
spec.compute_activation_exit_epoch(spec.get_current_epoch(state)) + queue_offset
)
@with_all_phases
@spec_state_test
def test_activation_queue_activation_and_ejection__1(spec, state):
yield from run_test_activation_queue_activation_and_ejection(spec, state, 1)
@with_all_phases
@spec_state_test
def test_activation_queue_activation_and_ejection__churn_limit(spec, state):
churn_limit = spec.get_validator_churn_limit(state)
assert churn_limit == spec.config.MIN_PER_EPOCH_CHURN_LIMIT
yield from run_test_activation_queue_activation_and_ejection(spec, state, churn_limit)
@with_all_phases
@spec_state_test
def test_activation_queue_activation_and_ejection__exceed_churn_limit(spec, state):
churn_limit = spec.get_validator_churn_limit(state)
assert churn_limit == spec.config.MIN_PER_EPOCH_CHURN_LIMIT
yield from run_test_activation_queue_activation_and_ejection(spec, state, churn_limit + 1)
@with_all_phases
@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=scaled_churn_balances, threshold_fn=lambda spec: spec.config.EJECTION_BALANCE)
@single_phase
def test_activation_queue_activation_and_ejection__scaled_churn_limit(spec, state):
churn_limit = spec.get_validator_churn_limit(state)
assert churn_limit > spec.config.MIN_PER_EPOCH_CHURN_LIMIT
yield from run_test_activation_queue_activation_and_ejection(spec, state, churn_limit)
@with_all_phases
@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=scaled_churn_balances, threshold_fn=lambda spec: spec.config.EJECTION_BALANCE)
@single_phase
def test_activation_queue_activation_and_ejection__exceed_scaled_churn_limit(spec, state):
churn_limit = spec.get_validator_churn_limit(state)
assert churn_limit > spec.config.MIN_PER_EPOCH_CHURN_LIMIT
yield from run_test_activation_queue_activation_and_ejection(spec, state, churn_limit * 2)

View File

@ -1,7 +1,11 @@
from random import Random
from eth2spec.test.context import spec_state_test, with_all_phases, is_post_altair
from eth2spec.test.helpers.epoch_processing import (
run_epoch_processing_with, run_epoch_processing_to
)
from eth2spec.test.helpers.random import randomize_state
from eth2spec.test.helpers.state import has_active_balance_differential
from eth2spec.test.helpers.voluntary_exits import get_unslashed_exited_validators
from eth2spec.test.helpers.state import next_epoch
@ -22,6 +26,9 @@ def slash_validators(spec, state, indices, out_epochs):
spec.get_current_epoch(state) % spec.EPOCHS_PER_SLASHINGS_VECTOR
] = total_slashed_balance
# verify some slashings happened...
assert total_slashed_balance != 0
def get_slashing_multiplier(spec):
if is_post_altair(spec):
@ -30,9 +37,7 @@ def get_slashing_multiplier(spec):
return spec.PROPORTIONAL_SLASHING_MULTIPLIER
@with_all_phases
@spec_state_test
def test_max_penalties(spec, state):
def _setup_process_slashings_test(spec, state, not_slashable_set=set()):
# Slashed count to ensure that enough validators are slashed to induce maximum penalties
slashed_count = min(
(len(state.validators) // get_slashing_multiplier(spec)) + 1,
@ -41,14 +46,23 @@ def test_max_penalties(spec, state):
)
out_epoch = spec.get_current_epoch(state) + (spec.EPOCHS_PER_SLASHINGS_VECTOR // 2)
slashed_indices = list(range(slashed_count))
slash_validators(spec, state, slashed_indices, [out_epoch] * slashed_count)
eligible_indices = set(range(slashed_count))
slashed_indices = eligible_indices.difference(not_slashable_set)
slash_validators(spec, state, sorted(slashed_indices), [out_epoch] * slashed_count)
total_balance = spec.get_total_active_balance(state)
total_penalties = sum(state.slashings)
assert total_balance // get_slashing_multiplier(spec) <= total_penalties
return slashed_indices
@with_all_phases
@spec_state_test
def test_max_penalties(spec, state):
slashed_indices = _setup_process_slashings_test(spec, state)
yield from run_process_slashings(spec, state)
for i in slashed_indices:
@ -171,3 +185,28 @@ def test_scaled_penalties(spec, state):
* spec.EFFECTIVE_BALANCE_INCREMENT
)
assert state.balances[i] == pre_slash_balances[i] - expected_penalty
@with_all_phases
@spec_state_test
def test_slashings_with_random_state(spec, state):
rng = Random(9998)
randomize_state(spec, state, rng)
pre_balances = state.balances.copy()
target_validators = get_unslashed_exited_validators(spec, state)
assert len(target_validators) != 0
assert has_active_balance_differential(spec, state)
slashed_indices = _setup_process_slashings_test(spec, state, not_slashable_set=target_validators)
# ensure no accidental slashings of protected set...
current_target_validators = get_unslashed_exited_validators(spec, state)
assert len(current_target_validators) != 0
assert current_target_validators == target_validators
yield from run_process_slashings(spec, state)
for i in slashed_indices:
assert state.balances[i] < pre_balances[i]

View File

@ -26,7 +26,6 @@ from eth2spec.test.helpers.state import (
next_epoch,
next_slots,
state_transition_and_sign_block,
transition_to,
)
@ -191,6 +190,10 @@ def test_on_block_before_finalized(spec, state):
@spec_state_test
@with_presets([MINIMAL], reason="too slow")
def test_on_block_finalized_skip_slots(spec, state):
"""
Test case was originally from https://github.com/ethereum/consensus-specs/pull/1579
And then rewrote largely.
"""
test_steps = []
# Initialization
store, anchor_block = get_genesis_forkchoice_store_and_block(spec, state)
@ -200,21 +203,31 @@ def test_on_block_finalized_skip_slots(spec, state):
on_tick_and_append_step(spec, store, current_time, test_steps)
assert store.time == current_time
# Create a finalized chain
for _ in range(4):
# Fill epoch 0 and the first slot of epoch 1
state, store, _ = yield from apply_next_slots_with_attestations(
spec, state, store, spec.SLOTS_PER_EPOCH, True, False, test_steps)
# Skip the rest slots of epoch 1 and the first slot of epoch 2
next_slots(spec, state, spec.SLOTS_PER_EPOCH)
# The state after the skipped slots
target_state = state.copy()
# Fill epoch 3 and 4
for _ in range(2):
state, store, _ = yield from apply_next_epoch_with_attestations(
spec, state, store, True, False, test_steps=test_steps)
assert store.finalized_checkpoint.epoch == 2
spec, state, store, True, True, test_steps=test_steps)
# Another chain
another_state = store.block_states[store.finalized_checkpoint.root].copy()
# Build block that includes the skipped slots up to finality in chain
block = build_empty_block(spec,
another_state,
spec.compute_start_slot_at_epoch(store.finalized_checkpoint.epoch) + 2)
block.body.graffiti = b'\x12' * 32
signed_block = state_transition_and_sign_block(spec, another_state, block)
# Now we get finalized epoch 2, where `compute_start_slot_at_epoch(2)` is a skipped slot
assert state.finalized_checkpoint.epoch == store.finalized_checkpoint.epoch == 2
assert store.finalized_checkpoint.root == spec.get_block_root(state, 1) == spec.get_block_root(state, 2)
assert state.current_justified_checkpoint.epoch == store.justified_checkpoint.epoch == 3
assert store.justified_checkpoint == state.current_justified_checkpoint
# Now build a block at later slot than finalized *epoch*
# Includes finalized block in chain and the skipped slots
block = build_empty_block_for_next_slot(spec, target_state)
signed_block = state_transition_and_sign_block(spec, target_state, block)
yield from tick_and_add_block(spec, store, signed_block, test_steps)
yield 'steps', test_steps
@ -224,36 +237,43 @@ def test_on_block_finalized_skip_slots(spec, state):
@spec_state_test
@with_presets([MINIMAL], reason="too slow")
def test_on_block_finalized_skip_slots_not_in_skip_chain(spec, state):
"""
Test case was originally from https://github.com/ethereum/consensus-specs/pull/1579
And then rewrote largely.
"""
test_steps = []
# Initialization
transition_to(spec, state, state.slot + spec.SLOTS_PER_EPOCH - 1)
block = build_empty_block_for_next_slot(spec, state)
transition_unsigned_block(spec, state, block)
block.state_root = state.hash_tree_root()
store = spec.get_forkchoice_store(state, block)
store, anchor_block = get_genesis_forkchoice_store_and_block(spec, state)
yield 'anchor_state', state
yield 'anchor_block', block
yield 'anchor_block', anchor_block
current_time = state.slot * spec.config.SECONDS_PER_SLOT + store.genesis_time
on_tick_and_append_step(spec, store, current_time, test_steps)
assert store.time == current_time
pre_finalized_checkpoint_epoch = store.finalized_checkpoint.epoch
# Fill epoch 0 and the first slot of epoch 1
state, store, _ = yield from apply_next_slots_with_attestations(
spec, state, store, spec.SLOTS_PER_EPOCH, True, False, test_steps)
# Finalized
for _ in range(3):
# Skip the rest slots of epoch 1 and the first slot of epoch 2
next_slots(spec, state, spec.SLOTS_PER_EPOCH)
# Fill epoch 3 and 4
for _ in range(2):
state, store, _ = yield from apply_next_epoch_with_attestations(
spec, state, store, True, False, test_steps=test_steps)
assert store.finalized_checkpoint.epoch == pre_finalized_checkpoint_epoch + 1
spec, state, store, True, True, test_steps=test_steps)
# Now build a block at later slot than finalized epoch
# Includes finalized block in chain, but not at appropriate skip slot
pre_state = store.block_states[block.hash_tree_root()].copy()
block = build_empty_block(spec,
state=pre_state,
slot=spec.compute_start_slot_at_epoch(store.finalized_checkpoint.epoch) + 2)
block.body.graffiti = b'\x12' * 32
signed_block = sign_block(spec, pre_state, block)
# Now we get finalized epoch 2, where `compute_start_slot_at_epoch(2)` is a skipped slot
assert state.finalized_checkpoint.epoch == store.finalized_checkpoint.epoch == 2
assert store.finalized_checkpoint.root == spec.get_block_root(state, 1) == spec.get_block_root(state, 2)
assert state.current_justified_checkpoint.epoch == store.justified_checkpoint.epoch == 3
assert store.justified_checkpoint == state.current_justified_checkpoint
# Now build a block after the block of the finalized **root**
# Includes finalized block in chain, but does not include finalized skipped slots
another_state = store.block_states[store.finalized_checkpoint.root].copy()
assert another_state.slot == spec.compute_start_slot_at_epoch(store.finalized_checkpoint.epoch - 1)
block = build_empty_block_for_next_slot(spec, another_state)
signed_block = state_transition_and_sign_block(spec, another_state, block)
yield from tick_and_add_block(spec, store, signed_block, test_steps, valid=False)
yield 'steps', test_steps
@ -483,7 +503,8 @@ def test_new_justified_is_later_than_store_justified(spec, state):
assert fork_2_state.finalized_checkpoint.epoch == 0
assert fork_2_state.current_justified_checkpoint.epoch == 5
# Check SAFE_SLOTS_TO_UPDATE_JUSTIFIED
spec.on_tick(store, store.genesis_time + fork_2_state.slot * spec.config.SECONDS_PER_SLOT)
time = store.genesis_time + fork_2_state.slot * spec.config.SECONDS_PER_SLOT
on_tick_and_append_step(spec, store, time, test_steps)
assert spec.compute_slots_since_epoch_start(spec.get_current_slot(store)) >= spec.SAFE_SLOTS_TO_UPDATE_JUSTIFIED
# Run on_block
yield from add_block(spec, store, signed_block, test_steps)
@ -526,7 +547,8 @@ def test_new_justified_is_later_than_store_justified(spec, state):
# # Apply blocks of `fork_3_state` to `store`
# for block in all_blocks:
# if store.time < spec.compute_time_at_slot(fork_2_state, block.message.slot):
# spec.on_tick(store, store.genesis_time + block.message.slot * spec.config.SECONDS_PER_SLOT)
# time = store.genesis_time + block.message.slot * spec.config.SECONDS_PER_SLOT
# on_tick_and_append_step(spec, store, time, test_steps)
# # valid_attestations=False because the attestations are outdated (older than previous epoch)
# yield from add_block(spec, store, block, test_steps, allow_invalid_attestations=False)
@ -643,7 +665,6 @@ def test_new_finalized_slot_is_justified_checkpoint_ancestor(spec, state):
# Process state
next_epoch(spec, state)
spec.on_tick(store, store.genesis_time + state.slot * spec.config.SECONDS_PER_SLOT)
state, store, _ = yield from apply_next_epoch_with_attestations(
spec, state, store, False, True, test_steps=test_steps)

View File

@ -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 PHASE0
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([PHASE0])
@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
# 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
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', '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', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}], 'state_randomizer': 'randomize_state'} # 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([PHASE0])
@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
# epochs:0,slots:0,with-block:no_block
# epochs:0,slots:0,with-block:no_block
# epochs:0,slots:0,with-block:random_block
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', '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', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}], 'state_randomizer': 'randomize_state'} # 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([PHASE0])
@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
# 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
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', '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', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}], 'state_randomizer': 'randomize_state'} # 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([PHASE0])
@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
# 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
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', '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', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}], 'state_randomizer': 'randomize_state'} # 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([PHASE0])
@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
# 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
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', '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', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}], 'state_randomizer': 'randomize_state'} # 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([PHASE0])
@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
# 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
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', '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', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}], 'state_randomizer': 'randomize_state'} # 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([PHASE0])
@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
# 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
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', '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', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}], 'state_randomizer': 'randomize_state'} # 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([PHASE0])
@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
# epochs:1,slots:0,with-block:no_block
# epochs:0,slots:0,with-block:no_block
# epochs:0,slots:0,with-block:random_block
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', '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', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}], 'state_randomizer': 'randomize_state'} # 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([PHASE0])
@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
# 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
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', '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', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}], 'state_randomizer': 'randomize_state'} # 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([PHASE0])
@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
# epochs:0,slots:0,with-block:no_block
# epochs:0,slots:0,with-block:no_block
# epochs:0,slots:0,with-block:random_block
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', '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', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}], 'state_randomizer': 'randomize_state'} # 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([PHASE0])
@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
# 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
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', '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', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}], 'state_randomizer': 'randomize_state'} # 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([PHASE0])
@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
# 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
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', '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', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}], 'state_randomizer': 'randomize_state'} # 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([PHASE0])
@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
# 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
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', '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', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}], 'state_randomizer': 'randomize_state'} # 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([PHASE0])
@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
# 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
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', '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', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}], 'state_randomizer': 'randomize_state'} # 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([PHASE0])
@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
# 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
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', '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', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}], 'state_randomizer': 'randomize_state'} # 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([PHASE0])
@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
# epochs:1,slots:0,with-block:no_block
# epochs:0,slots:0,with-block:no_block
# epochs:0,slots:0,with-block:random_block
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', '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', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}], 'state_randomizer': 'randomize_state'} # noqa: E501
yield from run_generated_randomized_test(
spec,
state,
scenario,
)

View File

@ -9,6 +9,13 @@ from eth2spec.test.context import (
low_balances, misc_balances,
)
import eth2spec.test.helpers.rewards as rewards_helpers
from eth2spec.test.helpers.random import (
randomize_state,
patch_state_to_non_leaking,
randomize_attestation_participation,
)
from eth2spec.test.helpers.state import has_active_balance_differential, next_epoch
from eth2spec.test.helpers.voluntary_exits import get_unslashed_exited_validators
@with_all_phases
@ -35,6 +42,21 @@ def test_full_random_3(spec, state):
yield from rewards_helpers.run_test_full_random(spec, state, rng=Random(4040))
@with_all_phases
@spec_state_test
def test_full_random_4(spec, state):
"""
Ensure a rewards test with some exited (but not slashed) validators.
"""
rng = Random(5050)
randomize_state(spec, state, rng)
assert spec.is_in_inactivity_leak(state)
target_validators = get_unslashed_exited_validators(spec, state)
assert len(target_validators) != 0
assert has_active_balance_differential(spec, state)
yield from rewards_helpers.run_deltas(spec, state)
@with_all_phases
@with_custom_state(balances_fn=low_balances, threshold_fn=lambda spec: spec.config.EJECTION_BALANCE)
@spec_test
@ -57,3 +79,52 @@ def test_full_random_low_balances_1(spec, state):
@single_phase
def test_full_random_misc_balances(spec, state):
yield from rewards_helpers.run_test_full_random(spec, state, rng=Random(7070))
@with_all_phases
@spec_state_test
def test_full_random_without_leak_0(spec, state):
rng = Random(1010)
randomize_state(spec, state, rng)
assert spec.is_in_inactivity_leak(state)
patch_state_to_non_leaking(spec, state)
assert not spec.is_in_inactivity_leak(state)
target_validators = get_unslashed_exited_validators(spec, state)
assert len(target_validators) != 0
assert has_active_balance_differential(spec, state)
yield from rewards_helpers.run_deltas(spec, state)
@with_all_phases
@spec_state_test
def test_full_random_without_leak_and_current_exit_0(spec, state):
"""
This test specifically ensures a validator exits in the current epoch
to ensure rewards are handled properly in this case.
"""
rng = Random(1011)
randomize_state(spec, state, rng)
assert spec.is_in_inactivity_leak(state)
patch_state_to_non_leaking(spec, state)
assert not spec.is_in_inactivity_leak(state)
target_validators = get_unslashed_exited_validators(spec, state)
assert len(target_validators) != 0
# move forward some epochs to process attestations added
# by ``randomize_state`` before we exit validators in
# what will be the current epoch
for _ in range(2):
next_epoch(spec, state)
current_epoch = spec.get_current_epoch(state)
for index in target_validators:
# patch exited validators to exit in the current epoch
validator = state.validators[index]
validator.exit_epoch = current_epoch
validator.withdrawable_epoch = current_epoch + spec.config.MIN_VALIDATOR_WITHDRAWABILITY_DELAY
# re-randomize attestation participation for the current epoch
randomize_attestation_participation(spec, state, rng)
assert has_active_balance_differential(spec, state)
yield from rewards_helpers.run_deltas(spec, state)

View File

@ -19,6 +19,7 @@ from eth2spec.test.helpers.attester_slashings import (
from eth2spec.test.helpers.proposer_slashings import get_valid_proposer_slashing, check_proposer_slashing_effect
from eth2spec.test.helpers.attestations import get_valid_attestation
from eth2spec.test.helpers.deposits import prepare_state_and_deposit
from eth2spec.test.helpers.execution_payload import build_empty_execution_payload
from eth2spec.test.helpers.voluntary_exits import prepare_signed_exits
from eth2spec.test.helpers.multi_operations import (
run_slash_and_exit,
@ -38,6 +39,7 @@ from eth2spec.test.context import (
with_custom_state,
large_validator_set,
is_post_altair,
is_post_merge,
)
@ -146,6 +148,11 @@ def process_and_sign_block_without_header_validations(spec, state, block):
spec.process_randao(state, block.body)
spec.process_eth1_data(state, block.body)
spec.process_operations(state, block.body)
if is_post_altair(spec):
spec.process_sync_aggregate(state, block.body.sync_aggregate)
if is_post_merge(spec):
if spec.is_execution_enabled(state, block.body):
spec.process_execution_payload(state, block.body.execution_payload, spec.EXECUTION_ENGINE)
# Insert post-state rot
block.state_root = state.hash_tree_root()
@ -188,6 +195,10 @@ def test_parent_from_same_slot(spec, state):
child_block = parent_block.copy()
child_block.parent_root = state.latest_block_header.hash_tree_root()
if is_post_merge(spec):
randao_mix = spec.compute_randao_mix(state, child_block.body.randao_reveal)
child_block.body.execution_payload = build_empty_execution_payload(spec, state, randao_mix)
# Show that normal path through transition fails
failed_state = state.copy()
expect_assertion_error(
@ -974,12 +985,9 @@ def test_historical_batch(spec, state):
@with_all_phases
@with_presets([MINIMAL], reason="suffices to test eth1 data voting without long voting period")
@spec_state_test
def test_eth1_data_votes_consensus(spec, state):
if spec.EPOCHS_PER_ETH1_VOTING_PERIOD > 2:
return dump_skipping_message("Skip test if config with longer `EPOCHS_PER_ETH1_VOTING_PERIOD` for saving time."
" Minimal config suffice to cover the target-of-test.")
voting_period_slots = spec.EPOCHS_PER_ETH1_VOTING_PERIOD * spec.SLOTS_PER_EPOCH
offset_block = build_empty_block(spec, state, slot=voting_period_slots - 1)
@ -1018,12 +1026,9 @@ def test_eth1_data_votes_consensus(spec, state):
@with_all_phases
@with_presets([MINIMAL], reason="suffices to test eth1 data voting without long voting period")
@spec_state_test
def test_eth1_data_votes_no_consensus(spec, state):
if spec.EPOCHS_PER_ETH1_VOTING_PERIOD > 2:
return dump_skipping_message("Skip test if config with longer `EPOCHS_PER_ETH1_VOTING_PERIOD` for saving time."
" Minimal config suffice to cover the target-of-test.")
voting_period_slots = spec.EPOCHS_PER_ETH1_VOTING_PERIOD * spec.SLOTS_PER_EPOCH
pre_eth1_hash = state.eth1_data.block_hash

View File

@ -1,5 +1,13 @@
from eth2spec.test.context import with_all_phases, spec_state_test
from eth2spec.test.helpers.fork_choice import get_genesis_forkchoice_store
from eth2spec.test.helpers.block import (
build_empty_block_for_next_slot,
)
from eth2spec.test.helpers.state import (
next_epoch,
state_transition_and_sign_block,
transition_to,
)
def run_on_tick(spec, store, time, new_justified_checkpoint=False):
@ -26,18 +34,92 @@ def test_basic(spec, state):
@with_all_phases
@spec_state_test
def test_update_justified_single(spec, state):
def test_update_justified_single_on_store_finalized_chain(spec, state):
store = get_genesis_forkchoice_store(spec, state)
next_epoch = spec.get_current_epoch(state) + 1
next_epoch_start_slot = spec.compute_start_slot_at_epoch(next_epoch)
seconds_until_next_epoch = next_epoch_start_slot * spec.config.SECONDS_PER_SLOT - store.time
store.best_justified_checkpoint = spec.Checkpoint(
epoch=store.justified_checkpoint.epoch + 1,
root=b'\x55' * 32,
# [Mock store.best_justified_checkpoint]
# Create a block at epoch 1
next_epoch(spec, state)
block = build_empty_block_for_next_slot(spec, state)
state_transition_and_sign_block(spec, state, block)
store.blocks[block.hash_tree_root()] = block.copy()
store.block_states[block.hash_tree_root()] = state.copy()
parent_block = block.copy()
# To make compute_slots_since_epoch_start(current_slot) == 0, transition to the end of the epoch
slot = state.slot + spec.SLOTS_PER_EPOCH - state.slot % spec.SLOTS_PER_EPOCH - 1
transition_to(spec, state, slot)
# Create a block at the start of epoch 2
block = build_empty_block_for_next_slot(spec, state)
# Mock state
state.current_justified_checkpoint = spec.Checkpoint(
epoch=spec.compute_epoch_at_slot(parent_block.slot),
root=parent_block.hash_tree_root(),
)
state_transition_and_sign_block(spec, state, block)
store.blocks[block.hash_tree_root()] = block
store.block_states[block.hash_tree_root()] = state
# Mock store.best_justified_checkpoint
store.best_justified_checkpoint = state.current_justified_checkpoint.copy()
run_on_tick(
spec,
store,
store.genesis_time + state.slot * spec.config.SECONDS_PER_SLOT,
new_justified_checkpoint=True
)
run_on_tick(spec, store, store.time + seconds_until_next_epoch, True)
@with_all_phases
@spec_state_test
def test_update_justified_single_not_on_store_finalized_chain(spec, state):
store = get_genesis_forkchoice_store(spec, state)
init_state = state.copy()
# Chain grows
# Create a block at epoch 1
next_epoch(spec, state)
block = build_empty_block_for_next_slot(spec, state)
block.body.graffiti = b'\x11' * 32
state_transition_and_sign_block(spec, state, block)
store.blocks[block.hash_tree_root()] = block.copy()
store.block_states[block.hash_tree_root()] = state.copy()
# Mock store.finalized_checkpoint
store.finalized_checkpoint = spec.Checkpoint(
epoch=spec.compute_epoch_at_slot(block.slot),
root=block.hash_tree_root(),
)
# [Mock store.best_justified_checkpoint]
# Create a block at epoch 1
state = init_state.copy()
next_epoch(spec, state)
block = build_empty_block_for_next_slot(spec, state)
block.body.graffiti = b'\x22' * 32
state_transition_and_sign_block(spec, state, block)
store.blocks[block.hash_tree_root()] = block.copy()
store.block_states[block.hash_tree_root()] = state.copy()
parent_block = block.copy()
# To make compute_slots_since_epoch_start(current_slot) == 0, transition to the end of the epoch
slot = state.slot + spec.SLOTS_PER_EPOCH - state.slot % spec.SLOTS_PER_EPOCH - 1
transition_to(spec, state, slot)
# Create a block at the start of epoch 2
block = build_empty_block_for_next_slot(spec, state)
# Mock state
state.current_justified_checkpoint = spec.Checkpoint(
epoch=spec.compute_epoch_at_slot(parent_block.slot),
root=parent_block.hash_tree_root(),
)
state_transition_and_sign_block(spec, state, block)
store.blocks[block.hash_tree_root()] = block.copy()
store.block_states[block.hash_tree_root()] = state.copy()
# Mock store.best_justified_checkpoint
store.best_justified_checkpoint = state.current_justified_checkpoint.copy()
run_on_tick(
spec,
store,
store.genesis_time + state.slot * spec.config.SECONDS_PER_SLOT,
)
@with_all_phases

View File

@ -0,0 +1,12 @@
from .utils import (
vector_test,
with_meta_tags,
build_transition_test,
)
__all__ = [ # avoid "unused import" lint error
"vector_test",
"with_meta_tags",
"build_transition_test",
]

View File

@ -0,0 +1,361 @@
"""
Utility code to generate randomized block tests
"""
import sys
import warnings
from random import Random
from typing import Callable
from eth2spec.test.helpers.multi_operations import (
build_random_block_from_state_for_next_slot,
get_random_sync_aggregate,
prepare_state_and_get_random_deposits,
)
from eth2spec.test.helpers.inactivity_scores import (
randomize_inactivity_scores,
)
from eth2spec.test.helpers.random import (
randomize_state as randomize_state_helper,
patch_state_to_non_leaking,
)
from eth2spec.test.helpers.state import (
next_slot,
next_epoch,
ensure_state_has_validators_across_lifecycle,
state_transition_and_sign_block,
)
# primitives:
# state
def _randomize_deposit_state(spec, state, stats):
"""
To introduce valid, randomized deposits, the ``state`` deposit sub-state
must be coordinated with the data that will ultimately go into blocks.
This function randomizes the ``state`` in a way that can signal downstream to
the block constructors how they should (or should not) make some randomized deposits.
"""
rng = Random(999)
block_count = stats.get("block_count", 0)
deposits = []
if block_count > 0:
num_deposits = rng.randrange(1, block_count * spec.MAX_DEPOSITS)
deposits = prepare_state_and_get_random_deposits(spec, state, rng, num_deposits=num_deposits)
return {
"deposits": deposits,
}
def randomize_state(spec, state, stats, exit_fraction=0.1, slash_fraction=0.1):
randomize_state_helper(spec, state, exit_fraction=exit_fraction, slash_fraction=slash_fraction)
scenario_state = _randomize_deposit_state(spec, state, stats)
return scenario_state
def randomize_state_altair(spec, state, stats):
scenario_state = randomize_state(spec, state, stats, exit_fraction=0.1, slash_fraction=0.1)
randomize_inactivity_scores(spec, state)
return scenario_state
# epochs
def epochs_until_leak(spec):
"""
State is "leaking" if the current epoch is at least
this value after the last finalized epoch.
"""
return spec.MIN_EPOCHS_TO_INACTIVITY_PENALTY + 1
def epochs_for_shard_committee_period(spec):
return spec.config.SHARD_COMMITTEE_PERIOD
# slots
def last_slot_in_epoch(spec):
return spec.SLOTS_PER_EPOCH - 1
def random_slot_in_epoch(spec, rng=Random(1336)):
return rng.randrange(1, spec.SLOTS_PER_EPOCH - 2)
def penultimate_slot_in_epoch(spec):
return spec.SLOTS_PER_EPOCH - 2
# blocks
def no_block(_spec, _pre_state, _signed_blocks, _scenario_state):
return None
# May need to make several attempts to find a block that does not correspond to a slashed
# proposer with the randomization helpers...
BLOCK_ATTEMPTS = 32
def _warn_if_empty_operations(block):
"""
NOTE: a block may be missing deposits depending on how many were created
and already inserted into existing blocks in a given scenario.
"""
if len(block.body.proposer_slashings) == 0:
warnings.warn(f"proposer slashings missing in block at slot {block.slot}")
if len(block.body.attester_slashings) == 0:
warnings.warn(f"attester slashings missing in block at slot {block.slot}")
if len(block.body.attestations) == 0:
warnings.warn(f"attestations missing in block at slot {block.slot}")
if len(block.body.voluntary_exits) == 0:
warnings.warn(f"voluntary exits missing in block at slot {block.slot}")
def _pull_deposits_from_scenario_state(spec, scenario_state, existing_block_count):
all_deposits = scenario_state.get("deposits", [])
start = existing_block_count * spec.MAX_DEPOSITS
return all_deposits[start:start + spec.MAX_DEPOSITS]
def random_block(spec, state, signed_blocks, scenario_state):
"""
Produce a random block.
NOTE: this helper may mutate state, as it will attempt
to produce a block over ``BLOCK_ATTEMPTS`` slots in order
to find a valid block in the event that the proposer has already been slashed.
"""
# NOTE: ``state`` has been "randomized" at this point and so will likely
# contain a large number of slashed validators. This function needs to return
# a valid block so it needs to check that the proposer of the next slot is not
# slashed.
# To do this, generate a ``temp_state`` to use for checking the propser in the next slot.
# This ensures no accidental mutations happen to the ``state`` the caller expects to get back
# after this function returns.
# Using a copy of the state for proposer sampling is also sound as any inputs used for the
# shuffling are fixed a few epochs prior to ``spec.get_current_epoch(state)``.
temp_state = state.copy()
next_slot(spec, temp_state)
for _ in range(BLOCK_ATTEMPTS):
proposer_index = spec.get_beacon_proposer_index(temp_state)
proposer = state.validators[proposer_index]
if proposer.slashed:
next_slot(spec, state)
next_slot(spec, temp_state)
else:
deposits_for_block = _pull_deposits_from_scenario_state(spec, scenario_state, len(signed_blocks))
block = build_random_block_from_state_for_next_slot(spec, state, deposits=deposits_for_block)
_warn_if_empty_operations(block)
return block
else:
raise AssertionError("could not find a block with an unslashed proposer, check ``state`` input")
SYNC_AGGREGATE_PARTICIPATION_BUCKETS = 4
def random_block_altair_with_cycling_sync_committee_participation(spec,
state,
signed_blocks,
scenario_state):
block = random_block(spec, state, signed_blocks, scenario_state)
block_index = len(signed_blocks) % SYNC_AGGREGATE_PARTICIPATION_BUCKETS
fraction_missed = block_index * (1 / SYNC_AGGREGATE_PARTICIPATION_BUCKETS)
fraction_participated = 1.0 - fraction_missed
previous_root = block.parent_root
block.body.sync_aggregate = get_random_sync_aggregate(
spec,
state,
block.slot - 1,
block_root=previous_root,
fraction_participated=fraction_participated,
)
return block
# validations
def no_op_validation(_spec, _state):
return True
def validate_is_leaking(spec, state):
return spec.is_in_inactivity_leak(state)
def validate_is_not_leaking(spec, state):
return not validate_is_leaking(spec, state)
# transitions
def with_validation(transition, validation):
if isinstance(transition, Callable):
transition = transition()
transition["validation"] = validation
return transition
def no_op_transition():
return {}
def epoch_transition(n=0):
return {
"epochs_to_skip": n,
}
def slot_transition(n=0):
return {
"slots_to_skip": n,
}
def transition_to_leaking():
return {
"epochs_to_skip": epochs_until_leak,
"validation": validate_is_leaking,
}
transition_without_leak = with_validation(no_op_transition, validate_is_not_leaking)
# block transitions
def transition_with_random_block(block_randomizer):
"""
Build a block transition with randomized data.
Provide optional sub-transitions to advance some
number of epochs or slots before applying the random block.
"""
return {
"block_producer": block_randomizer,
}
# setup and test gen
def _randomized_scenario_setup(state_randomizer):
"""
Return a sequence of pairs of ("mutation", "validation").
A "mutation" is a function that accepts (``spec``, ``state``, ``stats``) arguments and
allegedly performs some change to the state.
A "validation" is a function that accepts (spec, state) arguments and validates some change was made.
The "mutation" may return some state that should be available to any down-stream transitions
across the **entire** scenario.
The ``stats`` parameter reflects a summary of actions in a given scenario like
how many blocks will be produced. This data can be useful to construct a valid
pre-state and so is provided at the setup stage.
"""
def _skip_epochs(epoch_producer):
def f(spec, state, _stats):
"""
The unoptimized spec implementation is too slow to advance via ``next_epoch``.
Instead, just overwrite the ``state.slot`` and continue...
"""
epochs_to_skip = epoch_producer(spec)
slots_to_skip = epochs_to_skip * spec.SLOTS_PER_EPOCH
state.slot += slots_to_skip
return f
def _simulate_honest_execution(spec, state, _stats):
"""
Want to start tests not in a leak state; the finality data
may not reflect this condition with prior (arbitrary) mutations,
so this mutator addresses that fact.
"""
patch_state_to_non_leaking(spec, state)
return (
# NOTE: the block randomization function assumes at least 1 shard committee period
# so advance the state before doing anything else.
(_skip_epochs(epochs_for_shard_committee_period), no_op_validation),
(_simulate_honest_execution, no_op_validation),
(state_randomizer, ensure_state_has_validators_across_lifecycle),
)
# Run the generated tests:
# while the test implementation works via code-gen,
# references to helper code in this module are serialized as str names.
# to resolve this references at runtime, we need a reference to this module:
_this_module = sys.modules[__name__]
def _resolve_ref(ref):
if isinstance(ref, str):
return getattr(_this_module, ref)
return ref
def _iter_temporal(spec, description):
"""
Intended to advance some number of {epochs, slots}.
Caller can provide a constant integer or a callable deriving a number from
the ``spec`` under consideration.
"""
numeric = _resolve_ref(description)
if isinstance(numeric, Callable):
numeric = numeric(spec)
for i in range(numeric):
yield i
def _compute_statistics(scenario):
block_count = 0
for transition in scenario["transitions"]:
block_producer = _resolve_ref(transition.get("block_producer", None))
if block_producer and block_producer != no_block:
block_count += 1
return {
"block_count": block_count,
}
def run_generated_randomized_test(spec, state, scenario):
stats = _compute_statistics(scenario)
if "setup" not in scenario:
state_randomizer = _resolve_ref(scenario.get("state_randomizer", randomize_state))
scenario["setup"] = _randomized_scenario_setup(state_randomizer)
scenario_state = {}
for mutation, validation in scenario["setup"]:
additional_state = mutation(spec, state, stats)
validation(spec, state)
if additional_state:
scenario_state.update(additional_state)
yield "pre", state
blocks = []
for transition in scenario["transitions"]:
epochs_to_skip = _iter_temporal(spec, transition["epochs_to_skip"])
for _ in epochs_to_skip:
next_epoch(spec, state)
slots_to_skip = _iter_temporal(spec, transition["slots_to_skip"])
for _ in slots_to_skip:
next_slot(spec, state)
block_producer = _resolve_ref(transition["block_producer"])
block = block_producer(spec, state, blocks, scenario_state)
if block:
signed_block = state_transition_and_sign_block(spec, state, block)
blocks.append(signed_block)
validation = _resolve_ref(transition["validation"])
assert validation(spec, state)
yield "blocks", blocks
yield "post", state

View File

@ -80,26 +80,34 @@ checks: {<store_attibute>: value} -- the assertions.
`<store_attibute>` is the field member or property of [`Store`](../../../specs/phase0/fork-choice.md#store) object that maintained by client implementation. Currently, the possible fields included:
```yaml
head: { -- Encoded 32-byte value from get_head(store)
slot: slot,
root: string,
head: {
slot: int,
root: string, -- Encoded 32-byte value from get_head(store)
}
time: int -- store.time
genesis_time: int -- store.genesis_time
justified_checkpoint: {
epoch: int, -- Integer value from store.justified_checkpoint.epoch
root: string, -- Encoded 32-byte value from store.justified_checkpoint.root
}
finalized_checkpoint: {
epoch: int, -- Integer value from store.finalized_checkpoint.epoch
root: string, -- Encoded 32-byte value from store.finalized_checkpoint.root
}
best_justified_checkpoint: {
epoch: int, -- Integer value from store.best_justified_checkpoint.epoch
root: string, -- Encoded 32-byte value from store.best_justified_checkpoint.root
}
time: int -- store.time
genesis_time: int -- store.genesis_time
justified_checkpoint_root: string -- Encoded 32-byte value from store.justified_checkpoint.root
finalized_checkpoint_root: string -- Encoded 32-byte value from store.finalized_checkpoint.root
best_justified_checkpoint_root: string -- Encoded 32-byte value from store.best_justified_checkpoint.root
```
For example:
```yaml
- checks:
time: 144
genesis_time: 0
head: {slot: 17, root: '0xd2724c86002f7e1f8656ab44a341a409ad80e6e70a5225fd94835566deebb66f'}
justified_checkpoint_root: '0xcea6ecd3d3188e32ebf611f960eebd45b6c6f477a7cff242fa567a42653bfc7c'
finalized_checkpoint_root: '0xcea6ecd3d3188e32ebf611f960eebd45b6c6f477a7cff242fa567a42653bfc7c'
best_justified_checkpoint: '0xcea6ecd3d3188e32ebf611f960eebd45b6c6f477a7cff242fa567a42653bfc7c'
time: 192
head: {slot: 32, root: '0xdaa1d49d57594ced0c35688a6da133abb086d191a2ebdfd736fad95299325aeb'}
justified_checkpoint: {epoch: 3, root: '0xc25faab4acab38d3560864ca01e4d5cc4dc2cd473da053fbc03c2669143a2de4'}
finalized_checkpoint: {epoch: 2, root: '0x40d32d6283ec11c53317a46808bc88f55657d93b95a1af920403187accf48f4f'}
best_justified_checkpoint: {epoch: 3, root: '0xc25faab4acab38d3560864ca01e4d5cc4dc2cd473da053fbc03c2669143a2de4'}
```
*Note*: Each `checks` step may include one or multiple items. Each item has to be checked against the current store.

View File

@ -25,8 +25,7 @@ if __name__ == "__main__":
} # also run the previous phase 0 tests
# No epoch-processing changes in Merge and previous testing repeats with new types, so no additional tests required.
# TODO: rebase onto Altair testing later.
merge_mods = phase_0_mods
merge_mods = altair_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 [

View File

@ -5,7 +5,7 @@ from eth2spec.test.helpers.constants import PHASE0, ALTAIR, MERGE
if __name__ == "__main__":
phase_0_mods = {'finality': 'eth2spec.test.phase0.finality.test_finality'}
altair_mods = phase_0_mods # No additional Altair specific finality tests
merge_mods = phase_0_mods # No additional Merge specific finality tests
merge_mods = altair_mods # No additional Merge specific finality tests
all_mods = {
PHASE0: phase_0_mods,

View File

@ -9,8 +9,8 @@ if __name__ == "__main__":
]}
# No additional Altair specific finality tests, yet.
altair_mods = phase_0_mods
# No specific Merge tests yet. TODO: rebase onto Altair testing later.
merge_mods = phase_0_mods
# No specific Merge tests yet.
merge_mods = altair_mods
all_mods = {
PHASE0: phase_0_mods,

View File

@ -12,9 +12,9 @@ if __name__ == "__main__":
'voluntary_exit',
]}
altair_mods = {
**{key: 'eth2spec.test.altair.block_processing.sync_aggregate.test_process_' + key for key in [
'sync_aggregate',
'sync_aggregate_random',
**{'sync_aggregate': [
'eth2spec.test.altair.block_processing.sync_aggregate.test_process_' + key
for key in ['sync_aggregate', 'sync_aggregate_random']
]},
**phase_0_mods,
} # also run the previous phase 0 tests
@ -23,7 +23,7 @@ if __name__ == "__main__":
**{key: 'eth2spec.test.merge.block_processing.test_process_' + key for key in [
'execution_payload',
]},
**phase_0_mods, # TODO: runs phase0 tests. Rebase to include `altair_mods` testing later.
**altair_mods,
}
# TODO Custody Game testgen is disabled for now

View File

@ -0,0 +1,8 @@
all:
if ! test -d venv; then python3 -m venv venv; fi;
. ./venv/bin/activate
pip3 install -r requirements.txt
rm -f ../../core/pyspec/eth2spec/test/phase0/random/test_random.py
rm -f ../../core/pyspec/eth2spec/test/altair/random/test_random.py
python3 generate.py phase0 > ../../core/pyspec/eth2spec/test/phase0/random/test_random.py
python3 generate.py altair > ../../core/pyspec/eth2spec/test/altair/random/test_random.py

View File

@ -0,0 +1,31 @@
# Randomized tests
Randomized tests in the format of `sanity` blocks tests, with randomized operations.
Information on the format of the tests can be found in the [sanity test formats documentation](../../formats/sanity/README.md).
# To generate test sources
```bash
$ make
```
The necessary commands are in the `Makefile`, as the only target.
The generated files are committed to the repo so you should not need to do this.
# To run tests
Each of the generated test does produce a `pytest` test instance but by default is
currently skipped. Running the test via the generator (see next) will trigger any errors
that would arise during the running of `pytest`.
# To generate spec tests (from the generated files)
Run the test generator in the usual way.
E.g. from the root of this repo, you can run:
```bash
$ make gen_random
```

View File

@ -0,0 +1,258 @@
"""
This test format currently uses code generation to assemble the tests
as the current test infra does not have a facility to dynamically
generate tests that can be seen by ``pytest``.
This will likley change in future releases of the testing infra.
NOTE: To add additional scenarios, add test cases below in ``_generate_randomized_scenarios``.
"""
import sys
import random
import warnings
from typing import Callable
import itertools
from eth2spec.test.utils.randomized_block_tests import (
no_block,
no_op_validation,
randomize_state,
randomize_state_altair,
random_block,
random_block_altair_with_cycling_sync_committee_participation,
last_slot_in_epoch,
random_slot_in_epoch,
penultimate_slot_in_epoch,
epoch_transition,
slot_transition,
transition_with_random_block,
transition_to_leaking,
transition_without_leak,
)
from eth2spec.test.helpers.constants import PHASE0, ALTAIR
# Ensure this many blocks are present in *each* randomized scenario
BLOCK_TRANSITIONS_COUNT = 2
def _normalize_transition(transition):
"""
Provide "empty" or "no op" sub-transitions
to a given transition.
"""
if isinstance(transition, Callable):
transition = transition()
if "epochs_to_skip" not in transition:
transition["epochs_to_skip"] = 0
if "slots_to_skip" not in transition:
transition["slots_to_skip"] = 0
if "block_producer" not in transition:
transition["block_producer"] = no_block
if "validation" not in transition:
transition["validation"] = no_op_validation
return transition
def _normalize_scenarios(scenarios):
"""
"Normalize" a "scenario" so that a producer of a test case
does not need to provide every expected key/value.
"""
for scenario in scenarios:
transitions = scenario["transitions"]
for i, transition in enumerate(transitions):
transitions[i] = _normalize_transition(transition)
def _flatten(t):
leak_transition = t[0]
result = [leak_transition]
for transition_batch in t[1]:
for transition in transition_batch:
if isinstance(transition, tuple):
for subtransition in transition:
result.append(subtransition)
else:
result.append(transition)
return result
def _generate_randomized_scenarios(block_randomizer):
"""
Generates a set of randomized testing scenarios.
Return a sequence of "scenarios" where each scenario:
1. Provides some setup
2. Provides a sequence of transitions that mutate the state in some way,
possibly yielding blocks along the way
NOTE: scenarios are "normalized" with empty/no-op elements before returning
to the test generation to facilitate brevity when writing scenarios by hand.
NOTE: the main block driver builds a block for the **next** slot, so
the slot transitions are offset by -1 to target certain boundaries.
"""
# go forward 0 or 1 epochs
epochs_set = (
epoch_transition(n=0),
epoch_transition(n=1),
)
# within those epochs, go forward to:
slots_set = (
# the first slot in an epoch (see note in docstring about offsets...)
slot_transition(last_slot_in_epoch),
# the second slot in an epoch
slot_transition(n=0),
# some random number of slots, but not at epoch boundaries
slot_transition(random_slot_in_epoch),
# the last slot in an epoch (see note in docstring about offsets...)
slot_transition(penultimate_slot_in_epoch),
)
# and produce a block...
blocks_set = (
transition_with_random_block(block_randomizer),
)
rng = random.Random(1447)
all_skips = list(itertools.product(epochs_set, slots_set))
randomized_skips = (
rng.sample(all_skips, len(all_skips))
for _ in range(BLOCK_TRANSITIONS_COUNT)
)
# build a set of block transitions from combinations of sub-transitions
transitions_generator = (
itertools.product(prefix, blocks_set)
for prefix in randomized_skips
)
block_transitions = zip(*transitions_generator)
# and preface each set of block transitions with the possible leak transitions
leak_transitions = (
transition_without_leak,
transition_to_leaking,
)
scenarios = [
{"transitions": _flatten(t)}
for t in itertools.product(leak_transitions, block_transitions)
]
_normalize_scenarios(scenarios)
return scenarios
def _id_from_scenario(test_description):
"""
Construct a name for the scenario based its data.
"""
def _to_id_part(prefix, x):
suffix = str(x)
if isinstance(x, Callable):
suffix = x.__name__
return f"{prefix}{suffix}"
def _id_from_transition(transition):
return ",".join((
_to_id_part("epochs:", transition["epochs_to_skip"]),
_to_id_part("slots:", transition["slots_to_skip"]),
_to_id_part("with-block:", transition["block_producer"])
))
return "|".join(map(_id_from_transition, test_description["transitions"]))
test_imports_template = """\"\"\"
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 {phase}
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,
)"""
test_template = """
@only_generator(\"randomized test for broad coverage, not point-to-point CI\")
@with_phases([{phase}])
@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_{index}(spec, state):
# scenario as high-level, informal text:
{name_as_comment}
scenario = {scenario} # noqa: E501
yield from run_generated_randomized_test(
spec,
state,
scenario,
)"""
def _to_comment(name, indent_level):
parts = name.split("|")
indentation = " " * indent_level
parts = [
indentation + "# " + part for part in parts
]
return "\n".join(parts)
def run_generate_tests_to_std_out(phase, state_randomizer, block_randomizer):
scenarios = _generate_randomized_scenarios(block_randomizer)
test_content = {"phase": phase.upper()}
test_imports = test_imports_template.format(**test_content)
test_file = [test_imports]
for index, scenario in enumerate(scenarios):
# required for setup phase
scenario["state_randomizer"] = state_randomizer.__name__
# need to pass name, rather than function reference...
transitions = scenario["transitions"]
for transition in transitions:
for name, value in transition.items():
if isinstance(value, Callable):
transition[name] = value.__name__
test_content = test_content.copy()
name = _id_from_scenario(scenario)
test_content["name_as_comment"] = _to_comment(name, 1)
test_content["index"] = index
test_content["scenario"] = scenario
test_instance = test_template.format(**test_content)
test_file.append(test_instance)
print("\n\n".join(test_file))
if __name__ == "__main__":
did_generate = False
if PHASE0 in sys.argv:
did_generate = True
run_generate_tests_to_std_out(
PHASE0,
state_randomizer=randomize_state,
block_randomizer=random_block,
)
if ALTAIR in sys.argv:
did_generate = True
run_generate_tests_to_std_out(
ALTAIR,
state_randomizer=randomize_state_altair,
block_randomizer=random_block_altair_with_cycling_sync_committee_participation,
)
if not did_generate:
warnings.warn("no phase given for test generation")

View File

@ -0,0 +1,18 @@
from eth2spec.test.helpers.constants import PHASE0, ALTAIR
from eth2spec.gen_helpers.gen_from_tests.gen import run_state_test_generators
if __name__ == "__main__":
phase_0_mods = {key: 'eth2spec.test.phase0.random.test_' + key for key in [
'random',
]}
altair_mods = {key: 'eth2spec.test.altair.random.test_' + key for key in [
'random',
]}
all_mods = {
PHASE0: phase_0_mods,
ALTAIR: altair_mods,
}
run_state_test_generators(runner_name="random", all_mods=all_mods)

View File

@ -0,0 +1,2 @@
pytest>=4.4
../../../[generator]

View File

@ -14,7 +14,7 @@ if __name__ == "__main__":
# No additional merge specific rewards tests, yet.
# Note: Block rewards are non-epoch rewards and are tested as part of block processing tests.
# Transaction fees are part of the execution-layer.
merge_mods = phase_0_mods
merge_mods = altair_mods
all_mods = {
PHASE0: phase_0_mods,

View File

@ -9,12 +9,10 @@ if __name__ == "__main__":
]}
altair_mods = {**{key: 'eth2spec.test.altair.sanity.test_' + key for key in [
'blocks',
]}, **phase_0_mods} # also run the previous phase 0 tests
# Altair-specific test cases are ignored, but should be included after the Merge is rebased onto Altair work.
]}, **phase_0_mods}
merge_mods = {**{key: 'eth2spec.test.merge.sanity.test_' + key for key in [
'blocks',
]}, **phase_0_mods} # TODO: Merge inherits phase0 tests for now.
]}, **altair_mods}
all_mods = {
PHASE0: phase_0_mods,