Processing Pipeline

← Back to Transactors: Understanding the Lifecycle of a Transaction


Introduction

Every transaction in the XRP Ledger passes through a rigorous multi-phase validation pipeline before it can modify ledger state. This pipeline is designed to catch errors early, minimize wasted computation, and ensure that only valid transactions with proper authorization can alter the shared ledger.

Understanding this pipeline is crucial for implementing new transaction types, debugging validation failures, and reasoning about transaction behavior under different conditions.


Pipeline Overview

The transaction processing pipeline consists of four distinct phases:

┌─────────────────────────────────────────────────────────────────────┐
│                         TRANSACTION ARRIVES                          │
└─────────────────────────────────┬───────────────────────────────────┘


┌─────────────────────────────────────────────────────────────────────┐
│  PHASE 1: PREFLIGHT                                                  │
│  ─────────────────                                                   │
│  • Stateless validation (no ledger access)                          │
│  • Format and field validation                                       │
│  • Signature structure check (not verification)                      │
│  • Returns: NotTEC (cannot return tec codes)                        │
└─────────────────────────────────┬───────────────────────────────────┘

                         ┌────────┴────────┐
                         │                 │
                         ▼                 ▼
                    ✓ Pass            ✗ Fail
                         │                 │
                         │                 └──→ Reject (tem*, tef*, ter*)

┌─────────────────────────────────────────────────────────────────────┐
│  PHASE 2: PRECLAIM                                                   │
│  ────────────────                                                    │
│  • Read-only ledger access                                          │
│  • Account existence, balance, sequence checks                       │
│  • Fee validation                                                    │
│  • Signature verification                                            │
│  • Transaction-specific state checks                                 │
│  • Returns: TER (can return tec codes)                              │
└─────────────────────────────────┬───────────────────────────────────┘

                         ┌────────┴────────┐
                         │                 │
                         ▼                 ▼
                    ✓ Pass            ✗ Fail
                         │                 │
                         │                 └──→ Reject or Queue

┌─────────────────────────────────────────────────────────────────────┐
│  PHASE 3: DOAPPLY                                                    │
│  ───────────────                                                     │
│  • Full read/write ledger access                                    │
│  • Actual state modifications                                        │
│  • Creates, updates, or deletes ledger entries                      │
│  • Returns: TER                                                      │
└─────────────────────────────────┬───────────────────────────────────┘

                         ┌────────┴────────┐
                         │                 │
                         ▼                 ▼
                  tesSUCCESS          tec* Fail
                         │                 │
                         │                 └──→ Fee charged, changes reverted

┌─────────────────────────────────────────────────────────────────────┐
│  PHASE 4: FINALIZATION                                               │
│  ───────────────────                                                 │
│  • Commit changes to ledger                                         │
│  • Record transaction metadata                                       │
│  • Consume sequence number                                           │
└─────────────────────────────────────────────────────────────────────┘

Phase 1: Preflight

Preflight performs stateless validation—checks that depend only on the transaction content itself, not on any ledger state.

Characteristics

  • No ledger access: Only has access to PreflightContext (no view)

  • Deterministic: Same transaction always produces the same result

  • Can run in parallel: No shared state dependencies

  • Returns NotTEC: Cannot return tec codes (those require fee claiming)

What Preflight Checks

  1. Amendment/Feature enablement: Is the transaction type enabled?

  2. Flag validation: Are only valid flags set?

  3. Field presence: Are required fields present?

  4. Field format: Are field values well-formed?

  5. Field ranges: Are numeric values within valid ranges?

  6. Logical consistency: Are field combinations valid?

Implementation Pattern

Transaction-specific preflight is implemented as a static method:

Why Preflight Matters

  1. Early rejection: Catches obviously invalid transactions before expensive operations

  2. Security: Runs before signature verification, so malicious actors can't waste resources

  3. Cannot claim fees: Returns NotTEC, so no fees can be claimed for malformed transactions

Important: Preflight cannot return tec codes because those codes claim the transaction fee. Since preflight runs before signature verification, allowing fee claiming would let attackers drain accounts by submitting malformed transactions with valid account IDs but invalid signatures.


Phase 2: Preclaim

Preclaim performs ledger-state validation with read-only access to the ledger.

