Merge branch 'dev' into get_pow_block-not-found

This commit is contained in:
Hsiao-Wei Wang 2021-10-26 21:37:08 +08:00
commit 5b9652bf93
No known key found for this signature in database
GPG Key ID: 1111A8A81778319E
14 changed files with 236 additions and 87 deletions

View File

@ -90,7 +90,7 @@ jobs:
name: Install pyspec requirements name: Install pyspec requirements
command: make install_test command: make install_test
- save_pyspec_cached_venv - save_pyspec_cached_venv
test: test-phase0:
docker: docker:
- image: circleci/python:3.8 - image: circleci/python:3.8
working_directory: ~/specs-repo working_directory: ~/specs-repo
@ -100,7 +100,33 @@ jobs:
- restore_pyspec_cached_venv - restore_pyspec_cached_venv
- run: - run:
name: Run py-tests 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: - store_test_results:
path: tests/core/pyspec/test-reports path: tests/core/pyspec/test-reports
table_of_contents: table_of_contents:
@ -208,14 +234,20 @@ workflows:
- install_pyspec_test: - install_pyspec_test:
requires: requires:
- checkout_specs - checkout_specs
- test: - test-phase0:
requires:
- install_pyspec_test
- test-altair:
requires:
- install_pyspec_test
- test-merge:
requires: requires:
- install_pyspec_test - install_pyspec_test
- table_of_contents - table_of_contents
- codespell - codespell
- lint: - lint:
requires: requires:
- test - install_pyspec_test
# NOTE: Since phase 0 has been launched, we disabled the deposit contract tests. # NOTE: Since phase 0 has been launched, we disabled the deposit contract tests.
# - install_deposit_contract_web3_tester: # - install_deposit_contract_web3_tester:
# requires: # 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 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 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 python3 -m pytest -n 4 --bls-type=milagro --junitxml=eth2spec/test_results.xml eth2spec
endif
open_cov: open_cov:
((open "$(COV_INDEX_FILE)" || xdg-open "$(COV_INDEX_FILE)") &> /dev/null) & ((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 # TBD, 2**256-2**10 is a placeholder
TERMINAL_TOTAL_DIFFICULTY: 115792089237316195423570985008687907853269984665640564039457584007913129638912 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: 0x0000000000000000000000000000000000000000000000000000000000000000
TERMINAL_BLOCK_HASH_ACTIVATION_EPOCH: 18446744073709551615
# Genesis # Genesis

View File

@ -7,8 +7,10 @@ PRESET_BASE: 'minimal'
# --------------------------------------------------------------- # ---------------------------------------------------------------
# TBD, 2**256-2**10 is a placeholder # TBD, 2**256-2**10 is a placeholder
TERMINAL_TOTAL_DIFFICULTY: 115792089237316195423570985008687907853269984665640564039457584007913129638912 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: 0x0000000000000000000000000000000000000000000000000000000000000000
TERMINAL_BLOCK_HASH_ACTIVATION_EPOCH: 18446744073709551615
# Genesis # Genesis

View File

@ -1,3 +1,16 @@
# Mainnet preset - The Merge # 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

View File

@ -1,3 +1,16 @@
# Minimal preset - The Merge # 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

View File

@ -497,7 +497,7 @@ class MergeSpecBuilder(AltairSpecBuilder):
return super().imports(preset_name) + f''' return super().imports(preset_name) + f'''
from typing import Protocol from typing import Protocol
from eth2spec.altair import {preset_name} as altair 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 @classmethod
@ -527,16 +527,12 @@ class NoopExecutionEngine(ExecutionEngine):
def execute_payload(self: ExecutionEngine, execution_payload: ExecutionPayload) -> bool: def execute_payload(self: ExecutionEngine, execution_payload: ExecutionPayload) -> bool:
return True 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 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: def get_payload(self: ExecutionEngine, payload_id: PayloadId) -> ExecutionPayload:
raise NotImplementedError("no default block production") raise NotImplementedError("no default block production")
@ -547,7 +543,7 @@ EXECUTION_ENGINE = NoopExecutionEngine()"""
@classmethod @classmethod
def hardcoded_custom_type_dep_constants(cls) -> str: def hardcoded_custom_type_dep_constants(cls) -> str:
constants = { constants = {
'MAX_BYTES_PER_OPAQUE_TRANSACTION': 'uint64(2**20)', 'MAX_BYTES_PER_TRANSACTION': 'uint64(2**20)',
} }
return {**super().hardcoded_custom_type_dep_constants(), **constants} 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 # Access global dict of config vars for runtime configurables
for name in spec_object.config_vars.keys(): 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: def format_config_var(name: str, vardef: VariableDefinition) -> str:
if vardef.type_name is None: 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 = [ ignored_dependencies = [
'bit', 'boolean', 'Vector', 'List', 'Container', 'BLSPubkey', 'BLSSignature', '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', 'uint8', 'uint16', 'uint32', 'uint64', 'uint128', 'uint256',
'bytes', 'byte', 'ByteList', 'ByteVector', 'bytes', 'byte', 'ByteList', 'ByteVector',
'Dict', 'dict', 'field', 'ceillog2', 'floorlog2', 'Set', 'Dict', 'dict', 'field', 'ceillog2', 'floorlog2', 'Set',
@ -1021,7 +1017,7 @@ setup(
"py_ecc==5.2.0", "py_ecc==5.2.0",
"milagro_bls_binding==1.6.3", "milagro_bls_binding==1.6.3",
"dataclasses==0.6", "dataclasses==0.6",
"remerkleable==0.1.22", "remerkleable==0.1.24",
RUAMEL_YAML_VERSION, RUAMEL_YAML_VERSION,
"lru-dict==1.1.6", "lru-dict==1.1.6",
MARKO_VERSION, MARKO_VERSION,

View File

@ -50,8 +50,7 @@ This patch adds transaction execution to the beacon chain as part of the Merge f
| Name | SSZ equivalent | Description | | 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` | `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|
| `Transaction` | `Union[OpaqueTransaction]` | a transaction |
| `ExecutionAddress` | `Bytes20` | Address of account on the execution layer | | `ExecutionAddress` | `Bytes20` | Address of account on the execution layer |
## Constants ## Constants
@ -60,7 +59,7 @@ This patch adds transaction execution to the beacon chain as part of the Merge f
| Name | Value | | 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) | | `MAX_TRANSACTIONS_PER_PAYLOAD` | `uint64(2**14)` (= 16,384) |
| `BYTES_PER_LOGS_BLOOM` | `uint64(2**8)` (= 256) | | `BYTES_PER_LOGS_BLOOM` | `uint64(2**8)` (= 256) |
| `GAS_LIMIT_DENOMINATOR` | `uint64(2**10)` (= 1,024) | | `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 | | Name | Value |
| - | - | | - | - |
| `TERMINAL_TOTAL_DIFFICULTY` | **TBD** | | `TERMINAL_TOTAL_DIFFICULTY` | **TBD** |
| `TERMINAL_BLOCK_HASH` | `Hash32('0x0000000000000000000000000000000000000000000000000000000000000000')` | | `TERMINAL_BLOCK_HASH` | `Hash32()` |
| `TERMINAL_BLOCK_HASH_ACTIVATION_EPOCH` | `FAR_FUTURE_EPOCH` |
## Containers ## Containers

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). 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 ### 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. 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 this setting takes precedence over the pre-configured `TERMINAL_BLOCK_HASH` parameter. * 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,6 +12,7 @@
- [`ExecutionEngine`](#executionengine) - [`ExecutionEngine`](#executionengine)
- [`notify_forkchoice_updated`](#notify_forkchoice_updated) - [`notify_forkchoice_updated`](#notify_forkchoice_updated)
- [Helpers](#helpers) - [Helpers](#helpers)
- [`PayloadAttributes`](#payloadattributes)
- [`PowBlock`](#powblock) - [`PowBlock`](#powblock)
- [`get_pow_block`](#get_pow_block) - [`get_pow_block`](#get_pow_block)
- [`is_valid_terminal_pow_block`](#is_valid_terminal_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 * 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`. 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 ```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). *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 ## 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` ### `PowBlock`
```python ```python
@ -75,8 +95,8 @@ Used by fork-choice handler, `on_block`.
```python ```python
def is_valid_terminal_pow_block(block: PowBlock, parent: PowBlock) -> bool: def is_valid_terminal_pow_block(block: PowBlock, parent: PowBlock) -> bool:
if block.block_hash == TERMINAL_BLOCK_HASH: if TERMINAL_BLOCK_HASH != Hash32():
return True return block.block_hash == TERMINAL_BLOCK_HASH
is_total_difficulty_reached = block.total_difficulty >= TERMINAL_TOTAL_DIFFICULTY is_total_difficulty_reached = block.total_difficulty >= TERMINAL_TOTAL_DIFFICULTY
is_parent_total_difficulty_valid = parent.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] # [New in Merge]
if is_merge_block(pre_state, block.body): 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. # Note: unavailable PoW block(s) may later become available.
# Nodes should queue such beacon blocks for later processing. # Nodes should queue such beacon blocks for later processing.
pow_block = get_pow_block(block.body.execution_payload.parent_hash) 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) pow_parent = get_pow_block(pow_block.parent_hash)
assert pow_parent is not None assert pow_parent is not None
assert is_valid_terminal_pow_block(pow_block, pow_parent) 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 # Add new block to the store
store.blocks[hash_tree_root(block)] = block store.blocks[hash_tree_root(block)] = block

View File

@ -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)`. -- i.e. `execution_payload.timestamp == compute_timestamp_at_slot(state, block.slot)`.
- _[REJECT]_ Gas used is less than the gas limit -- - _[REJECT]_ Gas used is less than the gas limit --
i.e. `execution_payload.gas_used <= execution_payload.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, - _[REJECT]_ The execution payload transaction list data is within expected size limits,
the data MUST NOT be larger than the SSZ list-limit, the data MUST NOT be larger than the SSZ list-limit,
and a client MAY be more strict. and a client MAY be more strict.

View File

@ -11,9 +11,12 @@
- [Introduction](#introduction) - [Introduction](#introduction)
- [Prerequisites](#prerequisites) - [Prerequisites](#prerequisites)
- [Custom types](#custom-types) - [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) - [Protocols](#protocols)
- [`ExecutionEngine`](#executionengine) - [`ExecutionEngine`](#executionengine)
- [`prepare_payload`](#prepare_payload)
- [`get_payload`](#get_payload) - [`get_payload`](#get_payload)
- [Beacon chain responsibilities](#beacon-chain-responsibilities) - [Beacon chain responsibilities](#beacon-chain-responsibilities)
- [Block proposal](#block-proposal) - [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 | | 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 ## Protocols
### `ExecutionEngine` ### `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 body of this function is implementation dependent.
The Engine API may be used to implement them with an external execution engine. The Engine API may be used to implement it 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.
"""
...
```
#### `get_payload` #### `get_payload`
Given the `payload_id`, `get_payload` returns the most recent version of the execution payload that 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 ```python
def get_payload(self: ExecutionEngine, payload_id: PayloadId) -> ExecutionPayload: 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: 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 * `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 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 * `fee_recipient` is the value suggested to be used for the `coinbase` field of the execution payload
```python ```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, def prepare_execution_payload(state: BeaconState,
pow_chain: Sequence[PowBlock], pow_chain: Sequence[PowBlock],
finalized_block_hash: Hash32,
fee_recipient: ExecutionAddress, fee_recipient: ExecutionAddress,
execution_engine: ExecutionEngine) -> Optional[PayloadId]: execution_engine: ExecutionEngine) -> Optional[PayloadId]:
if not is_merge_complete(state): 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) terminal_pow_block = get_terminal_pow_block(pow_chain)
if terminal_pow_block is None: if terminal_pow_block is None:
# Pre-merge, no prepare payload call is needed # Pre-merge, no prepare payload call is needed
@ -138,9 +161,14 @@ def prepare_execution_payload(state: BeaconState,
# Post-merge, normal payload # Post-merge, normal payload
parent_hash = state.latest_execution_payload_header.block_hash parent_hash = state.latest_execution_payload_header.block_hash
timestamp = compute_timestamp_at_slot(state, state.slot) # Set the forkchoice head and initiate the payload build process
random = get_randao_mix(state, get_current_epoch(state)) payload_attributes = PayloadAttributes(
return execution_engine.prepare_payload(parent_hash, timestamp, random, fee_recipient) 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: 2. Set `block.body.execution_payload = get_execution_payload(payload_id, execution_engine)`, where:

View File

@ -1,4 +1,7 @@
from eth2spec.test import context from eth2spec.test import context
from eth2spec.test.helpers.constants import (
ALL_PHASES,
)
from eth2spec.utils import bls as bls_utils from eth2spec.utils import bls as bls_utils
# We import pytest only when it's present, i.e. when we are running tests. # 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", "--preset", action="store", type=str, default="minimal",
help="preset: make the pyspec use the specified preset" 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( parser.addoption(
"--disable-bls", action="store_true", default=False, "--disable-bls", action="store_true", default=False,
help="bls-default: make tests that are not dependent on BLS run without BLS" 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) @fixture(autouse=True)
def preset(request): def preset(request):
context.DEFAULT_TEST_PRESET = request.config.getoption("--preset") 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) @fixture(autouse=True)
def bls_default(request): def bls_default(request):
disable_bls = request.config.getoption("--disable-bls") 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. # Without pytest CLI arg or pyspec-test-generator 'preset' argument, this will be the config to apply.
DEFAULT_TEST_PRESET = MINIMAL 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. # TODO: currently phases are defined as python modules.
# It would be better if they would be more well-defined interfaces for stronger typing. # 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 decorator(fn):
def wrapper(*args, **kw): def wrapper(*args, **kw):
run_phases = phases run_phases = set(phases).intersection(DEFAULT_PYTEST_FORKS)
# limit phases if one explicitly specified # limit phases if one explicitly specified
if 'phase' in kw: if 'phase' in kw: