From 6fadd0e2ee92cfa876c6406280ee34b40a2c062d Mon Sep 17 00:00:00 2001 From: Pratik Mankawde <3397372+pratikmankawde@users.noreply.github.com> Date: Wed, 23 Sep 2026 13:49:54 +0100 Subject: [PATCH] fix(telemetry): emit consensus.mode_change only on a real transition MonitoredMode::set calls onModeChange on every round start, so the span was created whether or not the mode moved. A node with a steady mode therefore emitted one mode_change per round carrying mode_old == mode_new, which a live sweep confirmed on every round of both instrumented builds. The round span's own consensus_mode attribute is still written on every call, since that is where the round learns the mode it is running in. --- include/xrpl/consensus/ConsensusSpanNames.h | 2 +- src/xrpld/app/consensus/RCLConsensus.cpp | 13 ++++++++++--- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/include/xrpl/consensus/ConsensusSpanNames.h b/include/xrpl/consensus/ConsensusSpanNames.h index 46935294e8..db649443a4 100644 --- a/include/xrpl/consensus/ConsensusSpanNames.h +++ b/include/xrpl/consensus/ConsensusSpanNames.h @@ -73,7 +73,7 @@ * | Attrs: ledger_seq, proposing * | * +-- consensus.mode_change [main thread] - * Created: Adaptor::onModeChange() + * Created: Adaptor::onModeChange(), only when the mode moves * Attrs: mode_old, mode_new * * Standalone spans (no parent, created per-message in overlay): diff --git a/src/xrpld/app/consensus/RCLConsensus.cpp b/src/xrpld/app/consensus/RCLConsensus.cpp index c08839c6a0..c1d2834653 100644 --- a/src/xrpld/app/consensus/RCLConsensus.cpp +++ b/src/xrpld/app/consensus/RCLConsensus.cpp @@ -1135,9 +1135,16 @@ RCLConsensus::Adaptor::onModeChange(ConsensusMode before, ConsensusMode after) // thread-free SpanGuard, so parent explicitly via its context). A mode // change outside a round leaves roundSpanContext_ invalid, yielding a null // guard (no-op). - auto span = telemetry::SpanGuard::childSpan(cs::modeChange, roundSpanContext_); - span.setAttribute(cs::attr::modeOld, toDisplayString(before).c_str()); - span.setAttribute(cs::attr::modeNew, toDisplayString(after).c_str()); + // + // Only a real transition gets a span. MonitoredMode::set also calls this + // on every round start; the round's mode attribute below still needs that + // call, the span does not. + if (before != after) + { + auto span = telemetry::SpanGuard::childSpan(cs::modeChange, roundSpanContext_); + span.setAttribute(cs::attr::modeOld, toDisplayString(before).c_str()); + span.setAttribute(cs::attr::modeNew, toDisplayString(after).c_str()); + } JLOG(j_.info()) << "Consensus mode change before=" << to_string(before) << ", after=" << to_string(after);