Expose consensus parameters for simulation (RIPD-1355)

This commit is contained in:
Brad Chase
2017-03-31 14:30:27 -04:00
committed by seelabs
parent 7ae3c91015
commit 3dfb4a13f1
16 changed files with 441 additions and 282 deletions

View File

@@ -1859,16 +1859,18 @@
</ClInclude>
<ClInclude Include="..\..\src\ripple\conditions\impl\utils.h">
</ClInclude>
<ClCompile Include="..\..\src\ripple\consensus\Consensus.cpp">
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='debug|x64'">True</ExcludedFromBuild>
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='release|x64'">True</ExcludedFromBuild>
</ClCompile>
<ClInclude Include="..\..\src\ripple\consensus\Consensus.h">
</ClInclude>
<ClInclude Include="..\..\src\ripple\consensus\ConsensusParms.h">
</ClInclude>
<ClInclude Include="..\..\src\ripple\consensus\ConsensusProposal.h">
</ClInclude>
<ClInclude Include="..\..\src\ripple\consensus\DisputedTx.h">
</ClInclude>
<ClCompile Include="..\..\src\ripple\consensus\LedgerTiming.cpp">
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='debug|x64'">True</ExcludedFromBuild>
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='release|x64'">True</ExcludedFromBuild>
</ClCompile>
<ClInclude Include="..\..\src\ripple\consensus\LedgerTiming.h">
</ClInclude>
<ClInclude Include="..\..\src\ripple\consensus\Validations.h">

View File

@@ -2502,18 +2502,21 @@
<ClInclude Include="..\..\src\ripple\conditions\impl\utils.h">
<Filter>ripple\conditions\impl</Filter>
</ClInclude>
<ClCompile Include="..\..\src\ripple\consensus\Consensus.cpp">
<Filter>ripple\consensus</Filter>
</ClCompile>
<ClInclude Include="..\..\src\ripple\consensus\Consensus.h">
<Filter>ripple\consensus</Filter>
</ClInclude>
<ClInclude Include="..\..\src\ripple\consensus\ConsensusParms.h">
<Filter>ripple\consensus</Filter>
</ClInclude>
<ClInclude Include="..\..\src\ripple\consensus\ConsensusProposal.h">
<Filter>ripple\consensus</Filter>
</ClInclude>
<ClInclude Include="..\..\src\ripple\consensus\DisputedTx.h">
<Filter>ripple\consensus</Filter>
</ClInclude>
<ClCompile Include="..\..\src\ripple\consensus\LedgerTiming.cpp">
<Filter>ripple\consensus</Filter>
</ClCompile>
<ClInclude Include="..\..\src\ripple\consensus\LedgerTiming.h">
<Filter>ripple\consensus</Filter>
</ClInclude>

View File

@@ -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 \

View File

@@ -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)

View File

@@ -25,7 +25,7 @@
#include <ripple/app/ledger/AcceptedLedger.h>
#include <ripple/app/ledger/InboundLedgers.h>
#include <ripple/app/ledger/LedgerMaster.h>
#include <ripple/consensus/LedgerTiming.h>
#include <ripple/consensus/ConsensusParms.h>
#include <ripple/app/ledger/LedgerToJson.h>
#include <ripple/app/ledger/LocalTxs.h>
#include <ripple/app/ledger/OpenLedger.h>
@@ -639,7 +639,7 @@ void NetworkOPsImp::setStateTimer ()
void NetworkOPsImp::setHeartbeatTimer ()
{
m_heartbeatTimer.setExpiration (LEDGER_GRANULARITY);
m_heartbeatTimer.setExpiration (mConsensus->parms().ledgerGRANULARITY);
}
void NetworkOPsImp::setClusterTimer ()

View File

