mirror of
https://github.com/XRPLF/rippled.git
synced 2026-08-03 04:01:01 +00:00
211 lines
7.5 KiB
C++
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
|