Merge branch 'dev'

This commit is contained in:
Danny Ryan
2021-11-02 12:04:34 -06:00
25 changed files with 533 additions and 338 deletions

View File

@@ -90,7 +90,7 @@ jobs:
name: Install pyspec requirements
command: make install_test
- save_pyspec_cached_venv
test:
test-phase0:
docker:
- image: circleci/python:3.8
working_directory: ~/specs-repo
@@ -100,7 +100,33 @@ jobs:
- restore_pyspec_cached_venv
- run:
name: Run py-tests
command: make citest
command: make citest fork=phase0
- store_test_results:
path: tests/core/pyspec/test-reports
test-altair:
docker:
- image: circleci/python:3.8
working_directory: ~/specs-repo
steps:
- restore_cache:
key: v3-specs-repo-{{ .Branch }}-{{ .Revision }}
- restore_pyspec_cached_venv
- run:
name: Run py-tests
command: make citest fork=altair
- store_test_results:
path: tests/core/pyspec/test-reports
test-merge:
docker:
- image: circleci/python:3.8
working_directory: ~/specs-repo
steps:
- restore_cache:
key: v3-specs-repo-{{ .Branch }}-{{ .Revision }}
- restore_pyspec_cached_venv
- run:
name: Run py-tests
command: make citest fork=merge
- store_test_results:
path: tests/core/pyspec/test-reports
table_of_contents:
@@ -208,14 +234,20 @@ workflows:
- install_pyspec_test:
requires:
- checkout_specs
- test:
- test-phase0:
requires:
- install_pyspec_test
- test-altair:
requires:
- install_pyspec_test
- test-merge:
requires:
- install_pyspec_test
- table_of_contents
- codespell
- lint:
requires:
- test
- install_pyspec_test
# NOTE: Since phase 0 has been launched, we disabled the deposit contract tests.
# - install_deposit_contract_web3_tester:
# requires:

View File

@@ -105,8 +105,15 @@ find_test: pyspec
python3 -m pytest -k=$(K) --disable-bls --cov=eth2spec.phase0.minimal --cov=eth2spec.altair.minimal --cov-report="html:$(COV_HTML_OUT)" --cov-branch eth2spec
citest: pyspec
mkdir -p tests/core/pyspec/test-reports/eth2spec; . venv/bin/activate; cd $(PY_SPEC_DIR); \
mkdir -p tests/core/pyspec/test-reports/eth2spec;
ifdef fork
. venv/bin/activate; cd $(PY_SPEC_DIR); \
python3 -m pytest -n 4 --bls-type=milagro --fork=$(fork) --junitxml=eth2spec/test_results.xml eth2spec
else
. venv/bin/activate; cd $(PY_SPEC_DIR); \
python3 -m pytest -n 4 --bls-type=milagro --junitxml=eth2spec/test_results.xml eth2spec
endif
open_cov:
((open "$(COV_INDEX_FILE)" || xdg-open "$(COV_INDEX_FILE)") &> /dev/null) &

View File

@@ -7,8 +7,10 @@ PRESET_BASE: 'mainnet'
# ---------------------------------------------------------------
# TBD, 2**256-2**10 is a placeholder
TERMINAL_TOTAL_DIFFICULTY: 115792089237316195423570985008687907853269984665640564039457584007913129638912
# By default, don't use this param
# By default, don't use these params
TERMINAL_BLOCK_HASH: 0x0000000000000000000000000000000000000000000000000000000000000000
TERMINAL_BLOCK_HASH_ACTIVATION_EPOCH: 18446744073709551615
# Genesis

View File

@@ -7,8 +7,10 @@ PRESET_BASE: 'minimal'
# ---------------------------------------------------------------
# TBD, 2**256-2**10 is a placeholder
TERMINAL_TOTAL_DIFFICULTY: 115792089237316195423570985008687907853269984665640564039457584007913129638912
# By default, don't use this param
# By default, don't use these params
TERMINAL_BLOCK_HASH: 0x0000000000000000000000000000000000000000000000000000000000000000
TERMINAL_BLOCK_HASH_ACTIVATION_EPOCH: 18446744073709551615
# Genesis

View File

@@ -1,3 +1,21 @@
# Mainnet preset - The Merge
# No presets here.
# Updated penalty values
# ---------------------------------------------------------------
# 2**24 (= 16,777,216)
INACTIVITY_PENALTY_QUOTIENT_MERGE: 16777216
# 2**5 (= 32)
MIN_SLASHING_PENALTY_QUOTIENT_MERGE: 32
# 3
PROPORTIONAL_SLASHING_MULTIPLIER_MERGE: 3
# Execution
# ---------------------------------------------------------------
# 2**30 (= 1,073,741,824)
MAX_BYTES_PER_TRANSACTION: 1073741824
# 2**20 (= 1,048,576)
MAX_TRANSACTIONS_PER_PAYLOAD: 1048576
# 2**8 (= 256)
BYTES_PER_LOGS_BLOOM: 256
# 2**5 (= 32)
MAX_EXTRA_DATA_BYTES: 32

View File

@@ -1,3 +1,21 @@
# Minimal preset - The Merge
# No presets here.
# Updated penalty values
# ---------------------------------------------------------------
# 2**24 (= 16,777,216)
INACTIVITY_PENALTY_QUOTIENT_MERGE: 16777216
# 2**5 (= 32)
MIN_SLASHING_PENALTY_QUOTIENT_MERGE: 32
# 3
PROPORTIONAL_SLASHING_MULTIPLIER_MERGE: 3
# Execution
# ---------------------------------------------------------------
# 2**30 (= 1,073,741,824)
MAX_BYTES_PER_TRANSACTION: 1073741824
# 2**20 (= 1,048,576)
MAX_TRANSACTIONS_PER_PAYLOAD: 1048576
# 2**8 (= 256)
BYTES_PER_LOGS_BLOOM: 256
# 2**5 (= 32)
MAX_EXTRA_DATA_BYTES: 32

View File