@@ -19,9 +19,7 @@
#include <BeastConfig.h>
#include <ripple/basics/Log.h>
#include <ripple/consensus/LedgerTiming.h>
#include <algorithm>
#include <iterator>
#include <ripple/consensus/Consensus.h>
namespace ripple {
@@ -35,11 +33,13 @@ shouldCloseLedger(
std::chrono::milliseconds
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

View File

@@ -24,11 +24,79 @@
#include <ripple/basics/chrono.h>
#include <ripple/beast/utility/Journal.h>
#include <ripple/consensus/ConsensusProposal.h>
#include <ripple/consensus/ConsensusParms.h>
#include <ripple/consensus/LedgerTiming.h>
#include <ripple/consensus/DisputedTx.h>
#include <ripple/json/json_writer.h>
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<NodeID_t> deadNodes_;
// Parameters that control consensus algorithm
ConsensusParms const parms_;
// Journal for debugging
beast::Journal j_;
};
@@ -625,9 +704,12 @@ private:
template <class Derived, class Traits>
Consensus<Derived, Traits>::Consensus(
clock_type const& clock,
ConsensusParms const & p,
beast::Journal journal)
: lock_(std::make_unique<std::recursive_mutex>())
, clock_(clock)
: lock_{std::make_unique<std::recursive_mutex>()}
, clock_{clock}
, prevRoundTime_{p.ledgerIDLE_INTERVAL}
, parms_(p)
, j_{journal}
{
JLOG(j_.debug()) << "Creating consensus object";
@@ -1112,9 +1194,9 @@ Consensus<Derived, Traits>::phaseOpen()
sinceClose = -duration_cast<milliseconds>(lastCloseTime - now_);
}
auto const idleInterval = std::max<seconds>(
LEDGER_IDLE_INTERVAL,
duration_cast<seconds>(2 * previousLedger_.closeTimeResolution()));
auto const idleInterval = std::max<milliseconds>(
parms_.ledgerIDLE_INTERVAL,
2 * previousLedger_.closeTimeResolution());
// Decide if we should close the ledger
if (shouldCloseLedger(
@@ -1126,6 +1208,7 @@ Consensus<Derived, Traits>::phaseOpen()
sinceClose,
openTime_.read(),
idleInterval,
parms_,
j_))
{
closeLedger();
@@ -1143,10 +1226,10 @@ Consensus<Derived, Traits>::phaseEstablish()
result_->roundTime.tick(clock_.now());
convergePercent_ = result_->roundTime.read() * 100 /
std::max<milliseconds>(prevRoundTime_, AV_MIN_CONSENSUS_TIME);
std::max<milliseconds>(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<Derived, Traits>::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<NetClock::time_point, int> effCloseTimes;
@@ -1271,7 +1354,7 @@ Consensus<Derived, Traits>::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<Derived, Traits>::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<Derived, Traits>::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<Derived, Traits>::haveConsensus()
currentFinished,
prevRoundTime_,
result_->roundTime.read(),
parms_,
mode_ == Mode::proposing,
j_);

View File

@@ -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 <chrono>
#include <cstddef>
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

View File

@@ -23,7 +23,7 @@
#include <ripple/basics/Log.h>
#include <ripple/basics/base_uint.h>
#include <ripple/beast/utility/Journal.h>
#include <ripple/consensus/LedgerTiming.h>
#include <ripple/consensus/ConsensusParms.h>
#include <ripple/protocol/Serializer.h>
#include <ripple/protocol/UintTypes.h>
#include <memory>
@@ -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<Tx_t, NodeID_t>::unVote(NodeID_t const& peer)
template <class Tx_t, class NodeID_t>
bool
DisputedTx<Tx_t, NodeID_t>::updateVote(int percentTime, bool proposing)
DisputedTx<Tx_t, NodeID_t>::updateVote(
int percentTime,
bool proposing,
ConsensusParms const& p)
{
if (ourVote_ && (nays_ == 0))
return false;
@@ -212,14 +216,14 @@ DisputedTx<Tx_t, NodeID_t>::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
{

View File

@@ -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

View File

@@ -18,4 +18,4 @@
//==============================================================================
#include <BeastConfig.h>
#include <ripple/consensus/LedgerTiming.cpp>
#include <ripple/consensus/Consensus.cpp>

View File

@@ -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<milliseconds>(0.2 * LEDGER_GRANULARITY)}));
topology(
tg,
fixed{round<milliseconds>(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<milliseconds>(
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<milliseconds>(0.2 * LEDGER_GRANULARITY)}));
topology(
tg,
fixed{round<milliseconds>(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<milliseconds>(0.2 * LEDGER_GRANULARITY);
Sim sim(tg, topology(tg, fixed{netDelay}));
auto netDelay = round<milliseconds>(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<UNL> unls;
unls.push_back({0, 1});
@@ -337,14 +430,14 @@ public:
TrustGraph tg{unls, membership};
Sim sim(tg, topology(tg, fixed{round<milliseconds>(
0.2 * LEDGER_GRANULARITY)}));
Sim sim(parms, tg, topology(tg, fixed{round<milliseconds>(
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<milliseconds>(0.2 * LEDGER_GRANULARITY)}));
tg,
fixed{
round<milliseconds>(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<milliseconds>(0.2 * LEDGER_GRANULARITY)})};
topology(
tg,
fixed{round<milliseconds>(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();

View File

@@ -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();
}
};

View File

@@ -20,6 +20,7 @@
#define RIPPLE_TEST_CSF_LEDGER_H_INCLUDED
#include <ripple/basics/chrono.h>
#include <ripple/consensus/LedgerTiming.h>
#include <test/csf/Tx.h>
namespace ripple {

View File

@@ -173,8 +173,8 @@ struct Peer : public Consensus<Peer, Traits>
bool proposing_ = true;
//! All peers start from the default constructed ledger
Peer(PeerID i, BasicNetwork<Peer*>& n, UNL const& u)
: Consensus<Peer, Traits>(n.clock(), beast::Journal{})
Peer(PeerID i, BasicNetwork<Peer*>& n, UNL const& u, ConsensusParms p)
: Consensus<Peer, Traits>(n.clock(), p, beast::Journal{})
, id{i}
, net{n}
, unl(u)
@@ -395,12 +395,12 @@ struct Peer : public Consensus<Peer, Traits>
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<Peer, Traits>
// 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<NetClock::duration>(
net.now().time_since_epoch() + 86400s + clockSkew));
}
@@ -427,6 +428,8 @@ struct Peer : public Consensus<Peer, Traits>
void
schedule(std::chrono::nanoseconds when, T&& what)
{
using namespace std::chrono_literals;
if (when == 0ns)
what();
else

View File

@@ -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 <class Topology>
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)
{