From a7b3a0df6e69b0fc52e5d0d69394b8759c9e4bd8 Mon Sep 17 00:00:00 2001 From: Pratik Mankawde <3397372+pratikmankawde@users.noreply.github.com> Date: Tue, 22 Sep 2026 21:30:22 +0100 Subject: [PATCH 1/2] fix(tests): Locate the in-memory exporter by build config The find_library hint was pinned to the _RELEASE variable CMakeDeps generates, so in any other configuration it expanded to nothing. The archive was then found only via CMAKE_PREFIX_PATH, which can hand a Debug build the Release archive instead of failing. Derive the suffix from CMAKE_BUILD_TYPE and ask for the package's lib directory directly. Co-Authored-By: Claude Opus 5 (1M context) --- src/tests/libxrpl/CMakeLists.txt | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/tests/libxrpl/CMakeLists.txt b/src/tests/libxrpl/CMakeLists.txt index b4a1c2055b..5ca0c7ef87 100644 --- a/src/tests/libxrpl/CMakeLists.txt +++ b/src/tests/libxrpl/CMakeLists.txt @@ -99,10 +99,17 @@ if(telemetry) # libs, so neither the umbrella target nor opentelemetry-cpp::exporter_in_memory # puts the archive on the link line. Locate it by path and link it BEFORE # the umbrella so its undefined SDK references resolve against the umbrella. + # CMakeDeps suffixes its variables with the upper-cased build type, so a + # hardcoded _RELEASE hint is empty in any other config and the archive is + # then only found by luck. Derive the suffix instead. An empty hint is not + # fatal on its own: the Conan toolchain puts the package on + # CMAKE_PREFIX_PATH, which find_library searches anyway. REQUIRED turns a + # miss into a configure error rather than a link error. + string(TOUPPER "${CMAKE_BUILD_TYPE}" otel_build_type) find_library( OTEL_IN_MEMORY_EXPORTER_LIB NAMES opentelemetry_exporter_in_memory - HINTS "${opentelemetry-cpp_PACKAGE_FOLDER_RELEASE}/lib" + HINTS "${opentelemetry-cpp_LIB_DIRS_${otel_build_type}}" REQUIRED ) target_link_libraries( From 7272ea078cbb048bb54761427be47da5569b106c Mon Sep 17 00:00:00 2001 From: Pratik Mankawde <3397372+pratikmankawde@users.noreply.github.com> Date: Tue, 22 Sep 2026 21:30:23 +0100 Subject: [PATCH 2/2] test(telemetry): Assert the cross-node span relationships The harness checked parentage only within a node. Every declared relationship named two spans from one process, so a receive span that lost its cross-node parent still satisfied the contract: it is created either way, with all its normal attributes, and only its parent differs. Declare the three pairs that cross a node boundary, so a send side that stops propagating context fails the run. Co-Authored-By: Claude Opus 5 (1M context) --- docker/telemetry/workload/expected_spans.json | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/docker/telemetry/workload/expected_spans.json b/docker/telemetry/workload/expected_spans.json index 752ac13c25..003568e597 100644 --- a/docker/telemetry/workload/expected_spans.json +++ b/docker/telemetry/workload/expected_spans.json @@ -501,6 +501,21 @@ "parent": "consensus.establish", "child": "consensus.check", "description": "The establish phase contains each consensus-reached check." + }, + { + "parent": "tx.process", + "child": "tx.receive", + "description": "CROSS-NODE. The submitting node's tx.process span is the parent of the tx.receive span on every peer the transaction is relayed to. NetworkOPs::apply() injects the tx.process span's own context into the relayed TMTransaction (NetworkOPs.cpp:1979, the span created at :1630 by txProcessSpan), and PeerImp extracts it into a child span. Both ends emit on every run: tx_submitter.py submits to one node and the cluster relays." + }, + { + "parent": "consensus.proposal.send", + "child": "consensus.proposal.receive", + "description": "CROSS-NODE. RCLConsensus::Adaptor::propose() injects the send span's own context into the TMProposeSet, and the receiving peer parents consensus.proposal.receive to it (ConsensusReceiveTracing.h, the has_trace_context() branch). Asserted rather than joined on a hash, because this pair really is parent and child. Until the send side was fixed it injected the AMBIENT context, which is never set on that thread, so every receiver silently took its freshRoot fallback and this relationship held for nobody." + }, + { + "parent": "consensus.validation.send", + "child": "consensus.validation.receive", + "description": "CROSS-NODE. The same pairing for validations, injected by RCLConsensus::Adaptor::validate(). Declared separately from the proposal pair because the two send sites are independent, and a regression in one says nothing about the other." } ], "total_span_types": 41,