mirror of
https://github.com/XRPLF/rippled.git
synced 2025-11-24 04:55:52 +00:00
Be paranoid about ledger compatibility:
* Consider ledgers incompatible based on last valid ledger * Test against even ledgers not acquired yet * Don't validate an incompatible ledger * Don't switch to an incompatible ledger * Protect against an unreasonably small quorum
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -1038,6 +1038,14 @@ void LedgerConsensusImp::accept (std::shared_ptr<SHAMap> 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
|
||||
|
||||
@@ -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 <uint256, LedgerIndex> 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,12 +817,9 @@ 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
|
||||
// trusted validation
|
||||
|
||||
@@ -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 ();
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user