Skip to content

feat(fw): EIP-7892 BPO functionality added (related to issues #1797 , #1790) #1918

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 7 commits into
base: main
Choose a base branch
from
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
53 changes: 49 additions & 4 deletions src/ethereum_test_base_types/composite_types.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
from dataclasses import dataclass
from typing import Any, ClassVar, Dict, List, SupportsBytes, Type, TypeAlias

from pydantic import Field, PrivateAttr, TypeAdapter
from pydantic import BaseModel, Field, PrivateAttr, TypeAdapter

from .base_types import Address, Bytes, Hash, HashInt, HexNumber, ZeroPaddedHexNumber
from .conversions import BytesConvertible, NumberConvertible
Expand Down Expand Up @@ -487,7 +487,7 @@ class ForkBlobSchedule(CamelModel):


class BlobSchedule(EthereumTestRootModel[Dict[str, ForkBlobSchedule]]):
"""Blob schedule configuration dictionary."""
"""Blob schedule configuration dictionary. Key is fork name."""

root: Dict[str, ForkBlobSchedule] = Field(default_factory=dict, validate_default=True)

Expand All @@ -503,6 +503,51 @@ def last(self) -> ForkBlobSchedule | None:
return None
return list(self.root.values())[-1]

def __getitem__(self, key: str) -> ForkBlobSchedule:
def __getitem__(self, key: str) -> ForkBlobSchedule | None:
"""Return the schedule for a given fork."""
return self.root[key]
return self.root.get(key)


class TimestampBlobSchedule(BaseModel):
"""
Contains a list of dictionaries. Each dictionary is a scheduled BPO fork.
Each dictionary's key is the activation timestamp and the values are a ForkBlobSchedule
object with the fields max, target and base_fee_update_fraction.
"""

# never directly modify root, instead use `add_schedule()`
root: List[Dict[int, ForkBlobSchedule]] = Field(default_factory=list, validate_default=True)

def add_schedule(self, activation_timestamp: int, schedule: ForkBlobSchedule):
"""Add a schedule to the schedule list."""
assert activation_timestamp > -1
assert schedule.max_blobs_per_block > 0
assert schedule.base_fee_update_fraction > 0
assert schedule.target_blobs_per_block > 0

if self.root is None:
self.root = []

# ensure that the timestamp of each scheduled bpo fork is unique
existing_keys: set = set()
for d in self.root:
existing_keys.update(d.keys())
assert activation_timestamp not in existing_keys, (
f"No duplicate activation forks allowed: Timestamp {activation_timestamp} already "
"exists in the current schedule."
)

# add a scheduled bpo fork
self.root.append({activation_timestamp: schedule})

# sort list ascending by dict keys (timestamp)
self.root = sorted(self.root, key=lambda d: list(d.keys())[0])

# sanity check to ensure that timestamps are at least 3 time units apart (relevant for bpo test logic) # noqa: E501
prev_time: int = 0
for i in self.root:
cur_time = next(iter(i)) # get key of only key-value pair in dict
assert cur_time - prev_time >= 3, "The timestamp difference of the keys of scheduled "
"bpo_forks need to have a difference of at least 3! But you tried to append the "
f"dictionary {dict({activation_timestamp: schedule})} which would lead " # noqa: C418
f"to a timestamp difference of just {cur_time - prev_time} compared to prev element"
6 changes: 5 additions & 1 deletion src/ethereum_test_base_types/conversions.py
Original file line number Diff line number Diff line change
Expand Up @@ -100,4 +100,8 @@ def to_number(input_number: NumberConvertible) -> int:
return int(input_number, 0)
if isinstance(input_number, bytes) or isinstance(input_number, SupportsBytes):
return int.from_bytes(input_number, byteorder="big")
raise Exception("invalid type for `number`")

raise Exception(
f"Invalid type for `number`. Got {type(input_number)} but expected int, str or bytes!\n"
f"Value of `number` you passed: {input_number}"
)
68 changes: 54 additions & 14 deletions src/ethereum_test_specs/blockchain.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
"""Ethereum blockchain test spec definition and filler."""

import warnings
from pprint import pprint
from typing import Any, Callable, ClassVar, Dict, Generator, List, Optional, Sequence, Tuple, Type

import pytest
from pydantic import ConfigDict, Field, field_validator

from ethereum_clis import BlockExceptionWithMessage, Result, TransitionTool
from ethereum_clis.types import TransitionToolOutput
from ethereum_test_base_types import (
Address,
Bloom,
Expand All @@ -18,6 +18,7 @@
HexNumber,
Number,
)
from ethereum_test_base_types.composite_types import BlobSchedule, TimestampBlobSchedule
from ethereum_test_exceptions import (
BlockException,
EngineAPIError,
Expand Down Expand Up @@ -52,11 +53,14 @@
from ethereum_test_fixtures.common import FixtureBlobSchedule
from ethereum_test_forks import Fork
from ethereum_test_types import Alloc, Environment, Removable, Requests, Transaction, Withdrawal
from pytest_plugins.logging import get_logger

from .base import BaseTest, verify_result
from .debugging import print_traces
from .helpers import verify_block, verify_transactions

logger = get_logger(__name__)


def environment_from_parent_header(parent: "FixtureHeader") -> "Environment":
"""Instantiate new environment with the provided header as parent."""
Expand Down Expand Up @@ -407,6 +411,7 @@ class BlockchainTest(BaseTest):
verify_sync: bool = False
chain_id: int = 1
exclude_full_post_state_in_output: bool = False
bpo_schedule: TimestampBlobSchedule = None # type: ignore[assignment]
"""
Exclude the post state from the fixture output.
In this case, the state verification is only performed based on the state root.
Expand Down Expand Up @@ -489,21 +494,24 @@ def generate_block_data(
block: Block,
previous_env: Environment,
previous_alloc: Alloc,
bpo_schedule: TimestampBlobSchedule | None,
) -> BuiltBlock:
"""Generate common block data for both make_fixture and make_hive_fixture."""
env = block.set_environment(previous_env)
env = env.set_fork_requirements(fork)

txs: List[Transaction] = []

# check if any tests are gas-heavy
for tx in block.txs:
if not self.is_tx_gas_heavy_test() and tx.gas_limit >= Environment().gas_limit:
warnings.warn(
f"{self.node_id()} uses a high Transaction gas_limit: {tx.gas_limit}",
stacklevel=2,
)

txs.append(tx.with_signature_and_sender())

# ensure exception test comes at the end, if it exists at all
if failing_tx_count := len([tx for tx in txs if tx.error]) > 0:
if failing_tx_count > 1:
raise Exception(
Expand All @@ -515,15 +523,24 @@ def generate_block_data(
+ "must be the last transaction in the block"
)

transition_tool_output = t8n.evaluate(
# if a bpo schedule has been passed let it overwrite the blob_schedule
fork_blob_schedule: BlobSchedule | None = fork.blob_schedule()
assert fork_blob_schedule is not None
if bpo_schedule is not None:
# fork_blob_schedule = bpo_schedule
pass
# TODO:

# get transition tool response
transition_tool_output: TransitionToolOutput = t8n.evaluate(
transition_tool_data=TransitionTool.TransitionToolData(
alloc=previous_alloc,
txs=txs,
env=env,
fork=fork,
chain_id=self.chain_id,
reward=fork.get_reward(env.number, env.timestamp),
blob_schedule=fork.blob_schedule(),
blob_schedule=fork_blob_schedule,
),
debug_output_path=self.get_next_transition_tool_output_path(),
slow_request=self.is_tx_gas_heavy_test(),
Expand Down Expand Up @@ -617,9 +634,13 @@ def generate_block_data(
verify_result(transition_tool_output.result, env)
except Exception as e:
print_traces(t8n.get_traces())
pprint(transition_tool_output.result)
pprint(previous_alloc)
pprint(transition_tool_output.alloc)

# only spam the cmd with t8n if debug logging is explicitly activated
logger.debug(
f"T8n output: {transition_tool_output.result}\n"
f"Previous alloc: {previous_alloc}\n"
f"T8n alloc: {transition_tool_output.alloc}"
)
raise e

if len(rejected_txs) > 0 and block.exception is None:
Expand All @@ -646,11 +667,16 @@ def verify_post_state(self, t8n, t8n_state: Alloc, expected_state: Alloc | None
raise e

def make_fixture(
self,
t8n: TransitionTool,
fork: Fork,
self, t8n: TransitionTool, fork: Fork, bpo_schedule: TimestampBlobSchedule | None
) -> BlockchainFixture:
"""Create a fixture from the blockchain test definition."""
fork_blob_schedule: BlobSchedule | None = fork.blob_schedule()
assert fork_blob_schedule is not None
if bpo_schedule is not None:
# fork_blob_schedule = bpo_schedule
pass
# TODO:

fixture_blocks: List[FixtureBlock | InvalidFixtureBlock] = []

pre, genesis = self.make_genesis(fork=fork, apply_pre_allocation_blockchain=True)
Expand All @@ -669,6 +695,7 @@ def make_fixture(
block=block,
previous_env=env,
previous_alloc=alloc,
bpo_schedule=bpo_schedule,
)
fixture_blocks.append(built_block.get_fixture_block())
if block.exception is None:
Expand Down Expand Up @@ -696,7 +723,7 @@ def make_fixture(
post_state_hash=alloc.state_root() if self.exclude_full_post_state_in_output else None,
config=FixtureConfig(
fork=fork,
blob_schedule=FixtureBlobSchedule.from_blob_schedule(fork.blob_schedule()),
blob_schedule=FixtureBlobSchedule.from_blob_schedule(fork_blob_schedule),
chain_id=self.chain_id,
),
)
Expand All @@ -705,9 +732,17 @@ def make_hive_fixture(
self,
t8n: TransitionTool,
fork: Fork,
bpo_schedule: TimestampBlobSchedule | None,
fixture_format: FixtureFormat = BlockchainEngineFixture,
) -> BlockchainEngineFixture | BlockchainEngineXFixture:
"""Create a hive fixture from the blocktest definition."""
fork_blob_schedule: BlobSchedule | None = fork.blob_schedule()
assert fork_blob_schedule is not None
if bpo_schedule is not None:
# fork_blob_schedule = bpo_schedule
pass
# TODO:

fixture_payloads: List[FixtureEngineNewPayload] = []

pre, genesis = self.make_genesis(
Expand All @@ -725,6 +760,7 @@ def make_hive_fixture(
block=block,
previous_env=env,
previous_alloc=alloc,
bpo_schedule=bpo_schedule,
)
fixture_payloads.append(built_block.get_fixture_engine_new_payload())
if block.exception is None:
Expand Down Expand Up @@ -765,6 +801,7 @@ def make_hive_fixture(
block=Block(),
previous_env=env,
previous_alloc=alloc,
bpo_schedule=bpo_schedule,
)
sync_payload = sync_built_block.get_fixture_engine_new_payload()

Expand All @@ -779,7 +816,7 @@ def make_hive_fixture(
"config": FixtureConfig(
fork=fork,
chain_id=self.chain_id,
blob_schedule=FixtureBlobSchedule.from_blob_schedule(fork.blob_schedule()),
blob_schedule=FixtureBlobSchedule.from_blob_schedule(fork_blob_schedule),
),
}

Expand Down Expand Up @@ -813,13 +850,16 @@ def generate(
t8n: TransitionTool,
fork: Fork,
fixture_format: FixtureFormat,
bpo_schedule: TimestampBlobSchedule | None = None,
) -> BaseFixture:
"""Generate the BlockchainTest fixture."""
t8n.reset_traces()
if fixture_format in [BlockchainEngineFixture, BlockchainEngineXFixture]:
return self.make_hive_fixture(t8n, fork, fixture_format)
return self.make_hive_fixture(
t8n=t8n, fork=fork, bpo_schedule=bpo_schedule, fixture_format=fixture_format
)
elif fixture_format == BlockchainFixture:
return self.make_fixture(t8n, fork)
return self.make_fixture(t8n=t8n, fork=fork, bpo_schedule=bpo_schedule)

raise Exception(f"Unknown fixture format: {fixture_format}")

Expand Down
37 changes: 37 additions & 0 deletions src/ethereum_test_types/blob_bpo_config.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
{
"blobSchedule": {
"cancun": {
"target": 3,
"max": 6,
"baseFeeUpdateFraction": 3338477
},
"prague": {
"target": 6,
"max": 9,
"baseFeeUpdateFraction": 5007716
},
"osaka": {
"target": 6,
"max": 9,
"maxBlobsPerTx": 6,
"baseFeeUpdateFraction": 5007716
},
"bpo1": {
"target": 12,
"max": 16,
"maxBlobsPerTx": 12,
"baseFeeUpdateFraction": 5007716
},
"bpo2": {
"target": 16,
"max": 24,
"maxBlobsPerTx": 12,
"baseFeeUpdateFraction": 5007716
}
},
"cancunTime": 0,
"pragueTime": 0,
"osakaTime": 1747387400,
"bpo1Time": 1757387400,
"bpo2Time": 1767387784
}
Loading