ethereum.merkle_patricia_trie

Modified Merkle Patricia Trie (MPT), the data structure that commits to large amounts of state with a single hash.

The MPT is a 16-ary radix trie augmented with cryptographic hashing, so each unique mapping of keys to values has a deterministic root hash. Block headers commit to the state, transactions, and receipts through these roots, allowing nodes to verify any individual entry against the header without storing the entire trie.

The trie has three kinds of internal node:

  • A LeafNode terminates a path and stores a value.

  • An ExtensionNode compresses a sequence of nibbles shared by every key passing through it, avoiding chains of single-child branches.

  • A BranchNode has up to sixteen children, one per nibble value, plus an optional value for a key that terminates exactly at this node.

Keys are processed as nibbles (half-bytes, each 0 to 15 inclusive) by bytes_to_nibble_list, and stored within nodes in a compressed hex-prefix encoding produced by nibble_list_to_compact.

Some tries are secured, meaning their keys are hashed with keccak256 before insertion. Hashing distributes keys uniformly so adversarial choices cannot create a deeply unbalanced trie. The state and storage tries are secured; the transaction and receipt tries are not.

The mapping of keys to values is exposed through Trie; the root function reduces a trie to its 32-byte commitment.

EMPTY_TRIE_ROOT

Root hash of an empty Merkle Patricia Trie.

Used in block headers (state, transactions, receipts, etc.) when the corresponding trie has no entries. This is the keccak256 hash of the RLP encoding of an empty byte string, regardless of whether the trie would have been secured.

71
EMPTY_TRIE_ROOT = Root(
72
    hex_to_bytes(
73
        "56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421"
74
    )
75
)

LeafNode

Terminal node in the trie, holding a value at the end of a key path.

88
@final
89
@slotted_freezable
90
@dataclass
class LeafNode:

rest_of_key

Nibbles of the key not yet consumed by ancestor nodes.

96
    rest_of_key: Bytes

value

Value stored at this key, in its encoded form.

101
    value: Extended

ExtensionNode

Internal node that compresses a run of nibbles shared by every key passing through it.

Without this optimization, deep tries of similar keys would otherwise require long chains of single-child BranchNodes.

107
@final
108
@slotted_freezable
109
@dataclass
class ExtensionNode:

key_segment

Sequence of nibbles shared by all keys descending through this node.

121
    key_segment: Bytes

subnode

Encoded child node reached after consuming key_segment.

126
    subnode: Extended

BranchSubnodes

Children of a BranchNode, one per nibble value (0 to 15 inclusive).

Each entry is the encoded form of another internal node, or b"" if no key descends through that nibble.

134
BranchSubnodes = Tuple[
135
    Extended,
136
    Extended,
137
    Extended,
138
    Extended,
139
    Extended,
140
    Extended,
141
    Extended,
142
    Extended,
143
    Extended,
144
    Extended,
145
    Extended,
146
    Extended,
147
    Extended,
148
    Extended,
149
    Extended,
150
    Extended,
151
]

BranchNode

Internal node with up to sixteen children, one per nibble value, plus an optional value for a key that terminates exactly at this node.

162
@final
163
@slotted_freezable
164
@dataclass
class BranchNode:

subnodes

Encoded children, indexed by the next nibble of the key.

171
    subnodes: BranchSubnodes

value

Value for a key that terminates at this node, or b"" if no such key exists.

176
    value: Extended

InternalNode

Any of the three node types making up a Merkle Patricia Trie.

183
InternalNode = LeafNode | ExtensionNode | BranchNode

K

189
K = TypeVar("K", bound=Bytes)

V

190
V = TypeVar("V", bound=Extended | None)

encode_account

Encode an Account for inclusion in the state trie.

The Account dataclass holds nonce, balance, and code hash, but not the account's storage. The corresponding storage_root (the root of the account's own storage trie) is therefore passed in separately.

def encode_account(raw_account_data: Account, ​​storage_root: Bytes) -> Bytes:
194
    <snip>
203
    return rlp.encode(
204
        (
205
            raw_account_data.nonce,
206
            raw_account_data.balance,
207
            storage_root,
208
            raw_account_data.code_hash,
209
        )
210
    )

encode_internal_node

Encode an InternalNode into its RLP form.

When the resulting serialization is at least 32 bytes, keccak256 is applied so that parents store a fixed-size hash. Shorter encodings are returned unhashed and embedded directly into the parent, since storing a hash would only waste space.

None represents the absence of a node and is encoded as b"".

def encode_internal_node(node: Optional[InternalNode]) -> Extended:
214
    <snip>
227
    unencoded: Extended
228
    if node is None:
