Transactors: Transaction Processing Framework

← Back to Rippled II Overview


Introduction

Transactors are the heart of transaction processing in the XRP Ledger. Every transaction type—from simple XRP payments to complex escrow operations—is implemented as a Transactor, a C++ class that defines how that transaction is validated and executed. Understanding the transactor framework is essential for anyone who wants to contribute to the core protocol, implement custom transaction types, or debug transaction-related issues.

The transactor architecture ensures that all transactions undergo rigorous validation before modifying ledger state, maintaining the integrity and security that makes the XRP Ledger reliable for financial applications.


The Transactor Base Class

Architecture Overview

Every transaction type in Rippled inherits from the Transactor base class, which provides the fundamental framework for transaction processing. This inheritance model ensures consistent behavior across all transaction types while allowing each type to implement its specific business logic.

The base Transactor class is defined in src/ripple/app/tx/impl/Transactor.h and 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, transaction data, and application state

Base Class Structure

Key Concepts

ApplyContext: Provides access to the transaction being processed, the ledger view, and application services. This context object is passed through all stages of transaction processing.

Transaction Engine Result (TER): Every validation step returns a TER code indicating success (tesSUCCESS), temporary failure (ter codes), or permanent failure (tem or tef codes). These codes determine whether a transaction can be retried or should be permanently rejected.

Ledger Views: Transactors work with "views" of the ledger state, allowing tentative modifications that can be committed or rolled back. This ensures atomic transaction processing.


Three-Phase Validation Process

The transactor framework implements a rigorous three-phase validation process. Each phase has a specific purpose and access to different levels of information, creating a defense-in-depth approach to transaction validation.

Phase 1: Preflight

Purpose: Static validation that doesn't require ledger state

Access: Only the raw transaction data and protocol rules

When It Runs: Before any ledger state is accessed, can run in parallel

What It Checks:

  • Transaction format is valid

  • Required fields are present

  • Field values are within valid ranges

  • Amounts are positive and properly formatted

  • No malformed or contradictory data

Key Characteristic: Preflight checks are deterministic and stateless—they depend only on the transaction itself, not on current ledger state.

Preflight Example: Payment Transaction

Why Preflight Matters: By catching format errors early, preflight prevents wasting resources on obviously invalid transactions. It also provides fast feedback to clients about transaction formatting issues.

Phase 2: Preclaim

Purpose: Validation requiring read-only access to ledger state

Access: Current ledger state (read-only), transaction data, protocol rules

When It Runs: After preflight passes, but before any state modifications

What It Checks:

  • Source account exists and has sufficient balance

  • Destination account exists (or can be created)

  • Required authorizations are in place

  • Trust lines exist for non-XRP currencies

  • Account flags and settings permit the transaction

  • Sequence numbers are correct

Key Characteristic: Preclaim can read ledger state but cannot modify it. This allows for safe concurrent execution and caching of preclaim results.

Preclaim Example: Payment Transaction

Why Preclaim Matters: Preclaim catches state-dependent errors before attempting state modifications. This prevents partially-applied transactions and provides clear error messages about why a transaction cannot succeed.

Phase 3: DoApply

Purpose: Actual ledger state modification

Access: Full read/write access to ledger state

When It Runs: After both preflight and preclaim succeed

What It Does:

  • Debits source account

  • Credits destination account

  • Creates or modifies ledger objects

  • Applies transaction-specific business logic

  • Records transaction metadata

  • Consumes transaction fee

Key Characteristic: DoApply modifies ledger state. All changes are atomic—either the entire transaction succeeds and all changes are applied, or it fails and no changes are made.

DoApply Example: Payment Transaction

Why DoApply Matters: This is where the actual ledger state changes happen. DoApply ensures that only transactions that have passed all validation steps can modify the ledger, maintaining data integrity.


Transaction Types in Detail

The XRP Ledger supports numerous transaction types, each implemented as a specific transactor. Understanding the most common types helps you navigate the codebase and understand protocol capabilities.

Payment

File: src/ripple/app/tx/impl/Payment.cpp

Purpose: Transfer XRP or issued currencies between accounts

Key Features:

  • Direct XRP transfers

  • Issued currency transfers via trust lines

  • Path-based payments (automatic currency conversion)

  • Partial payments (deliver less than requested if full amount unavailable)

Common Fields:

  • Account - Source account

  • Destination - Recipient account

  • Amount - Amount to deliver

  • SendMax (optional) - Maximum amount to send

  • Paths (optional) - Payment paths for currency conversion

  • DestinationTag (optional) - Identifier for the recipient

