From 1587a3ff7aed15a2e532a19010c60f6d53238873 Mon Sep 17 00:00:00 2001 From: brech1 <113475764+brech1@users.noreply.github.com> Date: Thu, 7 May 2026 22:53:35 -0300 Subject: [PATCH 1/6] test(gloas): add on_payload_attestation_message tests --- .../test_on_payload_attestation_message.py | 569 ++++++++++++++++++ .../test/helpers/fork_choice.py | 33 +- tests/formats/fork_choice/README.md | 25 +- .../fork_choice/instantiators/test_case.py | 12 +- .../fork_choice/runner/test_run.py | 10 +- 5 files changed, 628 insertions(+), 21 deletions(-) create mode 100644 tests/core/pyspec/eth_consensus_specs/test/gloas/fork_choice/test_on_payload_attestation_message.py diff --git a/tests/core/pyspec/eth_consensus_specs/test/gloas/fork_choice/test_on_payload_attestation_message.py b/tests/core/pyspec/eth_consensus_specs/test/gloas/fork_choice/test_on_payload_attestation_message.py new file mode 100644 index 00000000000..11044af06a8 --- /dev/null +++ b/tests/core/pyspec/eth_consensus_specs/test/gloas/fork_choice/test_on_payload_attestation_message.py @@ -0,0 +1,569 @@ +from eth_consensus_specs.test.context import ( + spec_state_test, + with_gloas_and_later, +) +from eth_consensus_specs.test.helpers.block import ( + build_empty_block_for_next_slot, +) +from eth_consensus_specs.test.helpers.fork_choice import ( + add_payload_attestation_message, + add_payload_vote_checks, + get_genesis_forkchoice_store_and_block, + on_tick_and_append_step, + tick_and_add_block, +) +from eth_consensus_specs.test.helpers.keys import privkeys +from eth_consensus_specs.test.helpers.state import ( + state_transition_and_sign_block, +) + + +def _build_signed_payload_attestation_message( + spec, state, block_root, validator_index, payload_present=True, blob_data_available=True +): + """ + Build a signed PayloadAttestationMessage for a given block root and validator. + """ + data = spec.PayloadAttestationData( + beacon_block_root=block_root, + slot=state.slot, + payload_present=payload_present, + blob_data_available=blob_data_available, + ) + + domain = spec.get_domain(state, spec.DOMAIN_PTC_ATTESTER, spec.compute_epoch_at_slot(data.slot)) + signing_root = spec.compute_signing_root(data, domain) + signature = spec.bls.Sign(privkeys[validator_index], signing_root) + + return spec.PayloadAttestationMessage( + validator_index=validator_index, + data=data, + signature=signature, + ) + + +def _setup_store(spec, state, test_steps): + 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.SLOT_DURATION_MS // 1000) + store.genesis_time + on_tick_and_append_step(spec, store, current_time, test_steps) + return store + + +def _setup_ptc_block(spec, store, state, test_steps): + block = build_empty_block_for_next_slot(spec, state) + signed_block = state_transition_and_sign_block(spec, state, block) + yield from tick_and_add_block(spec, store, signed_block, test_steps) + + block_root = signed_block.message.hash_tree_root() + block_state = store.block_states[block_root] + ptc = spec.get_ptc(block_state, block_state.slot) + assert len(ptc) > 0 + + return block_root, block_state, ptc + + +def _move_store_to_slot(spec, store, slot, test_steps): + slot_time = store.genesis_time + slot * (spec.config.SLOT_DURATION_MS // 1000) + if store.time < slot_time: + on_tick_and_append_step(spec, store, slot_time, test_steps) + + +def _get_non_ptc_member(state, ptc): + ptc_set = set(ptc) + for validator_index in range(len(state.validators)): + if validator_index not in ptc_set: + return validator_index + raise AssertionError("expected a validator outside the PTC") + + +def _setup_test(spec, state): + test_steps = [] + store = yield from _setup_store(spec, state, test_steps) + block_root, block_state, ptc = yield from _setup_ptc_block(spec, store, state, test_steps) + _move_store_to_slot(spec, store, block_state.slot, test_steps) + return store, block_root, block_state, ptc, test_steps + + +@with_gloas_and_later +@spec_state_test +def test_on_payload_attestation_unknown_block_root(spec, state): + """ + Test that messages for an unknown beacon_block_root are rejected. + """ + store, block_root, block_state, ptc, test_steps = yield from _setup_test(spec, state) + + # Sign the message over an unknown block root + unknown_root = spec.Root(b"\xff" * 32) + ptc_message = _build_signed_payload_attestation_message( + spec, + block_state, + unknown_root, + ptc[0], + payload_present=True, + ) + + yield from add_payload_attestation_message( + spec, + store, + ptc_message, + test_steps, + valid=False, + ) + + # Vote arrays for the known block must remain at their default values + assert all(v is None for v in store.payload_timeliness_vote[block_root]) + assert all(v is None for v in store.payload_data_availability_vote[block_root]) + add_payload_vote_checks(spec, store, block_root, test_steps) + + yield "steps", test_steps + + +@with_gloas_and_later +@spec_state_test +def test_on_payload_attestation_future_slot_message_is_ignored(spec, state): + """ + Test that a message whose slot is ahead of the block's slot is dropped without error. + """ + store, block_root, block_state, ptc, test_steps = yield from _setup_test(spec, state) + + # Build message and set the slot ahead of the block's slot + ptc_message = _build_signed_payload_attestation_message( + spec, + block_state, + block_root, + ptc[0], + payload_present=True, + ) + ptc_message.data.slot = spec.Slot(block_state.slot + 1) + + # Spec returns silently on the slot mismatch + yield from add_payload_attestation_message(spec, store, ptc_message, test_steps) + + # Vote arrays must remain at their default values + assert all(v is None for v in store.payload_timeliness_vote[block_root]) + assert all(v is None for v in store.payload_data_availability_vote[block_root]) + add_payload_vote_checks(spec, store, block_root, test_steps) + + yield "steps", test_steps + + +@with_gloas_and_later +@spec_state_test +def test_on_payload_attestation_past_slot_message_is_ignored(spec, state): + """ + Test that a message whose slot is behind the block's slot is dropped without error. + """ + store, block_root, block_state, ptc, test_steps = yield from _setup_test(spec, state) + + # Build message and set the slot behind the block's slot + assert block_state.slot >= 1 + ptc_message = _build_signed_payload_attestation_message( + spec, + block_state, + block_root, + ptc[0], + payload_present=True, + ) + ptc_message.data.slot = spec.Slot(block_state.slot - 1) + + # Spec returns silently on the slot mismatch + yield from add_payload_attestation_message(spec, store, ptc_message, test_steps) + + # Vote arrays must remain at their default values + assert all(v is None for v in store.payload_timeliness_vote[block_root]) + assert all(v is None for v in store.payload_data_availability_vote[block_root]) + add_payload_vote_checks(spec, store, block_root, test_steps) + + yield "steps", test_steps + + +@with_gloas_and_later +@spec_state_test +def test_on_payload_attestation_slot_mismatch_precedes_ptc_check(spec, state): + """ + Test that the slot mismatch silent return precedes the PTC membership assertion. + """ + store, block_root, block_state, ptc, test_steps = yield from _setup_test(spec, state) + + # Pick a validator outside the PTC of both the current and the off slot + off_slot = spec.Slot(block_state.slot + 1) + ptc_off_slot = spec.get_ptc(block_state, off_slot) + non_ptc_member = _get_non_ptc_member(state, list(ptc) + list(ptc_off_slot)) + + # Build message with wrong slot from a non-PTC validator + ptc_message = _build_signed_payload_attestation_message( + spec, + block_state, + block_root, + non_ptc_member, + payload_present=True, + ) + ptc_message.data.slot = off_slot + + # Spec returns silently since the slot check runs before the PTC-membership check + yield from add_payload_attestation_message(spec, store, ptc_message, test_steps) + + # Vote arrays must remain at their default values + assert all(v is None for v in store.payload_timeliness_vote[block_root]) + assert all(v is None for v in store.payload_data_availability_vote[block_root]) + add_payload_vote_checks(spec, store, block_root, test_steps) + + yield "steps", test_steps + + +@with_gloas_and_later +@spec_state_test +def test_on_payload_attestation_not_ptc_member(spec, state): + """ + Test that a message from a validator outside the PTC is rejected. + """ + store, block_root, block_state, ptc, test_steps = yield from _setup_test(spec, state) + + # Pick non-PTC validator + non_ptc_member = _get_non_ptc_member(state, ptc) + ptc_message = _build_signed_payload_attestation_message( + spec, + block_state, + block_root, + non_ptc_member, + payload_present=True, + ) + + yield from add_payload_attestation_message( + spec, + store, + ptc_message, + test_steps, + valid=False, + ) + + # Assert rejected message didn't mutate the block vote arrays + assert all(v is None for v in store.payload_timeliness_vote[block_root]) + assert all(v is None for v in store.payload_data_availability_vote[block_root]) + add_payload_vote_checks(spec, store, block_root, test_steps) + + yield "steps", test_steps + + +@with_gloas_and_later +@spec_state_test +def test_on_payload_attestation_checks_current_slot_and_signature(spec, state): + """ + Test that non-block messages enforce the current-slot and signature checks. + """ + store, block_root, block_state, ptc, test_steps = yield from _setup_test(spec, state) + + # Bad signature + invalid_sig_msg = _build_signed_payload_attestation_message( + spec, + block_state, + block_root, + ptc[0], + payload_present=True, + ) + invalid_sig_msg.signature = spec.BLSSignature() + yield from add_payload_attestation_message( + spec, + store, + invalid_sig_msg, + test_steps, + valid=False, + ) + + # Vote arrays must remain at their default values + assert all(v is None for v in store.payload_timeliness_vote[block_root]) + assert all(v is None for v in store.payload_data_availability_vote[block_root]) + add_payload_vote_checks(spec, store, block_root, test_steps) + + # Valid signature, stale slot + valid_msg = _build_signed_payload_attestation_message( + spec, + block_state, + block_root, + ptc[0], + payload_present=True, + ) + _move_store_to_slot(spec, store, block_state.slot + 1, test_steps) + yield from add_payload_attestation_message( + spec, + store, + valid_msg, + test_steps, + valid=False, + ) + + # Rejected message must not partially mutate vote arrays + assert all(v is None for v in store.payload_timeliness_vote[block_root]) + assert all(v is None for v in store.payload_data_availability_vote[block_root]) + add_payload_vote_checks(spec, store, block_root, test_steps) + + yield "steps", test_steps + + +@with_gloas_and_later +@spec_state_test +def test_on_payload_attestation_updates_votes(spec, state): + """ + Test that valid messages update attestations. + """ + store, block_root, block_state, ptc, test_steps = yield from _setup_test(spec, state) + + # Initial state: no votes recorded for this block + assert all(v is None for v in store.payload_timeliness_vote[block_root]) + assert all(v is None for v in store.payload_data_availability_vote[block_root]) + + ptc_member = ptc[0] + ptc_index = ptc.index(ptc_member) + + # Attest both values + msg_1 = _build_signed_payload_attestation_message( + spec, + block_state, + block_root, + ptc_member, + payload_present=True, + blob_data_available=True, + ) + yield from add_payload_attestation_message(spec, store, msg_1, test_steps) + + assert store.payload_timeliness_vote[block_root][ptc_index] + assert store.payload_data_availability_vote[block_root][ptc_index] + add_payload_vote_checks(spec, store, block_root, test_steps) + + # Mixed vote + msg_2 = _build_signed_payload_attestation_message( + spec, + block_state, + block_root, + ptc_member, + payload_present=False, + blob_data_available=True, + ) + yield from add_payload_attestation_message(spec, store, msg_2, test_steps) + + timeliness = store.payload_timeliness_vote[block_root][ptc_index] + assert timeliness is not None and not timeliness + assert store.payload_data_availability_vote[block_root][ptc_index] + add_payload_vote_checks(spec, store, block_root, test_steps) + + yield "steps", test_steps + + +@with_gloas_and_later +@spec_state_test +def test_on_payload_attestation_multiple_ptc_members_vote_independently(spec, state): + """ + Test that two different PTC members voting for the same block update independent array slots. + """ + # Set two different validators on the PTC + block_slot = state.slot + 1 + window_idx = spec.SLOTS_PER_EPOCH + block_slot % spec.SLOTS_PER_EPOCH + state.ptc_window[window_idx][0] = spec.ValidatorIndex(0) + state.ptc_window[window_idx][1] = spec.ValidatorIndex(1) + + store, block_root, block_state, ptc, test_steps = yield from _setup_test(spec, state) + + index_a = 0 + index_b = 1 + ptc_member_a = ptc[index_a] + ptc_member_b = ptc[index_b] + assert ptc_member_a != ptc_member_b + + before_timeliness = list(store.payload_timeliness_vote[block_root]) + before_availability = list(store.payload_data_availability_vote[block_root]) + + msg_a = _build_signed_payload_attestation_message( + spec, + block_state, + block_root, + ptc_member_a, + payload_present=True, + blob_data_available=True, + ) + yield from add_payload_attestation_message(spec, store, msg_a, test_steps) + + msg_b = _build_signed_payload_attestation_message( + spec, + block_state, + block_root, + ptc_member_b, + payload_present=True, + blob_data_available=False, + ) + yield from add_payload_attestation_message(spec, store, msg_b, test_steps) + + timeliness = store.payload_timeliness_vote[block_root] + availability = store.payload_data_availability_vote[block_root] + + # Each validator updated its own slot only + assert timeliness[index_a] + assert timeliness[index_b] + assert availability[index_a] + assert availability[index_b] is not None and not availability[index_b] + + # Untouched indices kept their default values + for i in range(len(timeliness)): + if i == index_a or i == index_b: + continue + assert timeliness[i] == before_timeliness[i] + assert availability[i] == before_availability[i] + + add_payload_vote_checks(spec, store, block_root, test_steps) + + yield "steps", test_steps + + +@with_gloas_and_later +@spec_state_test +def test_on_payload_attestation_overwrites_both_vote_fields(spec, state): + """ + Test that a re-vote from the same validator overwrites both vote fields. + """ + store, block_root, block_state, ptc, test_steps = yield from _setup_test(spec, state) + + ptc_member = ptc[0] + ptc_index = ptc.index(ptc_member) + + # Initial vote: both values True + msg_initial = _build_signed_payload_attestation_message( + spec, + block_state, + block_root, + ptc_member, + payload_present=True, + blob_data_available=True, + ) + yield from add_payload_attestation_message(spec, store, msg_initial, test_steps) + + assert store.payload_timeliness_vote[block_root][ptc_index] + assert store.payload_data_availability_vote[block_root][ptc_index] + add_payload_vote_checks(spec, store, block_root, test_steps) + + # Re-vote with False values + msg_flipped = _build_signed_payload_attestation_message( + spec, + block_state, + block_root, + ptc_member, + payload_present=False, + blob_data_available=False, + ) + yield from add_payload_attestation_message(spec, store, msg_flipped, test_steps) + + timeliness = store.payload_timeliness_vote[block_root][ptc_index] + availability = store.payload_data_availability_vote[block_root][ptc_index] + + assert timeliness is not None and not timeliness + assert availability is not None and not availability + + add_payload_vote_checks(spec, store, block_root, test_steps) + + yield "steps", test_steps + + +@with_gloas_and_later +@spec_state_test +def test_on_payload_attestation_duplicate_validator(spec, state): + """ + Test that a vote from the same validator at multiple PTC positions counts once. + """ + # Set a duplicated validator in the PTC. + block_slot = state.slot + 1 + window_idx = spec.SLOTS_PER_EPOCH + block_slot % spec.SLOTS_PER_EPOCH + state.ptc_window[window_idx][1] = state.ptc_window[window_idx][0] + + store, block_root, block_state, ptc, test_steps = yield from _setup_test(spec, state) + + first_position = 0 + later_position = 1 + validator = ptc[first_position] + assert ptc[later_position] == validator + + msg = _build_signed_payload_attestation_message( + spec, + block_state, + block_root, + validator, + payload_present=True, + blob_data_available=True, + ) + yield from add_payload_attestation_message(spec, store, msg, test_steps) + + # Vote landed at the first position only + assert store.payload_timeliness_vote[block_root][first_position] + assert store.payload_data_availability_vote[block_root][first_position] + # Later position stayed at default + assert store.payload_timeliness_vote[block_root][later_position] is None + assert store.payload_data_availability_vote[block_root][later_position] is None + add_payload_vote_checks(spec, store, block_root, test_steps) + + yield "steps", test_steps + + +@with_gloas_and_later +@spec_state_test +def test_on_payload_attestation_message_from_block(spec, state): + """ + Test that a PayloadAttestation included in a block updates the vote arrays + """ + store, block_root, block_state, ptc, test_steps = yield from _setup_test(spec, state) + + # Pick a subset of the PTC to vote + ptc_list = list(ptc) + voters = ptc_list[:3] + voter_set = set(voters) + + # Build the validator messages + ptc_messages = [] + for validator_index in voters: + ptc_messages.append( + _build_signed_payload_attestation_message( + spec, + block_state, + block_root, + validator_index, + payload_present=True, + blob_data_available=True, + ) + ) + + # Build the PayloadAttestation aggregate + aggregation_bits = spec.Bitvector[spec.PTC_SIZE]() + for i, validator_index in enumerate(ptc_list): + if validator_index in voter_set: + aggregation_bits[i] = True + + sig_by_index = {m.validator_index: m.signature for m in ptc_messages} + aggregate_sig = spec.bls.Aggregate([sig_by_index[v] for v in ptc_list if v in voter_set]) + + aggregate = spec.PayloadAttestation( + aggregation_bits=aggregation_bits, + data=ptc_messages[0].data, + signature=aggregate_sig, + ) + + # Build the next block and add the aggregate + block_n1 = build_empty_block_for_next_slot(spec, state) + block_n1.body.payload_attestations.append(aggregate) + signed_block_n1 = state_transition_and_sign_block(spec, state, block_n1) + + # Apply block + yield from tick_and_add_block(spec, store, signed_block_n1, test_steps) + + # Votes landed at the voters' PTC positions + for voter in voters: + ptc_index = ptc_list.index(voter) + assert store.payload_timeliness_vote[block_root][ptc_index] + assert store.payload_data_availability_vote[block_root][ptc_index] + + # Non-voting PTC positions remain at their default values + for i, validator_index in enumerate(ptc_list): + if validator_index not in voter_set: + assert store.payload_timeliness_vote[block_root][i] is None + assert store.payload_data_availability_vote[block_root][i] is None + + add_payload_vote_checks(spec, store, block_root, test_steps) + yield "steps", test_steps diff --git a/tests/core/pyspec/eth_consensus_specs/test/helpers/fork_choice.py b/tests/core/pyspec/eth_consensus_specs/test/helpers/fork_choice.py index 061a9a0cf96..f0568c98f68 100644 --- a/tests/core/pyspec/eth_consensus_specs/test/helpers/fork_choice.py +++ b/tests/core/pyspec/eth_consensus_specs/test/helpers/fork_choice.py @@ -299,7 +299,7 @@ def get_sidecar_file_name(sidecar: DataColumnSidecar) -> str: def get_payload_attestation_message_file_name(ptc_message): - return f"payload_attestation_{encode_hex(ptc_message.hash_tree_root())}" + return f"payload_attestation_message_{encode_hex(ptc_message.hash_tree_root())}" def on_tick_and_append_step(spec, store, time, test_steps): @@ -498,13 +498,34 @@ def add_payload_attestation_message(spec, store, ptc_message, test_steps, valid= ptc_file_name = get_payload_attestation_message_file_name(ptc_message) yield ptc_file_name, ptc_message + run_on_payload_attestation_message(spec, store, ptc_message, valid=valid) + step = {"payload_attestation_message": ptc_file_name} + if not valid: - expect_assertion_error(lambda: spec.on_payload_attestation_message(store, ptc_message)) - test_steps.append({"payload_attestation": ptc_file_name, "valid": False}) - return + step["valid"] = False + test_steps.append(step) + - run_on_payload_attestation_message(spec, store, ptc_message) - test_steps.append({"payload_attestation": ptc_file_name}) +def add_payload_vote_checks(spec, store, block_root, test_steps): + timeliness = [None if v is None else bool(v) for v in store.payload_timeliness_vote[block_root]] + availability = [ + None if v is None else bool(v) for v in store.payload_data_availability_vote[block_root] + ] + + test_steps.append( + { + "checks": { + "payload_timeliness_vote": { + "block_root": encode_hex(block_root), + "votes": timeliness, + }, + "payload_data_availability_vote": { + "block_root": encode_hex(block_root), + "votes": availability, + }, + } + } + ) def _get_head_root(spec, store): diff --git a/tests/formats/fork_choice/README.md b/tests/formats/fork_choice/README.md index 78bfaa667e8..455f6dbe88d 100644 --- a/tests/formats/fork_choice/README.md +++ b/tests/formats/fork_choice/README.md @@ -17,12 +17,12 @@ components of the fork choice. - [`on_attester_slashing` execution step](#on_attester_slashing-execution-step) - [`on_payload_info` execution step](#on_payload_info-execution-step) - [`on_execution_payload_envelope` execution step](#on_execution_payload_envelope-execution-step) - - [`on_payload_attestation` execution step](#on_payload_attestation-execution-step) + - [`on_payload_attestation_message` execution step](#on_payload_attestation_message-execution-step) - [Checks step](#checks-step) - [`attestation_<32-byte-root>.ssz_snappy`](#attestation_32-byte-rootssz_snappy) - [`block_<32-byte-root>.ssz_snappy`](#block_32-byte-rootssz_snappy) - [`execution_payload_envelope_<32-byte-root>.ssz_snappy`](#execution_payload_envelope_32-byte-rootssz_snappy) - - [`payload_attestation_<32-byte-root>.ssz_snappy`](#payload_attestation_32-byte-rootssz_snappy) + - [`payload_attestation_message_<32-byte-root>.ssz_snappy`](#payload_attestation_message_32-byte-rootssz_snappy) - [Condition](#condition) @@ -202,15 +202,14 @@ The file is located in the same folder (see below). After this step, the `store` object may have been updated. -#### `on_payload_attestation` execution step +#### `on_payload_attestation_message` execution step The parameter that is required for executing `on_payload_attestation_message(store, ptc_message)`. ```yaml { - payload_attestation: string -- the name of the `payload_attestation_<32-byte-root>.ssz_snappy` file. - To execute `on_payload_attestation_message(store, ptc_message)` with the given message. + payload_attestation_message: string -- the name of the `payload_attestation_message_<32-byte-root>.ssz_snappy` file. To execute `on_payload_attestation_message(store, ptc_message)` with the given message. valid: bool -- optional, default to `true`. If it's `false`, this execution step is expected to be invalid. } @@ -267,6 +266,14 @@ should_override_forkchoice_update: { -- [New in Bellatrix] result: bool, -- The result of `should_override_forkchoice_update(store, head_root)`, where head_root is the result value from get_head(store) } head_payload_status: int -- The payload_status field from the ForkChoiceNode returned by get_head(store) +payload_timeliness_vote: { -- [New in Gloas] + block_root: string, -- Encoded 32-byte beacon block root + votes: [bool | null, ...] -- Votes ordered by PTC positions. Length is `PTC_SIZE`. +} +payload_data_availability_vote: { -- [New in Gloas] + block_root: string, -- Encoded 32-byte beacon block root + votes: [bool | null, ...] -- Votes ordered by PTC positions. Length is `PTC_SIZE`. +} ``` For example: @@ -308,7 +315,7 @@ Each file is an SSZ-snappy encoded `SignedBeaconBlock`. Each file is an SSZ-snappy encoded `SignedExecutionPayloadEnvelope`. -### `payload_attestation_<32-byte-root>.ssz_snappy` +### `payload_attestation_message_<32-byte-root>.ssz_snappy` `<32-byte-root>` is the hash tree root of the given payload attestation message. @@ -334,8 +341,8 @@ Each file is an SSZ-snappy encoded `PayloadAttestationMessage`. - For the `on_execution_payload_envelope` execution step: look up the corresponding `execution_payload_envelope_.ssz_snappy` file and execute `on_execution_payload_envelope(store, signed_envelope)`. - - For the `on_payload_attestation` execution step: look up the - corresponding `payload_attestation_.ssz_snappy` file and execute - `on_payload_attestation_message(store, ptc_message)`. + - For the `on_payload_attestation_message` execution step: look up the + corresponding `payload_attestation_message_.ssz_snappy` file and + execute `on_payload_attestation_message(store, ptc_message)`. - For each `checks` step, the assertions on the current store must be satisfied. diff --git a/tests/generators/compliance_runners/fork_choice/instantiators/test_case.py b/tests/generators/compliance_runners/fork_choice/instantiators/test_case.py index 4235a27e7c5..bdf68cf19aa 100644 --- a/tests/generators/compliance_runners/fork_choice/instantiators/test_case.py +++ b/tests/generators/compliance_runners/fork_choice/instantiators/test_case.py @@ -360,7 +360,10 @@ def yield_test_parts(spec, store, test_data: FCTestData, events): event_data ) test_steps.append( - {"payload_attestation": _payload_attestation_id, "valid": True} + { + "payload_attestation_message": _payload_attestation_id, + "valid": True, + } ) else: assert False @@ -404,7 +407,10 @@ def yield_test_parts(spec, store, test_data: FCTestData, events): ) assert recovery test_steps.append( - {"payload_attestation": _payload_attestation_id, "valid": True} + { + "payload_attestation_message": _payload_attestation_id, + "valid": True, + } ) else: assert False @@ -437,7 +443,7 @@ def yield_test_parts(spec, store, test_data: FCTestData, events): ptc_message = data ptc_message_id = get_payload_attestation_message_file_name(ptc_message) valid = scheduler.process_payload_attestation_message(ptc_message, is_from_block=False) - test_steps.append({"payload_attestation": ptc_message_id, "valid": valid}) + test_steps.append({"payload_attestation_message": ptc_message_id, "valid": valid}) output_store_checks(spec, store, test_steps) else: raise ValueError(f"not implemented {kind}") diff --git a/tests/generators/compliance_runners/fork_choice/runner/test_run.py b/tests/generators/compliance_runners/fork_choice/runner/test_run.py index 8e914f0de74..9eb78f967a3 100644 --- a/tests/generators/compliance_runners/fork_choice/runner/test_run.py +++ b/tests/generators/compliance_runners/fork_choice/runner/test_run.py @@ -4,6 +4,7 @@ from pathlib import Path import pytest +from eth_utils import decode_hex from ruamel.yaml import YAML from snappy import uncompress @@ -53,7 +54,7 @@ def get_prefix(p): }, { get_prefix(b): spec.PayloadAttestationMessage.decode_bytes(read_ssz_snappy(b)) - for b in glob(f"{td}/payload_attestation_*.ssz_snappy") + for b in glob(f"{td}/payload_attestation_message_*.ssz_snappy") }, read_yaml(f"{td}/steps.yaml"), ) @@ -136,8 +137,8 @@ def run_test(test_info): expect_assertion_error( lambda: spec.on_execution_payload_envelope(store, signed_envelope) ) - elif "payload_attestation" in step: - ptc_message_id = step["payload_attestation"] + elif "payload_attestation_message" in step: + ptc_message_id = step["payload_attestation_message"] valid = step.get("valid", True) ptc_message = payload_atts[ptc_message_id] if valid: @@ -185,6 +186,9 @@ def get_head(): elif check == "head_payload_status": head = get_head() assert head.payload_status == value + elif check in ("payload_timeliness_vote", "payload_data_availability_vote"): + target_root = spec.Root(decode_hex(value["block_root"])) + assert list(getattr(store, check)[target_root]) == value["votes"] else: assert False else: From abce12fe465964b95a64d3ccf62b48c21adf52af Mon Sep 17 00:00:00 2001 From: brech1 <113475764+brech1@users.noreply.github.com> Date: Mon, 11 May 2026 16:40:51 -0300 Subject: [PATCH 2/6] Apply #5222 updates --- .../test_on_payload_attestation_message.py | 283 ++++-------------- tests/formats/fork_choice/README.md | 7 +- 2 files changed, 67 insertions(+), 223 deletions(-) diff --git a/tests/core/pyspec/eth_consensus_specs/test/gloas/fork_choice/test_on_payload_attestation_message.py b/tests/core/pyspec/eth_consensus_specs/test/gloas/fork_choice/test_on_payload_attestation_message.py index 11044af06a8..1e6916871bf 100644 --- a/tests/core/pyspec/eth_consensus_specs/test/gloas/fork_choice/test_on_payload_attestation_message.py +++ b/tests/core/pyspec/eth_consensus_specs/test/gloas/fork_choice/test_on_payload_attestation_message.py @@ -42,47 +42,31 @@ def _build_signed_payload_attestation_message( ) -def _setup_store(spec, state, test_steps): +def _move_store_to_slot(spec, store, slot, test_steps): + slot_time = store.genesis_time + slot * (spec.config.SLOT_DURATION_MS // 1000) + if store.time < slot_time: + on_tick_and_append_step(spec, store, slot_time, test_steps) + + +def _setup_test(spec, state): + test_steps = [] + + # Build genesis store 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.SLOT_DURATION_MS // 1000) + store.genesis_time on_tick_and_append_step(spec, store, current_time, test_steps) - return store - -def _setup_ptc_block(spec, store, state, test_steps): + # Apply one block at slot 1 block = build_empty_block_for_next_slot(spec, state) signed_block = state_transition_and_sign_block(spec, state, block) yield from tick_and_add_block(spec, store, signed_block, test_steps) - block_root = signed_block.message.hash_tree_root() block_state = store.block_states[block_root] ptc = spec.get_ptc(block_state, block_state.slot) assert len(ptc) > 0 - return block_root, block_state, ptc - - -def _move_store_to_slot(spec, store, slot, test_steps): - slot_time = store.genesis_time + slot * (spec.config.SLOT_DURATION_MS // 1000) - if store.time < slot_time: - on_tick_and_append_step(spec, store, slot_time, test_steps) - - -def _get_non_ptc_member(state, ptc): - ptc_set = set(ptc) - for validator_index in range(len(state.validators)): - if validator_index not in ptc_set: - return validator_index - raise AssertionError("expected a validator outside the PTC") - - -def _setup_test(spec, state): - test_steps = [] - store = yield from _setup_store(spec, state, test_steps) - block_root, block_state, ptc = yield from _setup_ptc_block(spec, store, state, test_steps) _move_store_to_slot(spec, store, block_state.slot, test_steps) return store, block_root, block_state, ptc, test_steps @@ -123,13 +107,13 @@ def test_on_payload_attestation_unknown_block_root(spec, state): @with_gloas_and_later @spec_state_test -def test_on_payload_attestation_future_slot_message_is_ignored(spec, state): +def test_on_payload_attestation_slot_mismatch(spec, state): """ - Test that a message whose slot is ahead of the block's slot is dropped without error. + Test that a message whose slot doesn't match the block's slot is dropped without error. """ store, block_root, block_state, ptc, test_steps = yield from _setup_test(spec, state) - # Build message and set the slot ahead of the block's slot + # Build message and set the slot to a different one ptc_message = _build_signed_payload_attestation_message( spec, block_state, @@ -139,71 +123,7 @@ def test_on_payload_attestation_future_slot_message_is_ignored(spec, state): ) ptc_message.data.slot = spec.Slot(block_state.slot + 1) - # Spec returns silently on the slot mismatch - yield from add_payload_attestation_message(spec, store, ptc_message, test_steps) - - # Vote arrays must remain at their default values - assert all(v is None for v in store.payload_timeliness_vote[block_root]) - assert all(v is None for v in store.payload_data_availability_vote[block_root]) - add_payload_vote_checks(spec, store, block_root, test_steps) - - yield "steps", test_steps - - -@with_gloas_and_later -@spec_state_test -def test_on_payload_attestation_past_slot_message_is_ignored(spec, state): - """ - Test that a message whose slot is behind the block's slot is dropped without error. - """ - store, block_root, block_state, ptc, test_steps = yield from _setup_test(spec, state) - - # Build message and set the slot behind the block's slot - assert block_state.slot >= 1 - ptc_message = _build_signed_payload_attestation_message( - spec, - block_state, - block_root, - ptc[0], - payload_present=True, - ) - ptc_message.data.slot = spec.Slot(block_state.slot - 1) - - # Spec returns silently on the slot mismatch - yield from add_payload_attestation_message(spec, store, ptc_message, test_steps) - - # Vote arrays must remain at their default values - assert all(v is None for v in store.payload_timeliness_vote[block_root]) - assert all(v is None for v in store.payload_data_availability_vote[block_root]) - add_payload_vote_checks(spec, store, block_root, test_steps) - - yield "steps", test_steps - - -@with_gloas_and_later -@spec_state_test -def test_on_payload_attestation_slot_mismatch_precedes_ptc_check(spec, state): - """ - Test that the slot mismatch silent return precedes the PTC membership assertion. - """ - store, block_root, block_state, ptc, test_steps = yield from _setup_test(spec, state) - - # Pick a validator outside the PTC of both the current and the off slot - off_slot = spec.Slot(block_state.slot + 1) - ptc_off_slot = spec.get_ptc(block_state, off_slot) - non_ptc_member = _get_non_ptc_member(state, list(ptc) + list(ptc_off_slot)) - - # Build message with wrong slot from a non-PTC validator - ptc_message = _build_signed_payload_attestation_message( - spec, - block_state, - block_root, - non_ptc_member, - payload_present=True, - ) - ptc_message.data.slot = off_slot - - # Spec returns silently since the slot check runs before the PTC-membership check + # Spec function runs without error but returns early on the slot mismatch yield from add_payload_attestation_message(spec, store, ptc_message, test_steps) # Vote arrays must remain at their default values @@ -222,8 +142,9 @@ def test_on_payload_attestation_not_ptc_member(spec, state): """ store, block_root, block_state, ptc, test_steps = yield from _setup_test(spec, state) - # Pick non-PTC validator - non_ptc_member = _get_non_ptc_member(state, ptc) + # Pick a validator outside the PTC + ptc_set = set(ptc) + non_ptc_member = next(i for i in range(len(state.validators)) if i not in ptc_set) ptc_message = _build_signed_payload_attestation_message( spec, block_state, @@ -250,9 +171,9 @@ def test_on_payload_attestation_not_ptc_member(spec, state): @with_gloas_and_later @spec_state_test -def test_on_payload_attestation_checks_current_slot_and_signature(spec, state): +def test_on_payload_attestation_current_slot_and_signature(spec, state): """ - Test that non-block messages enforce the current-slot and signature checks. + Test that current slot and signature checks reject invalid messages. """ store, block_root, block_state, ptc, test_steps = yield from _setup_test(spec, state) @@ -305,7 +226,7 @@ def test_on_payload_attestation_checks_current_slot_and_signature(spec, state): @with_gloas_and_later @spec_state_test -def test_on_payload_attestation_updates_votes(spec, state): +def test_on_payload_attestation_valid_message(spec, state): """ Test that valid messages update attestations. """ @@ -316,7 +237,8 @@ def test_on_payload_attestation_updates_votes(spec, state): assert all(v is None for v in store.payload_data_availability_vote[block_root]) ptc_member = ptc[0] - ptc_index = ptc.index(ptc_member) + voter_positions = [i for i, v in enumerate(ptc) if v == ptc_member] + other_positions = [i for i in range(len(ptc)) if i not in voter_positions] # Attest both values msg_1 = _build_signed_payload_attestation_message( @@ -329,24 +251,33 @@ def test_on_payload_attestation_updates_votes(spec, state): ) yield from add_payload_attestation_message(spec, store, msg_1, test_steps) - assert store.payload_timeliness_vote[block_root][ptc_index] - assert store.payload_data_availability_vote[block_root][ptc_index] + for i in voter_positions: + assert store.payload_timeliness_vote[block_root][i] + assert store.payload_data_availability_vote[block_root][i] + for i in other_positions: + assert store.payload_timeliness_vote[block_root][i] is None + assert store.payload_data_availability_vote[block_root][i] is None add_payload_vote_checks(spec, store, block_root, test_steps) - # Mixed vote + # Re-vote with both fields False msg_2 = _build_signed_payload_attestation_message( spec, block_state, block_root, ptc_member, payload_present=False, - blob_data_available=True, + blob_data_available=False, ) yield from add_payload_attestation_message(spec, store, msg_2, test_steps) - timeliness = store.payload_timeliness_vote[block_root][ptc_index] - assert timeliness is not None and not timeliness - assert store.payload_data_availability_vote[block_root][ptc_index] + for i in voter_positions: + timeliness = store.payload_timeliness_vote[block_root][i] + availability = store.payload_data_availability_vote[block_root][i] + assert timeliness is not None and not timeliness + assert availability is not None and not availability + for i in other_positions: + assert store.payload_timeliness_vote[block_root][i] is None + assert store.payload_data_availability_vote[block_root][i] is None add_payload_vote_checks(spec, store, block_root, test_steps) yield "steps", test_steps @@ -356,7 +287,7 @@ def test_on_payload_attestation_updates_votes(spec, state): @spec_state_test def test_on_payload_attestation_multiple_ptc_members_vote_independently(spec, state): """ - Test that two different PTC members voting for the same block update independent array slots. + Test that two different PTC members voting for the same block update independent vote sets. """ # Set two different validators on the PTC block_slot = state.slot + 1 @@ -366,14 +297,14 @@ def test_on_payload_attestation_multiple_ptc_members_vote_independently(spec, st store, block_root, block_state, ptc, test_steps = yield from _setup_test(spec, state) - index_a = 0 - index_b = 1 - ptc_member_a = ptc[index_a] - ptc_member_b = ptc[index_b] + ptc_member_a = ptc[0] + ptc_member_b = ptc[1] assert ptc_member_a != ptc_member_b - before_timeliness = list(store.payload_timeliness_vote[block_root]) - before_availability = list(store.payload_data_availability_vote[block_root]) + # Compute every position each validator appears at + positions_a = [i for i, v in enumerate(ptc) if v == ptc_member_a] + positions_b = [i for i, v in enumerate(ptc) if v == ptc_member_b] + other_positions = [i for i in range(len(ptc)) if i not in positions_a and i not in positions_b] msg_a = _build_signed_payload_attestation_message( spec, @@ -398,111 +329,26 @@ def test_on_payload_attestation_multiple_ptc_members_vote_independently(spec, st timeliness = store.payload_timeliness_vote[block_root] availability = store.payload_data_availability_vote[block_root] - # Each validator updated its own slot only - assert timeliness[index_a] - assert timeliness[index_b] - assert availability[index_a] - assert availability[index_b] is not None and not availability[index_b] - - # Untouched indices kept their default values - for i in range(len(timeliness)): - if i == index_a or i == index_b: - continue - assert timeliness[i] == before_timeliness[i] - assert availability[i] == before_availability[i] - - add_payload_vote_checks(spec, store, block_root, test_steps) - - yield "steps", test_steps - - -@with_gloas_and_later -@spec_state_test -def test_on_payload_attestation_overwrites_both_vote_fields(spec, state): - """ - Test that a re-vote from the same validator overwrites both vote fields. - """ - store, block_root, block_state, ptc, test_steps = yield from _setup_test(spec, state) - - ptc_member = ptc[0] - ptc_index = ptc.index(ptc_member) - - # Initial vote: both values True - msg_initial = _build_signed_payload_attestation_message( - spec, - block_state, - block_root, - ptc_member, - payload_present=True, - blob_data_available=True, - ) - yield from add_payload_attestation_message(spec, store, msg_initial, test_steps) - - assert store.payload_timeliness_vote[block_root][ptc_index] - assert store.payload_data_availability_vote[block_root][ptc_index] - add_payload_vote_checks(spec, store, block_root, test_steps) + # Validator A's votes landed at every position A occupies + for i in positions_a: + assert timeliness[i] + assert availability[i] - # Re-vote with False values - msg_flipped = _build_signed_payload_attestation_message( - spec, - block_state, - block_root, - ptc_member, - payload_present=False, - blob_data_available=False, - ) - yield from add_payload_attestation_message(spec, store, msg_flipped, test_steps) + # Validator B's votes landed at every position B occupies + for i in positions_b: + assert timeliness[i] + assert availability[i] is not None and not availability[i] - timeliness = store.payload_timeliness_vote[block_root][ptc_index] - availability = store.payload_data_availability_vote[block_root][ptc_index] - - assert timeliness is not None and not timeliness - assert availability is not None and not availability + # Other positions stayed at their default values + for i in other_positions: + assert timeliness[i] is None + assert availability[i] is None add_payload_vote_checks(spec, store, block_root, test_steps) yield "steps", test_steps -@with_gloas_and_later -@spec_state_test -def test_on_payload_attestation_duplicate_validator(spec, state): - """ - Test that a vote from the same validator at multiple PTC positions counts once. - """ - # Set a duplicated validator in the PTC. - block_slot = state.slot + 1 - window_idx = spec.SLOTS_PER_EPOCH + block_slot % spec.SLOTS_PER_EPOCH - state.ptc_window[window_idx][1] = state.ptc_window[window_idx][0] - - store, block_root, block_state, ptc, test_steps = yield from _setup_test(spec, state) - - first_position = 0 - later_position = 1 - validator = ptc[first_position] - assert ptc[later_position] == validator - - msg = _build_signed_payload_attestation_message( - spec, - block_state, - block_root, - validator, - payload_present=True, - blob_data_available=True, - ) - yield from add_payload_attestation_message(spec, store, msg, test_steps) - - # Vote landed at the first position only - assert store.payload_timeliness_vote[block_root][first_position] - assert store.payload_data_availability_vote[block_root][first_position] - # Later position stayed at default - assert store.payload_timeliness_vote[block_root][later_position] is None - assert store.payload_data_availability_vote[block_root][later_position] is None - add_payload_vote_checks(spec, store, block_root, test_steps) - - yield "steps", test_steps - - @with_gloas_and_later @spec_state_test def test_on_payload_attestation_message_from_block(spec, state): @@ -553,15 +399,12 @@ def test_on_payload_attestation_message_from_block(spec, state): # Apply block yield from tick_and_add_block(spec, store, signed_block_n1, test_steps) - # Votes landed at the voters' PTC positions - for voter in voters: - ptc_index = ptc_list.index(voter) - assert store.payload_timeliness_vote[block_root][ptc_index] - assert store.payload_data_availability_vote[block_root][ptc_index] - - # Non-voting PTC positions remain at their default values + # Votes landed at every PTC position each voter occupies for i, validator_index in enumerate(ptc_list): - if validator_index not in voter_set: + if validator_index in voter_set: + assert store.payload_timeliness_vote[block_root][i] + assert store.payload_data_availability_vote[block_root][i] + else: assert store.payload_timeliness_vote[block_root][i] is None assert store.payload_data_availability_vote[block_root][i] is None diff --git a/tests/formats/fork_choice/README.md b/tests/formats/fork_choice/README.md index 455f6dbe88d..e3e81f746da 100644 --- a/tests/formats/fork_choice/README.md +++ b/tests/formats/fork_choice/README.md @@ -209,9 +209,10 @@ The parameter that is required for executing ```yaml { - payload_attestation_message: string -- the name of the `payload_attestation_message_<32-byte-root>.ssz_snappy` file. To execute `on_payload_attestation_message(store, ptc_message)` with the given message. - valid: bool -- optional, default to `true`. - If it's `false`, this execution step is expected to be invalid. + payload_attestation_message: string -- the name of the `payload_attestation_message_<32-byte-root>.ssz_snappy` file. + To execute `on_payload_attestation_message(store, ptc_message)` with the given message. + valid: bool -- optional, default to `true`. + If it's `false`, this execution step is expected to be invalid. } ``` From 126dd1990c78aa5f5cca30e979c1fe7256f9f07b Mon Sep 17 00:00:00 2001 From: brech1 <113475764+brech1@users.noreply.github.com> Date: Mon, 11 May 2026 17:20:30 -0300 Subject: [PATCH 3/6] fix: names --- .../test_on_payload_attestation_message.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/tests/core/pyspec/eth_consensus_specs/test/gloas/fork_choice/test_on_payload_attestation_message.py b/tests/core/pyspec/eth_consensus_specs/test/gloas/fork_choice/test_on_payload_attestation_message.py index 1e6916871bf..3a7c8a5946d 100644 --- a/tests/core/pyspec/eth_consensus_specs/test/gloas/fork_choice/test_on_payload_attestation_message.py +++ b/tests/core/pyspec/eth_consensus_specs/test/gloas/fork_choice/test_on_payload_attestation_message.py @@ -73,7 +73,7 @@ def _setup_test(spec, state): @with_gloas_and_later @spec_state_test -def test_on_payload_attestation_unknown_block_root(spec, state): +def test_on_payload_attestation_message_unknown_block_root(spec, state): """ Test that messages for an unknown beacon_block_root are rejected. """ @@ -107,7 +107,7 @@ def test_on_payload_attestation_unknown_block_root(spec, state): @with_gloas_and_later @spec_state_test -def test_on_payload_attestation_slot_mismatch(spec, state): +def test_on_payload_attestation_message_slot_mismatch(spec, state): """ Test that a message whose slot doesn't match the block's slot is dropped without error. """ @@ -136,7 +136,7 @@ def test_on_payload_attestation_slot_mismatch(spec, state): @with_gloas_and_later @spec_state_test -def test_on_payload_attestation_not_ptc_member(spec, state): +def test_on_payload_attestation_message_not_ptc_member(spec, state): """ Test that a message from a validator outside the PTC is rejected. """ @@ -171,7 +171,7 @@ def test_on_payload_attestation_not_ptc_member(spec, state): @with_gloas_and_later @spec_state_test -def test_on_payload_attestation_current_slot_and_signature(spec, state): +def test_on_payload_attestation_message_current_slot_and_signature(spec, state): """ Test that current slot and signature checks reject invalid messages. """ @@ -226,7 +226,7 @@ def test_on_payload_attestation_current_slot_and_signature(spec, state): @with_gloas_and_later @spec_state_test -def test_on_payload_attestation_valid_message(spec, state): +def test_on_payload_attestation_message_valid(spec, state): """ Test that valid messages update attestations. """ @@ -285,7 +285,7 @@ def test_on_payload_attestation_valid_message(spec, state): @with_gloas_and_later @spec_state_test -def test_on_payload_attestation_multiple_ptc_members_vote_independently(spec, state): +def test_on_payload_attestation_message_multiple_ptc_members_vote_independently(spec, state): """ Test that two different PTC members voting for the same block update independent vote sets. """ From 8985840598f19b111ee521e8abf7e4e96a0d0788 Mon Sep 17 00:00:00 2001 From: brech1 <113475764+brech1@users.noreply.github.com> Date: Mon, 11 May 2026 21:13:41 -0300 Subject: [PATCH 4/6] fix: justin's review --- .../test_on_payload_attestation_message.py | 34 ++++++++++++------- .../test/helpers/fork_choice.py | 2 +- 2 files changed, 22 insertions(+), 14 deletions(-) diff --git a/tests/core/pyspec/eth_consensus_specs/test/gloas/fork_choice/test_on_payload_attestation_message.py b/tests/core/pyspec/eth_consensus_specs/test/gloas/fork_choice/test_on_payload_attestation_message.py index 3a7c8a5946d..1138756251f 100644 --- a/tests/core/pyspec/eth_consensus_specs/test/gloas/fork_choice/test_on_payload_attestation_message.py +++ b/tests/core/pyspec/eth_consensus_specs/test/gloas/fork_choice/test_on_payload_attestation_message.py @@ -19,14 +19,22 @@ def _build_signed_payload_attestation_message( - spec, state, block_root, validator_index, payload_present=True, blob_data_available=True + spec, + state, + block_root, + validator_index, + payload_present=True, + blob_data_available=True, + slot=None, ): """ Build a signed PayloadAttestationMessage for a given block root and validator. """ + if slot is None: + slot = state.slot data = spec.PayloadAttestationData( beacon_block_root=block_root, - slot=state.slot, + slot=slot, payload_present=payload_present, blob_data_available=blob_data_available, ) @@ -100,7 +108,7 @@ def test_on_payload_attestation_message_unknown_block_root(spec, state): # Vote arrays for the known block must remain at their default values assert all(v is None for v in store.payload_timeliness_vote[block_root]) assert all(v is None for v in store.payload_data_availability_vote[block_root]) - add_payload_vote_checks(spec, store, block_root, test_steps) + add_payload_vote_checks(store, block_root, test_steps) yield "steps", test_steps @@ -113,15 +121,15 @@ def test_on_payload_attestation_message_slot_mismatch(spec, state): """ store, block_root, block_state, ptc, test_steps = yield from _setup_test(spec, state) - # Build message and set the slot to a different one + # Build message signed over a mismatching slot ptc_message = _build_signed_payload_attestation_message( spec, block_state, block_root, ptc[0], payload_present=True, + slot=spec.Slot(block_state.slot + 1), ) - ptc_message.data.slot = spec.Slot(block_state.slot + 1) # Spec function runs without error but returns early on the slot mismatch yield from add_payload_attestation_message(spec, store, ptc_message, test_steps) @@ -129,7 +137,7 @@ def test_on_payload_attestation_message_slot_mismatch(spec, state): # Vote arrays must remain at their default values assert all(v is None for v in store.payload_timeliness_vote[block_root]) assert all(v is None for v in store.payload_data_availability_vote[block_root]) - add_payload_vote_checks(spec, store, block_root, test_steps) + add_payload_vote_checks(store, block_root, test_steps) yield "steps", test_steps @@ -164,7 +172,7 @@ def test_on_payload_attestation_message_not_ptc_member(spec, state): # Assert rejected message didn't mutate the block vote arrays assert all(v is None for v in store.payload_timeliness_vote[block_root]) assert all(v is None for v in store.payload_data_availability_vote[block_root]) - add_payload_vote_checks(spec, store, block_root, test_steps) + add_payload_vote_checks(store, block_root, test_steps) yield "steps", test_steps @@ -197,7 +205,7 @@ def test_on_payload_attestation_message_current_slot_and_signature(spec, state): # Vote arrays must remain at their default values assert all(v is None for v in store.payload_timeliness_vote[block_root]) assert all(v is None for v in store.payload_data_availability_vote[block_root]) - add_payload_vote_checks(spec, store, block_root, test_steps) + add_payload_vote_checks(store, block_root, test_steps) # Valid signature, stale slot valid_msg = _build_signed_payload_attestation_message( @@ -219,7 +227,7 @@ def test_on_payload_attestation_message_current_slot_and_signature(spec, state): # Rejected message must not partially mutate vote arrays assert all(v is None for v in store.payload_timeliness_vote[block_root]) assert all(v is None for v in store.payload_data_availability_vote[block_root]) - add_payload_vote_checks(spec, store, block_root, test_steps) + add_payload_vote_checks(store, block_root, test_steps) yield "steps", test_steps @@ -257,7 +265,7 @@ def test_on_payload_attestation_message_valid(spec, state): for i in other_positions: assert store.payload_timeliness_vote[block_root][i] is None assert store.payload_data_availability_vote[block_root][i] is None - add_payload_vote_checks(spec, store, block_root, test_steps) + add_payload_vote_checks(store, block_root, test_steps) # Re-vote with both fields False msg_2 = _build_signed_payload_attestation_message( @@ -278,7 +286,7 @@ def test_on_payload_attestation_message_valid(spec, state): for i in other_positions: assert store.payload_timeliness_vote[block_root][i] is None assert store.payload_data_availability_vote[block_root][i] is None - add_payload_vote_checks(spec, store, block_root, test_steps) + add_payload_vote_checks(store, block_root, test_steps) yield "steps", test_steps @@ -344,7 +352,7 @@ def test_on_payload_attestation_message_multiple_ptc_members_vote_independently( assert timeliness[i] is None assert availability[i] is None - add_payload_vote_checks(spec, store, block_root, test_steps) + add_payload_vote_checks(store, block_root, test_steps) yield "steps", test_steps @@ -408,5 +416,5 @@ def test_on_payload_attestation_message_from_block(spec, state): assert store.payload_timeliness_vote[block_root][i] is None assert store.payload_data_availability_vote[block_root][i] is None - add_payload_vote_checks(spec, store, block_root, test_steps) + add_payload_vote_checks(store, block_root, test_steps) yield "steps", test_steps diff --git a/tests/core/pyspec/eth_consensus_specs/test/helpers/fork_choice.py b/tests/core/pyspec/eth_consensus_specs/test/helpers/fork_choice.py index f0568c98f68..19479154208 100644 --- a/tests/core/pyspec/eth_consensus_specs/test/helpers/fork_choice.py +++ b/tests/core/pyspec/eth_consensus_specs/test/helpers/fork_choice.py @@ -506,7 +506,7 @@ def add_payload_attestation_message(spec, store, ptc_message, test_steps, valid= test_steps.append(step) -def add_payload_vote_checks(spec, store, block_root, test_steps): +def add_payload_vote_checks(store, block_root, test_steps): timeliness = [None if v is None else bool(v) for v in store.payload_timeliness_vote[block_root]] availability = [ None if v is None else bool(v) for v in store.payload_data_availability_vote[block_root] From 60a60e47728980cbde071a59b65ec2b32a406faa Mon Sep 17 00:00:00 2001 From: brech1 <113475764+brech1@users.noreply.github.com> Date: Mon, 11 May 2026 21:27:43 -0300 Subject: [PATCH 5/6] fix: assertions --- .../test_on_payload_attestation_message.py | 38 +++++++++---------- 1 file changed, 18 insertions(+), 20 deletions(-) diff --git a/tests/core/pyspec/eth_consensus_specs/test/gloas/fork_choice/test_on_payload_attestation_message.py b/tests/core/pyspec/eth_consensus_specs/test/gloas/fork_choice/test_on_payload_attestation_message.py index 1138756251f..6e07baf1aac 100644 --- a/tests/core/pyspec/eth_consensus_specs/test/gloas/fork_choice/test_on_payload_attestation_message.py +++ b/tests/core/pyspec/eth_consensus_specs/test/gloas/fork_choice/test_on_payload_attestation_message.py @@ -260,11 +260,11 @@ def test_on_payload_attestation_message_valid(spec, state): yield from add_payload_attestation_message(spec, store, msg_1, test_steps) for i in voter_positions: - assert store.payload_timeliness_vote[block_root][i] - assert store.payload_data_availability_vote[block_root][i] + assert store.payload_timeliness_vote[block_root][i] == True + assert store.payload_data_availability_vote[block_root][i] == True for i in other_positions: - assert store.payload_timeliness_vote[block_root][i] is None - assert store.payload_data_availability_vote[block_root][i] is None + assert store.payload_timeliness_vote[block_root][i] == None + assert store.payload_data_availability_vote[block_root][i] == None add_payload_vote_checks(store, block_root, test_steps) # Re-vote with both fields False @@ -279,13 +279,11 @@ def test_on_payload_attestation_message_valid(spec, state): yield from add_payload_attestation_message(spec, store, msg_2, test_steps) for i in voter_positions: - timeliness = store.payload_timeliness_vote[block_root][i] - availability = store.payload_data_availability_vote[block_root][i] - assert timeliness is not None and not timeliness - assert availability is not None and not availability + assert store.payload_timeliness_vote[block_root][i] == False + assert store.payload_data_availability_vote[block_root][i] == False for i in other_positions: - assert store.payload_timeliness_vote[block_root][i] is None - assert store.payload_data_availability_vote[block_root][i] is None + assert store.payload_timeliness_vote[block_root][i] == None + assert store.payload_data_availability_vote[block_root][i] == None add_payload_vote_checks(store, block_root, test_steps) yield "steps", test_steps @@ -339,18 +337,18 @@ def test_on_payload_attestation_message_multiple_ptc_members_vote_independently( # Validator A's votes landed at every position A occupies for i in positions_a: - assert timeliness[i] - assert availability[i] + assert timeliness[i] == True + assert availability[i] == True # Validator B's votes landed at every position B occupies for i in positions_b: - assert timeliness[i] - assert availability[i] is not None and not availability[i] + assert timeliness[i] == True + assert availability[i] == False # Other positions stayed at their default values for i in other_positions: - assert timeliness[i] is None - assert availability[i] is None + assert timeliness[i] == None + assert availability[i] == None add_payload_vote_checks(store, block_root, test_steps) @@ -410,11 +408,11 @@ def test_on_payload_attestation_message_from_block(spec, state): # Votes landed at every PTC position each voter occupies for i, validator_index in enumerate(ptc_list): if validator_index in voter_set: - assert store.payload_timeliness_vote[block_root][i] - assert store.payload_data_availability_vote[block_root][i] + assert store.payload_timeliness_vote[block_root][i] == True + assert store.payload_data_availability_vote[block_root][i] == True else: - assert store.payload_timeliness_vote[block_root][i] is None - assert store.payload_data_availability_vote[block_root][i] is None + assert store.payload_timeliness_vote[block_root][i] == None + assert store.payload_data_availability_vote[block_root][i] == None add_payload_vote_checks(store, block_root, test_steps) yield "steps", test_steps From aaad36e98cb15da03326d052d86e65ac159d9dae Mon Sep 17 00:00:00 2001 From: Justin Traglia Date: Mon, 11 May 2026 20:13:04 -0500 Subject: [PATCH 6/6] Use more double equals --- .../test_on_payload_attestation_message.py | 24 +++++++++---------- 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/tests/core/pyspec/eth_consensus_specs/test/gloas/fork_choice/test_on_payload_attestation_message.py b/tests/core/pyspec/eth_consensus_specs/test/gloas/fork_choice/test_on_payload_attestation_message.py index 6e07baf1aac..86091cbce82 100644 --- a/tests/core/pyspec/eth_consensus_specs/test/gloas/fork_choice/test_on_payload_attestation_message.py +++ b/tests/core/pyspec/eth_consensus_specs/test/gloas/fork_choice/test_on_payload_attestation_message.py @@ -106,8 +106,8 @@ def test_on_payload_attestation_message_unknown_block_root(spec, state): ) # Vote arrays for the known block must remain at their default values - assert all(v is None for v in store.payload_timeliness_vote[block_root]) - assert all(v is None for v in store.payload_data_availability_vote[block_root]) + assert all(v == None for v in store.payload_timeliness_vote[block_root]) + assert all(v == None for v in store.payload_data_availability_vote[block_root]) add_payload_vote_checks(store, block_root, test_steps) yield "steps", test_steps @@ -135,8 +135,8 @@ def test_on_payload_attestation_message_slot_mismatch(spec, state): yield from add_payload_attestation_message(spec, store, ptc_message, test_steps) # Vote arrays must remain at their default values - assert all(v is None for v in store.payload_timeliness_vote[block_root]) - assert all(v is None for v in store.payload_data_availability_vote[block_root]) + assert all(v == None for v in store.payload_timeliness_vote[block_root]) + assert all(v == None for v in store.payload_data_availability_vote[block_root]) add_payload_vote_checks(store, block_root, test_steps) yield "steps", test_steps @@ -170,8 +170,8 @@ def test_on_payload_attestation_message_not_ptc_member(spec, state): ) # Assert rejected message didn't mutate the block vote arrays - assert all(v is None for v in store.payload_timeliness_vote[block_root]) - assert all(v is None for v in store.payload_data_availability_vote[block_root]) + assert all(v == None for v in store.payload_timeliness_vote[block_root]) + assert all(v == None for v in store.payload_data_availability_vote[block_root]) add_payload_vote_checks(store, block_root, test_steps) yield "steps", test_steps @@ -203,8 +203,8 @@ def test_on_payload_attestation_message_current_slot_and_signature(spec, state): ) # Vote arrays must remain at their default values - assert all(v is None for v in store.payload_timeliness_vote[block_root]) - assert all(v is None for v in store.payload_data_availability_vote[block_root]) + assert all(v == None for v in store.payload_timeliness_vote[block_root]) + assert all(v == None for v in store.payload_data_availability_vote[block_root]) add_payload_vote_checks(store, block_root, test_steps) # Valid signature, stale slot @@ -225,8 +225,8 @@ def test_on_payload_attestation_message_current_slot_and_signature(spec, state): ) # Rejected message must not partially mutate vote arrays - assert all(v is None for v in store.payload_timeliness_vote[block_root]) - assert all(v is None for v in store.payload_data_availability_vote[block_root]) + assert all(v == None for v in store.payload_timeliness_vote[block_root]) + assert all(v == None for v in store.payload_data_availability_vote[block_root]) add_payload_vote_checks(store, block_root, test_steps) yield "steps", test_steps @@ -241,8 +241,8 @@ def test_on_payload_attestation_message_valid(spec, state): store, block_root, block_state, ptc, test_steps = yield from _setup_test(spec, state) # Initial state: no votes recorded for this block - assert all(v is None for v in store.payload_timeliness_vote[block_root]) - assert all(v is None for v in store.payload_data_availability_vote[block_root]) + assert all(v == None for v in store.payload_timeliness_vote[block_root]) + assert all(v == None for v in store.payload_data_availability_vote[block_root]) ptc_member = ptc[0] voter_positions = [i for i, v in enumerate(ptc) if v == ptc_member]