#pragma once #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace xrpl { /** State information when preflighting a tx. */ struct PreflightContext { public: std::reference_wrapper registry; STTx const& tx; Rules const rules; ApplyFlags flags; std::optional parentBatchId; beast::Journal const j; PreflightContext( ServiceRegistry& registry, STTx const& tx, uint256 parentBatchId, Rules rules, ApplyFlags flags, beast::Journal j = beast::Journal{beast::Journal::getNullSink()}) : registry(registry) , tx(tx) , rules(std::move(rules)) , flags(flags) , parentBatchId(parentBatchId) , j(j) { XRPL_ASSERT((flags & TapBatch) == TapBatch, "Batch apply flag should be set"); } PreflightContext( ServiceRegistry& registry, STTx const& tx, Rules rules, ApplyFlags flags, beast::Journal j = beast::Journal{beast::Journal::getNullSink()}) : registry(registry), tx(tx), rules(std::move(rules)), flags(flags), j(j) { XRPL_ASSERT((flags & TapBatch) == 0, "Batch apply flag should not be set"); } PreflightContext& operator=(PreflightContext const&) = delete; }; /** State information when determining if a tx is likely to claim a fee. */ struct PreclaimContext { public: std::reference_wrapper registry; ReadView const& view; TER preflightResult; ApplyFlags flags; STTx const& tx; std::optional const parentBatchId; beast::Journal const j; PreclaimContext( ServiceRegistry& registry, ReadView const& view, TER preflightResult, STTx const& tx, ApplyFlags flags, std::optional parentBatchId, beast::Journal j = beast::Journal{beast::Journal::getNullSink()}) : registry(registry) , view(view) , preflightResult(preflightResult) , flags(flags) , tx(tx) , parentBatchId(parentBatchId) , j(j) { XRPL_ASSERT( parentBatchId.has_value() == ((flags & TapBatch) == TapBatch), "Parent Batch ID should be set if batch apply flag is set"); } PreclaimContext( ServiceRegistry& registry, ReadView const& view, TER preflightResult, STTx const& tx, ApplyFlags flags, beast::Journal j = beast::Journal{beast::Journal::getNullSink()}) : PreclaimContext(registry, view, preflightResult, tx, flags, std::nullopt, j) { XRPL_ASSERT((flags & TapBatch) == 0, "Batch apply flag should not be set"); } PreclaimContext& operator=(PreclaimContext const&) = delete; }; class TxConsequences; struct PreflightResult; // Needed for preflight specialization class Change; class Transactor { protected: ApplyContext& ctx_; beast::WrappedSink sink_; beast::Journal const j_; AccountID const accountID_; XRPAmount preFeeBalance_{}; // Balance before fees. public: virtual ~Transactor() = default; Transactor(Transactor const&) = delete; Transactor& operator=(Transactor const&) = delete; enum class ConsequencesFactoryType { Normal, Blocker, Custom }; /** Process the transaction. */ ApplyResult operator()(); ApplyView& view() { return ctx_.view(); } [[nodiscard]] ApplyView const& view() const { return ctx_.view(); } /** Check all invariants for the current transaction. * * Runs transaction-specific invariants first (visitInvariantEntry + * finalizeInvariants), then protocol-level invariants. Both layers * always run; the worst failure code is returned. * * @param result the tentative TER from transaction processing. * @param fee the fee consumed by the transaction. * * @return the final TER after all invariant checks. */ [[nodiscard]] TER checkInvariants(TER result, XRPAmount fee); ///////////////////////////////////////////////////// /* These static functions are called from invoke_preclaim using name hiding to accomplish compile-time polymorphism, so derived classes can override for different or extra functionality. Use with care, as these are not really virtual and so don't have the compiler-time protection that comes with it. */ 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); // Returns the fee in fee units, not scaled for load. static XRPAmount calculateBaseFee(ReadView const& view, STTx const& tx); // Returns the base fee plus extra base fee units, not scaled for load. static XRPAmount calculateBaseFee(ReadView const& view, STTx const& tx, std::uint32_t extraBaseFeeMultiplier); /* Do NOT define an invokePreflight function in a derived class. Instead, define: // Optional if the transaction is gated on an amendment that // isn't specified in transactions.macro static bool checkExtraFeatures(PreflightContext const& ctx); // Optional if the transaction uses any flags other than tfUniversal static std::uint32_t getFlagsMask(PreflightContext const& ctx); // Required, even if it just returns tesSUCCESS. static NotTEC preflight(PreflightContext const& ctx); // Optional, rarely needed, if the transaction does any expensive // checks after the signature is verified. static NotTEC preflightSigValidated(PreflightContext const& ctx); * Do not try to call preflight1 or preflight2 directly. * Do not check whether relevant amendments are enabled in preflight. Instead, define checkExtraFeatures. * Do not check flags in preflight. Instead, define getFlagsMask. */ template static NotTEC invokePreflight(PreflightContext const& ctx); static TER preclaim(PreclaimContext const& ctx) { // Most transactors do nothing // after checkSeq/Fee/Sign. return tesSUCCESS; } /** * This function can be overridden to introduce additional semantic constraints beyond the * granular template validation for granular permissions. It is called by the base * invokeCheckPermission method only after the transaction has successfully passed * checkGranularSandbox. */ static NotTEC checkGranularSemantics( ReadView const& view, STTx const& tx, std::unordered_set const& heldGranularPermissions) { return tesSUCCESS; } /** * Checks whether the transaction is authorized to be executed by the delegated account. * This function enforces the strict permission check hierarchy. It is explicitly * designed NOT to be overridden. Derived transactors must instead implement * checkGranularSemantics to add custom validation logic for granular permissions. * * The evaluation proceeds as follows: * - If transaction-level permission is granted, the function immediately returns tesSUCCESS. * - If transaction-level permission is not granted, the function checks whether the transaction * matches the granular permission template defined in permissions.macro. If it does, it then * calls checkGranularSemantics to perform any additional, fine-grained validation. * */ template static NotTEC invokeCheckPermission(ReadView const& view, STTx const& tx) { // heldGranularPermissions is passed by reference into checkPermission. // It is populated with the sender’s granular permissions only when the sender // lacks tx-level permission but has granular permissions that satisfy the // granular permission template. // // - result is terNO_DELEGATE_PERMISSION: return immediately. // - result is tesSUCCESS and heldGranularPermissions is empty: tx-level permission was // granted, so we returned success before populating it. // - result is tesSUCCESS and heldGranularPermissions is not empty: tx-level permission was // not granted, but the held granular permissions passed checkGranularSandbox, so we proceed // to checkGranularSemantics. // // WARNING: Do not simplify checkPermission to return only // heldGranularPermissions or the ter code. Both the result and the // populated set are required to enforce the strict permission hierarchy // described above. std::unordered_set heldGranularPermissions; if (NotTEC const result = checkPermission(view, tx, heldGranularPermissions); !isTesSuccess(result) || heldGranularPermissions.empty()) { return result; } return T::checkGranularSemantics(view, tx, heldGranularPermissions); } ///////////////////////////////////////////////////// // Interface used by AccountDelete static TER ticketDelete( ApplyView& view, AccountID const& account, uint256 const& ticketIndex, beast::Journal j); protected: TER apply(); explicit Transactor(ApplyContext& ctx); virtual void preCompute(); virtual TER doApply() = 0; /** Inspect a single ledger entry modified by this transaction. * * Called once for every SLE created, modified, or deleted by the * transaction, before finalizeInvariants. Implementations should * accumulate whatever state they need to verify transaction-specific * post-conditions. * * @param isDelete true if the entry was erased from the ledger. * @param before the entry's state before the transaction (nullptr * for newly created entries). * @param after the entry's state as supplied by the apply logic * for this transaction. For deletions, this is the * SLE being erased and is not guaranteed to be null; * callers must use isDelete rather than after == nullptr * to detect deletions. */ virtual void visitInvariantEntry(bool isDelete, SLE::const_ref before, SLE::const_ref after) = 0; /** Check transaction-specific post-conditions after all entries have * been visited. * * Called once after every modified ledger entry has been passed to * visitInvariantEntry. Returns true if all transaction-specific * invariants hold, or false to fail the transaction with * tecINVARIANT_FAILED. * * @param tx the transaction being applied. * @param result the tentative TER result so far. * @param fee the fee consumed by the transaction. * @param view read-only view of the ledger after the transaction. * @param j journal for logging invariant failures. * * @return true if all invariants pass; false otherwise. */ [[nodiscard]] virtual bool finalizeInvariants( STTx const& tx, TER result, XRPAmount fee, ReadView const& view, beast::Journal const& j) = 0; /** Compute the minimum fee required to process a transaction with a given baseFee based on the current server load. @param registry The service registry. @param baseFee The base fee of a candidate transaction @see xrpl::calculateBaseFee @param fees Fee settings from the current ledger @param flags Transaction processing fees */ static XRPAmount minimumFee(ServiceRegistry& registry, XRPAmount baseFee, Fees const& fees, ApplyFlags flags); // Returns the fee in fee units, not scaled for load. static XRPAmount calculateOwnerReserveFee(ReadView const& view, STTx const& tx); static NotTEC checkSign( ReadView const& view, ApplyFlags flags, std::optional const& parentBatchId, AccountID const& idAccount, STObject const& sigObject, beast::Journal const j, // A batch may carry an inner from an account that an earlier inner // creates, so the signer account need not exist yet; when it does not, // only its own master key may authorize it. Normal transactions require // the account to already exist. bool permitUncreatedAccount = false); // Base class always returns true static bool checkExtraFeatures(PreflightContext const& ctx); // Base class always returns tfUniversalMask static std::uint32_t getFlagsMask(PreflightContext const& ctx); // Base class always returns tesSUCCESS static NotTEC preflightSigValidated(PreflightContext const& ctx); static bool validDataLength(std::optional const& slice, std::size_t maxLength); template static bool validNumericRange(std::optional value, T max, T min = T{}); template static bool validNumericRange( std::optional value, unit::ValueUnit max, unit::ValueUnit min = unit::ValueUnit{}); /// Minimum will usually be zero. template static bool validNumericMinimum(std::optional value, T min = T{}); /// Minimum will usually be zero. template static bool validNumericMinimum( std::optional value, unit::ValueUnit min = unit::ValueUnit{}); // Signature-authorization helpers. protected so the Batch transactor can // reuse them when validating each BatchSigner in Batch::checkBatchSign. static NotTEC checkSingleSign( ReadView const& view, AccountID const& idSigner, AccountID const& idAccount, SLE::const_pointer sleAccount, beast::Journal const j); static NotTEC checkMultiSign( ReadView const& view, ApplyFlags flags, AccountID const& id, STObject const& sigObject, beast::Journal const j); private: static NotTEC checkPermission( ReadView const& view, STTx const& tx, std::unordered_set& heldGranularPermissions); std::pair reset(XRPAmount fee); TER consumeSeqProxy(SLE::pointer const& sleAccount); TER payFee(); std::tuple processPersistentChanges(TER result, XRPAmount fee); void trapTransaction(uint256) const; /** Performs early sanity checks on the account and fee fields. (And passes flagMask to preflight0) Do not try to call preflight1 from preflight() in derived classes. See the description of invokePreflight for details. */ static NotTEC preflight1(PreflightContext const& ctx, std::uint32_t flagMask); /** Checks whether the signature appears valid Do not try to call preflight2 from preflight() in derived classes. See the description of invokePreflight for details. */ static NotTEC preflight2(PreflightContext const& ctx); /** Universal validations - Valid MPTAmount and XRPAmount Do not try to call preflightUniversal from preflight() in derived classes. See the description of invokePreflight for details. */ static NotTEC preflightUniversal(PreflightContext const& ctx); /** Check transaction-specific invariants only. * * Walks every modified ledger entry via visitInvariantEntry, then * calls finalizeInvariants on the derived transactor. Returns * tecINVARIANT_FAILED if any transaction invariant is violated. * * @param result the tentative TER from transaction processing. * @param fee the fee consumed by the transaction. * * @return the original result if all invariants pass, or * tecINVARIANT_FAILED otherwise. */ [[nodiscard]] TER checkTransactionInvariants(TER result, XRPAmount fee); }; inline bool Transactor::checkExtraFeatures(PreflightContext const& ctx) { return true; } /** Performs early sanity checks on the txid and flags */ NotTEC preflight0(PreflightContext const& ctx, std::uint32_t flagMask); namespace detail { /** Checks the validity of the transactor signing key. * * Normally called from preflight1 with ctx.tx. */ NotTEC preflightCheckSigningKey(STObject const& sigObject, beast::Journal j); /** Checks the special signing key state needed for simulation * * Normally called from preflight2 with ctx.tx. */ std::optional preflightCheckSimulateKeys(ApplyFlags flags, STObject const& sigObject, beast::Journal j); } // namespace detail // Defined in Change.cpp template <> NotTEC Transactor::invokePreflight(PreflightContext const& ctx); template NotTEC Transactor::invokePreflight(PreflightContext const& ctx) { // Using this lookup does NOT require checking the fixDelegateV1_1. The data // exists regardless of whether it is enabled. auto const feature = Permission::getInstance().getTxFeature(ctx.tx.getTxnType()); if (feature && !ctx.rules.enabled(*feature)) return temDISABLED; if (!T::checkExtraFeatures(ctx)) return temDISABLED; if (auto const ret = preflight1(ctx, T::getFlagsMask(ctx))) return ret; if (auto const ret = preflightUniversal(ctx)) return ret; if (auto const ret = T::preflight(ctx)) return ret; if (auto const ret = preflight2(ctx)) return ret; return T::preflightSigValidated(ctx); } template bool Transactor::validNumericRange(std::optional value, T max, T min) { if (!value) return true; return value >= min && value <= max; } template bool Transactor::validNumericRange( std::optional value, unit::ValueUnit max, unit::ValueUnit min) { return validNumericRange(value, max.value(), min.value()); } template bool Transactor::validNumericMinimum(std::optional value, T min) { if (!value) return true; return value >= min; } template bool Transactor::validNumericMinimum(std::optional value, unit::ValueUnit min) { return validNumericMinimum(value, min.value()); } } // namespace xrpl