Skip to content

Commit e03b40f

Browse files
temp
1 parent cc21e89 commit e03b40f

7 files changed

Lines changed: 176 additions & 37 deletions

File tree

packages/testing/src/execution_testing/__init__.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,8 +40,12 @@
4040
Block,
4141
BlockchainTest,
4242
BlockchainTestFiller,
43+
BlockVerification,
44+
ColdSloadExpected,
4345
Header,
46+
NoTraceErrors,
4447
OpcodeTarget,
48+
ReceiptStatusExpected,
4549
StateTest,
4650
StateTestFiller,
4751
TransactionTest,
@@ -140,11 +144,13 @@
140144
"BlobsTest",
141145
"BlobsTestFiller",
142146
"Block",
147+
"BlockVerification",
143148
"BlockAccessList",
144149
"BlockAccessListExpectation",
145150
"BlockchainTest",
146151
"BlockchainTestFiller",
147152
"BlockException",
153+
"ColdSloadExpected",
148154
"Bytecode",
149155
"Bytes",
150156
"BytesConcatenation",
@@ -174,7 +180,9 @@
174180
"Macros",
175181
"MemoryVariable",
176182
"NetworkWrappedTransaction",
183+
"NoTraceErrors",
177184
"OpcodeTarget",
185+
"ReceiptStatusExpected",
178186
"Op",
179187
"Opcode",
180188
"OpcodeCallArg",

packages/testing/src/execution_testing/specs/__init__.py

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,13 @@
11
"""Test spec definitions and utilities."""
22