@@ -497,7 +497,7 @@ class MergeSpecBuilder(AltairSpecBuilder):
return super().imports(preset_name) + f'''
from typing import Protocol
from eth2spec.altair import {preset_name} as altair
from eth2spec.utils.ssz.ssz_typing import Bytes20, ByteList, ByteVector, uint256, Union
from eth2spec.utils.ssz.ssz_typing import Bytes8, Bytes20, ByteList, ByteVector, uint256
'''
@classmethod
@@ -510,7 +510,7 @@ from eth2spec.utils.ssz.ssz_typing import Bytes20, ByteList, ByteVector, uint256
ExecutionState = Any
def get_pow_block(hash: Bytes32) -> PowBlock:
def get_pow_block(hash: Bytes32) -> Optional[PowBlock]:
return PowBlock(block_hash=hash, parent_hash=Bytes32(), total_difficulty=uint256(0), difficulty=uint256(0))
@@ -527,16 +527,12 @@ class NoopExecutionEngine(ExecutionEngine):
def execute_payload(self: ExecutionEngine, execution_payload: ExecutionPayload) -> bool:
return True
def notify_forkchoice_updated(self: ExecutionEngine, head_block_hash: Hash32, finalized_block_hash: Hash32) -> None:
def notify_forkchoice_updated(self: ExecutionEngine,
head_block_hash: Hash32,
finalized_block_hash: Hash32,
payload_attributes: Optional[PayloadAttributes]) -> None:
pass
def prepare_payload(self: ExecutionEngine,
parent_hash: Hash32,
timestamp: uint64,
random: Bytes32,
feeRecipient: ExecutionAddress) -> PayloadId:
raise NotImplementedError("no default block production")
def get_payload(self: ExecutionEngine, payload_id: PayloadId) -> ExecutionPayload:
raise NotImplementedError("no default block production")
@@ -547,7 +543,7 @@ EXECUTION_ENGINE = NoopExecutionEngine()"""
@classmethod
def hardcoded_custom_type_dep_constants(cls) -> str:
constants = {
'MAX_BYTES_PER_OPAQUE_TRANSACTION': 'uint64(2**20)',
'MAX_BYTES_PER_TRANSACTION': 'uint64(2**30)',
}
return {**super().hardcoded_custom_type_dep_constants(), **constants}
@@ -603,7 +599,7 @@ def objects_to_spec(preset_name: str,
# Access global dict of config vars for runtime configurables
for name in spec_object.config_vars.keys():
functions_spec = functions_spec.replace(name, 'config.' + name)
functions_spec = re.sub(r"\b%s\b" % name, 'config.' + name, functions_spec)
def format_config_var(name: str, vardef: VariableDefinition) -> str:
if vardef.type_name is None:
@@ -683,7 +679,7 @@ def combine_dicts(old_dict: Dict[str, T], new_dict: Dict[str, T]) -> Dict[str, T
ignored_dependencies = [
'bit', 'boolean', 'Vector', 'List', 'Container', 'BLSPubkey', 'BLSSignature',
'Bytes1', 'Bytes4', 'Bytes20', 'Bytes32', 'Bytes48', 'Bytes96', 'Bitlist', 'Bitvector',
'Bytes1', 'Bytes4', 'Bytes8', 'Bytes20', 'Bytes32', 'Bytes48', 'Bytes96', 'Bitlist', 'Bitvector',
'uint8', 'uint16', 'uint32', 'uint64', 'uint128', 'uint256',
'bytes', 'byte', 'ByteList', 'ByteVector',
'Dict', 'dict', 'field', 'ceillog2', 'floorlog2', 'Set',
@@ -1021,7 +1017,7 @@ setup(
"py_ecc==5.2.0",
"milagro_bls_binding==1.6.3",
"dataclasses==0.6",
"remerkleable==0.1.22",
"remerkleable==0.1.24",
RUAMEL_YAML_VERSION,
"lru-dict==1.1.6",
MARKO_VERSION,

View File

@@ -10,8 +10,9 @@
- [Introduction](#introduction)
- [Custom types](#custom-types)
- [Constants](#constants)
- [Preset](#preset)
- [Execution](#execution)
- [Updated penalty values](#updated-penalty-values)
- [Configuration](#configuration)
- [Transition settings](#transition-settings)
- [Containers](#containers)
@@ -28,13 +29,18 @@
- [`is_execution_enabled`](#is_execution_enabled)
- [Misc](#misc)
- [`compute_timestamp_at_slot`](#compute_timestamp_at_slot)
- [Beacon state accessors](#beacon-state-accessors)
- [Modified `get_inactivity_penalty_deltas`](#modified-get_inactivity_penalty_deltas)
- [Beacon state mutators](#beacon-state-mutators)
- [Modified `slash_validator`](#modified-slash_validator)
- [Beacon chain state transition function](#beacon-chain-state-transition-function)
- [Execution engine](#execution-engine)
- [`execute_payload`](#execute_payload)
- [Block processing](#block-processing)
- [Execution payload processing](#execution-payload-processing)
- [`is_valid_gas_limit`](#is_valid_gas_limit)
- [`process_execution_payload`](#process_execution_payload)
- [Execution payload](#execution-payload)
- [`process_execution_payload`](#process_execution_payload)
- [Epoch processing](#epoch-processing)
- [Slashings](#slashings)
- [Testing](#testing)
<!-- END doctoc generated TOC please keep comment here to allow auto update -->
@@ -42,7 +48,10 @@
## Introduction
This patch adds transaction execution to the beacon chain as part of the Merge fork.
This upgrade adds transaction execution to the beacon chain as part of the Merge fork.
Additionally, this upgrade introduces the following minor changes:
* Penalty parameter updates to their planned maximally punitive values
## Custom types
@@ -50,23 +59,32 @@ This patch adds transaction execution to the beacon chain as part of the Merge f
| Name | SSZ equivalent | Description |
| - | - | - |
| `OpaqueTransaction` | `ByteList[MAX_BYTES_PER_OPAQUE_TRANSACTION]` | a [typed transaction envelope](https://eips.ethereum.org/EIPS/eip-2718#opaque-byte-array-rather-than-an-rlp-array) structured as `TransactionType \|\| TransactionPayload` |
| `Transaction` | `Union[OpaqueTransaction]` | a transaction |
| `Transaction` | `ByteList[MAX_BYTES_PER_TRANSACTION]` | either a [typed transaction envelope](https://eips.ethereum.org/EIPS/eip-2718#opaque-byte-array-rather-than-an-rlp-array) or a legacy transaction|
| `ExecutionAddress` | `Bytes20` | Address of account on the execution layer |
## Constants
## Preset
### Execution
| Name | Value |
| - | - |
| `MAX_BYTES_PER_OPAQUE_TRANSACTION` | `uint64(2**20)` (= 1,048,576) |
| `MAX_TRANSACTIONS_PER_PAYLOAD` | `uint64(2**14)` (= 16,384) |
| `MAX_BYTES_PER_TRANSACTION` | `uint64(2**30)` (= 1,073,741,824) |
| `MAX_TRANSACTIONS_PER_PAYLOAD` | `uint64(2**20)` (= 1,048,576) |
| `BYTES_PER_LOGS_BLOOM` | `uint64(2**8)` (= 256) |
| `GAS_LIMIT_DENOMINATOR` | `uint64(2**10)` (= 1,024) |
| `MIN_GAS_LIMIT` | `uint64(5000)` (= 5,000) |
| `MAX_EXTRA_DATA_BYTES` | `2**5` (= 32) |
### Updated penalty values
The Merge updates a few configuration values to move penalty parameters to their final, maximum security values.
*Note*: The spec does *not* override previous configuration values but instead creates new values and replaces usage throughout.
| Name | Value |
| - | - |
| `INACTIVITY_PENALTY_QUOTIENT_MERGE` | `uint64(2**24)` (= 16,777,216) |
| `MIN_SLASHING_PENALTY_QUOTIENT_MERGE` | `uint64(2**5)` (= 32) |
| `PROPORTIONAL_SLASHING_MULTIPLIER_MERGE` | `uint64(3)` |
## Configuration
### Transition settings
@@ -74,7 +92,8 @@ This patch adds transaction execution to the beacon chain as part of the Merge f
| Name | Value |
| - | - |
| `TERMINAL_TOTAL_DIFFICULTY` | **TBD** |
| `TERMINAL_BLOCK_HASH` | `Hash32('0x0000000000000000000000000000000000000000000000000000000000000000')` |
| `TERMINAL_BLOCK_HASH` | `Hash32()` |
| `TERMINAL_BLOCK_HASH_ACTIVATION_EPOCH` | `FAR_FUTURE_EPOCH` |
## Containers
@@ -223,6 +242,64 @@ def compute_timestamp_at_slot(state: BeaconState, slot: Slot) -> uint64:
return uint64(state.genesis_time + slots_since_genesis * SECONDS_PER_SLOT)
```
### Beacon state accessors
#### Modified `get_inactivity_penalty_deltas`
*Note*: The function `get_inactivity_penalty_deltas` is modified to use `INACTIVITY_PENALTY_QUOTIENT_MERGE`.
```python
def get_inactivity_penalty_deltas(state: BeaconState) -> Tuple[Sequence[Gwei], Sequence[Gwei]]:
"""
Return the inactivity penalty deltas by considering timely target participation flags and inactivity scores.
"""
rewards = [Gwei(0) for _ in range(len(state.validators))]
penalties = [Gwei(0) for _ in range(len(state.validators))]
previous_epoch = get_previous_epoch(state)
matching_target_indices = get_unslashed_participating_indices(state, TIMELY_TARGET_FLAG_INDEX, previous_epoch)
for index in get_eligible_validator_indices(state):
if index not in matching_target_indices:
penalty_numerator = state.validators[index].effective_balance * state.inactivity_scores[index]
# [Modified in Merge]
penalty_denominator = INACTIVITY_SCORE_BIAS * INACTIVITY_PENALTY_QUOTIENT_MERGE
penalties[index] += Gwei(penalty_numerator // penalty_denominator)
return rewards, penalties
```
### Beacon state mutators
#### Modified `slash_validator`
*Note*: The function `slash_validator` is modified to use `MIN_SLASHING_PENALTY_QUOTIENT_MERGE`.
```python
def slash_validator(state: BeaconState,
slashed_index: ValidatorIndex,
whistleblower_index: ValidatorIndex=None) -> None:
"""
Slash the validator with index ``slashed_index``.
"""
epoch = get_current_epoch(state)
initiate_validator_exit(state, slashed_index)
validator = state.validators[slashed_index]
validator.slashed = True
validator.withdrawable_epoch = max(validator.withdrawable_epoch, Epoch(epoch + EPOCHS_PER_SLASHINGS_VECTOR))
state.slashings[epoch % EPOCHS_PER_SLASHINGS_VECTOR] += validator.effective_balance
slashing_penalty = validator.effective_balance // MIN_SLASHING_PENALTY_QUOTIENT_MERGE # [Modified in Merge]
decrease_balance(state, slashed_index, slashing_penalty)
# Apply proposer and whistleblower rewards
proposer_index = get_beacon_proposer_index(state)
if whistleblower_index is None:
whistleblower_index = proposer_index
whistleblower_reward = Gwei(validator.effective_balance // WHISTLEBLOWER_REWARD_QUOTIENT)
proposer_reward = Gwei(whistleblower_reward * PROPOSER_WEIGHT // WEIGHT_DENOMINATOR)
increase_balance(state, proposer_index, proposer_reward)
increase_balance(state, whistleblower_index, Gwei(whistleblower_reward - proposer_reward))
```
## Beacon chain state transition function
### Execution engine
@@ -262,41 +339,15 @@ def process_block(state: BeaconState, block: BeaconBlock) -> None:
process_sync_aggregate(state, block.body.sync_aggregate)
```
### Execution payload processing
#### Execution payload
#### `is_valid_gas_limit`
```python
def is_valid_gas_limit(payload: ExecutionPayload, parent: ExecutionPayloadHeader) -> bool:
parent_gas_limit = parent.gas_limit
# Check if the payload used too much gas
if payload.gas_used > payload.gas_limit:
return False
# Check if the payload changed the gas limit too much
if payload.gas_limit >= parent_gas_limit + parent_gas_limit // GAS_LIMIT_DENOMINATOR:
return False
if payload.gas_limit <= parent_gas_limit - parent_gas_limit // GAS_LIMIT_DENOMINATOR:
return False
# Check if the gas limit is at least the minimum gas limit
if payload.gas_limit < MIN_GAS_LIMIT:
return False
return True
```
#### `process_execution_payload`
##### `process_execution_payload`
```python
def process_execution_payload(state: BeaconState, payload: ExecutionPayload, execution_engine: ExecutionEngine) -> None:
# Verify consistency of the parent hash, block number, base fee per gas and gas limit
# with respect to the previous execution payload header
# Verify consistency of the parent hash with respect to the previous execution payload header
if is_merge_complete(state):
assert payload.parent_hash == state.latest_execution_payload_header.block_hash
assert payload.block_number == state.latest_execution_payload_header.block_number + uint64(1)
assert is_valid_gas_limit(payload, state.latest_execution_payload_header)
# Verify random
assert payload.random == get_randao_mix(state, get_current_epoch(state))
# Verify timestamp
@@ -322,6 +373,28 @@ def process_execution_payload(state: BeaconState, payload: ExecutionPayload, exe
)
```
### Epoch processing
#### Slashings
*Note*: The function `process_slashings` is modified to use `PROPORTIONAL_SLASHING_MULTIPLIER_MERGE`.
```python
def process_slashings(state: BeaconState) -> None:
epoch = get_current_epoch(state)
total_balance = get_total_active_balance(state)
adjusted_total_slashing_balance = min(
sum(state.slashings) * PROPORTIONAL_SLASHING_MULTIPLIER_MERGE, # [Modified in Merge]
total_balance
)
for index, validator in enumerate(state.validators):
if validator.slashed and epoch + EPOCHS_PER_SLASHINGS_VECTOR // 2 == validator.withdrawable_epoch:
increment = EFFECTIVE_BALANCE_INCREMENT # Factored out from penalty numerator to avoid uint64 overflow
penalty_numerator = validator.effective_balance // increment * adjusted_total_slashing_balance
penalty = penalty_numerator // total_balance * increment
decrease_balance(state, ValidatorIndex(index), penalty)
```
## Testing
*Note*: The function `initialize_beacon_state_from_eth1` is modified for pure Merge testing only.

View File

@@ -18,11 +18,12 @@ This document specifies configurable settings that clients must implement for th
To coordinate manual overrides to [`TERMINAL_TOTAL_DIFFICULTY`](./beacon-chain.md#Transition-settings) parameter, clients must provide `--terminal-total-difficulty-override` as a configurable setting. The value provided by this setting must take precedence over pre-configured `TERMINAL_TOTAL_DIFFICULTY` parameter. Clients should accept the setting as a decimal value (i.e., *not* hexadecimal).
Except under exceptional scenarios, this setting is expected to not be used. Sufficient warning to the user about this exceptional configurable setting should be provided.
Except under exceptional scenarios, this setting is not expected to be used. Sufficient warning to the user about this exceptional configurable setting should be provided.
### Override terminal block hash
To allow for transition coordination around a specific PoW block, clients must also provide `--terminal-block-hash-override` as a configurable setting.
The value provided by this setting takes precedence over the pre-configured `TERMINAL_BLOCK_HASH` parameter.
To allow for transition coordination around a specific PoW block, clients must also provide `--terminal-block-hash-override` and `--terminal-block-hash-epoch-override` as configurable settings.
* The value provided by `--terminal-block-hash-override` takes precedence over the pre-configured `TERMINAL_BLOCK_HASH` parameter.
* The value provided by `--terminal-block-hash-epoch-override` takes precedence over the pre-configured `TERMINAL_BLOCK_HASH_ACTIVATION_EPOCH` parameter.
Except under exceptional scenarios, this setting is expected to not be used. Sufficient warning to the user about this exceptional configurable setting should be provided.
Except under exceptional scenarios, these settings are not expected to be used. Sufficient warning to the user about this exceptional configurable setting should be provided.

View File

@@ -12,9 +12,11 @@
- [`ExecutionEngine`](#executionengine)
- [`notify_forkchoice_updated`](#notify_forkchoice_updated)
- [Helpers](#helpers)
- [`PayloadAttributes`](#payloadattributes)
- [`PowBlock`](#powblock)
- [`get_pow_block`](#get_pow_block)
- [`is_valid_terminal_pow_block`](#is_valid_terminal_pow_block)
- [`validate_merge_block`](#validate_merge_block)
- [Updated fork-choice handlers](#updated-fork-choice-handlers)
- [`on_block`](#on_block)
@@ -43,15 +45,34 @@ This function performs two actions *atomically*:
* Applies finality to the execution state: it irreversibly persists the chain of all execution payloads
and corresponding state, up to and including `finalized_block_hash`.
Additionally, if `payload_attributes` is provided, this function sets in motion a payload build process on top of
`head_block_hash` with the result to be gathered by a followup call to `get_payload`.
```python
def notify_forkchoice_updated(self: ExecutionEngine, head_block_hash: Hash32, finalized_block_hash: Hash32) -> None:
def notify_forkchoice_updated(self: ExecutionEngine,
head_block_hash: Hash32,
finalized_block_hash: Hash32,
payload_attributes: Optional[PayloadAttributes]) -> None:
...
```
*Note*: The call of the `notify_forkchoice_updated` function maps on the `POS_FORKCHOICE_UPDATED` event defined in the [EIP-3675](https://eips.ethereum.org/EIPS/eip-3675#definitions).
As per EIP-3675, before a post-transition block is finalized, `notify_forkchoice_updated` must be called with `finalized_block_hash = Hash32()`.
## Helpers
### `PayloadAttributes`
Used to signal to initiate the payload build process via `notify_forkchoice_updated`.
```python
@dataclass
class PayloadAttributes(object):
timestamp: uint64
random: Bytes32
fee_recipient: ExecutionAddress
```
### `PowBlock`
```python
@@ -64,7 +85,8 @@ class PowBlock(Container):
### `get_pow_block`
Let `get_pow_block(block_hash: Hash32) -> PowBlock` be the function that given the hash of the PoW block returns its data.
Let `get_pow_block(block_hash: Hash32) -> Optional[PowBlock]` be the function that given the hash of the PoW block returns its data.
It may result in `None` if the requested block is not yet available.
*Note*: The `eth_getBlockByHash` JSON-RPC method may be used to pull this information from an execution client.
@@ -74,14 +96,37 @@ Used by fork-choice handler, `on_block`.
```python
def is_valid_terminal_pow_block(block: PowBlock, parent: PowBlock) -> bool:
if block.block_hash == TERMINAL_BLOCK_HASH:
return True
is_total_difficulty_reached = block.total_difficulty >= TERMINAL_TOTAL_DIFFICULTY
is_parent_total_difficulty_valid = parent.total_difficulty < TERMINAL_TOTAL_DIFFICULTY
return is_total_difficulty_reached and is_parent_total_difficulty_valid
```
### `validate_merge_block`
```python
def validate_merge_block(block: BeaconBlock) -> None:
"""
Check the parent PoW block of execution payload is a valid terminal PoW block.
Note: Unavailable PoW block(s) may later become available,
and a client software MAY delay a call to ``validate_merge_block``
until the PoW block(s) become available.
"""
if TERMINAL_BLOCK_HASH != Hash32():
# If `TERMINAL_BLOCK_HASH` is used as an override, the activation epoch must be reached.
assert compute_epoch_at_slot(block.slot) >= TERMINAL_BLOCK_HASH_ACTIVATION_EPOCH
return block.block_hash == TERMINAL_BLOCK_HASH
pow_block = get_pow_block(block.body.execution_payload.parent_hash)
# Check if `pow_block` is available
assert pow_block is not None
pow_parent = get_pow_block(pow_block.parent_hash)
# Check if `pow_parent` is available
assert pow_parent is not None
# Check if `pow_block` is a valid terminal PoW block
assert is_valid_terminal_pow_block(pow_block, pow_parent)
```
## Updated fork-choice handlers
### `on_block`
@@ -90,6 +135,12 @@ def is_valid_terminal_pow_block(block: PowBlock, parent: PowBlock) -> bool:
```python
def on_block(store: Store, signed_block: SignedBeaconBlock) -> None:
"""
Run ``on_block`` upon receiving a new block.
A block that is asserted as invalid due to unavailable PoW block may be valid at a later time,
consider scheduling it for later processing in such case.
"""
block = signed_block.message
# Parent block must be known
assert block.parent_root in store.block_states
@@ -110,9 +161,7 @@ def on_block(store: Store, signed_block: SignedBeaconBlock) -> None:
# [New in Merge]
if is_merge_block(pre_state, block.body):
pow_block = get_pow_block(block.body.execution_payload.parent_hash)
pow_parent = get_pow_block(pow_block.parent_hash)
assert is_valid_terminal_pow_block(pow_block, pow_parent)
validate_merge_block(block)
# Add new block to the store
store.blocks[hash_tree_root(block)] = block

View File

@@ -14,6 +14,7 @@ Readers should understand the Phase 0 and Altair documents and use them as a bas
- [Warning](#warning)
- [Modifications in the Merge](#modifications-in-the-merge)
- [Configuration](#configuration)
- [The gossip domain: gossipsub](#the-gossip-domain-gossipsub)
- [Topics and messages](#topics-and-messages)
- [Global topics](#global-topics)
@@ -23,6 +24,11 @@ Readers should understand the Phase 0 and Altair documents and use them as a bas
- [Messages](#messages)
- [BeaconBlocksByRange v2](#beaconblocksbyrange-v2)
- [BeaconBlocksByRoot v2](#beaconblocksbyroot-v2)
- [Design decision rationale](#design-decision-rationale)
- [Gossipsub](#gossipsub)
- [Why was the max gossip message size increased at the Merge?](#why-was-the-max-gossip-message-size-increased-at-the-merge)
- [Req/Resp](#reqresp)
- [Why was the max chunk response size increased at the Merge?](#why-was-the-max-chunk-response-size-increased-at-the-merge)
<!-- END doctoc generated TOC please keep comment here to allow auto update -->
<!-- /TOC -->
@@ -34,6 +40,15 @@ Refer to the note in the [validator guide](./validator.md) for further details.
# Modifications in the Merge
## Configuration
This section outlines modifications constants that are used in this spec.
| Name | Value | Description |
|---|---|---|
| `GOSSIP_MAX_SIZE_MERGE` | `10 * 2**20` (= 10,485,760, 10 MiB) | The maximum allowed size of uncompressed gossip messages starting at the Merge upgrade. |
| `MAX_CHUNK_SIZE_MERGE` | `10 * 2**20` (= 10,485,760, 10 MiB) | The maximum allowed size of uncompressed req/resp chunked responses starting at the Merge upgrade. |
## The gossip domain: gossipsub
Some gossip meshes are upgraded in the Merge to support upgraded types.
@@ -43,7 +58,12 @@ Some gossip meshes are upgraded in the Merge to support upgraded types.
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 specification around the creation, validation, and dissemination of messages has not changed from the Phase 0 and Altair documents.
The specification around the creation, validation, and dissemination of messages has not changed from the Phase 0 and Altair documents unless explicitly noted here.
Starting at the Merge upgrade, each gossipsub [message](https://github.com/libp2p/go-libp2p-pubsub/blob/master/pb/rpc.proto#L17-L24)
has a maximum size of `GOSSIP_MAX_SIZE_MERGE`.
Clients MUST reject (fail validation) messages that are over this size limit.
Likewise, clients MUST NOT emit or propagate messages larger than this limit.
The derivation of the `message-id` remains stable.
@@ -72,16 +92,6 @@ Alias `block = signed_beacon_block.message`, `execution_payload = block.body.exe
then validate the following:
- _[REJECT]_ The block's execution payload timestamp is correct with respect to the slot
-- i.e. `execution_payload.timestamp == compute_timestamp_at_slot(state, block.slot)`.
- _[REJECT]_ Gas used is less than the gas limit --
i.e. `execution_payload.gas_used <= execution_payload.gas_limit`.
- _[REJECT]_ The execution payload block hash is not equal to the parent hash --
i.e. `execution_payload.block_hash != execution_payload.parent_hash`.
- _[REJECT]_ The execution payload transaction list data is within expected size limits,
the data MUST NOT be larger than the SSZ list-limit,
and a client MAY be more strict.
*Note*: Additional [gossip validations](https://github.com/ethereum/devp2p/blob/master/caps/eth.md#block-encoding-and-validity)
(see block "data validity" conditions) that rely more heavily on execution-layer state and logic are currently under consideration.
### Transitioning the gossip
@@ -96,7 +106,12 @@ details on how to handle transitioning gossip topics for the Merge.
**Protocol ID:** `/eth2/beacon_chain/req/beacon_blocks_by_range/2/`
Request and Response remain unchanged.
Request and Response remain unchanged unless explicitly noted here.
Starting at the Merge upgrade,
a global maximum uncompressed byte size of `MAX_CHUNK_SIZE_MERGE` MUST be applied to all method response chunks
regardless of type specific bounds that *MUST* also be respected.
The Merge fork-digest is introduced to the `context` enum to specify the Merge block type.
Per `context = compute_fork_digest(fork_version, genesis_validators_root)`:
@@ -125,3 +140,44 @@ Per `context = compute_fork_digest(fork_version, genesis_validators_root)`:
| `GENESIS_FORK_VERSION` | `phase0.SignedBeaconBlock` |
| `ALTAIR_FORK_VERSION` | `altair.SignedBeaconBlock` |
| `MERGE_FORK_VERSION` | `merge.SignedBeaconBlock` |
# Design decision rationale
## Gossipsub
### Why was the max gossip message size increased at the Merge?
With the addition of `ExecutionPayload` to `BeaconBlock`s, there is a dynamic
field -- `transactions` -- which can validly exceed the `GOSSIP_MAX_SIZE` limit (1 MiB) put in place in
place at Phase 0. At the `GAS_LIMIT` (~30M) currently seen on mainnet in 2021, a single transaction
filled entirely with data at a cost of 16 gas per byte can create a valid
`ExecutionPayload` of ~2 MiB. Thus we need a size limit to at least account for
current mainnet conditions.
Geth currently has a [max gossip message size](https://github.com/ethereum/go-ethereum/blob/3ce9f6d96f38712f5d6756e97b59ccc20cc403b3/eth/protocols/eth/protocol.go#L49) of 10 MiB.
To support backward compatibility with this previously defined network limit,
we adopt `GOSSIP_MAX_SIZE_MERGE` of 10 MiB for maximum gossip sizes at the
point of the Merge and beyond. Note, that clients SHOULD still reject objects
that exceed their maximum theoretical bounds which in most cases is less than `GOSSIP_MAX_SIZE_MERGE`.
Note, that due to additional size induced by the `BeaconBlock` contents (e.g.
proposer signature, operations lists, etc) this does reduce the
theoretical max valid `ExecutionPayload` (and `transactions` list) size as
slightly lower than 10 MiB. Considering that `BeaconBlock` max size is on the
order of 128 KiB in the worst case and the current gas limit (~30M) bounds max blocksize to less
than 2 MiB today, this marginal difference in theoretical bounds will have zero
impact on network functionality and security.
## Req/Resp
### Why was the max chunk response size increased at the Merge?
Similar to the discussion about the maximum gossip size increase, the
`ExecutionPayload` type can cause `BeaconBlock`s to exceed the 1 MiB bounds put
in place during Phase 0.
As with the gossip limit, 10 MiB is selected because this is firmly below any
valid block sizes in the range of gas limits expected in the medium term.
As with both gossip and req/rsp maximum values, type-specific limits should
always by simultaneously respected.

View File

@@ -11,9 +11,12 @@
- [Introduction](#introduction)
- [Prerequisites](#prerequisites)
- [Custom types](#custom-types)
- [Helpers](#helpers)
- [`get_pow_block_at_terminal_total_difficulty`](#get_pow_block_at_terminal_total_difficulty)
- [`get_terminal_pow_block`](#get_terminal_pow_block)
- [`get_payload_id`](#get_payload_id)
- [Protocols](#protocols)
- [`ExecutionEngine`](#executionengine)
- [`prepare_payload`](#prepare_payload)
- [`get_payload`](#get_payload)
- [Beacon chain responsibilities](#beacon-chain-responsibilities)
- [Block proposal](#block-proposal)
@@ -39,38 +42,73 @@ Please see related Beacon Chain doc before continuing and use them as a referenc
| Name | SSZ equivalent | Description |
| - | - | - |
| `PayloadId` | `uint64` | Identifier of a payload building process |
| `PayloadId` | `Bytes8` | Identifier of a payload building process |
## Helpers
### `get_pow_block_at_terminal_total_difficulty`
```python
def get_pow_block_at_terminal_total_difficulty(pow_chain: Dict[Hash32, PowBlock]) -> Optional[PowBlock]:
# `pow_chain` abstractly represents all blocks in the PoW chain
for block in pow_chain:
parent = pow_chain[block.parent_hash]
block_reached_ttd = block.total_difficulty >= TERMINAL_TOTAL_DIFFICULTY
parent_reached_ttd = parent.total_difficulty >= TERMINAL_TOTAL_DIFFICULTY
if block_reached_ttd and not parent_reached_ttd:
return block
return None
```
### `get_terminal_pow_block`
```python
def get_terminal_pow_block(pow_chain: Dict[Hash32, PowBlock]) -> Optional[PowBlock]:
if TERMINAL_BLOCK_HASH != Hash32():
# Terminal block hash override takes precedence over terminal total difficulty
if TERMINAL_BLOCK_HASH in pow_chain:
return pow_chain[TERMINAL_BLOCK_HASH]
else:
return None
return get_pow_block_at_terminal_total_difficulty(pow_chain)
```
### `get_payload_id`
Given the `head_block_hash` and the `payload_attributes` that were used to
initiate the build process via `notify_forkchoice_updated`, `get_payload_id()`
returns the `payload_id` used to retrieve the payload via `get_payload`.
```python
def get_payload_id(parent_hash: Hash32, payload_attributes: PayloadAttributes) -> PayloadId:
return PayloadId(
hash(
parent_hash
+ uint_to_bytes(payload_attributes.timestamp)
+ payload_attributes.random
+ payload_attributes.fee_recipient
)[0:8]
)
```
*Note*: This function does *not* use simple serialize `hash_tree_root` as to
avoid requiring simple serialize hashing capabilities in the Execution Layer.
## Protocols
### `ExecutionEngine`
*Note*: `prepare_payload` and `get_payload` functions are added to the `ExecutionEngine` protocol for use as a validator.
*Note*: `get_payload` function is added to the `ExecutionEngine` protocol for use as a validator.
The body of each of these functions is implementation dependent.
The Engine API may be used to implement them with an external execution engine.
#### `prepare_payload`
Given the set of execution payload attributes, `prepare_payload` initiates a process of building an execution payload
on top of the execution chain tip identified by `parent_hash`.
```python
def prepare_payload(self: ExecutionEngine,
parent_hash: Hash32,
timestamp: uint64,
random: Bytes32,
fee_recipient: ExecutionAddress) -> PayloadId:
"""
Return ``payload_id`` that is used to obtain the execution payload in a subsequent ``get_payload`` call.
"""
...
```
The body of this function is implementation dependent.
The Engine API may be used to implement it with an external execution engine.
#### `get_payload`
Given the `payload_id`, `get_payload` returns the most recent version of the execution payload that
has been built since the corresponding call to `prepare_payload` method.
has been built since the corresponding call to `notify_forkchoice_updated` method.
```python
def get_payload(self: ExecutionEngine, payload_id: PayloadId) -> ExecutionPayload:
@@ -84,6 +122,8 @@ def get_payload(self: ExecutionEngine, payload_id: PayloadId) -> ExecutionPayloa
All validator responsibilities remain unchanged other than those noted below. Namely, the transition block handling and the addition of `ExecutionPayload`.
*Note*: A validator must not propose on or attest to a block that isn't deemed valid, i.e. hasn't yet passed the beacon chain state transition and execution validations. In future upgrades, an "execution Proof-of-Custody" will be integrated to prevent outsourcing of execution payload validations.
### Block proposal
#### Constructing the `BeaconBlockBody`
@@ -92,41 +132,26 @@ All validator responsibilities remain unchanged other than those noted below. Na
To obtain an execution payload, a block proposer building a block on top of a `state` must take the following actions:
1. Set `payload_id = prepare_execution_payload(state, pow_chain, fee_recipient, execution_engine)`, where:
1. Set `payload_id = prepare_execution_payload(state, pow_chain, finalized_block_hash, fee_recipient, execution_engine)`, where:
* `state` is the state object after applying `process_slots(state, slot)` transition to the resulting state of the parent block processing
* `pow_chain` is a list that abstractly represents all blocks in the PoW chain
* `pow_chain` is a `Dict[Hash32, PowBlock]` dictionary that abstractly represents all blocks in the PoW chain with block hash as the dictionary key
* `finalized_block_hash` is the hash of the latest finalized execution payload (`Hash32()` if none yet finalized)
* `fee_recipient` is the value suggested to be used for the `coinbase` field of the execution payload
```python
def get_pow_block_at_terminal_total_difficulty(pow_chain: Sequence[PowBlock]) -> Optional[PowBlock]:
# `pow_chain` abstractly represents all blocks in the PoW chain
for block in pow_chain:
parent = get_pow_block(block.parent_hash)
block_reached_ttd = block.total_difficulty >= TERMINAL_TOTAL_DIFFICULTY
parent_reached_ttd = parent.total_difficulty >= TERMINAL_TOTAL_DIFFICULTY
if block_reached_ttd and not parent_reached_ttd:
return block
return None
def get_terminal_pow_block(pow_chain: Sequence[PowBlock]) -> Optional[PowBlock]:
if TERMINAL_BLOCK_HASH != Hash32():
# Terminal block hash override takes precedence over terminal total difficulty
pow_block_overrides = [block for block in pow_chain if block.block_hash == TERMINAL_BLOCK_HASH]
if not any(pow_block_overrides):
return None
return pow_block_overrides[0]
return get_pow_block_at_terminal_total_difficulty(pow_chain)
def prepare_execution_payload(state: BeaconState,
pow_chain: Sequence[PowBlock],
pow_chain: Dict[Hash32, PowBlock],
finalized_block_hash: Hash32,
fee_recipient: ExecutionAddress,
execution_engine: ExecutionEngine) -> Optional[PayloadId]:
if not is_merge_complete(state):
is_terminal_block_hash_set = TERMINAL_BLOCK_HASH != Hash32()
is_activation_epoch_reached = get_current_epoch(state.slot) < TERMINAL_BLOCK_HASH_ACTIVATION_EPOCH
if is_terminal_block_hash_set and is_activation_epoch_reached:
# Terminal block hash is set but activation epoch is not yet reached, no prepare payload call is needed
return None
terminal_pow_block = get_terminal_pow_block(pow_chain)
if terminal_pow_block is None:
# Pre-merge, no prepare payload call is needed
@@ -137,9 +162,14 @@ def prepare_execution_payload(state: BeaconState,
# Post-merge, normal payload
parent_hash = state.latest_execution_payload_header.block_hash
timestamp = compute_timestamp_at_slot(state, state.slot)
random = get_randao_mix(state, get_current_epoch(state))
return execution_engine.prepare_payload(parent_hash, timestamp, random, fee_recipient)
# Set the forkchoice head and initiate the payload build process
payload_attributes = PayloadAttributes(
timestamp=compute_timestamp_at_slot(state, state.slot),
random=get_randao_mix(state, get_current_epoch(state)),
fee_recipient=fee_recipient,
)
execution_engine.notify_forkchoice_updated(parent_hash, finalized_block_hash, payload_attributes)
return get_payload_id(parent_hash, payload_attributes)
```
2. Set `block.body.execution_payload = get_execution_payload(payload_id, execution_engine)`, where:

View File

@@ -334,7 +334,7 @@ Each deposit in `block.body.deposits` must verify against `state.eth1_data.eth1_
###### `get_eth1_data`
Let `Eth1Block` be an abstract object representing Eth1 blocks with the `timestamp` and depost contract data available.
Let `Eth1Block` be an abstract object representing Eth1 blocks with the `timestamp` and deposit contract data available.
Let `get_eth1_data(block: Eth1Block) -> Eth1Data` be the function that returns the Eth1 data for a given Eth1 block.

View File

@@ -1 +1 @@
1.1.3
1.1.4

View File

@@ -1,4 +1,7 @@
from eth2spec.test import context
from eth2spec.test.helpers.constants import (
ALL_PHASES,
)
from eth2spec.utils import bls as bls_utils
# We import pytest only when it's present, i.e. when we are running tests.
@@ -29,6 +32,13 @@ def pytest_addoption(parser):
"--preset", action="store", type=str, default="minimal",
help="preset: make the pyspec use the specified preset"
)
parser.addoption(
"--fork", action="append", type=str,
help=(
"fork: make the pyspec only run with the specified phase."
" To run multiple phases, e.g., --fork=phase0 --fork=altair"
)
)
parser.addoption(
"--disable-bls", action="store_true", default=False,
help="bls-default: make tests that are not dependent on BLS run without BLS"
@@ -39,11 +49,31 @@ def pytest_addoption(parser):
)
def _validate_fork_name(forks):
for fork in forks:
if fork not in ALL_PHASES:
raise ValueError(
f'The given --fork argument "{fork}" is not an available fork.'
f' The available forks: {ALL_PHASES}'
)
@fixture(autouse=True)
def preset(request):
context.DEFAULT_TEST_PRESET = request.config.getoption("--preset")
@fixture(autouse=True)
def run_phases(request):
forks = request.config.getoption("--fork", default=None)
if forks:
forks = [fork.lower() for fork in forks]
_validate_fork_name(forks)
context.DEFAULT_PYTEST_FORKS = set(forks)
else:
context.DEFAULT_PYTEST_FORKS = ALL_PHASES
@fixture(autouse=True)
def bls_default(request):
disable_bls = request.config.getoption("--disable-bls")

View File

@@ -22,6 +22,9 @@ from lru import LRU
# Without pytest CLI arg or pyspec-test-generator 'preset' argument, this will be the config to apply.
DEFAULT_TEST_PRESET = MINIMAL
# Without pytest CLI arg or pyspec-test-generator 'run-phase' argument, this will be the config to apply.
DEFAULT_PYTEST_FORKS = ALL_PHASES
# TODO: currently phases are defined as python modules.
# It would be better if they would be more well-defined interfaces for stronger typing.
@@ -351,7 +354,7 @@ def with_phases(phases, other_phases=None):
"""
def decorator(fn):
def wrapper(*args, **kw):
run_phases = phases
run_phases = set(phases).intersection(DEFAULT_PYTEST_FORKS)
# limit phases if one explicitly specified
if 'phase' in kw:

