Files
rippled/src/xrpld/app/consensus/RCLValidations.cpp
Pratik Mankawde 2f31a749a3 fix(telemetry): record rejected validations, and use the label constants
Five review findings.

The validation_accept span started inside the "outcome is Current"
branch, so validation_status could only ever read "current" and the four
rejected values were unreachable. That defeated the attribute: a node
whose trusted validations are all rejected emitted no span at all, so it
looked the same as a quiet node. The span now starts before the outcome
is checked, still for trusted validations only, so a rejected one is
recorded with its real status.

The rest are consistency fixes: ValidatorSite passes the parse_error
constant instead of the literal, the 28 gauge callbacks that spelled the
`metric` label key as a literal now use label::metric like the other two,
a stray blank first line is gone from six files, and MetricsRegistry.cpp
loses a duplicate Doxygen block that documented addHistogramView but sat
above an unrelated constant.
2026-07-28 21:46:56 +01:00

266 lines
8.9 KiB
C++

#include <xrpld/app/consensus/RCLValidations.h>
#include <xrpld/app/ledger/InboundLedger.h>
#include <xrpld/app/ledger/InboundLedgers.h>
#include <xrpld/app/ledger/LedgerMaster.h>
#include <xrpld/app/main/Application.h>
#include <xrpld/app/misc/ValidatorList.h>
#include <xrpld/core/TimeKeeper.h>
#include <xrpl/basics/Log.h>
#include <xrpl/basics/chrono.h>
#include <xrpl/beast/utility/instrumentation.h>
#include <xrpl/consensus/ConsensusSpanNames.h>
#include <xrpl/consensus/Validations.h>
#include <xrpl/core/Job.h>
#include <xrpl/core/JobQueue.h>
#include <xrpl/core/PerfLog.h>
#include <xrpl/protocol/Indexes.h>
#include <xrpl/protocol/PublicKey.h>
#include <xrpl/protocol/RippleLedgerHash.h>
#include <xrpl/protocol/SField.h>
#include <xrpl/protocol/tokens.h>
#include <xrpl/telemetry/SpanGuard.h>
#include <algorithm>
#include <memory>
#include <optional>
namespace xrpl {
RCLValidatedLedger::RCLValidatedLedger(MakeGenesis)
: ledgerID_{0}, ledgerSeq_{0}, j_{beast::Journal::getNullSink()}
{
}
RCLValidatedLedger::RCLValidatedLedger(
std::shared_ptr<Ledger const> const& ledger,
beast::Journal j)
: ledgerID_{ledger->header().hash}, ledgerSeq_{ledger->seq()}, j_{j}
{
auto const hashIndex = ledger->read(keylet::skip());
if (hashIndex)
{
XRPL_ASSERT(
hashIndex->getFieldU32(sfLastLedgerSequence) == (seq() - 1),
"xrpl::RCLValidatedLedger::RCLValidatedLedger(Ledger) : valid "
"last ledger sequence");
ancestors_ = hashIndex->getFieldV256(sfHashes).value();
}
else
{
JLOG(j_.warn()) << "Ledger " << ledgerSeq_ << ":" << ledgerID_
<< " missing recent ancestor hashes";
}
}
auto
RCLValidatedLedger::minSeq() const -> Seq
{
return seq() - std::min(seq(), static_cast<Seq>(ancestors_.size()));
}
auto
RCLValidatedLedger::seq() const -> Seq
{
return ledgerSeq_;
}
auto
RCLValidatedLedger::id() const -> ID
{
return ledgerID_;
}
auto
RCLValidatedLedger::operator[](Seq const& s) const -> ID
{
if (s >= minSeq() && s <= seq())
{
if (s == seq())
return ledgerID_;
Seq const diff = seq() - s;
return ancestors_[ancestors_.size() - diff];
}
JLOG(j_.warn()) << "Unable to determine hash of ancestor seq=" << s
<< " from ledger hash=" << ledgerID_ << " seq=" << ledgerSeq_
<< " (available: " << minSeq() << "-" << seq() << ")";
// Default ID that is less than all others
return ID{0};
}
// Return the sequence number of the earliest possible mismatching ancestor
RCLValidatedLedger::Seq
mismatch(RCLValidatedLedger const& a, RCLValidatedLedger const& b)
{
using Seq = RCLValidatedLedger::Seq;
// Find overlapping interval for known sequence for the ledgers
Seq const lower = std::max(a.minSeq(), b.minSeq());
Seq const upper = std::min(a.seq(), b.seq());
Seq curr = upper;
while (curr != Seq{0} && a[curr] != b[curr] && curr >= lower)
--curr;
// If the searchable interval mismatches entirely, then we have to
// assume the ledgers mismatch starting post genesis ledger
return (curr < lower) ? Seq{1} : (curr + Seq{1});
}
RCLValidationsAdaptor::RCLValidationsAdaptor(Application& app, beast::Journal j) : app_(app), j_(j)
{
}
NetClock::time_point
RCLValidationsAdaptor::now() const
{
return app_.getTimeKeeper().closeTime();
}
std::optional<RCLValidatedLedger>
RCLValidationsAdaptor::acquire(LedgerHash const& hash)
{
using namespace std::chrono_literals;
auto ledger = perf::measureDurationAndLog(
[&]() { return app_.getLedgerMaster().getLedgerByHash(hash); },
"getLedgerByHash",
10ms,
j_);
if (!ledger)
{
JLOG(j_.warn()) << "Need validated ledger for preferred ledger analysis " << hash;
Application* pApp = &app_;
app_.getJobQueue().addJob(JtAdvance, "GetConsL2", [pApp, hash, this]() {
JLOG(j_.debug()) << "JOB advanceLedger getConsensusLedger2 started";
pApp->getInboundLedgers().acquireAsync(hash, 0, InboundLedger::Reason::CONSENSUS);
});
return std::nullopt;
}
XRPL_ASSERT(
!ledger->open() && ledger->isImmutable(),
"xrpl::RCLValidationsAdaptor::acquire : valid ledger state");
XRPL_ASSERT(
ledger->header().hash == hash, "xrpl::RCLValidationsAdaptor::acquire : ledger hash match");
return RCLValidatedLedger(ledger, j_);
}
void
handleNewValidation(
Application& app,
std::shared_ptr<STValidation> const& val,
std::string const& source,
BypassAccept const bypassAccept,
std::optional<beast::Journal> j)
{
auto const& signingKey = val->getSignerPublic();
auto const& hash = val->getLedgerHash();
auto const seq = val->getFieldU32(sfLedgerSequence);
// Ensure validation is marked as trusted if signer currently trusted
auto masterKey = app.getValidators().getTrustedKey(signingKey);
if (!val->isTrusted() && masterKey)
val->setTrusted();
// If not currently trusted, see if signer is currently listed
if (!masterKey)
masterKey = app.getValidators().getListedKey(signingKey);
auto& validations = app.getValidations();
// masterKey is seated only if validator is trusted or listed
auto const outcome = validations.add(calcNodeID(masterKey.value_or(signingKey)), val);
// Span this validation into the trace of the ledger it validates. The
// acceptance it may trigger emits its own span from LedgerMaster, possibly
// on another thread; both derive the trace id from the same ledger hash, so
// the two land in one trace.
//
// Started before the outcome is checked, so a rejected validation is
// recorded with its real status. Otherwise "validations are counting" and
// "validations are all rejected" both look like silence on a stuck node.
//
// Trusted only: untrusted validations cannot move acceptance, so this stays
// bounded by the UNL size per ledger.
namespace cs = telemetry::consensus::span;
std::optional<telemetry::SpanGuard> span;
if (val->isTrusted())
{
span.emplace(LedgerMaster::makeLedgerTraceSpan(cs::validationAccept, hash, seq));
span->setAttribute(
cs::attr::validationStatus, cs::validationStatusValue(static_cast<int>(outcome)));
span->setAttribute(cs::attr::fullValidation, val->isFull());
span->setAttribute(cs::attr::acceptGated, bypassAccept == BypassAccept::Yes);
}
if (outcome == ValStatus::Current)
{
if (val->isTrusted())
{
// The span stays alive across checkAccept below, so its duration is
// the time this validation spent driving the acceptance decision --
// the number that grows when a node is slow to validate. A rejected
// validation never reaches checkAccept, so its span is just the
// record that it arrived and was refused.
if (bypassAccept == BypassAccept::Yes)
{
XRPL_ASSERT(j, "xrpl::handleNewValidation : journal is available");
if (j.has_value())
{
JLOG(j->trace())
<< "Bypassing checkAccept for validation " << val->getLedgerHash();
}
}
else
{
app.getLedgerMaster().checkAccept(hash, seq);
}
}
return;
}
// Ensure that problematic validations from validators we trust are
// logged at the highest possible level.
//
// One might think that we should more than just log: we ought to also
// not relay validations that fail these checks. Alas, and somewhat
// counterintuitively, we *especially* want to forward such validations,
// so that our peers will also observe them and take independent notice of
// such validators, informing their operators.
if (auto const ls = val->isTrusted() ? validations.adaptor().journal().error()
: validations.adaptor().journal().info();
ls.active())
{
auto const id = [&masterKey, &signingKey]() {
auto ret = toBase58(TokenType::NodePublic, signingKey);
if (masterKey && masterKey != signingKey)
ret += ":" + toBase58(TokenType::NodePublic, *masterKey);
return ret;
}();
if (outcome == ValStatus::Conflicting)
{
ls << "Byzantine Behavior Detector: " << (val->isTrusted() ? "trusted " : "untrusted ")
<< id << ": Conflicting validation for " << seq << "!\n["
<< val->getSerializer().slice() << "]";
}
if (outcome == ValStatus::Multiple)
{
ls << "Byzantine Behavior Detector: " << (val->isTrusted() ? "trusted " : "untrusted ")
<< id << ": Multiple validations for " << seq << "/" << hash << "!\n["
<< val->getSerializer().slice() << "]";
}
}
}
} // namespace xrpl