Skip to content

Commit 139a02e

Browse files
feat: add new benchmark test type
1 parent 99823a2 commit 139a02e

File tree

4 files changed

+150
-4
lines changed

4 files changed

+150
-4
lines changed

src/ethereum_test_specs/__init__.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
from .base import BaseTest, TestSpec
44
from .base_static import BaseStaticTest
5+
from .benchmark import BenchmarkTest, BenchmarkTestFiller, BenchmarkTestSpec
56
from .blobs import BlobsTest, BlobsTestFiller, BlobsTestSpec
67
from .blockchain import (
78
BlockchainTest,
@@ -23,6 +24,9 @@
2324
__all__ = (
2425
"BaseStaticTest",
2526
"BaseTest",
27+
"BenchmarkTest",
28+
"BenchmarkTestFiller",
29+
"BenchmarkTestSpec",
2630
"BlobsTest",
2731
"BlobsTestFiller",
2832
"BlobsTestSpec",

src/ethereum_test_specs/benchmark.py

Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,137 @@
1+
"""Ethereum benchmark test spec definition and filler."""
2+
3+
import math
4+
from typing import Callable, ClassVar, Dict, Generator, List, Optional, Sequence, Type
5+
6+
from pydantic import Field
7+
8+
from ethereum_clis import TransitionTool
9+
from ethereum_test_base_types import HexNumber
10+
from ethereum_test_execution import (
11+
BaseExecute,
12+
ExecuteFormat,
13+
LabeledExecuteFormat,
14+
TransactionPost,
15+
)
16+
from ethereum_test_fixtures import (
17+
BaseFixture,
18+
BlockchainFixture,
19+
FixtureFormat,
20+
LabeledFixtureFormat,
21+
)
22+
from ethereum_test_forks import Fork
23+
from ethereum_test_types import Alloc, Environment, Transaction
24+
25+
from .base import BaseTest
26+
from .blockchain import Block, BlockchainTest
27+
28+
29+
class BenchmarkTest(BaseTest):
30+
"""
31+
Filler type designed specifically for benchmark test cases.
32+
33+
This test type is designed specifically for benchmark test cases and supports:
34+
- High compatibility with both blockchain_test and state_test formats
35+
- Support for EIP-7825 transaction gas limit cap (2^24)
36+
- Only generates blockchain_test format as output
37+
- Automatic splitting of tests when gas limit exceeds the cap
38+
"""
39+
40+
pre: Alloc
41+
post: Alloc
42+
tx: Optional[Transaction] = None
43+
blocks: Optional[List[Block]] = None
44+
genesis_environment: Environment = Field(default_factory=Environment)
45+
46+
# Only support blockchain test format
47+
supported_fixture_formats: ClassVar[Sequence[FixtureFormat | LabeledFixtureFormat]] = [
48+
BlockchainFixture,
49+
]
50+
51+
supported_execute_formats: ClassVar[Sequence[LabeledExecuteFormat]] = [
52+
LabeledExecuteFormat(
53+
TransactionPost,
54+
"benchmark_test",
55+
"An execute test derived from a benchmark test",
56+
),
57+
]
58+
59+
supported_markers: ClassVar[Dict[str, str]] = {
60+
"benchmark_test_only": "Only generate a benchmark test fixture",
61+
}
62+
63+
def split_transaction(self, tx: Transaction, gas_limit_cap: int | None) -> List[Transaction]:
64+
"""Split a transaction that exceeds the gas limit cap into multiple transactions."""
65+
if (gas_limit_cap is None) or (tx.gas_limit <= gas_limit_cap):
66+
return [tx]
67+
68+
# Calculate how many transactions we need
69+
total_gas = int(self.genesis_environment.gas_limit)
70+
num_splits = math.ceil(total_gas / gas_limit_cap)
71+
72+
split_transactions = []
73+
for i in range(num_splits):
74+
split_tx = tx.model_copy()
75+
total_gas -= gas_limit_cap
76+
split_tx.gas_limit = HexNumber(total_gas if i == num_splits - 1 else gas_limit_cap)
77+
split_tx.nonce = HexNumber(tx.nonce + i)
78+
split_transactions.append(split_tx)
79+
80+
return split_transactions
81+
82+
def generate_blockchain_test(self, fork: Fork) -> BlockchainTest:
83+
"""Create a BlockchainTest from this BenchmarkTest."""
84+
if self.blocks is not None:
85+
# We already have blocks, use them directly
86+
return BlockchainTest.from_test(
87+
base_test=self,
88+
genesis_environment=self.genesis_environment,
89+
pre=self.pre,
90+
post=self.post,
91+
blocks=self.blocks,
92+
)
93+
elif self.tx is not None:
94+
# Convert single transaction to a block
95+
gas_limit_cap = fork.transaction_gas_limit_cap()
96+
97+
transactions = self.split_transaction(self.tx, gas_limit_cap)
98+
99+
blocks = [Block(txs=transactions)]
100+
101+
return BlockchainTest.from_test(
102+
base_test=self,
103+
pre=self.pre,
104+
post=self.post,
105+
blocks=blocks,
106+
genesis_environment=self.genesis_environment,
107+
)
108+
else:
109+
raise ValueError("Cannot create BlockchainTest without transactions or blocks")
110+
111+
def generate(
112+
self,
113+
t8n: TransitionTool,
114+
fork: Fork,
115+
fixture_format: FixtureFormat,
116+
) -> BaseFixture:
117+
"""Generate the blockchain test fixture."""
118+
self.check_exception_test(exception=self.tx.error is not None if self.tx else False)
119+
blockchain_test = self.generate_blockchain_test(fork=fork)
120+
return blockchain_test.generate(t8n=t8n, fork=fork, fixture_format=fixture_format)
121+
122+
def execute(
123+
self,
124+
*,
125+
fork: Fork,
126+
execute_format: ExecuteFormat,
127+
) -> BaseExecute:
128+
"""Execute the benchmark test by sending it to the live network."""
129+
if execute_format == TransactionPost:
130+
# Convert to blockchain test and use its execute method
131+
blockchain_test = self.generate_blockchain_test(fork=fork)
132+
return blockchain_test.execute(fork=fork, execute_format=execute_format)
133+
raise Exception(f"Unsupported execute format: {execute_format}")
134+
135+
136+
BenchmarkTestSpec = Callable[[str], Generator[BenchmarkTest, None, None]]
137+
BenchmarkTestFiller = Type[BenchmarkTest]

src/ethereum_test_tools/__init__.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,8 @@
2525
from ethereum_test_fixtures import BaseFixture, FixtureCollector
2626
from ethereum_test_specs import (
2727
BaseTest,
28+
BenchmarkTest,
29+
BenchmarkTestFiller,
2830
BlobsTest,
2931
BlobsTestFiller,
3032
BlockchainTest,
@@ -98,6 +100,8 @@
98100
"AuthorizationTuple",
99101
"BaseFixture",
100102
"BaseTest",
103+
"BenchmarkTest",
104+
"BenchmarkTestFiller",
101105
"Blob",
102106
"BlobsTest",
103107
"BlobsTestFiller",

tests/benchmark/test_worst_blocks.py

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,8 @@
1212
Account,
1313
Address,
1414
Alloc,
15+
BenchmarkTestFiller,
1516
Block,
16-
BlockchainTestFiller,
1717
Environment,
1818
StateTestFiller,
1919
Transaction,
@@ -108,8 +108,9 @@ def ether_transfer_case(
108108
["a_to_a", "a_to_b", "diff_acc_to_b", "a_to_diff_acc", "diff_acc_to_diff_acc"],
109109
)
110110
def test_block_full_of_ether_transfers(
111-
blockchain_test: BlockchainTestFiller,
111+
benchmark_test: BenchmarkTestFiller,
112112
pre: Alloc,
113+
env: Environment,
113114
case_id: str,
114115
ether_transfer_case,
115116
iteration_count: int,
@@ -150,8 +151,8 @@ def test_block_full_of_ether_transfers(
150151
else {receiver: Account(balance=balance) for receiver, balance in balances.items()}
151152
)
152153

153-
blockchain_test(
154-
genesis_environment=Environment(),
154+
benchmark_test(
155+
genesis_environment=env,
155156
pre=pre,
156157
post=post_state,
157158
blocks=[Block(txs=txs)],

0 commit comments

Comments
 (0)