Transactor Architecture

← Back to Transactors: Understanding the Lifecycle of a Transaction


Introduction

Every transaction that modifies the XRP Ledger—whether it's a payment, an offer, a trust line, or any other operation—is processed by a Transactor. The transactor architecture provides a consistent, safe, and extensible framework for implementing transaction types while ensuring that the ledger remains in a valid state.

Understanding this architecture is essential for anyone who wants to implement new transaction types, debug validation failures, or contribute to the rippled codebase. This section explores the layered design of the transaction engine and how each component contributes to the safety and correctness of ledger modifications.


The Transactor Base Class

The Transactor class, defined in src/xrpld/app/tx/detail/Transactor.h, is the foundation of all transaction processing in rippled. Every transaction type inherits from this base class, which provides:

  • Common validation logic: Signature verification, fee checks, sequence number validation

  • Helper methods: Account balance queries, ledger state access, fee calculation

  • Virtual methods: Hooks for transaction-specific logic (preflight, preclaim, doApply)

  • Transaction context: Access to the ledger view, transaction data, and application state

Core Class Structure

class Transactor
{
protected:
    ApplyContext& ctx_;
    beast::WrappedSink sink_;
    beast::Journal const j_;

    AccountID const account_;
    XRPAmount mPriorBalance;   // Balance before fees.
    XRPAmount mSourceBalance;  // Balance after fees.

public:
    enum ConsequencesFactoryType { Normal, Blocker, Custom };

    // Main entry point for transaction application
    ApplyResult operator()();

    ApplyView& view();
    ApplyView const& view() const;

    // Static methods for validation phases
    static NotTEC checkSeqProxy(ReadView const& view, STTx const& tx, beast::Journal j);
    static NotTEC checkPriorTxAndLastLedger(PreclaimContext const& ctx);
    static TER checkFee(PreclaimContext const& ctx, XRPAmount baseFee);
    static NotTEC checkSign(PreclaimContext const& ctx);
    static XRPAmount calculateBaseFee(ReadView const& view, STTx const& tx);

    // Default implementations that derived classes can override
    static TER preclaim(PreclaimContext const& ctx) { return tesSUCCESS; }

protected:
    TER apply();
    explicit Transactor(ApplyContext& ctx);

    virtual void preCompute();
    virtual TER doApply() = 0;  // Pure virtual - must be implemented
};

Key observations:

  1. doApply() is pure virtual: Every transaction type must implement this method

  2. preclaim() has a default implementation: Returns tesSUCCESS, can be overridden

  3. mPriorBalance and mSourceBalance: Track balance before and after fee deduction

  4. ctx_: The ApplyContext provides access to the transaction, view, and application


Context Objects

The transactor framework uses three context objects that provide different levels of access at each processing phase:

PreflightContext

Used during the preflight phase for stateless validation:

What it provides:

  • Access to the raw transaction (tx)

  • Protocol rules that are currently enabled (rules)

  • Application flags (flags)

  • Logging journal (j)

What it does NOT provide:

  • Any ledger state (no view)

This limitation is intentional—preflight checks must be stateless and deterministic based solely on the transaction content.

PreclaimContext

Used during the preclaim phase for read-only ledger validation:

What it provides:

  • Read-only access to the ledger (view)

  • The result from preflight (preflightResult)

  • Transaction and application context

Key distinction: The view is ReadView const&—you can read ledger state but cannot modify it.

ApplyContext

Used during the doApply phase for ledger modification:

What it provides:

  • Full read/write access to the ledger via view()

  • Transaction data and application services

  • Methods for delivering amounts, tracking metadata


Transaction Type Registration

New transaction types must be registered with the transaction engine. This is done through a combination of:

  1. Transaction format definition in src/libxrpl/protocol/TxFormats.cpp

  2. Transactor class implementation in src/xrpld/app/tx/detail/

  3. Registration in applySteps.cpp

Example: CheckCreate Registration

In TxFormats.cpp:

This defines:

  • The JSON name (jss::CheckCreate)

  • The transaction type enum (ttCHECK_CREATE)

  • Required and optional fields

  • Common fields inherited by all transactions


The Transactor Hierarchy

All specific transaction types inherit from Transactor and implement their own validation logic:

Each derived class typically implements:

  1. preflight(): Static method for stateless validation

  2. preclaim(): Static method for ledger-state validation

  3. doApply(): Instance method for applying changes


The invokePreflight Template

The base class provides a template method that orchestrates the preflight phase:

This template ensures consistent ordering of validation steps across all transaction types.


Key Member Variables

account_

The AccountID of the transaction sender, extracted from the sfAccount field:

This is set during construction and used throughout transaction processing.

mPriorBalance and mSourceBalance

These track the sender's XRP balance:

  • mPriorBalance: Balance at the start of transaction processing

  • mSourceBalance: Balance after the transaction fee is deducted

This is used for reserve calculations—the reserve is checked against mPriorBalance to allow accounts to dip into reserves to pay fees.

ctx_

The ApplyContext provides access to:

  • ctx_.tx: The transaction being processed

  • ctx_.view(): The ledger view for reading/writing

  • ctx_.app: The application instance

  • ctx_.journal: Logging


Helper Methods

The base class provides several helper methods used by derived transactors:

view()

Returns the ledger view for reading and modifying ledger state.

calculateBaseFee()

Calculates the base transaction fee based on the transaction type and current fee settings.

minimumFee()

Calculates the minimum fee required considering current load and fee escalation.


Codebase References

File
Description

src/xrpld/app/tx/detail/Transactor.h

Base Transactor class definition

src/xrpld/app/tx/detail/Transactor.cpp

Base class implementation

src/xrpld/app/tx/detail/ApplyContext.h

ApplyContext definition

src/xrpld/app/tx/applySteps.cpp

Transaction type dispatch

src/libxrpl/protocol/TxFormats.cpp

Transaction format definitions


Key Takeaways

  1. Inheritance Model: All transaction types inherit from Transactor, ensuring consistent behavior

  2. Context Objects: Three distinct contexts provide appropriate access at each phase

  3. Pure Virtual doApply: Every transaction must implement its own state modification logic

  4. Template-Based Preflight: The invokePreflight template ensures consistent validation ordering

  5. Balance Tracking: mPriorBalance and mSourceBalance enable proper reserve handling

Last updated