Files
rippled/src/tests/libxrpl/telemetry/ConsensusSpanNames.cpp
Pratik Mankawde 258adf491e refactor(telemetry): split consensus span labels into their own header
Review of the preceding commits found a clang-tidy failure and a convention
break, both rooted in the same place: the enum-to-label helpers were put in
ConsensusSpanNames.h, which pulled two domain headers into it.

misc-include-cleaner rejected the new test: it used xrpl::LedgerCloseReason
without directly including ConsensusTypes.h, relying on the transitive
include. misc-* is enabled and this path is not in IgnoreHeaders, so it would
have failed CI.

ConsensusSpanNames.h had also become the only one of the eight *SpanNames.h
headers to include anything beyond SpanNames.h. That cost is paid by every
consumer: PeerImp.cpp, ConsensusReceiveTracing.h and RCLConsensus.cpp want
only name and key constants, but were newly compiling ConsensusTypes.h and
DisputedTx.h through it.

Move both helpers to a new ConsensusSpanLabels.h, which owns the domain
includes. ConsensusSpanNames.h is dependency-free again like its siblings, and
the labels reach their only production caller, Consensus.h, directly.

Also from the review:

- phaseOpen() had grown to 81 lines, over the 80-line limit. Extract
  annotateOpenStart() and annotateOpenClose(), which also removes the repeated
  span guards. phaseOpen is 72 lines; startRoundInternal drops 103 to 93,
  still over the limit but it was 99 before this work began.
- Note at the CLOG why the log text keeps the shouldCloseLedger name: existing
  consumers match on it.
- whyCloseLedger's doc claimed "both log identically", implying the wrapper
  logs too. It delegates, so the logging happens once either way.
- Cross-reference proposers_validated and proposers_finished, which sit eight
  lines apart and count different things: validators of the previous ledger
  versus those already past it.
- The two static_asserts no longer sit inside TEST bodies with SUCCEED(); they
  fire at compile time regardless. Also "consteval-safe" was wrong; they are
  constexpr.
- SpanGuardFactory.cpp claimed a libxrpl test cannot include the consensus
  span-name header. The new test in the same directory does exactly that, so
  the claim is corrected to name the real constraint: the rpc_* constants it
  needs live in an xrpld-level header.
2026-08-24 20:45:24 +01:00

118 lines
5.4 KiB
C++

