mirror of
https://github.com/Xahau/xahaud.git
synced 2025-11-04 10:45:50 +00:00
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:
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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 \
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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.
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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 ();
|
||||
|
||||
|
||||
@@ -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
|
||||
{
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
815
src/ripple/consensus/LedgerTrie.h
Normal file
815
src/ripple/consensus/LedgerTrie.h
Normal 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
204
src/test/app/RCLValidations_test.cpp
Normal file
204
src/test/app/RCLValidations_test.cpp
Normal 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
|
||||
@@ -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();
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
663
src/test/consensus/LedgerTrie_test.cpp
Normal file
663
src/test/consensus/LedgerTrie_test.cpp
Normal 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
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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
|
||||
{
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user