229
        unencoded = b""
230
    elif isinstance(node, LeafNode):
231
        unencoded = (
232
            nibble_list_to_compact(node.rest_of_key, True),
233
            node.value,
234
        )
235
    elif isinstance(node, ExtensionNode):
236
        unencoded = (
237
            nibble_list_to_compact(node.key_segment, False),
238
            node.subnode,
239
        )
240
    elif isinstance(node, BranchNode):
241
        unencoded = list(node.subnodes) + [node.value]
242
    else:
243
        raise AssertionError(f"Invalid internal node type {type(node)}!")
244
245
    encoded = rlp.encode(unencoded)
246
    if len(encoded) < 32:
247
        return unencoded
248
    else:
249
        return keccak256(encoded)

encode_node

Encode a value for storage in the trie.

Account values require a storage_root and are encoded with encode_account; raw Bytes are returned unchanged; everything else is RLP-encoded.

def encode_node(node: Extended, ​​storage_root: Bytes | None) -> Bytes:
253
    <snip>
263
    if isinstance(node, Account):
264
        assert storage_root is not None
265
        return encode_account(node, storage_root)
266
    elif isinstance(node, Bytes):
267
        return node
268
    else:
269
        return rlp.encode(node)

Trie

Key-value mapping with a single root hash that uniquely identifies its contents.

A trie may be secured, meaning keys are hashed with keccak256 before insertion to prevent adversarial keys from producing a deeply unbalanced trie. The state and storage tries are secured; the transaction and receipt tries are not.

A trie has a default value representing key absence: storing default at a key is equivalent to omitting the key, since the underlying MPT represents missing keys by leaving them out entirely.

272
@final
273
@dataclass
class Trie:

secured

When True, keys are hashed with keccak256 before being inserted into the underlying MPT.

292
    secured: bool

default

Value treated as key absence — storing this value through trie_set removes the key, and trie_get returns it for missing keys.

300
    default: V

_data

312
    _data: Dict[K, V] = field(default_factory=dict)

copy_trie

Create a copy of trie.

Since only frozen objects may be stored in a trie, the contents are shared between the original and the copy.

def copy_trie(trie: Trie[K, V]) -> Trie[K, V]:
316
    <snip>
322
    return Trie(trie.secured, trie.default, copy.copy(trie._data))

trie_set

Insert or update key in trie with the given value.

Storing the trie's default value deletes the key, since a trie represents default by omitting the key entirely.

def trie_set(trie: Trie[K, V], ​​key: K, ​​value: V) -> None:
326
    <snip>
334
    if value == trie.default:
335
        if key in trie._data:
336
            del trie._data[key]
337
    else:
338
        trie._data[key] = value

trie_get

Look up key in trie, returning the trie's default value if absent.

def trie_get(trie: Trie[K, V], ​​key: K) -> V:
342
    <snip>
347
    return trie._data.get(key, trie.default)

common_prefix_length

Find the length of the longest common prefix of two sequences.

def common_prefix_length(a: Sequence, ​​b: Sequence) -> int:
351
    <snip>
354
    for i in range(len(a)):
355
        if i >= len(b) or a[i] != b[i]:
356
            return i
357
    return len(a)

nibble_list_to_compact

Compress a list of nibbles into bytes using hex-prefix encoding.

Nibbles are packed two-per-byte, preceded by a single flag nibble at the high position of the first byte:

+---+---+---------+--------+
| _ | _ | is_leaf | parity |
+---+---+---------+--------+
  3   2      1        0

The lowest bit of the flag nibble encodes the parity of the nibble-list length (0 for even, 1 for odd). The next bit distinguishes leaf nodes from extension nodes. The two highest bits are unused.

When the length is odd the first nibble of x is packed into the same byte as the flag, leaving an even number of remaining nibbles to pair off.

def nibble_list_to_compact(x: Bytes, ​​is_leaf: bool) -> Bytes:
361
    <snip>
381
    compact = bytearray()
382
383
    if len(x) % 2 == 0:  # ie even length
384
        compact.append(16 * (2 * is_leaf))
385
        for i in range(0, len(x), 2):
386
            compact.append(16 * x[i] + x[i + 1])
387
    else:
388
        compact.append(16 * ((2 * is_leaf) + 1) + x[0])
389
        for i in range(1, len(x), 2):
390
            compact.append(16 * x[i] + x[i + 1])
391
392
    return Bytes(compact)

bytes_to_nibble_list

Split each input byte into its high and low nibble, producing a sequence of bytes each holding a value from 0 to 15 inclusive.

def bytes_to_nibble_list(bytes_: Bytes) -> Bytes:
396
    <snip>
