diff --git a/.circleci/config.yml b/.circleci/config.yml index bb1df9909..bcce74bd9 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -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: diff --git a/Makefile b/Makefile index 977141078..547ed215f 100644 --- a/Makefile +++ b/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) & diff --git a/configs/mainnet.yaml b/configs/mainnet.yaml index 77d7a136f..6d23073b7 100644 --- a/configs/mainnet.yaml +++ b/configs/mainnet.yaml @@ -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 diff --git a/configs/minimal.yaml b/configs/minimal.yaml index a8b30fd54..4f99fce31 100644 --- a/configs/minimal.yaml +++ b/configs/minimal.yaml @@ -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 diff --git a/presets/mainnet/merge.yaml b/presets/mainnet/merge.yaml index 97f07c7f0..66f7abe28 100644 --- a/presets/mainnet/merge.yaml +++ b/presets/mainnet/merge.yaml @@ -1,3 +1,16 @@ # Mainnet preset - The Merge -# No presets here. +# Execution +# --------------------------------------------------------------- +# 2**20 (= 1,048,576) +MAX_BYTES_PER_TRANSACTION: 1048576 +# 2**14 (= 16,384) +MAX_TRANSACTIONS_PER_PAYLOAD: 16384 +# 2**8 (= 256) +BYTES_PER_LOGS_BLOOM: 256 +# 2**10 (= 1,024) +GAS_LIMIT_DENOMINATOR: 1024 +# 5,000 +MIN_GAS_LIMIT: 5000 +# 2**5 (= 32) +MAX_EXTRA_DATA_BYTES: 32 diff --git a/presets/minimal/merge.yaml b/presets/minimal/merge.yaml index 88aa86c09..d9b5640dd 100644 --- a/presets/minimal/merge.yaml +++ b/presets/minimal/merge.yaml @@ -1,3 +1,16 @@ # Minimal preset - The Merge -# No presets here. +# Execution +# --------------------------------------------------------------- +# 2**20 (= 1,048,576) +MAX_BYTES_PER_TRANSACTION: 1048576 +# 2**14 (= 16,384) +MAX_TRANSACTIONS_PER_PAYLOAD: 16384 +# 2**8 (= 256) +BYTES_PER_LOGS_BLOOM: 256 +# 2**10 (= 1,024) +GAS_LIMIT_DENOMINATOR: 1024 +# 5,000 +MIN_GAS_LIMIT: 5000 +# 2**5 (= 32) +MAX_EXTRA_DATA_BYTES: 32 diff --git a/setup.py b/setup.py index 0611e5567..d9a122070 100644 --- a/setup.py +++ b/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 @@ -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**20)', } 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, diff --git a/specs/merge/beacon-chain.md b/specs/merge/beacon-chain.md index f5977db5e..8fafb0720 100644 --- a/specs/merge/beacon-chain.md +++ b/specs/merge/beacon-chain.md @@ -50,8 +50,7 @@ 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 @@ -60,7 +59,7 @@ This patch adds transaction execution to the beacon chain as part of the Merge f | Name | Value | | - | - | -| `MAX_BYTES_PER_OPAQUE_TRANSACTION` | `uint64(2**20)` (= 1,048,576) | +| `MAX_BYTES_PER_TRANSACTION` | `uint64(2**20)` (= 1,048,576) | | `MAX_TRANSACTIONS_PER_PAYLOAD` | `uint64(2**14)` (= 16,384) | | `BYTES_PER_LOGS_BLOOM` | `uint64(2**8)` (= 256) | | `GAS_LIMIT_DENOMINATOR` | `uint64(2**10)` (= 1,024) | @@ -74,7 +73,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 diff --git a/specs/merge/client-settings.md b/specs/merge/client-settings.md index 4a4b38d3e..64f912372 100644 --- a/specs/merge/client-settings.md +++ b/specs/merge/client-settings.md @@ -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. diff --git a/specs/merge/fork-choice.md b/specs/merge/fork-choice.md index 18f3d7d7f..7b066c801 100644 --- a/specs/merge/fork-choice.md +++ b/specs/merge/fork-choice.md @@ -12,6 +12,7 @@ - [`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) @@ -43,15 +44,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 @@ -75,8 +95,8 @@ 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 + if TERMINAL_BLOCK_HASH != Hash32(): + return block.block_hash == TERMINAL_BLOCK_HASH is_total_difficulty_reached = block.total_difficulty >= TERMINAL_TOTAL_DIFFICULTY is_parent_total_difficulty_valid = parent.total_difficulty < TERMINAL_TOTAL_DIFFICULTY @@ -117,6 +137,7 @@ def on_block(store: Store, signed_block: SignedBeaconBlock) -> None: # [New in Merge] if is_merge_block(pre_state, block.body): + # Check the parent PoW block of execution payload is a valid terminal PoW block. # Note: unavailable PoW block(s) may later become available. # Nodes should queue such beacon blocks for later processing. pow_block = get_pow_block(block.body.execution_payload.parent_hash) @@ -124,6 +145,9 @@ def on_block(store: Store, signed_block: SignedBeaconBlock) -> None: pow_parent = get_pow_block(pow_block.parent_hash) assert pow_parent is not None assert is_valid_terminal_pow_block(pow_block, pow_parent) + # If `TERMINAL_BLOCK_HASH` is used as an override, the activation epoch must be reached. + if TERMINAL_BLOCK_HASH != Hash32(): + assert compute_epoch_at_slot(block.slot) >= TERMINAL_BLOCK_HASH_ACTIVATION_EPOCH # Add new block to the store store.blocks[hash_tree_root(block)] = block diff --git a/specs/merge/p2p-interface.md b/specs/merge/p2p-interface.md index 954c1b008..3cbba2e23 100644 --- a/specs/merge/p2p-interface.md +++ b/specs/merge/p2p-interface.md @@ -74,8 +74,6 @@ Alias `block = signed_beacon_block.message`, `execution_payload = block.body.exe -- 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. diff --git a/specs/merge/validator.md b/specs/merge/validator.md index 9fd1a24f7..bd3c9585e 100644 --- a/specs/merge/validator.md +++ b/specs/merge/validator.md @@ -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,74 @@ 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: 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) + assert parent is not None + 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: 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) +``` + +### `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: @@ -92,42 +131,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 + * `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) - assert parent is not None - 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], + 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 @@ -138,9 +161,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: diff --git a/tests/core/pyspec/eth2spec/test/conftest.py b/tests/core/pyspec/eth2spec/test/conftest.py index e6d8352e0..b3c250c11 100644 --- a/tests/core/pyspec/eth2spec/test/conftest.py +++ b/tests/core/pyspec/eth2spec/test/conftest.py @@ -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") diff --git a/tests/core/pyspec/eth2spec/test/context.py b/tests/core/pyspec/eth2spec/test/context.py index 6062648d6..944351bfd 100644 --- a/tests/core/pyspec/eth2spec/test/context.py +++ b/tests/core/pyspec/eth2spec/test/context.py @@ -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: