#include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #pragma push_macro("TRANSACTION") #undef TRANSACTION // Do nothing #define TRANSACTION(...) #define TRANSACTION_INCLUDE 1 #include #undef TRANSACTION #pragma pop_macro("TRANSACTION") // DO NOT INCLUDE TRANSACTOR HEADER FILES HERE. // See the instructions at the top of transactions.macro instead. #include #include namespace xrpl { namespace { struct UnknownTxnType : std::exception { TxType txnType; UnknownTxnType(TxType t) : txnType{t} { } }; /** Look up the human-readable transaction type name for span attributes. * Returns nullptr if the type is unknown so the caller can skip the * attribute rather than emit an empty value. */ char const* txTypeName(TxType txnType) { if (auto const* fmt = TxFormats::getInstance().findByType(txnType)) return fmt->getName().c_str(); return nullptr; } /** Create a deterministic-trace span for an apply-pipeline stage. * * The trace_id is derived from txID[0:16] so the preflight, preclaim, and * transactor spans of one transaction share a trace even though they run * sequentially and often on different threads. Sets the stage, tx_type, and * (after the stage runs) ter_result attributes that drive the collector * spanmetrics dimensions. A no-op when telemetry is disabled. * * @param name Full span name (tx_apply_span::preflight / ::preclaim). * @param stage Stage attribute value (tx_apply_span::val::*). * @param tx The transaction supplying the id and type. */ [[nodiscard]] telemetry::SpanGuard makeStageSpan(std::string_view name, std::string_view stage, STTx const& tx) { auto const txID = tx.getTransactionID(); auto span = telemetry::SpanGuard::hashSpan( telemetry::TraceCategory::Transactions, name, txID.data(), txID.kBytes); // Guard the type lookup behind the active check: preflight runs for every // transaction, so findByType() must not run when tracing is off/disabled. if (span) { span.setAttribute(telemetry::tx_apply_span::attr::stage, stage); if (char const* typeName = txTypeName(tx.getTxnType())) span.setAttribute(telemetry::tx_apply_span::attr::txType, typeName); } return span; } // Call a lambda with the concrete transaction type as a template parameter // throw an "UnknownTxnType" exception on error template auto withTxnType(Rules const& rules, TxType txnType, F&& f) { // These global updates really should have been for every Transaction // step: preflight, preclaim, calculateBaseFee, and doApply. Unfortunately, // they were only included in doApply (via Transactor::operator()). That may // have been sufficient when the changes were only related to operations // that mutated data, but some features will now change how they read data, // so these need to be more global. // // To prevent unintentional side effects on existing checks, they will be // set for every operation only once at least one of the relevant amendments // are enabled. // // See also Transactor::operator(). // std::optional stNumberSO; std::optional rulesGuard; std::optional mantissaScaleGuard; createGuards(rules, stNumberSO, rulesGuard, mantissaScaleGuard); switch (txnType) { #pragma push_macro("TRANSACTION") #undef TRANSACTION #define TRANSACTION(tag, value, name, ...) \ case tag: \ return f.template operator()(); #include #undef TRANSACTION #pragma pop_macro("TRANSACTION") default: throw UnknownTxnType(txnType); } } } // namespace // Templates so preflight does the right thing with T::kConsequencesFactory. // // This could be done more easily using if constexpr, but Visual Studio // 2017 doesn't handle if constexpr correctly. So once we're no longer // building with Visual Studio 2017 we can consider replacing the four // templates with a single template function that uses if constexpr. // // For ConsequencesFactoryType::Normal // template requires(T::kConsequencesFactory == Transactor::ConsequencesFactoryType::Normal) TxConsequences consequencesHelper(PreflightContext const& ctx) { return TxConsequences(ctx.tx); }; // For ConsequencesFactoryType::Blocker template requires(T::kConsequencesFactory == Transactor::ConsequencesFactoryType::Blocker) TxConsequences consequencesHelper(PreflightContext const& ctx) { return TxConsequences(ctx.tx, TxConsequences::Category::Blocker); }; // For ConsequencesFactoryType::Custom template requires(T::kConsequencesFactory == Transactor::ConsequencesFactoryType::Custom) TxConsequences consequencesHelper(PreflightContext const& ctx) { return T::makeTxConsequences(ctx); }; static std::pair invokePreflight(PreflightContext const& ctx) { // Trace the preflight stage. The span shares the transaction's // deterministic trace_id so it correlates with preclaim and transactor. auto span = makeStageSpan( telemetry::tx_apply_span::preflight, telemetry::tx_apply_span::val::preflight, ctx.tx); try { auto result = withTxnType(ctx.rules, ctx.tx.getTxnType(), [&]() { auto const tec = Transactor::invokePreflight(ctx); return std::make_pair( tec, isTesSuccess(tec) ? consequencesHelper(ctx) : TxConsequences{tec}); }); if (span) span.setAttribute( telemetry::tx_apply_span::attr::terResult, transToken(result.first).c_str()); return result; } catch (UnknownTxnType const& e) { // Should never happen // LCOV_EXCL_START JLOG(ctx.j.fatal()) << "Unknown transaction type in preflight: " << e.txnType; span.recordException(e); UNREACHABLE("xrpl::invokePreflight : unknown transaction type"); return {temUNKNOWN, TxConsequences{temUNKNOWN}}; // LCOV_EXCL_STOP } catch (std::exception const& e) { // The caller's preflight() maps this to tefEXCEPTION. Record it on the // span before unwinding so per-stage error counts include exceptions. span.setAttribute( telemetry::tx_apply_span::attr::terResult, transToken(tefEXCEPTION).c_str()); span.recordException(e); throw; } } static TER invokePreclaim(PreclaimContext const& ctx) { // Trace the preclaim stage under the transaction's deterministic trace_id. auto span = makeStageSpan( telemetry::tx_apply_span::preclaim, telemetry::tx_apply_span::val::preclaim, ctx.tx); try { // use name hiding to accomplish compile-time polymorphism of static // class functions for Transactor and derived classes. TER const preclaimTer = withTxnType(ctx.view.rules(), ctx.tx.getTxnType(), [&]() -> TER { // preclaim functionality is divided into two sections: // 1. Up to and including the signature check: returns NotTEC. // All transaction checks before and including checkSign // MUST return NotTEC, or something more restrictive. // Allowing tec results in these steps risks theft or // destruction of funds, as a fee will be charged before the // signature is checked. // 2. After the signature check: returns TER. // If the transactor requires a valid account and the // transaction doesn't list one, preflight will have already // a flagged a failure. auto const id = ctx.tx.getAccountID(sfAccount); if (id != beast::kZero) { if (NotTEC const preSigResult = [&]() -> NotTEC { if (NotTEC const result = T::checkSeqProxy(ctx.view, ctx.tx, ctx.j)) return result; if (NotTEC const result = T::checkPriorTxAndLastLedger(ctx)) return result; if (NotTEC const result = T::checkPermission(ctx.view, ctx.tx)) return result; if (NotTEC const result = T::checkSign(ctx)) return result; return tesSUCCESS; }()) return preSigResult; if (TER const result = T::checkFee(ctx, calculateBaseFee(ctx.view, ctx.tx))) return result; } return T::preclaim(ctx); }); if (span) span.setAttribute( telemetry::tx_apply_span::attr::terResult, transToken(preclaimTer).c_str()); return preclaimTer; } catch (UnknownTxnType const& e) { // Should never happen // LCOV_EXCL_START JLOG(ctx.j.fatal()) << "Unknown transaction type in preclaim: " << e.txnType; span.recordException(e); UNREACHABLE("xrpl::invokePreclaim : unknown transaction type"); return temUNKNOWN; // LCOV_EXCL_STOP } catch (std::exception const& e) { // The caller's preclaim() maps this to tefEXCEPTION. Record it on the // span before unwinding so per-stage error counts include exceptions. span.setAttribute( telemetry::tx_apply_span::attr::terResult, transToken(tefEXCEPTION).c_str()); span.recordException(e); throw; } } /** * @brief Calculates the base fee for a given transaction. * * This function determines the base fee required for the specified transaction * by invoking the appropriate fee calculation logic based on the transaction * type. It uses a type-dispatch mechanism to select the correct calculation * method. * * @param view The ledger view to use for fee calculation. * @param tx The transaction for which the base fee is to be calculated. * @return The calculated base fee as an XRPAmount. * * @throws std::exception If an error occurs during fee calculation, including * but not limited to unknown transaction types or internal errors, the function * logs an error and returns an XRPAmount of zero. */ static XRPAmount invokeCalculateBaseFee(ReadView const& view, STTx const& tx) { try { return withTxnType(view.rules(), tx.getTxnType(), [&]() { return T::calculateBaseFee(view, tx); }); } catch (UnknownTxnType const& e) { // LCOV_EXCL_START UNREACHABLE("xrpl::invoke_calculateBaseFee : unknown transaction type"); return XRPAmount{0}; // LCOV_EXCL_STOP } } TxConsequences::TxConsequences(NotTEC pfResult) : isBlocker_(false) , fee_(beast::kZero) , potentialSpend_(beast::kZero) , seqProx_(SeqProxy::sequence(0)) , sequencesConsumed_(0) { XRPL_ASSERT( !isTesSuccess(pfResult), "xrpl::TxConsequences::TxConsequences : is not tesSUCCESS"); } TxConsequences::TxConsequences(STTx const& tx) : isBlocker_(false) , fee_(tx[sfFee].native() && !tx[sfFee].negative() ? tx[sfFee].xrp() : beast::kZero) , potentialSpend_(beast::kZero) , seqProx_(tx.getSeqProxy()) , sequencesConsumed_(tx.getSeqProxy().isSeq() ? 1 : 0) { } TxConsequences::TxConsequences(STTx const& tx, Category category) : TxConsequences(tx) { isBlocker_ = (category == TxConsequences::Category::Blocker); } TxConsequences::TxConsequences(STTx const& tx, XRPAmount potentialSpend) : TxConsequences(tx) { potentialSpend_ = potentialSpend; } TxConsequences::TxConsequences(STTx const& tx, std::uint32_t sequencesConsumed) : TxConsequences(tx) { sequencesConsumed_ = sequencesConsumed; } static ApplyResult invokeApply(ApplyContext& ctx) { try { return withTxnType(ctx.view().rules(), ctx.tx.getTxnType(), [&]() { T p(ctx); return p(); }); } catch (UnknownTxnType const& e) { // Should never happen // LCOV_EXCL_START JLOG(ctx.journal.fatal()) << "Unknown transaction type in apply: " << e.txnType; UNREACHABLE("xrpl::invokeApply : unknown transaction type"); return {temUNKNOWN, false}; // LCOV_EXCL_STOP } } // Test-only factory — not part of the public API. // The returned Transactor holds a raw reference to ctx; the caller must ensure // the ApplyContext outlives the Transactor. std::unique_ptr makeTransactor(ApplyContext& ctx) { return withTxnType( ctx.view().rules(), ctx.tx.getTxnType(), [&]() -> std::unique_ptr { return std::make_unique(ctx); }); } PreflightResult preflight( ServiceRegistry& registry, Rules const& rules, STTx const& tx, ApplyFlags flags, beast::Journal j) { PreflightContext const pfCtx(registry, tx, rules, flags, j); try { return {pfCtx, invokePreflight(pfCtx)}; } catch (std::exception const& e) { JLOG(j.fatal()) << "apply (preflight): " << e.what(); return {pfCtx, {tefEXCEPTION, TxConsequences{tx}}}; } } PreflightResult preflight( ServiceRegistry& registry, Rules const& rules, uint256 const& parentBatchId, STTx const& tx, ApplyFlags flags, beast::Journal j) { PreflightContext const pfCtx(registry, tx, parentBatchId, rules, flags, j); try { return {pfCtx, invokePreflight(pfCtx)}; } catch (std::exception const& e) { JLOG(j.fatal()) << "apply (preflight): " << e.what(); return {pfCtx, {tefEXCEPTION, TxConsequences{tx}}}; } } PreclaimResult preclaim(PreflightResult const& preflightResult, ServiceRegistry& registry, OpenView const& view) { std::optional ctx; if (preflightResult.rules != view.rules()) { auto secondFlight = [&]() { if (preflightResult.parentBatchId) { return preflight( registry, view.rules(), preflightResult.parentBatchId.value(), preflightResult.tx, preflightResult.flags, preflightResult.j); } return preflight( registry, view.rules(), preflightResult.tx, preflightResult.flags, preflightResult.j); }(); ctx.emplace( registry, view, secondFlight.ter, secondFlight.tx, secondFlight.flags, secondFlight.parentBatchId, secondFlight.j); } else { ctx.emplace( registry, view, preflightResult.ter, preflightResult.tx, preflightResult.flags, preflightResult.parentBatchId, preflightResult.j); } try { if (!isTesSuccess(ctx->preflightResult)) return {*ctx, ctx->preflightResult}; return {*ctx, invokePreclaim(*ctx)}; } catch (std::exception const& e) { JLOG(ctx->j.fatal()) << "apply (preclaim): " << e.what(); return {*ctx, tefEXCEPTION}; } } XRPAmount calculateBaseFee(ReadView const& view, STTx const& tx) { return invokeCalculateBaseFee(view, tx); } XRPAmount calculateDefaultBaseFee(ReadView const& view, STTx const& tx) { return Transactor::calculateBaseFee(view, tx); } ApplyResult doApply(PreclaimResult const& preclaimResult, ServiceRegistry& registry, OpenView& view) { if (preclaimResult.view.seq() != view.seq()) { // Logic error from the caller. Don't have enough // info to recover. return {tefEXCEPTION, false}; } try { if (!preclaimResult.likelyToClaimFee) return {preclaimResult.ter, false}; ApplyContext ctx( registry, view, preclaimResult.parentBatchId, preclaimResult.tx, preclaimResult.ter, calculateBaseFee(view, preclaimResult.tx), preclaimResult.flags, preclaimResult.j); return invokeApply(ctx); } catch (std::exception const& e) { JLOG(preclaimResult.j.fatal()) << "apply: " << e.what(); return {tefEXCEPTION, false}; } } } // namespace xrpl