Skip to content

Commit 72d4870

Browse files
committed
refactor using ethereum-rlp
1 parent 6d5ce47 commit 72d4870

File tree

5 files changed

+88
-56
lines changed

5 files changed

+88
-56
lines changed

pyproject.toml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,6 @@ dependencies = [
4343
"ethereum-types>=0.2.1,<0.3",
4444
"pyyaml>=6.0.2,<7",
4545
"types-pyyaml>=6.0.12.20240917,<7",
46-
"rlp>=3.0.0",
4746
"pytest-json-report>=1.5.0,<2",
4847
# TODO: bump questionary to a newer release, when it becomes available.
4948
# The current release questionary 2.0.1 requires `prompt_toolkit = ">=2.0,<=3.0.36"`.

src/ethereum_test_fixtures/schemas/blockchain/headers.py

Lines changed: 87 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,14 @@
11
"""Define genesisHeader schema for filled .json tests."""
22

3-
from typing import Any, Optional
4-
3+
from dataclasses import dataclass, fields
4+
from typing import Optional, Type
5+
6+
from ethereum_rlp import decode as rlp_decode
7+
from ethereum_rlp import decode_to as rlp_decode_to
8+
from ethereum_rlp import encode as rlp_encode
9+
from ethereum_types.bytes import Bytes
10+
from ethereum_types.numeric import Uint
511
from pydantic import BaseModel, Field, model_validator
6-
from rlp import decode as rlp_decode
712

813
from ..common.types import (
914
DataBytes,
@@ -52,6 +57,11 @@ class BlockRecordShanghai(BlockRecord):
5257
withdrawals: list
5358

5459

60+
@dataclass
61+
class BaseRLPHeader:
62+
"""Abstract base RLP header."""
63+
64+
5565
class FrontierHeader(BaseModel):
5666
"""Frontier block header in test json."""
5767

@@ -77,26 +87,29 @@ class Config:
7787

7888
extra = "forbid"
7989

80-
def get_field_rlp_order(self) -> dict[str, Any]:
81-
"""Order fields are encoded into rlp."""
82-
rlp_order: dict[str, Any] = {
83-
"parentHash": self.parentHash,
84-
"uncleHash": self.uncleHash,
85-
"coinbase": self.coinbase,
86-
"stateRoot": self.stateRoot,
87-
"transactionsTrie": self.transactionsTrie,
88-
"receiptTrie": self.receiptTrie,
89-
"bloom": self.bloom,
90-
"difficulty": self.difficulty,
91-
"number": self.number,
92-
"gasLimit": self.gasLimit,
93-
"gasUsed": self.gasUsed,
94-
"timestamp": self.timestamp,
95-
"extraData": self.extraData,
96-
"mixHash": self.mixHash,
97-
"nonce": self.nonce,
98-
}
99-
return rlp_order
90+
@dataclass
91+
class FrontierRLPHeader(BaseRLPHeader):
92+
"""Frontier block header representation in RLP format."""
93+
94+
parentHash: Bytes # noqa: N815"
95+
uncleHash: Bytes # noqa: N815"
96+
coinbase: Bytes
97+
stateRoot: Bytes # noqa: N815"
98+
transactionsTrie: Bytes # noqa: N815"
99+
receiptTrie: Bytes # noqa: N815"
100+
bloom: Bytes
101+
difficulty: Uint
102+
number: Uint
103+
gasLimit: Uint # noqa: N815"
104+
gasUsed: Uint # noqa: N815"
105+
timestamp: Uint
106+
extraData: Bytes # noqa: N815"
107+
mixHash: Bytes # noqa: N815"
108+
nonce: Bytes
109+
110+
def get_rlp_header_scheme(self) -> Type[BaseRLPHeader]:
111+
"""Return structure of fields as they are encoded in RLP."""
112+
return self.FrontierRLPHeader
100113

101114

102115
class HomesteadHeader(FrontierHeader):
@@ -124,11 +137,15 @@ class LondonHeader(BerlinHeader):
124137

125138
baseFeePerGas: PrefixedEvenHex # noqa: N815
126139

127-
def get_field_rlp_order(self) -> dict[str, Any]:
128-
"""Order fields are encoded into rlp."""
129-
rlp_order: dict[str, Any] = super().get_field_rlp_order()
130-
rlp_order["baseFeePerGas"] = self.baseFeePerGas
131-
return rlp_order
140+
@dataclass
141+
class LondonRLPHeader(FrontierHeader.FrontierRLPHeader):
142+
"""London block header representation in RLP format."""
143+
144+
baseFeePerGas: Uint # noqa: N815
145+
146+
def get_rlp_header_scheme(self) -> Type[BaseRLPHeader]:
147+
"""Return structure of fields as they are encoded in RLP."""
148+
return self.LondonRLPHeader
132149

133150

134151
class ParisHeader(LondonHeader):
@@ -146,11 +163,15 @@ class ShanghaiHeader(ParisHeader):
146163

147164
withdrawalsRoot: FixedHash32 # noqa: N815
148165

149-
def get_field_rlp_order(self) -> dict[str, Any]:
150-
"""Order fields are encoded into rlp."""
151-
rlp_order: dict[str, Any] = super().get_field_rlp_order()
152-
rlp_order["withdrawalsRoot"] = self.withdrawalsRoot
153-
return rlp_order
166+
@dataclass
167+
class ShanghaiRLPHeader(LondonHeader.LondonRLPHeader):
168+
"""Shanghai block header representation in RLP format."""
169+
170+
withdrawalsRoot: Bytes # noqa: N815
171+
172+
def get_rlp_header_scheme(self) -> Type[BaseRLPHeader]:
173+
"""Return structure of fields as they are encoded in RLP."""
174+
return self.ShanghaiRLPHeader
154175

155176

156177
class CancunHeader(ShanghaiHeader):
@@ -160,27 +181,44 @@ class CancunHeader(ShanghaiHeader):
160181
excessBlobGas: PrefixedEvenHex # noqa: N815
161182
parentBeaconBlockRoot: FixedHash32 # noqa: N815
162183

163-
def get_field_rlp_order(self) -> dict[str, Any]:
164-
"""Order fields are encoded into rlp."""
165-
rlp_order: dict[str, Any] = super().get_field_rlp_order()
166-
rlp_order["blobGasUsed"] = self.blobGasUsed
167-
rlp_order["excessBlobGas"] = self.excessBlobGas
168-
rlp_order["parentBeaconBlockRoot"] = self.parentBeaconBlockRoot
169-
return rlp_order
184+
@dataclass
185+
class CancunRLPHeader(ShanghaiHeader.ShanghaiRLPHeader):
186+
"""Cancun block header representation in RLP format."""
170187

188+
blobGasUsed: Uint # noqa: N815
189+
excessBlobGas: Uint # noqa: N815
190+
parentBeaconBlockRoot: Bytes # noqa: N815
171191

172-
def verify_block_header_vs_rlp_string(header: FrontierHeader, rlp_string: str):
192+
def get_rlp_header_scheme(self) -> Type[BaseRLPHeader]:
193+
"""Return structure of fields as they are encoded in RLP."""
194+
return self.CancunRLPHeader
195+
196+
197+
def verify_block_header_vs_rlp_string(header_json: FrontierHeader, rlp_string: str):
173198
"""Check that rlp encoding of block header match header object."""
174-
rlp = rlp_decode(bytes.fromhex(rlp_string[2:]))[0]
175-
for rlp_index, (field_name, field) in enumerate(header.get_field_rlp_order().items()):
176-
rlp_hex = rlp[rlp_index].hex()
199+
rlp_block = rlp_decode(bytes.fromhex(rlp_string[2:]))
200+
if isinstance(rlp_block[0], (list, tuple)):
201+
rlp_header = rlp_decode_to(header_json.get_rlp_header_scheme(), rlp_encode(rlp_block[0]))
202+
else:
203+
raise ValueError("Rlp block encoding must be a list, first element must be a list!")
204+
205+
for field in fields(header_json.get_rlp_header_scheme()):
206+
field_name = field.name
207+
208+
rlp_value = getattr(rlp_header, field_name)
209+
field_rlp_value = (
210+
rlp_value.hex() if isinstance(rlp_value, bytes) else rlp_value.to_be_bytes().hex()
211+
)
212+
177213
"""special rlp rule"""
178-
if rlp_hex == "" and field_name not in ["data", "to", "extraData"]:
179-
rlp_hex = "00"
214+
# Field number in header encoded as empty byte '80' so it is decoded as '' but it is '00'
215+
if field_rlp_value == "" and field_name not in ["data", "to", "extraData"]:
216+
field_rlp_value = "00"
180217
""""""
181-
json_hex = field.root[2:]
182-
if rlp_hex != json_hex:
218+
219+
field_json_value = getattr(header_json, field_name).root[2:]
220+
if field_json_value != field_rlp_value:
183221
raise ValueError(
184222
f"Field `{field_name}` in json not equal to it's rlp encoding:"
185-
f"\n {json_hex} != {rlp_hex}"
223+
f"\n {field_json_value} != {field_rlp_value}"
186224
)

src/ethereum_test_fixtures/verify_format.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,4 +22,4 @@ def verify_blockchain_fixture_json(self):
2222
except ValidationError as e:
2323
raise Exception(
2424
f"Error in generated blockchain test json ({self.fixture_name})" + e.json()
25-
) from ValidationError
25+
) from e

stubs/rlp/__init__.pyi

Lines changed: 0 additions & 3 deletions
This file was deleted.

stubs/rlp/codec.pyi

Lines changed: 0 additions & 2 deletions
This file was deleted.

0 commit comments

Comments
 (0)