diff --git a/Builds/VisualStudio2015/RippleD.vcxproj b/Builds/VisualStudio2015/RippleD.vcxproj index 44bbbfd43a..4a416a3540 100644 --- a/Builds/VisualStudio2015/RippleD.vcxproj +++ b/Builds/VisualStudio2015/RippleD.vcxproj @@ -1859,16 +1859,18 @@ + + True + True + + + - - True - True - diff --git a/Builds/VisualStudio2015/RippleD.vcxproj.filters b/Builds/VisualStudio2015/RippleD.vcxproj.filters index efaff19785..cf74448ecd 100644 --- a/Builds/VisualStudio2015/RippleD.vcxproj.filters +++ b/Builds/VisualStudio2015/RippleD.vcxproj.filters @@ -2502,18 +2502,21 @@ ripple\conditions\impl + + ripple\consensus + ripple\consensus + + ripple\consensus + ripple\consensus ripple\consensus - - ripple\consensus - ripple\consensus diff --git a/docs/source.dox b/docs/source.dox index 1fb7de3f06..5754d9b1ab 100644 --- a/docs/source.dox +++ b/docs/source.dox @@ -114,6 +114,7 @@ INPUT = \ ../src/ripple/consensus/DisputedTx.h \ ../src/ripple/consensus/LedgerTiming.h \ ../src/ripple/consensus/Validations.h \ + ../src/ripple/consensus/ConsensusParms.h \ ../src/ripple/app/consensus/RCLCxTx.h \ ../src/ripple/app/consensus/RCLCxLedger.h \ ../src/ripple/app/consensus/RCLConsensus.h \ diff --git a/src/ripple/app/consensus/RCLConsensus.cpp b/src/ripple/app/consensus/RCLConsensus.cpp index 2d95cdb99c..b26451201d 100644 --- a/src/ripple/app/consensus/RCLConsensus.cpp +++ b/src/ripple/app/consensus/RCLConsensus.cpp @@ -50,7 +50,7 @@ RCLConsensus::RCLConsensus( InboundTransactions& inboundTransactions, typename Base::clock_type const& clock, beast::Journal journal) - : Base(clock, journal) + : Base(clock, ConsensusParms{}, journal) , app_(app) , feeVote_(std::move(feeVote)) , ledgerMaster_(ledgerMaster) diff --git a/src/ripple/app/misc/NetworkOPs.cpp b/src/ripple/app/misc/NetworkOPs.cpp index 4c9690ebf7..53f8201f34 100644 --- a/src/ripple/app/misc/NetworkOPs.cpp +++ b/src/ripple/app/misc/NetworkOPs.cpp @@ -25,7 +25,7 @@ #include #include #include -#include +#include #include #include #include @@ -639,7 +639,7 @@ void NetworkOPsImp::setStateTimer () void NetworkOPsImp::setHeartbeatTimer () { - m_heartbeatTimer.setExpiration (LEDGER_GRANULARITY); + m_heartbeatTimer.setExpiration (mConsensus->parms().ledgerGRANULARITY); } void NetworkOPsImp::setClusterTimer () diff --git a/src/ripple/consensus/LedgerTiming.cpp b/src/ripple/consensus/Consensus.cpp similarity index 81% rename from src/ripple/consensus/LedgerTiming.cpp rename to src/ripple/consensus/Consensus.cpp index fe069a2a53..ac40e039e8 100644 --- a/src/ripple/consensus/LedgerTiming.cpp +++ b/src/ripple/consensus/Consensus.cpp @@ -19,9 +19,7 @@ #include #include -#include -#include -#include +#include namespace ripple { @@ -33,13 +31,15 @@ shouldCloseLedger( std::size_t proposersValidated, std::chrono::milliseconds prevRoundTime, std::chrono::milliseconds - timeSincePrevClose, // Time since last ledger's close time + timeSincePrevClose, // Time since last ledger's close time std::chrono::milliseconds openTime, // Time waiting to close this ledger - std::chrono::seconds idleInterval, + std::chrono::milliseconds idleInterval, + ConsensusParms const& parms, beast::Journal j) { using namespace std::chrono_literals; - if ((prevRoundTime < -1s) || (prevRoundTime > 10min) || (timeSincePrevClose > 10min)) + if ((prevRoundTime < -1s) || (prevRoundTime > 10min) || + (timeSincePrevClose > 10min)) { // These are unexpected cases, we just close the ledger JLOG(j.warn()) << "shouldCloseLedger Trans=" @@ -64,7 +64,7 @@ shouldCloseLedger( } // Preserve minimum ledger open time - if (openTime < LEDGER_MIN_CLOSE) + if (openTime < parms.ledgerMIN_CLOSE) { JLOG(j.debug()) << "Must wait minimum time before closing"; return false; @@ -84,7 +84,11 @@ shouldCloseLedger( } bool -checkConsensusReached(std::size_t agreeing, std::size_t total, bool count_self) +checkConsensusReached( + std::size_t agreeing, + std::size_t total, + bool count_self, + std::size_t minConsensusPct) { // If we are alone, we have a consensus if (total == 0) @@ -96,9 +100,9 @@ checkConsensusReached(std::size_t agreeing, std::size_t total, bool count_self) ++total; } - int currentPercentage = (agreeing * 100) / total; + std::size_t currentPercentage = (agreeing * 100) / total; - return currentPercentage > minimumConsensusPercentage; + return currentPercentage > minConsensusPct; } ConsensusState @@ -109,6 +113,7 @@ checkConsensus( std::size_t currentFinished, std::chrono::milliseconds previousAgreeTime, std::chrono::milliseconds currentAgreeTime, + ConsensusParms const& parms, bool proposing, beast::Journal j) { @@ -118,14 +123,14 @@ checkConsensus( << " time=" << currentAgreeTime.count() << "/" << previousAgreeTime.count(); - if (currentAgreeTime <= LEDGER_MIN_CONSENSUS) + if (currentAgreeTime <= parms.ledgerMIN_CONSENSUS) return ConsensusState::No; if (currentProposers < (prevProposers * 3 / 4)) { // Less than 3/4 of the last ledger's proposers are present; don't // rush: we may need more time. - if (currentAgreeTime < (previousAgreeTime + LEDGER_MIN_CONSENSUS)) + if (currentAgreeTime < (previousAgreeTime + parms.ledgerMIN_CONSENSUS)) { JLOG(j.trace()) << "too fast, not enough proposers"; return ConsensusState::No; @@ -134,7 +139,8 @@ checkConsensus( // Have we, together with the nodes on our UNL list, reached the threshold // to declare consensus? - if (checkConsensusReached(currentAgree, currentProposers, proposing)) + if (checkConsensusReached( + currentAgree, currentProposers, proposing, parms.minCONSENSUS_PCT)) { JLOG(j.debug()) << "normal consensus"; return ConsensusState::Yes; @@ -142,7 +148,8 @@ checkConsensus( // Have sufficient nodes on our UNL list moved on and reached the threshold // to declare consensus? - if (checkConsensusReached(currentFinished, currentProposers, false)) + if (checkConsensusReached( + currentFinished, currentProposers, false, parms.minCONSENSUS_PCT)) { JLOG(j.warn()) << "We see no consensus, but 80% of nodes have moved on"; return ConsensusState::MovedOn; @@ -153,4 +160,4 @@ checkConsensus( return ConsensusState::No; } -} // ripple +} // namespace ripple diff --git a/src/ripple/consensus/Consensus.h b/src/ripple/consensus/Consensus.h index 31c01bb5d4..f41431cfe4 100644 --- a/src/ripple/consensus/Consensus.h +++ b/src/ripple/consensus/Consensus.h @@ -24,11 +24,79 @@ #include #include #include +#include +#include #include #include namespace ripple { + +/** Determines whether the current ledger should close at this time. + + This function should be called when a ledger is open and there is no close + in progress, or when a transaction is received and no close is in progress. + + @param anyTransactions indicates whether any transactions have been received + @param prevProposers proposers in the last closing + @param proposersClosed proposers who have currently closed this ledger + @param proposersValidated proposers who have validated the last closed + ledger + @param prevRoundTime time for the previous ledger to reach consensus + @param timeSincePrevClose time since the previous ledger's (possibly rounded) + close time + @param openTime duration this ledger has been open + @param idleInterval the network's desired idle interval + @param parms Consensus constant parameters + @param j journal for logging +*/ +bool +shouldCloseLedger( + bool anyTransactions, + std::size_t prevProposers, + std::size_t proposersClosed, + std::size_t proposersValidated, + std::chrono::milliseconds prevRoundTime, + std::chrono::milliseconds timeSincePrevClose, + std::chrono::milliseconds openTime, + std::chrono::milliseconds idleInterval, + ConsensusParms const & parms, + beast::Journal j); + + +/** Whether we have or don't have a consensus */ +enum class ConsensusState { + No, //!< We do not have consensus + MovedOn, //!< The network has consensus without us + Yes //!< We have consensus along with the network +}; + +/** Determine whether the network reached consensus and whether we joined. + + @param prevProposers proposers in the last closing (not including us) + @param currentProposers proposers in this closing so far (not including us) + @param currentAgree proposers who agree with us + @param currentFinished proposers who have validated a ledger after this one + @param previousAgreeTime how long, in milliseconds, it took to agree on the + last ledger + @param currentAgreeTime how long, in milliseconds, we've been trying to + agree + @param parms Consensus constant parameters + @param proposing whether we should count ourselves + @param j journal for logging +*/ +ConsensusState +checkConsensus( + std::size_t prevProposers, + std::size_t currentProposers, + std::size_t currentAgree, + std::size_t currentFinished, + std::chrono::milliseconds previousAgreeTime, + std::chrono::milliseconds currentAgreeTime, + ConsensusParms const & parms, + bool proposing, + beast::Journal j); + /** Generic implementation of consensus algorithm. Achieves consensus on the next ledger. @@ -351,9 +419,10 @@ public: /** Constructor. @param clock The clock used to internally sample consensus progress + @param p Consensus parameters to use @param j The journal to log debug output */ - Consensus(clock_type const& clock, beast::Journal j); + Consensus(clock_type const& clock, ConsensusParms const & p, beast::Journal j); /** Kick-off the next round of consensus. @@ -474,6 +543,13 @@ public: Json::Value getJson(bool full) const; + /** Get the consensus parameters + */ + ConsensusParms const & + parms() const + { + return parms_; + } protected: // Prevent deleting derived instance through base pointer @@ -584,7 +660,7 @@ private: NetClock::duration closeResolution_ = ledgerDefaultTimeResolution; // Time it took for the last consensus round to converge - std::chrono::milliseconds prevRoundTime_ = LEDGER_IDLE_INTERVAL; + std::chrono::milliseconds prevRoundTime_; //------------------------------------------------------------------------- // Network time measurements of consensus progress @@ -618,6 +694,9 @@ private: // nodes that have bowed out of this consensus process hash_set deadNodes_; + // Parameters that control consensus algorithm + ConsensusParms const parms_; + // Journal for debugging beast::Journal j_; }; @@ -625,9 +704,12 @@ private: template Consensus::Consensus( clock_type const& clock, + ConsensusParms const & p, beast::Journal journal) - : lock_(std::make_unique()) - , clock_(clock) + : lock_{std::make_unique()} + , clock_{clock} + , prevRoundTime_{p.ledgerIDLE_INTERVAL} + , parms_(p) , j_{journal} { JLOG(j_.debug()) << "Creating consensus object"; @@ -1112,9 +1194,9 @@ Consensus::phaseOpen() sinceClose = -duration_cast(lastCloseTime - now_); } - auto const idleInterval = std::max( - LEDGER_IDLE_INTERVAL, - duration_cast(2 * previousLedger_.closeTimeResolution())); + auto const idleInterval = std::max( + parms_.ledgerIDLE_INTERVAL, + 2 * previousLedger_.closeTimeResolution()); // Decide if we should close the ledger if (shouldCloseLedger( @@ -1126,6 +1208,7 @@ Consensus::phaseOpen() sinceClose, openTime_.read(), idleInterval, + parms_, j_)) { closeLedger(); @@ -1143,10 +1226,10 @@ Consensus::phaseEstablish() result_->roundTime.tick(clock_.now()); convergePercent_ = result_->roundTime.read() * 100 / - std::max(prevRoundTime_, AV_MIN_CONSENSUS_TIME); + std::max(prevRoundTime_, parms_.avMIN_CONSENSUS_TIME); // Give everyone a chance to take an initial position - if (result_->roundTime.read() < LEDGER_MIN_CONSENSUS) + if (result_->roundTime.read() < parms_.ledgerMIN_CONSENSUS) return; updateOurPositions(); @@ -1230,8 +1313,8 @@ Consensus::updateOurPositions() assert(result_); // Compute a cutoff time - auto const peerCutoff = now_ - PROPOSE_FRESHNESS; - auto const ourCutoff = now_ - PROPOSE_INTERVAL; + auto const peerCutoff = now_ - parms_.proposeFRESHNESS; + auto const ourCutoff = now_ - parms_.proposeINTERVAL; // Verify freshness of peer positions and compute close times std::map effCloseTimes; @@ -1271,7 +1354,7 @@ Consensus::updateOurPositions() // Because the threshold for inclusion increases, // time can change our position on a dispute if (it.second.updateVote( - convergePercent_, (mode_ == Mode::proposing))) + convergePercent_, (mode_ == Mode::proposing), parms_)) { if (!mutableSet) mutableSet.emplace(result_->set); @@ -1309,14 +1392,14 @@ Consensus::updateOurPositions() { int neededWeight; - if (convergePercent_ < AV_MID_CONSENSUS_TIME) - neededWeight = AV_INIT_CONSENSUS_PCT; - else if (convergePercent_ < AV_LATE_CONSENSUS_TIME) - neededWeight = AV_MID_CONSENSUS_PCT; - else if (convergePercent_ < AV_STUCK_CONSENSUS_TIME) - neededWeight = AV_LATE_CONSENSUS_PCT; + if (convergePercent_ < parms_.avMID_CONSENSUS_TIME) + neededWeight = parms_.avINIT_CONSENSUS_PCT; + else if (convergePercent_ < parms_.avLATE_CONSENSUS_TIME) + neededWeight = parms_.avMID_CONSENSUS_PCT; + else if (convergePercent_ < parms_.avSTUCK_CONSENSUS_TIME) + neededWeight = parms_.avLATE_CONSENSUS_PCT; else - neededWeight = AV_STUCK_CONSENSUS_PCT; + neededWeight = parms_.avSTUCK_CONSENSUS_PCT; int participants = peerProposals_.size(); if (mode_ == Mode::proposing) @@ -1333,7 +1416,7 @@ Consensus::updateOurPositions() // Threshold to declare consensus int const threshConsensus = - participantsNeeded(participants, AV_CT_CONSENSUS_PCT); + participantsNeeded(participants, parms_.avCT_CONSENSUS_PCT); JLOG(j_.info()) << "Proposers:" << peerProposals_.size() << " nw:" << neededWeight << " thrV:" << threshVote @@ -1454,6 +1537,7 @@ Consensus::haveConsensus() currentFinished, prevRoundTime_, result_->roundTime.read(), + parms_, mode_ == Mode::proposing, j_); diff --git a/src/ripple/consensus/ConsensusParms.h b/src/ripple/consensus/ConsensusParms.h new file mode 100644 index 0000000000..02ed12a61f --- /dev/null +++ b/src/ripple/consensus/ConsensusParms.h @@ -0,0 +1,135 @@ +//------------------------------------------------------------------------------ +/* + This file is part of rippled: https://github.com/ripple/rippled + Copyright (c) 2012-2017 Ripple Labs Inc. + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== + +#ifndef RIPPLE_CONSENSUS_CONSENSUS_PARMS_H_INCLUDED +#define RIPPLE_CONSENSUS_CONSENSUS_PARMS_H_INCLUDED + +#include +#include + +namespace ripple { + +using namespace std::chrono_literals; + +/** Consensus algorithm parameters + + Parameters which control the consensus algorithm. This are not + meant to be changed arbitrarily. +*/ +struct ConsensusParms +{ + + //------------------------------------------------------------------------- + // 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 validationVALID_WALL = 5min; + + /** 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 validationVALID_LOCAL = 3min; + + /** 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 validationVALID_EARLY = 3min; + + + //! How long we consider a proposal fresh + std::chrono::seconds proposeFRESHNESS = 20s; + + //! How often we force generating a new proposal to keep ours fresh + std::chrono::seconds proposeINTERVAL = 12s; + + + //------------------------------------------------------------------------- + // Consensus durations are relative to the internal Consenus clock and use + // millisecond resolution. + + //! The percentage threshold above which we can declare consensus. + std::size_t minCONSENSUS_PCT = 80; + + //! The duration a ledger may remain idle before closing + std::chrono::milliseconds ledgerIDLE_INTERVAL = 15s; + + //! The number of seconds we wait minimum to ensure participation + std::chrono::milliseconds ledgerMIN_CONSENSUS = 1950ms; + + //! Minimum number of seconds to wait to ensure others have computed the LCL + std::chrono::milliseconds ledgerMIN_CLOSE = 2s; + + //! How often we check state or change positions + std::chrono::milliseconds ledgerGRANULARITY = 1s; + + /** 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 avMIN_CONSENSUS_TIME = 5s; + + //------------------------------------------------------------------------------ + // Avalanche tuning + // As a function of the percent this round's duration is of the prior round, + // we increase the threshold for yes vots to add a tranasaction to our + // position. + + //! Percentage of nodes on our UNL that must vote yes + std::size_t avINIT_CONSENSUS_PCT = 50; + + //! Percentage of previous round duration before we advance + std::size_t avMID_CONSENSUS_TIME = 50; + + //! Percentage of nodes that most vote yes after advancing + std::size_t avMID_CONSENSUS_PCT = 65; + + //! Percentage of previous round duration before we advance + std::size_t avLATE_CONSENSUS_TIME = 85; + + //! Percentage of nodes that most vote yes after advancing + std::size_t avLATE_CONSENSUS_PCT = 70; + + //! Percentage of previous round duration before we are stuck + std::size_t avSTUCK_CONSENSUS_TIME = 200; + + //! Percentage of nodes that must vote yes after we are stuck + std::size_t avSTUCK_CONSENSUS_PCT = 95; + + //! Percentage of nodes required to reach agreement on ledger close time + std::size_t avCT_CONSENSUS_PCT = 75; +}; + +} // ripple +#endif diff --git a/src/ripple/consensus/DisputedTx.h b/src/ripple/consensus/DisputedTx.h index 41e3d8ebe0..c0ef35f956 100644 --- a/src/ripple/consensus/DisputedTx.h +++ b/src/ripple/consensus/DisputedTx.h @@ -23,7 +23,7 @@ #include #include #include -#include +#include #include #include #include @@ -112,10 +112,11 @@ public: @param percentTime Percentage progress through consensus, e.g. 50% through or 90%. @param proposing Whether we are proposing to our peers in this round. + @param p Consensus parameters controlling thresholds for voting @return Whether our vote changed */ bool - updateVote(int percentTime, bool proposing); + updateVote(int percentTime, bool proposing, ConsensusParms const& p); //! JSON representation of dispute, used for debugging Json::Value @@ -190,7 +191,10 @@ DisputedTx::unVote(NodeID_t const& peer) template bool -DisputedTx::updateVote(int percentTime, bool proposing) +DisputedTx::updateVote( + int percentTime, + bool proposing, + ConsensusParms const& p) { if (ourVote_ && (nays_ == 0)) return false; @@ -212,14 +216,14 @@ DisputedTx::updateVote(int percentTime, bool proposing) // // To prevent avalanche stalls, we increase the needed weight slightly // over time. - if (percentTime < AV_MID_CONSENSUS_TIME) - newPosition = weight > AV_INIT_CONSENSUS_PCT; - else if (percentTime < AV_LATE_CONSENSUS_TIME) - newPosition = weight > AV_MID_CONSENSUS_PCT; - else if (percentTime < AV_STUCK_CONSENSUS_TIME) - newPosition = weight > AV_LATE_CONSENSUS_PCT; + if (percentTime < p.avMID_CONSENSUS_TIME) + newPosition = weight > p.avINIT_CONSENSUS_PCT; + else if (percentTime < p.avLATE_CONSENSUS_TIME) + newPosition = weight > p.avMID_CONSENSUS_PCT; + else if (percentTime < p.avSTUCK_CONSENSUS_TIME) + newPosition = weight > p.avLATE_CONSENSUS_PCT; else - newPosition = weight > AV_STUCK_CONSENSUS_PCT; + newPosition = weight > p.avSTUCK_CONSENSUS_PCT; } else { diff --git a/src/ripple/consensus/LedgerTiming.h b/src/ripple/consensus/LedgerTiming.h index 19f2914d03..08552c347d 100644 --- a/src/ripple/consensus/LedgerTiming.h +++ b/src/ripple/consensus/LedgerTiming.h @@ -27,15 +27,9 @@ namespace ripple { -//------------------------------------------------------------------------------ -// These are protocol parameters used to control the behavior of the system and -// they should not be changed arbitrarily. - -//! The percentage threshold above which we can declare consensus. -auto constexpr minimumConsensusPercentage = 80; - using namespace std::chrono_literals; -/** Possible close time resolutions. + +/** Possible ledger close time resolutions. Values should not be duplicated. @see getNextLedgerTimeResolution @@ -52,61 +46,6 @@ auto constexpr increaseLedgerTimeResolutionEvery = 8; //! How often we decrease the close time resolution (in numbers of ledgers) auto constexpr decreaseLedgerTimeResolutionEvery = 1; -//! The number of seconds a ledger may remain idle before closing -auto constexpr LEDGER_IDLE_INTERVAL = 15s; - -//! The number of seconds we wait minimum to ensure participation -auto constexpr LEDGER_MIN_CONSENSUS = 1950ms; - -//! Minimum number of seconds to wait to ensure others have computed the LCL -auto constexpr LEDGER_MIN_CLOSE = 2s; - -//! How often we check state or change positions -auto constexpr LEDGER_GRANULARITY = 1s; - -//! How long we consider a proposal fresh -auto constexpr PROPOSE_FRESHNESS = 20s; - -//! How often we force generating a new proposal to keep ours fresh -auto constexpr PROPOSE_INTERVAL = 12s; - -//------------------------------------------------------------------------------ -// Avalanche tuning -//! Percentage of nodes on our UNL that must vote yes -auto constexpr AV_INIT_CONSENSUS_PCT = 50; - -//! Percentage of previous close time before we advance -auto constexpr AV_MID_CONSENSUS_TIME = 50; - -//! Percentage of nodes that most vote yes after advancing -auto constexpr AV_MID_CONSENSUS_PCT = 65; - -//! Percentage of previous close time before we advance -auto constexpr AV_LATE_CONSENSUS_TIME = 85; - -//! Percentage of nodes that most vote yes after advancing -auto constexpr AV_LATE_CONSENSUS_PCT = 70; - -//! Percentage of previous close time before we are stuck -auto constexpr AV_STUCK_CONSENSUS_TIME = 200; - -//! Percentage of nodes that must vote yes after we are stuck -auto constexpr AV_STUCK_CONSENSUS_PCT = 95; - -//! Percentage of nodes required to reach agreement on ledger close time -auto constexpr AV_CT_CONSENSUS_PCT = 75; - -/** 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). -*/ -auto constexpr AV_MIN_CONSENSUS_TIME = 5s; /** Calculates the close time resolution for the specified ledger. @@ -200,6 +139,8 @@ effCloseTime( typename time_point::duration const resolution, time_point priorCloseTime) { + using namespace std::chrono_literals; + if (closeTime == time_point{}) return closeTime; @@ -207,78 +148,5 @@ effCloseTime( roundCloseTime(closeTime, resolution), (priorCloseTime + 1s)); } -/** Determines whether the current ledger should close at this time. - - This function should be called when a ledger is open and there is no close - in progress, or when a transaction is received and no close is in progress. - - @param anyTransactions indicates whether any transactions have been received - @param prevProposers proposers in the last closing - @param proposersClosed proposers who have currently closed this ledger - @param proposersValidated proposers who have validated the last closed - ledger - @param prevRoundTime time for the previous ledger to reach consensus - @param timeSincePrevClose time since the previous ledger's (possibly rounded) - close time - @param openTime duration this ledger has been open - @param idleInterval the network's desired idle interval - @param j journal for logging -*/ -bool -shouldCloseLedger( - bool anyTransactions, - std::size_t prevProposers, - std::size_t proposersClosed, - std::size_t proposersValidated, - std::chrono::milliseconds prevRoundTime, - std::chrono::milliseconds timeSincePrevClose, - std::chrono::milliseconds openTime, - std::chrono::seconds idleInterval, - beast::Journal j); - -/** Determine if a consensus has been reached - - This function determines if a consensus has been reached - - @param agreeing count of agreements with our position - @param total count of participants other than us - @param count_self whether we count ourselves - @return True if a consensus has been reached -*/ -bool -checkConsensusReached(std::size_t agreeing, std::size_t total, bool count_self); - -/** Whether we have or don't have a consensus */ -enum class ConsensusState { - No, //!< We do not have consensus - MovedOn, //!< The network has consensus without us - Yes //!< We have consensus along with the network -}; - -/** Determine whether the network reached consensus and whether we joined. - - @param prevProposers proposers in the last closing (not including us) - @param currentProposers proposers in this closing so far (not including us) - @param currentAgree proposers who agree with us - @param currentFinished proposers who have validated a ledger after this one - @param previousAgreeTime how long, in milliseconds, it took to agree on the - last ledger - @param currentAgreeTime how long, in milliseconds, we've been trying to - agree - @param proposing whether we should count ourselves - @param j journal for logging -*/ -ConsensusState -checkConsensus( - std::size_t prevProposers, - std::size_t currentProposers, - std::size_t currentAgree, - std::size_t currentFinished, - std::chrono::milliseconds previousAgreeTime, - std::chrono::milliseconds currentAgreeTime, - bool proposing, - beast::Journal j); - -} // ripple - +} #endif diff --git a/src/ripple/unity/consensus.cpp b/src/ripple/unity/consensus.cpp index 74a8b8aafe..8554a327ce 100644 --- a/src/ripple/unity/consensus.cpp +++ b/src/ripple/unity/consensus.cpp @@ -18,4 +18,4 @@ //============================================================================== #include -#include +#include diff --git a/src/test/consensus/Consensus_test.cpp b/src/test/consensus/Consensus_test.cpp index 0bffc4bdaa..ce3d58466b 100644 --- a/src/test/consensus/Consensus_test.cpp +++ b/src/test/consensus/Consensus_test.cpp @@ -31,13 +31,97 @@ namespace test { class Consensus_test : public beast::unit_test::suite { public: + void + testShouldCloseLedger() + { + using namespace std::chrono_literals; + + // Use default parameters + ConsensusParms p; + beast::Journal j; + + // Bizarre times forcibly close + BEAST_EXPECT( + shouldCloseLedger(true, 10, 10, 10, -10s, 10s, 1s, 1s, p, j)); + BEAST_EXPECT( + shouldCloseLedger(true, 10, 10, 10, 100h, 10s, 1s, 1s, p, j)); + BEAST_EXPECT( + shouldCloseLedger(true, 10, 10, 10, 10s, 100h, 1s, 1s, p, j)); + + // Rest of network has closed + BEAST_EXPECT( + shouldCloseLedger(true, 10, 3, 5, 10s, 10s, 10s, 10s, p, j)); + + // No transactions means wait until end of internval + BEAST_EXPECT( + !shouldCloseLedger(false, 10, 0, 0, 1s, 1s, 1s, 10s, p, j)); + BEAST_EXPECT( + shouldCloseLedger(false, 10, 0, 0, 1s, 10s, 1s, 10s, p, j)); + + // Enforce minimum ledger open time + BEAST_EXPECT( + !shouldCloseLedger(true, 10, 0, 0, 10s, 10s, 1s, 10s, p, j)); + + // Don't go too much faster than last time + BEAST_EXPECT( + !shouldCloseLedger(true, 10, 0, 0, 10s, 10s, 3s, 10s, p, j)); + + BEAST_EXPECT( + shouldCloseLedger(true, 10, 0, 0, 10s, 10s, 10s, 10s, p, j)); + } + + void + testCheckConsensus() + { + using namespace std::chrono_literals; + + // Use default parameterss + ConsensusParms p; + beast::Journal j; + + + // Not enough time has elapsed + BEAST_EXPECT( + ConsensusState::No == + checkConsensus(10, 2, 2, 0, 3s, 2s, p, true, j)); + + // If not enough peers have propsed, ensure + // more time for proposals + BEAST_EXPECT( + ConsensusState::No == + checkConsensus(10, 2, 2, 0, 3s, 4s, p, true, j)); + + // Enough time has elapsed and we all agree + BEAST_EXPECT( + ConsensusState::Yes == + checkConsensus(10, 2, 2, 0, 3s, 10s, p, true, j)); + + // Enough time has elapsed and we don't yet agree + BEAST_EXPECT( + ConsensusState::No == + checkConsensus(10, 2, 1, 0, 3s, 10s, p, true, j)); + + // Our peers have moved on + // Enough time has elapsed and we all agree + BEAST_EXPECT( + ConsensusState::MovedOn == + checkConsensus(10, 2, 1, 8, 3s, 10s, p, true, j)); + + // No peers makes it easy to agree + BEAST_EXPECT( + ConsensusState::Yes == + checkConsensus(0, 0, 0, 0, 3s, 10s, p, true, j)); + } + void testStandalone() { + using namespace std::chrono_literals; using namespace csf; + ConsensusParms parms; auto tg = TrustGraph::makeComplete(1); - Sim s(tg, topology(tg, fixed{LEDGER_GRANULARITY})); + Sim s(parms, tg, topology(tg, fixed{parms.ledgerGRANULARITY})); auto& p = s.peers[0]; @@ -63,10 +147,14 @@ public: using namespace csf; using namespace std::chrono; + ConsensusParms parms; auto tg = TrustGraph::makeComplete(5); Sim sim( + parms, tg, - topology(tg, fixed{round(0.2 * LEDGER_GRANULARITY)})); + topology( + tg, + fixed{round(0.2 * parms.ledgerGRANULARITY)})); // everyone submits their own ID as a TX and relay it to peers for (auto& p : sim.peers) @@ -98,12 +186,13 @@ public: for (auto isParticipant : {true, false}) { + ConsensusParms parms; auto tg = TrustGraph::makeComplete(5); - Sim sim(tg, topology(tg, [](PeerID i, PeerID j) { + Sim sim(parms, tg, topology(tg, [&](PeerID i, PeerID j) { auto delayFactor = (i == 0 || j == 0) ? 1.1 : 0.2; return round( - delayFactor * LEDGER_GRANULARITY); + delayFactor * parms.ledgerGRANULARITY); })); sim.peers[0].proposing_ = sim.peers[0].validating_ = isParticipant; @@ -183,7 +272,7 @@ public: // the skews need to be at least 10 seconds. // Complicating this matter is that nodes will ignore proposals - // with times more than PROPOSE_FRESHNESS =20s in the past. So at + // with times more than proposeFRESHNESS =20s in the past. So at // the minimum granularity, we have at most 3 types of skews // (0s,10s,20s). @@ -191,22 +280,26 @@ public: // skew. Then no majority (1/3 < 1/2) of nodes will agree on an // actual close time. + ConsensusParms parms; auto tg = TrustGraph::makeComplete(6); Sim sim( + parms, tg, - topology(tg, fixed{round(0.2 * LEDGER_GRANULARITY)})); + topology( + tg, + fixed{round(0.2 * parms.ledgerGRANULARITY)})); // Run consensus without skew until we have a short close time // resolution while (sim.peers.front().lastClosedLedger.closeTimeResolution() >= - PROPOSE_FRESHNESS) + parms.proposeFRESHNESS) sim.run(1); // Introduce a shift on the time of half the peers - sim.peers[0].clockSkew = PROPOSE_FRESHNESS / 2; - sim.peers[1].clockSkew = PROPOSE_FRESHNESS / 2; - sim.peers[2].clockSkew = PROPOSE_FRESHNESS; - sim.peers[3].clockSkew = PROPOSE_FRESHNESS; + sim.peers[0].clockSkew = parms.proposeFRESHNESS / 2; + sim.peers[1].clockSkew = parms.proposeFRESHNESS / 2; + sim.peers[2].clockSkew = parms.proposeFRESHNESS; + sim.peers[3].clockSkew = parms.proposeFRESHNESS; // Verify all peers have the same LCL and it has all the Txs sim.run(1); @@ -224,9 +317,12 @@ public: // Specialized test to exercise a temporary fork in which some peers // are working on an incorrect prior ledger. + ConsensusParms parms; + + // Vary the time it takes to process validations to exercise detecting // the wrong LCL at different phases of consensus - for (auto validationDelay : {0s, LEDGER_MIN_CLOSE}) + for (auto validationDelay : {0ms, parms.ledgerMIN_CLOSE}) { // Consider 10 peers: // 0 1 2 3 4 5 6 7 8 9 @@ -256,10 +352,10 @@ public: // This topology can fork, which is why we are using it for this // test. - BEAST_EXPECT(tg.canFork(minimumConsensusPercentage / 100.)); + BEAST_EXPECT(tg.canFork(parms.minCONSENSUS_PCT / 100.)); - auto netDelay = round(0.2 * LEDGER_GRANULARITY); - Sim sim(tg, topology(tg, fixed{netDelay})); + auto netDelay = round(0.2 * parms.ledgerGRANULARITY); + Sim sim(parms, tg, topology(tg, fixed{netDelay})); // initial round to set prior state sim.run(1); @@ -283,7 +379,7 @@ public: // wrong ones) and recover within that round since wrong LCL // is detected before we close // - // With a validation delay of LEDGER_MIN_CLOSE, we need 3 more + // With a validation delay of ledgerMIN_CLOSE, we need 3 more // rounds. // 1. Round to generate different ledgers // 2. Round to detect different prior ledgers (but still generate @@ -324,9 +420,6 @@ public: // switchLCL switched from establish to open phase, but still processed // the establish phase logic. { - using namespace csf; - using namespace std::chrono; - // A mostly disjoint topology std::vector unls; unls.push_back({0, 1}); @@ -337,14 +430,14 @@ public: TrustGraph tg{unls, membership}; - Sim sim(tg, topology(tg, fixed{round( - 0.2 * LEDGER_GRANULARITY)})); + Sim sim(parms, tg, topology(tg, fixed{round( + 0.2 * parms.ledgerGRANULARITY)})); // initial ground to set prior state sim.run(1); for (auto &p : sim.peers) { // A long delay to acquire a missing ledger from the network - p.missingLedgerDelay = 2 * LEDGER_MIN_CLOSE; + p.missingLedgerDelay = 2 * parms.ledgerMIN_CLOSE; // Everyone sees only their own LCL p.openTxs.insert(Tx(p.id)); @@ -367,11 +460,15 @@ public: int numPeers = 10; for (int overlap = 0; overlap <= numPeers; ++overlap) { + ConsensusParms parms; auto tg = TrustGraph::makeClique(numPeers, overlap); Sim sim( + parms, tg, topology( - tg, fixed{round(0.2 * LEDGER_GRANULARITY)})); + tg, + fixed{ + round(0.2 * parms.ledgerGRANULARITY)})); // Initial round to set prior state sim.run(1); @@ -406,7 +503,7 @@ public: simClockSkew() { using namespace csf; - + using namespace std::chrono_literals; // Attempting to test what happens if peers enter consensus well // separated in time. Initial round (in which peers are not staggered) // is used to get the network going, then transactions are submitted @@ -422,8 +519,9 @@ public: for (auto stagger : {800ms, 1600ms, 3200ms, 30000ms, 45000ms, 300000ms}) { + ConsensusParms parms; auto tg = TrustGraph::makeComplete(5); - Sim sim(tg, topology(tg, [](PeerID i, PeerID) { + Sim sim(parms, tg, topology(tg, [](PeerID i, PeerID) { return 200ms * (i + 1); })); @@ -474,6 +572,7 @@ public: double transProb = 0.5; std::mt19937_64 rng; + ConsensusParms parms; auto tg = TrustGraph::makeRandomRanked( N, @@ -483,8 +582,11 @@ public: rng); Sim sim{ + parms, tg, - topology(tg, fixed{round(0.2 * LEDGER_GRANULARITY)})}; + topology( + tg, + fixed{round(0.2 * parms.ledgerGRANULARITY)})}; // Initial round to set prior state sim.run(1); @@ -511,6 +613,9 @@ public: void run() override { + testShouldCloseLedger(); + testCheckConsensus(); + testStandalone(); testPeersAgree(); testSlowPeer(); diff --git a/src/test/consensus/LedgerTiming_test.cpp b/src/test/consensus/LedgerTiming_test.cpp index 1c9894ab86..b3ce73733d 100644 --- a/src/test/consensus/LedgerTiming_test.cpp +++ b/src/test/consensus/LedgerTiming_test.cpp @@ -77,6 +77,7 @@ class LedgerTiming_test : public beast::unit_test::suite void testRoundCloseTime() { + using namespace std::chrono_literals; // A closeTime equal to the epoch is not modified using tp = NetClock::time_point; tp def; @@ -94,67 +95,11 @@ class LedgerTiming_test : public beast::unit_test::suite } - void testShouldCloseLedger() - { - // Bizarre times forcibly close - BEAST_EXPECT(shouldCloseLedger(true, 10, 10, 10, -10s, 10s, 1s, 1s, j)); - BEAST_EXPECT(shouldCloseLedger(true, 10, 10, 10, 100h, 10s, 1s, 1s, j)); - BEAST_EXPECT(shouldCloseLedger(true, 10, 10, 10, 10s, 100h, 1s, 1s, j)); - - // Rest of network has closed - BEAST_EXPECT(shouldCloseLedger(true, 10, 3, 5, 10s, 10s, 10s, 10s, j)); - - // No transactions means wait until end of internval - BEAST_EXPECT(!shouldCloseLedger(false, 10, 0, 0, 1s, 1s, 1s, 10s, j)); - BEAST_EXPECT(shouldCloseLedger(false, 10, 0, 0, 1s, 10s, 1s, 10s, j)); - - // Enforce minimum ledger open time - BEAST_EXPECT(!shouldCloseLedger(true, 10, 0, 0, 10s, 10s, 1s, 10s, j)); - - // Don't go too much faster than last time - BEAST_EXPECT(!shouldCloseLedger(true, 10, 0, 0, 10s, 10s, 3s, 10s, j)); - - BEAST_EXPECT(shouldCloseLedger(true, 10, 0, 0, 10s, 10s, 10s, 10s, j)); - - } - - void testCheckConsensus() - { - // Not enough time has elapsed - BEAST_EXPECT( ConsensusState::No - == checkConsensus(10, 2, 2, 0, 3s, 2s, true, j)); - - // If not enough peers have propsed, ensure - // more time for proposals - BEAST_EXPECT( ConsensusState::No - == checkConsensus(10, 2, 2, 0, 3s, 4s, true, j)); - - // Enough time has elapsed and we all agree - BEAST_EXPECT( ConsensusState::Yes - == checkConsensus(10, 2, 2, 0, 3s, 10s, true, j)); - - // Enough time has elapsed and we don't yet agree - BEAST_EXPECT( ConsensusState::No - == checkConsensus(10, 2, 1, 0, 3s, 10s, true, j)); - - // Our peers have moved on - // Enough time has elapsed and we all agree - BEAST_EXPECT( ConsensusState::MovedOn - == checkConsensus(10, 2, 1, 8, 3s, 10s, true, j)); - - // No peers makes it easy to agree - BEAST_EXPECT( ConsensusState::Yes - == checkConsensus(0, 0, 0, 0, 3s, 10s, true, j)); - - } - void run() override { testGetNextLedgerTimeResolution(); testRoundCloseTime(); - testShouldCloseLedger(); - testCheckConsensus(); } }; diff --git a/src/test/csf/Ledger.h b/src/test/csf/Ledger.h index 1787ce33c4..addc064273 100644 --- a/src/test/csf/Ledger.h +++ b/src/test/csf/Ledger.h @@ -20,6 +20,7 @@ #define RIPPLE_TEST_CSF_LEDGER_H_INCLUDED #include +#include #include namespace ripple { diff --git a/src/test/csf/Peer.h b/src/test/csf/Peer.h index f713f487b1..86220d633e 100644 --- a/src/test/csf/Peer.h +++ b/src/test/csf/Peer.h @@ -173,8 +173,8 @@ struct Peer : public Consensus bool proposing_ = true; //! All peers start from the default constructed ledger - Peer(PeerID i, BasicNetwork& n, UNL const& u) - : Consensus(n.clock(), beast::Journal{}) + Peer(PeerID i, BasicNetwork& n, UNL const& u, ConsensusParms p) + : Consensus(n.clock(), p, beast::Journal{}) , id{i} , net{n} , unl(u) @@ -395,12 +395,12 @@ struct Peer : public Consensus Base::timerEntry(now()); // only reschedule if not completed if (completedLedgers < targetLedgers) - net.timer(LEDGER_GRANULARITY, [&]() { timerEntry(); }); + net.timer(parms().ledgerGRANULARITY, [&]() { timerEntry(); }); } void start() { - net.timer(LEDGER_GRANULARITY, [&]() { timerEntry(); }); + net.timer(parms().ledgerGRANULARITY, [&]() { timerEntry(); }); // The ID is the one we have seen the most validations for // In practice, we might not actually have that ledger itself yet, // so there is no gaurantee that bestLCL == lastClosedLedger.id() @@ -415,8 +415,9 @@ struct Peer : public Consensus // We don't care about the actual epochs, but do want the // generated NetClock time to be well past its epoch to ensure // any subtractions of two NetClock::time_point in the consensu - // code are positive. (e.g. PROPOSE_FRESHNESS) + // code are positive. (e.g. proposeFRESHNESS) using namespace std::chrono; + using namespace std::chrono_literals; return NetClock::time_point(duration_cast( net.now().time_since_epoch() + 86400s + clockSkew)); } @@ -427,6 +428,8 @@ struct Peer : public Consensus void schedule(std::chrono::nanoseconds when, T&& what) { + using namespace std::chrono_literals; + if (when == 0ns) what(); else diff --git a/src/test/csf/Sim.h b/src/test/csf/Sim.h index 4515deddf7..c88ef5e7c0 100644 --- a/src/test/csf/Sim.h +++ b/src/test/csf/Sim.h @@ -47,14 +47,15 @@ public: @param g The trust graph between peers. @param top The network topology between peers. + @param parms Consensus parameters to use in the simulation */ template - Sim(TrustGraph const& g, Topology const& top) + Sim(ConsensusParms parms, TrustGraph const& g, Topology const& top) { peers.reserve(g.numPeers()); for (int i = 0; i < g.numPeers(); ++i) - peers.emplace_back(i, net, g.unl(i)); + peers.emplace_back(i, net, g.unl(i), parms); for (int i = 0; i < peers.size(); ++i) {