Cleanup consensus helper functions:

* Reduce public class interfaces
* Use free functions when possible
* Add self-documenting function return values
* Simplify ledger close resolution calculations
This commit is contained in:
Nik Bougalis
2015-05-15 02:19:29 -07:00
committed by Vinnie Falco
parent e838b30def
commit 730cd5d513
7 changed files with 241 additions and 205 deletions

View File

@@ -115,7 +115,7 @@ public:
getApp().getInboundTransactions().newRound (mPreviousLedger->getLedgerSeq());
// Adapt close time resolution to recent network conditions
mCloseResolution = ContinuousLedgerTiming::getNextLedgerTimeResolution (
mCloseResolution = getNextLedgerTimeResolution (
mPreviousLedger->getCloseResolution (),
mPreviousLedger->getCloseAgree (),
previousLedger->getLedgerSeq () + 1);
@@ -292,11 +292,6 @@ public:
return ret;
}
Ledger::ref peekPreviousLedger ()
{
return mPreviousLedger;
}
uint256 getLCL ()
{
return mPrevLedgerHash;
@@ -564,9 +559,10 @@ public:
WriteLog (lsINFO, LedgerConsensus) << "Have the consensus ledger " << mPrevLedgerHash;
mHaveCorrectLCL = true;
mCloseResolution = ContinuousLedgerTiming::getNextLedgerTimeResolution (
mPreviousLedger->getCloseResolution (), mPreviousLedger->getCloseAgree (),
mPreviousLedger->getLedgerSeq () + 1);
mCloseResolution = getNextLedgerTimeResolution (
mPreviousLedger->getCloseResolution (),
mPreviousLedger->getCloseAgree (),
mPreviousLedger->getLedgerSeq () + 1);
}
@@ -666,7 +662,7 @@ public:
idleInterval = std::max (idleInterval, 2 * mPreviousLedger->getCloseResolution ());
// Decide if we should close the ledger
if (ContinuousLedgerTiming::shouldClose (anyTransactions
if (shouldCloseLedger (anyTransactions
, mPreviousProposers, proposersClosed, proposersValidated
, mPreviousMSeconds, sinceClose, mCurrentMSeconds
, idleInterval))
@@ -681,26 +677,27 @@ public:
*/
void stateEstablish ()
{
// Give everyone a chance to take an initial position
if (mCurrentMSeconds < LEDGER_MIN_CONSENSUS)
return;
updateOurPositions ();
// Nothing to do if we don't have consensus.
if (!haveConsensus ())
return;
if (!mHaveCloseTimeConsensus)
{
CondLog (haveConsensus (false), lsINFO, LedgerConsensus)
<< "We have TX consensus but not CT consensus";
}
else if (haveConsensus (true))
{
WriteLog (lsINFO, LedgerConsensus)
<< "Converge cutoff (" << mPeerPositions.size ()
<< " participants)";
mState = lcsFINISHED;
beginAccept (false);
WriteLog (lsINFO, LedgerConsensus) <<
"We have TX consensus but not CT consensus";
return;
}
WriteLog (lsINFO, LedgerConsensus) <<
"Converge cutoff (" << mPeerPositions.size () << " participants)";
mState = lcsFINISHED;
beginAccept (false);
}
void stateFinished ()
@@ -717,7 +714,7 @@ public:
/** Check if we've reached consensus
*/
bool haveConsensus (bool forReal)
bool haveConsensus ()
{
// CHECKME: should possibly count unacquired TX sets as disagreeing
int agree = 0, disagree = 0;
@@ -762,9 +759,20 @@ public:
<< ", disagree=" << disagree;
// Determine if we actually have consensus or not
return ContinuousLedgerTiming::haveConsensus (mPreviousProposers,
agree + disagree, agree, currentValidations
, mPreviousMSeconds, mCurrentMSeconds, forReal, mConsensusFail);
auto ret = checkConsensus (mPreviousProposers, agree + disagree, agree,
currentValidations, mPreviousMSeconds, mCurrentMSeconds);
if (ret == ConsensusState::No)
return false;
// There is consensus, but we need to track if the network moved on
// without us.
if (ret == ConsensusState::MovedOn)
mConsensusFail = true;
else
mConsensusFail = false;
return true;
}
std::shared_ptr<SHAMap> getTransactionTree (uint256 const& hash)
@@ -855,11 +863,6 @@ public:
return true;
}
bool isOurPubKey (const RippleAddress & k)
{
return k == mValPublic;
}
/** Simulate a consensus round without any network traffic
*/
void simulate ()

View File

@@ -46,31 +46,15 @@ public:
virtual Json::Value getJson (bool full) = 0;
virtual Ledger::ref peekPreviousLedger () = 0;
virtual uint256 getLCL () = 0;
virtual void mapComplete (uint256 const& hash,
std::shared_ptr<SHAMap> const& map, bool acquired) = 0;
virtual void checkLCL () = 0;
virtual void handleLCL (uint256 const& lclHash) = 0;
virtual void timerEntry () = 0;
// state handlers
virtual void statePreClose () = 0;
virtual void stateEstablish () = 0;
virtual void stateFinished () = 0;
virtual void stateAccepted () = 0;
virtual bool haveConsensus (bool forReal) = 0;
virtual bool peerPosition (LedgerProposal::ref) = 0;
virtual bool isOurPubKey (const RippleAddress & k) = 0;
// test/debug
virtual void simulate () = 0;
};

