From 132be4a2e4cd3d4956b930e28fac18f2773d94a0 Mon Sep 17 00:00:00 2001 From: Leo Lara Date: Thu, 19 Jun 2025 17:41:35 +0000 Subject: [PATCH 01/11] Add test test_simple_blob_data_peerdas --- .../test/fulu/fork_choice/test_on_block.py | 55 ++++++++++- .../eth2spec/test/helpers/fork_choice.py | 98 ++++++++++++++++--- 2 files changed, 136 insertions(+), 17 deletions(-) diff --git a/tests/core/pyspec/eth2spec/test/fulu/fork_choice/test_on_block.py b/tests/core/pyspec/eth2spec/test/fulu/fork_choice/test_on_block.py index fdead7e728..f89c7d50a2 100644 --- a/tests/core/pyspec/eth2spec/test/fulu/fork_choice/test_on_block.py +++ b/tests/core/pyspec/eth2spec/test/fulu/fork_choice/test_on_block.py @@ -1 +1,54 @@ -# TODO: add new data availability tests. + +from random import Random + +from eth2spec.test.helpers.fork_choice import get_genesis_forkchoice_store_and_block + +from eth2spec.test.context import ( + spec_state_test, + with_fulu_and_later, +) + +from eth2spec.test.deneb.fork_choice.test_on_block import get_block_with_blob +from eth2spec.test.helpers.fork_choice import BlobData, on_tick_and_append_step, tick_and_add_block_with_data +from eth2spec.test.helpers.state import state_transition_and_sign_block + +@with_fulu_and_later +@spec_state_test +def test_simple_blob_data_peerdas(spec, state): + """ + Similar to test_simple_blob_data, but in PeerDAS version that is from FULU onwards. + It covers code related to the blob sidecars because on_block calls `is_data_available` + and we are calling `get_data_column_sidecars_from_block` in the test itself. + """ + 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) + sidecars = spec.get_data_column_sidecars_from_block(signed_block, [spec.compute_cells_and_kzg_proofs(blob) for blob in blobs]) + blob_data = BlobData(blobs, blob_kzg_proofs, sidecars) + + 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 + block, blobs, blob_kzg_proofs = get_block_with_blob(spec, state, rng=rng) + signed_block = state_transition_and_sign_block(spec, state, block) + sidecars = spec.get_data_column_sidecars_from_block(signed_block, [spec.compute_cells_and_kzg_proofs(blob) for blob in blobs]) + blob_data = BlobData(blobs, blob_kzg_proofs, sidecars) + + 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() + + yield "steps", test_steps diff --git a/tests/core/pyspec/eth2spec/test/helpers/fork_choice.py b/tests/core/pyspec/eth2spec/test/helpers/fork_choice.py index 44ff4c0c26..bcdfecd871 100644 --- a/tests/core/pyspec/eth2spec/test/helpers/fork_choice.py +++ b/tests/core/pyspec/eth2spec/test/helpers/fork_choice.py @@ -1,5 +1,5 @@ from collections.abc import Sequence -from typing import Any, NamedTuple +from typing import Any, NamedTuple, Optional from eth_utils import encode_hex @@ -9,11 +9,12 @@ next_slots_with_attestations, state_transition_with_full_block, ) -from eth2spec.test.helpers.forks import is_post_eip7732 +from eth2spec.test.helpers.forks import (is_post_eip7732, is_post_fulu) from eth2spec.test.helpers.state import ( payload_state_transition, payload_state_transition_no_store, ) +from eth2spec.fulu.mainnet import DataColumnSidecar def check_head_against_root(spec, store, root): @@ -30,16 +31,33 @@ class BlobData(NamedTuple): """ blobs: Sequence[Any] - proofs: Sequence[bytes] + proofs: Optional[Sequence[bytes]] = None + sidecars: Optional[Sequence[DataColumnSidecar]] = None -def with_blob_data(spec, blob_data, func): +def with_blob_data(spec, blob_data: BlobData, func): + + if not is_post_fulu(spec): + if blob_data.proofs is None: + raise ValueError( + "blob_data.proofs must be provided when pre FULU fork" + ) + yield from with_blob_data_deneb(spec, blob_data, func) + else: + if blob_data.sidecars is None: + raise ValueError( + "blob_data.sidecars must be provided when post FULU fork" + ) + yield from with_blob_data_fulu(spec, blob_data, func) + + +def with_blob_data_deneb(spec, blob_data: BlobData, func): """ This helper runs the given ``func`` with monkeypatched ``retrieve_blobs_and_proofs`` that returns ``blob_data.blobs, blob_data.proofs``. """ - - def retrieve_blobs_and_proofs(beacon_block_root): + def retrieve_blobs_and_proofs(_): + assert blob_data.proofs is not None, "blob_data.proofs must be provided" return blob_data.blobs, blob_data.proofs retrieve_blobs_and_proofs_backup = spec.retrieve_blobs_and_proofs @@ -60,6 +78,32 @@ def wrap(flag: AtomicBoolean): spec.retrieve_blobs_and_proofs = retrieve_blobs_and_proofs_backup assert is_called.value +def with_blob_data_fulu(spec, blob_data: BlobData, func): + """ + This helper runs the given ``func`` with monkeypatched ``retrieve_column_sidecars`` + that returns ``blob_data``. + """ + def retrieve_column_sidecars(_): + assert blob_data.sidecars is not None, "blob_data.sidecars must be provided" + return blob_data.sidecars + + retrieve_column_sidecars_backup = spec.retrieve_column_sidecars + spec.retrieve_column_sidecars = retrieve_column_sidecars + + 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_column_sidecars = retrieve_column_sidecars_backup + assert is_called.value def get_anchor_root(spec, state): anchor_block_header = state.latest_block_header.copy() @@ -77,7 +121,7 @@ def tick_and_add_block( merge_block=False, block_not_found=False, is_optimistic=False, - blob_data=None, + blob_data: Optional[BlobData] =None, ): pre_state = get_store_full_state(spec, store, signed_block.message.parent_root) if merge_block: @@ -185,6 +229,21 @@ def get_blobs_file_name(blobs=None, blobs_root=None): else: return f"blobs_{encode_hex(blobs_root)}" +def get_sidecars_file_names(sidecars: Sequence[DataColumnSidecar]) -> Sequence[str]: + """ + Returns the file names for sidecars. + """ + return [ + get_sidecar_file_name(sidecar) for sidecar in sidecars + ] + +def get_sidecar_file_name(sidecar: DataColumnSidecar) -> str: + """ + Returns the file name for a single sidecar. + """ + return f"sidecar_{encode_hex(sidecar.hash_tree_root())}" + + def on_tick_and_append_step(spec, store, time, test_steps): assert time >= store.time @@ -234,18 +293,25 @@ def add_block( blobs_root = blobs.hash_tree_root() yield get_blobs_file_name(blobs_root=blobs_root), blobs + if blob_data.sidecars is not None: + for sidecar in blob_data.sidecars: + yield get_sidecar_file_name(sidecar), sidecar + 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( - { + def _append_step(valid=True): + if blob_data is not None: + step = { "block": get_block_file_name(signed_block), "blobs": get_blobs_file_name(blobs_root=blobs_root), - "proofs": [encode_hex(proof) for proof in blob_data.proofs], "valid": valid, } - ) + if blob_data.proofs is not None: + step["proofs"] = [encode_hex(proof) for proof in blob_data.proofs] + if blob_data.sidecars is not None: + step["sidecars"] = get_sidecars_file_names(blob_data.sidecars) + + test_steps.append(step) else: test_steps.append( { @@ -257,20 +323,20 @@ def _append_step(is_blob_data_test, valid=True): if not valid: if is_optimistic: run_on_block(spec, store, signed_block, valid=True) - _append_step(is_blob_data_test, valid=False) + _append_step(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 - _append_step(is_blob_data_test, valid=False) + _append_step(valid=False) return else: assert False else: run_on_block(spec, store, signed_block, valid=True) - _append_step(is_blob_data_test) + _append_step() # An on_block step implies receiving block's attestations for attestation in signed_block.message.body.attestations: From 4afdc18d17f3f3f28c6cdd50b71a4656f247a24e Mon Sep 17 00:00:00 2001 From: Leo Lara Date: Fri, 20 Jun 2025 08:05:47 +0000 Subject: [PATCH 02/11] Test is_data_available --- .../test/fulu/unittests/das/test_das.py | 45 +++++++++++++++++++ .../eth2spec/test/helpers/fork_choice.py | 4 ++ 2 files changed, 49 insertions(+) diff --git a/tests/core/pyspec/eth2spec/test/fulu/unittests/das/test_das.py b/tests/core/pyspec/eth2spec/test/fulu/unittests/das/test_das.py index 0f19548ab5..100bc258d4 100644 --- a/tests/core/pyspec/eth2spec/test/fulu/unittests/das/test_das.py +++ b/tests/core/pyspec/eth2spec/test/fulu/unittests/das/test_das.py @@ -11,6 +11,12 @@ get_sample_blob, ) +from tests.core.pyspec.eth2spec.test.context import spec_state_test +from tests.core.pyspec.eth2spec.test.deneb.fork_choice.test_on_block import get_block_with_blob +from tests.core.pyspec.eth2spec.test.helpers.fork_choice import BlobData, with_blob_data +from tests.core.pyspec.eth2spec.test.helpers.state import state_transition_and_sign_block +from tests.core.pyspec.eth2spec.utils.ssz.ssz_impl import hash_tree_root + def chunks(lst, n): """Helper that splits a list into N sized chunks.""" @@ -149,3 +155,42 @@ def test_get_extended_sample_count__table_in_spec(spec): spec.get_extended_sample_count(allowed_failures=allowed_failures) == expected_extended_sample_count ) + +@with_fulu_and_later +@spec_state_test +def test_is_data_available_peerdas(spec, state): + rng = random.Random(1234) + + block, blobs, blob_kzg_proofs = get_block_with_blob(spec, state, rng=rng) + # We need a signed block to call `get_data_column_sidecars_from_block` + signed_block = state_transition_and_sign_block(spec, state, block) + sidecars = spec.get_data_column_sidecars_from_block(signed_block, [spec.compute_cells_and_kzg_proofs(blob) for blob in blobs]) + blob_data = BlobData(blobs, blob_kzg_proofs, sidecars) + + def callback(): + yield spec.is_data_available(hash_tree_root(signed_block)) + + result = next(with_blob_data(spec, blob_data, callback)) + + assert result is True, "Data should be available for the block with blob data" + +@with_fulu_and_later +@spec_state_test +def test_is_data_available_peerdas_not_avail(spec, state): + rng = random.Random(1234) + + block, blobs, blob_kzg_proofs = get_block_with_blob(spec, state, rng=rng) + + # Empty sidecars will trigger the simulation of not enough columns being available + blob_data = BlobData(blobs, blob_kzg_proofs, []) + + def callback(): + try: + spec.is_data_available(hash_tree_root(block)) + yield False + except ValueError: + yield True + + result = next(with_blob_data(spec, blob_data, callback)) + + assert result is True, "Should throw an exception when data is not available" diff --git a/tests/core/pyspec/eth2spec/test/helpers/fork_choice.py b/tests/core/pyspec/eth2spec/test/helpers/fork_choice.py index bcdfecd871..3cb18b4171 100644 --- a/tests/core/pyspec/eth2spec/test/helpers/fork_choice.py +++ b/tests/core/pyspec/eth2spec/test/helpers/fork_choice.py @@ -85,6 +85,10 @@ def with_blob_data_fulu(spec, blob_data: BlobData, func): """ def retrieve_column_sidecars(_): assert blob_data.sidecars is not None, "blob_data.sidecars must be provided" + if len(blob_data.sidecars) == 0: + raise ValueError( + "Simulation: not all columns are available" + ) return blob_data.sidecars retrieve_column_sidecars_backup = spec.retrieve_column_sidecars From 66fff2f500cc81238c3bb311173ed43aac0e6650 Mon Sep 17 00:00:00 2001 From: Leo Lara Date: Tue, 24 Jun 2025 10:06:41 +0000 Subject: [PATCH 03/11] Add more PeerDAS tests --- pyproject.toml | 1 + .../test/deneb/fork_choice/test_on_block.py | 17 +- .../test/fulu/unittests/das/test_das.py | 278 ++++++++++++++++-- .../eth2spec/test/helpers/fork_choice.py | 2 +- 4 files changed, 269 insertions(+), 29 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index fa4ccac139..9a13ae69fc 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -35,6 +35,7 @@ test = [ "pytest-cov==6.1.1", "pytest-xdist==3.7.0", "pytest==8.4.0", + "deepdiff==8.5.0", ] lint = [ "codespell==2.4.1", diff --git a/tests/core/pyspec/eth2spec/test/deneb/fork_choice/test_on_block.py b/tests/core/pyspec/eth2spec/test/deneb/fork_choice/test_on_block.py index 4b5cdbc1ce..65e0d4c6ee 100644 --- a/tests/core/pyspec/eth2spec/test/deneb/fork_choice/test_on_block.py +++ b/tests/core/pyspec/eth2spec/test/deneb/fork_choice/test_on_block.py @@ -29,10 +29,10 @@ ) -def get_block_with_blob(spec, state, rng=None): +def get_block_with_blob(spec, state, rng=None, blob_count=1): block = build_empty_block_for_next_slot(spec, state) opaque_tx, blobs, blob_kzg_commitments, blob_kzg_proofs = get_sample_blob_tx( - spec, blob_count=1, rng=rng + spec, blob_count=blob_count, rng=rng ) block.body.execution_payload.transactions = [opaque_tx] block.body.execution_payload.block_hash = compute_el_block_hash( @@ -41,6 +41,19 @@ def get_block_with_blob(spec, state, rng=None): block.body.blob_kzg_commitments = blob_kzg_commitments return block, blobs, blob_kzg_proofs +def get_block_with_blob_and_sidecars(spec, state, rng=None, blob_count=1): + state = state.copy() + + block, blobs, blob_kzg_proofs = get_block_with_blob(spec, state, rng=rng, blob_count=blob_count) + cells_and_kzg_proofs = [spec.compute_cells_and_kzg_proofs(blob) for blob in blobs] + + # We need a signed block to call `get_data_column_sidecars_from_block` + signed_block = state_transition_and_sign_block(spec, state, block) + + sidecars = spec.get_data_column_sidecars_from_block( + signed_block, cells_and_kzg_proofs + ) + return block, blobs, blob_kzg_proofs, signed_block, sidecars # TODO(jtraglia): Use with_all_phases_from_to_except after EIP7732 is based on Fulu. # This applies to every other test in this file too. diff --git a/tests/core/pyspec/eth2spec/test/fulu/unittests/das/test_das.py b/tests/core/pyspec/eth2spec/test/fulu/unittests/das/test_das.py index 100bc258d4..a93f33b6f4 100644 --- a/tests/core/pyspec/eth2spec/test/fulu/unittests/das/test_das.py +++ b/tests/core/pyspec/eth2spec/test/fulu/unittests/das/test_das.py @@ -1,5 +1,6 @@ import random +from deepdiff import DeepDiff from eth2spec.test.context import ( expect_assertion_error, single_phase, @@ -11,11 +12,9 @@ get_sample_blob, ) -from tests.core.pyspec.eth2spec.test.context import spec_state_test -from tests.core.pyspec.eth2spec.test.deneb.fork_choice.test_on_block import get_block_with_blob -from tests.core.pyspec.eth2spec.test.helpers.fork_choice import BlobData, with_blob_data -from tests.core.pyspec.eth2spec.test.helpers.state import state_transition_and_sign_block -from tests.core.pyspec.eth2spec.utils.ssz.ssz_impl import hash_tree_root +from eth2spec.test.context import spec_state_test +from eth2spec.test.deneb.fork_choice.test_on_block import get_block_with_blob, get_block_with_blob_and_sidecars +from eth2spec.test.helpers.fork_choice import BlobData, with_blob_data def chunks(lst, n): @@ -156,41 +155,268 @@ def test_get_extended_sample_count__table_in_spec(spec): == expected_extended_sample_count ) + +def run_is_data_available_peerdas_test(spec, blob_data): + def callback(): + yield spec.is_data_available(spec.Root(b"\x00" * 32)) + + return next(with_blob_data(spec, blob_data, callback)) + +@with_fulu_and_later +@spec_state_test +def test_is_data_available_peerdas__valid(spec, state): + rng = random.Random(1234) + _, blobs, blob_kzg_proofs, _, sidecars = get_block_with_blob_and_sidecars(spec, state, rng=rng, blob_count=2) + blob_data = BlobData(blobs, blob_kzg_proofs, sidecars) + + result = run_is_data_available_peerdas_test(spec, blob_data) + + assert result is True, "Data should be available for the block with blob data" + @with_fulu_and_later @spec_state_test -def test_is_data_available_peerdas(spec, state): +def test_is_data_available_peerdas__not_avail(spec, state): rng = random.Random(1234) + _, blobs, blob_kzg_proofs = get_block_with_blob(spec, state, rng=rng, blob_count=2) + + # Empty sidecars will trigger the simulation of not enough columns being sampled + blob_data = BlobData(blobs, blob_kzg_proofs, []) - block, blobs, blob_kzg_proofs = get_block_with_blob(spec, state, rng=rng) - # We need a signed block to call `get_data_column_sidecars_from_block` - signed_block = state_transition_and_sign_block(spec, state, block) - sidecars = spec.get_data_column_sidecars_from_block(signed_block, [spec.compute_cells_and_kzg_proofs(blob) for blob in blobs]) + try: + run_is_data_available_peerdas_test(spec, blob_data) + result = False + except ValueError: + result = True + + assert result is True, "Should throw an exception when data is not available" + +@with_fulu_and_later +@spec_state_test +def test_is_data_available_peerdas__invalid_zero_blobs(spec, state): + rng = random.Random(1234) + + _, blobs, blob_kzg_proofs, _, sidecars = get_block_with_blob_and_sidecars(spec, state, rng=rng, blob_count=2) + + sidecars[0].column = [] + sidecars[0].kzg_commitments = [] + sidecars[0].kzg_proofs = [] blob_data = BlobData(blobs, blob_kzg_proofs, sidecars) - def callback(): - yield spec.is_data_available(hash_tree_root(signed_block)) + result = run_is_data_available_peerdas_test(spec, blob_data) - result = next(with_blob_data(spec, blob_data, callback)) + assert result is False, "Should return False when sidecars have zero blobs" - assert result is True, "Data should be available for the block with blob data" @with_fulu_and_later @spec_state_test -def test_is_data_available_peerdas_not_avail(spec, state): +def test_is_data_available_peerdas__invalid_index(spec, state): rng = random.Random(1234) - block, blobs, blob_kzg_proofs = get_block_with_blob(spec, state, rng=rng) + _, blobs, blob_kzg_proofs, _, sidecars = get_block_with_blob_and_sidecars(spec, state, rng=rng, blob_count=2) - # Empty sidecars will trigger the simulation of not enough columns being available - blob_data = BlobData(blobs, blob_kzg_proofs, []) + sidecars[0].index = 128 # Invalid index + blob_data = BlobData(blobs, blob_kzg_proofs, sidecars) - def callback(): - try: - spec.is_data_available(hash_tree_root(block)) - yield False - except ValueError: - yield True + result = run_is_data_available_peerdas_test(spec, blob_data) - result = next(with_blob_data(spec, blob_data, callback)) + assert result is False, "Should return False when sidecars have invalid index" + + sidecars[1].index = 256 # Invalid index + blob_data = BlobData(blobs, blob_kzg_proofs, sidecars) + + result = run_is_data_available_peerdas_test(spec, blob_data) + + assert result is False, "Should return False when sidecars have invalid index" + + +@with_fulu_and_later +@spec_state_test +def test_is_data_available_peerdas__invalid_mismatch_len_column(spec, state): + rng = random.Random(1234) + + _, blobs, blob_kzg_proofs, _, sidecars = get_block_with_blob_and_sidecars(spec, state, rng=rng, blob_count=2) + + sidecars[0].column = sidecars[0].column[1:] + blob_data = BlobData(blobs, blob_kzg_proofs, sidecars) + + result = run_is_data_available_peerdas_test(spec, blob_data) + + assert result is False, "Should return False when sidecars have mismatched length in column" + + _, blobs, blob_kzg_proofs, _, sidecars = get_block_with_blob_and_sidecars(spec, state, rng=rng, blob_count=2) + + sidecars[1].column = sidecars[1].column[1:] + blob_data = BlobData(blobs, blob_kzg_proofs, sidecars) + + result = run_is_data_available_peerdas_test(spec, blob_data) + + assert result is False, "Should return False when sidecars have mismatched length in column" + + +@with_fulu_and_later +@spec_state_test +def test_is_data_available_peerdas__invalid_mismatch_len_kzg_commitments(spec, state): + rng = random.Random(1234) + + _, blobs, blob_kzg_proofs, _, sidecars = get_block_with_blob_and_sidecars(spec, state, rng=rng, blob_count=2) + + sidecars[0].kzg_commitments = sidecars[0].kzg_commitments[1:] + blob_data = BlobData(blobs, blob_kzg_proofs, sidecars) + + result = run_is_data_available_peerdas_test(spec, blob_data) + + assert result is False, "Should return False when sidecars have mismatched length in kzg_commitments" + + _, blobs, blob_kzg_proofs, _, sidecars = get_block_with_blob_and_sidecars(spec, state, rng=rng, blob_count=2) + + sidecars[1].kzg_commitments = sidecars[1].kzg_commitments[1:] + blob_data = BlobData(blobs, blob_kzg_proofs, sidecars) + + result = run_is_data_available_peerdas_test(spec, blob_data) + + assert result is False, "Should return False when sidecars have mismatched length in kzg_commitments" + + +@with_fulu_and_later +@spec_state_test +def test_is_data_available_peerdas__invalid_mismatch_len_kzg_proofs(spec, state): + rng = random.Random(1234) + + _, blobs, blob_kzg_proofs, _, sidecars = get_block_with_blob_and_sidecars(spec, state, rng=rng, blob_count=2) + + sidecars[0].kzg_proofs = sidecars[0].kzg_proofs[1:] + blob_data = BlobData(blobs, blob_kzg_proofs, sidecars) + + result = run_is_data_available_peerdas_test(spec, blob_data) + + assert result is False, "Should return False when sidecars have mismatched length in kzg_proofs" + + _, blobs, blob_kzg_proofs, _, sidecars = get_block_with_blob_and_sidecars(spec, state, rng=rng, blob_count=2) + + sidecars[1].kzg_proofs = sidecars[1].kzg_proofs[1:] + blob_data = BlobData(blobs, blob_kzg_proofs, sidecars) + + result = run_is_data_available_peerdas_test(spec, blob_data) + + assert result is False, "Should return False when sidecars have mismatched length in kzg_proofs" + +@with_fulu_and_later +@spec_state_test +def test_is_data_available_peerdas__invalid_wrong_column(spec, state): + def flip_one_bit_in_bytes(data: bytes, index: int = 0) -> bytes: + """Flip one bit in a byte at the specified index.""" + constr = data.__class__ + + byte_index = index // 8 + bit_index = 7 - (index % 8) + byte = data[byte_index] + flipped_byte = byte ^ (1 << bit_index) + return constr(bytes(data[:byte_index]) + bytes([flipped_byte]) + bytes(data[byte_index + 1:])) + + rng = random.Random(1234) + + _, blobs, blob_kzg_proofs, _, sidecars = get_block_with_blob_and_sidecars(spec, state, rng=rng, blob_count=2) + + sidecars[0].column[0] = flip_one_bit_in_bytes(sidecars[0].column[0], 80) + blob_data = BlobData(blobs, blob_kzg_proofs, sidecars) + + result = run_is_data_available_peerdas_test(spec, blob_data) + + assert result is False, "Should return False when sidecars have wrong column data" + + _, blobs, blob_kzg_proofs, _, sidecars = get_block_with_blob_and_sidecars(spec, state, rng=rng, blob_count=2) + + sidecars[1].column[1] = flip_one_bit_in_bytes(sidecars[1].column[1], 20) + blob_data = BlobData(blobs, blob_kzg_proofs, sidecars) + + result = run_is_data_available_peerdas_test(spec, blob_data) + + assert result is False, "Should return False when sidecars have wrong column data" + + +@with_fulu_and_later +@spec_state_test +def test_is_data_available_peerdas__invalid_wrong_commitment(spec, state): + # Let's get an alternative blobs to use wrong commitments + rng = random.Random(4321) + + _, _, _, _, alt_sidecars = get_block_with_blob_and_sidecars(spec, state, rng=rng, blob_count=2) + + rng = random.Random(1234) + _, blobs, blob_kzg_proofs, _, sidecars = get_block_with_blob_and_sidecars(spec, state, rng=rng, blob_count=2) + + sidecars[0].kzg_commitments[0] = alt_sidecars[0].kzg_commitments[0] + blob_data = BlobData(blobs, blob_kzg_proofs, sidecars) + + result = run_is_data_available_peerdas_test(spec, blob_data) + + assert result is False, "Should return False when sidecars have wrong kzg_commitment data" + + _, blobs, blob_kzg_proofs, _, sidecars = get_block_with_blob_and_sidecars(spec, state, rng=rng, blob_count=2) + + sidecars[1].kzg_commitments[1] = alt_sidecars[1].kzg_commitments[1] + blob_data = BlobData(blobs, blob_kzg_proofs, sidecars) + + result = run_is_data_available_peerdas_test(spec, blob_data) + + assert result is False, "Should return False when sidecars have wrong kzg_commitment data" + + +@with_fulu_and_later +@spec_state_test +def test_is_data_available_peerdas__invalid_wrong_proof(spec, state): + # Let's get an alternative blobs to use wrong proofs + rng = random.Random(4321) + + _, _, _, _, alt_sidecars = get_block_with_blob_and_sidecars(spec, state, rng=rng, blob_count=2) + + rng = random.Random(1234) + _, blobs, blob_kzg_proofs, _, sidecars = get_block_with_blob_and_sidecars(spec, state, rng=rng, blob_count=2) + + sidecars[0].kzg_proofs[0] = alt_sidecars[0].kzg_proofs[0] + blob_data = BlobData(blobs, blob_kzg_proofs, sidecars) + + result = run_is_data_available_peerdas_test(spec, blob_data) + + assert result is False, "Should return False when sidecars have wrong kzg_commitment data" + + _, blobs, blob_kzg_proofs, _, sidecars = get_block_with_blob_and_sidecars(spec, state, rng=rng, blob_count=2) + + sidecars[1].kzg_proofs[1] = alt_sidecars[1].kzg_proofs[1] + blob_data = BlobData(blobs, blob_kzg_proofs, sidecars) + + result = run_is_data_available_peerdas_test(spec, blob_data) + + assert result is False, "Should return False when sidecars have wrong kzg_commitment data" + + +@with_fulu_and_later +@spec_state_test +def test_get_data_column_sidecars(spec, state): + rng = random.Random(1234) + _, blobs, _, signed_block, sidecars = get_block_with_blob_and_sidecars(spec, state, rng=rng, blob_count=2) + + sidecars_result = spec.get_data_column_sidecars( + signed_block_header= spec.compute_signed_block_header(signed_block), + kzg_commitments=sidecars[0].kzg_commitments, + kzg_commitments_inclusion_proof= sidecars[0].kzg_commitments_inclusion_proof, + cells_and_kzg_proofs= [spec.compute_cells_and_kzg_proofs(blob) for blob in blobs] + ) + + assert len(sidecars_result) == len(sidecars), "Should return the same number of sidecars as input" + assert DeepDiff(sidecars, sidecars_result) == {}, "Sidecars should match the expected sidecars" + +@with_fulu_and_later +@spec_state_test +def test_get_data_column_sidecars_from_column_sidecar(spec, state): + rng = random.Random(1234) + _, blobs, _, _, sidecars = get_block_with_blob_and_sidecars(spec, state, rng=rng, blob_count=2) + + sidecars_result = spec.get_data_column_sidecars_from_column_sidecar( + sidecar= sidecars[0], + cells_and_kzg_proofs= [spec.compute_cells_and_kzg_proofs(blob) for blob in blobs] + ) + + assert len(sidecars_result) == len(sidecars), "Should return the same number of sidecars as input" + assert DeepDiff(sidecars, sidecars_result) == {}, "Sidecars should match the expected sidecars" - assert result is True, "Should throw an exception when data is not available" diff --git a/tests/core/pyspec/eth2spec/test/helpers/fork_choice.py b/tests/core/pyspec/eth2spec/test/helpers/fork_choice.py index 3cb18b4171..415e9a9060 100644 --- a/tests/core/pyspec/eth2spec/test/helpers/fork_choice.py +++ b/tests/core/pyspec/eth2spec/test/helpers/fork_choice.py @@ -87,7 +87,7 @@ def retrieve_column_sidecars(_): assert blob_data.sidecars is not None, "blob_data.sidecars must be provided" if len(blob_data.sidecars) == 0: raise ValueError( - "Simulation: not all columns are available" + "Simulation: not all required columns have been sampled" ) return blob_data.sidecars From c0eb0b5defaa1f31be430960f51801fcf803148d Mon Sep 17 00:00:00 2001 From: Leo Lara Date: Tue, 24 Jun 2025 10:11:30 +0000 Subject: [PATCH 04/11] lint --- .../test/deneb/fork_choice/test_on_block.py | 6 +- .../test/fulu/fork_choice/test_on_block.py | 20 ++-- .../test/fulu/unittests/das/test_das.py | 113 +++++++++++++----- .../eth2spec/test/helpers/fork_choice.py | 46 ++++--- 4 files changed, 117 insertions(+), 68 deletions(-) diff --git a/tests/core/pyspec/eth2spec/test/deneb/fork_choice/test_on_block.py b/tests/core/pyspec/eth2spec/test/deneb/fork_choice/test_on_block.py index 65e0d4c6ee..f3461da7a9 100644 --- a/tests/core/pyspec/eth2spec/test/deneb/fork_choice/test_on_block.py +++ b/tests/core/pyspec/eth2spec/test/deneb/fork_choice/test_on_block.py @@ -41,6 +41,7 @@ def get_block_with_blob(spec, state, rng=None, blob_count=1): block.body.blob_kzg_commitments = blob_kzg_commitments return block, blobs, blob_kzg_proofs + def get_block_with_blob_and_sidecars(spec, state, rng=None, blob_count=1): state = state.copy() @@ -50,11 +51,10 @@ def get_block_with_blob_and_sidecars(spec, state, rng=None, blob_count=1): # We need a signed block to call `get_data_column_sidecars_from_block` signed_block = state_transition_and_sign_block(spec, state, block) - sidecars = spec.get_data_column_sidecars_from_block( - signed_block, cells_and_kzg_proofs - ) + sidecars = spec.get_data_column_sidecars_from_block(signed_block, cells_and_kzg_proofs) return block, blobs, blob_kzg_proofs, signed_block, sidecars + # TODO(jtraglia): Use with_all_phases_from_to_except after EIP7732 is based on Fulu. # This applies to every other test in this file too. @with_all_phases_from_except(DENEB, [FULU, EIP7732]) diff --git a/tests/core/pyspec/eth2spec/test/fulu/fork_choice/test_on_block.py b/tests/core/pyspec/eth2spec/test/fulu/fork_choice/test_on_block.py index f89c7d50a2..37b34b3cbd 100644 --- a/tests/core/pyspec/eth2spec/test/fulu/fork_choice/test_on_block.py +++ b/tests/core/pyspec/eth2spec/test/fulu/fork_choice/test_on_block.py @@ -1,17 +1,19 @@ - from random import Random -from eth2spec.test.helpers.fork_choice import get_genesis_forkchoice_store_and_block - from eth2spec.test.context import ( spec_state_test, with_fulu_and_later, ) - from eth2spec.test.deneb.fork_choice.test_on_block import get_block_with_blob -from eth2spec.test.helpers.fork_choice import BlobData, on_tick_and_append_step, tick_and_add_block_with_data +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 + @with_fulu_and_later @spec_state_test def test_simple_blob_data_peerdas(spec, state): @@ -34,7 +36,9 @@ def test_simple_blob_data_peerdas(spec, state): # 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) - sidecars = spec.get_data_column_sidecars_from_block(signed_block, [spec.compute_cells_and_kzg_proofs(blob) for blob in blobs]) + sidecars = spec.get_data_column_sidecars_from_block( + signed_block, [spec.compute_cells_and_kzg_proofs(blob) for blob in blobs] + ) blob_data = BlobData(blobs, blob_kzg_proofs, sidecars) yield from tick_and_add_block_with_data(spec, store, signed_block, test_steps, blob_data) @@ -44,7 +48,9 @@ def test_simple_blob_data_peerdas(spec, state): # On receiving a block of next epoch block, blobs, blob_kzg_proofs = get_block_with_blob(spec, state, rng=rng) signed_block = state_transition_and_sign_block(spec, state, block) - sidecars = spec.get_data_column_sidecars_from_block(signed_block, [spec.compute_cells_and_kzg_proofs(blob) for blob in blobs]) + sidecars = spec.get_data_column_sidecars_from_block( + signed_block, [spec.compute_cells_and_kzg_proofs(blob) for blob in blobs] + ) blob_data = BlobData(blobs, blob_kzg_proofs, sidecars) yield from tick_and_add_block_with_data(spec, store, signed_block, test_steps, blob_data) diff --git a/tests/core/pyspec/eth2spec/test/fulu/unittests/das/test_das.py b/tests/core/pyspec/eth2spec/test/fulu/unittests/das/test_das.py index a93f33b6f4..f15c3f8ee7 100644 --- a/tests/core/pyspec/eth2spec/test/fulu/unittests/das/test_das.py +++ b/tests/core/pyspec/eth2spec/test/fulu/unittests/das/test_das.py @@ -1,19 +1,22 @@ import random from deepdiff import DeepDiff + from eth2spec.test.context import ( expect_assertion_error, single_phase, + spec_state_test, spec_test, with_config_overrides, with_fulu_and_later, ) +from eth2spec.test.deneb.fork_choice.test_on_block import ( + get_block_with_blob, + get_block_with_blob_and_sidecars, +) from eth2spec.test.helpers.blob import ( get_sample_blob, ) - -from eth2spec.test.context import spec_state_test -from eth2spec.test.deneb.fork_choice.test_on_block import get_block_with_blob, get_block_with_blob_and_sidecars from eth2spec.test.helpers.fork_choice import BlobData, with_blob_data @@ -162,17 +165,21 @@ def callback(): return next(with_blob_data(spec, blob_data, callback)) + @with_fulu_and_later @spec_state_test def test_is_data_available_peerdas__valid(spec, state): rng = random.Random(1234) - _, blobs, blob_kzg_proofs, _, sidecars = get_block_with_blob_and_sidecars(spec, state, rng=rng, blob_count=2) + _, blobs, blob_kzg_proofs, _, sidecars = get_block_with_blob_and_sidecars( + spec, state, rng=rng, blob_count=2 + ) blob_data = BlobData(blobs, blob_kzg_proofs, sidecars) result = run_is_data_available_peerdas_test(spec, blob_data) assert result is True, "Data should be available for the block with blob data" + @with_fulu_and_later @spec_state_test def test_is_data_available_peerdas__not_avail(spec, state): @@ -190,12 +197,15 @@ def test_is_data_available_peerdas__not_avail(spec, state): assert result is True, "Should throw an exception when data is not available" + @with_fulu_and_later @spec_state_test def test_is_data_available_peerdas__invalid_zero_blobs(spec, state): rng = random.Random(1234) - _, blobs, blob_kzg_proofs, _, sidecars = get_block_with_blob_and_sidecars(spec, state, rng=rng, blob_count=2) + _, blobs, blob_kzg_proofs, _, sidecars = get_block_with_blob_and_sidecars( + spec, state, rng=rng, blob_count=2 + ) sidecars[0].column = [] sidecars[0].kzg_commitments = [] @@ -212,16 +222,18 @@ def test_is_data_available_peerdas__invalid_zero_blobs(spec, state): def test_is_data_available_peerdas__invalid_index(spec, state): rng = random.Random(1234) - _, blobs, blob_kzg_proofs, _, sidecars = get_block_with_blob_and_sidecars(spec, state, rng=rng, blob_count=2) + _, blobs, blob_kzg_proofs, _, sidecars = get_block_with_blob_and_sidecars( + spec, state, rng=rng, blob_count=2 + ) - sidecars[0].index = 128 # Invalid index + sidecars[0].index = 128 # Invalid index blob_data = BlobData(blobs, blob_kzg_proofs, sidecars) result = run_is_data_available_peerdas_test(spec, blob_data) assert result is False, "Should return False when sidecars have invalid index" - sidecars[1].index = 256 # Invalid index + sidecars[1].index = 256 # Invalid index blob_data = BlobData(blobs, blob_kzg_proofs, sidecars) result = run_is_data_available_peerdas_test(spec, blob_data) @@ -234,7 +246,9 @@ def test_is_data_available_peerdas__invalid_index(spec, state): def test_is_data_available_peerdas__invalid_mismatch_len_column(spec, state): rng = random.Random(1234) - _, blobs, blob_kzg_proofs, _, sidecars = get_block_with_blob_and_sidecars(spec, state, rng=rng, blob_count=2) + _, blobs, blob_kzg_proofs, _, sidecars = get_block_with_blob_and_sidecars( + spec, state, rng=rng, blob_count=2 + ) sidecars[0].column = sidecars[0].column[1:] blob_data = BlobData(blobs, blob_kzg_proofs, sidecars) @@ -243,7 +257,9 @@ def test_is_data_available_peerdas__invalid_mismatch_len_column(spec, state): assert result is False, "Should return False when sidecars have mismatched length in column" - _, blobs, blob_kzg_proofs, _, sidecars = get_block_with_blob_and_sidecars(spec, state, rng=rng, blob_count=2) + _, blobs, blob_kzg_proofs, _, sidecars = get_block_with_blob_and_sidecars( + spec, state, rng=rng, blob_count=2 + ) sidecars[1].column = sidecars[1].column[1:] blob_data = BlobData(blobs, blob_kzg_proofs, sidecars) @@ -258,23 +274,31 @@ def test_is_data_available_peerdas__invalid_mismatch_len_column(spec, state): def test_is_data_available_peerdas__invalid_mismatch_len_kzg_commitments(spec, state): rng = random.Random(1234) - _, blobs, blob_kzg_proofs, _, sidecars = get_block_with_blob_and_sidecars(spec, state, rng=rng, blob_count=2) + _, blobs, blob_kzg_proofs, _, sidecars = get_block_with_blob_and_sidecars( + spec, state, rng=rng, blob_count=2 + ) sidecars[0].kzg_commitments = sidecars[0].kzg_commitments[1:] blob_data = BlobData(blobs, blob_kzg_proofs, sidecars) result = run_is_data_available_peerdas_test(spec, blob_data) - assert result is False, "Should return False when sidecars have mismatched length in kzg_commitments" + assert result is False, ( + "Should return False when sidecars have mismatched length in kzg_commitments" + ) - _, blobs, blob_kzg_proofs, _, sidecars = get_block_with_blob_and_sidecars(spec, state, rng=rng, blob_count=2) + _, blobs, blob_kzg_proofs, _, sidecars = get_block_with_blob_and_sidecars( + spec, state, rng=rng, blob_count=2 + ) sidecars[1].kzg_commitments = sidecars[1].kzg_commitments[1:] blob_data = BlobData(blobs, blob_kzg_proofs, sidecars) result = run_is_data_available_peerdas_test(spec, blob_data) - assert result is False, "Should return False when sidecars have mismatched length in kzg_commitments" + assert result is False, ( + "Should return False when sidecars have mismatched length in kzg_commitments" + ) @with_fulu_and_later @@ -282,7 +306,9 @@ def test_is_data_available_peerdas__invalid_mismatch_len_kzg_commitments(spec, s def test_is_data_available_peerdas__invalid_mismatch_len_kzg_proofs(spec, state): rng = random.Random(1234) - _, blobs, blob_kzg_proofs, _, sidecars = get_block_with_blob_and_sidecars(spec, state, rng=rng, blob_count=2) + _, blobs, blob_kzg_proofs, _, sidecars = get_block_with_blob_and_sidecars( + spec, state, rng=rng, blob_count=2 + ) sidecars[0].kzg_proofs = sidecars[0].kzg_proofs[1:] blob_data = BlobData(blobs, blob_kzg_proofs, sidecars) @@ -291,7 +317,9 @@ def test_is_data_available_peerdas__invalid_mismatch_len_kzg_proofs(spec, state) assert result is False, "Should return False when sidecars have mismatched length in kzg_proofs" - _, blobs, blob_kzg_proofs, _, sidecars = get_block_with_blob_and_sidecars(spec, state, rng=rng, blob_count=2) + _, blobs, blob_kzg_proofs, _, sidecars = get_block_with_blob_and_sidecars( + spec, state, rng=rng, blob_count=2 + ) sidecars[1].kzg_proofs = sidecars[1].kzg_proofs[1:] blob_data = BlobData(blobs, blob_kzg_proofs, sidecars) @@ -300,6 +328,7 @@ def test_is_data_available_peerdas__invalid_mismatch_len_kzg_proofs(spec, state) assert result is False, "Should return False when sidecars have mismatched length in kzg_proofs" + @with_fulu_and_later @spec_state_test def test_is_data_available_peerdas__invalid_wrong_column(spec, state): @@ -311,11 +340,15 @@ def flip_one_bit_in_bytes(data: bytes, index: int = 0) -> bytes: bit_index = 7 - (index % 8) byte = data[byte_index] flipped_byte = byte ^ (1 << bit_index) - return constr(bytes(data[:byte_index]) + bytes([flipped_byte]) + bytes(data[byte_index + 1:])) + return constr( + bytes(data[:byte_index]) + bytes([flipped_byte]) + bytes(data[byte_index + 1 :]) + ) rng = random.Random(1234) - _, blobs, blob_kzg_proofs, _, sidecars = get_block_with_blob_and_sidecars(spec, state, rng=rng, blob_count=2) + _, blobs, blob_kzg_proofs, _, sidecars = get_block_with_blob_and_sidecars( + spec, state, rng=rng, blob_count=2 + ) sidecars[0].column[0] = flip_one_bit_in_bytes(sidecars[0].column[0], 80) blob_data = BlobData(blobs, blob_kzg_proofs, sidecars) @@ -324,7 +357,9 @@ def flip_one_bit_in_bytes(data: bytes, index: int = 0) -> bytes: assert result is False, "Should return False when sidecars have wrong column data" - _, blobs, blob_kzg_proofs, _, sidecars = get_block_with_blob_and_sidecars(spec, state, rng=rng, blob_count=2) + _, blobs, blob_kzg_proofs, _, sidecars = get_block_with_blob_and_sidecars( + spec, state, rng=rng, blob_count=2 + ) sidecars[1].column[1] = flip_one_bit_in_bytes(sidecars[1].column[1], 20) blob_data = BlobData(blobs, blob_kzg_proofs, sidecars) @@ -343,7 +378,9 @@ def test_is_data_available_peerdas__invalid_wrong_commitment(spec, state): _, _, _, _, alt_sidecars = get_block_with_blob_and_sidecars(spec, state, rng=rng, blob_count=2) rng = random.Random(1234) - _, blobs, blob_kzg_proofs, _, sidecars = get_block_with_blob_and_sidecars(spec, state, rng=rng, blob_count=2) + _, blobs, blob_kzg_proofs, _, sidecars = get_block_with_blob_and_sidecars( + spec, state, rng=rng, blob_count=2 + ) sidecars[0].kzg_commitments[0] = alt_sidecars[0].kzg_commitments[0] blob_data = BlobData(blobs, blob_kzg_proofs, sidecars) @@ -352,7 +389,9 @@ def test_is_data_available_peerdas__invalid_wrong_commitment(spec, state): assert result is False, "Should return False when sidecars have wrong kzg_commitment data" - _, blobs, blob_kzg_proofs, _, sidecars = get_block_with_blob_and_sidecars(spec, state, rng=rng, blob_count=2) + _, blobs, blob_kzg_proofs, _, sidecars = get_block_with_blob_and_sidecars( + spec, state, rng=rng, blob_count=2 + ) sidecars[1].kzg_commitments[1] = alt_sidecars[1].kzg_commitments[1] blob_data = BlobData(blobs, blob_kzg_proofs, sidecars) @@ -371,7 +410,9 @@ def test_is_data_available_peerdas__invalid_wrong_proof(spec, state): _, _, _, _, alt_sidecars = get_block_with_blob_and_sidecars(spec, state, rng=rng, blob_count=2) rng = random.Random(1234) - _, blobs, blob_kzg_proofs, _, sidecars = get_block_with_blob_and_sidecars(spec, state, rng=rng, blob_count=2) + _, blobs, blob_kzg_proofs, _, sidecars = get_block_with_blob_and_sidecars( + spec, state, rng=rng, blob_count=2 + ) sidecars[0].kzg_proofs[0] = alt_sidecars[0].kzg_proofs[0] blob_data = BlobData(blobs, blob_kzg_proofs, sidecars) @@ -380,7 +421,9 @@ def test_is_data_available_peerdas__invalid_wrong_proof(spec, state): assert result is False, "Should return False when sidecars have wrong kzg_commitment data" - _, blobs, blob_kzg_proofs, _, sidecars = get_block_with_blob_and_sidecars(spec, state, rng=rng, blob_count=2) + _, blobs, blob_kzg_proofs, _, sidecars = get_block_with_blob_and_sidecars( + spec, state, rng=rng, blob_count=2 + ) sidecars[1].kzg_proofs[1] = alt_sidecars[1].kzg_proofs[1] blob_data = BlobData(blobs, blob_kzg_proofs, sidecars) @@ -394,18 +437,23 @@ def test_is_data_available_peerdas__invalid_wrong_proof(spec, state): @spec_state_test def test_get_data_column_sidecars(spec, state): rng = random.Random(1234) - _, blobs, _, signed_block, sidecars = get_block_with_blob_and_sidecars(spec, state, rng=rng, blob_count=2) + _, blobs, _, signed_block, sidecars = get_block_with_blob_and_sidecars( + spec, state, rng=rng, blob_count=2 + ) sidecars_result = spec.get_data_column_sidecars( - signed_block_header= spec.compute_signed_block_header(signed_block), + signed_block_header=spec.compute_signed_block_header(signed_block), kzg_commitments=sidecars[0].kzg_commitments, - kzg_commitments_inclusion_proof= sidecars[0].kzg_commitments_inclusion_proof, - cells_and_kzg_proofs= [spec.compute_cells_and_kzg_proofs(blob) for blob in blobs] + kzg_commitments_inclusion_proof=sidecars[0].kzg_commitments_inclusion_proof, + cells_and_kzg_proofs=[spec.compute_cells_and_kzg_proofs(blob) for blob in blobs], ) - assert len(sidecars_result) == len(sidecars), "Should return the same number of sidecars as input" + assert len(sidecars_result) == len(sidecars), ( + "Should return the same number of sidecars as input" + ) assert DeepDiff(sidecars, sidecars_result) == {}, "Sidecars should match the expected sidecars" + @with_fulu_and_later @spec_state_test def test_get_data_column_sidecars_from_column_sidecar(spec, state): @@ -413,10 +461,11 @@ def test_get_data_column_sidecars_from_column_sidecar(spec, state): _, blobs, _, _, sidecars = get_block_with_blob_and_sidecars(spec, state, rng=rng, blob_count=2) sidecars_result = spec.get_data_column_sidecars_from_column_sidecar( - sidecar= sidecars[0], - cells_and_kzg_proofs= [spec.compute_cells_and_kzg_proofs(blob) for blob in blobs] + sidecar=sidecars[0], + cells_and_kzg_proofs=[spec.compute_cells_and_kzg_proofs(blob) for blob in blobs], ) - assert len(sidecars_result) == len(sidecars), "Should return the same number of sidecars as input" + assert len(sidecars_result) == len(sidecars), ( + "Should return the same number of sidecars as input" + ) assert DeepDiff(sidecars, sidecars_result) == {}, "Sidecars should match the expected sidecars" - diff --git a/tests/core/pyspec/eth2spec/test/helpers/fork_choice.py b/tests/core/pyspec/eth2spec/test/helpers/fork_choice.py index 415e9a9060..c180954cbd 100644 --- a/tests/core/pyspec/eth2spec/test/helpers/fork_choice.py +++ b/tests/core/pyspec/eth2spec/test/helpers/fork_choice.py @@ -1,20 +1,20 @@ from collections.abc import Sequence -from typing import Any, NamedTuple, Optional +from typing import Any, NamedTuple from eth_utils import encode_hex +from eth2spec.fulu.mainnet import DataColumnSidecar from eth2spec.test.exceptions import BlockNotFoundException from eth2spec.test.helpers.attestations import ( next_epoch_with_attestations, next_slots_with_attestations, state_transition_with_full_block, ) -from eth2spec.test.helpers.forks import (is_post_eip7732, is_post_fulu) +from eth2spec.test.helpers.forks import is_post_eip7732, is_post_fulu from eth2spec.test.helpers.state import ( payload_state_transition, payload_state_transition_no_store, ) -from eth2spec.fulu.mainnet import DataColumnSidecar def check_head_against_root(spec, store, root): @@ -31,23 +31,18 @@ class BlobData(NamedTuple): """ blobs: Sequence[Any] - proofs: Optional[Sequence[bytes]] = None - sidecars: Optional[Sequence[DataColumnSidecar]] = None + proofs: Sequence[bytes] | None = None + sidecars: Sequence[DataColumnSidecar] | None = None def with_blob_data(spec, blob_data: BlobData, func): - if not is_post_fulu(spec): if blob_data.proofs is None: - raise ValueError( - "blob_data.proofs must be provided when pre FULU fork" - ) + raise ValueError("blob_data.proofs must be provided when pre FULU fork") yield from with_blob_data_deneb(spec, blob_data, func) else: if blob_data.sidecars is None: - raise ValueError( - "blob_data.sidecars must be provided when post FULU fork" - ) + raise ValueError("blob_data.sidecars must be provided when post FULU fork") yield from with_blob_data_fulu(spec, blob_data, func) @@ -56,6 +51,7 @@ def with_blob_data_deneb(spec, blob_data: BlobData, func): This helper runs the given ``func`` with monkeypatched ``retrieve_blobs_and_proofs`` that returns ``blob_data.blobs, blob_data.proofs``. """ + def retrieve_blobs_and_proofs(_): assert blob_data.proofs is not None, "blob_data.proofs must be provided" return blob_data.blobs, blob_data.proofs @@ -78,17 +74,17 @@ def wrap(flag: AtomicBoolean): spec.retrieve_blobs_and_proofs = retrieve_blobs_and_proofs_backup assert is_called.value + def with_blob_data_fulu(spec, blob_data: BlobData, func): """ This helper runs the given ``func`` with monkeypatched ``retrieve_column_sidecars`` that returns ``blob_data``. """ + def retrieve_column_sidecars(_): assert blob_data.sidecars is not None, "blob_data.sidecars must be provided" if len(blob_data.sidecars) == 0: - raise ValueError( - "Simulation: not all required columns have been sampled" - ) + raise ValueError("Simulation: not all required columns have been sampled") return blob_data.sidecars retrieve_column_sidecars_backup = spec.retrieve_column_sidecars @@ -109,6 +105,7 @@ def wrap(flag: AtomicBoolean): spec.retrieve_column_sidecars = retrieve_column_sidecars_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(): @@ -125,7 +122,7 @@ def tick_and_add_block( merge_block=False, block_not_found=False, is_optimistic=False, - blob_data: Optional[BlobData] =None, + blob_data: BlobData | None = None, ): pre_state = get_store_full_state(spec, store, signed_block.message.parent_root) if merge_block: @@ -233,13 +230,13 @@ def get_blobs_file_name(blobs=None, blobs_root=None): else: return f"blobs_{encode_hex(blobs_root)}" + def get_sidecars_file_names(sidecars: Sequence[DataColumnSidecar]) -> Sequence[str]: """ Returns the file names for sidecars. """ - return [ - get_sidecar_file_name(sidecar) for sidecar in sidecars - ] + return [get_sidecar_file_name(sidecar) for sidecar in sidecars] + def get_sidecar_file_name(sidecar: DataColumnSidecar) -> str: """ @@ -248,7 +245,6 @@ def get_sidecar_file_name(sidecar: DataColumnSidecar) -> str: return f"sidecar_{encode_hex(sidecar.hash_tree_root())}" - def on_tick_and_append_step(spec, store, time, test_steps): assert time >= store.time spec.on_tick(store, time) @@ -301,15 +297,13 @@ def add_block( for sidecar in blob_data.sidecars: yield get_sidecar_file_name(sidecar), sidecar - is_blob_data_test = blob_data is not None - def _append_step(valid=True): if blob_data is not None: step = { - "block": get_block_file_name(signed_block), - "blobs": get_blobs_file_name(blobs_root=blobs_root), - "valid": valid, - } + "block": get_block_file_name(signed_block), + "blobs": get_blobs_file_name(blobs_root=blobs_root), + "valid": valid, + } if blob_data.proofs is not None: step["proofs"] = [encode_hex(proof) for proof in blob_data.proofs] if blob_data.sidecars is not None: From 187256f7bc9c521cf083875597237e3df9cb5762 Mon Sep 17 00:00:00 2001 From: Leo Lara Date: Tue, 24 Jun 2025 10:17:51 +0000 Subject: [PATCH 05/11] Add explanaition to fork_choice format about sidecards --- tests/formats/fork_choice/README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/formats/fork_choice/README.md b/tests/formats/fork_choice/README.md index 3966816fec..60fdd084b3 100644 --- a/tests/formats/fork_choice/README.md +++ b/tests/formats/fork_choice/README.md @@ -84,6 +84,7 @@ The parameter that is required for executing `on_block(store, block)`. blobs: string -- optional, the name of the `blobs_<32-byte-root>.ssz_snappy` file. The blobs file content is a `List[Blob, MAX_BLOB_COMMITMENTS_PER_BLOCK]` SSZ object. proofs: array of byte48 hex string -- optional, the proofs of blob commitments. + sidecars: string -- optional, array of the names of the `sidecar_<32-byte-root>.ssz_snappy` files. valid: bool -- optional, default to `true`. If it's `false`, this execution step is expected to be invalid. } From ec0640ecaef9dfde404e79ca72be56799ade1527 Mon Sep 17 00:00:00 2001 From: Leo Lara Date: Wed, 25 Jun 2025 15:42:26 +0700 Subject: [PATCH 06/11] Update tests/core/pyspec/eth2spec/test/fulu/unittests/das/test_das.py Co-authored-by: Justin Traglia <95511699+jtraglia@users.noreply.github.com> --- tests/core/pyspec/eth2spec/test/fulu/unittests/das/test_das.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/core/pyspec/eth2spec/test/fulu/unittests/das/test_das.py b/tests/core/pyspec/eth2spec/test/fulu/unittests/das/test_das.py index f15c3f8ee7..c5c30c36df 100644 --- a/tests/core/pyspec/eth2spec/test/fulu/unittests/das/test_das.py +++ b/tests/core/pyspec/eth2spec/test/fulu/unittests/das/test_das.py @@ -182,7 +182,7 @@ def test_is_data_available_peerdas__valid(spec, state): @with_fulu_and_later @spec_state_test -def test_is_data_available_peerdas__not_avail(spec, state): +def test_is_data_available_peerdas__not_available(spec, state): rng = random.Random(1234) _, blobs, blob_kzg_proofs = get_block_with_blob(spec, state, rng=rng, blob_count=2) From cd85037de7eff251204bfdda2f6bfbaab4485a01 Mon Sep 17 00:00:00 2001 From: Leo Lara Date: Wed, 25 Jun 2025 15:42:55 +0700 Subject: [PATCH 07/11] Update tests/core/pyspec/eth2spec/test/fulu/fork_choice/test_on_block.py Co-authored-by: Justin Traglia <95511699+jtraglia@users.noreply.github.com> --- .../core/pyspec/eth2spec/test/fulu/fork_choice/test_on_block.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/core/pyspec/eth2spec/test/fulu/fork_choice/test_on_block.py b/tests/core/pyspec/eth2spec/test/fulu/fork_choice/test_on_block.py index 37b34b3cbd..124e14c5dc 100644 --- a/tests/core/pyspec/eth2spec/test/fulu/fork_choice/test_on_block.py +++ b/tests/core/pyspec/eth2spec/test/fulu/fork_choice/test_on_block.py @@ -18,7 +18,7 @@ @spec_state_test def test_simple_blob_data_peerdas(spec, state): """ - Similar to test_simple_blob_data, but in PeerDAS version that is from FULU onwards. + Similar to test_simple_blob_data, but in PeerDAS version that is from Fulu onwards. It covers code related to the blob sidecars because on_block calls `is_data_available` and we are calling `get_data_column_sidecars_from_block` in the test itself. """ From 669fb91f233a14153124bf9987437ef8f905494c Mon Sep 17 00:00:00 2001 From: Leo Lara Date: Thu, 26 Jun 2025 09:23:27 +0000 Subject: [PATCH 08/11] Move blob helpers to right file --- .../test/deneb/fork_choice/test_on_block.py | 36 +------------------ .../test/fulu/fork_choice/test_on_block.py | 2 +- .../test/fulu/unittests/das/test_das.py | 2 +- .../core/pyspec/eth2spec/test/helpers/blob.py | 30 ++++++++++++++++ 4 files changed, 33 insertions(+), 37 deletions(-) diff --git a/tests/core/pyspec/eth2spec/test/deneb/fork_choice/test_on_block.py b/tests/core/pyspec/eth2spec/test/deneb/fork_choice/test_on_block.py index f3461da7a9..bf34138f21 100644 --- a/tests/core/pyspec/eth2spec/test/deneb/fork_choice/test_on_block.py +++ b/tests/core/pyspec/eth2spec/test/deneb/fork_choice/test_on_block.py @@ -4,20 +4,11 @@ spec_state_test, with_all_phases_from_except, ) -from eth2spec.test.helpers.blob import ( - get_sample_blob_tx, -) -from eth2spec.test.helpers.block import ( - build_empty_block_for_next_slot, -) from eth2spec.test.helpers.constants import ( DENEB, EIP7732, FULU, ) -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, @@ -27,32 +18,7 @@ from eth2spec.test.helpers.state import ( state_transition_and_sign_block, ) - - -def get_block_with_blob(spec, state, rng=None, blob_count=1): - block = build_empty_block_for_next_slot(spec, state) - opaque_tx, blobs, blob_kzg_commitments, blob_kzg_proofs = get_sample_blob_tx( - spec, blob_count=blob_count, rng=rng - ) - block.body.execution_payload.transactions = [opaque_tx] - block.body.execution_payload.block_hash = compute_el_block_hash( - spec, block.body.execution_payload, state - ) - block.body.blob_kzg_commitments = blob_kzg_commitments - return block, blobs, blob_kzg_proofs - - -def get_block_with_blob_and_sidecars(spec, state, rng=None, blob_count=1): - state = state.copy() - - block, blobs, blob_kzg_proofs = get_block_with_blob(spec, state, rng=rng, blob_count=blob_count) - cells_and_kzg_proofs = [spec.compute_cells_and_kzg_proofs(blob) for blob in blobs] - - # We need a signed block to call `get_data_column_sidecars_from_block` - signed_block = state_transition_and_sign_block(spec, state, block) - - sidecars = spec.get_data_column_sidecars_from_block(signed_block, cells_and_kzg_proofs) - return block, blobs, blob_kzg_proofs, signed_block, sidecars +from eth2spec.test.helpers.blob import get_block_with_blob # TODO(jtraglia): Use with_all_phases_from_to_except after EIP7732 is based on Fulu. diff --git a/tests/core/pyspec/eth2spec/test/fulu/fork_choice/test_on_block.py b/tests/core/pyspec/eth2spec/test/fulu/fork_choice/test_on_block.py index 124e14c5dc..5710abfc93 100644 --- a/tests/core/pyspec/eth2spec/test/fulu/fork_choice/test_on_block.py +++ b/tests/core/pyspec/eth2spec/test/fulu/fork_choice/test_on_block.py @@ -4,7 +4,7 @@ spec_state_test, with_fulu_and_later, ) -from eth2spec.test.deneb.fork_choice.test_on_block import get_block_with_blob +from eth2spec.test.helpers.blob import get_block_with_blob from eth2spec.test.helpers.fork_choice import ( BlobData, get_genesis_forkchoice_store_and_block, diff --git a/tests/core/pyspec/eth2spec/test/fulu/unittests/das/test_das.py b/tests/core/pyspec/eth2spec/test/fulu/unittests/das/test_das.py index c5c30c36df..0d4b47eb57 100644 --- a/tests/core/pyspec/eth2spec/test/fulu/unittests/das/test_das.py +++ b/tests/core/pyspec/eth2spec/test/fulu/unittests/das/test_das.py @@ -10,7 +10,7 @@ with_config_overrides, with_fulu_and_later, ) -from eth2spec.test.deneb.fork_choice.test_on_block import ( +from eth2spec.test.helpers.blob import ( get_block_with_blob, get_block_with_blob_and_sidecars, ) diff --git a/tests/core/pyspec/eth2spec/test/helpers/blob.py b/tests/core/pyspec/eth2spec/test/helpers/blob.py index 2cb37af643..f9faeaf068 100644 --- a/tests/core/pyspec/eth2spec/test/helpers/blob.py +++ b/tests/core/pyspec/eth2spec/test/helpers/blob.py @@ -1,12 +1,16 @@ import random +from random import Random from rlp import encode, Serializable from rlp.sedes import big_endian_int, Binary, binary, CountableList, List as RLPList +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.forks import ( is_post_electra, is_post_fulu, ) +from eth2spec.test.helpers.state import state_transition_and_sign_block class Eip4844RlpTransaction(Serializable): @@ -126,3 +130,29 @@ def get_max_blob_count(spec, state): return spec.config.MAX_BLOBS_PER_BLOCK_ELECTRA else: return spec.config.MAX_BLOBS_PER_BLOCK + + +def get_block_with_blob(spec, state, rng: Random | None = None, blob_count=1): + block = build_empty_block_for_next_slot(spec, state) + opaque_tx, blobs, blob_kzg_commitments, blob_kzg_proofs = get_sample_blob_tx( + spec, blob_count=blob_count, rng=rng or random.Random(5566) + ) + block.body.execution_payload.transactions = [opaque_tx] + block.body.execution_payload.block_hash = compute_el_block_hash( + spec, block.body.execution_payload, state + ) + block.body.blob_kzg_commitments = blob_kzg_commitments + return block, blobs, blob_kzg_proofs + + +def get_block_with_blob_and_sidecars(spec, state, rng=None, blob_count=1): + state = state.copy() + + block, blobs, blob_kzg_proofs = get_block_with_blob(spec, state, rng=rng, blob_count=blob_count) + cells_and_kzg_proofs = [spec.compute_cells_and_kzg_proofs(blob) for blob in blobs] + + # We need a signed block to call `get_data_column_sidecars_from_block` + signed_block = state_transition_and_sign_block(spec, state, block) + + sidecars = spec.get_data_column_sidecars_from_block(signed_block, cells_and_kzg_proofs) + return block, blobs, blob_kzg_proofs, signed_block, sidecars From 372d58a549a46245075714c4ed27b141def5a702 Mon Sep 17 00:00:00 2001 From: Leo Lara Date: Thu, 26 Jun 2025 09:29:23 +0000 Subject: [PATCH 09/11] Sort dependencies --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index f49eaa144a..7da5d185fe 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -32,10 +32,10 @@ dependencies = [ [project.optional-dependencies] test = [ + "deepdiff==8.5.0", "pytest-cov==6.2.1", "pytest-xdist==3.7.0", "pytest==8.4.0", - "deepdiff==8.5.0", ] lint = [ "codespell==2.4.1", From 8877cb9a90023f259fda18715b993a24da2ff232 Mon Sep 17 00:00:00 2001 From: Leo Lara Date: Sun, 29 Jun 2025 09:07:17 +0000 Subject: [PATCH 10/11] Move tests to gen fork choice vectors and cache KZG call --- .../test/deneb/fork_choice/test_on_block.py | 2 +- .../test/fulu/fork_choice/test_on_block.py | 299 +++++++++++++++++- .../test/fulu/unittests/das/test_das.py | 258 +-------------- .../core/pyspec/eth2spec/test/helpers/blob.py | 10 +- .../eth2spec/test/helpers/fork_choice.py | 2 +- 5 files changed, 298 insertions(+), 273 deletions(-) diff --git a/tests/core/pyspec/eth2spec/test/deneb/fork_choice/test_on_block.py b/tests/core/pyspec/eth2spec/test/deneb/fork_choice/test_on_block.py index bf34138f21..57ce36e878 100644 --- a/tests/core/pyspec/eth2spec/test/deneb/fork_choice/test_on_block.py +++ b/tests/core/pyspec/eth2spec/test/deneb/fork_choice/test_on_block.py @@ -4,6 +4,7 @@ spec_state_test, with_all_phases_from_except, ) +from eth2spec.test.helpers.blob import get_block_with_blob from eth2spec.test.helpers.constants import ( DENEB, EIP7732, @@ -18,7 +19,6 @@ from eth2spec.test.helpers.state import ( state_transition_and_sign_block, ) -from eth2spec.test.helpers.blob import get_block_with_blob # TODO(jtraglia): Use with_all_phases_from_to_except after EIP7732 is based on Fulu. diff --git a/tests/core/pyspec/eth2spec/test/fulu/fork_choice/test_on_block.py b/tests/core/pyspec/eth2spec/test/fulu/fork_choice/test_on_block.py index 5710abfc93..c2430720d4 100644 --- a/tests/core/pyspec/eth2spec/test/fulu/fork_choice/test_on_block.py +++ b/tests/core/pyspec/eth2spec/test/fulu/fork_choice/test_on_block.py @@ -4,19 +4,40 @@ spec_state_test, with_fulu_and_later, ) -from eth2spec.test.helpers.blob import get_block_with_blob +from eth2spec.test.helpers.blob import get_block_with_blob_and_sidecars 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 + + +def flip_one_bit_in_bytes(data: bytes, index: int = 0) -> bytes: + """ + Flip one bit in a bytes object at the given index. + """ + constr = data.__class__ + byte_index = index // 8 + bit_index = 7 - (index % 8) + byte = data[byte_index] + flipped_byte = byte ^ (1 << bit_index) + + return constr(bytes(data[:byte_index]) + bytes([flipped_byte]) + bytes(data[byte_index + 1 :])) + + +def get_alt_sidecars(spec, state): + """ + Get alternative sidecars for negative test cases. + """ + rng = Random(4321) + _, _, _, _, alt_sidecars = get_block_with_blob_and_sidecars(spec, state, rng=rng, blob_count=2) + return alt_sidecars @with_fulu_and_later @spec_state_test -def test_simple_blob_data_peerdas(spec, state): +def test_on_block_peerdas__ok(spec, state): """ Similar to test_simple_blob_data, but in PeerDAS version that is from Fulu onwards. It covers code related to the blob sidecars because on_block calls `is_data_available` @@ -34,10 +55,8 @@ def test_simple_blob_data_peerdas(spec, state): 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) - sidecars = spec.get_data_column_sidecars_from_block( - signed_block, [spec.compute_cells_and_kzg_proofs(blob) for blob in blobs] + _, blobs, blob_kzg_proofs, signed_block, sidecars = get_block_with_blob_and_sidecars( + spec, state, rng=rng, blob_count=2 ) blob_data = BlobData(blobs, blob_kzg_proofs, sidecars) @@ -46,10 +65,8 @@ def test_simple_blob_data_peerdas(spec, state): assert spec.get_head(store) == signed_block.message.hash_tree_root() # On receiving a block of next epoch - block, blobs, blob_kzg_proofs = get_block_with_blob(spec, state, rng=rng) - signed_block = state_transition_and_sign_block(spec, state, block) - sidecars = spec.get_data_column_sidecars_from_block( - signed_block, [spec.compute_cells_and_kzg_proofs(blob) for blob in blobs] + _, blobs, blob_kzg_proofs, signed_block, sidecars = get_block_with_blob_and_sidecars( + spec, state, rng=rng, blob_count=2 ) blob_data = BlobData(blobs, blob_kzg_proofs, sidecars) @@ -58,3 +75,263 @@ def test_simple_blob_data_peerdas(spec, state): assert spec.get_head(store) == signed_block.message.hash_tree_root() yield "steps", test_steps + + +def run_on_block_peerdas_invalid_test(spec, state, fn): + """ + Run a invalid PeerDAS on_block test with a sidecars mutation function. + """ + 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 + + _, blobs, blob_kzg_proofs, signed_block, sidecars = get_block_with_blob_and_sidecars( + spec, state, rng=rng, blob_count=2 + ) + sidecars = fn(sidecars) + 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_fulu_and_later +@spec_state_test +def test_on_block_peerdas__not_available(spec, state): + """ + Test is_data_available throws an exception when not enough columns are sampled. + """ + yield from run_on_block_peerdas_invalid_test( + spec, + state, + # Empty sidecars will trigger the simulation of not enough columns being sampled + lambda _: [], + ) + + +@with_fulu_and_later +@spec_state_test +def test_on_block_peerdas__invalid_zero_blobs(spec, state): + """ + Test is_data_available returns false when there are no blobs in the sidecars. + """ + + def invalid_zero_blobs(sidecars): + sidecars[0].column = [] + sidecars[0].kzg_commitments = [] + sidecars[0].kzg_proofs = [] + return sidecars + + yield from run_on_block_peerdas_invalid_test(spec, state, invalid_zero_blobs) + + +@with_fulu_and_later +@spec_state_test +def test_on_block_peerdas__invalid_index_1(spec, state): + """ + Test invalid index in sidecars for negative PeerDAS on_block test. + """ + + def invalid_index(sidecars): + sidecars[0].index = 128 # Invalid index + return sidecars + + run_on_block_peerdas_invalid_test(spec, state, invalid_index) + + +@with_fulu_and_later +@spec_state_test +def test_on_block_peerdas__invalid_index_2(spec, state): + """ + Test invalid index in sidecars for negative PeerDAS on_block test. + """ + + def invalid_index(sidecars): + sidecars[0].index = 256 # Invalid index + return sidecars + + run_on_block_peerdas_invalid_test(spec, state, invalid_index) + + +@with_fulu_and_later +@spec_state_test +def test_on_block_peerdas__invalid_mismatch_len_column_1(spec, state): + """ + Test mismatch length in column for negative PeerDAS on_block test. + """ + + def invalid_mismatch_len_column(sidecars): + sidecars[0].column = sidecars[0].column[1:] + return sidecars + + run_on_block_peerdas_invalid_test(spec, state, invalid_mismatch_len_column) + + +@with_fulu_and_later +@spec_state_test +def test_on_block_peerdas__invalid_mismatch_len_column_2(spec, state): + """ + Test mismatch length in column for negative PeerDAS on_block test. + """ + + def invalid_mismatch_len_column(sidecars): + sidecars[1].column = sidecars[1].column[1:] + return sidecars + + run_on_block_peerdas_invalid_test(spec, state, invalid_mismatch_len_column) + + +@with_fulu_and_later +@spec_state_test +def test_on_block_peerdas__invalid_mismatch_len_kzg_commitments_1(spec, state): + """ + Test mismatch length in kzg_commitments for negative PeerDAS on_block test. + """ + + def invalid_mismatch_len_kzg_commitments(sidecars): + sidecars[0].kzg_commitments = sidecars[0].kzg_commitments[1:] + return sidecars + + run_on_block_peerdas_invalid_test(spec, state, invalid_mismatch_len_kzg_commitments) + + +@with_fulu_and_later +@spec_state_test +def test_on_block_peerdas__invalid_mismatch_len_kzg_commitments_2(spec, state): + """ + Test mismatch length in kzg_commitments for negative PeerDAS on_block test. + """ + + def invalid_mismatch_len_kzg_commitments(sidecars): + sidecars[1].kzg_commitments = sidecars[1].kzg_commitments[1:] + return sidecars + + run_on_block_peerdas_invalid_test(spec, state, invalid_mismatch_len_kzg_commitments) + + +@with_fulu_and_later +@spec_state_test +def test_on_block_peerdas__invalid_mismatch_len_kzg_proofs_1(spec, state): + """ + Test mismatch length in kzg_proofs for negative PeerDAS on_block test. + """ + + def invalid_mismatch_len_kzg_proofs(sidecars): + sidecars[0].kzg_proofs = sidecars[0].kzg_proofs[1:] + return sidecars + + run_on_block_peerdas_invalid_test(spec, state, invalid_mismatch_len_kzg_proofs) + + +@with_fulu_and_later +@spec_state_test +def test_on_block_peerdas__invalid_mismatch_len_kzg_proofs_2(spec, state): + """ + Test mismatch length in kzg_proofs for negative PeerDAS on_block test. + """ + + def invalid_mismatch_len_kzg_proofs(sidecars): + sidecars[1].kzg_proofs = sidecars[1].kzg_proofs[1:] + return sidecars + + run_on_block_peerdas_invalid_test(spec, state, invalid_mismatch_len_kzg_proofs) + + +@with_fulu_and_later +@spec_state_test +def test_on_block_peerdas__invalid_wrong_column_1(spec, state): + """ + Test wrong column for negative PeerDAS on_block test. + """ + + def invalid_wrong_column(sidecars): + sidecars[0].column[0] = flip_one_bit_in_bytes(sidecars[0].column[0], 80) + return sidecars + + run_on_block_peerdas_invalid_test(spec, state, invalid_wrong_column) + + +@with_fulu_and_later +@spec_state_test +def test_on_block_peerdas__invalid_wrong_column_2(spec, state): + """ + Test wrong column for negative PeerDAS on_block test. + """ + + def invalid_wrong_column(sidecars): + sidecars[1].column[1] = flip_one_bit_in_bytes(sidecars[1].column[1], 20) + return sidecars + + run_on_block_peerdas_invalid_test(spec, state, invalid_wrong_column) + + +@with_fulu_and_later +@spec_state_test +def test_on_block_peerdas__invalid_wrong_commitment_1(spec, state): + """ + Test wrong commitment for negative PeerDAS on_block test. + """ + alt_sidecars = get_alt_sidecars(spec, state) + + def invalid_wrong_commitment(sidecars): + sidecars[0].kzg_commitments[0] = alt_sidecars[0].kzg_commitments[0] + return sidecars + + run_on_block_peerdas_invalid_test(spec, state, invalid_wrong_commitment) + + +@with_fulu_and_later +@spec_state_test +def test_on_block_peerdas__invalid_wrong_commitment_2(spec, state): + """ + Test wrong commitment for negative PeerDAS on_block test. + """ + alt_sidecars = get_alt_sidecars(spec, state) + + def invalid_wrong_commitment(sidecars): + sidecars[1].kzg_commitments[1] = alt_sidecars[1].kzg_commitments[1] + return sidecars + + run_on_block_peerdas_invalid_test(spec, state, invalid_wrong_commitment) + + +@with_fulu_and_later +@spec_state_test +def test_on_block_peerdas__invalid_wrong_proof_1(spec, state): + """ + Test wrong proof for negative PeerDAS on_block test. + """ + alt_sidecars = get_alt_sidecars(spec, state) + + def invalid_wrong_proof(sidecars): + sidecars[0].kzg_proofs[0] = alt_sidecars[0].kzg_proofs[0] + return sidecars + + run_on_block_peerdas_invalid_test(spec, state, invalid_wrong_proof) + + +@with_fulu_and_later +@spec_state_test +def test_on_block_peerdas__invalid_wrong_proof_2(spec, state): + """ + Test wrong proof for negative PeerDAS on_block test. + """ + alt_sidecars = get_alt_sidecars(spec, state) + + def invalid_wrong_proof(sidecars): + sidecars[1].kzg_proofs[1] = alt_sidecars[1].kzg_proofs[1] + return sidecars + + run_on_block_peerdas_invalid_test(spec, state, invalid_wrong_proof) diff --git a/tests/core/pyspec/eth2spec/test/fulu/unittests/das/test_das.py b/tests/core/pyspec/eth2spec/test/fulu/unittests/das/test_das.py index 0d4b47eb57..271f1d9f69 100644 --- a/tests/core/pyspec/eth2spec/test/fulu/unittests/das/test_das.py +++ b/tests/core/pyspec/eth2spec/test/fulu/unittests/das/test_das.py @@ -11,10 +11,7 @@ with_fulu_and_later, ) from eth2spec.test.helpers.blob import ( - get_block_with_blob, get_block_with_blob_and_sidecars, -) -from eth2spec.test.helpers.blob import ( get_sample_blob, ) from eth2spec.test.helpers.fork_choice import BlobData, with_blob_data @@ -168,7 +165,7 @@ def callback(): @with_fulu_and_later @spec_state_test -def test_is_data_available_peerdas__valid(spec, state): +def test_is_data_available_peerdas(spec, state): rng = random.Random(1234) _, blobs, blob_kzg_proofs, _, sidecars = get_block_with_blob_and_sidecars( spec, state, rng=rng, blob_count=2 @@ -180,259 +177,6 @@ def test_is_data_available_peerdas__valid(spec, state): assert result is True, "Data should be available for the block with blob data" -@with_fulu_and_later -@spec_state_test -def test_is_data_available_peerdas__not_available(spec, state): - rng = random.Random(1234) - _, blobs, blob_kzg_proofs = get_block_with_blob(spec, state, rng=rng, blob_count=2) - - # Empty sidecars will trigger the simulation of not enough columns being sampled - blob_data = BlobData(blobs, blob_kzg_proofs, []) - - try: - run_is_data_available_peerdas_test(spec, blob_data) - result = False - except ValueError: - result = True - - assert result is True, "Should throw an exception when data is not available" - - -@with_fulu_and_later -@spec_state_test -def test_is_data_available_peerdas__invalid_zero_blobs(spec, state): - rng = random.Random(1234) - - _, blobs, blob_kzg_proofs, _, sidecars = get_block_with_blob_and_sidecars( - spec, state, rng=rng, blob_count=2 - ) - - sidecars[0].column = [] - sidecars[0].kzg_commitments = [] - sidecars[0].kzg_proofs = [] - blob_data = BlobData(blobs, blob_kzg_proofs, sidecars) - - result = run_is_data_available_peerdas_test(spec, blob_data) - - assert result is False, "Should return False when sidecars have zero blobs" - - -@with_fulu_and_later -@spec_state_test -def test_is_data_available_peerdas__invalid_index(spec, state): - rng = random.Random(1234) - - _, blobs, blob_kzg_proofs, _, sidecars = get_block_with_blob_and_sidecars( - spec, state, rng=rng, blob_count=2 - ) - - sidecars[0].index = 128 # Invalid index - blob_data = BlobData(blobs, blob_kzg_proofs, sidecars) - - result = run_is_data_available_peerdas_test(spec, blob_data) - - assert result is False, "Should return False when sidecars have invalid index" - - sidecars[1].index = 256 # Invalid index - blob_data = BlobData(blobs, blob_kzg_proofs, sidecars) - - result = run_is_data_available_peerdas_test(spec, blob_data) - - assert result is False, "Should return False when sidecars have invalid index" - - -@with_fulu_and_later -@spec_state_test -def test_is_data_available_peerdas__invalid_mismatch_len_column(spec, state): - rng = random.Random(1234) - - _, blobs, blob_kzg_proofs, _, sidecars = get_block_with_blob_and_sidecars( - spec, state, rng=rng, blob_count=2 - ) - - sidecars[0].column = sidecars[0].column[1:] - blob_data = BlobData(blobs, blob_kzg_proofs, sidecars) - - result = run_is_data_available_peerdas_test(spec, blob_data) - - assert result is False, "Should return False when sidecars have mismatched length in column" - - _, blobs, blob_kzg_proofs, _, sidecars = get_block_with_blob_and_sidecars( - spec, state, rng=rng, blob_count=2 - ) - - sidecars[1].column = sidecars[1].column[1:] - blob_data = BlobData(blobs, blob_kzg_proofs, sidecars) - - result = run_is_data_available_peerdas_test(spec, blob_data) - - assert result is False, "Should return False when sidecars have mismatched length in column" - - -@with_fulu_and_later -@spec_state_test -def test_is_data_available_peerdas__invalid_mismatch_len_kzg_commitments(spec, state): - rng = random.Random(1234) - - _, blobs, blob_kzg_proofs, _, sidecars = get_block_with_blob_and_sidecars( - spec, state, rng=rng, blob_count=2 - ) - - sidecars[0].kzg_commitments = sidecars[0].kzg_commitments[1:] - blob_data = BlobData(blobs, blob_kzg_proofs, sidecars) - - result = run_is_data_available_peerdas_test(spec, blob_data) - - assert result is False, ( - "Should return False when sidecars have mismatched length in kzg_commitments" - ) - - _, blobs, blob_kzg_proofs, _, sidecars = get_block_with_blob_and_sidecars( - spec, state, rng=rng, blob_count=2 - ) - - sidecars[1].kzg_commitments = sidecars[1].kzg_commitments[1:] - blob_data = BlobData(blobs, blob_kzg_proofs, sidecars) - - result = run_is_data_available_peerdas_test(spec, blob_data) - - assert result is False, ( - "Should return False when sidecars have mismatched length in kzg_commitments" - ) - - -@with_fulu_and_later -@spec_state_test -def test_is_data_available_peerdas__invalid_mismatch_len_kzg_proofs(spec, state): - rng = random.Random(1234) - - _, blobs, blob_kzg_proofs, _, sidecars = get_block_with_blob_and_sidecars( - spec, state, rng=rng, blob_count=2 - ) - - sidecars[0].kzg_proofs = sidecars[0].kzg_proofs[1:] - blob_data = BlobData(blobs, blob_kzg_proofs, sidecars) - - result = run_is_data_available_peerdas_test(spec, blob_data) - - assert result is False, "Should return False when sidecars have mismatched length in kzg_proofs" - - _, blobs, blob_kzg_proofs, _, sidecars = get_block_with_blob_and_sidecars( - spec, state, rng=rng, blob_count=2 - ) - - sidecars[1].kzg_proofs = sidecars[1].kzg_proofs[1:] - blob_data = BlobData(blobs, blob_kzg_proofs, sidecars) - - result = run_is_data_available_peerdas_test(spec, blob_data) - - assert result is False, "Should return False when sidecars have mismatched length in kzg_proofs" - - -@with_fulu_and_later -@spec_state_test -def test_is_data_available_peerdas__invalid_wrong_column(spec, state): - def flip_one_bit_in_bytes(data: bytes, index: int = 0) -> bytes: - """Flip one bit in a byte at the specified index.""" - constr = data.__class__ - - byte_index = index // 8 - bit_index = 7 - (index % 8) - byte = data[byte_index] - flipped_byte = byte ^ (1 << bit_index) - return constr( - bytes(data[:byte_index]) + bytes([flipped_byte]) + bytes(data[byte_index + 1 :]) - ) - - rng = random.Random(1234) - - _, blobs, blob_kzg_proofs, _, sidecars = get_block_with_blob_and_sidecars( - spec, state, rng=rng, blob_count=2 - ) - - sidecars[0].column[0] = flip_one_bit_in_bytes(sidecars[0].column[0], 80) - blob_data = BlobData(blobs, blob_kzg_proofs, sidecars) - - result = run_is_data_available_peerdas_test(spec, blob_data) - - assert result is False, "Should return False when sidecars have wrong column data" - - _, blobs, blob_kzg_proofs, _, sidecars = get_block_with_blob_and_sidecars( - spec, state, rng=rng, blob_count=2 - ) - - sidecars[1].column[1] = flip_one_bit_in_bytes(sidecars[1].column[1], 20) - blob_data = BlobData(blobs, blob_kzg_proofs, sidecars) - - result = run_is_data_available_peerdas_test(spec, blob_data) - - assert result is False, "Should return False when sidecars have wrong column data" - - -@with_fulu_and_later -@spec_state_test -def test_is_data_available_peerdas__invalid_wrong_commitment(spec, state): - # Let's get an alternative blobs to use wrong commitments - rng = random.Random(4321) - - _, _, _, _, alt_sidecars = get_block_with_blob_and_sidecars(spec, state, rng=rng, blob_count=2) - - rng = random.Random(1234) - _, blobs, blob_kzg_proofs, _, sidecars = get_block_with_blob_and_sidecars( - spec, state, rng=rng, blob_count=2 - ) - - sidecars[0].kzg_commitments[0] = alt_sidecars[0].kzg_commitments[0] - blob_data = BlobData(blobs, blob_kzg_proofs, sidecars) - - result = run_is_data_available_peerdas_test(spec, blob_data) - - assert result is False, "Should return False when sidecars have wrong kzg_commitment data" - - _, blobs, blob_kzg_proofs, _, sidecars = get_block_with_blob_and_sidecars( - spec, state, rng=rng, blob_count=2 - ) - - sidecars[1].kzg_commitments[1] = alt_sidecars[1].kzg_commitments[1] - blob_data = BlobData(blobs, blob_kzg_proofs, sidecars) - - result = run_is_data_available_peerdas_test(spec, blob_data) - - assert result is False, "Should return False when sidecars have wrong kzg_commitment data" - - -@with_fulu_and_later -@spec_state_test -def test_is_data_available_peerdas__invalid_wrong_proof(spec, state): - # Let's get an alternative blobs to use wrong proofs - rng = random.Random(4321) - - _, _, _, _, alt_sidecars = get_block_with_blob_and_sidecars(spec, state, rng=rng, blob_count=2) - - rng = random.Random(1234) - _, blobs, blob_kzg_proofs, _, sidecars = get_block_with_blob_and_sidecars( - spec, state, rng=rng, blob_count=2 - ) - - sidecars[0].kzg_proofs[0] = alt_sidecars[0].kzg_proofs[0] - blob_data = BlobData(blobs, blob_kzg_proofs, sidecars) - - result = run_is_data_available_peerdas_test(spec, blob_data) - - assert result is False, "Should return False when sidecars have wrong kzg_commitment data" - - _, blobs, blob_kzg_proofs, _, sidecars = get_block_with_blob_and_sidecars( - spec, state, rng=rng, blob_count=2 - ) - - sidecars[1].kzg_proofs[1] = alt_sidecars[1].kzg_proofs[1] - blob_data = BlobData(blobs, blob_kzg_proofs, sidecars) - - result = run_is_data_available_peerdas_test(spec, blob_data) - - assert result is False, "Should return False when sidecars have wrong kzg_commitment data" - - @with_fulu_and_later @spec_state_test def test_get_data_column_sidecars(spec, state): diff --git a/tests/core/pyspec/eth2spec/test/helpers/blob.py b/tests/core/pyspec/eth2spec/test/helpers/blob.py index f9faeaf068..910db36e90 100644 --- a/tests/core/pyspec/eth2spec/test/helpers/blob.py +++ b/tests/core/pyspec/eth2spec/test/helpers/blob.py @@ -1,4 +1,5 @@ import random +from functools import cache from random import Random from rlp import encode, Serializable @@ -146,13 +147,16 @@ def get_block_with_blob(spec, state, rng: Random | None = None, blob_count=1): def get_block_with_blob_and_sidecars(spec, state, rng=None, blob_count=1): - state = state.copy() - block, blobs, blob_kzg_proofs = get_block_with_blob(spec, state, rng=rng, blob_count=blob_count) - cells_and_kzg_proofs = [spec.compute_cells_and_kzg_proofs(blob) for blob in blobs] + cells_and_kzg_proofs = [_cached_compute_cells_and_kzg_proofs(spec, blob) for blob in blobs] # We need a signed block to call `get_data_column_sidecars_from_block` signed_block = state_transition_and_sign_block(spec, state, block) sidecars = spec.get_data_column_sidecars_from_block(signed_block, cells_and_kzg_proofs) return block, blobs, blob_kzg_proofs, signed_block, sidecars + + +@cache +def _cached_compute_cells_and_kzg_proofs(spec, blob): + return spec.compute_cells_and_kzg_proofs(blob) diff --git a/tests/core/pyspec/eth2spec/test/helpers/fork_choice.py b/tests/core/pyspec/eth2spec/test/helpers/fork_choice.py index a5354f1aed..1a5efe4615 100644 --- a/tests/core/pyspec/eth2spec/test/helpers/fork_choice.py +++ b/tests/core/pyspec/eth2spec/test/helpers/fork_choice.py @@ -84,7 +84,7 @@ def with_blob_data_fulu(spec, blob_data: BlobData, func): def retrieve_column_sidecars(_): assert blob_data.sidecars is not None, "blob_data.sidecars must be provided" if len(blob_data.sidecars) == 0: - raise ValueError("Simulation: not all required columns have been sampled") + assert False, "Simulation: not all required columns have been sampled" return blob_data.sidecars retrieve_column_sidecars_backup = spec.retrieve_column_sidecars From d947ad8bbfb51bd9e680f43bfd84c5f8066c48ba Mon Sep 17 00:00:00 2001 From: Leo Lara Date: Sun, 29 Jun 2025 13:02:53 +0000 Subject: [PATCH 11/11] Remove code from wrong merge --- .../test/fulu/unittests/das/test_das.py | 82 ------------------- 1 file changed, 82 deletions(-) diff --git a/tests/core/pyspec/eth2spec/test/fulu/unittests/das/test_das.py b/tests/core/pyspec/eth2spec/test/fulu/unittests/das/test_das.py index 324feb1626..6b12586196 100644 --- a/tests/core/pyspec/eth2spec/test/fulu/unittests/das/test_das.py +++ b/tests/core/pyspec/eth2spec/test/fulu/unittests/das/test_das.py @@ -72,88 +72,6 @@ def test_recover_matrix(spec): assert recovered_matrix == matrix -@with_fulu_and_later -@spec_test -@single_phase -def test_get_extended_sample_count__1(spec): - rng = random.Random(1111) - allowed_failures = rng.randint(0, spec.config.NUMBER_OF_COLUMNS // 2) - spec.get_extended_sample_count(allowed_failures) - - -@with_fulu_and_later -@spec_test -@single_phase -def test_get_extended_sample_count__2(spec): - rng = random.Random(2222) - allowed_failures = rng.randint(0, spec.config.NUMBER_OF_COLUMNS // 2) - spec.get_extended_sample_count(allowed_failures) - - -@with_fulu_and_later -@spec_test -@single_phase -def test_get_extended_sample_count__3(spec): - rng = random.Random(3333) - allowed_failures = rng.randint(0, spec.config.NUMBER_OF_COLUMNS // 2) - spec.get_extended_sample_count(allowed_failures) - - -@with_fulu_and_later -@spec_test -@single_phase -def test_get_extended_sample_count__lower_bound(spec): - allowed_failures = 0 - spec.get_extended_sample_count(allowed_failures) - - -@with_fulu_and_later -@spec_test -@single_phase -def test_get_extended_sample_count__upper_bound(spec): - allowed_failures = spec.config.NUMBER_OF_COLUMNS // 2 - spec.get_extended_sample_count(allowed_failures) - - -@with_fulu_and_later -@spec_test -@single_phase -def test_get_extended_sample_count__upper_bound_exceed(spec): - allowed_failures = spec.config.NUMBER_OF_COLUMNS // 2 + 1 - expect_assertion_error(lambda: spec.get_extended_sample_count(allowed_failures)) - - -@with_fulu_and_later -@spec_test -@with_config_overrides( - { - "NUMBER_OF_COLUMNS": 128, - "SAMPLES_PER_SLOT": 16, - } -) -@single_phase -def test_get_extended_sample_count__table_in_spec(spec): - table = dict( - # (allowed_failures, expected_extended_sample_count) - { - 0: 16, - 1: 20, - 2: 24, - 3: 27, - 4: 29, - 5: 32, - 6: 35, - 7: 37, - 8: 40, - } - ) - for allowed_failures, expected_extended_sample_count in table.items(): - assert ( - spec.get_extended_sample_count(allowed_failures=allowed_failures) - == expected_extended_sample_count - ) - - def run_is_data_available_peerdas_test(spec, blob_data): def callback(): yield spec.is_data_available(spec.Root(b"\x00" * 32))