Use LedgerTrie for preferred ledger (RIPD-1551):

These changes augment the Validations class with a LedgerTrie to better
track the history of support for validated ledgers. This improves the
selection of the preferred working ledger for consensus. The Validations
class now tracks both full and partial validations. Partial validations
are only used to determine the working ledger; full validations are
required for any quorum related function. Validators are also now
explicitly restricted to sending validations with increasing ledger
sequence number.
This commit is contained in:
Brad Chase
2017-12-07 11:00:42 -05:00
parent 1c44c4a43e
commit 94c6a2a850
26 changed files with 3648 additions and 1043 deletions

View File

@@ -1634,6 +1634,8 @@
</ClInclude>
<ClInclude Include="..\..\src\ripple\consensus\LedgerTiming.h">
</ClInclude>
<ClInclude Include="..\..\src\ripple\consensus\LedgerTrie.h">
</ClInclude>
<ClInclude Include="..\..\src\ripple\consensus\Validations.h">
</ClInclude>
<ClInclude Include="..\..\src\ripple\core\ClosureCounter.h">
@@ -4501,6 +4503,10 @@
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='debug|x64'">True</ExcludedFromBuild>
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='release|x64'">True</ExcludedFromBuild>
</ClCompile>
<ClCompile Include="..\..\src\test\app\RCLValidations_test.cpp">
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='debug|x64'">True</ExcludedFromBuild>
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='release|x64'">True</ExcludedFromBuild>
</ClCompile>
<ClCompile Include="..\..\src\test\app\Regression_test.cpp">
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='debug|x64'">True</ExcludedFromBuild>
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='release|x64'">True</ExcludedFromBuild>
@@ -4685,6 +4691,10 @@
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='debug|x64'">True</ExcludedFromBuild>
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='release|x64'">True</ExcludedFromBuild>
</ClCompile>
<ClCompile Include="..\..\src\test\consensus\LedgerTrie_test.cpp">
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='debug|x64'">True</ExcludedFromBuild>
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='release|x64'">True</ExcludedFromBuild>
</ClCompile>
<ClCompile Include="..\..\src\test\consensus\ScaleFreeSim_test.cpp">
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='debug|x64'">True</ExcludedFromBuild>
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='release|x64'">True</ExcludedFromBuild>

View File

@@ -2151,6 +2151,9 @@
<ClInclude Include="..\..\src\ripple\consensus\LedgerTiming.h">
<Filter>ripple\consensus</Filter>
</ClInclude>
<ClInclude Include="..\..\src\ripple\consensus\LedgerTrie.h">
<Filter>ripple\consensus</Filter>
</ClInclude>
<ClInclude Include="..\..\src\ripple\consensus\Validations.h">
<Filter>ripple\consensus</Filter>
</ClInclude>
@@ -5268,6 +5271,9 @@
<ClCompile Include="..\..\src\test\app\PseudoTx_test.cpp">
<Filter>test\app</Filter>
</ClCompile>
<ClCompile Include="..\..\src\test\app\RCLValidations_test.cpp">
<Filter>test\app</Filter>
</ClCompile>
<ClCompile Include="..\..\src\test\app\Regression_test.cpp">
<Filter>test\app</Filter>
</ClCompile>
@@ -5406,6 +5412,9 @@
<ClCompile Include="..\..\src\test\consensus\LedgerTiming_test.cpp">
<Filter>test\consensus</Filter>
</ClCompile>
<ClCompile Include="..\..\src\test\consensus\LedgerTrie_test.cpp">
<Filter>test\consensus</Filter>
</ClCompile>
<ClCompile Include="..\..\src\test\consensus\ScaleFreeSim_test.cpp">
<Filter>test\consensus</Filter>
</ClCompile>

View File

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

View File

