Purpose and Scope
This document explains the Merkle Patricia Trie data structure used to store trie nodes in StableNet and the underlying database layer.The trie provides a cryptographically authenticated key-value store with efficient lookups, proof generation, and deterministic root hash computation. Main topics covered:
- Trie Structure: Node types (shortNode, fullNode, valueNode, hashNode) and tree composition
- Node Encoding: Key encoding schemes (keybytes, hex, compact) and RLP serialization
- Trie Database: Storage schemas (hash-based, path-based) and node persistence
- Merkle Proofs: Proof generation, verification, and use cases
For snapshot-based optimizations, see Caching and Snapshots.
For the ancient store handling historical block data, see Ancient Store and Data Lifecycle.
Merkle Patricia Trie Overview
The Merkle Patricia Trie is a cryptographically authenticated data structure that combines properties of Merkle trees and Patricia tries (radix trees). It enables:- Efficient key-value storage and lookup with O(log n) complexity
- State commitment via Merkle root hashing
- State proof generation without holding the entire state
- Deterministic root computation where all nodes derive the same result
- Account Trie
Maps account addresses to account state (balance, nonce, storage root, code hash). - Storage Trie
Each contract account has an independent storage trie mapping storage slots to values.
Trie Node Types
Node Type Description
| Node Type | Purpose | Structure | Usage |
|---|---|---|---|
| shortNode | Common prefix path compression | Key []byte, Val node | When multiple keys share a prefix |
| fullNode | Branch node | Children [17]node | 16 nibble branches + value slot |
| valueNode | Leaf value | []byte | Stores the actual RLP-encoded value |
| hashNode | Compact reference | 32-byte hash | References a sub-trie stored on disk |
nodeFlag structure manages the cache state of a node:
Key Encoding Schemes
The trie uses three key encoding formats for different purposes.Encoding Formats
| Format | Description | Usage | Example (key = 0x123) |
|---|---|---|---|
| keybytes | Raw bytes | External APIs | [0x12, 0x03] |
| hex | Nibble array + terminator | Internal trie operations | [1, 2, 3, 16] |
| compact | Compressed nibble format | Serialization / proofs | nibbles + flag bits |
16 indicates the end of the path.The compact format compresses nibbles and encodes leaf/extension information and length in flag bits.
2-Trie Architecture
Account Trie
The account trie uses the Keccak256-hashed address as the key.The value is the RLP-encoded
types.StateAccount structure.
Storage Trie
Each contract has its own storage trie, structured as follows:- Owner: Keccak256 hash of the account address
- Key: Keccak256 hash of the storage slot
- Value: RLP-encoded storage value (leading zeros removed)
Trie Operations
Get Operation
Trie.get() recursively traverses nodes from the root.When encountering a
hashNode, it loads the referenced node from the database.
Update Operation
Updates do not immediately compute hashes. Nodes are marked dirty instead.Actual hashing occurs during the commit phase, and the
unhashed counter tracks the number of unhashed leaves.
Delete Operation
Delete removes a key-value pair.Branch nodes with only a single remaining child are collapsed into a shortNode.
Hashing and Commit
Hasher Component
Thehasher performs top-down hash computation:
- Recursively hash child nodes
- Collapse nodes into
hashNodeif the RLP-encoded size is ≥ 32 bytes - Serialize the node structure with RLP
- Compute the Keccak256 hash
- Cache the result in
nodeFlag.hash
Committer Component
Thecommitter traverses the fully hashed trie and collects dirty nodes.
Output:trienode.NodeSet – a set mapping paths to node data, used for database writes.
State Root Calculation
The state root is calculated through the following steps:- Finalise
Move dirty storage to pending and update storage roots - updateRoot()
Apply per-account storage trie roots - updateStateObject()
Write account data into the account trie - IntermediateRoot()
Hash the account trie - Commit()
Commit all tries and generate the NodeSet
Proof Generation and Verification
Proof Structure
A Merkle proof consists of a sequence of RLP-encoded nodes from the root to a specific leaf.- Proof size: O(log n)
Approximately 500–2000 bytes for Ethereum state
- Light client state verification
- State synchronization
- Storage proofs for cross-chain bridges
Trie Database Layer
The trie database layer persists trie nodes to disk and resolveshashNode or path-based references into actual nodes.
StableNet supports two storage schemas.
Hash-Based Schema (Legacy)
Node key:Keccak256(RLP(node))
- Simple key-value structure
- No path information retained
- Requires garbage collection
- Inefficient for historical state access
Path-Based Schema (Modern)
Node key:owner_hash + path_from_root
- Path-based addressing
- Supports state history and fast rollbacks
- Change tracking via diff layers
- Suitable for archive nodes
Integration with State Management
Operation Flow
Account Access:- Check StateDB cache
- Load from snapshot or account trie
- Decode
types.StateAccount - Create
stateObject
- Lookup in dirty → pending → origin order
- Fallback to storage trie on miss
- RLP decoding
Finalise()updateRoot()updateStateObject()Commit()triedb.Update()
Key Implementation Details
Trie Prefetching
triePrefetcher asynchronously preloads nodes likely to be accessed during transaction execution to reduce latency.
Node Iterator
nodeIterator traverses all trie nodes using a pre-order traversal.
Tracer
tracer tracks trie modifications for use in path-based schemas and debugging.
The Merkle Patricia Trie is the core cryptographic structure of the StableNet state store.
Through the dual account/storage trie design and flexible storage schemas, StableNet efficiently and securely supports historical state access and light client verification.

