Storage Model
Aptos
Jellyfish Merkle Tree + Block-STM Parallelism

Aptos uses the Jellyfish Merkle Tree — an optimized sparse Merkle tree with efficient proofs. Combined with Block-STM (parallel execution via software transactional memory), Aptos achieves high throughput without pre-declaring account access.

Jellyfish Merkle Tree

🌿

Binary (not 16-way)

Ethereum MPT: 16-way trie (hex nibbles). JMT: binary tree (bit-by-bit). More space-efficient for sparse state.

💡

Sparse Optimization

Empty subtrees collapse to a single null hash. When most of the 2^256 key space is empty, this saves enormous space.

📚

Versioned

JMT supports multiple versions (like Cosmos IAVL). Historical proofs work without archive nodes.

jellyfish-merkle.rsrust
1// Jellyfish Merkle Tree (JMT) — Aptos's State Tree
2// An optimized Sparse Merkle Tree for account storage
3
4// Key difference from regular SMT and Ethereum MPT:
5// 1. Binary (not 16-way like MPT)
6// 2. Sparse (not dense like MPT)
7// 3. Leaf nodes store full key+value (efficient for sparse data)
8// 4. Internal nodes only exist when needed (sparse optimization)
9
10// Node types in JMT:
11enum Node {
12 // Leaf: stores the full key-value pair
13 Leaf { key: HashValue, value_hash: HashValue },
14
15 // Internal: binary branch (left/right, not 16-way)
16 Internal { left: Option<Child>, right: Option<Child> },
17
18 // Null: empty subtree (huge optimization!)
19 Null,
20}
21
22// Key derivation:
23// key = keccak256(address) (same as Ethereum!)
24// But traversal is BINARY (bit-by-bit), not 16-way (nibble-by-nibble)
25
26// Storage:
27// DB[hash(node)] = serialize(node) — same concept as Ethereum

Move Resources — Aptos's State Model

Unlike Sui (objects), Aptos stores state as resources under account addresses. Think: account address → {resource_type → data}.

move-resources.moverust
1// Aptos Move Resources — Account-Centric State
2// Different from Sui's object model
3
4// In Aptos, state lives under accounts as "resources"
5module aptos_framework::coin {
6 struct CoinStore<phantom CoinType> has key {
7 coin: Coin<CoinType>,
8 frozen: bool,
9 deposit_events: EventHandle<DepositEvent>,
10 withdraw_events: EventHandle<WithdrawEvent>,
11 }
12
13 struct Coin<phantom CoinType> has store {
14 value: u64,
15 }
16}
17
18// State access pattern:
19// borrow_global<CoinStore<AptosCoin>>(alice_address)
20// → looks up: alice_address / 0x1::coin::CoinStore<AptosCoin>
21
22// Storage path in JMT:
23// key = hash(account_address ++ struct_tag)
24// → Alice's APT balance path:
25// key = hash(alice ++ "0x1::coin::CoinStore<0x1::aptos_coin::AptosCoin>")
⚖️

Aptos vs Sui Object Model

Aptos (resources under accounts):
alice_address.CoinStore = { value: 100 }
alice_address.NFT = { id: 1, ... }
Sui (independent objects):
object_0x1234 = { owner: alice, value: 100 }
object_0x5678 = { owner: alice, id: 1, ... }
🔑

Storage Path Format

address / module::StructName

JMT key = hash(this path)

binary tree traversal

Storage Path → JMT Key

storage-path.rsrust
1// Aptos Storage Paths — Account + Resource + Key
2
3// State stored under account addresses as "resources":
4// path = account_address / module_address::module_name::StructName / (optional key)
5
6// Examples:
7alice_apt_balance =
8 alice_address / 0x1::coin::CoinStore<0x1::aptos_coin::AptosCoin>
9
10alice_nft =
11 alice_address / my_module::nft::NFT / nft_id
12
13// JMT key = hash(path) → binary trie traversal
14// Value = BCS-encoded (Binary Canonical Serialization) resource
15
16// Compare to Ethereum:
17// ETH: all state in one global trie, contract storage sub-trie
18// Aptos: state scattered across account subtrees in JMT
19
20// Multiple version support (like Cosmos IAVL):
21// JMT is versioned → can query historical state per version
22statedb.get_with_proof(key, version: 1000000)

Block-STM — Dynamic Parallel Execution

Unlike Solana (must pre-declare accounts), Aptos detects conflicts during execution and re-runs only conflicting transactions.

block-stm.rsrust
1// Block-STM — Aptos's Parallel Execution
2// Software Transactional Memory for blockchain
3
4// Unlike Solana (pre-declared accounts),
5// Aptos figures out conflicts DURING execution:
6
7// Step 1: Optimistically execute all txs in parallel
8// (assume no conflicts)
9
10// Step 2: Track read/write sets during execution
11tx1.reads = {alice.balance}
12tx1.writes = {alice.balance: 100, bob.balance: 200}
13
14tx2.reads = {alice.balance} // reads same as tx1 writes!
15tx2.writes = {alice.balance: 50}
16
17// Step 3: Detect conflicts — tx2 read alice.balance
18// but tx1 wrote it. tx2 must re-execute after tx1!
19
20// Step 4: Re-execute conflicting txs in correct order
21// Non-conflicting txs: committed immediately ✅
22// Conflicting txs: re-executed with updated state
23
24// WHY this is better than Solana for complex apps:
25// - No need to pre-declare all accessed accounts
26// - Works with Ethereum-like smart contract patterns
27// - Dynamic conflict detection = more flexible
Solana (Sealevel)

Pre-declare all accounts. Scheduler knows conflicts upfront. Simple but rigid.

Aptos (Block-STM)

Execute optimistically. Detect conflicts. Re-execute conflicting txs. More flexible.

Ethereum

Sequential. No parallelism. Every tx runs after the previous. Bottleneck.

How JMT + Block-STM → Capabilities

JMT sparse optimization

Very efficient for sparse state (most accounts don't exist). Proof size much smaller than dense MPT for realistic datasets.

Block-STM dynamic conflict detection

Developers don't need to pre-declare account access (unlike Solana). Easier to port Ethereum-like patterns to Aptos.

Resources under accounts (not global objects)

State is naturally co-located with accounts. Easy to query all resources for a user. But shared DeFi pools are less natural.

⚠️
Re-execution on conflict

High-contention scenarios (popular DEX, airdrop) can cause many re-executions. Throughput degrades under contention.

JMT versioning

Like Cosmos, historical queries possible. But Aptos prunes by default — need to configure to keep history.