fix(telemetry): make the consensus trace strategy an enum

consensus_trace_strategy was read as a std::string and compared against the
literal "attribute" in startRoundTracing(), while the runbook documented
"deterministic" and "random". The documented value "random" therefore fell
through to the default and did nothing.

Parse the setting once into ConsensusTraceStrategy, so the consensus code
branches on a type. The accepted spellings are now "deterministic" and
"random"; anything else fails at startup instead of silently defaulting.
The behaviour behind the old "attribute" name is unchanged and is now
reached by "random".

Document consensus_trace_strategy in xrpld-example.cfg, stating that
"random" is experimental and not used: it gives each node its own trace id,
so one round arrives as one trace per node.

Also state on the tx.included event that it covers the agreed consensus set
before the ledger is built, so it is a superset of the accepted ledger.
This commit is contained in:
Pratik Mankawde
2026-09-08 14:39:05 +01:00
parent 3d9ea4b9da
commit fb827dc0f1
11 changed files with 208 additions and 30 deletions

View File

@@ -168,14 +168,13 @@ public:
}
/**
* @return A fixed strategy label; the scope tests do not exercise
* deterministic trace-id correlation, so any stable value works.
* @return A fixed strategy; the scope tests do not exercise trace-id
* correlation, so either value works.
*/
[[nodiscard]] std::string const&
[[nodiscard]] ConsensusTraceStrategy
getConsensusTraceStrategy() const override
{
static std::string const kStrategy{"none"};
return kStrategy;
return ConsensusTraceStrategy::Deterministic;
}
opentelemetry::nostd::shared_ptr<opentelemetry::trace::Tracer>

View File

@@ -27,6 +27,7 @@ namespace key {
constexpr char const* batchSize = "batch_size";
constexpr char const* batchDelayMs = "batch_delay_ms";
constexpr char const* maxQueueSize = "max_queue_size";
constexpr char const* consensusTraceStrategy = "consensus_trace_strategy";
} // namespace key
/**
@@ -104,6 +105,7 @@ TEST(TelemetryConfig, setup_defaults)
EXPECT_TRUE(s.traceRpc);
EXPECT_TRUE(s.tracePeer);
EXPECT_TRUE(s.traceLedger);
EXPECT_EQ(s.consensusTraceStrategy, telemetry::ConsensusTraceStrategy::Deterministic);
}
TEST(TelemetryConfig, parse_empty_section)
@@ -286,6 +288,71 @@ TEST(TelemetryConfig, batch_size_equal_to_max_queue_size_is_accepted)
EXPECT_EQ(setup.maxQueueSize, 512u);
}
TEST(TelemetryConfig, consensus_trace_strategy_names_match_the_config_spellings)
{
// strategyName() feeds both the parser and the trace_strategy span
// attribute, so these two strings are the whole public vocabulary.
EXPECT_STREQ(
telemetry::strategyName(telemetry::ConsensusTraceStrategy::Deterministic), "deterministic");
EXPECT_STREQ(telemetry::strategyName(telemetry::ConsensusTraceStrategy::Random), "random");
}
TEST(TelemetryConfig, consensus_trace_strategy_defaults_to_deterministic)
{
// The key is absent, so the default applies. Deterministic is the only
// strategy in use, and a default of Random would break cross-node
// correlation on every node that omits the key.
EXPECT_EQ(
parseBatch({}).consensusTraceStrategy, telemetry::ConsensusTraceStrategy::Deterministic);
}
TEST(TelemetryConfig, consensus_trace_strategy_accepts_deterministic)
{
EXPECT_EQ(
parseBatch({{key::consensusTraceStrategy, "deterministic"}}).consensusTraceStrategy,
telemetry::ConsensusTraceStrategy::Deterministic);
}
TEST(TelemetryConfig, consensus_trace_strategy_accepts_random)
{
// Random is experimental and unused, but it is a documented spelling, so
// the parser must still map it to its own enumerator rather than reject it
// or fold it into the default.
EXPECT_EQ(
parseBatch({{key::consensusTraceStrategy, "random"}}).consensusTraceStrategy,
telemetry::ConsensusTraceStrategy::Random);
}
TEST(TelemetryConfig, consensus_trace_strategy_empty_value_is_the_default)
{
// `consensus_trace_strategy=` with nothing after it. An empty value means
// the operator wrote the key and no value, which is the default, not a typo.
EXPECT_EQ(
parseBatch({{key::consensusTraceStrategy, ""}}).consensusTraceStrategy,
telemetry::ConsensusTraceStrategy::Deterministic);
}
TEST(TelemetryConfig, consensus_trace_strategy_rejects_an_undocumented_value)
{
// "attribute" is not a spelling this parser accepts. Rejecting rather than
// defaulting is the point: a silent fallback would leave the operator
// believing a setting took effect.
EXPECT_EQ(
batchRejection({{key::consensusTraceStrategy, "attribute"}}),
"Invalid value 'consensus_trace_strategy' in [telemetry]: must be 'deterministic' or "
"'random'.");
}
TEST(TelemetryConfig, consensus_trace_strategy_matching_is_case_sensitive)
{
// Every other value in this section is matched exactly, so "Random" is a
// typo and must be reported as one.
EXPECT_EQ(
batchRejection({{key::consensusTraceStrategy, "Random"}}),
"Invalid value 'consensus_trace_strategy' in [telemetry]: must be 'deterministic' or "
"'random'.");
}
TEST(TelemetryConfig, null_telemetry_factory)
{
telemetry::Telemetry::Setup setup;