3-
from .base import BaseTest, TestSpec
3+
from .base import (
4+
BaseTest,
5+
BlockVerification,
6+
ColdSloadExpected,
7+
NoTraceErrors,
8+
ReceiptStatusExpected,
9+
TestSpec,
10+
)
411
from .base_static import BaseStaticTest
512
from .benchmark import (
613
BenchmarkTest,
@@ -27,6 +34,7 @@
2734
__all__ = (
2835
"BaseStaticTest",
2936
"BaseTest",
37+
"BlockVerification",
3038
"BenchmarkTest",
3139
"BenchmarkTestFiller",
3240
"BenchmarkTestSpec",
@@ -39,8 +47,11 @@
3947
"BlockchainTestFiller",
4048
"BlockchainTestSpec",
4149
"Block",
50+
"ColdSloadExpected",
4251
"Header",
52+
"NoTraceErrors",
4353
"OpcodeTarget",
54+
"ReceiptStatusExpected",
4455
"StateStaticTest",
4556
"StateTest",
4657
"StateTestFiller",

packages/testing/src/execution_testing/specs/base.py

Lines changed: 137 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,8 @@
22
Base test class and helper functions for Ethereum state and blockchain tests.
33
"""
44

5-
from abc import abstractmethod
5+
from abc import ABC, abstractmethod
6+
from dataclasses import dataclass
67
from enum import StrEnum, unique
78
from functools import reduce
89
from typing import (
@@ -37,9 +38,6 @@
3738
from execution_testing.forks import Fork, TransitionFork
3839
from execution_testing.forks.base_fork import BaseFork
3940
from execution_testing.test_types import Environment, Withdrawal
40-
from execution_testing.test_types.receipt_types import (
41-
TransactionReceipt,
42-
)
4341

4442

4543
class HashMismatchExceptionError(Exception):
@@ -99,6 +97,130 @@ class FillResult(BaseModel):
9997
metadata: Dict[str, Any] = Field(default_factory=dict)
10098

10199

100+
@dataclass
101+
class BlockVerification(ABC):
102+
"""
103+
Base class for block-level verification rules.
104+
105+
Each rule inspects the transition tool result for a
106+
single block and raises on failure. Add new rules by
107+
subclassing and implementing ``verify``.
108+
"""
109+
110+
@abstractmethod
111+
def verify(
112+
self,
113+
*,
114+
result: Result,
115+
block_number: int,
116+
) -> None:
117+
"""Verify the block result, raise on failure."""
118+
...
119+
120+
121+
@dataclass
122+
class NoTraceErrors(BlockVerification):
123+
"""
124+
Verify that no trace line contains an error.
125+
126+
Catches silent subcall failures, out of gas,
127+
invalid jumps, and stack errors.
128+
"""
129+
130+
def verify(
131+
self,
132+
*,
133+
result: Result,
134+
block_number: int,
135+
) -> None:
136+
"""Raise if any trace line has an error."""
137+
if result.traces is None:
138+
return
139+
for tx_idx, tx in enumerate(result.traces.root):
140+
for step, line in enumerate(tx.traces):
141+
if line.error is not None:
142+
raise Exception(
143+
f"Trace error in block "
144+
f"{block_number}, "
145+
f"tx {tx_idx}, "
146+
f"step {step} "
147+
f"(pc={line.pc}, "
148+
f"op={line.op_name}, "
149+
f"depth={line.depth}): "
150+
f"{line.error}"
151+
)
152+
153+
154+
@dataclass
155+
class ReceiptStatusExpected(BlockVerification):
156+
"""
157+
Verify all transaction receipts have the expected
158+
status. Default expects success (status=1).
159+
160+
Catches silent OOG failures that roll back state
161+
and invalidate benchmarks.
162+
"""
163+
164+
status: int = 1
165+
166+
def verify(
167+
self,
168+
*,
169+
result: Result,
170+
block_number: int,
171+
) -> None:
172+
"""Raise if any receipt status mismatches."""
173+
for i, receipt in enumerate(result.receipts):
174+
if receipt.status is not None and (
175+
int(receipt.status) != self.status
176+
):
177+
raise Exception(
178+
f"Transaction {i} in block "
179+
f"{block_number} has receipt "
180+
f"status {int(receipt.status)}, "
181+
f"expected {self.status}."
182+
)
183+
184+
185+
@dataclass
186+
class ColdSloadExpected(BlockVerification):
187+
"""
188+
Verify every SLOAD in the trace is a cold access.
189+
190+
Checks that SLOAD gas cost meets the minimum for
191+
cold storage access (default 2100). Useful for
192+
benchmarks measuring cold storage performance.
193+
"""
194+
195+
min_gas_cost: int = 2100
196+
197+
def verify(
198+
self,
199+
*,
200+
result: Result,
201+
block_number: int,
202+
) -> None:
203+
"""Raise if any SLOAD has warm-access gas cost."""
204+
if result.traces is None:
205+
return
206+
for tx_idx, tx in enumerate(result.traces.root):
207+
for step, line in enumerate(tx.traces):
208+
if (
209+
line.op_name == "SLOAD"
210+
and line.gas_cost is not None
211+
and int(line.gas_cost) < self.min_gas_cost
212+
):
213+
raise Exception(
214+
f"Warm SLOAD in block "
215+
f"{block_number}, "
216+
f"tx {tx_idx}, step {step} "
217+
f"(pc={line.pc}, "
218+
f"gas_cost={line.gas_cost}, "
219+
f"expected >= "
220+
f"{self.min_gas_cost})"
221+
)
222+
223+
102224
class BaseTest(BaseModel):
103225
"""
104226
Represents a base Ethereum test which must return a single test fixture.
@@ -115,7 +237,7 @@ class BaseTest(BaseModel):
115237
gas_optimization_max_gas_limit: int | None = None
116238
expected_benchmark_gas_used: int | None = None
117239
skip_gas_used_validation: bool = False
118-
expected_receipt_status: int | None = None
240+
verifications: List[BlockVerification] = Field(default_factory=list)
119241
is_tx_gas_heavy_test: bool = False
120242
is_exception_test: bool = False
121243

@@ -295,32 +417,23 @@ def validate_benchmark_gas(
295417
f"{gas_benchmark_value}"
296418
)
297419

298-
def validate_receipt_status(
420+
def run_block_verifications(
299421
self,
300422
*,
301-
receipts: List[TransactionReceipt],
423+
result: Result,
302424
block_number: int,
303425
) -> None:
304426
"""
305-
Validate receipt status for every transaction in a block.
427+
Run all block verification rules.
306428
307-
When expected_receipt_status is set, verify that all
308-
receipts match. Catches silent OOG failures that roll
309-
back state and invalidate benchmarks.
429+
Dispatch every rule in ``self.verifications``
430+
against the transition tool result for a block.
310431
"""
311-
if "expected_receipt_status" not in self.model_fields_set:
312-
return
313-
for i, receipt in enumerate(receipts):
314-
if receipt.status is not None and (
315-
int(receipt.status) != self.expected_receipt_status
316-
):
317-
raise Exception(
318-
f"Transaction {i} in block "
319-
f"{block_number} has receipt "
320-
f"status {int(receipt.status)}, "
321-
f"expected "
322-
f"{self.expected_receipt_status}."
323-
)
432+
for v in self.verifications:
433+
v.verify(
434+
result=result,
435+
block_number=block_number,
436+
)
324437

325438

326439
TestSpec = Callable[[Fork], Generator[BaseTest, None, None]]

packages/testing/src/execution_testing/specs/benchmark.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,11 @@
4141
from execution_testing.test_types import Alloc, Environment, Transaction
4242
from execution_testing.vm import Bytecode, Op
4343

44-
from .base import BaseTest, FillResult
44+
from .base import (
45+
BaseTest,
46+
BlockVerification,
47+
FillResult,
48+
)
4549
from .blockchain import Block, BlockchainTest
4650

4751

@@ -307,6 +311,7 @@ class BenchmarkTest(BaseTest):
307311
fixed_opcode_count: float | None = None
308312
target_opcode: Op | OpcodeTarget | None = None
309313
code_generator: BenchmarkCodeGenerator | None = None
314+
verifications: List[BlockVerification] = Field(default_factory=list)
310315
# By default, benchmark tests require neither of these
311316
include_full_post_state_in_output: bool = False
312317
include_tx_receipts_in_output: bool = False

packages/testing/src/execution_testing/specs/blockchain.py

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -867,9 +867,9 @@ def make_fixture(
867867
if is_last_block and self.operation_mode == OpMode.BENCHMARKING:
868868
benchmark_gas_used = int(built_block.result.gas_used)
869869
benchmark_opcode_count = built_block.result.opcode_count
870-
if built_block.result.receipts:
871-
self.validate_receipt_status(
872-
receipts=built_block.result.receipts,
870+
if block.exception is None:
871+
self.run_block_verifications(
872+
result=built_block.result,
873873
block_number=block_number,
874874
)
875875
include_receipts = (
@@ -966,9 +966,9 @@ def make_hive_fixture(
966966
if is_last_block and self.operation_mode == OpMode.BENCHMARKING:
967967
benchmark_gas_used = int(built_block.result.gas_used)
968968
benchmark_opcode_count = built_block.result.opcode_count
969-
if built_block.result.receipts:
970-
self.validate_receipt_status(
971-
receipts=built_block.result.receipts,
969+
if block.exception is None:
970+
self.run_block_verifications(
971+
result=built_block.result,
972972
block_number=block_number,
973973
)
974974
fixture_payloads.append(

tests/benchmark/stateful/bloatnet/test_multi_opcode.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
Create2PreimageLayout,
1818
Fork,
1919
Op,
20+
ReceiptStatusExpected,
2021
Transaction,
2122
While,
2223
)
@@ -563,5 +564,5 @@ def test_mixed_sload_sstore(
563564
pre=pre,
564565
blocks=[Block(txs=txs)],
565566
skip_gas_used_validation=True,
566-
expected_receipt_status=True,
567+
verifications=[ReceiptStatusExpected()],
567568
)

tests/benchmark/stateful/bloatnet/test_single_opcode.py

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
IteratingBytecode,
2828
JumpLoopGenerator,
2929
Op,
30+
ReceiptStatusExpected,
3031
SequentialAddressLayout,
3132
Storage,
3233
TestPhaseManager,
@@ -141,7 +142,7 @@ def run_bloated_eoa_benchmark(
141142
pre=pre,
142143
blocks=blocks,
143144
skip_gas_used_validation=True,
144-
expected_receipt_status=True,
145+
verifications=[ReceiptStatusExpected()],
145146
)
146147

147148

@@ -334,7 +335,7 @@ def test_sload_erc20_generic(
334335
pre=pre,
335336
blocks=blocks,
336337
skip_gas_used_validation=True,
337-
expected_receipt_status=True,
338+
verifications=[ReceiptStatusExpected()],
338339
)
339340

340341

@@ -640,7 +641,7 @@ def test_sstore_erc20_generic(
640641
pre=pre,
641642
blocks=blocks,
642643
skip_gas_used_validation=True,
643-
expected_receipt_status=True,
644+
verifications=[ReceiptStatusExpected()],
644645
)
645646

646647

@@ -2135,5 +2136,5 @@ def calldata(iteration_count: int, start_iteration: int) -> bytes:
21352136
blocks=blocks,
21362137
target_opcode=opcode,
21372138
skip_gas_used_validation=True,
2138-
expected_receipt_status=1,
2139+
verifications=[ReceiptStatusExpected()],
21392140
)

0 commit comments

Comments
 (0)