Skip to content

Commit e0a2b67

Browse files
committed
added bpo config + function to retrieve from it + unit test for that function
1 parent 8f2a856 commit e0a2b67

File tree

8 files changed

+232
-5
lines changed

8 files changed

+232
-5
lines changed

src/ethereum_test_base_types/composite_types.py

Lines changed: 34 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
from dataclasses import dataclass
44
from typing import Any, ClassVar, Dict, List, SupportsBytes, Type, TypeAlias
55

6-
from pydantic import Field, PrivateAttr, TypeAdapter
6+
from pydantic import BaseModel, Field, PrivateAttr, TypeAdapter
77

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

488488

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

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

@@ -503,6 +503,36 @@ def last(self) -> ForkBlobSchedule | None:
503503
return None
504504
return list(self.root.values())[-1]
505505

506-
def __getitem__(self, key: str) -> ForkBlobSchedule:
506+
def __getitem__(self, key: str) -> ForkBlobSchedule | None:
507507
"""Return the schedule for a given fork."""
508-
return self.root[key]
508+
return self.root.get(key)
509+
510+
511+
class TimestampBlobSchedule(BaseModel):
512+
"""
513+
Contains a list of dictionaries. Each dictionary is a scheduled BPO fork.
514+
Each dictionary's key is the activation timestamp and the values are a ForkBlobSchedule
515+
object with the fields max, target and base_fee_update_fraction.
516+
"""
517+
518+
root: List[Dict[int, ForkBlobSchedule]] = Field(default_factory=list, validate_default=True)
519+
520+
@classmethod
521+
def add_schedule(cls, activation_timestamp: int, schedule: ForkBlobSchedule):
522+
"""Add a schedule to the schedule list."""
523+
assert activation_timestamp > -1
524+
assert schedule.max_blobs_per_block > 0
525+
assert schedule.base_fee_update_fraction > 0
526+
assert schedule.target_blobs_per_block > 0
527+
528+
# ensure that the timestamp of each scheduled bpo fork is unique
529+
existing_keys: set = set()
530+
for d in cls.root:
531+
existing_keys.update(d.keys())
532+
assert activation_timestamp not in existing_keys, (
533+
f"No duplicate activation forks allowed: Timestamp {activation_timestamp} already "
534+
"exists in the current schedule."
535+
)
536+
537+
# add a scheduled bpo fork
538+
cls.root.append({activation_timestamp: schedule})
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
{
2+
"blobSchedule": {
3+
"cancun": {
4+
"target": 3,
5+
"max": 6,
6+
"baseFeeUpdateFraction": 3338477
7+
},
8+
"prague": {
9+
"target": 6,
10+
"max": 9,
11+
"baseFeeUpdateFraction": 5007716
12+
},
13+
"osaka": {
14+
"target": 6,
15+
"max": 9,
16+
"maxBlobsPerTx": 6,
17+
"baseFeeUpdateFraction": 5007716
18+
},
19+
"bpo1": {
20+
"target": 12,
21+
"max": 16,
22+
"maxBlobsPerTx": 12,
23+
"baseFeeUpdateFraction": 5007716
24+
},
25+
"bpo2": {
26+
"target": 16,
27+
"max": 24,
28+
"maxBlobsPerTx": 12,
29+
"baseFeeUpdateFraction": 5007716
30+
}
31+
},
32+
"cancunTime": 0,
33+
"pragueTime": 0,
34+
"osakaTime": 1747387400,
35+
"bpo1Time": 1757387400,
36+
"bpo2Time": 1767387784
37+
}

src/ethereum_test_types/blob_types.py

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
"""Blob-related types for Ethereum tests."""
22

3+
import json
34
import random
45
from enum import Enum
56
from hashlib import sha256
@@ -22,6 +23,51 @@
2223
logger = get_logger(__name__)
2324

2425