@@ -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<uint256, std::uint32_t> 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<STValidation>(
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

View File

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

View File

@@ -19,6 +19,8 @@
#include <BeastConfig.h>
#include <ripple/app/consensus/RCLValidations.h>
#include <ripple/app/ledger/InboundLedger.h>
#include <ripple/app/ledger/InboundLedgers.h>
#include <ripple/app/ledger/LedgerMaster.h>
#include <ripple/app/main/Application.h>
#include <ripple/app/misc/NetworkOPs.h>
@@ -36,19 +38,119 @@
namespace ripple {
RCLValidationsPolicy::RCLValidationsPolicy(Application& app) : app_(app)
RCLValidatedLedger::RCLValidatedLedger(MakeGenesis)
: ledgerID_{0}, ledgerSeq_{0}
{
}
RCLValidatedLedger::RCLValidatedLedger(
std::shared_ptr<Ledger const> 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<Seq>(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<RCLValidatedLedger>
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<PublicKey, RCLValidation>&& remaining)
RCLValidationsAdaptor::flush(hash_map<PublicKey, RCLValidation>&& remaining)
{
bool anyNew = false;
{
@@ -106,7 +208,7 @@ RCLValidationsPolicy::flush(hash_map<PublicKey, RCLValidation>&& 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<PublicKey> pubKey = app.validators().getTrustedKey(signer);
if (!val->isTrusted() && pubKey)
boost::optional<PublicKey> 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;
RCLValidations& validations = app.getValidations();
beast::Journal j = validations.adaptor().journal();
// only add trusted or listed
if (pubKey)
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;
if(j.error())
dmp(j.error(), "missing ledger sequence field");
return false;
}
AddOutcome const res = validations.add(*pubKey, val);
// 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 is a duplicate validation
if (res == AddOutcome::repeat)
return false;
// 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);
JLOG(j.warn()) << "Trusted node "
<< toBase58(TokenType::TOKEN_NODE_PUBLIC, *pubKey)
<< " published multiple validations for ledger "
<< seq;
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);
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

View File

@@ -20,9 +20,11 @@
#ifndef RIPPLE_APP_CONSENSUSS_VALIDATIONS_H_INCLUDED
#define RIPPLE_APP_CONSENSUSS_VALIDATIONS_H_INCLUDED
#include <ripple/app/ledger/Ledger.h>
#include <ripple/basics/ScopedLock.h>
#include <ripple/consensus/Validations.h>
#include <ripple/protocol/Protocol.h>
#include <ripple/protocol/RippleLedgerHash.h>
#include <ripple/protocol/STValidation.h>
#include <vector>
@@ -39,6 +41,8 @@ class RCLValidation
{
STValidation::pointer val_;
public:
using NodeKey = ripple::PublicKey;
using NodeID = ripple::NodeID;
/** Constructor
@@ -59,9 +63,7 @@ public:
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 @@ public:
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<std::uint32_t>
loadFee() const
@@ -115,35 +124,77 @@ public:
};
/** 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<LockType>;
using ScopedUnlockType = GenericScopedUnlock<LockType>;
Application& app_;
// Lock for managing staleValidations_ and writing_
std::mutex staleLock_;
std::vector<RCLValidation> 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&);
public:
using ID = LedgerHash;
using Seq = LedgerIndex;
struct MakeGenesis
{
};
RCLValidationsPolicy(Application & app);
RCLValidatedLedger(MakeGenesis);
RCLValidatedLedger(
std::shared_ptr<Ledger const> const& ledger,
beast::Journal j);
/// 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<uint256> 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;
RCLValidationsAdaptor(Application& app, beast::Journal j);
/** Current time used to determine if validations are stale.
*/
*/
NetClock::time_point
now() const;
@@ -163,20 +214,45 @@ public:
@param remaining The remaining validations to flush
*/
void
flush(hash_map<PublicKey, RCLValidation> && remaining);
flush(hash_map<PublicKey, RCLValidation>&& remaining);
/** Attempt to acquire the ledger with given id from the network */
boost::optional<RCLValidatedLedger>
acquire(LedgerHash const & id);
beast::Journal
journal() const
{
return j_;
}
private:
using ScopedLockType = std::lock_guard<Mutex>;
using ScopedUnlockType = GenericScopedUnlock<Mutex>;
Application& app_;
beast::Journal j_;
// Lock for managing staleValidations_ and writing_
std::mutex staleLock_;
std::vector<RCLValidation> 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<RCLValidationsPolicy, RCLValidation, std::mutex>;
using RCLValidations = Validations<RCLValidationsAdaptor>;
/** 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

View File

@@ -497,8 +497,7 @@ public:
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 @@ public:
// 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 ();

View File

@@ -74,12 +74,10 @@ class SHAMapStore;
using NodeCache = TaggedCache <SHAMapHash, Blob>;
template <class StalePolicy, class Validation, class MutexType>
template <class Adaptor>
class Validations;
class RCLValidation;
class RCLValidationsPolicy;
using RCLValidations =
Validations<RCLValidationsPolicy, RCLValidation, std::mutex>;
class RCLValidationsAdaptor;
using RCLValidations = Validations<RCLValidationsAdaptor>;
class Application : public beast::PropertyStream::Source
{

View File

@@ -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;
};
//-------------------------------------------------------------------------
// Determine preferred last closed ledger
hash_map<uint256, ValidationCount> ledgers;
{
hash_map<uint256, std::uint32_t> current =
app_.getValidations().currentTrustedDistribution(
closedLedger,
prevClosedLedger,
m_ledgerMaster.getValidLedgerIndex());
for (auto& it: current)
ledgers[it.first].trustedValidations += it.second;
}
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<uint256, std::uint32_t> peerCounts;
peerCounts[closedLedger] = 0;
if (mMode >= omTRACKING)
peerCounts[closedLedger]++;
for (auto& peer : peerList)
{
++ourVC.nodesUsing;
uint256 peerLedger = peer->getClosedLedgerHash();
if (peerLedger.isNonZero())
++peerCounts[peerLedger];
}
for (auto& peer: peerList)
{
uint256 peerLedger = peer->getClosedLedgerHash ();
for(auto const & it: peerCounts)
JLOG(m_journal.debug()) << "L: " << it.first << " n=" << it.second;
if (peerLedger.isNonZero ())
++ledgers[peerLedger].nodesUsing;
}
// 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;

View File

@@ -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<Adaptor>::haveConsensus()
++disagree;
}
}
auto currentFinished = adaptor_.proposersFinished(prevLedgerID_);
auto currentFinished =
adaptor_.proposersFinished(previousLedger_, prevLedgerID_);
JLOG(j_.debug()) << "Checking for TX consensus: agree=" << agree
<< ", disagree=" << disagree;

View File

@@ -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 <ripple/json/json_value.h>
#include <boost/optional.hpp>
#include <algorithm>
#include <memory>
#include <vector>
namespace ripple {
/** The tip of a span of ledger ancestry
*/
template <class Ledger>
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 Ledger>
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<Span>
from(Seq spot) const
{
return sub(spot, end_);
}
// Return the Span from [start_,spot) or none if no such valid span
boost::optional<Span>
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<Ledger>
tip() const
{
Seq tipSeq{end_ - Seq{1}};
return SpanTip<Ledger>{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<Span>
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 <class Ledger>
struct Node
{
Node() = default;
explicit Node(Ledger const& l) : span{l}, tipSupport{1}, branchSupport{1}
{
}
explicit Node(Span<Ledger> s) : span{std::move(s)}
{
}
Span<Ledger> span;
std::uint32_t tipSupport = 0;
std::uint32_t branchSupport = 0;
std::vector<std::unique_ptr<Node>> 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<Node> 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<std::uint32_t>(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 Ledger>
class LedgerTrie
{
using Seq = typename Ledger::Seq;
using ID = typename Ledger::ID;
using Node = ledger_trie_detail::Node<Ledger>;
using Span = ledger_trie_detail::Span<Ledger>;
// The root of the trie. The root is allowed to break the no-single child
// invariant.
std::unique_ptr<Node> root;
// Count of the tip support for each sequence number
std::map<Seq, std::uint32_t> 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<Node*, Seq>
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<Node> 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<Node> 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<Node> const& child : curr->children)
dumpImpl(o, child, offset + 1 + ss.str().size() + 2);
}
}
public:
LedgerTrie() : root{std::make_unique<Node>()}
{
}
/** 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<Span>'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<Span> prefix = loc->span.before(diffSeq);
boost::optional<Span> oldSuffix = loc->span.from(diffSeq);
boost::optional<Span> 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<Node>(*oldSuffix);
newNode->tipSupport = loc->tipSupport;
newNode->branchSupport = loc->branchSupport;
newNode->children = std::move(loc->children);
assert(loc->children.empty());
for(std::unique_ptr<Node> & 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<Node>(*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<Node> 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<Ledger>
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<Node> const& a,
std::unique_ptr<Node> 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<Seq, std::uint32_t> expectedSeqSupport;
std::stack<Node const*> 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

File diff suppressed because it is too large Load Diff

View File

@@ -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 <BeastConfig.h>
#include <ripple/app/consensus/RCLValidations.h>
#include <ripple/app/ledger/Ledger.h>
#include <ripple/basics/Log.h>
#include <ripple/ledger/View.h>
#include <test/jtx.h>
#include <ripple/beast/unit_test.h>
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<std::shared_ptr<Ledger const>> history;
jtx::Env env(*this);
Config config;
auto prev = std::make_shared<Ledger const>(
create_genesis, config,
std::vector<uint256>{}, env.app().family());
history.push_back(prev);
for (auto i = 0; i < (2*maxAncestors + 1); ++i)
{
auto next = std::make_shared<Ledger>(
*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<std::shared_ptr<Ledger const>> 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<Ledger>(
*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 const> 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

View File

@@ -512,8 +512,7 @@ public:
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 @@ public:
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 @@ public:
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 <class E>
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<milliseconds>(0.2 * parms.ledgerGRANULARITY);
SimDuration fDelay = round<milliseconds>(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<std::uint32_t>(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 @@ public:
testConsensusCloseTimeRounding();
testFork();
testHubNetwork();
testPreferredByBranch();
}
};

View File

@@ -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 <BeastConfig.h>
#include <ripple/beast/unit_test.h>
#include <ripple/consensus/LedgerTrie.h>
#include <test/csf/ledgers.h>
#include <unordered_map>
#include <random>
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<Ledger> 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<Ledger> 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<Ledger> 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<Ledger> 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<Ledger> 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<Ledger> 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<Ledger> 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<Ledger> 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<Ledger> 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<Ledger> 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<Ledger> 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<Ledger> 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<Ledger> 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<Ledger> 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<Ledger> 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<Ledger> t;
LedgerHistoryHelper h;
t.insert(h["abc"]);
BEAST_EXPECT(t.getPreferred(Seq{3}).id == h["abc"].id());
}
// Single node smaller child support
{
LedgerTrie<Ledger> 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<Ledger> 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<Ledger> 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<Ledger> 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<Ledger> 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<Ledger> 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<Ledger> 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<Ledger> 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<Ledger> 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<Ledger> 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<Ledger> 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

File diff suppressed because it is too large Load Diff

View File

@@ -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<PeerKey, Validation>&& remaining)
{
}
};
/** Non-locking mutex to avoid locks in generic Validations
*/
struct NotAMutex
{
void
lock()
{
}
void
unlock()
boost::optional<Ledger>
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<Ledger::ID, Ledger> ledgers;
//! Validations from trusted nodes
Validations<StalePolicy, Validation, NotAMutex> validations;
using AddOutcome =
Validations<StalePolicy, Validation, NotAMutex>::AddOutcome;
Validations<ValAdaptor> 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<TxSet::ID, TxSet> txSets;
// Ledgers and txSets that we have already attempted to acquire
bc::flat_set<Ledger::ID> acquiringLedgers;
bc::flat_set<TxSet::ID> acquiringTxSets;
// Ledgers/TxSets we are acquiring and when that request times out
bc::flat_map<Ledger::ID,SimTime> acquiringLedgers;
bc::flat_map<TxSet::ID,SimTime> 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<int>::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<Ledger::Seq> 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<std::size_t>(std::ceil(numTrustedPeers * 0.8));
if (count >= quorum)
if (count >= quorum && ledger.isAncestor(fullyValidatedLedger))
{
issue(FullyValidateLedger{ledger, fullyValidatedLedger});
fullyValidatedLedger = ledger;
@@ -825,21 +865,16 @@ struct Peer
void
startRound()
{
auto const valDistribution = validations.currentTrustedDistribution(
lastClosedLedger.id(),
lastClosedLedger.parentID(),
earliestAllowedSeq());
// Between rounds, we take the majority ledger and use the
Ledger::ID const bestLCL =
getPreferredLedger(lastClosedLedger.id(), valDistribution);
// Between rounds, we take the majority ledger
// In the future, consider taking peer dominant ledger if no validations
// yet
Ledger::ID bestLCL =
validations.getPreferred(lastClosedLedger, earliestAllowedSeq());
if(bestLCL == Ledger::ID{0})
bestLCL = lastClosedLedger.id();
issue(StartRound{bestLCL, lastClosedLedger});
// TODO:
// - Get dominant peer ledger if no validated available?
// - Check that we are switching to something compatible with our
// (network) validated history of ledgers?
consensus.startRound(
now(), bestLCL, lastClosedLedger, runAsValidator);
}

View File

@@ -47,7 +47,6 @@ public:
using const_reference = peers_type::const_reference;
PeerGroup() = default;
PeerGroup(PeerGroup const&) = default;
PeerGroup(Peer* peer) : peers_{1, peer}
{
}
@@ -62,7 +61,6 @@ public:
PeerGroup(std::set<Peer*> const& peers) : peers_{peers.begin(), peers.end()}
{
}
iterator
@@ -101,6 +99,14 @@ public:
return std::find(peers_.begin(), peers_.end(), p) != peers_.end();
}
bool
contains(PeerID id)
{
return std::find_if(peers_.begin(), peers_.end(), [id](Peer const* p) {
return p->id == id;
}) != peers_.end();
}
std::size_t
size() const
{

View File

@@ -65,6 +65,7 @@ class Sim
// Use a deque to have stable pointers even when dynamically adding peers
// - Alternatively consider using unique_ptrs allocated from arena
std::deque<Peer> peers;
PeerGroup allPeers;
public:
std::mt19937_64 rng;
@@ -113,7 +114,9 @@ public:
j);
newPeers.emplace_back(&peers.back());
}
return PeerGroup{newPeers};
PeerGroup res{newPeers};
allPeers = allPeers + res;
return res;
}
//! The number of peers in the simulation
@@ -136,18 +139,28 @@ public:
void
run(SimDuration const& dur);
/** Check whether all peers in the network are synchronized.
/** Check whether all peers in the group are synchronized.
Nodes in the network are synchronized if they share the same last
Nodes in the group are synchronized if they share the same last
fully validated and last generated ledger.
*/
bool
synchronized(PeerGroup const& g) const;
/** Check whether all peers in the network are synchronized
*/
bool
synchronized() const;
/** Calculate the number of branches in the network.
/** Calculate the number of branches in the group.
A branch occurs if two peers have fullyValidatedLedgers that are not on
the same chain of ledgers.
A branch occurs if two nodes in the group have fullyValidatedLedgers
that are not on the same chain of ledgers.
*/
std::size_t
branches(PeerGroup const& g) const;
/** Calculate the number of branches in the network
*/
std::size_t
branches() const;

View File

@@ -53,17 +53,21 @@ class Validation
NetClock::time_point seenTime_;
PeerKey key_;
PeerID nodeID_{0};
bool trusted_ = true;
bool trusted_ = false;
bool full_ = false;
boost::optional<std::uint32_t> loadFee_;
public:
using NodeKey = PeerKey;
using NodeID = PeerID;
Validation(Ledger::ID id,
Ledger::Seq seq,
NetClock::time_point sign,
NetClock::time_point seen,
PeerKey key,
PeerID nodeID,
bool trusted,
bool full,
boost::optional<std::uint32_t> loadFee = boost::none)
: ledgerID_{id}
, seq_{seq}
@@ -71,7 +75,7 @@ public:
, seenTime_{seen}
, key_{key}
, nodeID_{nodeID}
, trusted_{trusted}
, full_{full}
, loadFee_{loadFee}
{
}
@@ -118,6 +122,13 @@ public:
return trusted_;
}
bool
full() const
{
return full_;
}
boost::optional<std::uint32_t>
loadFee() const
{
@@ -133,8 +144,16 @@ public:
auto
asTie() const
{
return std::tie(ledgerID_, seq_, signTime_, seenTime_, key_, nodeID_,
trusted_, loadFee_);
// trusted is a status set by the receiver, so it is not part of the tie
return std::tie(
ledgerID_,
seq_,
signTime_,
seenTime_,
key_,
nodeID_,
loadFee_,
full_);
}
bool
operator==(Validation const& o) const

View File

@@ -48,25 +48,36 @@ Sim::run(SimDuration const & dur)
bool
Sim::synchronized() const
{
if (peers.size() < 1)
return synchronized(allPeers);
}
bool
Sim::synchronized(PeerGroup const & g) const
{
if (g.size() < 1)
return true;
Peer const& ref = peers.front();
return std::all_of(peers.begin(), peers.end(), [&ref](Peer const& p) {
return p.lastClosedLedger.id() ==
ref.lastClosedLedger.id() &&
p.fullyValidatedLedger.id() ==
ref.fullyValidatedLedger.id();
Peer const * ref = g[0];
return std::all_of(g.begin(), g.end(), [&ref](Peer const* p) {
return p->lastClosedLedger.id() ==
ref->lastClosedLedger.id() &&
p->fullyValidatedLedger.id() ==
ref->fullyValidatedLedger.id();
});
}
std::size_t
Sim::branches() const
{
if(peers.size() < 1)
return branches(allPeers);
}
std::size_t
Sim::branches(PeerGroup const & g) const
{
if(g.size() < 1)
return 0;
std::set<Ledger> ledgers;
for(auto const & peer : peers)
ledgers.insert(peer.fullyValidatedLedger);
for(auto const & peer : g)
ledgers.insert(peer->fullyValidatedLedger);
return oracle.branches(ledgers);
}

View File

@@ -18,6 +18,7 @@
//==============================================================================
#include <BeastConfig.h>
#include <test/csf/ledgers.h>
#include <algorithm>
#include <sstream>
@@ -36,6 +37,53 @@ Ledger::getJson() const
return res;
}
bool
Ledger::isAncestor(Ledger const& ancestor) const
{
if (ancestor.seq() < seq())
return operator[](ancestor.seq()) == ancestor.id();
return false;
}
Ledger::ID
Ledger::operator[](Seq s) const
{
if(s > seq())
return {};
if(s== seq())
return id();
return instance_->ancestors[static_cast<Seq::value_type>(s)];
}
Ledger::Seq
mismatch(Ledger const& a, Ledger const& b)
{
using Seq = Ledger::Seq;
// end is 1 past end of range
Seq start{0};
Seq end = std::min(a.seq() + Seq{1}, b.seq() + Seq{1});
// Find mismatch in [start,end)
// Binary search
Seq count = end - start;
while(count > Seq{0})
{
Seq step = count/Seq{2};
Seq curr = start + step;
if(a[curr] == b[curr])
{
// go to second half
start = ++curr;
count -= step + Seq{1};
}
else
count = step;
}
return start;
}
LedgerOracle::LedgerOracle()
{
instances_.insert(InstanceEntry{Ledger::genesis, nextID()});
@@ -67,6 +115,8 @@ LedgerOracle::accept(
next.parentCloseTime = parent.closeTime();
next.parentID = parent.id();
next.ancestors.push_back(parent.id());
auto it = instances_.left.find(next);
if (it == instances_.left.end())
{
@@ -88,19 +138,6 @@ LedgerOracle::lookup(Ledger::ID const & id) const
}
bool
LedgerOracle::isAncestor(Ledger const & ancestor, Ledger const& descendant) const
{
// The ancestor must have an earlier sequence number than the descendent
if(ancestor.seq() >= descendant.seq())
return false;
boost::optional<Ledger> current{descendant};
while(current && current->seq() > ancestor.seq())
current = lookup(current->parentID());
return current && (current->id() == ancestor.id());
}
std::size_t
LedgerOracle::branches(std::set<Ledger> const & ledgers) const
{
@@ -121,7 +158,7 @@ LedgerOracle::branches(std::set<Ledger> const & ledgers) const
bool const idxEarlier = tips[idx].seq() < ledger.seq();
Ledger const & earlier = idxEarlier ? tips[idx] : ledger;
Ledger const & later = idxEarlier ? ledger : tips[idx] ;
if (isAncestor(earlier, later))
if (later.isAncestor(earlier))
{
tips[idx] = later;
found = true;

View File

@@ -66,6 +66,7 @@ public:
struct IdTag;
using ID = tagged_integer<std::uint32_t, IdTag>;
struct MakeGenesis {};
private:
// The instance is the common immutable data that will be assigned a unique
// ID by the oracle
@@ -94,6 +95,11 @@ private:
//! Parent ledger close time
NetClock::time_point parentCloseTime;
//! IDs of this ledgers ancestors. Since each ledger already has unique
//! ancestors based on the parentID, this member is not needed for any
//! of the operators below.
std::vector<Ledger::ID> ancestors;
auto
asTie() const
{
@@ -137,7 +143,13 @@ private:
}
public:
Ledger() : id_{0}, instance_(&genesis)
Ledger(MakeGenesis) : instance_(&genesis)
{
}
// This is required by the generic Consensus for now and should be
// migrated to the MakeGenesis approach above.
Ledger() : Ledger(MakeGenesis{})
{
}
@@ -189,6 +201,21 @@ public:
return instance_->txs;
}
/** Determine whether ancestor is really an ancestor of this ledger */
bool
isAncestor(Ledger const& ancestor) const;
/** Return the id of the ancestor with the given seq (if exists/known)
*/
ID
operator[](Seq seq) const;
/** Return the sequence number of the first mismatching ancestor
*/
friend
Ledger::Seq
mismatch(Ledger const & a, Ledger const & o);
Json::Value getJson() const;
friend bool
@@ -238,9 +265,16 @@ public:
NetClock::duration closeTimeResolution,
NetClock::time_point const& consensusCloseTime);
/** Determine whether ancestor is really an ancestor of descendent */
bool
isAncestor(Ledger const & ancestor, Ledger const& descendant) const;
Ledger
accept(Ledger const& curr, Tx tx)
{
using namespace std::chrono_literals;
return accept(
curr,
TxSetType{tx},
curr.closeTimeResolution(),
curr.closeTime() + 1s);
}
/** Determine the number of distinct branches for the set of ledgers.
@@ -256,6 +290,57 @@ public:
};
/** Helper for writing unit tests with controlled ledger histories.
This class allows clients to refer to distinct ledgers as strings, where
each character in the string indicates a unique ledger. It enforces the
uniqueness at runtime, but this simplifies creation of alternate ledger
histories, e.g.
HistoryHelper hh;
hh["a"]
hh["ab"]
hh["ac"]
hh["abd"]
Creates a history like
b - d
/
a - c
*/
struct LedgerHistoryHelper
{
LedgerOracle oracle;
Tx::ID nextTx{0};
std::unordered_map<std::string, Ledger> ledgers;
std::set<char> seen;
LedgerHistoryHelper()
{
ledgers[""] = Ledger{Ledger::MakeGenesis{}};
}
/** Get or create the ledger with the given string history.
Creates any necessary intermediate ledgers, but asserts if
a letter is re-used (e.g. "abc" then "adc" would assert)
*/
Ledger const& operator[](std::string const& s)
{
auto it = ledgers.find(s);
if (it != ledgers.end())
return it->second;
// enforce that the new suffix has never been seen
assert(seen.emplace(s.back()).second);
Ledger const& parent = (*this)[s.substr(0, s.size() - 1)];
return ledgers.emplace(s, oracle.accept(parent, ++nextTx))
.first->second;
}
};
} // csf
} // test
} // ripple

View File

@@ -22,6 +22,7 @@
#include <test/app/PayChan_test.cpp>
#include <test/app/PayStrand_test.cpp>
#include <test/app/PseudoTx_test.cpp>
#include <test/app/RCLValidations_test.cpp>
#include <test/app/Regression_test.cpp>
#include <test/app/SetAuth_test.cpp>
#include <test/app/SetRegularKey_test.cpp>

View File

@@ -21,5 +21,6 @@
#include <test/consensus/Consensus_test.cpp>
#include <test/consensus/DistributedValidatorsSim_test.cpp>
#include <test/consensus/LedgerTiming_test.cpp>
#include <test/consensus/LedgerTrie_test.cpp>
#include <test/consensus/ScaleFreeSim_test.cpp>
#include <test/consensus/Validations_test.cpp>