#include <xrpl/consensus/ConsensusSpanNames.h>
#include <xrpl/consensus/ConsensusParms.h>
#include <xrpl/consensus/ConsensusSpanLabels.h>
#include <xrpl/consensus/ConsensusTypes.h>
#include <gtest/gtest.h>
#include <string_view>
/**
* Contract tests for the consensus phase-span attribute constants.
*
* The keys in ConsensusSpanNames.h are the single source of truth (L1) that
* `.github/scripts/otel-naming/check_otel_naming.py` derives its valid key
* set from, and that the collector's spanmetrics dimensions, the Tempo span
* filters and the Grafana dashboards query by literal string. A silent rename
* here compiles cleanly but blanks panels, so these tests pin the wire values.
* They need no telemetry runtime and run in every build.
*
* Scope: the attributes carried by `consensus.phase.open` and
* `consensus.establish`. The round-level attrs are covered by the
* pre-existing key set and are deliberately NOT duplicated onto the phase
* children (a child span does not inherit parent attributes, but copying
* `ledger_seq` down would store the same value twice per trace).
*/
using namespace xrpl::telemetry::consensus::span;
TEST(ConsensusSpanNames, phase_open_start_attribute_keys)
{
// Set once when the open-phase span is created in startRoundInternal().
EXPECT_EQ(std::string_view(attr::startReason), "start_reason");
EXPECT_EQ(std::string_view(attr::previousCloseAgree), "previous_close_agree");
EXPECT_EQ(std::string_view(attr::peerPositionsAtOpen), "peer_positions_at_open");
EXPECT_EQ(std::string_view(attr::earlyCloseTriggered), "early_close_triggered");
}
TEST(ConsensusSpanNames, phase_open_end_attribute_keys)
{
// Existing end-of-phase metadata, pinned alongside the new key so a rename
// of either shows up here.
EXPECT_EQ(std::string_view(attr::openDurationMs), "open_duration_ms");
EXPECT_EQ(std::string_view(attr::peerPositionsAtClose), "peer_positions_at_close");
EXPECT_EQ(std::string_view(attr::txSetsAcquired), "tx_sets_acquired");
EXPECT_EQ(std::string_view(attr::closeReason), "close_reason");
EXPECT_EQ(std::string_view(attr::proposersValidated), "proposers_validated");
}
TEST(ConsensusSpanNames, close_reason_values_are_the_close_paths)
{
// One per branch of whyCloseLedger() that closes the ledger. keep_open is
// never emitted (the attribute is only set on the closing path) but is
// labelled rather than left blank so the mapping is total.
EXPECT_EQ(std::string_view(val::closeKeepOpen), "keep_open");
EXPECT_EQ(std::string_view(val::closeAnomaly), "anomaly");
EXPECT_EQ(std::string_view(val::closeOthersClosed), "others_closed");
EXPECT_EQ(std::string_view(val::closeIdle), "idle");
EXPECT_EQ(std::string_view(val::closeNormal), "normal");
}
TEST(ConsensusSpanNames, close_reason_label_maps_every_enum_state)
{
// A missed branch would attribute a close to the wrong cause, which is the
// whole point of the attribute, so every enumerator is asserted.
EXPECT_EQ(closeReasonLabel(xrpl::LedgerCloseReason::KeepOpen), "keep_open");
EXPECT_EQ(closeReasonLabel(xrpl::LedgerCloseReason::Anomaly), "anomaly");
EXPECT_EQ(closeReasonLabel(xrpl::LedgerCloseReason::OthersClosed), "others_closed");
EXPECT_EQ(closeReasonLabel(xrpl::LedgerCloseReason::Idle), "idle");
EXPECT_EQ(closeReasonLabel(xrpl::LedgerCloseReason::Normal), "normal");
}
TEST(ConsensusSpanNames, establish_attribute_keys)
{
// Qualified: DisputedTx tracks a second, per-transaction avalanche.
EXPECT_EQ(std::string_view(attr::closeTimeAvalancheState), "close_time_avalanche_state");
}
TEST(ConsensusSpanNames, start_reason_values_are_the_two_entry_paths)
{
// startRoundInternal() is entered fresh, or re-entered by handleWrongLedger
// after acquiring the correct prior ledger. A round that recovers emits a
// SECOND consensus.phase.open span, so the label is what tells them apart.
EXPECT_EQ(std::string_view(val::startInitial), "initial");
EXPECT_EQ(std::string_view(val::startRecovered), "recovered");
}
TEST(ConsensusSpanNames, avalanche_state_values_match_the_parms_enum)
{
EXPECT_EQ(std::string_view(val::avalancheInit), "init");
EXPECT_EQ(std::string_view(val::avalancheMid), "mid");
EXPECT_EQ(std::string_view(val::avalancheLate), "late");
EXPECT_EQ(std::string_view(val::avalancheStuck), "stuck");
EXPECT_EQ(std::string_view(val::unknown), "unknown");
}
TEST(ConsensusSpanNames, avalanche_state_label_maps_every_enum_state)
{
// A missed branch here would silently report the wrong convergence regime
// for the round, so every enumerator is asserted explicitly rather than
// round-tripped through a table.
using AvalancheState = xrpl::ConsensusParms::AvalancheState;
EXPECT_EQ(avalancheStateLabel(AvalancheState::Init), "init");
EXPECT_EQ(avalancheStateLabel(AvalancheState::Mid), "mid");
EXPECT_EQ(avalancheStateLabel(AvalancheState::Late), "late");
EXPECT_EQ(avalancheStateLabel(AvalancheState::Stuck), "stuck");
}
// Both mappings are constexpr, so the labels cost nothing at their call sites.
// These fire at compile time and need no test body.
static_assert(
closeReasonLabel(xrpl::LedgerCloseReason::Idle) == "idle",
"closeReasonLabel must be constexpr-evaluable");
static_assert(
avalancheStateLabel(xrpl::ConsensusParms::AvalancheState::Stuck) == "stuck",
"avalancheStateLabel must be constexpr-evaluable");