26+
class BPO_Parameters(Enum): # noqa: N801
27+
"""Define BPO keys for IDE autocomplete."""
28+
29+
TARGET = "target"
30+
MAX = "max"
31+
BASE_FEE_UPDATE_FRACTION = "baseFeeUpdateFraction"
32+
TIME = "Time" # actually it is: <fork>Time
33+
34+
35+
def bpo_get_value(bpo_fork: str, bpo_parameter: BPO_Parameters) -> int: # noqa: D417
36+
"""
37+
Retrieve BPO values from the JSON config.
38+
39+
Arguments:
40+
- bpo_fork: Any fork (e.g. cancun) or bpo forks (e.g. bpo1 or bpo2)
41+
- bpo_parameter: Enum value that specifies what you want to access in the bpo config
42+
43+
Returns the retrieved int.
44+
45+
"""
46+
# ensure the bpo config exists and can be read
47+
bpo_config_path = Path("src") / "ethereum_test_types" / "blob_bpo_config.json"
48+
if not bpo_config_path.exists():
49+
raise FileNotFoundError(f"Failed to find BPO config json: {bpo_config_path}")
50+
with open(bpo_config_path, "r") as file:
51+
bpo_config = json.load(file)
52+
53+
# force-lowercase the provided fork
54+
bpo_fork = bpo_fork.lower()
55+
56+
# retrieve requested value
57+
if bpo_parameter == BPO_Parameters.TARGET:
58+
return bpo_config["blobSchedule"][bpo_fork][BPO_Parameters.TARGET.value]
59+
elif bpo_parameter == BPO_Parameters.MAX:
60+
return bpo_config["blobSchedule"][bpo_fork][BPO_Parameters.MAX.value]
61+
elif bpo_parameter == BPO_Parameters.BASE_FEE_UPDATE_FRACTION:
62+
return bpo_config["blobSchedule"][bpo_fork][BPO_Parameters.BASE_FEE_UPDATE_FRACTION.value]
63+
elif bpo_parameter == BPO_Parameters.TIME:
64+
return bpo_config[bpo_fork + BPO_Parameters.TIME.value]
65+
66+
raise NotImplementedError(
67+
f"This function has not yet been updated to handle BPO Parameter: {bpo_parameter}"
68+
)
69+
70+
2571
def clear_blob_cache(cached_blobs_folder_path: Path):
2672
"""Delete all cached blobs."""
2773
if not cached_blobs_folder_path.is_dir():

src/ethereum_test_types/tests/test_blob_types.py

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,13 @@
1515
ShanghaiToCancunAtTime15k,
1616
)
1717

18-
from ..blob_types import CACHED_BLOBS_DIRECTORY, Blob, clear_blob_cache
18+
from ..blob_types import (
19+
CACHED_BLOBS_DIRECTORY,
20+
Blob,
21+
BPO_Parameters,
22+
bpo_get_value,
23+
clear_blob_cache,
24+
)
1925

2026

2127
@pytest.mark.parametrize("seed", [0, 10, 100])
@@ -109,3 +115,24 @@ def test_transition_fork_blobs(
109115
f"Transition fork failure! Fork {fork.name()} at timestamp: {timestamp} should have "
110116
f"transitioned to {post_transition_fork_at_15k.name()} but is still at {b.fork.name()}"
111117
)
118+
119+
120+
@pytest.mark.parametrize("bpo_fork", ["cancun", "prague", "osaka", "bpo1", "bpo2"])
121+
@pytest.mark.parametrize(
122+
"bpo_parameter",
123+
[
124+
BPO_Parameters.TARGET,
125+
BPO_Parameters.MAX,
126+
BPO_Parameters.BASE_FEE_UPDATE_FRACTION,
127+
BPO_Parameters.TIME,
128+
],
129+
)
130+
def test_bpo_parameter_lookup(bpo_fork, bpo_parameter):
131+
"""Tries looking up different values from the BPO configuration json."""
132+
result = bpo_get_value(bpo_fork=bpo_fork, bpo_parameter=bpo_parameter)
133+
print(
134+
f"\nbpo_fork: {bpo_fork}\n"
135+
f"bpo_parameter: {bpo_parameter}\n"
136+
f"retrieved value from config: {result}\n"
137+
)
138+
# TODO: when one day actual bpo_config is known assert correction of retrieved values

