Merge branch 'dev' into lc-blockfuncs

This commit is contained in:
Etan Kissling
2022-11-19 12:20:12 +01:00
93 changed files with 2434 additions and 927 deletions

View File

@@ -41,6 +41,8 @@ CURRENT_DIR = ${CURDIR}
LINTER_CONFIG_FILE = $(CURRENT_DIR)/linter.ini
GENERATOR_ERROR_LOG_FILE = $(CURRENT_DIR)/$(TEST_VECTOR_DIR)/testgen_error_log.txt
SCRIPTS_DIR = ${CURRENT_DIR}/scripts
export DAPP_SKIP_BUILD:=1
export DAPP_SRC:=$(SOLIDITY_DEPOSIT_CONTRACT_DIR)
export DAPP_LIB:=$(SOLIDITY_DEPOSIT_CONTRACT_DIR)/lib
@@ -195,6 +197,14 @@ $(TEST_VECTOR_DIR):
$(TEST_VECTOR_DIR)/:
$(info ignoring duplicate tests dir)
gen_kzg_setups:
cd $(SCRIPTS_DIR); \
if ! test -d venv; then python3 -m venv venv; fi; \
. venv/bin/activate; \
pip3 install -r requirements.txt; \
python3 ./gen_kzg_trusted_setups.py --secret=1337 --length=4 --output-dir ${CURRENT_DIR}/presets/minimal/trusted_setups; \
python3 ./gen_kzg_trusted_setups.py --secret=1337 --length=4096 --output-dir ${CURRENT_DIR}/presets/mainnet/trusted_setups
# For any generator, build it using the run_generator function.
# (creation of output dir is a dependency)
gen_%: $(TEST_VECTOR_DIR)

View File

@@ -1,23 +1,11 @@
# Mainnet preset - Capella
# Misc
# ---------------------------------------------------------------
# 2**8 (= 256) withdrawals
MAX_PARTIAL_WITHDRAWALS_PER_EPOCH: 256
# State list lengths
# ---------------------------------------------------------------
# 2**40 (= 1,099,511,627,776) withdrawals
WITHDRAWAL_QUEUE_LIMIT: 1099511627776
# Max operations per block
# ---------------------------------------------------------------
# 2**4 (= 16)
MAX_BLS_TO_EXECUTION_CHANGES: 16
# Execution
# ---------------------------------------------------------------
# 2**4 (= 16) withdrawals

File diff suppressed because one or more lines are too long

View File

@@ -1,17 +1,5 @@
# Minimal preset - Capella
# Misc
# ---------------------------------------------------------------
# [customized] 16 for more interesting tests at low validator count
MAX_PARTIAL_WITHDRAWALS_PER_EPOCH: 16
# State list lengths
# ---------------------------------------------------------------
# 2**40 (= 1,099,511,627,776) withdrawals
WITHDRAWAL_QUEUE_LIMIT: 1099511627776
# Max operations per block
# ---------------------------------------------------------------
# 2**4 (= 16)
@@ -20,5 +8,5 @@ MAX_BLS_TO_EXECUTION_CHANGES: 16
# Execution
# ---------------------------------------------------------------
# [customized] Lower than MAX_PARTIAL_WITHDRAWALS_PER_EPOCH so not all processed in one block
MAX_WITHDRAWALS_PER_PAYLOAD: 8
# [customized] 2**2 (= 4)
MAX_WITHDRAWALS_PER_PAYLOAD: 4

View File

@@ -0,0 +1 @@
{"setup_G1": ["0x97f1d3a73197d7942695638c4fa9ac0fc3688c4f9774b905a14e3a3f171bac586c55e83ff97a1aeffb3af00adb22c6bb", "0x854262641262cb9e056a8512808ea6864d903dbcad713fd6da8dddfa5ce40d85612c912063ace060ed8c4bf005bab839", "0x86f708eee5ae0cf40be36993e760d9cb3b2371f22db3209947c5d21ea68e55186b30871c50bf11ef29e5248bf42d5678", "0x94f9c0bafb23cbbf34a93a64243e3e0f934b57593651f3464de7dc174468123d9698f1b9dfa22bb5b6eb96eae002f29f"], "setup_G2": ["0x93e02b6052719f607dacd3a088274f65596bd0d09920b61ab5da61bbdc7f5049334cf11213945d57e5ac7d055d042b7e024aa2b2f08f0a91260805272dc51051c6e47ad4fa403b02b4510b647ae3d1770bac0326a805bbefd48056c8c121bdb8", "0x99aca9fb2f7760cecb892bf7262c176b334824f5727f680bba701a33e322cb6667531410dfc7c8e4321a3f0ea8af48cb1436638a2093123f046f0f504cc2a864825542873edbbc5d7ed17af125a4f2cf6433c6f4f61b81173726981dd989761d", "0x88e2e982982bf8231e747e9dfcd14c05bd02623d1332734d2af26246c6869fb56ee6c994843f593178a040495ba61f4a083b0e18110b1d9f5224783d8f9a895e8ee744e87929430e9ba96bd29251cbf61240b256d1525600f3d562894d93d659", "0xa2d33775e3d9e6af0d1b27d389e6c021a578e617a3d6627686db6288d4b3dffd7a847a00f7ef01828b7f42885b660e4204923402aca18fbae74ccd4e9c50dd8c2281b38dc09c022342ed1ac695d53f7081cb21f05fdfc0a3508c04759196fcd3"], "setup_G1_lagrange": ["0x91131b2e3c1e5f0b51df8970e67080032f411571b66d301436c46f25bbfddf9ca16756430dc470bdb0d85b47fedcdbc1", "0x934d35b2a46e169915718b77127b0d4efbacdad7fdde4593af7d21d37ebcb77fe6c8dde6b8a9537854d70ef1f291a585", "0x9410ca1d0342fe7419f02194281df45e1c1ff42fd8b439de5644cc312815c21ddd2e3eeb63fb807cf837e68b76668bd5", "0xb163df7e9baeb60f69b6ee5faa538c3a564b62eb8cde6a3616083c8cb2171eedd583c9143e7e916df59bf27da5e024e8"], "roots_of_unity": [1, 3465144826073652318776269530687742778270252468765361963008, 52435875175126190479447740508185965837690552500527637822603658699938581184512, 52435875175126190475982595682112313518914282969839895044333406231173219221505]}

View File

@@ -0,0 +1,36 @@
import os
from pathlib import Path
from eth2spec.utils.kzg import (
dump_kzg_trusted_setup_files,
)
if __name__ == '__main__':
import argparse
parser = argparse.ArgumentParser()
parser.add_argument(
"--secret",
dest="secret",
type=int,
required=True,
help='the secret of trusted setup',
)
parser.add_argument(
"--length",
dest="length",
type=int,
required=True,
help='the length of trusted setup',
)
parser.add_argument(
"-o",
"--output-dir",
dest="output_dir",
required=True,
help='the output directory',
)
args = parser.parse_args()
dump_kzg_trusted_setup_files(args.secret, args.length, args.output_dir)

1
scripts/requirements.txt Normal file
View File

@@ -0,0 +1 @@
../[generator]

View File

