#pragma once #include #include #include #include #include #include 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 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> 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