Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions docs/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ Test fixtures for use by clients are available for each release on the [Github r

- 🐞 Allow `evmone` to fill Prague and Osaka blockchain tests (mainly modified deposit contract tests) ([#1689](https://github.com/ethereum/execution-specs/pull/1689)).
- 🐞 Turn off Block-Level Access List related checks when filling tests for Amsterdam ([#1737](https://github.com/ethereum/execution-specs/pull/1737)).
- ✨ Optimize the filling process by lazily loading the t8n response only when it’s actually needed. Otherwise, pass it verbatim into the next t8n execution, skipping both pydantic validation and model serialization entirely ([#1804](https://github.com/ethereum/execution-specs/pull/1804)).

#### `consume`

Expand Down
2 changes: 2 additions & 0 deletions packages/testing/src/execution_testing/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
Account,
Address,
Bytes,
CoerceBytes,
Hash,
Storage,
TestAddress,
Expand Down Expand Up @@ -199,6 +200,7 @@
"TransactionType",
"UndefinedOpcodes",
"While",
"CoerceBytes",
"Withdrawal",
"WithdrawalRequest",
"add_kzg_version",
Expand Down
2 changes: 2 additions & 0 deletions packages/testing/src/execution_testing/base_types/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
BLSPublicKey,
BLSSignature,
Bytes,
CoerceBytes,
FixedSizeBytes,
ForkHash,
Hash,
Expand Down Expand Up @@ -56,6 +57,7 @@
"BLSSignature",
"Bytes",
"CamelModel",
"CoerceBytes",
"EmptyOmmersRoot",
"EmptyTrieRoot",
"EthereumTestBaseModel",
Expand Down
43 changes: 16 additions & 27 deletions packages/testing/src/execution_testing/base_types/base_types.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
"""Basic type primitives used to define other types."""

from hashlib import sha256
from re import sub
from typing import Annotated, Any, ClassVar, SupportsBytes, Type, TypeVar

from Crypto.Hash import keccak
Expand Down Expand Up @@ -57,15 +58,6 @@ def hex(self) -> str:
"""Return the hexadecimal representation of the number."""
return hex(self)

@classmethod
def or_none(
cls: Type[Self], input_number: Self | NumberConvertible | None
) -> Self | None:
"""Convert the input to a Number while accepting None."""
if input_number is None:
return input_number
return cls(input_number)


class Wei(Number):
"""Class that helps represent wei that can be parsed from strings."""
Expand Down Expand Up @@ -198,15 +190,6 @@ def hex(self, *args: Any, **kwargs: Any) -> str:
"""Return the hexadecimal representation of the bytes."""
return "0x" + super().hex(*args, **kwargs)

@classmethod
def or_none(
cls, input_bytes: "Bytes | BytesConvertible | None"
) -> "Bytes | None":
"""Convert the input to a Bytes while accepting None."""
if input_bytes is None:
return input_bytes
return cls(input_bytes)

def keccak256(self) -> "Hash":
"""Return the keccak256 hash of the opcode byte representation."""
k = keccak.new(digest_bits=256)
Expand Down Expand Up @@ -235,6 +218,21 @@ def __get_pydantic_core_schema__(
)


class CoerceBytes(Bytes):
"""
Class that helps represent bytes of variable length in tests and
supports removing white spaces anywhere in the string.
"""

def __new__(cls, input_bytes: BytesConvertible = b"") -> Self:
"""Create a new CoerceBytes object."""
if type(input_bytes) is cls:
return input_bytes
if isinstance(input_bytes, str):
input_bytes = sub(r"\s+", "", input_bytes)
return super(Bytes, cls).__new__(cls, to_bytes(input_bytes))


class FixedSizeHexNumber(int, ToStringSchema):
"""
A base class that helps represent an integer as a fixed byte-length
Expand Down Expand Up @@ -347,15 +345,6 @@ def __hash__(self) -> int:
"""Return the hash of the bytes."""
return super(FixedSizeBytes, self).__hash__()

@classmethod
def or_none(
cls: Type[Self], input_bytes: Self | FixedSizeBytesConvertible | None
) -> Self | None:
"""Convert the input to a Fixed Size Bytes while accepting None."""
if input_bytes is None:
return input_bytes
return cls(input_bytes)

def __eq__(self, other: object) -> bool:
"""Compare two FixedSizeBytes objects to be equal."""
if other is None:
Expand Down
35 changes: 4 additions & 31 deletions packages/testing/src/execution_testing/base_types/conversions.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
"""Common conversion methods."""

from re import sub
from typing import Any, List, Optional, SupportsBytes, TypeAlias
from typing import List, SupportsBytes, TypeAlias

BytesConvertible: TypeAlias = str | bytes | SupportsBytes | List[int]
FixedSizeBytesConvertible: TypeAlias = (
Expand All @@ -10,46 +9,20 @@
NumberConvertible: TypeAlias = str | bytes | SupportsBytes | int


def int_or_none(input_value: Any, default: Optional[int] = None) -> int | None:
"""Convert a value to int or returns a default (None)."""
if input_value is None:
return default
if isinstance(input_value, int):
return input_value
return int(input_value, 0)


def str_or_none(input_value: Any, default: Optional[str] = None) -> str | None:
"""Convert a value to string or returns a default (None)."""
if input_value is None:
return default
if isinstance(input_value, str):
return input_value
return str(input_value)


def to_bytes(input_bytes: BytesConvertible) -> bytes:
"""Convert multiple types into bytes."""
if input_bytes is None:
raise Exception("Cannot convert `None` input to bytes")

if (
isinstance(input_bytes, SupportsBytes)
or isinstance(input_bytes, bytes)
or isinstance(input_bytes, list)
):
return bytes(input_bytes)

if isinstance(input_bytes, str):
# We can have a hex representation of bytes with spaces for readability
input_bytes = sub(r"\s+", "", input_bytes)
if input_bytes.startswith("0x"):
input_bytes = input_bytes[2:]
if len(input_bytes) % 2 == 1:
input_bytes = "0" + input_bytes
return bytes.fromhex(input_bytes)

raise Exception("invalid type for `bytes`")
return bytes(input_bytes)


def to_fixed_size_bytes(
Expand Down Expand Up @@ -85,9 +58,9 @@ def to_fixed_size_bytes(
)
if len(input_bytes) < size:
if left_padding:
return bytes(input_bytes).rjust(size, b"\x00")
return input_bytes.rjust(size, b"\x00")
if right_padding:
return bytes(input_bytes).ljust(size, b"\x00")
return input_bytes.ljust(size, b"\x00")
raise Exception(
f"input is too small for fixed size bytes: {len(input_bytes)} < {size}\n"
"Use `left_padding=True` or `right_padding=True` to allow padding."
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,8 @@
FixtureFillingPhase,
LabeledFixtureFormat,
PreAllocGroup,
PreAllocGroupBuilder,
PreAllocGroupBuilders,
PreAllocGroups,
TestInfo,
)
Expand Down Expand Up @@ -231,7 +233,8 @@ class FillingSession:
fixture_output: FixtureOutput
phase_manager: PhaseManager
format_selector: FormatSelector
pre_alloc_groups: PreAllocGroups | None
pre_alloc_groups: PreAllocGroups | None = None
pre_alloc_group_builders: PreAllocGroupBuilders | None = None

@classmethod
def from_config(cls, config: pytest.Config) -> "Self":
Expand Down Expand Up @@ -263,7 +266,7 @@ def _initialize_pre_alloc_groups(self) -> None:
"""Initialize pre-allocation groups based on the current phase."""
if self.phase_manager.is_pre_alloc_generation:
# Phase 1: Create empty container for collecting groups
self.pre_alloc_groups = PreAllocGroups(root={})
self.pre_alloc_group_builders = PreAllocGroupBuilders(root={})
elif self.phase_manager.is_fill_after_pre_alloc:
# Phase 2: Load pre-alloc groups from disk
self._load_pre_alloc_groups_from_folder()
Expand Down Expand Up @@ -326,15 +329,15 @@ def get_pre_alloc_group(self, hash_key: str) -> PreAllocGroup:

return self.pre_alloc_groups[hash_key]

def update_pre_alloc_group(
self, hash_key: str, group: PreAllocGroup
def update_pre_alloc_group_builder(
self, hash_key: str, group_builder: PreAllocGroupBuilder
) -> None:
"""
Update or add a pre-allocation group.

Args:
hash_key: The hash of the pre-alloc group.
group: The pre-allocation group.
group_builder: The pre-allocation group builder.

Raises:
ValueError: If not in pre-alloc generation phase.
Expand All @@ -345,44 +348,19 @@ def update_pre_alloc_group(
"Can only update pre-alloc groups in generation phase"
)

if self.pre_alloc_groups is None:
self.pre_alloc_groups = PreAllocGroups(root={})
if self.pre_alloc_group_builders is None:
self.pre_alloc_group_builders = PreAllocGroupBuilders(root={})

self.pre_alloc_groups[hash_key] = group
self.pre_alloc_group_builders.root[hash_key] = group_builder

def save_pre_alloc_groups(self) -> None:
"""Save pre-allocation groups to disk."""
if self.pre_alloc_groups is None:
if self.pre_alloc_group_builders is None:
return

pre_alloc_folder = self.fixture_output.pre_alloc_groups_folder_path
pre_alloc_folder.mkdir(parents=True, exist_ok=True)
self.pre_alloc_groups.to_folder(pre_alloc_folder)

def aggregate_pre_alloc_groups(
self, worker_groups: PreAllocGroups
) -> None:
"""
Aggregate pre-alloc groups from a worker process (xdist support).

Args:
worker_groups: Pre-alloc groups from a worker process.

"""
if self.pre_alloc_groups is None:
self.pre_alloc_groups = PreAllocGroups(root={})

for hash_key, group in worker_groups.items():
if hash_key in self.pre_alloc_groups:
# Merge if exists (should not happen in practice)
existing = self.pre_alloc_groups[hash_key]
if existing.pre != group.pre:
raise ValueError(
f"Conflicting pre-alloc groups for hash {hash_key}: "
f"existing={self.pre_alloc_groups[hash_key].pre}, new={group.pre}"
)
else:
self.pre_alloc_groups[hash_key] = group
self.pre_alloc_group_builders.to_folder(pre_alloc_folder)


def calculate_post_state_diff(
Expand Down Expand Up @@ -1382,7 +1360,7 @@ def __init__(self, *args: Any, **kwargs: Any) -> None:
# Use the original update_pre_alloc_groups method which
# returns the groups
self.update_pre_alloc_groups(
session.pre_alloc_groups, request.node.nodeid
session.pre_alloc_group_builders, request.node.nodeid
)
return # Skip fixture generation in phase 1

Expand Down
Loading
Loading