400
    nibble_list = bytearray(2 * len(bytes_))
401
    for byte_index, byte in enumerate(bytes_):
402
        nibble_list[byte_index * 2] = (byte & 0xF0) >> 4
403
        nibble_list[byte_index * 2 + 1] = byte & 0x0F
404
    return Bytes(nibble_list)

_prepare_trie

Convert a Trie into the nibble-keyed mapping consumed by patricialize.

Each value is encoded with encode_node; if the value is an Account, get_storage_root must be supplied to provide its storage root. Keys are hashed with keccak256 when the trie is secured, then expanded into nibble form via bytes_to_nibble_list.

def _prepare_trie(trie: Trie[K, V], ​​get_storage_root: Optional[Callable[[Address], Root]]) -> Mapping[Bytes, Bytes]:
411
    <snip>
427
    mapped: MutableMapping[Bytes, Bytes] = {}
428
429
    for preimage, value in trie._data.items():
430
        if isinstance(value, Account):
431
            assert get_storage_root is not None
432
            address = Address(preimage)
433
            encoded_value = encode_node(value, get_storage_root(address))
434
        elif value is None:
435
            raise AssertionError("cannot encode `None`")
436
        else:
437
            encoded_value = encode_node(value)
438
        if encoded_value == b"":
439
            raise AssertionError
440
        key: Bytes
441
        if trie.secured:
442
            # "secure" tries hash keys once before construction
443
            key = keccak256(preimage)
444
        else:
445
            key = preimage
446
        mapped[bytes_to_nibble_list(key)] = encoded_value
447
448
    return mapped

root

Compute the root hash of trie.

The trie is patricialized into a tree of InternalNodes; the root node is RLP-encoded and hashed with keccak256 to produce the fixed-size Hash32 used in block headers.

get_storage_root is required when encoding Account values, so it must be supplied when computing the state root.

def root(trie: Trie[K, V], ​​get_storage_root: Optional[Callable[[Address], Root]]) -> Root:
455
    <snip>
470
    obj = _prepare_trie(trie, get_storage_root)
471
472
    root_node = encode_internal_node(patricialize(obj, Uint(0)))
473
    if len(rlp.encode(root_node)) < 32:
474
        return keccak256(rlp.encode(root_node))
475
    else:
476
        assert isinstance(root_node, Bytes)
477
        return Root(root_node)

patricialize

Recursively build the trie for obj, starting level nibbles into the keys.

The structure of the returned subtree is determined by inspecting the keys present at the current level:

  1. With no keys, the subtree is empty and None is returned.

  2. With a single key, a LeafNode holds the remaining nibbles and the value.

  3. When every key shares a non-empty prefix at this level, an ExtensionNode consumes the prefix and the algorithm recurses for the rest.

  4. Otherwise the keys are partitioned by their next nibble into up to sixteen groups, each recursively patricialized, and combined into a BranchNode.

def patricialize(obj: Mapping[Bytes, Bytes], ​​level: Uint) -> Optional[InternalNode]:
483
    <snip>
504
    if len(obj) == 0:
505
        return None
506
507
    arbitrary_key = next(iter(obj))
508
509
    # if leaf node
510
    if len(obj) == 1:
511
        leaf = LeafNode(arbitrary_key[level:], obj[arbitrary_key])
512
        return leaf
513
514
    # prepare for extension node check by finding max j such that all keys in
515
    # obj have the same key[i:j]
516
    substring = arbitrary_key[level:]
517
    prefix_length = len(substring)
518
    for key in obj:
519
        prefix_length = min(
520
            prefix_length, common_prefix_length(substring, key[level:])
521
        )
522
523
        # finished searching, found another key at the current level
524
        if prefix_length == 0:
525
            break
526
527
    # if extension node
528
    if prefix_length > 0:
529
        prefix = arbitrary_key[int(level) : int(level) + prefix_length]
530
        return ExtensionNode(
531
            prefix,
532
            encode_internal_node(
533
                patricialize(obj, level + Uint(prefix_length))
534
            ),
535
        )
536
537
    branches: List[MutableMapping[Bytes, Bytes]] = []
538
    for _ in range(16):
539
        branches.append({})
540
    value = b""
541
    for key in obj:
542
        if len(key) == level:
543
            value = obj[key]
544
        else:
545
            branches[key[level]][key] = obj[key]
546
547
    subnodes = tuple(
548
        encode_internal_node(patricialize(branches[k], level + Uint(1)))
549
        for k in range(16)
550
    )
551
    return BranchNode(
552
        cast(BranchSubnodes, assert_type(subnodes, Tuple[Extended, ...])),
553
        value,
554
    )