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:
JoelKatz
2015-07-28 15:14:51 -07:00
committed by Nik Bougalis
parent 38c6083a2f
commit 0bb570a36d
6 changed files with 183 additions and 11 deletions

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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 ();

View File

@@ -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

View File

@@ -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)