mirror of
https://github.com/XRPLF/rippled.git
synced 2025-11-23 20:45:51 +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 getCurrentLedgerIndex () = 0;
|
||||||
virtual LedgerIndex getValidLedgerIndex () = 0;
|
virtual LedgerIndex getValidLedgerIndex () = 0;
|
||||||
|
|
||||||
|
virtual bool isCompatible (Ledger::pointer,
|
||||||
|
beast::Journal::Stream, const char* reason) = 0;
|
||||||
|
|
||||||
virtual LockType& peekMutex () = 0;
|
virtual LockType& peekMutex () = 0;
|
||||||
|
|
||||||
// The current ledger is the ledger we believe new transactions should go in
|
// 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
|
// Tell directly connected peers that we have a new LCL
|
||||||
statusChange (protocol::neACCEPTED_LEDGER, *newLCL);
|
statusChange (protocol::neACCEPTED_LEDGER, *newLCL);
|
||||||
|
|
||||||
|
if (mValidating &&
|
||||||
|
! ledgerMaster_.isCompatible (newLCL,
|
||||||
|
deprecatedLogs().journal("LedgerConsensus").warning,
|
||||||
|
"Not validating"))
|
||||||
|
{
|
||||||
|
mValidating = false;
|
||||||
|
}
|
||||||
|
|
||||||
if (mValidating && !mConsensusFail)
|
if (mValidating && !mConsensusFail)
|
||||||
{
|
{
|
||||||
// Build validation
|
// Build validation
|
||||||
|
|||||||
@@ -76,6 +76,9 @@ public:
|
|||||||
Ledger::pointer mPathLedger; // The last ledger we did pathfinding against
|
Ledger::pointer mPathLedger; // The last ledger we did pathfinding against
|
||||||
Ledger::pointer mHistLedger; // The last ledger we handled fetching history
|
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;
|
LedgerHistory mLedgerHistory;
|
||||||
|
|
||||||
CanonicalTXSet mHeldTransactions;
|
CanonicalTXSet mHeldTransactions;
|
||||||
@@ -124,6 +127,7 @@ public:
|
|||||||
beast::insight::Collector::ptr const& collector, beast::Journal journal)
|
beast::insight::Collector::ptr const& collector, beast::Journal journal)
|
||||||
: LedgerMaster (parent)
|
: LedgerMaster (parent)
|
||||||
, m_journal (journal)
|
, m_journal (journal)
|
||||||
|
, mLastValidLedger (std::make_pair (uint256(), 0))
|
||||||
, mLedgerHistory (collector)
|
, mLedgerHistory (collector)
|
||||||
, mHeldTransactions (uint256 ())
|
, mHeldTransactions (uint256 ())
|
||||||
, mLedgerCleaner (make_LedgerCleaner (
|
, mLedgerCleaner (make_LedgerCleaner (
|
||||||
@@ -164,6 +168,31 @@ public:
|
|||||||
return mValidLedgerSeq;
|
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 ()
|
int getPublishedLedgerAge ()
|
||||||
{
|
{
|
||||||
std::uint32_t pubClose = mPubLedgerClose.load();
|
std::uint32_t pubClose = mPubLedgerClose.load();
|
||||||
@@ -748,10 +777,32 @@ public:
|
|||||||
void checkAccept (uint256 const& hash, std::uint32_t seq)
|
void checkAccept (uint256 const& hash, std::uint32_t seq)
|
||||||
{
|
{
|
||||||
|
|
||||||
|
int valCount;
|
||||||
|
|
||||||
if (seq != 0)
|
if (seq != 0)
|
||||||
{
|
{
|
||||||
// Ledger is too old
|
// 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;
|
return;
|
||||||
|
|
||||||
// Ledger could match the ledger we're already building
|
// Ledger could match the ledger we're already building
|
||||||
@@ -766,11 +817,8 @@ public:
|
|||||||
if ((seq != 0) && (getValidLedgerIndex() == 0))
|
if ((seq != 0) && (getValidLedgerIndex() == 0))
|
||||||
{
|
{
|
||||||
// Set peers sane early if we can
|
// Set peers sane early if we can
|
||||||
if (getApp().getValidations().getTrustedValidationCount (hash) >=
|
if (valCount >= mMinValidations)
|
||||||
mMinValidations)
|
|
||||||
{
|
|
||||||
getApp().overlay().checkSanity (seq);
|
getApp().overlay().checkSanity (seq);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// FIXME: We may not want to fetch a ledger with just one
|
// FIXME: We may not want to fetch a ledger with just one
|
||||||
|
|||||||
@@ -1262,6 +1262,21 @@ bool NetworkOPsImp::checkLastClosedLedger (
|
|||||||
if (!switchLedgers)
|
if (!switchLedgers)
|
||||||
return false;
|
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.warning << "We are not running on the consensus ledger";
|
||||||
m_journal.info << "Our LCL: " << getJson (*ourClosed);
|
m_journal.info << "Our LCL: " << getJson (*ourClosed);
|
||||||
m_journal.info << "Net LCL " << closedLedger;
|
m_journal.info << "Net LCL " << closedLedger;
|
||||||
@@ -1269,12 +1284,6 @@ bool NetworkOPsImp::checkLastClosedLedger (
|
|||||||
if ((mMode == omTRACKING) || (mMode == omFULL))
|
if ((mMode == omTRACKING) || (mMode == omFULL))
|
||||||
setMode (omCONNECTED);
|
setMode (omCONNECTED);
|
||||||
|
|
||||||
Ledger::pointer consensus = m_ledgerMaster.getLedgerByHash (closedLedger);
|
|
||||||
|
|
||||||
if (!consensus)
|
|
||||||
consensus = getApp().getInboundLedgers().acquire (
|
|
||||||
closedLedger, 0, InboundLedger::fcCONSENSUS);
|
|
||||||
|
|
||||||
if (consensus)
|
if (consensus)
|
||||||
{
|
{
|
||||||
clearNeedNetworkLedger ();
|
clearNeedNetworkLedger ();
|
||||||
|
|||||||
@@ -169,6 +169,17 @@ getCandidateLedger (LedgerIndex requested)
|
|||||||
return (requested + 255) & (~255);
|
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
|
// Modifiers
|
||||||
|
|||||||
@@ -307,6 +307,99 @@ rippleTransferRate (ReadView const& view,
|
|||||||
: rippleTransferRate(view, issuer);
|
: 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
|
bool
|
||||||
dirIsEmpty (ReadView const& view,
|
dirIsEmpty (ReadView const& view,
|
||||||
Keylet const& k)
|
Keylet const& k)
|
||||||
|
|||||||
Reference in New Issue
Block a user