Characteristics

  • Read-only ledger access: Has ReadView const& view

  • Signature verified: By this point, the signature has been checked

  • Can return tec codes: Fee can be claimed

  • Determines if transaction should be queued or applied

What Preclaim Checks

  1. Account existence: Does the sender account exist?

  2. Sequence number: Is the sequence correct?

  3. Fee adequacy: Is the fee sufficient?

  4. Balance sufficiency: Can the account pay the fee?

  5. Destination checks: Does destination exist? Are permissions met?

  6. Trust line state: Are trust lines frozen? Is authorization required?

  7. Expiration: Has the transaction or related object expired?

Implementation Pattern

Key Utility Functions for Preclaim

Function
Purpose

ctx.view.read(keylet)

Read a ledger entry

isGlobalFrozen(view, issuer)

Check if an issuer has globally frozen

isFrozen(view, account, issue)

Check if a specific trust line is frozen

hasExpired(view, expiration)

Check if a time has passed

isPseudoAccount(sle)

Check if account is a pseudo-account (AMM, etc.)


Phase 3: doApply

doApply performs the actual ledger modifications. This is where state changes happen.

Characteristics

  • Full read/write access: Has ApplyView& view()

  • Atomic execution: All changes succeed or all are reverted

  • Can create, update, delete ledger entries

  • Must handle reserve requirements

Implementation Pattern

Key Operations in doApply

Operation
Method
Description

Read entry

view().peek(keylet)

Get modifiable reference to entry

Create entry

view().insert(sle)

Add new entry to ledger

Update entry

view().update(sle)

Mark entry as modified

Delete entry

view().erase(sle)

Remove entry from ledger

Add to directory

view().dirInsert(...)

Add entry to owner directory

Update owner count

adjustOwnerCount(...)

Increment/decrement owner count


Phase 4: Finalization

After doApply succeeds (or fails with a tec code), the transaction is finalized:

  1. Fee consumption: Transaction fee is deducted from the sender

  2. Sequence advancement: Account sequence number is incremented

  3. Metadata recording: Changes are recorded in transaction metadata

  4. State commitment: Changes are committed to the ledger (or reverted for tec)

This phase is handled by the engine, not by individual transactors.


Error Propagation

Different phases can return different categories of result codes:

Phase
Can Return
Fee Charged?
Notes

Preflight

tem*, tef*, ter*, tes

No

No tec codes allowed

Preclaim

All codes

For tec* only

May queue for ter*

doApply

All codes

For tec*, tes

Changes reverted for tec*


Practical Example: Tracing a CheckCreate

Let's trace a CheckCreate transaction through all phases:

Preflight:

  1. Check rAlice != rBob → Pass

  2. Validate 100 XRP is positive and legal → Pass

  3. Validate expiration 750000000 != 0 → Pass

  4. Result: tesSUCCESS

Preclaim:

  1. Read Bob's account → Exists

  2. Check lsfDisallowIncomingCheck → Not set

  3. Check isPseudoAccount(Bob) → False

  4. Check lsfRequireDestTag → Not set (or tag provided)

  5. Check isGlobalFrozen(XRP) → XRP can't be frozen, skip

  6. Check hasExpired(750000000) → Not expired

  7. Result: tesSUCCESS

doApply:

  1. Read Alice's account (peek)

  2. Calculate reserve for +1 owner count → e.g., 12 XRP

  3. Check mPriorBalance >= reserve → Pass

  4. Create Check SLE with all fields

  5. Insert Check into ledger

  6. Add to Bob's owner directory → Get page number

  7. Add to Alice's owner directory → Get page number

  8. Increment Alice's owner count

  9. Result: tesSUCCESS

Finalization:

  1. Deduct fee from Alice

  2. Increment Alice's sequence

  3. Record metadata

  4. Commit changes


Best Practices for Implementing Transactors

  1. Preflight should be stateless: Never access ledger state in preflight

  2. Fail early: Check the cheapest conditions first

  3. Use appropriate error codes: tem* for format errors, tec* for state-dependent failures

  4. Check reserves before creating objects: Use mPriorBalance for reserve checks

  5. Always update owner count: When creating or deleting owned objects

  6. Always update directories: Add/remove from owner directories

  7. Log warnings: Use JLOG to help with debugging


Codebase References

File
Description

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

Transaction dispatch and phase orchestration

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

Core apply logic

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

Base class phase implementations

Last updated