Files
rippled/include/xrpl/consensus/ConsensusParms.h
2026-07-23 21:38:21 +00:00

211 lines
7.5 KiB
C++

#pragma once
#include <xrpl/beast/utility/instrumentation.h>
#include <chrono>
#include <cstddef>
#include <map>
#include <optional>
#include <utility>
namespace xrpl {
/**
* Consensus algorithm parameters
*
* Parameters which control the consensus algorithm. This are not
* meant to be changed arbitrarily.
*/
struct ConsensusParms
{
explicit ConsensusParms() = default;
//-------------------------------------------------------------------------
// Validation and proposal durations are relative to NetClock times, so use
// second resolution
/**
* The duration a validation remains current after its ledger's
* close time.
*
* This is a safety to protect against very old validations and the time
* it takes to adjust the close time accuracy window.
*/
std::chrono::seconds const validationValidWall = std::chrono::minutes{5};
/**
* Duration a validation remains current after first observed.
*
* The duration a validation remains current after the time we
* first saw it. This provides faster recovery in very rare cases where the
* number of validations produced by the network is lower than normal
*/
std::chrono::seconds const validationValidLocal = std::chrono::minutes{3};
/**
* Duration pre-close in which validations are acceptable.
*
* The number of seconds before a close time that we consider a validation
* acceptable. This protects against extreme clock errors
*/
std::chrono::seconds const validationValidEarly = std::chrono::minutes{3};
/**
* How long we consider a proposal fresh
*/
std::chrono::seconds const proposeFRESHNESS = std::chrono::seconds{20};
/**
* How often we force generating a new proposal to keep ours fresh
*/
std::chrono::seconds const proposeINTERVAL = std::chrono::seconds{12};
//-------------------------------------------------------------------------
// Consensus durations are relative to the internal Consensus clock and use
// millisecond resolution.
/**
* The percentage threshold above which we can declare consensus.
*/
std::size_t const minConsensusPct = 80;
/**
* The duration a ledger may remain idle before closing
*/
std::chrono::milliseconds const ledgerIdleInterval = std::chrono::seconds{15};
/**
* The number of seconds we wait minimum to ensure participation
*/
std::chrono::milliseconds const ledgerMinConsensus = std::chrono::milliseconds{1950};
/**
* The maximum amount of time to spend pausing for laggards.
*
* This should be sufficiently less than validationFRESHNESS so that
* validators don't appear to be offline that are merely waiting for
* laggards.
*/
std::chrono::milliseconds const ledgerMaxConsensus = std::chrono::seconds{15};
/**
* Minimum number of seconds to wait to ensure others have computed the LCL
*/
std::chrono::milliseconds const ledgerMinClose = std::chrono::seconds{2};
/**
* How often we check state or change positions
*/
std::chrono::milliseconds const ledgerGRANULARITY = std::chrono::seconds{1};
/**
* How long to wait before completely abandoning consensus
*/
std::size_t const ledgerAbandonConsensusFactor = 10;
/**
* Maximum amount of time to give a consensus round
*
* Does not include the time to build the LCL, so there is no reason for a
* round to go this long, regardless of how big the ledger is.
*/
std::chrono::milliseconds const ledgerAbandonConsensus = std::chrono::seconds{120};
/**
* The minimum amount of time to consider the previous round
* to have taken.
*
* The minimum amount of time to consider the previous round
* to have taken. This ensures that there is an opportunity
* for a round at each avalanche threshold even if the
* previous consensus was very fast. This should be at least
* twice the interval between proposals (0.7s) divided by
* the interval between mid and late consensus ([85-50]/100).
*/
std::chrono::milliseconds const avMinConsensusTime = std::chrono::seconds{5};
//------------------------------------------------------------------------------
// Avalanche tuning
// As a function of the percent this round's duration is of the prior round,
// we increase the threshold for yes votes to add a transaction to our
// position.
enum class AvalancheState { Init, Mid, Late, Stuck };
struct AvalancheCutoff
{
int const consensusTime;
std::size_t const consensusPct;
AvalancheState const next;
};
/**
* Map the consensus requirement avalanche state to the amount of time that
* must pass before moving to that state, the agreement percentage required
* at that state, and the next state. "stuck" loops back on itself because
* once we're stuck, we're stuck.
* This structure allows for "looping" of states if needed.
*/
std::map<AvalancheState, AvalancheCutoff> const avalancheCutoffs{
// {state, {time, percent, nextState}},
// Initial state: 50% of nodes must vote yes
{AvalancheState::Init,
{.consensusTime = 0, .consensusPct = 50, .next = AvalancheState::Mid}},
// mid-consensus starts after 50% of the previous round time, and
// requires 65% yes
{AvalancheState::Mid,
{.consensusTime = 50, .consensusPct = 65, .next = AvalancheState::Late}},
// late consensus starts after 85% time, and requires 70% yes
{AvalancheState::Late,
{.consensusTime = 85, .consensusPct = 70, .next = AvalancheState::Stuck}},
// we're stuck after 2x time, requires 95% yes votes
{AvalancheState::Stuck,
{.consensusTime = 200, .consensusPct = 95, .next = AvalancheState::Stuck}},
};
/**
* Percentage of nodes required to reach agreement on ledger close time
*/
std::size_t const avCtConsensusPct = 75;
/**
* Number of rounds before certain actions can happen.
*/
// (Moving to the next avalanche level, considering that votes are stalled
// without consensus.)
std::size_t const avMinRounds = 2;
/**
* Number of rounds before a stuck vote is considered unlikely to change
* because voting stalled
*/
std::size_t const avStalledRounds = 4;
};
inline std::pair<std::size_t, std::optional<ConsensusParms::AvalancheState>>
getNeededWeight(
ConsensusParms const& p,
ConsensusParms::AvalancheState currentState,
int percentTime,
std::size_t currentRounds,
std::size_t minimumRounds)
{
// at() can throw, but the map is built by hand to ensure all valid
// values are available.
auto const& currentCutoff = p.avalancheCutoffs.at(currentState);
// Should we consider moving to the next state?
if (currentCutoff.next != currentState && currentRounds >= minimumRounds)
{
// at() can throw, but the map is built by hand to ensure all
// valid values are available.
auto const& nextCutoff = p.avalancheCutoffs.at(currentCutoff.next);
// See if enough time has passed to move on to the next.
XRPL_ASSERT(
nextCutoff.consensusTime >= currentCutoff.consensusTime,
"xrpl::getNeededWeight : next state valid");
if (percentTime >= nextCutoff.consensusTime)
{
return {nextCutoff.consensusPct, currentCutoff.next};
}
}
return {currentCutoff.consensusPct, {}};
}
} // namespace xrpl