diff --git a/include/xrpl/telemetry/SpanNames.h b/include/xrpl/telemetry/SpanNames.h index 9bc70a99b1..b848f96c03 100644 --- a/include/xrpl/telemetry/SpanNames.h +++ b/include/xrpl/telemetry/SpanNames.h @@ -146,6 +146,19 @@ inline constexpr auto closeResolutionMs = makeStr("close_resolution_ms"); */ inline constexpr auto ledgerHash = makeStr("ledger_hash"); inline constexpr auto fullValidation = makeStr("full_validation"); + +/** + * Shared "ledger being worked on" attrs — the open/tentative or in-flight + * consensus-build ledger a transaction is applied into, NOT an established or + * validated ledger (that is `ledgerSeq`, set on ledger.build / consensus.round). + * Named after the RPC field `ledger_current_index` and the `currentLedgerSeq` + * log usage. Reused by the tx lifecycle, apply-pipeline, and TxQ spans so a + * transaction's work can be correlated to the ledger it targeted. + * `currentLedgerHash` is the current view's parent-ledger hash, which equals the + * consensus.round deterministic trace-id seed on the consensus-build path. + */ +inline constexpr auto currentLedgerSeq = makeStr("current_ledger_seq"); +inline constexpr auto currentLedgerHash = makeStr("current_ledger_hash"); } // namespace attr // ===== Shared attribute values ============================================= diff --git a/include/xrpl/tx/detail/TxApplySpanNames.h b/include/xrpl/tx/detail/TxApplySpanNames.h index 1228bd57ef..b44019fc62 100644 --- a/include/xrpl/tx/detail/TxApplySpanNames.h +++ b/include/xrpl/tx/detail/TxApplySpanNames.h @@ -101,6 +101,15 @@ inline constexpr auto transactor = join(seg::tx, op::transactor); // ===== Attribute keys ====================================================== namespace attr { +/** + * Shared "ledger being worked on" attrs (defined in SpanNames.h). Set on + * tx.preclaim and tx.transactor (both run against a view whose seq() is the + * ledger being applied into). tx.preflight is stateless (no view) and is the + * documented exception — it carries neither. + */ +using ::xrpl::telemetry::attr::currentLedgerHash; +using ::xrpl::telemetry::attr::currentLedgerSeq; + /** * "stage" — which apply-pipeline stage this span represents. Drives the * collector spanmetrics `stage` dimension for per-stage RED metrics. diff --git a/src/libxrpl/tx/Transactor.cpp b/src/libxrpl/tx/Transactor.cpp index 6a4eb6f3bb..f541d788cf 100644 --- a/src/libxrpl/tx/Transactor.cpp +++ b/src/libxrpl/tx/Transactor.cpp @@ -1607,6 +1607,13 @@ Transactor::operator()() span.setAttribute(telemetry::tx_apply_span::attr::stage, telemetry::tx_apply_span::val::apply); if (auto const* fmt = TxFormats::getInstance().findByType(ctx_.tx.getTxnType())) span.setAttribute(telemetry::tx_apply_span::attr::txType, fmt->getName().c_str()); + // The ledger being worked on (seq + parent hash) — correlates this apply + // stage to the ledger/consensus trace it is building into. + span.setAttribute( + telemetry::tx_apply_span::attr::currentLedgerSeq, static_cast(view().seq())); + span.setAttribute( + telemetry::tx_apply_span::attr::currentLedgerHash, + to_string(view().header().parentHash).c_str()); JLOG(j_.trace()) << "apply: " << ctx_.tx.getTransactionID(); diff --git a/src/libxrpl/tx/applySteps.cpp b/src/libxrpl/tx/applySteps.cpp index f982f447e3..f2f99e9536 100644 --- a/src/libxrpl/tx/applySteps.cpp +++ b/src/libxrpl/tx/applySteps.cpp @@ -78,9 +78,20 @@ txTypeName(TxType txnType) * @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. + * @param curLedgerSeq Seq of the ledger being worked on, set as the + * current_ledger_seq attribute. Passed by the + * view-bearing stages (preclaim); nullopt for the + * stateless preflight, which then omits it. + * @param curLedgerParentHash Parent hash of that ledger, set as + * current_ledger_hash. nullptr to omit. */ [[nodiscard]] telemetry::SpanGuard -makeStageSpan(std::string_view name, std::string_view stage, STTx const& tx) +makeStageSpan( + std::string_view name, + std::string_view stage, + STTx const& tx, + std::optional curLedgerSeq = std::nullopt, + uint256 const* curLedgerParentHash = nullptr) { auto const txID = tx.getTransactionID(); auto span = telemetry::SpanGuard::hashSpan( @@ -92,6 +103,17 @@ makeStageSpan(std::string_view name, std::string_view stage, STTx const& tx) 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); + // The ledger being worked on — set only by the view-bearing stages + // (preclaim/transactor). Preflight is stateless and passes nullopt, so + // it carries no ledger attribute (documented exception). + if (curLedgerSeq) + span.setAttribute( + telemetry::tx_apply_span::attr::currentLedgerSeq, + static_cast(*curLedgerSeq)); + if (curLedgerParentHash) + span.setAttribute( + telemetry::tx_apply_span::attr::currentLedgerHash, + to_string(*curLedgerParentHash).c_str()); } return span; } @@ -224,8 +246,14 @@ static TER invokePreclaim(PreclaimContext const& ctx) { // Trace the preclaim stage under the transaction's deterministic trace_id. + // Preclaim has a ledger view, so tag the span with the ledger being worked + // on (seq + parent hash) to correlate it to the ledger/consensus trace. auto span = makeStageSpan( - telemetry::tx_apply_span::preclaim, telemetry::tx_apply_span::val::preclaim, ctx.tx); + telemetry::tx_apply_span::preclaim, + telemetry::tx_apply_span::val::preclaim, + ctx.tx, + ctx.view.seq(), + &ctx.view.header().parentHash); try { // use name hiding to accomplish compile-time polymorphism of static diff --git a/src/tests/libxrpl/telemetry/TxApplySpanNames.cpp b/src/tests/libxrpl/telemetry/TxApplySpanNames.cpp index 52c4bdab0e..ad6bb4efa8 100644 --- a/src/tests/libxrpl/telemetry/TxApplySpanNames.cpp +++ b/src/tests/libxrpl/telemetry/TxApplySpanNames.cpp @@ -51,3 +51,12 @@ TEST(TxApplySpanNames, stage_values_are_the_three_pipeline_stages) EXPECT_EQ(std::string_view(tx_apply_span::val::preclaim), "preclaim"); EXPECT_EQ(std::string_view(tx_apply_span::val::apply), "apply"); } + +TEST(TxApplySpanNames, current_ledger_keys_are_shared_constants) +{ + // "ledger being worked on" keys, re-exported from the base SpanNames.h so + // every emitter (tx.*, txq.*, apply pipeline) shares one string. Set on + // preclaim/transactor (view-bearing); preflight omits them (stateless). + EXPECT_EQ(std::string_view(tx_apply_span::attr::currentLedgerSeq), "current_ledger_seq"); + EXPECT_EQ(std::string_view(tx_apply_span::attr::currentLedgerHash), "current_ledger_hash"); +} diff --git a/src/xrpld/app/misc/NetworkOPs.cpp b/src/xrpld/app/misc/NetworkOPs.cpp index 54733cf3e0..b5d5218e21 100644 --- a/src/xrpld/app/misc/NetworkOPs.cpp +++ b/src/xrpld/app/misc/NetworkOPs.cpp @@ -1390,6 +1390,12 @@ NetworkOPsImp::processTransaction( auto span = std::make_shared(txProcessSpan(transaction->getID())); span->setAttribute(tx_span::attr::txHash, to_string(transaction->getID()).c_str()); span->setAttribute(tx_span::attr::local, bLocal); + // The current (open) ledger index at submission/relay time — the ledger + // being worked on. Correlates this tx.process to the ledger trace; the tx + // has not yet been applied to a specific ledger here, so there is no hash. + span->setAttribute( + tx_span::attr::currentLedgerSeq, + static_cast(ledgerMaster_.getCurrentLedgerIndex())); if (auto const& stx = transaction->getSTransaction()) { if (auto const* fmt = TxFormats::getInstance().findByType(stx->getTxnType())) @@ -1600,8 +1606,19 @@ NetworkOPsImp::apply(std::unique_lock& batchLock) if (e.failType == FailHard::Yes) flags |= TapFailHard; + // Parent the txq.enqueue span to this tx's tx.process span + // via an explicit captured context (the parent is explicit, + // not ambient-inherited). Null on the open-ledger rebuild + // path, where no tx.process span exists. + auto const txProcessCtx = + (e.span && *e.span) ? e.span->spanContext() : telemetry::SpanContext{}; auto const result = registry_.get().getTxQ().apply( - registry_.get().getApp(), view, e.transaction->getSTransaction(), flags, j); + registry_.get().getApp(), + view, + e.transaction->getSTransaction(), + flags, + j, + txProcessCtx.isValid() ? &txProcessCtx : nullptr); e.result = result.ter; e.applied = result.applied; changed = changed || result.applied; diff --git a/src/xrpld/app/misc/TxQ.h b/src/xrpld/app/misc/TxQ.h index 65b4e9778c..bbcbffecf3 100644 --- a/src/xrpld/app/misc/TxQ.h +++ b/src/xrpld/app/misc/TxQ.h @@ -16,6 +16,7 @@ #include #include #include +#include #include #include @@ -334,7 +335,8 @@ public: OpenView& view, std::shared_ptr const& tx, ApplyFlags flags, - beast::Journal j); + beast::Journal j, + telemetry::SpanContext const* parentCtx = nullptr); /** * Fill the new open ledger with transactions from the queue. diff --git a/src/xrpld/app/misc/detail/TxQ.cpp b/src/xrpld/app/misc/detail/TxQ.cpp index e0c988a418..016974a024 100644 --- a/src/xrpld/app/misc/detail/TxQ.cpp +++ b/src/xrpld/app/misc/detail/TxQ.cpp @@ -749,13 +749,35 @@ TxQ::apply( OpenView& view, std::shared_ptr const& tx, ApplyFlags flags, - beast::Journal j) + beast::Journal j, + telemetry::SpanContext const* parentCtx) { using namespace telemetry; - ScopedSpanGuard span(TraceCategory::Transactions, txq_span::prefix::txq, txq_span::op::enqueue); + // Parent the enqueue span to the caller's tx.process span when a context is + // supplied (submission path), using the explicit-context factory: the parent + // comes from the captured ctx rather than being inherited from whatever span + // is ambient, and this ScopedSpanGuard's scope is RAII-bounded to this fully + // synchronous call (no coroutine yield), so no unrelated parent leaks in and + // its scope cannot leak out onto a reused worker. + // On the open-ledger rebuild path parentCtx is null and this is a root; the + // current_ledger_seq attribute below correlates it to the ledger instead. + // A lambda (not a ternary) picks the factory: ScopedSpanGuard's move ctor is + // deleted, so guaranteed copy elision on each return is the only way to + // construct it conditionally. + auto span = [&]() -> ScopedSpanGuard { + if (parentCtx && parentCtx->isValid()) + return ScopedSpanGuard::childSpan(txq_span::enqueue, *parentCtx); + return ScopedSpanGuard( + TraceCategory::Transactions, txq_span::prefix::txq, txq_span::op::enqueue); + }(); span.setAttribute(txq_span::attr::txHash, to_string(tx->getTransactionID()).c_str()); if (auto const* fmt = TxFormats::getInstance().findByType(tx->getTxnType())) span.setAttribute(txq_span::attr::txType, fmt->getName().c_str()); + // The ledger being worked on (open/tentative apply or in-flight consensus + // build) — correlates this enqueue to the ledger trace in every context. + span.setAttribute(txq_span::attr::currentLedgerSeq, static_cast(view.seq())); + span.setAttribute( + txq_span::attr::currentLedgerHash, to_string(view.header().parentHash).c_str()); // Default outcome; overridden below on the direct-apply and queued paths. // Every other early return leaves the tx rejected from the queue. span.setAttribute(txq_span::attr::txqStatus, txq_span::val::rejected); diff --git a/src/xrpld/app/misc/detail/TxQSpanNames.h b/src/xrpld/app/misc/detail/TxQSpanNames.h index e062e361d1..6b64b76a57 100644 --- a/src/xrpld/app/misc/detail/TxQSpanNames.h +++ b/src/xrpld/app/misc/detail/TxQSpanNames.h @@ -73,12 +73,22 @@ inline constexpr auto acceptTx = makeStr("accept_tx"); inline constexpr auto cleanup = makeStr("cleanup"); } // namespace op +// ===== Full span names (prefix.op) =========================================== +// +// Joined "txq." names for the explicit-context factories +// (SpanGuard::childSpan(name, ctx)) that take one full span name rather than a +// prefix/suffix pair. + +inline constexpr auto enqueue = join(prefix::txq, op::enqueue); + // ===== Attribute keys ====================================================== namespace attr { /** * Canonical shared constants (defined in SpanNames.h). */ +using ::xrpl::telemetry::attr::currentLedgerHash; +using ::xrpl::telemetry::attr::currentLedgerSeq; using ::xrpl::telemetry::attr::ledgerSeq; using ::xrpl::telemetry::attr::txHash; diff --git a/src/xrpld/overlay/detail/PeerImp.cpp b/src/xrpld/overlay/detail/PeerImp.cpp index 0ff824e0bc..0af09b81bd 100644 --- a/src/xrpld/overlay/detail/PeerImp.cpp +++ b/src/xrpld/overlay/detail/PeerImp.cpp @@ -1318,6 +1318,12 @@ PeerImp::handleTransaction( auto span = std::make_shared(txReceiveSpan(txID, *m)); span->setAttribute(tx_span::attr::txHash, to_string(txID).c_str()); span->setAttribute(tx_span::attr::peerId, static_cast(id_)); + // The current (open) ledger index when the relayed tx was received — + // the ledger being worked on. Correlates this tx.receive to the ledger + // trace; not yet applied to a specific ledger here, so no hash. + span->setAttribute( + tx_span::attr::currentLedgerSeq, + static_cast(app_.getLedgerMaster().getCurrentLedgerIndex())); if (auto const* fmt = TxFormats::getInstance().findByType(stx->getTxnType())) span->setAttribute(tx_span::attr::txType, fmt->getName().c_str()); if (auto const version = getVersion(); !version.empty()) diff --git a/src/xrpld/telemetry/TxSpanNames.h b/src/xrpld/telemetry/TxSpanNames.h index e8d75f17af..2a6aa9824c 100644 --- a/src/xrpld/telemetry/TxSpanNames.h +++ b/src/xrpld/telemetry/TxSpanNames.h @@ -50,6 +50,13 @@ namespace attr { using ::xrpl::telemetry::attr::peerId; using ::xrpl::telemetry::attr::txHash; +/** + * "current_ledger_seq" — the open/current ledger index at the time the tx is + * received or submitted (the ledger being worked on), NOT an established ledger. + * No parent-ledger hash here: these entry points have no ledger view. + */ +using ::xrpl::telemetry::attr::currentLedgerSeq; + /** * "local" — whether tx originated locally. */