diff --git a/src/ripple/app/ledger/LedgerMaster.h b/src/ripple/app/ledger/LedgerMaster.h index 39eb37925f..5359388e4c 100644 --- a/src/ripple/app/ledger/LedgerMaster.h +++ b/src/ripple/app/ledger/LedgerMaster.h @@ -64,6 +64,9 @@ public: virtual LedgerIndex getCurrentLedgerIndex () = 0; virtual LedgerIndex getValidLedgerIndex () = 0; + virtual bool isCompatible (Ledger::pointer, + beast::Journal::Stream, const char* reason) = 0; + virtual LockType& peekMutex () = 0; // The current ledger is the ledger we believe new transactions should go in diff --git a/src/ripple/app/ledger/impl/LedgerConsensusImp.cpp b/src/ripple/app/ledger/impl/LedgerConsensusImp.cpp index b215def9b3..7967a35121 100644 --- a/src/ripple/app/ledger/impl/LedgerConsensusImp.cpp +++ b/src/ripple/app/ledger/impl/LedgerConsensusImp.cpp @@ -1038,6 +1038,14 @@ void LedgerConsensusImp::accept (std::shared_ptr set) // Tell directly connected peers that we have a new LCL statusChange (protocol::neACCEPTED_LEDGER, *newLCL); + if (mValidating && + ! ledgerMaster_.isCompatible (newLCL, + deprecatedLogs().journal("LedgerConsensus").warning, + "Not validating")) + { + mValidating = false; + } + if (mValidating && !mConsensusFail) { // Build validation diff --git a/src/ripple/app/ledger/impl/LedgerMaster.cpp b/src/ripple/app/ledger/impl/LedgerMaster.cpp index 97c1fb11fa..7866d9fcbf 100644 --- a/src/ripple/app/ledger/impl/LedgerMaster.cpp +++ b/src/ripple/app/ledger/impl/LedgerMaster.cpp @@ -76,6 +76,9 @@ public: Ledger::pointer mPathLedger; // The last ledger we did pathfinding against Ledger::pointer mHistLedger; // The last ledger we handled fetching history + // Fully validated ledger, whether or not we have the ledger resident + std::pair mLastValidLedger; + LedgerHistory mLedgerHistory; CanonicalTXSet mHeldTransactions; @@ -124,6 +127,7 @@ public: beast::insight::Collector::ptr const& collector, beast::Journal journal) : LedgerMaster (parent) , m_journal (journal) + , mLastValidLedger (std::make_pair (uint256(), 0)) , mLedgerHistory (collector) , mHeldTransactions (uint256 ()) , mLedgerCleaner (make_LedgerCleaner ( @@ -164,6 +168,31 @@ public: return mValidLedgerSeq; } + bool isCompatible (Ledger::pointer ledger, + beast::Journal::Stream s, const char* reason) + { + auto validLedger = getValidatedLedger(); + + if (validLedger && + ! areCompatible (*validLedger, *ledger, s, reason)) + { + return false; + } + + { + ScopedLockType sl (m_mutex); + + if ((mLastValidLedger.second != 0) && + ! areCompatible (mLastValidLedger.first, + mLastValidLedger.second, *ledger, s, reason)) + { + return false; + } + } + + return true; + } + int getPublishedLedgerAge () { std::uint32_t pubClose = mPubLedgerClose.load(); @@ -748,10 +777,32 @@ public: void checkAccept (uint256 const& hash, std::uint32_t seq) { + int valCount; + if (seq != 0) { // Ledger is too old - if (seq <= mValidLedgerSeq) + if (seq < mValidLedgerSeq) + return; + + valCount = + getApp().getValidations().getTrustedValidationCount (hash); + + if (valCount >= mMinValidations) + { + ScopedLockType ml (m_mutex); + if (seq > mLastValidLedger.second) + mLastValidLedger = std::make_pair (hash, seq); + + if (mMinValidations < (valCount/2 + 1)) + { + mMinValidations = (valCount/2 + 1); + WriteLog (lsINFO, LedgerMaster) + << "Raising minimum validations to " << mMinValidations; + } + } + + if (seq == mValidLedgerSeq) return; // Ledger could match the ledger we're already building @@ -766,11 +817,8 @@ public: if ((seq != 0) && (getValidLedgerIndex() == 0)) { // Set peers sane early if we can - if (getApp().getValidations().getTrustedValidationCount (hash) >= - mMinValidations) - { + if (valCount >= mMinValidations) getApp().overlay().checkSanity (seq); - } } // FIXME: We may not want to fetch a ledger with just one diff --git a/src/ripple/app/misc/NetworkOPs.cpp b/src/ripple/app/misc/NetworkOPs.cpp index edb7d3040d..58183f1923 100644 --- a/src/ripple/app/misc/NetworkOPs.cpp +++ b/src/ripple/app/misc/NetworkOPs.cpp @@ -1262,6 +1262,21 @@ bool NetworkOPsImp::checkLastClosedLedger ( if (!switchLedgers) return false; + Ledger::pointer consensus = m_ledgerMaster.getLedgerByHash (closedLedger); + + if (!consensus) + consensus = getApp().getInboundLedgers().acquire ( + closedLedger, 0, InboundLedger::fcCONSENSUS); + + if (consensus && + ! m_ledgerMaster.isCompatible (consensus, m_journal.debug, + "Not switching")) + { + // Don't switch to a ledger not on the validated chain + networkClosed = ourClosed->getHash (); + return false; + } + m_journal.warning << "We are not running on the consensus ledger"; m_journal.info << "Our LCL: " << getJson (*ourClosed); m_journal.info << "Net LCL " << closedLedger; @@ -1269,12 +1284,6 @@ bool NetworkOPsImp::checkLastClosedLedger ( if ((mMode == omTRACKING) || (mMode == omFULL)) setMode (omCONNECTED); - Ledger::pointer consensus = m_ledgerMaster.getLedgerByHash (closedLedger); - - if (!consensus) - consensus = getApp().getInboundLedgers().acquire ( - closedLedger, 0, InboundLedger::fcCONSENSUS); - if (consensus) { clearNeedNetworkLedger (); diff --git a/src/ripple/ledger/View.h b/src/ripple/ledger/View.h index e6f76906b9..aa8307dadc 100644 --- a/src/ripple/ledger/View.h +++ b/src/ripple/ledger/View.h @@ -169,6 +169,17 @@ getCandidateLedger (LedgerIndex requested) return (requested + 255) & (~255); } +/** Return false if the test ledger is provably incompatible + with the valid ledger, that is, they could not possibly + both be valid. Use the first form if you have both ledgers, + use the second form if you have not acquired the valid ledger yet +*/ +bool areCompatible (ReadView const& validLedger, ReadView const& testLedger, + beast::Journal::Stream& s, const char* reason); + +bool areCompatible (uint256 const& validHash, LedgerIndex validIndex, + ReadView const& testLedger, beast::Journal::Stream& s, const char* reason); + //------------------------------------------------------------------------------ // // Modifiers diff --git a/src/ripple/ledger/impl/View.cpp b/src/ripple/ledger/impl/View.cpp index a664d1049e..8c9c527b87 100644 --- a/src/ripple/ledger/impl/View.cpp +++ b/src/ripple/ledger/impl/View.cpp @@ -307,6 +307,99 @@ rippleTransferRate (ReadView const& view, : rippleTransferRate(view, issuer); } +bool +areCompatible (ReadView const& validLedger, ReadView const& testLedger, + beast::Journal::Stream& s, const char* reason) +{ + bool ret = true; + + if (validLedger.info().seq < testLedger.info().seq) + { + // valid -> ... -> test + auto hash = hashOfSeq (testLedger, validLedger.info().seq, + beast::Journal()); + if (hash && (*hash != validLedger.info().hash)) + { + JLOG(s) << reason << " incompatible with valid ledger"; + + JLOG(s) << "Hash(VSeq): " << to_string (*hash); + + ret = false; + } + } + else if (validLedger.info().seq > testLedger.info().seq) + { + // test -> ... -> valid + auto hash = hashOfSeq (validLedger, testLedger.info().seq, + beast::Journal()); + if (hash && (*hash != testLedger.info().hash)) + { + JLOG(s) << reason << " incompatible preceding ledger"; + + JLOG(s) << "Hash(NSeq): " << to_string (*hash); + + ret = false; + } + } + else if ((validLedger.info().seq == testLedger.info().seq) && + (validLedger.info().hash != testLedger.info().hash)) + { + // Same sequence number, different hash + JLOG(s) << reason << " incompatible ledger"; + + ret = false; + } + + if (! ret) + { + JLOG(s) << "Val: " << validLedger.info().seq << + " " << to_string (validLedger.info().hash); + + JLOG(s) << "New: " << testLedger.info().seq << + " " << to_string (testLedger.info().hash); + } + + return ret; +} + +bool areCompatible (uint256 const& validHash, LedgerIndex validIndex, + ReadView const& testLedger, beast::Journal::Stream& s, const char* reason) +{ + bool ret = true; + + if (testLedger.info().seq > validIndex) + { + // Ledger we are testing follows last valid ledger + auto hash = hashOfSeq (testLedger, validIndex, + beast::Journal()); + if (hash && (*hash != validHash)) + { + JLOG(s) << reason << " incompatible following ledger"; + JLOG(s) << "Hash(VSeq): " << to_string (*hash); + + ret = false; + } + } + else if ((validIndex == testLedger.info().seq) && + (testLedger.info().hash != validHash)) + { + JLOG(s) << reason << " incompatible ledger"; + + ret = false; + } + + if (! ret) + { + JLOG(s) << "Val: " << validIndex << + " " << to_string (validHash); + + JLOG(s) << "New: " << testLedger.info().seq << + " " << to_string (testLedger.info().hash); + } + + return ret; +} + bool dirIsEmpty (ReadView const& view, Keylet const& k)