Use Cases:

  • Simple XRP transfers

  • Issued currency payments

  • Cross-currency payments

  • Payment channel settlements

OfferCreate

File: src/ripple/app/tx/impl/CreateOffer.cpp

Purpose: Place an offer on the decentralized exchange (DEX)

Key Features:

  • Buy or sell any currency pair

  • Immediate-or-cancel orders

  • Fill-or-kill orders

  • Passive offers (don't consume existing offers)

  • Auto-bridging via XRP

Common Fields:

  • TakerPays - Asset the taker (matcher) pays

  • TakerGets - Asset the taker receives

  • Expiration (optional) - When offer expires

  • OfferSequence (optional) - Sequence of offer to replace

Use Cases:

  • Currency exchange

  • Market making

  • Arbitrage

  • Limit orders

OfferCancel

File: src/ripple/app/tx/impl/CancelOffer.cpp

Purpose: Remove an offer from the order book

Key Features:

  • Cancel by offer sequence number

  • Only offer owner can cancel

Common Fields:

  • OfferSequence - Sequence number of offer to cancel

TrustSet

File: src/ripple/app/tx/impl/SetTrust.cpp

Purpose: Create or modify a trust line for issued currencies

Key Features:

  • Set trust limit for a currency

  • Authorize/deauthorize trust lines

  • Configure trust line flags

Common Fields:

  • LimitAmount - Trust line limit and currency

  • QualityIn (optional) - Exchange rate for incoming transfers

  • QualityOut (optional) - Exchange rate for outgoing transfers

Use Cases:

  • Accept issued currencies

  • Set credit limits

  • Freeze trust lines

EscrowCreate

File: src/ripple/app/tx/impl/Escrow.cpp

Purpose: Lock XRP until conditions are met

Key Features:

  • Time-based release (CryptoConditions)

  • Conditional release (Interledger Protocol conditions)

  • Guaranteed delivery or return

Common Fields:

  • Destination - Who can claim the escrow

  • Amount - Amount of XRP to escrow

  • FinishAfter (optional) - Earliest finish time

  • CancelAfter (optional) - When escrow can be cancelled

  • Condition (optional) - Cryptographic condition for release

EscrowFinish

File: src/ripple/app/tx/impl/Escrow.cpp

Purpose: Complete an escrow and deliver XRP

Key Features:

  • Must meet time and/or condition requirements

  • Can be executed by anyone (typically destination)

Common Fields:

  • Owner - Account that created the escrow

  • OfferSequence - Sequence of EscrowCreate transaction

  • Fulfillment (optional) - Fulfillment of cryptographic condition

EscrowCancel

File: src/ripple/app/tx/impl/Escrow.cpp

Purpose: Return escrowed XRP to owner

Key Features:

  • Only after CancelAfter time passes

  • Can be executed by anyone

AccountSet

File: src/ripple/app/tx/impl/SetAccount.cpp

Purpose: Modify account settings and flags

Key Features:

  • Set account flags

  • Configure transfer rate

  • Set domain and message key

  • Configure email hash

Common Fields:

  • SetFlag / ClearFlag - Flags to modify

  • TransferRate (optional) - Fee for transferring issued currencies

  • Domain (optional) - Domain associated with account

  • MessageKey (optional) - Public key for encrypted messaging

Important Flags:

  • asfRequireDest - Require destination tag

  • asfRequireAuth - Require authorization for trust lines

  • asfDisallowXRP - Disallow XRP payments

  • asfDefaultRipple - Enable rippling by default

SignerListSet

File: src/ripple/app/tx/impl/SetSignerList.cpp

Purpose: Create or modify multi-signature configuration

Key Features:

  • Define list of authorized signers

  • Set signing quorum

  • Enable complex authorization schemes

Common Fields:

  • SignerQuorum - Required signature weight

  • SignerEntries - List of authorized signers with weights

PaymentChannelCreate

File: src/ripple/app/tx/impl/PayChan.cpp

Purpose: Open a unidirectional payment channel

Key Features:

  • Lock XRP for fast, off-ledger payments

  • Asynchronous payments with cryptographic claims

  • Efficient micropayments


Creating Custom Transactors

When implementing new features through amendments, you'll often need to create custom transactors. Here's the complete process:

Step 1: Define Transaction Format

Add your transaction type to src/ripple/protocol/TxFormats.cpp:

Step 2: Create Transactor Class

Create src/ripple/app/tx/impl/MyCustomTx.h:

Step 3: Implement Preflight

Create src/ripple/app/tx/impl/MyCustomTx.cpp:

Step 4: Implement Preclaim

Step 5: Implement DoApply

Step 6: Register the Transactor

Add to src/ripple/app/tx/applySteps.cpp:

Step 7: Write Tests

Create src/test/app/MyCustomTx_test.cpp:


Transaction Lifecycle Within Framework

Understanding how a transaction flows through the transactor framework helps debug issues and optimize performance.

Complete Flow Diagram

Error Code Categories

tem (Malformed): Transaction is permanently invalid due to format issues

  • Example: temMALFORMED, temBAD_AMOUNT, temDISABLED

  • Action: Reject immediately, never retry

tef (Failure): Transaction failed during local checks

  • Example: tefFAILURE, tefPAST_SEQ

  • Action: Reject, may indicate client error

ter (Retry): Transaction failed but might succeed later

  • Example: terQUEUED, terPRE_SEQ

  • Action: Can be retried after conditions change

tec (Claimed Fee): Transaction failed but consumed fee

  • Example: tecUNFUNDED, tecNO_DST, tecNO_PERMISSION

  • Action: Failed permanently, fee charged

tes (Success): Transaction succeeded

  • Example: tesSUCCESS

  • Action: Changes committed to ledger


Hands-On Exercise

Exercise: Trace and Modify a Payment Transaction

Objective: Understand the payment transactor implementation through debugging and modification.

Part 1: Code Exploration

Step 1: Navigate to the Payment transactor

Step 2: Identify the three phases

Find and read:

  • Payment::preflight() - Lines implementing static checks

  • Payment::preclaim() - Lines checking ledger state

  • Payment::doApply() - Lines modifying state

Step 3: Trace a specific check

Follow how the Payment transactor checks if a destination requires a destination tag:

Questions:

  • Where is lsfRequireDestTag defined?

  • How is this flag set on an account?

  • What transaction type sets this flag?

Part 2: Debug a Payment

Step 1: Set up standalone mode with logging

Enable transaction logging:

Step 2: Create test accounts

Step 3: Set destination tag requirement

Step 4: Try payment without destination tag

Step 5: Try payment with destination tag

Part 3: Modify the Transactor (Advanced)

Step 1: Add custom logging

Edit Payment.cpp and add logging to doApply():

Step 2: Recompile rippled

Step 3: Run with your modified code

Step 4: Submit a payment and observe your logs

Analysis Questions

Answer these based on your exploration:

  1. What happens in each validation phase?

    • List the checks performed in preflight

    • List the checks performed in preclaim

    • What state modifications occur in doApply?

  2. How are transaction fees handled?

    • Where is payFee() called?

    • What happens if an account can't pay the fee?

  3. How does the code handle XRP vs issued currencies?

    • Find the code that distinguishes between them

    • How do payment paths work for issued currencies?

  4. What's the role of the accountSend() helper?

    • Where is it implemented?

    • What does it do internally?


Key Takeaways

Core Concepts

Three-Phase Validation: Preflight (static), Preclaim (read state), DoApply (modify state) ensures robust transaction processing

Inheritance Architecture: All transaction types inherit from Transactor base class, ensuring consistent behavior

Error Code System: tem/tef/ter/tec/tes codes provide clear feedback about transaction status

Atomic Execution: Transactions either fully succeed or fully fail (except fee consumption)

State Views: Ledger modifications happen in views that can be committed or rolled back

Development Skills

Codebase Location: Transaction implementations in src/ripple/app/tx/impl/

Creating Custom Transactions: Follow the pattern of defining format, implementing phases, registering transactor

Debugging: Use standalone mode and logging to trace transaction execution

Testing: Write comprehensive unit tests for all transaction scenarios

Amendment Integration: New transaction types typically require amendments for activation


Common Patterns and Best Practices

Pattern 1: Check-Then-Act

Always validate before modifying state:

Pattern 2: Use Helper Functions

The Transactor base class provides many helpers:

Pattern 3: Ledger Object Patterns

Creating, modifying, and deleting ledger objects:


Additional Resources

Official Documentation

Codebase References

  • src/ripple/app/tx/impl/ - All transactor implementations

  • src/ripple/app/tx/impl/Transactor.h - Base transactor class

  • src/ripple/protocol/TxFormats.cpp - Transaction format definitions

  • src/ripple/protocol/TER.h - Transaction result codes


Last updated