diff --git a/OpenTelemetryPlan/09-data-collection-reference.md b/OpenTelemetryPlan/09-data-collection-reference.md index 0f2d14f7f9..0d666a34bf 100644 --- a/OpenTelemetryPlan/09-data-collection-reference.md +++ b/OpenTelemetryPlan/09-data-collection-reference.md @@ -163,14 +163,14 @@ or, for the apply pipeline: `{resource.service.name="xrpld" && name=~"tx.preflig Controlled by `trace_transactions=1` in `[telemetry]` config. -| Span Name | Parent | Source File | Description | -| ------------------ | ------------- | ----------- | --------------------------------------------------- | -| `txq.enqueue` | `tx.process` | TxQ.cpp | Enqueue decision when a tx is submitted | -| `txq.apply_direct` | `txq.enqueue` | TxQ.cpp | Direct apply attempt that bypasses the queue | -| `txq.batch_clear` | `txq.enqueue` | TxQ.cpp | Batch clear of an account's queued txs | -| `txq.accept` | — | TxQ.cpp | Ledger-close accept loop (drains the queue) | -| `txq.accept_tx` | `txq.accept` | TxQ.cpp | Per-queued-transaction apply inside the accept loop | -| `txq.cleanup` | — | TxQ.cpp | Post-close cleanup of expired queue entries | +| Span Name | Parent | Source File | Description | +| ------------------ | ----------------------------------------------------------- | ----------- | ------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `txq.enqueue` | `tx.process` (submission path; root on open-ledger rebuild) | TxQ.cpp | Queue admission decision (apply/queue/reject). Parents to `tx.process` via explicit context on submit; correlates via `current_ledger_seq` on all paths | +| `txq.apply_direct` | `txq.enqueue` | TxQ.cpp | Direct apply attempt that bypasses the queue | +| `txq.batch_clear` | `txq.enqueue` | TxQ.cpp | Batch clear of an account's queued txs | +| `txq.accept` | — | TxQ.cpp | Ledger-close accept loop (drains the queue) | +| `txq.accept_tx` | `txq.accept` | TxQ.cpp | Per-queued-transaction apply inside the accept loop | +| `txq.cleanup` | — | TxQ.cpp | Post-close cleanup of expired queue entries | **Where to find**: Tempo → TraceQL: `{resource.service.name="xrpld" && name=~"txq.*"}` @@ -310,42 +310,47 @@ aggregation. Per the 2026-05-13 naming redesign, span-attribute keys use the #### Transaction Attributes -| Attribute | Type | Set On | Description | -| -------------- | ------- | ------------------------------------------------------------ | --------------------------------------------------------------------- | -| `tx_hash` | string | `tx.process`, `tx.receive` | Transaction hash (hex-encoded) | -| `local` | boolean | `tx.process` | `true` if locally submitted, `false` if peer-relayed | -| `path` | string | `tx.process` | Submission path: `"sync"` or `"async"` | -| `tx_type` | string | `tx.process`, `tx.preflight`, `tx.preclaim`, `tx.transactor` | Transaction type name (e.g., `Payment`) | -| `fee` | int64 | `tx.process` | Transaction fee in drops | -| `sequence` | int64 | `tx.process` | Transaction sequence number | -| `suppressed` | boolean | `tx.receive` | `true` if transaction was suppressed (duplicate) | -| `tx_status` | string | `tx.receive` | Transaction status (e.g., `"known_bad"`) | -| `peer_id` | int64 | `tx.receive` | Peer identifier (also set on peer spans) | -| `peer_version` | string | `tx.receive` | Peer protocol version string | -| `stage` | string | `tx.preflight`, `tx.preclaim`, `tx.transactor` | Apply-pipeline stage: `preflight`, `preclaim`, or `apply` | -| `ter_result` | string | `tx.preflight`, `tx.preclaim`, `tx.transactor` | Engine result token for that stage (e.g., `tesSUCCESS`, `terPRE_SEQ`) | -| `applied` | boolean | `tx.transactor` | `true` if the transaction was applied to the ledger | +| Attribute | Type | Set On | Description | +| --------------------- | ------- | ------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------- | +| `tx_hash` | string | `tx.process`, `tx.receive` | Transaction hash (hex-encoded) | +| `local` | boolean | `tx.process` | `true` if locally submitted, `false` if peer-relayed | +| `path` | string | `tx.process` | Submission path: `"sync"` or `"async"` | +| `tx_type` | string | `tx.process`, `tx.preflight`, `tx.preclaim`, `tx.transactor` | Transaction type name (e.g., `Payment`) | +| `fee` | int64 | `tx.process` | Transaction fee in drops | +| `sequence` | int64 | `tx.process` | Transaction sequence number | +| `suppressed` | boolean | `tx.receive` | `true` if transaction was suppressed (duplicate) | +| `tx_status` | string | `tx.receive` | Transaction status (e.g., `"known_bad"`) | +| `peer_id` | int64 | `tx.receive` | Peer identifier (also set on peer spans) | +| `peer_version` | string | `tx.receive` | Peer protocol version string | +| `stage` | string | `tx.preflight`, `tx.preclaim`, `tx.transactor` | Apply-pipeline stage: `preflight`, `preclaim`, or `apply` | +| `ter_result` | string | `tx.preflight`, `tx.preclaim`, `tx.transactor` | Engine result token for that stage (e.g., `tesSUCCESS`, `terPRE_SEQ`) | +| `applied` | boolean | `tx.transactor` | `true` if the transaction was applied to the ledger | +| `current_ledger_seq` | int64 | `tx.process`, `tx.receive`, `tx.preclaim`, `tx.transactor` | Seq of the ledger being worked on (open/in-flight, not established) — joins the txID-keyed spans to the ledger trace | +| `current_ledger_hash` | string | `tx.preclaim`, `tx.transactor` | Parent hash of that ledger (= `consensus.round` trace-id seed on the build path). View-bearing stages only; `tx.preflight` omits both | **Tempo query**: `{span.tx_hash=""}` to trace a specific transaction across nodes. +Join a transaction's work to its ledger with `{span.current_ledger_seq=}`. **Prometheus labels**: `local`, `suppressed`, `tx_type`, `ter_result`, `stage` (SpanMetrics dimensions). #### Transaction Queue (TxQ) Attributes -| Attribute | Type | Set On | Description | -| -------------------- | ------- | ------------------------------ | ----------------------------------------------------------- | -| `tx_hash` | string | `txq.enqueue`, `txq.accept_tx` | Transaction hash | -| `tx_type` | string | `txq.enqueue` | Transaction type name | -| `txq_status` | string | `txq.enqueue`, `txq.accept_tx` | Queue outcome (e.g. `queued`, `applied_direct`, `rejected`) | -| `fee_level_paid` | int64 | `txq.enqueue` | Fee level paid by the queued tx | -| `required_fee_level` | int64 | `txq.enqueue` | Minimum fee level for inclusion | -| `num_cleared` | int64 | `txq.batch_clear` | Entries cleared in a batch | -| `queue_size` | int64 | `txq.accept` | Current TxQ depth | -| `ledger_changed` | boolean | `txq.accept` | Whether the ledger changed since last attempt | -| `ter_code` | int64 | `txq.accept_tx` | Transaction engine result code | -| `retries_remaining` | int64 | `txq.accept_tx` | Retries left before discard | -| `ledger_seq` | int64 | `txq.cleanup` | Ledger sequence number | -| `expired_count` | int64 | `txq.cleanup` | Number of expired entries cleared | +| Attribute | Type | Set On | Description | +| --------------------- | ------- | ------------------------------ | -------------------------------------------------------------------------------- | +| `tx_hash` | string | `txq.enqueue`, `txq.accept_tx` | Transaction hash | +| `tx_type` | string | `txq.enqueue` | Transaction type name | +| `current_ledger_seq` | int64 | `txq.enqueue` | Seq of the ledger being worked on — correlates the enqueue to the ledger trace | +| `current_ledger_hash` | string | `txq.enqueue` | Parent hash of that ledger (= `consensus.round` trace-id seed on the build path) | +| `txq_status` | string | `txq.enqueue`, `txq.accept_tx` | Queue outcome (e.g. `queued`, `applied_direct`, `rejected`) | +| `fee_level_paid` | int64 | `txq.enqueue` | Fee level paid by the queued tx | +| `required_fee_level` | int64 | `txq.enqueue` | Minimum fee level for inclusion | +| `num_cleared` | int64 | `txq.batch_clear` | Entries cleared in a batch | +| `queue_size` | int64 | `txq.accept` | Current TxQ depth | +| `ledger_changed` | boolean | `txq.accept` | Whether the ledger changed since last attempt | +| `ter_code` | int64 | `txq.accept_tx` | Transaction engine result code | +| `retries_remaining` | int64 | `txq.accept_tx` | Retries left before discard | +| `ledger_seq` | int64 | `txq.cleanup` | Ledger sequence number | +| `expired_count` | int64 | `txq.cleanup` | Number of expired entries cleared | **Prometheus label**: `txq_status` (SpanMetrics dimension). diff --git a/docs/telemetry-runbook.md b/docs/telemetry-runbook.md index f249d77f0b..57a5c7ecba 100644 --- a/docs/telemetry-runbook.md +++ b/docs/telemetry-runbook.md @@ -78,14 +78,14 @@ All spans instrumented in xrpld, grouped by subsystem: ### Transaction Spans -| Span Name | Source File | Attributes | Description | -| --------------- | --------------- | --------------------------------------------------------------------------------- | ------------------------------------- | -| `tx.process` | NetworkOPs.cpp | `tx_hash`, `local`, `path`, `tx_type`, `fee`, `sequence`, `ter_result`, `applied` | Transaction submission and processing | -| `tx.receive` | PeerImp.cpp | `peer_id`, `tx_hash`, `tx_type`, `peer_version`, `suppressed`, `tx_status` | Transaction received from peer relay | -| `tx.apply` | BuildLedger.cpp | `ledger_seq`, `tx_count`, `tx_failed` | Transaction set applied per ledger | -| `tx.preflight` | applySteps.cpp | `stage`, `tx_type`, `ter_result` | Stateless checks stage | -| `tx.preclaim` | applySteps.cpp | `stage`, `tx_type`, `ter_result` | Ledger-aware checks stage | -| `tx.transactor` | Transactor.cpp | `stage`, `tx_type`, `ter_result`, `applied` | Apply stage (transactor runs) | +| Span Name | Source File | Attributes | Description | +| --------------- | --------------- | ------------------------------------------------------------------------------------------------------- | ------------------------------------- | +| `tx.process` | NetworkOPs.cpp | `tx_hash`, `local`, `path`, `tx_type`, `fee`, `sequence`, `ter_result`, `applied`, `current_ledger_seq` | Transaction submission and processing | +| `tx.receive` | PeerImp.cpp | `peer_id`, `tx_hash`, `tx_type`, `peer_version`, `suppressed`, `tx_status`, `current_ledger_seq` | Transaction received from peer relay | +| `tx.apply` | BuildLedger.cpp | `ledger_seq`, `tx_count`, `tx_failed` | Transaction set applied per ledger | +| `tx.preflight` | applySteps.cpp | `stage`, `tx_type`, `ter_result` | Stateless checks stage | +| `tx.preclaim` | applySteps.cpp | `stage`, `tx_type`, `ter_result`, `current_ledger_seq`, `current_ledger_hash` | Ledger-aware checks stage | +| `tx.transactor` | Transactor.cpp | `stage`, `tx_type`, `ter_result`, `applied`, `current_ledger_seq`, `current_ledger_hash` | Apply stage (transactor runs) | The three apply-pipeline spans (`tx.preflight`, `tx.preclaim`, `tx.transactor`) share a deterministic `trace_id` from `txID[0:16]`, so they group under one @@ -93,16 +93,23 @@ trace per transaction. The `stage` attribute (`preflight` / `preclaim` / `apply`) drives the collector spanmetrics `stage` dimension, giving per-stage RED metrics on the _Transaction Overview_ dashboard. +`current_ledger_seq` is the current (open/in-flight) ledger index a span acted on +— the ledger being worked on, not an established one — so a transaction's +txID-keyed spans can be joined to the ledger trace it targeted +(`span.current_ledger_seq`). The view-bearing stages (`tx.preclaim`, +`tx.transactor`) also carry `current_ledger_hash` (the current ledger's parent +hash); `tx.preflight` is stateless and omits both. + ### Transaction Queue Spans -| Span Name | Source File | Attributes | Description | -| ------------------ | ----------- | -------------------------------------------------------- | -------------------------------------------------- | -| `txq.enqueue` | TxQ.cpp | `tx_hash`, `tx_type` | Transaction enqueue decision (child of tx.process) | -| `txq.apply_direct` | TxQ.cpp | -- | Direct apply attempt (bypassing queue) | -| `txq.batch_clear` | TxQ.cpp | -- | Batch clear of queued transactions for an account | -| `txq.accept` | TxQ.cpp | `queue_size`, `ledger_changed` | Ledger-close accept loop over queued transactions | -| `txq.accept_tx` | TxQ.cpp | `tx_hash`, `retries_remaining`, `ter_code`, `txq_status` | Per-transaction apply during accept | -| `txq.cleanup` | TxQ.cpp | `ledger_seq` | Post-close cleanup of expired queue entries | +| Span Name | Source File | Attributes | Description | +| ------------------ | ----------- | ----------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `txq.enqueue` | TxQ.cpp | `tx_hash`, `tx_type`, `current_ledger_seq`, `current_ledger_hash` | Enqueue decision; parents to `tx.process` on the submission path (explicit context), a root on the open-ledger rebuild path — `current_ledger_seq` correlates it to the ledger in both cases | +| `txq.apply_direct` | TxQ.cpp | -- | Direct apply attempt (bypassing queue) | +| `txq.batch_clear` | TxQ.cpp | -- | Batch clear of queued transactions for an account | +| `txq.accept` | TxQ.cpp | `queue_size`, `ledger_changed` | Ledger-close accept loop over queued transactions | +| `txq.accept_tx` | TxQ.cpp | `tx_hash`, `retries_remaining`, `ter_code`, `txq_status` | Per-transaction apply during accept | +| `txq.cleanup` | TxQ.cpp | `ledger_seq` | Post-close cleanup of expired queue entries | ### PathFinding Spans @@ -345,6 +352,28 @@ sum by (stage) (rate(span_calls_total{span_name=~"tx.preflight|tx.preclaim|tx.tr | "Was consensus outcome normal?" | `consensus.accept` | `consensus_state` | | "Did a validator bow out?" | `consensus.proposal.send` | `is_bow_out` | | "Which ledger was validated?" | `consensus.validation.send` | `ledger_hash` | +| "What tx work fed a given ledger?" | `tx.*` / `txq.*` | `current_ledger_seq` | + +### Correlating a transaction to the ledger it was worked on + +The `tx.process`, `tx.receive`, `txq.enqueue`, `tx.preclaim`, and `tx.transactor` +spans carry `current_ledger_seq` — the open/in-flight ledger they acted on (not +an established ledger). Because these spans are keyed on the transaction id (their +own trace) while the ledger/consensus spans are keyed on the ledger, use the +attribute to bridge the two id-spaces: + +``` +# All transaction-side work recorded against ledger N +{span.current_ledger_seq = N} + +# Join to the ledger build/consensus trace for the same ledger +{name="ledger.build" && span.ledger_seq = N} +``` + +`txq.enqueue` and the view-bearing apply stages also carry `current_ledger_hash` +(the current ledger's parent hash), which equals the `consensus.round` +deterministic trace-id seed on the consensus-build path. `tx.preflight` is +stateless and omits both attributes. --- 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 05de8d557a..a8f5db0508 100644 --- a/src/xrpld/overlay/detail/PeerImp.cpp +++ b/src/xrpld/overlay/detail/PeerImp.cpp @@ -1320,6 +1320,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. */