@@ -7,13 +7,14 @@ import os
import re
import string
import textwrap
from typing import Dict, NamedTuple, List, Sequence, Optional, TypeVar
from typing import Dict, NamedTuple, List, Sequence, Optional, TypeVar, Tuple
from abc import ABC, abstractmethod
import ast
import subprocess
import sys
import copy
from collections import OrderedDict
import json
# NOTE: have to programmatically include third-party dependencies in `setup.py`.
@@ -121,7 +122,7 @@ def _get_self_type_from_source(source: str) -> Optional[str]:
return args[0].annotation.id
def _get_class_info_from_source(source: str) -> (str, Optional[str]):
def _get_class_info_from_source(source: str) -> Tuple[str, Optional[str]]:
class_def = ast.parse(source).body[0]
base = class_def.bases[0]
if isinstance(base, ast.Name):
@@ -140,6 +141,28 @@ def _is_constant_id(name: str) -> bool:
return all(map(lambda c: c in string.ascii_uppercase + '_' + string.digits, name[1:]))
def _load_kzg_trusted_setups(preset_name):
"""
[TODO] it's not the final mainnet trusted setup.
We will update it after the KZG ceremony.
"""
file_path = str(Path(__file__).parent) + '/presets/' + preset_name + '/trusted_setups/testing_trusted_setups.json'
with open(file_path, 'r') as f:
json_data = json.load(f)
trusted_setup_G1 = json_data['setup_G1']
trusted_setup_G2 = json_data['setup_G2']
trusted_setup_G1_lagrange = json_data['setup_G1_lagrange']
roots_of_unity = json_data['roots_of_unity']
return trusted_setup_G1, trusted_setup_G2, trusted_setup_G1_lagrange, roots_of_unity
ALL_KZG_SETUPS = {
'minimal': _load_kzg_trusted_setups('minimal'),
'mainnet': _load_kzg_trusted_setups('mainnet')
}
ETH2_SPEC_COMMENT_PREFIX = "eth2spec:"
@@ -167,7 +190,16 @@ def _parse_value(name: str, typed_value: str, type_hint: Optional[str]=None) ->
return VariableDefinition(type_name=type_name, value=typed_value[i+1:-1], comment=comment, type_hint=type_hint)
def get_spec(file_name: Path, preset: Dict[str, str], config: Dict[str, str]) -> SpecObject:
def _update_constant_vars_with_kzg_setups(constant_vars, preset_name):
comment = "noqa: E501"
kzg_setups = ALL_KZG_SETUPS[preset_name]
constant_vars['KZG_SETUP_G1'] = VariableDefinition(constant_vars['KZG_SETUP_G1'].value, str(kzg_setups[0]), comment, None)
constant_vars['KZG_SETUP_G2'] = VariableDefinition(constant_vars['KZG_SETUP_G2'].value, str(kzg_setups[1]), comment, None)
constant_vars['KZG_SETUP_LAGRANGE'] = VariableDefinition(constant_vars['KZG_SETUP_LAGRANGE'].value, str(kzg_setups[2]), comment, None)
constant_vars['ROOTS_OF_UNITY'] = VariableDefinition(constant_vars['ROOTS_OF_UNITY'].value, str(kzg_setups[3]), comment, None)
def get_spec(file_name: Path, preset: Dict[str, str], config: Dict[str, str], preset_name=str) -> SpecObject:
functions: Dict[str, str] = {}
protocols: Dict[str, ProtocolDefinition] = {}
constant_vars: Dict[str, VariableDefinition] = {}
@@ -256,6 +288,10 @@ def get_spec(file_name: Path, preset: Dict[str, str], config: Dict[str, str]) ->
if comment == "skip":
should_skip = True
# Load KZG trusted setup from files
if any('KZG_SETUP' in name for name in constant_vars):
_update_constant_vars_with_kzg_setups(constant_vars, preset_name)
return SpecObject(
functions=functions,
protocols=protocols,
@@ -582,14 +618,13 @@ from eth2spec.bellatrix import {preset_name} as bellatrix
#
# EIP4844SpecBuilder
#
class EIP4844SpecBuilder(BellatrixSpecBuilder):
class EIP4844SpecBuilder(CapellaSpecBuilder):
fork: str = EIP4844
@classmethod
def imports(cls, preset_name: str):
return super().imports(preset_name) + f'''
from eth2spec.utils import kzg
from eth2spec.bellatrix import {preset_name} as bellatrix
from eth2spec.capella import {preset_name} as capella
'''
@@ -602,19 +637,31 @@ T = TypeVar('T') # For generic function
@classmethod
def sundry_functions(cls) -> str:
return super().sundry_functions() + '\n\n' + '''
# TODO: for mainnet, load pre-generated trusted setup file to reduce building time.
# TESTING_FIELD_ELEMENTS_PER_BLOB is hardcoded copy from minimal presets
TESTING_FIELD_ELEMENTS_PER_BLOB = 4
TESTING_SECRET = 1337
TESTING_KZG_SETUP_G1 = kzg.generate_setup(bls.G1, TESTING_SECRET, TESTING_FIELD_ELEMENTS_PER_BLOB)
TESTING_KZG_SETUP_G2 = kzg.generate_setup(bls.G2, TESTING_SECRET, TESTING_FIELD_ELEMENTS_PER_BLOB)
TESTING_KZG_SETUP_LAGRANGE = kzg.get_lagrange(TESTING_KZG_SETUP_G1)
#
# Temporarily disable Withdrawals functions for EIP4844 testnets
#
KZG_SETUP_G1 = [bls.G1_to_bytes48(p) for p in TESTING_KZG_SETUP_G1]
KZG_SETUP_G2 = [bls.G2_to_bytes96(p) for p in TESTING_KZG_SETUP_G2]
KZG_SETUP_LAGRANGE = TESTING_KZG_SETUP_LAGRANGE
ROOTS_OF_UNITY = kzg.compute_roots_of_unity(TESTING_FIELD_ELEMENTS_PER_BLOB)
def no_op(fn): # type: ignore
def wrapper(*args, **kw): # type: ignore
return None
return wrapper
def get_empty_list_result(fn): # type: ignore
def wrapper(*args, **kw): # type: ignore
return []
return wrapper
process_withdrawals = no_op(process_withdrawals)
process_bls_to_execution_change = no_op(process_bls_to_execution_change)
get_expected_withdrawals = get_empty_list_result(get_expected_withdrawals)
#
# End
#
def retrieve_blobs_sidecar(slot: Slot, beacon_block_root: Root) -> Optional[BlobsSidecar]:
return "TEST"'''
@@ -629,7 +676,6 @@ def retrieve_blobs_sidecar(slot: Slot, beacon_block_root: Root) -> Optional[Blob
return {**super().hardcoded_custom_type_dep_constants(spec_object), **constants}
spec_builders = {
builder.fork: builder
for builder in (Phase0SpecBuilder, AltairSpecBuilder, BellatrixSpecBuilder, CapellaSpecBuilder, EIP4844SpecBuilder)
@@ -880,7 +926,7 @@ def _build_spec(preset_name: str, fork: str,
source_files: Sequence[Path], preset_files: Sequence[Path], config_file: Path) -> str:
preset = load_preset(preset_files)
config = load_config(config_file)
all_specs = [get_spec(spec, preset, config) for spec in source_files]
all_specs = [get_spec(spec, preset, config, preset_name) for spec in source_files]
spec_object = all_specs[0]
for value in all_specs[1:]:
@@ -967,7 +1013,7 @@ class PySpecCommand(Command):
specs/bellatrix/p2p-interface.md
sync/optimistic.md
"""
if self.spec_fork == CAPELLA:
if self.spec_fork in (CAPELLA, EIP4844):
self.md_doc_paths += """
specs/capella/beacon-chain.md
specs/capella/fork.md

View File

@@ -59,7 +59,7 @@ New global topics are added to provide light clients with the latest updates.
This topic is used to propagate the latest `LightClientFinalityUpdate` to light clients, allowing them to keep track of the latest `finalized_header`.
The following validations MUST pass before forwarding the `finality_update` on the network.
- _[IGNORE]_ No other `finality_update` with a lower or equal `finalized_header.slot` was already forwarded on the network
- _[IGNORE]_ The `finalized_header.slot` is greater than that of all previously forwarded `finality_update`s
- _[IGNORE]_ The `finality_update` is received after the block at `signature_slot` was given enough time to propagate through the network -- i.e. validate that one-third of `finality_update.signature_slot` has transpired (`SECONDS_PER_SLOT / INTERVALS_PER_SLOT` seconds after the start of the slot, with a `MAXIMUM_GOSSIP_CLOCK_DISPARITY` allowance)
For full nodes, the following validations MUST additionally pass before forwarding the `finality_update` on the network.
@@ -87,7 +87,7 @@ Per `context = compute_fork_digest(fork_version, genesis_validators_root)`:
This topic is used to propagate the latest `LightClientOptimisticUpdate` to light clients, allowing them to keep track of the latest `optimistic_header`.
The following validations MUST pass before forwarding the `optimistic_update` on the network.
- _[IGNORE]_ No other `optimistic_update` with a lower or equal `attested_header.slot` was already forwarded on the network
- _[IGNORE]_ The `attested_header.slot` is greater than that of all previously forwarded `optimistic_update`s
- _[IGNORE]_ The `optimistic_update` is received after the block at `signature_slot` was given enough time to propagate through the network -- i.e. validate that one-third of `optimistic_update.signature_slot` has transpired (`SECONDS_PER_SLOT / INTERVALS_PER_SLOT` seconds after the start of the slot, with a `MAXIMUM_GOSSIP_CLOCK_DISPARITY` allowance)
For full nodes, the following validations MUST additionally pass before forwarding the `optimistic_update` on the network.

View File

@@ -11,11 +11,8 @@
- [Constants](#constants)
- [Domain types](#domain-types)
- [Preset](#preset)
- [Misc](#misc)
- [State list lengths](#state-list-lengths)
- [Max operations per block](#max-operations-per-block)
- [Execution](#execution)
- [Configuration](#configuration)
- [Containers](#containers)
- [New containers](#new-containers)
- [`Withdrawal`](#withdrawal)
@@ -27,17 +24,13 @@
- [`BeaconBlockBody`](#beaconblockbody)
- [`BeaconState`](#beaconstate)
- [Helpers](#helpers)
- [Beacon state mutators](#beacon-state-mutators)
- [`withdraw_balance`](#withdraw_balance)
- [Predicates](#predicates)
- [`has_eth1_withdrawal_credential`](#has_eth1_withdrawal_credential)
- [`is_fully_withdrawable_validator`](#is_fully_withdrawable_validator)
- [`is_partially_withdrawable_validator`](#is_partially_withdrawable_validator)
- [Beacon chain state transition function](#beacon-chain-state-transition-function)
- [Epoch processing](#epoch-processing)
- [Full withdrawals](#full-withdrawals)
- [Partial withdrawals](#partial-withdrawals)
- [Block processing](#block-processing)
- [New `get_expected_withdrawals`](#new-get_expected_withdrawals)
- [New `process_withdrawals`](#new-process_withdrawals)
- [Modified `process_execution_payload`](#modified-process_execution_payload)
- [Modified `process_operations`](#modified-process_operations)
@@ -75,18 +68,6 @@ We define the following Python custom types for type hinting and readability:
## Preset
### Misc
| Name | Value |
| - | - |
| `MAX_PARTIAL_WITHDRAWALS_PER_EPOCH` | `uint64(2**8)` (= 256) |
### State list lengths
| Name | Value | Unit |
| - | - | :-: |
| `WITHDRAWAL_QUEUE_LIMIT` | `uint64(2**40)` (= 1,099,511,627,776) | withdrawals enqueued in state |
### Max operations per block
| Name | Value |
@@ -99,8 +80,6 @@ We define the following Python custom types for type hinting and readability:
| - | - | - |
| `MAX_WITHDRAWALS_PER_PAYLOAD` | `uint64(2**4)` (= 16) | Maximum amount of withdrawals allowed in each payload |
## Configuration
## Containers
### New containers
@@ -241,32 +220,12 @@ class BeaconState(Container):
# Execution
latest_execution_payload_header: ExecutionPayloadHeader
# Withdrawals
withdrawal_queue: List[Withdrawal, WITHDRAWAL_QUEUE_LIMIT] # [New in Capella]
next_withdrawal_index: WithdrawalIndex # [New in Capella]
next_partial_withdrawal_validator_index: ValidatorIndex # [New in Capella]
next_withdrawal_validator_index: ValidatorIndex # [New in Capella]
```
## Helpers
### Beacon state mutators
#### `withdraw_balance`
```python
def withdraw_balance(state: BeaconState, validator_index: ValidatorIndex, amount: Gwei) -> None:
# Decrease the validator's balance
decrease_balance(state, validator_index, amount)
# Create a corresponding withdrawal receipt
withdrawal = Withdrawal(
index=state.next_withdrawal_index,
validator_index=validator_index,
address=ExecutionAddress(state.validators[validator_index].withdrawal_credentials[12:]),
amount=amount,
)
state.next_withdrawal_index = WithdrawalIndex(state.next_withdrawal_index + 1)
state.withdrawal_queue.append(withdrawal)
```
### Predicates
#### `has_eth1_withdrawal_credential`
@@ -307,66 +266,6 @@ def is_partially_withdrawable_validator(validator: Validator, balance: Gwei) ->
## Beacon chain state transition function
### Epoch processing
```python
def process_epoch(state: BeaconState) -> None:
process_justification_and_finalization(state)
process_inactivity_updates(state)
process_rewards_and_penalties(state)
process_registry_updates(state)
process_slashings(state)
process_eth1_data_reset(state)
process_effective_balance_updates(state)
process_slashings_reset(state)
process_randao_mixes_reset(state)
process_historical_roots_update(state)
process_participation_flag_updates(state)
process_sync_committee_updates(state)
process_full_withdrawals(state) # [New in Capella]
process_partial_withdrawals(state) # [New in Capella]
```
#### Full withdrawals
*Note*: The function `process_full_withdrawals` is new.
```python
def process_full_withdrawals(state: BeaconState) -> None:
current_epoch = get_current_epoch(state)
for index in range(len(state.validators)):
balance = state.balances[index]
validator = state.validators[index]
if is_fully_withdrawable_validator(validator, balance, current_epoch):
withdraw_balance(state, ValidatorIndex(index), balance)
```
#### Partial withdrawals
*Note*: The function `process_partial_withdrawals` is new.
```python
def process_partial_withdrawals(state: BeaconState) -> None:
partial_withdrawals_count = 0
# Begin where we left off last time
validator_index = state.next_partial_withdrawal_validator_index
for _ in range(len(state.validators)):
balance = state.balances[validator_index]
validator = state.validators[validator_index]
if is_partially_withdrawable_validator(validator, balance):
withdraw_balance(state, validator_index, balance - MAX_EFFECTIVE_BALANCE)
partial_withdrawals_count += 1
# Iterate to next validator to check for partial withdrawal
validator_index = ValidatorIndex((validator_index + 1) % len(state.validators))
# Exit if performed maximum allowable withdrawals
if partial_withdrawals_count == MAX_PARTIAL_WITHDRAWALS_PER_EPOCH:
break
state.next_partial_withdrawal_validator_index = validator_index
```
### Block processing
```python
@@ -377,23 +276,58 @@ def process_block(state: BeaconState, block: BeaconBlock) -> None:
process_execution_payload(state, block.body.execution_payload, EXECUTION_ENGINE) # [Modified in Capella]
process_randao(state, block.body)
process_eth1_data(state, block.body)
process_operations(state, block.body)
process_operations(state, block.body) # [Modified in Capella]
process_sync_aggregate(state, block.body.sync_aggregate)
```
#### New `get_expected_withdrawals`
```python
def get_expected_withdrawals(state: BeaconState) -> Sequence[Withdrawal]:
epoch = get_current_epoch(state)
withdrawal_index = state.next_withdrawal_index
validator_index = state.next_withdrawal_validator_index
withdrawals: List[Withdrawal] = []
for _ in range(len(state.validators)):
validator = state.validators[validator_index]
balance = state.balances[validator_index]
if is_fully_withdrawable_validator(validator, balance, epoch):
withdrawals.append(Withdrawal(
index=withdrawal_index,
validator_index=validator_index,
address=ExecutionAddress(validator.withdrawal_credentials[12:]),
amount=balance,
))
withdrawal_index += WithdrawalIndex(1)
elif is_partially_withdrawable_validator(validator, balance):
withdrawals.append(Withdrawal(
index=withdrawal_index,
validator_index=validator_index,
address=ExecutionAddress(validator.withdrawal_credentials[12:]),
amount=balance - MAX_EFFECTIVE_BALANCE,
))
withdrawal_index += WithdrawalIndex(1)
if len(withdrawals) == MAX_WITHDRAWALS_PER_PAYLOAD:
break
validator_index = ValidatorIndex((validator_index + 1) % len(state.validators))
return withdrawals
```
#### New `process_withdrawals`
```python
def process_withdrawals(state: BeaconState, payload: ExecutionPayload) -> None:
num_withdrawals = min(MAX_WITHDRAWALS_PER_PAYLOAD, len(state.withdrawal_queue))
dequeued_withdrawals = state.withdrawal_queue[:num_withdrawals]
expected_withdrawals = get_expected_withdrawals(state)
assert len(payload.withdrawals) == len(expected_withdrawals)
assert len(dequeued_withdrawals) == len(payload.withdrawals)
for dequeued_withdrawal, withdrawal in zip(dequeued_withdrawals, payload.withdrawals):
assert dequeued_withdrawal == withdrawal
# Remove dequeued withdrawals from state
state.withdrawal_queue = state.withdrawal_queue[num_withdrawals:]
for expected_withdrawal, withdrawal in zip(expected_withdrawals, payload.withdrawals):
assert withdrawal == expected_withdrawal
decrease_balance(state, withdrawal.validator_index, withdrawal.amount)
if len(expected_withdrawals) > 0:
latest_withdrawal = expected_withdrawals[-1]
state.next_withdrawal_index = WithdrawalIndex(latest_withdrawal.index + 1)
next_validator_index = ValidatorIndex((latest_withdrawal.validator_index + 1) % len(state.validators))
state.next_withdrawal_validator_index = next_validator_index
```
#### Modified `process_execution_payload`

View File

@@ -129,9 +129,8 @@ def upgrade_to_capella(pre: bellatrix.BeaconState) -> BeaconState:
# Execution-layer
latest_execution_payload_header=latest_execution_payload_header,
# Withdrawals
withdrawal_queue=[],
next_withdrawal_index=WithdrawalIndex(0),
next_partial_withdrawal_validator_index=ValidatorIndex(0),
next_withdrawal_validator_index=ValidatorIndex(0),
)
return post

View File

@@ -19,6 +19,8 @@
- [Constructing the `BeaconBlockBody`](#constructing-the-beaconblockbody)
- [ExecutionPayload](#executionpayload)
- [BLS to execution changes](#bls-to-execution-changes)
- [Enabling validator withdrawals](#enabling-validator-withdrawals)
- [Changing from BLS to execution withdrawal credentials](#changing-from-bls-to-execution-withdrawal-credentials)
<!-- END doctoc generated TOC please keep comment here to allow auto update -->
<!-- /TOC -->
@@ -59,12 +61,8 @@ All validator responsibilities remain unchanged other than those noted below.
expected withdrawals for the slot must be gathered from the `state` (utilizing the
helper `get_expected_withdrawals`) and passed into the `ExecutionEngine` within `prepare_execution_payload`.
```python
def get_expected_withdrawals(state: BeaconState) -> Sequence[Withdrawal]:
num_withdrawals = min(MAX_WITHDRAWALS_PER_PAYLOAD, len(state.withdrawal_queue))
return state.withdrawal_queue[:num_withdrawals]
```
*Note*: In this section, `state` is the state of the slot for the block proposal _without_ the block yet applied.
That is, `state` is the `previous_state` processed through any empty slots up to the assigned slot using `process_slots(previous_state, slot)`.
*Note*: The only change made to `prepare_execution_payload` is to call
`get_expected_withdrawals()` to set the new `withdrawals` field of `PayloadAttributes`.
@@ -111,3 +109,39 @@ def prepare_execution_payload(state: BeaconState,
##### BLS to execution changes
Up to `MAX_BLS_TO_EXECUTION_CHANGES`, [`BLSToExecutionChange`](./beacon-chain.md#blstoexecutionchange) objects can be included in the `block`. The BLS to execution changes must satisfy the verification conditions found in [BLS to execution change processing](./beacon-chain.md#new-process_bls_to_execution_change).
## Enabling validator withdrawals
Validator balances are fully or partially withdrawn via an automatic process.
For validators, there is one prerequisite for this automated process:
withdrawal credentials pointing to an execution layer address, i.e. having an `ETH1_ADDRESS_WITHDRAWAL_PREFIX`.
If a validator has a `BLS_WITHDRAWAL_PREFIX` withdrawal credential prefix, to participate in withdrawals the validator must
create a one-time message to change their withdrawal credential from the version authenticated with a BLS key to the
version compatible with the execution layer. This message -- a `BLSToExecutionChange` -- is available starting in Capella
Validators who wish to enable withdrawals **MUST** assemble, sign, and broadcast this message so that it is accepted
on the beacon chain. Validators who do not want to enable withdrawals and have the `BLS_WITHDRAWAL_PREFIX` version of
withdrawal credentials can delay creating this message until they are ready to enable withdrawals.
### Changing from BLS to execution withdrawal credentials
First, the validator must construct a valid [`BLSToExecutionChange`](./beacon-chain.md#blstoexecutionchange) `message`.
This `message` contains the `validator_index` for the validator who wishes to change their credentials, the `from_bls_pubkey` -- the BLS public key corresponding to the **withdrawal BLS secret key** used to form the `BLS_WITHDRAWAL_PREFIX` withdrawal credential, and the `to_execution_address` specifying the execution layer address to which the validator's balances will be withdrawn.
*Note*: The withdrawal key pair used to construct the `BLS_WITHDRAWAL_PREFIX` withdrawal credential should be distinct from the signing key pair used to operate the validator under typical circumstances. Consult your validator deposit tooling documentation for further details if you are not aware of the difference.
*Warning*: This message can only be included on-chain once and is
irreversible so ensure the correctness and accessibility to `to_execution_address`.
Next, the validator signs the assembled `message: BLSToExecutionChange` with the **withdrawal BLS secret key** and this
`signature` is placed into a `SignedBLSToExecutionChange` message along with the inner `BLSToExecutionChange` `message`.
Note that the `SignedBLSToExecutionChange` message should pass all of the validations in [`process_bls_to_execution_change`](./beacon-chain.md#new-process_bls_to_execution_change).
The `SignedBLSToExecutionChange` message should then be submitted to the consensus layer network. Once included on-chain,
the withdrawal credential change takes effect. No further action is required for a validator to enter into the automated
withdrawal process.
*Note*: A node *should* prioritize locally received `BLSToExecutionChange` operations to ensure these changes make it on-chain
through self published blocks even if the rest of the network censors.

View File

@@ -122,7 +122,7 @@ This backbone is based on a pure function of the *node* identity and time:
peers on a vertical topic can be found by searching the local peerstore for identities that hash to the desired topic(s),
assuming the peerstore already has a large enough variety of peers.
- Nodes can be held accountable for contributing to the backbone:
peers that particpate in DAS but are not active on the appropriate backbone topics can be scored down.
peers that participate in DAS but are not active on the appropriate backbone topics can be scored down.
*Note: This is experimental, DAS should be light enough for all participants to run, but scoring needs to undergo testing*
A node should anticipate backbone topics to subscribe to based their own identity.

View File

@@ -12,7 +12,6 @@
- [Custom types](#custom-types)
- [Constants](#constants)
- [Blob](#blob)
- [Domain types](#domain-types)
- [Preset](#preset)
- [Execution](#execution)
- [Configuration](#configuration)
@@ -34,13 +33,14 @@
- [`process_execution_payload`](#process_execution_payload)
- [Blob KZG commitments](#blob-kzg-commitments)
- [Testing](#testing)
- [Disabling Withdrawals](#disabling-withdrawals)
<!-- END doctoc generated TOC please keep comment here to allow auto update -->
<!-- /TOC -->
## Introduction
This upgrade adds blobs to the beacon chain as part of EIP-4844.
This upgrade adds blobs to the beacon chain as part of EIP-4844. This is an extension of the Capella upgrade.
## Custom types
@@ -57,12 +57,6 @@ This upgrade adds blobs to the beacon chain as part of EIP-4844.
| `BLOB_TX_TYPE` | `uint8(0x05)` |
| `VERSIONED_HASH_VERSION_KZG` | `Bytes1(0x01)` |
### Domain types
| Name | Value |
| - | - |
| `DOMAIN_BLOBS_SIDECAR` | `DomainType('0x0a000000')` |
## Preset
### Execution
@@ -95,7 +89,8 @@ class BeaconBlockBody(Container):
voluntary_exits: List[SignedVoluntaryExit, MAX_VOLUNTARY_EXITS]
sync_aggregate: SyncAggregate
# Execution
execution_payload: ExecutionPayload
execution_payload: ExecutionPayload # [Modified in EIP-4844]
bls_to_execution_changes: List[SignedBLSToExecutionChange, MAX_BLS_TO_EXECUTION_CHANGES]
blob_kzg_commitments: List[KZGCommitment, MAX_BLOBS_PER_BLOCK] # [New in EIP-4844]
```
@@ -116,10 +111,11 @@ class ExecutionPayload(Container):
timestamp: uint64
extra_data: ByteList[MAX_EXTRA_DATA_BYTES]
base_fee_per_gas: uint256
excess_blobs: uint64 # [New in EIP-4844]
excess_data_gas: uint256 # [New in EIP-4844]
# Extra payload fields
block_hash: Hash32 # Hash of execution block
transactions: List[Transaction, MAX_TRANSACTIONS_PER_PAYLOAD]
withdrawals: List[Withdrawal, MAX_WITHDRAWALS_PER_PAYLOAD]
```
#### `ExecutionPayloadHeader`
@@ -139,10 +135,11 @@ class ExecutionPayloadHeader(Container):
timestamp: uint64
extra_data: ByteList[MAX_EXTRA_DATA_BYTES]
base_fee_per_gas: uint256
excess_blobs: uint64 # [New in EIP-4844]
excess_data_gas: uint256 # [New in EIP-4844]
# Extra payload fields
block_hash: Hash32 # Hash of execution block
transactions_root: Root
withdrawals_root: Root
```
## Helper functions
@@ -203,10 +200,10 @@ See [the full details of `blob_versioned_hashes` offset calculation](https://gis
def tx_peek_blob_versioned_hashes(opaque_tx: Transaction) -> Sequence[VersionedHash]:
assert opaque_tx[0] == BLOB_TX_TYPE
message_offset = 1 + uint32.decode_bytes(opaque_tx[1:5])
# field offset: 32 + 8 + 32 + 32 + 8 + 4 + 32 + 4 + 4 = 156
# field offset: 32 + 8 + 32 + 32 + 8 + 4 + 32 + 4 + 4 + 32 = 188
blob_versioned_hashes_offset = (
message_offset
+ uint32.decode_bytes(opaque_tx[(message_offset + 156):(message_offset + 160)])
+ uint32.decode_bytes(opaque_tx[(message_offset + 188):(message_offset + 192)])
)
return [
VersionedHash(opaque_tx[x:(x + 32)])
@@ -234,7 +231,8 @@ def verify_kzg_commitments_against_transactions(transactions: Sequence[Transacti
def process_block(state: BeaconState, block: BeaconBlock) -> None:
process_block_header(state, block)
if is_execution_enabled(state, block.body):
process_execution_payload(state, block.body.execution_payload, EXECUTION_ENGINE)
process_withdrawals(state, block.body.execution_payload)
process_execution_payload(state, block.body.execution_payload, EXECUTION_ENGINE) # [Modified in EIP-4844]
process_randao(state, block.body)
process_eth1_data(state, block.body)
process_operations(state, block.body)
@@ -260,6 +258,7 @@ def process_execution_payload(state: BeaconState, payload: ExecutionPayload, exe
assert payload.timestamp == compute_timestamp_at_slot(state, state.slot)
# Verify the execution payload is valid
assert execution_engine.notify_new_payload(payload)
# Cache execution payload header
state.latest_execution_payload_header = ExecutionPayloadHeader(
parent_hash=payload.parent_hash,
@@ -274,9 +273,10 @@ def process_execution_payload(state: BeaconState, payload: ExecutionPayload, exe
timestamp=payload.timestamp,
extra_data=payload.extra_data,
base_fee_per_gas=payload.base_fee_per_gas,
excess_blobs=payload.excess_blobs, # [New in EIP-4844]
excess_data_gas=payload.excess_data_gas, # [New in EIP-4844]
block_hash=payload.block_hash,
transactions_root=hash_tree_root(payload.transactions),
withdrawals_root=hash_tree_root(payload.withdrawals),
)
```
@@ -342,3 +342,10 @@ def initialize_beacon_state_from_eth1(eth1_block_hash: Hash32,
return state
```
### Disabling Withdrawals
During testing we avoid Capella-specific updates to the state transition. We do this by replacing the following functions with a no-op implementation:
- `process_withdrawals`
- `process_bls_to_execution_change`
The `get_expected_withdrawals` function is also modified to return an empty withdrawals list. As such, the PayloadAttributes used to update forkchoice does not contain withdrawals.

View File

@@ -44,6 +44,8 @@ def compute_fork_version(epoch: Epoch) -> Version:
"""
if epoch >= EIP4844_FORK_EPOCH:
return EIP4844_FORK_VERSION
if epoch >= CAPELLA_FORK_EPOCH:
return CAPELLA_FORK_VERSION
if epoch >= BELLATRIX_FORK_EPOCH:
return BELLATRIX_FORK_VERSION
if epoch >= ALTAIR_FORK_EPOCH:
@@ -62,12 +64,29 @@ Note that for the pure EIP-4844 networks, we don't apply `upgrade_to_eip4844` si
### Upgrading the state
Since the `eip4844.BeaconState` format is equal to the `bellatrix.BeaconState` format, we only have to update `BeaconState.fork`.
Since the `eip4844.BeaconState` format is equal to the `capella.BeaconState` format, we only have to update `BeaconState.fork`.
```python
def upgrade_to_eip4844(pre: bellatrix.BeaconState) -> BeaconState:
# TODO: if Capella gets scheduled, add sync it with Capella.BeaconState
epoch = bellatrix.get_current_epoch(pre)
def upgrade_to_eip4844(pre: capella.BeaconState) -> BeaconState:
epoch = capella.get_current_epoch(pre)
latest_execution_payload_header = ExecutionPayloadHeader(
parent_hash=pre.latest_execution_payload_header.parent_hash,
fee_recipient=pre.latest_execution_payload_header.fee_recipient,
state_root=pre.latest_execution_payload_header.state_root,
receipts_root=pre.latest_execution_payload_header.receipts_root,
logs_bloom=pre.latest_execution_payload_header.logs_bloom,
prev_randao=pre.latest_execution_payload_header.prev_randao,
block_number=pre.latest_execution_payload_header.block_number,
gas_limit=pre.latest_execution_payload_header.gas_limit,
gas_used=pre.latest_execution_payload_header.gas_used,
timestamp=pre.latest_execution_payload_header.timestamp,
extra_data=pre.latest_execution_payload_header.extra_data,
base_fee_per_gas=pre.latest_execution_payload_header.base_fee_per_gas,
excess_data_gas=uint256(0), # [New in EIP-4844]
block_hash=pre.latest_execution_payload_header.block_hash,
transactions_root=pre.latest_execution_payload_header.transactions_root,
withdrawals_root=pre.latest_execution_payload_header.withdrawals_root,
)
post = BeaconState(
# Versioning
genesis_time=pre.genesis_time,
@@ -108,7 +127,10 @@ def upgrade_to_eip4844(pre: bellatrix.BeaconState) -> BeaconState:
current_sync_committee=pre.current_sync_committee,
next_sync_committee=pre.next_sync_committee,
# Execution-layer
latest_execution_payload_header=pre.latest_execution_payload_header,
latest_execution_payload_header=latest_execution_payload_header, # [Modified in EIP4844]
# Withdrawals
next_withdrawal_index=pre.next_withdrawal_index,
next_withdrawal_validator_index=pre.next_withdrawal_validator_index,
)
return post

View File

@@ -17,12 +17,14 @@ The specification of these changes continues in the same format as the network s
- [The gossip domain: gossipsub](#the-gossip-domain-gossipsub)
- [Topics and messages](#topics-and-messages)
- [Global topics](#global-topics)
- [`beacon_block`](#beacon_block)
- [`beacon_block_and_blobs_sidecar`](#beacon_block_and_blobs_sidecar)
- [Transitioning the gossip](#transitioning-the-gossip)
- [The Req/Resp domain](#the-reqresp-domain)
- [Messages](#messages)
- [BeaconBlocksByRange v2](#beaconblocksbyrange-v2)
- [BeaconBlocksByRoot v2](#beaconblocksbyroot-v2)
- [BeaconBlockAndBlobsSidecarByRoot v1](#beaconblockandblobssidecarbyroot-v1)
- [BlobsSidecarsByRange v1](#blobssidecarsbyrange-v1)
- [Design decision rationale](#design-decision-rationale)
- [Why are blobs relayed as a sidecar, separate from beacon blocks?](#why-are-blobs-relayed-as-a-sidecar-separate-from-beacon-blocks)
@@ -64,9 +66,9 @@ Some gossip meshes are upgraded in the fork of EIP4844 to support upgraded types
### Topics and messages
Topics follow the same specification as in prior upgrades.
All topics remain stable except the beacon block topic which is updated with the modified type.
The `beacon_block` topic is deprecated and replaced by the `beacon_block_and_blobs_sidecar` topic. All other topics remain stable.
The specification around the creation, validation, and dissemination of messages has not changed from the Bellatrix document unless explicitly noted here.
The specification around the creation, validation, and dissemination of messages has not changed from the Capella document unless explicitly noted here.
The derivation of the `message-id` remains stable.
@@ -81,11 +83,19 @@ The new topics along with the type of the `data` field of a gossipsub message ar
EIP4844 introduces a new global topic for beacon block and blobs-sidecars.
##### `beacon_block`
This topic is deprecated and clients **MUST NOT** expose in their topic set to any peer. Implementers do not need to do
anything beyond simply skip implementation, and it is explicitly called out as it is a departure from previous versioning
of this topic.
Refer to [the section below](#transitioning-the-gossip) for details on how to transition the gossip.
##### `beacon_block_and_blobs_sidecar`
This topic is used to propagate new signed and coupled beacon blocks and blobs sidecars to all nodes on the networks.
The following validations MUST pass before forwarding the `signed_beacon_block_and_blobs_sidecar` on the network.
In addition to the gossip validations for the `beacon_block` topic from prior specifications, the following validations MUST pass before forwarding the `signed_beacon_block_and_blobs_sidecar` on the network.
Alias `signed_beacon_block = signed_beacon_block_and_blobs_sidecar.beacon_block`, `block = signed_beacon_block.message`, `execution_payload = block.body.execution_payload`.
- _[REJECT]_ The KZG commitments of the blobs are all correctly encoded compressed BLS G1 Points.
-- i.e. `all(bls.KeyValidate(commitment) for commitment in block.body.blob_kzg_commitments)`
@@ -96,8 +106,8 @@ Alias `sidecar = signed_beacon_block_and_blobs_sidecar.blobs_sidecar`.
- _[IGNORE]_ the `sidecar.beacon_block_slot` is for the current slot (with a `MAXIMUM_GOSSIP_CLOCK_DISPARITY` allowance) -- i.e. `sidecar.beacon_block_slot == block.slot`.
- _[REJECT]_ the `sidecar.blobs` are all well formatted, i.e. the `BLSFieldElement` in valid range (`x < BLS_MODULUS`).
- _[REJECT]_ The KZG proof is a correctly encoded compressed BLS G1 Point -- i.e. `bls.KeyValidate(blobs_sidecar.kzg_aggregated_proof)`
Once the sidecar and beacon block are received together, `validate_blobs_sidecar` can unlock the data-availability fork-choice dependency.
- _[REJECT]_ The KZG commitments in the block are valid against the provided blobs sidecar.
-- i.e. `validate_blobs_sidecar(block.slot, hash_tree_root(block), block.body.blob_kzg_commitments, sidecar)`
### Transitioning the gossip
@@ -123,13 +133,15 @@ Per `context = compute_fork_digest(fork_version, genesis_validators_root)`:
| `GENESIS_FORK_VERSION` | `phase0.SignedBeaconBlock` |
| `ALTAIR_FORK_VERSION` | `altair.SignedBeaconBlock` |
| `BELLATRIX_FORK_VERSION` | `bellatrix.SignedBeaconBlock` |
| `CAPELLA_FORK_VERSION` | `capella.SignedBeaconBlock` |
| `EIP4844_FORK_VERSION` | `eip4844.SignedBeaconBlock` |
#### BeaconBlocksByRoot v2
**Protocol ID:** `/eth2/beacon_chain/req/beacon_blocks_by_root/2/`
The EIP-4844 fork-digest is introduced to the `context` enum to specify EIP-4844 beacon block type.
After `EIP4844_FORK_EPOCH`, `BeaconBlocksByRootV2` is replaced by `BeaconBlockAndBlobsSidecarByRootV1`
clients MUST support requesting blocks by root for pre-fork-epoch blocks.
Per `context = compute_fork_digest(fork_version, genesis_validators_root)`:
@@ -140,7 +152,43 @@ Per `context = compute_fork_digest(fork_version, genesis_validators_root)`:
| `GENESIS_FORK_VERSION` | `phase0.SignedBeaconBlock` |
| `ALTAIR_FORK_VERSION` | `altair.SignedBeaconBlock` |
| `BELLATRIX_FORK_VERSION` | `bellatrix.SignedBeaconBlock` |
| `EIP4844_FORK_VERSION` | `eip4844.SignedBeaconBlock` |
| `CAPELLA_FORK_VERSION` | `capella.SignedBeaconBlock` |
#### BeaconBlockAndBlobsSidecarByRoot v1
**Protocol ID:** `/eth2/beacon_chain/req/beacon_block_and_blobs_sidecar_by_root/1/`
Request Content:
```
(
List[Root, MAX_REQUEST_BLOCKS]
)
```
Response Content:
```
(
List[SignedBeaconBlockAndBlobsSidecar, MAX_REQUEST_BLOCKS]
)
```
Requests blocks by block root (= `hash_tree_root(SignedBeaconBlockAndBlobsSidecar.beacon_block.message)`).
The response is a list of `SignedBeaconBlockAndBlobsSidecar` whose length is less than or equal to the number of requests.
It may be less in the case that the responding peer is missing blocks and sidecars.
No more than `MAX_REQUEST_BLOCKS` may be requested at a time.
`BeaconBlockAndBlobsSidecarByRoot` is primarily used to recover recent blocks and sidecars (e.g. when receiving a block or attestation whose parent is unknown).
The response MUST consist of zero or more `response_chunk`.
Each _successful_ `response_chunk` MUST contain a single `SignedBeaconBlockAndBlobsSidecar` payload.
Clients MUST support requesting blocks and sidecars since the latest finalized epoch.
Clients MUST respond with at least one block and sidecar, if they have it.
Clients MAY limit the number of blocks and sidecars in the response.
#### BlobsSidecarsByRange v1

View File

@@ -32,6 +32,7 @@
- [KZG](#kzg)
- [`blob_to_kzg_commitment`](#blob_to_kzg_commitment)
- [`verify_kzg_proof`](#verify_kzg_proof)
- [`verify_kzg_proof_impl`](#verify_kzg_proof_impl)
- [`compute_kzg_proof`](#compute_kzg_proof)
- [`compute_aggregated_poly_and_commitment`](#compute_aggregated_poly_and_commitment)
- [`compute_aggregate_kzg_proof`](#compute_aggregate_kzg_proof)
@@ -45,6 +46,8 @@
This document specifies basic polynomial operations and KZG polynomial commitment operations as they are needed for the EIP-4844 specification. The implementations are not optimized for performance, but readability. All practical implementations should optimize the polynomial operations.
Functions flagged as "Public method" MUST be provided by the underlying KZG library as public functions. All other functions are private functions used internally by the KZG library.
## Custom types
| Name | SSZ equivalent | Description |
@@ -276,7 +279,7 @@ def evaluate_polynomial_in_evaluation_form(polynomial: Polynomial,
result = 0
for i in range(width):
result += div(int(polynomial[i]) * int(roots_of_unity_brp[i]), (int(z) - roots_of_unity_brp[i]))
result += div(int(polynomial[i]) * int(roots_of_unity_brp[i]), (int(z) - int(roots_of_unity_brp[i])))
result = result * (pow(z, width, BLS_MODULUS) - 1) * inverse_width % BLS_MODULUS
return result
```
@@ -289,6 +292,9 @@ KZG core functions. These are also defined in EIP-4844 execution specs.
```python
def blob_to_kzg_commitment(blob: Blob) -> KZGCommitment:
"""
Public method.
"""
return g1_lincomb(bit_reversal_permutation(KZG_SETUP_LAGRANGE), blob_to_polynomial(blob))
```
@@ -296,11 +302,27 @@ def blob_to_kzg_commitment(blob: Blob) -> KZGCommitment:
```python
def verify_kzg_proof(polynomial_kzg: KZGCommitment,
z: BLSFieldElement,
y: BLSFieldElement,
z: Bytes32,
y: Bytes32,
kzg_proof: KZGProof) -> bool:
"""
Verify KZG proof that ``p(z) == y`` where ``p(z)`` is the polynomial represented by ``polynomial_kzg``.
Receives inputs as bytes.
Public method.
"""
return verify_kzg_proof_impl(polynomial_kzg, bytes_to_bls_field(z), bytes_to_bls_field(y), kzg_proof)
```
#### `verify_kzg_proof_impl`
```python
def verify_kzg_proof_impl(polynomial_kzg: KZGCommitment,
z: BLSFieldElement,
y: BLSFieldElement,
kzg_proof: KZGProof) -> bool:
"""
Verify KZG proof that ``p(z) == y`` where ``p(z)`` is the polynomial represented by ``polynomial_kzg``.
"""
# Verify: P - y = Q * (X - z)
X_minus_z = bls.add(bls.bytes96_to_G2(KZG_SETUP_G2[1]), bls.multiply(bls.G2, BLS_MODULUS - z))
@@ -329,7 +351,7 @@ def compute_kzg_proof(polynomial: Polynomial, z: BLSFieldElement) -> KZGProof:
# Make sure we won't divide by zero during division
assert z not in ROOTS_OF_UNITY
denominator_poly = [(x - z) % BLS_MODULUS for x in bit_reversal_permutation(ROOTS_OF_UNITY)]
denominator_poly = [(int(x) - z) % BLS_MODULUS for x in bit_reversal_permutation(ROOTS_OF_UNITY)]
# Calculate quotient polynomial by doing point-by-point division
quotient_polynomial = [div(a, b) for a, b in zip(polynomial_shifted, denominator_poly)]
@@ -367,6 +389,9 @@ def compute_aggregated_poly_and_commitment(
```python
def compute_aggregate_kzg_proof(blobs: Sequence[Blob]) -> KZGProof:
"""
Public method.
"""
commitments = [blob_to_kzg_commitment(blob) for blob in blobs]
aggregated_poly, aggregated_poly_commitment, evaluation_challenge = compute_aggregated_poly_and_commitment(
blobs,
@@ -381,6 +406,9 @@ def compute_aggregate_kzg_proof(blobs: Sequence[Blob]) -> KZGProof:
def verify_aggregate_kzg_proof(blobs: Sequence[Blob],
expected_kzg_commitments: Sequence[KZGCommitment],
kzg_aggregated_proof: KZGCommitment) -> bool:
"""
Public method.
"""
aggregated_poly, aggregated_poly_commitment, evaluation_challenge = compute_aggregated_poly_and_commitment(
blobs,
expected_kzg_commitments,
@@ -390,5 +418,5 @@ def verify_aggregate_kzg_proof(blobs: Sequence[Blob],
y = evaluate_polynomial_in_evaluation_form(aggregated_poly, evaluation_challenge)
# Verify aggregated proof
return verify_kzg_proof(aggregated_poly_commitment, evaluation_challenge, y, kzg_aggregated_proof)
return verify_kzg_proof_impl(aggregated_poly_commitment, evaluation_challenge, y, kzg_aggregated_proof)
```

View File

@@ -29,7 +29,7 @@ This document represents the changes to be made in the code of an "honest valida
## Prerequisites
This document is an extension of the [Bellatrix -- Honest Validator](../bellatrix/validator.md) guide.
This document is an extension of the [Capella -- Honest Validator](../capella/validator.md) guide.
All behaviors and definitions defined in this document, and documents it extends, carry over unless explicitly noted or overridden.
All terminology, constants, functions, and protocol mechanics defined in the updated [Beacon Chain doc of EIP4844](./beacon-chain.md) are requisite for this document and used throughout.
@@ -60,7 +60,7 @@ Namely, the blob handling and the addition of `SignedBeaconBlockAndBlobsSidecar`
##### Blob KZG commitments
1. After retrieving the execution payload from the execution engine as specified in Bellatrix,
1. After retrieving the execution payload from the execution engine as specified in Capella,
use the `payload_id` to retrieve `blobs` and `blob_kzg_commitments` via `get_blobs_and_kzg_commitments(payload_id)`.
2. Validate `blobs` and `blob_kzg_commitments`:

View File

@@ -32,7 +32,7 @@
- [`interpolate_polynomial`](#interpolate_polynomial)
- [`evaluate_polynomial_in_evaluation_form`](#evaluate_polynomial_in_evaluation_form)
- [KZG Operations](#kzg-operations)
- [Elliptic curve helper functoins](#elliptic-curve-helper-functoins)
- [Elliptic curve helper functions](#elliptic-curve-helper-functions)
- [`elliptic_curve_lincomb`](#elliptic_curve_lincomb)
- [Hash to field](#hash-to-field)
- [`hash_to_bls_field`](#hash_to_bls_field)
@@ -47,7 +47,7 @@
## Introduction
This document specifies basic polynomial operations and KZG polynomial commitment operations as they are needed for the sharding specification. The implementations are not optimized for performance, but readability. All practical implementations should optimize the polynomial operations, and hints what the best known algorithms for these implementations are are included below.
This document specifies basic polynomial operations and KZG polynomial commitment operations as they are needed for the sharding specification. The implementations are not optimized for performance, but readability. All practical implementations should optimize the polynomial operations, and hints what the best known algorithms for these implementations are included below.
## Constants
@@ -313,7 +313,7 @@ def evaluate_polynomial_in_evaluation_form(poly: BLSPolynomialByEvaluations, x:
We are using the KZG10 polynomial commitment scheme (Kate, Zaverucha and Goldberg, 2010: https://www.iacr.org/archive/asiacrypt2010/6477178/6477178.pdf).
### Elliptic curve helper functoins
### Elliptic curve helper functions
#### `elliptic_curve_lincomb`

View File

@@ -143,7 +143,7 @@ This ensures that blocks are only optimistically imported if one or more of the
following are true:
1. The parent of the block has execution enabled.
1. The current slot (as per the system clock) is at least
2. The current slot (as per the system clock) is at least
`SAFE_SLOTS_TO_IMPORT_OPTIMISTICALLY` ahead of the slot of the block being
imported.

View File

@@ -10,12 +10,12 @@ Use an OS that has Python 3.8 or above. For example, Debian 11 (bullseye)
```sh
sudo apt install -y make git wget python3-venv gcc python3-dev
```
1. Download the latest [consensus specs](https://github.com/ethereum/consensus-specs)
2. Download the latest [consensus specs](https://github.com/ethereum/consensus-specs)
```sh
git clone https://github.com/ethereum/consensus-specs.git
cd consensus-specs
```
1. Create the specifications and tests:
3. Create the specifications and tests:
```sh
make install_test
make pyspec
@@ -31,12 +31,12 @@ To read more about creating the environment, [see here](core/pyspec/README.md).
cd ~/consensus-specs
. venv/bin/activate
```
1. Run a sanity check test against Altair fork:
2. Run a sanity check test against Altair fork:
```sh
cd tests/core/pyspec
python -m pytest -k test_empty_block_transition --fork altair eth2spec
```
1. The output should be similar to:
3. The output should be similar to:
```
============================= test session starts ==============================
platform linux -- Python 3.9.2, pytest-6.2.5, py-1.10.0, pluggy-1.0.0
@@ -114,7 +114,7 @@ In Python `yield` is used by [generators](https://wiki.python.org/moin/Generator
we can treat it as a partial return statement that doesn't stop the function's processing, only adds to a list
of return values. Here we add two values, the string `'pre'` and the initial state, to the list of return values.
[You can read more about test generators and how the are used here](generators).
[You can read more about test generators and how they are used here](generators).
```python
block = build_empty_block_for_next_slot(spec, state)
@@ -417,7 +417,7 @@ In the last line you can see two conditions being asserted:
1. `data.slot + MIN_ATTESTATION_INCLUSION_DELAY <= state.slot` which verifies that the attestation doesn't
arrive too early.
1. `state.slot <= data.slot + SLOTS_PER_EPOCH` which verifies that the attestation doesn't
2. `state.slot <= data.slot + SLOTS_PER_EPOCH` which verifies that the attestation doesn't
arrive too late.
This is how the consensus layer tests deal with edge cases, by asserting the conditions required for the
@@ -431,7 +431,7 @@ Now we'll write a similar test that verifies that being `SLOTS_PER_EPOCH` away i
`test_after_epoch_slots` function. We need two changes:
1. Call `transition_to_slot_via_block` with one less slot to advance
1. Don't tell `run_attestation_processing` to return an empty post state.
2. Don't tell `run_attestation_processing` to return an empty post state.
The modified function is:

View File

@@ -1 +1 @@
1.3.0-alpha.0
1.3.0-alpha.1

View File

@@ -4,7 +4,6 @@ import time
import shutil
import argparse
from pathlib import Path
from filelock import FileLock
import sys
import json
from typing import Iterable, AnyStr, Any, Callable
@@ -13,6 +12,7 @@ from ruamel.yaml import (
YAML,
)
from filelock import FileLock
from snappy import compress
from eth2spec.test import context
@@ -141,6 +141,10 @@ def run_generator(generator_name, test_providers: Iterable[TestProvider]):
tprov.prepare()
for test_case in tprov.make_cases():
# If preset list is assigned, filter by presets.
if len(presets) != 0 and test_case.preset_name not in presets:
continue
case_dir = (
Path(output_dir) / Path(test_case.preset_name) / Path(test_case.fork_name)
/ Path(test_case.runner_name) / Path(test_case.handler_name)
@@ -179,7 +183,16 @@ def run_generator(generator_name, test_providers: Iterable[TestProvider]):
try:
fn(case_dir)
except IOError as e:
sys.exit(f'Error when dumping test "{case_dir}", part "{name}", kind "{out_kind}": {e}')
error_message = (
f'[Error] error when dumping test "{case_dir}", part "{name}", kind "{out_kind}": {e}'
)
# Write to error log file
with log_file.open("a+") as f:
f.write(error_message)
traceback.print_exc(file=f)
f.write('\n')
sys.exit(error_message)
meta = dict()
@@ -210,13 +223,13 @@ def run_generator(generator_name, test_providers: Iterable[TestProvider]):
if not written_part:
print(f"test case {case_dir} did not produce any test case parts")
except Exception as e:
print(f"ERROR: failed to generate vector(s) for test {case_dir}: {e}")
traceback.print_exc()
# Write to log file
error_message = f"[ERROR] failed to generate vector(s) for test {case_dir}: {e}"
# Write to error log file
with log_file.open("a+") as f:
f.write(f"ERROR: failed to generate vector(s) for test {case_dir}: {e}")
f.write(error_message)
traceback.print_exc(file=f)
f.write('\n')
traceback.print_exc()
else:
# If no written_part, the only file was incomplete_tag_file. Clear the existing case_dir folder.
if not written_part:

View File

@@ -49,7 +49,7 @@ def generate_from_tests(runner_name: str, handler_name: str, src: Any,
preset_name=preset_name,
runner_name=runner_name,
handler_name=handler_name,
suite_name='pyspec_tests',
suite_name=getattr(tfn, 'suite_name', 'pyspec_tests'),
case_name=case_name,
# TODO: with_all_phases and other per-phase tooling, should be replaced with per-fork equivalent.
case_fn=lambda: tfn(generator_mode=True, phase=phase, preset=preset_name, bls_active=bls_active)

View File

@@ -212,7 +212,7 @@ def slash_some_validators_for_inactivity_scores_test(spec, state, rng=Random(404
next_epoch_via_block(spec, future_state)
proposer_index = spec.get_beacon_proposer_index(future_state)
# Slash ~1/4 of validaors
# Slash ~1/4 of validators
for validator_index in range(len(state.validators)):
if rng.choice(range(4)) == 0 and validator_index != proposer_index:
spec.slash_validator(state, validator_index)

View File

@@ -1,14 +1,17 @@
from eth2spec.test.context import (
spec_state_test,
with_altair_and_later,
with_test_suite_name,
)
@with_test_suite_name("BeaconState")
@with_altair_and_later
@spec_state_test
def test_current_sync_committee_merkle_proof(spec, state):
yield "state", state
current_sync_committee_branch = spec.compute_merkle_proof_for_state(state, spec.CURRENT_SYNC_COMMITTEE_INDEX)
yield "object", state
current_sync_committee_branch = \
spec.compute_merkle_proof_for_state(state, spec.CURRENT_SYNC_COMMITTEE_INDEX)
yield "proof", {
"leaf": "0x" + state.current_sync_committee.hash_tree_root().hex(),
"leaf_index": spec.CURRENT_SYNC_COMMITTEE_INDEX,
@@ -23,11 +26,13 @@ def test_current_sync_committee_merkle_proof(spec, state):
)
@with_test_suite_name("BeaconState")
@with_altair_and_later
@spec_state_test
def test_next_sync_committee_merkle_proof(spec, state):
yield "state", state
next_sync_committee_branch = spec.compute_merkle_proof_for_state(state, spec.NEXT_SYNC_COMMITTEE_INDEX)
yield "object", state
next_sync_committee_branch = \
spec.compute_merkle_proof_for_state(state, spec.NEXT_SYNC_COMMITTEE_INDEX)
yield "proof", {
"leaf": "0x" + state.next_sync_committee.hash_tree_root().hex(),
"leaf_index": spec.NEXT_SYNC_COMMITTEE_INDEX,
@@ -42,11 +47,13 @@ def test_next_sync_committee_merkle_proof(spec, state):
)
@with_test_suite_name("BeaconState")
@with_altair_and_later
@spec_state_test
def test_finality_root_merkle_proof(spec, state):
yield "state", state
finality_branch = spec.compute_merkle_proof_for_state(state, spec.FINALIZED_ROOT_INDEX)
yield "object", state
finality_branch = \
spec.compute_merkle_proof_for_state(state, spec.FINALIZED_ROOT_INDEX)
yield "proof", {
"leaf": "0x" + state.finalized_checkpoint.root.hex(),
"leaf_index": spec.FINALIZED_ROOT_INDEX,

View File

@@ -136,6 +136,6 @@ def test_inactivity_scores_full_participation_leaking(spec, state):
yield 'blocks', [signed_block]
yield 'post', state
# Full particiaption during a leak so all scores should decrease by 1
# Full participation during a leak so all scores should decrease by 1
for pre, post in zip(previous_inactivity_scores, state.inactivity_scores):
assert post == pre - 1

View File

@@ -1,12 +1,13 @@
from eth2spec.test.context import (
is_post_capella,
is_post_eip4844,
spec_configured_state_test,
spec_state_test_with_matching_config,
with_all_phases,
with_phases,
)
from eth2spec.test.helpers.constants import ALTAIR
from eth2spec.test.helpers.forks import (
is_post_capella, is_post_eip4844,
)
@with_phases([ALTAIR])

View File

@@ -55,7 +55,7 @@ def test_is_assigned_to_sync_committee(spec, state):
disqualified_pubkeys = set(
filter(lambda key: key not in sync_committee_pubkeys, active_pubkeys)
)
# NOTE: only check `disqualified_pubkeys` if SYNC_COMMITEE_SIZE < validator count
# NOTE: only check `disqualified_pubkeys` if SYNC_COMMITTEE_SIZE < validator count
if disqualified_pubkeys:
sample_size = 3
assert validator_count >= sample_size

View File

@@ -1,7 +1,8 @@
from eth2spec.test.helpers.constants import CAPELLA
from eth2spec.test.helpers.keys import pubkeys
from eth2spec.test.helpers.bls_to_execution_changes import get_signed_address_change
from eth2spec.test.context import spec_state_test, expect_assertion_error, with_capella_and_later, always_bls
from eth2spec.test.context import spec_state_test, expect_assertion_error, with_phases, always_bls
def run_bls_to_execution_change_processing(spec, state, signed_address_change, valid=True):
@@ -37,14 +38,14 @@ def run_bls_to_execution_change_processing(spec, state, signed_address_change, v
yield 'post', state
@with_capella_and_later
@with_phases([CAPELLA])
@spec_state_test
def test_success(spec, state):
signed_address_change = get_signed_address_change(spec, state)
yield from run_bls_to_execution_change_processing(spec, state, signed_address_change)
@with_capella_and_later
@with_phases([CAPELLA])
@spec_state_test
def test_success_not_activated(spec, state):
validator_index = 3
@@ -62,7 +63,7 @@ def test_success_not_activated(spec, state):
assert not spec.is_fully_withdrawable_validator(validator, balance, spec.get_current_epoch(state))
@with_capella_and_later
@with_phases([CAPELLA])
@spec_state_test
def test_success_in_activation_queue(spec, state):
validator_index = 3
@@ -80,7 +81,7 @@ def test_success_in_activation_queue(spec, state):
assert not spec.is_fully_withdrawable_validator(validator, balance, spec.get_current_epoch(state))
@with_capella_and_later
@with_phases([CAPELLA])
@spec_state_test
def test_success_in_exit_queue(spec, state):
validator_index = 3
@@ -93,7 +94,7 @@ def test_success_in_exit_queue(spec, state):
yield from run_bls_to_execution_change_processing(spec, state, signed_address_change)
@with_capella_and_later
@with_phases([CAPELLA])
@spec_state_test
def test_success_exited(spec, state):
validator_index = 4
@@ -110,7 +111,7 @@ def test_success_exited(spec, state):
assert not spec.is_fully_withdrawable_validator(validator, balance, spec.get_current_epoch(state))
@with_capella_and_later
@with_phases([CAPELLA])
@spec_state_test
def test_success_withdrawable(spec, state):
validator_index = 4
@@ -128,7 +129,7 @@ def test_success_withdrawable(spec, state):
assert spec.is_fully_withdrawable_validator(validator, balance, spec.get_current_epoch(state))
@with_capella_and_later
@with_phases([CAPELLA])
@spec_state_test
def test_fail_val_index_out_of_range(spec, state):
# Create for one validator beyond the validator list length
@@ -137,7 +138,7 @@ def test_fail_val_index_out_of_range(spec, state):
yield from run_bls_to_execution_change_processing(spec, state, signed_address_change, valid=False)
@with_capella_and_later
@with_phases([CAPELLA])
@spec_state_test
def test_fail_already_0x01(spec, state):
# Create for one validator beyond the validator list length
@@ -149,7 +150,7 @@ def test_fail_already_0x01(spec, state):
yield from run_bls_to_execution_change_processing(spec, state, signed_address_change, valid=False)
@with_capella_and_later
@with_phases([CAPELLA])
@spec_state_test
def test_fail_incorrect_from_bls_pubkey(spec, state):
# Create for one validator beyond the validator list length
@@ -163,7 +164,7 @@ def test_fail_incorrect_from_bls_pubkey(spec, state):
yield from run_bls_to_execution_change_processing(spec, state, signed_address_change, valid=False)
@with_capella_and_later
@with_phases([CAPELLA])
@spec_state_test
@always_bls
def test_fail_bad_signature(spec, state):

View File

@@ -1,7 +1,8 @@
from eth2spec.test.context import (
spec_state_test,
with_capella_and_later,
with_phases,
)
from eth2spec.test.helpers.constants import CAPELLA
from eth2spec.test.helpers.state import next_epoch_via_block
from eth2spec.test.helpers.deposits import (
prepare_state_and_deposit,
@@ -10,7 +11,7 @@ from eth2spec.test.helpers.deposits import (
from eth2spec.test.helpers.withdrawals import set_validator_fully_withdrawable
@with_capella_and_later
@with_phases([CAPELLA])
@spec_state_test
def test_success_top_up_to_withdrawn_validator(spec, state):
validator_index = 0

View File

@@ -1,28 +1,54 @@
import random
from eth2spec.test.context import (
spec_state_test,
expect_assertion_error,
with_presets,
with_phases,
)
from eth2spec.test.helpers.constants import MINIMAL, CAPELLA
from eth2spec.test.helpers.execution_payload import (
build_empty_execution_payload,
)
from eth2spec.test.context import spec_state_test, expect_assertion_error, with_capella_and_later
from eth2spec.test.helpers.state import next_slot
from eth2spec.test.helpers.random import (
randomize_state,
)
from eth2spec.test.helpers.state import (
next_epoch,
next_slot,
)
from eth2spec.test.helpers.withdrawals import (
prepare_expected_withdrawals,
set_eth1_withdrawal_credential_with_balance,
set_validator_fully_withdrawable,
set_validator_partially_withdrawable,
)
def prepare_withdrawal_queue(spec, state, num_withdrawals):
pre_queue_len = len(state.withdrawal_queue)
validator_len = len(state.validators)
for i in range(num_withdrawals):
withdrawal = spec.Withdrawal(
index=i + 5,
validator_index=(i + 1000) % validator_len,
address=b'\x42' * 20,
amount=200000 + i,
)
state.withdrawal_queue.append(withdrawal)
def verify_post_state(state, spec, expected_withdrawals,
fully_withdrawable_indices, partial_withdrawals_indices):
# Consider verifying also the condition when no withdrawals are expected.
if len(expected_withdrawals) == 0:
return
assert len(state.withdrawal_queue) == num_withdrawals + pre_queue_len
expected_withdrawals_validator_indices = [withdrawal.validator_index for withdrawal in expected_withdrawals]
assert state.next_withdrawal_index == expected_withdrawals[-1].index + 1
next_withdrawal_validator_index = (expected_withdrawals_validator_indices[-1] + 1) % len(state.validators)
assert state.next_withdrawal_validator_index == next_withdrawal_validator_index
for index in fully_withdrawable_indices:
if index in expected_withdrawals_validator_indices:
assert state.balances[index] == 0
else:
assert state.balances[index] > 0
for index in partial_withdrawals_indices:
if index in expected_withdrawals_validator_indices:
assert state.balances[index] == spec.MAX_EFFECTIVE_BALANCE
else:
assert state.balances[index] > spec.MAX_EFFECTIVE_BALANCE
def run_withdrawals_processing(spec, state, execution_payload, valid=True):
def run_withdrawals_processing(spec, state, execution_payload, num_expected_withdrawals=None,
fully_withdrawable_indices=None, partial_withdrawals_indices=None, valid=True):
"""
Run ``process_execution_payload``, yielding:
- pre-state ('pre')
@@ -30,10 +56,12 @@ def run_withdrawals_processing(spec, state, execution_payload, valid=True):
- post-state ('post').
If ``valid == False``, run expecting ``AssertionError``
"""
expected_withdrawals = spec.get_expected_withdrawals(state)
assert len(expected_withdrawals) <= spec.MAX_WITHDRAWALS_PER_PAYLOAD
if num_expected_withdrawals is not None:
assert len(expected_withdrawals) == num_expected_withdrawals
pre_withdrawal_queue = state.withdrawal_queue.copy()
num_withdrawals = min(spec.MAX_WITHDRAWALS_PER_PAYLOAD, len(pre_withdrawal_queue))
pre_state = state.copy()
yield 'pre', state
yield 'execution_payload', execution_payload
@@ -46,18 +74,23 @@ def run_withdrawals_processing(spec, state, execution_payload, valid=True):
yield 'post', state
if len(pre_withdrawal_queue) == 0:
assert len(state.withdrawal_queue) == 0
elif len(pre_withdrawal_queue) <= num_withdrawals:
assert len(state.withdrawal_queue) == 0
else:
assert state.withdrawal_queue == pre_withdrawal_queue[num_withdrawals:]
if len(expected_withdrawals) == 0:
assert state == pre_state
elif len(expected_withdrawals) < spec.MAX_WITHDRAWALS_PER_PAYLOAD:
assert len(spec.get_expected_withdrawals(state)) == 0
elif len(expected_withdrawals) > spec.MAX_WITHDRAWALS_PER_PAYLOAD:
raise ValueError('len(expected_withdrawals) should not be greater than MAX_WITHDRAWALS_PER_PAYLOAD')
if fully_withdrawable_indices is not None or partial_withdrawals_indices is not None:
verify_post_state(state, spec, expected_withdrawals, fully_withdrawable_indices, partial_withdrawals_indices)
return expected_withdrawals
@with_capella_and_later
@with_phases([CAPELLA])
@spec_state_test
def test_success_empty_queue(spec, state):
assert len(state.withdrawal_queue) == 0
def test_success_zero_expected_withdrawals(spec, state):
assert len(spec.get_expected_withdrawals(state)) == 0
next_slot(spec, state)
execution_payload = build_empty_execution_payload(spec, state)
@@ -65,48 +98,98 @@ def test_success_empty_queue(spec, state):
yield from run_withdrawals_processing(spec, state, execution_payload)
@with_capella_and_later
@with_phases([CAPELLA])
@spec_state_test
def test_success_one_in_queue(spec, state):
prepare_withdrawal_queue(spec, state, 1)
def test_success_one_full_withdrawal(spec, state):
fully_withdrawable_indices, partial_withdrawals_indices = prepare_expected_withdrawals(
spec, state, num_full_withdrawals=1)
assert len(fully_withdrawable_indices) == 1
assert len(partial_withdrawals_indices) == 0
next_slot(spec, state)
execution_payload = build_empty_execution_payload(spec, state)
yield from run_withdrawals_processing(spec, state, execution_payload)
yield from run_withdrawals_processing(
spec, state, execution_payload,
fully_withdrawable_indices=fully_withdrawable_indices,
partial_withdrawals_indices=partial_withdrawals_indices)
@with_capella_and_later
@with_phases([CAPELLA])
@spec_state_test
def test_success_max_per_slot_in_queue(spec, state):
prepare_withdrawal_queue(spec, state, spec.MAX_WITHDRAWALS_PER_PAYLOAD)
def test_success_one_partial_withdrawal(spec, state):
fully_withdrawable_indices, partial_withdrawals_indices = prepare_expected_withdrawals(
spec, state, num_partial_withdrawals=1)
assert len(fully_withdrawable_indices) == 0
assert len(partial_withdrawals_indices) == 1
for index in partial_withdrawals_indices:
assert state.balances[index] > spec.MAX_EFFECTIVE_BALANCE
next_slot(spec, state)
execution_payload = build_empty_execution_payload(spec, state)
yield from run_withdrawals_processing(spec, state, execution_payload)
yield from run_withdrawals_processing(
spec, state, execution_payload,
fully_withdrawable_indices=fully_withdrawable_indices,
partial_withdrawals_indices=partial_withdrawals_indices
)
@with_capella_and_later
@with_phases([CAPELLA])
@spec_state_test
def test_success_a_lot_in_queue(spec, state):
prepare_withdrawal_queue(spec, state, spec.MAX_WITHDRAWALS_PER_PAYLOAD * 4)
def test_success_max_per_slot(spec, state):
num_full_withdrawals = spec.MAX_WITHDRAWALS_PER_PAYLOAD // 2
num_partial_withdrawals = spec.MAX_WITHDRAWALS_PER_PAYLOAD - num_full_withdrawals
fully_withdrawable_indices, partial_withdrawals_indices = prepare_expected_withdrawals(
spec, state,
num_full_withdrawals=num_full_withdrawals, num_partial_withdrawals=num_partial_withdrawals)
next_slot(spec, state)
execution_payload = build_empty_execution_payload(spec, state)
yield from run_withdrawals_processing(spec, state, execution_payload)
yield from run_withdrawals_processing(
spec, state, execution_payload,
fully_withdrawable_indices=fully_withdrawable_indices,
partial_withdrawals_indices=partial_withdrawals_indices)
@with_phases([CAPELLA])
@spec_state_test
def test_success_all_fully_withdrawable(spec, state):
fully_withdrawable_indices, partial_withdrawals_indices = prepare_expected_withdrawals(
spec, state, num_full_withdrawals=len(state.validators))
next_slot(spec, state)
execution_payload = build_empty_execution_payload(spec, state)
yield from run_withdrawals_processing(
spec, state, execution_payload,
fully_withdrawable_indices=fully_withdrawable_indices,
partial_withdrawals_indices=partial_withdrawals_indices)
@with_phases([CAPELLA])
@spec_state_test
def test_success_all_partially_withdrawable(spec, state):
fully_withdrawable_indices, partial_withdrawals_indices = prepare_expected_withdrawals(
spec, state, num_partial_withdrawals=len(state.validators))
next_slot(spec, state)
execution_payload = build_empty_execution_payload(spec, state)
yield from run_withdrawals_processing(
spec, state, execution_payload,
fully_withdrawable_indices=fully_withdrawable_indices,
partial_withdrawals_indices=partial_withdrawals_indices)
#
# Failure cases in which the number of withdrawals in the execution_payload is incorrect
#
@with_capella_and_later
@with_phases([CAPELLA])
@spec_state_test
def test_fail_empty_queue_non_empty_withdrawals(spec, state):
assert len(state.withdrawal_queue) == 0
def test_fail_non_withdrawable_non_empty_withdrawals(spec, state):
next_slot(spec, state)
execution_payload = build_empty_execution_payload(spec, state)
withdrawal = spec.Withdrawal(
@@ -120,10 +203,10 @@ def test_fail_empty_queue_non_empty_withdrawals(spec, state):
yield from run_withdrawals_processing(spec, state, execution_payload, valid=False)
@with_capella_and_later
@with_phases([CAPELLA])
@spec_state_test
def test_fail_one_in_queue_none_in_withdrawals(spec, state):
prepare_withdrawal_queue(spec, state, 1)
def test_fail_one_expected_full_withdrawal_and_none_in_withdrawals(spec, state):
prepare_expected_withdrawals(spec, state, num_full_withdrawals=1)
next_slot(spec, state)
execution_payload = build_empty_execution_payload(spec, state)
@@ -132,10 +215,22 @@ def test_fail_one_in_queue_none_in_withdrawals(spec, state):
yield from run_withdrawals_processing(spec, state, execution_payload, valid=False)
@with_capella_and_later
@with_phases([CAPELLA])
@spec_state_test
def test_fail_one_in_queue_two_in_withdrawals(spec, state):
prepare_withdrawal_queue(spec, state, 1)
def test_fail_one_expected_partial_withdrawal_and_none_in_withdrawals(spec, state):
prepare_expected_withdrawals(spec, state, num_partial_withdrawals=1)
next_slot(spec, state)
execution_payload = build_empty_execution_payload(spec, state)
execution_payload.withdrawals = []
yield from run_withdrawals_processing(spec, state, execution_payload, valid=False)
@with_phases([CAPELLA])
@spec_state_test
def test_fail_one_expected_full_withdrawal_and_duplicate_in_withdrawals(spec, state):
prepare_expected_withdrawals(spec, state, num_full_withdrawals=2)
next_slot(spec, state)
execution_payload = build_empty_execution_payload(spec, state)
@@ -144,10 +239,22 @@ def test_fail_one_in_queue_two_in_withdrawals(spec, state):
yield from run_withdrawals_processing(spec, state, execution_payload, valid=False)
@with_capella_and_later
@with_phases([CAPELLA])
@spec_state_test
def test_fail_max_per_slot_in_queue_one_less_in_withdrawals(spec, state):
prepare_withdrawal_queue(spec, state, spec.MAX_WITHDRAWALS_PER_PAYLOAD)
def test_fail_two_expected_partial_withdrawal_and_duplicate_in_withdrawals(spec, state):
prepare_expected_withdrawals(spec, state, num_partial_withdrawals=2)
next_slot(spec, state)
execution_payload = build_empty_execution_payload(spec, state)
execution_payload.withdrawals.append(execution_payload.withdrawals[0].copy())
yield from run_withdrawals_processing(spec, state, execution_payload, valid=False)
@with_phases([CAPELLA])
@spec_state_test
def test_fail_max_per_slot_full_withdrawals_and_one_less_in_withdrawals(spec, state):
prepare_expected_withdrawals(spec, state, num_full_withdrawals=spec.MAX_WITHDRAWALS_PER_PAYLOAD)
next_slot(spec, state)
execution_payload = build_empty_execution_payload(spec, state)
@@ -156,10 +263,47 @@ def test_fail_max_per_slot_in_queue_one_less_in_withdrawals(spec, state):
yield from run_withdrawals_processing(spec, state, execution_payload, valid=False)
@with_capella_and_later
@with_phases([CAPELLA])
@spec_state_test
def test_fail_a_lot_in_queue_too_few_in_withdrawals(spec, state):
prepare_withdrawal_queue(spec, state, spec.MAX_WITHDRAWALS_PER_PAYLOAD * 4)
def test_fail_max_per_slot_partial_withdrawals_and_one_less_in_withdrawals(spec, state):
prepare_expected_withdrawals(spec, state, num_partial_withdrawals=spec.MAX_WITHDRAWALS_PER_PAYLOAD)
next_slot(spec, state)
execution_payload = build_empty_execution_payload(spec, state)
execution_payload.withdrawals = execution_payload.withdrawals[:-1]
yield from run_withdrawals_processing(spec, state, execution_payload, valid=False)
@with_phases([CAPELLA])
@spec_state_test
def test_fail_a_lot_fully_withdrawable_too_few_in_withdrawals(spec, state):
prepare_expected_withdrawals(spec, state, num_full_withdrawals=spec.MAX_WITHDRAWALS_PER_PAYLOAD * 4)
next_slot(spec, state)
execution_payload = build_empty_execution_payload(spec, state)
execution_payload.withdrawals = execution_payload.withdrawals[:-1]
yield from run_withdrawals_processing(spec, state, execution_payload, valid=False)
@with_phases([CAPELLA])
@spec_state_test
def test_fail_a_lot_partially_withdrawable_too_few_in_withdrawals(spec, state):
prepare_expected_withdrawals(spec, state, num_partial_withdrawals=spec.MAX_WITHDRAWALS_PER_PAYLOAD * 4)
next_slot(spec, state)
execution_payload = build_empty_execution_payload(spec, state)
execution_payload.withdrawals = execution_payload.withdrawals[:-1]
yield from run_withdrawals_processing(spec, state, execution_payload, valid=False)
@with_phases([CAPELLA])
@spec_state_test
def test_fail_a_lot_mixed_withdrawable_in_queue_too_few_in_withdrawals(spec, state):
prepare_expected_withdrawals(spec, state, num_full_withdrawals=spec.MAX_WITHDRAWALS_PER_PAYLOAD * 4,
num_partial_withdrawals=spec.MAX_WITHDRAWALS_PER_PAYLOAD * 4)
next_slot(spec, state)
execution_payload = build_empty_execution_payload(spec, state)
@@ -172,10 +316,10 @@ def test_fail_a_lot_in_queue_too_few_in_withdrawals(spec, state):
# Failure cases in which the withdrawals in the execution_payload are incorrect
#
@with_capella_and_later
@with_phases([CAPELLA])
@spec_state_test
def test_fail_incorrect_dequeue_index(spec, state):
prepare_withdrawal_queue(spec, state, 1)
def test_fail_incorrect_withdrawal_index(spec, state):
prepare_expected_withdrawals(spec, state, num_full_withdrawals=1)
next_slot(spec, state)
execution_payload = build_empty_execution_payload(spec, state)
@@ -184,10 +328,10 @@ def test_fail_incorrect_dequeue_index(spec, state):
yield from run_withdrawals_processing(spec, state, execution_payload, valid=False)
@with_capella_and_later
@with_phases([CAPELLA])
@spec_state_test
def test_fail_incorrect_dequeue_address(spec, state):
prepare_withdrawal_queue(spec, state, 1)
def test_fail_incorrect_address_full(spec, state):
prepare_expected_withdrawals(spec, state, num_full_withdrawals=1)
next_slot(spec, state)
execution_payload = build_empty_execution_payload(spec, state)
@@ -196,10 +340,22 @@ def test_fail_incorrect_dequeue_address(spec, state):
yield from run_withdrawals_processing(spec, state, execution_payload, valid=False)
@with_capella_and_later
@with_phases([CAPELLA])
@spec_state_test
def test_fail_incorrect_dequeue_amount(spec, state):
prepare_withdrawal_queue(spec, state, 1)
def test_fail_incorrect_address_partial(spec, state):
prepare_expected_withdrawals(spec, state, num_partial_withdrawals=1)
next_slot(spec, state)
execution_payload = build_empty_execution_payload(spec, state)
execution_payload.withdrawals[0].address = b'\xff' * 20
yield from run_withdrawals_processing(spec, state, execution_payload, valid=False)
@with_phases([CAPELLA])
@spec_state_test
def test_fail_incorrect_amount_full(spec, state):
prepare_expected_withdrawals(spec, state, num_full_withdrawals=1)
next_slot(spec, state)
execution_payload = build_empty_execution_payload(spec, state)
@@ -208,10 +364,22 @@ def test_fail_incorrect_dequeue_amount(spec, state):
yield from run_withdrawals_processing(spec, state, execution_payload, valid=False)
@with_capella_and_later
@with_phases([CAPELLA])
@spec_state_test
def test_fail_one_of_many_dequeued_incorrectly(spec, state):
prepare_withdrawal_queue(spec, state, spec.MAX_WITHDRAWALS_PER_PAYLOAD * 4)
def test_fail_incorrect_amount_partial(spec, state):
prepare_expected_withdrawals(spec, state, num_full_withdrawals=1)
next_slot(spec, state)
execution_payload = build_empty_execution_payload(spec, state)
execution_payload.withdrawals[0].amount += 1
yield from run_withdrawals_processing(spec, state, execution_payload, valid=False)
@with_phases([CAPELLA])
@spec_state_test
def test_fail_one_of_many_incorrectly_full(spec, state):
prepare_expected_withdrawals(spec, state, num_full_withdrawals=spec.MAX_WITHDRAWALS_PER_PAYLOAD * 4)
next_slot(spec, state)
execution_payload = build_empty_execution_payload(spec, state)
@@ -226,10 +394,28 @@ def test_fail_one_of_many_dequeued_incorrectly(spec, state):
yield from run_withdrawals_processing(spec, state, execution_payload, valid=False)
@with_capella_and_later
@with_phases([CAPELLA])
@spec_state_test
def test_fail_many_dequeued_incorrectly(spec, state):
prepare_withdrawal_queue(spec, state, spec.MAX_WITHDRAWALS_PER_PAYLOAD * 4)
def test_fail_one_of_many_incorrectly_partial(spec, state):
prepare_expected_withdrawals(spec, state, num_partial_withdrawals=spec.MAX_WITHDRAWALS_PER_PAYLOAD * 4)
next_slot(spec, state)
execution_payload = build_empty_execution_payload(spec, state)
num_withdrawals = len(execution_payload.withdrawals)
# Pick withdrawal in middle of list and mutate
withdrawal = execution_payload.withdrawals[num_withdrawals // 2]
withdrawal.index += 1
withdrawal.address = b'\x99' * 20
withdrawal.amount += 4000000
yield from run_withdrawals_processing(spec, state, execution_payload, valid=False)
@with_phases([CAPELLA])
@spec_state_test
def test_fail_many_incorrectly_full(spec, state):
prepare_expected_withdrawals(spec, state, num_full_withdrawals=spec.MAX_WITHDRAWALS_PER_PAYLOAD * 4)
next_slot(spec, state)
execution_payload = build_empty_execution_payload(spec, state)
@@ -242,3 +428,367 @@ def test_fail_many_dequeued_incorrectly(spec, state):
withdrawal.amount += 1
yield from run_withdrawals_processing(spec, state, execution_payload, valid=False)
@with_phases([CAPELLA])
@spec_state_test
def test_fail_many_incorrectly_partial(spec, state):
prepare_expected_withdrawals(spec, state, num_partial_withdrawals=spec.MAX_WITHDRAWALS_PER_PAYLOAD * 4)
next_slot(spec, state)
execution_payload = build_empty_execution_payload(spec, state)
for i, withdrawal in enumerate(execution_payload.withdrawals):
if i % 3 == 0:
withdrawal.index += 1
elif i % 3 == 1:
withdrawal.address = i.to_bytes(20, 'big')
else:
withdrawal.amount += 1
yield from run_withdrawals_processing(spec, state, execution_payload, valid=False)
#
# More full withdrawal cases
#
@with_phases([CAPELLA])
@spec_state_test
def test_withdrawable_epoch_but_0_balance(spec, state):
current_epoch = spec.get_current_epoch(state)
set_validator_fully_withdrawable(spec, state, 0, current_epoch)
state.validators[0].effective_balance = 10000000000
state.balances[0] = 0
execution_payload = build_empty_execution_payload(spec, state)
yield from run_withdrawals_processing(spec, state, execution_payload, num_expected_withdrawals=0)
@with_phases([CAPELLA])
@spec_state_test
def test_withdrawable_epoch_but_0_effective_balance_0_balance(spec, state):
current_epoch = spec.get_current_epoch(state)
set_validator_fully_withdrawable(spec, state, 0, current_epoch)
state.validators[0].effective_balance = 0
state.balances[0] = 0
execution_payload = build_empty_execution_payload(spec, state)
yield from run_withdrawals_processing(spec, state, execution_payload, num_expected_withdrawals=0)
@with_phases([CAPELLA])
@spec_state_test
def test_withdrawable_epoch_but_0_effective_balance_nonzero_balance(spec, state):
current_epoch = spec.get_current_epoch(state)
set_validator_fully_withdrawable(spec, state, 0, current_epoch)
state.validators[0].effective_balance = 0
state.balances[0] = 100000000
execution_payload = build_empty_execution_payload(spec, state)
yield from run_withdrawals_processing(spec, state, execution_payload, num_expected_withdrawals=1)
@with_phases([CAPELLA])
@spec_state_test
def test_no_withdrawals_but_some_next_epoch(spec, state):
current_epoch = spec.get_current_epoch(state)
# Make a few validators withdrawable at the *next* epoch
for index in range(3):
set_validator_fully_withdrawable(spec, state, index, current_epoch + 1)
execution_payload = build_empty_execution_payload(spec, state)
yield from run_withdrawals_processing(spec, state, execution_payload, num_expected_withdrawals=0)
@with_phases([CAPELLA])
@spec_state_test
def test_all_withdrawal(spec, state):
# Make all validators withdrawable
for index in range(len(state.validators)):
set_validator_fully_withdrawable(spec, state, index)
execution_payload = build_empty_execution_payload(spec, state)
yield from run_withdrawals_processing(
spec, state, execution_payload,
num_expected_withdrawals=spec.MAX_WITHDRAWALS_PER_PAYLOAD)
def run_random_full_withdrawals_test(spec, state, rng):
randomize_state(spec, state, rng)
for index in range(len(state.validators)):
# 50% withdrawable
if rng.choice([True, False]):
set_validator_fully_withdrawable(spec, state, index)
validator = state.validators[index]
# 12.5% unset credentials
if rng.randint(0, 7) == 0:
validator.withdrawal_credentials = spec.BLS_WITHDRAWAL_PREFIX + validator.withdrawal_credentials[1:]
# 12.5% not enough balance
if rng.randint(0, 7) == 0:
state.balances[index] = 0
# 12.5% not close enough epoch
if rng.randint(0, 7) == 0:
validator.withdrawable_epoch += 1
execution_payload = build_empty_execution_payload(spec, state)
yield from run_withdrawals_processing(spec, state, execution_payload)
@with_phases([CAPELLA])
@spec_state_test
def test_random_full_withdrawals_0(spec, state):
yield from run_random_full_withdrawals_test(spec, state, random.Random(444))
@with_phases([CAPELLA])
@spec_state_test
def test_random_full_withdrawals_1(spec, state):
yield from run_random_full_withdrawals_test(spec, state, random.Random(420))
@with_phases([CAPELLA])
@spec_state_test
def test_random_full_withdrawals_2(spec, state):
yield from run_random_full_withdrawals_test(spec, state, random.Random(200))
@with_phases([CAPELLA])
@spec_state_test
def test_random_full_withdrawals_3(spec, state):
yield from run_random_full_withdrawals_test(spec, state, random.Random(2000000))
#
# More partial withdrawal cases
#
@with_phases([CAPELLA])
@spec_state_test
def test_success_no_max_effective_balance(spec, state):
validator_index = len(state.validators) // 2
# To be partially withdrawable, the validator's effective balance must be maxed out
set_eth1_withdrawal_credential_with_balance(spec, state, validator_index, spec.MAX_EFFECTIVE_BALANCE - 1)
validator = state.validators[validator_index]
assert validator.effective_balance < spec.MAX_EFFECTIVE_BALANCE
assert not spec.is_partially_withdrawable_validator(validator, state.balances[validator_index])
execution_payload = build_empty_execution_payload(spec, state)
yield from run_withdrawals_processing(spec, state, execution_payload, num_expected_withdrawals=0)
@with_phases([CAPELLA])
@spec_state_test
def test_success_no_excess_balance(spec, state):
validator_index = len(state.validators) // 2
# To be partially withdrawable, the validator needs an excess balance
set_eth1_withdrawal_credential_with_balance(spec, state, validator_index, spec.MAX_EFFECTIVE_BALANCE)
validator = state.validators[validator_index]
assert validator.effective_balance == spec.MAX_EFFECTIVE_BALANCE
assert not spec.is_partially_withdrawable_validator(validator, state.balances[validator_index])
execution_payload = build_empty_execution_payload(spec, state)
yield from run_withdrawals_processing(spec, state, execution_payload, num_expected_withdrawals=0)
@with_phases([CAPELLA])
@spec_state_test
def test_success_excess_balance_but_no_max_effective_balance(spec, state):
validator_index = len(state.validators) // 2
set_validator_partially_withdrawable(spec, state, validator_index)
validator = state.validators[validator_index]
# To be partially withdrawable, the validator needs both a maxed out effective balance and an excess balance
validator.effective_balance = spec.MAX_EFFECTIVE_BALANCE - 1
assert not spec.is_partially_withdrawable_validator(validator, state.balances[validator_index])
execution_payload = build_empty_execution_payload(spec, state)
yield from run_withdrawals_processing(spec, state, execution_payload, num_expected_withdrawals=0)
@with_phases([CAPELLA])
@spec_state_test
def test_success_one_partial_withdrawable_not_yet_active(spec, state):
validator_index = len(state.validators) // 2
state.validators[validator_index].activation_epoch += 4
set_validator_partially_withdrawable(spec, state, validator_index)
assert not spec.is_active_validator(state.validators[validator_index], spec.get_current_epoch(state))
execution_payload = build_empty_execution_payload(spec, state)
yield from run_withdrawals_processing(spec, state, execution_payload, num_expected_withdrawals=1)
@with_phases([CAPELLA])
@spec_state_test
def test_success_one_partial_withdrawable_in_exit_queue(spec, state):
validator_index = len(state.validators) // 2
state.validators[validator_index].exit_epoch = spec.get_current_epoch(state) + 1
set_validator_partially_withdrawable(spec, state, validator_index)
assert spec.is_active_validator(state.validators[validator_index], spec.get_current_epoch(state))
assert not spec.is_active_validator(state.validators[validator_index], spec.get_current_epoch(state) + 1)
execution_payload = build_empty_execution_payload(spec, state)
yield from run_withdrawals_processing(spec, state, execution_payload, num_expected_withdrawals=1)
@with_phases([CAPELLA])
@spec_state_test
def test_success_one_partial_withdrawable_exited(spec, state):
validator_index = len(state.validators) // 2
state.validators[validator_index].exit_epoch = spec.get_current_epoch(state)
set_validator_partially_withdrawable(spec, state, validator_index)
assert not spec.is_active_validator(state.validators[validator_index], spec.get_current_epoch(state))
execution_payload = build_empty_execution_payload(spec, state)
yield from run_withdrawals_processing(spec, state, execution_payload, num_expected_withdrawals=1)
@with_phases([CAPELLA])
@spec_state_test
def test_success_one_partial_withdrawable_active_and_slashed(spec, state):
validator_index = len(state.validators) // 2
state.validators[validator_index].slashed = True
set_validator_partially_withdrawable(spec, state, validator_index)
assert spec.is_active_validator(state.validators[validator_index], spec.get_current_epoch(state))
execution_payload = build_empty_execution_payload(spec, state)
yield from run_withdrawals_processing(spec, state, execution_payload, num_expected_withdrawals=1)
@with_phases([CAPELLA])
@spec_state_test
def test_success_one_partial_withdrawable_exited_and_slashed(spec, state):
validator_index = len(state.validators) // 2
state.validators[validator_index].slashed = True
state.validators[validator_index].exit_epoch = spec.get_current_epoch(state)
set_validator_partially_withdrawable(spec, state, validator_index)
assert not spec.is_active_validator(state.validators[validator_index], spec.get_current_epoch(state))
execution_payload = build_empty_execution_payload(spec, state)
yield from run_withdrawals_processing(spec, state, execution_payload, num_expected_withdrawals=1)
@with_phases([CAPELLA])
@spec_state_test
def test_success_two_partial_withdrawable(spec, state):
set_validator_partially_withdrawable(spec, state, 0)
set_validator_partially_withdrawable(spec, state, 1)
execution_payload = build_empty_execution_payload(spec, state)
yield from run_withdrawals_processing(spec, state, execution_payload, num_expected_withdrawals=2)
@with_phases([CAPELLA])
@spec_state_test
def test_success_max_partial_withdrawable(spec, state):
# Sanity check that this test works for this state
assert len(state.validators) >= spec.MAX_WITHDRAWALS_PER_PAYLOAD
for i in range(spec.MAX_WITHDRAWALS_PER_PAYLOAD):
set_validator_partially_withdrawable(spec, state, i)
execution_payload = build_empty_execution_payload(spec, state)
yield from run_withdrawals_processing(
spec, state, execution_payload, num_expected_withdrawals=spec.MAX_WITHDRAWALS_PER_PAYLOAD)
@with_phases([CAPELLA])
@with_presets([MINIMAL], reason="not enough validators with mainnet config")
@spec_state_test
def test_success_max_plus_one_withdrawable(spec, state):
# Sanity check that this test works for this state
assert len(state.validators) >= spec.MAX_WITHDRAWALS_PER_PAYLOAD + 1
# More than MAX_WITHDRAWALS_PER_PAYLOAD partially withdrawable
for i in range(spec.MAX_WITHDRAWALS_PER_PAYLOAD + 1):
set_validator_partially_withdrawable(spec, state, i)
execution_payload = build_empty_execution_payload(spec, state)
# Should only have MAX_WITHDRAWALS_PER_PAYLOAD withdrawals created
yield from run_withdrawals_processing(
spec, state, execution_payload, num_expected_withdrawals=spec.MAX_WITHDRAWALS_PER_PAYLOAD)
def run_random_partial_withdrawals_test(spec, state, rng):
for _ in range(rng.randint(0, 2)):
next_epoch(spec, state)
randomize_state(spec, state, rng)
num_validators = len(state.validators)
state.next_withdrawal_validator_index = rng.randint(0, num_validators - 1)
num_partially_withdrawable = rng.randint(0, num_validators - 1)
partially_withdrawable_indices = rng.sample(range(num_validators), num_partially_withdrawable)
for index in partially_withdrawable_indices:
set_validator_partially_withdrawable(spec, state, index, excess_balance=rng.randint(1, 1000000000))
execution_payload = build_empty_execution_payload(spec, state)
# Note: due to the randomness and other block processing, some of these set as "partially withdrawable"
# may not be partially withdrawable once we get to ``process_withdrawals``,
# thus *not* using the optional third param in this call
yield from run_withdrawals_processing(spec, state, execution_payload)
@with_phases([CAPELLA])
@spec_state_test
def test_random_0(spec, state):
yield from run_random_partial_withdrawals_test(spec, state, random.Random(0))
@with_phases([CAPELLA])
@spec_state_test
def test_random_partial_withdrawals_1(spec, state):
yield from run_random_partial_withdrawals_test(spec, state, random.Random(1))
@with_phases([CAPELLA])
@spec_state_test
def test_random_partial_withdrawals_2(spec, state):
yield from run_random_partial_withdrawals_test(spec, state, random.Random(2))
@with_phases([CAPELLA])
@spec_state_test
def test_random_partial_withdrawals_3(spec, state):
yield from run_random_partial_withdrawals_test(spec, state, random.Random(3))
@with_phases([CAPELLA])
@spec_state_test
def test_random_partial_withdrawals_4(spec, state):
yield from run_random_partial_withdrawals_test(spec, state, random.Random(4))
@with_phases([CAPELLA])
@spec_state_test
def test_random_partial_withdrawals_5(spec, state):
yield from run_random_partial_withdrawals_test(spec, state, random.Random(5))

View File

@@ -1,174 +0,0 @@
from random import Random
from eth2spec.test.context import (
with_capella_and_later,
spec_state_test,
)
from eth2spec.test.helpers.random import (
randomize_state,
)
from eth2spec.test.helpers.epoch_processing import (
run_epoch_processing_to,
)
from eth2spec.test.helpers.withdrawals import (
set_validator_fully_withdrawable,
)
def run_process_full_withdrawals(spec, state, num_expected_withdrawals=None):
run_epoch_processing_to(spec, state, 'process_full_withdrawals')
pre_next_withdrawal_index = state.next_withdrawal_index
pre_withdrawal_queue = state.withdrawal_queue.copy()
to_be_withdrawn_indices = [
index for index, validator in enumerate(state.validators)
if spec.is_fully_withdrawable_validator(validator, state.balances[index], spec.get_current_epoch(state))
]
if num_expected_withdrawals is not None:
assert len(to_be_withdrawn_indices) == num_expected_withdrawals
else:
num_expected_withdrawals = len(to_be_withdrawn_indices)
yield 'pre', state
spec.process_full_withdrawals(state)
yield 'post', state
for index in to_be_withdrawn_indices:
assert state.balances[index] == 0
assert len(state.withdrawal_queue) == len(pre_withdrawal_queue) + num_expected_withdrawals
assert state.next_withdrawal_index == pre_next_withdrawal_index + num_expected_withdrawals
@with_capella_and_later
@spec_state_test
def test_no_withdrawable_validators(spec, state):
pre_validators = state.validators.copy()
yield from run_process_full_withdrawals(spec, state, 0)
assert pre_validators == state.validators
@with_capella_and_later
@spec_state_test
def test_withdrawable_epoch_but_0_balance(spec, state):
current_epoch = spec.get_current_epoch(state)
set_validator_fully_withdrawable(spec, state, 0, current_epoch)
state.validators[0].effective_balance = 10000000000
state.balances[0] = 0
yield from run_process_full_withdrawals(spec, state, 0)
@with_capella_and_later
@spec_state_test
def test_withdrawable_epoch_but_0_effective_balance_0_balance(spec, state):
current_epoch = spec.get_current_epoch(state)
set_validator_fully_withdrawable(spec, state, 0, current_epoch)
state.validators[0].effective_balance = 0
state.balances[0] = 0
yield from run_process_full_withdrawals(spec, state, 0)
@with_capella_and_later
@spec_state_test
def test_withdrawable_epoch_but_0_effective_balance_nonzero_balance(spec, state):
current_epoch = spec.get_current_epoch(state)
set_validator_fully_withdrawable(spec, state, 0, current_epoch)
state.validators[0].effective_balance = 0
state.balances[0] = 100000000
yield from run_process_full_withdrawals(spec, state, 1)
@with_capella_and_later
@spec_state_test
def test_no_withdrawals_but_some_next_epoch(spec, state):
current_epoch = spec.get_current_epoch(state)
# Make a few validators withdrawable at the *next* epoch
for index in range(3):
set_validator_fully_withdrawable(spec, state, index, current_epoch + 1)
yield from run_process_full_withdrawals(spec, state, 0)
@with_capella_and_later
@spec_state_test
def test_single_withdrawal(spec, state):
# Make one validator withdrawable
set_validator_fully_withdrawable(spec, state, 0)
assert state.next_withdrawal_index == 0
yield from run_process_full_withdrawals(spec, state, 1)
assert state.next_withdrawal_index == 1
@with_capella_and_later
@spec_state_test
def test_multi_withdrawal(spec, state):
# Make a few validators withdrawable
for index in range(3):
set_validator_fully_withdrawable(spec, state, index)
yield from run_process_full_withdrawals(spec, state, 3)
@with_capella_and_later
@spec_state_test
def test_all_withdrawal(spec, state):
# Make all validators withdrawable
for index in range(len(state.validators)):
set_validator_fully_withdrawable(spec, state, index)
yield from run_process_full_withdrawals(spec, state, len(state.validators))
def run_random_full_withdrawals_test(spec, state, rng):
randomize_state(spec, state, rng)
for index in range(len(state.validators)):
# 50% withdrawable
if rng.choice([True, False]):
set_validator_fully_withdrawable(spec, state, index)
validator = state.validators[index]
# 12.5% unset credentials
if rng.randint(0, 7) == 0:
validator.withdrawal_credentials = spec.BLS_WITHDRAWAL_PREFIX + validator.withdrawal_credentials[1:]
# 12.5% not enough balance
if rng.randint(0, 7) == 0:
state.balances[index] = 0
# 12.5% not close enough epoch
if rng.randint(0, 7) == 0:
validator.withdrawable_epoch += 1
yield from run_process_full_withdrawals(spec, state, None)
@with_capella_and_later
@spec_state_test
def test_random_withdrawals_0(spec, state):
yield from run_random_full_withdrawals_test(spec, state, Random(444))
@with_capella_and_later
@spec_state_test
def test_random_withdrawals_1(spec, state):
yield from run_random_full_withdrawals_test(spec, state, Random(420))
@with_capella_and_later
@spec_state_test
def test_random_withdrawals_2(spec, state):
yield from run_random_full_withdrawals_test(spec, state, Random(200))
@with_capella_and_later
@spec_state_test
def test_random_withdrawals_3(spec, state):
yield from run_random_full_withdrawals_test(spec, state, Random(2000000))

View File

@@ -1,262 +0,0 @@
import random
from eth2spec.test.helpers.constants import MINIMAL
from eth2spec.test.context import (
with_capella_and_later,
spec_state_test,
with_presets,
)
from eth2spec.test.helpers.epoch_processing import run_epoch_processing_to
from eth2spec.test.helpers.state import next_epoch
from eth2spec.test.helpers.random import randomize_state
from eth2spec.test.helpers.withdrawals import (
set_validator_partially_withdrawable,
set_eth1_withdrawal_credential_with_balance,
)
def run_process_partial_withdrawals(spec, state, num_expected_withdrawals=None):
# Run rest of epoch processing before predicting partial withdrawals as
# balance changes can affect withdrawability
run_epoch_processing_to(spec, state, 'process_partial_withdrawals')
pre_next_withdrawal_index = state.next_withdrawal_index
pre_withdrawal_queue = state.withdrawal_queue.copy()
partially_withdrawable_indices = [
index for index, validator in enumerate(state.validators)
if spec.is_partially_withdrawable_validator(validator, state.balances[index])
]
num_partial_withdrawals = min(len(partially_withdrawable_indices), spec.MAX_PARTIAL_WITHDRAWALS_PER_EPOCH)
if num_expected_withdrawals is not None:
assert num_partial_withdrawals == num_expected_withdrawals
else:
num_expected_withdrawals = num_partial_withdrawals
yield 'pre', state
spec.process_partial_withdrawals(state)
yield 'post', state
post_partially_withdrawable_indices = [
index for index, validator in enumerate(state.validators)
if spec.is_partially_withdrawable_validator(validator, state.balances[index])
]
assert len(partially_withdrawable_indices) - num_partial_withdrawals == len(post_partially_withdrawable_indices)
assert len(state.withdrawal_queue) == len(pre_withdrawal_queue) + num_expected_withdrawals
assert state.next_withdrawal_index == pre_next_withdrawal_index + num_expected_withdrawals
@with_capella_and_later
@spec_state_test
def test_success_no_withdrawable(spec, state):
pre_validators = state.validators.copy()
yield from run_process_partial_withdrawals(spec, state, 0)
assert pre_validators == state.validators
@with_capella_and_later
@spec_state_test
def test_success_no_max_effective_balance(spec, state):
validator_index = len(state.validators) // 2
# To be partially withdrawable, the validator's effective balance must be maxed out
set_eth1_withdrawal_credential_with_balance(spec, state, validator_index, spec.MAX_EFFECTIVE_BALANCE - 1)
validator = state.validators[validator_index]
assert validator.effective_balance < spec.MAX_EFFECTIVE_BALANCE
assert not spec.is_partially_withdrawable_validator(validator, state.balances[validator_index])
yield from run_process_partial_withdrawals(spec, state, 0)
@with_capella_and_later
@spec_state_test
def test_success_no_excess_balance(spec, state):
validator_index = len(state.validators) // 2
# To be partially withdrawable, the validator needs an excess balance
set_eth1_withdrawal_credential_with_balance(spec, state, validator_index, spec.MAX_EFFECTIVE_BALANCE)
validator = state.validators[validator_index]
assert validator.effective_balance == spec.MAX_EFFECTIVE_BALANCE
assert not spec.is_partially_withdrawable_validator(validator, state.balances[validator_index])
yield from run_process_partial_withdrawals(spec, state, 0)
@with_capella_and_later
@spec_state_test
def test_success_excess_balance_but_no_max_effective_balance(spec, state):
validator_index = len(state.validators) // 2
set_validator_partially_withdrawable(spec, state, validator_index)
validator = state.validators[validator_index]
# To be partially withdrawable, the validator needs both a maxed out effective balance and an excess balance
validator.effective_balance = spec.MAX_EFFECTIVE_BALANCE - 1
assert not spec.is_partially_withdrawable_validator(validator, state.balances[validator_index])
yield from run_process_partial_withdrawals(spec, state, 0)
@with_capella_and_later
@spec_state_test
def test_success_one_partial_withdrawable(spec, state):
validator_index = len(state.validators) // 2
set_validator_partially_withdrawable(spec, state, validator_index)
yield from run_process_partial_withdrawals(spec, state, 1)
@with_capella_and_later
@spec_state_test
def test_success_one_partial_withdrawable_not_yet_active(spec, state):
validator_index = len(state.validators) // 2
state.validators[validator_index].activation_epoch += 4
set_validator_partially_withdrawable(spec, state, validator_index)
assert not spec.is_active_validator(state.validators[validator_index], spec.get_current_epoch(state))
yield from run_process_partial_withdrawals(spec, state, 1)
@with_capella_and_later
@spec_state_test
def test_success_one_partial_withdrawable_in_exit_queue(spec, state):
validator_index = len(state.validators) // 2
state.validators[validator_index].exit_epoch = spec.get_current_epoch(state) + 1
set_validator_partially_withdrawable(spec, state, validator_index)
assert spec.is_active_validator(state.validators[validator_index], spec.get_current_epoch(state))
assert not spec.is_active_validator(state.validators[validator_index], spec.get_current_epoch(state) + 1)
yield from run_process_partial_withdrawals(spec, state, 1)
@with_capella_and_later
@spec_state_test
def test_success_one_partial_withdrawable_exited(spec, state):
validator_index = len(state.validators) // 2
state.validators[validator_index].exit_epoch = spec.get_current_epoch(state)
set_validator_partially_withdrawable(spec, state, validator_index)
assert not spec.is_active_validator(state.validators[validator_index], spec.get_current_epoch(state))
yield from run_process_partial_withdrawals(spec, state, 1)
@with_capella_and_later
@spec_state_test
def test_success_one_partial_withdrawable_active_and_slashed(spec, state):
validator_index = len(state.validators) // 2
state.validators[validator_index].slashed = True
set_validator_partially_withdrawable(spec, state, validator_index)
assert spec.is_active_validator(state.validators[validator_index], spec.get_current_epoch(state))
yield from run_process_partial_withdrawals(spec, state, 1)
@with_capella_and_later
@spec_state_test
def test_success_one_partial_withdrawable_exited_and_slashed(spec, state):
validator_index = len(state.validators) // 2
state.validators[validator_index].slashed = True
state.validators[validator_index].exit_epoch = spec.get_current_epoch(state)
set_validator_partially_withdrawable(spec, state, validator_index)
assert not spec.is_active_validator(state.validators[validator_index], spec.get_current_epoch(state))
yield from run_process_partial_withdrawals(spec, state, 1)
@with_capella_and_later
@spec_state_test
def test_success_two_partial_withdrawable(spec, state):
set_validator_partially_withdrawable(spec, state, 0)
set_validator_partially_withdrawable(spec, state, 1)
yield from run_process_partial_withdrawals(spec, state, 2)
@with_capella_and_later
@spec_state_test
def test_success_max_partial_withdrawable(spec, state):
# Sanity check that this test works for this state
assert len(state.validators) >= spec.MAX_PARTIAL_WITHDRAWALS_PER_EPOCH
for i in range(spec.MAX_PARTIAL_WITHDRAWALS_PER_EPOCH):
set_validator_partially_withdrawable(spec, state, i)
yield from run_process_partial_withdrawals(spec, state, spec.MAX_PARTIAL_WITHDRAWALS_PER_EPOCH)
@with_capella_and_later
@with_presets([MINIMAL], reason="not enough validators with mainnet config")
@spec_state_test
def test_success_max_plus_one_withdrawable(spec, state):
# Sanity check that this test works for this state
assert len(state.validators) >= spec.MAX_PARTIAL_WITHDRAWALS_PER_EPOCH + 1
# More than MAX_PARTIAL_WITHDRAWALS_PER_EPOCH partially withdrawable
for i in range(spec.MAX_PARTIAL_WITHDRAWALS_PER_EPOCH + 1):
set_validator_partially_withdrawable(spec, state, i)
# Should only have MAX_PARTIAL_WITHDRAWALS_PER_EPOCH withdrawals created
yield from run_process_partial_withdrawals(spec, state, spec.MAX_PARTIAL_WITHDRAWALS_PER_EPOCH)
def run_random_partial_withdrawals_test(spec, state, rng):
for _ in range(rng.randint(0, 2)):
next_epoch(spec, state)
randomize_state(spec, state, rng)
num_validators = len(state.validators)
state.next_partial_withdrawal_validator_index = rng.randint(0, num_validators - 1)
num_partially_withdrawable = rng.randint(0, num_validators - 1)
partially_withdrawable_indices = rng.sample(range(num_validators), num_partially_withdrawable)
for index in partially_withdrawable_indices:
set_validator_partially_withdrawable(spec, state, index, excess_balance=rng.randint(1, 1000000000))
# Note: due to the randomness and other epoch processing, some of these set as "partially withdrawable"
# may not be partially withdrawable once we get to ``process_partial_withdrawals``,
# thus *not* using the optional third param in this call
yield from run_process_partial_withdrawals(spec, state)
@with_capella_and_later
@spec_state_test
def test_random_0(spec, state):
yield from run_random_partial_withdrawals_test(spec, state, random.Random(0))
@with_capella_and_later
@spec_state_test
def test_random_1(spec, state):
yield from run_random_partial_withdrawals_test(spec, state, random.Random(1))
@with_capella_and_later
@spec_state_test
def test_random_2(spec, state):
yield from run_random_partial_withdrawals_test(spec, state, random.Random(2))
@with_capella_and_later
@spec_state_test
def test_random_3(spec, state):
yield from run_random_partial_withdrawals_test(spec, state, random.Random(3))
@with_capella_and_later
@spec_state_test
def test_random_4(spec, state):
yield from run_random_partial_withdrawals_test(spec, state, random.Random(4))
@with_capella_and_later
@spec_state_test
def test_random_5(spec, state):
yield from run_random_partial_withdrawals_test(spec, state, random.Random(5))

View File

@@ -0,0 +1,84 @@
from random import Random
from eth2spec.test.context import (
with_phases,
with_custom_state,
with_presets,
spec_test, with_state,
low_balances, misc_balances, large_validator_set,
)
from eth2spec.test.utils import with_meta_tags
from eth2spec.test.helpers.constants import (
BELLATRIX, CAPELLA,
MINIMAL,
)
from eth2spec.test.helpers.capella.fork import (
CAPELLA_FORK_TEST_META_TAGS,
run_fork_test,
)
from eth2spec.test.helpers.random import randomize_state
@with_phases(phases=[BELLATRIX], other_phases=[CAPELLA])
@spec_test
@with_state
@with_meta_tags(CAPELLA_FORK_TEST_META_TAGS)
def test_capella_fork_random_0(spec, phases, state):
randomize_state(spec, state, rng=Random(1010))
yield from run_fork_test(phases[CAPELLA], state)
@with_phases(phases=[BELLATRIX], other_phases=[CAPELLA])
@spec_test
@with_state
@with_meta_tags(CAPELLA_FORK_TEST_META_TAGS)
def test_capella_fork_random_1(spec, phases, state):
randomize_state(spec, state, rng=Random(2020))
yield from run_fork_test(phases[CAPELLA], state)
@with_phases(phases=[BELLATRIX], other_phases=[CAPELLA])
@spec_test
@with_state
@with_meta_tags(CAPELLA_FORK_TEST_META_TAGS)
def test_capella_fork_random_2(spec, phases, state):
randomize_state(spec, state, rng=Random(3030))
yield from run_fork_test(phases[CAPELLA], state)
@with_phases(phases=[BELLATRIX], other_phases=[CAPELLA])
@spec_test
@with_state
@with_meta_tags(CAPELLA_FORK_TEST_META_TAGS)
def test_capella_fork_random_3(spec, phases, state):
randomize_state(spec, state, rng=Random(4040))
yield from run_fork_test(phases[CAPELLA], state)
@with_phases(phases=[BELLATRIX], other_phases=[CAPELLA])
@spec_test
@with_custom_state(balances_fn=low_balances, threshold_fn=lambda spec: spec.config.EJECTION_BALANCE)
@with_meta_tags(CAPELLA_FORK_TEST_META_TAGS)
def test_capella_fork_random_low_balances(spec, phases, state):
randomize_state(spec, state, rng=Random(5050))
yield from run_fork_test(phases[CAPELLA], state)
@with_phases(phases=[BELLATRIX], other_phases=[CAPELLA])
@spec_test
@with_custom_state(balances_fn=misc_balances, threshold_fn=lambda spec: spec.config.EJECTION_BALANCE)
@with_meta_tags(CAPELLA_FORK_TEST_META_TAGS)
def test_capella_fork_random_misc_balances(spec, phases, state):
randomize_state(spec, state, rng=Random(6060))
yield from run_fork_test(phases[CAPELLA], state)
@with_phases(phases=[BELLATRIX], other_phases=[CAPELLA])
@with_presets([MINIMAL],
reason="mainnet config leads to larger validator set than limit of public/private keys pre-generated")
@spec_test
@with_custom_state(balances_fn=large_validator_set, threshold_fn=lambda spec: spec.config.EJECTION_BALANCE)
@with_meta_tags(CAPELLA_FORK_TEST_META_TAGS)
def test_capella_fork_random_large_validator_set(spec, phases, state):
randomize_state(spec, state, rng=Random(7070))
yield from run_fork_test(phases[CAPELLA], state)

View File

@@ -1,24 +1,33 @@
from eth2spec.test.context import (
with_capella_and_later, spec_state_test
with_phases, spec_state_test
)
from eth2spec.test.helpers.constants import CAPELLA
from eth2spec.test.helpers.state import (
state_transition_and_sign_block,
)
from eth2spec.test.helpers.block import (
build_empty_block_for_next_slot, build_empty_block,
build_empty_block_for_next_slot,
build_empty_block,
)
from eth2spec.test.helpers.bls_to_execution_changes import get_signed_address_change
from eth2spec.test.helpers.state import (
next_slot,
)
from eth2spec.test.helpers.withdrawals import (
set_validator_fully_withdrawable,
set_validator_partially_withdrawable,
prepare_expected_withdrawals,
)
from eth2spec.test.helpers.voluntary_exits import prepare_signed_exits
@with_capella_and_later
#
# BLSToExecutionChange
#
@with_phases([CAPELLA])
@spec_state_test
def test_successful_bls_change(spec, state):
def test_success_bls_change(spec, state):
index = 0
signed_address_change = get_signed_address_change(spec, state, validator_index=index)
pre_credentials = state.validators[index].withdrawal_credentials
@@ -39,76 +48,9 @@ def test_successful_bls_change(spec, state):
assert post_credentials[12:] == signed_address_change.message.to_execution_address
@with_capella_and_later
@with_phases([CAPELLA])
@spec_state_test
def test_full_withdrawal_in_epoch_transition(spec, state):
index = 0
current_epoch = spec.get_current_epoch(state)
set_validator_fully_withdrawable(spec, state, index, current_epoch)
yield 'pre', state
# trigger epoch transition
block = build_empty_block(spec, state, state.slot + spec.SLOTS_PER_EPOCH)
signed_block = state_transition_and_sign_block(spec, state, block)
yield 'blocks', [signed_block]
yield 'post', state
assert state.balances[index] == 0
@with_capella_and_later
@spec_state_test
def test_partial_withdrawal_in_epoch_transition(spec, state):
index = state.next_withdrawal_index
set_validator_partially_withdrawable(spec, state, index, excess_balance=1000000000000)
pre_balance = state.balances[index]
pre_withdrawal_queue_len = len(state.withdrawal_queue)
yield 'pre', state
# trigger epoch transition
block = build_empty_block(spec, state, state.slot + spec.SLOTS_PER_EPOCH)
signed_block = state_transition_and_sign_block(spec, state, block)
yield 'blocks', [signed_block]
yield 'post', state
assert state.balances[index] < pre_balance
# Potentially less than due to sync committee penalty
assert state.balances[index] <= spec.MAX_EFFECTIVE_BALANCE
# Withdrawal is processed within the context of the block so queue empty
assert len(state.withdrawal_queue) == pre_withdrawal_queue_len
@with_capella_and_later
@spec_state_test
def test_many_partial_withdrawals_in_epoch_transition(spec, state):
assert len(state.validators) > spec.MAX_WITHDRAWALS_PER_PAYLOAD
assert spec.MAX_PARTIAL_WITHDRAWALS_PER_EPOCH > spec.MAX_WITHDRAWALS_PER_PAYLOAD
for i in range(spec.MAX_WITHDRAWALS_PER_PAYLOAD + 1):
index = (i + state.next_withdrawal_index) % len(state.validators)
set_validator_partially_withdrawable(spec, state, index, excess_balance=1000000000000)
pre_withdrawal_queue_len = len(state.withdrawal_queue)
yield 'pre', state
# trigger epoch transition
block = build_empty_block(spec, state, state.slot + spec.SLOTS_PER_EPOCH)
signed_block = state_transition_and_sign_block(spec, state, block)
yield 'blocks', [signed_block]
yield 'post', state
# All new partial withdrawals processed except 1
assert len(state.withdrawal_queue) == pre_withdrawal_queue_len + 1
@with_capella_and_later
@spec_state_test
def test_exit_and_bls_change(spec, state):
def test_success_exit_and_bls_change(spec, state):
# move state forward SHARD_COMMITTEE_PERIOD epochs to allow for exit
state.slot += spec.config.SHARD_COMMITTEE_PERIOD * spec.SLOTS_PER_EPOCH
@@ -133,3 +75,184 @@ def test_exit_and_bls_change(spec, state):
assert not spec.is_fully_withdrawable_validator(validator, balance, current_epoch)
assert validator.withdrawable_epoch < spec.FAR_FUTURE_EPOCH
assert spec.is_fully_withdrawable_validator(validator, balance, validator.withdrawable_epoch)
@with_phases([CAPELLA])
@spec_state_test
def test_invalid_duplicate_bls_changes_same_block(spec, state):
index = 0
signed_address_change = get_signed_address_change(spec, state, validator_index=index)
yield 'pre', state
block = build_empty_block_for_next_slot(spec, state)
# Double BLSToExecutionChange of the same validator
for _ in range(2):
block.body.bls_to_execution_changes.append(signed_address_change)
signed_block = state_transition_and_sign_block(spec, state, block, expect_fail=True)
yield 'blocks', [signed_block]
yield 'post', None
@with_phases([CAPELLA])
@spec_state_test
def test_invalid_two_bls_changes_of_different_addresses_same_validator_same_block(spec, state):
index = 0
signed_address_change_1 = get_signed_address_change(spec, state, validator_index=index,
to_execution_address=b'\x12' * 20)
signed_address_change_2 = get_signed_address_change(spec, state, validator_index=index,
to_execution_address=b'\x34' * 20)
assert signed_address_change_1 != signed_address_change_2
yield 'pre', state
block = build_empty_block_for_next_slot(spec, state)
block.body.bls_to_execution_changes.append(signed_address_change_1)
block.body.bls_to_execution_changes.append(signed_address_change_2)
signed_block = state_transition_and_sign_block(spec, state, block, expect_fail=True)
yield 'blocks', [signed_block]
yield 'post', None
#
# Withdrawals
#
@with_phases([CAPELLA])
@spec_state_test
def test_full_withdrawal_in_epoch_transition(spec, state):
index = 0
current_epoch = spec.get_current_epoch(state)
set_validator_fully_withdrawable(spec, state, index, current_epoch)
assert len(spec.get_expected_withdrawals(state)) == 1
yield 'pre', state
# trigger epoch transition
block = build_empty_block(spec, state, state.slot + spec.SLOTS_PER_EPOCH)
signed_block = state_transition_and_sign_block(spec, state, block)
yield 'blocks', [signed_block]
yield 'post', state
assert state.balances[index] == 0
assert len(spec.get_expected_withdrawals(state)) == 0
@with_phases([CAPELLA])
@spec_state_test
def test_partial_withdrawal_in_epoch_transition(spec, state):
index = state.next_withdrawal_index
set_validator_partially_withdrawable(spec, state, index, excess_balance=1000000000000)
pre_balance = state.balances[index]
assert len(spec.get_expected_withdrawals(state)) == 1
yield 'pre', state
# trigger epoch transition
block = build_empty_block(spec, state, state.slot + spec.SLOTS_PER_EPOCH)
signed_block = state_transition_and_sign_block(spec, state, block)
yield 'blocks', [signed_block]
yield 'post', state
assert state.balances[index] < pre_balance
# Potentially less than due to sync committee penalty
assert state.balances[index] <= spec.MAX_EFFECTIVE_BALANCE
assert len(spec.get_expected_withdrawals(state)) == 0
@with_phases([CAPELLA])
@spec_state_test
def test_many_partial_withdrawals_in_epoch_transition(spec, state):
assert len(state.validators) > spec.MAX_WITHDRAWALS_PER_PAYLOAD
for i in range(spec.MAX_WITHDRAWALS_PER_PAYLOAD + 1):
index = (i + state.next_withdrawal_index) % len(state.validators)
set_validator_partially_withdrawable(spec, state, index, excess_balance=1000000000000)
assert len(spec.get_expected_withdrawals(state)) == spec.MAX_WITHDRAWALS_PER_PAYLOAD
yield 'pre', state
# trigger epoch transition
block = build_empty_block(spec, state, state.slot + spec.SLOTS_PER_EPOCH)
signed_block = state_transition_and_sign_block(spec, state, block)
yield 'blocks', [signed_block]
yield 'post', state
assert len(spec.get_expected_withdrawals(state)) == 1
def _perform_valid_withdrawal(spec, state):
fully_withdrawable_indices, partial_withdrawals_indices = prepare_expected_withdrawals(
spec, state, num_partial_withdrawals=spec.MAX_WITHDRAWALS_PER_PAYLOAD * 4,
num_full_withdrawals=spec.MAX_WITHDRAWALS_PER_PAYLOAD * 4)
next_slot(spec, state)
pre_next_withdrawal_index = state.next_withdrawal_index
expected_withdrawals = spec.get_expected_withdrawals(state)
pre_state = state.copy()
# Block 1
block = build_empty_block_for_next_slot(spec, state)
signed_block_1 = state_transition_and_sign_block(spec, state, block)
withdrawn_indices = [withdrawal.validator_index for withdrawal in expected_withdrawals]
fully_withdrawable_indices = list(set(fully_withdrawable_indices).difference(set(withdrawn_indices)))
partial_withdrawals_indices = list(set(partial_withdrawals_indices).difference(set(withdrawn_indices)))
assert state.next_withdrawal_index == pre_next_withdrawal_index + spec.MAX_WITHDRAWALS_PER_PAYLOAD
withdrawn_indices = [withdrawal.validator_index for withdrawal in expected_withdrawals]
fully_withdrawable_indices = list(set(fully_withdrawable_indices).difference(set(withdrawn_indices)))
partial_withdrawals_indices = list(set(partial_withdrawals_indices).difference(set(withdrawn_indices)))
assert state.next_withdrawal_index == pre_next_withdrawal_index + spec.MAX_WITHDRAWALS_PER_PAYLOAD
return pre_state, signed_block_1, pre_next_withdrawal_index
@with_phases([CAPELLA])
@spec_state_test
def test_withdrawal_success_two_blocks(spec, state):
pre_state, signed_block_1, pre_next_withdrawal_index = _perform_valid_withdrawal(spec, state)
yield 'pre', pre_state
# Block 2
block = build_empty_block_for_next_slot(spec, state)
signed_block_2 = state_transition_and_sign_block(spec, state, block)
assert state.next_withdrawal_index == pre_next_withdrawal_index + spec.MAX_WITHDRAWALS_PER_PAYLOAD * 2
yield 'blocks', [signed_block_1, signed_block_2]
yield 'post', state
@with_phases([CAPELLA])
@spec_state_test
def test_withdrawal_fail_second_block_payload_isnt_compatible(spec, state):
_perform_valid_withdrawal(spec, state)
# Block 2
block = build_empty_block_for_next_slot(spec, state)
# Modify state.next_withdrawal_index to incorrect number
state.next_withdrawal_index += 1
# Only need to output the state transition of signed_block_2
yield 'pre', state
signed_block_2 = state_transition_and_sign_block(spec, state, block, expect_fail=True)
yield 'blocks', [signed_block_2]
yield 'post', None

View File

@@ -13,9 +13,10 @@ from .exceptions import SkippedTest
from .helpers.constants import (
PHASE0, ALTAIR, BELLATRIX, CAPELLA, EIP4844, SHARDING,
MINIMAL, MAINNET,
ALL_PHASES, FORKS_BEFORE_ALTAIR, FORKS_BEFORE_BELLATRIX,
ALL_PHASES,
ALL_FORK_UPGRADES,
)
from .helpers.forks import is_post_fork
from .helpers.typing import SpecForkName, PresetBaseName
from .helpers.genesis import create_genesis_state
from .utils import (
@@ -305,6 +306,7 @@ def config_fork_epoch_overrides(spec, state):
elif _check_current_version(spec, state, EIP4844):
overrides['ALTAIR_FORK_EPOCH'] = spec.GENESIS_EPOCH
overrides['BELLATRIX_FORK_EPOCH'] = spec.GENESIS_EPOCH
overrides['CAPELLA_FORK_EPOCH'] = spec.GENESIS_EPOCH
overrides['EIP4844_FORK_EPOCH'] = spec.GENESIS_EPOCH
elif _check_current_version(spec, state, SHARDING):
overrides['ALTAIR_FORK_EPOCH'] = spec.GENESIS_EPOCH
@@ -408,6 +410,15 @@ def with_all_phases(fn):
return with_phases(ALL_PHASES)(fn)
def with_all_phases_from(earliest_phase):
"""
A decorator factory for running a tests with every phase except the ones listed
"""
def decorator(fn):
return with_phases([phase for phase in ALL_PHASES if is_post_fork(phase, earliest_phase)])(fn)
return decorator
def with_all_phases_except(exclusion_phases):
"""
A decorator factory for running a tests with every phase except the ones listed
@@ -417,6 +428,12 @@ def with_all_phases_except(exclusion_phases):
return decorator
with_altair_and_later = with_all_phases_from(ALTAIR)
with_bellatrix_and_later = with_all_phases_from(BELLATRIX)
with_capella_and_later = with_all_phases_from(CAPELLA)
with_eip4844_and_later = with_all_phases_from(EIP4844)
def _get_preset_targets(kw):
preset_name = DEFAULT_TEST_PRESET
if 'preset' in kw:
@@ -587,28 +604,6 @@ def with_config_overrides(config_overrides):
return decorator
def is_post_altair(spec):
return spec.fork not in FORKS_BEFORE_ALTAIR
def is_post_bellatrix(spec):
return spec.fork not in FORKS_BEFORE_BELLATRIX
def is_post_capella(spec):
return spec.fork == CAPELLA
def is_post_eip4844(spec):
return spec.fork == EIP4844
with_altair_and_later = with_all_phases_except([PHASE0])
with_bellatrix_and_later = with_all_phases_except([PHASE0, ALTAIR])
with_capella_and_later = with_all_phases_except([PHASE0, ALTAIR, BELLATRIX, EIP4844])
with_eip4844_and_later = with_all_phases_except([PHASE0, ALTAIR, BELLATRIX, CAPELLA])
def only_generator(reason):
def _decorator(inner):
def _wrapper(*args, **kwargs):
@@ -620,6 +615,13 @@ def only_generator(reason):
return _decorator
def with_test_suite_name(suite_name: str):
def _decorator(inner):
inner.suite_name = suite_name
return inner
return _decorator
#
# Fork transition state tests
#

View File

@@ -0,0 +1,40 @@
from eth2spec.test.helpers.bls_to_execution_changes import get_signed_address_change
from eth2spec.test.context import spec_state_test, expect_assertion_error, with_eip4844_and_later
def run_bls_to_execution_change_processing_no_op(spec, state, signed_address_change, valid=True):
"""
Run ``process_bls_to_execution_change``, yielding:
- pre-state ('pre')
- address-change ('address_change')
- post-state ('post').
If ``valid == False``, run expecting ``AssertionError``
"""
pre_state = state.copy()
# yield pre-state
yield 'pre', state
yield 'address_change', signed_address_change
# If the address_change is invalid, processing is aborted, and there is no post-state.
if not valid:
expect_assertion_error(lambda: spec.process_bls_to_execution_change(state, signed_address_change))
yield 'post', None
return
# process address change
spec.process_bls_to_execution_change(state, signed_address_change)
# yield post-state
yield 'post', state
# Make sure state has NOT been changed
assert state == pre_state
@with_eip4844_and_later
@spec_state_test
def test_no_op(spec, state):
signed_address_change = get_signed_address_change(spec, state)
yield from run_bls_to_execution_change_processing_no_op(spec, state, signed_address_change)

View File

@@ -0,0 +1,41 @@
from eth2spec.test.context import spec_state_test, expect_assertion_error, with_eip4844_and_later
from eth2spec.test.helpers.execution_payload import (
build_empty_execution_payload,
)
from eth2spec.test.helpers.state import next_slot
def run_withdrawals_processing(spec, state, execution_payload, valid=True):
"""
Run ``process_execution_payload``, yielding:
- pre-state ('pre')
- execution payload ('execution_payload')
- post-state ('post').
If ``valid == False``, run expecting ``AssertionError``
"""
pre_state = state.copy()
yield 'pre', state
yield 'execution_payload', execution_payload
if not valid:
expect_assertion_error(lambda: spec.process_withdrawals(state, execution_payload))
yield 'post', None
return
spec.process_withdrawals(state, execution_payload)
yield 'post', state
# Make sure state has NOT been changed
assert state == pre_state
@with_eip4844_and_later
@spec_state_test
def test_no_op(spec, state):
next_slot(spec, state)
execution_payload = build_empty_execution_payload(spec, state)
yield from run_withdrawals_processing(spec, state, execution_payload)

View File

@@ -0,0 +1,82 @@
from eth2spec.test.context import (
with_phases,
with_custom_state,
with_presets,
spec_test, with_state,
low_balances, misc_balances, large_validator_set,
)
from eth2spec.test.utils import with_meta_tags
from eth2spec.test.helpers.constants import (
CAPELLA, EIP4844,
MINIMAL,
)
from eth2spec.test.helpers.state import (
next_epoch,
next_epoch_via_block,
)
from eth2spec.test.helpers.eip4844.fork import (
EIP4844_FORK_TEST_META_TAGS,
run_fork_test,
)
@with_phases(phases=[CAPELLA], other_phases=[EIP4844])
@spec_test
@with_state
@with_meta_tags(EIP4844_FORK_TEST_META_TAGS)
def test_fork_base_state(spec, phases, state):
yield from run_fork_test(phases[EIP4844], state)
@with_phases(phases=[CAPELLA], other_phases=[EIP4844])
@spec_test
@with_state
@with_meta_tags(EIP4844_FORK_TEST_META_TAGS)
def test_fork_next_epoch(spec, phases, state):
next_epoch(spec, state)
yield from run_fork_test(phases[EIP4844], state)
@with_phases(phases=[CAPELLA], other_phases=[EIP4844])
@spec_test
@with_state
@with_meta_tags(EIP4844_FORK_TEST_META_TAGS)
def test_fork_next_epoch_with_block(spec, phases, state):
next_epoch_via_block(spec, state)
yield from run_fork_test(phases[EIP4844], state)
@with_phases(phases=[CAPELLA], other_phases=[EIP4844])
@spec_test
@with_state
@with_meta_tags(EIP4844_FORK_TEST_META_TAGS)
def test_fork_many_next_epoch(spec, phases, state):
for _ in range(3):
next_epoch(spec, state)
yield from run_fork_test(phases[EIP4844], state)
@with_phases(phases=[CAPELLA], other_phases=[EIP4844])
@with_custom_state(balances_fn=low_balances, threshold_fn=lambda spec: spec.config.EJECTION_BALANCE)
@spec_test
@with_meta_tags(EIP4844_FORK_TEST_META_TAGS)
def test_fork_random_low_balances(spec, phases, state):
yield from run_fork_test(phases[EIP4844], state)
@with_phases(phases=[CAPELLA], other_phases=[EIP4844])
@with_custom_state(balances_fn=misc_balances, threshold_fn=lambda spec: spec.config.EJECTION_BALANCE)
@spec_test
@with_meta_tags(EIP4844_FORK_TEST_META_TAGS)
def test_fork_random_misc_balances(spec, phases, state):
yield from run_fork_test(phases[EIP4844], state)
@with_phases(phases=[CAPELLA], other_phases=[EIP4844])
@with_presets([MINIMAL],
reason="mainnet config leads to larger validator set than limit of public/private keys pre-generated")
@with_custom_state(balances_fn=large_validator_set, threshold_fn=lambda spec: spec.config.EJECTION_BALANCE)
@spec_test
@with_meta_tags(EIP4844_FORK_TEST_META_TAGS)
def test_fork_random_large_validator_set(spec, phases, state):
yield from run_fork_test(phases[EIP4844], state)

View File

@@ -0,0 +1,84 @@
from random import Random
from eth2spec.test.context import (
with_phases,
with_custom_state,
with_presets,
spec_test, with_state,
low_balances, misc_balances, large_validator_set,
)
from eth2spec.test.utils import with_meta_tags
from eth2spec.test.helpers.constants import (
CAPELLA, EIP4844,
MINIMAL,
)
from eth2spec.test.helpers.eip4844.fork import (
EIP4844_FORK_TEST_META_TAGS,
run_fork_test,
)
from eth2spec.test.helpers.random import randomize_state
@with_phases(phases=[CAPELLA], other_phases=[EIP4844])
@spec_test
@with_state
@with_meta_tags(EIP4844_FORK_TEST_META_TAGS)
def test_eip4844_fork_random_0(spec, phases, state):
randomize_state(spec, state, rng=Random(1010))
yield from run_fork_test(phases[EIP4844], state)
@with_phases(phases=[CAPELLA], other_phases=[EIP4844])
@spec_test
@with_state
@with_meta_tags(EIP4844_FORK_TEST_META_TAGS)
def test_eip4844_fork_random_1(spec, phases, state):
randomize_state(spec, state, rng=Random(2020))
yield from run_fork_test(phases[EIP4844], state)
@with_phases(phases=[CAPELLA], other_phases=[EIP4844])
@spec_test
@with_state
@with_meta_tags(EIP4844_FORK_TEST_META_TAGS)
def test_eip4844_fork_random_2(spec, phases, state):
randomize_state(spec, state, rng=Random(3030))
yield from run_fork_test(phases[EIP4844], state)
@with_phases(phases=[CAPELLA], other_phases=[EIP4844])
@spec_test
@with_state
@with_meta_tags(EIP4844_FORK_TEST_META_TAGS)
def test_eip4844_fork_random_3(spec, phases, state):
randomize_state(spec, state, rng=Random(4040))
yield from run_fork_test(phases[EIP4844], state)
@with_phases(phases=[CAPELLA], other_phases=[EIP4844])
@spec_test
@with_custom_state(balances_fn=low_balances, threshold_fn=lambda spec: spec.config.EJECTION_BALANCE)
@with_meta_tags(EIP4844_FORK_TEST_META_TAGS)
def test_eip4844_fork_random_low_balances(spec, phases, state):
randomize_state(spec, state, rng=Random(5050))
yield from run_fork_test(phases[EIP4844], state)
@with_phases(phases=[CAPELLA], other_phases=[EIP4844])
@spec_test
@with_custom_state(balances_fn=misc_balances, threshold_fn=lambda spec: spec.config.EJECTION_BALANCE)
@with_meta_tags(EIP4844_FORK_TEST_META_TAGS)
def test_eip4844_fork_random_misc_balances(spec, phases, state):
randomize_state(spec, state, rng=Random(6060))
yield from run_fork_test(phases[EIP4844], state)
@with_phases(phases=[CAPELLA], other_phases=[EIP4844])
@with_presets([MINIMAL],
reason="mainnet config leads to larger validator set than limit of public/private keys pre-generated")
@spec_test
@with_custom_state(balances_fn=large_validator_set, threshold_fn=lambda spec: spec.config.EJECTION_BALANCE)
@with_meta_tags(EIP4844_FORK_TEST_META_TAGS)
def test_eip4844_fork_random_large_validator_set(spec, phases, state):
randomize_state(spec, state, rng=Random(7070))
yield from run_fork_test(phases[EIP4844], state)

View File

@@ -0,0 +1,438 @@
"""
This module is generated from the ``random`` test generator.
Please do not edit this file manually.
See the README for that generator for more information.
"""
from eth2spec.test.helpers.constants import EIP4844
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([EIP4844])
@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_eip4844
# 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_eip4844
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_eip4844', '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_eip4844', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}], 'state_randomizer': 'randomize_state_eip4844'} # 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([EIP4844])
@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_eip4844
# epochs:0,slots:0,with-block:no_block
# epochs:0,slots:0,with-block:no_block
# epochs:0,slots:0,with-block:random_block_eip4844
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_eip4844', '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_eip4844', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}], 'state_randomizer': 'randomize_state_eip4844'} # 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([EIP4844])
@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_eip4844
# 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_eip4844
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_eip4844', '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_eip4844', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}], 'state_randomizer': 'randomize_state_eip4844'} # 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([EIP4844])
@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_eip4844
# 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_eip4844
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_eip4844', '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_eip4844', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}], 'state_randomizer': 'randomize_state_eip4844'} # 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([EIP4844])
@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_eip4844
# 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_eip4844
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_eip4844', '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_eip4844', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}], 'state_randomizer': 'randomize_state_eip4844'} # 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([EIP4844])
@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_eip4844
# 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_eip4844
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_eip4844', '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_eip4844', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}], 'state_randomizer': 'randomize_state_eip4844'} # 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([EIP4844])
@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_eip4844
# 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_eip4844
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_eip4844', '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_eip4844', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}], 'state_randomizer': 'randomize_state_eip4844'} # 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([EIP4844])
@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_eip4844
# epochs:1,slots:0,with-block:no_block
# epochs:0,slots:0,with-block:no_block
# epochs:0,slots:0,with-block:random_block_eip4844
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_eip4844', '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_eip4844', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}], 'state_randomizer': 'randomize_state_eip4844'} # 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([EIP4844])
@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_eip4844
# 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_eip4844
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_eip4844', '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_eip4844', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}], 'state_randomizer': 'randomize_state_eip4844'} # 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([EIP4844])
@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_eip4844
# epochs:0,slots:0,with-block:no_block
# epochs:0,slots:0,with-block:no_block
# epochs:0,slots:0,with-block:random_block_eip4844
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_eip4844', '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_eip4844', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}], 'state_randomizer': 'randomize_state_eip4844'} # 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([EIP4844])
@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_eip4844
# 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_eip4844
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_eip4844', '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_eip4844', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}], 'state_randomizer': 'randomize_state_eip4844'} # 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([EIP4844])
@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_eip4844
# 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_eip4844
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_eip4844', '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_eip4844', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}], 'state_randomizer': 'randomize_state_eip4844'} # 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([EIP4844])
@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_eip4844
# 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_eip4844
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_eip4844', '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_eip4844', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}], 'state_randomizer': 'randomize_state_eip4844'} # 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([EIP4844])
@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_eip4844
# 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_eip4844
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_eip4844', '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_eip4844', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}], 'state_randomizer': 'randomize_state_eip4844'} # 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([EIP4844])
@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_eip4844
# 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_eip4844
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_eip4844', '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_eip4844', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}], 'state_randomizer': 'randomize_state_eip4844'} # 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([EIP4844])
@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_eip4844
# epochs:1,slots:0,with-block:no_block
# epochs:0,slots:0,with-block:no_block
# epochs:0,slots:0,with-block:random_block_eip4844
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_eip4844', '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_eip4844', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}], 'state_randomizer': 'randomize_state_eip4844'} # noqa: E501
yield from run_generated_randomized_test(
spec,
state,
scenario,
)

View File

@@ -17,4 +17,4 @@ def test_verify_kzg_proof(spec, state):
proof = spec.compute_kzg_proof(polynomial, x)
y = spec.evaluate_polynomial_in_evaluation_form(polynomial, x)
assert spec.verify_kzg_proof(commitment, x, y, proof)
assert spec.verify_kzg_proof_impl(commitment, x, y, proof)

View File

@@ -0,0 +1,23 @@
from eth2spec.test.helpers.constants import (
EIP4844,
MINIMAL,
)
from eth2spec.test.helpers.sharding import (
get_sample_opaque_tx,
)
from eth2spec.test.context import (
with_phases,
spec_state_test,
with_presets,
)
@with_phases([EIP4844])
@spec_state_test
@with_presets([MINIMAL])
def test_tx_peek_blob_versioned_hashes(spec, state):
otx, blobs, commitments = get_sample_opaque_tx(spec)
data_hashes = spec.tx_peek_blob_versioned_hashes(otx)
expected = [spec.kzg_commitment_to_versioned_hash(blob_commitment) for blob_commitment in commitments]
assert expected == data_hashes

View File

@@ -2,9 +2,10 @@ from lru import LRU
from typing import List
from eth2spec.test.context import expect_assertion_error, is_post_altair
from eth2spec.test.context import expect_assertion_error
from eth2spec.test.helpers.state import state_transition_and_sign_block, next_epoch, next_slot
from eth2spec.test.helpers.block import build_empty_block_for_next_slot
from eth2spec.test.helpers.forks import is_post_altair
from eth2spec.test.helpers.keys import privkeys
from eth2spec.utils import bls
from eth2spec.utils.ssz.ssz_typing import Bitlist
@@ -254,7 +255,7 @@ def state_transition_with_full_block(spec,
sync_aggregate=None,
block=None):
"""
Build and apply a block with attestions at the calculated `slot_to_attest` of current epoch and/or previous epoch.
Build and apply a block with attestations at the calculated `slot_to_attest` of current epoch and/or previous epoch.
"""
if block is None:
block = build_empty_block_for_next_slot(spec, state)

View File

@@ -1,5 +1,5 @@
from eth2spec.test.context import is_post_altair, is_post_bellatrix
from eth2spec.test.helpers.execution_payload import build_empty_execution_payload
from eth2spec.test.helpers.forks import is_post_altair, is_post_bellatrix
from eth2spec.test.helpers.keys import privkeys
from eth2spec.utils import bls
from eth2spec.utils.bls import only_with_bls

View File

@@ -2,7 +2,7 @@ from eth2spec.utils import bls
from eth2spec.test.helpers.keys import pubkeys, privkeys, pubkey_to_privkey
def get_signed_address_change(spec, state, validator_index=None, withdrawal_pubkey=None):
def get_signed_address_change(spec, state, validator_index=None, withdrawal_pubkey=None, to_execution_address=None):
if validator_index is None:
validator_index = 0
@@ -13,11 +13,14 @@ def get_signed_address_change(spec, state, validator_index=None, withdrawal_pubk
else:
withdrawal_privkey = pubkey_to_privkey[withdrawal_pubkey]
if to_execution_address is None:
to_execution_address = b'\x42' * 20
domain = spec.get_domain(state, spec.DOMAIN_BLS_TO_EXECUTION_CHANGE)
address_change = spec.BLSToExecutionChange(
validator_index=validator_index,
from_bls_pubkey=withdrawal_pubkey,
to_execution_address=b'\x42' * 20,
to_execution_address=to_execution_address,
)
signing_root = spec.compute_signing_root(address_change, domain)

View File

@@ -26,18 +26,16 @@ ALL_PHASES = (
# The forks that output to the test vectors.
TESTGEN_FORKS = (PHASE0, ALTAIR, BELLATRIX, CAPELLA, EIP4844)
FORKS_BEFORE_ALTAIR = (PHASE0,)
FORKS_BEFORE_BELLATRIX = (PHASE0, ALTAIR)
# TODO: no EIP4844 fork tests now. Should add when we figure out the content of Capella.
ALL_FORK_UPGRADES = {
# pre_fork_name: post_fork_name
PHASE0: ALTAIR,
ALTAIR: BELLATRIX,
BELLATRIX: CAPELLA,
CAPELLA: EIP4844,
}
ALL_PRE_POST_FORKS = ALL_FORK_UPGRADES.items()
AFTER_BELLATRIX_UPGRADES = {key: value for key, value in ALL_FORK_UPGRADES.items() if key not in FORKS_BEFORE_ALTAIR}
AFTER_BELLATRIX_UPGRADES = {key: value for key, value in ALL_FORK_UPGRADES.items() if key != PHASE0}
AFTER_BELLATRIX_PRE_POST_FORKS = AFTER_BELLATRIX_UPGRADES.items()
#

View File

@@ -1,9 +1,7 @@
from random import Random
from eth2spec.test.context import (
is_post_altair,
expect_assertion_error,
)
from eth2spec.test.context import expect_assertion_error
from eth2spec.test.helpers.forks import is_post_altair
from eth2spec.test.helpers.keys import pubkeys, privkeys
from eth2spec.test.helpers.state import get_balance
from eth2spec.utils import bls

View File

@@ -0,0 +1,63 @@
from eth2spec.test.helpers.constants import (
EIP4844,
)
EIP4844_FORK_TEST_META_TAGS = {
'fork': EIP4844,
}
def run_fork_test(post_spec, pre_state):
yield 'pre', pre_state
post_state = post_spec.upgrade_to_eip4844(pre_state)
# Stable fields
stable_fields = [
'genesis_time', 'genesis_validators_root', 'slot',
# History
'latest_block_header', 'block_roots', 'state_roots', 'historical_roots',
# Eth1
'eth1_data', 'eth1_data_votes', 'eth1_deposit_index',
# Registry
'validators', 'balances',
# Randomness
'randao_mixes',
# Slashings
'slashings',
# Participation
'previous_epoch_participation', 'current_epoch_participation',
# Finality
'justification_bits', 'previous_justified_checkpoint', 'current_justified_checkpoint', 'finalized_checkpoint',
# Inactivity
'inactivity_scores',
# Sync
'current_sync_committee', 'next_sync_committee',
# Withdrawals
'next_withdrawal_index', 'next_withdrawal_validator_index',
]
for field in stable_fields:
assert getattr(pre_state, field) == getattr(post_state, field)
# Modified fields
modified_fields = ['fork', 'latest_execution_payload_header']
for field in modified_fields:
assert getattr(pre_state, field) != getattr(post_state, field)
assert len(pre_state.validators) == len(post_state.validators)
for pre_validator, post_validator in zip(pre_state.validators, post_state.validators):
stable_validator_fields = [
'pubkey', 'withdrawal_credentials',
'effective_balance',
'slashed',
'activation_eligibility_epoch', 'activation_epoch', 'exit_epoch', 'withdrawable_epoch',
]
for field in stable_validator_fields:
assert getattr(pre_validator, field) == getattr(post_validator, field)
assert pre_state.fork.current_version == post_state.fork.previous_version
assert post_state.fork.current_version == post_spec.config.EIP4844_FORK_VERSION
assert post_state.fork.epoch == post_spec.get_current_epoch(post_state)
yield 'post', post_state

View File

@@ -1,5 +1,5 @@
from eth2spec.test.context import is_post_altair
from eth2spec.test.helpers.forks import is_post_altair
def get_process_calls(spec):

View File

@@ -1,5 +1,5 @@
from eth2spec.debug.random_value import get_random_bytes_list
from eth2spec.test.context import is_post_capella
from eth2spec.test.helpers.forks import is_post_capella
def build_empty_execution_payload(spec, state, randao_mix=None):
@@ -30,8 +30,7 @@ def build_empty_execution_payload(spec, state, randao_mix=None):
transactions=empty_txs,
)
if is_post_capella(spec):
num_withdrawals = min(spec.MAX_WITHDRAWALS_PER_PAYLOAD, len(state.withdrawal_queue))
payload.withdrawals = state.withdrawal_queue[:num_withdrawals]
payload.withdrawals = spec.get_expected_withdrawals(state)
# TODO: real RLP + block hash logic would be nice, requires RLP and keccak256 dependency however.
payload.block_hash = spec.Hash32(spec.hash(payload.hash_tree_root() + b"FAKE RLP HASH"))

View File

@@ -0,0 +1,33 @@
from .constants import (
PHASE0, ALTAIR, BELLATRIX, CAPELLA, EIP4844,
)
def is_post_fork(a, b):
if a == EIP4844:
return b in [PHASE0, ALTAIR, BELLATRIX, CAPELLA, EIP4844]
if a == CAPELLA:
return b in [PHASE0, ALTAIR, BELLATRIX, CAPELLA]
if a == BELLATRIX:
return b in [PHASE0, ALTAIR, BELLATRIX]
if a == ALTAIR:
return b in [PHASE0, ALTAIR]
if a == PHASE0:
return b in [PHASE0]
raise ValueError("Unknown fork name %s" % a)
def is_post_altair(spec):
return is_post_fork(spec.fork, ALTAIR)
def is_post_bellatrix(spec):
return is_post_fork(spec.fork, BELLATRIX)
def is_post_capella(spec):
return is_post_fork(spec.fork, CAPELLA)
def is_post_eip4844(spec):
return is_post_fork(spec.fork, EIP4844)

View File

@@ -1,6 +1,8 @@
from eth2spec.test.helpers.constants import (
ALTAIR, BELLATRIX, CAPELLA, EIP4844,
FORKS_BEFORE_ALTAIR, FORKS_BEFORE_BELLATRIX,
)
from eth2spec.test.helpers.forks import (
is_post_altair, is_post_bellatrix,
)
from eth2spec.test.helpers.keys import pubkeys
@@ -88,7 +90,7 @@ def create_genesis_state(spec, validator_balances, activation_threshold):
if validator.effective_balance >= activation_threshold:
validator.activation_eligibility_epoch = spec.GENESIS_EPOCH
validator.activation_epoch = spec.GENESIS_EPOCH
if spec.fork not in FORKS_BEFORE_ALTAIR:
if is_post_altair(spec):
state.previous_epoch_participation.append(spec.ParticipationFlags(0b0000_0000))
state.current_epoch_participation.append(spec.ParticipationFlags(0b0000_0000))
state.inactivity_scores.append(spec.uint64(0))
@@ -96,13 +98,13 @@ def create_genesis_state(spec, validator_balances, activation_threshold):
# Set genesis validators root for domain separation and chain versioning
state.genesis_validators_root = spec.hash_tree_root(state.validators)
if spec.fork not in FORKS_BEFORE_ALTAIR:
if is_post_altair(spec):
# Fill in sync committees
# Note: A duplicate committee is assigned for the current and next committee at genesis
state.current_sync_committee = spec.get_next_sync_committee(state)
state.next_sync_committee = spec.get_next_sync_committee(state)
if spec.fork not in FORKS_BEFORE_BELLATRIX:
if is_post_bellatrix(spec):
# Initialize the execution payload header (with block number and genesis time set to 0)
state.latest_execution_payload_header = get_sample_genesis_execution_payload_header(
spec,

View File

@@ -1,5 +1,5 @@
from eth2spec.test.context import is_post_altair, is_post_bellatrix
from eth2spec.test.helpers.block_header import sign_block_header
from eth2spec.test.helpers.forks import is_post_altair, is_post_bellatrix
from eth2spec.test.helpers.keys import pubkey_to_privkey
from eth2spec.test.helpers.state import get_balance
from eth2spec.test.helpers.sync_committee import (

View File

@@ -1,8 +1,8 @@
from random import Random
from eth2spec.test.helpers.attestations import cached_prepare_state_with_attestations
from eth2spec.test.context import is_post_altair
from eth2spec.test.helpers.deposits import mock_deposit
from eth2spec.test.helpers.forks import is_post_altair
from eth2spec.test.helpers.state import next_epoch

View File

@@ -2,7 +2,7 @@ from random import Random
from lru import LRU
from eth2spec.phase0.mainnet import VALIDATOR_REGISTRY_LIMIT # equal everywhere, fine to import
from eth2spec.test.context import is_post_altair, is_post_bellatrix
from eth2spec.test.helpers.forks import is_post_altair, is_post_bellatrix
from eth2spec.test.helpers.state import (
next_epoch,
)
@@ -206,7 +206,7 @@ def run_get_inclusion_delay_deltas(spec, state):
rewarded_proposer_indices.add(earliest_attestation.proposer_index)
# Ensure all expected proposers have been rewarded
# Track rewarde indices
# Track reward indices
proposing_indices = [a.proposer_index for a in eligible_attestations]
for index in proposing_indices:
if index in rewarded_proposer_indices:

View File

@@ -34,13 +34,14 @@ class ECDSASignature(Container):
class BlobTransaction(Container):
chain_id: uint256
nonce: uint64
priority_fee_per_gas: uint256
max_basefee_per_gas: uint256
max_priority_fee_per_gas: uint256
max_fee_per_gas: uint256
gas: uint64
to: Union[None, Bytes20] # Address = Bytes20
value: uint256
data: ByteList[MAX_CALLDATA_SIZE]
access_list: List[AccessTuple, MAX_ACCESS_LIST_SIZE]
max_fee_per_data_gas: uint256
blob_versioned_hashes: List[Bytes32, MAX_VERSIONED_HASHES_LIST_SIZE]

View File

@@ -1,5 +1,6 @@
from eth2spec.test.context import expect_assertion_error, is_post_altair
from eth2spec.test.context import expect_assertion_error
from eth2spec.test.helpers.block import apply_empty_block, sign_block, transition_unsigned_block
from eth2spec.test.helpers.forks import is_post_altair
from eth2spec.test.helpers.voluntary_exits import get_unslashed_exited_validators

View File

@@ -1,10 +1,13 @@
import random
def set_validator_fully_withdrawable(spec, state, index, withdrawable_epoch=None):
if withdrawable_epoch is None:
withdrawable_epoch = spec.get_current_epoch(state)
validator = state.validators[index]
validator.withdrawable_epoch = withdrawable_epoch
# set exit epoch as well to avoid interactions with other epoch process, e.g. forced ejecions
# set exit epoch as well to avoid interactions with other epoch process, e.g. forced ejections
if validator.exit_epoch > withdrawable_epoch:
validator.exit_epoch = withdrawable_epoch
@@ -29,3 +32,19 @@ def set_validator_partially_withdrawable(spec, state, index, excess_balance=1000
validator = state.validators[index]
assert spec.is_partially_withdrawable_validator(validator, state.balances[index])
def prepare_expected_withdrawals(spec, state,
num_full_withdrawals=0, num_partial_withdrawals=0, rng=random.Random(5566)):
assert num_full_withdrawals + num_partial_withdrawals <= len(state.validators)
all_validator_indices = list(range(len(state.validators)))
sampled_indices = rng.sample(all_validator_indices, num_full_withdrawals + num_partial_withdrawals)
fully_withdrawable_indices = rng.sample(sampled_indices, num_full_withdrawals)
partial_withdrawals_indices = list(set(sampled_indices).difference(set(fully_withdrawable_indices)))
for index in fully_withdrawable_indices:
set_validator_fully_withdrawable(spec, state, index)
for index in partial_withdrawals_indices:
set_validator_partially_withdrawable(spec, state, index)
return fully_withdrawable_indices, partial_withdrawals_indices

View File

@@ -1,8 +1,9 @@
from random import Random
from eth2spec.test.context import is_post_altair, spec_state_test, with_all_phases
from eth2spec.test.context import spec_state_test, with_all_phases
from eth2spec.test.helpers.epoch_processing import (
run_epoch_processing_with,
)
from eth2spec.test.helpers.forks import is_post_altair
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

View File

@@ -88,7 +88,7 @@ def test_activation_queue_no_activation_no_finality(spec, state):
def test_activation_queue_sorting(spec, state):
churn_limit = spec.get_validator_churn_limit(state)
# try to activate more than the per-epoch churn linmit
# try to activate more than the per-epoch churn limit
mock_activations = churn_limit * 2
epoch = spec.get_current_epoch(state)

View File

@@ -5,6 +5,8 @@ from eth2spec.test.context import (
with_custom_state,
zero_activation_threshold,
misc_balances, low_single_balance,
)
from eth2spec.test.helpers.forks import (
is_post_altair,
)
from eth2spec.test.helpers.state import (

View File

@@ -1,8 +1,9 @@
from random import Random
from eth2spec.test.context import spec_state_test, with_all_phases, is_post_altair, is_post_bellatrix
from eth2spec.test.context import spec_state_test, with_all_phases
from eth2spec.test.helpers.epoch_processing import (
run_epoch_processing_with, run_epoch_processing_to
)
from eth2spec.test.helpers.forks import is_post_altair, is_post_bellatrix
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

View File

@@ -2,7 +2,6 @@ import random
from eth_utils import encode_hex
from eth2spec.test.context import (
is_post_altair,
spec_state_test,
with_all_phases,
with_presets,
@@ -24,6 +23,9 @@ from eth2spec.test.helpers.fork_choice import (
tick_and_run_on_attestation,
tick_and_add_block,
)
from eth2spec.test.helpers.forks import (
is_post_altair,
)
from eth2spec.test.helpers.state import (
next_slots,
next_epoch,

View File

@@ -1,5 +1,4 @@
from eth2spec.test.context import (
is_post_altair,
single_phase,
spec_test,
with_presets,
@@ -10,6 +9,9 @@ from eth2spec.test.helpers.deposits import (
prepare_full_genesis_deposits,
prepare_random_genesis_deposits,
)
from eth2spec.test.helpers.forks import (
is_post_altair,
)
def get_post_altair_description(spec):

View File

@@ -1,5 +1,4 @@
from eth2spec.test.context import (
is_post_altair,
spec_test,
single_phase,
with_presets,
@@ -9,6 +8,9 @@ from eth2spec.test.helpers.constants import MINIMAL
from eth2spec.test.helpers.deposits import (
prepare_full_genesis_deposits,
)
from eth2spec.test.helpers.forks import (
is_post_altair,
)
def get_post_altair_description(spec):

View File

@@ -30,6 +30,7 @@ from eth2spec.test.helpers.sync_committee import (
compute_sync_committee_participant_reward_and_penalty,
)
from eth2spec.test.helpers.constants import PHASE0, MINIMAL
from eth2spec.test.helpers.forks import is_post_altair, is_post_bellatrix
from eth2spec.test.context import (
spec_test, spec_state_test, dump_skipping_message,
with_phases, with_all_phases, single_phase,
@@ -37,8 +38,6 @@ from eth2spec.test.context import (
with_presets,
with_custom_state,
large_validator_set,
is_post_altair,
is_post_bellatrix,
)
@@ -434,7 +433,7 @@ def test_proposer_slashing(spec, state):
@with_all_phases
@spec_state_test
def test_double_same_proposer_slashings_same_block(spec, state):
def test_invalid_duplicate_proposer_slashings_same_block(spec, state):
proposer_slashing = get_valid_proposer_slashing(spec, state, signed_1=True, signed_2=True)
slashed_index = proposer_slashing.signed_header_1.message.proposer_index
assert not state.validators[slashed_index].slashed
@@ -451,7 +450,7 @@ def test_double_same_proposer_slashings_same_block(spec, state):
@with_all_phases
@spec_state_test
def test_double_similar_proposer_slashings_same_block(spec, state):
def test_invalid_similar_proposer_slashings_same_block(spec, state):
slashed_index = spec.get_active_validator_indices(state, spec.get_current_epoch(state))[-1]
# Same validator, but different slashable offences in the same block
@@ -550,7 +549,7 @@ def test_attester_slashing(spec, state):
@with_all_phases
@spec_state_test
def test_duplicate_attester_slashing(spec, state):
def test_invalid_duplicate_attester_slashing_same_block(spec, state):
if spec.MAX_ATTESTER_SLASHINGS < 2:
return dump_skipping_message("Skip test if config cannot handle multiple AttesterSlashings per block")
@@ -745,6 +744,27 @@ def test_deposit_in_block(spec, state):
assert state.validators[validator_index].pubkey == pubkeys[validator_index]
@with_all_phases
@spec_state_test
def test_invalid_duplicate_deposit_same_block(spec, state):
validator_index = len(state.validators)
amount = spec.MAX_EFFECTIVE_BALANCE
deposit = prepare_state_and_deposit(spec, state, validator_index, amount, signed=True)
yield 'pre', state
block = build_empty_block_for_next_slot(spec, state)
# The same deposit of the same validator
for _ in range(2):
block.body.deposits.append(deposit)
signed_block = state_transition_and_sign_block(spec, state, block, expect_fail=True)
yield 'blocks', [signed_block]
yield 'post', None
@with_all_phases
@spec_state_test
def test_deposit_top_up(spec, state):
@@ -832,6 +852,49 @@ def test_attestation(spec, state):
assert spec.hash_tree_root(state.previous_epoch_participation) == pre_current_epoch_participation_root
@with_all_phases
@spec_state_test
def test_duplicate_attestation_same_block(spec, state):
next_epoch(spec, state)
yield 'pre', state
attestation_block = build_empty_block(spec, state, state.slot + spec.MIN_ATTESTATION_INCLUSION_DELAY)
index = 0
attestation = get_valid_attestation(spec, state, index=index, signed=True)
if not is_post_altair(spec):
pre_current_attestations_len = len(state.current_epoch_attestations)
# Add to state via block transition
for _ in range(2):
attestation_block.body.attestations.append(attestation)
signed_attestation_block = state_transition_and_sign_block(spec, state, attestation_block)
if not is_post_altair(spec):
assert len(state.current_epoch_attestations) == pre_current_attestations_len + 2
# Epoch transition should move to previous_epoch_attestations
pre_current_attestations_root = spec.hash_tree_root(state.current_epoch_attestations)
else:
pre_current_epoch_participation_root = spec.hash_tree_root(state.current_epoch_participation)
epoch_block = build_empty_block(spec, state, state.slot + spec.SLOTS_PER_EPOCH)
signed_epoch_block = state_transition_and_sign_block(spec, state, epoch_block)
yield 'blocks', [signed_attestation_block, signed_epoch_block]
yield 'post', state
if not is_post_altair(spec):
assert len(state.current_epoch_attestations) == 0
assert spec.hash_tree_root(state.previous_epoch_attestations) == pre_current_attestations_root
else:
for index in range(len(state.validators)):
assert state.current_epoch_participation[index] == spec.ParticipationFlags(0b0000_0000)
assert spec.hash_tree_root(state.previous_epoch_participation) == pre_current_epoch_participation_root
# After SHARDING is enabled, a committee is computed for SHARD_COMMITTEE_PERIOD slots ago,
# exceeding the minimal-config randao mixes memory size.
# Applies to all voluntary-exit sanity block tests.
@@ -867,7 +930,7 @@ def test_voluntary_exit(spec, state):
@with_all_phases
@spec_state_test
def test_double_validator_exit_same_block(spec, state):
def test_invalid_duplicate_validator_exit_same_block(spec, state):
validator_index = spec.get_active_validator_indices(state, spec.get_current_epoch(state))[-1]
# move state forward SHARD_COMMITTEE_PERIOD epochs to allow for exit

View File

@@ -1,9 +1,11 @@
from eth2spec.test.context import (
spec_state_test,
with_all_phases,
is_post_altair, is_post_bellatrix,
)
from eth2spec.test.helpers.constants import MAX_UINT_64
from eth2spec.test.helpers.forks import (
is_post_altair, is_post_bellatrix,
)
def check_bound(value, lower_bound, upper_bound):

View File

@@ -20,6 +20,9 @@ from eth2spec.test.helpers.random import (
randomize_state as randomize_state_helper,
patch_state_to_non_leaking,
)
from eth2spec.test.helpers.sharding import (
get_sample_opaque_tx,
)
from eth2spec.test.helpers.state import (
next_slot,
next_epoch,
@@ -78,6 +81,17 @@ def randomize_state_capella(spec, state, stats, exit_fraction=0.1, slash_fractio
stats,
exit_fraction=exit_fraction,
slash_fraction=slash_fraction)
# TODO: randomize withdrawals
return scenario_state
def randomize_state_eip4844(spec, state, stats, exit_fraction=0.1, slash_fraction=0.1):
scenario_state = randomize_state_capella(spec,
state,
stats,
exit_fraction=exit_fraction,
slash_fraction=slash_fraction)
# TODO: randomize execution payload
return scenario_state
@@ -215,6 +229,16 @@ def random_block_capella(spec, state, signed_blocks, scenario_state, rng=Random(
return block
def random_block_eip4844(spec, state, signed_blocks, scenario_state, rng=Random(3456)):
block = random_block_capella(spec, state, signed_blocks, scenario_state)
# TODO: more commitments. blob_kzg_commitments: List[KZGCommitment, MAX_BLOBS_PER_BLOCK]
opaque_tx, _, blob_kzg_commitments = get_sample_opaque_tx(spec, blob_count=1)
block.body.execution_payload.transactions = [opaque_tx]
block.body.blob_kzg_commitments = blob_kzg_commitments
return block
# validations
def no_op_validation(_spec, _state):

View File

@@ -5,6 +5,7 @@ from py_ecc.optimized_bls12_381 import ( # noqa: F401
G2,
Z1,
Z2,
FQ,
add,
multiply,
neg,

View File

@@ -1,6 +1,15 @@
# Ref:
# - https://github.com/ethereum/research/blob/8f084630528ba33d92b2bc05edf5338dd193c6f1/trusted_setup/trusted_setup.py
# - https://github.com/asn-d6/kzgverify
import json
import os
from typing import (
Tuple,
Sequence,
)
from pathlib import Path
from eth_utils import encode_hex
from py_ecc.optimized_bls12_381 import ( # noqa: F401
G1,
G2,
@@ -11,13 +20,16 @@ from py_ecc.optimized_bls12_381 import ( # noqa: F401
multiply,
neg,
)
from py_ecc.typing import (
Optimized_Point3D,
)
from eth2spec.utils import bls
PRIMITIVE_ROOT_OF_UNITY = 7
def generate_setup(generator, secret, length):
def generate_setup(generator: Optimized_Point3D, secret: int, length: int) -> Tuple[Optimized_Point3D]:
"""
Generate trusted setup of ``generator`` in ``length``.
"""
@@ -27,7 +39,7 @@ def generate_setup(generator, secret, length):
return tuple(result)
def fft(vals, modulus, domain):
def fft(vals: Sequence[Optimized_Point3D], modulus: int, domain: int) -> Sequence[Optimized_Point3D]:
"""
FFT for group elements
"""
@@ -43,7 +55,7 @@ def fft(vals, modulus, domain):
return o
def compute_root_of_unity(length) -> int:
def compute_root_of_unity(length: int) -> int:
"""
Generate a w such that ``w**length = 1``.
"""
@@ -51,11 +63,12 @@ def compute_root_of_unity(length) -> int:
return pow(PRIMITIVE_ROOT_OF_UNITY, (BLS_MODULUS - 1) // length, BLS_MODULUS)
def compute_roots_of_unity(field_elements_per_blob):
def compute_roots_of_unity(field_elements_per_blob: int) -> Tuple[int]:
"""
Compute a list of roots of unity for a given order.
The order must divide the BLS multiplicative group order, i.e. BLS_MODULUS - 1
"""
field_elements_per_blob = int(field_elements_per_blob) # to non-SSZ int
assert (BLS_MODULUS - 1) % field_elements_per_blob == 0
root_of_unity = compute_root_of_unity(length=field_elements_per_blob)
@@ -64,10 +77,10 @@ def compute_roots_of_unity(field_elements_per_blob):
for _ in range(field_elements_per_blob):
roots.append(current_root_of_unity)
current_root_of_unity = current_root_of_unity * root_of_unity % BLS_MODULUS
return roots
return tuple(roots)
def get_lagrange(setup):
def get_lagrange(setup: Sequence[Optimized_Point3D]) -> Tuple[bytes]:
"""
Convert a G1 or G2 portion of a setup into the Lagrange basis.
"""
@@ -77,4 +90,34 @@ def get_lagrange(setup):
# TODO: introduce an IFFT function for simplicity
fft_output = fft(setup, BLS_MODULUS, domain)
inv_length = pow(len(setup), BLS_MODULUS - 2, BLS_MODULUS)
return [bls.G1_to_bytes48(multiply(fft_output[-i], inv_length)) for i in range(len(fft_output))]
return tuple(bls.G1_to_bytes48(multiply(fft_output[-i], inv_length)) for i in range(len(fft_output)))
def dump_kzg_trusted_setup_files(secret: int, length: int, output_dir: str) -> None:
setup_g1 = generate_setup(bls.G1, secret, length)
setup_g2 = generate_setup(bls.G2, secret, length)
setup_g1_lagrange = get_lagrange(setup_g1)
roots_of_unity = compute_roots_of_unity(length)
serailized_setup_g1 = [encode_hex(bls.G1_to_bytes48(p)) for p in setup_g1]
serialized_setup_g2 = [encode_hex(bls.G2_to_bytes96(p)) for p in setup_g2]
serialized_setup_g1_lagrange = [encode_hex(x) for x in setup_g1_lagrange]
output_dir_path = Path(output_dir)
if not os.path.exists(output_dir_path):
os.makedirs(output_dir_path)
print("Created directory: ", output_dir_path)
file_path = output_dir_path / 'testing_trusted_setups.json'
with open(file_path, 'w+') as f:
json.dump(
{
"setup_G1": serailized_setup_g1,
"setup_G2": serialized_setup_g2,
"setup_G1_lagrange": serialized_setup_g1_lagrange,
"roots_of_unity": roots_of_unity,
}, f)
print(f'Generated trusted setup file: {file_path}\n')

View File

@@ -5,24 +5,26 @@ generation and verification of merkle proofs based on static data.
## Test case format
### `state.ssz_snappy`
Tests for each individual SSZ type are grouped into a `suite` indicating the SSZ type name.
An SSZ-snappy encoded `BeaconState` object from which other data is generated.
### `object.yaml`
A SSZ-snappy encoded object from which other data is generated. The SSZ type can be determined from the test `suite` name.
### `proof.yaml`
A proof of the leaf value (a merkle root) at generalized-index `leaf_index` in the given `state`.
A proof of the leaf value (a merkle root) at generalized-index `leaf_index` in the given `object`.
```yaml
leaf: Bytes32 # string, hex encoded, with 0x prefix
leaf_index: int # integer, decimal
branch: list of Bytes32 # list, each element is a string, hex encoded, with 0x prefix
branch: list of Bytes32 # list, each element is a string, hex encoded, with 0x prefix
```
## Condition
A test-runner can implement the following assertions:
- Check that `is_valid_merkle_branch` confirms `leaf` at `leaf_index` to verify
against `has_tree_root(state)` and `proof`.
against `hash_tree_root(object)` and `branch`.
- If the implementation supports generating merkle proofs, check that the
self-generated proof matches the `proof` provided with the test.
self-generated proof matches the `branch` provided with the test.

View File

@@ -40,7 +40,7 @@ Blocks must be processed in order, following the main transition function
Blocks are encoded as `SignedBeaconBlock`s from the relevant spec version
as indicated by the `post_fork` and `fork_block` data in the `meta.yaml`.
As blocks span fork boundaires, a `fork_block` number is given in
As blocks span fork boundaries, a `fork_block` number is given in
the `meta.yaml` to help resolve which blocks belong to which fork.
The `fork_block` is the index in the test data of the **last** block

View File

@@ -186,7 +186,7 @@ if __name__ == "__main__":
ALTAIR: altair_mods,
}
run_state_test_generators(runner_name="sanity", specs=specs, all_mods=all_mods)
run_state_test_generators(runner_name="sanity", all_mods=all_mods)
```
Here multiple phases load the configuration, and the stream of test cases is derived from a pytest file using the `eth2spec.gen_helpers.gen_from_tests.gen.run_state_test_generators` utility. Note that this helper generates all available tests of `TESTGEN_FORKS` forks of `ALL_CONFIGS` configs of the given runner.
@@ -210,7 +210,7 @@ To add a new test generator that builds `New Tests`:
with any dependencies it may need. Leave it empty if your generator has none.
3. Your generator is assumed to have a `main.py` file in its root.
By adding the base generator to your requirements, you can make a generator really easily. See docs below.
4. Your generator is called with `-o some/file/path/for_testing/can/be_anything -c some/other/path/to_configs/`.
4. Your generator is called with `-o some/file/path/for_testing/can/be_anything --preset-list mainnet minimal`.
The base generator helps you handle this; you only have to define test case providers.
5. Finally, add any linting or testing commands to the
[circleci config file](../../.circleci/config.yml) if desired to increase code quality.

View File

@@ -1,5 +1,5 @@
from eth2spec.gen_helpers.gen_from_tests.gen import run_state_test_generators, combine_mods
from eth2spec.test.helpers.constants import PHASE0, ALTAIR, BELLATRIX, CAPELLA
from eth2spec.test.helpers.constants import PHASE0, ALTAIR, BELLATRIX, CAPELLA, EIP4844
if __name__ == "__main__":
@@ -27,11 +27,13 @@ if __name__ == "__main__":
# so no additional tests required.
bellatrix_mods = altair_mods
_new_capella_mods = {key: 'eth2spec.test.capella.epoch_processing.test_process_' + key for key in [
'full_withdrawals',
'partial_withdrawals',
]}
capella_mods = combine_mods(_new_capella_mods, altair_mods)
# No epoch-processing changes in Capella and previous testing repeats with new types,
# so no additional tests required.
capella_mods = bellatrix_mods
# No epoch-processing changes in EIP4844 and previous testing repeats with new types,
# so no additional tests required.
eip4844_mods = capella_mods
# TODO Custody Game testgen is disabled for now
# custody_game_mods = {**{key: 'eth2spec.test.custody_game.epoch_processing.test_process_' + key for key in [
@@ -45,6 +47,7 @@ if __name__ == "__main__":
ALTAIR: altair_mods,
BELLATRIX: bellatrix_mods,
CAPELLA: capella_mods,
EIP4844: eip4844_mods,
}
run_state_test_generators(runner_name="epoch_processing", all_mods=all_mods)

View File

@@ -1,5 +1,5 @@
from eth2spec.gen_helpers.gen_from_tests.gen import run_state_test_generators
from eth2spec.test.helpers.constants import PHASE0, ALTAIR, BELLATRIX, CAPELLA
from eth2spec.test.helpers.constants import PHASE0, ALTAIR, BELLATRIX, CAPELLA, EIP4844
if __name__ == "__main__":
@@ -7,12 +7,14 @@ if __name__ == "__main__":
altair_mods = phase_0_mods # No additional Altair specific finality tests
bellatrix_mods = altair_mods # No additional Bellatrix specific finality tests
capella_mods = bellatrix_mods # No additional Capella specific finality tests
eip4844_mods = capella_mods # No additional EIP4844 specific finality tests
all_mods = {
PHASE0: phase_0_mods,
ALTAIR: altair_mods,
BELLATRIX: bellatrix_mods,
CAPELLA: capella_mods,
EIP4844: eip4844_mods,
}
run_state_test_generators(runner_name="finality", all_mods=all_mods)

View File

@@ -1,5 +1,5 @@
from eth2spec.gen_helpers.gen_from_tests.gen import run_state_test_generators, combine_mods
from eth2spec.test.helpers.constants import PHASE0, ALTAIR, BELLATRIX, CAPELLA
from eth2spec.test.helpers.constants import PHASE0, ALTAIR, BELLATRIX, CAPELLA, EIP4844
if __name__ == "__main__":
@@ -18,11 +18,14 @@ if __name__ == "__main__":
]}
bellatrix_mods = combine_mods(_new_bellatrix_mods, altair_mods)
capella_mods = bellatrix_mods # No additional Capella specific fork choice tests
eip4844_mods = capella_mods # No additional Capella specific fork choice tests
all_mods = {
PHASE0: phase_0_mods,
ALTAIR: altair_mods,
BELLATRIX: bellatrix_mods,
CAPELLA: capella_mods,
EIP4844: eip4844_mods,
}
run_state_test_generators(runner_name="fork_choice", all_mods=all_mods)

View File

@@ -1,13 +1,14 @@
from typing import Iterable
from eth2spec.test.helpers.constants import (
PHASE0, ALTAIR, BELLATRIX, CAPELLA,
PHASE0, ALTAIR, BELLATRIX, CAPELLA, EIP4844,
MINIMAL, MAINNET,
)
from eth2spec.test.helpers.typing import SpecForkName, PresetBaseName
from eth2spec.test.altair.fork import test_altair_fork_basic, test_altair_fork_random
from eth2spec.test.bellatrix.fork import test_bellatrix_fork_basic, test_bellatrix_fork_random
from eth2spec.test.capella.fork import test_capella_fork_basic, test_capella_fork_random
from eth2spec.test.eip4844.fork import test_eip4844_fork_basic, test_eip4844_fork_random
from eth2spec.gen_helpers.gen_base import gen_runner, gen_typing
from eth2spec.gen_helpers.gen_from_tests.gen import generate_from_tests
@@ -39,6 +40,8 @@ def _get_fork_tests_providers():
yield create_provider(test_bellatrix_fork_random, preset, ALTAIR, BELLATRIX)
yield create_provider(test_capella_fork_basic, preset, BELLATRIX, CAPELLA)
yield create_provider(test_capella_fork_random, preset, BELLATRIX, CAPELLA)
yield create_provider(test_eip4844_fork_basic, preset, CAPELLA, EIP4844)
yield create_provider(test_eip4844_fork_random, preset, CAPELLA, EIP4844)
if __name__ == "__main__":

View File

@@ -1,5 +1,5 @@
from eth2spec.gen_helpers.gen_from_tests.gen import run_state_test_generators, combine_mods
from eth2spec.test.helpers.constants import PHASE0, ALTAIR, BELLATRIX, CAPELLA
from eth2spec.test.helpers.constants import PHASE0, ALTAIR, BELLATRIX, CAPELLA, EIP4844
if __name__ == "__main__":
@@ -16,11 +16,13 @@ if __name__ == "__main__":
]}
bellatrix_mods = combine_mods(_new_bellatrix_mods, altair_mods)
capella_mods = bellatrix_mods # No additional Capella specific genesis tests
eip4844_mods = capella_mods # No additional EIP4844 specific genesis tests
all_mods = {
PHASE0: phase_0_mods,
ALTAIR: altair_mods,
BELLATRIX: bellatrix_mods,
CAPELLA: capella_mods,
EIP4844: eip4844_mods,
}
run_state_test_generators(runner_name="genesis", all_mods=all_mods)

View File

@@ -1,4 +1,4 @@
from eth2spec.test.helpers.constants import ALTAIR, BELLATRIX, CAPELLA
from eth2spec.test.helpers.constants import ALTAIR, BELLATRIX, CAPELLA, EIP4844
from eth2spec.gen_helpers.gen_from_tests.gen import run_state_test_generators
@@ -10,11 +10,13 @@ if __name__ == "__main__":
]}
bellatrix_mods = altair_mods
capella_mods = bellatrix_mods
eip4844_mods = capella_mods
all_mods = {
ALTAIR: altair_mods,
BELLATRIX: bellatrix_mods,
CAPELLA: capella_mods,
EIP4844: eip4844_mods,
}
run_state_test_generators(runner_name="light_client", all_mods=all_mods)

View File

@@ -1,5 +1,5 @@
from eth2spec.gen_helpers.gen_from_tests.gen import run_state_test_generators, combine_mods
from eth2spec.test.helpers.constants import PHASE0, ALTAIR, BELLATRIX, CAPELLA
from eth2spec.test.helpers.constants import PHASE0, ALTAIR, BELLATRIX, CAPELLA, EIP4844
if __name__ == "__main__":
@@ -31,6 +31,12 @@ if __name__ == "__main__":
]}
capella_mods = combine_mods(_new_capella_mods, bellatrix_mods)
_new_eip4844_mods = {key: 'eth2spec.test.eip4844.block_processing.test_process_' + key for key in [
'bls_to_execution_change',
'withdrawals',
]}
eip4844_mods = combine_mods(_new_eip4844_mods, capella_mods)
# TODO Custody Game testgen is disabled for now
# _new_custody_game_mods = {key: 'eth2spec.test.custody_game.block_processing.test_process_' + key for key in [
# 'attestation',
@@ -46,6 +52,7 @@ if __name__ == "__main__":
ALTAIR: altair_mods,
BELLATRIX: bellatrix_mods,
CAPELLA: capella_mods,
EIP4844: eip4844_mods,
}
run_state_test_generators(runner_name="operations", all_mods=all_mods)

View File

@@ -6,7 +6,9 @@ all:
rm -f ../../core/pyspec/eth2spec/test/altair/random/test_random.py
rm -f ../../core/pyspec/eth2spec/test/bellatrix/random/test_random.py
rm -f ../../core/pyspec/eth2spec/test/capella/random/test_random.py
rm -f ../../core/pyspec/eth2spec/test/eip4844/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
python3 generate.py bellatrix > ../../core/pyspec/eth2spec/test/bellatrix/random/test_random.py
python3 generate.py capella > ../../core/pyspec/eth2spec/test/capella/random/test_random.py
python3 generate.py eip4844 > ../../core/pyspec/eth2spec/test/eip4844/random/test_random.py

View File

@@ -21,10 +21,12 @@ from eth2spec.test.utils.randomized_block_tests import (
randomize_state_altair,
randomize_state_bellatrix,
randomize_state_capella,
randomize_state_eip4844,
random_block,
random_block_altair_with_cycling_sync_committee_participation,
random_block_bellatrix,
random_block_capella,
random_block_eip4844,
last_slot_in_epoch,
random_slot_in_epoch,
penultimate_slot_in_epoch,
@@ -34,7 +36,7 @@ from eth2spec.test.utils.randomized_block_tests import (
transition_to_leaking,
transition_without_leak,
)
from eth2spec.test.helpers.constants import PHASE0, ALTAIR, BELLATRIX, CAPELLA
from eth2spec.test.helpers.constants import PHASE0, ALTAIR, BELLATRIX, CAPELLA, EIP4844
# Ensure this many blocks are present in *each* randomized scenario
@@ -272,5 +274,12 @@ if __name__ == "__main__":
state_randomizer=randomize_state_capella,
block_randomizer=random_block_capella,
)
if EIP4844 in sys.argv:
did_generate = True
run_generate_tests_to_std_out(
EIP4844,
state_randomizer=randomize_state_eip4844,
block_randomizer=random_block_eip4844,
)
if not did_generate:
warnings.warn("no phase given for test generation")

View File

@@ -1,4 +1,4 @@
from eth2spec.test.helpers.constants import PHASE0, ALTAIR, BELLATRIX, CAPELLA
from eth2spec.test.helpers.constants import PHASE0, ALTAIR, BELLATRIX, CAPELLA, EIP4844
from eth2spec.gen_helpers.gen_from_tests.gen import run_state_test_generators
@@ -15,12 +15,16 @@ if __name__ == "__main__":
capella_mods = {key: 'eth2spec.test.capella.random.test_' + key for key in [
'random',
]}
eip4844_mods = {key: 'eth2spec.test.eip4844.random.test_' + key for key in [
'random',
]}
all_mods = {
PHASE0: phase_0_mods,
ALTAIR: altair_mods,
BELLATRIX: bellatrix_mods,
CAPELLA: capella_mods,
EIP4844: eip4844_mods,
}
run_state_test_generators(runner_name="random", all_mods=all_mods)

View File

@@ -1,5 +1,5 @@
from eth2spec.gen_helpers.gen_from_tests.gen import run_state_test_generators
from eth2spec.test.helpers.constants import PHASE0, ALTAIR, BELLATRIX, CAPELLA
from eth2spec.test.helpers.constants import PHASE0, ALTAIR, BELLATRIX, CAPELLA, EIP4844
if __name__ == "__main__":
@@ -16,12 +16,14 @@ if __name__ == "__main__":
# Transaction fees are part of the execution-layer.
bellatrix_mods = altair_mods
capella_mods = bellatrix_mods
eip4844_mods = capella_mods
all_mods = {
PHASE0: phase_0_mods,
ALTAIR: altair_mods,
BELLATRIX: bellatrix_mods,
CAPELLA: capella_mods,
EIP4844: eip4844_mods,
}
run_state_test_generators(runner_name="rewards", all_mods=all_mods)

View File

@@ -1,4 +1,4 @@
from eth2spec.test.helpers.constants import PHASE0, ALTAIR, BELLATRIX, CAPELLA
from eth2spec.test.helpers.constants import PHASE0, ALTAIR, BELLATRIX, CAPELLA, EIP4844
from eth2spec.gen_helpers.gen_from_tests.gen import run_state_test_generators, combine_mods
@@ -23,11 +23,17 @@ if __name__ == "__main__":
]}
capella_mods = combine_mods(_new_capella_mods, bellatrix_mods)
_new_eip4844_mods = {key: 'eth2spec.test.eip4844.sanity.test_' + key for key in [
'blocks',
]}
eip4844_mods = combine_mods(_new_eip4844_mods, capella_mods)
all_mods = {
PHASE0: phase_0_mods,
ALTAIR: altair_mods,
BELLATRIX: bellatrix_mods,
CAPELLA: capella_mods,
EIP4844: eip4844_mods,
}
run_state_test_generators(runner_name="sanity", all_mods=all_mods)

View File

@@ -1,5 +1,5 @@
from eth2spec.gen_helpers.gen_from_tests.gen import run_state_test_generators
from eth2spec.test.helpers.constants import BELLATRIX, CAPELLA
from eth2spec.test.helpers.constants import BELLATRIX, CAPELLA, EIP4844
if __name__ == "__main__":
@@ -7,10 +7,12 @@ if __name__ == "__main__":
'optimistic',
]}
capella_mods = bellatrix_mods
eip4844_mods = capella_mods
all_mods = {
BELLATRIX: bellatrix_mods,
CAPELLA: capella_mods,
EIP4844: eip4844_mods,
}
run_state_test_generators(runner_name="sync", all_mods=all_mods)