View File

@@ -53,7 +53,7 @@ Ledger::Ledger (RippleAddress const& masterID, std::uint64_t startAmount)
, mLedgerSeq (1) // First Ledger
, mCloseTime (0)
, mParentCloseTime (0)
, mCloseResolution (LEDGER_TIME_ACCURACY)
, mCloseResolution (ledgerDefaultTimeResolution)
, mCloseFlags (0)
, mClosed (false)
, mValidated (false)
@@ -179,10 +179,8 @@ Ledger::Ledger (bool /* dummy */,
assert (mParentHash.isNonZero ());
mCloseResolution = ContinuousLedgerTiming::getNextLedgerTimeResolution (
prevLedger.mCloseResolution,
prevLedger.getCloseAgree (),
mLedgerSeq);
mCloseResolution = getNextLedgerTimeResolution (prevLedger.mCloseResolution,
prevLedger.getCloseAgree (), mLedgerSeq);
if (prevLedger.mCloseTime == 0)
{
@@ -230,7 +228,7 @@ Ledger::Ledger (std::uint32_t ledgerSeq, std::uint32_t closeTime)
mLedgerSeq (ledgerSeq),
mCloseTime (closeTime),
mParentCloseTime (0),
mCloseResolution (LEDGER_TIME_ACCURACY),
mCloseResolution (ledgerDefaultTimeResolution),
mCloseFlags (0),
mClosed (false),
mValidated (false),

View File

@@ -20,31 +20,26 @@
#include <BeastConfig.h>
#include <ripple/app/ledger/LedgerTiming.h>
#include <ripple/basics/Log.h>
#include <algorithm>
#include <iterator>
namespace ripple {
// VFALCO Should rename ContinuousLedgerTiming to LedgerTiming
// NOTE: First and last times must be repeated
int ContinuousLedgerTiming::LedgerTimeResolution[] = { 10, 10, 20, 30, 60, 90, 120, 120 };
// Called when a ledger is open and no close is in progress -- when a transaction is received and no close
// is in process, or when a close completes. Returns the number of seconds the ledger should be be open.
bool ContinuousLedgerTiming::shouldClose (
bool shouldCloseLedger (
bool anyTransactions,
int previousProposers, // proposers in the last closing
int proposersClosed, // proposers who have currently closed this ledgers
int proposersValidated, // proposers who have validated the last closed ledger
int previousMSeconds, // milliseconds the previous ledger took to reach consensus
int currentMSeconds, // milliseconds since the previous ledger closed
int openMSeconds, // milliseconds since the previous LCL was computed
int idleInterval) // network's desired idle interval
int previousProposers,
int proposersClosed,
int proposersValidated,
int previousMSeconds,
int currentMSeconds,
int openMSeconds,
int idleInterval)
{
if ((previousMSeconds < -1000) || (previousMSeconds > 600000) ||
(currentMSeconds < -1000) || (currentMSeconds > 600000))
{
WriteLog (lsWARNING, LedgerTiming) <<
"CLC::shouldClose range Trans=" << (anyTransactions ? "yes" : "no") <<
"shouldCloseLedger Trans=" << (anyTransactions ? "yes" : "no") <<
" Prop: " << previousProposers << "/" << proposersClosed <<
" Secs: " << currentMSeconds << " (last: " << previousMSeconds << ")";
return true;
@@ -52,8 +47,8 @@ bool ContinuousLedgerTiming::shouldClose (
if (!anyTransactions)
{
// no transactions so far this interval
if (proposersClosed > (previousProposers / 4)) // did we miss a transaction?
// did we miss a transaction?
if (proposersClosed > (previousProposers / 4))
{
WriteLog (lsTRACE, LedgerTiming) <<
"no transactions, many proposers: now (" << proposersClosed <<
@@ -61,118 +56,124 @@ bool ContinuousLedgerTiming::shouldClose (
return true;
}
#if 0 // This false triggers on the genesis ledger
if (previousMSeconds > (1000 * (LEDGER_IDLE_INTERVAL + 2))) // the last ledger was very slow to close
{
WriteLog (lsTRACE, LedgerTiming) << "was slow to converge (p=" << (previousMSeconds) << ")";
if (previousMSeconds < 2000)
return previousMSeconds;
return previousMSeconds - 1000;
}
#endif
// Only close if we have idled for too long.
return currentMSeconds >= (idleInterval * 1000); // normal idle
}
if ((openMSeconds < LEDGER_MIN_CLOSE) && ((proposersClosed + proposersValidated) < (previousProposers / 2 )))
// If we have any transactions, we don't want to close too frequently:
if (openMSeconds < LEDGER_MIN_CLOSE)
{
WriteLog (lsDEBUG, LedgerTiming) <<
"Must wait minimum time before closing";
return false;
}
if ((currentMSeconds < previousMSeconds) && ((proposersClosed + proposersValidated) < previousProposers))
{
WriteLog (lsDEBUG, LedgerTiming) <<
"We are waiting for more closes/validations";
return false;
}
return true; // this ledger should close now
}
// Returns whether we have a consensus or not. If so, we expect all honest nodes
// to already have everything they need to accept a consensus. Our vote is 'locked in'.
bool ContinuousLedgerTiming::haveConsensus (
int previousProposers, // proposers in the last closing (not including us)
int currentProposers, // proposers in this closing so far (not including us)
int currentAgree, // proposers who agree with us
int currentFinished, // proposers who have validated a ledger after this one
int previousAgreeTime, // how long it took to agree on the last ledger
int currentAgreeTime, // how long we've been trying to agree
bool forReal, // deciding whether to stop consensus process
bool& failed) // we can't reach a consensus
{
WriteLog (lsTRACE, LedgerTiming) <<
"CLC::haveConsensus: prop=" << currentProposers <<
"/" << previousProposers <<
" agree=" << currentAgree << " validated=" << currentFinished <<
" time=" << currentAgreeTime << "/" << previousAgreeTime <<
(forReal ? "" : "X");
if (currentAgreeTime <= LEDGER_MIN_CONSENSUS)
return false;
if (currentProposers < (previousProposers * 3 / 4))
{
// Less than 3/4 of the last ledger's proposers are present, we may need more time
if (currentAgreeTime < (previousAgreeTime + LEDGER_MIN_CONSENSUS))
if ((proposersClosed + proposersValidated) < (previousProposers / 2 ))
{
CondLog (forReal, lsTRACE, LedgerTiming) <<
"too fast, not enough proposers";
WriteLog (lsDEBUG, LedgerTiming) <<
"Must wait minimum time before closing";
return false;
}
}
// If 80% of current proposers (plus us) agree on a set, we have consensus
if (((currentAgree * 100 + 100) / (currentProposers + 1)) > 80)
if (currentMSeconds < previousMSeconds)
{
CondLog (forReal, lsDEBUG, LedgerTiming) << "normal consensus";
failed = false;
return true;
if ((proposersClosed + proposersValidated) < previousProposers)
{
WriteLog (lsDEBUG, LedgerTiming) <<
"We are waiting for more closes/validations";
return false;
}
}
// If 80% of the nodes on your UNL have moved on, you should declare consensus
if (((currentFinished * 100) / (currentProposers + 1)) > 80)
return true;
}
bool
checkConsensusReached (int agreeing, int proposing)
{
int currentPercentage = (agreeing * 100) / (proposing + 1);
return currentPercentage > minimumConsensusPercentage;
}
ConsensusState checkConsensus (
int previousProposers,
int currentProposers,
int currentAgree,
int currentFinished,
int previousAgreeTime,
int currentAgreeTime)
{
WriteLog (lsTRACE, LedgerTiming) <<
"checkConsensus: prop=" << currentProposers <<
"/" << previousProposers <<
" agree=" << currentAgree << " validated=" << currentFinished <<
" time=" << currentAgreeTime << "/" << previousAgreeTime;
if (currentAgreeTime <= LEDGER_MIN_CONSENSUS)
return ConsensusState::No;
if (currentProposers < (previousProposers * 3 / 4))
{
CondLog (forReal, lsWARNING, LedgerTiming) <<
// 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))
{
WriteLog (lsTRACE, LedgerTiming) <<
"too fast, not enough proposers";
return ConsensusState::No;
}
}
// Have we, together with the nodes on our UNL list, reached the treshold
// to declare consensus?
if (checkConsensusReached (currentAgree + 1, currentProposers))
{
WriteLog (lsDEBUG, LedgerTiming) << "normal consensus";
return ConsensusState::Yes;
}
// Have sufficient nodes on our UNL list moved on and reached the threshold
// to declare consensus?
if (checkConsensusReached (currentFinished, currentProposers))
{
WriteLog (lsWARNING, LedgerTiming) <<
"We see no consensus, but 80% of nodes have moved on";
failed = true;
return true;
return ConsensusState::MovedOn;
}
// no consensus yet
CondLog (forReal, lsTRACE, LedgerTiming) << "no consensus";
return false;
WriteLog (lsTRACE, LedgerTiming) << "no consensus";
return ConsensusState::No;
}
int ContinuousLedgerTiming::getNextLedgerTimeResolution (int previousResolution, bool previousAgree, int ledgerSeq)
int getNextLedgerTimeResolution (
int previousResolution,
bool previousAgree,
std::uint32_t ledgerSeq)
{
assert (ledgerSeq);
if ((!previousAgree) && ((ledgerSeq % LEDGER_RES_DECREASE) == 0))
// Find the current resolution:
auto iter = std::find (std::begin (ledgerPossibleTimeResolutions),
std::end (ledgerPossibleTimeResolutions), previousResolution);
assert (iter != std::end (ledgerPossibleTimeResolutions));
// This should never happen, but just as a precaution
if (iter == std::end (ledgerPossibleTimeResolutions))
return previousResolution;
// If we did not previously agree, we try to decrease the resolution to
// improve the chance that we will agree now.
if (!previousAgree && ((ledgerSeq % decreaseLedgerTimeResolutionEvery) == 0))
{
// reduce resolution
int i = 1;
while (LedgerTimeResolution[i] != previousResolution)
++i;
return LedgerTimeResolution[i + 1];
if (++iter != std::end (ledgerPossibleTimeResolutions))
return *iter;
}
if ((previousAgree) && ((ledgerSeq % LEDGER_RES_INCREASE) == 0))
// If we previously agreed, we try to increase the resolution to determine
// if we can continue to agree.
if (previousAgree && ((ledgerSeq % increaseLedgerTimeResolutionEvery) == 0))
{
// increase resolution
int i = 1;
while (LedgerTimeResolution[i] != previousResolution)
++i;
return LedgerTimeResolution[i - 1];
if (iter-- != std::begin (ledgerPossibleTimeResolutions))
return *iter;
}
return previousResolution;

View File

@@ -20,42 +20,115 @@
#ifndef RIPPLE_APP_LEDGER_LEDGERTIMING_H_INCLUDED
#define RIPPLE_APP_LEDGER_LEDGERTIMING_H_INCLUDED
#include <cstdint>
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 previousProposers 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 previousMSeconds time, in milliseconds, for the previous ledger to
reach consensus (in milliseconds)
@param currentMSeconds time, in milliseconds since the previous ledger closed
@param openMSeconds time, in milliseconds, since the previous LCL was computed
@param idleInterval the network's desired idle interval
*/
bool shouldCloseLedger (
bool anyTransactions,
int previousProposers,
int proposersClosed,
int proposersValidated,
int previousMSeconds,
int currentMSeconds,
int openMSeconds,
int idleInterval);
/** What state the consensus process is on. */
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 previousProposers 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
*/
ConsensusState checkConsensus (
int previousProposers,
int currentProposers,
int currentAgree,
int currentClosed,
int previousAgreeTime,
int currentAgreeTime);
/** Calculates the close time resolution for the specified ledger.
The Ripple protocol uses binning to represent time intervals using only one
timestamp. This allows servers to derive a common time for the next ledger,
without the need for perfectly synchronized clocks.
The time resolution (i.e. the size of the intervals) is adjusted dynamically
based on what happened in the last ledger, to try to avoid disagreements.
*/
int getNextLedgerTimeResolution (
int previousResolution,
bool previousAgree,
std::uint32_t ledgerSeq);
//------------------------------------------------------------------------------
// 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.
int const minimumConsensusPercentage = 80;
// All possible close time resolutions. Values should not be duplicated.
int const ledgerPossibleTimeResolutions[] = { 10, 20, 30, 60, 90, 120 };
// Initial resolution of ledger close time.
int const ledgerDefaultTimeResolution = ledgerPossibleTimeResolutions[2];
// How often we increase the close time resolution
int const increaseLedgerTimeResolutionEvery = 8;
// How often we decrease the close time resolution
int const decreaseLedgerTimeResolutionEvery = 1;
// The number of seconds a ledger may remain idle before closing
const int LEDGER_IDLE_INTERVAL = 15;
// The number of seconds 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
// The number of seconds 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
const int LEDGER_VAL_INTERVAL = 300;
// The number of seconds before a close time that we consider a validation acceptable
// This protects against extreme clock errors
// The number of seconds before a close time that we consider a validation
// acceptable. This protects against extreme clock errors
const int LEDGER_EARLY_INTERVAL = 180;
// The number of milliseconds we wait minimum to ensure participation
const int LEDGER_MIN_CONSENSUS = 2000;
// The number of milliseconds we wait minimum to ensure others have computed the LCL
// Minimum number of milliseconds to wait to ensure others have computed the LCL
const int LEDGER_MIN_CLOSE = 2000;
// Initial resolution of ledger close time
const int LEDGER_TIME_ACCURACY = 30;
// How often to increase resolution
const int LEDGER_RES_INCREASE = 8;
// How often to decrease resolution
const int LEDGER_RES_DECREASE = 1;
// How often we check state or change positions (in milliseconds)
const int LEDGER_GRANULARITY = 1000;
// The percentage of active trusted validators that must be able to
// keep up with the network or we consider the network overloaded
const int LEDGER_NET_RATIO = 70;
// How long we consider a proposal fresh
const int PROPOSE_FRESHNESS = 20;
@@ -83,29 +156,6 @@ const int AV_STUCK_CONSENSUS_PCT = 95;
const int AV_CT_CONSENSUS_PCT = 75;
class ContinuousLedgerTiming
{
public:
static int LedgerTimeResolution[];
// Returns the number of seconds the ledger was or should be open
// Call when a consensus is reached and when any transaction is relayed to be added
static bool shouldClose (
bool anyTransactions,
int previousProposers, int proposersClosed, int proposerersValidated,
int previousMSeconds, int currentMSeconds, int openMSeconds,
int idleInterval);
static bool haveConsensus (
int previousProposers, int currentProposers,
int currentAgree, int currentClosed,
int previousAgreeTime, int currentAgreeTime,
bool forReal, bool& failed);
static int getNextLedgerTimeResolution (int previousResolution, bool previousAgree, int ledgerSeq);
};
} // ripple
#endif

View File

@@ -449,7 +449,7 @@ close_and_advance(Ledger::pointer& ledger, Ledger::pointer& LCL)
std::uint32_t closeTime = time_point_cast<seconds> // now
(system_clock::now() - epoch_offset).
time_since_epoch().count();
int closeResolution = seconds(LEDGER_TIME_ACCURACY).count();
int closeResolution = seconds(ledgerDefaultTimeResolution).count();
bool closeTimeCorrect = true;
newLCL->setAccepted(closeTime, closeResolution, closeTimeCorrect);

View File

@@ -19,7 +19,7 @@
#include <BeastConfig.h>
#include <ripple/shamap/SHAMap.h>
namespace ripple {
// This code is used to compare another node's transaction tree
@@ -123,12 +123,12 @@ SHAMap::compare (std::shared_ptr<SHAMap> const& otherMap,
assert (isValid () && otherMap && otherMap->isValid ());
using StackEntry = std::pair <SHAMapTreeNode*, SHAMapTreeNode*>;
std::stack <StackEntry, std::vector<StackEntry>> nodeStack; // track nodes we've pushed
if (getHash () == otherMap->getHash ())
return true;
using StackEntry = std::pair <SHAMapTreeNode*, SHAMapTreeNode*>;
std::stack <StackEntry, std::vector<StackEntry>> nodeStack; // track nodes we've pushed
nodeStack.push ({root_.get(), otherMap->root_.get()});
while (!nodeStack.empty ())
{
@@ -221,12 +221,12 @@ SHAMap::compare (std::shared_ptr<SHAMap> const& otherMap,
void SHAMap::walkMap (std::vector<SHAMapMissingNode>& missingNodes, int maxMissing) const
{
std::stack <std::shared_ptr<SHAMapTreeNode>,
std::vector <std::shared_ptr<SHAMapTreeNode>>> nodeStack;
if (!root_->isInner ()) // root_ is only node, and we have it
return;
using StackEntry = std::shared_ptr<SHAMapTreeNode>;
std::stack <StackEntry, std::vector <StackEntry>> nodeStack;
nodeStack.push (root_);
while (!nodeStack.empty ())