Skip to content
Open
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
109 changes: 8 additions & 101 deletions src/ethereum_spec_tools/evm_tools/t8n/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@
import json
import os
from contextlib import AbstractContextManager
from dataclasses import astuple, dataclass
from typing import Any, Final, TextIO, Type, TypeVar

from ethereum_rlp import rlp
Expand All @@ -18,7 +17,11 @@
from ethereum.exceptions import EthereumException, InvalidBlock
from ethereum.fork_criteria import ByBlockNumber, ByTimestamp, Unscheduled
from ethereum.merkle_patricia_trie import copy_trie
from ethereum_spec_tools.forks import Hardfork, TemporaryHardfork
from ethereum_spec_tools.forks import (
ForkOverrides,
Hardfork,
TemporaryHardfork,
)

from ..loaders.fixture_loader import Load
from ..utils import (
Expand All @@ -34,7 +37,6 @@
from .t8n_types import Alloc, Result, Txs

T = TypeVar("T")
ForkCriteriaArgument = ByBlockNumber | ByTimestamp | Unscheduled | None


def t8n_arguments(subparsers: argparse._SubParsersAction) -> None:
Expand Down Expand Up @@ -92,95 +94,12 @@ def t8n_arguments(subparsers: argparse._SubParsersAction) -> None:
t8n_parser.add_argument("--state-test", action="store_true")


@dataclass(frozen=True)
class _ForkOverrides:
"""Store temporary hardfork override values."""

fork_criteria: ForkCriteriaArgument = None
blob_target_gas_per_block: U64 | None = None
gas_per_blob: U64 | None = None
blob_min_gasprice: Uint | None = None
blob_base_fee_update_fraction: Uint | None = None
max_blob_gas_per_block: U64 | None = None
blob_schedule_target: U64 | None = None
blob_schedule_max: U64 | None = None

def is_empty(self) -> bool:
"""Return true when all override values are unset."""
return all(value is None for value in astuple(self))

@staticmethod
def _matches_field(override: object | None, on: object, name: str) -> bool:
if override is None:
return True

try:
default = getattr(on, name)
except AttributeError:
return False

return override == default

def matches_template(
self,
template: Hardfork,
) -> bool:
"""Return true when the requested overrides match the template."""
if self.is_empty():
return True

if (
self.fork_criteria is not None
and self.fork_criteria != template.criteria
):
return False

fork_mod = template.module("fork")
gas_costs = template.module("vm.gas").GasCosts

checks = (
(
self.max_blob_gas_per_block,
fork_mod,
"MAX_BLOB_GAS_PER_BLOCK",
),
(
self.blob_target_gas_per_block,
gas_costs,
"BLOB_TARGET_GAS_PER_BLOCK",
),
(self.gas_per_blob, gas_costs, "PER_BLOB"),
(
self.blob_min_gasprice,
gas_costs,
"BLOB_MIN_GASPRICE",
),
(
self.blob_base_fee_update_fraction,
gas_costs,
"BLOB_BASE_FEE_UPDATE_FRACTION",
),
(
self.blob_schedule_target,
gas_costs,
"BLOB_SCHEDULE_TARGET",
),
(
self.blob_schedule_max,
gas_costs,
"BLOB_SCHEDULE_MAX",
),
)

return all(self._matches_field(*x) for x in checks)


class ForkCache(AbstractContextManager):
"""
Stores references to temporary hardforks and cleans them up when exited.
"""

_cache: Final[dict[tuple[str, _ForkOverrides], TemporaryHardfork]]
_cache: Final[dict[tuple[str, ForkOverrides], TemporaryHardfork]]

def __init__(self) -> None:
self._cache = {}
Expand All @@ -207,7 +126,7 @@ def get(
Search the cache for a matching hardfork, or create one if it doesn't
exist.
"""
overrides = _ForkOverrides(
overrides = ForkOverrides(
fork_criteria=fork_criteria,
blob_target_gas_per_block=blob_target_gas_per_block,
gas_per_blob=gas_per_blob,
Expand All @@ -226,19 +145,7 @@ def get(
except KeyError:
pass

clone = Hardfork.clone(
template=template,
fork_criteria=overrides.fork_criteria,
blob_target_gas_per_block=overrides.blob_target_gas_per_block,
gas_per_blob=overrides.gas_per_blob,
blob_min_gasprice=overrides.blob_min_gasprice,
blob_base_fee_update_fraction=(
overrides.blob_base_fee_update_fraction
),
max_blob_gas_per_block=overrides.max_blob_gas_per_block,
blob_schedule_target=overrides.blob_schedule_target,
blob_schedule_max=overrides.blob_schedule_max,
)
clone = Hardfork.clone(template=template, overrides=overrides)
self._cache[cache_key] = clone
return clone

Expand Down
151 changes: 118 additions & 33 deletions src/ethereum_spec_tools/forks.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
import random
import sys
from contextlib import AbstractContextManager
from dataclasses import astuple, dataclass
from enum import Enum, auto
from importlib.machinery import ModuleSpec, PathFinder
from pathlib import Path
Expand All @@ -26,20 +27,16 @@
Optional,
Type,
TypeVar,
Union,
cast,
)

from ethereum_types.numeric import U64, U256, Uint
from typing_extensions import override

from ethereum.fork_criteria import ByBlockNumber, ByTimestamp, Unscheduled

if TYPE_CHECKING:
from ethereum.fork_criteria import (
ByBlockNumber,
ByTimestamp,
ForkCriteria,
Unscheduled,
)
from ethereum.fork_criteria import ForkCriteria


class ConsensusType(Enum):
Expand All @@ -64,6 +61,96 @@ def is_pos(self) -> bool:


H = TypeVar("H", bound="Hardfork")
ForkCriteriaArgument = ByBlockNumber | ByTimestamp | Unscheduled | None


@dataclass(frozen=True)
class ForkOverrides:
"""
Temporary hardfork override values.
"""

fork_criteria: ForkCriteriaArgument = None
blob_target_gas_per_block: U64 | None = None
gas_per_blob: U64 | None = None
blob_min_gasprice: Uint | None = None
blob_base_fee_update_fraction: Uint | None = None
max_blob_gas_per_block: U64 | None = None
blob_schedule_target: U64 | None = None
blob_schedule_max: U64 | None = None

def is_empty(self) -> bool:
"""
Return true when all override values are unset.
"""
return all(value is None for value in astuple(self))

@staticmethod
def _matches_field(override: object | None, on: object, name: str) -> bool:
if override is None:
return True

try:
default = getattr(on, name)
except AttributeError:
return False

return override == default

def matches_template(
self,
template: "Hardfork",
) -> bool:
"""
Return true when the requested overrides match the template.
"""
if self.is_empty():
return True

if (
self.fork_criteria is not None
and self.fork_criteria != template.criteria
):
return False

fork_mod = template.module("fork")
gas_costs = template.module("vm.gas").GasCosts

checks = (
(
self.max_blob_gas_per_block,
fork_mod,
"MAX_BLOB_GAS_PER_BLOCK",
),
(
self.blob_target_gas_per_block,
gas_costs,
"BLOB_TARGET_GAS_PER_BLOCK",
),
(self.gas_per_blob, gas_costs, "PER_BLOB"),
(
self.blob_min_gasprice,
gas_costs,
"BLOB_MIN_GASPRICE",
),
(
self.blob_base_fee_update_fraction,
gas_costs,
"BLOB_BASE_FEE_UPDATE_FRACTION",
),
(
self.blob_schedule_target,
gas_costs,
"BLOB_SCHEDULE_TARGET",
),
(
self.blob_schedule_max,
gas_costs,
"BLOB_SCHEDULE_MAX",
),
)

return all(self._matches_field(*x) for x in checks)


class Hardfork:
Expand Down Expand Up @@ -204,23 +291,17 @@ def load_from_json(cls: Type[H], json: Any) -> List[H]:
@staticmethod
def clone(
template: H | str,
fork_criteria: Union[
"ByBlockNumber", "ByTimestamp", "Unscheduled", None
] = None,
blob_target_gas_per_block: U64 | None = None,
gas_per_blob: U64 | None = None,
blob_min_gasprice: Uint | None = None,
blob_base_fee_update_fraction: Uint | None = None,
max_blob_gas_per_block: U64 | None = None,
blob_schedule_target: U64 | None = None,
blob_schedule_max: U64 | None = None,
overrides: ForkOverrides | None = None,
) -> "TemporaryHardfork":
"""
Create a temporary clone of an existing fork, optionally tweaking its
parameters.
"""
from .new_fork.builder import ForkBuilder

if overrides is None:
overrides = ForkOverrides()

maybe_directory: TemporaryDirectory | None = TemporaryDirectory()

try:
Expand All @@ -240,33 +321,37 @@ def clone(

builder.output = Path(directory.name)

if fork_criteria is not None:
builder.fork_criteria = fork_criteria
if overrides.fork_criteria is not None:
builder.fork_criteria = overrides.fork_criteria

if blob_target_gas_per_block is not None:
if overrides.blob_target_gas_per_block is not None:
builder.modify_target_blob_gas_per_block(
blob_target_gas_per_block
overrides.blob_target_gas_per_block
)

if gas_per_blob is not None:
builder.modify_gas_per_blob(gas_per_blob)
if overrides.gas_per_blob is not None:
builder.modify_gas_per_blob(overrides.gas_per_blob)

if blob_min_gasprice is not None:
builder.modify_min_blob_gasprice(blob_min_gasprice)
if overrides.blob_min_gasprice is not None:
builder.modify_min_blob_gasprice(overrides.blob_min_gasprice)

if blob_base_fee_update_fraction is not None:
if overrides.blob_base_fee_update_fraction is not None:
builder.modify_blob_base_fee_update_fraction(
blob_base_fee_update_fraction
overrides.blob_base_fee_update_fraction
)

if max_blob_gas_per_block is not None:
builder.modify_max_blob_gas_per_block(max_blob_gas_per_block)
if overrides.max_blob_gas_per_block is not None:
builder.modify_max_blob_gas_per_block(
overrides.max_blob_gas_per_block
)

if blob_schedule_target is not None:
builder.modify_blob_schedule_target(blob_schedule_target)
if overrides.blob_schedule_target is not None:
builder.modify_blob_schedule_target(
overrides.blob_schedule_target
)

if blob_schedule_max is not None:
builder.modify_blob_schedule_max(blob_schedule_max)
if overrides.blob_schedule_max is not None:
builder.modify_blob_schedule_max(overrides.blob_schedule_max)

builder.build()

Expand Down
Loading