-
Notifications
You must be signed in to change notification settings - Fork 1k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Deneb fork choice tests - take 2 #3463
Changes from all commits
2210cea
d2d351f
e79caff
85b0ae8
32056b2
39134d5
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,182 @@ | ||
from random import Random | ||
|
||
from eth2spec.test.context import ( | ||
spec_state_test, | ||
with_deneb_and_later, | ||
) | ||
|
||
from eth2spec.test.helpers.block import ( | ||
build_empty_block_for_next_slot, | ||
) | ||
from eth2spec.test.helpers.execution_payload import ( | ||
compute_el_block_hash, | ||
) | ||
from eth2spec.test.helpers.fork_choice import ( | ||
BlobData, | ||
get_genesis_forkchoice_store_and_block, | ||
on_tick_and_append_step, | ||
tick_and_add_block_with_data, | ||
) | ||
from eth2spec.test.helpers.state import ( | ||
state_transition_and_sign_block, | ||
) | ||
from eth2spec.test.helpers.sharding import ( | ||
get_sample_opaque_tx, | ||
) | ||
|
||
|
||
def get_block_with_blob(spec, state, rng=None): | ||
block = build_empty_block_for_next_slot(spec, state) | ||
opaque_tx, blobs, blob_kzg_commitments, blob_kzg_proofs = get_sample_opaque_tx(spec, blob_count=1, rng=rng) | ||
block.body.execution_payload.transactions = [opaque_tx] | ||
block.body.execution_payload.block_hash = compute_el_block_hash(spec, block.body.execution_payload) | ||
block.body.blob_kzg_commitments = blob_kzg_commitments | ||
return block, blobs, blob_kzg_proofs | ||
|
||
|
||
@with_deneb_and_later | ||
@spec_state_test | ||
def test_simple_blob_data(spec, state): | ||
rng = Random(1234) | ||
|
||
test_steps = [] | ||
# Initialization | ||
store, anchor_block = get_genesis_forkchoice_store_and_block(spec, state) | ||
yield 'anchor_state', state | ||
yield 'anchor_block', anchor_block | ||
current_time = state.slot * spec.config.SECONDS_PER_SLOT + store.genesis_time | ||
on_tick_and_append_step(spec, store, current_time, test_steps) | ||
assert store.time == current_time | ||
|
||
# On receiving a block of `GENESIS_SLOT + 1` slot | ||
block, blobs, blob_kzg_proofs = get_block_with_blob(spec, state, rng=rng) | ||
signed_block = state_transition_and_sign_block(spec, state, block) | ||
blob_data = BlobData(blobs, blob_kzg_proofs) | ||
|
||
yield from tick_and_add_block_with_data(spec, store, signed_block, test_steps, blob_data) | ||
|
||
assert spec.get_head(store) == signed_block.message.hash_tree_root() | ||
|
||
# On receiving a block of next epoch | ||
store.time = current_time + spec.config.SECONDS_PER_SLOT * spec.SLOTS_PER_EPOCH | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ahh nice catch! I think this line should be removed because |
||
block, blobs, blob_kzg_proofs = get_block_with_blob(spec, state, rng=rng) | ||
signed_block = state_transition_and_sign_block(spec, state, block) | ||
blob_data = BlobData(blobs, blob_kzg_proofs) | ||
|
||
yield from tick_and_add_block_with_data(spec, store, signed_block, test_steps, blob_data) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. there is no |
||
|
||
assert spec.get_head(store) == signed_block.message.hash_tree_root() | ||
|
||
yield 'steps', test_steps | ||
|
||
|
||
@with_deneb_and_later | ||
@spec_state_test | ||
def test_invalid_incorrect_proof(spec, state): | ||
rng = Random(1234) | ||
|
||
test_steps = [] | ||
# Initialization | ||
store, anchor_block = get_genesis_forkchoice_store_and_block(spec, state) | ||
yield 'anchor_state', state | ||
yield 'anchor_block', anchor_block | ||
current_time = state.slot * spec.config.SECONDS_PER_SLOT + store.genesis_time | ||
on_tick_and_append_step(spec, store, current_time, test_steps) | ||
assert store.time == current_time | ||
|
||
# On receiving a block of `GENESIS_SLOT + 1` slot | ||
block, blobs, _ = get_block_with_blob(spec, state, rng=rng) | ||
signed_block = state_transition_and_sign_block(spec, state, block) | ||
# Insert incorrect proof | ||
blob_kzg_proofs = [b'\xc0' + b'\x00' * 47] | ||
blob_data = BlobData(blobs, blob_kzg_proofs) | ||
|
||
yield from tick_and_add_block_with_data(spec, store, signed_block, test_steps, blob_data, valid=False) | ||
|
||
assert spec.get_head(store) != signed_block.message.hash_tree_root() | ||
|
||
yield 'steps', test_steps | ||
|
||
|
||
@with_deneb_and_later | ||
@spec_state_test | ||
def test_invalid_data_unavailable(spec, state): | ||
rng = Random(1234) | ||
|
||
test_steps = [] | ||
# Initialization | ||
store, anchor_block = get_genesis_forkchoice_store_and_block(spec, state) | ||
yield 'anchor_state', state | ||
yield 'anchor_block', anchor_block | ||
current_time = state.slot * spec.config.SECONDS_PER_SLOT + store.genesis_time | ||
on_tick_and_append_step(spec, store, current_time, test_steps) | ||
assert store.time == current_time | ||
|
||
# On receiving a block of `GENESIS_SLOT + 1` slot | ||
block, _, _ = get_block_with_blob(spec, state, rng=rng) | ||
signed_block = state_transition_and_sign_block(spec, state, block) | ||
|
||
# data unavailable | ||
blob_data = BlobData([], []) | ||
|
||
yield from tick_and_add_block_with_data(spec, store, signed_block, test_steps, blob_data, valid=False) | ||
|
||
assert spec.get_head(store) != signed_block.message.hash_tree_root() | ||
|
||
yield 'steps', test_steps | ||
|
||
|
||
@with_deneb_and_later | ||
@spec_state_test | ||
def test_invalid_wrong_proofs_length(spec, state): | ||
rng = Random(1234) | ||
|
||
test_steps = [] | ||
# Initialization | ||
store, anchor_block = get_genesis_forkchoice_store_and_block(spec, state) | ||
yield 'anchor_state', state | ||
yield 'anchor_block', anchor_block | ||
current_time = state.slot * spec.config.SECONDS_PER_SLOT + store.genesis_time | ||
on_tick_and_append_step(spec, store, current_time, test_steps) | ||
assert store.time == current_time | ||
|
||
# On receiving a block of `GENESIS_SLOT + 1` slot | ||
block, blobs, _ = get_block_with_blob(spec, state, rng=rng) | ||
signed_block = state_transition_and_sign_block(spec, state, block) | ||
|
||
# unavailable proofs | ||
blob_data = BlobData(blobs, []) | ||
|
||
yield from tick_and_add_block_with_data(spec, store, signed_block, test_steps, blob_data, valid=False) | ||
|
||
assert spec.get_head(store) != signed_block.message.hash_tree_root() | ||
|
||
yield 'steps', test_steps | ||
|
||
|
||
@with_deneb_and_later | ||
@spec_state_test | ||
def test_invalid_wrong_blobs_length(spec, state): | ||
rng = Random(1234) | ||
|
||
test_steps = [] | ||
# Initialization | ||
store, anchor_block = get_genesis_forkchoice_store_and_block(spec, state) | ||
yield 'anchor_state', state | ||
yield 'anchor_block', anchor_block | ||
current_time = state.slot * spec.config.SECONDS_PER_SLOT + store.genesis_time | ||
on_tick_and_append_step(spec, store, current_time, test_steps) | ||
assert store.time == current_time | ||
|
||
# On receiving a block of `GENESIS_SLOT + 1` slot | ||
block, _, blob_kzg_proofs = get_block_with_blob(spec, state, rng=rng) | ||
signed_block = state_transition_and_sign_block(spec, state, block) | ||
|
||
# unavailable blobs | ||
blob_data = BlobData([], blob_kzg_proofs) | ||
|
||
yield from tick_and_add_block_with_data(spec, store, signed_block, test_steps, blob_data, valid=False) | ||
|
||
assert spec.get_head(store) != signed_block.message.hash_tree_root() | ||
|
||
yield 'steps', test_steps |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,3 +1,5 @@ | ||
from typing import NamedTuple, Sequence, Any | ||
|
||
from eth_utils import encode_hex | ||
from eth2spec.test.exceptions import BlockNotFoundException | ||
from eth2spec.test.helpers.attestations import ( | ||
|
@@ -7,6 +9,40 @@ | |
) | ||
|
||
|
||
class BlobData(NamedTuple): | ||
""" | ||
The return values of ``retrieve_blobs_and_proofs`` helper. | ||
""" | ||
blobs: Sequence[Any] | ||
proofs: Sequence[bytes] | ||
|
||
|
||
def with_blob_data(spec, blob_data, func): | ||
""" | ||
This helper runs the given ``func`` with monkeypatched ``retrieve_blobs_and_proofs`` | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. fancy. |
||
that returns ``blob_data.blobs, blob_data.proofs``. | ||
""" | ||
def retrieve_blobs_and_proofs(beacon_block_root): | ||
return blob_data.blobs, blob_data.proofs | ||
|
||
retrieve_blobs_and_proofs_backup = spec.retrieve_blobs_and_proofs | ||
spec.retrieve_blobs_and_proofs = retrieve_blobs_and_proofs | ||
|
||
class AtomicBoolean(): | ||
value = False | ||
is_called = AtomicBoolean() | ||
|
||
def wrap(flag: AtomicBoolean): | ||
yield from func() | ||
flag.value = True | ||
|
||
try: | ||
yield from wrap(is_called) | ||
finally: | ||
spec.retrieve_blobs_and_proofs = retrieve_blobs_and_proofs_backup | ||
assert is_called.value | ||
|
||
|
||
def get_anchor_root(spec, state): | ||
anchor_block_header = state.latest_block_header.copy() | ||
if anchor_block_header.state_root == spec.Bytes32(): | ||
|
@@ -15,7 +51,8 @@ def get_anchor_root(spec, state): | |
|
||
|
||
def tick_and_add_block(spec, store, signed_block, test_steps, valid=True, | ||
merge_block=False, block_not_found=False, is_optimistic=False): | ||
merge_block=False, block_not_found=False, is_optimistic=False, | ||
blob_data=None): | ||
pre_state = store.block_states[signed_block.message.parent_root] | ||
if merge_block: | ||
assert spec.is_merge_transition_block(pre_state, signed_block.message.body) | ||
|
@@ -30,11 +67,19 @@ def tick_and_add_block(spec, store, signed_block, test_steps, valid=True, | |
valid=valid, | ||
block_not_found=block_not_found, | ||
is_optimistic=is_optimistic, | ||
blob_data=blob_data, | ||
) | ||
|
||
return post_state | ||
|
||
|
||
def tick_and_add_block_with_data(spec, store, signed_block, test_steps, blob_data, valid=True): | ||
def run_func(): | ||
yield from tick_and_add_block(spec, store, signed_block, test_steps, blob_data=blob_data, valid=valid) | ||
|
||
yield from with_blob_data(spec, blob_data, run_func) | ||
|
||
|
||
def add_attestation(spec, store, attestation, test_steps, is_from_block=False): | ||
spec.on_attestation(store, attestation, is_from_block=is_from_block) | ||
yield get_attestation_file_name(attestation), attestation | ||
|
@@ -94,6 +139,13 @@ def get_attester_slashing_file_name(attester_slashing): | |
return f"attester_slashing_{encode_hex(attester_slashing.hash_tree_root())}" | ||
|
||
|
||
def get_blobs_file_name(blobs=None, blobs_root=None): | ||
if blobs: | ||
return f"blobs_{encode_hex(blobs.hash_tree_root())}" | ||
else: | ||
return f"blobs_{encode_hex(blobs_root)}" | ||
|
||
|
||
def on_tick_and_append_step(spec, store, time, test_steps): | ||
spec.on_tick(store, time) | ||
test_steps.append({'tick': int(time)}) | ||
|
@@ -119,35 +171,52 @@ def add_block(spec, | |
test_steps, | ||
valid=True, | ||
block_not_found=False, | ||
is_optimistic=False): | ||
is_optimistic=False, | ||
blob_data=None): | ||
""" | ||
Run on_block and on_attestation | ||
""" | ||
yield get_block_file_name(signed_block), signed_block | ||
|
||
if not valid: | ||
if is_optimistic: | ||
run_on_block(spec, store, signed_block, valid=True) | ||
# Check blob_data | ||
if blob_data is not None: | ||
blobs = spec.List[spec.Blob, spec.MAX_BLOBS_PER_BLOCK](blob_data.blobs) | ||
blobs_root = blobs.hash_tree_root() | ||
yield get_blobs_file_name(blobs_root=blobs_root), blobs | ||
|
||
is_blob_data_test = blob_data is not None | ||
|
||
def _append_step(is_blob_data_test, valid=True): | ||
if is_blob_data_test: | ||
test_steps.append({ | ||
'block': get_block_file_name(signed_block), | ||
'valid': False, | ||
'blobs': get_blobs_file_name(blobs_root=blobs_root), | ||
'proofs': [encode_hex(proof) for proof in blob_data.proofs], | ||
'valid': valid, | ||
}) | ||
else: | ||
test_steps.append({ | ||
'block': get_block_file_name(signed_block), | ||
'valid': valid, | ||
}) | ||
|
||
if not valid: | ||
if is_optimistic: | ||
run_on_block(spec, store, signed_block, valid=True) | ||
_append_step(is_blob_data_test, valid=False) | ||
else: | ||
try: | ||
run_on_block(spec, store, signed_block, valid=True) | ||
except (AssertionError, BlockNotFoundException) as e: | ||
if isinstance(e, BlockNotFoundException) and not block_not_found: | ||
assert False | ||
test_steps.append({ | ||
'block': get_block_file_name(signed_block), | ||
'valid': False, | ||
}) | ||
_append_step(is_blob_data_test, valid=False) | ||
return | ||
else: | ||
assert False | ||
else: | ||
run_on_block(spec, store, signed_block, valid=True) | ||
test_steps.append({'block': get_block_file_name(signed_block)}) | ||
_append_step(is_blob_data_test) | ||
|
||
# An on_block step implies receiving block's attestations | ||
for attestation in signed_block.message.body.attestations: | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
shouldn't there be a
on_tick_and_append_step
to run tick processing in fork choice?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Without the
on_tick_and_append_step
, the followupchecks
insteps.yaml
will fail as the time doesn't match with the test runner fork choice. test runner fork choice needs to be told manually to advance to the target time, viasteps.yaml