From 730cd5d513cae19206cadc3c1ea826ec200c851e Mon Sep 17 00:00:00 2001 From: Nik Bougalis Date: Fri, 15 May 2015 02:19:29 -0700 Subject: [PATCH] Cleanup consensus helper functions: * Reduce public class interfaces * Use free functions when possible * Add self-documenting function return values * Simplify ledger close resolution calculations --- src/ripple/app/consensus/LedgerConsensus.cpp | 63 +++--- src/ripple/app/consensus/LedgerConsensus.h | 16 -- src/ripple/app/ledger/Ledger.cpp | 10 +- src/ripple/app/ledger/LedgerTiming.cpp | 207 ++++++++++--------- src/ripple/app/ledger/LedgerTiming.h | 134 ++++++++---- src/ripple/app/tests/common_ledger.cpp | 2 +- src/ripple/shamap/impl/SHAMapDelta.cpp | 14 +- 7 files changed, 241 insertions(+), 205 deletions(-) diff --git a/src/ripple/app/consensus/LedgerConsensus.cpp b/src/ripple/app/consensus/LedgerConsensus.cpp index de2634bf92..25b0e6b2a8 100644 --- a/src/ripple/app/consensus/LedgerConsensus.cpp +++ b/src/ripple/app/consensus/LedgerConsensus.cpp @@ -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 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 () diff --git a/src/ripple/app/consensus/LedgerConsensus.h b/src/ripple/app/consensus/LedgerConsensus.h index 22b8e505d4..7d26e5f0bd 100644 --- a/src/ripple/app/consensus/LedgerConsensus.h +++ b/src/ripple/app/consensus/LedgerConsensus.h @@ -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 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; }; diff --git a/src/ripple/app/ledger/Ledger.cpp b/src/ripple/app/ledger/Ledger.cpp index b18ee92a3a..6b5ddf4820 100644 --- a/src/ripple/app/ledger/Ledger.cpp +++ b/src/ripple/app/ledger/Ledger.cpp @@ -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), diff --git a/src/ripple/app/ledger/LedgerTiming.cpp b/src/ripple/app/ledger/LedgerTiming.cpp index 48190f3528..d7cff8459b 100644 --- a/src/ripple/app/ledger/LedgerTiming.cpp +++ b/src/ripple/app/ledger/LedgerTiming.cpp @@ -20,31 +20,26 @@ #include #include #include +#include +#include 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; diff --git a/src/ripple/app/ledger/LedgerTiming.h b/src/ripple/app/ledger/LedgerTiming.h index a57307b908..e16ddee3a1 100644 --- a/src/ripple/app/ledger/LedgerTiming.h +++ b/src/ripple/app/ledger/LedgerTiming.h @@ -20,42 +20,115 @@ #ifndef RIPPLE_APP_LEDGER_LEDGERTIMING_H_INCLUDED #define RIPPLE_APP_LEDGER_LEDGERTIMING_H_INCLUDED +#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 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 diff --git a/src/ripple/app/tests/common_ledger.cpp b/src/ripple/app/tests/common_ledger.cpp index 2a98c7b3a6..866c0c96b3 100644 --- a/src/ripple/app/tests/common_ledger.cpp +++ b/src/ripple/app/tests/common_ledger.cpp @@ -449,7 +449,7 @@ close_and_advance(Ledger::pointer& ledger, Ledger::pointer& LCL) std::uint32_t closeTime = time_point_cast // 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); diff --git a/src/ripple/shamap/impl/SHAMapDelta.cpp b/src/ripple/shamap/impl/SHAMapDelta.cpp index 1038cc3902..7d6e62429e 100644 --- a/src/ripple/shamap/impl/SHAMapDelta.cpp +++ b/src/ripple/shamap/impl/SHAMapDelta.cpp @@ -19,7 +19,7 @@ #include #include - + namespace ripple { // This code is used to compare another node's transaction tree @@ -123,12 +123,12 @@ SHAMap::compare (std::shared_ptr const& otherMap, assert (isValid () && otherMap && otherMap->isValid ()); - using StackEntry = std::pair ; - std::stack > nodeStack; // track nodes we've pushed - if (getHash () == otherMap->getHash ()) return true; + using StackEntry = std::pair ; + std::stack > 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 const& otherMap, void SHAMap::walkMap (std::vector& missingNodes, int maxMissing) const { - std::stack , - std::vector >> nodeStack; - if (!root_->isInner ()) // root_ is only node, and we have it return; + using StackEntry = std::shared_ptr; + std::stack > nodeStack; + nodeStack.push (root_); while (!nodeStack.empty ())