feat(telemetry): add avalanche threshold and close time consensus attributes

Record the close time voting threshold and consensus state on
consensus.update_positions and consensus.check spans:

- xrpl.consensus.close_time_threshold: the avCT_CONSENSUS_PCT (75%)
  threshold required for close time agreement
- xrpl.consensus.have_close_time_consensus: whether validators
  reached close time consensus in this iteration

These attributes enable dashboards to show how the close time
voting process converges (or stalls) across consensus iterations.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Pratik Mankawde
2026-04-27 19:48:09 +01:00
parent c6f11f5b86
commit bea6f6c141
3 changed files with 25 additions and 3 deletions

View File

@@ -668,12 +668,17 @@ details.
thresholds based on `currentAgreeTime`. Threshold values come from
`ConsensusParms::avalancheCutoffs` (defined in `ConsensusParms.h`).
The escalation states are `ConsensusParms::AvalancheState::{init, mid, late, stuck}`.
Record the effective threshold as an attribute on the span:
- `xrpl.consensus.threshold_percent` — current threshold from `avalancheCutoffs`
Record the effective threshold and close time consensus state:
- `xrpl.consensus.threshold_percent` — consensus threshold (avCT_CONSENSUS_PCT = 75%)
- `xrpl.consensus.close_time_threshold` — close time voting threshold (avCT_CONSENSUS_PCT)
- `xrpl.consensus.have_close_time_consensus` — whether close time consensus was reached
- `xrpl.consensus.avalanche_threshold` — the avalanche-escalated weight from `getNeededWeight()`
These are recorded on both `consensus.update_positions` and `consensus.check` spans.
**Key modified files**:
- `src/xrpld/consensus/Consensus.h` — `haveConsensus()` method
- `src/xrpld/consensus/Consensus.h` — `haveConsensus()` and `updateOurPositions()` methods
---

View File

@@ -100,6 +100,15 @@ inline constexpr auto establishCount = join(xrplConsensus, makeStr("establish_co
/// "xrpl.consensus.proposers_agreed"
inline constexpr auto proposersAgreed = join(xrplConsensus, makeStr("proposers_agreed"));
// Avalanche threshold attributes
/// "xrpl.consensus.avalanche_threshold"
inline constexpr auto avalancheThreshold = join(xrplConsensus, makeStr("avalanche_threshold"));
/// "xrpl.consensus.close_time_threshold"
inline constexpr auto closeTimeThreshold = join(xrplConsensus, makeStr("close_time_threshold"));
/// "xrpl.consensus.have_close_time_consensus"
inline constexpr auto haveCloseTimeConsensus =
join(xrplConsensus, makeStr("have_close_time_consensus"));
// Consensus check attributes
/// "xrpl.consensus.agree_count"
inline constexpr auto agreeCount = join(xrplConsensus, makeStr("agree_count"));

View File

@@ -1588,6 +1588,10 @@ Consensus<Adaptor>::updateOurPositions(std::unique_ptr<std::stringstream> const&
}
}
span.setAttribute(cons_span::attr::haveCloseTimeConsensus, haveCloseTimeConsensus_);
span.setAttribute(
cons_span::attr::closeTimeThreshold, static_cast<int64_t>(parms.avCT_CONSENSUS_PCT));
if (!ourNewSet &&
((consensusCloseTime != asCloseTime(result_->position.closeTime())) ||
result_->position.isStale(ourCutoff)))
@@ -1741,6 +1745,10 @@ Consensus<Adaptor>::haveConsensus(std::unique_ptr<std::stringstream> const& clog
span.setAttribute(cons_span::attr::agreeCount, static_cast<int64_t>(agree));
span.setAttribute(cons_span::attr::disagreeCount, static_cast<int64_t>(disagree));
span.setAttribute(cons_span::attr::convergePercent, static_cast<int64_t>(convergePercent_));
span.setAttribute(cons_span::attr::haveCloseTimeConsensus, haveCloseTimeConsensus_);
span.setAttribute(
cons_span::attr::thresholdPercent,
static_cast<int64_t>(adaptor_.parms().avCT_CONSENSUS_PCT));
char const* stateStr = "no";
if (result_->state == ConsensusState::Yes)