Ledger Data Structures
← Back to Consensus I: Node, Consensus, and Ledger Fundamentals
Introduction
The XRP Ledger's reliability and performance depend on carefully designed data structures. This chapter explores the core classes that represent ledgers, manage their lifecycle, and provide efficient access to historical data.
The XRPL ledger is the authoritative record of the network's state at a given point in time. It contains all account balances, offers, escrows, and other objects, as well as a record of all transactions included in that ledger.
Every server always has an open ledger. All received new transactions are applied to the open ledger. The open ledger can't close until consensus is reached on the previous ledger and either there is at least one transaction or the ledger's close time has been reached.
Understanding these structures is essential for:
Working with the codebase effectively
Debugging ledger-related issues
Implementing new features that interact with ledger state
Understanding how consensus and validation work
The Ledger Class
The Ledger class is the primary representation of a single ledger instance. It manages both the state (account balances, offers, escrows, etc.) and transaction data for a specific ledger.
Location: LedgerHeader.h, Ledger.cpp
// Ledger.h (actual source code)
class Ledger final : public std::enable_shared_from_this<Ledger>,
public DigestAwareReadView,
public TxsRawView
{
private:
// Ledger metadata (sequence, hashes, close time, etc.)
LedgerHeader header_;
// State tree (all account states, trust lines, offers, escrows, etc.)
SHAMap mutable stateMap_;
// Transaction tree (all transactions and their metadata)
SHAMap mutable txMap_;
// Immutability flag - once true, ledger cannot be modified
bool mImmutable;
// Protocol rules and enabled amendments for this ledger
Rules rules_;
// Fee schedule for this ledger
Fees fees_;
// ... additional private members ...
};Key Points:
Immutability: Once
mImmutableis set totrueviasetImmutable(), the ledger cannot be modified. Only immutable ledgers can be stored in aLedgerHolderor published to the network.SHAMaps: Both
stateMap_andtxMap_are Merkle trees. The root hash of each tree is stored inheader_.accountHashandheader_.txHashrespectively.Rules: The
rules_object determines which amendments are active, affecting how transactions are processed.
Core Components:
Construction Methods
Ledgers can be created in several ways, depending on the source of data:
1. Genesis Ledger:
Used to create the very first ledger in a new network.
2. From Previous Ledger:
This is the most common constructor used during consensus to build the next ledger.
3. From Serialized Data:
Used when loading historical ledgers from the database or acquiring them from peers.
LedgerHeader Structure
The LedgerHeader struct contains all the metadata that uniquely identifies a ledger and its state. This is the data that hashes to the ledger's hash.
Location: LedgerHeader.h
Key Header Fields:
seq
Ledger sequence number, incrementing from genesis
parentHash
Cryptographic link to previous ledger
accountHash
Root hash of state tree (from stateMap_.getHash())
txHash
Root hash of transaction tree (from txMap_.getHash())
drops
Total XRP in existence (decreases as fees are burned)
closeTime
When this ledger closed (consensus-agreed time)
parentCloseTime
Parent ledger's close time
closeTimeResolution
Granularity of close time (2-120 seconds)
closeFlags
Indicates if close time had consensus
validated
Set to true once ledger receives quorum validations
Header Hashing:
The ledger hash uniquely identifies the ledger and is computed by serializing and hashing the header fields:
This creates a unique, tamper-evident fingerprint for the entire ledger state. Any change to the state tree, transaction tree, or metadata produces a different hash.
Note: The "ledger base" refers to a query/response that includes the ledger header and may also contain the root node of the state tree. This is used during ledger acquisition from peers.
LedgerHolder
A thread-safe container that holds an immutable ledger. Only immutable ledgers can be held - this is enforced at runtime.
Location: LedgerHolder.h
Why immutability matters:
Multiple threads can safely read from an immutable ledger without locks
Once set, the ledger's state never changes, preventing race conditions
LedgerMaster uses LedgerHolders to track
mValidLedger,mClosedLedger, etc.
Usage Pattern:
LedgerHistory
Manages the cache and retrieval of historical ledgers.
Location: LedgerHistory.h, LedgerHistory.cpp
Cache Organization:
Key Operations:
The insert() method (from LedgerHistory.cpp) adds ledgers to both caches and ensures the index-to-hash mapping is correct. The fixIndex() method is used to correct any inconsistencies in the index mapping that may occur during acquisition.
Immutability Enforcement
Once a ledger is finalized, it can never be changed. This is enforced both at compile time (via const correctness) and runtime (via the mImmutable flag).
Immutability Lifecycle:
Immutability Rules:
Modify state (rawInsert, rawReplace)
✅ Allowed
❌ Forbidden
Add transactions
✅ Allowed
❌ Forbidden
Store in LedgerHolder
❌ Runtime error
✅ Required
Publish to network
❌ Forbidden
✅ Required
Cache in LedgerHistory
❌ Not typical
✅ Allowed
Read operations (read, peek)
✅ Allowed
✅ Allowed
SHAMap Integration
Ledgers use SHAMaps for state and transaction storage. The SHAMap is a Merkle-Patricia trie covered in detail in later modules. Here we focus on how the Ledger class interacts with it:
When setImmutable(true) is called, the final root hashes are retrieved from the SHAMaps and stored in the LedgerHeader.
The Rules Class
The Rules class encapsulates which amendments (protocol upgrades) are enabled for a specific ledger. All nodes must agree on which amendments are active for deterministic transaction processing.
Location: Rules.h
How Rules Are Used:
Throughout transaction processing, the code checks if specific amendments are enabled to determine which logic path to use:
Amendment Activation:
Why This Matters:
Consensus requires determinism: All validators must process transactions identically
Amendments change behavior: Different amendment sets → different results
Rules track the truth: The
Rulesobject for ledger N reflects exactly which amendments were active when ledger N was built
See Amendments in Consensus II for full details on the amendment system.
Key Operations
The Ledger class provides methods to read and modify state objects. All operations work through the stateMap_ SHAMap.
Reading State:
The read() method uses a Keylet for type-safe lookups. A Keylet combines a key with a type checker, ensuring you retrieve the correct object type (e.g., keylet::account(accountID) for account roots).
Modifying State (Mutable Ledgers Only):
These methods:
Only work on mutable ledgers (before
setImmutable()is called)Serialize the SLE to binary format
Add/update/remove items in
stateMap_Throw
LogicErrorif the operation fails (duplicate key, missing key, etc.)
Database Persistence
Ledgers are stored using a two-tier system:
Why Two Storage Systems?
SQL Database: Stores ledger headers for fast sequential access and indexing
NodeStore: Stores SHAMap tree nodes for efficient content-addressable storage
When loading a ledger:
Retrieve header from SQL database by sequence or hash
Construct empty SHAMaps with root hashes from header
Load tree nodes from NodeStore on-demand as they're accessed
Integrity Verification:
Before persisting, the system verifies hash consistency:
This ensures the header accurately represents the tree contents. Mismatches indicate data corruption, Byzantine attacks, or implementation bugs.
Implementation: See Node.cpp and SQLiteDatabase.cpp for details.
Summary
Core Data Structures:
Ledger
Single ledger instance
State + Tx maps, immutability
LedgerHeader
Header metadata
Hashing, identification
LedgerHolder
Thread-safe container
Mutex protection, const enforcement
LedgerHistory
Cache management
Hash/seq lookup, validation tracking
Rules
Protocol configuration
Amendment checking
Key Properties:
Immutability: Validated ledgers cannot be modified
Efficient Lookup: Dual indexing by hash and sequence
Thread Safety: LedgerHolder provides safe concurrent access
Persistence: Multiple storage backends supported
Integrity: Hash verification at all levels
Design Patterns:
Shared pointers for memory management
Copy-on-write for efficient branching
Const correctness for immutability
Tagged caches for LRU eviction
Pimpl idiom for Rules (hides implementation details)
Source Code References:
Ledger.h - Ledger class definition
Ledger.cpp - Ledger implementation
LedgerHeader.h - LedgerHeader structure
LedgerHolder.h - Thread-safe holder
LedgerHistory.h - History cache
Rules.h - Amendment rules
In the next chapter, we'll explore how ledgers are acquired from the network and validated.
Last updated

