diff --git a/setup.py b/setup.py index 4bb26bccd..355f6e2c1 100644 --- a/setup.py +++ b/setup.py @@ -530,7 +530,7 @@ class NoopExecutionEngine(ExecutionEngine): def notify_forkchoice_updated(self: ExecutionEngine, head_block_hash: Hash32, finalized_block_hash: Hash32, - payload_attributes: Optional[PayloadAttributes]) -> None: + payload_attributes: Optional[PayloadAttributes]) -> Optional[PayloadId]: pass def get_payload(self: ExecutionEngine, payload_id: PayloadId) -> ExecutionPayload: diff --git a/specs/merge/beacon-chain.md b/specs/merge/beacon-chain.md index f8b9a8378..a4b4a23c7 100644 --- a/specs/merge/beacon-chain.md +++ b/specs/merge/beacon-chain.md @@ -10,9 +10,8 @@ - [Introduction](#introduction) - [Custom types](#custom-types) -- [Constants](#constants) - - [Execution](#execution) - [Preset](#preset) + - [Execution](#execution) - [Updated penalty values](#updated-penalty-values) - [Configuration](#configuration) - [Transition settings](#transition-settings) @@ -63,7 +62,7 @@ Additionally, this upgrade introduces the following minor changes: | `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 @@ -74,8 +73,6 @@ Additionally, this upgrade introduces the following minor changes: | `BYTES_PER_LOGS_BLOOM` | `uint64(2**8)` (= 256) | | `MAX_EXTRA_DATA_BYTES` | `2**5` (= 32) | -## Preset - ### Updated penalty values The Merge updates a few configuration values to move penalty parameters to their final, maximum security values. diff --git a/specs/merge/fork-choice.md b/specs/merge/fork-choice.md index e1e12ad7a..3f0c67200 100644 --- a/specs/merge/fork-choice.md +++ b/specs/merge/fork-choice.md @@ -8,6 +8,7 @@ - [Introduction](#introduction) +- [Custom types](#custom-types) - [Protocols](#protocols) - [`ExecutionEngine`](#executionengine) - [`notify_forkchoice_updated`](#notify_forkchoice_updated) @@ -29,6 +30,12 @@ This is the modification of the fork choice according to the executable beacon c *Note*: It introduces the process of transition from the last PoW block to the first PoS block. +## Custom types + +| Name | SSZ equivalent | Description | +| - | - | - | +| `PayloadId` | `Bytes8` | Identifier of a payload building process | + ## Protocols ### `ExecutionEngine` @@ -46,13 +53,13 @@ This function performs two actions *atomically*: 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`. +`head_block_hash` and returns an identifier of initiated process. ```python def notify_forkchoice_updated(self: ExecutionEngine, head_block_hash: Hash32, finalized_block_hash: Hash32, - payload_attributes: Optional[PayloadAttributes]) -> None: + payload_attributes: Optional[PayloadAttributes]) -> Optional[PayloadId]: ... ``` @@ -96,9 +103,6 @@ Used by fork-choice handler, `on_block`. ```python def is_valid_terminal_pow_block(block: PowBlock, parent: PowBlock) -> bool: - 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 return is_total_difficulty_reached and is_parent_total_difficulty_valid @@ -115,6 +119,12 @@ def validate_merge_block(block: BeaconBlock) -> None: 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 + assert block.body.execution_payload.parent_hash == TERMINAL_BLOCK_HASH + return + pow_block = get_pow_block(block.body.execution_payload.parent_hash) # Check if `pow_block` is available assert pow_block is not None @@ -123,10 +133,6 @@ def validate_merge_block(block: BeaconBlock) -> None: 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) - - # 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 ``` ## Updated fork-choice handlers diff --git a/specs/merge/validator.md b/specs/merge/validator.md index f47ede4f7..22fc3eb13 100644 --- a/specs/merge/validator.md +++ b/specs/merge/validator.md @@ -10,11 +10,9 @@ - [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) - [`get_payload`](#get_payload) @@ -38,12 +36,6 @@ All behaviors and definitions defined in this document, and documents it extends All terminology, constants, functions, and protocol mechanics defined in the updated Beacon Chain doc of [The Merge](./beacon-chain.md) are requisite for this document and used throughout. Please see related Beacon Chain doc before continuing and use them as a reference throughout. -## Custom types - -| Name | SSZ equivalent | Description | -| - | - | - | -| `PayloadId` | `Bytes8` | Identifier of a payload building process | - ## Helpers ### `get_pow_block_at_terminal_total_difficulty` @@ -52,8 +44,11 @@ Please see related Beacon Chain doc before continuing and use them as a referenc 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 + # If genesis block, no parent exists so reaching TTD alone qualifies as valid terminal block + if block_reached_ttd and block.parent_hash == Hash32(): + return block + parent = pow_chain[block.parent_hash] parent_reached_ttd = parent.total_difficulty >= TERMINAL_TOTAL_DIFFICULTY if block_reached_ttd and not parent_reached_ttd: return block @@ -75,24 +70,6 @@ def get_terminal_pow_block(pow_chain: Dict[Hash32, PowBlock]) -> Optional[PowBlo 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. @@ -147,8 +124,8 @@ def prepare_execution_payload(state: BeaconState, 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: + is_activation_epoch_reached = get_current_epoch(state.slot) >= TERMINAL_BLOCK_HASH_ACTIVATION_EPOCH + if is_terminal_block_hash_set and not is_activation_epoch_reached: # Terminal block hash is set but activation epoch is not yet reached, no prepare payload call is needed return None @@ -168,8 +145,7 @@ def prepare_execution_payload(state: BeaconState, 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) + return execution_engine.notify_forkchoice_updated(parent_hash, finalized_block_hash, payload_attributes) ``` 2. Set `block.body.execution_payload = get_execution_payload(payload_id, execution_engine)`, where: diff --git a/specs/phase0/validator.md b/specs/phase0/validator.md index 8de16e805..cc5b7aeb5 100644 --- a/specs/phase0/validator.md +++ b/specs/phase0/validator.md @@ -274,7 +274,7 @@ Specifically a validator should: ## Beacon chain responsibilities -A validator has two primary responsibilities to the beacon chain: [proposing blocks](#block-proposal) and [creating attestations](#attestations-1). Proposals happen infrequently, whereas attestations should be created once per epoch. +A validator has two primary responsibilities to the beacon chain: [proposing blocks](#block-proposal) and [creating attestations](#attesting). Proposals happen infrequently, whereas attestations should be created once per epoch. ### Block proposal diff --git a/tests/core/pyspec/eth2spec/VERSION.txt b/tests/core/pyspec/eth2spec/VERSION.txt index 9c1218c20..314c3d717 100644 --- a/tests/core/pyspec/eth2spec/VERSION.txt +++ b/tests/core/pyspec/eth2spec/VERSION.txt @@ -1 +1 @@ -1.1.3 \ No newline at end of file +1.1.5 \ No newline at end of file diff --git a/tests/core/pyspec/eth2spec/test/helpers/fork_choice.py b/tests/core/pyspec/eth2spec/test/helpers/fork_choice.py index 04843078f..7c6ac89a5 100644 --- a/tests/core/pyspec/eth2spec/test/helpers/fork_choice.py +++ b/tests/core/pyspec/eth2spec/test/helpers/fork_choice.py @@ -1,7 +1,5 @@ -from random import Random from eth_utils import encode_hex from eth2spec.test.exceptions import BlockNotFoundException -from eth2spec.utils.ssz.ssz_typing import uint256 from eth2spec.test.helpers.attestations import ( next_epoch_with_attestations, next_slots_with_attestations, @@ -247,15 +245,6 @@ def apply_next_slots_with_attestations(spec, return post_state, store, last_signed_block -def prepare_empty_pow_block(spec, rng=Random(3131)): - return spec.PowBlock( - block_hash=spec.Hash32(spec.hash(bytearray(rng.getrandbits(8) for _ in range(32)))), - parent_hash=spec.Hash32(spec.hash(bytearray(rng.getrandbits(8) for _ in range(32)))), - total_difficulty=uint256(0), - difficulty=uint256(0) - ) - - def get_pow_block_file_name(pow_block): return f"pow_block_{encode_hex(pow_block.block_hash)}" diff --git a/tests/core/pyspec/eth2spec/test/helpers/pow_block.py b/tests/core/pyspec/eth2spec/test/helpers/pow_block.py new file mode 100644 index 000000000..58989a420 --- /dev/null +++ b/tests/core/pyspec/eth2spec/test/helpers/pow_block.py @@ -0,0 +1,34 @@ +from random import Random +from eth2spec.utils.ssz.ssz_typing import uint256 + + +class PowChain: + blocks = [] + + def __init__(self, blocks): + self.blocks = blocks + + def __iter__(self): + return iter(self.blocks) + + def head(self, offset=0): + assert offset <= 0 + return self.blocks[offset - 1] + + +def prepare_random_pow_block(spec, rng=Random(3131)): + return spec.PowBlock( + block_hash=spec.Hash32(spec.hash(bytearray(rng.getrandbits(8) for _ in range(32)))), + parent_hash=spec.Hash32(spec.hash(bytearray(rng.getrandbits(8) for _ in range(32)))), + total_difficulty=uint256(0), + difficulty=uint256(0) + ) + + +def prepare_random_pow_chain(spec, length, rng=Random(3131)) -> PowChain: + assert length > 0 + chain = [prepare_random_pow_block(spec, rng)] + for i in range(1, length): + chain.append(prepare_random_pow_block(spec, rng)) + chain[i].parent_hash = chain[i - 1].block_hash + return PowChain(chain) diff --git a/tests/core/pyspec/eth2spec/test/merge/fork_choice/test_on_merge_block.py b/tests/core/pyspec/eth2spec/test/merge/fork_choice/test_on_merge_block.py index 830f20f23..e0703fdf7 100644 --- a/tests/core/pyspec/eth2spec/test/merge/fork_choice/test_on_merge_block.py +++ b/tests/core/pyspec/eth2spec/test/merge/fork_choice/test_on_merge_block.py @@ -13,9 +13,11 @@ from eth2spec.test.helpers.state import ( state_transition_and_sign_block, ) from eth2spec.test.helpers.fork_choice import ( - prepare_empty_pow_block, add_pow_block, ) +from eth2spec.test.helpers.pow_block import ( + prepare_random_pow_block, +) from eth2spec.test.helpers.execution_payload import ( build_state_with_incomplete_transition, ) @@ -58,9 +60,9 @@ def test_all_valid(spec, state): on_tick_and_append_step(spec, store, current_time, test_steps) assert store.time == current_time - pow_block_parent = prepare_empty_pow_block(spec) + pow_block_parent = prepare_random_pow_block(spec) pow_block_parent.total_difficulty = spec.config.TERMINAL_TOTAL_DIFFICULTY - uint256(1) - pow_block = prepare_empty_pow_block(spec) + pow_block = prepare_random_pow_block(spec) pow_block.parent_hash = pow_block_parent.block_hash pow_block.total_difficulty = spec.config.TERMINAL_TOTAL_DIFFICULTY pow_blocks = [pow_block, pow_block_parent] @@ -92,7 +94,7 @@ def test_block_lookup_failed(spec, state): on_tick_and_append_step(spec, store, current_time, test_steps) assert store.time == current_time - pow_block = prepare_empty_pow_block(spec) + pow_block = prepare_random_pow_block(spec) pow_block.total_difficulty = spec.config.TERMINAL_TOTAL_DIFFICULTY - uint256(1) pow_blocks = [pow_block] for pb in pow_blocks: @@ -122,9 +124,9 @@ def test_too_early_for_merge(spec, state): on_tick_and_append_step(spec, store, current_time, test_steps) assert store.time == current_time - pow_block_parent = prepare_empty_pow_block(spec) + pow_block_parent = prepare_random_pow_block(spec) pow_block_parent.total_difficulty = spec.config.TERMINAL_TOTAL_DIFFICULTY - uint256(2) - pow_block = prepare_empty_pow_block(spec) + pow_block = prepare_random_pow_block(spec) pow_block.parent_hash = pow_block_parent.block_hash pow_block.total_difficulty = spec.config.TERMINAL_TOTAL_DIFFICULTY - uint256(1) pow_blocks = [pow_block, pow_block_parent] @@ -154,9 +156,9 @@ def test_too_late_for_merge(spec, state): on_tick_and_append_step(spec, store, current_time, test_steps) assert store.time == current_time - pow_block_parent = prepare_empty_pow_block(spec) + pow_block_parent = prepare_random_pow_block(spec) pow_block_parent.total_difficulty = spec.config.TERMINAL_TOTAL_DIFFICULTY - pow_block = prepare_empty_pow_block(spec) + pow_block = prepare_random_pow_block(spec) pow_block.parent_hash = pow_block_parent.block_hash pow_block.total_difficulty = spec.config.TERMINAL_TOTAL_DIFFICULTY + uint256(1) pow_blocks = [pow_block, pow_block_parent] diff --git a/tests/core/pyspec/eth2spec/test/merge/unittests/test_is_valid_terminal_pow_block.py b/tests/core/pyspec/eth2spec/test/merge/unittests/test_is_valid_terminal_pow_block.py new file mode 100644 index 000000000..f20c15e35 --- /dev/null +++ b/tests/core/pyspec/eth2spec/test/merge/unittests/test_is_valid_terminal_pow_block.py @@ -0,0 +1,44 @@ +from eth2spec.utils.ssz.ssz_typing import uint256 +from eth2spec.test.helpers.pow_block import ( + prepare_random_pow_block, +) +from eth2spec.test.context import ( + spec_state_test, + with_merge_and_later, +) + + +@with_merge_and_later +@spec_state_test +def test_is_valid_terminal_pow_block_success_valid(spec, state): + parent_block = prepare_random_pow_block(spec) + parent_block.total_difficulty = spec.config.TERMINAL_TOTAL_DIFFICULTY - uint256(1) + block = prepare_random_pow_block(spec) + block.parent_hash = parent_block.block_hash + block.total_difficulty = spec.config.TERMINAL_TOTAL_DIFFICULTY + + assert spec.is_valid_terminal_pow_block(block, parent_block) + + +@with_merge_and_later +@spec_state_test +def test_is_valid_terminal_pow_block_fail_before_terminal(spec, state): + parent_block = prepare_random_pow_block(spec) + parent_block.total_difficulty = spec.config.TERMINAL_TOTAL_DIFFICULTY - uint256(2) + block = prepare_random_pow_block(spec) + block.parent_hash = parent_block.block_hash + block.total_difficulty = spec.config.TERMINAL_TOTAL_DIFFICULTY - uint256(1) + + assert not spec.is_valid_terminal_pow_block(block, parent_block) + + +@with_merge_and_later +@spec_state_test +def test_is_valid_terminal_pow_block_fail_just_after_terminal(spec, state): + parent_block = prepare_random_pow_block(spec) + parent_block.total_difficulty = spec.config.TERMINAL_TOTAL_DIFFICULTY + block = prepare_random_pow_block(spec) + block.parent_hash = parent_block.block_hash + block.total_difficulty = spec.config.TERMINAL_TOTAL_DIFFICULTY + uint256(1) + + assert not spec.is_valid_terminal_pow_block(block, parent_block) diff --git a/tests/core/pyspec/eth2spec/test/merge/unittests/test_terminal_validity.py b/tests/core/pyspec/eth2spec/test/merge/unittests/test_terminal_validity.py deleted file mode 100644 index cfd5ea091..000000000 --- a/tests/core/pyspec/eth2spec/test/merge/unittests/test_terminal_validity.py +++ /dev/null @@ -1,143 +0,0 @@ -from eth2spec.test.exceptions import BlockNotFoundException -from eth2spec.utils.ssz.ssz_typing import uint256 -from eth2spec.test.helpers.fork_choice import ( - prepare_empty_pow_block, -) -from eth2spec.test.context import spec_state_test, with_merge_and_later - - -# Copy of conditional merge part of `on_block(store: Store, signed_block: SignedBeaconBlock)` handler -def validate_transition_execution_payload(spec, execution_payload): - pow_block = spec.get_pow_block(execution_payload.parent_hash) - pow_parent = spec.get_pow_block(pow_block.parent_hash) - assert spec.is_valid_terminal_pow_block(pow_block, pow_parent) - - -def run_validate_transition_execution_payload(spec, block, parent_block, payload, - valid=True, block_lookup_success=True): - """ - Run ``validate_transition_execution_payload`` - If ``valid == False``, run expecting ``AssertionError`` - If ``block_lookup_success == False``, run expecting ``BlockNotFoundException`` - """ - - def get_pow_block(hash: spec.Bytes32) -> spec.PowBlock: - if hash == block.block_hash: - return block - elif hash == parent_block.block_hash: - return parent_block - else: - raise BlockNotFoundException() - save_pow_block = spec.get_pow_block - - # Guido authorized everyone to do this - spec.get_pow_block = get_pow_block - exception_caught = False - block_not_found_exception_caught = False - try: - validate_transition_execution_payload(spec, payload) - except BlockNotFoundException: - block_not_found_exception_caught = True - except AssertionError: - exception_caught = True - except Exception as e: - spec.get_pow_block = save_pow_block - raise e - spec.get_pow_block = save_pow_block - - if block_lookup_success: - assert not block_not_found_exception_caught - else: - assert block_not_found_exception_caught - if valid: - assert not exception_caught - else: - assert exception_caught - - -@with_merge_and_later -@spec_state_test -def test_valid_terminal_pow_block_success_valid(spec, state): - parent_block = prepare_empty_pow_block(spec) - parent_block.total_difficulty = spec.config.TERMINAL_TOTAL_DIFFICULTY - uint256(1) - block = prepare_empty_pow_block(spec) - block.parent_hash = parent_block.block_hash - block.total_difficulty = spec.config.TERMINAL_TOTAL_DIFFICULTY - - assert spec.is_valid_terminal_pow_block(block, parent_block) - - -@with_merge_and_later -@spec_state_test -def test_valid_terminal_pow_block_fail_before_terminal(spec, state): - parent_block = prepare_empty_pow_block(spec) - parent_block.total_difficulty = spec.config.TERMINAL_TOTAL_DIFFICULTY - uint256(2) - block = prepare_empty_pow_block(spec) - block.parent_hash = parent_block.block_hash - block.total_difficulty = spec.config.TERMINAL_TOTAL_DIFFICULTY - uint256(1) - - assert not spec.is_valid_terminal_pow_block(block, parent_block) - - -@with_merge_and_later -@spec_state_test -def test_valid_terminal_pow_block_fail_just_after_terminal(spec, state): - parent_block = prepare_empty_pow_block(spec) - parent_block.total_difficulty = spec.config.TERMINAL_TOTAL_DIFFICULTY - block = prepare_empty_pow_block(spec) - block.parent_hash = parent_block.block_hash - block.total_difficulty = spec.config.TERMINAL_TOTAL_DIFFICULTY + uint256(1) - - assert not spec.is_valid_terminal_pow_block(block, parent_block) - - -@with_merge_and_later -@spec_state_test -def test_validate_transition_execution_payload_success(spec, state): - parent_block = prepare_empty_pow_block(spec) - parent_block.total_difficulty = spec.config.TERMINAL_TOTAL_DIFFICULTY - uint256(1) - block = prepare_empty_pow_block(spec) - block.parent_hash = parent_block.block_hash - block.total_difficulty = spec.config.TERMINAL_TOTAL_DIFFICULTY - payload = spec.ExecutionPayload() - payload.parent_hash = block.block_hash - run_validate_transition_execution_payload(spec, block, parent_block, payload) - - -@with_merge_and_later -@spec_state_test -def test_validate_transition_execution_payload_fail_block_lookup(spec, state): - parent_block = prepare_empty_pow_block(spec) - parent_block.total_difficulty = spec.config.TERMINAL_TOTAL_DIFFICULTY - uint256(1) - block = prepare_empty_pow_block(spec) - block.parent_hash = parent_block.block_hash - block.total_difficulty = spec.config.TERMINAL_TOTAL_DIFFICULTY - payload = spec.ExecutionPayload() - run_validate_transition_execution_payload(spec, block, parent_block, payload, - block_lookup_success=False) - - -@with_merge_and_later -@spec_state_test -def test_validate_transition_execution_payload_fail_parent_block_lookup(spec, state): - parent_block = prepare_empty_pow_block(spec) - parent_block.total_difficulty = spec.config.TERMINAL_TOTAL_DIFFICULTY - uint256(1) - block = prepare_empty_pow_block(spec) - block.total_difficulty = spec.config.TERMINAL_TOTAL_DIFFICULTY - payload = spec.ExecutionPayload() - payload.parent_hash = block.block_hash - run_validate_transition_execution_payload(spec, block, parent_block, payload, - block_lookup_success=False) - - -@with_merge_and_later -@spec_state_test -def test_validate_transition_execution_payload_fail_after_terminal(spec, state): - parent_block = prepare_empty_pow_block(spec) - parent_block.total_difficulty = spec.config.TERMINAL_TOTAL_DIFFICULTY - block = prepare_empty_pow_block(spec) - block.parent_hash = parent_block.block_hash - block.total_difficulty = spec.config.TERMINAL_TOTAL_DIFFICULTY + 1 - payload = spec.ExecutionPayload() - payload.parent_hash = block.block_hash - run_validate_transition_execution_payload(spec, block, parent_block, payload, valid=False) diff --git a/tests/core/pyspec/eth2spec/test/merge/unittests/test_validate_merge_block.py b/tests/core/pyspec/eth2spec/test/merge/unittests/test_validate_merge_block.py new file mode 100644 index 000000000..cf4c2234a --- /dev/null +++ b/tests/core/pyspec/eth2spec/test/merge/unittests/test_validate_merge_block.py @@ -0,0 +1,153 @@ +from typing import Optional +from eth2spec.utils.ssz.ssz_typing import uint256, Bytes32 +from eth2spec.test.helpers.block import ( + build_empty_block_for_next_slot, +) +from eth2spec.test.helpers.pow_block import ( + prepare_random_pow_chain, +) +from eth2spec.test.context import ( + spec_state_test, + with_merge_and_later, + spec_configured_state_test +) + + +TERMINAL_BLOCK_HASH_CONFIG_VAR = '0x0000000000000000000000000000000000000000000000000000000000000001' +TERMINAL_BLOCK_HASH = Bytes32(TERMINAL_BLOCK_HASH_CONFIG_VAR) + + +def run_validate_merge_block(spec, pow_chain, beacon_block, valid=True): + """ + Run ``validate_merge_block`` + If ``valid == False``, run expecting ``AssertionError`` + """ + + def get_pow_block(hash: spec.Bytes32) -> Optional[spec.PowBlock]: + for block in pow_chain: + if block.block_hash == hash: + return block + return None + + get_pow_block_backup = spec.get_pow_block + + # Guido authorized everyone to do this + spec.get_pow_block = get_pow_block + assertion_error_caught = False + try: + spec.validate_merge_block(beacon_block) + except AssertionError: + assertion_error_caught = True + except Exception as e: + spec.get_pow_block = get_pow_block_backup + raise e + spec.get_pow_block = get_pow_block_backup + + if valid: + assert not assertion_error_caught + else: + assert assertion_error_caught + + +@with_merge_and_later +@spec_state_test +def test_validate_merge_block_success(spec, state): + pow_chain = prepare_random_pow_chain(spec, 2) + pow_chain.head(-1).total_difficulty = spec.config.TERMINAL_TOTAL_DIFFICULTY - uint256(1) + pow_chain.head().total_difficulty = spec.config.TERMINAL_TOTAL_DIFFICULTY + block = build_empty_block_for_next_slot(spec, state) + block.body.execution_payload.parent_hash = pow_chain.head().block_hash + run_validate_merge_block(spec, pow_chain, block) + + +@with_merge_and_later +@spec_state_test +def test_validate_merge_block_fail_block_lookup(spec, state): + pow_chain = prepare_random_pow_chain(spec, 2) + pow_chain.head(-1).total_difficulty = spec.config.TERMINAL_TOTAL_DIFFICULTY - uint256(1) + pow_chain.head().total_difficulty = spec.config.TERMINAL_TOTAL_DIFFICULTY + block = build_empty_block_for_next_slot(spec, state) + run_validate_merge_block(spec, pow_chain, block, valid=False) + + +@with_merge_and_later +@spec_state_test +def test_validate_merge_block_fail_parent_block_lookup(spec, state): + pow_chain = prepare_random_pow_chain(spec, 1) + pow_chain.head().total_difficulty = spec.config.TERMINAL_TOTAL_DIFFICULTY + block = build_empty_block_for_next_slot(spec, state) + block.body.execution_payload.parent_hash = pow_chain.head().block_hash + run_validate_merge_block(spec, pow_chain, block, valid=False) + + +@with_merge_and_later +@spec_state_test +def test_validate_merge_block_fail_after_terminal(spec, state): + pow_chain = prepare_random_pow_chain(spec, 2) + pow_chain.head(-1).total_difficulty = spec.config.TERMINAL_TOTAL_DIFFICULTY + pow_chain.head().total_difficulty = spec.config.TERMINAL_TOTAL_DIFFICULTY + uint256(1) + block = build_empty_block_for_next_slot(spec, state) + block.body.execution_payload.parent_hash = pow_chain.head().block_hash + run_validate_merge_block(spec, pow_chain, block, valid=False) + + +@with_merge_and_later +@spec_configured_state_test({ + 'TERMINAL_BLOCK_HASH': TERMINAL_BLOCK_HASH_CONFIG_VAR, + 'TERMINAL_BLOCK_HASH_ACTIVATION_EPOCH': '0' +}) +def test_validate_merge_block_tbh_override_success(spec, state): + pow_chain = prepare_random_pow_chain(spec, 2) + # should fail if TTD check is reached + pow_chain.head(-1).total_difficulty = spec.config.TERMINAL_TOTAL_DIFFICULTY - uint256(2) + pow_chain.head().total_difficulty = spec.config.TERMINAL_TOTAL_DIFFICULTY - uint256(1) + pow_chain.head().block_hash = TERMINAL_BLOCK_HASH + block = build_empty_block_for_next_slot(spec, state) + block.body.execution_payload.parent_hash = pow_chain.head().block_hash + run_validate_merge_block(spec, pow_chain, block) + + +@with_merge_and_later +@spec_configured_state_test({ + 'TERMINAL_BLOCK_HASH': TERMINAL_BLOCK_HASH_CONFIG_VAR, + 'TERMINAL_BLOCK_HASH_ACTIVATION_EPOCH': '0' +}) +def test_validate_merge_block_fail_parent_hash_is_not_tbh(spec, state): + pow_chain = prepare_random_pow_chain(spec, 2) + # shouldn't fail if TTD check is reached + pow_chain.head(-1).total_difficulty = spec.config.TERMINAL_TOTAL_DIFFICULTY - uint256(1) + pow_chain.head().total_difficulty = spec.config.TERMINAL_TOTAL_DIFFICULTY + block = build_empty_block_for_next_slot(spec, state) + block.body.execution_payload.parent_hash = pow_chain.head().block_hash + run_validate_merge_block(spec, pow_chain, block, valid=False) + + +@with_merge_and_later +@spec_configured_state_test({ + 'TERMINAL_BLOCK_HASH': TERMINAL_BLOCK_HASH_CONFIG_VAR, + 'TERMINAL_BLOCK_HASH_ACTIVATION_EPOCH': '1' +}) +def test_validate_merge_block_terminal_block_hash_fail_activation_not_reached(spec, state): + pow_chain = prepare_random_pow_chain(spec, 2) + # shouldn't fail if TTD check is reached + pow_chain.head(-1).total_difficulty = spec.config.TERMINAL_TOTAL_DIFFICULTY - uint256(1) + pow_chain.head().total_difficulty = spec.config.TERMINAL_TOTAL_DIFFICULTY + pow_chain.head().block_hash = TERMINAL_BLOCK_HASH + block = build_empty_block_for_next_slot(spec, state) + block.body.execution_payload.parent_hash = pow_chain.head().block_hash + run_validate_merge_block(spec, pow_chain, block, valid=False) + + +@with_merge_and_later +@spec_configured_state_test({ + 'TERMINAL_BLOCK_HASH': TERMINAL_BLOCK_HASH_CONFIG_VAR, + 'TERMINAL_BLOCK_HASH_ACTIVATION_EPOCH': '1' +}) +def test_validate_merge_block_fail_activation_not_reached_parent_hash_is_not_tbh(spec, state): + pow_chain = prepare_random_pow_chain(spec, 2) + # shouldn't fail if TTD check is reached + pow_chain.head(-1).total_difficulty = spec.config.TERMINAL_TOTAL_DIFFICULTY - uint256(1) + pow_chain.head().total_difficulty = spec.config.TERMINAL_TOTAL_DIFFICULTY + block = build_empty_block_for_next_slot(spec, state) + block.body.execution_payload.parent_hash = pow_chain.head().block_hash + run_validate_merge_block(spec, pow_chain, block, valid=False) diff --git a/tests/core/pyspec/eth2spec/test/phase0/fork_choice/test_on_block.py b/tests/core/pyspec/eth2spec/test/phase0/fork_choice/test_on_block.py index c00a91b28..79bf353b1 100644 --- a/tests/core/pyspec/eth2spec/test/phase0/fork_choice/test_on_block.py +++ b/tests/core/pyspec/eth2spec/test/phase0/fork_choice/test_on_block.py @@ -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