How validators sign ledgers, how validations are collected, and how a ledger becomes fully validated.
What you'll learn
≈75 min · Advanced · builds on Ledger acquisition & the tx lifecycle
Watch this short video by XRPL Commons first, then dive into the details below.
Agreement isn't final until validators sign off. In this module you'll learn what a validation (STValidation) is, how validations are collected and counted, and how a quorum of them turns a merely-built ledger into a fully validated one. This is the step that makes a ledger official.
In brief: what a validator signs to vouch for a specific ledger (an STValidation).
Location: rippled/include/xrpl/protocol/STValidation.h and rippled/src/libxrpl/protocol/STValidation.cpp
Purpose: Represents a validation message in the XRP Ledger consensus protocol.
Components:
Signed Statements:
Ledger Identification:
Validator Identity:
Consensus Data:
Required fields include:
sfLedgerHash - The hash of the ledger being validatedsfLedgerSequence - The sequence number of the ledgersfSigningTime - When the validation was signedsfSigningPubKey - Validator's public signing keysfSignature - Cryptographic signatureOptional fields may include:
sfConsensusHash - Transaction set hash agreed uponsfAmendments - Amendment votessfBaseFee / sfReserveBase / sfReserveIncrement - Fee votesCryptographic Integrity:
Temporal Ordering:
Unique Identification:
Extensibility:
Key idea. A ledger is only "fully validated" once a quorum of trusted validations agree on it. Until then, it is merely built.
In brief: the timing rules that keep validations orderly.
Location: rippled/src/xrpld/consensus/Validations.h
The ValidationParms struct defines critical timing parameters:
struct ValidationParms
{
std::chrono::seconds validationCURRENT_WALL = std::chrono::minutes{5};
std::chrono::seconds validationCURRENT_LOCAL = std::chrono::minutes{3};
std::chrono::seconds validationCURRENT_EARLY = std::chrono::minutes{3};
std::chrono::seconds validationSET_EXPIRES = std::chrono::minutes{10};
std::chrono::seconds validationFRESHNESS = std::chrono::seconds{20};
};
validationCURRENT_WALL (5 minutes):
signTime < (now + validationCURRENT_WALL)validationCURRENT_LOCAL (3 minutes):
seenTime < (now + validationCURRENT_LOCAL)validationCURRENT_EARLY (3 minutes):
signTime > (now - validationCURRENT_EARLY)validationSET_EXPIRES (10 minutes):
validationFRESHNESS (20 seconds):
now < (seenTime + validationFRESHNESS)Critical Distinction:
Network Latency Accommodation:
Clock Skew Tolerance:
Stale Data Prevention:
Consensus Window Management:
Prevents Fragmentation:
Handles Network Partitions:
Maintains Liveness:
Security Boundaries:
Location: rippled/src/xrpld/consensus/Validations.h
Purpose: Manages current and historical validations, enforces sequence rules, tracks trusted/untrusted validators, and maintains a ledger trie for efficient consensus operations.
Key Data Structures:
current_ Map:
hash_map<NodeID, Validation>byLedger_ Map:
AgedUnorderedMap<LedgerID, (NodeID, Validation) pairs>bySequence_ Map:
AgedUnorderedMap<SequenceNumber, (NodeID, Validation) pairs>Why These Structures?
Location: rippled/src/xrpld/consensus/Validations.h
Purpose: Ensures validation sequences increase properly, prevents replay attacks, and identifies Byzantine validators.
Functionality:
How it Works:
ValStatus::badSeqWhy SeqEnforcer Matters:
Purpose: Organizes ledgers in parent-child relationships for efficient ancestry tracking
Key Features:
Tree Structure:
Branch Support:
Preferred Ledger Logic:
Memory Efficiency:
In brief: how validations are collected and weighed by trust.
The lifecycle of a validation message involves several stages:
Location: rippled/src/xrpld/app/consensus/RCLConsensus.cpp
Process:
STValidation object for the newly built ledgerhandleNewValidation to process own validationImportant Notes:
When a validation message arrives from the network:
Location: rippled/src/xrpld/app/consensus/RCLValidations.cpp
Process:
Validations::add() and receive a ValStatusLedgerMaster::checkAccept to see if the ledger should be accepted as validatedReturns: ValStatus enum indicating the result
Possible Return Values:
enum class ValStatus {
current, // Validation accepted and current
stale, // Validation too old or not timely
badSeq, // Sequence number invalid for this node
multiple, // Node submitted multiple validations for same ledger
conflicting // Node submitted conflicting validations for same sequence
};
Process:
SeqEnforcer to verify sequence number is validValidationParms to verify validation is current (not stale or too early)current_ with latest validation for this nodebyLedger_ indexbySequence_ indexIf a validation is current and trusted, the system checks if enough validations exist to accept the ledger:
LedgerMaster::checkAccept Process:
Validations::getTrustedForLedger(hash, seq)Automatic Cleanup:
validationSET_EXPIRES parameter (10 minutes)Explicit Trust Lists:
Reputation Tracking:
Dynamic Adjustment:
Byzantine Tolerance:
currentTrusted():
getTrustedForLedger(hash, seq):
vfFullValidation:
vfFullyCanonicalSig:
Usage: Returned by the add() method in Validations to indicate result of adding a validation
Values and Meanings:
current:
stale:
validationCURRENT_WALL or validationCURRENT_LOCAL windowsbadSeq:
multiple:
conflicting:
enum class BypassAccept : bool { no = false, yes };
Usage: Indicates whether to bypass certain acceptance checks when processing a validation
Purpose: Allows special operational modes or testing scenarios
In brief: catching validators that sign conflicting ledgers.
The validation system detects and rejects Byzantine behavior through multiple mechanisms:
SeqEnforcer Class (Validations.h:73-107)
Enforces monotonically increasing sequence numbers per validator:
bool operator()(time_point now, Seq s, ValidationParms const& p)
{
if (now > (when_ + p.validationSET_EXPIRES))
seq_ = Seq{0}; // Reset after expiration
if (s <= seq_)
return false; // Reject non-increasing sequence
seq_ = s;
return true;
}
Detects:
ValStatus::badSeq on violationCode: Validations.h:600-640
Tracks multiple validations per sequence and detects conflicts:
// If same sequence but different ledger hash → conflict
if (seqit->second.ledgerID() != val.ledgerID())
return ValStatus::conflicting;
Detects:
isCurrent() Function (Validations.h:129-147)
Validates timing using three parameters:
return (signTime > (now - p.validationCURRENT_EARLY)) &&
(signTime < (now + p.validationCURRENT_WALL)) &&
(seenTime < (now + p.validationCURRENT_LOCAL));
Detects:
ValStatus::stale on violationBuilt into validation processing (before reaching Validations class)
Detects:
Code: Validations.h:640-650
Prevents same validation from being processed multiple times:
if (byLedger_.find(val.ledgerID()) != byLedger_.end())
{
if (existing validation found)
return ValStatus::multiple;
}
Detects:
ValStatus::multipleValStatus enum provides rejection reasonvalidationFRESHNESSAutomatic Filtering:
Reputation Penalties:
Network Alerts:
UNL Updates:
Concurrent Access:
Data Consistency:
Performance:
Deadlock Prevention:
Mutex Protection:
Validations protected by mutexesAtomic Operations:
Immutable Data:
STValidation objects are immutable after creationLock Guards:
All public methods in Validations class:
Iterator Safety:
Cached Results:
isValid() in STValidation caches its resultWhy Thread Safety Matters:
Consensus Speed:
System Reliability:
Scalability:
Real-Time Requirements:
RCLValidationsAdaptor:
Location: rippled/src/xrpld/app/consensus/RCLValidations.h
Purpose: Adapts the generic validation handling framework to XRPL-specific types
Key Responsibilities:
Interface Abstraction:
STValidation to generic Validation templateType Safety:
Flexibility:
Testability:
Event-Driven Architecture:
Callback Mechanisms:
Data Flow:
State Synchronization:
Loose Coupling:
Testing:
Performance:
Reliability:
The validation subsystem interacts with several other key modules:
Validations Module:
RCLValidationsAdaptor:
LedgerTrie:
LedgerMaster:
ValidatorList:
Consensus Process Flow:
rippled/src/xrpld/consensus/Validations.h - Core validation data structures and logicrippled/src/xrpld/app/consensus/RCLValidations.h - XRPL-specific validation handlingrippled/src/xrpld/app/consensus/RCLValidations.cpp - Validation processing implementationrippled/src/libxrpl/protocol/STValidation.cpp - Validation message implementationrippled/include/xrpl/protocol/STValidation.h - Validation message structurerippled/src/xrpld/app/ledger/detail/LedgerMaster.cpp - Ledger acceptance logicrippled/src/xrpld/app/ledger/LedgerHistory.cpp - Historical ledger trackingrippled/src/xrpld/app/misc/ValidatorList.h - Trusted validator managementrippled/src/xrpld/app/misc/NegativeUNLVote.cpp - Validator reliability trackingThis module covered validations, the signed statements by which validators vouch for a specific ledger. You learned the STValidation message, how validations are collected and weighed by trust, and how a quorum of trusted validations turns a merely-built ledger into a fully validated, and irreversible, one. You also saw safeguards like sequence enforcement that catch a validator signing conflicting ledgers.
To remember:
STValidation = a validator's signed statement: ledger hash + sequence (+ amendment votes and more)src/xrpld/consensus/Validations.h + RCLValidations in src/xrpld/app/consensusSeqEnforcer catches a validator signing two different ledgers at the same sequence (Byzantine guard)LedgerTrie organizes candidate chains and picks the preferred branchsubscribe with "streams": ["validations"] on any public nodeNext up. Validations only count if you trust their authors. Next: the UNL and the negative UNL, the trust layer that decides whose voice matters.
Resources
Assignments
0 of 2 complete