Merge branch 'dev' into tests/merge
This commit is contained in:
commit
f38750ae61
|
@ -45,6 +45,7 @@ The merge is still actively in development. The exact specification has not been
|
||||||
* [Merge fork](specs/merge/fork.md)
|
* [Merge fork](specs/merge/fork.md)
|
||||||
* [Fork Choice changes](specs/merge/fork-choice.md)
|
* [Fork Choice changes](specs/merge/fork-choice.md)
|
||||||
* [Validator additions](specs/merge/validator.md)
|
* [Validator additions](specs/merge/validator.md)
|
||||||
|
* [Client settings](specs/merge/client_settings.md)
|
||||||
|
|
||||||
### Sharding
|
### Sharding
|
||||||
|
|
||||||
|
@ -53,7 +54,7 @@ Sharding follows the merge, and is divided into three parts:
|
||||||
* Sharding base functionality - In early engineering phase
|
* Sharding base functionality - In early engineering phase
|
||||||
* [Beacon Chain changes](specs/sharding/beacon-chain.md)
|
* [Beacon Chain changes](specs/sharding/beacon-chain.md)
|
||||||
* [P2P Network changes](specs/sharding/p2p-interface.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)
|
* [Beacon Chain changes](specs/custody_game/beacon-chain.md)
|
||||||
* [Validator custody work](specs/custody_game/validator.md)
|
* [Validator custody work](specs/custody_game/validator.md)
|
||||||
* Data Availability Sampling - In active R&D
|
* Data Availability Sampling - In active R&D
|
||||||
|
|
|
@ -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.
|
|
@ -58,8 +58,8 @@ INACTIVITY_SCORE_RECOVERY_RATE: 16
|
||||||
EJECTION_BALANCE: 16000000000
|
EJECTION_BALANCE: 16000000000
|
||||||
# 2**2 (= 4)
|
# 2**2 (= 4)
|
||||||
MIN_PER_EPOCH_CHURN_LIMIT: 4
|
MIN_PER_EPOCH_CHURN_LIMIT: 4
|
||||||
# 2**16 (= 65,536)
|
# [customized] scale queue churn at much lower validator counts for testing
|
||||||
CHURN_LIMIT_QUOTIENT: 65536
|
CHURN_LIMIT_QUOTIENT: 32
|
||||||
|
|
||||||
|
|
||||||
# Deposit contract
|
# Deposit contract
|
||||||
|
|
2
setup.py
2
setup.py
|
@ -223,7 +223,7 @@ def get_spec(file_name: Path, preset: Dict[str, str], config: Dict[str, str]) ->
|
||||||
|
|
||||||
if not _is_constant_id(name):
|
if not _is_constant_id(name):
|
||||||
# Check for short type declarations
|
# 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
|
custom_types[name] = value
|
||||||
continue
|
continue
|
||||||
|
|
||||||
|
|
|
@ -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).
|
|
@ -73,7 +73,7 @@ def finalize_block(self: ExecutionEngine, block_hash: Hash32) -> bool:
|
||||||
```python
|
```python
|
||||||
@dataclass
|
@dataclass
|
||||||
class TransitionStore(object):
|
class TransitionStore(object):
|
||||||
transition_total_difficulty: uint256
|
terminal_total_difficulty: uint256
|
||||||
```
|
```
|
||||||
|
|
||||||
### `PowBlock`
|
### `PowBlock`
|
||||||
|
@ -101,8 +101,8 @@ Used by fork-choice handler, `on_block`.
|
||||||
|
|
||||||
```python
|
```python
|
||||||
def is_valid_terminal_pow_block(transition_store: TransitionStore, block: PowBlock, parent: PowBlock) -> bool:
|
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_total_difficulty_reached = block.total_difficulty >= transition_store.terminal_total_difficulty
|
||||||
is_parent_total_difficulty_valid = parent.total_difficulty < transition_store.transition_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
|
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
|
assert get_ancestor(store, block.parent_root, finalized_slot) == store.finalized_checkpoint.root
|
||||||
|
|
||||||
# [New in Merge]
|
# [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
|
# 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_block = get_pow_block(block.body.execution_payload.parent_hash)
|
||||||
pow_parent = get_pow_block(pow_block.parent_hash)
|
pow_parent = get_pow_block(pow_block.parent_hash)
|
||||||
|
|
|
@ -97,18 +97,18 @@ def upgrade_to_merge(pre: altair.BeaconState) -> BeaconState:
|
||||||
# Execution-layer
|
# Execution-layer
|
||||||
latest_execution_payload_header=ExecutionPayloadHeader(),
|
latest_execution_payload_header=ExecutionPayloadHeader(),
|
||||||
)
|
)
|
||||||
|
|
||||||
return post
|
return post
|
||||||
```
|
```
|
||||||
|
|
||||||
### Initializing transition store
|
### 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.
|
Transition store initialization occurs after the state has been modified by corresponding `upgrade_to_merge` function.
|
||||||
|
|
||||||
```python
|
```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
|
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_per_voting_period = seconds_per_voting_period // SECONDS_PER_ETH1_BLOCK
|
||||||
pow_blocks_to_merge = TARGET_SECONDS_TO_MERGE // 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:
|
def get_transition_store(anchor_pow_block: PowBlock) -> TransitionStore:
|
||||||
transition_total_difficulty = compute_transition_total_difficulty(anchor_pow_block)
|
terminal_total_difficulty = compute_terminal_total_difficulty(anchor_pow_block)
|
||||||
return TransitionStore(transition_total_difficulty=transition_total_difficulty)
|
return TransitionStore(terminal_total_difficulty=terminal_total_difficulty)
|
||||||
|
|
||||||
|
|
||||||
def initialize_transition_store(state: BeaconState) -> TransitionStore:
|
def initialize_transition_store(state: BeaconState) -> TransitionStore:
|
||||||
pow_block = get_pow_block(state.eth1_data.block_hash)
|
pow_block = get_pow_block(state.eth1_data.block_hash)
|
||||||
return get_transition_store(pow_block)
|
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).
|
||||||
|
|
|
@ -95,7 +95,7 @@ def get_execution_payload(state: BeaconState,
|
||||||
execution_engine: ExecutionEngine,
|
execution_engine: ExecutionEngine,
|
||||||
pow_chain: Sequence[PowBlock]) -> ExecutionPayload:
|
pow_chain: Sequence[PowBlock]) -> ExecutionPayload:
|
||||||
if not is_merge_complete(state):
|
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:
|
if terminal_pow_block is None:
|
||||||
# Pre-merge, empty payload
|
# Pre-merge, empty payload
|
||||||
return ExecutionPayload()
|
return ExecutionPayload()
|
||||||
|
|
|
@ -327,9 +327,13 @@ def on_tick(store: Store, time: uint64) -> None:
|
||||||
# Not a new epoch, return
|
# Not a new epoch, return
|
||||||
if not (current_slot > previous_slot and compute_slots_since_epoch_start(current_slot) == 0):
|
if not (current_slot > previous_slot and compute_slots_since_epoch_start(current_slot) == 0):
|
||||||
return
|
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:
|
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`
|
#### `on_block`
|
||||||
|
|
|
@ -32,7 +32,7 @@
|
||||||
- [`Builder`](#builder)
|
- [`Builder`](#builder)
|
||||||
- [`DataCommitment`](#datacommitment)
|
- [`DataCommitment`](#datacommitment)
|
||||||
- [`AttestedDataCommitment`](#attesteddatacommitment)
|
- [`AttestedDataCommitment`](#attesteddatacommitment)
|
||||||
- [ShardBlobBody](#shardblobbody)
|
- [`ShardBlobBody`](#shardblobbody)
|
||||||
- [`ShardBlobBodySummary`](#shardblobbodysummary)
|
- [`ShardBlobBodySummary`](#shardblobbodysummary)
|
||||||
- [`ShardBlob`](#shardblob)
|
- [`ShardBlob`](#shardblob)
|
||||||
- [`ShardBlobHeader`](#shardblobheader)
|
- [`ShardBlobHeader`](#shardblobheader)
|
||||||
|
@ -257,7 +257,7 @@ class AttestedDataCommitment(Container):
|
||||||
includer_index: ValidatorIndex
|
includer_index: ValidatorIndex
|
||||||
```
|
```
|
||||||
|
|
||||||
### ShardBlobBody
|
### `ShardBlobBody`
|
||||||
|
|
||||||
Unsigned shard data, bundled by a shard-builder.
|
Unsigned shard data, bundled by a shard-builder.
|
||||||
Unique, signing different bodies as shard proposer for the same `(slot, shard)` is slashable.
|
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`
|
#### `get_shard_proposer_index`
|
||||||
|
|
||||||
```python
|
```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``.
|
Return the proposer's index of shard block at ``slot``.
|
||||||
"""
|
"""
|
||||||
epoch = compute_epoch_at_slot(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)
|
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`
|
#### `get_start_shard`
|
||||||
|
@ -520,7 +520,7 @@ def get_start_shard(state: BeaconState, slot: Slot) -> Shard:
|
||||||
"""
|
"""
|
||||||
Return the start shard at ``slot``.
|
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)
|
committee_count = get_committee_count_per_slot(state, epoch)
|
||||||
active_shard_count = get_active_shard_count(state, epoch)
|
active_shard_count = get_active_shard_count(state, epoch)
|
||||||
return committee_count * slot % active_shard_count
|
return committee_count * slot % active_shard_count
|
||||||
|
@ -759,7 +759,7 @@ def process_shard_header(state: BeaconState, signed_header: SignedShardBlobHeade
|
||||||
commitment=body_summary.commitment,
|
commitment=body_summary.commitment,
|
||||||
root=header_root,
|
root=header_root,
|
||||||
includer_index=get_beacon_proposer_index(state),
|
includer_index=get_beacon_proposer_index(state),
|
||||||
)
|
),
|
||||||
votes=initial_votes,
|
votes=initial_votes,
|
||||||
weight=0,
|
weight=0,
|
||||||
update_slot=state.slot,
|
update_slot=state.slot,
|
||||||
|
@ -885,7 +885,7 @@ def reset_pending_shard_work(state: BeaconState) -> None:
|
||||||
selector=SHARD_WORK_PENDING,
|
selector=SHARD_WORK_PENDING,
|
||||||
value=List[PendingShardHeader, MAX_SHARD_HEADERS_PER_SHARD](
|
value=List[PendingShardHeader, MAX_SHARD_HEADERS_PER_SHARD](
|
||||||
PendingShardHeader(
|
PendingShardHeader(
|
||||||
attested=AttestedDataCommitment()
|
attested=AttestedDataCommitment(),
|
||||||
votes=Bitlist[MAX_VALIDATORS_PER_COMMITTEE]([0] * committee_length),
|
votes=Bitlist[MAX_VALIDATORS_PER_COMMITTEE]([0] * committee_length),
|
||||||
weight=0,
|
weight=0,
|
||||||
update_slot=slot,
|
update_slot=slot,
|
||||||
|
|
|
@ -1 +1 @@
|
||||||
1.1.0-beta.3
|
1.1.0-beta.4
|
|
@ -1,4 +1,5 @@
|
||||||
import os
|
import os
|
||||||
|
import time
|
||||||
import shutil
|
import shutil
|
||||||
import argparse
|
import argparse
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
@ -22,6 +23,9 @@ from .gen_typing import TestProvider
|
||||||
context.is_pytest = False
|
context.is_pytest = False
|
||||||
|
|
||||||
|
|
||||||
|
TIME_THRESHOLD_TO_PRINT = 1.0 # seconds
|
||||||
|
|
||||||
|
|
||||||
def validate_output_dir(path_str):
|
def validate_output_dir(path_str):
|
||||||
path = Path(path_str)
|
path = Path(path_str)
|
||||||
|
|
||||||
|
@ -73,6 +77,13 @@ def run_generator(generator_name, test_providers: Iterable[TestProvider]):
|
||||||
required=False,
|
required=False,
|
||||||
help="specify presets to run with. Allows all if no preset names are specified.",
|
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()
|
args = parser.parse_args()
|
||||||
output_dir = args.output_dir
|
output_dir = args.output_dir
|
||||||
|
@ -96,11 +107,15 @@ def run_generator(generator_name, test_providers: Iterable[TestProvider]):
|
||||||
if len(presets) != 0:
|
if len(presets) != 0:
|
||||||
print(f"Filtering test-generator runs to only include presets: {', '.join(presets)}")
|
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
|
generated_test_count = 0
|
||||||
skipped_test_count = 0
|
skipped_test_count = 0
|
||||||
|
provider_start = time.time()
|
||||||
for tprov in test_providers:
|
for tprov in test_providers:
|
||||||
# runs anything that we don't want to repeat for every test case.
|
if not collect_only:
|
||||||
tprov.prepare()
|
# runs anything that we don't want to repeat for every test case.
|
||||||
|
tprov.prepare()
|
||||||
|
|
||||||
for test_case in tprov.make_cases():
|
for test_case in tprov.make_cases():
|
||||||
case_dir = (
|
case_dir = (
|
||||||
|
@ -110,8 +125,14 @@ def run_generator(generator_name, test_providers: Iterable[TestProvider]):
|
||||||
)
|
)
|
||||||
incomplete_tag_file = case_dir / "INCOMPLETE"
|
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 case_dir.exists():
|
||||||
if not args.force and not incomplete_tag_file.exists():
|
if not args.force and not incomplete_tag_file.exists():
|
||||||
|
skipped_test_count += 1
|
||||||
print(f'Skipping already existing test: {case_dir}')
|
print(f'Skipping already existing test: {case_dir}')
|
||||||
continue
|
continue
|
||||||
else:
|
else:
|
||||||
|
@ -121,6 +142,7 @@ def run_generator(generator_name, test_providers: Iterable[TestProvider]):
|
||||||
shutil.rmtree(case_dir)
|
shutil.rmtree(case_dir)
|
||||||
|
|
||||||
print(f'Generating test: {case_dir}')
|
print(f'Generating test: {case_dir}')
|
||||||
|
test_start = time.time()
|
||||||
|
|
||||||
written_part = False
|
written_part = False
|
||||||
|
|
||||||
|
@ -178,10 +200,22 @@ def run_generator(generator_name, test_providers: Iterable[TestProvider]):
|
||||||
generated_test_count += 1
|
generated_test_count += 1
|
||||||
# Only remove `INCOMPLETE` tag file
|
# Only remove `INCOMPLETE` tag file
|
||||||
os.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"
|
provider_end = time.time()
|
||||||
summary_message += f" ({skipped_test_count} skipped tests)"
|
span = round(provider_end - provider_start, 2)
|
||||||
print(summary_message)
|
|
||||||
|
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):
|
def dump_yaml_fn(data: Any, name: str, file_mode: str, yaml_encoder: YAML):
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
from importlib import import_module
|
from importlib import import_module
|
||||||
from inspect import getmembers, isfunction
|
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.utils import bls
|
||||||
from eth2spec.test.helpers.constants import ALL_PRESETS, TESTGEN_FORKS
|
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],
|
def get_provider(create_provider_fn: Callable[[SpecForkName, PresetBaseName, str, str], TestProvider],
|
||||||
fork_name: SpecForkName,
|
fork_name: SpecForkName,
|
||||||
preset_name: PresetBaseName,
|
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():
|
for key, mod_name in all_mods[fork_name].items():
|
||||||
|
if not isinstance(mod_name, List):
|
||||||
|
mod_name = [mod_name]
|
||||||
yield create_provider_fn(
|
yield create_provider_fn(
|
||||||
fork_name=fork_name,
|
fork_name=fork_name,
|
||||||
preset_name=preset_name,
|
preset_name=preset_name,
|
||||||
|
@ -75,16 +77,17 @@ def get_create_provider_fn(runner_name: str) -> Callable[[SpecForkName, str, str
|
||||||
return
|
return
|
||||||
|
|
||||||
def create_provider(fork_name: SpecForkName, preset_name: PresetBaseName,
|
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]:
|
def cases_fn() -> Iterable[TestCase]:
|
||||||
tests_src = import_module(tests_src_mod_name)
|
for mod_name in tests_src_mod_name:
|
||||||
return generate_from_tests(
|
tests_src = import_module(mod_name)
|
||||||
runner_name=runner_name,
|
yield from generate_from_tests(
|
||||||
handler_name=handler_name,
|
runner_name=runner_name,
|
||||||
src=tests_src,
|
handler_name=handler_name,
|
||||||
fork_name=fork_name,
|
src=tests_src,
|
||||||
preset_name=preset_name,
|
fork_name=fork_name,
|
||||||
)
|
preset_name=preset_name,
|
||||||
|
)
|
||||||
|
|
||||||
return TestProvider(prepare=prepare_fn, make_cases=cases_fn)
|
return TestProvider(prepare=prepare_fn, make_cases=cases_fn)
|
||||||
return create_provider
|
return create_provider
|
||||||
|
|
|
@ -5,6 +5,7 @@ from eth2spec.test.helpers.block import (
|
||||||
from eth2spec.test.helpers.state import (
|
from eth2spec.test.helpers.state import (
|
||||||
state_transition_and_sign_block,
|
state_transition_and_sign_block,
|
||||||
transition_to,
|
transition_to,
|
||||||
|
next_epoch_via_block,
|
||||||
)
|
)
|
||||||
from eth2spec.test.helpers.constants import (
|
from eth2spec.test.helpers.constants import (
|
||||||
MAINNET, MINIMAL,
|
MAINNET, MINIMAL,
|
||||||
|
@ -12,10 +13,12 @@ from eth2spec.test.helpers.constants import (
|
||||||
from eth2spec.test.helpers.sync_committee import (
|
from eth2spec.test.helpers.sync_committee import (
|
||||||
compute_aggregate_sync_committee_signature,
|
compute_aggregate_sync_committee_signature,
|
||||||
compute_committee_indices,
|
compute_committee_indices,
|
||||||
get_committee_indices,
|
|
||||||
run_sync_committee_processing,
|
run_sync_committee_processing,
|
||||||
run_successful_sync_committee_test,
|
run_successful_sync_committee_test,
|
||||||
)
|
)
|
||||||
|
from eth2spec.test.helpers.voluntary_exits import (
|
||||||
|
get_unslashed_exited_validators,
|
||||||
|
)
|
||||||
from eth2spec.test.context import (
|
from eth2spec.test.context import (
|
||||||
with_altair_and_later,
|
with_altair_and_later,
|
||||||
with_presets,
|
with_presets,
|
||||||
|
@ -28,19 +31,17 @@ from eth2spec.test.context import (
|
||||||
@spec_state_test
|
@spec_state_test
|
||||||
@always_bls
|
@always_bls
|
||||||
def test_invalid_signature_bad_domain(spec, state):
|
def test_invalid_signature_bad_domain(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)
|
|
||||||
|
|
||||||
block = build_empty_block_for_next_slot(spec, state)
|
block = build_empty_block_for_next_slot(spec, state)
|
||||||
# Exclude one participant whose signature was included.
|
|
||||||
block.body.sync_aggregate = spec.SyncAggregate(
|
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(
|
sync_committee_signature=compute_aggregate_sync_committee_signature(
|
||||||
spec,
|
spec,
|
||||||
state,
|
state,
|
||||||
block.slot - 1,
|
block.slot - 1,
|
||||||
committee_indices, # full committee signs
|
committee_indices, # full committee signs
|
||||||
|
block_root=block.parent_root,
|
||||||
domain_type=spec.DOMAIN_BEACON_ATTESTER, # Incorrect domain
|
domain_type=spec.DOMAIN_BEACON_ATTESTER, # Incorrect domain
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
@ -51,7 +52,7 @@ def test_invalid_signature_bad_domain(spec, state):
|
||||||
@spec_state_test
|
@spec_state_test
|
||||||
@always_bls
|
@always_bls
|
||||||
def test_invalid_signature_missing_participant(spec, state):
|
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)
|
rng = random.Random(2020)
|
||||||
random_participant = rng.choice(committee_indices)
|
random_participant = rng.choice(committee_indices)
|
||||||
|
|
||||||
|
@ -64,6 +65,7 @@ def test_invalid_signature_missing_participant(spec, state):
|
||||||
state,
|
state,
|
||||||
block.slot - 1,
|
block.slot - 1,
|
||||||
committee_indices, # full committee signs
|
committee_indices, # full committee signs
|
||||||
|
block_root=block.parent_root,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
yield from run_sync_committee_processing(spec, state, block, expect_exception=True)
|
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
|
@spec_state_test
|
||||||
@always_bls
|
@always_bls
|
||||||
def test_invalid_signature_extra_participant(spec, state):
|
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)
|
rng = random.Random(3030)
|
||||||
random_participant = rng.choice(committee_indices)
|
random_participant = rng.choice(committee_indices)
|
||||||
|
|
||||||
|
@ -127,6 +129,7 @@ def test_invalid_signature_extra_participant(spec, state):
|
||||||
state,
|
state,
|
||||||
block.slot - 1,
|
block.slot - 1,
|
||||||
[index for index in committee_indices if index != random_participant],
|
[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")
|
@with_presets([MINIMAL], reason="to create nonduplicate committee")
|
||||||
@spec_state_test
|
@spec_state_test
|
||||||
def test_sync_committee_rewards_nonduplicate_committee(spec, state):
|
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_size = len(committee_indices)
|
||||||
committee_bits = [True] * committee_size
|
committee_bits = [True] * committee_size
|
||||||
active_validator_count = len(spec.get_active_validator_indices(state, spec.get_current_epoch(state)))
|
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")
|
@with_presets([MAINNET], reason="to create duplicate committee")
|
||||||
@spec_state_test
|
@spec_state_test
|
||||||
def test_sync_committee_rewards_duplicate_committee_no_participation(spec, state):
|
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_size = len(committee_indices)
|
||||||
committee_bits = [False] * committee_size
|
committee_bits = [False] * committee_size
|
||||||
active_validator_count = len(spec.get_active_validator_indices(state, spec.get_current_epoch(state)))
|
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")
|
@with_presets([MAINNET], reason="to create duplicate committee")
|
||||||
@spec_state_test
|
@spec_state_test
|
||||||
def test_sync_committee_rewards_duplicate_committee_half_participation(spec, state):
|
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_size = len(committee_indices)
|
||||||
committee_bits = [True] * (committee_size // 2) + [False] * (committee_size // 2)
|
committee_bits = [True] * (committee_size // 2) + [False] * (committee_size // 2)
|
||||||
assert len(committee_bits) == committee_size
|
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")
|
@with_presets([MAINNET], reason="to create duplicate committee")
|
||||||
@spec_state_test
|
@spec_state_test
|
||||||
def test_sync_committee_rewards_duplicate_committee_full_participation(spec, state):
|
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_size = len(committee_indices)
|
||||||
committee_bits = [True] * committee_size
|
committee_bits = [True] * committee_size
|
||||||
active_validator_count = len(spec.get_active_validator_indices(state, spec.get_current_epoch(state)))
|
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
|
@spec_state_test
|
||||||
@always_bls
|
@always_bls
|
||||||
def test_sync_committee_rewards_not_full_participants(spec, state):
|
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)
|
rng = random.Random(1010)
|
||||||
committee_bits = [rng.choice([True, False]) for _ in committee_indices]
|
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
|
@spec_state_test
|
||||||
@always_bls
|
@always_bls
|
||||||
def test_sync_committee_rewards_empty_participants(spec, state):
|
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]
|
committee_bits = [False for _ in committee_indices]
|
||||||
|
|
||||||
yield from run_successful_sync_committee_test(spec, state, committee_indices, committee_bits)
|
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
|
@spec_state_test
|
||||||
@always_bls
|
@always_bls
|
||||||
def test_invalid_signature_past_block(spec, state):
|
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):
|
for _ in range(2):
|
||||||
# NOTE: need to transition twice to move beyond the degenerate case at genesis
|
# 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,
|
state,
|
||||||
block.slot - 1,
|
block.slot - 1,
|
||||||
committee_indices,
|
committee_indices,
|
||||||
|
block_root=block.parent_root,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -286,6 +290,7 @@ def test_invalid_signature_previous_committee(spec, state):
|
||||||
state,
|
state,
|
||||||
block.slot - 1,
|
block.slot - 1,
|
||||||
committee_indices,
|
committee_indices,
|
||||||
|
block_root=block.parent_root,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -327,6 +332,7 @@ def test_valid_signature_future_committee(spec, state):
|
||||||
state,
|
state,
|
||||||
block.slot - 1,
|
block.slot - 1,
|
||||||
committee_indices,
|
committee_indices,
|
||||||
|
block_root=block.parent_root,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -360,6 +366,7 @@ def test_proposer_in_committee_without_participation(spec, state):
|
||||||
state,
|
state,
|
||||||
block.slot - 1,
|
block.slot - 1,
|
||||||
participants,
|
participants,
|
||||||
|
block_root=block.parent_root,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -396,6 +403,7 @@ def test_proposer_in_committee_with_participation(spec, state):
|
||||||
state,
|
state,
|
||||||
block.slot - 1,
|
block.slot - 1,
|
||||||
committee_indices,
|
committee_indices,
|
||||||
|
block_root=block.parent_root,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -406,3 +414,191 @@ def test_proposer_in_committee_with_participation(spec, state):
|
||||||
else:
|
else:
|
||||||
state_transition_and_sign_block(spec, state, block)
|
state_transition_and_sign_block(spec, state, block)
|
||||||
raise AssertionError("failed to find a proposer in the sync committee set; check test setup")
|
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)
|
||||||
|
|
|
@ -2,10 +2,19 @@ import random
|
||||||
from eth2spec.test.helpers.constants import (
|
from eth2spec.test.helpers.constants import (
|
||||||
MAINNET, MINIMAL,
|
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 (
|
from eth2spec.test.helpers.sync_committee import (
|
||||||
get_committee_indices,
|
compute_committee_indices,
|
||||||
run_successful_sync_committee_test,
|
run_successful_sync_committee_test,
|
||||||
)
|
)
|
||||||
|
from eth2spec.test.helpers.voluntary_exits import (
|
||||||
|
get_unslashed_exited_validators,
|
||||||
|
)
|
||||||
from eth2spec.test.context import (
|
from eth2spec.test.context import (
|
||||||
with_altair_and_later,
|
with_altair_and_later,
|
||||||
spec_state_test,
|
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):
|
def _test_harness_for_randomized_test_case(spec, state, expect_duplicates=False, participation_fn=None):
|
||||||
committee_indices = get_committee_indices(spec, state, duplicates=duplicates)
|
committee_indices = compute_committee_indices(spec, state)
|
||||||
|
|
||||||
if participation_fn:
|
if participation_fn:
|
||||||
participating_indices = participation_fn(committee_indices)
|
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_bits = [index in participating_indices for index in committee_indices]
|
||||||
committee_size = len(committee_indices)
|
committee_size = len(committee_indices)
|
||||||
if duplicates:
|
if expect_duplicates:
|
||||||
assert committee_size > len(set(committee_indices))
|
assert committee_size > len(set(committee_indices))
|
||||||
else:
|
else:
|
||||||
assert committee_size == len(set(committee_indices))
|
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(
|
yield from _test_harness_for_randomized_test_case(
|
||||||
spec,
|
spec,
|
||||||
state,
|
state,
|
||||||
duplicates=True,
|
expect_duplicates=True,
|
||||||
participation_fn=lambda comm: [rng.choice(comm)],
|
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(
|
yield from _test_harness_for_randomized_test_case(
|
||||||
spec,
|
spec,
|
||||||
state,
|
state,
|
||||||
duplicates=True,
|
expect_duplicates=True,
|
||||||
participation_fn=lambda comm: rng.sample(comm, int(len(comm) * 0.25)),
|
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(
|
yield from _test_harness_for_randomized_test_case(
|
||||||
spec,
|
spec,
|
||||||
state,
|
state,
|
||||||
duplicates=True,
|
expect_duplicates=True,
|
||||||
participation_fn=lambda comm: rng.sample(comm, int(len(comm) * 0.75)),
|
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(
|
yield from _test_harness_for_randomized_test_case(
|
||||||
spec,
|
spec,
|
||||||
state,
|
state,
|
||||||
duplicates=True,
|
expect_duplicates=True,
|
||||||
participation_fn=lambda comm: rng.sample(comm, len(comm) - 1),
|
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(
|
yield from _test_harness_for_randomized_test_case(
|
||||||
spec,
|
spec,
|
||||||
state,
|
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),
|
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,
|
state,
|
||||||
participation_fn=lambda comm: rng.sample(comm, len(comm) // 2),
|
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),
|
||||||
|
)
|
||||||
|
|
|
@ -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,
|
||||||
|
)
|
|
@ -126,6 +126,17 @@ def default_balances(spec):
|
||||||
return [spec.MAX_EFFECTIVE_BALANCE] * num_validators
|
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)
|
with_state = with_custom_state(default_balances, default_activation_threshold)
|
||||||
|
|
||||||
|
|
||||||
|
@ -152,6 +163,22 @@ def misc_balances(spec):
|
||||||
return balances
|
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):
|
def low_single_balance(spec):
|
||||||
"""
|
"""
|
||||||
Helper method to create a single of balance of 1 Gwei.
|
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.
|
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):
|
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
|
A decorator to construct a "transition" test from one fork of the eth2 spec
|
||||||
|
|
|
@ -156,9 +156,18 @@ def add_block(spec, store, signed_block, test_steps, valid=True, allow_invalid_a
|
||||||
'checks': {
|
'checks': {
|
||||||
'time': int(store.time),
|
'time': int(store.time),
|
||||||
'head': get_formatted_head_output(spec, store),
|
'head': get_formatted_head_output(spec, store),
|
||||||
'justified_checkpoint_root': encode_hex(store.justified_checkpoint.root),
|
'justified_checkpoint': {
|
||||||
'finalized_checkpoint_root': encode_hex(store.finalized_checkpoint.root),
|
'epoch': int(store.justified_checkpoint.epoch),
|
||||||
'best_justified_checkpoint': encode_hex(store.best_justified_checkpoint.root),
|
'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),
|
||||||
|
},
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
|
@ -7,6 +7,10 @@ from eth2spec.test.helpers.state import (
|
||||||
from eth2spec.test.helpers.block import (
|
from eth2spec.test.helpers.block import (
|
||||||
build_empty_block_for_next_slot,
|
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.proposer_slashings import get_valid_proposer_slashing
|
||||||
from eth2spec.test.helpers.attester_slashings import get_valid_attester_slashing_by_indices
|
from eth2spec.test.helpers.attester_slashings import get_valid_attester_slashing_by_indices
|
||||||
from eth2spec.test.helpers.attestations import get_valid_attestation
|
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):
|
def get_random_proposer_slashings(spec, state, rng):
|
||||||
num_slashings = rng.randrange(spec.MAX_PROPOSER_SLASHINGS)
|
num_slashings = rng.randrange(1, spec.MAX_PROPOSER_SLASHINGS)
|
||||||
indices = spec.get_active_validator_indices(state, spec.get_current_epoch(state)).copy()
|
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 = [
|
slashings = [
|
||||||
get_valid_proposer_slashing(
|
get_valid_proposer_slashing(
|
||||||
spec, state,
|
spec, state,
|
||||||
|
@ -56,14 +64,33 @@ def get_random_proposer_slashings(spec, state, rng):
|
||||||
return slashings
|
return slashings
|
||||||
|
|
||||||
|
|
||||||
def get_random_attester_slashings(spec, state, rng):
|
def get_random_attester_slashings(spec, state, rng, slashed_indices=[]):
|
||||||
num_slashings = rng.randrange(spec.MAX_ATTESTER_SLASHINGS)
|
"""
|
||||||
indices = spec.get_active_validator_indices(state, spec.get_current_epoch(state)).copy()
|
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))
|
slot_range = list(range(state.slot - spec.SLOTS_PER_HISTORICAL_ROOT + 1, state.slot))
|
||||||
slashings = [
|
slashings = [
|
||||||
get_valid_attester_slashing_by_indices(
|
get_valid_attester_slashing_by_indices(
|
||||||
spec, state,
|
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))),
|
slot=slot_range.pop(rng.randrange(len(slot_range))),
|
||||||
signed_1=True, signed_2=True,
|
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):
|
def get_random_attestations(spec, state, rng):
|
||||||
num_attestations = rng.randrange(spec.MAX_ATTESTATIONS)
|
num_attestations = rng.randrange(1, spec.MAX_ATTESTATIONS)
|
||||||
|
|
||||||
attestations = [
|
attestations = [
|
||||||
get_valid_attestation(
|
get_valid_attestation(
|
||||||
|
@ -86,8 +113,12 @@ def get_random_attestations(spec, state, rng):
|
||||||
return attestations
|
return attestations
|
||||||
|
|
||||||
|
|
||||||
def prepare_state_and_get_random_deposits(spec, state, rng):
|
def get_random_deposits(spec, state, rng, num_deposits=None):
|
||||||
num_deposits = rng.randrange(spec.MAX_DEPOSITS)
|
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))]
|
deposit_data_leaves = [spec.DepositData() for _ in range(len(state.validators))]
|
||||||
deposits = []
|
deposits = []
|
||||||
|
@ -105,38 +136,82 @@ def prepare_state_and_get_random_deposits(spec, state, rng):
|
||||||
signed=True,
|
signed=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
state.eth1_data.deposit_root = root
|
|
||||||
state.eth1_data.deposit_count += num_deposits
|
|
||||||
|
|
||||||
# Then for that context, build deposits/proofs
|
# Then for that context, build deposits/proofs
|
||||||
for i in range(num_deposits):
|
for i in range(num_deposits):
|
||||||
index = len(state.validators) + i
|
index = len(state.validators) + i
|
||||||
deposit, _, _ = deposit_from_context(spec, deposit_data_leaves, index)
|
deposit, _, _ = deposit_from_context(spec, deposit_data_leaves, index)
|
||||||
deposits.append(deposit)
|
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
|
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):
|
def get_random_voluntary_exits(spec, state, to_be_slashed_indices, rng):
|
||||||
num_exits = rng.randrange(spec.MAX_VOLUNTARY_EXITS)
|
num_exits = rng.randrange(1, spec.MAX_VOLUNTARY_EXITS)
|
||||||
indices = set(spec.get_active_validator_indices(state, spec.get_current_epoch(state)).copy())
|
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
|
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)
|
return prepare_signed_exits(spec, state, exit_indices)
|
||||||
|
|
||||||
|
|
||||||
def run_test_full_random_operations(spec, state, rng=Random(2080)):
|
def get_random_sync_aggregate(spec, state, slot, block_root=None, fraction_participated=1.0, rng=Random(2099)):
|
||||||
# move state forward SHARD_COMMITTEE_PERIOD epochs to allow for exit
|
committee_indices = compute_committee_indices(spec, state, state.current_sync_committee)
|
||||||
state.slot += spec.config.SHARD_COMMITTEE_PERIOD * spec.SLOTS_PER_EPOCH
|
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 = build_empty_block_for_next_slot(spec, state)
|
||||||
block.body.proposer_slashings = get_random_proposer_slashings(spec, state, rng)
|
proposer_slashings = get_random_proposer_slashings(spec, state, rng)
|
||||||
block.body.attester_slashings = get_random_attester_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.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
|
# cannot include to be slashed indices as exits
|
||||||
slashed_indices = set([
|
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)
|
slashed_indices = slashed_indices.union(attester_slashing.attestation_2.attesting_indices)
|
||||||
block.body.voluntary_exits = get_random_voluntary_exits(spec, state, slashed_indices, rng)
|
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
|
yield 'pre', state
|
||||||
|
|
||||||
signed_block = state_transition_and_sign_block(spec, state, block)
|
signed_block = state_transition_and_sign_block(spec, state, block)
|
||||||
|
|
|
@ -20,32 +20,40 @@ def set_some_new_deposits(spec, state, rng):
|
||||||
state.validators[index].activation_eligibility_epoch = spec.get_current_epoch(state)
|
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:
|
if spec.get_current_epoch(state) < 5:
|
||||||
# Move epochs forward to allow for some validators already exited/withdrawable
|
# Move epochs forward to allow for some validators already exited/withdrawable
|
||||||
for _ in range(5):
|
for _ in range(5):
|
||||||
next_epoch(spec, state)
|
next_epoch(spec, state)
|
||||||
|
|
||||||
current_epoch = spec.get_current_epoch(state)
|
current_epoch = spec.get_current_epoch(state)
|
||||||
# Exit ~1/2 of validators
|
|
||||||
for index in spec.get_active_validator_indices(state, current_epoch):
|
for index in spec.get_active_validator_indices(state, current_epoch):
|
||||||
if rng.choice([True, False]):
|
sampled = rng.random() < fraction
|
||||||
|
if not sampled:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
validator = state.validators[index]
|
validator = state.validators[index]
|
||||||
validator.exit_epoch = rng.choice([current_epoch - 1, current_epoch - 2, current_epoch - 3])
|
validator.exit_epoch = rng.choice([current_epoch, current_epoch - 1, current_epoch - 2, current_epoch - 3])
|
||||||
# ~1/2 are withdrawable
|
# ~1/2 are withdrawable (note, unnatural span between exit epoch and withdrawable epoch)
|
||||||
if rng.choice([True, False]):
|
if rng.choice([True, False]):
|
||||||
validator.withdrawable_epoch = current_epoch
|
validator.withdrawable_epoch = current_epoch
|
||||||
else:
|
else:
|
||||||
validator.withdrawable_epoch = current_epoch + 1
|
validator.withdrawable_epoch = current_epoch + 1
|
||||||
|
|
||||||
|
|
||||||
def slash_random_validators(spec, state, rng):
|
def slash_random_validators(spec, state, rng, fraction=None):
|
||||||
# Slash ~1/2 of validators
|
if fraction is None:
|
||||||
|
# Slash ~1/2 of validators
|
||||||
|
fraction = 0.5
|
||||||
|
|
||||||
for index in range(len(state.validators)):
|
for index in range(len(state.validators)):
|
||||||
# slash at least one validator
|
# 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)
|
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)
|
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)
|
set_some_new_deposits(spec, state, rng)
|
||||||
exit_random_validators(spec, state, rng)
|
exit_random_validators(spec, state, rng, fraction=exit_fraction)
|
||||||
slash_random_validators(spec, state, rng)
|
slash_random_validators(spec, state, rng, fraction=slash_fraction)
|
||||||
randomize_attestation_participation(spec, state, rng)
|
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,
|
||||||
|
)
|
||||||
|
|
|
@ -255,7 +255,19 @@ def run_get_inactivity_penalty_deltas(spec, state):
|
||||||
else:
|
else:
|
||||||
assert penalties[index] > base_penalty
|
assert penalties[index] > base_penalty
|
||||||
else:
|
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):
|
def transition_state_to_leak(spec, state, epochs=None):
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
from eth2spec.test.context import expect_assertion_error, is_post_altair
|
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.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):
|
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):
|
def set_empty_participation(spec, state, rng=None):
|
||||||
_set_empty_participation(spec, state)
|
_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
|
||||||
|
|
|
@ -9,7 +9,6 @@ from eth2spec.test.helpers.block import (
|
||||||
)
|
)
|
||||||
from eth2spec.test.helpers.block_processing import run_block_processing_to
|
from eth2spec.test.helpers.block_processing import run_block_processing_to
|
||||||
from eth2spec.utils import bls
|
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):
|
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)
|
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
|
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]
|
all_pubkeys = [v.pubkey for v in state.validators]
|
||||||
return [all_pubkeys.index(pubkey) for pubkey in committee.pubkeys]
|
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,
|
state,
|
||||||
block.slot - 1,
|
block.slot - 1,
|
||||||
[index for index, bit in zip(committee_indices, committee_bits) if bit],
|
[index for index, bit in zip(committee_indices, committee_bits) if bit],
|
||||||
|
block_root=block.parent_root,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
return block
|
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):
|
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)
|
block = _build_block_for_next_slot_with_sync_participation(spec, state, committee_indices, committee_bits)
|
||||||
yield from run_sync_committee_processing(spec, state, block)
|
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])
|
|
||||||
|
|
|
@ -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]
|
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):
|
def exit_validators(spec, state, validator_count, rng=None):
|
||||||
if rng is None:
|
if rng is None:
|
||||||
rng = Random(1337)
|
rng = Random(1337)
|
||||||
|
|
|
@ -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.keys import pubkey_to_privkey
|
||||||
from eth2spec.test.helpers.voluntary_exits import sign_voluntary_exit
|
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)
|
yield from run_voluntary_exit_processing(spec, state, signed_voluntary_exit, False)
|
||||||
|
|
||||||
|
|
||||||
@with_all_phases
|
def run_test_success_exit_queue(spec, state):
|
||||||
@spec_state_test
|
|
||||||
def test_success_exit_queue(spec, state):
|
|
||||||
# move state forward SHARD_COMMITTEE_PERIOD epochs to allow for exit
|
# move state forward SHARD_COMMITTEE_PERIOD epochs to allow for exit
|
||||||
state.slot += spec.config.SHARD_COMMITTEE_PERIOD * spec.SLOTS_PER_EPOCH
|
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
|
# 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)
|
yield from run_voluntary_exit_processing(spec, state, signed_voluntary_exit)
|
||||||
|
|
||||||
assert (
|
for index in initial_indices:
|
||||||
state.validators[validator_index].exit_epoch ==
|
assert (
|
||||||
state.validators[initial_indices[0]].exit_epoch + 1
|
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
|
@with_all_phases
|
||||||
|
|
|
@ -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.context import is_post_altair, spec_state_test, with_all_phases
|
||||||
from eth2spec.test.helpers.epoch_processing import (
|
from eth2spec.test.helpers.epoch_processing import (
|
||||||
run_epoch_processing_with,
|
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):
|
def run_process_just_and_fin(spec, state):
|
||||||
|
@ -300,3 +302,76 @@ def test_12_ok_support_messed_target(spec, state):
|
||||||
@spec_state_test
|
@spec_state_test
|
||||||
def test_12_poor_support(spec, state):
|
def test_12_poor_support(spec, state):
|
||||||
yield from finalize_on_12(spec, state, 3, False, False)
|
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
|
||||||
|
|
|
@ -1,6 +1,12 @@
|
||||||
from eth2spec.test.helpers.deposits import mock_deposit
|
from eth2spec.test.helpers.deposits import mock_deposit
|
||||||
from eth2spec.test.helpers.state import next_epoch, next_slots
|
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
|
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
|
assert state.validators[churn_limit - 1].activation_epoch != spec.FAR_FUTURE_EPOCH
|
||||||
|
|
||||||
|
|
||||||
@with_all_phases
|
def run_test_activation_queue_efficiency(spec, state):
|
||||||
@spec_state_test
|
|
||||||
def test_activation_queue_efficiency(spec, state):
|
|
||||||
churn_limit = spec.get_validator_churn_limit(state)
|
churn_limit = spec.get_validator_churn_limit(state)
|
||||||
mock_activations = churn_limit * 2
|
mock_activations = churn_limit * 2
|
||||||
|
|
||||||
|
@ -128,23 +132,45 @@ def test_activation_queue_efficiency(spec, state):
|
||||||
|
|
||||||
state.finalized_checkpoint.epoch = epoch + 1
|
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
|
# Run first registry update. Do not yield test vectors
|
||||||
for _ in run_process_registry_updates(spec, state):
|
for _ in run_process_registry_updates(spec, state):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
# Half should churn in first run of registry update
|
# Half should churn in first run of registry update
|
||||||
for i in range(mock_activations):
|
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
|
assert state.validators[i].activation_epoch < spec.FAR_FUTURE_EPOCH
|
||||||
else:
|
else:
|
||||||
assert state.validators[i].activation_epoch == spec.FAR_FUTURE_EPOCH
|
assert state.validators[i].activation_epoch == spec.FAR_FUTURE_EPOCH
|
||||||
|
|
||||||
# Second half should churn in second run of registry update
|
# 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)
|
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
|
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
|
@with_all_phases
|
||||||
@spec_state_test
|
@spec_state_test
|
||||||
def test_ejection(spec, state):
|
def test_ejection(spec, state):
|
||||||
|
@ -165,9 +191,7 @@ def test_ejection(spec, state):
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@with_all_phases
|
def run_test_ejection_past_churn_limit(spec, state):
|
||||||
@spec_state_test
|
|
||||||
def test_ejection_past_churn_limit(spec, state):
|
|
||||||
churn_limit = spec.get_validator_churn_limit(state)
|
churn_limit = spec.get_validator_churn_limit(state)
|
||||||
|
|
||||||
# try to eject more than per-epoch churn limit
|
# 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
|
# first third ejected in normal speed
|
||||||
if i < mock_ejections // 3:
|
if i < mock_ejections // 3:
|
||||||
assert state.validators[i].exit_epoch == expected_ejection_epoch
|
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:
|
elif mock_ejections // 3 <= i < mock_ejections * 2 // 3:
|
||||||
assert state.validators[i].exit_epoch == expected_ejection_epoch + 1
|
assert state.validators[i].exit_epoch == expected_ejection_epoch + 1
|
||||||
# second thirdgets delayed by 2 epochs
|
# final third gets delayed by 2 epochs
|
||||||
else:
|
else:
|
||||||
assert state.validators[i].exit_epoch == expected_ejection_epoch + 2
|
assert state.validators[i].exit_epoch == expected_ejection_epoch + 2
|
||||||
|
|
||||||
|
|
||||||
@with_all_phases
|
@with_all_phases
|
||||||
@spec_state_test
|
@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
|
# move past first two irregular epochs wrt finality
|
||||||
next_epoch(spec, state)
|
next_epoch(spec, state)
|
||||||
next_epoch(spec, state)
|
next_epoch(spec, state)
|
||||||
|
|
||||||
# ready for entrance into activation queue
|
# ready for entrance into activation queue
|
||||||
activation_queue_index = 0
|
activation_queue_start_index = 0
|
||||||
mock_deposit(spec, state, activation_queue_index)
|
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
|
# ready for activation
|
||||||
activation_index = 1
|
|
||||||
mock_deposit(spec, state, activation_index)
|
|
||||||
state.finalized_checkpoint.epoch = spec.get_current_epoch(state) - 1
|
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
|
# ready for ejection
|
||||||
ejection_index = 2
|
ejection_start_index = num_per_status * 2
|
||||||
state.validators[ejection_index].effective_balance = spec.config.EJECTION_BALANCE
|
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)
|
yield from run_process_registry_updates(spec, state)
|
||||||
|
|
||||||
# validator moved into activation queue
|
# all eligible validators moved into activation queue
|
||||||
validator = state.validators[activation_queue_index]
|
for validator_index in activation_queue_indices:
|
||||||
assert validator.activation_eligibility_epoch != spec.FAR_FUTURE_EPOCH
|
validator = state.validators[validator_index]
|
||||||
assert validator.activation_epoch == spec.FAR_FUTURE_EPOCH
|
assert validator.activation_eligibility_epoch != spec.FAR_FUTURE_EPOCH
|
||||||
assert not spec.is_active_validator(validator, spec.get_current_epoch(state))
|
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
|
# up to churn limit validators get activated for future epoch from the queue
|
||||||
validator = state.validators[activation_index]
|
for validator_index in activation_indices[:churn_limit]:
|
||||||
assert validator.activation_eligibility_epoch != spec.FAR_FUTURE_EPOCH
|
validator = state.validators[validator_index]
|
||||||
assert validator.activation_epoch != spec.FAR_FUTURE_EPOCH
|
assert validator.activation_eligibility_epoch != spec.FAR_FUTURE_EPOCH
|
||||||
assert not spec.is_active_validator(validator, spec.get_current_epoch(state))
|
assert validator.activation_epoch != spec.FAR_FUTURE_EPOCH
|
||||||
assert spec.is_active_validator(
|
assert not spec.is_active_validator(validator, spec.get_current_epoch(state))
|
||||||
validator,
|
assert spec.is_active_validator(
|
||||||
spec.compute_activation_exit_epoch(spec.get_current_epoch(state))
|
validator,
|
||||||
)
|
spec.compute_activation_exit_epoch(spec.get_current_epoch(state))
|
||||||
|
)
|
||||||
|
|
||||||
# validator ejected for future epoch
|
# any remaining validators do not exit the activation queue
|
||||||
validator = state.validators[ejection_index]
|
for validator_index in activation_indices[churn_limit:]:
|
||||||
assert validator.exit_epoch != spec.FAR_FUTURE_EPOCH
|
validator = state.validators[validator_index]
|
||||||
assert spec.is_active_validator(validator, spec.get_current_epoch(state))
|
assert validator.activation_eligibility_epoch != spec.FAR_FUTURE_EPOCH
|
||||||
assert not spec.is_active_validator(
|
assert validator.activation_epoch == spec.FAR_FUTURE_EPOCH
|
||||||
validator,
|
|
||||||
spec.compute_activation_exit_epoch(spec.get_current_epoch(state))
|
# 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)
|
||||||
|
|
|
@ -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.context import spec_state_test, with_all_phases, is_post_altair
|
||||||
from eth2spec.test.helpers.epoch_processing import (
|
from eth2spec.test.helpers.epoch_processing import (
|
||||||
run_epoch_processing_with, run_epoch_processing_to
|
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
|
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
|
spec.get_current_epoch(state) % spec.EPOCHS_PER_SLASHINGS_VECTOR
|
||||||
] = total_slashed_balance
|
] = total_slashed_balance
|
||||||
|
|
||||||
|
# verify some slashings happened...
|
||||||
|
assert total_slashed_balance != 0
|
||||||
|
|
||||||
|
|
||||||
def get_slashing_multiplier(spec):
|
def get_slashing_multiplier(spec):
|
||||||
if is_post_altair(spec):
|
if is_post_altair(spec):
|
||||||
|
@ -30,9 +37,7 @@ def get_slashing_multiplier(spec):
|
||||||
return spec.PROPORTIONAL_SLASHING_MULTIPLIER
|
return spec.PROPORTIONAL_SLASHING_MULTIPLIER
|
||||||
|
|
||||||
|
|
||||||
@with_all_phases
|
def _setup_process_slashings_test(spec, state, not_slashable_set=set()):
|
||||||
@spec_state_test
|
|
||||||
def test_max_penalties(spec, state):
|
|
||||||
# Slashed count to ensure that enough validators are slashed to induce maximum penalties
|
# Slashed count to ensure that enough validators are slashed to induce maximum penalties
|
||||||
slashed_count = min(
|
slashed_count = min(
|
||||||
(len(state.validators) // get_slashing_multiplier(spec)) + 1,
|
(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)
|
out_epoch = spec.get_current_epoch(state) + (spec.EPOCHS_PER_SLASHINGS_VECTOR // 2)
|
||||||
|
|
||||||
slashed_indices = list(range(slashed_count))
|
eligible_indices = set(range(slashed_count))
|
||||||
slash_validators(spec, state, slashed_indices, [out_epoch] * 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_balance = spec.get_total_active_balance(state)
|
||||||
total_penalties = sum(state.slashings)
|
total_penalties = sum(state.slashings)
|
||||||
|
|
||||||
assert total_balance // get_slashing_multiplier(spec) <= total_penalties
|
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)
|
yield from run_process_slashings(spec, state)
|
||||||
|
|
||||||
for i in slashed_indices:
|
for i in slashed_indices:
|
||||||
|
@ -171,3 +185,28 @@ def test_scaled_penalties(spec, state):
|
||||||
* spec.EFFECTIVE_BALANCE_INCREMENT
|
* spec.EFFECTIVE_BALANCE_INCREMENT
|
||||||
)
|
)
|
||||||
assert state.balances[i] == pre_slash_balances[i] - expected_penalty
|
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]
|
||||||
|
|
|
@ -26,7 +26,6 @@ from eth2spec.test.helpers.state import (
|
||||||
next_epoch,
|
next_epoch,
|
||||||
next_slots,
|
next_slots,
|
||||||
state_transition_and_sign_block,
|
state_transition_and_sign_block,
|
||||||
transition_to,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@ -191,6 +190,10 @@ def test_on_block_before_finalized(spec, state):
|
||||||
@spec_state_test
|
@spec_state_test
|
||||||
@with_presets([MINIMAL], reason="too slow")
|
@with_presets([MINIMAL], reason="too slow")
|
||||||
def test_on_block_finalized_skip_slots(spec, state):
|
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 = []
|
test_steps = []
|
||||||
# Initialization
|
# Initialization
|
||||||
store, anchor_block = get_genesis_forkchoice_store_and_block(spec, state)
|
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)
|
on_tick_and_append_step(spec, store, current_time, test_steps)
|
||||||
assert store.time == current_time
|
assert store.time == current_time
|
||||||
|
|
||||||
# Create a finalized chain
|
# Fill epoch 0 and the first slot of epoch 1
|
||||||
for _ in range(4):
|
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(
|
state, store, _ = yield from apply_next_epoch_with_attestations(
|
||||||
spec, state, store, True, False, test_steps=test_steps)
|
spec, state, store, True, True, test_steps=test_steps)
|
||||||
assert store.finalized_checkpoint.epoch == 2
|
|
||||||
|
|
||||||
# Another chain
|
# Now we get finalized epoch 2, where `compute_start_slot_at_epoch(2)` is a skipped slot
|
||||||
another_state = store.block_states[store.finalized_checkpoint.root].copy()
|
assert state.finalized_checkpoint.epoch == store.finalized_checkpoint.epoch == 2
|
||||||
# Build block that includes the skipped slots up to finality in chain
|
assert store.finalized_checkpoint.root == spec.get_block_root(state, 1) == spec.get_block_root(state, 2)
|
||||||
block = build_empty_block(spec,
|
assert state.current_justified_checkpoint.epoch == store.justified_checkpoint.epoch == 3
|
||||||
another_state,
|
assert store.justified_checkpoint == state.current_justified_checkpoint
|
||||||
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 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 from tick_and_add_block(spec, store, signed_block, test_steps)
|
||||||
|
|
||||||
yield 'steps', test_steps
|
yield 'steps', test_steps
|
||||||
|
@ -224,36 +237,43 @@ def test_on_block_finalized_skip_slots(spec, state):
|
||||||
@spec_state_test
|
@spec_state_test
|
||||||
@with_presets([MINIMAL], reason="too slow")
|
@with_presets([MINIMAL], reason="too slow")
|
||||||
def test_on_block_finalized_skip_slots_not_in_skip_chain(spec, state):
|
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 = []
|
test_steps = []
|
||||||
# Initialization
|
# Initialization
|
||||||
transition_to(spec, state, state.slot + spec.SLOTS_PER_EPOCH - 1)
|
store, anchor_block = get_genesis_forkchoice_store_and_block(spec, state)
|
||||||
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)
|
|
||||||
yield 'anchor_state', 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
|
current_time = state.slot * spec.config.SECONDS_PER_SLOT + store.genesis_time
|
||||||
on_tick_and_append_step(spec, store, current_time, test_steps)
|
on_tick_and_append_step(spec, store, current_time, test_steps)
|
||||||
assert store.time == current_time
|
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
|
# Skip the rest slots of epoch 1 and the first slot of epoch 2
|
||||||
for _ in range(3):
|
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(
|
state, store, _ = yield from apply_next_epoch_with_attestations(
|
||||||
spec, state, store, True, False, test_steps=test_steps)
|
spec, state, store, True, True, test_steps=test_steps)
|
||||||
assert store.finalized_checkpoint.epoch == pre_finalized_checkpoint_epoch + 1
|
|
||||||
|
|
||||||
# Now build a block at later slot than finalized epoch
|
# Now we get finalized epoch 2, where `compute_start_slot_at_epoch(2)` is a skipped slot
|
||||||
# Includes finalized block in chain, but not at appropriate skip slot
|
assert state.finalized_checkpoint.epoch == store.finalized_checkpoint.epoch == 2
|
||||||
pre_state = store.block_states[block.hash_tree_root()].copy()
|
assert store.finalized_checkpoint.root == spec.get_block_root(state, 1) == spec.get_block_root(state, 2)
|
||||||
block = build_empty_block(spec,
|
assert state.current_justified_checkpoint.epoch == store.justified_checkpoint.epoch == 3
|
||||||
state=pre_state,
|
assert store.justified_checkpoint == state.current_justified_checkpoint
|
||||||
slot=spec.compute_start_slot_at_epoch(store.finalized_checkpoint.epoch) + 2)
|
|
||||||
block.body.graffiti = b'\x12' * 32
|
# Now build a block after the block of the finalized **root**
|
||||||
signed_block = sign_block(spec, pre_state, block)
|
# 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 from tick_and_add_block(spec, store, signed_block, test_steps, valid=False)
|
||||||
|
|
||||||
yield 'steps', test_steps
|
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.finalized_checkpoint.epoch == 0
|
||||||
assert fork_2_state.current_justified_checkpoint.epoch == 5
|
assert fork_2_state.current_justified_checkpoint.epoch == 5
|
||||||
# Check SAFE_SLOTS_TO_UPDATE_JUSTIFIED
|
# 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
|
assert spec.compute_slots_since_epoch_start(spec.get_current_slot(store)) >= spec.SAFE_SLOTS_TO_UPDATE_JUSTIFIED
|
||||||
# Run on_block
|
# Run on_block
|
||||||
yield from add_block(spec, store, signed_block, test_steps)
|
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`
|
# # Apply blocks of `fork_3_state` to `store`
|
||||||
# for block in all_blocks:
|
# for block in all_blocks:
|
||||||
# if store.time < spec.compute_time_at_slot(fork_2_state, block.message.slot):
|
# 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)
|
# # 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)
|
# 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
|
# Process state
|
||||||
next_epoch(spec, 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(
|
state, store, _ = yield from apply_next_epoch_with_attestations(
|
||||||
spec, state, store, False, True, test_steps=test_steps)
|
spec, state, store, False, True, test_steps=test_steps)
|
||||||
|
|
|
@ -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,
|
||||||
|
)
|
|
@ -9,6 +9,13 @@ from eth2spec.test.context import (
|
||||||
low_balances, misc_balances,
|
low_balances, misc_balances,
|
||||||
)
|
)
|
||||||
import eth2spec.test.helpers.rewards as rewards_helpers
|
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
|
@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))
|
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_all_phases
|
||||||
@with_custom_state(balances_fn=low_balances, threshold_fn=lambda spec: spec.config.EJECTION_BALANCE)
|
@with_custom_state(balances_fn=low_balances, threshold_fn=lambda spec: spec.config.EJECTION_BALANCE)
|
||||||
@spec_test
|
@spec_test
|
||||||
|
@ -57,3 +79,52 @@ def test_full_random_low_balances_1(spec, state):
|
||||||
@single_phase
|
@single_phase
|
||||||
def test_full_random_misc_balances(spec, state):
|
def test_full_random_misc_balances(spec, state):
|
||||||
yield from rewards_helpers.run_test_full_random(spec, state, rng=Random(7070))
|
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)
|
||||||
|
|
|
@ -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.proposer_slashings import get_valid_proposer_slashing, check_proposer_slashing_effect
|
||||||
from eth2spec.test.helpers.attestations import get_valid_attestation
|
from eth2spec.test.helpers.attestations import get_valid_attestation
|
||||||
from eth2spec.test.helpers.deposits import prepare_state_and_deposit
|
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.voluntary_exits import prepare_signed_exits
|
||||||
from eth2spec.test.helpers.multi_operations import (
|
from eth2spec.test.helpers.multi_operations import (
|
||||||
run_slash_and_exit,
|
run_slash_and_exit,
|
||||||
|
@ -38,6 +39,7 @@ from eth2spec.test.context import (
|
||||||
with_custom_state,
|
with_custom_state,
|
||||||
large_validator_set,
|
large_validator_set,
|
||||||
is_post_altair,
|
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_randao(state, block.body)
|
||||||
spec.process_eth1_data(state, block.body)
|
spec.process_eth1_data(state, block.body)
|
||||||
spec.process_operations(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
|
# Insert post-state rot
|
||||||
block.state_root = state.hash_tree_root()
|
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_block.copy()
|
||||||
child_block.parent_root = state.latest_block_header.hash_tree_root()
|
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
|
# Show that normal path through transition fails
|
||||||
failed_state = state.copy()
|
failed_state = state.copy()
|
||||||
expect_assertion_error(
|
expect_assertion_error(
|
||||||
|
@ -974,12 +985,9 @@ def test_historical_batch(spec, state):
|
||||||
|
|
||||||
|
|
||||||
@with_all_phases
|
@with_all_phases
|
||||||
|
@with_presets([MINIMAL], reason="suffices to test eth1 data voting without long voting period")
|
||||||
@spec_state_test
|
@spec_state_test
|
||||||
def test_eth1_data_votes_consensus(spec, state):
|
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
|
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)
|
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_all_phases
|
||||||
|
@with_presets([MINIMAL], reason="suffices to test eth1 data voting without long voting period")
|
||||||
@spec_state_test
|
@spec_state_test
|
||||||
def test_eth1_data_votes_no_consensus(spec, state):
|
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
|
voting_period_slots = spec.EPOCHS_PER_ETH1_VOTING_PERIOD * spec.SLOTS_PER_EPOCH
|
||||||
|
|
||||||
pre_eth1_hash = state.eth1_data.block_hash
|
pre_eth1_hash = state.eth1_data.block_hash
|
||||||
|
|
|
@ -1,5 +1,13 @@
|
||||||
from eth2spec.test.context import with_all_phases, spec_state_test
|
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.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):
|
def run_on_tick(spec, store, time, new_justified_checkpoint=False):
|
||||||
|
@ -26,18 +34,92 @@ def test_basic(spec, state):
|
||||||
|
|
||||||
@with_all_phases
|
@with_all_phases
|
||||||
@spec_state_test
|
@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)
|
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(
|
# [Mock store.best_justified_checkpoint]
|
||||||
epoch=store.justified_checkpoint.epoch + 1,
|
# Create a block at epoch 1
|
||||||
root=b'\x55' * 32,
|
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
|
@with_all_phases
|
||||||
|
|
|
@ -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",
|
||||||
|
]
|
|
@ -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
|
|
@ -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:
|
`<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
|
```yaml
|
||||||
head: { -- Encoded 32-byte value from get_head(store)
|
head: {
|
||||||
slot: slot,
|
slot: int,
|
||||||
root: string,
|
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:
|
For example:
|
||||||
```yaml
|
```yaml
|
||||||
- checks:
|
- checks:
|
||||||
time: 144
|
time: 192
|
||||||
genesis_time: 0
|
head: {slot: 32, root: '0xdaa1d49d57594ced0c35688a6da133abb086d191a2ebdfd736fad95299325aeb'}
|
||||||
head: {slot: 17, root: '0xd2724c86002f7e1f8656ab44a341a409ad80e6e70a5225fd94835566deebb66f'}
|
justified_checkpoint: {epoch: 3, root: '0xc25faab4acab38d3560864ca01e4d5cc4dc2cd473da053fbc03c2669143a2de4'}
|
||||||
justified_checkpoint_root: '0xcea6ecd3d3188e32ebf611f960eebd45b6c6f477a7cff242fa567a42653bfc7c'
|
finalized_checkpoint: {epoch: 2, root: '0x40d32d6283ec11c53317a46808bc88f55657d93b95a1af920403187accf48f4f'}
|
||||||
finalized_checkpoint_root: '0xcea6ecd3d3188e32ebf611f960eebd45b6c6f477a7cff242fa567a42653bfc7c'
|
best_justified_checkpoint: {epoch: 3, root: '0xc25faab4acab38d3560864ca01e4d5cc4dc2cd473da053fbc03c2669143a2de4'}
|
||||||
best_justified_checkpoint: '0xcea6ecd3d3188e32ebf611f960eebd45b6c6f477a7cff242fa567a42653bfc7c'
|
|
||||||
```
|
```
|
||||||
|
|
||||||
*Note*: Each `checks` step may include one or multiple items. Each item has to be checked against the current store.
|
*Note*: Each `checks` step may include one or multiple items. Each item has to be checked against the current store.
|
||||||
|
|
|
@ -25,8 +25,7 @@ if __name__ == "__main__":
|
||||||
} # also run the previous phase 0 tests
|
} # 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.
|
# 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 = altair_mods
|
||||||
merge_mods = phase_0_mods
|
|
||||||
|
|
||||||
# TODO Custody Game testgen is disabled for now
|
# TODO Custody Game testgen is disabled for now
|
||||||
# custody_game_mods = {**{key: 'eth2spec.test.custody_game.epoch_processing.test_process_' + key for key in [
|
# custody_game_mods = {**{key: 'eth2spec.test.custody_game.epoch_processing.test_process_' + key for key in [
|
||||||
|
|
|
@ -5,7 +5,7 @@ from eth2spec.test.helpers.constants import PHASE0, ALTAIR, MERGE
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
phase_0_mods = {'finality': 'eth2spec.test.phase0.finality.test_finality'}
|
phase_0_mods = {'finality': 'eth2spec.test.phase0.finality.test_finality'}
|
||||||
altair_mods = phase_0_mods # No additional Altair specific finality tests
|
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 = {
|
all_mods = {
|
||||||
PHASE0: phase_0_mods,
|
PHASE0: phase_0_mods,
|
||||||
|
|
|
@ -9,8 +9,8 @@ if __name__ == "__main__":
|
||||||
]}
|
]}
|
||||||
# No additional Altair specific finality tests, yet.
|
# No additional Altair specific finality tests, yet.
|
||||||
altair_mods = phase_0_mods
|
altair_mods = phase_0_mods
|
||||||
# No specific Merge tests yet. TODO: rebase onto Altair testing later.
|
# No specific Merge tests yet.
|
||||||
merge_mods = phase_0_mods
|
merge_mods = altair_mods
|
||||||
|
|
||||||
all_mods = {
|
all_mods = {
|
||||||
PHASE0: phase_0_mods,
|
PHASE0: phase_0_mods,
|
||||||
|
|
|
@ -12,9 +12,9 @@ if __name__ == "__main__":
|
||||||
'voluntary_exit',
|
'voluntary_exit',
|
||||||
]}
|
]}
|
||||||
altair_mods = {
|
altair_mods = {
|
||||||
**{key: 'eth2spec.test.altair.block_processing.sync_aggregate.test_process_' + key for key in [
|
**{'sync_aggregate': [
|
||||||
'sync_aggregate',
|
'eth2spec.test.altair.block_processing.sync_aggregate.test_process_' + key
|
||||||
'sync_aggregate_random',
|
for key in ['sync_aggregate', 'sync_aggregate_random']
|
||||||
]},
|
]},
|
||||||
**phase_0_mods,
|
**phase_0_mods,
|
||||||
} # also run the previous phase 0 tests
|
} # 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 [
|
**{key: 'eth2spec.test.merge.block_processing.test_process_' + key for key in [
|
||||||
'execution_payload',
|
'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
|
# TODO Custody Game testgen is disabled for now
|
||||||
|
|
|
@ -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
|
|
@ -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
|
||||||
|
```
|
|
@ -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")
|
|
@ -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)
|
|
@ -0,0 +1,2 @@
|
||||||
|
pytest>=4.4
|
||||||
|
../../../[generator]
|
|
@ -14,7 +14,7 @@ if __name__ == "__main__":
|
||||||
# No additional merge specific rewards tests, yet.
|
# No additional merge specific rewards tests, yet.
|
||||||
# Note: Block rewards are non-epoch rewards and are tested as part of block processing tests.
|
# Note: Block rewards are non-epoch rewards and are tested as part of block processing tests.
|
||||||
# Transaction fees are part of the execution-layer.
|
# Transaction fees are part of the execution-layer.
|
||||||
merge_mods = phase_0_mods
|
merge_mods = altair_mods
|
||||||
|
|
||||||
all_mods = {
|
all_mods = {
|
||||||
PHASE0: phase_0_mods,
|
PHASE0: phase_0_mods,
|
||||||
|
|
|
@ -9,12 +9,10 @@ if __name__ == "__main__":
|
||||||
]}
|
]}
|
||||||
altair_mods = {**{key: 'eth2spec.test.altair.sanity.test_' + key for key in [
|
altair_mods = {**{key: 'eth2spec.test.altair.sanity.test_' + key for key in [
|
||||||
'blocks',
|
'blocks',
|
||||||
]}, **phase_0_mods} # also run the previous phase 0 tests
|
]}, **phase_0_mods}
|
||||||
|
|
||||||
# Altair-specific test cases are ignored, but should be included after the Merge is rebased onto Altair work.
|
|
||||||
merge_mods = {**{key: 'eth2spec.test.merge.sanity.test_' + key for key in [
|
merge_mods = {**{key: 'eth2spec.test.merge.sanity.test_' + key for key in [
|
||||||
'blocks',
|
'blocks',
|
||||||
]}, **phase_0_mods} # TODO: Merge inherits phase0 tests for now.
|
]}, **altair_mods}
|
||||||
|
|
||||||
all_mods = {
|
all_mods = {
|
||||||
PHASE0: phase_0_mods,
|
PHASE0: phase_0_mods,
|
||||||
|
|
Loading…
Reference in New Issue