1
1
"""Define genesisHeader schema for filled .json tests."""
2
2
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
5
11
from pydantic import BaseModel , Field , model_validator
6
- from rlp import decode as rlp_decode
7
12
8
13
from ..common .types import (
9
14
DataBytes ,
@@ -52,6 +57,11 @@ class BlockRecordShanghai(BlockRecord):
52
57
withdrawals : list
53
58
54
59
60
+ @dataclass
61
+ class BaseRLPHeader :
62
+ """Abstract base RLP header."""
63
+
64
+
55
65
class FrontierHeader (BaseModel ):
56
66
"""Frontier block header in test json."""
57
67
@@ -77,26 +87,29 @@ class Config:
77
87
78
88
extra = "forbid"
79
89
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
100
113
101
114
102
115
class HomesteadHeader (FrontierHeader ):
@@ -124,11 +137,15 @@ class LondonHeader(BerlinHeader):
124
137
125
138
baseFeePerGas : PrefixedEvenHex # noqa: N815
126
139
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
132
149
133
150
134
151
class ParisHeader (LondonHeader ):
@@ -146,11 +163,15 @@ class ShanghaiHeader(ParisHeader):
146
163
147
164
withdrawalsRoot : FixedHash32 # noqa: N815
148
165
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
154
175
155
176
156
177
class CancunHeader (ShanghaiHeader ):
@@ -160,27 +181,44 @@ class CancunHeader(ShanghaiHeader):
160
181
excessBlobGas : PrefixedEvenHex # noqa: N815
161
182
parentBeaconBlockRoot : FixedHash32 # noqa: N815
162
183
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."""
170
187
188
+ blobGasUsed : Uint # noqa: N815
189
+ excessBlobGas : Uint # noqa: N815
190
+ parentBeaconBlockRoot : Bytes # noqa: N815
171
191
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 ):
173
198
"""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
+
177
213
"""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"
180
217
""""""
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 :
183
221
raise ValueError (
184
222
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 } "
186
224
)
0 commit comments