Skip to content

Commit d4c95a4

Browse files
JackCC703SamWilsn
andauthored
fix(evm_tools): avoid cloning unchanged fork template in ForkCache (#2738)
* fix(evm_tools): handle GasCosts defaults in template match * fix(evm_tools): avoid cloning identical fork overrides * simplify fork cache template stuff --------- Co-authored-by: Sam Wilson <sam@binarycake.ca>
1 parent 610d538 commit d4c95a4

2 files changed

Lines changed: 376 additions & 21 deletions

File tree

src/ethereum_spec_tools/evm_tools/t8n/__init__.py

Lines changed: 108 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,8 @@
77
import json
88
import os
99
from contextlib import AbstractContextManager
10-
from typing import Any, Final, TextIO, Tuple, Type, TypeVar
10+
from dataclasses import astuple, dataclass
11+
from typing import Any, Final, TextIO, Type, TypeVar
1112

1213
from ethereum_rlp import rlp
1314
from ethereum_types.numeric import U64, U256, Uint
@@ -40,6 +41,7 @@
4041
from .t8n_types import Alloc, Result, Txs
4142

4243
T = TypeVar("T")
44+
ForkCriteriaArgument = ByBlockNumber | ByTimestamp | Unscheduled | None
4345

4446

4547
def t8n_arguments(subparsers: argparse._SubParsersAction) -> None:
@@ -97,12 +99,95 @@ def t8n_arguments(subparsers: argparse._SubParsersAction) -> None:
9799
t8n_parser.add_argument("--state-test", action="store_true")
98100

99101

102+
@dataclass(frozen=True)
103+
class _ForkOverrides:
104+
"""Store temporary hardfork override values."""
105+
106+
fork_criteria: ForkCriteriaArgument = None
107+
blob_target_gas_per_block: U64 | None = None
108+
gas_per_blob: U64 | None = None
109+
blob_min_gasprice: Uint | None = None
110+
blob_base_fee_update_fraction: Uint | None = None
111+
max_blob_gas_per_block: U64 | None = None
112+
blob_schedule_target: U64 | None = None
113+
blob_schedule_max: U64 | None = None
114+
115+
def is_empty(self) -> bool:
116+
"""Return true when all override values are unset."""
117+
return all(value is None for value in astuple(self))
118+
119+
@staticmethod
120+
def _matches_field(override: object | None, on: object, name: str) -> bool:
121+
if override is None:
122+
return True
123+
124+
try:
125+
default = getattr(on, name)
126+
except AttributeError:
127+
return False
128+
129+
return override == default
130+
131+
def matches_template(
132+
self,
133+
template: Hardfork,
134+
) -> bool:
135+
"""Return true when the requested overrides match the template."""
136+
if self.is_empty():
137+
return True
138+
139+
if (
140+
self.fork_criteria is not None
141+
and self.fork_criteria != template.criteria
142+
):
143+
return False
144+
145+
fork_mod = template.module("fork")
146+
gas_costs = template.module("vm.gas").GasCosts
147+
148+
checks = (
149+
(
150+
self.max_blob_gas_per_block,
151+
fork_mod,
152+
"MAX_BLOB_GAS_PER_BLOCK",
153+
),
154+
(
155+
self.blob_target_gas_per_block,
156+
gas_costs,
157+
"BLOB_TARGET_GAS_PER_BLOCK",
158+
),
159+
(self.gas_per_blob, gas_costs, "PER_BLOB"),
160+
(
161+
self.blob_min_gasprice,
162+
gas_costs,
163+
"BLOB_MIN_GASPRICE",
164+
),
165+
(
166+
self.blob_base_fee_update_fraction,
167+
gas_costs,
168+
"BLOB_BASE_FEE_UPDATE_FRACTION",
169+
),
170+
(
171+
self.blob_schedule_target,
172+
gas_costs,
173+
"BLOB_SCHEDULE_TARGET",
174+
),
175+
(
176+
self.blob_schedule_max,
177+
gas_costs,
178+
"BLOB_SCHEDULE_MAX",
179+
),
180+
)
181+
182+
return all(self._matches_field(*x) for x in checks)
183+
184+
100185
class ForkCache(AbstractContextManager):
101186
"""
102187
Stores references to temporary hardforks and cleans them up when exited.
103188
"""
104189

105-
_cache: Final[dict[Tuple[object, ...], TemporaryHardfork]]
190+
_cache: Final[dict[tuple[str, _ForkOverrides], TemporaryHardfork]]
106191

107192
def __init__(self) -> None:
108193
self._cache = {}
@@ -129,35 +214,37 @@ def get(
129214
Search the cache for a matching hardfork, or create one if it doesn't
130215
exist.
131216
"""
132-
cache_key = (
133-
template.short_name,
134-
fork_criteria,
135-
blob_target_gas_per_block,
136-
gas_per_blob,
137-
blob_min_gasprice,
138-
blob_base_fee_update_fraction,
139-
max_blob_gas_per_block,
140-
blob_schedule_target,
141-
blob_schedule_max,
217+
overrides = _ForkOverrides(
218+
fork_criteria=fork_criteria,
219+
blob_target_gas_per_block=blob_target_gas_per_block,
220+
gas_per_blob=gas_per_blob,
221+
blob_min_gasprice=blob_min_gasprice,
222+
blob_base_fee_update_fraction=blob_base_fee_update_fraction,
223+
max_blob_gas_per_block=max_blob_gas_per_block,
224+
blob_schedule_target=blob_schedule_target,
225+
blob_schedule_max=blob_schedule_max,
142226
)
143-
if all(x is None for x in cache_key[1:]):
227+
if overrides.matches_template(template):
144228
return template
145229

230+
cache_key = (template.short_name, overrides)
146231
try:
147232
return self._cache[cache_key]
148233
except KeyError:
149234
pass
150235

151236
clone = Hardfork.clone(
152237
template=template,
153-
fork_criteria=fork_criteria,
154-
blob_target_gas_per_block=blob_target_gas_per_block,
155-
gas_per_blob=gas_per_blob,
156-
blob_min_gasprice=blob_min_gasprice,
157-
blob_base_fee_update_fraction=blob_base_fee_update_fraction,
158-
max_blob_gas_per_block=max_blob_gas_per_block,
159-
blob_schedule_target=blob_schedule_target,
160-
blob_schedule_max=blob_schedule_max,
238+
fork_criteria=overrides.fork_criteria,
239+
blob_target_gas_per_block=overrides.blob_target_gas_per_block,
240+
gas_per_blob=overrides.gas_per_blob,
241+
blob_min_gasprice=overrides.blob_min_gasprice,
242+
blob_base_fee_update_fraction=(
243+
overrides.blob_base_fee_update_fraction
244+
),
245+
max_blob_gas_per_block=overrides.max_blob_gas_per_block,
246+
blob_schedule_target=overrides.blob_schedule_target,
247+
blob_schedule_max=overrides.blob_schedule_max,
161248
)
162249
self._cache[cache_key] = clone
163250
return clone

0 commit comments

Comments
 (0)