tests/osaka/eip7892_bpo/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
"""EIP-7892 Tests."""

tests/osaka/eip7892_bpo/conftest.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
"""Pytest (plugin) definitions local to EIP-7892 tests."""

tests/osaka/eip7892_bpo/spec.py

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
"""Defines EIP-7892 specification constants and functions."""
2+
3+
from dataclasses import dataclass
4+
5+
# Base the spec on EIP-4844 which EIP-7892 extends
6+
from ...cancun.eip4844_blobs.spec import Spec as EIP4844Spec
7+
8+
9+
@dataclass(frozen=True)
10+
class ReferenceSpec:
11+
"""Defines the reference spec version and git path."""
12+
13+
git_path: str
14+
version: str
15+
16+
17+
ref_spec_7892 = ReferenceSpec("EIPS/eip-7892.md", "e42c14f83052bfaa8c38832dcbc46e357dd1a1d9")
18+
19+
20+
@dataclass(frozen=True)
21+
class Spec(EIP4844Spec):
22+
"""Parameters from the EIP-7892 specifications."""
23+
24+
pass

tests/osaka/eip7892_bpo/test_bpo.py

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
"""abstract: Test [EIP-7892: Blob Parameter Only Hardforks](https://eips.ethereum.org/EIPS/eip-7892)."""
2+
3+
import pytest
4+
5+
from ethereum_test_base_types.composite_types import ForkBlobSchedule, TimestampBlobSchedule
6+
from ethereum_test_forks import Fork
7+
from ethereum_test_tools import (
8+
Alloc,
9+
Block,
10+
BlockchainTestFiller,
11+
)
12+
from ethereum_test_types import Environment
13+
14+
from .spec import ref_spec_7892 # type: ignore
15+
16+
REFERENCE_SPEC_GIT_PATH = ref_spec_7892.git_path
17+
REFERENCE_SPEC_VERSION = ref_spec_7892.version
18+
19+
20+
@pytest.mark.valid_from("Osaka")
21+
def test_bpo_schedule(
22+
blockchain_test: BlockchainTestFiller,
23+
pre: Alloc,
24+
post: Alloc,
25+
env: Environment,
26+
fork: Fork,
27+
):
28+
"""Test whether clients correctly set provided BPO schedules."""
29+
bpo_schedule = TimestampBlobSchedule()
30+
# below ensure that there is a timestamp difference of at least 3 between each scheduled fork
31+
bpo_schedule.add_schedule(
32+
1234, ForkBlobSchedule(max=6, target=5, base_fee_update_fraction=5007716)
33+
)
34+
bpo_schedule.add_schedule(
35+
2345, ForkBlobSchedule(max=4, target=3, base_fee_update_fraction=5007716)
36+
)
37+
38+
blocks = []
39+
for schedule_dict in bpo_schedule.root:
40+
for t in schedule_dict:
41+
# add block before bpo
42+
blocks.append(Block(timestamp=t - 1))
43+
# add block at bpo
44+
blocks.append(Block(timestamp=t))
45+
# add block after bpo
46+
blocks.append(Block(timestamp=t + 1))
47+
48+
# amount of created blocks = 3 * len(bpo_schedule.root)
49+
assert len(blocks) == 3 * len(bpo_schedule.root)
50+
51+
# TODO:
52+
# for each block the client should report the current values of: max, target and base_fee_update_fraction # noqa: E501
53+
# we need to signal to the client that the expected response is according to the bpo_schedule defined above # noqa: E501
54+
55+
blockchain_test(
56+
genesis_environment=env,
57+
pre=pre,
58+
post=post,
59+
blocks=blocks,
60+
bpo_schedule=bpo_schedule,
61+
)

0 commit comments

Comments
 (0)