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
24 changes: 21 additions & 3 deletions .github/workflows/test.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -80,18 +80,27 @@ jobs:
- label: pre-shanghai
from_fork: Frontier
until_fork: Paris
fill_paths: tests
- label: shanghai-cancun
from_fork: Shanghai
until_fork: Cancun
fill_paths: tests
- label: prague
from_fork: Prague
until_fork: Prague
fill_paths: tests
- label: osaka
from_fork: Osaka
until_fork: Osaka
- label: amsterdam
from_fork: Amsterdam
until_fork: Amsterdam
fill_paths: tests
# TODO: The EIP-8141 frame transaction tests fill under the
# ``Bogota`` pseudo-fork (Amsterdam spec + EIP-8141). Re-add an
# ``amsterdam`` entry with ``fill_paths: tests`` as the rest of
# the suite is brought up to date in subsequent PRs.
- label: bogota
from_fork: Bogota
until_fork: Bogota
fill_paths: tests/amsterdam/eip8141_frame_transactions
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- uses: ./.github/actions/setup-uv
Expand All @@ -103,6 +112,7 @@ jobs:
-m "not slow and not derived_test"
env:
PYTEST_XDIST_AUTO_NUM_WORKERS: auto
FILL_PATHS: ${{ matrix.fill_paths }}
- name: Upload coverage reports to Codecov
uses: codecov/codecov-action@671740ac38dd9b0130fbe1cec585b89eea48d3de # v5.5.2
with:
Expand Down Expand Up @@ -132,9 +142,13 @@ jobs:
with:
python-version: "pypy3.11"
- uses: ./.github/actions/setup-env-pypy
# TODO: Stop short of Amsterdam while only the EIP-8141 frame
# transaction tests are filled for that fork. Drop FILL_UNTIL as the
# test run is expanded in subsequent PRs.
- name: Run fill-pypy tests
run: just fill-pypy
env:
FILL_UNTIL: Osaka
PYPY_GC_MAX: "2G"
PYPY_GC_MIN: "1G"

Expand All @@ -146,9 +160,13 @@ jobs:
- uses: ./.github/actions/setup-uv
with:
python-version: "3.14"
# TODO: Stop short of Amsterdam while only the EIP-8141 frame
# transaction tests are filled for that fork. Drop FILL_UNTIL as the
# test run is expanded in subsequent PRs.
- name: Fill and run json-loader tests
run: just json-loader
env:
FILL_UNTIL: Osaka
PYTEST_XDIST_AUTO_NUM_WORKERS: auto
- name: Upload coverage reports to Codecov
uses: codecov/codecov-action@671740ac38dd9b0130fbe1cec585b89eea48d3de # v5.5.2
Expand Down
16 changes: 13 additions & 3 deletions Justfile
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,18 @@ xdist_workers := env("PYTEST_XDIST_AUTO_NUM_WORKERS", "6")
# `-n auto` mode, does not warn on non-numeric values such as "auto".
export PYTEST_XDIST_AUTO_NUM_WORKERS := ""
evm_bin := env("EVM_BIN", "evm")
# Tests for the ``Bogota`` pseudo-fork (e.g. EIP-8141) are outside the
# default fill range; fill them with `just fill --from Bogota --until
# Bogota`. Fold ``Bogota`` into the range once it is a real fork.
latest_fork := "Amsterdam"

# Test paths filled by `just fill`, overridable so CI can narrow the run.
fill_paths := env("FILL_PATHS", "tests")

# Last fork filled by the integration test recipes, overridable so CI can
# stop short of the fork under development.
fill_until := env("FILL_UNTIL", latest_fork)

# Use the faster sys.monitoring coverage core (default on 3.14, opt-in below).
export COVERAGE_CORE := "sysmon"

Expand Down Expand Up @@ -141,7 +151,7 @@ fill *args: (_tmp-logs "fill")
--until "{{ latest_fork }}" \
--durations=50 \
"$@" \
tests
{{ fill_paths }}

