diff --git a/Builds/VisualStudio2015/RippleD.vcxproj b/Builds/VisualStudio2015/RippleD.vcxproj
index f682212b208..b3c119e5015 100644
--- a/Builds/VisualStudio2015/RippleD.vcxproj
+++ b/Builds/VisualStudio2015/RippleD.vcxproj
@@ -1634,6 +1634,8 @@
+
+
@@ -4501,6 +4503,10 @@
True
True
+
+ True
+ True
+
True
True
@@ -4685,6 +4691,10 @@
True
True
+
+ True
+ True
+
True
True
diff --git a/Builds/VisualStudio2015/RippleD.vcxproj.filters b/Builds/VisualStudio2015/RippleD.vcxproj.filters
index 7e954e279be..7681fc79bca 100644
--- a/Builds/VisualStudio2015/RippleD.vcxproj.filters
+++ b/Builds/VisualStudio2015/RippleD.vcxproj.filters
@@ -2151,6 +2151,9 @@
ripple\consensus
+
+ ripple\consensus
+
ripple\consensus
@@ -5268,6 +5271,9 @@
test\app
+
+ test\app
+
test\app
@@ -5406,6 +5412,9 @@
test\consensus
+
+ test\consensus
+
test\consensus
diff --git a/docs/source.dox b/docs/source.dox
index d0d82d7a86d..96b1fa73e38 100644
--- a/docs/source.dox
+++ b/docs/source.dox
@@ -114,6 +114,7 @@ INPUT = \
../src/ripple/consensus/ConsensusTypes.h \
../src/ripple/consensus/DisputedTx.h \
../src/ripple/consensus/LedgerTiming.h \
+ ../src/ripple/consensus/LedgerTrie.h \
../src/ripple/consensus/Validations.h \
../src/ripple/consensus/ConsensusParms.h \
../src/ripple/app/consensus/RCLCxTx.h \
diff --git a/src/ripple/app/consensus/RCLConsensus.cpp b/src/ripple/app/consensus/RCLConsensus.cpp
index 925b701a497..996a3f8bd3d 100644
--- a/src/ripple/app/consensus/RCLConsensus.cpp
+++ b/src/ripple/app/consensus/RCLConsensus.cpp
@@ -233,9 +233,13 @@ RCLConsensus::Adaptor::proposersValidated(LedgerHash const& h) const
}
std::size_t
-RCLConsensus::Adaptor::proposersFinished(LedgerHash const& h) const
+RCLConsensus::Adaptor::proposersFinished(
+ RCLCxLedger const& ledger,
+ LedgerHash const& h) const
{
- return app_.getValidations().getNodesAfter(h);
+ RCLValidations& vals = app_.getValidations();
+ return vals.getNodesAfter(
+ RCLValidatedLedger(ledger.ledger_, vals.adaptor().journal()), h);
}
uint256
@@ -244,29 +248,17 @@ RCLConsensus::Adaptor::getPrevLedger(
RCLCxLedger const& ledger,
ConsensusMode mode)
{
- uint256 parentID;
- // Only set the parent ID if we believe ledger is the right ledger
- if (mode != ConsensusMode::wrongLedger)
- parentID = ledger.parentID();
-
- // Get validators that are on our ledger, or "close" to being on
- // our ledger.
- hash_map ledgerCounts =
- app_.getValidations().currentTrustedDistribution(
- ledgerID, parentID, ledgerMaster_.getValidLedgerIndex());
-
- uint256 netLgr = getPreferredLedger(ledgerID, ledgerCounts);
+ RCLValidations& vals = app_.getValidations();
+ uint256 netLgr = vals.getPreferred(
+ RCLValidatedLedger{ledger.ledger_, vals.adaptor().journal()},
+ ledgerMaster_.getValidLedgerIndex());
if (netLgr != ledgerID)
{
if (mode != ConsensusMode::wrongLedger)
app_.getOPs().consensusViewChange();
- if (auto stream = j_.debug())
- {
- for (auto const & it : ledgerCounts)
- stream << "V: " << it.first << ", " << it.second;
- }
+ JLOG(j_.debug())<< Json::Compact(app_.getValidations().getJsonTrie());
}
return netLgr;
@@ -454,7 +446,8 @@ RCLConsensus::Adaptor::doAccept(
app_.journal("LedgerConsensus").warn(),
"Not validating");
- if (validating_ && !consensusFail)
+ if (validating_ && !consensusFail &&
+ app_.getValidations().canValidateSeq(sharedLCL.seq()))
{
validate(sharedLCL, proposing);
JLOG(j_.info()) << "CNF Val " << newLCLHash;
@@ -841,7 +834,10 @@ RCLConsensus::Adaptor::validate(RCLCxLedger const& ledger, bool proposing)
// Build validation
auto v = std::make_shared(
- ledger.id(), validationTime, valPublic_, proposing);
+ ledger.id(),
+ validationTime,
+ valPublic_,
+ proposing /* full if proposed */);
v->setFieldU32(sfLedgerSequence, ledger.seq());
// Add our load fee to the validation
diff --git a/src/ripple/app/consensus/RCLConsensus.h b/src/ripple/app/consensus/RCLConsensus.h
index c200589b070..8bded9a15fc 100644
--- a/src/ripple/app/consensus/RCLConsensus.h
+++ b/src/ripple/app/consensus/RCLConsensus.h
@@ -201,12 +201,13 @@ class RCLConsensus
/** Number of proposers that have validated a ledger descended from
requested ledger.
- @param h The hash of the ledger of interest.
+ @param ledger The current working ledger
+ @param h The hash of the preferred working ledger
@return The number of validating peers that have validated a ledger
- succeeding the one provided.
+ descended from the preferred working ledger.
*/
std::size_t
- proposersFinished(LedgerHash const& h) const;
+ proposersFinished(RCLCxLedger const & ledger, LedgerHash const& h) const;
/** Propose the given position to my peers.
diff --git a/src/ripple/app/consensus/RCLValidations.cpp b/src/ripple/app/consensus/RCLValidations.cpp
index 14c352fc2f0..85bf7fd69f7 100644
--- a/src/ripple/app/consensus/RCLValidations.cpp
+++ b/src/ripple/app/consensus/RCLValidations.cpp
@@ -19,6 +19,8 @@
#include
#include
+#include
+#include
#include
#include
#include
@@ -36,19 +38,119 @@
namespace ripple {
-RCLValidationsPolicy::RCLValidationsPolicy(Application& app) : app_(app)
+RCLValidatedLedger::RCLValidatedLedger(MakeGenesis)
+ : ledgerID_{0}, ledgerSeq_{0}
+{
+}
+
+RCLValidatedLedger::RCLValidatedLedger(
+ std::shared_ptr const& ledger,
+ beast::Journal j)
+ : ledgerID_{ledger->info().hash}, ledgerSeq_{ledger->seq()}, j_{j}
+{
+ auto const hashIndex = ledger->read(keylet::skip());
+ if (hashIndex)
+ {
+ assert(hashIndex->getFieldU32(sfLastLedgerSequence) == (seq() - 1));
+ ancestors_ = hashIndex->getFieldV256(sfHashes).value();
+ }
+ else
+ JLOG(j_.warn()) << "Ledger " << ledgerSeq_ << ":" << ledgerID_
+ << " missing recent ancestor hashes";
+}
+
+auto
+RCLValidatedLedger::minSeq() const -> Seq
+{
+ return seq() - std::min(seq(), static_cast(ancestors_.size()));
+}
+
+auto
+RCLValidatedLedger::seq() const -> Seq
+{
+ return ledgerSeq_;
+}
+auto
+RCLValidatedLedger::id() const -> ID
+{
+ return ledgerID_;
+}
+
+auto RCLValidatedLedger::operator[](Seq const& s) const -> ID
+{
+ if (s >= minSeq() && s <= seq())
+ {
+ if (s == seq())
+ return ledgerID_;
+ Seq const diff = seq() - s;
+ return ancestors_[ancestors_.size() - diff];
+ }
+
+ JLOG(j_.warn()) << "Unable to determine hash of ancestor seq=" << s
+ << " from ledger hash=" << ledgerID_
+ << " seq=" << ledgerSeq_;
+ // Default ID that is less than all others
+ return ID{0};
+}
+
+// Return the sequence number of the earliest possible mismatching ancestor
+RCLValidatedLedger::Seq
+mismatch(RCLValidatedLedger const& a, RCLValidatedLedger const& b)
+{
+ using Seq = RCLValidatedLedger::Seq;
+
+ // Find overlapping interval for known sequence for the ledgers
+ Seq const lower = std::max(a.minSeq(), b.minSeq());
+ Seq const upper = std::min(a.seq(), b.seq());
+
+ Seq curr = upper;
+ while (curr != Seq{0} && a[curr] != b[curr] && curr >= lower)
+ --curr;
+
+ // If the searchable interval mismatches entirely, then we have to
+ // assume the ledgers mismatch starting post genesis ledger
+ return (curr < lower) ? Seq{1} : (curr + Seq{1});
+}
+
+RCLValidationsAdaptor::RCLValidationsAdaptor(Application& app, beast::Journal j)
+ : app_(app), j_(j)
{
staleValidations_.reserve(512);
}
NetClock::time_point
-RCLValidationsPolicy::now() const
+RCLValidationsAdaptor::now() const
{
return app_.timeKeeper().closeTime();
}
+boost::optional
+RCLValidationsAdaptor::acquire(LedgerHash const & hash)
+{
+ auto ledger = app_.getLedgerMaster().getLedgerByHash(hash);
+ if (!ledger)
+ {
+ JLOG(j_.debug())
+ << "Need validated ledger for preferred ledger analysis " << hash;
+
+ Application * pApp = &app_;
+
+ app_.getJobQueue().addJob(
+ jtADVANCE, "getConsensusLedger", [pApp, hash](Job&) {
+ pApp ->getInboundLedgers().acquire(
+ hash, 0, InboundLedger::Reason::CONSENSUS);
+ });
+ return boost::none;
+ }
+
+ assert(!ledger->open() && ledger->isImmutable());
+ assert(ledger->info().hash == hash);
+
+ return RCLValidatedLedger(std::move(ledger), j_);
+}
+
void
-RCLValidationsPolicy::onStale(RCLValidation&& v)
+RCLValidationsAdaptor::onStale(RCLValidation&& v)
{
// Store the newly stale validation; do not do significant work in this
// function since this is a callback from Validations, which may be
@@ -60,7 +162,7 @@ RCLValidationsPolicy::onStale(RCLValidation&& v)
return;
// addJob() may return false (Job not added) at shutdown.
- staleWriting_ = app_.getJobQueue().addJob(
+ staleWriting_ = app_.getJobQueue().addJob(
jtWRITE, "Validations::doStaleWrite", [this](Job&) {
auto event =
app_.getJobQueue().makeLoadEvent(jtDISK, "ValidationWrite");
@@ -70,7 +172,7 @@ RCLValidationsPolicy::onStale(RCLValidation&& v)
}
void
-RCLValidationsPolicy::flush(hash_map&& remaining)
+RCLValidationsAdaptor::flush(hash_map&& remaining)
{
bool anyNew = false;
{
@@ -106,7 +208,7 @@ RCLValidationsPolicy::flush(hash_map&& remaining)
// NOTE: doStaleWrite() must be called with staleLock_ *locked*. The passed
// ScopedLockType& acts as a reminder to future maintainers.
void
-RCLValidationsPolicy::doStaleWrite(ScopedLockType&)
+RCLValidationsAdaptor::doStaleWrite(ScopedLockType&)
{
static const std::string insVal(
"INSERT INTO Validations "
@@ -131,10 +233,13 @@ RCLValidationsPolicy::doStaleWrite(ScopedLockType&)
Serializer s(1024);
soci::transaction tr(*db);
- for (auto const& rclValidation : currentStale)
+ for (RCLValidation const& wValidation : currentStale)
{
+ // Only save full validations until we update the schema
+ if(!wValidation.full())
+ continue;
s.erase();
- STValidation::pointer const& val = rclValidation.unwrap();
+ STValidation::pointer const& val = wValidation.unwrap();
val->add(s);
auto const ledgerHash = to_string(val->getLedgerHash());
@@ -174,97 +279,75 @@ handleNewValidation(Application& app,
STValidation::ref val,
std::string const& source)
{
- PublicKey const& signer = val->getSignerPublic();
+ PublicKey const& signingKey = val->getSignerPublic();
uint256 const& hash = val->getLedgerHash();
// Ensure validation is marked as trusted if signer currently trusted
- boost::optional pubKey = app.validators().getTrustedKey(signer);
- if (!val->isTrusted() && pubKey)
+ boost::optional masterKey =
+ app.validators().getTrustedKey(signingKey);
+ if (!val->isTrusted() && masterKey)
val->setTrusted();
- RCLValidations& validations = app.getValidations();
-
- beast::Journal j = validations.journal();
-
- // Do not process partial validations.
- if (!val->isFull())
- {
- const bool current = isCurrent(
- validations.parms(),
- app.timeKeeper().closeTime(),
- val->getSignTime(),
- val->getSeenTime());
-
- JLOG(j.debug()) << "Val (partial) for " << hash << " from "
- << toBase58(TokenType::TOKEN_NODE_PUBLIC, signer)
- << " ignored "
- << (val->isTrusted() ? "trusted/" : "UNtrusted/")
- << (current ? "current" : "stale");
-
- // Only forward if current and trusted
- return current && val->isTrusted();
- }
-
- if (!val->isTrusted())
- {
- JLOG(j.trace()) << "Node "
- << toBase58(TokenType::TOKEN_NODE_PUBLIC, signer)
- << " not in UNL st="
- << val->getSignTime().time_since_epoch().count()
- << ", hash=" << hash
- << ", shash=" << val->getSigningHash()
- << " src=" << source;
- }
// If not currently trusted, see if signer is currently listed
- if (!pubKey)
- pubKey = app.validators().getListedKey(signer);
+ if (!masterKey)
+ masterKey = app.validators().getListedKey(signingKey);
bool shouldRelay = false;
-
- // only add trusted or listed
- if (pubKey)
+ RCLValidations& validations = app.getValidations();
+ beast::Journal j = validations.adaptor().journal();
+
+ auto dmp = [&](beast::Journal::Stream s, std::string const& msg) {
+ s << "Val for " << hash
+ << (val->isTrusted() ? " trusted/" : " UNtrusted/")
+ << (val->isFull() ? "full" : "partial") << " from "
+ << (masterKey ? toBase58(TokenType::TOKEN_NODE_PUBLIC, *masterKey)
+ : "unknown")
+ << " signing key "
+ << toBase58(TokenType::TOKEN_NODE_PUBLIC, signingKey) << " " << msg
+ << " src=" << source;
+ };
+
+ if(!val->isFieldPresent(sfLedgerSequence))
{
- using AddOutcome = RCLValidations::AddOutcome;
-
- AddOutcome const res = validations.add(*pubKey, val);
+ if(j.error())
+ dmp(j.error(), "missing ledger sequence field");
+ return false;
+ }
- // This is a duplicate validation
- if (res == AddOutcome::repeat)
- return false;
+ // masterKey is seated only if validator is trusted or listed
+ if (masterKey)
+ {
+ ValStatus const outcome = validations.add(*masterKey, val);
+ if(j.debug())
+ dmp(j.debug(), to_string(outcome));
- // This validation replaced a prior one with the same sequence number
- if (res == AddOutcome::sameSeq)
+ if(outcome == ValStatus::badSeq && j.warn())
+ {
+ auto const seq = val->getFieldU32(sfLedgerSequence);
+ dmp(j.warn(),
+ "already validated sequence at or past " + to_string(seq));
+ }
+ else if(outcome == ValStatus::repeatID && j.warn())
{
auto const seq = val->getFieldU32(sfLedgerSequence);
- JLOG(j.warn()) << "Trusted node "
- << toBase58(TokenType::TOKEN_NODE_PUBLIC, *pubKey)
- << " published multiple validations for ledger "
- << seq;
+ dmp(j.warn(),
+ "already validated ledger with same id but different seq "
+ "than" + to_string(seq));
}
- JLOG(j.debug()) << "Val for " << hash << " from "
- << toBase58(TokenType::TOKEN_NODE_PUBLIC, signer)
- << " added "
- << (val->isTrusted() ? "trusted/" : "UNtrusted/")
- << ((res == AddOutcome::current) ? "current" : "stale");
-
- // Trusted current validations should be checked and relayed.
- // Trusted validations with sameSeq replaced an older validation
- // with that sequence number, so should still be checked and relayed.
- if (val->isTrusted() &&
- (res == AddOutcome::current || res == AddOutcome::sameSeq))
+ if (val->isTrusted() && outcome == ValStatus::current)
{
app.getLedgerMaster().checkAccept(
hash, val->getFieldU32(sfLedgerSequence));
-
shouldRelay = true;
}
+
}
else
{
JLOG(j.debug()) << "Val for " << hash << " from "
- << toBase58(TokenType::TOKEN_NODE_PUBLIC, signer)
- << " not added UNtrusted/";
+ << toBase58(TokenType::TOKEN_NODE_PUBLIC, signingKey)
+ << " not added UNlisted";
}
// This currently never forwards untrusted validations, though we may
@@ -277,4 +360,6 @@ handleNewValidation(Application& app,
// ability/bandwidth to. None of that was implemented.
return shouldRelay;
}
+
+
} // namespace ripple
diff --git a/src/ripple/app/consensus/RCLValidations.h b/src/ripple/app/consensus/RCLValidations.h
index cb8dbff0578..1bd3978eded 100644
--- a/src/ripple/app/consensus/RCLValidations.h
+++ b/src/ripple/app/consensus/RCLValidations.h
@@ -20,9 +20,11 @@
#ifndef RIPPLE_APP_CONSENSUSS_VALIDATIONS_H_INCLUDED
#define RIPPLE_APP_CONSENSUSS_VALIDATIONS_H_INCLUDED
+#include
#include
#include
#include
+#include
#include
#include
@@ -39,6 +41,8 @@ class RCLValidation
{
STValidation::pointer val_;
public:
+ using NodeKey = ripple::PublicKey;
+ using NodeID = ripple::NodeID;
/** Constructor
@@ -59,9 +63,7 @@ class RCLValidation
std::uint32_t
seq() const
{
- if(auto res = (*val_)[~sfLedgerSequence])
- return *res;
- return 0;
+ return val_->getFieldU32(sfLedgerSequence);
}
/// Validation's signing time
@@ -99,6 +101,13 @@ class RCLValidation
return val_->isTrusted();
}
+ /// Whether the validatioon is full (not-partial)
+ bool
+ full() const
+ {
+ return val_->isFull();
+ }
+
/// Get the load fee of the validation if it exists
boost::optional
loadFee() const
@@ -115,35 +124,77 @@ class RCLValidation
};
-/** Implements the StalePolicy policy class for adapting Validations in the RCL
+/** Wraps a ledger instance for use in generic Validations LedgerTrie.
- Manages storing and writing stale RCLValidations to the sqlite DB.
+ The LedgerTrie models a ledger's history as a map from Seq -> ID. Any
+ two ledgers that have the same ID for a given Seq have the same ID for
+ all earlier sequences (e.g. shared ancestry). In practice, a ledger only
+ conveniently has the prior 256 ancestor hashes available. For
+ RCLValidatedLedger, we treat any ledgers separated by more than 256 Seq as
+ distinct.
*/
-class RCLValidationsPolicy
+class RCLValidatedLedger
{
- using LockType = std::mutex;
- using ScopedLockType = std::lock_guard;
- using ScopedUnlockType = GenericScopedUnlock;
+public:
+ using ID = LedgerHash;
+ using Seq = LedgerIndex;
+ struct MakeGenesis
+ {
+ };
- Application& app_;
+ RCLValidatedLedger(MakeGenesis);
- // Lock for managing staleValidations_ and writing_
- std::mutex staleLock_;
- std::vector staleValidations_;
- bool staleWriting_ = false;
+ RCLValidatedLedger(
+ std::shared_ptr const& ledger,
+ beast::Journal j);
- // Write the stale validations to sqlite DB, the scoped lock argument
- // is used to remind callers that the staleLock_ must be *locked* prior
- // to making the call
- void
- doStaleWrite(ScopedLockType&);
+ /// The sequence (index) of the ledger
+ Seq
+ seq() const;
+
+ /// The ID (hash) of the ledger
+ ID
+ id() const;
+
+ /** Lookup the ID of the ancestor ledger
+
+ @param s The sequence (index) of the ancestor
+ @return The ID of this ledger's ancestor with that sequence number or
+ ID{0} if one was not determined
+ */
+ ID operator[](Seq const& s) const;
+
+ /// Find the sequence number of the earliest mismatching ancestor
+ friend Seq
+ mismatch(RCLValidatedLedger const& a, RCLValidatedLedger const& b);
+
+ Seq
+ minSeq() const;
+
+private:
+ ID ledgerID_;
+ Seq ledgerSeq_;
+ std::vector ancestors_;
+ beast::Journal j_;
+};
+
+/** Generic validations adaptor class for RCL
+ Manages storing and writing stale RCLValidations to the sqlite DB and
+ acquiring validated ledgers from the network.
+*/
+class RCLValidationsAdaptor
+{
public:
+ // Type definitions for generic Validation
+ using Mutex = std::mutex;
+ using Validation = RCLValidation;
+ using Ledger = RCLValidatedLedger;
- RCLValidationsPolicy(Application & app);
+ RCLValidationsAdaptor(Application& app, beast::Journal j);
/** Current time used to determine if validations are stale.
- */
+ */
NetClock::time_point
now() const;
@@ -163,20 +214,45 @@ class RCLValidationsPolicy
@param remaining The remaining validations to flush
*/
void
- flush(hash_map && remaining);
-};
+ flush(hash_map&& remaining);
+
+ /** Attempt to acquire the ledger with given id from the network */
+ boost::optional
+ acquire(LedgerHash const & id);
+
+ beast::Journal
+ journal() const
+ {
+ return j_;
+ }
+
+private:
+ using ScopedLockType = std::lock_guard;
+ using ScopedUnlockType = GenericScopedUnlock;
+
+ Application& app_;
+ beast::Journal j_;
+
+ // Lock for managing staleValidations_ and writing_
+ std::mutex staleLock_;
+ std::vector staleValidations_;
+ bool staleWriting_ = false;
+ // Write the stale validations to sqlite DB, the scoped lock argument
+ // is used to remind callers that the staleLock_ must be *locked* prior
+ // to making the call
+ void
+ doStaleWrite(ScopedLockType&);
+};
/// Alias for RCL-specific instantiation of generic Validations
-using RCLValidations =
- Validations;
+using RCLValidations = Validations;
+
/** Handle a new validation
- 1. Set the trust status of a validation based on the validating node's
- public key and this node's current UNL.
- 2. Add the validation to the set of validations if current.
- 3. If new and trusted, send the validation to the ledgerMaster.
+ Also sets the trust status of a validation based on the validating node's
+ public key and this node's current UNL.
@param app Application object containing validations and ledgerMaster
@param val The validation to add
@@ -185,8 +261,10 @@ using RCLValidations =
@return Whether the validation should be relayed
*/
bool
-handleNewValidation(Application & app, STValidation::ref val, std::string const& source);
-
+handleNewValidation(
+ Application& app,
+ STValidation::ref val,
+ std::string const& source);
} // namespace ripple
diff --git a/src/ripple/app/main/Application.cpp b/src/ripple/app/main/Application.cpp
index 998edcbac93..1bf3408f9ef 100644
--- a/src/ripple/app/main/Application.cpp
+++ b/src/ripple/app/main/Application.cpp
@@ -497,8 +497,7 @@ class ApplicationImp
stopwatch(), HashRouter::getDefaultHoldTime (),
HashRouter::getDefaultRecoverLimit ()))
- , mValidations (ValidationParms(),stopwatch(), logs_->journal("Validations"),
- *this)
+ , mValidations (ValidationParms(),stopwatch(), *this, logs_->journal("Validations"))
, m_loadManager (make_LoadManager (*this, *this, logs_->journal("LoadManager")))
@@ -916,7 +915,9 @@ class ApplicationImp
// before we declare ourselves stopped.
waitHandlerCounter_.join("Application", 1s, m_journal);
+ JLOG(m_journal.debug()) << "Flushing validations";
mValidations.flush ();
+ JLOG(m_journal.debug()) << "Validations flushed";
validatorSites_->stop ();
diff --git a/src/ripple/app/main/Application.h b/src/ripple/app/main/Application.h
index 032ae781b3b..6dcac7a3d5e 100644
--- a/src/ripple/app/main/Application.h
+++ b/src/ripple/app/main/Application.h
@@ -74,12 +74,10 @@ class SHAMapStore;
using NodeCache = TaggedCache ;
-template
+template
class Validations;
-class RCLValidation;
-class RCLValidationsPolicy;
-using RCLValidations =
- Validations;
+class RCLValidationsAdaptor;
+using RCLValidations = Validations;
class Application : public beast::PropertyStream::Source
{
diff --git a/src/ripple/app/misc/NetworkOPs.cpp b/src/ripple/app/misc/NetworkOPs.cpp
index fba0c3b5f68..583573880c5 100644
--- a/src/ripple/app/misc/NetworkOPs.cpp
+++ b/src/ripple/app/misc/NetworkOPs.cpp
@@ -1273,84 +1273,39 @@ bool NetworkOPsImp::checkLastClosedLedger (
JLOG(m_journal.trace()) << "OurClosed: " << closedLedger;
JLOG(m_journal.trace()) << "PrevClosed: " << prevClosedLedger;
- struct ValidationCount
- {
- std::uint32_t trustedValidations = 0;
- std::uint32_t nodesUsing = 0;
- };
-
- hash_map ledgers;
- {
- hash_map current =
- app_.getValidations().currentTrustedDistribution(
- closedLedger,
- prevClosedLedger,
- m_ledgerMaster.getValidLedgerIndex());
-
- for (auto& it: current)
- ledgers[it.first].trustedValidations += it.second;
- }
+ //-------------------------------------------------------------------------
+ // Determine preferred last closed ledger
- auto& ourVC = ledgers[closedLedger];
+ auto & validations = app_.getValidations();
+ JLOG(m_journal.debug())
+ << "ValidationTrie " << Json::Compact(validations.getJsonTrie());
+ // Will rely on peer LCL if no trusted validations exist
+ hash_map peerCounts;
+ peerCounts[closedLedger] = 0;
if (mMode >= omTRACKING)
- {
- ++ourVC.nodesUsing;
- }
+ peerCounts[closedLedger]++;
- for (auto& peer: peerList)
+ for (auto& peer : peerList)
{
- uint256 peerLedger = peer->getClosedLedgerHash ();
+ uint256 peerLedger = peer->getClosedLedgerHash();
- if (peerLedger.isNonZero ())
- ++ledgers[peerLedger].nodesUsing;
+ if (peerLedger.isNonZero())
+ ++peerCounts[peerLedger];
}
+ for(auto const & it: peerCounts)
+ JLOG(m_journal.debug()) << "L: " << it.first << " n=" << it.second;
- // 3) Is there a network ledger we'd like to switch to? If so, do we have
- // it?
- bool switchLedgers = false;
- ValidationCount bestCounts = ledgers[closedLedger];
-
- for (auto const& it: ledgers)
- {
- uint256 const & currLedger = it.first;
- ValidationCount const & currCounts = it.second;
-
- JLOG(m_journal.debug()) << "L: " << currLedger
- << " t=" << currCounts.trustedValidations
- << ", n=" << currCounts.nodesUsing;
-
- bool const preferCurr = [&]()
- {
- // Prefer ledger with more trustedValidations
- if (currCounts.trustedValidations > bestCounts.trustedValidations)
- return true;
- if (currCounts.trustedValidations < bestCounts.trustedValidations)
- return false;
- // If neither are trusted, prefer more nodesUsing
- if (currCounts.trustedValidations == 0)
- {
- if (currCounts.nodesUsing > bestCounts.nodesUsing)
- return true;
- if (currCounts.nodesUsing < bestCounts.nodesUsing)
- return false;
- }
- // If tied trustedValidations (non-zero) or tied nodesUsing,
- // prefer higher ledger hash
- return currLedger > closedLedger;
-
- }();
-
- // Switch to current ledger if it is preferred over best so far
- if (preferCurr)
- {
- bestCounts = currCounts;
- closedLedger = currLedger;
- switchLedgers = true;
- }
- }
+ uint256 preferredLCL = validations.getPreferredLCL(
+ RCLValidatedLedger{ourClosed, validations.adaptor().journal()},
+ m_ledgerMaster.getValidLedgerIndex(),
+ peerCounts);
+ bool switchLedgers = preferredLCL != closedLedger;
+ if(switchLedgers)
+ closedLedger = preferredLCL;
+ //-------------------------------------------------------------------------
if (switchLedgers && (closedLedger == prevClosedLedger))
{
// don't switch to our own previous ledger
@@ -1364,15 +1319,15 @@ bool NetworkOPsImp::checkLastClosedLedger (
if (!switchLedgers)
return false;
- auto consensus = m_ledgerMaster.getLedgerByHash (closedLedger);
+ auto consensus = m_ledgerMaster.getLedgerByHash(closedLedger);
if (!consensus)
- consensus = app_.getInboundLedgers().acquire (
+ consensus = app_.getInboundLedgers().acquire(
closedLedger, 0, InboundLedger::Reason::CONSENSUS);
if (consensus &&
- ! m_ledgerMaster.isCompatible (*consensus, m_journal.debug(),
- "Not switching"))
+ !m_ledgerMaster.isCompatible(
+ *consensus, m_journal.debug(), "Not switching"))
{
// Don't switch to a ledger not on the validated chain
networkClosed = ourClosed->info().hash;
@@ -1380,18 +1335,18 @@ bool NetworkOPsImp::checkLastClosedLedger (
}
JLOG(m_journal.warn()) << "We are not running on the consensus ledger";
- JLOG(m_journal.info()) << "Our LCL: " << getJson (*ourClosed);
+ JLOG(m_journal.info()) << "Our LCL: " << getJson(*ourClosed);
JLOG(m_journal.info()) << "Net LCL " << closedLedger;
if ((mMode == omTRACKING) || (mMode == omFULL))
- setMode (omCONNECTED);
+ setMode(omCONNECTED);
if (consensus)
{
- // FIXME: If this rewinds the ledger sequence, or has the same sequence, we
- // should update the status on any stored transactions in the invalidated
- // ledgers.
- switchLastClosedLedger (consensus);
+ // FIXME: If this rewinds the ledger sequence, or has the same
+ // sequence, we should update the status on any stored transactions
+ // in the invalidated ledgers.
+ switchLastClosedLedger(consensus);
}
return true;
diff --git a/src/ripple/consensus/Consensus.h b/src/ripple/consensus/Consensus.h
index 891dd18b676..820fd9c3ce3 100644
--- a/src/ripple/consensus/Consensus.h
+++ b/src/ripple/consensus/Consensus.h
@@ -225,8 +225,10 @@ checkConsensus(
std::size_t proposersValidated(Ledger::ID const & prevLedger) const;
// Number of proposers that have validated a ledger descended from the
- // given ledger
- std::size_t proposersFinished(Ledger::ID const & prevLedger) const;
+ // given ledger; if prevLedger.id() != prevLedgerID, use prevLedgerID
+ // for the determination
+ std::size_t proposersFinished(Ledger const & prevLedger,
+ Ledger::ID const & prevLedger) const;
// Return the ID of the last closed (and validated) ledger that the
// application thinks consensus should use as the prior ledger.
@@ -1410,7 +1412,8 @@ Consensus::haveConsensus()
++disagree;
}
}
- auto currentFinished = adaptor_.proposersFinished(prevLedgerID_);
+ auto currentFinished =
+ adaptor_.proposersFinished(previousLedger_, prevLedgerID_);
JLOG(j_.debug()) << "Checking for TX consensus: agree=" << agree
<< ", disagree=" << disagree;
diff --git a/src/ripple/consensus/LedgerTrie.h b/src/ripple/consensus/LedgerTrie.h
new file mode 100644
index 00000000000..a80c0471d3e
--- /dev/null
+++ b/src/ripple/consensus/LedgerTrie.h
@@ -0,0 +1,815 @@
+//------------------------------------------------------------------------------
+/*
+ This file is part of rippled: https://github.com/ripple/rippled
+ Copyright (c) 2017 Ripple Labs Inc.
+
+ Permission to use, copy, modify, and/or distribute this software for any
+ purpose with or without fee is hereby granted, provided that the above
+ copyright notice and this permission notice appear in all copies.
+
+ THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+*/
+//==============================================================================
+
+#ifndef RIPPLE_APP_CONSENSUS_LEDGERS_TRIE_H_INCLUDED
+#define RIPPLE_APP_CONSENSUS_LEDGERS_TRIE_H_INCLUDED
+
+#include
+#include
+#include
+#include
+#include
+
+namespace ripple {
+
+/** The tip of a span of ledger ancestry
+*/
+template
+class SpanTip
+{
+public:
+ using Seq = typename Ledger::Seq;
+ using ID = typename Ledger::ID;
+
+ SpanTip(Seq s, ID i, Ledger const lgr)
+ : seq{s}, id{i}, ledger{std::move(lgr)}
+ {
+ }
+
+ // The sequence number of the tip ledger
+ Seq seq;
+ // The ID of the tip ledger
+ ID id;
+
+ /** Lookup the ID of an ancestor of the tip ledger
+
+ @param s The sequence number of the ancestor
+ @return The ID of the ancestor with that sequence number
+
+ @note s must be less than or equal to the sequence number of the
+ tip ledger
+ */
+ ID
+ ancestor(Seq const& s) const
+ {
+ assert(s <= seq);
+ return ledger[s];
+ }
+
+private:
+ Ledger const ledger;
+};
+
+namespace ledger_trie_detail {
+
+// Represents a span of ancestry of a ledger
+template
+class Span
+{
+ using Seq = typename Ledger::Seq;
+ using ID = typename Ledger::ID;
+
+ // The span is the half-open interval [start,end) of ledger_
+ Seq start_{0};
+ Seq end_{1};
+ Ledger ledger_;
+
+public:
+ Span() : ledger_{typename Ledger::MakeGenesis{}}
+ {
+ // Require default ledger to be genesis seq
+ assert(ledger_.seq() == start_);
+ }
+
+ Span(Ledger ledger)
+ : start_{0}, end_{ledger.seq() + Seq{1}}, ledger_{std::move(ledger)}
+ {
+ }
+
+ Span(Span const& s) = default;
+ Span(Span&& s) = default;
+ Span&
+ operator=(Span const&) = default;
+ Span&
+ operator=(Span&&) = default;
+
+ Seq
+ start() const
+ {
+ return start_;
+ }
+
+ Seq
+ end() const
+ {
+ return end_;
+ }
+
+ // Return the Span from [spot,end_) or none if no such valid span
+ boost::optional
+ from(Seq spot) const
+ {
+ return sub(spot, end_);
+ }
+
+ // Return the Span from [start_,spot) or none if no such valid span
+ boost::optional
+ before(Seq spot) const
+ {
+ return sub(start_, spot);
+ }
+
+ // Return the ID of the ledger that starts this span
+ ID
+ startID() const
+ {
+ return ledger_[start_];
+ }
+
+ // Return the ledger sequence number of the first possible difference
+ // between this span and a given ledger.
+ Seq
+ diff(Ledger const& o) const
+ {
+ return clamp(mismatch(ledger_, o));
+ }
+
+ // The tip of this span
+ SpanTip
+ tip() const
+ {
+ Seq tipSeq{end_ - Seq{1}};
+ return SpanTip{tipSeq, ledger_[tipSeq], ledger_};
+ }
+
+private:
+ Span(Seq start, Seq end, Ledger const& l)
+ : start_{start}, end_{end}, ledger_{l}
+ {
+ // Spans cannot be empty
+ assert(start < end);
+ }
+
+ Seq
+ clamp(Seq val) const
+ {
+ return std::min(std::max(start_, val), end_);
+ };
+
+ // Return a span of this over the half-open interval [from,to)
+ boost::optional
+ sub(Seq from, Seq to) const
+ {
+ Seq newFrom = clamp(from);
+ Seq newTo = clamp(to);
+ if (newFrom < newTo)
+ return Span(newFrom, newTo, ledger_);
+ return boost::none;
+ }
+
+ friend std::ostream&
+ operator<<(std::ostream& o, Span const& s)
+ {
+ return o << s.tip().id << "[" << s.start_ << "," << s.end_ << ")";
+ }
+
+ friend Span
+ merge(Span const& a, Span const& b)
+ {
+ // Return combined span, using ledger_ from higher sequence span
+ if (a.end_ < b.end_)
+ return Span(std::min(a.start_, b.start_), b.end_, b.ledger_);
+
+ return Span(std::min(a.start_, b.start_), a.end_, a.ledger_);
+ }
+};
+
+// A node in the trie
+template
+struct Node
+{
+ Node() = default;
+
+ explicit Node(Ledger const& l) : span{l}, tipSupport{1}, branchSupport{1}
+ {
+ }
+
+ explicit Node(Span s) : span{std::move(s)}
+ {
+ }
+
+ Span span;
+ std::uint32_t tipSupport = 0;
+ std::uint32_t branchSupport = 0;
+
+ std::vector> children;
+ Node* parent = nullptr;
+
+ /** Remove the given node from this Node's children
+
+ @param child The address of the child node to remove
+ @note The child must be a member of the vector. The passed pointer
+ will be dangling as a result of this call
+ */
+ void
+ erase(Node const* child)
+ {
+ auto it = std::find_if(
+ children.begin(),
+ children.end(),
+ [child](std::unique_ptr const& curr) {
+ return curr.get() == child;
+ });
+ assert(it != children.end());
+ std::swap(*it, children.back());
+ children.pop_back();
+ }
+
+ friend std::ostream&
+ operator<<(std::ostream& o, Node const& s)
+ {
+ return o << s.span << "(T:" << s.tipSupport << ",B:" << s.branchSupport
+ << ")";
+ }
+
+ Json::Value
+ getJson() const
+ {
+ Json::Value res;
+ res["id"] = to_string(span.tip().id);
+ res["seq"] = static_cast(span.tip().seq);
+ res["tipSupport"] = tipSupport;
+ res["branchSupport"] = branchSupport;
+ if (!children.empty())
+ {
+ Json::Value& cs = (res["children"] = Json::arrayValue);
+ for (auto const& child : children)
+ {
+ cs.append(child->getJson());
+ }
+ }
+ return res;
+ }
+};
+} // namespace ledger_trie_detail
+
+/** Ancestry trie of ledgers
+
+ A compressed trie tree that maintains validation support of recent ledgers
+ based on their ancestry.
+
+ The compressed trie structure comes from recognizing that ledger history
+ can be viewed as a string over the alphabet of ledger ids. That is,
+ a given ledger with sequence number `seq` defines a length `seq` string,
+ with i-th entry equal to the id of the ancestor ledger with sequence
+ number i. "Sequence" strings with a common prefix share those ancestor
+ ledgers in common. Tracking this ancestry information and relations across
+ all validated ledgers is done conveniently in a compressed trie. A node in
+ the trie is an ancestor of all its children. If a parent node has sequence
+ number `seq`, each child node has a different ledger starting at `seq+1`.
+ The compression comes from the invariant that any non-root node with 0 tip
+ support has either no children or multiple children. In other words, a
+ non-root 0-tip-support node can be combined with its single child.
+
+ Each node has a tipSupport, which is the number of current validations for
+ that particular ledger. The node's branch support is the sum of the tip
+ support and the branch support of that node's children:
+
+ @code
+ node->branchSupport = node->tipSupport;
+ for (child : node->children)
+ node->branchSupport += child->branchSupport;
+ @endcode
+
+ The templated Ledger type represents a ledger which has a unique history.
+ It should be lightweight and cheap to copy.
+
+ @code
+ // Identifier types that should be equality-comparable and copyable
+ struct ID;
+ struct Seq;
+
+ struct Ledger
+ {
+ struct MakeGenesis{};
+
+ // The genesis ledger represents a ledger that prefixes all other
+ // ledgers
+ Ledger(MakeGenesis{});
+
+ Ledger(Ledger const&);
+ Ledger& operator=(Ledger const&);
+
+ // Return the sequence number of this ledger
+ Seq seq() const;
+
+ // Return the ID of this ledger's ancestor with given sequence number
+ // or ID{0} if unknown
+ ID
+ operator[](Seq s);
+
+ };
+
+ // Return the sequence number of the first possible mismatching ancestor
+ // between two ledgers
+ Seq
+ mismatch(ledgerA, ledgerB);
+ @endcode
+
+ The unique history invariant of ledgers requires any ledgers that agree
+ on the id of a given sequence number agree on ALL ancestors before that
+ ledger:
+
+ @code
+ Ledger a,b;
+ // For all Seq s:
+ if(a[s] == b[s]);
+ for(Seq p = 0; p < s; ++p)
+ assert(a[p] == b[p]);
+ @endcode
+
+ @tparam Ledger A type representing a ledger and its history
+*/
+template
+class LedgerTrie
+{
+ using Seq = typename Ledger::Seq;
+ using ID = typename Ledger::ID;
+
+ using Node = ledger_trie_detail::Node;
+ using Span = ledger_trie_detail::Span;
+
+ // The root of the trie. The root is allowed to break the no-single child
+ // invariant.
+ std::unique_ptr root;
+
+ // Count of the tip support for each sequence number
+ std::map seqSupport;
+
+ /** Find the node in the trie that represents the longest common ancestry
+ with the given ledger.
+
+ @return Pair of the found node and the sequence number of the first
+ ledger difference.
+ */
+ std::pair
+ find(Ledger const& ledger) const
+ {
+ Node* curr = root.get();
+
+ // Root is always defined and is in common with all ledgers
+ assert(curr);
+ Seq pos = curr->span.diff(ledger);
+
+ bool done = false;
+
+ // Continue searching for a better span as long as the current position
+ // matches the entire span
+ while (!done && pos == curr->span.end())
+ {
+ done = true;
+ // Find the child with the longest ancestry match
+ for (std::unique_ptr const& child : curr->children)
+ {
+ auto const childPos = child->span.diff(ledger);
+ if (childPos > pos)
+ {
+ done = false;
+ pos = childPos;
+ curr = child.get();
+ break;
+ }
+ }
+ }
+ return std::make_pair(curr, pos);
+ }
+
+ void
+ dumpImpl(std::ostream& o, std::unique_ptr const& curr, int offset)
+ const
+ {
+ if (curr)
+ {
+ if (offset > 0)
+ o << std::setw(offset) << "|-";
+
+ std::stringstream ss;
+ ss << *curr;
+ o << ss.str() << std::endl;
+ for (std::unique_ptr const& child : curr->children)
+ dumpImpl(o, child, offset + 1 + ss.str().size() + 2);
+ }
+ }
+
+public:
+ LedgerTrie() : root{std::make_unique()}
+ {
+ }
+
+ /** Insert and/or increment the support for the given ledger.
+
+ @param ledger A ledger and its ancestry
+ @param count The count of support for this ledger
+ */
+ void
+ insert(Ledger const& ledger, std::uint32_t count = 1)
+ {
+ Node* loc;
+ Seq diffSeq;
+ std::tie(loc, diffSeq) = find(ledger);
+
+ // There is always a place to insert
+ assert(loc);
+
+ // Node from which to start incrementing branchSupport
+ Node* incNode = loc;
+
+ // loc->span has the longest common prefix with Span{ledger} of all
+ // existing nodes in the trie. The optional's below represent
+ // the possible common suffixes between loc->span and Span{ledger}.
+ //
+ // loc->span
+ // a b c | d e f
+ // prefix | oldSuffix
+ //
+ // Span{ledger}
+ // a b c | g h i
+ // prefix | newSuffix
+
+ boost::optional prefix = loc->span.before(diffSeq);
+ boost::optional oldSuffix = loc->span.from(diffSeq);
+ boost::optional newSuffix = Span{ledger}.from(diffSeq);
+
+ if (oldSuffix)
+ {
+ // Have
+ // abcdef -> ....
+ // Inserting
+ // abc
+ // Becomes
+ // abc -> def -> ...
+
+ // Create oldSuffix node that takes over loc
+ auto newNode = std::make_unique(*oldSuffix);
+ newNode->tipSupport = loc->tipSupport;
+ newNode->branchSupport = loc->branchSupport;
+ newNode->children = std::move(loc->children);
+ assert(loc->children.empty());
+ for(std::unique_ptr & child : newNode->children)
+ child->parent = newNode.get();
+
+ // Loc truncates to prefix and newNode is its child
+ assert(prefix);
+ loc->span = *prefix;
+ newNode->parent = loc;
+ loc->children.emplace_back(std::move(newNode));
+ loc->tipSupport = 0;
+ }
+ if (newSuffix)
+ {
+ // Have
+ // abc -> ...
+ // Inserting
+ // abcdef-> ...
+ // Becomes
+ // abc -> ...
+ // \-> def
+
+ auto newNode = std::make_unique(*newSuffix);
+ newNode->parent = loc;
+ // increment support starting from the new node
+ incNode = newNode.get();
+ loc->children.push_back(std::move(newNode));
+ }
+
+ incNode->tipSupport += count;
+ while (incNode)
+ {
+ incNode->branchSupport += count;
+ incNode = incNode->parent;
+ }
+
+ seqSupport[ledger.seq()] += count;
+ }
+
+ /** Decrease support for a ledger, removing and compressing if possible.
+
+ @param ledger The ledger history to remove
+ @param count The amount of tip support to remove
+
+ @return Whether a matching node was decremented and possibly removed.
+ */
+ bool
+ remove(Ledger const& ledger, std::uint32_t count = 1)
+ {
+ Node* loc;
+ Seq diffSeq;
+ std::tie(loc, diffSeq) = find(ledger);
+
+ // Cannot erase root
+ if (loc && loc != root.get())
+ {
+ // Must be exact match with tip support
+ if (diffSeq == loc->span.end() && diffSeq > ledger.seq() &&
+ loc->tipSupport > 0)
+ {
+ count = std::min(count, loc->tipSupport);
+ loc->tipSupport -= count;
+
+ auto const it = seqSupport.find(ledger.seq());
+ assert(it != seqSupport.end() && it->second >= count);
+ it->second -= count;
+ if(it->second == 0)
+ seqSupport.erase(it->first);
+
+ Node* decNode = loc;
+ while (decNode)
+ {
+ decNode->branchSupport -= count;
+ decNode = decNode->parent;
+ }
+
+ while (loc->tipSupport == 0 && loc != root.get())
+ {
+ Node* parent = loc->parent;
+ if (loc->children.empty())
+ {
+ // this node can be erased
+ parent->erase(loc);
+ }
+ else if (loc->children.size() == 1)
+ {
+ // This node can be combined with its child
+ std::unique_ptr child =
+ std::move(loc->children.front());
+ child->span = merge(loc->span, child->span);
+ child->parent = parent;
+ parent->children.emplace_back(std::move(child));
+ parent->erase(loc);
+ }
+ else
+ break;
+ loc = parent;
+ }
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /** Return count of tip support for the specific ledger.
+
+ @param ledger The ledger to lookup
+ @return The number of entries in the trie for this *exact* ledger
+ */
+ std::uint32_t
+ tipSupport(Ledger const& ledger) const
+ {
+ Node const* loc;
+ Seq diffSeq;
+ std::tie(loc, diffSeq) = find(ledger);
+
+ // Exact match
+ if (loc && diffSeq == loc->span.end() && diffSeq > ledger.seq())
+ return loc->tipSupport;
+ return 0;
+ }
+
+ /** Return the count of branch support for the specific ledger
+
+ @param ledger The ledger to lookup
+ @return The number of entries in the trie for this ledger or a descendant
+ */
+ std::uint32_t
+ branchSupport(Ledger const& ledger) const
+ {
+ Node const* loc;
+ Seq diffSeq;
+ std::tie(loc, diffSeq) = find(ledger);
+
+ // Check that ledger is is an exact match or proper
+ // prefix of loc
+ if (loc && diffSeq > ledger.seq() &&
+ ledger.seq() < loc->span.end())
+ {
+ return loc->branchSupport;
+ }
+ return 0;
+ }
+
+ /** Return the preferred ledger ID
+
+ The preferred ledger is used to determine the working ledger
+ for consensus amongst competing alternatives.
+
+ Recall that each validator is normally validating a chain of ledgers,
+ e.g. A->B->C->D. However, if due to network connectivity or other
+ issues, validators generate different chains
+
+ @code
+ /->C
+ A->B
+ \->D->E
+ @endcode
+
+ we need a way for validators to converge on the chain with the most
+ support. We call this the preferred ledger. Intuitively, the idea is to
+ be conservative and only switch to a different branch when you see
+ enough peer validations to *know* another branch won't have preferred
+ support.
+
+ The preferred ledger is found by walking this tree of validated ledgers
+ starting from the common ancestor ledger.
+
+ At each sequence number, we have
+
+ - The prior sequence preferred ledger, e.g. B.
+ - The (tip) support of ledgers with this sequence number,e.g. the
+ number of validators whose last validation was for C or D.
+ - The (branch) total support of all descendants of the current
+ sequence number ledgers, e.g. the branch support of D is the
+ tip support of D plus the tip support of E; the branch support of
+ C is just the tip support of C.
+ - The number of validators that have yet to validate a ledger
+ with this sequence number (uncommitted support). Uncommitted
+ includes all validators whose last sequence number is smaller than
+ our last issued sequence number, since due to asynchrony, we may
+ not have heard from those nodes yet.
+
+ The preferred ledger for this sequence number is then the ledger
+ with relative majority of support, where uncommitted support
+ can be given to ANY ledger at that sequence number
+ (including one not yet known). If no such preferred ledger exists, then
+ the prior sequence preferred ledger is the overall preferred ledger.
+
+ In this example, for D to be preferred, the number of validators
+ supporting it or a descendant must exceed the number of validators
+ supporting C _plus_ the current uncommitted support. This is because if
+ all uncommitted validators end up validating C, that new support must
+ be less than that for D to be preferred.
+
+ If a preferred ledger does exist, then we continue with the next
+ sequence using that ledger as the root.
+
+ @param largestIssued The sequence number of the largest validation
+ issued by this node.
+ @return Pair with the sequence number and ID of the preferred ledger
+ */
+ SpanTip
+ getPreferred(Seq const largestIssued) const
+ {
+ Node* curr = root.get();
+
+ bool done = false;
+
+ std::uint32_t uncommitted = 0;
+ auto uncommittedIt = seqSupport.begin();
+
+ while (curr && !done)
+ {
+ // Within a single span, the preferred by branch strategy is simply
+ // to continue along the span as long as the branch support of
+ // the next ledger exceeds the uncommitted support for that ledger.
+ {
+ // Add any initial uncommitted support prior for ledgers
+ // earlier than nextSeq or earlier than largestIssued
+ Seq nextSeq = curr->span.start() + Seq{1};
+ while (uncommittedIt != seqSupport.end() &&
+ uncommittedIt->first < std::max(nextSeq, largestIssued))
+ {
+ uncommitted += uncommittedIt->second;
+ uncommittedIt++;
+ }
+
+ // Advance nextSeq along the span
+ while (nextSeq < curr->span.end() &&
+ curr->branchSupport > uncommitted)
+ {
+ // Jump to the next seqSupport change
+ if (uncommittedIt != seqSupport.end() &&
+ uncommittedIt->first < curr->span.end())
+ {
+ nextSeq = uncommittedIt->first + Seq{1};
+ uncommitted += uncommittedIt->second;
+ uncommittedIt++;
+ }
+ else // otherwise we jump to the end of the span
+ nextSeq = curr->span.end();
+ }
+ // We did not consume the entire span, so we have found the
+ // preferred ledger
+ if (nextSeq < curr->span.end())
+ return curr->span.before(nextSeq)->tip();
+ }
+
+ // We have reached the end of the current span, so we need to
+ // find the best child
+ Node* best = nullptr;
+ std::uint32_t margin = 0;
+ if (curr->children.size() == 1)
+ {
+ best = curr->children[0].get();
+ margin = best->branchSupport;
+ }
+ else if (!curr->children.empty())
+ {
+ // Sort placing children with largest branch support in the
+ // front, breaking ties with the span's starting ID
+ std::partial_sort(
+ curr->children.begin(),
+ curr->children.begin() + 2,
+ curr->children.end(),
+ [](std::unique_ptr const& a,
+ std::unique_ptr const& b) {
+ return std::make_tuple(a->branchSupport, a->span.startID()) >
+ std::make_tuple(b->branchSupport, b->span.startID());
+ });
+
+ best = curr->children[0].get();
+ margin = curr->children[0]->branchSupport -
+ curr->children[1]->branchSupport;
+
+ // If best holds the tie-breaker, gets one larger margin
+ // since the second best needs additional branchSupport
+ // to overcome the tie
+ if (best->span.startID() > curr->children[1]->span.startID())
+ margin++;
+ }
+
+ // If the best child has margin exceeding the uncommitted support,
+ // continue from that child, otherwise we are done
+ if (best && ((margin > uncommitted) || (uncommitted == 0)))
+ curr = best;
+ else // current is the best
+ done = true;
+ }
+ return curr->span.tip();
+ }
+
+ /** Dump an ascii representation of the trie to the stream
+ */
+ void
+ dump(std::ostream& o) const
+ {
+ dumpImpl(o, root, 0);
+ }
+
+ /** Dump JSON representation of trie state
+ */
+ Json::Value
+ getJson() const
+ {
+ return root->getJson();
+ }
+
+ /** Check the compressed trie and support invariants.
+ */
+ bool
+ checkInvariants() const
+ {
+ std::map expectedSeqSupport;
+
+ std::stack nodes;
+ nodes.push(root.get());
+ while (!nodes.empty())
+ {
+ Node const* curr = nodes.top();
+ nodes.pop();
+ if (!curr)
+ continue;
+
+ // Node with 0 tip support must have multiple children
+ // unless it is the root node
+ if (curr != root.get() && curr->tipSupport == 0 &&
+ curr->children.size() < 2)
+ return false;
+
+ // branchSupport = tipSupport + sum(child->branchSupport)
+ std::size_t support = curr->tipSupport;
+ if (curr->tipSupport != 0)
+ expectedSeqSupport[curr->span.end() - Seq{1}] +=
+ curr->tipSupport;
+
+ for (auto const& child : curr->children)
+ {
+ if(child->parent != curr)
+ return false;
+
+ support += child->branchSupport;
+ nodes.push(child.get());
+ }
+ if (support != curr->branchSupport)
+ return false;
+ }
+ return expectedSeqSupport == seqSupport;
+ }
+};
+
+} // namespace ripple
+#endif
diff --git a/src/ripple/consensus/Validations.h b/src/ripple/consensus/Validations.h
index ad80698e5ab..52a06185266 100644
--- a/src/ripple/consensus/Validations.h
+++ b/src/ripple/consensus/Validations.h
@@ -25,7 +25,7 @@
#include
#include
#include
-#include
+#include
#include
#include
#include
@@ -73,7 +73,49 @@ struct ValidationParms
std::chrono::seconds validationSET_EXPIRES = std::chrono::minutes{10};
};
+/** Enforce validation increasing sequence requirement.
+ Helper class for enforcing that a validation must be larger than all
+ unexpired validation sequence numbers previously issued by the validator
+ tracked by the instance of this class.
+*/
+template
+class SeqEnforcer
+{
+ using time_point = std::chrono::steady_clock::time_point;
+ Seq seq_{0};
+ time_point when_;
+public:
+ /** Try advancing the largest observed validation ledger sequence
+
+ Try setting the largest validation sequence observed, but return false
+ if it violates the invariant that a validation must be larger than all
+ unexpired validation sequence numbers.
+
+ @param now The current time
+ @param s The sequence number we want to validate
+ @param p Validation parameters
+
+ @return Whether the validation satisfies the invariant
+ */
+ bool
+ operator()(time_point now, Seq s, ValidationParms const & p)
+ {
+ if(now > (when_ + p.validationSET_EXPIRES))
+ seq_ = Seq{0};
+ if(s <= seq_)
+ return false;
+ seq_ = s;
+ when_ = now;
+ return true;
+ }
+
+ Seq
+ largest() const
+ {
+ return seq_;
+ }
+};
/** Whether a validation is still current
Determines whether a validation can still be considered the current
@@ -103,35 +145,36 @@ isCurrent(
(seenTime < (now + p.validationCURRENT_LOCAL)));
}
-/** Determine the preferred ledger based on its support
- @param current The current ledger the node follows
- @param dist Ledger IDs and corresponding counts of support
- @return The ID of the ledger with most support, preferring to stick with
- current ledger in the case of equal support
-*/
-template
-inline LedgerID
-getPreferredLedger(
- LedgerID const& current,
- hash_map const& dist)
+/** Status of newly received validation
+ */
+enum class ValStatus {
+ /// This was a new validation and was added
+ current,
+ /// Already had this validation for this ID but different seq
+ repeatID,
+ /// Not current or was older than current from this node
+ stale,
+ /// A validation violates the increasing seq requirement
+ badSeq
+};
+
+inline std::string
+to_string(ValStatus m)
{
- LedgerID netLgr = current;
- int netLgrCount = 0;
- for (auto const& it : dist)
+ switch (m)
{
- // Switch to ledger supported by more peers
- // On a tie, prefer the current ledger, or the one with higher ID
- if ((it.second > netLgrCount) ||
- ((it.second == netLgrCount) &&
- ((it.first == current) ||
- (it.first > netLgr && netLgr != current))))
- {
- netLgr = it.first;
- netLgrCount = it.second;
- }
+ case ValStatus::current:
+ return "current";
+ case ValStatus::repeatID:
+ return "repeatID";
+ case ValStatus::stale:
+ return "stale";
+ case ValStatus::badSeq:
+ return "badSeq";
+ default:
+ return "unknown";
}
- return netLgr;
}
/** Maintains current and recent ledger validations.
@@ -144,28 +187,30 @@ getPreferredLedger(
and implementations should take care to use `trusted` member functions or
check the validation's trusted status.
- This class uses a policy design to allow adapting the handling of stale
- validations in various circumstances. Below is a set of stubs illustrating
- the required type interface.
+ This class uses a generic interface to allow adapting Validations for
+ specific applications. The Adaptor template implements a set of helper
+ functions and type definitions. The code stubs below outline the
+ interface and type requirements.
+
- @warning The MutexType is used to manage concurrent access to private
- members of Validations but does not manage any data in the
- StalePolicy instance.
+ @warning The Adaptor::MutexType is used to manage concurrent access to
+ private members of Validations but does not manage any data in the
+ Adaptor instance itself.
@code
- // Identifier types that should be equality-comparable and copyable
- struct LedgerID;
- struct NodeID;
- struct NodeKey;
+ // Conforms to the Ledger type requirements of LedgerTrie
+ struct Ledger;
struct Validation
{
+ using NodeKey = ...;
+
// Ledger ID associated with this validation
- LedgerID ledgerID() const;
+ Ledger::ID ledgerID() const;
// Sequence number of validation's ledger (0 means no sequence number)
- std::uint32_t seq() const
+ Ledger::Seq seq() const
// When the validation was signed
NetClock::time_point signTime() const;
@@ -176,21 +221,25 @@ getPreferredLedger(
// Signing key of node that published the validation
NodeKey key() const;
- // Identifier of node that published the validation
- NodeID nodeID() const;
-
// Whether the publishing node was trusted at the time the validation
// arrived
bool trusted() const;
+ // Whether this is a full or partial validation
+ bool full() const;
+
implementation_specific_t
unwrap() -> return the implementation-specific type being wrapped
// ... implementation specific
};
- class StalePolicy
+ class Adaptor
{
+ using Mutex = std::mutex;
+ using Validation = Validation;
+ using Ledger = Ledger;
+
// Handle a newly stale validation, this should do minimal work since
// it is called by Validations while it may be iterating Validations
// under lock
@@ -202,74 +251,201 @@ getPreferredLedger(
// Return the current network time (used to determine staleness)
NetClock::time_point now() const;
+ // Attempt to acquire a specific ledger.
+ boost::optional acquire(Ledger::ID const & ledgerID);
+
// ... implementation specific
};
@endcode
- @tparam StalePolicy Determines how to determine and handle stale validations
- @tparam Validation Conforming type representing a ledger validation
- @tparam MutexType Mutex used to manage concurrent access
-
+ @tparam Adaptor Provides type definitions and callbacks
*/
-template
+template
class Validations
{
- template
- using decay_result_t = std::decay_t>;
+ using Mutex = typename Adaptor::Mutex;
+ using Validation = typename Adaptor::Validation;
+ using Ledger = typename Adaptor::Ledger;
+ using ID = typename Ledger::ID;
+ using Seq = typename Ledger::Seq;
+ using NodeKey = typename Validation::NodeKey;
- using WrappedValidationType =
- decay_result_t;
- using LedgerID =
- decay_result_t;
- using NodeKey = decay_result_t;
- using NodeID = decay_result_t;
- using SeqType = decay_result_t;
+ using WrappedValidationType = std::decay_t<
+ std::result_of_t>;
+ using ScopedLock = std::lock_guard;
- using ScopedLock = std::lock_guard;
+ // Manages concurrent access to members
+ mutable Mutex mutex_;
- // Manages concurrent access to current_ and byLedger_
- MutexType mutex_;
+ // Validations from currently listed and trusted nodes (partial and full)
+ hash_map current_;
- //! For the most recent validation, we also want to store the ID
- //! of the ledger it replaces
- struct ValidationAndPrevID
- {
- ValidationAndPrevID(Validation const& v) : val{v}, prevLedgerID{0}
- {
- }
-
- Validation val;
- LedgerID prevLedgerID;
- };
+ // Used to enforce the largest validation invariant for the local node
+ SeqEnforcer localSeqEnforcer_;
- //! The latest validation from each node
- hash_map current_;
+ // Sequence of the largest validation received from each node
+ hash_map> seqEnforcers_;
- //! Recent validations from nodes, indexed by ledger identifier
+ //! Validations from listed nodes, indexed by ledger id (partial and full)
beast::aged_unordered_map<
- LedgerID,
+ ID,
hash_map,
std::chrono::steady_clock,
beast::uhash<>>
byLedger_;
- //! Parameters to determine validation staleness
- ValidationParms const parms_;
+ // Represents the ancestry of validated ledgers
+ LedgerTrie trie_;
+
+ // Last (validated) ledger successfully acquired. If in this map, it is
+ // accounted for in the trie.
+ hash_map lastLedger_;
- beast::Journal j_;
+ // Set of ledgers being acquired from the network
+ hash_map, hash_set> acquiring_;
- //! StalePolicy details providing now(), onStale() and flush() callbacks
- //! Is NOT managed by the mutex_ above
- StalePolicy stalePolicy_;
+ // Parameters to determine validation staleness
+ ValidationParms const parms_;
+
+ // Adaptor instance
+ // Is NOT managed by the mutex_ above
+ Adaptor adaptor_;
private:
+ // Remove support of a validated ledger
+ void
+ removeTrie(ScopedLock const&, NodeKey const& key, Validation const& val)
+ {
+ {
+ auto it = acquiring_.find(std::make_pair(val.seq(), val.ledgerID()));
+ if (it != acquiring_.end())
+ {
+ it->second.erase(key);
+ if (it->second.empty())
+ acquiring_.erase(it);
+ }
+ }
+ {
+ auto it = lastLedger_.find(key);
+ if (it != lastLedger_.end() && it->second.id() == val.ledgerID())
+ {
+ trie_.remove(it->second);
+ lastLedger_.erase(key);
+ }
+ }
+ }
+
+ // Check if any pending acquire ledger requests are complete
+ void
+ checkAcquired(ScopedLock const& lock)
+ {
+ for (auto it = acquiring_.begin(); it != acquiring_.end();)
+ {
+ if (boost::optional ledger =
+ adaptor_.acquire(it->first.second))
+ {
+ for (NodeKey const& key : it->second)
+ updateTrie(lock, key, *ledger);
+
+ it = acquiring_.erase(it);
+ }
+ else
+ ++it;
+ }
+ }
+
+ // Update the trie to reflect a new validated ledger
+ void
+ updateTrie(ScopedLock const&, NodeKey const& key, Ledger ledger)
+ {
+ auto ins = lastLedger_.emplace(key, ledger);
+ if (!ins.second)
+ {
+ trie_.remove(ins.first->second);
+ ins.first->second = ledger;
+ }
+ trie_.insert(ledger);
+ }
+
+ /** Process a new validation
+
+ Process a new trusted validation from a validator. This will be
+ reflected only after the validated ledger is successfully acquired by
+ the local node. In the interim, the prior validated ledger from this
+ node remains.
+
+ @param lock Existing lock of mutex_
+ @param key The master public key identifying the validating node
+ @param val The trusted validation issued by the node
+ @param prior If not none, the last current validated ledger Seq,ID of key
+ */
+ void
+ updateTrie(
+ ScopedLock const& lock,
+ NodeKey const& key,
+ Validation const& val,
+ boost::optional> prior)
+ {
+ assert(val.trusted());
+
+ // Clear any prior acquiring ledger for this node
+ if (prior)
+ {
+ auto it = acquiring_.find(*prior);
+ if (it != acquiring_.end())
+ {
+ it->second.erase(key);
+ if (it->second.empty())
+ acquiring_.erase(it);
+ }
+ }
+
+ checkAcquired(lock);
+
+ std::pair valPair{val.seq(),val.ledgerID()};
+ auto it = acquiring_.find(valPair);
+ if(it != acquiring_.end())
+ {
+ it->second.insert(key);
+ }
+ else
+ {
+ if (boost::optional ledger = adaptor_.acquire(val.ledgerID()))
+ updateTrie(lock, key, *ledger);
+ else
+ acquiring_[valPair].insert(key);
+ }
+
+ }
+
+ /** Use the trie for a calculation
+
+ Accessing the trie through this helper ensures acquiring validations
+ are checked and any stale validations are flushed from the trie.
+
+ @param lock Existing lock of mutex_
+ @param f Invokable with signature (LedgerTrie &)
+
+ @warning The invokable `f` is expected to be a simple transformation of
+ its arguments and will be called with mutex_ under lock.
+
+ */
+ template
+ auto
+ withTrie(ScopedLock const& lock, F&& f)
+ {
+ // Call current to flush any stale validations
+ current(lock, [](auto){}, [](auto, auto){});
+ checkAcquired(lock);
+ return f(trie_);
+ }
+
/** Iterate current validations.
- Iterate current validations, optionally removing any stale validations
- if a time is specified.
+ Iterate current validations, flushing any which are stale.
- @param t (Optional) Time used to determine staleness
+ @param lock Existing lock of mutex_
@param pre Invokable with signature (std::size_t) called prior to
looping.
@param f Invokable with signature (NodeKey const &, Validations const &)
@@ -283,20 +459,19 @@ class Validations
template
void
- current(boost::optional t, Pre&& pre, F&& f)
+ current(ScopedLock const& lock, Pre&& pre, F&& f)
{
- ScopedLock lock{mutex_};
+ NetClock::time_point t = adaptor_.now();
pre(current_.size());
auto it = current_.begin();
while (it != current_.end())
{
- // Check for staleness, if time specified
- if (t &&
- !isCurrent(
- parms_, *t, it->second.val.signTime(), it->second.val.seenTime()))
+ // Check for staleness
+ if (!isCurrent(
+ parms_, t, it->second.signTime(), it->second.seenTime()))
{
- // contains a stale record
- stalePolicy_.onStale(std::move(it->second.val));
+ removeTrie(lock, it->first, it->second);
+ adaptor_.onStale(std::move(it->second));
it = current_.erase(it);
}
else
@@ -311,6 +486,7 @@ class Validations
/** Iterate the set of validations associated with a given ledger id
+ @param lock Existing lock on mutex_
@param ledgerID The identifier of the ledger
@param pre Invokable with signature(std::size_t)
@param f Invokable with signature (NodeKey const &, Validation const &)
@@ -322,9 +498,8 @@ class Validations
*/
template
void
- byLedger(LedgerID const& ledgerID, Pre&& pre, F&& f)
+ byLedger(ScopedLock const&, ID const& ledgerID, Pre&& pre, F&& f)
{
- ScopedLock lock{mutex_};
auto it = byLedger_.find(ledgerID);
if (it != byLedger_.end())
{
@@ -341,17 +516,23 @@ class Validations
@param p ValidationParms to control staleness/expiration of validaitons
@param c Clock to use for expiring validations stored by ledger
- @param j Journal used for logging
- @param ts Parameters for constructing StalePolicy instance
+ @param ts Parameters for constructing Adaptor instance
*/
template
Validations(
ValidationParms const& p,
beast::abstract_clock& c,
- beast::Journal j,
Ts&&... ts)
- : byLedger_(c), parms_(p), j_(j), stalePolicy_(std::forward(ts)...)
+ : byLedger_(c), parms_(p), adaptor_(std::forward(ts)...)
+ {
+ }
+
+ /** Return the adaptor instance
+ */
+ Adaptor const &
+ adaptor() const
{
+ return adaptor_;
}
/** Return the validation timing parameters
@@ -362,148 +543,75 @@ class Validations
return parms_;
}
- /** Return the journal
- */
- beast::Journal
- journal() const
+ /** Return whether the local node can issue a validation for the given sequence
+ number
+
+ @param s The sequence number of the ledger the node wants to validate
+ @return Whether the validation satisfies the invariant, updating the
+ largest sequence number seen accordingly
+ */
+ bool
+ canValidateSeq(Seq const s)
{
- return j_;
+ ScopedLock lock{mutex_};
+ return localSeqEnforcer_(byLedger_.clock().now(), s, parms_);
}
- /** Result of adding a new validation
- */
- enum class AddOutcome {
- /// This was a new validation and was added
- current,
- /// Already had this validation
- repeat,
- /// Not current or was older than current from this node
- stale,
- /// Had a validation with same sequence number
- sameSeq,
- };
-
/** Add a new validation
Attempt to add a new validation.
- @param key The NodeKey to use for the validation
+ @param key The master key associated with this validation
@param val The validation to store
- @return The outcome of the attempt
-
- @note The provided key may differ from the validation's
- key() member since we might be storing by master key and the
- validation might be signed by a temporary or rotating key.
+ @return The outcome
+ @note The provided key may differ from the validation's key()
+ member if the validator is using ephemeral signing keys.
*/
- AddOutcome
+ ValStatus
add(NodeKey const& key, Validation const& val)
{
- NetClock::time_point t = stalePolicy_.now();
- if (!isCurrent(parms_, t, val.signTime(), val.seenTime()))
- return AddOutcome::stale;
-
- LedgerID const& id = val.ledgerID();
-
- // This is only seated if a validation became stale
- boost::optional maybeStaleValidation;
-
- AddOutcome result = AddOutcome::current;
+ if (!isCurrent(parms_, adaptor_.now(), val.signTime(), val.seenTime()))
+ return ValStatus::stale;
{
ScopedLock lock{mutex_};
- auto const ret = byLedger_[id].emplace(key, val);
+ // Check that validation sequence is greater than any non-expired
+ // validations sequence from that validator
+ auto const now = byLedger_.clock().now();
+ SeqEnforcer& enforcer = seqEnforcers_[key];
+ if (!enforcer(now, val.seq(), parms_))
+ return ValStatus::badSeq;
// This validation is a repeat if we already have
- // one with the same id and signing key.
+ // one with the same id for this key
+ auto const ret = byLedger_[val.ledgerID()].emplace(key, val);
if (!ret.second && ret.first->second.key() == val.key())
- return AddOutcome::repeat;
+ return ValStatus::repeatID;
- // Attempt to insert
auto const ins = current_.emplace(key, val);
-
if (!ins.second)
{
- // Had a previous validation from the node, consider updating
- Validation& oldVal = ins.first->second.val;
- LedgerID const previousLedgerID = ins.first->second.prevLedgerID;
-
- SeqType const oldSeq{oldVal.seq()};
- SeqType const newSeq{val.seq()};
-
- // Sequence of 0 indicates a missing sequence number
- if ((oldSeq != SeqType{0}) && (newSeq != SeqType{0}) &&
- oldSeq == newSeq)
+ // Replace existing only if this one is newer
+ Validation& oldVal = ins.first->second;
+ if (val.signTime() > oldVal.signTime())
{
- result = AddOutcome::sameSeq;
-
- // If the validation key was revoked, update the
- // existing validation in the byLedger_ set
- if (val.key() != oldVal.key())
- {
- auto const mapIt = byLedger_.find(oldVal.ledgerID());
- if (mapIt != byLedger_.end())
- {
- auto& validationMap = mapIt->second;
- // If a new validation with the same ID was
- // reissued we simply replace.
- if(oldVal.ledgerID() == val.ledgerID())
- {
- auto replaceRes = validationMap.emplace(key, val);
- // If it was already there, replace
- if(!replaceRes.second)
- replaceRes.first->second = val;
- }
- else
- {
- // If the new validation has a different ID,
- // we remove the old.
- validationMap.erase(key);
- // Erase the set if it is now empty
- if (validationMap.empty())
- byLedger_.erase(mapIt);
- }
- }
- }
- }
-
- if (val.signTime() > oldVal.signTime() ||
- val.key() != oldVal.key())
- {
- // This is either a newer validation or a new signing key
- LedgerID const prevID = [&]() {
- // In the normal case, the prevID is the ID of the
- // ledger we replace
- if (oldVal.ledgerID() != val.ledgerID())
- return oldVal.ledgerID();
- // In the case the key was revoked and a new validation
- // for the same ledger ID was sent, the previous ledger
- // is still the one the now revoked validation had
- return previousLedgerID;
- }();
-
- // Allow impl to take over oldVal
- maybeStaleValidation.emplace(std::move(oldVal));
- // Replace old val in the map and set the previous ledger ID
- ins.first->second.val = val;
- ins.first->second.prevLedgerID = prevID;
+ std::pair old(oldVal.seq(),oldVal.ledgerID());
+ adaptor_.onStale(std::move(oldVal));
+ ins.first->second = val;
+ if (val.trusted())
+ updateTrie(lock, key, val, old);
}
else
- {
- // We already have a newer validation from this source
- result = AddOutcome::stale;
- }
+ return ValStatus::stale;
+ }
+ else if (val.trusted())
+ {
+ updateTrie(lock, key, val, boost::none);
}
}
-
- // Handle the newly stale validation outside the lock
- if (maybeStaleValidation)
- {
- stalePolicy_.onStale(std::move(*maybeStaleValidation));
- }
-
- return result;
+ return ValStatus::current;
}
/** Expire old validation sets
@@ -518,103 +626,173 @@ class Validations
beast::expire(byLedger_, parms_.validationSET_EXPIRES);
}
- /** Distribution of current trusted validations
+ Json::Value
+ getJsonTrie() const
+ {
+ ScopedLock lock{mutex_};
+ return trie_.getJson();
+ }
+
+ /** Return the sequence number and ID of the preferred working ledger
- Calculates the distribution of current validations but allows
- ledgers one away from the current ledger to count as the current.
+ A ledger is preferred if it has more support amongst trusted validators
+ and is *not* an ancestor of the current working ledger; otherwise it
+ remains the current working ledger.
- @param currentLedger The identifier of the ledger we believe is current
- (0 if unknown)
- @param priorLedger The identifier of our previous current ledger
- (0 if unknown)
- @param cutoffBefore Ignore ledgers with sequence number before this
+ @param curr The local node's current working ledger
- @return Map representing the distribution of ledgerID by count
+ @return The sequence and id of the preferred working ledger,
+ or Seq{0},ID{0} if no trusted validations are available to
+ determine the preferred ledger.
*/
- hash_map
- currentTrustedDistribution(
- LedgerID const& currentLedger,
- LedgerID const& priorLedger,
- SeqType cutoffBefore)
+ std::pair
+ getPreferred(Ledger const& curr)
{
- bool const valCurrentLedger = currentLedger != LedgerID{0};
- bool const valPriorLedger = priorLedger != LedgerID{0};
+ ScopedLock lock{mutex_};
+ SpanTip preferred =
+ withTrie(lock, [this](LedgerTrie& trie) {
+ return trie.getPreferred(localSeqEnforcer_.largest());
+ });
- hash_map ret;
+ // No trusted validations to determine branch
+ if (preferred.seq == Seq{0})
+ {
+ // fall back to majority over acquiring ledgers
+ auto it = std::max_element(
+ acquiring_.begin(),
+ acquiring_.end(),
+ [](auto const& a, auto const& b) {
+ std::pair const& aKey = a.first;
+ typename hash_set::size_type const& aSize =
+ a.second.size();
+ std::pair const& bKey = b.first;
+ typename hash_set::size_type const& bSize =
+ b.second.size();
+ // order by number of trusted peers validating that ledger
+ // break ties with ledger ID
+ return std::tie(aSize, aKey.second) <
+ std::tie(bSize, bKey.second);
+ });
+ if(it != acquiring_.end())
+ return it->first;
+ return std::make_pair(preferred.seq, preferred.id);
+ }
- current(
- stalePolicy_.now(),
- // The number of validations does not correspond to the number of
- // distinct ledgerIDs so we do not call reserve on ret.
- [](std::size_t) {},
- [this,
- &cutoffBefore,
- ¤tLedger,
- &valCurrentLedger,
- &valPriorLedger,
- &priorLedger,
- &ret](NodeKey const&, ValidationAndPrevID const& vp) {
- Validation const& v = vp.val;
- LedgerID const& prevLedgerID = vp.prevLedgerID;
- if (!v.trusted())
- return;
-
- SeqType const seq = v.seq();
- if ((seq == SeqType{0}) || (seq >= cutoffBefore))
- {
- // contains a live record
- bool countPreferred =
- valCurrentLedger && (v.ledgerID() == currentLedger);
-
- if (!countPreferred && // allow up to one ledger slip in
- // either direction
- ((valCurrentLedger &&
- (prevLedgerID == currentLedger)) ||
- (valPriorLedger && (v.ledgerID() == priorLedger))))
- {
- countPreferred = true;
- JLOG(this->j_.trace()) << "Counting for " << currentLedger
- << " not " << v.ledgerID();
- }
-
- if (countPreferred)
- ret[currentLedger]++;
- else
- ret[v.ledgerID()]++;
- }
+ // If we are the parent of the preferred ledger, stick with our
+ // current ledger since we might be about to generate it
+ if (preferred.seq == curr.seq() + Seq{1} &&
+ preferred.ancestor(curr.seq()) == curr.id())
+ return std::make_pair(curr.seq(), curr.id());
+
+ // A ledger ahead of us is preferred regardless of whether it is
+ // a descendant of our working ledger or it is on a different chain
+ if (preferred.seq > curr.seq())
+ return std::make_pair(preferred.seq, preferred.id);
+
+ // Only switch to earlier or same sequence number
+ // if it is a different chain.
+ if (curr[preferred.seq] != preferred.id)
+ return std::make_pair(preferred.seq, preferred.id);
+
+ // Stick with current ledger
+ return std::make_pair(curr.seq(), curr.id());
+ }
+
+ /** Get the ID of the preferred working ledger that exceeds a minimum valid
+ ledger sequence number
+
+ @param curr Current working ledger
+ @param minValidSeq Minimum allowed sequence number
+
+ @return ID Of the preferred ledger, or curr if the preferred ledger
+ is not valid
+ */
+ ID
+ getPreferred(Ledger const& curr, Seq minValidSeq)
+ {
+ std::pair preferred = getPreferred(curr);
+ if(preferred.first >= minValidSeq && preferred.second != ID{0})
+ return preferred.second;
+ return curr.id();
+
+ }
+
+
+ /** Determine the preferred last closed ledger for the next consensus round.
+
+ Called before starting the next round of ledger consensus to determine the
+ preferred working ledger. Uses the dominant peerCount ledger if no
+ trusted validations are available.
+
+ @param lcl Last closed ledger by this node
+ @param minSeq Minimum allowed sequence number of the trusted preferred ledger
+ @param peerCounts Map from ledger ids to count of peers with that as the
+ last closed ledger
+ @return The preferred last closed ledger ID
+
+ @note The minSeq does not apply to the peerCounts, since this function
+ does not know their sequence number
+ */
+ ID
+ getPreferredLCL(
+ Ledger const & lcl,
+ Seq minSeq,
+ hash_map const& peerCounts)
+ {
+ std::pair preferred = getPreferred(lcl);
+
+ // Trusted validations exist
+ if (preferred.second != ID{0} && preferred.first > Seq{0})
+ return (preferred.first >= minSeq) ? preferred.second : lcl.id();
+
+ // Otherwise, rely on peer ledgers
+ auto it = std::max_element(
+ peerCounts.begin(), peerCounts.end(), [](auto& a, auto& b) {
+ // Prefer larger counts, then larger ids on ties
+ // (max_element expects this to return true if a < b)
+ return std::tie(a.second, a.first) <
+ std::tie(b.second, b.first);
});
- return ret;
+ if (it != peerCounts.end())
+ return it->first;
+ return lcl.id();
}
- /** Count the number of current trusted validators working on the next
- ledger.
+ /** Count the number of current trusted validators working on a ledger
+ after the specified one.
- Counts the number of current trusted validations that replaced the
- provided ledger. Does not check or update staleness of the validations.
+ @param ledger The working ledger
+ @param ledgerID The preferred ledger
+ @return The number of current trusted validators working on a descendent
+ of the preferred ledger
- @param ledgerID The identifier of the preceding ledger of interest
- @return The number of current trusted validators with ledgerID as the
- prior ledger.
+ @note If ledger.id() != ledgerID, only counts immediate child ledgers of
+ ledgerID
*/
std::size_t
- getNodesAfter(LedgerID const& ledgerID)
+ getNodesAfter(Ledger const& ledger, ID const& ledgerID)
{
- std::size_t count = 0;
+ ScopedLock lock{mutex_};
- // Historically this did not not check for stale validations
- // That may not be important, but this preserves the behavior
- current(
- boost::none,
- [&](std::size_t) {}, // nothing to reserve
- [&](NodeKey const&, ValidationAndPrevID const& v) {
- if (v.val.trusted() && v.prevLedgerID == ledgerID)
- ++count;
+ // Use trie if ledger is the right one
+ if (ledger.id() == ledgerID)
+ return withTrie(lock, [&ledger](LedgerTrie& trie) {
+ return trie.branchSupport(ledger) - trie.tipSupport(ledger);
+ });
+
+ // Count parent ledgers as fallback
+ return std::count_if(
+ lastLedger_.begin(),
+ lastLedger_.end(),
+ [&ledgerID](auto const& it) {
+ auto const& curr = it.second;
+ return curr.seq() > Seq{0} &&
+ curr[curr.seq() - Seq{1}] == ledgerID;
});
- return count;
}
- /** Get the currently trusted validations
+ /** Get the currently trusted full validations
@return Vector of validations from currently trusted validators
*/
@@ -622,107 +800,115 @@ class Validations
currentTrusted()
{
std::vector ret;
-
+ ScopedLock lock{mutex_};
current(
- stalePolicy_.now(),
+ lock,
[&](std::size_t numValidations) { ret.reserve(numValidations); },
- [&](NodeKey const&, ValidationAndPrevID const& v) {
- if (v.val.trusted())
- ret.push_back(v.val.unwrap());
+ [&](NodeKey const&, Validation const& v) {
+ if (v.trusted() && v.full())
+ ret.push_back(v.unwrap());
});
return ret;
}
/** Get the set of known public keys associated with current validations
- @return The set of of knowns keys for current trusted and untrusted
- validations
+ @return The set of known keys for current listed validators
*/
hash_set
getCurrentPublicKeys()
{
hash_set ret;
+ ScopedLock lock{mutex_};
current(
- stalePolicy_.now(),
+ lock,
[&](std::size_t numValidations) { ret.reserve(numValidations); },
- [&](NodeKey const& k, ValidationAndPrevID const&) { ret.insert(k); });
+ [&](NodeKey const& k, Validation const&) { ret.insert(k); });
return ret;
}
- /** Count the number of trusted validations for the given ledger
+ /** Count the number of trusted full validations for the given ledger
@param ledgerID The identifier of ledger of interest
@return The number of trusted validations
*/
std::size_t
- numTrustedForLedger(LedgerID const& ledgerID)
+ numTrustedForLedger(ID const& ledgerID)
{
std::size_t count = 0;
+ ScopedLock lock{mutex_};
byLedger(
+ lock,
ledgerID,
- [&](std::size_t) {}, // nothing to reserve
+ [&](std::size_t) {}, // nothing to reserve
[&](NodeKey const&, Validation const& v) {
- if (v.trusted())
+ if (v.trusted() && v.full())
++count;
});
return count;
}
- /** Get set of trusted validations associated with a given ledger
+ /** Get trusted full validations for a specific ledger
@param ledgerID The identifier of ledger of interest
@return Trusted validations associated with ledger
*/
std::vector
- getTrustedForLedger(LedgerID const& ledgerID)
+ getTrustedForLedger(ID const& ledgerID)
{
std::vector res;
+ ScopedLock lock{mutex_};
byLedger(
+ lock,
ledgerID,
[&](std::size_t numValidations) { res.reserve(numValidations); },
[&](NodeKey const&, Validation const& v) {
- if (v.trusted())
+ if (v.trusted() && v.full())
res.emplace_back(v.unwrap());
});
return res;
}
- /** Return the sign times of all validations associated with a given ledger
+ /** Return the sign times of all trusted full validations
@param ledgerID The identifier of ledger of interest
@return Vector of times
*/
std::vector
- getTrustedValidationTimes(LedgerID const& ledgerID)
+ getTrustedValidationTimes(ID const& ledgerID)
{
std::vector times;
+ ScopedLock lock{mutex_};
byLedger(
+ lock,
ledgerID,
[&](std::size_t numValidations) { times.reserve(numValidations); },
[&](NodeKey const&, Validation const& v) {
- if (v.trusted())
+ if (v.trusted() && v.full())
times.emplace_back(v.signTime());
});
return times;
}
- /** Returns fees reported by trusted validators in the given ledger
+ /** Returns fees reported by trusted full validators in the given ledger
@param ledgerID The identifier of ledger of interest
@param baseFee The fee to report if not present in the validation
@return Vector of fees
*/
std::vector
- fees(LedgerID const& ledgerID, std::uint32_t baseFee)
+ fees(ID const& ledgerID, std::uint32_t baseFee)
{
std::vector res;
+ ScopedLock lock{mutex_};
byLedger(
+ lock,
ledgerID,
[&](std::size_t numValidations) { res.reserve(numValidations); },
[&](NodeKey const&, Validation const& v) {
- if (v.trusted())
+ if (v.trusted() && v.full())
{
boost::optional loadFee = v.loadFee();
if (loadFee)
@@ -739,22 +925,19 @@ class Validations
void
flush()
{
- JLOG(j_.info()) << "Flushing validations";
-
hash_map flushed;
{
ScopedLock lock{mutex_};
for (auto it : current_)
{
- flushed.emplace(it.first, std::move(it.second.val));
+ flushed.emplace(it.first, std::move(it.second));
}
current_.clear();
}
- stalePolicy_.flush(std::move(flushed));
-
- JLOG(j_.debug()) << "Validations flushed";
+ adaptor_.flush(std::move(flushed));
}
};
+
} // namespace ripple
#endif
diff --git a/src/test/app/RCLValidations_test.cpp b/src/test/app/RCLValidations_test.cpp
new file mode 100644
index 00000000000..0feeb2b7a80
--- /dev/null
+++ b/src/test/app/RCLValidations_test.cpp
@@ -0,0 +1,204 @@
+//------------------------------------------------------------------------------
+/*
+ This file is part of rippled: https://github.com/ripple/rippled
+ Copyright 2017 Ripple Labs Inc.
+
+ Permission to use, copy, modify, and/or distribute this software for any
+ purpose with or without fee is hereby granted, provided that the above
+ copyright notice and this permission notice appear in all copies.
+
+ THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+*/
+//==============================================================================
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+namespace ripple {
+namespace test {
+
+class RCLValidations_test : public beast::unit_test::suite
+{
+
+public:
+ void
+ run() override
+ {
+ beast::Journal j;
+
+ using Seq = RCLValidatedLedger::Seq;
+ using ID = RCLValidatedLedger::ID;
+
+ // This tests RCLValidatedLedger properly implements the type
+ // requirements of a LedgerTrie ledger, with its added behavior that
+ // only the 256 prior ledger hashes are available to determine ancestry.
+ Seq const maxAncestors = 256;
+
+ //----------------------------------------------------------------------
+ // Generate two ledger histories that agree on the first maxAncestors
+ // ledgers, then diverge.
+
+ std::vector> history;
+
+ jtx::Env env(*this);
+ Config config;
+ auto prev = std::make_shared(
+ create_genesis, config,
+ std::vector{}, env.app().family());
+ history.push_back(prev);
+ for (auto i = 0; i < (2*maxAncestors + 1); ++i)
+ {
+ auto next = std::make_shared(
+ *prev,
+ env.app().timeKeeper().closeTime());
+ next->updateSkipList();
+ history.push_back(next);
+ prev = next;
+ }
+
+ // altHistory agrees with first half of regular history
+ Seq const diverge = history.size()/2;
+ std::vector> altHistory(
+ history.begin(), history.begin() + diverge);
+ // advance clock to get new ledgers
+ env.timeKeeper().set(env.timeKeeper().now() + 1200s);
+ prev = altHistory.back();
+ bool forceHash = true;
+ while(altHistory.size() < history.size())
+ {
+ auto next = std::make_shared(
+ *prev,
+ env.app().timeKeeper().closeTime());
+ // Force a different hash on the first iteration
+ next->updateSkipList();
+ if(forceHash)
+ {
+ next->setImmutable(config);
+ forceHash = false;
+ }
+
+ altHistory.push_back(next);
+ prev = next;
+ }
+
+ //----------------------------------------------------------------------
+
+
+ // Empty ledger
+ {
+ RCLValidatedLedger a{RCLValidatedLedger::MakeGenesis{}};
+ BEAST_EXPECT(a.seq() == Seq{0});
+ BEAST_EXPECT(a[Seq{0}] == ID{0});
+ BEAST_EXPECT(a.minSeq() == Seq{0});
+ }
+
+ // Full history ledgers
+ {
+ std::shared_ptr ledger = history.back();
+ RCLValidatedLedger a{ledger, j};
+ BEAST_EXPECT(a.seq() == ledger->info().seq);
+ BEAST_EXPECT(
+ a.minSeq() == a.seq() - maxAncestors);
+ // Ensure the ancestral 256 ledgers have proper ID
+ for(Seq s = a.seq(); s > 0; s--)
+ {
+ if(s >= a.minSeq())
+ BEAST_EXPECT(a[s] == history[s-1]->info().hash);
+ else
+ BEAST_EXPECT(a[s] == ID{0});
+ }
+ }
+
+ // Mismatch tests
+
+ // Empty with non-empty
+ {
+ RCLValidatedLedger a{RCLValidatedLedger::MakeGenesis{}};
+
+ for (auto ledger : {history.back(),
+ history[maxAncestors - 1]})
+ {
+ RCLValidatedLedger b{ledger, j};
+ BEAST_EXPECT(mismatch(a, b) == 1);
+ BEAST_EXPECT(mismatch(b, a) == 1);
+ }
+ }
+ // Same chains, different seqs
+ {
+ RCLValidatedLedger a{history.back(), j};
+ for(Seq s = a.seq(); s > 0; s--)
+ {
+ RCLValidatedLedger b{history[s-1], j};
+ if(s >= a.minSeq())
+ {
+ BEAST_EXPECT(mismatch(a, b) == b.seq() + 1);
+ BEAST_EXPECT(mismatch(b, a) == b.seq() + 1);
+ }
+ else
+ {
+ BEAST_EXPECT(mismatch(a, b) == Seq{1});
+ BEAST_EXPECT(mismatch(b, a) == Seq{1});
+ }
+ }
+
+ }
+ // Different chains, same seqs
+ {
+ // Alt history diverged at history.size()/2
+ for(Seq s = 1; s < history.size(); ++s)
+ {
+ RCLValidatedLedger a{history[s-1], j};
+ RCLValidatedLedger b{altHistory[s-1], j};
+
+ BEAST_EXPECT(a.seq() == b.seq());
+ if(s <= diverge)
+ {
+ BEAST_EXPECT(a[a.seq()] == b[b.seq()]);
+ BEAST_EXPECT(mismatch(a,b) == a.seq() + 1);
+ BEAST_EXPECT(mismatch(b,a) == a.seq() + 1);
+ }
+ else
+ {
+ BEAST_EXPECT(a[a.seq()] != b[b.seq()]);
+ BEAST_EXPECT(mismatch(a,b) == diverge + 1);
+ BEAST_EXPECT(mismatch(b,a) == diverge + 1);
+ }
+ }
+ }
+ // Different chains, different seqs
+ {
+ // Compare around the divergence point
+ RCLValidatedLedger a{history[diverge], j};
+ for(Seq offset = diverge/2; offset < 3*diverge/2; ++offset)
+ {
+ RCLValidatedLedger b{altHistory[offset-1], j};
+ if(offset <= diverge)
+ {
+ BEAST_EXPECT(mismatch(a,b) == b.seq() + 1);
+ }
+ else
+ {
+ BEAST_EXPECT(mismatch(a,b) == diverge + 1);
+ }
+ }
+ }
+
+
+ }
+};
+
+BEAST_DEFINE_TESTSUITE(RCLValidations, app, ripple);
+
+} // namespace test
+} // namespace ripple
diff --git a/src/test/consensus/Consensus_test.cpp b/src/test/consensus/Consensus_test.cpp
index fb31ed4e3c8..93bf57bd076 100644
--- a/src/test/consensus/Consensus_test.cpp
+++ b/src/test/consensus/Consensus_test.cpp
@@ -512,8 +512,7 @@ class Consensus_test : public beast::unit_test::suite
peerJumps.closeJumps.front();
// Jump is to a different chain
BEAST_EXPECT(jump.from.seq() <= jump.to.seq());
- BEAST_EXPECT(
- !sim.oracle.isAncestor(jump.from, jump.to));
+ BEAST_EXPECT(!jump.to.isAncestor(jump.from));
}
}
// fully validated jump forward in same chain
@@ -525,8 +524,7 @@ class Consensus_test : public beast::unit_test::suite
peerJumps.fullyValidatedJumps.front();
// Jump is to a different chain with same seq
BEAST_EXPECT(jump.from.seq() < jump.to.seq());
- BEAST_EXPECT(
- sim.oracle.isAncestor(jump.from, jump.to));
+ BEAST_EXPECT(jump.to.isAncestor(jump.from));
}
}
}
@@ -825,6 +823,168 @@ class Consensus_test : public beast::unit_test::suite
BEAST_EXPECT(sim.synchronized());
}
+
+ // Helper collector for testPreferredByBranch
+ // Invasively disconnects network at bad times to cause splits
+ struct Disruptor
+ {
+ csf::PeerGroup& network;
+ csf::PeerGroup& groupCfast;
+ csf::PeerGroup& groupCsplit;
+ csf::SimDuration delay;
+ bool reconnected = false;
+
+ Disruptor(
+ csf::PeerGroup& net,
+ csf::PeerGroup& c,
+ csf::PeerGroup& split,
+ csf::SimDuration d)
+ : network(net), groupCfast(c), groupCsplit(split), delay(d)
+ {
+ }
+
+ template
+ void
+ on(csf::PeerID, csf::SimTime, E const&)
+ {
+ }
+
+
+ void
+ on(csf::PeerID who, csf::SimTime, csf::FullyValidateLedger const& e)
+ {
+ using namespace std::chrono;
+ // As soon as the the fastC node fully validates C, disconnect
+ // ALL c nodes from the network. The fast C node needs to disconnect
+ // as well to prevent it from relaying the validations it did see
+ if (who == groupCfast[0]->id &&
+ e.ledger.seq() == csf::Ledger::Seq{2})
+ {
+ network.disconnect(groupCsplit);
+ network.disconnect(groupCfast);
+ }
+ }
+
+ void
+ on(csf::PeerID who, csf::SimTime, csf::AcceptLedger const& e)
+ {
+ // As soon as anyone generates a child of B or C, reconnect the
+ // network so those validations make it through
+ if (!reconnected && e.ledger.seq() == csf::Ledger::Seq{3})
+ {
+ reconnected = true;
+ network.connect(groupCsplit, delay);
+ }
+ }
+
+
+ };
+
+ void
+ testPreferredByBranch()
+ {
+ using namespace csf;
+ using namespace std::chrono;
+
+ // Simulate network splits that are prevented from forking when using
+ // preferred ledger by trie. This is a contrived example that involves
+ // excessive network splits, but demonstrates the safety improvement
+ // from the preferred ledger by trie approach.
+
+ // Consider 10 validating nodes that comprise a single common UNL
+ // Ledger history:
+ // 1: A
+ // _/ \_
+ // 2: B C
+ // _/ _/ \_
+ // 3: D C' |||||||| (8 different ledgers)
+
+ // - All nodes generate the common ledger A
+ // - 2 nodes generate B and 8 nodes generate C
+ // - Only 1 of the C nodes sees all the C validations and fully
+ // validates C. The rest of the C nodes split at just the right time
+ // such that they never see any C validations but their own.
+ // - The C nodes continue and generate 8 different child ledgers.
+ // - Meanwhile, the D nodes only saw 1 validation for C and 2 validations
+ // for B.
+ // - The network reconnects and the validations for generation 3 ledgers
+ // are observed (D and the 8 C's)
+ // - In the old approach, 2 votes for D outweights 1 vote for each C'
+ // so the network would avalanche towards D and fully validate it
+ // EVEN though C was fully validated by one node
+ // - In the new approach, 2 votes for D are not enough to outweight the
+ // 8 implicit votes for C, so nodes will avalanche to C instead
+
+
+ ConsensusParms const parms{};
+ Sim sim;
+
+ // Goes A->B->D
+ PeerGroup groupABD = sim.createGroup(2);
+ // Single node that initially fully validates C before the split
+ PeerGroup groupCfast = sim.createGroup(1);
+ // Generates C, but fails to fully validate before the split
+ PeerGroup groupCsplit = sim.createGroup(7);
+
+ PeerGroup groupNotFastC = groupABD + groupCsplit;
+ PeerGroup network = groupABD + groupCsplit + groupCfast;
+
+ SimDuration delay = round(0.2 * parms.ledgerGRANULARITY);
+ SimDuration fDelay = round(0.1 * parms.ledgerGRANULARITY);
+
+ network.trust(network);
+ // C must have a shorter delay to see all the validations before the
+ // other nodes
+ network.connect(groupCfast, fDelay);
+ // The rest of the network is connected at the same speed
+ groupNotFastC.connect(groupNotFastC, delay);
+
+ Disruptor dc(network, groupCfast, groupCsplit, delay);
+ sim.collectors.add(dc);
+
+ // Consensus round to generate ledger A
+ sim.run(1);
+ BEAST_EXPECT(sim.synchronized());
+
+ // Next round generates B and C
+ // To force B, we inject an extra transaction in to those nodes
+ for(Peer * peer : groupABD)
+ {
+ peer->txInjections.emplace(
+ peer->lastClosedLedger.seq(), Tx{42});
+ }
+ // The Disruptor will ensure that nodes disconnect before the C
+ // validations make it to all but the fastC node
+ sim.run(1);
+
+ // We are no longer in sync, but have not yet forked:
+ // 9 nodes consider A the last fully validated ledger and fastC sees C
+ BEAST_EXPECT(!sim.synchronized());
+ BEAST_EXPECT(sim.branches() == 1);
+
+ // Run another round to generate the 8 different C' ledgers
+ for (Peer * p : network)
+ p->submit(Tx(static_cast(p->id)));
+ sim.run(1);
+
+ // Still not forked
+ BEAST_EXPECT(!sim.synchronized());
+ BEAST_EXPECT(sim.branches() == 1);
+
+ // Disruptor will reconnect all but the fastC node
+ sim.run(1);
+
+ if(BEAST_EXPECT(sim.branches() == 1))
+ {
+ BEAST_EXPECT(sim.synchronized());
+ }
+ else // old approach caused a fork
+ {
+ BEAST_EXPECT(sim.branches(groupNotFastC) == 1);
+ BEAST_EXPECT(sim.synchronized(groupNotFastC) == 1);
+ }
+ }
+
void
run() override
{
@@ -839,6 +999,7 @@ class Consensus_test : public beast::unit_test::suite
testConsensusCloseTimeRounding();
testFork();
testHubNetwork();
+ testPreferredByBranch();
}
};
diff --git a/src/test/consensus/LedgerTrie_test.cpp b/src/test/consensus/LedgerTrie_test.cpp
new file mode 100644
index 00000000000..651bf10383d
--- /dev/null
+++ b/src/test/consensus/LedgerTrie_test.cpp
@@ -0,0 +1,663 @@
+//------------------------------------------------------------------------------
+/*
+ This file is part of rippled: https://github.com/ripple/rippled
+ Copyright (c) 2012-2017 Ripple Labs Inc.
+
+ Permission to use, copy, modify, and/or distribute this software for any
+ purpose with or without fee is hereby granted, provided that the above
+ copyright notice and this permission notice appear in all copies.
+
+ THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+*/
+//==============================================================================
+#include
+#include
+#include
+#include
+#include
+#include
+
+namespace ripple {
+namespace test {
+
+class LedgerTrie_test : public beast::unit_test::suite
+{
+ beast::Journal j;
+
+
+ void
+ testInsert()
+ {
+ using namespace csf;
+ // Single entry by itself
+ {
+ LedgerTrie t;
+ LedgerHistoryHelper h;
+ t.insert(h["abc"]);
+ BEAST_EXPECT(t.checkInvariants());
+ BEAST_EXPECT(t.tipSupport(h["abc"]) == 1);
+ BEAST_EXPECT(t.branchSupport(h["abc"]) == 1);
+
+ t.insert(h["abc"]);
+ BEAST_EXPECT(t.checkInvariants());
+ BEAST_EXPECT(t.tipSupport(h["abc"]) == 2);
+ BEAST_EXPECT(t.branchSupport(h["abc"]) == 2);
+ }
+ // Suffix of existing (extending tree)
+ {
+ LedgerTrie t;
+ LedgerHistoryHelper h;
+ t.insert(h["abc"]);
+ BEAST_EXPECT(t.checkInvariants());
+ // extend with no siblings
+ t.insert(h["abcd"]);
+ BEAST_EXPECT(t.checkInvariants());
+
+ BEAST_EXPECT(t.tipSupport(h["abc"]) == 1);
+ BEAST_EXPECT(t.branchSupport(h["abc"]) == 2);
+ BEAST_EXPECT(t.tipSupport(h["abcd"]) == 1);
+ BEAST_EXPECT(t.branchSupport(h["abcd"]) == 1);
+
+ // extend with existing sibling
+ t.insert(h["abce"]);
+ BEAST_EXPECT(t.tipSupport(h["abc"]) == 1);
+ BEAST_EXPECT(t.branchSupport(h["abc"]) == 3);
+ BEAST_EXPECT(t.tipSupport(h["abcd"]) == 1);
+ BEAST_EXPECT(t.branchSupport(h["abcd"]) == 1);
+ BEAST_EXPECT(t.tipSupport(h["abce"]) == 1);
+ BEAST_EXPECT(t.branchSupport(h["abce"]) == 1);
+ }
+ // uncommitted of existing node
+ {
+ LedgerTrie t;
+ LedgerHistoryHelper h;
+ t.insert(h["abcd"]);
+ BEAST_EXPECT(t.checkInvariants());
+ // uncommitted with no siblings
+ t.insert(h["abcdf"]);
+ BEAST_EXPECT(t.checkInvariants());
+
+ BEAST_EXPECT(t.tipSupport(h["abcd"]) == 1);
+ BEAST_EXPECT(t.branchSupport(h["abcd"]) == 2);
+ BEAST_EXPECT(t.tipSupport(h["abcdf"]) == 1);
+ BEAST_EXPECT(t.branchSupport(h["abcdf"]) == 1);
+
+ // uncommitted with existing child
+ t.insert(h["abc"]);
+ BEAST_EXPECT(t.checkInvariants());
+
+ BEAST_EXPECT(t.tipSupport(h["abc"]) == 1);
+ BEAST_EXPECT(t.branchSupport(h["abc"]) == 3);
+ BEAST_EXPECT(t.tipSupport(h["abcd"]) == 1);
+ BEAST_EXPECT(t.branchSupport(h["abcd"]) == 2);
+ BEAST_EXPECT(t.tipSupport(h["abcdf"]) == 1);
+ BEAST_EXPECT(t.branchSupport(h["abcdf"]) == 1);
+ }
+ // Suffix + uncommitted of existing node
+ {
+ LedgerTrie t;
+ LedgerHistoryHelper h;
+ t.insert(h["abcd"]);
+ BEAST_EXPECT(t.checkInvariants());
+ t.insert(h["abce"]);
+ BEAST_EXPECT(t.checkInvariants());
+
+ BEAST_EXPECT(t.tipSupport(h["abc"]) == 0);
+ BEAST_EXPECT(t.branchSupport(h["abc"]) == 2);
+ BEAST_EXPECT(t.tipSupport(h["abcd"]) == 1);
+ BEAST_EXPECT(t.branchSupport(h["abcd"]) == 1);
+ BEAST_EXPECT(t.tipSupport(h["abce"]) == 1);
+ BEAST_EXPECT(t.branchSupport(h["abce"]) == 1);
+ }
+ // Suffix + uncommitted with existing child
+ {
+ // abcd : abcde, abcf
+
+ LedgerTrie t;
+ LedgerHistoryHelper h;
+ t.insert(h["abcd"]);
+ BEAST_EXPECT(t.checkInvariants());
+ t.insert(h["abcde"]);
+ BEAST_EXPECT(t.checkInvariants());
+ t.insert(h["abcf"]);
+ BEAST_EXPECT(t.checkInvariants());
+
+ BEAST_EXPECT(t.tipSupport(h["abc"]) == 0);
+ BEAST_EXPECT(t.branchSupport(h["abc"]) == 3);
+ BEAST_EXPECT(t.tipSupport(h["abcd"]) == 1);
+ BEAST_EXPECT(t.branchSupport(h["abcd"]) == 2);
+ BEAST_EXPECT(t.tipSupport(h["abcf"]) == 1);
+ BEAST_EXPECT(t.branchSupport(h["abcf"]) == 1);
+ BEAST_EXPECT(t.tipSupport(h["abcde"]) == 1);
+ BEAST_EXPECT(t.branchSupport(h["abcde"]) == 1);
+ }
+
+ // Multiple counts
+ {
+ LedgerTrie t;
+ LedgerHistoryHelper h;
+ t.insert(h["ab"],4);
+ BEAST_EXPECT(t.tipSupport(h["ab"]) == 4);
+ BEAST_EXPECT(t.branchSupport(h["ab"]) == 4);
+ BEAST_EXPECT(t.tipSupport(h["a"]) == 0);
+ BEAST_EXPECT(t.branchSupport(h["a"]) == 4);
+
+ t.insert(h["abc"],2);
+ BEAST_EXPECT(t.tipSupport(h["abc"]) == 2);
+ BEAST_EXPECT(t.branchSupport(h["abc"]) == 2);
+ BEAST_EXPECT(t.tipSupport(h["ab"]) == 4);
+ BEAST_EXPECT(t.branchSupport(h["ab"]) == 6);
+ BEAST_EXPECT(t.tipSupport(h["a"]) == 0);
+ BEAST_EXPECT(t.branchSupport(h["a"]) == 6);
+
+ }
+ }
+
+ void
+ testRemove()
+ {
+ using namespace csf;
+ // Not in trie
+ {
+ LedgerTrie t;
+ LedgerHistoryHelper h;
+ t.insert(h["abc"]);
+
+ BEAST_EXPECT(!t.remove(h["ab"]));
+ BEAST_EXPECT(t.checkInvariants());
+ BEAST_EXPECT(!t.remove(h["a"]));
+ BEAST_EXPECT(t.checkInvariants());
+ }
+ // In trie but with 0 tip support
+ {
+ LedgerTrie t;
+ LedgerHistoryHelper h;
+ t.insert(h["abcd"]);
+ t.insert(h["abce"]);
+
+ BEAST_EXPECT(t.tipSupport(h["abc"]) == 0);
+ BEAST_EXPECT(t.branchSupport(h["abc"]) == 2);
+ BEAST_EXPECT(!t.remove(h["abc"]));
+ BEAST_EXPECT(t.checkInvariants());
+ BEAST_EXPECT(t.tipSupport(h["abc"]) == 0);
+ BEAST_EXPECT(t.branchSupport(h["abc"]) == 2);
+ }
+ // In trie with > 1 tip support
+ {
+ LedgerTrie t;
+ LedgerHistoryHelper h;
+ t.insert(h["abc"],2);
+
+ BEAST_EXPECT(t.tipSupport(h["abc"]) == 2);
+ BEAST_EXPECT(t.remove(h["abc"]));
+ BEAST_EXPECT(t.checkInvariants());
+ BEAST_EXPECT(t.tipSupport(h["abc"]) == 1);
+
+ t.insert(h["abc"], 1);
+ BEAST_EXPECT(t.tipSupport(h["abc"]) == 2);
+ BEAST_EXPECT(t.remove(h["abc"], 2));
+ BEAST_EXPECT(t.checkInvariants());
+ BEAST_EXPECT(t.tipSupport(h["abc"]) == 0);
+
+ t.insert(h["abc"], 3);
+ BEAST_EXPECT(t.tipSupport(h["abc"]) == 3);
+ BEAST_EXPECT(t.remove(h["abc"], 300));
+ BEAST_EXPECT(t.checkInvariants());
+ BEAST_EXPECT(t.tipSupport(h["abc"]) == 0);
+
+ }
+ // In trie with = 1 tip support, no children
+ {
+ LedgerTrie t;
+ LedgerHistoryHelper h;
+ t.insert(h["ab"]);
+ t.insert(h["abc"]);
+
+ BEAST_EXPECT(t.tipSupport(h["ab"]) == 1);
+ BEAST_EXPECT(t.branchSupport(h["ab"]) == 2);
+ BEAST_EXPECT(t.tipSupport(h["abc"]) == 1);
+ BEAST_EXPECT(t.branchSupport(h["abc"]) == 1);
+
+ BEAST_EXPECT(t.remove(h["abc"]));
+ BEAST_EXPECT(t.checkInvariants());
+ BEAST_EXPECT(t.tipSupport(h["ab"]) == 1);
+ BEAST_EXPECT(t.branchSupport(h["ab"]) == 1);
+ BEAST_EXPECT(t.tipSupport(h["abc"]) == 0);
+ BEAST_EXPECT(t.branchSupport(h["abc"]) == 0);
+ }
+ // In trie with = 1 tip support, 1 child
+ {
+ LedgerTrie t;
+ LedgerHistoryHelper h;
+ t.insert(h["ab"]);
+ t.insert(h["abc"]);
+ t.insert(h["abcd"]);
+
+ BEAST_EXPECT(t.tipSupport(h["abc"]) == 1);
+ BEAST_EXPECT(t.branchSupport(h["abc"]) == 2);
+ BEAST_EXPECT(t.tipSupport(h["abcd"]) == 1);
+ BEAST_EXPECT(t.branchSupport(h["abcd"]) == 1);
+
+ BEAST_EXPECT(t.remove(h["abc"]));
+ BEAST_EXPECT(t.checkInvariants());
+ BEAST_EXPECT(t.tipSupport(h["abc"]) == 0);
+ BEAST_EXPECT(t.branchSupport(h["abc"]) == 1);
+ BEAST_EXPECT(t.tipSupport(h["abcd"]) == 1);
+ BEAST_EXPECT(t.branchSupport(h["abcd"]) == 1);
+ }
+ // In trie with = 1 tip support, > 1 children
+ {
+ LedgerTrie t;
+ LedgerHistoryHelper h;
+ t.insert(h["ab"]);
+ t.insert(h["abc"]);
+ t.insert(h["abcd"]);
+ t.insert(h["abce"]);
+
+ BEAST_EXPECT(t.tipSupport(h["abc"]) == 1);
+ BEAST_EXPECT(t.branchSupport(h["abc"]) == 3);
+
+ BEAST_EXPECT(t.remove(h["abc"]));
+ BEAST_EXPECT(t.checkInvariants());
+ BEAST_EXPECT(t.tipSupport(h["abc"]) == 0);
+ BEAST_EXPECT(t.branchSupport(h["abc"]) == 2);
+ }
+
+ // In trie with = 1 tip support, parent compaction
+ {
+ LedgerTrie t;
+ LedgerHistoryHelper h;
+ t.insert(h["ab"]);
+ t.insert(h["abc"]);
+ t.insert(h["abd"]);
+ BEAST_EXPECT(t.checkInvariants());
+ t.remove(h["ab"]);
+ BEAST_EXPECT(t.checkInvariants());
+ BEAST_EXPECT(t.tipSupport(h["abc"]) == 1);
+ BEAST_EXPECT(t.tipSupport(h["abd"]) == 1);
+ BEAST_EXPECT(t.tipSupport(h["ab"]) == 0);
+ BEAST_EXPECT(t.branchSupport(h["ab"]) == 2);
+
+ t.remove(h["abd"]);
+ BEAST_EXPECT(t.checkInvariants());
+
+ BEAST_EXPECT(t.tipSupport(h["abc"]) == 1);
+ BEAST_EXPECT(t.branchSupport(h["ab"]) == 1);
+
+ }
+ }
+
+ void
+ testSupport()
+ {
+ using namespace csf;
+ using Seq = Ledger::Seq;
+
+
+ LedgerTrie t;
+ LedgerHistoryHelper h;
+ BEAST_EXPECT(t.tipSupport(h["a"]) == 0);
+ BEAST_EXPECT(t.tipSupport(h["axy"]) == 0);
+
+ BEAST_EXPECT(t.branchSupport(h["a"]) == 0);
+ BEAST_EXPECT(t.branchSupport(h["axy"]) == 0);
+
+ t.insert(h["abc"]);
+ BEAST_EXPECT(t.tipSupport(h["a"]) == 0);
+ BEAST_EXPECT(t.tipSupport(h["ab"]) == 0);
+ BEAST_EXPECT(t.tipSupport(h["abc"]) == 1);
+ BEAST_EXPECT(t.tipSupport(h["abcd"]) == 0);
+
+ BEAST_EXPECT(t.branchSupport(h["a"]) == 1);
+ BEAST_EXPECT(t.branchSupport(h["ab"]) == 1);
+ BEAST_EXPECT(t.branchSupport(h["abc"]) == 1);
+ BEAST_EXPECT(t.branchSupport(h["abcd"]) == 0);
+
+ t.insert(h["abe"]);
+ BEAST_EXPECT(t.tipSupport(h["a"]) == 0);
+ BEAST_EXPECT(t.tipSupport(h["ab"]) == 0);
+ BEAST_EXPECT(t.tipSupport(h["abc"]) == 1);
+ BEAST_EXPECT(t.tipSupport(h["abe"]) == 1);
+
+ BEAST_EXPECT(t.branchSupport(h["a"]) == 2);
+ BEAST_EXPECT(t.branchSupport(h["ab"]) == 2);
+ BEAST_EXPECT(t.branchSupport(h["abc"]) == 1);
+ BEAST_EXPECT(t.branchSupport(h["abe"]) == 1);
+
+ t.remove(h["abc"]);
+ BEAST_EXPECT(t.tipSupport(h["a"]) == 0);
+ BEAST_EXPECT(t.tipSupport(h["ab"]) == 0);
+ BEAST_EXPECT(t.tipSupport(h["abc"]) == 0);
+ BEAST_EXPECT(t.tipSupport(h["abe"]) == 1);
+
+ BEAST_EXPECT(t.branchSupport(h["a"]) == 1);
+ BEAST_EXPECT(t.branchSupport(h["ab"]) == 1);
+ BEAST_EXPECT(t.branchSupport(h["abc"]) == 0);
+ BEAST_EXPECT(t.branchSupport(h["abe"]) == 1);
+
+ }
+
+ void
+ testGetPreferred()
+ {
+ using namespace csf;
+ using Seq = Ledger::Seq;
+ // Empty
+ {
+ LedgerTrie t;
+ LedgerHistoryHelper h;
+ BEAST_EXPECT(t.getPreferred(Seq{0}).id == h[""].id());
+ BEAST_EXPECT(t.getPreferred(Seq{2}).id == h[""].id());
+ }
+ // Single node no children
+ {
+ LedgerTrie t;
+ LedgerHistoryHelper h;
+ t.insert(h["abc"]);
+ BEAST_EXPECT(t.getPreferred(Seq{3}).id == h["abc"].id());
+ }
+ // Single node smaller child support
+ {
+ LedgerTrie t;
+ LedgerHistoryHelper h;
+ t.insert(h["abc"]);
+ t.insert(h["abcd"]);
+ BEAST_EXPECT(t.getPreferred(Seq{3}).id == h["abc"].id());
+ BEAST_EXPECT(t.getPreferred(Seq{4}).id == h["abc"].id());
+ }
+ // Single node larger child
+ {
+ LedgerTrie t;
+ LedgerHistoryHelper h;
+ t.insert(h["abc"]);
+ t.insert(h["abcd"],2);
+ BEAST_EXPECT(t.getPreferred(Seq{3}).id == h["abcd"].id());
+ BEAST_EXPECT(t.getPreferred(Seq{4}).id == h["abcd"].id());
+ }
+ // Single node smaller children support
+ {
+ LedgerTrie t;
+ LedgerHistoryHelper h;
+ t.insert(h["abc"]);
+ t.insert(h["abcd"]);
+ t.insert(h["abce"]);
+ BEAST_EXPECT(t.getPreferred(Seq{3}).id == h["abc"].id());
+ BEAST_EXPECT(t.getPreferred(Seq{4}).id == h["abc"].id());
+
+ t.insert(h["abc"]);
+ BEAST_EXPECT(t.getPreferred(Seq{3}).id == h["abc"].id());
+ BEAST_EXPECT(t.getPreferred(Seq{4}).id == h["abc"].id());
+ }
+ // Single node larger children
+ {
+ LedgerTrie t;
+ LedgerHistoryHelper h;
+ t.insert(h["abc"]);
+ t.insert(h["abcd"],2);
+ t.insert(h["abce"]);
+ BEAST_EXPECT(t.getPreferred(Seq{3}).id == h["abc"].id());
+ BEAST_EXPECT(t.getPreferred(Seq{4}).id == h["abc"].id());
+
+ t.insert(h["abcd"]);
+ BEAST_EXPECT(t.getPreferred(Seq{3}).id == h["abcd"].id());
+ BEAST_EXPECT(t.getPreferred(Seq{4}).id == h["abcd"].id());
+ }
+ // Tie-breaker by id
+ {
+ LedgerTrie t;
+ LedgerHistoryHelper h;
+ t.insert(h["abcd"],2);
+ t.insert(h["abce"],2);
+
+ BEAST_EXPECT(h["abce"].id() > h["abcd"].id());
+ BEAST_EXPECT(t.getPreferred(Seq{4}).id == h["abce"].id());
+
+ t.insert(h["abcd"]);
+ BEAST_EXPECT(h["abce"].id() > h["abcd"].id());
+ BEAST_EXPECT(t.getPreferred(Seq{4}).id == h["abcd"].id());
+ }
+
+ // Tie-breaker not needed
+ {
+ LedgerTrie t;
+ LedgerHistoryHelper h;
+ t.insert(h["abc"]);
+ t.insert(h["abcd"]);
+ t.insert(h["abce"],2);
+ // abce only has a margin of 1, but it owns the tie-breaker
+ BEAST_EXPECT(h["abce"].id() > h["abcd"].id());
+ BEAST_EXPECT(t.getPreferred(Seq{3}).id == h["abce"].id());
+ BEAST_EXPECT(t.getPreferred(Seq{4}).id == h["abce"].id());
+
+ // Switch support from abce to abcd, tie-breaker now needed
+ t.remove(h["abce"]);
+ t.insert(h["abcd"]);
+ BEAST_EXPECT(t.getPreferred(Seq{3}).id == h["abc"].id());
+ BEAST_EXPECT(t.getPreferred(Seq{4}).id == h["abc"].id());
+ }
+
+ // Single node larger grand child
+ {
+ LedgerTrie t;
+ LedgerHistoryHelper h;
+ t.insert(h["abc"]);
+ t.insert(h["abcd"],2);
+ t.insert(h["abcde"],4);
+ BEAST_EXPECT(t.getPreferred(Seq{3}).id == h["abcde"].id());
+ BEAST_EXPECT(t.getPreferred(Seq{4}).id == h["abcde"].id());
+ BEAST_EXPECT(t.getPreferred(Seq{5}).id == h["abcde"].id());
+ }
+
+ // Too much uncommitted support from competing branches
+ {
+ LedgerTrie t;
+ LedgerHistoryHelper h;
+ t.insert(h["abc"]);
+ t.insert(h["abcde"],2);
+ t.insert(h["abcfg"],2);
+ // 'de' and 'fg' are tied without 'abc' vote
+ BEAST_EXPECT(t.getPreferred(Seq{3}).id == h["abc"].id());
+ BEAST_EXPECT(t.getPreferred(Seq{4}).id == h["abc"].id());
+ BEAST_EXPECT(t.getPreferred(Seq{5}).id == h["abc"].id());
+
+ t.remove(h["abc"]);
+ t.insert(h["abcd"]);
+
+ // 'de' branch has 3 votes to 2, so earlier sequences see it as
+ // preferred
+ BEAST_EXPECT(t.getPreferred(Seq{3}).id == h["abcde"].id());
+ BEAST_EXPECT(t.getPreferred(Seq{4}).id == h["abcde"].id());
+
+ // However, if you validated a ledger with Seq 5, potentially on
+ // a different branch, you do not yet know if they chose abcd
+ // or abcf because of you, so abc remains preferred
+ BEAST_EXPECT(t.getPreferred(Seq{5}).id == h["abc"].id());
+ }
+
+ // Changing largestSeq perspective changes preferred branch
+ {
+ /** Build the tree below with initial tip support annotated
+ A
+ / \
+ B(1) C(1)
+ / | |
+ H D F(1)
+ |
+ E(2)
+ |
+ G
+ */
+ LedgerTrie t;
+ LedgerHistoryHelper h;
+ t.insert(h["ab"]);
+ t.insert(h["ac"]);
+ t.insert(h["acf"]);
+ t.insert(h["abde"],2);
+
+ // B has more branch support
+ BEAST_EXPECT(t.getPreferred(Seq{1}).id == h["ab"].id());
+ BEAST_EXPECT(t.getPreferred(Seq{2}).id == h["ab"].id());
+ // But if you last validated D,F or E, you do not yet know
+ // if someone used that validation to commit to B or C
+ BEAST_EXPECT(t.getPreferred(Seq{3}).id == h["a"].id());
+ BEAST_EXPECT(t.getPreferred(Seq{4}).id == h["a"].id());
+
+ /** One of E advancing to G doesn't change anything
+ A
+ / \
+ B(1) C(1)
+ / | |
+ H D F(1)
+ |
+ E(1)
+ |
+ G(1)
+ */
+ t.remove(h["abde"]);
+ t.insert(h["abdeg"]);
+
+ BEAST_EXPECT(t.getPreferred(Seq{1}).id == h["ab"].id());
+ BEAST_EXPECT(t.getPreferred(Seq{2}).id == h["ab"].id());
+ BEAST_EXPECT(t.getPreferred(Seq{3}).id == h["a"].id());
+ BEAST_EXPECT(t.getPreferred(Seq{4}).id == h["a"].id());
+ BEAST_EXPECT(t.getPreferred(Seq{5}).id == h["a"].id());
+
+ /** C advancing to H does advance the seq 3 preferred ledger
+ A
+ / \
+ B(1) C
+ / | |
+ H(1)D F(1)
+ |
+ E(1)
+ |
+ G(1)
+ */
+ t.remove(h["ac"]);
+ t.insert(h["abh"]);
+ BEAST_EXPECT(t.getPreferred(Seq{1}).id == h["ab"].id());
+ BEAST_EXPECT(t.getPreferred(Seq{2}).id == h["ab"].id());
+ BEAST_EXPECT(t.getPreferred(Seq{3}).id == h["ab"].id());
+ BEAST_EXPECT(t.getPreferred(Seq{4}).id == h["a"].id());
+ BEAST_EXPECT(t.getPreferred(Seq{5}).id == h["a"].id());
+
+ /** F advancing to E also moves the preferred ledger forward
+ A
+ / \
+ B(1) C
+ / | |
+ H(1)D F
+ |
+ E(2)
+ |
+ G(1)
+ */
+ t.remove(h["acf"]);
+ t.insert(h["abde"]);
+ BEAST_EXPECT(t.getPreferred(Seq{1}).id == h["abde"].id());
+ BEAST_EXPECT(t.getPreferred(Seq{2}).id == h["abde"].id());
+ BEAST_EXPECT(t.getPreferred(Seq{3}).id == h["abde"].id());
+ BEAST_EXPECT(t.getPreferred(Seq{4}).id == h["ab"].id());
+ BEAST_EXPECT(t.getPreferred(Seq{5}).id == h["ab"].id());
+
+ }
+
+
+ }
+
+ void
+ testRootRelated()
+ {
+ using namespace csf;
+ using Seq = Ledger::Seq;
+ // Since the root is a special node that breaks the no-single child
+ // invariant, do some tests that exercise it.
+
+ LedgerTrie t;
+ LedgerHistoryHelper h;
+ BEAST_EXPECT(!t.remove(h[""]));
+ BEAST_EXPECT(t.branchSupport(h[""]) == 0);
+ BEAST_EXPECT(t.tipSupport(h[""]) == 0);
+
+ t.insert(h["a"]);
+ BEAST_EXPECT(t.checkInvariants());
+ BEAST_EXPECT(t.branchSupport(h[""]) == 1);
+ BEAST_EXPECT(t.tipSupport(h[""]) == 0);
+
+ t.insert(h["e"]);
+ BEAST_EXPECT(t.checkInvariants());
+ BEAST_EXPECT(t.branchSupport(h[""]) == 2);
+ BEAST_EXPECT(t.tipSupport(h[""]) == 0);
+
+ BEAST_EXPECT(t.remove(h["e"]));
+ BEAST_EXPECT(t.checkInvariants());
+ BEAST_EXPECT(t.branchSupport(h[""]) == 1);
+ BEAST_EXPECT(t.tipSupport(h[""]) == 0);
+ }
+
+ void
+ testStress()
+ {
+ using namespace csf;
+ LedgerTrie t;
+ LedgerHistoryHelper h;
+
+ // Test quasi-randomly add/remove supporting for different ledgers
+ // from a branching history.
+
+ // Ledgers have sequence 1,2,3,4
+ std::uint32_t const depth = 4;
+ // Each ledger has 4 possible children
+ std::uint32_t const width = 4;
+
+ std::uint32_t const iterations = 10000;
+
+ // Use explicit seed to have same results for CI
+ std::mt19937 gen{ 42 };
+ std::uniform_int_distribution<> depthDist(0, depth-1);
+ std::uniform_int_distribution<> widthDist(0, width-1);
+ std::uniform_int_distribution<> flip(0, 1);
+ for(std::uint32_t i = 0; i < iterations; ++i)
+ {
+ // pick a random ledger history
+ std::string curr = "";
+ char depth = depthDist(gen);
+ char offset = 0;
+ for(char d = 0; d < depth; ++d)
+ {
+ char a = offset + widthDist(gen);
+ curr += a;
+ offset = (a + 1) * width;
+ }
+
+ // 50-50 to add remove
+ if(flip(gen) == 0)
+ t.insert(h[curr]);
+ else
+ t.remove(h[curr]);
+ if(!BEAST_EXPECT(t.checkInvariants()))
+ return;
+ }
+ }
+
+ void
+ run()
+ {
+ testInsert();
+ testRemove();
+ testSupport();
+ testGetPreferred();
+ testRootRelated();
+ testStress();
+ }
+};
+
+BEAST_DEFINE_TESTSUITE(LedgerTrie, consensus, ripple);
+} // namespace test
+} // namespace ripple
diff --git a/src/test/consensus/Validations_test.cpp b/src/test/consensus/Validations_test.cpp
index 0f31210c681..24821fd47d3 100644
--- a/src/test/consensus/Validations_test.cpp
+++ b/src/test/consensus/Validations_test.cpp
@@ -23,10 +23,10 @@
#include
#include
+#include
#include
#include
#include
-#include
namespace ripple {
namespace test {
@@ -112,24 +112,58 @@ class Validations_test : public beast::unit_test::suite
// Issue a new validation with given sequence number and id and
// with signing and seen times offset from the common clock
Validation
- validation(Ledger::Seq seq,
- Ledger::ID i,
+ validate(
+ Ledger::ID id,
+ Ledger::Seq seq,
+ NetClock::duration signOffset,
+ NetClock::duration seenOffset,
+ bool full) const
+ {
+ Validation v{id,
+ seq,
+ now() + signOffset,
+ now() + seenOffset,
+ currKey(),
+ nodeID_,
+ full,
+ loadFee_};
+ if (trusted_)
+ v.setTrusted();
+ return v;
+ }
+
+ Validation
+ validate(
+ Ledger ledger,
NetClock::duration signOffset,
NetClock::duration seenOffset) const
{
- return Validation{i, seq, now() + signOffset, now() + seenOffset,
- currKey(), nodeID_, trusted_, loadFee_};
+ return validate(
+ ledger.id(), ledger.seq(), signOffset, seenOffset, true);
}
- // Issue a new validation with the given sequence number and id
Validation
- validation(Ledger::Seq seq, Ledger::ID i) const
+ validate(Ledger ledger) const
{
- return validation(
- seq, i, NetClock::duration{0}, NetClock::duration{0});
+ return validate(
+ ledger.id(),
+ ledger.seq(),
+ NetClock::duration{0},
+ NetClock::duration{0},
+ true);
}
- };
+ Validation
+ partial(Ledger ledger) const
+ {
+ return validate(
+ ledger.id(),
+ ledger.seq(),
+ NetClock::duration{0},
+ NetClock::duration{0},
+ false);
+ }
+ };
// Saved StaleData for inspection in test
struct StaleData
@@ -138,15 +172,34 @@ class Validations_test : public beast::unit_test::suite
hash_map flushed;
};
- // Generic Validations policy that saves stale/flushed data into
+ // Generic Validations adaptor that saves stale/flushed data into
// a StaleData instance.
- class StalePolicy
+ class Adaptor
{
StaleData& staleData_;
clock_type& c_;
+ LedgerOracle& oracle_;
public:
- StalePolicy(StaleData& sd, clock_type& c) : staleData_{sd}, c_{c}
+ // Non-locking mutex to avoid locks in generic Validations
+ struct Mutex
+ {
+ void
+ lock()
+ {
+ }
+
+ void
+ unlock()
+ {
+ }
+ };
+
+ using Validation = csf::Validation;
+ using Ledger = csf::Ledger;
+
+ Adaptor(StaleData& sd, clock_type& c, LedgerOracle& o)
+ : staleData_{sd}, c_{c}, oracle_{o}
{
}
@@ -167,27 +220,16 @@ class Validations_test : public beast::unit_test::suite
{
staleData_.flushed = std::move(remaining);
}
- };
-
- // Non-locking mutex to avoid locks in generic Validations
- struct NotAMutex
- {
- void
- lock()
- {
- }
- void
- unlock()
+ boost::optional
+ acquire(Ledger::ID const& id)
{
+ return oracle_.lookup(id);
}
};
// Specialize generic Validations using the above types
- using TestValidations = Validations;
-
- // Hoist enum for writing simpler tests
- using AddOutcome = TestValidations::AddOutcome;
+ using TestValidations = Validations;
// Gather the dependencies of TestValidations in a single class and provide
// accessors for simplifying test logic
@@ -196,28 +238,20 @@ class Validations_test : public beast::unit_test::suite
StaleData staleData_;
ValidationParms p_;
beast::manual_clock clock_;
- beast::Journal j_;
TestValidations tv_;
PeerID nextNodeId_{0};
public:
- TestHarness() : tv_(p_, clock_, j_, staleData_, clock_)
- {
- }
-
- // Helper to add an existing validation
- AddOutcome
- add(Node const& n, Validation const& v)
+ TestHarness(LedgerOracle& o)
+ : tv_(p_, clock_, staleData_, clock_, o)
{
- return tv_.add(n.masterKey(), v);
}
- // Helper to directly create the validation
- template
- std::enable_if_t<(sizeof...(Ts) > 1), AddOutcome>
- add(Node const& n, Ts&&... ts)
+ ValStatus
+ add(Validation const& v)
{
- return add(n, n.validation(std::forward(ts)...));
+ PeerKey masterKey{v.nodeID(), 0};
+ return tv_.add(masterKey, v);
}
TestValidations&
@@ -257,140 +291,157 @@ class Validations_test : public beast::unit_test::suite
}
};
+ Ledger const genesisLedger{Ledger::MakeGenesis{}};
+
void
testAddValidation()
{
- // Test adding current,stale,repeat,sameSeq validations
using namespace std::chrono_literals;
- TestHarness harness;
- Node a = harness.makeNode();
+ testcase("Add validation");
+ LedgerHistoryHelper h;
+ Ledger ledgerA = h["a"];
+ Ledger ledgerAB = h["ab"];
+ Ledger ledgerAZ = h["az"];
+ Ledger ledgerABC = h["abc"];
+ Ledger ledgerABCD = h["abcd"];
+ Ledger ledgerABCDE = h["abcde"];
+
{
- {
- auto const v = a.validation(Ledger::Seq{1}, Ledger::ID{1});
+ TestHarness harness(h.oracle);
+ Node n = harness.makeNode();
- // Add a current validation
- BEAST_EXPECT(AddOutcome::current == harness.add(a, v));
+ auto const v = n.validate(ledgerA);
- // Re-adding is repeat
- BEAST_EXPECT(AddOutcome::repeat == harness.add(a, v));
- }
+ // Add a current validation
+ BEAST_EXPECT(ValStatus::current == harness.add(v));
- {
- harness.clock().advance(1s);
- // Replace with a new validation and ensure the old one is stale
- BEAST_EXPECT(harness.stale().empty());
+ // Re-adding violates the increasing seq requirement for full
+ // validations
+ BEAST_EXPECT(ValStatus::badSeq == harness.add(v));
- BEAST_EXPECT(AddOutcome::current ==
- harness.add(a, Ledger::Seq{2}, Ledger::ID{2}));
+ harness.clock().advance(1s);
+ // Replace with a new validation and ensure the old one is stale
+ BEAST_EXPECT(harness.stale().empty());
- BEAST_EXPECT(harness.stale().size() == 1);
+ BEAST_EXPECT(
+ ValStatus::current == harness.add(n.validate(ledgerAB)));
- BEAST_EXPECT(harness.stale()[0].ledgerID() == Ledger::ID{1});
- }
+ BEAST_EXPECT(harness.stale().size() == 1);
- {
- // Test the node changing signing key, then reissuing a ledger
+ BEAST_EXPECT(harness.stale()[0].ledgerID() == ledgerA.id());
- // Confirm old ledger on hand, but not new ledger
- BEAST_EXPECT(
- harness.vals().numTrustedForLedger(Ledger::ID{2}) == 1);
- BEAST_EXPECT(
- harness.vals().numTrustedForLedger(Ledger::ID{20}) == 0);
+ // Test the node changing signing key
- // Issue a new signing key and re-issue the validation with a
- // new ID but the same sequence number
- a.advanceKey();
+ // Confirm old ledger on hand, but not new ledger
+ BEAST_EXPECT(
+ harness.vals().numTrustedForLedger(ledgerAB.id()) == 1);
+ BEAST_EXPECT(
+ harness.vals().numTrustedForLedger(ledgerABC.id()) == 0);
- // No validations following ID{2}
- BEAST_EXPECT(harness.vals().getNodesAfter(Ledger::ID{2}) == 0);
+ // Rotate signing keys
+ n.advanceKey();
- BEAST_EXPECT(
- AddOutcome::sameSeq == harness.add(a, Ledger::Seq{2}, Ledger::ID{20}));
+ harness.clock().advance(1s);
- // Old ID should be gone ...
- BEAST_EXPECT(harness.vals().numTrustedForLedger(Ledger::ID{2}) == 0);
- BEAST_EXPECT(harness.vals().numTrustedForLedger(Ledger::ID{20}) == 1);
- {
- // Should be the only trusted for ID{20}
- auto trustedVals =
- harness.vals().getTrustedForLedger(Ledger::ID{20});
- BEAST_EXPECT(trustedVals.size() == 1);
- BEAST_EXPECT(trustedVals[0].key() == a.currKey());
- // ... and should be the only node after ID{2}
- BEAST_EXPECT(harness.vals().getNodesAfter(Ledger::ID{2}) == 1);
+ // Cannot re-do the same full validation sequence
+ BEAST_EXPECT(
+ ValStatus::badSeq == harness.add(n.validate(ledgerAB)));
+ // Cannot send the same partial validation sequence
+ BEAST_EXPECT(
+ ValStatus::badSeq == harness.add(n.partial(ledgerAB)));
- }
+ // Now trusts the newest ledger too
+ harness.clock().advance(1s);
+ BEAST_EXPECT(
+ ValStatus::current == harness.add(n.validate(ledgerABC)));
+ BEAST_EXPECT(
+ harness.vals().numTrustedForLedger(ledgerAB.id()) == 1);
+ BEAST_EXPECT(
+ harness.vals().numTrustedForLedger(ledgerABC.id()) == 1);
- // A new key, but re-issue a validation with the same ID and
- // Sequence
- a.advanceKey();
+ // Processing validations out of order should ignore the older
+ // validation
+ harness.clock().advance(2s);
+ auto const valABCDE = n.validate(ledgerABCDE);
- BEAST_EXPECT(
- AddOutcome::sameSeq == harness.add(a, Ledger::Seq{2}, Ledger::ID{20}));
- {
- // Still the only trusted validation for ID{20}
- auto trustedVals =
- harness.vals().getTrustedForLedger(Ledger::ID{20});
- BEAST_EXPECT(trustedVals.size() == 1);
- BEAST_EXPECT(trustedVals[0].key() == a.currKey());
- // and still follows ID{2} since it was a re-issue
- BEAST_EXPECT(harness.vals().getNodesAfter(Ledger::ID{2}) == 1);
- }
- }
+ harness.clock().advance(4s);
+ auto const valABCD = n.validate(ledgerABCD);
- {
- // Processing validations out of order should ignore the older
- harness.clock().advance(2s);
- auto const val3 = a.validation(Ledger::Seq{3}, Ledger::ID{3});
+ BEAST_EXPECT(ValStatus::current == harness.add(valABCD));
- harness.clock().advance(4s);
- auto const val4 = a.validation(Ledger::Seq{4}, Ledger::ID{4});
+ BEAST_EXPECT(ValStatus::stale == harness.add(valABCDE));
+ }
- BEAST_EXPECT(AddOutcome::current == harness.add(a, val4));
+ {
+ // Process validations out of order with shifted times
- BEAST_EXPECT(AddOutcome::stale == harness.add(a, val3));
+ TestHarness harness(h.oracle);
+ Node n = harness.makeNode();
- // re-issued should not be added
- auto const val4reissue =
- a.validation(Ledger::Seq{4}, Ledger::ID{44});
+ // Establish a new current validation
+ BEAST_EXPECT(
+ ValStatus::current == harness.add(n.validate(ledgerA)));
- BEAST_EXPECT(AddOutcome::stale == harness.add(a, val4reissue));
- }
- {
- // Process validations out of order with shifted times
+ // Process a validation that has "later" seq but early sign time
+ BEAST_EXPECT(
+ ValStatus::stale ==
+ harness.add(n.validate(ledgerAB, -1s, -1s)));
- // flush old validations
- harness.clock().advance(1h);
+ // Process a validation that has a later seq and later sign
+ // time
+ BEAST_EXPECT(
+ ValStatus::current ==
+ harness.add(n.validate(ledgerABC, 1s, 1s)));
+ }
- // Establish a new current validation
- BEAST_EXPECT(AddOutcome::current ==
- harness.add(a, Ledger::Seq{8}, Ledger::ID{8}));
+ {
+ // Test stale on arrival validations
+ TestHarness harness(h.oracle);
+ Node n = harness.makeNode();
- // Process a validation that has "later" seq but early sign time
- BEAST_EXPECT(AddOutcome::stale ==
- harness.add(a, Ledger::Seq{9}, Ledger::ID{9}, -1s, -1s));
+ BEAST_EXPECT(
+ ValStatus::stale ==
+ harness.add(n.validate(
+ ledgerA, -harness.parms().validationCURRENT_EARLY, 0s)));
- // Process a validation that has an "earlier" seq but later sign
- // time
- BEAST_EXPECT(AddOutcome::current ==
- harness.add(a, Ledger::Seq{7}, Ledger::ID{7}, 1s, 1s));
- }
- {
- // Test stale on arrival validations
- harness.clock().advance(1h);
+ BEAST_EXPECT(
+ ValStatus::stale ==
+ harness.add(n.validate(
+ ledgerA, harness.parms().validationCURRENT_WALL, 0s)));
- BEAST_EXPECT(AddOutcome::stale ==
- harness.add(a, Ledger::Seq{15}, Ledger::ID{15},
- -harness.parms().validationCURRENT_EARLY, 0s));
+ BEAST_EXPECT(
+ ValStatus::stale ==
+ harness.add(n.validate(
+ ledgerA, 0s, harness.parms().validationCURRENT_LOCAL)));
+ }
+
+ {
+ // Test that full or partials cannot be sent for older sequence
+ // numbers, unless time-out has happened
+ for (bool doFull : {true, false})
+ {
+ TestHarness harness(h.oracle);
+ Node n = harness.makeNode();
- BEAST_EXPECT(AddOutcome::stale ==
- harness.add(a, Ledger::Seq{15}, Ledger::ID{15},
- harness.parms().validationCURRENT_WALL, 0s));
+ auto process = [&](Ledger & lgr)
+ {
+ if(doFull)
+ return harness.add(n.validate(lgr));
+ return harness.add(n.partial(lgr));
+ };
- BEAST_EXPECT(AddOutcome::stale ==
- harness.add(a, Ledger::Seq{15}, Ledger::ID{15}, 0s,
- harness.parms().validationCURRENT_LOCAL));
+ BEAST_EXPECT(ValStatus::current == process(ledgerABC));
+ harness.clock().advance(1s);
+ BEAST_EXPECT(ledgerAB.seq() < ledgerABC.seq());
+ BEAST_EXPECT(ValStatus::badSeq == process(ledgerAB));
+
+ // If we advance far enough for AB to expire, we can fully
+ // validate or partially validate that sequence number again
+ BEAST_EXPECT(ValStatus::badSeq == process(ledgerAZ));
+ harness.clock().advance(
+ harness.parms().validationSET_EXPIRES + 1ms);
+ BEAST_EXPECT(ValStatus::current == process(ledgerAZ));
}
}
}
@@ -398,97 +449,142 @@ class Validations_test : public beast::unit_test::suite
void
testOnStale()
{
- // Verify validation becomes stale based solely on time passing
- TestHarness harness;
- Node a = harness.makeNode();
+ testcase("Stale validation");
+ // Verify validation becomes stale based solely on time passing, but
+ // use different functions to trigger the check for staleness
- BEAST_EXPECT(AddOutcome::current ==
- harness.add(a, Ledger::Seq{1}, Ledger::ID{1}));
- harness.vals().currentTrusted();
- BEAST_EXPECT(harness.stale().empty());
- harness.clock().advance(harness.parms().validationCURRENT_LOCAL);
+ LedgerHistoryHelper h;
+ Ledger ledgerA = h["a"];
+ Ledger ledgerAB = h["ab"];
- // trigger iteration over current
- harness.vals().currentTrusted();
- BEAST_EXPECT(harness.stale().size() == 1);
- BEAST_EXPECT(harness.stale()[0].ledgerID() == Ledger::ID{1});
+ using Trigger = std::function;
+
+ std::vector triggers = {
+ [&](TestValidations& vals) { vals.currentTrusted(); },
+ [&](TestValidations& vals) { vals.getCurrentPublicKeys(); },
+ [&](TestValidations& vals) { vals.getPreferred(genesisLedger); },
+ [&](TestValidations& vals) {
+ vals.getNodesAfter(ledgerA, ledgerA.id());
+ }};
+ for (Trigger trigger : triggers)
+ {
+ TestHarness harness(h.oracle);
+ Node n = harness.makeNode();
+
+ BEAST_EXPECT(
+ ValStatus::current == harness.add(n.validate(ledgerAB)));
+ trigger(harness.vals());
+ BEAST_EXPECT(
+ harness.vals().getNodesAfter(ledgerA, ledgerA.id()) == 1);
+ BEAST_EXPECT(
+ harness.vals().getPreferred(genesisLedger) ==
+ std::make_pair(ledgerAB.seq(), ledgerAB.id()));
+ BEAST_EXPECT(harness.stale().empty());
+ harness.clock().advance(harness.parms().validationCURRENT_LOCAL);
+
+ // trigger check for stale
+ trigger(harness.vals());
+
+ BEAST_EXPECT(harness.stale().size() == 1);
+ BEAST_EXPECT(harness.stale()[0].ledgerID() == ledgerAB.id());
+ BEAST_EXPECT(
+ harness.vals().getNodesAfter(ledgerA, ledgerA.id()) == 0);
+ BEAST_EXPECT(
+ harness.vals().getPreferred(genesisLedger) ==
+ std::make_pair(Ledger::Seq{0}, Ledger::ID{0}));
+ }
}
void
testGetNodesAfter()
{
- // Test getting number of nodes working on a validation following
- // a prescribed one
+ // Test getting number of nodes working on a validation descending
+ // a prescribed one. This count should only be for trusted nodes, but
+ // includes partial and full validations
+
using namespace std::chrono_literals;
+ testcase("Get nodes after");
- TestHarness harness;
+ LedgerHistoryHelper h;
+ Ledger ledgerA = h["a"];
+ Ledger ledgerAB = h["ab"];
+ Ledger ledgerABC = h["abc"];
+ Ledger ledgerAD = h["ad"];
+
+ TestHarness harness(h.oracle);
Node a = harness.makeNode(), b = harness.makeNode(),
c = harness.makeNode(), d = harness.makeNode();
-
c.untrust();
- // first round a,b,c agree, d has differing id
- for (auto const& node : {a, b, c})
- BEAST_EXPECT(AddOutcome::current ==
- harness.add(node, Ledger::Seq{1}, Ledger::ID{1}));
- BEAST_EXPECT(AddOutcome::current ==
- harness.add(d, Ledger::Seq{1}, Ledger::ID{10}));
+ // first round a,b,c agree, d has is partial
+ BEAST_EXPECT(ValStatus::current == harness.add(a.validate(ledgerA)));
+ BEAST_EXPECT(ValStatus::current == harness.add(b.validate(ledgerA)));
+ BEAST_EXPECT(ValStatus::current == harness.add(c.validate(ledgerA)));
+ BEAST_EXPECT(ValStatus::current == harness.add(d.partial(ledgerA)));
- // Nothing past ledger 1 yet
- BEAST_EXPECT(harness.vals().getNodesAfter(Ledger::ID{1}) == 0);
+ for (Ledger const& ledger : {ledgerA, ledgerAB, ledgerABC, ledgerAD})
+ BEAST_EXPECT(
+ harness.vals().getNodesAfter(ledger, ledger.id()) == 0);
harness.clock().advance(5s);
- // a and b have the same prior id, but b has a different current id
- // c is untrusted but on the same prior id
- // d has a different prior id
- BEAST_EXPECT(AddOutcome::current ==
- harness.add(a, Ledger::Seq{2}, Ledger::ID{2}));
- BEAST_EXPECT(AddOutcome::current ==
- harness.add(b, Ledger::Seq{2}, Ledger::ID{20}));
- BEAST_EXPECT(AddOutcome::current ==
- harness.add(c, Ledger::Seq{2}, Ledger::ID{2}));
- BEAST_EXPECT(AddOutcome::current ==
- harness.add(d, Ledger::Seq{2}, Ledger::ID{2}));
-
- BEAST_EXPECT(harness.vals().getNodesAfter(Ledger::ID{1}) == 2);
+ BEAST_EXPECT(ValStatus::current == harness.add(a.validate(ledgerAB)));
+ BEAST_EXPECT(ValStatus::current == harness.add(b.validate(ledgerABC)));
+ BEAST_EXPECT(ValStatus::current == harness.add(c.validate(ledgerAB)));
+ BEAST_EXPECT(ValStatus::current == harness.add(d.partial(ledgerABC)));
+
+ BEAST_EXPECT(harness.vals().getNodesAfter(ledgerA, ledgerA.id()) == 3);
+ BEAST_EXPECT(
+ harness.vals().getNodesAfter(ledgerAB, ledgerAB.id()) == 2);
+ BEAST_EXPECT(
+ harness.vals().getNodesAfter(ledgerABC, ledgerABC.id()) == 0);
+ BEAST_EXPECT(
+ harness.vals().getNodesAfter(ledgerAD, ledgerAD.id()) == 0);
+
+ // If given a ledger inconsistent with the id, is still able to check
+ // using slower method
+ BEAST_EXPECT(harness.vals().getNodesAfter(ledgerAD, ledgerA.id()) == 1);
+ BEAST_EXPECT(
+ harness.vals().getNodesAfter(ledgerAD, ledgerAB.id()) == 2);
}
void
testCurrentTrusted()
{
- // Test getting current trusted validations
using namespace std::chrono_literals;
+ testcase("Current trusted validations");
- TestHarness harness;
+ LedgerHistoryHelper h;
+ Ledger ledgerA = h["a"];
+ Ledger ledgerB = h["b"];
+ Ledger ledgerAC = h["ac"];
+
+ TestHarness harness(h.oracle);
Node a = harness.makeNode(), b = harness.makeNode();
b.untrust();
- BEAST_EXPECT(AddOutcome::current ==
- harness.add(a, Ledger::Seq{1}, Ledger::ID{1}));
- BEAST_EXPECT(AddOutcome::current ==
- harness.add(b, Ledger::Seq{1}, Ledger::ID{3}));
+ BEAST_EXPECT(ValStatus::current == harness.add(a.validate(ledgerA)));
+ BEAST_EXPECT(ValStatus::current == harness.add(b.validate(ledgerB)));
// Only a is trusted
BEAST_EXPECT(harness.vals().currentTrusted().size() == 1);
BEAST_EXPECT(
- harness.vals().currentTrusted()[0].ledgerID() == Ledger::ID{1});
- BEAST_EXPECT(
- harness.vals().currentTrusted()[0].seq() == Ledger::Seq{1});
+ harness.vals().currentTrusted()[0].ledgerID() == ledgerA.id());
+ BEAST_EXPECT(harness.vals().currentTrusted()[0].seq() == ledgerA.seq());
harness.clock().advance(3s);
for (auto const& node : {a, b})
- BEAST_EXPECT(AddOutcome::current ==
- harness.add(node, Ledger::Seq{2}, Ledger::ID{2}));
+ BEAST_EXPECT(
+ ValStatus::current == harness.add(node.validate(ledgerAC)));
// New validation for a
BEAST_EXPECT(harness.vals().currentTrusted().size() == 1);
BEAST_EXPECT(
- harness.vals().currentTrusted()[0].ledgerID() == Ledger::ID{2});
+ harness.vals().currentTrusted()[0].ledgerID() == ledgerAC.id());
BEAST_EXPECT(
- harness.vals().currentTrusted()[0].seq() == Ledger::Seq{2});
+ harness.vals().currentTrusted()[0].seq() == ledgerAC.seq());
// Pass enough time for it to go stale
harness.clock().advance(harness.parms().validationCURRENT_LOCAL);
@@ -498,36 +594,40 @@ class Validations_test : public beast::unit_test::suite
void
testGetCurrentPublicKeys()
{
- // Test getting current keys validations
using namespace std::chrono_literals;
+ testcase("Current public keys");
+
+ LedgerHistoryHelper h;
+ Ledger ledgerA = h["a"];
+ Ledger ledgerAC = h["ac"];
- TestHarness harness;
+ TestHarness harness(h.oracle);
Node a = harness.makeNode(), b = harness.makeNode();
b.untrust();
for (auto const& node : {a, b})
- BEAST_EXPECT(AddOutcome::current ==
- harness.add(node, Ledger::Seq{1}, Ledger::ID{1}));
+ BEAST_EXPECT(
+ ValStatus::current == harness.add(node.validate(ledgerA)));
{
- hash_set const expectedKeys = {
- a.masterKey(), b.masterKey()};
+ hash_set const expectedKeys = {a.masterKey(),
+ b.masterKey()};
BEAST_EXPECT(harness.vals().getCurrentPublicKeys() == expectedKeys);
}
harness.clock().advance(3s);
- // Change keys
+ // Change keys and issue partials
a.advanceKey();
b.advanceKey();
for (auto const& node : {a, b})
- BEAST_EXPECT(AddOutcome::current ==
- harness.add(node, Ledger::Seq{2}, Ledger::ID{2}));
+ BEAST_EXPECT(
+ ValStatus::current == harness.add(node.partial(ledgerAC)));
{
- hash_set const expectedKeys = {
- a.masterKey(), b.masterKey()};
+ hash_set const expectedKeys = {a.masterKey(),
+ b.masterKey()};
BEAST_EXPECT(harness.vals().getCurrentPublicKeys() == expectedKeys);
}
@@ -536,98 +636,12 @@ class Validations_test : public beast::unit_test::suite
BEAST_EXPECT(harness.vals().getCurrentPublicKeys().empty());
}
- void
- testCurrentTrustedDistribution()
- {
- // Test the trusted distribution calculation, including ledger slips
- // and sequence cutoffs
- using namespace std::chrono_literals;
-
- TestHarness harness;
-
- Node baby = harness.makeNode(), papa = harness.makeNode(),
- mama = harness.makeNode(), goldilocks = harness.makeNode();
- goldilocks.untrust();
-
- // Stagger the validations around sequence 2
- // papa on seq 1 is behind
- // baby on seq 2 is just right
- // mama on seq 3 is ahead
- // goldilocks on seq 2, but is not trusted
-
- for (auto const& node : {baby, papa, mama, goldilocks})
- BEAST_EXPECT(AddOutcome::current ==
- harness.add(node, Ledger::Seq{1}, Ledger::ID{1}));
-
- harness.clock().advance(1s);
- for (auto const& node : {baby, mama, goldilocks})
- BEAST_EXPECT(AddOutcome::current ==
- harness.add(node, Ledger::Seq{2}, Ledger::ID{2}));
-
- harness.clock().advance(1s);
- BEAST_EXPECT(AddOutcome::current ==
- harness.add(mama, Ledger::Seq{3}, Ledger::ID{3}));
-
- {
- // Allow slippage that treats all trusted as the current ledger
- auto res = harness.vals().currentTrustedDistribution(
- Ledger::ID{2}, // Current ledger
- Ledger::ID{1}, // Prior ledger
- Ledger::Seq{0}); // No cutoff
-
- BEAST_EXPECT(res.size() == 1);
- BEAST_EXPECT(res[Ledger::ID{2}] == 3);
- BEAST_EXPECT(
- getPreferredLedger(Ledger::ID{2}, res) == Ledger::ID{2});
- }
-
- {
- // Don't allow slippage back for prior ledger
- auto res = harness.vals().currentTrustedDistribution(
- Ledger::ID{2}, // Current ledger
- Ledger::ID{0}, // No prior ledger
- Ledger::Seq{0}); // No cutoff
-
- BEAST_EXPECT(res.size() == 2);
- BEAST_EXPECT(res[Ledger::ID{2}] == 2);
- BEAST_EXPECT(res[Ledger::ID{1}] == 1);
- BEAST_EXPECT(
- getPreferredLedger(Ledger::ID{2}, res) == Ledger::ID{2});
- }
-
- {
- // Don't allow any slips
- auto res = harness.vals().currentTrustedDistribution(
- Ledger::ID{0}, // No current ledger
- Ledger::ID{0}, // No prior ledger
- Ledger::Seq{0}); // No cutoff
-
- BEAST_EXPECT(res.size() == 3);
- BEAST_EXPECT(res[Ledger::ID{1}] == 1);
- BEAST_EXPECT(res[Ledger::ID{2}] == 1);
- BEAST_EXPECT(res[Ledger::ID{3}] == 1);
- BEAST_EXPECT(
- getPreferredLedger(Ledger::ID{0}, res) == Ledger::ID{3});
- }
-
- {
- // Cutoff old sequence numbers
- auto res = harness.vals().currentTrustedDistribution(
- Ledger::ID{2}, // current ledger
- Ledger::ID{1}, // prior ledger
- Ledger::Seq{2}); // Only sequence 2 or later
- BEAST_EXPECT(res.size() == 1);
- BEAST_EXPECT(res[Ledger::ID{2}] == 2);
- BEAST_EXPECT(
- getPreferredLedger(Ledger::ID{2}, res) == Ledger::ID{2});
- }
- }
-
void
testTrustedByLedgerFunctions()
{
// Test the Validations functions that calculate a value by ledger ID
using namespace std::chrono_literals;
+ testcase("By ledger functions");
// Several Validations functions return a set of values associated
// with trusted ledgers sharing the same ledger ID. The tests below
@@ -635,14 +649,19 @@ class Validations_test : public beast::unit_test::suite
// verifying that the Validations member functions all calculate the
// proper transformation of the available ledgers.
- TestHarness harness;
+ LedgerHistoryHelper h;
+ TestHarness harness(h.oracle);
+
Node a = harness.makeNode(), b = harness.makeNode(),
- c = harness.makeNode(), d = harness.makeNode();
+ c = harness.makeNode(), d = harness.makeNode(),
+ e = harness.makeNode();
+
c.untrust();
// Mix of load fees
a.setLoadFee(12);
b.setLoadFee(1);
c.setLoadFee(12);
+ e.setLoadFee(12);
hash_map> trustedValidations;
@@ -658,9 +677,11 @@ class Validations_test : public beast::unit_test::suite
auto const& id = it.first;
auto const& expectedValidations = it.second;
- BEAST_EXPECT(harness.vals().numTrustedForLedger(id) ==
+ BEAST_EXPECT(
+ harness.vals().numTrustedForLedger(id) ==
expectedValidations.size());
- BEAST_EXPECT(sorted(harness.vals().getTrustedForLedger(id)) ==
+ BEAST_EXPECT(
+ sorted(harness.vals().getTrustedForLedger(id)) ==
sorted(expectedValidations));
std::vector expectedTimes;
@@ -672,45 +693,60 @@ class Validations_test : public beast::unit_test::suite
expectedFees.push_back(val.loadFee().value_or(baseFee));
}
- BEAST_EXPECT(sorted(harness.vals().fees(id, baseFee)) ==
+ BEAST_EXPECT(
+ sorted(harness.vals().fees(id, baseFee)) ==
sorted(expectedFees));
- BEAST_EXPECT(sorted(harness.vals().getTrustedValidationTimes(
- id)) == sorted(expectedTimes));
+ BEAST_EXPECT(
+ sorted(harness.vals().getTrustedValidationTimes(id)) ==
+ sorted(expectedTimes));
}
};
//----------------------------------------------------------------------
+ Ledger ledgerA = h["a"];
+ Ledger ledgerB = h["b"];
+ Ledger ledgerAC = h["ac"];
+
// Add a dummy ID to cover unknown ledger identifiers
trustedValidations[Ledger::ID{100}] = {};
- // first round a,b,c agree, d differs
+ // first round a,b,c agree
for (auto const& node : {a, b, c})
{
- auto const val = node.validation(Ledger::Seq{1}, Ledger::ID{1});
- BEAST_EXPECT(AddOutcome::current == harness.add(node, val));
+ auto const val = node.validate(ledgerA);
+ BEAST_EXPECT(ValStatus::current == harness.add(val));
if (val.trusted())
trustedValidations[val.ledgerID()].emplace_back(val);
}
+ // d diagrees
{
- auto const val = d.validation(Ledger::Seq{1}, Ledger::ID{11});
- BEAST_EXPECT(AddOutcome::current == harness.add(d, val));
+ auto const val = d.validate(ledgerB);
+ BEAST_EXPECT(ValStatus::current == harness.add(val));
trustedValidations[val.ledgerID()].emplace_back(val);
}
+ // e only issues partials
+ {
+ BEAST_EXPECT(ValStatus::current == harness.add(e.partial(ledgerA)));
+ }
harness.clock().advance(5s);
- // second round, a,b,c move to ledger 2, d now thinks ledger 1
+ // second round, a,b,c move to ledger 2
for (auto const& node : {a, b, c})
{
- auto const val = node.validation(Ledger::Seq{2}, Ledger::ID{2});
- BEAST_EXPECT(AddOutcome::current == harness.add(node, val));
+ auto const val = node.validate(ledgerAC);
+ BEAST_EXPECT(ValStatus::current == harness.add(val));
if (val.trusted())
trustedValidations[val.ledgerID()].emplace_back(val);
}
+ // d now thinks ledger 1, but cannot re-issue a previously used seq
{
- auto const val = d.validation(Ledger::Seq{2}, Ledger::ID{1});
- BEAST_EXPECT(AddOutcome::current == harness.add(d, val));
- trustedValidations[val.ledgerID()].emplace_back(val);
+ BEAST_EXPECT(ValStatus::badSeq == harness.add(d.partial(ledgerA)));
+ }
+ // e only issues partials
+ {
+ BEAST_EXPECT(
+ ValStatus::current == harness.add(e.partial(ledgerAC)));
}
compare();
@@ -720,16 +756,18 @@ class Validations_test : public beast::unit_test::suite
testExpire()
{
// Verify expiring clears out validations stored by ledger
-
- TestHarness harness;
+ testcase("Expire validations");
+ LedgerHistoryHelper h;
+ TestHarness harness(h.oracle);
Node a = harness.makeNode();
- BEAST_EXPECT(AddOutcome::current ==
- harness.add(a, Ledger::Seq{1}, Ledger::ID{1}));
- BEAST_EXPECT(harness.vals().numTrustedForLedger(Ledger::ID{1}));
+ Ledger ledgerA = h["a"];
+
+ BEAST_EXPECT(ValStatus::current == harness.add(a.validate(ledgerA)));
+ BEAST_EXPECT(harness.vals().numTrustedForLedger(ledgerA.id()));
harness.clock().advance(harness.parms().validationSET_EXPIRES);
harness.vals().expire();
- BEAST_EXPECT(!harness.vals().numTrustedForLedger(Ledger::ID{1}));
+ BEAST_EXPECT(!harness.vals().numTrustedForLedger(ledgerA.id()));
}
void
@@ -737,28 +775,31 @@ class Validations_test : public beast::unit_test::suite
{
// Test final flush of validations
using namespace std::chrono_literals;
+ testcase("Flush validations");
- TestHarness harness;
-
+ LedgerHistoryHelper h;
+ TestHarness harness(h.oracle);
Node a = harness.makeNode(), b = harness.makeNode(),
c = harness.makeNode();
c.untrust();
+ Ledger ledgerA = h["a"];
+ Ledger ledgerAB = h["ab"];
+
hash_map expected;
for (auto const& node : {a, b, c})
{
- auto const val = node.validation(Ledger::Seq{1}, Ledger::ID{1});
- BEAST_EXPECT(AddOutcome::current == harness.add(node, val));
+ auto const val = node.validate(ledgerA);
+ BEAST_EXPECT(ValStatus::current == harness.add(val));
expected.emplace(node.masterKey(), val);
}
Validation staleA = expected.find(a.masterKey())->second;
-
// Send in a new validation for a, saving the new one into the expected
// map after setting the proper prior ledger ID it replaced
harness.clock().advance(1s);
- auto newVal = a.validation(Ledger::Seq{2}, Ledger::ID{2});
- BEAST_EXPECT(AddOutcome::current == harness.add(a, newVal));
+ auto newVal = a.validate(ledgerAB);
+ BEAST_EXPECT(ValStatus::current == harness.add(newVal));
expected.find(a.masterKey())->second = newVal;
// Now flush
@@ -777,52 +818,242 @@ class Validations_test : public beast::unit_test::suite
void
testGetPreferredLedger()
{
- using Distribution = hash_map;
+ using namespace std::chrono_literals;
+ testcase("Preferred Ledger");
- {
- Ledger::ID const current{1};
- Distribution dist;
- BEAST_EXPECT(getPreferredLedger(current, dist) == current);
- }
+ LedgerHistoryHelper h;
+ TestHarness harness(h.oracle);
+ Node a = harness.makeNode(), b = harness.makeNode(),
+ c = harness.makeNode(), d = harness.makeNode();
+ c.untrust();
- {
- Ledger::ID const current{1};
- Distribution dist;
- dist[Ledger::ID{2}] = 2;
- BEAST_EXPECT(getPreferredLedger(current, dist) == Ledger::ID{2});
- }
+ Ledger ledgerA = h["a"];
+ Ledger ledgerB = h["b"];
+ Ledger ledgerAC = h["ac"];
+ Ledger ledgerACD = h["acd"];
- {
- Ledger::ID const current{1};
- Distribution dist;
- dist[Ledger::ID{1}] = 1;
- dist[Ledger::ID{2}] = 2;
- BEAST_EXPECT(getPreferredLedger(current, dist) == Ledger::ID{2});
- }
+ using Seq = Ledger::Seq;
+ using ID = Ledger::ID;
- {
- Ledger::ID const current{1};
- Distribution dist;
- dist[Ledger::ID{1}] = 2;
- dist[Ledger::ID{2}] = 2;
- BEAST_EXPECT(getPreferredLedger(current, dist) == current);
- }
+ auto pref = [](Ledger ledger) {
+ return std::make_pair(ledger.seq(), ledger.id());
+ };
- {
- Ledger::ID const current{2};
- Distribution dist;
- dist[Ledger::ID{1}] = 2;
- dist[Ledger::ID{2}] = 2;
- BEAST_EXPECT(getPreferredLedger(current, dist) == current);
- }
+ // Empty (no ledgers)
+ BEAST_EXPECT(
+ harness.vals().getPreferred(ledgerA) == pref(genesisLedger));
- {
- Ledger::ID const current{1};
- Distribution dist;
- dist[Ledger::ID{2}] = 2;
- dist[Ledger::ID{3}] = 2;
- BEAST_EXPECT(getPreferredLedger(current, dist) == Ledger::ID{3});
- }
+ // Single ledger
+ BEAST_EXPECT(ValStatus::current == harness.add(a.validate(ledgerB)));
+ BEAST_EXPECT(harness.vals().getPreferred(ledgerA) == pref(ledgerB));
+ BEAST_EXPECT(harness.vals().getPreferred(ledgerB) == pref(ledgerB));
+
+ // Minimum valid sequence
+ BEAST_EXPECT(
+ harness.vals().getPreferred(ledgerA, Seq{10}) == ledgerA.id());
+
+ // Untrusted doesn't impact preferred ledger
+ // (ledgerB has tie-break over ledgerA)
+ BEAST_EXPECT(ValStatus::current == harness.add(b.validate(ledgerA)));
+ BEAST_EXPECT(ValStatus::current == harness.add(c.validate(ledgerA)));
+ BEAST_EXPECT(ledgerB.id() > ledgerA.id());
+ BEAST_EXPECT(harness.vals().getPreferred(ledgerA) == pref(ledgerB));
+ BEAST_EXPECT(harness.vals().getPreferred(ledgerB) == pref(ledgerB));
+
+ // Partial does break ties
+ BEAST_EXPECT(ValStatus::current == harness.add(d.partial(ledgerA)));
+ BEAST_EXPECT(harness.vals().getPreferred(ledgerA) == pref(ledgerA));
+ BEAST_EXPECT(harness.vals().getPreferred(ledgerB) == pref(ledgerA));
+
+ harness.clock().advance(5s);
+
+ // Parent of preferred-> stick with ledger
+ for (auto const& node : {a, b, c, d})
+ BEAST_EXPECT(
+ ValStatus::current == harness.add(node.validate(ledgerAC)));
+ // Parent of preferred stays put
+ BEAST_EXPECT(harness.vals().getPreferred(ledgerA) == pref(ledgerA));
+ // Earlier different chain, switch
+ BEAST_EXPECT(harness.vals().getPreferred(ledgerB) == pref(ledgerAC));
+ // Later on chain, stays where it is
+ BEAST_EXPECT(harness.vals().getPreferred(ledgerACD) == pref(ledgerACD));
+
+ // Any later grandchild or different chain is preferred
+ harness.clock().advance(5s);
+ for (auto const& node : {a, b, c, d})
+ BEAST_EXPECT(
+ ValStatus::current == harness.add(node.validate(ledgerACD)));
+ for (auto const& ledger : {ledgerA, ledgerB, ledgerACD})
+ BEAST_EXPECT(
+ harness.vals().getPreferred(ledger) == pref(ledgerACD));
+ }
+
+ void
+ testGetPreferredLCL()
+ {
+ using namespace std::chrono_literals;
+ testcase("Get preferred LCL");
+
+ LedgerHistoryHelper h;
+ TestHarness harness(h.oracle);
+ Node a = harness.makeNode();
+
+ Ledger ledgerA = h["a"];
+ Ledger ledgerB = h["b"];
+ Ledger ledgerC = h["c"];
+
+ using ID = Ledger::ID;
+ using Seq = Ledger::Seq;
+
+ hash_map peerCounts;
+
+ // No trusted validations or counts sticks with current ledger
+ BEAST_EXPECT(
+ harness.vals().getPreferredLCL(ledgerA, Seq{0}, peerCounts) ==
+ ledgerA.id());
+
+ ++peerCounts[ledgerB.id()];
+
+ // No trusted validations, rely on peer counts
+ BEAST_EXPECT(
+ harness.vals().getPreferredLCL(ledgerA, Seq{0}, peerCounts) ==
+ ledgerB.id());
+
+ ++peerCounts[ledgerC.id()];
+ // No trusted validations, tied peers goes with larger ID
+ BEAST_EXPECT(ledgerC.id() > ledgerB.id());
+
+ BEAST_EXPECT(
+ harness.vals().getPreferredLCL(ledgerA, Seq{0}, peerCounts) ==
+ ledgerC.id());
+
+ peerCounts[ledgerC.id()] += 1000;
+
+ // Single trusted always wins over peer counts
+ BEAST_EXPECT(ValStatus::current == harness.add(a.validate(ledgerA)));
+ BEAST_EXPECT(
+ harness.vals().getPreferredLCL(ledgerA, Seq{0}, peerCounts) ==
+ ledgerA.id());
+ BEAST_EXPECT(
+ harness.vals().getPreferredLCL(ledgerB, Seq{0}, peerCounts) ==
+ ledgerA.id());
+ BEAST_EXPECT(
+ harness.vals().getPreferredLCL(ledgerC, Seq{0}, peerCounts) ==
+ ledgerA.id());
+
+ // Stick with current ledger if trusted validation ledger has too old
+ // of a sequence
+ BEAST_EXPECT(
+ harness.vals().getPreferredLCL(ledgerB, Seq{2}, peerCounts) ==
+ ledgerB.id());
+ }
+
+ void
+ testAcquireValidatedLedger()
+ {
+ using namespace std::chrono_literals;
+ testcase("Acquire validated ledger");
+
+ LedgerHistoryHelper h;
+ TestHarness harness(h.oracle);
+ Node a = harness.makeNode();
+ Node b = harness.makeNode();
+
+ using ID = Ledger::ID;
+ using Seq = Ledger::Seq;
+
+ // Validate the ledger before it is actually available
+ Validation val = a.validate(ID{2}, Seq{2}, 0s, 0s, true);
+
+ BEAST_EXPECT(ValStatus::current == harness.add(val));
+ // Validation is available
+ BEAST_EXPECT(harness.vals().numTrustedForLedger(ID{2}) == 1);
+ // but ledger based data is not
+ BEAST_EXPECT(harness.vals().getNodesAfter(genesisLedger, ID{0}) == 0);
+ // Initial preferred branch falls back to the ledger we are trying to
+ // acquire
+ BEAST_EXPECT(
+ harness.vals().getPreferred(genesisLedger) ==
+ std::make_pair(Seq{2}, ID{2}));
+
+ // After adding another unavailable validation, the preferred ledger
+ // breaks ties via higher ID
+ BEAST_EXPECT(
+ ValStatus::current ==
+ harness.add(b.validate(ID{3}, Seq{2}, 0s, 0s, true)));
+ BEAST_EXPECT(
+ harness.vals().getPreferred(genesisLedger) ==
+ std::make_pair(Seq{2}, ID{3}));
+
+ // Create the ledger
+ Ledger ledgerAB = h["ab"];
+ // Now it should be available
+ BEAST_EXPECT(harness.vals().getNodesAfter(genesisLedger, ID{0}) == 1);
+
+ // Create a validation that is not available
+ harness.clock().advance(5s);
+ Validation val2 = a.validate(ID{4}, Seq{4}, 0s, 0s, true);
+ BEAST_EXPECT(ValStatus::current == harness.add(val2));
+ BEAST_EXPECT(harness.vals().numTrustedForLedger(ID{4}) == 1);
+ BEAST_EXPECT(
+ harness.vals().getPreferred(genesisLedger) ==
+ std::make_pair(ledgerAB.seq(), ledgerAB.id()));
+
+ // Another node requesting that ledger still doesn't change things
+ Validation val3 = b.validate(ID{4}, Seq{4}, 0s, 0s, true);
+ BEAST_EXPECT(ValStatus::current == harness.add(val3));
+ BEAST_EXPECT(harness.vals().numTrustedForLedger(ID{4}) == 2);
+ BEAST_EXPECT(
+ harness.vals().getPreferred(genesisLedger) ==
+ std::make_pair(ledgerAB.seq(), ledgerAB.id()));
+
+ // Switch to validation that is available
+ harness.clock().advance(5s);
+ Ledger ledgerABCDE = h["abcde"];
+ BEAST_EXPECT(ValStatus::current == harness.add(a.partial(ledgerABCDE)));
+ BEAST_EXPECT(ValStatus::current == harness.add(b.partial(ledgerABCDE)));
+ BEAST_EXPECT(
+ harness.vals().getPreferred(genesisLedger) ==
+ std::make_pair(ledgerABCDE.seq(), ledgerABCDE.id()));
+ }
+
+ void
+ testNumTrustedForLedger()
+ {
+ testcase("NumTrustedForLedger");
+ LedgerHistoryHelper h;
+ TestHarness harness(h.oracle);
+ Node a = harness.makeNode();
+ Node b = harness.makeNode();
+ Ledger ledgerA = h["a"];
+
+ BEAST_EXPECT(ValStatus::current == harness.add(a.partial(ledgerA)));
+ BEAST_EXPECT(harness.vals().numTrustedForLedger(ledgerA.id()) == 0);
+
+ BEAST_EXPECT(ValStatus::current == harness.add(b.validate(ledgerA)));
+ BEAST_EXPECT(harness.vals().numTrustedForLedger(ledgerA.id()) == 1);
+ }
+
+ void
+ testSeqEnforcer()
+ {
+ testcase("SeqEnforcer");
+ using Seq = Ledger::Seq;
+ using namespace std::chrono;
+
+ beast::manual_clock clock;
+ SeqEnforcer enforcer;
+
+ ValidationParms p;
+
+ BEAST_EXPECT(enforcer(clock.now(), Seq{1}, p));
+ BEAST_EXPECT(enforcer(clock.now(), Seq{10}, p));
+ BEAST_EXPECT(!enforcer(clock.now(), Seq{5}, p));
+ BEAST_EXPECT(!enforcer(clock.now(), Seq{9}, p));
+ clock.advance(p.validationSET_EXPIRES - 1ms);
+ BEAST_EXPECT(!enforcer(clock.now(), Seq{1}, p));
+ clock.advance(2ms);
+ BEAST_EXPECT(enforcer(clock.now(), Seq{1}, p));
}
void
@@ -833,15 +1064,18 @@ class Validations_test : public beast::unit_test::suite
testGetNodesAfter();
testCurrentTrusted();
testGetCurrentPublicKeys();
- testCurrentTrustedDistribution();
testTrustedByLedgerFunctions();
testExpire();
testFlush();
testGetPreferredLedger();
+ testGetPreferredLCL();
+ testAcquireValidatedLedger();
+ testNumTrustedForLedger();
+ testSeqEnforcer();
}
};
BEAST_DEFINE_TESTSUITE(Validations, consensus, ripple);
-} // csf
-} // test
-} // ripple
+} // namespace csf
+} // namespace test
+} // namespace ripple
diff --git a/src/test/csf/Peer.h b/src/test/csf/Peer.h
index fa07dd3dea4..19c94c82bd3 100644
--- a/src/test/csf/Peer.h
+++ b/src/test/csf/Peer.h
@@ -110,14 +110,30 @@ struct Peer
}
};
- /** Generic Validations policy that simply ignores recently stale validations
+ /** Generic Validations adaptor that simply ignores recently stale validations
*/
- class StalePolicy
+ class ValAdaptor
{
Peer& p_;
public:
- StalePolicy(Peer& p) : p_{p}
+ struct Mutex
+ {
+ void
+ lock()
+ {
+ }
+
+ void
+ unlock()
+ {
+ }
+ };
+
+ using Validation = csf::Validation;
+ using Ledger = csf::Ledger;
+
+ ValAdaptor(Peer& p) : p_{p}
{
}
@@ -136,20 +152,13 @@ struct Peer
flush(hash_map&& remaining)
{
}
- };
- /** Non-locking mutex to avoid locks in generic Validations
- */
- struct NotAMutex
- {
- void
- lock()
- {
- }
-
- void
- unlock()
+ boost::optional
+ acquire(Ledger::ID const & id)
{
+ if(Ledger const * ledger = p_.acquireLedger(id))
+ return *ledger;
+ return boost::none;
}
};
@@ -195,9 +204,7 @@ struct Peer
hash_map ledgers;
//! Validations from trusted nodes
- Validations validations;
- using AddOutcome =
- Validations::AddOutcome;
+ Validations validations;
//! The most recent ledger that has been fully validated by the network from
//! the perspective of this Peer
@@ -213,12 +220,13 @@ struct Peer
//! TxSet associated with a TxSet::ID
bc::flat_map txSets;
- // Ledgers and txSets that we have already attempted to acquire
- bc::flat_set acquiringLedgers;
- bc::flat_set acquiringTxSets;
+ // Ledgers/TxSets we are acquiring and when that request times out
+ bc::flat_map acquiringLedgers;
+ bc::flat_map acquiringTxSets;
//! The number of ledgers this peer has completed
int completedLedgers = 0;
+
//! The number of ledgers this peer should complete before stopping to run
int targetLedgers = std::numeric_limits::max();
@@ -231,6 +239,9 @@ struct Peer
//! Whether to simulate running as validator or a tracking node
bool runAsValidator = true;
+ //! Enforce invariants on validation sequence numbers
+ SeqEnforcer seqEnforcer;
+
//TODO: Consider removing these two, they are only a convenience for tests
// Number of proposers in the prior round
std::size_t prevProposers = 0;
@@ -275,7 +286,9 @@ struct Peer
, scheduler{s}
, net{n}
, trustGraph(tg)
- , validations{ValidationParms{}, s.clock(), j, *this}
+ , lastClosedLedger{Ledger::MakeGenesis{}}
+ , validations{ValidationParms{}, s.clock(), *this}
+ , fullyValidatedLedger{Ledger::MakeGenesis{}}
, collectors{c}
{
// All peers start from the default constructed genesis ledger
@@ -380,16 +393,30 @@ struct Peer
Ledger const*
acquireLedger(Ledger::ID const& ledgerID)
{
+ using namespace std::chrono;
+
auto it = ledgers.find(ledgerID);
if (it != ledgers.end())
return &(it->second);
- // Don't retry if we already are acquiring it
- if(!acquiringLedgers.emplace(ledgerID).second)
+ // No peers
+ if(net.links(this).empty())
return nullptr;
+ // Don't retry if we already are acquiring it and haven't timed out
+ auto aIt = acquiringLedgers.find(ledgerID);
+ if(aIt!= acquiringLedgers.end())
+ {
+ if(scheduler.now() < aIt->second)
+ return nullptr;
+ }
+
+
+ SimDuration minDuration{10s};
for (auto const& link : net.links(this))
{
+ minDuration = std::min(minDuration, link.data.delay);
+
// Send a messsage to neighbors to find the ledger
net.send(
this, link.target, [ to = link.target, from = this, ledgerID ]() {
@@ -400,11 +427,13 @@ struct Peer
// requesting peer where it is added to the available
// ledgers
to->net.send(to, from, [ from, ledger = it->second ]() {
+ from->acquiringLedgers.erase(ledger.id());
from->ledgers.emplace(ledger.id(), ledger);
});
}
});
}
+ acquiringLedgers[ledgerID] = scheduler.now() + 2 * minDuration;
return nullptr;
}
@@ -416,12 +445,22 @@ struct Peer
if (it != txSets.end())
return &(it->second);
- // Don't retry if we already are acquiring it
- if(!acquiringTxSets.emplace(setId).second)
+ // No peers
+ if(net.links(this).empty())
return nullptr;
+ // Don't retry if we already are acquiring it and haven't timed out
+ auto aIt = acquiringTxSets.find(setId);
+ if(aIt!= acquiringTxSets.end())
+ {
+ if(scheduler.now() < aIt->second)
+ return nullptr;
+ }
+
+ SimDuration minDuration{10s};
for (auto const& link : net.links(this))
{
+ minDuration = std::min(minDuration, link.data.delay);
// Send a message to neighbors to find the tx set
net.send(
this, link.target, [ to = link.target, from = this, setId ]() {
@@ -432,11 +471,13 @@ struct Peer
// requesting peer, where it is handled like a TxSet
// that was broadcast over the network
to->net.send(to, from, [ from, txSet = it->second ]() {
+ from->acquiringTxSets.erase(txSet.id());
from->handle(txSet);
});
}
});
}
+ acquiringTxSets[setId] = scheduler.now() + 2 * minDuration;
return nullptr;
}
@@ -453,9 +494,9 @@ struct Peer
}
std::size_t
- proposersFinished(Ledger::ID const& prevLedger)
+ proposersFinished(Ledger const & prevLedger, Ledger::ID const& prevLedgerID)
{
- return validations.getNodesAfter(prevLedger);
+ return validations.getNodesAfter(prevLedger, prevLedgerID);
}
Result
@@ -504,7 +545,10 @@ struct Peer
ConsensusMode const& mode,
Json::Value&& consensusJson)
{
- schedule(delays.ledgerAccept, [&]() {
+ schedule(delays.ledgerAccept, [=]() {
+ const bool proposing = mode == ConsensusMode::proposing;
+ const bool consensusFail = result.state == ConsensusState::MovedOn;
+
TxSet const acceptedTxs = injectTxs(prevLedger, result.set);
Ledger const newLedger = oracle.accept(
prevLedger,
@@ -527,18 +571,23 @@ struct Peer
// Only send validation if the new ledger is compatible with our
// fully validated ledger
bool const isCompatible =
- oracle.isAncestor(fullyValidatedLedger, newLedger);
+ newLedger.isAncestor(fullyValidatedLedger);
- if (runAsValidator && isCompatible)
+ // Can only send one validated ledger per seq
+ if (runAsValidator && isCompatible && !consensusFail &&
+ seqEnforcer(
+ scheduler.now(), newLedger.seq(), validations.parms()))
{
+ bool isFull = proposing;
+
Validation v{newLedger.id(),
newLedger.seq(),
now(),
now(),
key,
id,
- false};
- // share is not trusted
+ isFull};
+ // share the new validation; it is trusted by the receiver
share(v);
// we trust ourselves
addTrustedValidation(v);
@@ -564,9 +613,7 @@ struct Peer
Ledger::Seq
earliestAllowedSeq() const
{
- if (lastClosedLedger.seq() > Ledger::Seq{20})
- return lastClosedLedger.seq() - Ledger::Seq{20};
- return Ledger::Seq{0};
+ return fullyValidatedLedger.seq();
}
Ledger::ID
@@ -579,22 +626,15 @@ struct Peer
if (ledger.seq() == Ledger::Seq{0})
return ledgerID;
- Ledger::ID parentID{0};
- // Only set the parent ID if we believe ledger is the right ledger
- if (mode != ConsensusMode::wrongLedger)
- parentID = ledger.parentID();
-
- // Get validators that are on our ledger, or "close" to being on
- // our ledger.
- auto const ledgerCounts = validations.currentTrustedDistribution(
- ledgerID, parentID, earliestAllowedSeq());
-
- Ledger::ID const netLgr = getPreferredLedger(ledgerID, ledgerCounts);
+ Ledger::ID const netLgr =
+ validations.getPreferred(ledger, earliestAllowedSeq());
if (netLgr != ledgerID)
{
+ JLOG(j.trace()) << Json::Compact(validations.getJsonTrie());
issue(WrongPrevLedger{ledgerID, netLgr});
}
+
return netLgr;
}
@@ -641,9 +681,9 @@ struct Peer
{
v.setTrusted();
v.setSeen(now());
- AddOutcome const res = validations.add(v.key(), v);
+ ValStatus const res = validations.add(v.key(), v);
- if(res == AddOutcome::stale || res == AddOutcome::repeat)
+ if(res == ValStatus::stale || res == ValStatus::repeatID)
return false;
// Acquire will try to get from network if not already local
@@ -663,7 +703,7 @@ struct Peer
std::size_t const count = validations.numTrustedForLedger(ledger.id());
std::size_t const numTrustedPeers = trustGraph.graph().outDegree(this);
quorum = static_cast