ethereum.cancun.trie
State Trie ^^^^^^^^^^
.. contents:: Table of Contents :backlinks: none :local:
Introduction
The state trie is the structure responsible for storing
.fork_types.Account
objects.
EMPTY_TRIE_ROOT
60 | EMPTY_TRIE_ROOT = Root( |
---|---|
61 | hex_to_bytes( |
62 | "56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421" |
63 | ) |
64 | ) |
Node
66 | Node = Union[ |
---|---|
67 | Account, Bytes, LegacyTransaction, Receipt, Uint, U256, Withdrawal, None |
68 | ] |
K
69 | K = TypeVar("K", bound=Bytes) |
---|
V
70 | V = TypeVar( |
---|---|
71 | "V", |
72 | Optional[Account], |
73 | Optional[Bytes], |
74 | Bytes, |
75 | Optional[Union[LegacyTransaction, Bytes]], |
76 | Optional[Union[Receipt, Bytes]], |
77 | Optional[Union[Withdrawal, Bytes]], |
78 | Uint, |
79 | U256, |
80 | ) |
LeafNode
Leaf node in the Merkle Trie
83 | @slotted_freezable |
---|
84 | @dataclass |
---|
class LeafNode:
rest_of_key
88 | rest_of_key: Bytes |
---|
value
89 | value: rlp.Extended |
---|
ExtensionNode
Extension node in the Merkle Trie
92 | @slotted_freezable |
---|
93 | @dataclass |
---|
class ExtensionNode:
key_segment
97 | key_segment: Bytes |
---|
subnode
98 | subnode: rlp.Extended |
---|
BranchSubnodes
101 | BranchSubnodes = Tuple[ |
---|---|
102 | rlp.Extended, |
103 | rlp.Extended, |
104 | rlp.Extended, |
105 | rlp.Extended, |
106 | rlp.Extended, |
107 | rlp.Extended, |
108 | rlp.Extended, |
109 | rlp.Extended, |
110 | rlp.Extended, |
111 | rlp.Extended, |
112 | rlp.Extended, |
113 | rlp.Extended, |
114 | rlp.Extended, |
115 | rlp.Extended, |
116 | rlp.Extended, |
117 | rlp.Extended, |
118 | ] |
BranchNode
Branch node in the Merkle Trie
121 | @slotted_freezable |
---|
122 | @dataclass |
---|
class BranchNode:
subnodes
126 | subnodes: BranchSubnodes |
---|
value
127 | value: rlp.Extended |
---|
InternalNode
130 | InternalNode = Union[LeafNode, ExtensionNode, BranchNode] |
---|
encode_internal_node
Encodes a Merkle Trie node into its RLP form. The RLP will then be
serialized into a Bytes
and hashed unless it is less that 32 bytes
when serialized.
This function also accepts None
, representing the absence of a node,
which is encoded to b""
.
Parameters
node : Optional[InternalNode] The node to encode.
Returns
encoded : rlp.Extended
The node encoded as RLP.
def encode_internal_node(node: Optional[InternalNode]) -> ethereum_rlp.rlp.Extended:
134 | """ |
---|---|
135 | Encodes a Merkle Trie node into its RLP form. The RLP will then be |
136 | serialized into a `Bytes` and hashed unless it is less that 32 bytes |
137 | when serialized. |
138 |
|
139 | This function also accepts `None`, representing the absence of a node, |
140 | which is encoded to `b""`. |
141 |
|
142 | Parameters |
143 | ---------- |
144 | node : Optional[InternalNode] |
145 | The node to encode. |
146 |
|
147 | Returns |
148 | ------- |
149 | encoded : `rlp.Extended` |
150 | The node encoded as RLP. |
151 | """ |
152 | unencoded: rlp.Extended |
153 | if node is None: |
154 | unencoded = b"" |
155 | elif isinstance(node, LeafNode): |
156 | unencoded = ( |
157 | nibble_list_to_compact(node.rest_of_key, True), |
158 | node.value, |
159 | ) |
160 | elif isinstance(node, ExtensionNode): |
161 | unencoded = ( |
162 | nibble_list_to_compact(node.key_segment, False), |
163 | node.subnode, |
164 | ) |
165 | elif isinstance(node, BranchNode): |
166 | unencoded = list(node.subnodes) + [node.value] |
167 | else: |
168 | raise AssertionError(f"Invalid internal node type {type(node)}!") |
169 | |
170 | encoded = rlp.encode(unencoded) |
171 | if len(encoded) < 32: |
172 | return unencoded |
173 | else: |
174 | return keccak256(encoded) |
encode_node
Encode a Node for storage in the Merkle Trie.
Currently mostly an unimplemented stub.
def encode_node(node: Node, storage_root: Optional[Bytes]) -> Bytes:
178 | """ |
---|---|
179 | Encode a Node for storage in the Merkle Trie. |
180 |
|
181 | Currently mostly an unimplemented stub. |
182 | """ |
183 | if isinstance(node, Account): |
184 | assert storage_root is not None |
185 | return encode_account(node, storage_root) |
186 | elif isinstance(node, (LegacyTransaction, Receipt, Withdrawal, U256)): |
187 | return rlp.encode(node) |
188 | elif isinstance(node, Bytes): |
189 | return node |
190 | else: |
191 | return previous_trie.encode_node(node, storage_root) |
Trie
The Merkle Trie.
194 | @dataclass |
---|
class Trie:
secured
200 | secured: bool |
---|
default
201 | default: V |
---|
_data
202 | _data: Dict[K, V] = field(default_factory=dict) |
---|
copy_trie
Create a copy of trie
. Since only frozen objects may be stored in tries,
the contents are reused.
Parameters
trie: Trie
Trie to copy.
Returns
new_trie : Trie[K, V]
A copy of the trie.
def copy_trie(trie: Trie[K, V]) -> Trie[K, V]:
206 | """ |
---|---|
207 | Create a copy of `trie`. Since only frozen objects may be stored in tries, |
208 | the contents are reused. |
209 |
|
210 | Parameters |
211 | ---------- |
212 | trie: `Trie` |
213 | Trie to copy. |
214 |
|
215 | Returns |
216 | ------- |
217 | new_trie : `Trie[K, V]` |
218 | A copy of the trie. |
219 | """ |
220 | return Trie(trie.secured, trie.default, copy.copy(trie._data)) |
trie_set
Stores an item in a Merkle Trie.
This method deletes the key if value == trie.default
, because the Merkle
Trie represents the default value by omitting it from the trie.
Parameters
trie: Trie
Trie to store in.
key : Bytes
Key to lookup.
value : V
Node to insert at key
.
def trie_set(trie: Trie[K, V], key: K, value: V) -> None:
224 | """ |
---|---|
225 | Stores an item in a Merkle Trie. |
226 |
|
227 | This method deletes the key if `value == trie.default`, because the Merkle |
228 | Trie represents the default value by omitting it from the trie. |
229 |
|
230 | Parameters |
231 | ---------- |
232 | trie: `Trie` |
233 | Trie to store in. |
234 | key : `Bytes` |
235 | Key to lookup. |
236 | value : `V` |
237 | Node to insert at `key`. |
238 | """ |
239 | if value == trie.default: |
240 | if key in trie._data: |
241 | del trie._data[key] |
242 | else: |
243 | trie._data[key] = value |
trie_get
Gets an item from the Merkle Trie.
This method returns trie.default
if the key is missing.
Parameters
trie: Trie to lookup in. key : Key to lookup.
Returns
node : V
Node at key
in the trie.
def trie_get(trie: Trie[K, V], key: K) -> V:
247 | """ |
---|---|
248 | Gets an item from the Merkle Trie. |
249 |
|
250 | This method returns `trie.default` if the key is missing. |
251 |
|
252 | Parameters |
253 | ---------- |
254 | trie: |
255 | Trie to lookup in. |
256 | key : |
257 | Key to lookup. |
258 |
|
259 | Returns |
260 | ------- |
261 | node : `V` |
262 | Node at `key` in the trie. |
263 | """ |
264 | return trie._data.get(key, trie.default) |
common_prefix_length
Find the longest common prefix of two sequences.
def common_prefix_length(a: Sequence, b: Sequence) -> int:
268 | """ |
---|---|
269 | Find the longest common prefix of two sequences. |
270 | """ |
271 | for i in range(len(a)): |
272 | if i >= len(b) or a[i] != b[i]: |
273 | return i |
274 | return len(a) |
nibble_list_to_compact
Compresses nibble-list into a standard byte array with a flag.
A nibble-list is a list of byte values no greater than 15
. The flag is
encoded in high nibble of the highest byte. The flag nibble can be broken
down into two two-bit flags.
Highest nibble::
+---+---+----------+--------+
| _ | _ | is_leaf | parity |
+---+---+----------+--------+
3 2 1 0
The lowest bit of the nibble encodes the parity of the length of the
remaining nibbles -- 0
when even and 1
when odd. The second lowest bit
is used to distinguish leaf and extension nodes. The other two bits are not
used.
Parameters
x : Array of nibbles. is_leaf : True if this is part of a leaf node, or false if it is an extension node.
Returns
compressed : bytearray
Compact byte array.
def nibble_list_to_compact(x: Bytes, is_leaf: bool) -> Bytes:
278 | """ |
---|---|
279 | Compresses nibble-list into a standard byte array with a flag. |
280 |
|
281 | A nibble-list is a list of byte values no greater than `15`. The flag is |
282 | encoded in high nibble of the highest byte. The flag nibble can be broken |
283 | down into two two-bit flags. |
284 |
|
285 | Highest nibble:: |
286 |
|
287 | +---+---+----------+--------+ |
288 | | _ | _ | is_leaf | parity | |
289 | +---+---+----------+--------+ |
290 | 3 2 1 0 |
291 |
|
292 |
|
293 | The lowest bit of the nibble encodes the parity of the length of the |
294 | remaining nibbles -- `0` when even and `1` when odd. The second lowest bit |
295 | is used to distinguish leaf and extension nodes. The other two bits are not |
296 | used. |
297 |
|
298 | Parameters |
299 | ---------- |
300 | x : |
301 | Array of nibbles. |
302 | is_leaf : |
303 | True if this is part of a leaf node, or false if it is an extension |
304 | node. |
305 |
|
306 | Returns |
307 | ------- |
308 | compressed : `bytearray` |
309 | Compact byte array. |
310 | """ |
311 | compact = bytearray() |
312 | |
313 | if len(x) % 2 == 0: # ie even length |
314 | compact.append(16 * (2 * is_leaf)) |
315 | for i in range(0, len(x), 2): |
316 | compact.append(16 * x[i] + x[i + 1]) |
317 | else: |
318 | compact.append(16 * ((2 * is_leaf) + 1) + x[0]) |
319 | for i in range(1, len(x), 2): |
320 | compact.append(16 * x[i] + x[i + 1]) |
321 | |
322 | return Bytes(compact) |
bytes_to_nibble_list
Converts a Bytes
into to a sequence of nibbles (bytes with value < 16).
Parameters
bytes_:
The Bytes
to convert.
Returns
nibble_list : Bytes
The Bytes
in nibble-list format.
def bytes_to_nibble_list(bytes_: Bytes) -> Bytes:
326 | """ |
---|---|
327 | Converts a `Bytes` into to a sequence of nibbles (bytes with value < 16). |
328 |
|
329 | Parameters |
330 | ---------- |
331 | bytes_: |
332 | The `Bytes` to convert. |
333 |
|
334 | Returns |
335 | ------- |
336 | nibble_list : `Bytes` |
337 | The `Bytes` in nibble-list format. |
338 | """ |
339 | nibble_list = bytearray(2 * len(bytes_)) |
340 | for byte_index, byte in enumerate(bytes_): |
341 | nibble_list[byte_index * 2] = (byte & 0xF0) >> 4 |
342 | nibble_list[byte_index * 2 + 1] = byte & 0x0F |
343 | return Bytes(nibble_list) |
_prepare_trie
Prepares the trie for root calculation. Removes values that are empty,
hashes the keys (if secured == True
) and encodes all the nodes.
Parameters
trie :
The Trie
to prepare.
get_storage_root :
Function to get the storage root of an account. Needed to encode
Account
objects.
Returns
out : Mapping[ethereum.base_types.Bytes, Node]
Object with keys mapped to nibble-byte form.
def _prepare_trie(trie: Trie[K, V], get_storage_root: Optional[Callable[[Address], Root]]) -> Mapping[Bytes, Bytes]:
350 | """ |
---|---|
351 | Prepares the trie for root calculation. Removes values that are empty, |
352 | hashes the keys (if `secured == True`) and encodes all the nodes. |
353 |
|
354 | Parameters |
355 | ---------- |
356 | trie : |
357 | The `Trie` to prepare. |
358 | get_storage_root : |
359 | Function to get the storage root of an account. Needed to encode |
360 | `Account` objects. |
361 |
|
362 | Returns |
363 | ------- |
364 | out : `Mapping[ethereum.base_types.Bytes, Node]` |
365 | Object with keys mapped to nibble-byte form. |
366 | """ |
367 | mapped: MutableMapping[Bytes, Bytes] = {} |
368 | |
369 | for preimage, value in trie._data.items(): |
370 | if isinstance(value, Account): |
371 | assert get_storage_root is not None |
372 | address = Address(preimage) |
373 | encoded_value = encode_node(value, get_storage_root(address)) |
374 | else: |
375 | encoded_value = encode_node(value) |
376 | if encoded_value == b"": |
377 | raise AssertionError |
378 | key: Bytes |
379 | if trie.secured: |
380 | # "secure" tries hash keys once before construction |
381 | key = keccak256(preimage) |
382 | else: |
383 | key = preimage |
384 | mapped[bytes_to_nibble_list(key)] = encoded_value |
385 | |
386 | return mapped |
root
Computes the root of a modified merkle patricia trie (MPT).
Parameters
trie :
Trie
to get the root of.
get_storage_root :
Function to get the storage root of an account. Needed to encode
Account
objects.
Returns
root : .fork_types.Root
MPT root of the underlying key-value pairs.
def root(trie: Trie[K, V], get_storage_root: Optional[Callable[[Address], Root]]) -> Root:
393 | """ |
---|---|
394 | Computes the root of a modified merkle patricia trie (MPT). |
395 |
|
396 | Parameters |
397 | ---------- |
398 | trie : |
399 | `Trie` to get the root of. |
400 | get_storage_root : |
401 | Function to get the storage root of an account. Needed to encode |
402 | `Account` objects. |
403 |
|
404 |
|
405 | Returns |
406 | ------- |
407 | root : `.fork_types.Root` |
408 | MPT root of the underlying key-value pairs. |
409 | """ |
410 | obj = _prepare_trie(trie, get_storage_root) |
411 | |
412 | root_node = encode_internal_node(patricialize(obj, Uint(0))) |
413 | if len(rlp.encode(root_node)) < 32: |
414 | return keccak256(rlp.encode(root_node)) |
415 | else: |
416 | assert isinstance(root_node, Bytes) |
417 | return Root(root_node) |
patricialize
Structural composition function.
Used to recursively patricialize and merkleize a dictionary. Includes memoization of the tree structure and hashes.
Parameters
obj : Underlying trie key-value pairs, with keys in nibble-list format. level : Current trie level.
Returns
node : ethereum.base_types.Bytes
Root node of obj
.
def patricialize(obj: Mapping[Bytes, Bytes], level: Uint) -> Optional[InternalNode]:
423 | """ |
---|---|
424 | Structural composition function. |
425 |
|
426 | Used to recursively patricialize and merkleize a dictionary. Includes |
427 | memoization of the tree structure and hashes. |
428 |
|
429 | Parameters |
430 | ---------- |
431 | obj : |
432 | Underlying trie key-value pairs, with keys in nibble-list format. |
433 | level : |
434 | Current trie level. |
435 |
|
436 | Returns |
437 | ------- |
438 | node : `ethereum.base_types.Bytes` |
439 | Root node of `obj`. |
440 | """ |
441 | if len(obj) == 0: |
442 | return None |
443 | |
444 | arbitrary_key = next(iter(obj)) |
445 | |
446 | # if leaf node |
447 | if len(obj) == 1: |
448 | leaf = LeafNode(arbitrary_key[level:], obj[arbitrary_key]) |
449 | return leaf |
450 | |
451 | # prepare for extension node check by finding max j such that all keys in |
452 | # obj have the same key[i:j] |
453 | substring = arbitrary_key[level:] |
454 | prefix_length = len(substring) |
455 | for key in obj: |
456 | prefix_length = min( |
457 | prefix_length, common_prefix_length(substring, key[level:]) |
458 | ) |
459 |
|
460 | # finished searching, found another key at the current level |
461 | if prefix_length == 0: |
462 | break |
463 | |
464 | # if extension node |
465 | if prefix_length > 0: |
466 | prefix = arbitrary_key[int(level) : int(level) + prefix_length] |
467 | return ExtensionNode( |
468 | prefix, |
469 | encode_internal_node( |
470 | patricialize(obj, level + Uint(prefix_length)) |
471 | ), |
472 | ) |
473 | |
474 | branches: List[MutableMapping[Bytes, Bytes]] = [] |
475 | for _ in range(16): |
476 | branches.append({}) |
477 | value = b"" |
478 | for key in obj: |
479 | if len(key) == level: |
480 | # shouldn't ever have an account or receipt in an internal node |
481 | if isinstance(obj[key], (Account, Receipt, Uint)): |
482 | raise AssertionError |
483 | value = obj[key] |
484 | else: |
485 | branches[key[level]][key] = obj[key] |
486 | |
487 | subnodes = tuple( |
488 | encode_internal_node(patricialize(branches[k], level + Uint(1))) |
489 | for k in range(16) |
490 | ) |
491 | return BranchNode( |
492 | cast(BranchSubnodes, assert_type(subnodes, Tuple[rlp.Extended, ...])), |
493 | value, |
494 | ) |