From d63d5bd45e851b3a3bb75db3cd4a4e97cd4982c5 Mon Sep 17 00:00:00 2001 From: Pratik Mankawde <3397372+pratikmankawde@users.noreply.github.com> Date: Tue, 21 Jul 2026 20:10:10 +0100 Subject: [PATCH] fix(telemetry): hashSpan standalone spans are true roots via DeterministicIdGenerator The single-arg hashSpan fabricated a synthetic DefaultSpan parent (random span_id) to carry the deterministic trace_id. That left every standalone hash span with a phantom parent_span_id pointing at a span that is never exported, so Tempo reported "root span not yet received". Inject the deterministic trace_id through the DeterministicIdGenerator instead: start the span on the SDK root branch (Context{kIsRootSpanKey,true}) with a PendingTraceId pinning hashData[0:16]. The SDK's no-valid-parent branch calls GenerateTraceId(), which returns the pinned id, yielding a TRUE root (empty parent_span_id) whose trace_id == hashData[0:16]. The deterministic bytes are unchanged (still raw hashData[0:16]). The cross-node overload (real remote parent, remote=true) is untouched and must keep producing a child of the sender's span. Drop the now-unused include; add and DeterministicIdGenerator.h. Co-Authored-By: Claude Opus 4.8 (1M context) --- src/libxrpl/telemetry/SpanGuard.cpp | 36 ++++++++++++++--------------- 1 file changed, 17 insertions(+), 19 deletions(-) diff --git a/src/libxrpl/telemetry/SpanGuard.cpp b/src/libxrpl/telemetry/SpanGuard.cpp index ba837bd607..242a565817 100644 --- a/src/libxrpl/telemetry/SpanGuard.cpp +++ b/src/libxrpl/telemetry/SpanGuard.cpp @@ -28,8 +28,8 @@ #include -#include #include +#include #include #include @@ -49,6 +49,7 @@ #include #include +#include #include #include #include @@ -303,6 +304,7 @@ SpanGuard::linkedSpan(std::string_view name, SpanContext const& linkCtx) // ===== Hash-derived span (category-gated) ================================== +// Standalone hash-derived span: a TRUE root whose trace_id == hashData[0:16]. SpanGuard SpanGuard::hashSpan( TraceCategory const cat, @@ -316,26 +318,22 @@ SpanGuard::hashSpan( if ((tel == nullptr) || !tel->isEnabled() || !isCategoryEnabled(*tel, cat)) return {}; - otel_trace::TraceId const traceId( - opentelemetry::nostd::span(hashData, 16)); - - auto const rval = defaultPrng()(); - std::uint8_t spanIdBytes[8]; - std::memcpy(spanIdBytes, &rval, sizeof(spanIdBytes)); - otel_trace::SpanId const spanId( - opentelemetry::nostd::span(spanIdBytes, 8)); - - otel_trace::SpanContext const syntheticCtx( - traceId, spanId, otel_trace::TraceFlags(1), /* remote = */ false); - - auto parentCtx = opentelemetry::context::Context{}.SetValue( - otel_trace::kSpanKey, - opentelemetry::nostd::shared_ptr( - new otel_trace::DefaultSpan(syntheticCtx))); - - return SpanGuard(std::make_unique(tel->startSpan(std::string(name), parentCtx))); + // Force the deterministic trace_id via the DeterministicIdGenerator, on the + // SDK root branch, so the span is a TRUE root (empty parent_span_id) whose + // trace_id == hashData[0:16]. The kIsRootSpanKey context forces the SDK's + // no-valid-parent branch, where GenerateTraceId() is called and returns the + // pending id. The PendingTraceId guard scopes that id to this one startSpan. + std::array tid{}; + std::memcpy(tid.data(), hashData, 16); + auto rootCtx = opentelemetry::context::Context{otel_trace::kIsRootSpanKey, true}; + PendingTraceId const pending{tid}; + return SpanGuard( + std::make_unique( + tel->startSpan(std::string(name), rootCtx, categoryToSpanKind(cat)))); } +// Cross-node hash-derived span: a CHILD of the sender's real remote span +// (remote=true), not a root — so it must NOT use PendingTraceId / rootCtx. SpanGuard SpanGuard::hashSpan( TraceCategory const cat,