Merge pull request #2713 from mkalinin/validate-merge-block-tests

Refine validate_merge_block unit tests
This commit is contained in:
Danny Ryan
2021-11-06 08:10:26 -06:00
committed by GitHub
6 changed files with 241 additions and 162 deletions

View File

@@ -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)}"

View File

@@ -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)

View File

@@ -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]

View File

@@ -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)

View File

@@ -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)

View File

@@ -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)