State Modification Patterns

← Back to Transactors: Understanding the Lifecycle of a Transaction


Introduction

When a transaction modifies the XRP Ledger, it doesn't work directly with the permanent ledger state. Instead, it operates through views—abstraction layers that provide controlled access to ledger data with support for atomic commits and rollbacks.

Understanding how to correctly read, create, update, and delete ledger entries (SLEs) through views is essential for implementing transactors that maintain ledger integrity.


Ledger Views

The view system provides three levels of access:

ReadView

Read-only access to ledger state. Used in preclaim:

class ReadView
{
public:
    // Read a ledger entry (returns nullptr if not found)
    virtual std::shared_ptr<SLE const> read(Keylet const& k) const = 0;

    // Check if an entry exists
    virtual bool exists(Keylet const& k) const = 0;

    // Get current fees
    virtual Fees const& fees() const = 0;

    // Get current rules (amendments)
    virtual Rules const& rules() const = 0;

    // Get ledger sequence
    virtual LedgerIndex seq() const = 0;
};

Example usage in preclaim:

ApplyView

Read/write access to ledger state. Used in doApply:

OpenView

Used internally by the ledger for staging changes during consensus.


Serialized Ledger Entries (SLEs)

Ledger entries are represented as SLE objects (Serialized Ledger Entries). Each SLE has:

  • A type (AccountRoot, Check, Offer, TrustLine, etc.)

  • A key (256-bit unique identifier)

  • Fields specific to that type

Creating an SLE

Reading an SLE

Modifying an SLE

Deleting an SLE


Keylets

Keylets are typed wrappers around ledger entry keys. They combine a type and a key, ensuring type safety when accessing ledger entries.

Example:


Directory Management

Directories are linked lists of ledger entry keys, used to track which objects an account owns. Every account has an owner directory that lists all objects owned by that account.

Adding to a Directory

When creating a new ledger object, add it to the appropriate directories:

For objects that relate to two accounts (like Checks), add to both directories:

Removing from a Directory

When deleting a ledger object, remove it from all directories:


Owner Count Management

Each account tracks how many ledger objects it owns via the sfOwnerCount field. This count affects the account's reserve requirement.

Incrementing Owner Count

When creating a new owned object:

Decrementing Owner Count

When deleting an owned object:

The adjustOwnerCount Function

This function:

  1. Gets the current owner count

  2. Adds the adjustment

  3. Updates the account SLE


Reserve Checking

Before creating a new object, verify the account can afford the increased reserve:

Why use mPriorBalance?

The reserve is checked against the balance before the transaction fee is deducted. This allows accounts to dip into their reserve to pay fees, which is important for cleaning up objects when an account is low on funds.


Atomic Operations

All changes made through views are staged and only committed if the transaction succeeds. If the transaction fails with a tec code:

  1. All state changes are reverted

  2. The fee is still charged

  3. The sequence number is still consumed

This ensures that failed transactions never leave the ledger in an inconsistent state.

How Atomicity Works

  1. Staging: Changes are made to a view layer, not the actual ledger

  2. Validation: All invariants are checked

  3. Commit or Rollback:

    • On tesSUCCESS: Changes are committed

    • On tec*: Changes are reverted, but fee/sequence applied

    • On other failures: Nothing is applied


Common Utility Functions

Reading Account Balances

Checking Freeze Status

Checking Expiration


Best Practices

  1. Always check entry existence: Use peek() or read() before accessing fields

  2. Use keylets for type safety: Don't construct keys manually

  3. Update directories on create/delete: Maintain bidirectional links

  4. Update owner count on create/delete: Keep reserve calculations correct

  5. Check reserves before creating: Prevent tecINSUFFICIENT_RESERVE failures

  6. Remove from directories before erasing: Clean up all references

  7. Use mPriorBalance for reserve checks: Allow fee payment from reserves


Codebase References

File
Description

include/xrpl/ledger/ReadView.h

ReadView interface

include/xrpl/ledger/ApplyView.h

ApplyView interface

include/xrpl/ledger/View.h

View utility functions

src/xrpld/ledger/detail/View.cpp

View implementation

include/xrpl/protocol/Indexes.h

Keylet definitions

Last updated