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.harrow-up-right, Ledger.cpparrow-up-right

// 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 mImmutable is set to true via setImmutable(), the ledger cannot be modified. Only immutable ledgers can be stored in a LedgerHolder or published to the network.

  • SHAMaps: Both stateMap_ and txMap_ are Merkle trees. The root hash of each tree is stored in header_.accountHash and header_.txHash respectively.

  • 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.harrow-up-right

Key Header Fields:

Field
Purpose

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.harrow-up-right

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.harrow-up-right, LedgerHistory.cpparrow-up-right

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:

Operation
Mutable Ledger
Immutable Ledger

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.harrow-up-right

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 Rules object 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 LogicError if the operation fails (duplicate key, missing key, etc.)

Database Persistence

Ledgers are stored using a two-tier system:

Why Two Storage Systems?

  1. SQL Database: Stores ledger headers for fast sequential access and indexing

  2. NodeStore: Stores SHAMap tree nodes for efficient content-addressable storage

When loading a ledger:

  1. Retrieve header from SQL database by sequence or hash

  2. Construct empty SHAMaps with root hashes from header

  3. 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.cpparrow-up-right and SQLiteDatabase.cpparrow-up-right for details.

Summary

Core Data Structures:

Structure
Purpose
Key Features

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:

  1. Immutability: Validated ledgers cannot be modified

  2. Efficient Lookup: Dual indexing by hash and sequence

  3. Thread Safety: LedgerHolder provides safe concurrent access

  4. Persistence: Multiple storage backends supported

  5. 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:

In the next chapter, we'll explore how ledgers are acquired from the network and validated.

Last updated