mirror of
https://github.com/ethereum/consensus-specs.git
synced 2026-02-02 01:25:16 -05:00
Merge branch 'dev'
This commit is contained in:
@@ -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:
|
||||
|
||||
9
Makefile
9
Makefile
@@ -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) &
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
24
setup.py
24
setup.py
@@ -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,
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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.
|
||||
|
||||
|
||||
@@ -1 +1 @@
|
||||
1.1.3
|
||||
1.1.4
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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]
|
||||
|
||||
Reference in New Issue
Block a user