View File

@@ -244,15 +244,6 @@ def run_transition_with_operation(state,
signed_exits = prepare_signed_exits(spec, state, [selected_validator_index])
operation_dict = {'voluntary_exits': signed_exits}
blocks = []
if is_right_before_fork:
# add a block with operation.
block = build_empty_block_for_next_slot(spec, state)
_set_operations_by_dict(block, operation_dict)
signed_block = state_transition_and_sign_block(spec, state, block)
blocks.append(pre_tag(signed_block))
def _check_state():
if operation_type == OperationType.PROPOSER_SLASHING:
slashed_proposer = state.validators[proposer_slashing.signed_header_1.message.proposer_index]
@@ -274,11 +265,19 @@ def run_transition_with_operation(state,
validator = state.validators[selected_validator_index]
assert validator.exit_epoch < post_spec.FAR_FUTURE_EPOCH
if is_right_before_fork:
_check_state()
yield "pre", state
blocks = []
if is_right_before_fork:
# add a block with operation.
block = build_empty_block_for_next_slot(spec, state)
_set_operations_by_dict(block, operation_dict)
signed_block = state_transition_and_sign_block(spec, state, block)
blocks.append(pre_tag(signed_block))
_check_state()
# irregular state transition to handle fork:
_operation_at_slot = operation_dict if is_at_fork else None
state, block = do_altair_fork(state, spec, post_spec, fork_epoch, operation_dict=_operation_at_slot)
@@ -289,7 +288,7 @@ def run_transition_with_operation(state,
# after the fork
if operation_type == OperationType.DEPOSIT:
_transition_until_active(post_spec, state, post_tag, blocks, selected_validator_index)
state = _transition_until_active(post_spec, state, post_tag, blocks, selected_validator_index)
else:
# avoid using the slashed validators as block proposers
ignoring_proposers = [selected_validator_index] if is_slashing_operation else None
@@ -333,3 +332,5 @@ def _transition_until_active(post_spec, state, post_tag, blocks, validator_index
state_transition_across_slots(post_spec, state, to_slot, block_filter=only_at(to_slot))
])
assert post_spec.is_active_validator(state.validators[validator_index], post_spec.get_current_epoch(state))
return state

View File

@@ -1,4 +1,4 @@
from eth2spec.test.context import is_post_altair
from eth2spec.test.context import is_post_altair, is_post_merge
from eth2spec.test.helpers.block_header import sign_block_header
from eth2spec.test.helpers.keys import pubkey_to_privkey
from eth2spec.test.helpers.state import get_balance
@@ -9,7 +9,9 @@ from eth2spec.test.helpers.sync_committee import (
def get_min_slashing_penalty_quotient(spec):
if is_post_altair(spec):
if is_post_merge(spec):
return spec.MIN_SLASHING_PENALTY_QUOTIENT_MERGE
elif is_post_altair(spec):
return spec.MIN_SLASHING_PENALTY_QUOTIENT_ALTAIR
else:
return spec.MIN_SLASHING_PENALTY_QUOTIENT

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
from eth2spec.test.context import is_post_altair, is_post_merge
from eth2spec.test.helpers.state import (
next_epoch,
)
@@ -21,6 +21,15 @@ class Deltas(Container):
penalties: List[uint64, VALIDATOR_REGISTRY_LIMIT]
def get_inactivity_penalty_quotient(spec):
if is_post_merge(spec):
return spec.INACTIVITY_PENALTY_QUOTIENT_MERGE
elif is_post_altair(spec):
return spec.INACTIVITY_PENALTY_QUOTIENT_ALTAIR
else:
return spec.INACTIVITY_PENALTY_QUOTIENT
def has_enough_for_reward(spec, state, index):
"""
Check if base_reward will be non-zero.
@@ -45,7 +54,7 @@ def has_enough_for_leak_penalty(spec, state, index):
if is_post_altair(spec):
return (
state.validators[index].effective_balance * state.inactivity_scores[index]
> spec.config.INACTIVITY_SCORE_BIAS * spec.INACTIVITY_PENALTY_QUOTIENT_ALTAIR
> spec.config.INACTIVITY_SCORE_BIAS * get_inactivity_penalty_quotient(spec)
)
else:
return (
@@ -266,7 +275,7 @@ def run_get_inactivity_penalty_deltas(spec, state):
else:
# copied from spec:
penalty_numerator = state.validators[index].effective_balance * state.inactivity_scores[index]
penalty_denominator = spec.config.INACTIVITY_SCORE_BIAS * spec.INACTIVITY_PENALTY_QUOTIENT_ALTAIR
penalty_denominator = spec.config.INACTIVITY_SCORE_BIAS * get_inactivity_penalty_quotient(spec)
assert penalties[index] == penalty_numerator // penalty_denominator

View File

@@ -1,4 +1,3 @@
from eth2spec.utils.ssz.ssz_typing import uint64
from eth2spec.test.helpers.execution_payload import (
build_empty_execution_payload,
get_execution_payload_header,
@@ -173,20 +172,6 @@ def test_bad_random_regular_payload(spec, state):
yield from run_execution_payload_processing(spec, state, execution_payload, valid=False)
@with_merge_and_later
@spec_state_test
def test_bad_number_regular_payload(spec, state):
# pre-state
state = build_state_with_complete_transition(spec, state)
next_slot(spec, state)
# execution payload
execution_payload = build_empty_execution_payload(spec, state)
execution_payload.block_number = execution_payload.block_number + 1
yield from run_execution_payload_processing(spec, state, execution_payload, valid=False)
@with_merge_and_later
@spec_state_test
def test_bad_everything_regular_payload(spec, state):
@@ -197,7 +182,8 @@ def test_bad_everything_regular_payload(spec, state):
# execution payload
execution_payload = build_empty_execution_payload(spec, state)
execution_payload.parent_hash = spec.Hash32()
execution_payload.block_number = execution_payload.block_number + 1
execution_payload.random = spec.Bytes32()
execution_payload.timestamp = 0
yield from run_execution_payload_processing(spec, state, execution_payload, valid=False)
@@ -228,157 +214,3 @@ def test_bad_timestamp_regular_payload(spec, state):
execution_payload.timestamp = execution_payload.timestamp + 1
yield from run_execution_payload_processing(spec, state, execution_payload, valid=False)
@with_merge_and_later
@spec_state_test
def test_gaslimit_zero_first_payload(spec, state):
# pre-state
state = build_state_with_incomplete_transition(spec, state)
next_slot(spec, state)
# execution payload
execution_payload = build_empty_execution_payload(spec, state)
execution_payload.gas_limit = uint64(0)
yield from run_execution_payload_processing(spec, state, execution_payload)
@with_merge_and_later
@spec_state_test
def test_gaslimit_max_first_payload(spec, state):
# pre-state
state = build_state_with_incomplete_transition(spec, state)
next_slot(spec, state)
# execution payload
execution_payload = build_empty_execution_payload(spec, state)
execution_payload.gas_limit = uint64(2**64 - 1)
yield from run_execution_payload_processing(spec, state, execution_payload)
@with_merge_and_later
@spec_state_test
def test_gaslimit_upper_plus_regular_payload(spec, state):
# pre-state
state = build_state_with_complete_transition(spec, state)
next_slot(spec, state)
# execution payload
execution_payload = build_empty_execution_payload(spec, state)
execution_payload.gas_limit = (
execution_payload.gas_limit +
execution_payload.gas_limit // spec.GAS_LIMIT_DENOMINATOR
)
yield from run_execution_payload_processing(spec, state, execution_payload, valid=False)
@with_merge_and_later
@spec_state_test
def test_gaslimit_upper_regular_payload(spec, state):
# pre-state
state = build_state_with_complete_transition(spec, state)
next_slot(spec, state)
# execution payload
execution_payload = build_empty_execution_payload(spec, state)
execution_payload.gas_limit = (
execution_payload.gas_limit +
execution_payload.gas_limit // spec.GAS_LIMIT_DENOMINATOR - uint64(1)
)
yield from run_execution_payload_processing(spec, state, execution_payload)
@with_merge_and_later
@spec_state_test
def test_gaslimit_lower_minus_regular_payload(spec, state):
# pre-state
state = build_state_with_complete_transition(spec, state)
next_slot(spec, state)
# execution payload
execution_payload = build_empty_execution_payload(spec, state)
execution_payload.gas_limit = (
execution_payload.gas_limit -
execution_payload.gas_limit // spec.GAS_LIMIT_DENOMINATOR
)
yield from run_execution_payload_processing(spec, state, execution_payload, valid=False)
@with_merge_and_later
@spec_state_test
def test_gaslimit_lower_regular_payload(spec, state):
# pre-state
state = build_state_with_complete_transition(spec, state)
next_slot(spec, state)
# execution payload
execution_payload = build_empty_execution_payload(spec, state)
execution_payload.gas_limit = (
execution_payload.gas_limit -
execution_payload.gas_limit // spec.GAS_LIMIT_DENOMINATOR + uint64(1)
)
yield from run_execution_payload_processing(spec, state, execution_payload)
@with_merge_and_later
@spec_state_test
def test_gaslimit_minimum_regular_payload(spec, state):
# pre-state
state = build_state_with_complete_transition(spec, state)
next_slot(spec, state)
state.latest_execution_payload_header.gas_limit = spec.MIN_GAS_LIMIT
# execution payload
execution_payload = build_empty_execution_payload(spec, state)
execution_payload.gas_limit = execution_payload.gas_limit
yield from run_execution_payload_processing(spec, state, execution_payload)
@with_merge_and_later
@spec_state_test
def test_gaslimit_minimum_minus_regular_payload(spec, state):
# pre-state
state = build_state_with_complete_transition(spec, state)
next_slot(spec, state)
state.latest_execution_payload_header.gas_limit = spec.MIN_GAS_LIMIT
# execution payload
execution_payload = build_empty_execution_payload(spec, state)
execution_payload.gas_limit = execution_payload.gas_limit - uint64(1)
yield from run_execution_payload_processing(spec, state, execution_payload, valid=False)
@with_merge_and_later
@spec_state_test
def test_gasused_gaslimit_regular_payload(spec, state):
# pre-state
state = build_state_with_complete_transition(spec, state)
next_slot(spec, state)
# execution payload
execution_payload = build_empty_execution_payload(spec, state)
execution_payload.gas_used = execution_payload.gas_limit
yield from run_execution_payload_processing(spec, state, execution_payload)
@with_merge_and_later
@spec_state_test
def test_gasused_gaslimit_plus_regular_payload(spec, state):
# pre-state
state = build_state_with_complete_transition(spec, state)
next_slot(spec, state)
# execution payload
execution_payload = build_empty_execution_payload(spec, state)
execution_payload.gas_used = execution_payload.gas_limit + uint64(1)
yield from run_execution_payload_processing(spec, state, execution_payload, valid=False)

View File

@@ -275,10 +275,38 @@ def test_invalid_current_source_root(spec, state):
state.previous_justified_checkpoint = spec.Checkpoint(epoch=3, root=b'\x01' * 32)
state.current_justified_checkpoint = spec.Checkpoint(epoch=4, root=b'\x32' * 32)
attestation = get_valid_attestation(spec, state, slot=(spec.SLOTS_PER_EPOCH * 3) + 1)
next_slots(spec, state, spec.MIN_ATTESTATION_INCLUSION_DELAY)
attestation = get_valid_attestation(spec, state, slot=spec.SLOTS_PER_EPOCH * 5)
# Test logic sanity checks:
assert attestation.data.target.epoch == spec.get_current_epoch(state)
assert state.current_justified_checkpoint.root != state.previous_justified_checkpoint.root
assert attestation.data.source.root == state.current_justified_checkpoint.root
# Make attestation source root invalid: should be current justified, not previous one
attestation.data.source.root = state.previous_justified_checkpoint.root
sign_attestation(spec, state, attestation)
yield from run_attestation_processing(spec, state, attestation, False)
@with_all_phases
@spec_state_test
def test_invalid_previous_source_root(spec, state):
next_slots(spec, state, spec.SLOTS_PER_EPOCH * 5)
state.finalized_checkpoint.epoch = 2
state.previous_justified_checkpoint = spec.Checkpoint(epoch=3, root=b'\x01' * 32)
state.current_justified_checkpoint = spec.Checkpoint(epoch=4, root=b'\x32' * 32)
attestation = get_valid_attestation(spec, state, slot=(spec.SLOTS_PER_EPOCH * 4) + 1)
next_slots(spec, state, spec.MIN_ATTESTATION_INCLUSION_DELAY)
# Test logic sanity checks:
assert attestation.data.target.epoch == spec.get_previous_epoch(state)
assert state.current_justified_checkpoint.root != state.previous_justified_checkpoint.root
assert attestation.data.source.root == state.previous_justified_checkpoint.root

View File

@@ -1,5 +1,5 @@
from random import Random
from eth2spec.test.context import spec_state_test, with_all_phases, is_post_altair
from eth2spec.test.context import spec_state_test, with_all_phases, is_post_altair, is_post_merge
from eth2spec.test.helpers.epoch_processing import (
run_epoch_processing_with, run_epoch_processing_to
)
@@ -31,7 +31,9 @@ def slash_validators(spec, state, indices, out_epochs):
def get_slashing_multiplier(spec):
if is_post_altair(spec):
if is_post_merge(spec):
return spec.PROPORTIONAL_SLASHING_MULTIPLIER_MERGE
elif is_post_altair(spec):
return spec.PROPORTIONAL_SLASHING_MULTIPLIER_ALTAIR
else:
return spec.PROPORTIONAL_SLASHING_MULTIPLIER

View File

@@ -564,6 +564,7 @@ def test_new_justified_is_later_than_store_justified(spec, state):
@with_all_phases
@spec_state_test
@with_presets([MINIMAL], reason="too slow")
def test_new_finalized_slot_is_not_justified_checkpoint_ancestor(spec, state):
"""
J: Justified
@@ -641,6 +642,7 @@ def test_new_finalized_slot_is_not_justified_checkpoint_ancestor(spec, state):
@with_all_phases
@spec_state_test
@with_presets([MINIMAL], reason="too slow")
def test_new_finalized_slot_is_justified_checkpoint_ancestor(spec, state):
"""
J: Justified

View File

@@ -1,7 +1,7 @@
from eth2spec.test.context import (
spec_state_test,
with_all_phases,
is_post_altair,
is_post_altair, is_post_merge,
)
from eth2spec.test.helpers.constants import MAX_UINT_64
@@ -52,7 +52,9 @@ def test_hysteresis_quotient(spec, state):
@spec_state_test
def test_incentives(spec, state):
# Ensure no ETH is minted in slash_validator
if is_post_altair(spec):
if is_post_merge(spec):
assert spec.MIN_SLASHING_PENALTY_QUOTIENT_MERGE <= spec.WHISTLEBLOWER_REWARD_QUOTIENT
elif is_post_altair(spec):
assert spec.MIN_SLASHING_PENALTY_QUOTIENT_ALTAIR <= spec.WHISTLEBLOWER_REWARD_QUOTIENT
else:
assert spec.MIN_SLASHING_PENALTY_QUOTIENT <= spec.WHISTLEBLOWER_REWARD_QUOTIENT

View File

@@ -180,7 +180,7 @@ class ComplexTestStruct(Container):
A: uint16
B: List[uint16, 128]
C: uint8
D: Bytes[256]
D: ByteList[256]
E: VarTestStruct
F: Vector[FixedTestStruct, 4]
G: Vector[VarTestStruct, 2]