# Callers append the feature params, fork range and output; last flag wins.
# Fill fixtures with the flags shared by all fixture releases
Expand Down Expand Up @@ -174,7 +184,7 @@ fill-pypy *args: (_tmp-logs "fill-pypy")
--basetemp="{{ output_dir }}/fill-pypy/tmp" \
--log-to "{{ output_dir }}/fill-pypy/logs" \
--clean \
--until "{{ latest_fork }}" \
--until "{{ fill_until }}" \
--ignore=tests/ported_static \
"$@" \
tests
Expand All @@ -184,7 +194,7 @@ fill-pypy *args: (_tmp-logs "fill-pypy")
json-loader *args: (_tmp "json-loader")
uv run fill \
-m "eels_base_coverage and not derived_test" \
--until "{{ latest_fork }}" \
--until "{{ fill_until }}" \
-n {{ xdist_workers }} --dist=loadgroup \
--skip-index \
--clean \
Expand Down
6 changes: 6 additions & 0 deletions packages/testing/src/execution_testing/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,9 @@
DepositRequest,
Environment,
FeeSystemContractRequest,
Frame,
FrameReceipt,
FrameSignature,
NetworkWrappedTransaction,
Removable,
Requests,
Expand Down Expand Up @@ -176,6 +179,9 @@
"EIPChecklist",
"EngineAPIError",
"Environment",
"Frame",
"FrameReceipt",
"FrameSignature",
"EOA",
"FeeSystemContractRequest",
"FixedIterationsBytecode",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -262,6 +262,15 @@ class ExecutionSpecsExceptionMapper(ExceptionMapper):
"Block access list exceeds gas limit"
),
TransactionException.LOG_MISMATCH: "LogMismatchError",
TransactionException.TYPE_6_INVALID_FRAME_FORMAT: (
"InvalidFrameError"
),
TransactionException.TYPE_6_INVALID_SIGNATURE: (
"InvalidSignatureError"
),
TransactionException.TYPE_6_INVALID_FRAME_EXECUTION: (
"FrameTransactionExecutionError"
),
}
mapping_regex: ClassVar[Dict[ExceptionBase, str]] = {
# Temporary solution for issue #1981.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,15 @@ class GethExceptionMapper(ExceptionMapper):
TransactionException.TYPE_4_TX_PRE_FORK: (
"transaction type not supported"
),
TransactionException.TYPE_6_INVALID_FRAME_FORMAT: (
"invalid frame tx format"
),
TransactionException.TYPE_6_INVALID_SIGNATURE: (
"invalid frame tx signature"
),
TransactionException.TYPE_6_INVALID_FRAME_EXECUTION: (
"invalid frame execution"
),
TransactionException.INITCODE_SIZE_EXCEEDED: (
"max initcode size exceeded"
),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -194,5 +194,22 @@ class TransactionException(ExceptionBase):
"""
TYPE_4_TX_PRE_FORK = auto()
"""Transaction type 4 included before activation fork."""
TYPE_6_INVALID_FRAME_FORMAT = auto()
"""
Transaction is type 6, but violates a static frame transaction
constraint (frame count, mode, flags, value, signature entry
structure, expiry verifier frame shape, blob fields, etc.).
"""
TYPE_6_INVALID_SIGNATURE = auto()
"""
Transaction is type 6, but a signature entry failed protocol
validation.
"""
TYPE_6_INVALID_FRAME_EXECUTION = auto()
"""
Transaction is type 6, but frame execution invalidated it (a SENDER
frame ran before execution approval, a VERIFY frame reverted, or no
frame approved gas payment).
"""
LOG_MISMATCH = auto()
"""Transaction receipt logs do not match expected logs."""
4 changes: 4 additions & 0 deletions packages/testing/src/execution_testing/fixtures/blockchain.py
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,8 @@
from .common import (
FixtureAuthorizationTuple,
FixtureBlobSchedule,
FixtureFrame,
FixtureFrameSignature,
FixtureTransactionReceipt,
)

Expand Down Expand Up @@ -692,6 +694,8 @@ class FixtureTransaction(

authorization_list: List[FixtureAuthorizationTuple] | None = None
initcodes: List[Bytes] | None = None
frames: List[FixtureFrame] | None = None
signatures: List[FixtureFrameSignature] | None = None

@classmethod
def from_transaction(cls, tx: Transaction) -> Self:
Expand Down
46 changes: 46 additions & 0 deletions packages/testing/src/execution_testing/fixtures/common.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@
)
from execution_testing.test_types.transaction_types import (
AuthorizationTupleGeneric,
FrameGeneric,
FrameSignatureGeneric,
Transaction,
)

Expand Down Expand Up @@ -104,6 +106,22 @@ def sign(self) -> None:
return


class FixtureFrame(FrameGeneric[ZeroPaddedHexNumber]):
"""Fixture variant of the EIP-8141 Frame type."""

# Allow extra fields: FixtureFrame is constructed from Frame via
# model_dump(), which may include extra fields.
model_config = CamelModel.model_config | {"extra": "ignore"}


class FixtureFrameSignature(FrameSignatureGeneric[ZeroPaddedHexNumber]):
"""Fixture variant of the EIP-8141 signature entry type."""

# Allow extra fields: FixtureFrameSignature is constructed from
# FrameSignature via model_dump(), which may include extra fields.
model_config = CamelModel.model_config | {"extra": "ignore"}


class FixtureTransactionLog(CamelModel, RLPSerializable):
"""Fixture variant of the TransactionLog type."""

Expand All @@ -126,6 +144,22 @@ class FixtureReceiptDelegation(ReceiptDelegation):
nonce: ZeroPaddedHexNumber


class FixtureFrameReceipt(CamelModel, RLPSerializable):
"""Fixture variant of the EIP-8141 FrameReceipt type."""

model_config = CamelModel.model_config | {"extra": "ignore"}

status: ZeroPaddedHexNumber
gas_used: ZeroPaddedHexNumber
logs: List[FixtureTransactionLog]

rlp_fields: ClassVar[List[str]] = [
"status",
"gas_used",
"logs",
]


class FixtureTransactionReceipt(CamelModel, RLPSerializable):
"""Fixture variant of the TransactionReceipt type."""

Expand All @@ -137,6 +171,9 @@ class FixtureTransactionReceipt(CamelModel, RLPSerializable):
post_state: Hash | None = None
status: bool | None = None

payer: Address | None = None
frame_receipts: List[FixtureFrameReceipt] | None = None

rlp_fields: ClassVar[List[str]] = [
"post_state",
"status",
Expand All @@ -146,6 +183,15 @@ class FixtureTransactionReceipt(CamelModel, RLPSerializable):
]
rlp_exclude_none: ClassVar[bool] = True

def get_rlp_fields(self) -> List[str]:
"""
Return the RLP field list, using the EIP-8141 frame receipt
payload for frame transactions.
"""
if self.payer is not None:
return ["cumulative_gas_used", "payer", "frame_receipts"]
return self.rlp_fields

@model_validator(mode="before")
@classmethod
def _drop_computed_fields(cls, data: Any) -> Any:
Expand Down
4 changes: 4 additions & 0 deletions packages/testing/src/execution_testing/fixtures/state.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@
from .common import (
FixtureAuthorizationTuple,
FixtureBlobSchedule,
FixtureFrame,
FixtureFrameSignature,
FixtureTransactionReceipt,
)

Expand Down Expand Up @@ -58,6 +60,8 @@ class FixtureTransaction(TransactionFixtureConverter):
access_lists: List[List[AccessList] | None] | None = None
authorization_list: List[FixtureAuthorizationTuple] | None = None
initcodes: List[Bytes] | None = None
frames: List[FixtureFrame] | None = None
signatures: List[FixtureFrameSignature] | None = None
max_fee_per_blob_gas: ZeroPaddedHexNumber | None = None
blob_versioned_hashes: Sequence[Hash] | None = None
sender: Address | None = None
Expand Down
2 changes: 2 additions & 0 deletions packages/testing/src/execution_testing/forks/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
Amsterdam,
ArrowGlacier,
Berlin,
Bogota,
Byzantium,
Cancun,
Constantinople,
Expand Down Expand Up @@ -93,6 +94,7 @@
"ArrowGlacier",
"Berlin",
"BerlinToLondonAt5",
"Bogota",
"Byzantium",
"Constantinople",
"ConstantinopleFix",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
"""
EIP-8141: Frame Transaction.

Add a new transaction type constructed from a series of frames,
abstractly defining validity conditions and gas payment.

https://eips.ethereum.org/EIPS/eip-8141
"""

from typing import List, Mapping

from ....base_fork import BaseFork

EXPIRY_VERIFIER_ADDRESS = 0x0000000000000000000000000000000000008141
EXPIRY_VERIFIER_BYTECODE = bytes.fromhex(
"60083614600a575f5ffd5b5f3560c01c4211601657005b5f5ffd"
)


class EIP8141(BaseFork):
"""EIP-8141 class."""

@classmethod
def tx_types(cls) -> List[int]:
"""Frame transactions (type 6) are introduced."""
return super(EIP8141, cls).tx_types() + [6]

@classmethod
def pre_allocation(cls) -> Mapping:
"""Pre-allocate the expiry verifier contract."""
return {
EXPIRY_VERIFIER_ADDRESS: {
# EIP-8141 installs only the runtime code at
# activation; the nonce stays zero.
"nonce": 0,
"code": EXPIRY_VERIFIER_BYTECODE,
}
} | super(EIP8141, cls).pre_allocation() # type: ignore

@classmethod
def pre_allocation_blockchain(cls) -> Mapping:
"""Pre-allocate the expiry verifier contract."""
return {
EXPIRY_VERIFIER_ADDRESS: {
# EIP-8141 installs only the runtime code at
# activation; the nonce stays zero.
"nonce": 0,
"code": EXPIRY_VERIFIER_BYTECODE,
}
} | super(EIP8141, cls).pre_allocation_blockchain() # type: ignore
15 changes: 15 additions & 0 deletions packages/testing/src/execution_testing/forks/forks/forks.py
Original file line number Diff line number Diff line change
Expand Up @@ -1649,3 +1649,18 @@ class Amsterdam(
# live on mainnet.

pass


class Bogota(
eips.EIP8141,
Amsterdam,
deployed=False,
):
"""
Pseudo Bogota fork.
For testing purposes only. Labels fixtures for features slated for the
fork after Amsterdam while the specs repository has no dedicated Bogota
fork module yet; execution uses the Amsterdam spec module.
"""

pass
Loading
Loading