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
classTransactor{protected: ApplyContext& ctx_;beast::WrappedSink sink_;beast::Journal const j_; AccountID const account_; XRPAmount mPriorBalance;// Balance before fees. XRPAmount mSourceBalance;// Balance after fees.public:enumConsequencesFactoryType{Normal,Blocker,Custom};// Main entry point for transaction applicationApplyResultoperator()();ApplyView&view(); ApplyView const&view()const;// Static methods for validation phasesstaticNotTECcheckSeqProxy(ReadViewconst&view,STTxconst&tx,beast::Journalj);staticNotTECcheckPriorTxAndLastLedger(PreclaimContextconst&ctx);staticTERcheckFee(PreclaimContextconst&ctx,XRPAmountbaseFee);staticNotTECcheckSign(PreclaimContextconst&ctx);staticXRPAmountcalculateBaseFee(ReadViewconst&view,STTxconst&tx);// Default implementations that derived classes can overridestaticTERpreclaim(PreclaimContextconst&ctx){return tesSUCCESS;}protected:TERapply();explicitTransactor(ApplyContext&ctx);virtualvoidpreCompute();virtualTERdoApply()=0;// Pure virtual - must be implemented};
Key observations:
doApply() is pure virtual: Every transaction type must implement this method
preclaim() has a default implementation: Returns tesSUCCESS, can be overridden
mPriorBalance and mSourceBalance: Track balance before and after fee deduction
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:
Transaction format definition in src/libxrpl/protocol/TxFormats.cpp
Transactor class implementation in src/xrpld/app/tx/detail/
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:
preflight(): Static method for stateless validation
preclaim(): Static method for ledger-state validation
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
Inheritance Model: All transaction types inherit from Transactor, ensuring consistent behavior
Context Objects: Three distinct contexts provide appropriate access at each phase
Pure Virtual doApply: Every transaction must implement its own state modification logic
Template-Based Preflight: The invokePreflight template ensures consistent validation ordering
Balance Tracking: mPriorBalance and mSourceBalance enable proper reserve handling
template <class T>
NotTEC
Transactor::invokePreflight(PreflightContext const& ctx)
{
// 1. Check if the transaction type's feature is enabled
auto const feature =
Permission::getInstance().getTxFeature(ctx.tx.getTxnType());
if (feature && !ctx.rules.enabled(*feature))
return temDISABLED;
// 2. Check any extra features the transaction requires
if (!T::checkExtraFeatures(ctx))
return temDISABLED;
// 3. Run preflight1 (account, fee, flags validation)
if (auto const ret = preflight1(ctx, T::getFlagsMask(ctx)))
return ret;
// 4. Run the transaction-specific preflight
if (auto const ret = T::preflight(ctx))
return ret;
// 5. Run preflight2 (signature validation)
if (auto const ret = preflight2(ctx))
return ret;
// 6. Run any post-signature validation
return T::preflightSigValidated(ctx);
}
AccountID const account_;
XRPAmount mPriorBalance; // Balance before fees
XRPAmount mSourceBalance; // Balance after fees