From 36de9a2023b621c61e404f781ea2ab80f76750db Mon Sep 17 00:00:00 2001 From: Brad Chase Date: Thu, 7 Dec 2017 11:00:42 -0500 Subject: [PATCH 01/38] [FOLD] Prepare preferred ledger by branch test: Create a test that forks using the current preferred branching approach that will not fork using the preferred by branch algorithm. Update Peer to retry acquiring ledgers and txSets if a request times out. --- src/test/consensus/Consensus_test.cpp | 170 ++++++++++++++++++++++++++ src/test/csf/Peer.h | 46 +++++-- src/test/csf/PeerGroup.h | 9 +- src/test/csf/Sim.h | 21 +++- src/test/csf/impl/Sim.cpp | 31 +++-- 5 files changed, 254 insertions(+), 23 deletions(-) diff --git a/src/test/consensus/Consensus_test.cpp b/src/test/consensus/Consensus_test.cpp index fb31ed4e3c8..788d2b46bab 100644 --- a/src/test/consensus/Consensus_test.cpp +++ b/src/test/consensus/Consensus_test.cpp @@ -825,6 +825,175 @@ class Consensus_test : public beast::unit_test::suite BEAST_EXPECT(sim.synchronized()); } + + // Helper collector for testPreferredByBranch + // Invasively disconnects network at bad times to cause splits + struct Disruptor + { + csf::PeerGroup& network; + csf::PeerGroup& groupCfast; + csf::PeerGroup& groupCsplit; + csf::SimDuration delay; + bool reconnected = false; + + Disruptor( + csf::PeerGroup& net, + csf::PeerGroup& c, + csf::PeerGroup& split, + csf::SimDuration d) + : network(net), groupCfast(c), groupCsplit(split), delay(d) + { + } + + template + void + on(csf::PeerID, csf::SimTime, E const&) + { + } + + + void + on(csf::PeerID who, csf::SimTime, csf::FullyValidateLedger const& e) + { + using namespace std::chrono; + // As soon as the the fastC node fully validates C, disconnect + // ALL c nodes from the network. The fast C node needs to disconnect + // as well to prevent it from relaying the validations it did see + if (who == groupCfast[0]->id && + e.ledger.seq() == csf::Ledger::Seq{2}) + { + network.disconnect(groupCsplit); + network.disconnect(groupCfast); + } + } + + void + on(csf::PeerID who, csf::SimTime, csf::AcceptLedger const& e) + { + // As soon as anyone generates a child of B or C, reconnect the + // network so those validation 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 disconnect 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 C. + // - The network reconnects and the validations for generation 3 ledgers + // are observed (D and the 8 C's) + // - In the old approach, 2 votes for D outweights 1 vote for each C' + // so the network would avalanche towards D and fully validate it + // EVEN though C was fully validated by one node + // - In the new approach, 2 votes for D are not enough to outweight the + // 8 implicit votes for C, so nodes will avalanche to C instead + + + ConsensusParms const parms{}; + Sim sim; + + // Goes A->B->D + PeerGroup groupABD = sim.createGroup(2); + // Single node that initially fully validates C before the split + PeerGroup groupCfast = sim.createGroup(1); + // Generates C, but fails to fully validate before the split + PeerGroup groupCsplit = sim.createGroup(7); + + PeerGroup groupNotFastC = groupABD + groupCsplit; + PeerGroup network = groupABD + groupCsplit + groupCfast; + + SimDuration delay = round(0.2 * parms.ledgerGRANULARITY); + SimDuration fDelay = round(0.1 * parms.ledgerGRANULARITY); + + network.trust(network); + // C must have a shorter delay to see all the validations before the + // other nodes + network.connect(groupCfast, fDelay); + // The rest of the network is connected at the same speed + (network - groupCfast).connect(network - groupCfast, delay); + + Disruptor dc(network, groupCfast, groupCsplit, delay); + sim.collectors.add(dc); + + // Consensus round to generate ledger A + sim.run(1); + BEAST_EXPECT(sim.synchronized()); + + // Next round generates B and C + // To force B, we inject an extra transaction in to those nodes + for(Peer * peer : groupABD) + { + peer->txInjections.emplace( + peer->lastClosedLedger.seq(), Tx{42}); + } + // The Disruptor will ensure that nodes disconnect before the C + // validations make it to all but the fastC node + sim.run(1); + + // We are no longer in sync, but have not yet forked: + // 9 nodes consider A the last fully validated ledger and fastC sees C + BEAST_EXPECT(!sim.synchronized()); + BEAST_EXPECT(sim.branches() == 1); + + // Run another round to generate the 8 different C' ledgers + for (Peer * p : network) + p->submit(Tx(static_cast(p->id))); + sim.run(1); + + // Still not forked + BEAST_EXPECT(!sim.synchronized()); + BEAST_EXPECT(sim.branches() == 1); + + // Disruptor will reconnect all but the fastC node + sim.run(1); + BEAST_EXPECT(!sim.synchronized()); + + if(BEAST_EXPECT(sim.branches() == 1)) + { + // New approach will not fork and will resync once the fast node + // reconnects for a few rounds + network.connect(groupCfast, fDelay); + sim.run(2); + BEAST_EXPECT(sim.synchronized()); + BEAST_EXPECT(sim.branches() == 1); + + } + else // old approach caused a fork + { + BEAST_EXPECT(sim.branches(groupNotFastC) == 1); + BEAST_EXPECT(sim.synchronized(groupNotFastC) == 1); + } + } void run() override { @@ -839,6 +1008,7 @@ class Consensus_test : public beast::unit_test::suite testConsensusCloseTimeRounding(); testFork(); testHubNetwork(); + testPreferredByBranch(); } }; diff --git a/src/test/csf/Peer.h b/src/test/csf/Peer.h index fa07dd3dea4..6813201a247 100644 --- a/src/test/csf/Peer.h +++ b/src/test/csf/Peer.h @@ -213,9 +213,9 @@ struct Peer //! TxSet associated with a TxSet::ID bc::flat_map txSets; - // Ledgers and txSets that we have already attempted to acquire - bc::flat_set acquiringLedgers; - bc::flat_set acquiringTxSets; + // Ledgers/TxSets we are acquiring and when that request times out + bc::flat_map acquiringLedgers; + bc::flat_map acquiringTxSets; //! The number of ledgers this peer has completed int completedLedgers = 0; @@ -380,16 +380,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 +414,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 +432,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 +458,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; } @@ -663,7 +691,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::ceil(numTrustedPeers * 0.8)); - if (count >= quorum) + if (count >= quorum && oracle.isAncestor(fullyValidatedLedger, ledger)) { issue(FullyValidateLedger{ledger, fullyValidatedLedger}); fullyValidatedLedger = ledger; @@ -830,7 +858,7 @@ struct Peer lastClosedLedger.parentID(), earliestAllowedSeq()); - // Between rounds, we take the majority ledger and use the + // Between rounds, we take the majority ledger and use the Ledger::ID const bestLCL = getPreferredLedger(lastClosedLedger.id(), valDistribution); diff --git a/src/test/csf/PeerGroup.h b/src/test/csf/PeerGroup.h index 66ff962577a..cf14828ec23 100644 --- a/src/test/csf/PeerGroup.h +++ b/src/test/csf/PeerGroup.h @@ -62,7 +62,6 @@ class PeerGroup PeerGroup(std::set const& peers) : peers_{peers.begin(), peers.end()} { - } iterator @@ -101,6 +100,14 @@ class PeerGroup 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 { diff --git a/src/test/csf/Sim.h b/src/test/csf/Sim.h index 23c10d735fd..6ecad0604fd 100644 --- a/src/test/csf/Sim.h +++ b/src/test/csf/Sim.h @@ -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 peers; + PeerGroup allPeers; public: std::mt19937_64 rng; @@ -113,7 +114,9 @@ class Sim 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,20 +139,32 @@ class Sim 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 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. */ std::size_t + branches(PeerGroup const & g) const; + + /** Calculate the number of branches in the network + */ + std::size_t branches() const; }; diff --git a/src/test/csf/impl/Sim.cpp b/src/test/csf/impl/Sim.cpp index 2860744ca62..bb3bb1a789a 100644 --- a/src/test/csf/impl/Sim.cpp +++ b/src/test/csf/impl/Sim.cpp @@ -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 ledgers; - for(auto const & peer : peers) - ledgers.insert(peer.fullyValidatedLedger); + for(auto const & peer : g) + ledgers.insert(peer->fullyValidatedLedger); return oracle.branches(ledgers); } From 5e35cf9bb6a9ad31672d195377ea7d56a8a5c762 Mon Sep 17 00:00:00 2001 From: Brad Chase Date: Thu, 7 Dec 2017 16:16:05 -0500 Subject: [PATCH 02/38] [FOLD] Add LedgerTrie and tests --- Builds/VisualStudio2015/RippleD.vcxproj | 6 + .../VisualStudio2015/RippleD.vcxproj.filters | 6 + src/ripple/consensus/LedgerTrie.h | 699 ++++++++++++++++++ src/test/consensus/Consensus_test.cpp | 6 +- src/test/consensus/LedgerTrie_test.cpp | 537 ++++++++++++++ src/test/csf/Peer.h | 4 +- src/test/csf/impl/ledgers.cpp | 65 +- src/test/csf/ledgers.h | 84 ++- src/test/unity/consensus_test_unity.cpp | 1 + 9 files changed, 1385 insertions(+), 23 deletions(-) create mode 100644 src/ripple/consensus/LedgerTrie.h create mode 100644 src/test/consensus/LedgerTrie_test.cpp diff --git a/Builds/VisualStudio2015/RippleD.vcxproj b/Builds/VisualStudio2015/RippleD.vcxproj index f682212b208..f10eb5b9fd4 100644 --- a/Builds/VisualStudio2015/RippleD.vcxproj +++ b/Builds/VisualStudio2015/RippleD.vcxproj @@ -1634,6 +1634,8 @@ + + @@ -4685,6 +4687,10 @@ True True + + True + True + True True diff --git a/Builds/VisualStudio2015/RippleD.vcxproj.filters b/Builds/VisualStudio2015/RippleD.vcxproj.filters index 7e954e279be..4d9fef3d71b 100644 --- a/Builds/VisualStudio2015/RippleD.vcxproj.filters +++ b/Builds/VisualStudio2015/RippleD.vcxproj.filters @@ -2151,6 +2151,9 @@ ripple\consensus + + ripple\consensus + ripple\consensus @@ -5406,6 +5409,9 @@ test\consensus + + test\consensus + test\consensus diff --git a/src/ripple/consensus/LedgerTrie.h b/src/ripple/consensus/LedgerTrie.h new file mode 100644 index 00000000000..31a68ff08aa --- /dev/null +++ b/src/ripple/consensus/LedgerTrie.h @@ -0,0 +1,699 @@ +//------------------------------------------------------------------------------ +/* + This file is part of rippled: https://github.com/ripple/rippled + Copyright (c) 2017 Ripple Labs Inc. + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== + +#ifndef RIPPLE_APP_CONSENSUS_LEDGERS_TRIE_H_INCLUDED +#define RIPPLE_APP_CONSENSUS_LEDGERS_TRIE_H_INCLUDED + +#include +#include +#include +#include + +namespace ripple { + +/** Ancestry trie of ledgers + + Combination of a compressed trie and merkle-ish 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. + + The merkle-ish property is based on the branch support calculation. Each + node has a tipSupport, which is the number of current validations for that + particular ledger. The branch support is the sum of the tip support and + the branch support of that node's children: + + @code + node->branchSupport = node->tipSupport + + sum_(child : node->children) child->branchSupport + @endcode + + This is analagous to the merkle tree property in which a node's hash is + the hash of the concatenation of its child node hashes. + + 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 + { + // The default ledger represents a ledger that prefixes all other + // ledgers, e.g. the genesis ledger + Ledger(); + + Ledger(Ledger &); + Ledger& operator=(Ledger ); + + // Return the sequence number of this ledger + Seq seq() const; + + // Return the ID of this ledger's ancestor with given sequence number + // or ID{0} if unknown + ID + operator[](Seq s); + + }; + + // Return the sequence number of the first possible mismatching ancestor + // between two ledgers + Seq + mismatch(ledgerA, ledgerB); + @endcode + + The unique history invariant of ledgers requires any ledgers that agree + on the id of a given sequence number agree on ALL ancestors before that + ledger: + + @code + Ledger a,b; + // For all Seq s: + if(a[s] == b[s]); + for(Seq p = 0; p < s; ++p) + assert(a[p] == b[p]); + @endcode + + @tparam Ledger A type representing a ledger and its history +*/ +template +class LedgerTrie +{ + using Seq = typename Ledger::Seq; + using ID = typename Ledger::ID; + + /// Represents a span of ancestry of a ledger + class Span + { + // The span is the half-open interval [start,end) of ledger_ + Seq start_{0}; + Seq end_{1}; + Ledger ledger_; + + public: + Span() + { + // 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 + end() const + { + return end_; + } + + // Return the Span from (spot,end_] + Span + from(Seq spot) + { + return sub(spot, end_); + } + + // Return the Span from (start_,spot] + Span + before(Seq spot) + { + return sub(start_, spot); + } + + bool + empty() const + { + return start_ == end_; + } + + //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 Seq and ID of the end of the span + std::pair + tip() const + { + Seq tipSeq{end_ -Seq{1}}; + return {tipSeq, ledger_[tipSeq]}; + } + + private: + Span(Seq start, Seq end, Ledger const& l) + : start_{start}, end_{end}, ledger_{l} + { + 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) + Span + sub(Seq from, Seq to) + { + return Span(clamp(from), clamp(to), ledger_); + } + + friend std::ostream& + operator<<(std::ostream& o, Span const& s) + { + return o << s.tip().second << "(" << 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 + struct Node + { + Node() : span{}, tipSupport{0}, branchSupport{0} + { + } + + Node(Ledger const& l) : span{l}, tipSupport{1}, branchSupport{1} + { + } + + Node(Span s) : span{std::move(s)} + { + } + + Span span; + std::uint32_t tipSupport = 0; + std::uint32_t branchSupport = 0; + + std::vector> children; + Node* parent = nullptr; + + /** Remove the given node from this Node's children + + @param child The address of the child node to remove + @note The child must be a member of the vector. The passed pointer + will be dangling as a result of this call + */ + void + erase(Node const* child) + { + auto it = std::remove_if( + children.begin(), + children.end(), + [child](std::unique_ptr const& curr) { + return curr.get() == child; + }); + assert(it != children.end()); + children.erase(it, children.end()); + } + + 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().second); + res["seq"] = static_cast(span.tip().first); + 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; + } + }; + + // The root of the trie. The root is allowed to break the no-single child + // invariant. + std::unique_ptr root; + + /** Find the node in the trie that represents the longest common ancestry + with the given ledger. + + @return Pair of the found node and the sequence number of the first + ledger difference. + */ + std::pair + find(Ledger const& ledger) const + { + Node* curr = root.get(); + + // Root is always defined and is in common with all ledgers + assert(curr); + Seq pos = curr->span.diff(ledger); + + bool done = false; + + // Continue searching for a better span as long as the current position + // matches the entire span + while (!done && pos == curr->span.end()) + { + done = true; + // Find the child with the longest ancestry match + for (std::unique_ptr const& child : curr->children) + { + auto childPos = child->span.diff(ledger); + if (childPos > pos) + { + done = false; + pos = childPos; + curr = child.get(); + break; + } + } + } + return std::make_pair(curr, pos); + } + + void + dumpImpl(std::ostream& o, std::unique_ptr const& curr, int offset) + const + { + if (curr) + { + if (offset > 0) + o << std::setw(offset) << "|-"; + + std::stringstream ss; + ss << *curr; + o << ss.str() << std::endl; + for (std::unique_ptr const& child : curr->children) + dumpImpl(o, child, offset + 1 + ss.str().size() + 2); + } + } + +public: + LedgerTrie() : root{std::make_unique()} + { + } + + /** Insert and/or increment the support for the given ledger. + + @param ledger A ledger and its ancestry + @param count The count of support for this ledger + */ + void + insert(Ledger const& ledger, std::uint32_t count = 1) + { + Node* loc; + Seq diffSeq; + std::tie(loc, diffSeq) = find(ledger); + + // There is always a place to insert + assert(loc); + + Span lTmp{ledger}; + Span prefix = lTmp.before(diffSeq); + Span oldSuffix = loc->span.from(diffSeq); + Span newSuffix = lTmp.from(diffSeq); + Node* incNode = loc; + + if (!oldSuffix.empty()) + { + // new is a prefix of current + // e.g. abcdef->..., adding abcd + // becomes abcd->ef->... + + // Create oldSuffix node that takes over loc + std::unique_ptr newNode{std::make_unique(oldSuffix)}; + newNode->tipSupport = loc->tipSupport; + newNode->branchSupport = loc->branchSupport; + using std::swap; + swap(newNode->children, loc->children); + for(std::unique_ptr & child : newNode->children) + child->parent = newNode.get(); + + // Loc truncates to prefix and newNode is its child + loc->span = prefix; + newNode->parent = loc; + loc->children.emplace_back(std::move(newNode)); + loc->tipSupport = 0; + } + if (!newSuffix.empty()) + { + // current is a substring of new + // e.g. abc->... adding abcde + // -> abc-> ... + // -> de + + std::unique_ptr newNode{std::make_unique(newSuffix)}; + newNode->parent = loc; + // increment support starting from the new node + incNode = newNode.get(); + loc->children.push_back(std::move(newNode)); + } + + incNode->tipSupport += count; + while (incNode) + { + incNode->branchSupport += count; + incNode = incNode->parent; + } + } + + /** 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; + + Node* decNode = loc; + while (decNode) + { + decNode->branchSupport -= count; + decNode = decNode->parent; + } + + while (loc->tipSupport == 0 && loc != root.get()) + { + Node* parent = loc->parent; + if (loc->children.empty()) + { + // this node can be erased + parent->erase(loc); + } + else if (loc->children.size() == 1) + { + // This node can be combined with its child + std::unique_ptr child = + std::move(loc->children.front()); + child->span = merge(loc->span, child->span); + child->parent = parent; + parent->children.emplace_back(std::move(child)); + parent->erase(loc); + } + else + break; + loc = parent; + } + return true; + } + } + return false; + } + + /** Return count of tip support for the specific ledger. + + @param ledger The ledger to lookup + @return The number of entries in the trie for this *exact* ledger + */ + std::uint32_t + tipSupport(Ledger const& ledger) const + { + Node const* loc; + Seq diffSeq; + std::tie(loc, diffSeq) = find(ledger); + + // Exact match + if (loc && diffSeq == loc->span.end() && diffSeq > ledger.seq()) + return loc->tipSupport; + return 0; + } + + /** Return the count of branch support for the specific ledger + + @param ledger The ledger to lookup + @return The number of entries in the trie for this ledger or a descendent + */ + 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. + This ensures the preferred branch has monotonically increasing 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 (B). + - The support of ledgers that have been explicitly validated by a + validator (C,D), or are an ancestor of that validators current + validated ledger (E). + - The number of validators that have yet to validate a ledger + with this sequence number (prefixSupport). + + The preferred ledger for this sequence number is then the ledger + with relative majority of support, where prefixSupport can be given to + ANY ledger at that sequence number (including one not yet known). If no + such preferred ledger exists, than prior sequence preferred ledger is + the overall preferred ledger. If one does exist, then we continue + with the next sequence but increase prefixSupport with the non + preferred ones this round, e.g. if C were preferred over D, then + prefixSupport would incerase by the support of D and E. + + */ + std::pair + getPreferred() + { + Node* curr = root.get(); + + bool done = false; + std::uint32_t prefixSupport = curr->tipSupport; + while (curr && !done) + { + Node* best = nullptr; + std::uint32_t margin = 0; + + if (curr->children.size() == 1) + { + best = curr->children[0].get(); + margin = best->branchSupport; + } + else if (!curr->children.empty()) + { + // Sort placing children with largest branch support in the + // front, breaking ties with the span's starting ID + std::partial_sort( + curr->children.begin(), + curr->children.begin() + 2, + curr->children.end(), + [](std::unique_ptr const& a, + std::unique_ptr const& b) { + return std::make_tuple(a->branchSupport, a->span.startID()) > + std::make_tuple(b->branchSupport, b->span.startID()); + }); + + best = curr->children[0].get(); + margin = curr->children[0]->branchSupport - + curr->children[1]->branchSupport; + + // If best holds the tie-breaker, gets one larger margin + // since the second best needs additional branchSupport + // to overcome the tie + if (best->span.startID() > curr->children[1]->span.startID()) + margin++; + } + + // If the best child has margin exceeding the prefix support, + // continue from that child, otherwise we are done + if (best && ((margin > prefixSupport) || (prefixSupport == 0))) + { + // Prefix support is all the support not on the branch we + // are moving to + // curr + // _/ | \_ + // A B best + // At curr, the prefix support already includes the tip support + // of curr and its ancestors, along with the branch support of + // any of its siblings that are inconsistent. + // + // The additional prefix suppport that is carried to best is + // A->branchSupport + B->branchSupport + best->tipSupport + // This is the amount of support that has not yet voted + // on a descendent of best, or has voted on a conflicting + // descendent and will switch to best in the future. This means + // that they may support an arbitrary descendent of best. + // + // The calculation is simplified using + // A->branchSupport+B->branchSupport + // = curr->branchSupport - best->branchSupport + // - curr->tipSupport + // + // This will not overflow by definition of the above quantities + prefixSupport += (curr->branchSupport - best->branchSupport + - curr->tipSupport) + best->tipSupport; + + 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::stack nodes; + nodes.push(root.get()); + while (!nodes.empty()) + { + Node const* curr = nodes.top(); + nodes.pop(); + if (!curr) + continue; + + // Node with 0 tip support must have multiple children + // unless it is the root node + if (curr != root.get() && curr->tipSupport == 0 && + curr->children.size() < 2) + return false; + + // branchSupport = tipSupport + sum(child->branchSupport) + std::size_t support = curr->tipSupport; + 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 true; + } +}; + +} // namespace ripple +#endif diff --git a/src/test/consensus/Consensus_test.cpp b/src/test/consensus/Consensus_test.cpp index 788d2b46bab..5df162aa8b6 100644 --- a/src/test/consensus/Consensus_test.cpp +++ b/src/test/consensus/Consensus_test.cpp @@ -512,8 +512,7 @@ class Consensus_test : public beast::unit_test::suite peerJumps.closeJumps.front(); // Jump is to a different chain BEAST_EXPECT(jump.from.seq() <= jump.to.seq()); - BEAST_EXPECT( - !sim.oracle.isAncestor(jump.from, jump.to)); + BEAST_EXPECT(!jump.to.isAncestor(jump.from)); } } // fully validated jump forward in same chain @@ -525,8 +524,7 @@ class Consensus_test : public beast::unit_test::suite peerJumps.fullyValidatedJumps.front(); // Jump is to a different chain with same seq BEAST_EXPECT(jump.from.seq() < jump.to.seq()); - BEAST_EXPECT( - sim.oracle.isAncestor(jump.from, jump.to)); + BEAST_EXPECT(jump.to.isAncestor(jump.from)); } } } diff --git a/src/test/consensus/LedgerTrie_test.cpp b/src/test/consensus/LedgerTrie_test.cpp new file mode 100644 index 00000000000..f149bb45b4d --- /dev/null +++ b/src/test/consensus/LedgerTrie_test.cpp @@ -0,0 +1,537 @@ +//------------------------------------------------------------------------------ +/* + This file is part of rippled: https://github.com/ripple/rippled + Copyright (c) 2012-2017 Ripple Labs Inc. + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== +#include +#include +#include +#include +#include +#include + +namespace ripple { +namespace test { + +class LedgerTrie_test : public beast::unit_test::suite +{ + beast::Journal j; + + + void + testInsert() + { + using namespace csf; + // Single entry by itself + { + LedgerTrie t; + LedgerHistoryHelper h; + t.insert(h["abc"]); + BEAST_EXPECT(t.checkInvariants()); + BEAST_EXPECT(t.tipSupport(h["abc"]) == 1); + BEAST_EXPECT(t.branchSupport(h["abc"]) == 1); + + t.insert(h["abc"]); + BEAST_EXPECT(t.checkInvariants()); + BEAST_EXPECT(t.tipSupport(h["abc"]) == 2); + BEAST_EXPECT(t.branchSupport(h["abc"]) == 2); + } + // Suffix of existing (extending tree) + { + LedgerTrie t; + LedgerHistoryHelper h; + t.insert(h["abc"]); + BEAST_EXPECT(t.checkInvariants()); + // extend with no siblings + t.insert(h["abcd"]); + BEAST_EXPECT(t.checkInvariants()); + + BEAST_EXPECT(t.tipSupport(h["abc"]) == 1); + BEAST_EXPECT(t.branchSupport(h["abc"]) == 2); + BEAST_EXPECT(t.tipSupport(h["abcd"]) == 1); + BEAST_EXPECT(t.branchSupport(h["abcd"]) == 1); + + // extend with existing sibling + t.insert(h["abce"]); + BEAST_EXPECT(t.tipSupport(h["abc"]) == 1); + BEAST_EXPECT(t.branchSupport(h["abc"]) == 3); + BEAST_EXPECT(t.tipSupport(h["abcd"]) == 1); + BEAST_EXPECT(t.branchSupport(h["abcd"]) == 1); + BEAST_EXPECT(t.tipSupport(h["abce"]) == 1); + BEAST_EXPECT(t.branchSupport(h["abce"]) == 1); + } + // Prefix of existing node + { + LedgerTrie t; + LedgerHistoryHelper h; + t.insert(h["abcd"]); + BEAST_EXPECT(t.checkInvariants()); + // prefix 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); + + // prefix 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 + prefix of existing node + { + LedgerTrie t; + LedgerHistoryHelper h; + t.insert(h["abcd"]); + BEAST_EXPECT(t.checkInvariants()); + t.insert(h["abce"]); + BEAST_EXPECT(t.checkInvariants()); + + BEAST_EXPECT(t.tipSupport(h["abc"]) == 0); + BEAST_EXPECT(t.branchSupport(h["abc"]) == 2); + BEAST_EXPECT(t.tipSupport(h["abcd"]) == 1); + BEAST_EXPECT(t.branchSupport(h["abcd"]) == 1); + BEAST_EXPECT(t.tipSupport(h["abce"]) == 1); + BEAST_EXPECT(t.branchSupport(h["abce"]) == 1); + } + // Suffix + prefix with existing child + { + // abcd : abcde, abcf + + LedgerTrie t; + LedgerHistoryHelper h; + t.insert(h["abcd"]); + BEAST_EXPECT(t.checkInvariants()); + t.insert(h["abcde"]); + BEAST_EXPECT(t.checkInvariants()); + t.insert(h["abcf"]); + BEAST_EXPECT(t.checkInvariants()); + + BEAST_EXPECT(t.tipSupport(h["abc"]) == 0); + BEAST_EXPECT(t.branchSupport(h["abc"]) == 3); + BEAST_EXPECT(t.tipSupport(h["abcd"]) == 1); + BEAST_EXPECT(t.branchSupport(h["abcd"]) == 2); + BEAST_EXPECT(t.tipSupport(h["abcf"]) == 1); + BEAST_EXPECT(t.branchSupport(h["abcf"]) == 1); + BEAST_EXPECT(t.tipSupport(h["abcde"]) == 1); + BEAST_EXPECT(t.branchSupport(h["abcde"]) == 1); + } + + // Multiple counts + { + LedgerTrie t; + LedgerHistoryHelper h; + t.insert(h["ab"],4); + BEAST_EXPECT(t.tipSupport(h["ab"]) == 4); + BEAST_EXPECT(t.branchSupport(h["ab"]) == 4); + BEAST_EXPECT(t.tipSupport(h["a"]) == 0); + BEAST_EXPECT(t.branchSupport(h["a"]) == 4); + + t.insert(h["abc"],2); + BEAST_EXPECT(t.tipSupport(h["abc"]) == 2); + BEAST_EXPECT(t.branchSupport(h["abc"]) == 2); + BEAST_EXPECT(t.tipSupport(h["ab"]) == 4); + BEAST_EXPECT(t.branchSupport(h["ab"]) == 6); + BEAST_EXPECT(t.tipSupport(h["a"]) == 0); + BEAST_EXPECT(t.branchSupport(h["a"]) == 6); + + } + } + + void + testRemove() + { + using namespace csf; + // Not in trie + { + LedgerTrie t; + LedgerHistoryHelper h; + t.insert(h["abc"]); + + BEAST_EXPECT(!t.remove(h["ab"])); + BEAST_EXPECT(t.checkInvariants()); + BEAST_EXPECT(!t.remove(h["a"])); + BEAST_EXPECT(t.checkInvariants()); + } + // In trie but with 0 tip support + { + LedgerTrie t; + LedgerHistoryHelper h; + t.insert(h["abcd"]); + t.insert(h["abce"]); + + BEAST_EXPECT(t.tipSupport(h["abc"]) == 0); + BEAST_EXPECT(t.branchSupport(h["abc"]) == 2); + BEAST_EXPECT(!t.remove(h["abc"])); + BEAST_EXPECT(t.checkInvariants()); + BEAST_EXPECT(t.tipSupport(h["abc"]) == 0); + BEAST_EXPECT(t.branchSupport(h["abc"]) == 2); + } + // In trie with > 1 tip support + { + LedgerTrie t; + LedgerHistoryHelper h; + t.insert(h["abc"],2); + + BEAST_EXPECT(t.tipSupport(h["abc"]) == 2); + BEAST_EXPECT(t.remove(h["abc"])); + BEAST_EXPECT(t.checkInvariants()); + BEAST_EXPECT(t.tipSupport(h["abc"]) == 1); + + t.insert(h["abc"], 1); + BEAST_EXPECT(t.tipSupport(h["abc"]) == 2); + BEAST_EXPECT(t.remove(h["abc"], 2)); + BEAST_EXPECT(t.checkInvariants()); + BEAST_EXPECT(t.tipSupport(h["abc"]) == 0); + + t.insert(h["abc"], 3); + BEAST_EXPECT(t.tipSupport(h["abc"]) == 3); + BEAST_EXPECT(t.remove(h["abc"], 300)); + BEAST_EXPECT(t.checkInvariants()); + BEAST_EXPECT(t.tipSupport(h["abc"]) == 0); + + } + // In trie with = 1 tip support, no children + { + LedgerTrie t; + LedgerHistoryHelper h; + t.insert(h["ab"]); + t.insert(h["abc"]); + + BEAST_EXPECT(t.tipSupport(h["ab"]) == 1); + BEAST_EXPECT(t.branchSupport(h["ab"]) == 2); + BEAST_EXPECT(t.tipSupport(h["abc"]) == 1); + BEAST_EXPECT(t.branchSupport(h["abc"]) == 1); + + BEAST_EXPECT(t.remove(h["abc"])); + BEAST_EXPECT(t.checkInvariants()); + BEAST_EXPECT(t.tipSupport(h["ab"]) == 1); + BEAST_EXPECT(t.branchSupport(h["ab"]) == 1); + BEAST_EXPECT(t.tipSupport(h["abc"]) == 0); + BEAST_EXPECT(t.branchSupport(h["abc"]) == 0); + } + // In trie with = 1 tip support, 1 child + { + LedgerTrie t; + LedgerHistoryHelper h; + t.insert(h["ab"]); + t.insert(h["abc"]); + t.insert(h["abcd"]); + + BEAST_EXPECT(t.tipSupport(h["abc"]) == 1); + BEAST_EXPECT(t.branchSupport(h["abc"]) == 2); + BEAST_EXPECT(t.tipSupport(h["abcd"]) == 1); + BEAST_EXPECT(t.branchSupport(h["abcd"]) == 1); + + BEAST_EXPECT(t.remove(h["abc"])); + BEAST_EXPECT(t.checkInvariants()); + BEAST_EXPECT(t.tipSupport(h["abc"]) == 0); + BEAST_EXPECT(t.branchSupport(h["abc"]) == 1); + BEAST_EXPECT(t.tipSupport(h["abcd"]) == 1); + BEAST_EXPECT(t.branchSupport(h["abcd"]) == 1); + } + // In trie with = 1 tip support, > 1 children + { + LedgerTrie t; + LedgerHistoryHelper h; + t.insert(h["ab"]); + t.insert(h["abc"]); + t.insert(h["abcd"]); + t.insert(h["abce"]); + + BEAST_EXPECT(t.tipSupport(h["abc"]) == 1); + BEAST_EXPECT(t.branchSupport(h["abc"]) == 3); + + BEAST_EXPECT(t.remove(h["abc"])); + BEAST_EXPECT(t.checkInvariants()); + BEAST_EXPECT(t.tipSupport(h["abc"]) == 0); + BEAST_EXPECT(t.branchSupport(h["abc"]) == 2); + } + + // In trie with = 1 tip support, parent compaction + { + LedgerTrie t; + LedgerHistoryHelper h; + t.insert(h["ab"]); + t.insert(h["abc"]); + t.insert(h["abd"]); + BEAST_EXPECT(t.checkInvariants()); + t.remove(h["ab"]); + BEAST_EXPECT(t.checkInvariants()); + BEAST_EXPECT(t.tipSupport(h["abc"]) == 1); + BEAST_EXPECT(t.tipSupport(h["abd"]) == 1); + BEAST_EXPECT(t.tipSupport(h["ab"]) == 0); + BEAST_EXPECT(t.branchSupport(h["ab"]) == 2); + + t.remove(h["abd"]); + BEAST_EXPECT(t.checkInvariants()); + + BEAST_EXPECT(t.tipSupport(h["abc"]) == 1); + BEAST_EXPECT(t.branchSupport(h["ab"]) == 1); + + } + } + + void + testTipAndBranchSupport() + { + using namespace csf; + LedgerTrie t; + LedgerHistoryHelper h; + BEAST_EXPECT(t.tipSupport(h["a"]) == 0); + BEAST_EXPECT(t.tipSupport(h["axy"]) == 0); + BEAST_EXPECT(t.branchSupport(h["a"]) == 0); + BEAST_EXPECT(t.branchSupport(h["axy"]) == 0); + + t.insert(h["abc"]); + BEAST_EXPECT(t.tipSupport(h["a"]) == 0); + BEAST_EXPECT(t.tipSupport(h["ab"]) == 0); + BEAST_EXPECT(t.tipSupport(h["abc"]) == 1); + BEAST_EXPECT(t.tipSupport(h["abcd"]) == 0); + BEAST_EXPECT(t.branchSupport(h["a"]) == 1); + BEAST_EXPECT(t.branchSupport(h["ab"]) == 1); + BEAST_EXPECT(t.branchSupport(h["abc"]) == 1); + BEAST_EXPECT(t.branchSupport(h["abcd"]) == 0); + + t.insert(h["abe"]); + BEAST_EXPECT(t.tipSupport(h["a"]) == 0); + BEAST_EXPECT(t.tipSupport(h["ab"]) == 0); + BEAST_EXPECT(t.tipSupport(h["abc"]) == 1); + BEAST_EXPECT(t.tipSupport(h["abe"]) == 1); + + BEAST_EXPECT(t.branchSupport(h["a"]) == 2); + BEAST_EXPECT(t.branchSupport(h["ab"]) == 2); + BEAST_EXPECT(t.branchSupport(h["abc"]) == 1); + BEAST_EXPECT(t.branchSupport(h["abe"]) == 1); + } + + void + testGetPreferred() + { + using namespace csf; + // Empty + { + LedgerTrie t; + LedgerHistoryHelper h; + BEAST_EXPECT(t.getPreferred().second == Ledger::ID{0}); + } + // Single node no children + { + LedgerTrie t; + LedgerHistoryHelper h; + t.insert(h["abc"]); + BEAST_EXPECT(t.getPreferred().second == h["abc"].id()); + } + // Single node smaller child support + { + LedgerTrie t; + LedgerHistoryHelper h; + t.insert(h["abc"]); + t.insert(h["abcd"]); + BEAST_EXPECT(t.getPreferred().second == h["abc"].id()); + + t.insert(h["abc"]); + BEAST_EXPECT(t.getPreferred().second == h["abc"].id()); + } + // Single node larger child + { + LedgerTrie t; + LedgerHistoryHelper h; + t.insert(h["abc"]); + t.insert(h["abcd"],2); + BEAST_EXPECT(t.getPreferred().second == h["abcd"].id()); + } + // Single node smaller children support + { + LedgerTrie t; + LedgerHistoryHelper h; + t.insert(h["abc"]); + t.insert(h["abcd"]); + t.insert(h["abce"]); + BEAST_EXPECT(t.getPreferred().second == h["abc"].id()); + + t.insert(h["abc"]); + BEAST_EXPECT(t.getPreferred().second == h["abc"].id()); + } + // Single node larger children + { + LedgerTrie t; + LedgerHistoryHelper h; + t.insert(h["abc"]); + t.insert(h["abcd"],2); + t.insert(h["abce"]); + BEAST_EXPECT(t.getPreferred().second == h["abc"].id()); + t.insert(h["abcd"]); + BEAST_EXPECT(t.getPreferred().second == h["abcd"].id()); + } + // Tie-breaker by id + { + LedgerTrie t; + LedgerHistoryHelper h; + t.insert(h["abcd"],2); + t.insert(h["abce"],2); + + BEAST_EXPECT(h["abce"].id() > h["abcd"].id()); + BEAST_EXPECT(t.getPreferred().second == h["abce"].id()); + + t.insert(h["abcd"]); + BEAST_EXPECT(h["abce"].id() > h["abcd"].id()); + BEAST_EXPECT(t.getPreferred().second == h["abcd"].id()); + } + + // Tie-breaker not needed + { + LedgerTrie t; + LedgerHistoryHelper h; + t.insert(h["abc"]); + t.insert(h["abcd"]); + t.insert(h["abce"],2); + // abce only has a margin of 1, but it owns the tie-breaker + BEAST_EXPECT(h["abce"].id() > h["abcd"].id()); + BEAST_EXPECT(t.getPreferred().second == 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().second == h["abc"].id()); + } + + // Single node larger grand child + { + LedgerTrie t; + LedgerHistoryHelper h; + t.insert(h["abc"]); + t.insert(h["abcd"],2); + t.insert(h["abcde"],4); + BEAST_EXPECT(t.getPreferred().second == h["abcde"].id()); + } + + // Too much prefix support from competing branches + { + LedgerTrie t; + LedgerHistoryHelper h; + t.insert(h["abc"]); + t.insert(h["abcde"],2); + t.insert(h["abcfg"],2); + // 'de' and 'fg' are tied without 'abc' vote + BEAST_EXPECT(t.getPreferred().second == h["abc"].id()); + t.remove(h["abc"]); + t.insert(h["abcd"]); + // 'de' branch has 3 votes to 2, but not enough suport for 'e' + // since the node on 'd' and the 2 on 'fg' could go in a + // different direction + BEAST_EXPECT(t.getPreferred().second == h["abcd"].id()); + } + } + + void + testRootRelated() + { + using namespace csf; + // Since the root is a special node that breaks the no-single child + // invariant, do some tests that exercise it. + + LedgerTrie t; + LedgerHistoryHelper h; + BEAST_EXPECT(!t.remove(h[""])); + BEAST_EXPECT(t.branchSupport(h[""]) == 0); + BEAST_EXPECT(t.tipSupport(h[""]) == 0); + + t.insert(h["a"]); + BEAST_EXPECT(t.checkInvariants()); + BEAST_EXPECT(t.branchSupport(h[""]) == 1); + BEAST_EXPECT(t.tipSupport(h[""]) == 0); + + t.insert(h["e"]); + BEAST_EXPECT(t.checkInvariants()); + BEAST_EXPECT(t.branchSupport(h[""]) == 2); + BEAST_EXPECT(t.tipSupport(h[""]) == 0); + + BEAST_EXPECT(t.remove(h["e"])); + BEAST_EXPECT(t.checkInvariants()); + BEAST_EXPECT(t.branchSupport(h[""]) == 1); + BEAST_EXPECT(t.tipSupport(h[""]) == 0); + } + + void + testStress() + { + using namespace csf; + LedgerTrie t; + LedgerHistoryHelper h; + + // Test quasi-randomly add/remove supporting for different ledgers + // from a branching history. + + // Ledgers have sequence 1,2,3,4 + std::uint32_t const depth = 4; + // Each ledger has 4 possible children + std::uint32_t const width = 4; + + std::uint32_t const iterations = 10000; + + // Use explicit seed to have same results for CI + std::mt19937 gen{ 42 }; + std::uniform_int_distribution<> depthDist(0, depth-1); + std::uniform_int_distribution<> widthDist(0, width-1); + std::uniform_int_distribution<> flip(0, 1); + for(std::uint32_t i = 0; i < iterations; ++i) + { + // pick a random ledger history + std::string curr = ""; + char depth = depthDist(gen); + char offset = 0; + for(char d = 0; d < depth; ++d) + { + char a = offset + widthDist(gen); + curr += a; + offset = (a + 1) * width; + } + + // 50-50 to add remove + if(flip(gen) == 0) + t.insert(h[curr]); + else + t.remove(h[curr]); + if(!BEAST_EXPECT(t.checkInvariants())) + return; + } + } + + void + run() + { + testInsert(); + testRemove(); + testTipAndBranchSupport(); + testGetPreferred(); + testRootRelated(); + testStress(); + + } +}; + +BEAST_DEFINE_TESTSUITE(LedgerTrie, consensus, ripple); +} // namespace test +} // namespace ripple diff --git a/src/test/csf/Peer.h b/src/test/csf/Peer.h index 6813201a247..1216c5607d9 100644 --- a/src/test/csf/Peer.h +++ b/src/test/csf/Peer.h @@ -555,7 +555,7 @@ 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) { @@ -691,7 +691,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::ceil(numTrustedPeers * 0.8)); - if (count >= quorum && oracle.isAncestor(fullyValidatedLedger, ledger)) + if (count >= quorum && ledger.isAncestor(fullyValidatedLedger)) { issue(FullyValidateLedger{ledger, fullyValidatedLedger}); fullyValidatedLedger = ledger; diff --git a/src/test/csf/impl/ledgers.cpp b/src/test/csf/impl/ledgers.cpp index eb8e9a7adab..1fffb902574 100644 --- a/src/test/csf/impl/ledgers.cpp +++ b/src/test/csf/impl/ledgers.cpp @@ -18,6 +18,7 @@ //============================================================================== #include #include +#include #include @@ -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(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 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 const & ledgers) const { @@ -121,7 +158,7 @@ LedgerOracle::branches(std::set 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; diff --git a/src/test/csf/ledgers.h b/src/test/csf/ledgers.h index 9e48d933144..6d706f1a769 100644 --- a/src/test/csf/ledgers.h +++ b/src/test/csf/ledgers.h @@ -94,6 +94,11 @@ class Ledger //! 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 foor any + //! of the operators below. + std::vector ancestors; + auto asTie() const { @@ -189,6 +194,21 @@ class Ledger 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 +258,16 @@ class LedgerOracle 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 +283,57 @@ class LedgerOracle }; +/** 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 +{ + csf::LedgerOracle oracle; + csf::Tx::ID nextTx{0}; + std::unordered_map ledgers; + std::set seen; + + LedgerHistoryHelper() + { + ledgers[""] = csf::Ledger{}; + } + + /** Get or create the ledger with the given string history. + + Creates an necessary intermediate ledgers, but asserts if + a letter is re-used (e.g. "abc" then "adc" would assert) + */ + csf::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); + + csf::Ledger const& parent = (*this)[s.substr(0, s.size() - 1)]; + return ledgers.emplace(s, oracle.accept(parent, ++nextTx)) + .first->second; + } +}; + } // csf } // test } // ripple diff --git a/src/test/unity/consensus_test_unity.cpp b/src/test/unity/consensus_test_unity.cpp index 1a9a347a336..f73a01d919e 100644 --- a/src/test/unity/consensus_test_unity.cpp +++ b/src/test/unity/consensus_test_unity.cpp @@ -21,5 +21,6 @@ #include #include #include +#include #include #include From 72e7ac960f82f3080d230c2fc4b74916d3d29481 Mon Sep 17 00:00:00 2001 From: Brad Chase Date: Fri, 15 Dec 2017 10:07:17 -0500 Subject: [PATCH 03/38] [FOLD] Switch to Adaptor design in Validations: In preparation of using the LedgerTrie to track validations, these changes do some minor reorganizing of the StalePolicy helper into a general Adaptor in the generic validations code. --- src/ripple/app/consensus/RCLValidations.cpp | 40 +++-- src/ripple/app/consensus/RCLValidations.h | 75 +++++--- src/ripple/app/main/Application.cpp | 5 +- src/ripple/app/main/Application.h | 8 +- src/ripple/consensus/Validations.h | 181 ++++++++++---------- src/test/consensus/Validations_test.cpp | 42 +++-- src/test/csf/Peer.h | 44 +++-- src/test/csf/Validation.h | 3 + 8 files changed, 220 insertions(+), 178 deletions(-) diff --git a/src/ripple/app/consensus/RCLValidations.cpp b/src/ripple/app/consensus/RCLValidations.cpp index 14c352fc2f0..2508fe83362 100644 --- a/src/ripple/app/consensus/RCLValidations.cpp +++ b/src/ripple/app/consensus/RCLValidations.cpp @@ -36,19 +36,20 @@ namespace ripple { -RCLValidationsPolicy::RCLValidationsPolicy(Application& app) : app_(app) +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(); } 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 +61,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 +71,7 @@ RCLValidationsPolicy::onStale(RCLValidation&& v) } void -RCLValidationsPolicy::flush(hash_map&& remaining) +RCLValidationsAdaptor::flush(hash_map&& remaining) { bool anyNew = false; { @@ -106,7 +107,7 @@ RCLValidationsPolicy::flush(hash_map&& remaining) // NOTE: doStaleWrite() must be called with staleLock_ *locked*. The passed // ScopedLockType& acts as a reminder to future maintainers. void -RCLValidationsPolicy::doStaleWrite(ScopedLockType&) +RCLValidationsAdaptor::doStaleWrite(ScopedLockType&) { static const std::string insVal( "INSERT INTO Validations " @@ -170,7 +171,8 @@ RCLValidationsPolicy::doStaleWrite(ScopedLockType&) } bool -handleNewValidation(Application& app, +handleNewValidation( + Application& app, STValidation::ref val, std::string const& source) { @@ -181,9 +183,9 @@ handleNewValidation(Application& app, boost::optional pubKey = app.validators().getTrustedKey(signer); if (!val->isTrusted() && pubKey) val->setTrusted(); - RCLValidations& validations = app.getValidations(); + RCLValidations& validations = app.getValidations(); - beast::Journal j = validations.journal(); + beast::Journal j = validations.adaptor().journal(); // Do not process partial validations. if (!val->isFull()) @@ -195,10 +197,10 @@ handleNewValidation(Application& app, val->getSeenTime()); JLOG(j.debug()) << "Val (partial) for " << hash << " from " - << toBase58(TokenType::TOKEN_NODE_PUBLIC, signer) - << " ignored " - << (val->isTrusted() ? "trusted/" : "UNtrusted/") - << (current ? "current" : "stale"); + << toBase58(TokenType::TOKEN_NODE_PUBLIC, signer) + << " ignored " + << (val->isTrusted() ? "trusted/" : "UNtrusted/") + << (current ? "current" : "stale"); // Only forward if current and trusted return current && val->isTrusted(); @@ -243,10 +245,10 @@ handleNewValidation(Application& app, } JLOG(j.debug()) << "Val for " << hash << " from " - << toBase58(TokenType::TOKEN_NODE_PUBLIC, signer) - << " added " - << (val->isTrusted() ? "trusted/" : "UNtrusted/") - << ((res == AddOutcome::current) ? "current" : "stale"); + << 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 @@ -263,8 +265,8 @@ handleNewValidation(Application& app, else { JLOG(j.debug()) << "Val for " << hash << " from " - << toBase58(TokenType::TOKEN_NODE_PUBLIC, signer) - << " not added UNtrusted/"; + << toBase58(TokenType::TOKEN_NODE_PUBLIC, signer) + << " not added UNtrusted/"; } // This currently never forwards untrusted validations, though we may diff --git a/src/ripple/app/consensus/RCLValidations.h b/src/ripple/app/consensus/RCLValidations.h index cb8dbff0578..2fae8bf774a 100644 --- a/src/ripple/app/consensus/RCLValidations.h +++ b/src/ripple/app/consensus/RCLValidations.h @@ -23,6 +23,7 @@ #include #include #include +#include #include #include @@ -39,6 +40,8 @@ class RCLValidation { STValidation::pointer val_; public: + using NodeKey = ripple::PublicKey; + using NodeID = ripple::NodeID; /** Constructor @@ -115,35 +118,31 @@ class RCLValidation }; -/** Implements the StalePolicy policy class for adapting Validations in the RCL - - Manages storing and writing stale RCLValidations to the sqlite DB. -*/ -class RCLValidationsPolicy +class RCLValidatedLedger { - using LockType = std::mutex; - using ScopedLockType = std::lock_guard; - using ScopedUnlockType = GenericScopedUnlock; +public: + using ID = LedgerHash; + using Seq = LedgerIndex; - Application& app_; +}; - // Lock for managing staleValidations_ and writing_ - std::mutex staleLock_; - std::vector staleValidations_; - bool staleWriting_ = false; - - // Write the stale validations to sqlite DB, the scoped lock argument - // is used to remind callers that the staleLock_ must be *locked* prior - // to making the call - void - doStaleWrite(ScopedLockType&); +/** Generic validations adaptor classs for RCL + Manages storing and writing stale RCLValidations to the sqlite DB and + acquiring validated ledgers from the network. +*/ +class RCLValidationsAdaptor +{ public: + // Type definitions for generic Validation + using Mutex = std::mutex; + using Validation = RCLValidation; + using Ledger = RCLValidatedLedger; - RCLValidationsPolicy(Application & app); + RCLValidationsAdaptor(Application& app, beast::Journal j); /** Current time used to determine if validations are stale. - */ + */ NetClock::time_point now() const; @@ -163,13 +162,39 @@ class RCLValidationsPolicy @param remaining The remaining validations to flush */ void - flush(hash_map && remaining); -}; + flush(hash_map&& remaining); + /** Attempt to acquire the ledger with given id from the network */ + boost::optional + acquire(LedgerHash const & id); + + beast::Journal + journal() const + { + return j_; + } + +private: + using ScopedLockType = std::lock_guard; + using ScopedUnlockType = GenericScopedUnlock; + + Application& app_; + beast::Journal j_; + + // Lock for managing staleValidations_ and writing_ + std::mutex staleLock_; + std::vector staleValidations_; + bool staleWriting_ = false; + + // Write the stale validations to sqlite DB, the scoped lock argument + // is used to remind callers that the staleLock_ must be *locked* prior + // to making the call + void + doStaleWrite(ScopedLockType&); +}; /// Alias for RCL-specific instantiation of generic Validations -using RCLValidations = - Validations; +using RCLValidations = Validations; /** Handle a new validation diff --git a/src/ripple/app/main/Application.cpp b/src/ripple/app/main/Application.cpp index 998edcbac93..1bf3408f9ef 100644 --- a/src/ripple/app/main/Application.cpp +++ b/src/ripple/app/main/Application.cpp @@ -497,8 +497,7 @@ class ApplicationImp stopwatch(), HashRouter::getDefaultHoldTime (), HashRouter::getDefaultRecoverLimit ())) - , mValidations (ValidationParms(),stopwatch(), logs_->journal("Validations"), - *this) + , mValidations (ValidationParms(),stopwatch(), *this, logs_->journal("Validations")) , m_loadManager (make_LoadManager (*this, *this, logs_->journal("LoadManager"))) @@ -916,7 +915,9 @@ class ApplicationImp // before we declare ourselves stopped. waitHandlerCounter_.join("Application", 1s, m_journal); + JLOG(m_journal.debug()) << "Flushing validations"; mValidations.flush (); + JLOG(m_journal.debug()) << "Validations flushed"; validatorSites_->stop (); diff --git a/src/ripple/app/main/Application.h b/src/ripple/app/main/Application.h index 032ae781b3b..6dcac7a3d5e 100644 --- a/src/ripple/app/main/Application.h +++ b/src/ripple/app/main/Application.h @@ -74,12 +74,10 @@ class SHAMapStore; using NodeCache = TaggedCache ; -template +template class Validations; -class RCLValidation; -class RCLValidationsPolicy; -using RCLValidations = - Validations; +class RCLValidationsAdaptor; +using RCLValidations = Validations; class Application : public beast::PropertyStream::Source { diff --git a/src/ripple/consensus/Validations.h b/src/ripple/consensus/Validations.h index ad80698e5ab..860013889df 100644 --- a/src/ripple/consensus/Validations.h +++ b/src/ripple/consensus/Validations.h @@ -110,13 +110,13 @@ isCurrent( @return The ID of the ledger with most support, preferring to stick with current ledger in the case of equal support */ -template -inline LedgerID +template +inline ID getPreferredLedger( - LedgerID const& current, - hash_map const& dist) + ID const& current, + hash_map const& dist) { - LedgerID netLgr = current; + ID netLgr = current; int netLgrCount = 0; for (auto const& it : dist) { @@ -144,28 +144,34 @@ getPreferredLedger( and implementations should take care to use `trusted` member functions or check the validation's trusted status. - This class uses a policy design to allow adapting the handling of stale - validations in various circumstances. Below is a set of stubs illustrating - the required type interface. + This class uses a generic interface to allow adapting Validations for + specific applications. The Adaptor template implements a set of helper + functions and type definitions. The code stubs below outline the + interface and type requirements. - @warning The MutexType is used to manage concurrent access to private - members of Validations but does not manage any data in the - StalePolicy instance. + + @warning The Adaptor::MutexType is used to manage concurrent access to + private members of Validations but does not manage any data in the + Adaptor instance itself. @code - // Identifier types that should be equality-comparable and copyable - struct LedgerID; - struct NodeID; - struct NodeKey; + struct Ledger + { + using ID = ...; + using Seq = ...; + //... + } struct Validation { + using NodeKey = ...; + // Ledger ID associated with this validation - LedgerID ledgerID() const; + Ledger::ID ledgerID() const; // Sequence number of validation's ledger (0 means no sequence number) - std::uint32_t seq() const + Ledger::Seq seq() const // When the validation was signed NetClock::time_point signTime() const; @@ -176,9 +182,6 @@ getPreferredLedger( // Signing key of node that published the validation NodeKey key() const; - // Identifier of node that published the validation - NodeID nodeID() const; - // Whether the publishing node was trusted at the time the validation // arrived bool trusted() const; @@ -189,8 +192,12 @@ getPreferredLedger( // ... implementation specific }; - class StalePolicy + class Adaptor { + using Mutex = std::mutex; + using Validation = Validation; + using Ledger = Ledger; + // Handle a newly stale validation, this should do minimal work since // it is called by Validations while it may be iterating Validations // under lock @@ -200,36 +207,31 @@ getPreferredLedger( void flush(hash_map && remaining); // Return the current network time (used to determine staleness) - NetClock::time_point now() const; + NetClock::time_point now() const // ... implementation specific }; @endcode - @tparam StalePolicy Determines how to determine and handle stale validations - @tparam Validation Conforming type representing a ledger validation - @tparam MutexType Mutex used to manage concurrent access - + @tparam Adaptor Provides type definitions and callbacks */ -template +template class Validations { - template - using decay_result_t = std::decay_t>; - - using WrappedValidationType = - decay_result_t; - using LedgerID = - decay_result_t; - using NodeKey = decay_result_t; - using NodeID = decay_result_t; - using SeqType = decay_result_t; + using Mutex = typename Adaptor::Mutex; + using Validation = typename Adaptor::Validation; + using Ledger = typename Adaptor::Ledger; + using ID = typename Ledger::ID; + using Seq = typename Ledger::Seq; + using NodeKey = typename Validation::NodeKey; + using WrappedValidationType = std::decay_t< + std::result_of_t>; - using ScopedLock = std::lock_guard; + using ScopedLock = std::lock_guard; // Manages concurrent access to current_ and byLedger_ - MutexType mutex_; + Mutex mutex_; //! For the most recent validation, we also want to store the ID //! of the ledger it replaces @@ -240,7 +242,7 @@ class Validations } Validation val; - LedgerID prevLedgerID; + ID prevLedgerID; }; //! The latest validation from each node @@ -248,7 +250,7 @@ class Validations //! Recent validations from nodes, indexed by ledger identifier beast::aged_unordered_map< - LedgerID, + ID, hash_map, std::chrono::steady_clock, beast::uhash<>> @@ -257,11 +259,9 @@ class Validations //! Parameters to determine validation staleness ValidationParms const parms_; - beast::Journal j_; - - //! StalePolicy details providing now(), onStale() and flush() callbacks - //! Is NOT managed by the mutex_ above - StalePolicy stalePolicy_; + // Adaptor instance + // Is NOT managed by the mutex_ above + Adaptor adaptor_; private: /** Iterate current validations. @@ -296,7 +296,7 @@ class Validations parms_, *t, it->second.val.signTime(), it->second.val.seenTime())) { // contains a stale record - stalePolicy_.onStale(std::move(it->second.val)); + adaptor_.onStale(std::move(it->second.val)); it = current_.erase(it); } else @@ -322,7 +322,7 @@ class Validations */ template void - byLedger(LedgerID const& ledgerID, Pre&& pre, F&& f) + byLedger(ID const& ledgerID, Pre&& pre, F&& f) { ScopedLock lock{mutex_}; auto it = byLedger_.find(ledgerID); @@ -341,19 +341,25 @@ class Validations @param p ValidationParms to control staleness/expiration of validaitons @param c Clock to use for expiring validations stored by ledger - @param j Journal used for logging - @param ts Parameters for constructing StalePolicy instance + @param ts Parameters for constructing Adaptor instance */ template Validations( ValidationParms const& p, beast::abstract_clock& c, - beast::Journal j, Ts&&... ts) - : byLedger_(c), parms_(p), j_(j), stalePolicy_(std::forward(ts)...) + : byLedger_(c), parms_(p), adaptor_(std::forward(ts)...) { } + /** Return the adaptor instance + */ + Adaptor const & + adaptor() const + { + return adaptor_; + } + /** Return the validation timing parameters */ ValidationParms const& @@ -362,14 +368,6 @@ class Validations return parms_; } - /** Return the journal - */ - beast::Journal - journal() const - { - return j_; - } - /** Result of adding a new validation */ enum class AddOutcome { @@ -399,11 +397,11 @@ class Validations AddOutcome add(NodeKey const& key, Validation const& val) { - NetClock::time_point t = stalePolicy_.now(); + NetClock::time_point t = adaptor_.now(); if (!isCurrent(parms_, t, val.signTime(), val.seenTime())) return AddOutcome::stale; - LedgerID const& id = val.ledgerID(); + ID const& id = val.ledgerID(); // This is only seated if a validation became stale boost::optional maybeStaleValidation; @@ -427,13 +425,13 @@ class Validations { // Had a previous validation from the node, consider updating Validation& oldVal = ins.first->second.val; - LedgerID const previousLedgerID = ins.first->second.prevLedgerID; + ID const previousLedgerID = ins.first->second.prevLedgerID; - SeqType const oldSeq{oldVal.seq()}; - SeqType const newSeq{val.seq()}; + Seq const oldSeq{oldVal.seq()}; + Seq const newSeq{val.seq()}; // Sequence of 0 indicates a missing sequence number - if ((oldSeq != SeqType{0}) && (newSeq != SeqType{0}) && + if ((oldSeq != Seq{0}) && (newSeq != Seq{0}) && oldSeq == newSeq) { result = AddOutcome::sameSeq; @@ -472,7 +470,7 @@ class Validations val.key() != oldVal.key()) { // This is either a newer validation or a new signing key - LedgerID const prevID = [&]() { + ID const prevID = [&]() { // In the normal case, the prevID is the ID of the // ledger we replace if (oldVal.ledgerID() != val.ledgerID()) @@ -500,7 +498,7 @@ class Validations // Handle the newly stale validation outside the lock if (maybeStaleValidation) { - stalePolicy_.onStale(std::move(*maybeStaleValidation)); + adaptor_.onStale(std::move(*maybeStaleValidation)); } return result; @@ -531,19 +529,19 @@ class Validations @return Map representing the distribution of ledgerID by count */ - hash_map + hash_map currentTrustedDistribution( - LedgerID const& currentLedger, - LedgerID const& priorLedger, - SeqType cutoffBefore) + ID const& currentLedger, + ID const& priorLedger, + Seq cutoffBefore) { - bool const valCurrentLedger = currentLedger != LedgerID{0}; - bool const valPriorLedger = priorLedger != LedgerID{0}; + bool const valCurrentLedger = currentLedger != ID{0}; + bool const valPriorLedger = priorLedger != ID{0}; - hash_map ret; + hash_map ret; current( - stalePolicy_.now(), + adaptor_.now(), // The number of validations does not correspond to the number of // distinct ledgerIDs so we do not call reserve on ret. [](std::size_t) {}, @@ -555,12 +553,12 @@ class Validations &priorLedger, &ret](NodeKey const&, ValidationAndPrevID const& vp) { Validation const& v = vp.val; - LedgerID const& prevLedgerID = vp.prevLedgerID; + ID const& prevLedgerID = vp.prevLedgerID; if (!v.trusted()) return; - SeqType const seq = v.seq(); - if ((seq == SeqType{0}) || (seq >= cutoffBefore)) + Seq const seq = v.seq(); + if ((seq == Seq{0}) || (seq >= cutoffBefore)) { // contains a live record bool countPreferred = @@ -573,8 +571,9 @@ class Validations (valPriorLedger && (v.ledgerID() == priorLedger)))) { countPreferred = true; - JLOG(this->j_.trace()) << "Counting for " << currentLedger - << " not " << v.ledgerID(); + JLOG(this->adaptor_.journal().trace()) + << "Counting for " << currentLedger << " not " + << v.ledgerID(); } if (countPreferred) @@ -598,7 +597,7 @@ class Validations prior ledger. */ std::size_t - getNodesAfter(LedgerID const& ledgerID) + getNodesAfter(ID const& ledgerID) { std::size_t count = 0; @@ -624,7 +623,7 @@ class Validations std::vector ret; current( - stalePolicy_.now(), + adaptor_.now(), [&](std::size_t numValidations) { ret.reserve(numValidations); }, [&](NodeKey const&, ValidationAndPrevID const& v) { if (v.val.trusted()) @@ -643,7 +642,7 @@ class Validations { hash_set ret; current( - stalePolicy_.now(), + adaptor_.now(), [&](std::size_t numValidations) { ret.reserve(numValidations); }, [&](NodeKey const& k, ValidationAndPrevID const&) { ret.insert(k); }); @@ -656,7 +655,7 @@ class Validations @return The number of trusted validations */ std::size_t - numTrustedForLedger(LedgerID const& ledgerID) + numTrustedForLedger(ID const& ledgerID) { std::size_t count = 0; byLedger( @@ -675,7 +674,7 @@ class Validations @return Trusted validations associated with ledger */ std::vector - getTrustedForLedger(LedgerID const& ledgerID) + getTrustedForLedger(ID const& ledgerID) { std::vector res; byLedger( @@ -695,7 +694,7 @@ class Validations @return Vector of times */ std::vector - getTrustedValidationTimes(LedgerID const& ledgerID) + getTrustedValidationTimes(ID const& ledgerID) { std::vector times; byLedger( @@ -715,7 +714,7 @@ class Validations @return Vector of fees */ std::vector - fees(LedgerID const& ledgerID, std::uint32_t baseFee) + fees(ID const& ledgerID, std::uint32_t baseFee) { std::vector res; byLedger( @@ -739,8 +738,6 @@ class Validations void flush() { - JLOG(j_.info()) << "Flushing validations"; - hash_map flushed; { ScopedLock lock{mutex_}; @@ -751,9 +748,7 @@ class Validations current_.clear(); } - stalePolicy_.flush(std::move(flushed)); - - JLOG(j_.debug()) << "Validations flushed"; + adaptor_.flush(std::move(flushed)); } }; } // namespace ripple diff --git a/src/test/consensus/Validations_test.cpp b/src/test/consensus/Validations_test.cpp index 0f31210c681..63a851a82a1 100644 --- a/src/test/consensus/Validations_test.cpp +++ b/src/test/consensus/Validations_test.cpp @@ -138,15 +138,34 @@ class Validations_test : public beast::unit_test::suite hash_map flushed; }; - // Generic Validations policy that saves stale/flushed data into + // Generic Validations adaptor that saves stale/flushed data into // a StaleData instance. - class StalePolicy + class Adaptor { StaleData& staleData_; clock_type& c_; + beast::Journal j_; public: - StalePolicy(StaleData& sd, clock_type& c) : staleData_{sd}, c_{c} + // Non-locking mutex to avoid locks in generic Validations + struct Mutex + { + void + lock() + { + } + + void + unlock() + { + } + }; + + using Validation = csf::Validation; + using Ledger = csf::Ledger; + + Adaptor(StaleData& sd, clock_type& c, beast::Journal j) + : staleData_{sd}, c_{c}, j_{j} { } @@ -167,24 +186,17 @@ class Validations_test : public beast::unit_test::suite { staleData_.flushed = std::move(remaining); } - }; - // Non-locking mutex to avoid locks in generic Validations - struct NotAMutex - { - void - lock() + beast::Journal + journal() const { + return j_; } - void - unlock() - { - } }; // Specialize generic Validations using the above types - using TestValidations = Validations; + using TestValidations = Validations; // Hoist enum for writing simpler tests using AddOutcome = TestValidations::AddOutcome; @@ -201,7 +213,7 @@ class Validations_test : public beast::unit_test::suite PeerID nextNodeId_{0}; public: - TestHarness() : tv_(p_, clock_, j_, staleData_, clock_) + TestHarness() : tv_(p_, clock_, staleData_, clock_, j_) { } diff --git a/src/test/csf/Peer.h b/src/test/csf/Peer.h index 1216c5607d9..340f49001fa 100644 --- a/src/test/csf/Peer.h +++ b/src/test/csf/Peer.h @@ -110,14 +110,30 @@ struct Peer } }; - /** Generic Validations policy that simply ignores recently stale validations + /** Generic Validations adaptor that simply ignores recently stale validations */ - class StalePolicy + class ValAdaptor { Peer& p_; public: - StalePolicy(Peer& p) : p_{p} + struct Mutex + { + void + lock() + { + } + + void + unlock() + { + } + }; + + using Validation = csf::Validation; + using Ledger = csf::Ledger; + + ValAdaptor(Peer& p) : p_{p} { } @@ -136,20 +152,11 @@ struct Peer flush(hash_map&& remaining) { } - }; - - /** Non-locking mutex to avoid locks in generic Validations - */ - struct NotAMutex - { - void - lock() - { - } - void - unlock() + beast::Journal + journal() const { + return p_.j; } }; @@ -195,9 +202,8 @@ struct Peer hash_map ledgers; //! Validations from trusted nodes - Validations validations; - using AddOutcome = - Validations::AddOutcome; + Validations validations; + using AddOutcome = Validations::AddOutcome; //! The most recent ledger that has been fully validated by the network from //! the perspective of this Peer @@ -275,7 +281,7 @@ struct Peer , scheduler{s} , net{n} , trustGraph(tg) - , validations{ValidationParms{}, s.clock(), j, *this} + , validations{ValidationParms{}, s.clock(), *this} , collectors{c} { // All peers start from the default constructed genesis ledger diff --git a/src/test/csf/Validation.h b/src/test/csf/Validation.h index ab45e4878c3..4a30002e9cb 100644 --- a/src/test/csf/Validation.h +++ b/src/test/csf/Validation.h @@ -57,6 +57,9 @@ class Validation boost::optional loadFee_; public: + using NodeKey = PeerKey; + using NodeID = PeerID; + Validation(Ledger::ID id, Ledger::Seq seq, NetClock::time_point sign, From 0685beb66ee5c47a67c0d1dbe0805de3c0fb35dc Mon Sep 17 00:00:00 2001 From: Brad Chase Date: Fri, 15 Dec 2017 10:29:57 -0500 Subject: [PATCH 04/38] [FOLD] Add full/partial to validation type --- src/ripple/app/consensus/RCLValidations.h | 5 +++++ src/ripple/consensus/Validations.h | 3 +++ src/test/csf/Validation.h | 17 +++++++++++++---- 3 files changed, 21 insertions(+), 4 deletions(-) diff --git a/src/ripple/app/consensus/RCLValidations.h b/src/ripple/app/consensus/RCLValidations.h index 2fae8bf774a..f17c8327781 100644 --- a/src/ripple/app/consensus/RCLValidations.h +++ b/src/ripple/app/consensus/RCLValidations.h @@ -102,6 +102,11 @@ class RCLValidation return val_->isTrusted(); } + bool + full() const + { + return val_->isFull(); + } /// Get the load fee of the validation if it exists boost::optional loadFee() const diff --git a/src/ripple/consensus/Validations.h b/src/ripple/consensus/Validations.h index 860013889df..b392619316a 100644 --- a/src/ripple/consensus/Validations.h +++ b/src/ripple/consensus/Validations.h @@ -186,6 +186,9 @@ getPreferredLedger( // arrived bool trusted() const; + // Whether this is a full or partial validation + bool full() const; + implementation_specific_t unwrap() -> return the implementation-specific type being wrapped diff --git a/src/test/csf/Validation.h b/src/test/csf/Validation.h index 4a30002e9cb..14f11d4ea3d 100644 --- a/src/test/csf/Validation.h +++ b/src/test/csf/Validation.h @@ -53,7 +53,8 @@ class Validation NetClock::time_point seenTime_; PeerKey key_; PeerID nodeID_{0}; - bool trusted_ = true; + bool trusted_ = false; + bool full_ = false; boost::optional loadFee_; public: @@ -66,7 +67,7 @@ class Validation NetClock::time_point seen, PeerKey key, PeerID nodeID, - bool trusted, + bool full, boost::optional loadFee = boost::none) : ledgerID_{id} , seq_{seq} @@ -74,7 +75,7 @@ class Validation , seenTime_{seen} , key_{key} , nodeID_{nodeID} - , trusted_{trusted} + , full_{full} , loadFee_{loadFee} { } @@ -121,6 +122,13 @@ class Validation return trusted_; } + bool + full() const + { + return full_; + } + + boost::optional loadFee() const { @@ -136,8 +144,9 @@ class Validation auto asTie() const { + // 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_, - trusted_, loadFee_); + trusted_, loadFee_, full_); } bool operator==(Validation const& o) const From 62a9c73fe66d69a5b9b6b7d42ed3afa6fc762192 Mon Sep 17 00:00:00 2001 From: Brad Chase Date: Fri, 15 Dec 2017 10:30:25 -0500 Subject: [PATCH 05/38] [FOLD] Simplify Validations test harness: Remove the overly generic add member in favor of Node issuing validations (and partial validations) directly. --- src/test/consensus/Validations_test.cpp | 169 ++++++++++++++---------- 1 file changed, 100 insertions(+), 69 deletions(-) diff --git a/src/test/consensus/Validations_test.cpp b/src/test/consensus/Validations_test.cpp index 63a851a82a1..56d41f6ad66 100644 --- a/src/test/consensus/Validations_test.cpp +++ b/src/test/consensus/Validations_test.cpp @@ -112,21 +112,53 @@ class Validations_test : public beast::unit_test::suite // Issue a new validation with given sequence number and id and // with signing and seen times offset from the common clock Validation - validation(Ledger::Seq seq, - Ledger::ID i, + validate( + Ledger::Seq seq, + Ledger::ID id, + NetClock::duration signOffset, + NetClock::duration seenOffset, + bool full) const + { + Validation v{id, + seq, + now() + signOffset, + now() + seenOffset, + currKey(), + nodeID_, + full, + loadFee_}; + if (trusted_) + v.setTrusted(); + return v; + } + + Validation + validate( + Ledger::Seq seq, + Ledger::ID id, NetClock::duration signOffset, NetClock::duration seenOffset) const { - return Validation{i, seq, now() + signOffset, now() + seenOffset, - currKey(), nodeID_, trusted_, loadFee_}; + return validate( + seq, id, signOffset, seenOffset, true); + } + + Validation + validate(Ledger::Seq seq, Ledger::ID id) const + { + return validate( + seq, id, NetClock::duration{0}, NetClock::duration{0}, true); } - // Issue a new validation with the given sequence number and id Validation - validation(Ledger::Seq seq, Ledger::ID i) const + partial(Ledger::Seq seq, Ledger::ID id) const { - return validation( - seq, i, NetClock::duration{0}, NetClock::duration{0}); + return validate( + seq, + id, + NetClock::duration{0}, + NetClock::duration{0}, + false); } }; @@ -219,17 +251,10 @@ class Validations_test : public beast::unit_test::suite // Helper to add an existing validation AddOutcome - add(Node const& n, Validation const& v) + add(Validation const& v) { - return tv_.add(n.masterKey(), v); - } - - // Helper to directly create the validation - template - std::enable_if_t<(sizeof...(Ts) > 1), AddOutcome> - add(Node const& n, Ts&&... ts) - { - return add(n, n.validation(std::forward(ts)...)); + PeerKey masterKey{v.nodeID(), 0}; + return tv_.add(masterKey, v); } TestValidations& @@ -279,13 +304,13 @@ class Validations_test : public beast::unit_test::suite Node a = harness.makeNode(); { { - auto const v = a.validation(Ledger::Seq{1}, Ledger::ID{1}); + auto const v = a.validate(Ledger::Seq{1}, Ledger::ID{1}); // Add a current validation - BEAST_EXPECT(AddOutcome::current == harness.add(a, v)); + BEAST_EXPECT(AddOutcome::current == harness.add(v)); // Re-adding is repeat - BEAST_EXPECT(AddOutcome::repeat == harness.add(a, v)); + BEAST_EXPECT(AddOutcome::repeat == harness.add(v)); } { @@ -294,7 +319,7 @@ class Validations_test : public beast::unit_test::suite BEAST_EXPECT(harness.stale().empty()); BEAST_EXPECT(AddOutcome::current == - harness.add(a, Ledger::Seq{2}, Ledger::ID{2})); + harness.add(a.validate(Ledger::Seq{2}, Ledger::ID{2}))); BEAST_EXPECT(harness.stale().size() == 1); @@ -318,7 +343,8 @@ class Validations_test : public beast::unit_test::suite BEAST_EXPECT(harness.vals().getNodesAfter(Ledger::ID{2}) == 0); BEAST_EXPECT( - AddOutcome::sameSeq == harness.add(a, Ledger::Seq{2}, Ledger::ID{20})); + AddOutcome::sameSeq == + harness.add(a.validate(Ledger::Seq{2}, Ledger::ID{20}))); // Old ID should be gone ... BEAST_EXPECT(harness.vals().numTrustedForLedger(Ledger::ID{2}) == 0); @@ -339,7 +365,8 @@ class Validations_test : public beast::unit_test::suite a.advanceKey(); BEAST_EXPECT( - AddOutcome::sameSeq == harness.add(a, Ledger::Seq{2}, Ledger::ID{20})); + AddOutcome::sameSeq == + harness.add(a.validate(Ledger::Seq{2}, Ledger::ID{20}))); { // Still the only trusted validation for ID{20} auto trustedVals = @@ -354,20 +381,20 @@ class Validations_test : public beast::unit_test::suite { // Processing validations out of order should ignore the older harness.clock().advance(2s); - auto const val3 = a.validation(Ledger::Seq{3}, Ledger::ID{3}); + auto const val3 = a.validate(Ledger::Seq{3}, Ledger::ID{3}); harness.clock().advance(4s); - auto const val4 = a.validation(Ledger::Seq{4}, Ledger::ID{4}); + auto const val4 = a.validate(Ledger::Seq{4}, Ledger::ID{4}); - BEAST_EXPECT(AddOutcome::current == harness.add(a, val4)); + BEAST_EXPECT(AddOutcome::current == harness.add(val4)); - BEAST_EXPECT(AddOutcome::stale == harness.add(a, val3)); + BEAST_EXPECT(AddOutcome::stale == harness.add(val3)); // re-issued should not be added auto const val4reissue = - a.validation(Ledger::Seq{4}, Ledger::ID{44}); + a.validate(Ledger::Seq{4}, Ledger::ID{44}); - BEAST_EXPECT(AddOutcome::stale == harness.add(a, val4reissue)); + BEAST_EXPECT(AddOutcome::stale == harness.add(val4reissue)); } { // Process validations out of order with shifted times @@ -377,32 +404,36 @@ class Validations_test : public beast::unit_test::suite // Establish a new current validation BEAST_EXPECT(AddOutcome::current == - harness.add(a, Ledger::Seq{8}, Ledger::ID{8})); + harness.add(a.validate(Ledger::Seq{8}, Ledger::ID{8}))); // Process a validation that has "later" seq but early sign time - BEAST_EXPECT(AddOutcome::stale == - harness.add(a, Ledger::Seq{9}, Ledger::ID{9}, -1s, -1s)); + BEAST_EXPECT( + AddOutcome::stale == + harness.add( + a.validate(Ledger::Seq{9}, Ledger::ID{9}, -1s, -1s))); // Process a validation that has an "earlier" seq but later sign // time - BEAST_EXPECT(AddOutcome::current == - harness.add(a, Ledger::Seq{7}, Ledger::ID{7}, 1s, 1s)); + BEAST_EXPECT( + AddOutcome::current == + harness.add( + a.validate(Ledger::Seq{7}, Ledger::ID{7}, 1s, 1s))); } { // Test stale on arrival validations harness.clock().advance(1h); BEAST_EXPECT(AddOutcome::stale == - harness.add(a, Ledger::Seq{15}, Ledger::ID{15}, - -harness.parms().validationCURRENT_EARLY, 0s)); + harness.add(a.validate(Ledger::Seq{15}, Ledger::ID{15}, + -harness.parms().validationCURRENT_EARLY, 0s))); BEAST_EXPECT(AddOutcome::stale == - harness.add(a, Ledger::Seq{15}, Ledger::ID{15}, - harness.parms().validationCURRENT_WALL, 0s)); + harness.add(a.validate(Ledger::Seq{15}, Ledger::ID{15}, + harness.parms().validationCURRENT_WALL, 0s))); BEAST_EXPECT(AddOutcome::stale == - harness.add(a, Ledger::Seq{15}, Ledger::ID{15}, 0s, - harness.parms().validationCURRENT_LOCAL)); + harness.add(a.validate(Ledger::Seq{15}, Ledger::ID{15}, 0s, + harness.parms().validationCURRENT_LOCAL))); } } } @@ -415,7 +446,7 @@ class Validations_test : public beast::unit_test::suite Node a = harness.makeNode(); BEAST_EXPECT(AddOutcome::current == - harness.add(a, Ledger::Seq{1}, Ledger::ID{1})); + harness.add(a.validate(Ledger::Seq{1}, Ledger::ID{1}))); harness.vals().currentTrusted(); BEAST_EXPECT(harness.stale().empty()); harness.clock().advance(harness.parms().validationCURRENT_LOCAL); @@ -443,9 +474,9 @@ class Validations_test : public beast::unit_test::suite // first round a,b,c agree, d has differing id for (auto const& node : {a, b, c}) BEAST_EXPECT(AddOutcome::current == - harness.add(node, Ledger::Seq{1}, Ledger::ID{1})); + harness.add(node.validate(Ledger::Seq{1}, Ledger::ID{1}))); BEAST_EXPECT(AddOutcome::current == - harness.add(d, Ledger::Seq{1}, Ledger::ID{10})); + harness.add(d.validate(Ledger::Seq{1}, Ledger::ID{10}))); // Nothing past ledger 1 yet BEAST_EXPECT(harness.vals().getNodesAfter(Ledger::ID{1}) == 0); @@ -456,13 +487,13 @@ class Validations_test : public beast::unit_test::suite // c is untrusted but on the same prior id // d has a different prior id BEAST_EXPECT(AddOutcome::current == - harness.add(a, Ledger::Seq{2}, Ledger::ID{2})); + harness.add(a.validate(Ledger::Seq{2}, Ledger::ID{2}))); BEAST_EXPECT(AddOutcome::current == - harness.add(b, Ledger::Seq{2}, Ledger::ID{20})); + harness.add(b.validate(Ledger::Seq{2}, Ledger::ID{20}))); BEAST_EXPECT(AddOutcome::current == - harness.add(c, Ledger::Seq{2}, Ledger::ID{2})); + harness.add(c.validate(Ledger::Seq{2}, Ledger::ID{2}))); BEAST_EXPECT(AddOutcome::current == - harness.add(d, Ledger::Seq{2}, Ledger::ID{2})); + harness.add(d.validate(Ledger::Seq{2}, Ledger::ID{2}))); BEAST_EXPECT(harness.vals().getNodesAfter(Ledger::ID{1}) == 2); } @@ -478,9 +509,9 @@ class Validations_test : public beast::unit_test::suite b.untrust(); BEAST_EXPECT(AddOutcome::current == - harness.add(a, Ledger::Seq{1}, Ledger::ID{1})); + harness.add(a.validate(Ledger::Seq{1}, Ledger::ID{1}))); BEAST_EXPECT(AddOutcome::current == - harness.add(b, Ledger::Seq{1}, Ledger::ID{3})); + harness.add(b.validate(Ledger::Seq{1}, Ledger::ID{3}))); // Only a is trusted BEAST_EXPECT(harness.vals().currentTrusted().size() == 1); @@ -493,7 +524,7 @@ class Validations_test : public beast::unit_test::suite for (auto const& node : {a, b}) BEAST_EXPECT(AddOutcome::current == - harness.add(node, Ledger::Seq{2}, Ledger::ID{2})); + harness.add(node.validate(Ledger::Seq{2}, Ledger::ID{2}))); // New validation for a BEAST_EXPECT(harness.vals().currentTrusted().size() == 1); @@ -519,7 +550,7 @@ class Validations_test : public beast::unit_test::suite for (auto const& node : {a, b}) BEAST_EXPECT(AddOutcome::current == - harness.add(node, Ledger::Seq{1}, Ledger::ID{1})); + harness.add(node.validate(Ledger::Seq{1}, Ledger::ID{1}))); { hash_set const expectedKeys = { @@ -535,7 +566,7 @@ class Validations_test : public beast::unit_test::suite for (auto const& node : {a, b}) BEAST_EXPECT(AddOutcome::current == - harness.add(node, Ledger::Seq{2}, Ledger::ID{2})); + harness.add(node.validate(Ledger::Seq{2}, Ledger::ID{2}))); { hash_set const expectedKeys = { @@ -569,16 +600,16 @@ class Validations_test : public beast::unit_test::suite for (auto const& node : {baby, papa, mama, goldilocks}) BEAST_EXPECT(AddOutcome::current == - harness.add(node, Ledger::Seq{1}, Ledger::ID{1})); + harness.add(node.validate(Ledger::Seq{1}, Ledger::ID{1}))); harness.clock().advance(1s); for (auto const& node : {baby, mama, goldilocks}) BEAST_EXPECT(AddOutcome::current == - harness.add(node, Ledger::Seq{2}, Ledger::ID{2})); + harness.add(node.validate(Ledger::Seq{2}, Ledger::ID{2}))); harness.clock().advance(1s); BEAST_EXPECT(AddOutcome::current == - harness.add(mama, Ledger::Seq{3}, Ledger::ID{3})); + harness.add(mama.validate(Ledger::Seq{3}, Ledger::ID{3}))); { // Allow slippage that treats all trusted as the current ledger @@ -699,14 +730,14 @@ class Validations_test : public beast::unit_test::suite // first round a,b,c agree, d differs for (auto const& node : {a, b, c}) { - auto const val = node.validation(Ledger::Seq{1}, Ledger::ID{1}); - BEAST_EXPECT(AddOutcome::current == harness.add(node, val)); + auto const val = node.validate(Ledger::Seq{1}, Ledger::ID{1}); + BEAST_EXPECT(AddOutcome::current == harness.add(val)); if (val.trusted()) trustedValidations[val.ledgerID()].emplace_back(val); } { - auto const val = d.validation(Ledger::Seq{1}, Ledger::ID{11}); - BEAST_EXPECT(AddOutcome::current == harness.add(d, val)); + auto const val = d.validate(Ledger::Seq{1}, Ledger::ID{11}); + BEAST_EXPECT(AddOutcome::current == harness.add(val)); trustedValidations[val.ledgerID()].emplace_back(val); } @@ -714,14 +745,14 @@ class Validations_test : public beast::unit_test::suite // second round, a,b,c move to ledger 2, d now thinks ledger 1 for (auto const& node : {a, b, c}) { - auto const val = node.validation(Ledger::Seq{2}, Ledger::ID{2}); - BEAST_EXPECT(AddOutcome::current == harness.add(node, val)); + auto const val = node.validate(Ledger::Seq{2}, Ledger::ID{2}); + BEAST_EXPECT(AddOutcome::current == harness.add(val)); if (val.trusted()) trustedValidations[val.ledgerID()].emplace_back(val); } { - auto const val = d.validation(Ledger::Seq{2}, Ledger::ID{1}); - BEAST_EXPECT(AddOutcome::current == harness.add(d, val)); + auto const val = d.validate(Ledger::Seq{2}, Ledger::ID{1}); + BEAST_EXPECT(AddOutcome::current == harness.add(val)); trustedValidations[val.ledgerID()].emplace_back(val); } @@ -737,7 +768,7 @@ class Validations_test : public beast::unit_test::suite Node a = harness.makeNode(); BEAST_EXPECT(AddOutcome::current == - harness.add(a, Ledger::Seq{1}, Ledger::ID{1})); + harness.add(a.validate(Ledger::Seq{1}, Ledger::ID{1}))); BEAST_EXPECT(harness.vals().numTrustedForLedger(Ledger::ID{1})); harness.clock().advance(harness.parms().validationSET_EXPIRES); harness.vals().expire(); @@ -759,8 +790,8 @@ class Validations_test : public beast::unit_test::suite hash_map expected; for (auto const& node : {a, b, c}) { - auto const val = node.validation(Ledger::Seq{1}, Ledger::ID{1}); - BEAST_EXPECT(AddOutcome::current == harness.add(node, val)); + auto const val = node.validate(Ledger::Seq{1}, Ledger::ID{1}); + BEAST_EXPECT(AddOutcome::current == harness.add(val)); expected.emplace(node.masterKey(), val); } Validation staleA = expected.find(a.masterKey())->second; @@ -769,8 +800,8 @@ class Validations_test : public beast::unit_test::suite // Send in a new validation for a, saving the new one into the expected // map after setting the proper prior ledger ID it replaced harness.clock().advance(1s); - auto newVal = a.validation(Ledger::Seq{2}, Ledger::ID{2}); - BEAST_EXPECT(AddOutcome::current == harness.add(a, newVal)); + auto newVal = a.validate(Ledger::Seq{2}, Ledger::ID{2}); + BEAST_EXPECT(AddOutcome::current == harness.add(newVal)); expected.find(a.masterKey())->second = newVal; // Now flush From 892caad20681f2eea5632185e32d963ae3770396 Mon Sep 17 00:00:00 2001 From: Brad Chase Date: Fri, 15 Dec 2017 10:36:09 -0500 Subject: [PATCH 06/38] [FOLD] Hoist and rename AddOutcome to ValStatus --- src/ripple/app/consensus/RCLValidations.cpp | 12 ++-- src/ripple/consensus/Validations.h | 56 ++++++++++----- src/test/consensus/Validations_test.cpp | 77 ++++++++++----------- src/test/csf/Peer.h | 5 +- 4 files changed, 81 insertions(+), 69 deletions(-) diff --git a/src/ripple/app/consensus/RCLValidations.cpp b/src/ripple/app/consensus/RCLValidations.cpp index 2508fe83362..75bc2edd49c 100644 --- a/src/ripple/app/consensus/RCLValidations.cpp +++ b/src/ripple/app/consensus/RCLValidations.cpp @@ -226,16 +226,14 @@ handleNewValidation( // only add trusted or listed if (pubKey) { - using AddOutcome = RCLValidations::AddOutcome; - - AddOutcome const res = validations.add(*pubKey, val); + ValStatus const res = validations.add(*pubKey, val); // This is a duplicate validation - if (res == AddOutcome::repeat) + if (res == ValStatus::repeat) return false; // This validation replaced a prior one with the same sequence number - if (res == AddOutcome::sameSeq) + if (res == ValStatus::sameSeq) { auto const seq = val->getFieldU32(sfLedgerSequence); JLOG(j.warn()) << "Trusted node " @@ -248,13 +246,13 @@ handleNewValidation( << toBase58(TokenType::TOKEN_NODE_PUBLIC, signer) << " added " << (val->isTrusted() ? "trusted/" : "UNtrusted/") - << ((res == AddOutcome::current) ? "current" : "stale"); + << ((res == ValStatus::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)) + (res == ValStatus::current || res == ValStatus::sameSeq)) { app.getLedgerMaster().checkAccept( hash, val->getFieldU32(sfLedgerSequence)); diff --git a/src/ripple/consensus/Validations.h b/src/ripple/consensus/Validations.h index b392619316a..d3b94f005c2 100644 --- a/src/ripple/consensus/Validations.h +++ b/src/ripple/consensus/Validations.h @@ -134,6 +134,37 @@ getPreferredLedger( return netLgr; } +/** Status of newly received validation + */ +enum class ValStatus { + /// This was a new validation and was added + current, + /// Already had this exact same validation + repeat, + /// Not current or was older than current from this node + stale, + /// A validation was re-issued for the same sequence number + sameSeq +}; + +inline std::string +to_string(ValStatus m) +{ + switch (m) + { + case ValStatus::current: + return "current"; + case ValStatus::repeat: + return "repeat"; + case ValStatus::stale: + return "stale"; + case ValStatus::sameSeq: + return "sameSeq"; + default: + return "unknown"; + } +} + /** Maintains current and recent ledger validations. Manages storage and queries related to validations received on the network. @@ -371,19 +402,6 @@ class Validations return parms_; } - /** Result of adding a new validation - */ - enum class AddOutcome { - /// This was a new validation and was added - current, - /// Already had this validation - repeat, - /// Not current or was older than current from this node - stale, - /// Had a validation with same sequence number - sameSeq, - }; - /** Add a new validation Attempt to add a new validation. @@ -397,19 +415,19 @@ class Validations validation might be signed by a temporary or rotating key. */ - AddOutcome + ValStatus add(NodeKey const& key, Validation const& val) { NetClock::time_point t = adaptor_.now(); if (!isCurrent(parms_, t, val.signTime(), val.seenTime())) - return AddOutcome::stale; + return ValStatus::stale; ID const& id = val.ledgerID(); // This is only seated if a validation became stale boost::optional maybeStaleValidation; - AddOutcome result = AddOutcome::current; + ValStatus result = ValStatus::current; { ScopedLock lock{mutex_}; @@ -419,7 +437,7 @@ class Validations // This validation is a repeat if we already have // one with the same id and signing key. if (!ret.second && ret.first->second.key() == val.key()) - return AddOutcome::repeat; + return ValStatus::repeat; // Attempt to insert auto const ins = current_.emplace(key, val); @@ -437,7 +455,7 @@ class Validations if ((oldSeq != Seq{0}) && (newSeq != Seq{0}) && oldSeq == newSeq) { - result = AddOutcome::sameSeq; + result = ValStatus::sameSeq; // If the validation key was revoked, update the // existing validation in the byLedger_ set @@ -493,7 +511,7 @@ class Validations else { // We already have a newer validation from this source - result = AddOutcome::stale; + result = ValStatus::stale; } } } diff --git a/src/test/consensus/Validations_test.cpp b/src/test/consensus/Validations_test.cpp index 56d41f6ad66..b03dc82e526 100644 --- a/src/test/consensus/Validations_test.cpp +++ b/src/test/consensus/Validations_test.cpp @@ -230,9 +230,6 @@ class Validations_test : public beast::unit_test::suite // Specialize generic Validations using the above types using TestValidations = Validations; - // Hoist enum for writing simpler tests - using AddOutcome = TestValidations::AddOutcome; - // Gather the dependencies of TestValidations in a single class and provide // accessors for simplifying test logic class TestHarness @@ -250,7 +247,7 @@ class Validations_test : public beast::unit_test::suite } // Helper to add an existing validation - AddOutcome + ValStatus add(Validation const& v) { PeerKey masterKey{v.nodeID(), 0}; @@ -307,10 +304,10 @@ class Validations_test : public beast::unit_test::suite auto const v = a.validate(Ledger::Seq{1}, Ledger::ID{1}); // Add a current validation - BEAST_EXPECT(AddOutcome::current == harness.add(v)); + BEAST_EXPECT(ValStatus::current == harness.add(v)); // Re-adding is repeat - BEAST_EXPECT(AddOutcome::repeat == harness.add(v)); + BEAST_EXPECT(ValStatus::repeat == harness.add(v)); } { @@ -318,7 +315,7 @@ class Validations_test : public beast::unit_test::suite // Replace with a new validation and ensure the old one is stale BEAST_EXPECT(harness.stale().empty()); - BEAST_EXPECT(AddOutcome::current == + BEAST_EXPECT(ValStatus::current == harness.add(a.validate(Ledger::Seq{2}, Ledger::ID{2}))); BEAST_EXPECT(harness.stale().size() == 1); @@ -343,7 +340,7 @@ class Validations_test : public beast::unit_test::suite BEAST_EXPECT(harness.vals().getNodesAfter(Ledger::ID{2}) == 0); BEAST_EXPECT( - AddOutcome::sameSeq == + ValStatus::sameSeq == harness.add(a.validate(Ledger::Seq{2}, Ledger::ID{20}))); // Old ID should be gone ... @@ -365,7 +362,7 @@ class Validations_test : public beast::unit_test::suite a.advanceKey(); BEAST_EXPECT( - AddOutcome::sameSeq == + ValStatus::sameSeq == harness.add(a.validate(Ledger::Seq{2}, Ledger::ID{20}))); { // Still the only trusted validation for ID{20} @@ -386,15 +383,15 @@ class Validations_test : public beast::unit_test::suite harness.clock().advance(4s); auto const val4 = a.validate(Ledger::Seq{4}, Ledger::ID{4}); - BEAST_EXPECT(AddOutcome::current == harness.add(val4)); + BEAST_EXPECT(ValStatus::current == harness.add(val4)); - BEAST_EXPECT(AddOutcome::stale == harness.add(val3)); + BEAST_EXPECT(ValStatus::stale == harness.add(val3)); // re-issued should not be added auto const val4reissue = a.validate(Ledger::Seq{4}, Ledger::ID{44}); - BEAST_EXPECT(AddOutcome::stale == harness.add(val4reissue)); + BEAST_EXPECT(ValStatus::stale == harness.add(val4reissue)); } { // Process validations out of order with shifted times @@ -403,19 +400,19 @@ class Validations_test : public beast::unit_test::suite harness.clock().advance(1h); // Establish a new current validation - BEAST_EXPECT(AddOutcome::current == + BEAST_EXPECT(ValStatus::current == harness.add(a.validate(Ledger::Seq{8}, Ledger::ID{8}))); // Process a validation that has "later" seq but early sign time BEAST_EXPECT( - AddOutcome::stale == + ValStatus::stale == harness.add( a.validate(Ledger::Seq{9}, Ledger::ID{9}, -1s, -1s))); // Process a validation that has an "earlier" seq but later sign // time BEAST_EXPECT( - AddOutcome::current == + ValStatus::current == harness.add( a.validate(Ledger::Seq{7}, Ledger::ID{7}, 1s, 1s))); } @@ -423,15 +420,15 @@ class Validations_test : public beast::unit_test::suite // Test stale on arrival validations harness.clock().advance(1h); - BEAST_EXPECT(AddOutcome::stale == + BEAST_EXPECT(ValStatus::stale == harness.add(a.validate(Ledger::Seq{15}, Ledger::ID{15}, -harness.parms().validationCURRENT_EARLY, 0s))); - BEAST_EXPECT(AddOutcome::stale == + BEAST_EXPECT(ValStatus::stale == harness.add(a.validate(Ledger::Seq{15}, Ledger::ID{15}, harness.parms().validationCURRENT_WALL, 0s))); - BEAST_EXPECT(AddOutcome::stale == + BEAST_EXPECT(ValStatus::stale == harness.add(a.validate(Ledger::Seq{15}, Ledger::ID{15}, 0s, harness.parms().validationCURRENT_LOCAL))); } @@ -445,7 +442,7 @@ class Validations_test : public beast::unit_test::suite TestHarness harness; Node a = harness.makeNode(); - BEAST_EXPECT(AddOutcome::current == + BEAST_EXPECT(ValStatus::current == harness.add(a.validate(Ledger::Seq{1}, Ledger::ID{1}))); harness.vals().currentTrusted(); BEAST_EXPECT(harness.stale().empty()); @@ -473,9 +470,9 @@ class Validations_test : public beast::unit_test::suite // first round a,b,c agree, d has differing id for (auto const& node : {a, b, c}) - BEAST_EXPECT(AddOutcome::current == + BEAST_EXPECT(ValStatus::current == harness.add(node.validate(Ledger::Seq{1}, Ledger::ID{1}))); - BEAST_EXPECT(AddOutcome::current == + BEAST_EXPECT(ValStatus::current == harness.add(d.validate(Ledger::Seq{1}, Ledger::ID{10}))); // Nothing past ledger 1 yet @@ -486,13 +483,13 @@ class Validations_test : public beast::unit_test::suite // a and b have the same prior id, but b has a different current id // c is untrusted but on the same prior id // d has a different prior id - BEAST_EXPECT(AddOutcome::current == + BEAST_EXPECT(ValStatus::current == harness.add(a.validate(Ledger::Seq{2}, Ledger::ID{2}))); - BEAST_EXPECT(AddOutcome::current == + BEAST_EXPECT(ValStatus::current == harness.add(b.validate(Ledger::Seq{2}, Ledger::ID{20}))); - BEAST_EXPECT(AddOutcome::current == + BEAST_EXPECT(ValStatus::current == harness.add(c.validate(Ledger::Seq{2}, Ledger::ID{2}))); - BEAST_EXPECT(AddOutcome::current == + BEAST_EXPECT(ValStatus::current == harness.add(d.validate(Ledger::Seq{2}, Ledger::ID{2}))); BEAST_EXPECT(harness.vals().getNodesAfter(Ledger::ID{1}) == 2); @@ -508,9 +505,9 @@ class Validations_test : public beast::unit_test::suite Node a = harness.makeNode(), b = harness.makeNode(); b.untrust(); - BEAST_EXPECT(AddOutcome::current == + BEAST_EXPECT(ValStatus::current == harness.add(a.validate(Ledger::Seq{1}, Ledger::ID{1}))); - BEAST_EXPECT(AddOutcome::current == + BEAST_EXPECT(ValStatus::current == harness.add(b.validate(Ledger::Seq{1}, Ledger::ID{3}))); // Only a is trusted @@ -523,7 +520,7 @@ class Validations_test : public beast::unit_test::suite harness.clock().advance(3s); for (auto const& node : {a, b}) - BEAST_EXPECT(AddOutcome::current == + BEAST_EXPECT(ValStatus::current == harness.add(node.validate(Ledger::Seq{2}, Ledger::ID{2}))); // New validation for a @@ -549,7 +546,7 @@ class Validations_test : public beast::unit_test::suite b.untrust(); for (auto const& node : {a, b}) - BEAST_EXPECT(AddOutcome::current == + BEAST_EXPECT(ValStatus::current == harness.add(node.validate(Ledger::Seq{1}, Ledger::ID{1}))); { @@ -565,7 +562,7 @@ class Validations_test : public beast::unit_test::suite b.advanceKey(); for (auto const& node : {a, b}) - BEAST_EXPECT(AddOutcome::current == + BEAST_EXPECT(ValStatus::current == harness.add(node.validate(Ledger::Seq{2}, Ledger::ID{2}))); { @@ -599,16 +596,16 @@ class Validations_test : public beast::unit_test::suite // goldilocks on seq 2, but is not trusted for (auto const& node : {baby, papa, mama, goldilocks}) - BEAST_EXPECT(AddOutcome::current == + BEAST_EXPECT(ValStatus::current == harness.add(node.validate(Ledger::Seq{1}, Ledger::ID{1}))); harness.clock().advance(1s); for (auto const& node : {baby, mama, goldilocks}) - BEAST_EXPECT(AddOutcome::current == + BEAST_EXPECT(ValStatus::current == harness.add(node.validate(Ledger::Seq{2}, Ledger::ID{2}))); harness.clock().advance(1s); - BEAST_EXPECT(AddOutcome::current == + BEAST_EXPECT(ValStatus::current == harness.add(mama.validate(Ledger::Seq{3}, Ledger::ID{3}))); { @@ -731,13 +728,13 @@ class Validations_test : public beast::unit_test::suite for (auto const& node : {a, b, c}) { auto const val = node.validate(Ledger::Seq{1}, Ledger::ID{1}); - BEAST_EXPECT(AddOutcome::current == harness.add(val)); + BEAST_EXPECT(ValStatus::current == harness.add(val)); if (val.trusted()) trustedValidations[val.ledgerID()].emplace_back(val); } { auto const val = d.validate(Ledger::Seq{1}, Ledger::ID{11}); - BEAST_EXPECT(AddOutcome::current == harness.add(val)); + BEAST_EXPECT(ValStatus::current == harness.add(val)); trustedValidations[val.ledgerID()].emplace_back(val); } @@ -746,13 +743,13 @@ class Validations_test : public beast::unit_test::suite for (auto const& node : {a, b, c}) { auto const val = node.validate(Ledger::Seq{2}, Ledger::ID{2}); - BEAST_EXPECT(AddOutcome::current == harness.add(val)); + BEAST_EXPECT(ValStatus::current == harness.add(val)); if (val.trusted()) trustedValidations[val.ledgerID()].emplace_back(val); } { auto const val = d.validate(Ledger::Seq{2}, Ledger::ID{1}); - BEAST_EXPECT(AddOutcome::current == harness.add(val)); + BEAST_EXPECT(ValStatus::current == harness.add(val)); trustedValidations[val.ledgerID()].emplace_back(val); } @@ -767,7 +764,7 @@ class Validations_test : public beast::unit_test::suite TestHarness harness; Node a = harness.makeNode(); - BEAST_EXPECT(AddOutcome::current == + BEAST_EXPECT(ValStatus::current == harness.add(a.validate(Ledger::Seq{1}, Ledger::ID{1}))); BEAST_EXPECT(harness.vals().numTrustedForLedger(Ledger::ID{1})); harness.clock().advance(harness.parms().validationSET_EXPIRES); @@ -791,7 +788,7 @@ class Validations_test : public beast::unit_test::suite for (auto const& node : {a, b, c}) { auto const val = node.validate(Ledger::Seq{1}, Ledger::ID{1}); - BEAST_EXPECT(AddOutcome::current == harness.add(val)); + BEAST_EXPECT(ValStatus::current == harness.add(val)); expected.emplace(node.masterKey(), val); } Validation staleA = expected.find(a.masterKey())->second; @@ -801,7 +798,7 @@ class Validations_test : public beast::unit_test::suite // map after setting the proper prior ledger ID it replaced harness.clock().advance(1s); auto newVal = a.validate(Ledger::Seq{2}, Ledger::ID{2}); - BEAST_EXPECT(AddOutcome::current == harness.add(newVal)); + BEAST_EXPECT(ValStatus::current == harness.add(newVal)); expected.find(a.masterKey())->second = newVal; // Now flush diff --git a/src/test/csf/Peer.h b/src/test/csf/Peer.h index 340f49001fa..1e7e7e5b164 100644 --- a/src/test/csf/Peer.h +++ b/src/test/csf/Peer.h @@ -203,7 +203,6 @@ struct Peer //! Validations from trusted nodes Validations validations; - using AddOutcome = Validations::AddOutcome; //! The most recent ledger that has been fully validated by the network from //! the perspective of this Peer @@ -675,9 +674,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::repeat) return false; // Acquire will try to get from network if not already local From 085e4005fbd30f51bb5438f192ae9cd343cca0fa Mon Sep 17 00:00:00 2001 From: Brad Chase Date: Fri, 15 Dec 2017 10:54:40 -0500 Subject: [PATCH 07/38] [FOLD] Make lock use explicit --- src/ripple/consensus/Validations.h | 43 +++++++++++++++++------------- 1 file changed, 25 insertions(+), 18 deletions(-) diff --git a/src/ripple/consensus/Validations.h b/src/ripple/consensus/Validations.h index d3b94f005c2..a3cffdaefbc 100644 --- a/src/ripple/consensus/Validations.h +++ b/src/ripple/consensus/Validations.h @@ -300,10 +300,9 @@ class Validations private: /** Iterate current validations. - Iterate current validations, optionally removing any stale validations - if a time is specified. + Iterate current validations, flushing any which are stale. - @param t (Optional) Time used to determine staleness + @param lock Existing lock of mutex_ @param pre Invokable with signature (std::size_t) called prior to looping. @param f Invokable with signature (NodeKey const &, Validations const &) @@ -317,19 +316,17 @@ class Validations template void - current(boost::optional t, Pre&& pre, F&& f) + current(ScopedLock const& lock, Pre&& pre, F&& f) { - ScopedLock lock{mutex_}; + NetClock::time_point t = adaptor_.now(); pre(current_.size()); auto it = current_.begin(); while (it != current_.end()) { - // Check for staleness, if time specified - if (t && - !isCurrent( - parms_, *t, it->second.val.signTime(), it->second.val.seenTime())) + // Check for staleness + if (!isCurrent( + parms_, t, it->second.val.signTime(), it->second.val.seenTime())) { - // contains a stale record adaptor_.onStale(std::move(it->second.val)); it = current_.erase(it); } @@ -345,6 +342,7 @@ class Validations /** Iterate the set of validations associated with a given ledger id + @param lock Existing lock on mutex_ @param ledgerID The identifier of the ledger @param pre Invokable with signature(std::size_t) @param f Invokable with signature (NodeKey const &, Validation const &) @@ -356,9 +354,8 @@ class Validations */ template void - byLedger(ID const& ledgerID, Pre&& pre, F&& f) + byLedger(ScopedLock const&, ID const& ledgerID, Pre&& pre, F&& f) { - ScopedLock lock{mutex_}; auto it = byLedger_.find(ledgerID); if (it != byLedger_.end()) { @@ -561,8 +558,9 @@ class Validations hash_map ret; + ScopedLock lock{mutex_}; current( - adaptor_.now(), + lock, // The number of validations does not correspond to the number of // distinct ledgerIDs so we do not call reserve on ret. [](std::size_t) {}, @@ -622,10 +620,9 @@ class Validations { std::size_t count = 0; - // Historically this did not not check for stale validations - // That may not be important, but this preserves the behavior + ScopedLock lock{mutex_}; current( - boost::none, + lock, [&](std::size_t) {}, // nothing to reserve [&](NodeKey const&, ValidationAndPrevID const& v) { if (v.val.trusted() && v.prevLedgerID == ledgerID) @@ -643,8 +640,9 @@ class Validations { std::vector ret; + ScopedLock lock{mutex_}; current( - adaptor_.now(), + lock, [&](std::size_t numValidations) { ret.reserve(numValidations); }, [&](NodeKey const&, ValidationAndPrevID const& v) { if (v.val.trusted()) @@ -662,8 +660,9 @@ class Validations getCurrentPublicKeys() { hash_set ret; + ScopedLock lock{mutex_}; current( - adaptor_.now(), + lock, [&](std::size_t numValidations) { ret.reserve(numValidations); }, [&](NodeKey const& k, ValidationAndPrevID const&) { ret.insert(k); }); @@ -679,7 +678,9 @@ class Validations numTrustedForLedger(ID const& ledgerID) { std::size_t count = 0; + ScopedLock lock{mutex_}; byLedger( + lock, ledgerID, [&](std::size_t) {}, // nothing to reserve [&](NodeKey const&, Validation const& v) { @@ -698,7 +699,9 @@ class Validations getTrustedForLedger(ID const& ledgerID) { std::vector res; + ScopedLock lock{mutex_}; byLedger( + lock, ledgerID, [&](std::size_t numValidations) { res.reserve(numValidations); }, [&](NodeKey const&, Validation const& v) { @@ -718,7 +721,9 @@ class Validations getTrustedValidationTimes(ID const& ledgerID) { std::vector times; + ScopedLock lock{mutex_}; byLedger( + lock, ledgerID, [&](std::size_t numValidations) { times.reserve(numValidations); }, [&](NodeKey const&, Validation const& v) { @@ -738,7 +743,9 @@ class Validations fees(ID const& ledgerID, std::uint32_t baseFee) { std::vector res; + ScopedLock lock{mutex_}; byLedger( + lock, ledgerID, [&](std::size_t numValidations) { res.reserve(numValidations); }, [&](NodeKey const&, Validation const& v) { From 2abf76635c4606419bc66a0c1ba730a1302bb571 Mon Sep 17 00:00:00 2001 From: Brad Chase Date: Fri, 15 Dec 2017 10:59:08 -0500 Subject: [PATCH 08/38] [FOLD] Require full validations for trusted calculations --- src/ripple/consensus/Validations.h | 24 ++++++++++++++---------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/src/ripple/consensus/Validations.h b/src/ripple/consensus/Validations.h index a3cffdaefbc..7a1608152f5 100644 --- a/src/ripple/consensus/Validations.h +++ b/src/ripple/consensus/Validations.h @@ -573,7 +573,7 @@ class Validations &ret](NodeKey const&, ValidationAndPrevID const& vp) { Validation const& v = vp.val; ID const& prevLedgerID = vp.prevLedgerID; - if (!v.trusted()) + if (!v.trusted() || !v.full()) return; Seq const seq = v.seq(); @@ -623,9 +623,10 @@ class Validations ScopedLock lock{mutex_}; current( lock, - [&](std::size_t) {}, // nothing to reserve + [&](std::size_t) {}, // nothing to reserve [&](NodeKey const&, ValidationAndPrevID const& v) { - if (v.val.trusted() && v.prevLedgerID == ledgerID) + if (v.val.trusted() && v.prevLedgerID == ledgerID && + v.val.full()) ++count; }); return count; @@ -645,7 +646,7 @@ class Validations lock, [&](std::size_t numValidations) { ret.reserve(numValidations); }, [&](NodeKey const&, ValidationAndPrevID const& v) { - if (v.val.trusted()) + if (v.val.trusted() && v.val.full()) ret.push_back(v.val.unwrap()); }); return ret; @@ -654,7 +655,8 @@ class Validations /** Get the set of known public keys associated with current validations @return The set of of knowns keys for current trusted and untrusted - validations + validations. This includes nodes whose last validation is + partial. */ hash_set getCurrentPublicKeys() @@ -664,7 +666,9 @@ class Validations current( lock, [&](std::size_t numValidations) { ret.reserve(numValidations); }, - [&](NodeKey const& k, ValidationAndPrevID const&) { ret.insert(k); }); + [&](NodeKey const& k, ValidationAndPrevID const& ) { + ret.insert(k); + }); return ret; } @@ -684,7 +688,7 @@ class Validations ledgerID, [&](std::size_t) {}, // nothing to reserve [&](NodeKey const&, Validation const& v) { - if (v.trusted()) + if (v.trusted() && v.full()) ++count; }); return count; @@ -705,7 +709,7 @@ class Validations ledgerID, [&](std::size_t numValidations) { res.reserve(numValidations); }, [&](NodeKey const&, Validation const& v) { - if (v.trusted()) + if (v.trusted() && v.full()) res.emplace_back(v.unwrap()); }); @@ -727,7 +731,7 @@ class Validations ledgerID, [&](std::size_t numValidations) { times.reserve(numValidations); }, [&](NodeKey const&, Validation const& v) { - if (v.trusted()) + if (v.trusted() && v.full()) times.emplace_back(v.signTime()); }); return times; @@ -749,7 +753,7 @@ class Validations ledgerID, [&](std::size_t numValidations) { res.reserve(numValidations); }, [&](NodeKey const&, Validation const& v) { - if (v.trusted()) + if (v.trusted() && v.full()) { boost::optional loadFee = v.loadFee(); if (loadFee) From f4c86447a4825dfa453ea70e1de8f2b20fbce961 Mon Sep 17 00:00:00 2001 From: Brad Chase Date: Fri, 15 Dec 2017 11:37:18 -0500 Subject: [PATCH 09/38] [FOLD] Simplify Validations::add: In the case when a validator switched signing keys, the old code tried to detect a case where the new validator re-issued the same sequence validation with the new key, but for a different ledger. This is not ideal, since there is no gaurantee when other validators see the two different validations or the manifest updates. The next changeset requiring increasing full validation sequence numbers will makes this change more relevant, so updating tests will be deferred until then. --- src/ripple/app/consensus/RCLValidations.cpp | 102 ++++++------------ src/ripple/consensus/Validations.h | 114 +++----------------- src/test/consensus/Validations_test.cpp | 37 +------ 3 files changed, 52 insertions(+), 201 deletions(-) diff --git a/src/ripple/app/consensus/RCLValidations.cpp b/src/ripple/app/consensus/RCLValidations.cpp index 75bc2edd49c..9cb4d73b367 100644 --- a/src/ripple/app/consensus/RCLValidations.cpp +++ b/src/ripple/app/consensus/RCLValidations.cpp @@ -171,100 +171,60 @@ RCLValidationsAdaptor::doStaleWrite(ScopedLockType&) } bool -handleNewValidation( - Application& app, +handleNewValidation(Application& app, STValidation::ref val, std::string const& source) { - PublicKey const& signer = val->getSignerPublic(); + PublicKey const& signingKey = val->getSignerPublic(); uint256 const& hash = val->getLedgerHash(); // Ensure validation is marked as trusted if signer currently trusted - boost::optional pubKey = app.validators().getTrustedKey(signer); - if (!val->isTrusted() && pubKey) + boost::optional masterKey = + app.validators().getTrustedKey(signingKey); + if (!val->isTrusted() && masterKey) val->setTrusted(); - RCLValidations& validations = app.getValidations(); - - beast::Journal j = validations.adaptor().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) + // masterKey is seated only if validator is trusted or listed + if (masterKey) { - ValStatus const res = validations.add(*pubKey, val); - - // This is a duplicate validation - if (res == ValStatus::repeat) - return false; - - // This validation replaced a prior one with the same sequence number - if (res == ValStatus::sameSeq) - { - auto const seq = val->getFieldU32(sfLedgerSequence); - JLOG(j.warn()) << "Trusted node " - << toBase58(TokenType::TOKEN_NODE_PUBLIC, *pubKey) - << " published multiple validations for ledger " - << seq; - } - - JLOG(j.debug()) << "Val for " << hash << " from " - << toBase58(TokenType::TOKEN_NODE_PUBLIC, signer) - << " added " - << (val->isTrusted() ? "trusted/" : "UNtrusted/") - << ((res == ValStatus::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 == ValStatus::current || res == ValStatus::sameSeq)) + ValStatus const outcome = validations.add(*masterKey, val); + + auto dmp = [&](beast::Journal::Stream s, std::string const& msg) { + s << "Val for " << hash + << (val->isTrusted() ? " trusted/" : " UNtrusted/") + << (val->isFull() ? " full" : "partial") << " from " + << toBase58(TokenType::TOKEN_NODE_PUBLIC, *masterKey) + << "signing key " + << toBase58(TokenType::TOKEN_NODE_PUBLIC, signingKey) << " " + << msg + << " src=" << source; + }; + + if(j.debug()) + dmp(j.debug(), to_string(outcome)); + + 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 diff --git a/src/ripple/consensus/Validations.h b/src/ripple/consensus/Validations.h index 7a1608152f5..2e27a2eca53 100644 --- a/src/ripple/consensus/Validations.h +++ b/src/ripple/consensus/Validations.h @@ -142,9 +142,7 @@ enum class ValStatus { /// Already had this exact same validation repeat, /// Not current or was older than current from this node - stale, - /// A validation was re-issued for the same sequence number - sameSeq + stale }; inline std::string @@ -158,8 +156,6 @@ to_string(ValStatus m) return "repeat"; case ValStatus::stale: return "stale"; - case ValStatus::sameSeq: - return "sameSeq"; default: return "unknown"; } @@ -403,123 +399,45 @@ class Validations Attempt to add a new validation. - @param key The NodeKey to use for the validation - @param val The validation to store - @return The outcome of the attempt - - @note The provided key may differ from the validation's - key() member since we might be storing by master key and the - validation might be signed by a temporary or rotating key. + @param key The master key associated with this validation + @param val The validationo to store + @return The outcome + @note The provided key may differ from the validations's key() + member if the validator is using ephemeral signing keys. */ ValStatus add(NodeKey const& key, Validation const& val) { - NetClock::time_point t = adaptor_.now(); - if (!isCurrent(parms_, t, val.signTime(), val.seenTime())) + if (!isCurrent(parms_, adaptor_.now(), val.signTime(), val.seenTime())) return ValStatus::stale; - ID const& id = val.ledgerID(); - - // This is only seated if a validation became stale - boost::optional maybeStaleValidation; - - ValStatus result = ValStatus::current; - { ScopedLock lock{mutex_}; - auto const ret = byLedger_[id].emplace(key, val); - // This validation is a repeat if we already have - // one with the same id and signing key. + // one with the same id for this key + auto const ret = byLedger_[val.ledgerID()].emplace(key, val); if (!ret.second && ret.first->second.key() == val.key()) return ValStatus::repeat; - // Attempt to insert auto const ins = current_.emplace(key, val); - if (!ins.second) { - // Had a previous validation from the node, consider updating + // Replace existing only if this one is newer Validation& oldVal = ins.first->second.val; - ID const previousLedgerID = ins.first->second.prevLedgerID; - - Seq const oldSeq{oldVal.seq()}; - Seq const newSeq{val.seq()}; - - // Sequence of 0 indicates a missing sequence number - if ((oldSeq != Seq{0}) && (newSeq != Seq{0}) && - oldSeq == newSeq) + if (val.signTime() > oldVal.signTime()) { - result = ValStatus::sameSeq; - - // If the validation key was revoked, update the - // existing validation in the byLedger_ set - if (val.key() != oldVal.key()) - { - auto const mapIt = byLedger_.find(oldVal.ledgerID()); - if (mapIt != byLedger_.end()) - { - auto& validationMap = mapIt->second; - // If a new validation with the same ID was - // reissued we simply replace. - if(oldVal.ledgerID() == val.ledgerID()) - { - auto replaceRes = validationMap.emplace(key, val); - // If it was already there, replace - if(!replaceRes.second) - replaceRes.first->second = val; - } - else - { - // If the new validation has a different ID, - // we remove the old. - validationMap.erase(key); - // Erase the set if it is now empty - if (validationMap.empty()) - byLedger_.erase(mapIt); - } - } - } - } - - if (val.signTime() > oldVal.signTime() || - val.key() != oldVal.key()) - { - // This is either a newer validation or a new signing key - ID const prevID = [&]() { - // In the normal case, the prevID is the ID of the - // ledger we replace - if (oldVal.ledgerID() != val.ledgerID()) - return oldVal.ledgerID(); - // In the case the key was revoked and a new validation - // for the same ledger ID was sent, the previous ledger - // is still the one the now revoked validation had - return previousLedgerID; - }(); - - // Allow impl to take over oldVal - maybeStaleValidation.emplace(std::move(oldVal)); - // Replace old val in the map and set the previous ledger ID + ID oldID = oldVal.ledgerID(); + adaptor_.onStale(std::move(oldVal)); ins.first->second.val = val; - ins.first->second.prevLedgerID = prevID; + ins.first->second.prevLedgerID = oldID; } else - { - // We already have a newer validation from this source - result = ValStatus::stale; - } + return ValStatus::stale; } } - - // Handle the newly stale validation outside the lock - if (maybeStaleValidation) - { - adaptor_.onStale(std::move(*maybeStaleValidation)); - } - - return result; + return ValStatus::current; } /** Expire old validation sets diff --git a/src/test/consensus/Validations_test.cpp b/src/test/consensus/Validations_test.cpp index b03dc82e526..f5dbf900d88 100644 --- a/src/test/consensus/Validations_test.cpp +++ b/src/test/consensus/Validations_test.cpp @@ -294,7 +294,7 @@ class Validations_test : public beast::unit_test::suite void testAddValidation() { - // Test adding current,stale,repeat,sameSeq validations + // Test adding current,stale,repeat validations using namespace std::chrono_literals; TestHarness harness; @@ -340,39 +340,12 @@ class Validations_test : public beast::unit_test::suite BEAST_EXPECT(harness.vals().getNodesAfter(Ledger::ID{2}) == 0); BEAST_EXPECT( - ValStatus::sameSeq == + ValStatus::stale == harness.add(a.validate(Ledger::Seq{2}, Ledger::ID{20}))); - // Old ID should be gone ... - BEAST_EXPECT(harness.vals().numTrustedForLedger(Ledger::ID{2}) == 0); - BEAST_EXPECT(harness.vals().numTrustedForLedger(Ledger::ID{20}) == 1); - { - // Should be the only trusted for ID{20} - auto trustedVals = - harness.vals().getTrustedForLedger(Ledger::ID{20}); - BEAST_EXPECT(trustedVals.size() == 1); - BEAST_EXPECT(trustedVals[0].key() == a.currKey()); - // ... and should be the only node after ID{2} - BEAST_EXPECT(harness.vals().getNodesAfter(Ledger::ID{2}) == 1); - - } - - // A new key, but re-issue a validation with the same ID and - // Sequence - a.advanceKey(); - - BEAST_EXPECT( - ValStatus::sameSeq == - harness.add(a.validate(Ledger::Seq{2}, Ledger::ID{20}))); - { - // Still the only trusted validation for ID{20} - auto trustedVals = - harness.vals().getTrustedForLedger(Ledger::ID{20}); - BEAST_EXPECT(trustedVals.size() == 1); - BEAST_EXPECT(trustedVals[0].key() == a.currKey()); - // and still follows ID{2} since it was a re-issue - BEAST_EXPECT(harness.vals().getNodesAfter(Ledger::ID{2}) == 1); - } + BEAST_EXPECT(harness.vals().numTrustedForLedger(Ledger::ID{2}) == 1); + // THIS FAILS pending filtering on increasing seq #'s + BEAST_EXPECT(harness.vals().numTrustedForLedger(Ledger::ID{20}) == 0); } { From 9187516985277a4a0623b51278305b80c52e6e6a Mon Sep 17 00:00:00 2001 From: Brad Chase Date: Fri, 15 Dec 2017 12:28:29 -0500 Subject: [PATCH 10/38] [FOLD] Require increasing full validation sequence numbers: With the preferred ledger by branch strategy for determing the proper ledger, we can now leverage partial validations to detect when validators recognize they are on the wrong branch and recover. In some cases, this could mean issuing a partial validation for an earlier sequenced ledger. However, for full validations, we do not want a case where a validator validates two different ledgers with the same sequence number. This changeset prevents validators from issuing such validations as part of consensus, and also prevents receiving validations that violate this invariant. --- src/ripple/app/consensus/RCLConsensus.cpp | 10 ++++++- src/ripple/app/consensus/RCLConsensus.h | 3 ++ src/ripple/app/consensus/RCLValidations.cpp | 6 ++++ src/ripple/consensus/Validations.h | 31 +++++++++++++++++---- src/test/consensus/Validations_test.cpp | 27 ++++++------------ src/test/csf/Peer.h | 20 ++++++++++--- 6 files changed, 67 insertions(+), 30 deletions(-) diff --git a/src/ripple/app/consensus/RCLConsensus.cpp b/src/ripple/app/consensus/RCLConsensus.cpp index 925b701a497..dd24e33acf2 100644 --- a/src/ripple/app/consensus/RCLConsensus.cpp +++ b/src/ripple/app/consensus/RCLConsensus.cpp @@ -839,9 +839,14 @@ RCLConsensus::Adaptor::validate(RCLCxLedger const& ledger, bool proposing) validationTime = lastValidationTime_ + 1s; lastValidationTime_ = validationTime; + // Can only fully validate later sequenced ledgers + const bool isFull = proposing && ledger.seq() > largestFullValidationSeq_; + largestFullValidationSeq_ = + std::max(largestFullValidationSeq_, ledger.seq()); + // Build validation auto v = std::make_shared( - ledger.id(), validationTime, valPublic_, proposing); + ledger.id(), validationTime, valPublic_, isFull); v->setFieldU32(sfLedgerSequence, ledger.seq()); // Add our load fee to the validation @@ -956,6 +961,9 @@ RCLConsensus::Adaptor::preStartRound(RCLCxLedger const & prevLgr) prevLgr.seq() >= app_.getMaxDisallowedLedger() && !app_.getOPs().isAmendmentBlocked(); + largestFullValidationSeq_ = + std::max(largestFullValidationSeq_, app_.getMaxDisallowedLedger()); + const bool synced = app_.getOPs().getOperatingMode() == NetworkOPs::omFULL; if (validating_) diff --git a/src/ripple/app/consensus/RCLConsensus.h b/src/ripple/app/consensus/RCLConsensus.h index c200589b070..5dcb3fcc284 100644 --- a/src/ripple/app/consensus/RCLConsensus.h +++ b/src/ripple/app/consensus/RCLConsensus.h @@ -69,6 +69,9 @@ class RCLConsensus // The timestamp of the last validation we used NetClock::time_point lastValidationTime_; + // Largest sequence number fully validated + LedgerIndex largestFullValidationSeq_ = 0; + // These members are queried via public accesors and are atomic for // thread safety. std::atomic validating_{false}; diff --git a/src/ripple/app/consensus/RCLValidations.cpp b/src/ripple/app/consensus/RCLValidations.cpp index 9cb4d73b367..98e4132de8b 100644 --- a/src/ripple/app/consensus/RCLValidations.cpp +++ b/src/ripple/app/consensus/RCLValidations.cpp @@ -211,6 +211,12 @@ handleNewValidation(Application& app, if(j.debug()) dmp(j.debug(), to_string(outcome)); + if(outcome == ValStatus::badFullSeq && j.warn()) + { + auto const seq = val->getFieldU32(sfLedgerSequence); + dmp(j.warn(), " already fully validated sequence past " + to_string(seq)); + } + if (val->isTrusted() && outcome == ValStatus::current) { app.getLedgerMaster().checkAccept( diff --git a/src/ripple/consensus/Validations.h b/src/ripple/consensus/Validations.h index 2e27a2eca53..76cabf7e548 100644 --- a/src/ripple/consensus/Validations.h +++ b/src/ripple/consensus/Validations.h @@ -142,7 +142,9 @@ enum class ValStatus { /// Already had this exact same validation repeat, /// Not current or was older than current from this node - stale + stale, + /// A validation was marked full but it violates increasing seq requirement + badFullSeq }; inline std::string @@ -156,6 +158,8 @@ to_string(ValStatus m) return "repeat"; case ValStatus::stale: return "stale"; + case ValStatus::badFullSeq: + return "badFullSeq"; default: return "unknown"; } @@ -263,8 +267,8 @@ class Validations // Manages concurrent access to current_ and byLedger_ Mutex mutex_; - //! For the most recent validation, we also want to store the ID - //! of the ledger it replaces + // For the most recent validation, we also want to store the ID + // of the ledger it replaces struct ValidationAndPrevID { ValidationAndPrevID(Validation const& v) : val{v}, prevLedgerID{0} @@ -275,10 +279,13 @@ class Validations ID prevLedgerID; }; - //! The latest validation from each node + // Validations from currently listed and trusted nodes (partial and full) hash_map current_; - //! Recent validations from nodes, indexed by ledger identifier + // Sequence of the largest full validation received from each node + hash_map largestFullValidation_; + + // Validations from listed nodes, indexed by ledger id (partial and full) beast::aged_unordered_map< ID, hash_map, @@ -286,7 +293,7 @@ class Validations beast::uhash<>> byLedger_; - //! Parameters to determine validation staleness + // Parameters to determine validation staleness ValidationParms const parms_; // Adaptor instance @@ -415,6 +422,18 @@ class Validations { ScopedLock lock{mutex_}; + // Ensure full validations are for increasing sequence numbers + if (val.full() && val.seq() != Seq{0}) + { + auto const ins = largestFullValidation_.emplace(key, val.seq()); + if (!ins.second) + { + if (val.seq() <= ins.first->second) + return ValStatus::badFullSeq; + ins.first->second = val.seq(); + } + } + // This validation is a repeat if we already have // one with the same id for this key auto const ret = byLedger_[val.ledgerID()].emplace(key, val); diff --git a/src/test/consensus/Validations_test.cpp b/src/test/consensus/Validations_test.cpp index f5dbf900d88..6d71d88c10f 100644 --- a/src/test/consensus/Validations_test.cpp +++ b/src/test/consensus/Validations_test.cpp @@ -307,7 +307,7 @@ class Validations_test : public beast::unit_test::suite BEAST_EXPECT(ValStatus::current == harness.add(v)); // Re-adding is repeat - BEAST_EXPECT(ValStatus::repeat == harness.add(v)); + BEAST_EXPECT(ValStatus::badFullSeq == harness.add(v)); } { @@ -340,31 +340,27 @@ class Validations_test : public beast::unit_test::suite BEAST_EXPECT(harness.vals().getNodesAfter(Ledger::ID{2}) == 0); BEAST_EXPECT( - ValStatus::stale == + ValStatus::badFullSeq == harness.add(a.validate(Ledger::Seq{2}, Ledger::ID{20}))); - BEAST_EXPECT(harness.vals().numTrustedForLedger(Ledger::ID{2}) == 1); - // THIS FAILS pending filtering on increasing seq #'s - BEAST_EXPECT(harness.vals().numTrustedForLedger(Ledger::ID{20}) == 0); + BEAST_EXPECT( + harness.vals().numTrustedForLedger(Ledger::ID{2}) == 1); + BEAST_EXPECT( + harness.vals().numTrustedForLedger(Ledger::ID{20}) == 0); } { // Processing validations out of order should ignore the older harness.clock().advance(2s); - auto const val3 = a.validate(Ledger::Seq{3}, Ledger::ID{3}); + auto const val3 = a.validate(Ledger::Seq{4}, Ledger::ID{4}); harness.clock().advance(4s); - auto const val4 = a.validate(Ledger::Seq{4}, Ledger::ID{4}); + auto const val4 = a.validate(Ledger::Seq{3}, Ledger::ID{3}); BEAST_EXPECT(ValStatus::current == harness.add(val4)); BEAST_EXPECT(ValStatus::stale == harness.add(val3)); - // re-issued should not be added - auto const val4reissue = - a.validate(Ledger::Seq{4}, Ledger::ID{44}); - - BEAST_EXPECT(ValStatus::stale == harness.add(val4reissue)); } { // Process validations out of order with shifted times @@ -381,13 +377,6 @@ class Validations_test : public beast::unit_test::suite ValStatus::stale == harness.add( a.validate(Ledger::Seq{9}, Ledger::ID{9}, -1s, -1s))); - - // Process a validation that has an "earlier" seq but later sign - // time - BEAST_EXPECT( - ValStatus::current == - harness.add( - a.validate(Ledger::Seq{7}, Ledger::ID{7}, 1s, 1s))); } { // Test stale on arrival validations diff --git a/src/test/csf/Peer.h b/src/test/csf/Peer.h index 1e7e7e5b164..afbf3d27cda 100644 --- a/src/test/csf/Peer.h +++ b/src/test/csf/Peer.h @@ -236,6 +236,9 @@ struct Peer //! Whether to simulate running as validator or a tracking node bool runAsValidator = true; + //! Sequence number of largest full validation thus far + Ledger::Seq largestFullValidation{0}; + //TODO: Consider removing these two, they are only a convenience for tests // Number of proposers in the prior round std::size_t prevProposers = 0; @@ -537,7 +540,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, @@ -562,16 +568,22 @@ struct Peer bool const isCompatible = newLedger.isAncestor(fullyValidatedLedger); - if (runAsValidator && isCompatible) + if (runAsValidator && isCompatible && !consensusFail) { + // Can only send one fully validated ledger per seq + bool isFull = + proposing && newLedger.seq() > largestFullValidation; + largestFullValidation = + std::max(largestFullValidation, newLedger.seq()); + Validation v{newLedger.id(), newLedger.seq(), now(), now(), key, id, - false}; - // share is not trusted + isFull}; + // share thew new validation; it is trusted by the receiver share(v); // we trust ourselves addTrustedValidation(v); From 6d3c2e0a2c093974e1765354c06986e4cb91902a Mon Sep 17 00:00:00 2001 From: Brad Chase Date: Fri, 15 Dec 2017 13:55:30 -0500 Subject: [PATCH 11/38] [FOLD] Use Ledgers in Validations_test: In a future changeset, adding LedgerTrie to the Validations class will require actual ledgers with ancestry. This changeset uses the LedgerHistoryHelper and LedgerOracle to use proper ledgers when testing the current Validations code. --- src/test/consensus/Validations_test.cpp | 452 ++++++++++++++---------- 1 file changed, 272 insertions(+), 180 deletions(-) diff --git a/src/test/consensus/Validations_test.cpp b/src/test/consensus/Validations_test.cpp index 6d71d88c10f..10c59496d86 100644 --- a/src/test/consensus/Validations_test.cpp +++ b/src/test/consensus/Validations_test.cpp @@ -113,8 +113,8 @@ class Validations_test : public beast::unit_test::suite // with signing and seen times offset from the common clock Validation validate( - Ledger::Seq seq, Ledger::ID id, + Ledger::Seq seq, NetClock::duration signOffset, NetClock::duration seenOffset, bool full) const @@ -134,28 +134,31 @@ class Validations_test : public beast::unit_test::suite Validation validate( - Ledger::Seq seq, - Ledger::ID id, + Ledger ledger, NetClock::duration signOffset, NetClock::duration seenOffset) const { return validate( - seq, id, signOffset, seenOffset, true); + ledger.id(), ledger.seq(), signOffset, seenOffset, true); } Validation - validate(Ledger::Seq seq, Ledger::ID id) const + validate(Ledger ledger) const { return validate( - seq, id, NetClock::duration{0}, NetClock::duration{0}, true); + ledger.id(), + ledger.seq(), + NetClock::duration{0}, + NetClock::duration{0}, + true); } Validation - partial(Ledger::Seq seq, Ledger::ID id) const + partial(Ledger ledger) const { return validate( - seq, - id, + ledger.id(), + ledger.seq(), NetClock::duration{0}, NetClock::duration{0}, false); @@ -176,6 +179,7 @@ class Validations_test : public beast::unit_test::suite { StaleData& staleData_; clock_type& c_; + LedgerOracle& oracle_; beast::Journal j_; public: @@ -196,8 +200,8 @@ class Validations_test : public beast::unit_test::suite using Validation = csf::Validation; using Ledger = csf::Ledger; - Adaptor(StaleData& sd, clock_type& c, beast::Journal j) - : staleData_{sd}, c_{c}, j_{j} + Adaptor(StaleData& sd, clock_type& c, LedgerOracle & o, beast::Journal j) + : staleData_{sd}, c_{c}, oracle_{o}, j_{j} { } @@ -242,7 +246,8 @@ class Validations_test : public beast::unit_test::suite PeerID nextNodeId_{0}; public: - TestHarness() : tv_(p_, clock_, staleData_, clock_, j_) + TestHarness(LedgerOracle& o) + : tv_(p_, clock_, staleData_, clock_, o, j_) { } @@ -294,203 +299,252 @@ class Validations_test : public beast::unit_test::suite void testAddValidation() { - // Test adding current,stale,repeat validations using namespace std::chrono_literals; - TestHarness harness; - Node a = harness.makeNode(); + testcase("Add validation"); + LedgerHistoryHelper h; + Ledger ledgerA = h["a"]; + Ledger ledgerAB = h["ab"]; + Ledger ledgerABC = h["abc"]; + Ledger ledgerABCD = h["abcd"]; + Ledger ledgerABCDE = h["abcde"]; + { - { - auto const v = a.validate(Ledger::Seq{1}, Ledger::ID{1}); + TestHarness harness(h.oracle); + Node n = harness.makeNode(); - // Add a current validation - BEAST_EXPECT(ValStatus::current == harness.add(v)); + auto const v = n.validate(ledgerA); - // Re-adding is repeat - BEAST_EXPECT(ValStatus::badFullSeq == harness.add(v)); - } + // Add a current validation + BEAST_EXPECT(ValStatus::current == harness.add(v)); - { - harness.clock().advance(1s); - // Replace with a new validation and ensure the old one is stale - BEAST_EXPECT(harness.stale().empty()); + // Re-adding violates the increasing seq requirement for full + // validations + BEAST_EXPECT(ValStatus::badFullSeq == harness.add(v)); - BEAST_EXPECT(ValStatus::current == - harness.add(a.validate(Ledger::Seq{2}, Ledger::ID{2}))); + harness.clock().advance(1s); + // Replace with a new validation and ensure the old one is stale + BEAST_EXPECT(harness.stale().empty()); - BEAST_EXPECT(harness.stale().size() == 1); + BEAST_EXPECT( + ValStatus::current == harness.add(n.validate(ledgerAB))); - BEAST_EXPECT(harness.stale()[0].ledgerID() == Ledger::ID{1}); - } + BEAST_EXPECT(harness.stale().size() == 1); - { - // Test the node changing signing key, then reissuing a ledger + BEAST_EXPECT(harness.stale()[0].ledgerID() == ledgerA.id()); - // Confirm old ledger on hand, but not new ledger - BEAST_EXPECT( - harness.vals().numTrustedForLedger(Ledger::ID{2}) == 1); - BEAST_EXPECT( - harness.vals().numTrustedForLedger(Ledger::ID{20}) == 0); + // Test the node changing signing key - // Issue a new signing key and re-issue the validation with a - // new ID but the same sequence number - a.advanceKey(); + // Confirm old ledger on hand, but not new ledger + BEAST_EXPECT( + harness.vals().numTrustedForLedger(ledgerAB.id()) == 1); + BEAST_EXPECT( + harness.vals().numTrustedForLedger(ledgerABC.id()) == 0); - // No validations following ID{2} - BEAST_EXPECT(harness.vals().getNodesAfter(Ledger::ID{2}) == 0); + // Rotate signing keys + n.advanceKey(); - BEAST_EXPECT( - ValStatus::badFullSeq == - harness.add(a.validate(Ledger::Seq{2}, Ledger::ID{20}))); + harness.clock().advance(1s); - BEAST_EXPECT( - harness.vals().numTrustedForLedger(Ledger::ID{2}) == 1); - BEAST_EXPECT( - harness.vals().numTrustedForLedger(Ledger::ID{20}) == 0); - } + // Cannot re-do the same full validation sequence + BEAST_EXPECT( + ValStatus::badFullSeq == harness.add(n.validate(ledgerAB))); + // Can send a new partial validation + BEAST_EXPECT( + ValStatus::current == harness.add(n.partial(ledgerAB))); - { - // Processing validations out of order should ignore the older - harness.clock().advance(2s); - auto const val3 = a.validate(Ledger::Seq{4}, Ledger::ID{4}); + // Now trusts the newest ledger too + harness.clock().advance(1s); + BEAST_EXPECT( + ValStatus::current == harness.add(n.validate(ledgerABC))); + BEAST_EXPECT( + harness.vals().numTrustedForLedger(ledgerAB.id()) == 1); + BEAST_EXPECT( + harness.vals().numTrustedForLedger(ledgerABC.id()) == 1); - harness.clock().advance(4s); - auto const val4 = a.validate(Ledger::Seq{3}, Ledger::ID{3}); + // Processing validations out of order should ignore the older + // validation + harness.clock().advance(2s); + auto const val3 = n.validate(ledgerABCDE); - BEAST_EXPECT(ValStatus::current == harness.add(val4)); + harness.clock().advance(4s); + auto const val4 = n.validate(ledgerABCD); - BEAST_EXPECT(ValStatus::stale == harness.add(val3)); + BEAST_EXPECT(ValStatus::current == harness.add(val4)); - } - { - // Process validations out of order with shifted times + BEAST_EXPECT(ValStatus::stale == harness.add(val3)); + } - // flush old validations - harness.clock().advance(1h); + { + // Process validations out of order with shifted times - // Establish a new current validation - BEAST_EXPECT(ValStatus::current == - harness.add(a.validate(Ledger::Seq{8}, Ledger::ID{8}))); + TestHarness harness(h.oracle); + Node n = harness.makeNode(); - // Process a validation that has "later" seq but early sign time - BEAST_EXPECT( - ValStatus::stale == - harness.add( - a.validate(Ledger::Seq{9}, Ledger::ID{9}, -1s, -1s))); - } - { - // Test stale on arrival validations - harness.clock().advance(1h); + // Establish a new current validation + BEAST_EXPECT( + ValStatus::current == harness.add(n.validate(ledgerA))); + + // Process a validation that has "later" seq but early sign time + BEAST_EXPECT( + ValStatus::stale == + harness.add(n.validate(ledgerAB, -1s, -1s))); - BEAST_EXPECT(ValStatus::stale == - harness.add(a.validate(Ledger::Seq{15}, Ledger::ID{15}, - -harness.parms().validationCURRENT_EARLY, 0s))); + // Process a validation that has an "leter" seq and later sign + // time + BEAST_EXPECT( + ValStatus::current == + harness.add(n.validate(ledgerABC, 1s, 1s))); + } - BEAST_EXPECT(ValStatus::stale == - harness.add(a.validate(Ledger::Seq{15}, Ledger::ID{15}, - harness.parms().validationCURRENT_WALL, 0s))); + { + // Test stale on arrival validations + TestHarness harness(h.oracle); + Node n = harness.makeNode(); - BEAST_EXPECT(ValStatus::stale == - harness.add(a.validate(Ledger::Seq{15}, Ledger::ID{15}, 0s, - harness.parms().validationCURRENT_LOCAL))); - } + BEAST_EXPECT( + ValStatus::stale == + harness.add(n.validate( + ledgerA, -harness.parms().validationCURRENT_EARLY, 0s))); + + BEAST_EXPECT( + ValStatus::stale == + harness.add(n.validate( + ledgerA, harness.parms().validationCURRENT_WALL, 0s))); + + BEAST_EXPECT( + ValStatus::stale == + harness.add(n.validate( + ledgerA, 0s, harness.parms().validationCURRENT_LOCAL))); + } + + { + // Test partials for older sequence numbers + TestHarness harness(h.oracle); + Node n = harness.makeNode(); + BEAST_EXPECT( + ValStatus::current == harness.add(n.validate(ledgerABC))); + harness.clock().advance(1s); + BEAST_EXPECT(ledgerAB.seq() < ledgerABC.seq()); + BEAST_EXPECT( + ValStatus::badFullSeq == harness.add(n.validate(ledgerAB))); + BEAST_EXPECT( + ValStatus::current == harness.add(n.partial(ledgerAB))); } } void testOnStale() { - // Verify validation becomes stale based solely on time passing - TestHarness harness; - Node a = harness.makeNode(); + testcase("Stale validation"); + // Verify validation becomes stale based solely on time passing, but + // use different functions to trigger the check for staleness - BEAST_EXPECT(ValStatus::current == - harness.add(a.validate(Ledger::Seq{1}, Ledger::ID{1}))); - harness.vals().currentTrusted(); - BEAST_EXPECT(harness.stale().empty()); - harness.clock().advance(harness.parms().validationCURRENT_LOCAL); + LedgerHistoryHelper h; + Ledger ledgerA = h["a"]; + Ledger ledgerAB = h["ab"]; - // trigger iteration over current - harness.vals().currentTrusted(); + using Trigger = std::function; + std::vector triggers = { + [&](TestValidations& vals) { vals.currentTrusted(); }, + [&](TestValidations& vals) { + vals.getNodesAfter(ledgerA.id()); + }}; + for (Trigger trigger : triggers) + { + TestHarness harness(h.oracle); + Node n = harness.makeNode(); - BEAST_EXPECT(harness.stale().size() == 1); - BEAST_EXPECT(harness.stale()[0].ledgerID() == Ledger::ID{1}); + BEAST_EXPECT( + ValStatus::current == harness.add(n.validate(ledgerAB))); + harness.vals().currentTrusted(); + BEAST_EXPECT(harness.stale().empty()); + harness.clock().advance(harness.parms().validationCURRENT_LOCAL); + + // trigger check for stale + trigger(harness.vals()); + + BEAST_EXPECT(harness.stale().size() == 1); + BEAST_EXPECT(harness.stale()[0].ledgerID() == ledgerAB.id()); + } } void testGetNodesAfter() { - // Test getting number of nodes working on a validation following - // a prescribed one + // Test getting number of nodes working on a validation descending + // a prescribed one. This count should only be for trusted nodes, but + // includes partial and full validations + using namespace std::chrono_literals; + testcase("Get nodes after"); + + LedgerHistoryHelper h; + Ledger ledgerA = h["a"]; + Ledger ledgerAB = h["ab"]; + Ledger ledgerABC = h["abc"]; + Ledger ledgerAD = h["ad"]; - TestHarness harness; + TestHarness harness(h.oracle); Node a = harness.makeNode(), b = harness.makeNode(), c = harness.makeNode(), d = harness.makeNode(); - c.untrust(); // first round a,b,c agree, d has differing id - for (auto const& node : {a, b, c}) - BEAST_EXPECT(ValStatus::current == - harness.add(node.validate(Ledger::Seq{1}, Ledger::ID{1}))); - BEAST_EXPECT(ValStatus::current == - harness.add(d.validate(Ledger::Seq{1}, Ledger::ID{10}))); + BEAST_EXPECT(ValStatus::current == harness.add(a.validate(ledgerA))); + BEAST_EXPECT(ValStatus::current == harness.add(b.validate(ledgerA))); + BEAST_EXPECT(ValStatus::current == harness.add(c.validate(ledgerA))); + BEAST_EXPECT(ValStatus::current == harness.add(d.partial(ledgerA))); - // Nothing past ledger 1 yet - BEAST_EXPECT(harness.vals().getNodesAfter(Ledger::ID{1}) == 0); + for (Ledger const& ledger : {ledgerA, ledgerAB, ledgerABC, ledgerAD}) + BEAST_EXPECT(harness.vals().getNodesAfter(ledger.id()) == 0); harness.clock().advance(5s); - // a and b have the same prior id, but b has a different current id - // c is untrusted but on the same prior id - // d has a different prior id - BEAST_EXPECT(ValStatus::current == - harness.add(a.validate(Ledger::Seq{2}, Ledger::ID{2}))); - BEAST_EXPECT(ValStatus::current == - harness.add(b.validate(Ledger::Seq{2}, Ledger::ID{20}))); - BEAST_EXPECT(ValStatus::current == - harness.add(c.validate(Ledger::Seq{2}, Ledger::ID{2}))); - BEAST_EXPECT(ValStatus::current == - harness.add(d.validate(Ledger::Seq{2}, Ledger::ID{2}))); - - BEAST_EXPECT(harness.vals().getNodesAfter(Ledger::ID{1}) == 2); + BEAST_EXPECT(ValStatus::current == harness.add(a.validate(ledgerAB))); + BEAST_EXPECT(ValStatus::current == harness.add(b.validate(ledgerABC))); + BEAST_EXPECT(ValStatus::current == harness.add(c.validate(ledgerAB))); + BEAST_EXPECT(ValStatus::current == harness.add(d.partial(ledgerABC))); + + BEAST_EXPECT(harness.vals().getNodesAfter(ledgerA.id()) == 2); } void testCurrentTrusted() { - // Test getting current trusted validations using namespace std::chrono_literals; + testcase("Current trusted validations"); + + LedgerHistoryHelper h; + Ledger ledgerA = h["a"]; + Ledger ledgerB = h["b"]; + Ledger ledgerAC = h["ac"]; - TestHarness harness; + TestHarness harness(h.oracle); Node a = harness.makeNode(), b = harness.makeNode(); b.untrust(); - BEAST_EXPECT(ValStatus::current == - harness.add(a.validate(Ledger::Seq{1}, Ledger::ID{1}))); - BEAST_EXPECT(ValStatus::current == - harness.add(b.validate(Ledger::Seq{1}, Ledger::ID{3}))); + BEAST_EXPECT(ValStatus::current == harness.add(a.validate(ledgerA))); + BEAST_EXPECT(ValStatus::current == harness.add(b.validate(ledgerB))); // Only a is trusted BEAST_EXPECT(harness.vals().currentTrusted().size() == 1); BEAST_EXPECT( - harness.vals().currentTrusted()[0].ledgerID() == Ledger::ID{1}); - BEAST_EXPECT( - harness.vals().currentTrusted()[0].seq() == Ledger::Seq{1}); + harness.vals().currentTrusted()[0].ledgerID() == ledgerA.id()); + BEAST_EXPECT(harness.vals().currentTrusted()[0].seq() == ledgerA.seq()); harness.clock().advance(3s); for (auto const& node : {a, b}) - BEAST_EXPECT(ValStatus::current == - harness.add(node.validate(Ledger::Seq{2}, Ledger::ID{2}))); + BEAST_EXPECT( + ValStatus::current == harness.add(node.validate(ledgerAC))); // New validation for a BEAST_EXPECT(harness.vals().currentTrusted().size() == 1); BEAST_EXPECT( - harness.vals().currentTrusted()[0].ledgerID() == Ledger::ID{2}); + harness.vals().currentTrusted()[0].ledgerID() == ledgerAC.id()); BEAST_EXPECT( - harness.vals().currentTrusted()[0].seq() == Ledger::Seq{2}); + harness.vals().currentTrusted()[0].seq() == ledgerAC.seq()); // Pass enough time for it to go stale harness.clock().advance(harness.parms().validationCURRENT_LOCAL); @@ -500,36 +554,40 @@ class Validations_test : public beast::unit_test::suite void testGetCurrentPublicKeys() { - // Test getting current keys validations using namespace std::chrono_literals; + testcase("Current public keys"); + + LedgerHistoryHelper h; + Ledger ledgerA = h["a"]; + Ledger ledgerAC = h["ac"]; - TestHarness harness; + TestHarness harness(h.oracle); Node a = harness.makeNode(), b = harness.makeNode(); b.untrust(); for (auto const& node : {a, b}) - BEAST_EXPECT(ValStatus::current == - harness.add(node.validate(Ledger::Seq{1}, Ledger::ID{1}))); + BEAST_EXPECT( + ValStatus::current == harness.add(node.validate(ledgerA))); { - hash_set const expectedKeys = { - a.masterKey(), b.masterKey()}; + hash_set const expectedKeys = {a.masterKey(), + b.masterKey()}; BEAST_EXPECT(harness.vals().getCurrentPublicKeys() == expectedKeys); } harness.clock().advance(3s); - // Change keys + // Change keys and issue partials a.advanceKey(); b.advanceKey(); for (auto const& node : {a, b}) - BEAST_EXPECT(ValStatus::current == - harness.add(node.validate(Ledger::Seq{2}, Ledger::ID{2}))); + BEAST_EXPECT( + ValStatus::current == harness.add(node.partial(ledgerAC))); { - hash_set const expectedKeys = { - a.masterKey(), b.masterKey()}; + hash_set const expectedKeys = {a.masterKey(), + b.masterKey()}; BEAST_EXPECT(harness.vals().getCurrentPublicKeys() == expectedKeys); } @@ -544,8 +602,14 @@ class Validations_test : public beast::unit_test::suite // Test the trusted distribution calculation, including ledger slips // and sequence cutoffs using namespace std::chrono_literals; + testcase("Current trusted distribution"); + + LedgerHistoryHelper h; + Ledger ledgerA = h["a"]; + Ledger ledgerAB = h["ab"]; + Ledger ledgerABC = h["abc"]; - TestHarness harness; + TestHarness harness(h.oracle); Node baby = harness.makeNode(), papa = harness.makeNode(), mama = harness.makeNode(), goldilocks = harness.makeNode(); @@ -558,17 +622,17 @@ class Validations_test : public beast::unit_test::suite // goldilocks on seq 2, but is not trusted for (auto const& node : {baby, papa, mama, goldilocks}) - BEAST_EXPECT(ValStatus::current == - harness.add(node.validate(Ledger::Seq{1}, Ledger::ID{1}))); + BEAST_EXPECT( + ValStatus::current == harness.add(node.validate(ledgerA))); harness.clock().advance(1s); for (auto const& node : {baby, mama, goldilocks}) - BEAST_EXPECT(ValStatus::current == - harness.add(node.validate(Ledger::Seq{2}, Ledger::ID{2}))); + BEAST_EXPECT( + ValStatus::current == harness.add(node.validate(ledgerAB))); harness.clock().advance(1s); - BEAST_EXPECT(ValStatus::current == - harness.add(mama.validate(Ledger::Seq{3}, Ledger::ID{3}))); + BEAST_EXPECT( + ValStatus::current == harness.add(mama.validate(ledgerABC))); { // Allow slippage that treats all trusted as the current ledger @@ -630,6 +694,7 @@ class Validations_test : public beast::unit_test::suite { // Test the Validations functions that calculate a value by ledger ID using namespace std::chrono_literals; + testcase("By ledger functions"); // Several Validations functions return a set of values associated // with trusted ledgers sharing the same ledger ID. The tests below @@ -637,14 +702,19 @@ class Validations_test : public beast::unit_test::suite // verifying that the Validations member functions all calculate the // proper transformation of the available ledgers. - TestHarness harness; + LedgerHistoryHelper h; + TestHarness harness(h.oracle); + Node a = harness.makeNode(), b = harness.makeNode(), - c = harness.makeNode(), d = harness.makeNode(); + c = harness.makeNode(), d = harness.makeNode(), + e = harness.makeNode(); + c.untrust(); // Mix of load fees a.setLoadFee(12); b.setLoadFee(1); c.setLoadFee(12); + e.setLoadFee(12); hash_map> trustedValidations; @@ -660,9 +730,11 @@ class Validations_test : public beast::unit_test::suite auto const& id = it.first; auto const& expectedValidations = it.second; - BEAST_EXPECT(harness.vals().numTrustedForLedger(id) == + BEAST_EXPECT( + harness.vals().numTrustedForLedger(id) == expectedValidations.size()); - BEAST_EXPECT(sorted(harness.vals().getTrustedForLedger(id)) == + BEAST_EXPECT( + sorted(harness.vals().getTrustedForLedger(id)) == sorted(expectedValidations)); std::vector expectedTimes; @@ -674,64 +746,81 @@ class Validations_test : public beast::unit_test::suite expectedFees.push_back(val.loadFee().value_or(baseFee)); } - BEAST_EXPECT(sorted(harness.vals().fees(id, baseFee)) == + BEAST_EXPECT( + sorted(harness.vals().fees(id, baseFee)) == sorted(expectedFees)); - BEAST_EXPECT(sorted(harness.vals().getTrustedValidationTimes( - id)) == sorted(expectedTimes)); + BEAST_EXPECT( + sorted(harness.vals().getTrustedValidationTimes(id)) == + sorted(expectedTimes)); } }; //---------------------------------------------------------------------- + Ledger ledgerA = h["a"]; + Ledger ledgerB = h["b"]; + Ledger ledgerAC = h["ac"]; + // Add a dummy ID to cover unknown ledger identifiers trustedValidations[Ledger::ID{100}] = {}; - // first round a,b,c agree, d differs + // first round a,b,c agree for (auto const& node : {a, b, c}) { - auto const val = node.validate(Ledger::Seq{1}, Ledger::ID{1}); + auto const val = node.validate(ledgerA); BEAST_EXPECT(ValStatus::current == harness.add(val)); if (val.trusted()) trustedValidations[val.ledgerID()].emplace_back(val); } + // d diagrees { - auto const val = d.validate(Ledger::Seq{1}, Ledger::ID{11}); + auto const val = d.validate(ledgerB); BEAST_EXPECT(ValStatus::current == harness.add(val)); trustedValidations[val.ledgerID()].emplace_back(val); } + // e only issues partials + { + BEAST_EXPECT(ValStatus::current == harness.add(e.partial(ledgerA))); + } harness.clock().advance(5s); - // second round, a,b,c move to ledger 2, d now thinks ledger 1 + // second round, a,b,c move to ledger 2 for (auto const& node : {a, b, c}) { - auto const val = node.validate(Ledger::Seq{2}, Ledger::ID{2}); + auto const val = node.validate(ledgerAC); BEAST_EXPECT(ValStatus::current == harness.add(val)); if (val.trusted()) trustedValidations[val.ledgerID()].emplace_back(val); } + // d now thinks ledger 1, but had to issue a partial to switch { - auto const val = d.validate(Ledger::Seq{2}, Ledger::ID{1}); - BEAST_EXPECT(ValStatus::current == harness.add(val)); - trustedValidations[val.ledgerID()].emplace_back(val); + BEAST_EXPECT(ValStatus::current == harness.add(d.partial(ledgerA))); + } + // e only issues partials + { + BEAST_EXPECT( + ValStatus::current == harness.add(e.partial(ledgerAC))); } compare(); } - void + void testExpire() { // Verify expiring clears out validations stored by ledger - - TestHarness harness; + testcase("Expire validations"); + LedgerHistoryHelper h; + TestHarness harness(h.oracle); Node a = harness.makeNode(); - BEAST_EXPECT(ValStatus::current == - harness.add(a.validate(Ledger::Seq{1}, Ledger::ID{1}))); - BEAST_EXPECT(harness.vals().numTrustedForLedger(Ledger::ID{1})); + Ledger ledgerA = h["a"]; + + BEAST_EXPECT(ValStatus::current == harness.add(a.validate(ledgerA))); + BEAST_EXPECT(harness.vals().numTrustedForLedger(ledgerA.id())); harness.clock().advance(harness.parms().validationSET_EXPIRES); harness.vals().expire(); - BEAST_EXPECT(!harness.vals().numTrustedForLedger(Ledger::ID{1})); + BEAST_EXPECT(!harness.vals().numTrustedForLedger(ledgerA.id())); } void @@ -739,27 +828,30 @@ class Validations_test : public beast::unit_test::suite { // Test final flush of validations using namespace std::chrono_literals; + testcase("Flush validations"); - TestHarness harness; - + LedgerHistoryHelper h; + TestHarness harness(h.oracle); Node a = harness.makeNode(), b = harness.makeNode(), c = harness.makeNode(); c.untrust(); + Ledger ledgerA = h["a"]; + Ledger ledgerAB = h["ab"]; + hash_map expected; for (auto const& node : {a, b, c}) { - auto const val = node.validate(Ledger::Seq{1}, Ledger::ID{1}); + auto const val = node.validate(ledgerA); BEAST_EXPECT(ValStatus::current == harness.add(val)); expected.emplace(node.masterKey(), val); } Validation staleA = expected.find(a.masterKey())->second; - // Send in a new validation for a, saving the new one into the expected // map after setting the proper prior ledger ID it replaced harness.clock().advance(1s); - auto newVal = a.validate(Ledger::Seq{2}, Ledger::ID{2}); + auto newVal = a.validate(ledgerAB); BEAST_EXPECT(ValStatus::current == harness.add(newVal)); expected.find(a.masterKey())->second = newVal; From fa1a96eda5b5af00dbd8b283e0b3e150e29bf5bd Mon Sep 17 00:00:00 2001 From: Brad Chase Date: Fri, 15 Dec 2017 14:28:25 -0500 Subject: [PATCH 12/38] [FOLD] Add RCLValidatedLedger: RCLValidatedLedger wraps Ledger instances for use by the generic LedgerTrie class. Since ledgers only have the prior 256 ancestor hashes in their header, the adaptor class treats any ledgers seperated by more than that as on distinct chains. --- Builds/VisualStudio2015/RippleD.vcxproj | 4 + .../VisualStudio2015/RippleD.vcxproj.filters | 3 + src/ripple/app/consensus/RCLValidations.cpp | 71 ++++++ src/ripple/app/consensus/RCLValidations.h | 41 ++++ src/test/app/RCLValidations_test.cpp | 207 ++++++++++++++++++ src/test/unity/app_test_unity2.cpp | 1 + 6 files changed, 327 insertions(+) create mode 100644 src/test/app/RCLValidations_test.cpp diff --git a/Builds/VisualStudio2015/RippleD.vcxproj b/Builds/VisualStudio2015/RippleD.vcxproj index f10eb5b9fd4..b3c119e5015 100644 --- a/Builds/VisualStudio2015/RippleD.vcxproj +++ b/Builds/VisualStudio2015/RippleD.vcxproj @@ -4503,6 +4503,10 @@ True True + + True + True + True True diff --git a/Builds/VisualStudio2015/RippleD.vcxproj.filters b/Builds/VisualStudio2015/RippleD.vcxproj.filters index 4d9fef3d71b..7681fc79bca 100644 --- a/Builds/VisualStudio2015/RippleD.vcxproj.filters +++ b/Builds/VisualStudio2015/RippleD.vcxproj.filters @@ -5271,6 +5271,9 @@ test\app + + test\app + test\app diff --git a/src/ripple/app/consensus/RCLValidations.cpp b/src/ripple/app/consensus/RCLValidations.cpp index 98e4132de8b..c449cc67c85 100644 --- a/src/ripple/app/consensus/RCLValidations.cpp +++ b/src/ripple/app/consensus/RCLValidations.cpp @@ -36,6 +36,77 @@ namespace ripple { +RCLValidatedLedger::RCLValidatedLedger( + std::shared_ptr ledger, + beast::Journal j) + : ledger_{std::move(ledger)}, 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 " << ledger_->seq() << ":" + << ledger_->info().hash + << " missing recent ancestor hashes"; +} + +auto +RCLValidatedLedger::minSeq() const -> Seq +{ + return seq() - std::min(seq(), static_cast(ancestors_.size())); +} + +auto +RCLValidatedLedger::seq() const -> Seq +{ + return ledger_ ? ledger_->info().seq : Seq{0}; +} +auto +RCLValidatedLedger::id() const -> ID +{ + return ledger_ ? ledger_->info().hash : ID{0}; +} + +auto RCLValidatedLedger::operator[](Seq const& s) const -> ID +{ + if (ledger_ && s >= minSeq() && s <= seq()) + { + if (s == seq()) + return ledger_->info().hash; + Seq const diff = seq() - s; + if (ancestors_.size() >= diff) + return ancestors_[ancestors_.size() - diff]; + } + + JLOG(j_.warn()) << "Unable to determine hash of ancestor seq=" << s + << " from ledger hash=" << ledger_->info().hash + << " seq=" << ledger_->info().seq; + // Default ID that is less than all others + return ID{}; +} + +// 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 the ledgers + Seq const lower = std::max(a.minSeq(), b.minSeq()); + Seq const upper = std::min(a.seq(), b.seq()); + + Seq curr = upper; + while (a[curr] != b[curr] && curr >= lower && curr != Seq{0}) + --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) { diff --git a/src/ripple/app/consensus/RCLValidations.h b/src/ripple/app/consensus/RCLValidations.h index f17c8327781..22b073a38c7 100644 --- a/src/ripple/app/consensus/RCLValidations.h +++ b/src/ripple/app/consensus/RCLValidations.h @@ -20,6 +20,7 @@ #ifndef RIPPLE_APP_CONSENSUSS_VALIDATIONS_H_INCLUDED #define RIPPLE_APP_CONSENSUSS_VALIDATIONS_H_INCLUDED +#include #include #include #include @@ -123,12 +124,52 @@ class RCLValidation }; +/** Wraps a ledger instance for use in generic Validations LedgerTrie. + + 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 RCLValidatedLedger { public: using ID = LedgerHash; using Seq = LedgerIndex; + RCLValidatedLedger() = default; + + RCLValidatedLedger(std::shared_ptr 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{} 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: + std::shared_ptr ledger_; + std::vector ancestors_; + beast::Journal j_; }; /** Generic validations adaptor classs for RCL diff --git a/src/test/app/RCLValidations_test.cpp b/src/test/app/RCLValidations_test.cpp new file mode 100644 index 00000000000..f08e2e5f6e7 --- /dev/null +++ b/src/test/app/RCLValidations_test.cpp @@ -0,0 +1,207 @@ +//------------------------------------------------------------------------------ +/* + This file is part of rippled: https://github.com/ripple/rippled + Copyright 2017 Ripple Labs Inc. + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== + +#include +#include +#include +#include +#include +#include +#include + +namespace ripple { +namespace test { + +class RCLValidations_test : public beast::unit_test::suite +{ + +public: + void + run() override + { + beast::Journal j; + + using Seq = RCLValidatedLedger::Seq; + using ID = RCLValidatedLedger::ID; + + // This tests RCLValidatedLedger properly implements the type + // requirements of a LedgerTrie ledger, with its added behavior that + // only the 256 prior ledger hashes are available to determine ancestry. + Seq const maxAncestors = 256; + + //---------------------------------------------------------------------- + // Generate two ledger histories that agree on the first maxAncestors + // ledgers, then diverge. + + std::vector> history; + + jtx::Env env(*this); + Config config; + auto prev = std::make_shared( + create_genesis, config, + std::vector{}, env.app().family()); + history.push_back(prev); + for (auto i = 0; i < (2*maxAncestors + 1); ++i) + { + auto next = std::make_shared( + *prev, + env.app().timeKeeper().closeTime()); + next->updateSkipList(); + history.push_back(next); + prev = next; + } + + // altHistory agrees with first half of regular history + Seq const diverge = history.size()/2; + std::vector> altHistory( + history.begin(), history.begin() + diverge); + // advance clock too get new ledgers + env.timeKeeper().set(env.timeKeeper().now() + 1200s); + prev = altHistory.back(); + bool forceHash = true; + while(altHistory.size() < history.size()) + { + auto next = std::make_shared( + *prev, + env.app().timeKeeper().closeTime()); + // Force a different hash on the first iteration + next->updateSkipList(); + if(forceHash) + { + next->setImmutable(config); + forceHash = false; + } + + altHistory.push_back(next); + prev = next; + } + + //---------------------------------------------------------------------- + + + // Empty ledger + { + RCLValidatedLedger a; + BEAST_EXPECT(a.seq() == Seq{0}); + BEAST_EXPECT(a[Seq{0}] == ID{0}); + BEAST_EXPECT(a.minSeq() == Seq{0}); + } + + // Full history ledgers + { + std::shared_ptr ledger = history.back(); + RCLValidatedLedger a{ledger, j}; + BEAST_EXPECT(a.seq() == ledger->info().seq); + BEAST_EXPECT( + a.minSeq() == a.seq() - maxAncestors); + // Ensure the ancestral 256 ledgers have proper ID + for(Seq s = a.seq(); s > 0; s--) + { + if(s >= a.minSeq()) + BEAST_EXPECT(a[s] == history[s-1]->info().hash); + else + BEAST_EXPECT(a[s] == ID{0}); + } + } + + // Mismatch tests + + // Empty with non-empty + { + RCLValidatedLedger a; + + 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 + for(auto ledger : {history[diverge]}) + { + RCLValidatedLedger a{ledger, 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); + } + } + } + } + + + } +}; // namespace test + +BEAST_DEFINE_TESTSUITE(RCLValidations, app, ripple); + +} // namespace test +} // namespace ripple diff --git a/src/test/unity/app_test_unity2.cpp b/src/test/unity/app_test_unity2.cpp index 2253e00fd5e..e9ea03e1d42 100644 --- a/src/test/unity/app_test_unity2.cpp +++ b/src/test/unity/app_test_unity2.cpp @@ -22,6 +22,7 @@ #include #include #include +#include #include #include #include From bd2b860fd0ac4a33eb24acee52b0f139a5d922c2 Mon Sep 17 00:00:00 2001 From: Brad Chase Date: Fri, 15 Dec 2017 14:40:31 -0500 Subject: [PATCH 13/38] [FOLD] Don't save partial validations to database --- src/ripple/app/consensus/RCLValidations.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/ripple/app/consensus/RCLValidations.cpp b/src/ripple/app/consensus/RCLValidations.cpp index c449cc67c85..337c0bf57d9 100644 --- a/src/ripple/app/consensus/RCLValidations.cpp +++ b/src/ripple/app/consensus/RCLValidations.cpp @@ -203,8 +203,11 @@ RCLValidationsAdaptor::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.isFull()) + continue; s.erase(); STValidation::pointer const& val = rclValidation.unwrap(); val->add(s); From f0e6efdee3bb01a801216d986daacc5182d443e7 Mon Sep 17 00:00:00 2001 From: Brad Chase Date: Fri, 15 Dec 2017 15:08:28 -0500 Subject: [PATCH 14/38] [FOLD] Use LedgerTrie in generic validations: Use the LedgerTrie in generic validations to track the ancestry of validated ledgers. This is used to track the preferred working ledger for consensus and the number of nodes that have moved on from the current ledger (getNodesAfter). --- src/ripple/app/consensus/RCLValidations.cpp | 31 +- src/ripple/consensus/Validations.h | 393 ++++++++++++++--- src/test/consensus/Validations_test.cpp | 442 ++++++++++++++------ src/test/csf/Peer.h | 8 + 4 files changed, 668 insertions(+), 206 deletions(-) diff --git a/src/ripple/app/consensus/RCLValidations.cpp b/src/ripple/app/consensus/RCLValidations.cpp index 337c0bf57d9..f1b1d7421d2 100644 --- a/src/ripple/app/consensus/RCLValidations.cpp +++ b/src/ripple/app/consensus/RCLValidations.cpp @@ -19,6 +19,8 @@ #include #include +#include +#include #include #include #include @@ -119,6 +121,31 @@ RCLValidationsAdaptor::now() const return app_.timeKeeper().closeTime(); } +boost::optional +RCLValidationsAdaptor::acquire(LedgerHash const & hash) +{ + auto ledger = app_.getLedgerMaster().getLedgerByHash(hash); + if (!ledger) + { + JLOG(j_.debug()) + << "Need validated ledger for preferred ledger analysis " << hash; + + Application * pApp = &app_; + + app_.getJobQueue().addJob( + jtADVANCE, "getConsensusLedger", [pApp, hash](Job&) { + pApp ->getInboundLedgers().acquire( + hash, 0, InboundLedger::fcVALIDATION); + }); + return boost::none; + } + + assert(!ledger->open() && ledger->isImmutable()); + assert(ledger->info().hash == hash); + + return RCLValidatedLedger(std::move(ledger), j_); +} + void RCLValidationsAdaptor::onStale(RCLValidation&& v) { @@ -206,10 +233,10 @@ RCLValidationsAdaptor::doStaleWrite(ScopedLockType&) for (RCLValidation const& wValidation : currentStale) { // Only save full validations until we update the schema - if(!wValidation.isFull()) + 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()); diff --git a/src/ripple/consensus/Validations.h b/src/ripple/consensus/Validations.h index 76cabf7e548..0cbd2920aad 100644 --- a/src/ripple/consensus/Validations.h +++ b/src/ripple/consensus/Validations.h @@ -26,6 +26,7 @@ #include #include #include +#include #include #include #include @@ -73,7 +74,6 @@ struct ValidationParms std::chrono::seconds validationSET_EXPIRES = std::chrono::minutes{10}; }; - /** Whether a validation is still current Determines whether a validation can still be considered the current @@ -187,12 +187,8 @@ to_string(ValStatus m) @code - struct Ledger - { - using ID = ...; - using Seq = ...; - //... - } + // Conforms to the Ledger type requirements of LedgerTrie + struct Ledger; struct Validation { @@ -241,7 +237,10 @@ to_string(ValStatus m) void flush(hash_map && remaining); // Return the current network time (used to determine staleness) - NetClock::time_point now() const + NetClock::time_point now() const; + + // Attempt to acquire a specific ledger. + boost::optional acquire(Ledger::ID const & ledgerID); // ... implementation specific }; @@ -264,28 +263,16 @@ class Validations using ScopedLock = std::lock_guard; - // Manages concurrent access to current_ and byLedger_ - Mutex mutex_; - - // For the most recent validation, we also want to store the ID - // of the ledger it replaces - struct ValidationAndPrevID - { - ValidationAndPrevID(Validation const& v) : val{v}, prevLedgerID{0} - { - } - - Validation val; - ID prevLedgerID; - }; + // Manages concurrent access to members + mutable Mutex mutex_; // Validations from currently listed and trusted nodes (partial and full) - hash_map current_; + hash_map current_; // Sequence of the largest full validation received from each node hash_map largestFullValidation_; - // Validations from listed nodes, indexed by ledger id (partial and full) + //! Validations from listed nodes, indexed by ledger id (partial and full) beast::aged_unordered_map< ID, hash_map, @@ -293,6 +280,16 @@ class Validations beast::uhash<>> byLedger_; + // Represents the ancestry of validated ledgers + LedgerTrie trie_; + + // Last (validated) ledger successfully acquired. If in this map, it is + // accounted for in the trie. + hash_map lastLedger_; + + // Set of ledgers being acquired from the network + hash_map> acquiring_; + // Parameters to determine validation staleness ValidationParms const parms_; @@ -301,6 +298,123 @@ class Validations Adaptor adaptor_; private: + // Remove support of a validated ledger + void + removeTrie(ScopedLock const&, NodeKey const& key, Validation const& val) + { + { + auto it = acquiring_.find(val.ledgerID()); + if (it != acquiring_.end()) + { + it->second.erase(key); + if (it->second.empty()) + acquiring_.erase(it); + } + } + { + auto it = lastLedger_.find(key); + if (it != lastLedger_.end() && it->second.id() == val.ledgerID()) + { + trie_.remove(it->second); + lastLedger_.erase(key); + } + } + } + + // Check if any pending acquire ledger requests are complete + void + checkAcquired(ScopedLock const& lock) + { + for (auto it = acquiring_.begin(); it != acquiring_.end();) + { + if (boost::optional ledger = adaptor_.acquire(it->first)) + { + for (NodeKey const& key : it->second) + updateTrie(lock, key, *ledger); + + it = acquiring_.erase(it); + } + else + ++it; + } + } + + // Update the trie to reflect a new validated ledger + void + updateTrie(ScopedLock const&, NodeKey const& key, Ledger ledger) + { + auto ins = lastLedger_.emplace(key, ledger); + if (!ins.second) + { + trie_.remove(ins.first->second); + ins.first->second = ledger; + } + trie_.insert(ledger); + } + + /** Process a new validation + + Process a new trusted validation from a validator. This will be + reflected only after the validated ledger is succesfully acquired by + the local node. In the interim, the prior validated ledger from this + node remains. + + @param lock Existing lock of mutex_ + @param key The master public key identifying the validating node + @param val The trusted validation issued by the node + @param priorID If not none, the ID of the last current validated ledger. + */ + void + updateTrie( + ScopedLock const& lock, + NodeKey const& key, + Validation const& val, + boost::optional priorID) + { + assert(val.trusted()); + + // Clear any prior acquiring ledger for this node + if (priorID) + { + auto it = acquiring_.find(*priorID); + if (it != acquiring_.end()) + { + it->second.erase(key); + if (it->second.empty()) + acquiring_.erase(it); + } + } + + checkAcquired(lock); + + if (boost::optional ledger = adaptor_.acquire(val.ledgerID())) + updateTrie(lock, key, *ledger); + else + acquiring_[val.ledgerID()].insert(key); + } + + /** Use the trie for a calculation + + Accessing the trie through this helper ensures acquiring validations + are checked and any stale validations are flushed from the trie. + + @param lock Existing locked of mutex_ + @param f Invokable with signature (LedgerTrie &) + + @warning The invokable `f` is expected to be a simple transformation of + its arguments and will be called with mutex_ under lock. + + */ + template + auto + withTrie(ScopedLock const& lock, F&& f) + { + // Call current to flush any stale validations + current(lock, [](auto){}, [](auto, auto){}); + checkAcquired(lock); + return f(trie_); + } + /** Iterate current validations. Iterate current validations, flushing any which are stale. @@ -328,9 +442,10 @@ class Validations { // Check for staleness if (!isCurrent( - parms_, t, it->second.val.signTime(), it->second.val.seenTime())) + parms_, t, it->second.signTime(), it->second.seenTime())) { - adaptor_.onStale(std::move(it->second.val)); + removeTrie(lock, it->first, it->second); + adaptor_.onStale(std::move(it->second)); it = current_.erase(it); } else @@ -444,17 +559,22 @@ class Validations if (!ins.second) { // Replace existing only if this one is newer - Validation& oldVal = ins.first->second.val; + Validation& oldVal = ins.first->second; if (val.signTime() > oldVal.signTime()) { ID oldID = oldVal.ledgerID(); adaptor_.onStale(std::move(oldVal)); - ins.first->second.val = val; - ins.first->second.prevLedgerID = oldID; + ins.first->second = val; + if (val.trusted()) + updateTrie(lock, key, val, oldID); } else return ValStatus::stale; } + else if (val.trusted()) + { + updateTrie(lock, key, val, boost::none); + } } return ValStatus::current; } @@ -471,6 +591,13 @@ class Validations beast::expire(byLedger_, parms_.validationSET_EXPIRES); } + Json::Value + getJsonTrie() const + { + ScopedLock lock{mutex_}; + return trie_.getJson(); + } + /** Distribution of current trusted validations Calculates the distribution of current validations but allows @@ -507,9 +634,17 @@ class Validations &valCurrentLedger, &valPriorLedger, &priorLedger, - &ret](NodeKey const&, ValidationAndPrevID const& vp) { - Validation const& v = vp.val; - ID const& prevLedgerID = vp.prevLedgerID; + &ret](NodeKey const& k, Validation const& v) { + + ID prevLedgerID; + auto it = lastLedger_.find(k); + if(it != lastLedger_.end()) + { + Ledger const & ledger = it->second; + if(ledger.seq() > Seq{1}) + prevLedgerID = ledger[ledger.seq() - Seq{1}]; + } + if (!v.trusted() || !v.full()) return; @@ -542,34 +677,164 @@ class Validations return ret; } - /** Count the number of current trusted validators working on the next - ledger. + /** Return the sequence number and ID of the preferred working ledger - Counts the number of current trusted validations that replaced the - provided ledger. Does not check or update staleness of the validations. + A ledger is preferred if it has more support amongst trusted validators + and is *not* an ancestor of the current working ledger; otherwise it + remains the current working ledger. - @param ledgerID The identifier of the preceding ledger of interest - @return The number of current trusted validators with ledgerID as the - prior ledger. + @param currLedger The local nodes current working ledger + + @return The sequence and id of the preferred working ledger, + or Seq{0},ID{} if no trusted validations are available to + determine the preferred ledger. */ - std::size_t - getNodesAfter(ID const& ledgerID) + std::pair + getPreferred(Ledger const& currLedger) { - std::size_t count = 0; + Seq preferredSeq; + ID preferredID; ScopedLock lock{mutex_}; - current( - lock, - [&](std::size_t) {}, // nothing to reserve - [&](NodeKey const&, ValidationAndPrevID const& v) { - if (v.val.trusted() && v.prevLedgerID == ledgerID && - v.val.full()) - ++count; + std::tie(preferredSeq, preferredID) = withTrie( + lock, [](LedgerTrie& trie) { return trie.getPreferred(); }); + + // No trusted validations to determine branch + if (preferredSeq == Seq{0}) + return std::make_pair(preferredSeq, preferredID); + + Seq currSeq = currLedger.seq(); + ID currID = currLedger.id(); + + // If we are the parent of the preferred ledger, stick with our + // current ledger since we might be about to generate it + if (preferredSeq == currSeq + Seq{1}) + { + for (auto const& it : lastLedger_) + { + Ledger const& ledger = it.second; + if (ledger.seq() == preferredSeq && + ledger.id() == preferredID && ledger[currSeq] == currID) + return std::make_pair(currSeq, currID); + } + } + + // A ledger ahead of us is preferred regardless of whether it is + // a descendent of our working ledger or it is on a different chain + if (preferredSeq > currSeq) + return std::make_pair(preferredSeq, preferredID); + + // Only switch to earlier sequence numbers if it is a different + // chain to avoid jumping backward unnecessarily + if (currLedger[preferredSeq] != preferredID) + return std::make_pair(preferredSeq, preferredID); + + // Stick with current ledger + return std::make_pair(currSeq, currID); + } + + /** Get the ID of the preferred working ledger that exceeds a minimum valid + ledger sequence number + + @param currLedger Current working ledger + @param minValidSeq Minimum allowed sequence number + + @return ID Of the preferred ledger, or currLedger if the preferred ledger + is not valid + */ + ID + getPreferred(Ledger const& currLedger, Seq minValidSeq) + { + std::pair preferred = getPreferred(currLedger); + if(preferred.first >= minValidSeq && preferred.second != ID{}) + return preferred.second; + return currLedger.id(); + + } + + + /** Determine the preferred last closed ledger for the next consensus round. + + Called before starting the next round of ledger consensus to determine the + preferred working ledger. Uses the dominant peerCount ledger if no + trusted validations are available. + + @param lcl Last closed ledger by this node + @param minSeq Minimum allowed sequence number of the trusted preferred ledger + @param peerCounts Map from ledger ids to count of peers with that as the + last closed ledger + @return The preferred last closed ledger ID + + @note The minSeq does not apply to the peerCounts, since this function + does not know their sequence number + */ + ID + getPreferredLCL( + Ledger const & lcl, + Seq minSeq, + hash_map const& peerCounts) + { + std::pair preferred = getPreferred(lcl); + + // Trusted validations exist + if (preferred.second != ID{} && preferred.first > Seq{0}) + return (preferred.first >= minSeq) ? preferred.second : lcl.id(); + + // Otherwise, rely on peer ledgers + auto it = std::max_element( + peerCounts.begin(), peerCounts.end(), [](auto& a, auto& b) { + // Prefer larger counts, then larger ids on ties + // (max_element expects this to return true if a < b) + return std::tie(a.second, a.first) < + std::tie(b.second, b.first); + }); + + if (it != peerCounts.end()) + return it->first; + return lcl.id(); + } + + /** Count the number of current trusted validators working on a ledger + after the specified one. + + @param ledger The working ledger + @param ledgerID The preferred ledger + @return The number of current trusted validators working on a descendent + of the preferred ledger + + @note If ledger.id() != ledgerID, only counts immediate child ledgers of + ledgerID + */ + std::size_t + getNodesAfter(Ledger const& ledger, ID const& ledgerID) + { + ScopedLock lock{mutex_}; + + // Use trie if ledger is the right one + if (ledger.id() == ledgerID) + return withTrie(lock, [&ledger](LedgerTrie& trie) { + return trie.branchSupport(ledger) - trie.tipSupport(ledger); }); + + // Count parent ledgers as fallback + std::size_t count = 0; + for (auto const& it : lastLedger_) + { + Ledger const& curr = it.second; + if (curr.seq() > Seq{0} && curr[curr.seq() - Seq{1}] == ledgerID) + ++count; + } return count; } - /** Get the currently trusted validations + // Temporary pending refactor to use getNodesAfter above + std::size_t + getNodesAfter(ID const& ledgerID) + { + return getNodesAfter(Ledger{}, ledgerID); + } + + /** Get the currently trusted full validations @return Vector of validations from currently trusted validators */ @@ -577,23 +842,20 @@ class Validations currentTrusted() { std::vector ret; - ScopedLock lock{mutex_}; current( lock, [&](std::size_t numValidations) { ret.reserve(numValidations); }, - [&](NodeKey const&, ValidationAndPrevID const& v) { - if (v.val.trusted() && v.val.full()) - ret.push_back(v.val.unwrap()); + [&](NodeKey const&, Validation const& v) { + if (v.trusted() && v.full()) + ret.push_back(v.unwrap()); }); return ret; } /** Get the set of known public keys associated with current validations - @return The set of of knowns keys for current trusted and untrusted - validations. This includes nodes whose last validation is - partial. + @return The set of of knowns keys for current listed validators */ hash_set getCurrentPublicKeys() @@ -603,14 +865,12 @@ class Validations current( lock, [&](std::size_t numValidations) { ret.reserve(numValidations); }, - [&](NodeKey const& k, ValidationAndPrevID const& ) { - ret.insert(k); - }); + [&](NodeKey const& k, Validation const&) { ret.insert(k); }); return ret; } - /** Count the number of trusted validations for the given ledger + /** Count the number of trusted full validations for the given ledger @param ledgerID The identifier of ledger of interest @return The number of trusted validations @@ -623,7 +883,7 @@ class Validations byLedger( lock, ledgerID, - [&](std::size_t) {}, // nothing to reserve + [&](std::size_t) {}, // nothing to reserve [&](NodeKey const&, Validation const& v) { if (v.trusted() && v.full()) ++count; @@ -631,7 +891,7 @@ class Validations return count; } - /** Get set of trusted validations associated with a given ledger + /** Get trusted full validations for a specific ledger @param ledgerID The identifier of ledger of interest @return Trusted validations associated with ledger @@ -653,7 +913,7 @@ class Validations return res; } - /** Return the sign times of all validations associated with a given ledger + /** Return the sign times of all trusted full validations @param ledgerID The identifier of ledger of interest @return Vector of times @@ -674,7 +934,7 @@ class Validations return times; } - /** Returns fees reported by trusted validators in the given ledger + /** Returns fees reported by trusted full validators in the given ledger @param ledgerID The identifier of ledger of interest @param baseFee The fee to report if not present in the validation @@ -712,7 +972,7 @@ class Validations ScopedLock lock{mutex_}; for (auto it : current_) { - flushed.emplace(it.first, std::move(it.second.val)); + flushed.emplace(it.first, std::move(it.second)); } current_.clear(); } @@ -720,5 +980,6 @@ class Validations adaptor_.flush(std::move(flushed)); } }; + } // namespace ripple #endif diff --git a/src/test/consensus/Validations_test.cpp b/src/test/consensus/Validations_test.cpp index 10c59496d86..4830b0d9c04 100644 --- a/src/test/consensus/Validations_test.cpp +++ b/src/test/consensus/Validations_test.cpp @@ -23,10 +23,10 @@ #include #include +#include #include #include #include -#include namespace ripple { namespace test { @@ -165,7 +165,6 @@ class Validations_test : public beast::unit_test::suite } }; - // Saved StaleData for inspection in test struct StaleData { @@ -200,7 +199,7 @@ class Validations_test : public beast::unit_test::suite using Validation = csf::Validation; using Ledger = csf::Ledger; - Adaptor(StaleData& sd, clock_type& c, LedgerOracle & o, beast::Journal j) + Adaptor(StaleData& sd, clock_type& c, LedgerOracle& o, beast::Journal j) : staleData_{sd}, c_{c}, oracle_{o}, j_{j} { } @@ -223,12 +222,17 @@ class Validations_test : public beast::unit_test::suite staleData_.flushed = std::move(remaining); } + boost::optional + acquire(Ledger::ID const& id) + { + return oracle_.lookup(id); + } + beast::Journal journal() const { return j_; } - }; // Specialize generic Validations using the above types @@ -251,7 +255,6 @@ class Validations_test : public beast::unit_test::suite { } - // Helper to add an existing validation ValStatus add(Validation const& v) { @@ -447,8 +450,9 @@ class Validations_test : public beast::unit_test::suite using Trigger = std::function; std::vector triggers = { [&](TestValidations& vals) { vals.currentTrusted(); }, + [&](TestValidations& vals) { vals.getPreferred(Ledger{}); }, [&](TestValidations& vals) { - vals.getNodesAfter(ledgerA.id()); + vals.getNodesAfter(ledgerA, ledgerA.id()); }}; for (Trigger trigger : triggers) { @@ -458,6 +462,11 @@ class Validations_test : public beast::unit_test::suite BEAST_EXPECT( ValStatus::current == harness.add(n.validate(ledgerAB))); harness.vals().currentTrusted(); + BEAST_EXPECT( + harness.vals().getNodesAfter(ledgerA, ledgerA.id()) == 1); + BEAST_EXPECT( + harness.vals().getPreferred(Ledger{}) == + std::make_pair(ledgerAB.seq(), ledgerAB.id())); BEAST_EXPECT(harness.stale().empty()); harness.clock().advance(harness.parms().validationCURRENT_LOCAL); @@ -466,6 +475,11 @@ class Validations_test : public beast::unit_test::suite BEAST_EXPECT(harness.stale().size() == 1); BEAST_EXPECT(harness.stale()[0].ledgerID() == ledgerAB.id()); + BEAST_EXPECT( + harness.vals().getNodesAfter(ledgerA, ledgerA.id()) == 0); + BEAST_EXPECT( + harness.vals().getPreferred(Ledger{}) == + std::make_pair(Ledger::Seq{0}, Ledger::ID{0})); } } @@ -497,7 +511,8 @@ class Validations_test : public beast::unit_test::suite BEAST_EXPECT(ValStatus::current == harness.add(d.partial(ledgerA))); for (Ledger const& ledger : {ledgerA, ledgerAB, ledgerABC, ledgerAD}) - BEAST_EXPECT(harness.vals().getNodesAfter(ledger.id()) == 0); + BEAST_EXPECT( + harness.vals().getNodesAfter(ledger, ledger.id()) == 0); harness.clock().advance(5s); @@ -506,7 +521,19 @@ class Validations_test : public beast::unit_test::suite BEAST_EXPECT(ValStatus::current == harness.add(c.validate(ledgerAB))); BEAST_EXPECT(ValStatus::current == harness.add(d.partial(ledgerABC))); - BEAST_EXPECT(harness.vals().getNodesAfter(ledgerA.id()) == 2); + BEAST_EXPECT(harness.vals().getNodesAfter(ledgerA, ledgerA.id()) == 3); + BEAST_EXPECT( + harness.vals().getNodesAfter(ledgerAB, ledgerAB.id()) == 2); + BEAST_EXPECT( + harness.vals().getNodesAfter(ledgerABC, ledgerABC.id()) == 0); + BEAST_EXPECT( + harness.vals().getNodesAfter(ledgerAD, ledgerAD.id()) == 0); + + // If given a ledger inconsistent with the id, is still able to check + // using slower method + BEAST_EXPECT(harness.vals().getNodesAfter(ledgerAD, ledgerA.id()) == 1); + BEAST_EXPECT( + harness.vals().getNodesAfter(ledgerAD, ledgerAB.id()) == 2); } void @@ -596,99 +623,6 @@ class Validations_test : public beast::unit_test::suite BEAST_EXPECT(harness.vals().getCurrentPublicKeys().empty()); } - void - testCurrentTrustedDistribution() - { - // Test the trusted distribution calculation, including ledger slips - // and sequence cutoffs - using namespace std::chrono_literals; - testcase("Current trusted distribution"); - - LedgerHistoryHelper h; - Ledger ledgerA = h["a"]; - Ledger ledgerAB = h["ab"]; - Ledger ledgerABC = h["abc"]; - - TestHarness harness(h.oracle); - - Node baby = harness.makeNode(), papa = harness.makeNode(), - mama = harness.makeNode(), goldilocks = harness.makeNode(); - goldilocks.untrust(); - - // Stagger the validations around sequence 2 - // papa on seq 1 is behind - // baby on seq 2 is just right - // mama on seq 3 is ahead - // goldilocks on seq 2, but is not trusted - - for (auto const& node : {baby, papa, mama, goldilocks}) - BEAST_EXPECT( - ValStatus::current == harness.add(node.validate(ledgerA))); - - harness.clock().advance(1s); - for (auto const& node : {baby, mama, goldilocks}) - BEAST_EXPECT( - ValStatus::current == harness.add(node.validate(ledgerAB))); - - harness.clock().advance(1s); - BEAST_EXPECT( - ValStatus::current == harness.add(mama.validate(ledgerABC))); - - { - // Allow slippage that treats all trusted as the current ledger - auto res = harness.vals().currentTrustedDistribution( - Ledger::ID{2}, // Current ledger - Ledger::ID{1}, // Prior ledger - Ledger::Seq{0}); // No cutoff - - BEAST_EXPECT(res.size() == 1); - BEAST_EXPECT(res[Ledger::ID{2}] == 3); - BEAST_EXPECT( - getPreferredLedger(Ledger::ID{2}, res) == Ledger::ID{2}); - } - - { - // Don't allow slippage back for prior ledger - auto res = harness.vals().currentTrustedDistribution( - Ledger::ID{2}, // Current ledger - Ledger::ID{0}, // No prior ledger - Ledger::Seq{0}); // No cutoff - - BEAST_EXPECT(res.size() == 2); - BEAST_EXPECT(res[Ledger::ID{2}] == 2); - BEAST_EXPECT(res[Ledger::ID{1}] == 1); - BEAST_EXPECT( - getPreferredLedger(Ledger::ID{2}, res) == Ledger::ID{2}); - } - - { - // Don't allow any slips - auto res = harness.vals().currentTrustedDistribution( - Ledger::ID{0}, // No current ledger - Ledger::ID{0}, // No prior ledger - Ledger::Seq{0}); // No cutoff - - BEAST_EXPECT(res.size() == 3); - BEAST_EXPECT(res[Ledger::ID{1}] == 1); - BEAST_EXPECT(res[Ledger::ID{2}] == 1); - BEAST_EXPECT(res[Ledger::ID{3}] == 1); - BEAST_EXPECT( - getPreferredLedger(Ledger::ID{0}, res) == Ledger::ID{3}); - } - - { - // Cutoff old sequence numbers - auto res = harness.vals().currentTrustedDistribution( - Ledger::ID{2}, // current ledger - Ledger::ID{1}, // prior ledger - Ledger::Seq{2}); // Only sequence 2 or later - BEAST_EXPECT(res.size() == 1); - BEAST_EXPECT(res[Ledger::ID{2}] == 2); - BEAST_EXPECT( - getPreferredLedger(Ledger::ID{2}, res) == Ledger::ID{2}); - } - } - void testTrustedByLedgerFunctions() { @@ -805,7 +739,7 @@ class Validations_test : public beast::unit_test::suite compare(); } - void + void testExpire() { // Verify expiring clears out validations stored by ledger @@ -871,51 +805,280 @@ class Validations_test : public beast::unit_test::suite void testGetPreferredLedger() { - using Distribution = hash_map; + using namespace std::chrono_literals; + testcase("Preferred Ledger"); - { - Ledger::ID const current{1}; - Distribution dist; - BEAST_EXPECT(getPreferredLedger(current, dist) == current); - } + LedgerHistoryHelper h; + TestHarness harness(h.oracle); + Node a = harness.makeNode(), b = harness.makeNode(), + c = harness.makeNode(), d = harness.makeNode(); + c.untrust(); - { - Ledger::ID const current{1}; - Distribution dist; - dist[Ledger::ID{2}] = 2; - BEAST_EXPECT(getPreferredLedger(current, dist) == Ledger::ID{2}); - } + Ledger ledgerA = h["a"]; + Ledger ledgerB = h["b"]; + Ledger ledgerAC = h["ac"]; + Ledger ledgerACD = h["acd"]; + + using Seq = Ledger::Seq; + using ID = Ledger::ID; + + auto pref = [](Ledger ledger) { + return std::make_pair(ledger.seq(), ledger.id()); + }; + + // Empty (no ledgers) + BEAST_EXPECT(harness.vals().getPreferred(ledgerA) == pref(Ledger{})); + + // Single ledger + BEAST_EXPECT(ValStatus::current == harness.add(a.validate(ledgerB))); + BEAST_EXPECT(harness.vals().getPreferred(ledgerA) == pref(ledgerB)); + BEAST_EXPECT(harness.vals().getPreferred(ledgerB) == pref(ledgerB)); + + // Minimum valid sequence + BEAST_EXPECT( + harness.vals().getPreferred(ledgerA, Seq{10}) == ledgerA.id()); + + // Untrusted doesn't impact preferred ledger + // (ledgerB has tie-break over ledgerA) + BEAST_EXPECT(ValStatus::current == harness.add(b.validate(ledgerA))); + BEAST_EXPECT(ValStatus::current == harness.add(c.validate(ledgerA))); + BEAST_EXPECT(ledgerB.id() > ledgerA.id()); + BEAST_EXPECT(harness.vals().getPreferred(ledgerA) == pref(ledgerB)); + BEAST_EXPECT(harness.vals().getPreferred(ledgerB) == pref(ledgerB)); + + // Partial does break ties + BEAST_EXPECT(ValStatus::current == harness.add(d.partial(ledgerA))); + BEAST_EXPECT(harness.vals().getPreferred(ledgerA) == pref(ledgerA)); + BEAST_EXPECT(harness.vals().getPreferred(ledgerB) == pref(ledgerA)); + + harness.clock().advance(5s); + + // Parent of preferred-> stick with ledger + for (auto const& node : {a, b, c, d}) + BEAST_EXPECT( + ValStatus::current == harness.add(node.validate(ledgerAC))); + BEAST_EXPECT(harness.vals().getPreferred(ledgerA) == pref(ledgerA)); + // Earlier different chain, switch + BEAST_EXPECT(harness.vals().getPreferred(ledgerB) == pref(ledgerAC)); + // Earlier same chain stays where it is + BEAST_EXPECT(harness.vals().getPreferred(ledgerACD) == pref(ledgerACD)); + + // Any later grandchild or different chain is preferred + harness.clock().advance(5s); + for (auto const& node : {a, b, c, d}) + BEAST_EXPECT( + ValStatus::current == harness.add(node.validate(ledgerACD))); + for (auto const& ledger : {ledgerA, ledgerB, ledgerACD}) + BEAST_EXPECT( + harness.vals().getPreferred(ledger) == pref(ledgerACD)); + } + + void + testGetPreferredLCL() + { + using namespace std::chrono_literals; + testcase("Get preferred LCL"); + + LedgerHistoryHelper h; + TestHarness harness(h.oracle); + Node a = harness.makeNode(); + + Ledger ledgerA = h["a"]; + Ledger ledgerB = h["b"]; + Ledger ledgerC = h["c"]; + + using ID = Ledger::ID; + using Seq = Ledger::Seq; + + hash_map peerCounts; + + // No trusted validations or counts sticks with current ledger + BEAST_EXPECT( + harness.vals().getPreferredLCL(ledgerA, Seq{0}, peerCounts) == + ledgerA.id()); + + ++peerCounts[ledgerB.id()]; + + // No trusted validations, rely on peer counts + BEAST_EXPECT( + harness.vals().getPreferredLCL(ledgerA, Seq{0}, peerCounts) == + ledgerB.id()); + + ++peerCounts[ledgerC.id()]; + // No trusted validations, tied peers goes with larger ID + BEAST_EXPECT(ledgerC.id() > ledgerB.id()); + + BEAST_EXPECT( + harness.vals().getPreferredLCL(ledgerA, Seq{0}, peerCounts) == + ledgerC.id()); + + peerCounts[ledgerC.id()] += 1000; + + // Single trusted always wins over peer counts + BEAST_EXPECT(ValStatus::current == harness.add(a.validate(ledgerA))); + BEAST_EXPECT( + harness.vals().getPreferredLCL(ledgerB, Seq{0}, peerCounts) == + ledgerA.id()); + + // Stick with current ledger if trusted validation ledger has to old + // of a sequence + BEAST_EXPECT( + harness.vals().getPreferredLCL(ledgerB, Seq{2}, peerCounts) == + ledgerB.id()); + } + + void + testAcquireValidatedLedger() + { + using namespace std::chrono_literals; + testcase("Acquire validated ledger"); + + LedgerHistoryHelper h; + TestHarness harness(h.oracle); + Node a = harness.makeNode(); + + using ID = Ledger::ID; + using Seq = Ledger::Seq; + + // Validate the ledger before it is actually available + Validation val = a.validate(ID{2}, Seq{2}, 0s, 0s, true); + + BEAST_EXPECT(ValStatus::current == harness.add(val)); + // Validation is available + BEAST_EXPECT(harness.vals().numTrustedForLedger(ID{2}) == 1); + // but ledger based data is not + BEAST_EXPECT(harness.vals().getNodesAfter(Ledger{}, ID{0}) == 0); + + // Create the ledger + Ledger ledgerAB = h["ab"]; + // Now it should be available + BEAST_EXPECT(harness.vals().getNodesAfter(Ledger{}, ID{0}) == 1); + + // Create a validation that is not available + harness.clock().advance(5s); + Validation val2 = a.validate(ID{5}, Seq{5}, 0s, 0s, true); + BEAST_EXPECT(ValStatus::current == harness.add(val2)); + BEAST_EXPECT(harness.vals().numTrustedForLedger(ID{5}) == 1); + BEAST_EXPECT( + harness.vals().getPreferred(Ledger{}) == + std::make_pair(ledgerAB.seq(), ledgerAB.id())); + + // Switch to validation that is available + harness.clock().advance(5s); + Ledger ledgerABC = h["abc"]; + BEAST_EXPECT(ValStatus::current == harness.add(a.partial(ledgerABC))); + BEAST_EXPECT( + harness.vals().getPreferred(Ledger{}) == + std::make_pair(ledgerABC.seq(), ledgerABC.id())); + } + + void + testNumTrustedForLedger() + { + testcase("NumTrustedForLedger"); + LedgerHistoryHelper h; + TestHarness harness(h.oracle); + Node a = harness.makeNode(); + Node b = harness.makeNode(); + Ledger ledgerA = h["a"]; + + BEAST_EXPECT(ValStatus::current == harness.add(a.partial(ledgerA))); + BEAST_EXPECT(harness.vals().numTrustedForLedger(ledgerA.id()) == 0); + + BEAST_EXPECT(ValStatus::current == harness.add(b.validate(ledgerA))); + BEAST_EXPECT(harness.vals().numTrustedForLedger(ledgerA.id()) == 1); + } + + void + testCurrentTrustedDistribution() + { + // Test the trusted distribution calculation, including ledger slips + // and sequence cutoffs + using namespace std::chrono_literals; + testcase("Current trusted distribution"); + + LedgerHistoryHelper h; + Ledger ledgerA = h["a"]; + Ledger ledgerAB = h["ab"]; + Ledger ledgerABC = h["abc"]; + + TestHarness harness(h.oracle); + + Node baby = harness.makeNode(), papa = harness.makeNode(), + mama = harness.makeNode(), goldilocks = harness.makeNode(); + goldilocks.untrust(); + + // Stagger the validations around sequence 2 + // papa on seq 1 is behind + // baby on seq 2 is just right + // mama on seq 3 is ahead + // goldilocks on seq 2, but is not trusted + + for (auto const& node : {baby, papa, mama, goldilocks}) + BEAST_EXPECT( + ValStatus::current == harness.add(node.validate(ledgerA))); + + harness.clock().advance(1s); + for (auto const& node : {baby, mama, goldilocks}) + BEAST_EXPECT( + ValStatus::current == harness.add(node.validate(ledgerAB))); + + harness.clock().advance(1s); + BEAST_EXPECT( + ValStatus::current == harness.add(mama.validate(ledgerABC))); { - Ledger::ID const current{1}; - Distribution dist; - dist[Ledger::ID{1}] = 1; - dist[Ledger::ID{2}] = 2; - BEAST_EXPECT(getPreferredLedger(current, dist) == Ledger::ID{2}); + // Allow slippage that treats all trusted as the current ledger + auto res = harness.vals().currentTrustedDistribution( + Ledger::ID{2}, // Current ledger + Ledger::ID{1}, // Prior ledger + Ledger::Seq{0}); // No cutoff + + BEAST_EXPECT(res.size() == 1); + BEAST_EXPECT(res[Ledger::ID{2}] == 3); + BEAST_EXPECT( + getPreferredLedger(Ledger::ID{2}, res) == Ledger::ID{2}); } { - Ledger::ID const current{1}; - Distribution dist; - dist[Ledger::ID{1}] = 2; - dist[Ledger::ID{2}] = 2; - BEAST_EXPECT(getPreferredLedger(current, dist) == current); + // Don't allow slippage back for prior ledger + auto res = harness.vals().currentTrustedDistribution( + Ledger::ID{2}, // Current ledger + Ledger::ID{0}, // No prior ledger + Ledger::Seq{0}); // No cutoff + + BEAST_EXPECT(res.size() == 2); + BEAST_EXPECT(res[Ledger::ID{2}] == 2); + BEAST_EXPECT(res[Ledger::ID{1}] == 1); + BEAST_EXPECT( + getPreferredLedger(Ledger::ID{2}, res) == Ledger::ID{2}); } { - Ledger::ID const current{2}; - Distribution dist; - dist[Ledger::ID{1}] = 2; - dist[Ledger::ID{2}] = 2; - BEAST_EXPECT(getPreferredLedger(current, dist) == current); + // Don't allow any slips + auto res = harness.vals().currentTrustedDistribution( + Ledger::ID{0}, // No current ledger + Ledger::ID{0}, // No prior ledger + Ledger::Seq{0}); // No cutoff + + BEAST_EXPECT(res.size() == 3); + BEAST_EXPECT(res[Ledger::ID{1}] == 1); + BEAST_EXPECT(res[Ledger::ID{2}] == 1); + BEAST_EXPECT(res[Ledger::ID{3}] == 1); + BEAST_EXPECT( + getPreferredLedger(Ledger::ID{0}, res) == Ledger::ID{3}); } { - Ledger::ID const current{1}; - Distribution dist; - dist[Ledger::ID{2}] = 2; - dist[Ledger::ID{3}] = 2; - BEAST_EXPECT(getPreferredLedger(current, dist) == Ledger::ID{3}); + // Cutoff old sequence numbers + auto res = harness.vals().currentTrustedDistribution( + Ledger::ID{2}, // current ledger + Ledger::ID{1}, // prior ledger + Ledger::Seq{2}); // Only sequence 2 or later + BEAST_EXPECT(res.size() == 1); + BEAST_EXPECT(res[Ledger::ID{2}] == 2); + BEAST_EXPECT( + getPreferredLedger(Ledger::ID{2}, res) == Ledger::ID{2}); } } @@ -927,15 +1090,18 @@ class Validations_test : public beast::unit_test::suite testGetNodesAfter(); testCurrentTrusted(); testGetCurrentPublicKeys(); - testCurrentTrustedDistribution(); testTrustedByLedgerFunctions(); testExpire(); testFlush(); testGetPreferredLedger(); + testGetPreferredLCL(); + testAcquireValidatedLedger(); + testNumTrustedForLedger(); + testCurrentTrustedDistribution(); } }; BEAST_DEFINE_TESTSUITE(Validations, consensus, ripple); -} // csf -} // test -} // ripple +} // namespace csf +} // namespace test +} // namespace ripple diff --git a/src/test/csf/Peer.h b/src/test/csf/Peer.h index afbf3d27cda..9dd7ababdf2 100644 --- a/src/test/csf/Peer.h +++ b/src/test/csf/Peer.h @@ -153,6 +153,14 @@ struct Peer { } + boost::optional + acquire(Ledger::ID const & id) + { + if(Ledger const * ledger = p_.acquireLedger(id)) + return *ledger; + return boost::none; + } + beast::Journal journal() const { From c614718270c1e30fdbe79ff395c026cb9f726a8b Mon Sep 17 00:00:00 2001 From: Brad Chase Date: Fri, 15 Dec 2017 15:31:13 -0500 Subject: [PATCH 15/38] [FOLD] Use trie in generic Consensus: Use the validations ledger trie for determing the working ledger during a round of consensus and for determing the number of validators that have moved on to the next round. --- src/ripple/app/consensus/RCLConsensus.cpp | 30 ++++++---------- src/ripple/app/consensus/RCLConsensus.h | 7 ++-- src/ripple/app/consensus/RCLValidations.cpp | 22 ++++++++++++ src/ripple/app/consensus/RCLValidations.h | 27 ++++++++++---- src/ripple/consensus/Consensus.h | 9 +++-- src/test/csf/Peer.h | 40 ++++++++------------- 6 files changed, 77 insertions(+), 58 deletions(-) diff --git a/src/ripple/app/consensus/RCLConsensus.cpp b/src/ripple/app/consensus/RCLConsensus.cpp index dd24e33acf2..6a1f31b1b7c 100644 --- a/src/ripple/app/consensus/RCLConsensus.cpp +++ b/src/ripple/app/consensus/RCLConsensus.cpp @@ -233,9 +233,11 @@ 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); + return getNodesAfter(app_.getValidations(), ledger.ledger_, h); } uint256 @@ -244,29 +246,17 @@ RCLConsensus::Adaptor::getPrevLedger( RCLCxLedger const& ledger, ConsensusMode mode) { - uint256 parentID; - // Only set the parent ID if we believe ledger is the right ledger - if (mode != ConsensusMode::wrongLedger) - parentID = ledger.parentID(); - - // Get validators that are on our ledger, or "close" to being on - // our ledger. - hash_map ledgerCounts = - app_.getValidations().currentTrustedDistribution( - ledgerID, parentID, ledgerMaster_.getValidLedgerIndex()); - - uint256 netLgr = getPreferredLedger(ledgerID, ledgerCounts); + uint256 netLgr = getPreferred( + app_.getValidations(), + ledger.ledger_, + ledgerMaster_.getValidLedgerIndex()); - if (netLgr != ledgerID) + if (netLgr != ledgerID && netLgr != uint256{}) { 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; diff --git a/src/ripple/app/consensus/RCLConsensus.h b/src/ripple/app/consensus/RCLConsensus.h index 5dcb3fcc284..13ef724eec9 100644 --- a/src/ripple/app/consensus/RCLConsensus.h +++ b/src/ripple/app/consensus/RCLConsensus.h @@ -204,12 +204,13 @@ class RCLConsensus /** Number of proposers that have validated a ledger descended from requested ledger. - @param h The hash of the ledger of interest. + @param ledger The current working ledger + @param h The hash of the preferred working ledger @return The number of validating peers that have validated a ledger - succeeding the one provided. + descended from the preferred working ledger. */ std::size_t - proposersFinished(LedgerHash const& h) const; + proposersFinished(RCLCxLedger const & ledger, LedgerHash const& h) const; /** Propose the given position to my peers. diff --git a/src/ripple/app/consensus/RCLValidations.cpp b/src/ripple/app/consensus/RCLValidations.cpp index f1b1d7421d2..5d21a8c44d5 100644 --- a/src/ripple/app/consensus/RCLValidations.cpp +++ b/src/ripple/app/consensus/RCLValidations.cpp @@ -344,4 +344,26 @@ handleNewValidation(Application& app, // ability/bandwidth to. None of that was implemented. return shouldRelay; } + +std::size_t +getNodesAfter( + RCLValidations& vals, + std::shared_ptr ledger, + uint256 const& ledgerID) +{ + return vals.getNodesAfter( + RCLValidatedLedger{std::move(ledger), vals.adaptor().journal()}, + ledgerID); +} + +uint256 +getPreferred( + RCLValidations& vals, + std::shared_ptr ledger, + LedgerIndex minValidSeq) +{ + return vals.getPreferred( + RCLValidatedLedger{std::move(ledger), vals.adaptor().journal()}, + minValidSeq); +} } // namespace ripple diff --git a/src/ripple/app/consensus/RCLValidations.h b/src/ripple/app/consensus/RCLValidations.h index 22b073a38c7..159c3922bc7 100644 --- a/src/ripple/app/consensus/RCLValidations.h +++ b/src/ripple/app/consensus/RCLValidations.h @@ -242,12 +242,11 @@ class RCLValidationsAdaptor /// Alias for RCL-specific instantiation of generic Validations using RCLValidations = Validations; + /** Handle a new validation - 1. Set the trust status of a validation based on the validating node's - public key and this node's current UNL. - 2. Add the validation to the set of validations if current. - 3. If new and trusted, send the validation to the ledgerMaster. + Also sets the trust status of a validation based on the validating node's + public key and this node's current UNL. @param app Application object containing validations and ledgerMaster @param val The validation to add @@ -256,8 +255,24 @@ using RCLValidations = Validations; @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); + +// @see Validations::getNodesAfter +std::size_t +getNodesAfter( + RCLValidations& vals, + std::shared_ptr ledger, + uint256 const& ledgerID); + +// @see Validations::getPreferred +uint256 +getPreferred( + RCLValidations& vals, + std::shared_ptr ledger, + LedgerIndex minSeq); } // namespace ripple diff --git a/src/ripple/consensus/Consensus.h b/src/ripple/consensus/Consensus.h index 891dd18b676..d535bee5710 100644 --- a/src/ripple/consensus/Consensus.h +++ b/src/ripple/consensus/Consensus.h @@ -225,8 +225,10 @@ checkConsensus( std::size_t proposersValidated(Ledger::ID const & prevLedger) const; // Number of proposers that have validated a ledger descended from the - // given ledger - std::size_t proposersFinished(Ledger::ID const & prevLedger) const; + // given ledger; if prevLedger.id() != prevLedgerID, use prevLedgerID + // for the determination + std::size_t proposersFinished(Ledger conost & prev, + Ledger::ID const & prevLedger) const; // Return the ID of the last closed (and validated) ledger that the // application thinks consensus should use as the prior ledger. @@ -1410,7 +1412,8 @@ Consensus::haveConsensus() ++disagree; } } - auto currentFinished = adaptor_.proposersFinished(prevLedgerID_); + auto currentFinished = + adaptor_.proposersFinished(previousLedger_, prevLedgerID_); JLOG(j_.debug()) << "Checking for TX consensus: agree=" << agree << ", disagree=" << disagree; diff --git a/src/test/csf/Peer.h b/src/test/csf/Peer.h index 9dd7ababdf2..880c4cb4e92 100644 --- a/src/test/csf/Peer.h +++ b/src/test/csf/Peer.h @@ -497,9 +497,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 @@ -632,22 +632,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(); + Ledger::ID const netLgr = + validations.getPreferred(ledger, earliestAllowedSeq()); - // 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); - - if (netLgr != ledgerID) + if (netLgr != ledgerID && netLgr != Ledger::ID{}) { + JLOG(j.trace()) << Json::Compact(validations.getJsonTrie()); issue(WrongPrevLedger{ledgerID, netLgr}); } + return netLgr; } @@ -878,21 +871,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{}) + 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); } From 8315578ae2c71fa1c3f8c2379a195ad99502c53d Mon Sep 17 00:00:00 2001 From: Brad Chase Date: Fri, 15 Dec 2017 15:33:07 -0500 Subject: [PATCH 16/38] 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 full validations with increasing ledger index, otherwise the validation is marked partial. --- src/ripple/app/consensus/RCLValidations.cpp | 13 +++ src/ripple/app/consensus/RCLValidations.h | 7 ++ src/ripple/app/misc/NetworkOPs.cpp | 112 ++++++-------------- 3 files changed, 54 insertions(+), 78 deletions(-) diff --git a/src/ripple/app/consensus/RCLValidations.cpp b/src/ripple/app/consensus/RCLValidations.cpp index 5d21a8c44d5..527ae4ce31f 100644 --- a/src/ripple/app/consensus/RCLValidations.cpp +++ b/src/ripple/app/consensus/RCLValidations.cpp @@ -366,4 +366,17 @@ getPreferred( RCLValidatedLedger{std::move(ledger), vals.adaptor().journal()}, minValidSeq); } + +uint256 +getPreferredLCL( + RCLValidations& vals, + std::shared_ptr ledger, + LedgerIndex minSeq, + hash_map const& peerCounts) +{ + return vals.getPreferredLCL( + RCLValidatedLedger{std::move(ledger), vals.adaptor().journal()}, + minSeq, + peerCounts); +} } // namespace ripple diff --git a/src/ripple/app/consensus/RCLValidations.h b/src/ripple/app/consensus/RCLValidations.h index 159c3922bc7..57cf62e9056 100644 --- a/src/ripple/app/consensus/RCLValidations.h +++ b/src/ripple/app/consensus/RCLValidations.h @@ -274,6 +274,13 @@ getPreferred( std::shared_ptr ledger, LedgerIndex minSeq); +// @see Validations::getPreferredLCL +uint256 +getPreferredLCL(RCLValidations& vals, + std::shared_ptr ledger, + LedgerIndex minSeq, + hash_map const & peerCounts); + } // namespace ripple #endif diff --git a/src/ripple/app/misc/NetworkOPs.cpp b/src/ripple/app/misc/NetworkOPs.cpp index fba0c3b5f68..330d39fb53b 100644 --- a/src/ripple/app/misc/NetworkOPs.cpp +++ b/src/ripple/app/misc/NetworkOPs.cpp @@ -1273,84 +1273,40 @@ bool NetworkOPsImp::checkLastClosedLedger ( JLOG(m_journal.trace()) << "OurClosed: " << closedLedger; JLOG(m_journal.trace()) << "PrevClosed: " << prevClosedLedger; - struct ValidationCount - { - std::uint32_t trustedValidations = 0; - std::uint32_t nodesUsing = 0; - }; - - hash_map ledgers; - { - hash_map current = - app_.getValidations().currentTrustedDistribution( - closedLedger, - prevClosedLedger, - m_ledgerMaster.getValidLedgerIndex()); - - for (auto& it: current) - ledgers[it.first].trustedValidations += it.second; - } + //------------------------------------------------------------------------- + // Determine preferred last closed ledger - auto& ourVC = ledgers[closedLedger]; + auto & validations = app_.getValidations(); + JLOG(m_journal.debug()) + << "ValidationTrie " << Json::Compact(validations.getJsonTrie()); + // Will rely on peer LCL if no trusted validations exist + hash_map peerCounts; + peerCounts[closedLedger] = 0; if (mMode >= omTRACKING) - { - ++ourVC.nodesUsing; - } + peerCounts[closedLedger]++; - for (auto& peer: peerList) + for (auto& peer : peerList) { - uint256 peerLedger = peer->getClosedLedgerHash (); + uint256 peerLedger = peer->getClosedLedgerHash(); - if (peerLedger.isNonZero ()) - ++ledgers[peerLedger].nodesUsing; + if (peerLedger.isNonZero()) + ++peerCounts[peerLedger]; } + for(auto const & it: peerCounts) + JLOG(m_journal.debug()) << "L: " << it.first << " n=" << it.second; - // 3) Is there a network ledger we'd like to switch to? If so, do we have - // it? - bool switchLedgers = false; - ValidationCount bestCounts = ledgers[closedLedger]; - - for (auto const& it: ledgers) - { - uint256 const & currLedger = it.first; - ValidationCount const & currCounts = it.second; - - JLOG(m_journal.debug()) << "L: " << currLedger - << " t=" << currCounts.trustedValidations - << ", n=" << currCounts.nodesUsing; - - bool const preferCurr = [&]() - { - // Prefer ledger with more trustedValidations - if (currCounts.trustedValidations > bestCounts.trustedValidations) - return true; - if (currCounts.trustedValidations < bestCounts.trustedValidations) - return false; - // If neither are trusted, prefer more nodesUsing - if (currCounts.trustedValidations == 0) - { - if (currCounts.nodesUsing > bestCounts.nodesUsing) - return true; - if (currCounts.nodesUsing < bestCounts.nodesUsing) - return false; - } - // If tied trustedValidations (non-zero) or tied nodesUsing, - // prefer higher ledger hash - return currLedger > closedLedger; - - }(); - - // Switch to current ledger if it is preferred over best so far - if (preferCurr) - { - bestCounts = currCounts; - closedLedger = currLedger; - switchLedgers = true; - } - } + uint256 preferredLCL = getPreferredLCL( + validations, + ourClosed, + 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 +1320,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 +1336,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; From b5f528acc7318d61e4c0520248252dead2c41646 Mon Sep 17 00:00:00 2001 From: Brad Chase Date: Fri, 15 Dec 2017 15:47:45 -0500 Subject: [PATCH 17/38] [FOLD] Update docs --- docs/source.dox | 1 + src/ripple/consensus/LedgerTrie.h | 41 ++++++++++++++++++++----------- 2 files changed, 28 insertions(+), 14 deletions(-) diff --git a/docs/source.dox b/docs/source.dox index d0d82d7a86d..96b1fa73e38 100644 --- a/docs/source.dox +++ b/docs/source.dox @@ -114,6 +114,7 @@ INPUT = \ ../src/ripple/consensus/ConsensusTypes.h \ ../src/ripple/consensus/DisputedTx.h \ ../src/ripple/consensus/LedgerTiming.h \ + ../src/ripple/consensus/LedgerTrie.h \ ../src/ripple/consensus/Validations.h \ ../src/ripple/consensus/ConsensusParms.h \ ../src/ripple/app/consensus/RCLCxTx.h \ diff --git a/src/ripple/consensus/LedgerTrie.h b/src/ripple/consensus/LedgerTrie.h index 31a68ff08aa..f37ed322dbd 100644 --- a/src/ripple/consensus/LedgerTrie.h +++ b/src/ripple/consensus/LedgerTrie.h @@ -526,8 +526,8 @@ class LedgerTrie 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 + e.g. A->B->C->D. However, if due to network connectivity or other + issues, validators generate different chains @code /->C @@ -537,19 +537,23 @@ class LedgerTrie 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. - This ensures the preferred branch has monotonically increasing support. + be conservative and only switch to a different branch when you see + enough peer validations to *know* another branch won't have preferred + support. This ensures the preferred branch has monotonically increasing + 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 (B). - - The support of ledgers that have been explicitly validated by a - validator (C,D), or are an ancestor of that validators current - validated ledger (E). + - 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 descendents 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 (prefixSupport). @@ -557,11 +561,20 @@ class LedgerTrie with relative majority of support, where prefixSupport can be given to ANY ledger at that sequence number (including one not yet known). If no such preferred ledger exists, than prior sequence preferred ledger is - the overall preferred ledger. If one does exist, then we continue - with the next sequence but increase prefixSupport with the non - preferred ones this round, e.g. if C were preferred over D, then - prefixSupport would incerase by the support of D and E. - + 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 prefix support. This is because if all + the prefix support 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 but increase prefixSupport with the non-preferred tip support + this round, e.g. if C were preferred over D, then prefixSupport would + incerase by the support of D and E, since if those validators are + following the protocl, they will switch to the C branch, but might + initially support a different descendant. */ std::pair getPreferred() From fa576e5da7c6ee356fdeeb5a707db1b2fb92e5eb Mon Sep 17 00:00:00 2001 From: Brad Chase Date: Fri, 15 Dec 2017 16:09:20 -0500 Subject: [PATCH 18/38] [FOLD] Remove old validation code --- src/ripple/consensus/Validations.h | 87 ------------------- src/test/consensus/Validations_test.cpp | 108 +----------------------- src/test/csf/Peer.h | 6 -- 3 files changed, 3 insertions(+), 198 deletions(-) diff --git a/src/ripple/consensus/Validations.h b/src/ripple/consensus/Validations.h index 0cbd2920aad..47d669e4ad1 100644 --- a/src/ripple/consensus/Validations.h +++ b/src/ripple/consensus/Validations.h @@ -25,7 +25,6 @@ #include #include #include -#include #include #include #include @@ -598,85 +597,6 @@ class Validations return trie_.getJson(); } - /** Distribution of current trusted validations - - Calculates the distribution of current validations but allows - ledgers one away from the current ledger to count as the current. - - @param currentLedger The identifier of the ledger we believe is current - (0 if unknown) - @param priorLedger The identifier of our previous current ledger - (0 if unknown) - @param cutoffBefore Ignore ledgers with sequence number before this - - @return Map representing the distribution of ledgerID by count - */ - hash_map - currentTrustedDistribution( - ID const& currentLedger, - ID const& priorLedger, - Seq cutoffBefore) - { - bool const valCurrentLedger = currentLedger != ID{0}; - bool const valPriorLedger = priorLedger != ID{0}; - - hash_map ret; - - ScopedLock lock{mutex_}; - current( - lock, - // The number of validations does not correspond to the number of - // distinct ledgerIDs so we do not call reserve on ret. - [](std::size_t) {}, - [this, - &cutoffBefore, - ¤tLedger, - &valCurrentLedger, - &valPriorLedger, - &priorLedger, - &ret](NodeKey const& k, Validation const& v) { - - ID prevLedgerID; - auto it = lastLedger_.find(k); - if(it != lastLedger_.end()) - { - Ledger const & ledger = it->second; - if(ledger.seq() > Seq{1}) - prevLedgerID = ledger[ledger.seq() - Seq{1}]; - } - - if (!v.trusted() || !v.full()) - return; - - Seq const seq = v.seq(); - if ((seq == Seq{0}) || (seq >= cutoffBefore)) - { - // contains a live record - bool countPreferred = - valCurrentLedger && (v.ledgerID() == currentLedger); - - if (!countPreferred && // allow up to one ledger slip in - // either direction - ((valCurrentLedger && - (prevLedgerID == currentLedger)) || - (valPriorLedger && (v.ledgerID() == priorLedger)))) - { - countPreferred = true; - JLOG(this->adaptor_.journal().trace()) - << "Counting for " << currentLedger << " not " - << v.ledgerID(); - } - - if (countPreferred) - ret[currentLedger]++; - else - ret[v.ledgerID()]++; - } - }); - - return ret; - } - /** Return the sequence number and ID of the preferred working ledger A ledger is preferred if it has more support amongst trusted validators @@ -827,13 +747,6 @@ class Validations return count; } - // Temporary pending refactor to use getNodesAfter above - std::size_t - getNodesAfter(ID const& ledgerID) - { - return getNodesAfter(Ledger{}, ledgerID); - } - /** Get the currently trusted full validations @return Vector of validations from currently trusted validators diff --git a/src/test/consensus/Validations_test.cpp b/src/test/consensus/Validations_test.cpp index 4830b0d9c04..d197dc1a1e3 100644 --- a/src/test/consensus/Validations_test.cpp +++ b/src/test/consensus/Validations_test.cpp @@ -179,7 +179,6 @@ class Validations_test : public beast::unit_test::suite StaleData& staleData_; clock_type& c_; LedgerOracle& oracle_; - beast::Journal j_; public: // Non-locking mutex to avoid locks in generic Validations @@ -199,8 +198,8 @@ class Validations_test : public beast::unit_test::suite using Validation = csf::Validation; using Ledger = csf::Ledger; - Adaptor(StaleData& sd, clock_type& c, LedgerOracle& o, beast::Journal j) - : staleData_{sd}, c_{c}, oracle_{o}, j_{j} + Adaptor(StaleData& sd, clock_type& c, LedgerOracle& o) + : staleData_{sd}, c_{c}, oracle_{o} { } @@ -227,12 +226,6 @@ class Validations_test : public beast::unit_test::suite { return oracle_.lookup(id); } - - beast::Journal - journal() const - { - return j_; - } }; // Specialize generic Validations using the above types @@ -245,13 +238,12 @@ class Validations_test : public beast::unit_test::suite StaleData staleData_; ValidationParms p_; beast::manual_clock clock_; - beast::Journal j_; TestValidations tv_; PeerID nextNodeId_{0}; public: TestHarness(LedgerOracle& o) - : tv_(p_, clock_, staleData_, clock_, o, j_) + : tv_(p_, clock_, staleData_, clock_, o) { } @@ -989,99 +981,6 @@ class Validations_test : public beast::unit_test::suite BEAST_EXPECT(harness.vals().numTrustedForLedger(ledgerA.id()) == 1); } - void - testCurrentTrustedDistribution() - { - // Test the trusted distribution calculation, including ledger slips - // and sequence cutoffs - using namespace std::chrono_literals; - testcase("Current trusted distribution"); - - LedgerHistoryHelper h; - Ledger ledgerA = h["a"]; - Ledger ledgerAB = h["ab"]; - Ledger ledgerABC = h["abc"]; - - TestHarness harness(h.oracle); - - Node baby = harness.makeNode(), papa = harness.makeNode(), - mama = harness.makeNode(), goldilocks = harness.makeNode(); - goldilocks.untrust(); - - // Stagger the validations around sequence 2 - // papa on seq 1 is behind - // baby on seq 2 is just right - // mama on seq 3 is ahead - // goldilocks on seq 2, but is not trusted - - for (auto const& node : {baby, papa, mama, goldilocks}) - BEAST_EXPECT( - ValStatus::current == harness.add(node.validate(ledgerA))); - - harness.clock().advance(1s); - for (auto const& node : {baby, mama, goldilocks}) - BEAST_EXPECT( - ValStatus::current == harness.add(node.validate(ledgerAB))); - - harness.clock().advance(1s); - BEAST_EXPECT( - ValStatus::current == harness.add(mama.validate(ledgerABC))); - - { - // Allow slippage that treats all trusted as the current ledger - auto res = harness.vals().currentTrustedDistribution( - Ledger::ID{2}, // Current ledger - Ledger::ID{1}, // Prior ledger - Ledger::Seq{0}); // No cutoff - - BEAST_EXPECT(res.size() == 1); - BEAST_EXPECT(res[Ledger::ID{2}] == 3); - BEAST_EXPECT( - getPreferredLedger(Ledger::ID{2}, res) == Ledger::ID{2}); - } - - { - // Don't allow slippage back for prior ledger - auto res = harness.vals().currentTrustedDistribution( - Ledger::ID{2}, // Current ledger - Ledger::ID{0}, // No prior ledger - Ledger::Seq{0}); // No cutoff - - BEAST_EXPECT(res.size() == 2); - BEAST_EXPECT(res[Ledger::ID{2}] == 2); - BEAST_EXPECT(res[Ledger::ID{1}] == 1); - BEAST_EXPECT( - getPreferredLedger(Ledger::ID{2}, res) == Ledger::ID{2}); - } - - { - // Don't allow any slips - auto res = harness.vals().currentTrustedDistribution( - Ledger::ID{0}, // No current ledger - Ledger::ID{0}, // No prior ledger - Ledger::Seq{0}); // No cutoff - - BEAST_EXPECT(res.size() == 3); - BEAST_EXPECT(res[Ledger::ID{1}] == 1); - BEAST_EXPECT(res[Ledger::ID{2}] == 1); - BEAST_EXPECT(res[Ledger::ID{3}] == 1); - BEAST_EXPECT( - getPreferredLedger(Ledger::ID{0}, res) == Ledger::ID{3}); - } - - { - // Cutoff old sequence numbers - auto res = harness.vals().currentTrustedDistribution( - Ledger::ID{2}, // current ledger - Ledger::ID{1}, // prior ledger - Ledger::Seq{2}); // Only sequence 2 or later - BEAST_EXPECT(res.size() == 1); - BEAST_EXPECT(res[Ledger::ID{2}] == 2); - BEAST_EXPECT( - getPreferredLedger(Ledger::ID{2}, res) == Ledger::ID{2}); - } - } - void run() override { @@ -1097,7 +996,6 @@ class Validations_test : public beast::unit_test::suite testGetPreferredLCL(); testAcquireValidatedLedger(); testNumTrustedForLedger(); - testCurrentTrustedDistribution(); } }; diff --git a/src/test/csf/Peer.h b/src/test/csf/Peer.h index 880c4cb4e92..cf9c6da6b6d 100644 --- a/src/test/csf/Peer.h +++ b/src/test/csf/Peer.h @@ -160,12 +160,6 @@ struct Peer return *ledger; return boost::none; } - - beast::Journal - journal() const - { - return p_.j; - } }; //! Type definitions for generic consensus From 95012785d605a4388308801d40356827f0af0484 Mon Sep 17 00:00:00 2001 From: seelabs Date: Fri, 15 Dec 2017 17:15:29 -0500 Subject: [PATCH 19/38] [FOLD] Fix misspellings --- src/ripple/consensus/LedgerTrie.h | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/ripple/consensus/LedgerTrie.h b/src/ripple/consensus/LedgerTrie.h index f37ed322dbd..c886abae0ed 100644 --- a/src/ripple/consensus/LedgerTrie.h +++ b/src/ripple/consensus/LedgerTrie.h @@ -55,7 +55,7 @@ namespace ripple { + sum_(child : node->children) child->branchSupport @endcode - This is analagous to the merkle tree property in which a node's hash is + This is analogous to the merkle tree property in which a node's hash is the hash of the concatenation of its child node hashes. The templated Ledger type represents a ledger which has a unique history. @@ -501,7 +501,7 @@ class LedgerTrie /** 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 descendent + @return The number of entries in the trie for this ledger or a descendant */ std::uint32_t branchSupport(Ledger const& ledger) const @@ -550,7 +550,7 @@ class LedgerTrie - 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 descendents of the current + - 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. @@ -572,8 +572,8 @@ class LedgerTrie If a preferred ledger does exist, then we continue with the next sequence but increase prefixSupport with the non-preferred tip support this round, e.g. if C were preferred over D, then prefixSupport would - incerase by the support of D and E, since if those validators are - following the protocl, they will switch to the C branch, but might + increase by the support of D and E, since if those validators are + following the protocol, they will switch to the C branch, but might initially support a different descendant. */ std::pair @@ -631,12 +631,12 @@ class LedgerTrie // of curr and its ancestors, along with the branch support of // any of its siblings that are inconsistent. // - // The additional prefix suppport that is carried to best is + // The additional prefix support that is carried to best is // A->branchSupport + B->branchSupport + best->tipSupport // This is the amount of support that has not yet voted - // on a descendent of best, or has voted on a conflicting - // descendent and will switch to best in the future. This means - // that they may support an arbitrary descendent of best. + // on a descendant of best, or has voted on a conflicting + // descendant and will switch to best in the future. This means + // that they may support an arbitrary descendant of best. // // The calculation is simplified using // A->branchSupport+B->branchSupport From 329ca8863fa9f8fa2463f8a93acaf804d1137a31 Mon Sep 17 00:00:00 2001 From: Brad Chase Date: Mon, 18 Dec 2017 08:54:33 -0500 Subject: [PATCH 20/38] [FOLD] Fix clang PeerGroup warnings --- src/test/csf/PeerGroup.h | 1 - 1 file changed, 1 deletion(-) diff --git a/src/test/csf/PeerGroup.h b/src/test/csf/PeerGroup.h index cf14828ec23..4ad8bd5396e 100644 --- a/src/test/csf/PeerGroup.h +++ b/src/test/csf/PeerGroup.h @@ -47,7 +47,6 @@ class PeerGroup using const_reference = peers_type::const_reference; PeerGroup() = default; - PeerGroup(PeerGroup const&) = default; PeerGroup(Peer* peer) : peers_{1, peer} { } From ad35ee2f053c20ba1ccb83ca71a4448b0628f5e8 Mon Sep 17 00:00:00 2001 From: Brad Chase Date: Wed, 20 Dec 2017 12:53:05 -0500 Subject: [PATCH 21/38] [FOLD] Address PR suggestions --- src/ripple/app/consensus/RCLValidations.h | 3 +- src/ripple/consensus/LedgerTrie.h | 74 +++++++++++------------ src/test/app/RCLValidations_test.cpp | 4 +- src/test/consensus/Validations_test.cpp | 21 ++++--- src/test/csf/Peer.h | 2 + src/test/csf/ledgers.h | 21 ++++--- 6 files changed, 70 insertions(+), 55 deletions(-) diff --git a/src/ripple/app/consensus/RCLValidations.h b/src/ripple/app/consensus/RCLValidations.h index 57cf62e9056..7b441d835d8 100644 --- a/src/ripple/app/consensus/RCLValidations.h +++ b/src/ripple/app/consensus/RCLValidations.h @@ -138,8 +138,9 @@ class RCLValidatedLedger public: using ID = LedgerHash; using Seq = LedgerIndex; + struct MakeGenesis{}; - RCLValidatedLedger() = default; + RCLValidatedLedger(MakeGenesis) {}; RCLValidatedLedger(std::shared_ptr ledger, beast::Journal j); diff --git a/src/ripple/consensus/LedgerTrie.h b/src/ripple/consensus/LedgerTrie.h index c886abae0ed..be23712c3c7 100644 --- a/src/ripple/consensus/LedgerTrie.h +++ b/src/ripple/consensus/LedgerTrie.h @@ -27,10 +27,11 @@ namespace ripple { + /** Ancestry trie of ledgers - Combination of a compressed trie and merkle-ish tree that maintains - validation support of recent ledgers based on their ancestry. + 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, @@ -45,19 +46,16 @@ namespace ripple { 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. - The merkle-ish property is based on the branch support calculation. Each - node has a tipSupport, which is the number of current validations for that - particular ledger. The branch support is the sum of the tip support and - the branch support of that node's children: + 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 - + sum_(child : node->children) child->branchSupport + node->branchSupport = node->tipSupport; + for (child : node->children) + node->branchSupport += child->branchSupport; @endcode - This is analogous to the merkle tree property in which a node's hash is - the hash of the concatenation of its child node hashes. - The templated Ledger type represents a ledger which has a unique history. It should be lightweight and cheap to copy. @@ -68,12 +66,14 @@ namespace ripple { struct Ledger { - // The default ledger represents a ledger that prefixes all other - // ledgers, e.g. the genesis ledger - Ledger(); + struct MakeGenesis{}; - Ledger(Ledger &); - Ledger& operator=(Ledger ); + // 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; @@ -120,7 +120,7 @@ class LedgerTrie Ledger ledger_; public: - Span() + Span() : ledger_{typename Ledger::MakeGenesis{}} { // Require default ledger to be genesis seq assert(ledger_.seq() == start_); @@ -183,7 +183,7 @@ class LedgerTrie std::pair tip() const { - Seq tipSeq{end_ -Seq{1}}; + Seq tipSeq{end_ - Seq{1}}; return {tipSeq, ledger_[tipSeq]}; } @@ -210,8 +210,8 @@ class LedgerTrie friend std::ostream& operator<<(std::ostream& o, Span const& s) { - return o << s.tip().second << "(" << s.start_ << "," << s.end_ - << "]"; + return o << s.tip().second << "[" << s.start_ << "," << s.end_ + << ")"; } friend Span @@ -228,15 +228,13 @@ class LedgerTrie // A node in the trie struct Node { - Node() : span{}, tipSupport{0}, branchSupport{0} - { - } + Node() = default; - Node(Ledger const& l) : span{l}, tipSupport{1}, branchSupport{1} + explicit Node(Ledger const& l) : span{l}, tipSupport{1}, branchSupport{1} { } - Node(Span s) : span{std::move(s)} + explicit Node(Span s) : span{std::move(s)} { } @@ -256,14 +254,16 @@ class LedgerTrie void erase(Node const* child) { - auto it = std::remove_if( - children.begin(), - children.end(), - [child](std::unique_ptr const& curr) { - return curr.get() == child; - }); + auto it = std::find_if( + children.begin(), + children.end(), + [child](std::unique_ptr const& curr) { + return curr.get() == child; + }); assert(it != children.end()); - children.erase(it, children.end()); + using std::swap; + swap(*it, children.back()); + children.pop_back(); } friend std::ostream& @@ -284,7 +284,7 @@ class LedgerTrie if(!children.empty()) { Json::Value &cs = (res["children"] = Json::arrayValue); - for(auto const & child : children) + for (auto const& child : children) { cs.append(child->getJson()); } @@ -322,7 +322,7 @@ class LedgerTrie // Find the child with the longest ancestry match for (std::unique_ptr const& child : curr->children) { - auto childPos = child->span.diff(ledger); + auto const childPos = child->span.diff(ledger); if (childPos > pos) { done = false; @@ -385,7 +385,7 @@ class LedgerTrie // becomes abcd->ef->... // Create oldSuffix node that takes over loc - std::unique_ptr newNode{std::make_unique(oldSuffix)}; + auto newNode = std::make_unique(oldSuffix); newNode->tipSupport = loc->tipSupport; newNode->branchSupport = loc->branchSupport; using std::swap; @@ -406,7 +406,7 @@ class LedgerTrie // -> abc-> ... // -> de - std::unique_ptr newNode{std::make_unique(newSuffix)}; + auto newNode = std::make_unique(newSuffix); newNode->parent = loc; // increment support starting from the new node incNode = newNode.get(); @@ -560,7 +560,7 @@ class LedgerTrie The preferred ledger for this sequence number is then the ledger with relative majority of support, where prefixSupport can be given to ANY ledger at that sequence number (including one not yet known). If no - such preferred ledger exists, than prior sequence preferred ledger is + such preferred ledger exists, then prior sequence preferred ledger is the overall preferred ledger. In this example, for D to be preferred, the number of validators diff --git a/src/test/app/RCLValidations_test.cpp b/src/test/app/RCLValidations_test.cpp index f08e2e5f6e7..8eb1858063c 100644 --- a/src/test/app/RCLValidations_test.cpp +++ b/src/test/app/RCLValidations_test.cpp @@ -97,7 +97,7 @@ class RCLValidations_test : public beast::unit_test::suite // Empty ledger { - RCLValidatedLedger a; + RCLValidatedLedger a{RCLValidatedLedger::MakeGenesis{}}; BEAST_EXPECT(a.seq() == Seq{0}); BEAST_EXPECT(a[Seq{0}] == ID{0}); BEAST_EXPECT(a.minSeq() == Seq{0}); @@ -124,7 +124,7 @@ class RCLValidations_test : public beast::unit_test::suite // Empty with non-empty { - RCLValidatedLedger a; + RCLValidatedLedger a{RCLValidatedLedger::MakeGenesis{}}; for (auto ledger : {history.back(), history[maxAncestors - 1]}) diff --git a/src/test/consensus/Validations_test.cpp b/src/test/consensus/Validations_test.cpp index d197dc1a1e3..de76d2f9e1b 100644 --- a/src/test/consensus/Validations_test.cpp +++ b/src/test/consensus/Validations_test.cpp @@ -291,6 +291,8 @@ class Validations_test : public beast::unit_test::suite } }; + Ledger const genesisLedger{Ledger::MakeGenesis{}}; + void testAddValidation() { @@ -439,10 +441,12 @@ class Validations_test : public beast::unit_test::suite Ledger ledgerA = h["a"]; Ledger ledgerAB = h["ab"]; + using Trigger = std::function; + std::vector triggers = { [&](TestValidations& vals) { vals.currentTrusted(); }, - [&](TestValidations& vals) { vals.getPreferred(Ledger{}); }, + [&](TestValidations& vals) { vals.getPreferred(genesisLedger); }, [&](TestValidations& vals) { vals.getNodesAfter(ledgerA, ledgerA.id()); }}; @@ -457,7 +461,7 @@ class Validations_test : public beast::unit_test::suite BEAST_EXPECT( harness.vals().getNodesAfter(ledgerA, ledgerA.id()) == 1); BEAST_EXPECT( - harness.vals().getPreferred(Ledger{}) == + harness.vals().getPreferred(genesisLedger) == std::make_pair(ledgerAB.seq(), ledgerAB.id())); BEAST_EXPECT(harness.stale().empty()); harness.clock().advance(harness.parms().validationCURRENT_LOCAL); @@ -470,7 +474,7 @@ class Validations_test : public beast::unit_test::suite BEAST_EXPECT( harness.vals().getNodesAfter(ledgerA, ledgerA.id()) == 0); BEAST_EXPECT( - harness.vals().getPreferred(Ledger{}) == + harness.vals().getPreferred(genesisLedger) == std::make_pair(Ledger::Seq{0}, Ledger::ID{0})); } } @@ -819,7 +823,8 @@ class Validations_test : public beast::unit_test::suite }; // Empty (no ledgers) - BEAST_EXPECT(harness.vals().getPreferred(ledgerA) == pref(Ledger{})); + BEAST_EXPECT( + harness.vals().getPreferred(ledgerA) == pref(genesisLedger)); // Single ledger BEAST_EXPECT(ValStatus::current == harness.add(a.validate(ledgerB))); @@ -939,12 +944,12 @@ class Validations_test : public beast::unit_test::suite // Validation is available BEAST_EXPECT(harness.vals().numTrustedForLedger(ID{2}) == 1); // but ledger based data is not - BEAST_EXPECT(harness.vals().getNodesAfter(Ledger{}, ID{0}) == 0); + BEAST_EXPECT(harness.vals().getNodesAfter(genesisLedger, ID{0}) == 0); // Create the ledger Ledger ledgerAB = h["ab"]; // Now it should be available - BEAST_EXPECT(harness.vals().getNodesAfter(Ledger{}, ID{0}) == 1); + BEAST_EXPECT(harness.vals().getNodesAfter(genesisLedger, ID{0}) == 1); // Create a validation that is not available harness.clock().advance(5s); @@ -952,7 +957,7 @@ class Validations_test : public beast::unit_test::suite BEAST_EXPECT(ValStatus::current == harness.add(val2)); BEAST_EXPECT(harness.vals().numTrustedForLedger(ID{5}) == 1); BEAST_EXPECT( - harness.vals().getPreferred(Ledger{}) == + harness.vals().getPreferred(genesisLedger) == std::make_pair(ledgerAB.seq(), ledgerAB.id())); // Switch to validation that is available @@ -960,7 +965,7 @@ class Validations_test : public beast::unit_test::suite Ledger ledgerABC = h["abc"]; BEAST_EXPECT(ValStatus::current == harness.add(a.partial(ledgerABC))); BEAST_EXPECT( - harness.vals().getPreferred(Ledger{}) == + harness.vals().getPreferred(genesisLedger) == std::make_pair(ledgerABC.seq(), ledgerABC.id())); } diff --git a/src/test/csf/Peer.h b/src/test/csf/Peer.h index cf9c6da6b6d..0fb950860dd 100644 --- a/src/test/csf/Peer.h +++ b/src/test/csf/Peer.h @@ -285,7 +285,9 @@ struct Peer , scheduler{s} , net{n} , trustGraph(tg) + , lastClosedLedger{Ledger::MakeGenesis{}} , validations{ValidationParms{}, s.clock(), *this} + , fullyValidatedLedger{Ledger::MakeGenesis{}} , collectors{c} { // All peers start from the default constructed genesis ledger diff --git a/src/test/csf/ledgers.h b/src/test/csf/ledgers.h index 6d706f1a769..9c0e8334a77 100644 --- a/src/test/csf/ledgers.h +++ b/src/test/csf/ledgers.h @@ -66,6 +66,7 @@ class Ledger struct IdTag; using ID = tagged_integer; + struct MakeGenesis {}; private: // The instance is the common immutable data that will be assigned a unique // ID by the oracle @@ -142,7 +143,13 @@ class Ledger } 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{}) { } @@ -304,14 +311,14 @@ class LedgerOracle */ struct LedgerHistoryHelper { - csf::LedgerOracle oracle; - csf::Tx::ID nextTx{0}; - std::unordered_map ledgers; + LedgerOracle oracle; + Tx::ID nextTx{0}; + std::unordered_map ledgers; std::set seen; LedgerHistoryHelper() { - ledgers[""] = csf::Ledger{}; + ledgers[""] = Ledger{Ledger::MakeGenesis{}}; } /** Get or create the ledger with the given string history. @@ -319,7 +326,7 @@ struct LedgerHistoryHelper Creates an necessary intermediate ledgers, but asserts if a letter is re-used (e.g. "abc" then "adc" would assert) */ - csf::Ledger const& operator[](std::string const& s) + Ledger const& operator[](std::string const& s) { auto it = ledgers.find(s); if (it != ledgers.end()) @@ -328,7 +335,7 @@ struct LedgerHistoryHelper // enforce that the new suffix has never been seen assert(seen.emplace(s.back()).second); - csf::Ledger const& parent = (*this)[s.substr(0, s.size() - 1)]; + Ledger const& parent = (*this)[s.substr(0, s.size() - 1)]; return ledgers.emplace(s, oracle.accept(parent, ++nextTx)) .first->second; } From cff3191a3314e0d88c8f260b0c92d215d242ab68 Mon Sep 17 00:00:00 2001 From: Howard Hinnant Date: Thu, 21 Dec 2017 11:34:42 -0500 Subject: [PATCH 22/38] [FOLD] Make a few memer functions const --- src/ripple/consensus/LedgerTrie.h | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/ripple/consensus/LedgerTrie.h b/src/ripple/consensus/LedgerTrie.h index be23712c3c7..ba7a0d3e89f 100644 --- a/src/ripple/consensus/LedgerTrie.h +++ b/src/ripple/consensus/LedgerTrie.h @@ -146,14 +146,14 @@ class LedgerTrie // Return the Span from (spot,end_] Span - from(Seq spot) + from(Seq spot) const { return sub(spot, end_); } // Return the Span from (start_,spot] Span - before(Seq spot) + before(Seq spot) const { return sub(start_, spot); } @@ -202,7 +202,7 @@ class LedgerTrie // Return a span of this over the half-open interval [from,to) Span - sub(Seq from, Seq to) + sub(Seq from, Seq to) const { return Span(clamp(from), clamp(to), ledger_); } @@ -577,7 +577,7 @@ class LedgerTrie initially support a different descendant. */ std::pair - getPreferred() + getPreferred() const { Node* curr = root.get(); From f9de5beff744cbbeed41bc170a114e8cd96ec622 Mon Sep 17 00:00:00 2001 From: Brad Chase Date: Thu, 21 Dec 2017 13:31:05 -0500 Subject: [PATCH 23/38] [FOLD] Change full validation sequence number invariant: Require a new full validation to be for a sequence number larger than any unexpired previously issued full validation sequence number from that validator. This is less strict than the earlier changeset which did not allow expiration of what is the largest issued full validation sequence number. --- src/ripple/app/consensus/RCLConsensus.cpp | 12 +++-- src/ripple/app/consensus/RCLConsensus.h | 5 ++- src/ripple/consensus/Validations.h | 53 +++++++++++++++++++---- src/test/consensus/Consensus_test.cpp | 1 + src/test/consensus/Validations_test.cpp | 32 ++++++++++++++ src/test/csf/Peer.h | 11 +++-- 6 files changed, 90 insertions(+), 24 deletions(-) diff --git a/src/ripple/app/consensus/RCLConsensus.cpp b/src/ripple/app/consensus/RCLConsensus.cpp index 6a1f31b1b7c..90d0dfb76c5 100644 --- a/src/ripple/app/consensus/RCLConsensus.cpp +++ b/src/ripple/app/consensus/RCLConsensus.cpp @@ -829,10 +829,11 @@ RCLConsensus::Adaptor::validate(RCLCxLedger const& ledger, bool proposing) validationTime = lastValidationTime_ + 1s; lastValidationTime_ = validationTime; - // Can only fully validate later sequenced ledgers - const bool isFull = proposing && ledger.seq() > largestFullValidationSeq_; - largestFullValidationSeq_ = - std::max(largestFullValidationSeq_, ledger.seq()); + // Can only fully validate if proposed and increasing full sequence invariant + // satisfied + const bool isFull = proposing && + fullSeqEnforcer_.tryAdvance( + stopwatch().now(), ledger.seq(), app_.getValidations().parms()); // Build validation auto v = std::make_shared( @@ -951,9 +952,6 @@ RCLConsensus::Adaptor::preStartRound(RCLCxLedger const & prevLgr) prevLgr.seq() >= app_.getMaxDisallowedLedger() && !app_.getOPs().isAmendmentBlocked(); - largestFullValidationSeq_ = - std::max(largestFullValidationSeq_, app_.getMaxDisallowedLedger()); - const bool synced = app_.getOPs().getOperatingMode() == NetworkOPs::omFULL; if (validating_) diff --git a/src/ripple/app/consensus/RCLConsensus.h b/src/ripple/app/consensus/RCLConsensus.h index 13ef724eec9..011702672df 100644 --- a/src/ripple/app/consensus/RCLConsensus.h +++ b/src/ripple/app/consensus/RCLConsensus.h @@ -29,6 +29,7 @@ #include #include #include +#include #include #include #include @@ -69,8 +70,8 @@ class RCLConsensus // The timestamp of the last validation we used NetClock::time_point lastValidationTime_; - // Largest sequence number fully validated - LedgerIndex largestFullValidationSeq_ = 0; + // Enforces invariants on issuing full validations + FullSeqEnforcer fullSeqEnforcer_; // These members are queried via public accesors and are atomic for // thread safety. diff --git a/src/ripple/consensus/Validations.h b/src/ripple/consensus/Validations.h index 47d669e4ad1..5e62bfa8e9f 100644 --- a/src/ripple/consensus/Validations.h +++ b/src/ripple/consensus/Validations.h @@ -73,6 +73,43 @@ struct ValidationParms std::chrono::seconds validationSET_EXPIRES = std::chrono::minutes{10}; }; +/** Enforce full validation increasing sequence requirement. + + Helper class for enforcing that a full validation must be larger than all + unexpired full validation sequence numbers previously received from the + issuing validator tracked by the instance of this class. +*/ +template +class FullSeqEnforcer +{ + using time_point = std::chrono::steady_clock::time_point; + Seq seq_{0}; + time_point when_; +public: + /** Try advancing the largest observed full validation ledger sequence + + Try setting the largest full sequence observed, but return false if it + violates the invaraint that a full validation must be larger than all + unexpired full validation sequence numbers. + + @param now The current time + @param s The sequence number we want to fully validate + @param p Validation parameters + + @return Whether the validation can be marked full + */ + bool + tryAdvance(time_point now, Seq s, ValidationParms const & p) + { + if(now > (when_ + p.validationSET_EXPIRES)) + seq_ = Seq{0}; + if(seq_ != Seq{0} && s <= seq_) + return false; + seq_ = s; + when_ = now; + return true; + } +}; /** Whether a validation is still current Determines whether a validation can still be considered the current @@ -269,7 +306,7 @@ class Validations hash_map current_; // Sequence of the largest full validation received from each node - hash_map largestFullValidation_; + hash_map> fullSeqEnforcers_; //! Validations from listed nodes, indexed by ledger id (partial and full) beast::aged_unordered_map< @@ -536,16 +573,14 @@ class Validations { ScopedLock lock{mutex_}; - // Ensure full validations are for increasing sequence numbers + // Check that full validation is greater than any non-expired + // full validations if (val.full() && val.seq() != Seq{0}) { - auto const ins = largestFullValidation_.emplace(key, val.seq()); - if (!ins.second) - { - if (val.seq() <= ins.first->second) - return ValStatus::badFullSeq; - ins.first->second = val.seq(); - } + auto const now = byLedger_.clock().now(); + FullSeqEnforcer& enforcer = fullSeqEnforcers_[key]; + if (!enforcer.tryAdvance(now, val.seq(), parms_)) + return ValStatus::badFullSeq; } // This validation is a repeat if we already have diff --git a/src/test/consensus/Consensus_test.cpp b/src/test/consensus/Consensus_test.cpp index 5df162aa8b6..18d86805d66 100644 --- a/src/test/consensus/Consensus_test.cpp +++ b/src/test/consensus/Consensus_test.cpp @@ -992,6 +992,7 @@ class Consensus_test : public beast::unit_test::suite BEAST_EXPECT(sim.synchronized(groupNotFastC) == 1); } } + void run() override { diff --git a/src/test/consensus/Validations_test.cpp b/src/test/consensus/Validations_test.cpp index de76d2f9e1b..30e35ca5c6d 100644 --- a/src/test/consensus/Validations_test.cpp +++ b/src/test/consensus/Validations_test.cpp @@ -302,6 +302,7 @@ class Validations_test : public beast::unit_test::suite LedgerHistoryHelper h; Ledger ledgerA = h["a"]; Ledger ledgerAB = h["ab"]; + Ledger ledgerAZ = h["az"]; Ledger ledgerABC = h["abc"]; Ledger ledgerABCD = h["abcd"]; Ledger ledgerABCDE = h["abcde"]; @@ -427,6 +428,14 @@ class Validations_test : public beast::unit_test::suite ValStatus::badFullSeq == harness.add(n.validate(ledgerAB))); BEAST_EXPECT( ValStatus::current == harness.add(n.partial(ledgerAB))); + // If we advance far enough for AB to expire, we can fully validate + // that sequence number again + BEAST_EXPECT( + ValStatus::badFullSeq == harness.add(n.validate(ledgerAZ))); + harness.clock().advance( + harness.parms().validationSET_EXPIRES + 1ms); + BEAST_EXPECT( + ValStatus::current == harness.add(n.validate(ledgerAZ))); } } @@ -986,6 +995,28 @@ class Validations_test : public beast::unit_test::suite BEAST_EXPECT(harness.vals().numTrustedForLedger(ledgerA.id()) == 1); } + void + testFullSeqEnforcer() + { + testcase("FullSeqEnforcer"); + using Seq = Ledger::Seq; + using namespace std::chrono; + + beast::manual_clock clock; + FullSeqEnforcer enforcer; + + ValidationParms p; + + BEAST_EXPECT(enforcer.tryAdvance(clock.now(), Seq{1}, p)); + BEAST_EXPECT(enforcer.tryAdvance(clock.now(), Seq{10}, p)); + BEAST_EXPECT(!enforcer.tryAdvance(clock.now(), Seq{9}, p)); + BEAST_EXPECT(!enforcer.tryAdvance(clock.now(), Seq{5}, p)); + clock.advance(p.validationSET_EXPIRES - 1ms); + BEAST_EXPECT(!enforcer.tryAdvance(clock.now(), Seq{1}, p)); + clock.advance(2ms); + BEAST_EXPECT(enforcer.tryAdvance(clock.now(), Seq{1}, p)); + } + void run() override { @@ -1001,6 +1032,7 @@ class Validations_test : public beast::unit_test::suite testGetPreferredLCL(); testAcquireValidatedLedger(); testNumTrustedForLedger(); + testFullSeqEnforcer(); } }; diff --git a/src/test/csf/Peer.h b/src/test/csf/Peer.h index 0fb950860dd..4538353019b 100644 --- a/src/test/csf/Peer.h +++ b/src/test/csf/Peer.h @@ -238,8 +238,8 @@ struct Peer //! Whether to simulate running as validator or a tracking node bool runAsValidator = true; - //! Sequence number of largest full validation thus far - Ledger::Seq largestFullValidation{0}; + //! Enforce invariants on full validation sequence numbers + FullSeqEnforcer fullSeqEnforcer; //TODO: Consider removing these two, they are only a convenience for tests // Number of proposers in the prior round @@ -575,10 +575,9 @@ struct Peer if (runAsValidator && isCompatible && !consensusFail) { // Can only send one fully validated ledger per seq - bool isFull = - proposing && newLedger.seq() > largestFullValidation; - largestFullValidation = - std::max(largestFullValidation, newLedger.seq()); + bool isFull = proposing && + fullSeqEnforcer.tryAdvance( + scheduler.now(), newLedger.seq(), validations.parms()); Validation v{newLedger.id(), newLedger.seq(), From fd58f28b51a4dbc7709fbba22e8e69a9849cf86a Mon Sep 17 00:00:00 2001 From: Brad Chase Date: Wed, 27 Dec 2017 14:11:41 -0500 Subject: [PATCH 24/38] [FOLD] Address PR comments --- src/ripple/app/consensus/RCLConsensus.cpp | 17 +++--- src/ripple/app/consensus/RCLValidations.cpp | 59 ++++++--------------- src/ripple/app/consensus/RCLValidations.h | 34 ++++-------- src/ripple/app/misc/NetworkOPs.cpp | 5 +- src/ripple/consensus/LedgerTrie.h | 2 +- src/ripple/consensus/Validations.h | 52 ++++-------------- src/test/app/RCLValidations_test.cpp | 8 +-- src/test/consensus/Validations_test.cpp | 12 ++--- src/test/csf/Peer.h | 7 +-- 9 files changed, 62 insertions(+), 134 deletions(-) diff --git a/src/ripple/app/consensus/RCLConsensus.cpp b/src/ripple/app/consensus/RCLConsensus.cpp index 90d0dfb76c5..55e2b4302ab 100644 --- a/src/ripple/app/consensus/RCLConsensus.cpp +++ b/src/ripple/app/consensus/RCLConsensus.cpp @@ -237,7 +237,9 @@ RCLConsensus::Adaptor::proposersFinished( RCLCxLedger const& ledger, LedgerHash const& h) const { - return getNodesAfter(app_.getValidations(), ledger.ledger_, h); + RCLValidations& vals = app_.getValidations(); + return vals.getNodesAfter( + RCLValidatedLedger(ledger.ledger_, vals.adaptor().journal()), h); } uint256 @@ -246,12 +248,12 @@ RCLConsensus::Adaptor::getPrevLedger( RCLCxLedger const& ledger, ConsensusMode mode) { - uint256 netLgr = getPreferred( - app_.getValidations(), - ledger.ledger_, + RCLValidations& vals = app_.getValidations(); + uint256 netLgr = vals.getPreferred( + RCLValidatedLedger{ledger.ledger_, vals.adaptor().journal()}, ledgerMaster_.getValidLedgerIndex()); - if (netLgr != ledgerID && netLgr != uint256{}) + if (netLgr != ledgerID) { if (mode != ConsensusMode::wrongLedger) app_.getOPs().consensusViewChange(); @@ -831,8 +833,9 @@ RCLConsensus::Adaptor::validate(RCLCxLedger const& ledger, bool proposing) // Can only fully validate if proposed and increasing full sequence invariant // satisfied - const bool isFull = proposing && - fullSeqEnforcer_.tryAdvance( + const bool isFull = + proposing && + fullSeqEnforcer_( stopwatch().now(), ledger.seq(), app_.getValidations().parms()); // Build validation diff --git a/src/ripple/app/consensus/RCLValidations.cpp b/src/ripple/app/consensus/RCLValidations.cpp index 527ae4ce31f..ac45a4f58a1 100644 --- a/src/ripple/app/consensus/RCLValidations.cpp +++ b/src/ripple/app/consensus/RCLValidations.cpp @@ -38,20 +38,24 @@ namespace ripple { +RCLValidatedLedger::RCLValidatedLedger(MakeGenesis) + : ledgerID_{0}, ledgerSeq_{0} +{ +} + RCLValidatedLedger::RCLValidatedLedger( - std::shared_ptr ledger, + std::shared_ptr const& ledger, beast::Journal j) - : ledger_{std::move(ledger)}, j_{j} + : ledgerID_{ledger->info().hash}, ledgerSeq_{ledger->seq()}, j_{j} { - auto const hashIndex = ledger_->read(keylet::skip()); + 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 " << ledger_->seq() << ":" - << ledger_->info().hash + JLOG(j_.warn()) << "Ledger " << ledgerSeq_ << ":" << ledgerID_ << " missing recent ancestor hashes"; } @@ -64,28 +68,28 @@ RCLValidatedLedger::minSeq() const -> Seq auto RCLValidatedLedger::seq() const -> Seq { - return ledger_ ? ledger_->info().seq : Seq{0}; + return ledgerSeq_; } auto RCLValidatedLedger::id() const -> ID { - return ledger_ ? ledger_->info().hash : ID{0}; + return ledgerID_; } auto RCLValidatedLedger::operator[](Seq const& s) const -> ID { - if (ledger_ && s >= minSeq() && s <= seq()) + if (s >= minSeq() && s <= seq()) { if (s == seq()) - return ledger_->info().hash; + return ledgerID_; Seq const diff = seq() - s; if (ancestors_.size() >= diff) return ancestors_[ancestors_.size() - diff]; } JLOG(j_.warn()) << "Unable to determine hash of ancestor seq=" << s - << " from ledger hash=" << ledger_->info().hash - << " seq=" << ledger_->info().seq; + << " from ledger hash=" << ledgerID_ + << " seq=" << ledgerSeq_; // Default ID that is less than all others return ID{}; } @@ -345,38 +349,5 @@ handleNewValidation(Application& app, return shouldRelay; } -std::size_t -getNodesAfter( - RCLValidations& vals, - std::shared_ptr ledger, - uint256 const& ledgerID) -{ - return vals.getNodesAfter( - RCLValidatedLedger{std::move(ledger), vals.adaptor().journal()}, - ledgerID); -} - -uint256 -getPreferred( - RCLValidations& vals, - std::shared_ptr ledger, - LedgerIndex minValidSeq) -{ - return vals.getPreferred( - RCLValidatedLedger{std::move(ledger), vals.adaptor().journal()}, - minValidSeq); -} -uint256 -getPreferredLCL( - RCLValidations& vals, - std::shared_ptr ledger, - LedgerIndex minSeq, - hash_map const& peerCounts) -{ - return vals.getPreferredLCL( - RCLValidatedLedger{std::move(ledger), vals.adaptor().journal()}, - minSeq, - peerCounts); -} } // namespace ripple diff --git a/src/ripple/app/consensus/RCLValidations.h b/src/ripple/app/consensus/RCLValidations.h index 7b441d835d8..1d2a8aa7790 100644 --- a/src/ripple/app/consensus/RCLValidations.h +++ b/src/ripple/app/consensus/RCLValidations.h @@ -138,11 +138,15 @@ class RCLValidatedLedger public: using ID = LedgerHash; using Seq = LedgerIndex; - struct MakeGenesis{}; + struct MakeGenesis + { + }; - RCLValidatedLedger(MakeGenesis) {}; + RCLValidatedLedger(MakeGenesis); - RCLValidatedLedger(std::shared_ptr ledger, beast::Journal j); + RCLValidatedLedger( + std::shared_ptr const& ledger, + beast::Journal j); /// The sequence (index) of the ledger Seq @@ -168,7 +172,8 @@ class RCLValidatedLedger minSeq() const; private: - std::shared_ptr ledger_; + ID ledgerID_; + Seq ledgerSeq_; std::vector ancestors_; beast::Journal j_; }; @@ -261,27 +266,6 @@ handleNewValidation( STValidation::ref val, std::string const& source); -// @see Validations::getNodesAfter -std::size_t -getNodesAfter( - RCLValidations& vals, - std::shared_ptr ledger, - uint256 const& ledgerID); - -// @see Validations::getPreferred -uint256 -getPreferred( - RCLValidations& vals, - std::shared_ptr ledger, - LedgerIndex minSeq); - -// @see Validations::getPreferredLCL -uint256 -getPreferredLCL(RCLValidations& vals, - std::shared_ptr ledger, - LedgerIndex minSeq, - hash_map const & peerCounts); - } // namespace ripple #endif diff --git a/src/ripple/app/misc/NetworkOPs.cpp b/src/ripple/app/misc/NetworkOPs.cpp index 330d39fb53b..583573880c5 100644 --- a/src/ripple/app/misc/NetworkOPs.cpp +++ b/src/ripple/app/misc/NetworkOPs.cpp @@ -1297,9 +1297,8 @@ bool NetworkOPsImp::checkLastClosedLedger ( for(auto const & it: peerCounts) JLOG(m_journal.debug()) << "L: " << it.first << " n=" << it.second; - uint256 preferredLCL = getPreferredLCL( - validations, - ourClosed, + uint256 preferredLCL = validations.getPreferredLCL( + RCLValidatedLedger{ourClosed, validations.adaptor().journal()}, m_ledgerMaster.getValidLedgerIndex(), peerCounts); diff --git a/src/ripple/consensus/LedgerTrie.h b/src/ripple/consensus/LedgerTrie.h index ba7a0d3e89f..26be8d3048a 100644 --- a/src/ripple/consensus/LedgerTrie.h +++ b/src/ripple/consensus/LedgerTrie.h @@ -20,10 +20,10 @@ #ifndef RIPPLE_APP_CONSENSUS_LEDGERS_TRIE_H_INCLUDED #define RIPPLE_APP_CONSENSUS_LEDGERS_TRIE_H_INCLUDED +#include #include #include #include -#include namespace ripple { diff --git a/src/ripple/consensus/Validations.h b/src/ripple/consensus/Validations.h index 5e62bfa8e9f..e78d288c058 100644 --- a/src/ripple/consensus/Validations.h +++ b/src/ripple/consensus/Validations.h @@ -89,7 +89,7 @@ class FullSeqEnforcer /** Try advancing the largest observed full validation ledger sequence Try setting the largest full sequence observed, but return false if it - violates the invaraint that a full validation must be larger than all + violates the invariant that a full validation must be larger than all unexpired full validation sequence numbers. @param now The current time @@ -99,7 +99,7 @@ class FullSeqEnforcer @return Whether the validation can be marked full */ bool - tryAdvance(time_point now, Seq s, ValidationParms const & p) + operator()(time_point now, Seq s, ValidationParms const & p) { if(now > (when_ + p.validationSET_EXPIRES)) seq_ = Seq{0}; @@ -139,36 +139,6 @@ isCurrent( (seenTime < (now + p.validationCURRENT_LOCAL))); } -/** Determine the preferred ledger based on its support - - @param current The current ledger the node follows - @param dist Ledger IDs and corresponding counts of support - @return The ID of the ledger with most support, preferring to stick with - current ledger in the case of equal support -*/ -template -inline ID -getPreferredLedger( - ID const& current, - hash_map const& dist) -{ - ID netLgr = current; - int netLgrCount = 0; - for (auto const& it : dist) - { - // Switch to ledger supported by more peers - // On a tie, prefer the current ledger, or the one with higher ID - if ((it.second > netLgrCount) || - ((it.second == netLgrCount) && - ((it.first == current) || - (it.first > netLgr && netLgr != current)))) - { - netLgr = it.first; - netLgrCount = it.second; - } - } - return netLgr; -} /** Status of newly received validation */ @@ -579,7 +549,7 @@ class Validations { auto const now = byLedger_.clock().now(); FullSeqEnforcer& enforcer = fullSeqEnforcers_[key]; - if (!enforcer.tryAdvance(now, val.seq(), parms_)) + if (!enforcer(now, val.seq(), parms_)) return ValStatus::badFullSeq; } @@ -772,14 +742,14 @@ class Validations }); // Count parent ledgers as fallback - std::size_t count = 0; - for (auto const& it : lastLedger_) - { - Ledger const& curr = it.second; - if (curr.seq() > Seq{0} && curr[curr.seq() - Seq{1}] == ledgerID) - ++count; - } - return count; + return std::count_if( + lastLedger_.begin(), + lastLedger_.end(), + [&ledgerID](auto const& it) { + auto const& curr = it.second; + return curr.seq() > Seq{0} && + curr[curr.seq() - Seq{1}] == ledgerID; + }); } /** Get the currently trusted full validations diff --git a/src/test/app/RCLValidations_test.cpp b/src/test/app/RCLValidations_test.cpp index 8eb1858063c..6581994e6c7 100644 --- a/src/test/app/RCLValidations_test.cpp +++ b/src/test/app/RCLValidations_test.cpp @@ -49,11 +49,11 @@ class RCLValidations_test : public beast::unit_test::suite // Generate two ledger histories that agree on the first maxAncestors // ledgers, then diverge. - std::vector> history; + std::vector> history; jtx::Env env(*this); Config config; - auto prev = std::make_shared( + auto prev = std::make_shared( create_genesis, config, std::vector{}, env.app().family()); history.push_back(prev); @@ -69,7 +69,7 @@ class RCLValidations_test : public beast::unit_test::suite // altHistory agrees with first half of regular history Seq const diverge = history.size()/2; - std::vector> altHistory( + std::vector> altHistory( history.begin(), history.begin() + diverge); // advance clock too get new ledgers env.timeKeeper().set(env.timeKeeper().now() + 1200s); @@ -105,7 +105,7 @@ class RCLValidations_test : public beast::unit_test::suite // Full history ledgers { - std::shared_ptr ledger = history.back(); + std::shared_ptr ledger = history.back(); RCLValidatedLedger a{ledger, j}; BEAST_EXPECT(a.seq() == ledger->info().seq); BEAST_EXPECT( diff --git a/src/test/consensus/Validations_test.cpp b/src/test/consensus/Validations_test.cpp index 30e35ca5c6d..5dc4ad452ab 100644 --- a/src/test/consensus/Validations_test.cpp +++ b/src/test/consensus/Validations_test.cpp @@ -1007,14 +1007,14 @@ class Validations_test : public beast::unit_test::suite ValidationParms p; - BEAST_EXPECT(enforcer.tryAdvance(clock.now(), Seq{1}, p)); - BEAST_EXPECT(enforcer.tryAdvance(clock.now(), Seq{10}, p)); - BEAST_EXPECT(!enforcer.tryAdvance(clock.now(), Seq{9}, p)); - BEAST_EXPECT(!enforcer.tryAdvance(clock.now(), Seq{5}, p)); + BEAST_EXPECT(enforcer(clock.now(), Seq{1}, p)); + BEAST_EXPECT(enforcer(clock.now(), Seq{10}, p)); + BEAST_EXPECT(!enforcer(clock.now(), Seq{9}, p)); + BEAST_EXPECT(!enforcer(clock.now(), Seq{5}, p)); clock.advance(p.validationSET_EXPIRES - 1ms); - BEAST_EXPECT(!enforcer.tryAdvance(clock.now(), Seq{1}, p)); + BEAST_EXPECT(!enforcer(clock.now(), Seq{1}, p)); clock.advance(2ms); - BEAST_EXPECT(enforcer.tryAdvance(clock.now(), Seq{1}, p)); + BEAST_EXPECT(enforcer(clock.now(), Seq{1}, p)); } void diff --git a/src/test/csf/Peer.h b/src/test/csf/Peer.h index 4538353019b..ff5fd198a69 100644 --- a/src/test/csf/Peer.h +++ b/src/test/csf/Peer.h @@ -575,8 +575,9 @@ struct Peer if (runAsValidator && isCompatible && !consensusFail) { // Can only send one fully validated ledger per seq - bool isFull = proposing && - fullSeqEnforcer.tryAdvance( + bool isFull = + proposing && + fullSeqEnforcer( scheduler.now(), newLedger.seq(), validations.parms()); Validation v{newLedger.id(), @@ -630,7 +631,7 @@ struct Peer Ledger::ID const netLgr = validations.getPreferred(ledger, earliestAllowedSeq()); - if (netLgr != ledgerID && netLgr != Ledger::ID{}) + if (netLgr != ledgerID) { JLOG(j.trace()) << Json::Compact(validations.getJsonTrie()); issue(WrongPrevLedger{ledgerID, netLgr}); From 2b2ba69af5a14e85f8c642b69dc189a29ad66a75 Mon Sep 17 00:00:00 2001 From: Brad Chase Date: Thu, 28 Dec 2017 09:31:52 -0500 Subject: [PATCH 25/38] [FOLD] Explicitly initialize zero IDs --- src/ripple/app/consensus/RCLValidations.cpp | 2 +- src/ripple/app/consensus/RCLValidations.h | 2 +- src/ripple/consensus/Validations.h | 10 +++++----- src/test/csf/Peer.h | 2 +- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/ripple/app/consensus/RCLValidations.cpp b/src/ripple/app/consensus/RCLValidations.cpp index ac45a4f58a1..155222738f8 100644 --- a/src/ripple/app/consensus/RCLValidations.cpp +++ b/src/ripple/app/consensus/RCLValidations.cpp @@ -91,7 +91,7 @@ auto RCLValidatedLedger::operator[](Seq const& s) const -> ID << " from ledger hash=" << ledgerID_ << " seq=" << ledgerSeq_; // Default ID that is less than all others - return ID{}; + return ID{0}; } // Return the sequence number of the earliest possible mismatching ancestor diff --git a/src/ripple/app/consensus/RCLValidations.h b/src/ripple/app/consensus/RCLValidations.h index 1d2a8aa7790..86e50a1a52a 100644 --- a/src/ripple/app/consensus/RCLValidations.h +++ b/src/ripple/app/consensus/RCLValidations.h @@ -160,7 +160,7 @@ class RCLValidatedLedger @param s The sequence (index) of the ancestor @return The ID of this ledger's ancestor with that sequence number or - ID{} if one was not determined + ID{0} if one was not determined */ ID operator[](Seq const& s) const; diff --git a/src/ripple/consensus/Validations.h b/src/ripple/consensus/Validations.h index e78d288c058..717bf7b3fee 100644 --- a/src/ripple/consensus/Validations.h +++ b/src/ripple/consensus/Validations.h @@ -611,14 +611,14 @@ class Validations @param currLedger The local nodes current working ledger @return The sequence and id of the preferred working ledger, - or Seq{0},ID{} if no trusted validations are available to + or Seq{0},ID{0} if no trusted validations are available to determine the preferred ledger. */ std::pair getPreferred(Ledger const& currLedger) { - Seq preferredSeq; - ID preferredID; + Seq preferredSeq{0}; + ID preferredID{0}; ScopedLock lock{mutex_}; std::tie(preferredSeq, preferredID) = withTrie( @@ -671,7 +671,7 @@ class Validations getPreferred(Ledger const& currLedger, Seq minValidSeq) { std::pair preferred = getPreferred(currLedger); - if(preferred.first >= minValidSeq && preferred.second != ID{}) + if(preferred.first >= minValidSeq && preferred.second != ID{0}) return preferred.second; return currLedger.id(); @@ -702,7 +702,7 @@ class Validations std::pair preferred = getPreferred(lcl); // Trusted validations exist - if (preferred.second != ID{} && preferred.first > Seq{0}) + if (preferred.second != ID{0} && preferred.first > Seq{0}) return (preferred.first >= minSeq) ? preferred.second : lcl.id(); // Otherwise, rely on peer ledgers diff --git a/src/test/csf/Peer.h b/src/test/csf/Peer.h index ff5fd198a69..776e03b6fa9 100644 --- a/src/test/csf/Peer.h +++ b/src/test/csf/Peer.h @@ -872,7 +872,7 @@ struct Peer // yet Ledger::ID bestLCL = validations.getPreferred(lastClosedLedger, earliestAllowedSeq()); - if(bestLCL == Ledger::ID{}) + if(bestLCL == Ledger::ID{0}) bestLCL = lastClosedLedger.id(); issue(StartRound{bestLCL, lastClosedLedger}); From 0b58ac4d5a164887d80eb57c9ea53986e449905d Mon Sep 17 00:00:00 2001 From: Brad Chase Date: Mon, 8 Jan 2018 10:29:35 -0500 Subject: [PATCH 26/38] [FOLD] Address PR comments: Eliminate empty() span possibility. --- src/ripple/consensus/LedgerTrie.h | 88 +++++++++++++++++++------------ 1 file changed, 54 insertions(+), 34 deletions(-) diff --git a/src/ripple/consensus/LedgerTrie.h b/src/ripple/consensus/LedgerTrie.h index 26be8d3048a..ec4e2c56f41 100644 --- a/src/ripple/consensus/LedgerTrie.h +++ b/src/ripple/consensus/LedgerTrie.h @@ -21,6 +21,7 @@ #define RIPPLE_APP_CONSENSUS_LEDGERS_TRIE_H_INCLUDED #include +#include #include #include #include @@ -144,26 +145,20 @@ class LedgerTrie return end_; } - // Return the Span from (spot,end_] - Span + // Return the Span from [spot,end_) or none if no such valid span + boost::optional from(Seq spot) const { return sub(spot, end_); } - // Return the Span from (start_,spot] - Span + // Return the Span from [start_,spot) or none if no such valid span + boost::optional before(Seq spot) const { return sub(start_, spot); } - bool - empty() const - { - return start_ == end_; - } - //Return the ID of the ledger that starts this span ID startID() const @@ -191,7 +186,8 @@ class LedgerTrie Span(Seq start, Seq end, Ledger const& l) : start_{start}, end_{end}, ledger_{l} { - assert(start <= end); + // Spans cannot be empty + assert(start < end); } Seq @@ -201,10 +197,14 @@ class LedgerTrie }; // Return a span of this over the half-open interval [from,to) - Span + boost::optional sub(Seq from, Seq to) const { - return Span(clamp(from), clamp(to), ledger_); + Seq newFrom = clamp(from); + Seq newTo = clamp(to); + if(newFrom < newTo) + return Span(newFrom, newTo, ledger_); + return boost::none; } friend std::ostream& @@ -261,8 +261,7 @@ class LedgerTrie return curr.get() == child; }); assert(it != children.end()); - using std::swap; - swap(*it, children.back()); + std::swap(*it, children.back()); children.pop_back(); } @@ -372,41 +371,62 @@ class LedgerTrie // There is always a place to insert assert(loc); - Span lTmp{ledger}; - Span prefix = lTmp.before(diffSeq); - Span oldSuffix = loc->span.from(diffSeq); - Span newSuffix = lTmp.from(diffSeq); + // Node from which to start incrementing branchSupport Node* incNode = loc; - if (!oldSuffix.empty()) + // loc->span has the longest common prefix with Span{ledger} of all + // existing nodes in the trie. The optional's below represent + // the possible common suffixes between loc->span and Span{ledger}. + // + // loc->span + // a b c | d e f + // prefix | oldSuffix + // + // Span{ledger} + // a b c | g h i + // prefix | newSuffix + + boost::optional prefix = loc->span.before(diffSeq); + boost::optional oldSuffix = loc->span.from(diffSeq); + boost::optional newSuffix = Span{ledger}.from(diffSeq); + + + if (oldSuffix) { - // new is a prefix of current - // e.g. abcdef->..., adding abcd - // becomes abcd->ef->... + // Have + // abcdef -> .... + // Inserting + // abc + // Becomes + // abc -> def -> ... // Create oldSuffix node that takes over loc - auto newNode = std::make_unique(oldSuffix); + auto newNode = std::make_unique(*oldSuffix); newNode->tipSupport = loc->tipSupport; newNode->branchSupport = loc->branchSupport; - using std::swap; - swap(newNode->children, loc->children); + newNode->children = std::move(loc->children); + assert(loc->children.empty()); for(std::unique_ptr & child : newNode->children) child->parent = newNode.get(); // Loc truncates to prefix and newNode is its child - loc->span = prefix; + assert(!prefix.empty()); + loc->span = *prefix; newNode->parent = loc; loc->children.emplace_back(std::move(newNode)); loc->tipSupport = 0; } - if (!newSuffix.empty()) + if (newSuffix) { - // current is a substring of new - // e.g. abc->... adding abcde - // -> abc-> ... - // -> de - - auto newNode = std::make_unique(newSuffix); + // Have + // abc -> ... + // Inserting + // abcdef-> ... + // Becomes + // abc -> ... + // \-> def + + auto newNode = std::make_unique(*newSuffix); newNode->parent = loc; // increment support starting from the new node incNode = newNode.get(); From 7cdd0e7385204c137178ca1f07cca3e5bcf99cbb Mon Sep 17 00:00:00 2001 From: Brad Chase Date: Mon, 8 Jan 2018 10:50:32 -0500 Subject: [PATCH 27/38] [FOLD] Move support classes to namespace --- src/ripple/consensus/LedgerTrie.h | 366 +++++++++++++++--------------- 1 file changed, 187 insertions(+), 179 deletions(-) diff --git a/src/ripple/consensus/LedgerTrie.h b/src/ripple/consensus/LedgerTrie.h index ec4e2c56f41..546780bff92 100644 --- a/src/ripple/consensus/LedgerTrie.h +++ b/src/ripple/consensus/LedgerTrie.h @@ -28,6 +28,191 @@ namespace ripple { +namespace ledger_trie_detail { +// Represents a span of ancestry of a ledger +template +class Span +{ + using Seq = typename Ledger::Seq; + using ID = typename Ledger::ID; + + // The span is the half-open interval [start,end) of ledger_ + Seq start_{0}; + Seq end_{1}; + Ledger ledger_; + +public: + Span() : ledger_{typename Ledger::MakeGenesis{}} + { + // Require default ledger to be genesis seq + assert(ledger_.seq() == start_); + } + + Span(Ledger ledger) + : start_{0}, end_{ledger.seq() + Seq{1}}, ledger_{std::move(ledger)} + { + } + + Span(Span const& s) = default; + Span(Span&& s) = default; + Span& + operator=(Span const&) = default; + Span& + operator=(Span&&) = default; + + Seq + end() const + { + return end_; + } + + // Return the Span from [spot,end_) or none if no such valid span + boost::optional + from(Seq spot) const + { + return sub(spot, end_); + } + + // Return the Span from [start_,spot) or none if no such valid span + boost::optional + before(Seq spot) const + { + return sub(start_, spot); + } + + // Return the ID of the ledger that starts this span + ID + startID() const + { + return ledger_[start_]; + } + + // Return the ledger sequence number of the first possible difference + // between this span and a given ledger. + Seq + diff(Ledger const& o) const + { + return clamp(mismatch(ledger_, o)); + } + + // The Seq and ID of the end of the span + std::pair + tip() const + { + Seq tipSeq{end_ - Seq{1}}; + return {tipSeq, ledger_[tipSeq]}; + } + +private: + Span(Seq start, Seq end, Ledger const& l) + : start_{start}, end_{end}, ledger_{l} + { + // Spans cannot be empty + assert(start < end); + } + + Seq + clamp(Seq val) const + { + return std::min(std::max(start_, val), end_); + }; + + // Return a span of this over the half-open interval [from,to) + boost::optional + sub(Seq from, Seq to) const + { + Seq newFrom = clamp(from); + Seq newTo = clamp(to); + if (newFrom < newTo) + return Span(newFrom, newTo, ledger_); + return boost::none; + } + + friend std::ostream& + operator<<(std::ostream& o, Span const& s) + { + return o << s.tip().second << "[" << s.start_ << "," << s.end_ << ")"; + } + + friend Span + merge(Span const& a, Span const& b) + { + // Return combined span, using ledger_ from higher sequence span + if (a.end_ < b.end_) + return Span(std::min(a.start_, b.start_), b.end_, b.ledger_); + + return Span(std::min(a.start_, b.start_), a.end_, a.ledger_); + } +}; + +// A node in the trie +template +struct Node +{ + Node() = default; + + explicit Node(Ledger const& l) : span{l}, tipSupport{1}, branchSupport{1} + { + } + + explicit Node(Span s) : span{std::move(s)} + { + } + + Span span; + std::uint32_t tipSupport = 0; + std::uint32_t branchSupport = 0; + + std::vector> children; + Node* parent = nullptr; + + /** Remove the given node from this Node's children + + @param child The address of the child node to remove + @note The child must be a member of the vector. The passed pointer + will be dangling as a result of this call + */ + void + erase(Node const* child) + { + auto it = std::find_if( + children.begin(), + children.end(), + [child](std::unique_ptr const& curr) { + return curr.get() == child; + }); + assert(it != children.end()); + std::swap(*it, children.back()); + children.pop_back(); + } + + friend std::ostream& + operator<<(std::ostream& o, Node const& s) + { + return o << s.span << "(T:" << s.tipSupport << ",B:" << s.branchSupport + << ")"; + } + + Json::Value + getJson() const + { + Json::Value res; + res["id"] = to_string(span.tip().second); + res["seq"] = static_cast(span.tip().first); + 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 @@ -112,185 +297,8 @@ class LedgerTrie using Seq = typename Ledger::Seq; using ID = typename Ledger::ID; - /// Represents a span of ancestry of a ledger - class Span - { - // 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 - end() const - { - return end_; - } - - // Return the Span from [spot,end_) or none if no such valid span - boost::optional - from(Seq spot) const - { - return sub(spot, end_); - } - - // Return the Span from [start_,spot) or none if no such valid span - boost::optional - before(Seq spot) const - { - return sub(start_, spot); - } - - //Return the ID of the ledger that starts this span - ID - startID() const - { - return ledger_[start_]; - } - - // Return the ledger sequence number of the first possible difference - // between this span and a given ledger. - Seq - diff(Ledger const& o) const - { - return clamp(mismatch(ledger_, o)); - } - - // The Seq and ID of the end of the span - std::pair - tip() const - { - Seq tipSeq{end_ - Seq{1}}; - return {tipSeq, ledger_[tipSeq]}; - } - - private: - Span(Seq start, Seq end, Ledger const& l) - : start_{start}, end_{end}, ledger_{l} - { - // Spans cannot be empty - assert(start < end); - } - - Seq - clamp(Seq val) const - { - return std::min(std::max(start_, val), end_); - }; - - // Return a span of this over the half-open interval [from,to) - boost::optional - sub(Seq from, Seq to) const - { - Seq newFrom = clamp(from); - Seq newTo = clamp(to); - if(newFrom < newTo) - return Span(newFrom, newTo, ledger_); - return boost::none; - } - - friend std::ostream& - operator<<(std::ostream& o, Span const& s) - { - return o << s.tip().second << "[" << 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 - struct Node - { - Node() = default; - - explicit Node(Ledger const& l) : span{l}, tipSupport{1}, branchSupport{1} - { - } - - explicit Node(Span s) : span{std::move(s)} - { - } - - Span span; - std::uint32_t tipSupport = 0; - std::uint32_t branchSupport = 0; - - std::vector> children; - Node* parent = nullptr; - - /** Remove the given node from this Node's children - - @param child The address of the child node to remove - @note The child must be a member of the vector. The passed pointer - will be dangling as a result of this call - */ - void - erase(Node const* child) - { - auto it = std::find_if( - children.begin(), - children.end(), - [child](std::unique_ptr const& curr) { - return curr.get() == child; - }); - assert(it != children.end()); - std::swap(*it, children.back()); - children.pop_back(); - } - - friend std::ostream& - operator<<(std::ostream& o, Node const& s) - { - return o << s.span << "(T:" << s.tipSupport - << ",B:" << s.branchSupport << ")"; - } - - Json::Value - getJson() const - { - Json::Value res; - res["id"] = to_string(span.tip().second); - res["seq"] = static_cast(span.tip().first); - 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; - } - }; + using Node = ledger_trie_detail::Node; + using Span = ledger_trie_detail::Span; // The root of the trie. The root is allowed to break the no-single child // invariant. From 961747f2ad3394f52862cf71546a781f14636b0d Mon Sep 17 00:00:00 2001 From: Brad Chase Date: Mon, 8 Jan 2018 11:25:20 -0500 Subject: [PATCH 28/38] [FOLD] Fix assert --- src/ripple/consensus/LedgerTrie.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ripple/consensus/LedgerTrie.h b/src/ripple/consensus/LedgerTrie.h index 546780bff92..56c06e14bf5 100644 --- a/src/ripple/consensus/LedgerTrie.h +++ b/src/ripple/consensus/LedgerTrie.h @@ -418,7 +418,7 @@ class LedgerTrie child->parent = newNode.get(); // Loc truncates to prefix and newNode is its child - assert(!prefix.empty()); + assert(prefix); loc->span = *prefix; newNode->parent = loc; loc->children.emplace_back(std::move(newNode)); From 37c583df6b6138306c47f9f441d6900c36f9bb56 Mon Sep 17 00:00:00 2001 From: Brad Chase Date: Fri, 19 Jan 2018 09:50:54 -0500 Subject: [PATCH 29/38] [FOLD] Enforce increasing sequence invariant on all validations: Require all validations, full or partial, to be for a sequence number larger than any unexpired validations already issued by that node. --- src/ripple/app/consensus/RCLConsensus.cpp | 15 ++--- src/ripple/app/consensus/RCLConsensus.h | 4 -- src/ripple/app/consensus/RCLValidations.cpp | 6 +- src/ripple/consensus/Validations.h | 65 ++++++++++++------ src/test/consensus/Consensus_test.cpp | 2 +- src/test/consensus/Validations_test.cpp | 75 +++++++++++---------- src/test/csf/Peer.h | 16 ++--- 7 files changed, 103 insertions(+), 80 deletions(-) diff --git a/src/ripple/app/consensus/RCLConsensus.cpp b/src/ripple/app/consensus/RCLConsensus.cpp index 55e2b4302ab..996a3f8bd3d 100644 --- a/src/ripple/app/consensus/RCLConsensus.cpp +++ b/src/ripple/app/consensus/RCLConsensus.cpp @@ -446,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; @@ -831,16 +832,12 @@ RCLConsensus::Adaptor::validate(RCLCxLedger const& ledger, bool proposing) validationTime = lastValidationTime_ + 1s; lastValidationTime_ = validationTime; - // Can only fully validate if proposed and increasing full sequence invariant - // satisfied - const bool isFull = - proposing && - fullSeqEnforcer_( - stopwatch().now(), ledger.seq(), app_.getValidations().parms()); - // Build validation auto v = std::make_shared( - ledger.id(), validationTime, valPublic_, isFull); + ledger.id(), + validationTime, + valPublic_, + proposing /* full if proposed */); v->setFieldU32(sfLedgerSequence, ledger.seq()); // Add our load fee to the validation diff --git a/src/ripple/app/consensus/RCLConsensus.h b/src/ripple/app/consensus/RCLConsensus.h index 011702672df..8bded9a15fc 100644 --- a/src/ripple/app/consensus/RCLConsensus.h +++ b/src/ripple/app/consensus/RCLConsensus.h @@ -29,7 +29,6 @@ #include #include #include -#include #include #include #include @@ -70,9 +69,6 @@ class RCLConsensus // The timestamp of the last validation we used NetClock::time_point lastValidationTime_; - // Enforces invariants on issuing full validations - FullSeqEnforcer fullSeqEnforcer_; - // These members are queried via public accesors and are atomic for // thread safety. std::atomic validating_{false}; diff --git a/src/ripple/app/consensus/RCLValidations.cpp b/src/ripple/app/consensus/RCLValidations.cpp index 155222738f8..3bbb3eeed19 100644 --- a/src/ripple/app/consensus/RCLValidations.cpp +++ b/src/ripple/app/consensus/RCLValidations.cpp @@ -139,7 +139,7 @@ RCLValidationsAdaptor::acquire(LedgerHash const & hash) app_.getJobQueue().addJob( jtADVANCE, "getConsensusLedger", [pApp, hash](Job&) { pApp ->getInboundLedgers().acquire( - hash, 0, InboundLedger::fcVALIDATION); + hash, 0, InboundLedger::Reason::CONSENSUS); }); return boost::none; } @@ -316,10 +316,10 @@ handleNewValidation(Application& app, if(j.debug()) dmp(j.debug(), to_string(outcome)); - if(outcome == ValStatus::badFullSeq && j.warn()) + if(outcome == ValStatus::badSeq && j.warn()) { auto const seq = val->getFieldU32(sfLedgerSequence); - dmp(j.warn(), " already fully validated sequence past " + to_string(seq)); + dmp(j.warn(), " already validated sequence past " + to_string(seq)); } if (val->isTrusted() && outcome == ValStatus::current) diff --git a/src/ripple/consensus/Validations.h b/src/ripple/consensus/Validations.h index 717bf7b3fee..cd30f63a019 100644 --- a/src/ripple/consensus/Validations.h +++ b/src/ripple/consensus/Validations.h @@ -73,30 +73,30 @@ struct ValidationParms std::chrono::seconds validationSET_EXPIRES = std::chrono::minutes{10}; }; -/** Enforce full validation increasing sequence requirement. +/** Enforce validation increasing sequence requirement. - Helper class for enforcing that a full validation must be larger than all - unexpired full validation sequence numbers previously received from the - issuing validator tracked by the instance of this class. + Helper class for enforcing that a validation must be larger than all + unexpired validation sequence numbers previously issued by the validator + tracked by the instance of this class. */ template -class FullSeqEnforcer +class SeqEnforcer { using time_point = std::chrono::steady_clock::time_point; Seq seq_{0}; time_point when_; public: - /** Try advancing the largest observed full validation ledger sequence + /** Try advancing the largest observed validation ledger sequence - Try setting the largest full sequence observed, but return false if it - violates the invariant that a full validation must be larger than all - unexpired full validation sequence numbers. + Try setting the largest validation sequence observed, but return false + if it violates the invariant that a validation must be larger than all + unexpired validation sequence numbers. @param now The current time - @param s The sequence number we want to fully validate + @param s The sequence number we want to validate @param p Validation parameters - @return Whether the validation can be marked full + @return Whether the validation satisfies the invariant */ bool operator()(time_point now, Seq s, ValidationParms const & p) @@ -109,6 +109,12 @@ class FullSeqEnforcer when_ = now; return true; } + + Seq + largest() const + { + return seq_; + } }; /** Whether a validation is still current @@ -150,7 +156,7 @@ enum class ValStatus { /// Not current or was older than current from this node stale, /// A validation was marked full but it violates increasing seq requirement - badFullSeq + badSeq }; inline std::string @@ -164,8 +170,8 @@ to_string(ValStatus m) return "repeat"; case ValStatus::stale: return "stale"; - case ValStatus::badFullSeq: - return "badFullSeq"; + case ValStatus::badSeq: + return "badSeq"; default: return "unknown"; } @@ -275,8 +281,11 @@ class Validations // Validations from currently listed and trusted nodes (partial and full) hash_map current_; - // Sequence of the largest full validation received from each node - hash_map> fullSeqEnforcers_; + // Used to enforce the largest validation invariant for the local node + SeqEnforcer localSeqEnforcer_; + + // Sequence of the largest validation received from each node + hash_map> seqEnforcers_; //! Validations from listed nodes, indexed by ledger id (partial and full) beast::aged_unordered_map< @@ -523,6 +532,20 @@ class Validations return parms_; } + /** Return whether the local node can issue a validation for the given sequence + number + + @param s The sequence number of the ledger the node wants to validate + @return Whether the validation satisfies the invariant, updating the + largest sequence number seen accordingly + */ + bool + canValidateSeq(Seq const s) + { + ScopedLock lock{mutex_}; + return localSeqEnforcer_(byLedger_.clock().now(), s, parms_); + } + /** Add a new validation Attempt to add a new validation. @@ -543,14 +566,14 @@ class Validations { ScopedLock lock{mutex_}; - // Check that full validation is greater than any non-expired - // full validations - if (val.full() && val.seq() != Seq{0}) + // Check that validation sequence is greater than any non-expired + // validations sequence from that validator + if (val.seq() != Seq{0}) { auto const now = byLedger_.clock().now(); - FullSeqEnforcer& enforcer = fullSeqEnforcers_[key]; + SeqEnforcer& enforcer = seqEnforcers_[key]; if (!enforcer(now, val.seq(), parms_)) - return ValStatus::badFullSeq; + return ValStatus::badSeq; } // This validation is a repeat if we already have diff --git a/src/test/consensus/Consensus_test.cpp b/src/test/consensus/Consensus_test.cpp index 18d86805d66..cf47fd6a291 100644 --- a/src/test/consensus/Consensus_test.cpp +++ b/src/test/consensus/Consensus_test.cpp @@ -981,7 +981,7 @@ class Consensus_test : public beast::unit_test::suite // New approach will not fork and will resync once the fast node // reconnects for a few rounds network.connect(groupCfast, fDelay); - sim.run(2); + sim.run(4); BEAST_EXPECT(sim.synchronized()); BEAST_EXPECT(sim.branches() == 1); diff --git a/src/test/consensus/Validations_test.cpp b/src/test/consensus/Validations_test.cpp index 5dc4ad452ab..d377f5f06d0 100644 --- a/src/test/consensus/Validations_test.cpp +++ b/src/test/consensus/Validations_test.cpp @@ -318,7 +318,7 @@ class Validations_test : public beast::unit_test::suite // Re-adding violates the increasing seq requirement for full // validations - BEAST_EXPECT(ValStatus::badFullSeq == harness.add(v)); + BEAST_EXPECT(ValStatus::badSeq == harness.add(v)); harness.clock().advance(1s); // Replace with a new validation and ensure the old one is stale @@ -346,10 +346,10 @@ class Validations_test : public beast::unit_test::suite // Cannot re-do the same full validation sequence BEAST_EXPECT( - ValStatus::badFullSeq == harness.add(n.validate(ledgerAB))); - // Can send a new partial validation + ValStatus::badSeq == harness.add(n.validate(ledgerAB))); + // Cannot send the same partial validation sequence BEAST_EXPECT( - ValStatus::current == harness.add(n.partial(ledgerAB))); + ValStatus::badSeq == harness.add(n.partial(ledgerAB))); // Now trusts the newest ledger too harness.clock().advance(1s); @@ -417,25 +417,32 @@ class Validations_test : public beast::unit_test::suite } { - // Test partials for older sequence numbers - TestHarness harness(h.oracle); - Node n = harness.makeNode(); - BEAST_EXPECT( - ValStatus::current == harness.add(n.validate(ledgerABC))); - harness.clock().advance(1s); - BEAST_EXPECT(ledgerAB.seq() < ledgerABC.seq()); - BEAST_EXPECT( - ValStatus::badFullSeq == harness.add(n.validate(ledgerAB))); - BEAST_EXPECT( - ValStatus::current == harness.add(n.partial(ledgerAB))); - // If we advance far enough for AB to expire, we can fully validate - // that sequence number again - BEAST_EXPECT( - ValStatus::badFullSeq == harness.add(n.validate(ledgerAZ))); - harness.clock().advance( - harness.parms().validationSET_EXPIRES + 1ms); - BEAST_EXPECT( - ValStatus::current == harness.add(n.validate(ledgerAZ))); + // Test that full or partials cannot be sent for older sequence + // numbers, unless time-out has happened + for (bool doFull : {true, false}) + { + TestHarness harness(h.oracle); + Node n = harness.makeNode(); + + auto process = [&](Ledger & lgr) + { + if(doFull) + return harness.add(n.validate(lgr)); + return harness.add(n.partial(lgr)); + }; + + BEAST_EXPECT(ValStatus::current == process(ledgerABC)); + harness.clock().advance(1s); + BEAST_EXPECT(ledgerAB.seq() < ledgerABC.seq()); + BEAST_EXPECT(ValStatus::badSeq == process(ledgerAB)); + + // If we advance far enough for AB to expire, we can fully + // validate or partially validate that sequence number again + BEAST_EXPECT(ValStatus::badSeq == process(ledgerAZ)); + harness.clock().advance( + harness.parms().validationSET_EXPIRES + 1ms); + BEAST_EXPECT(ValStatus::current == process(ledgerAZ)); + } } } @@ -731,9 +738,9 @@ class Validations_test : public beast::unit_test::suite if (val.trusted()) trustedValidations[val.ledgerID()].emplace_back(val); } - // d now thinks ledger 1, but had to issue a partial to switch + // d now thinks ledger 1, but cannot re-issue a previously used seq { - BEAST_EXPECT(ValStatus::current == harness.add(d.partial(ledgerA))); + BEAST_EXPECT(ValStatus::badSeq == harness.add(d.partial(ledgerA))); } // e only issues partials { @@ -962,20 +969,20 @@ class Validations_test : public beast::unit_test::suite // Create a validation that is not available harness.clock().advance(5s); - Validation val2 = a.validate(ID{5}, Seq{5}, 0s, 0s, true); + Validation val2 = a.validate(ID{4}, Seq{4}, 0s, 0s, true); BEAST_EXPECT(ValStatus::current == harness.add(val2)); - BEAST_EXPECT(harness.vals().numTrustedForLedger(ID{5}) == 1); + BEAST_EXPECT(harness.vals().numTrustedForLedger(ID{4}) == 1); BEAST_EXPECT( harness.vals().getPreferred(genesisLedger) == std::make_pair(ledgerAB.seq(), ledgerAB.id())); // Switch to validation that is available harness.clock().advance(5s); - Ledger ledgerABC = h["abc"]; - BEAST_EXPECT(ValStatus::current == harness.add(a.partial(ledgerABC))); + Ledger ledgerABCDE = h["abcde"]; + BEAST_EXPECT(ValStatus::current == harness.add(a.partial(ledgerABCDE))); BEAST_EXPECT( harness.vals().getPreferred(genesisLedger) == - std::make_pair(ledgerABC.seq(), ledgerABC.id())); + std::make_pair(ledgerABCDE.seq(), ledgerABCDE.id())); } void @@ -996,14 +1003,14 @@ class Validations_test : public beast::unit_test::suite } void - testFullSeqEnforcer() + testSeqEnforcer() { - testcase("FullSeqEnforcer"); + testcase("SeqEnforcer"); using Seq = Ledger::Seq; using namespace std::chrono; beast::manual_clock clock; - FullSeqEnforcer enforcer; + SeqEnforcer enforcer; ValidationParms p; @@ -1032,7 +1039,7 @@ class Validations_test : public beast::unit_test::suite testGetPreferredLCL(); testAcquireValidatedLedger(); testNumTrustedForLedger(); - testFullSeqEnforcer(); + testSeqEnforcer(); } }; diff --git a/src/test/csf/Peer.h b/src/test/csf/Peer.h index 776e03b6fa9..3b711e0ad00 100644 --- a/src/test/csf/Peer.h +++ b/src/test/csf/Peer.h @@ -226,6 +226,7 @@ struct Peer //! The number of ledgers this peer has completed int completedLedgers = 0; + //! The number of ledgers this peer should complete before stopping to run int targetLedgers = std::numeric_limits::max(); @@ -238,8 +239,8 @@ struct Peer //! Whether to simulate running as validator or a tracking node bool runAsValidator = true; - //! Enforce invariants on full validation sequence numbers - FullSeqEnforcer fullSeqEnforcer; + //! Enforce invariants on validation sequence numbers + SeqEnforcer seqEnforcer; //TODO: Consider removing these two, they are only a convenience for tests // Number of proposers in the prior round @@ -572,13 +573,12 @@ struct Peer bool const isCompatible = newLedger.isAncestor(fullyValidatedLedger); - if (runAsValidator && isCompatible && !consensusFail) + // Can only send one validated ledger per seq + if (runAsValidator && isCompatible && !consensusFail && + seqEnforcer( + scheduler.now(), newLedger.seq(), validations.parms())) { - // Can only send one fully validated ledger per seq - bool isFull = - proposing && - fullSeqEnforcer( - scheduler.now(), newLedger.seq(), validations.parms()); + bool isFull = proposing; Validation v{newLedger.id(), newLedger.seq(), From b6e19b7cf31d5f46570b1dd359c03f55046e7d39 Mon Sep 17 00:00:00 2001 From: Brad Chase Date: Wed, 24 Jan 2018 14:30:41 -0500 Subject: [PATCH 30/38] [FOLD] Use uncommitted support in preferred branch: Updates the preferred branch algorithm to use uncommitted rather than prefix support as the required margin for selecting a ledger. Uncommitted suppport is the number of validated ledgers a node has received whose sequence number is less than the sequence number of interest or less than the sequence number last validated by the node. --- src/ripple/consensus/LedgerTrie.h | 129 +++++++++++++-------- src/ripple/consensus/Validations.h | 9 +- src/test/consensus/Consensus_test.cpp | 7 -- src/test/consensus/LedgerTrie_test.cpp | 143 ++++++++++++++++++++---- src/test/consensus/Validations_test.cpp | 3 +- src/test/csf/Peer.h | 4 +- 6 files changed, 211 insertions(+), 84 deletions(-) diff --git a/src/ripple/consensus/LedgerTrie.h b/src/ripple/consensus/LedgerTrie.h index 56c06e14bf5..2cd20133b6c 100644 --- a/src/ripple/consensus/LedgerTrie.h +++ b/src/ripple/consensus/LedgerTrie.h @@ -60,6 +60,12 @@ class Span Span& operator=(Span&&) = default; + Seq + start() const + { + return start_; + } + Seq end() const { @@ -304,6 +310,9 @@ class LedgerTrie // invariant. std::unique_ptr root; + // Count of the tip support for each sequence number + std::map seqSupport; + /** Find the node in the trie that represents the longest common ancestry with the given ledger. @@ -447,6 +456,8 @@ class LedgerTrie incNode->branchSupport += count; incNode = incNode->parent; } + + seqSupport[ledger.seq()] += count; } /** Decrease support for a ledger, removing and compressing if possible. @@ -473,6 +484,12 @@ class LedgerTrie 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) { @@ -567,7 +584,6 @@ class LedgerTrie 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. This ensures the preferred branch has monotonically increasing support. The preferred ledger is found by walking this tree of validated ledgers @@ -583,39 +599,81 @@ class LedgerTrie 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 (prefixSupport). + 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 prefixSupport can be given to - ANY ledger at that sequence number (including one not yet known). If no - such preferred ledger exists, then prior sequence preferred ledger is - the overall preferred ledger. + with relative majority of support, where uncommited 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 prefix support. This is because if all - the prefix support validators end up validating C, that new support must + 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 but increase prefixSupport with the non-preferred tip support - this round, e.g. if C were preferred over D, then prefixSupport would - increase by the support of D and E, since if those validators are - following the protocol, they will switch to the C branch, but might - initially support a different descendant. + sequence using that ledger as the root. + + @param largestIssued The sequence number of the largest validation + issued by this node. + @return Pair with the seqeuence number and ID of the preferred ledger */ std::pair - getPreferred() const + getPreferred(Seq const largestIssued) const { Node* curr = root.get(); bool done = false; - std::uint32_t prefixSupport = curr->tipSupport; + + 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 uncommited support for that ledger. + { + // Add any initial uncommited 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(); @@ -646,37 +704,10 @@ class LedgerTrie margin++; } - // If the best child has margin exceeding the prefix support, + // If the best child has margin exceeding the uncommited support, // continue from that child, otherwise we are done - if (best && ((margin > prefixSupport) || (prefixSupport == 0))) - { - // Prefix support is all the support not on the branch we - // are moving to - // curr - // _/ | \_ - // A B best - // At curr, the prefix support already includes the tip support - // of curr and its ancestors, along with the branch support of - // any of its siblings that are inconsistent. - // - // The additional prefix support that is carried to best is - // A->branchSupport + B->branchSupport + best->tipSupport - // This is the amount of support that has not yet voted - // on a descendant of best, or has voted on a conflicting - // descendant and will switch to best in the future. This means - // that they may support an arbitrary descendant of best. - // - // The calculation is simplified using - // A->branchSupport+B->branchSupport - // = curr->branchSupport - best->branchSupport - // - curr->tipSupport - // - // This will not overflow by definition of the above quantities - prefixSupport += (curr->branchSupport - best->branchSupport - - curr->tipSupport) + best->tipSupport; - + if (best && ((margin > uncommitted) || (uncommitted == 0))) curr = best; - } else // current is the best done = true; } @@ -704,6 +735,8 @@ class LedgerTrie bool checkInvariants() const { + std::map expectedSeqSupport; + std::stack nodes; nodes.push(root.get()); while (!nodes.empty()) @@ -721,6 +754,10 @@ class LedgerTrie // 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) @@ -732,7 +769,7 @@ class LedgerTrie if (support != curr->branchSupport) return false; } - return true; + return expectedSeqSupport == seqSupport; } }; diff --git a/src/ripple/consensus/Validations.h b/src/ripple/consensus/Validations.h index cd30f63a019..257e7839e04 100644 --- a/src/ripple/consensus/Validations.h +++ b/src/ripple/consensus/Validations.h @@ -644,8 +644,10 @@ class Validations ID preferredID{0}; ScopedLock lock{mutex_}; - std::tie(preferredSeq, preferredID) = withTrie( - lock, [](LedgerTrie& trie) { return trie.getPreferred(); }); + std::tie(preferredSeq, preferredID) = + withTrie(lock, [this](LedgerTrie& trie) { + return trie.getPreferred(localSeqEnforcer_.largest()); + }); // No trusted validations to determine branch if (preferredSeq == Seq{0}) @@ -672,8 +674,7 @@ class Validations if (preferredSeq > currSeq) return std::make_pair(preferredSeq, preferredID); - // Only switch to earlier sequence numbers if it is a different - // chain to avoid jumping backward unnecessarily + // Only switch to earlier sequence numbers if it is a different chain. if (currLedger[preferredSeq] != preferredID) return std::make_pair(preferredSeq, preferredID); diff --git a/src/test/consensus/Consensus_test.cpp b/src/test/consensus/Consensus_test.cpp index cf47fd6a291..f5e751acc92 100644 --- a/src/test/consensus/Consensus_test.cpp +++ b/src/test/consensus/Consensus_test.cpp @@ -974,17 +974,10 @@ class Consensus_test : public beast::unit_test::suite // Disruptor will reconnect all but the fastC node sim.run(1); - BEAST_EXPECT(!sim.synchronized()); if(BEAST_EXPECT(sim.branches() == 1)) { - // New approach will not fork and will resync once the fast node - // reconnects for a few rounds - network.connect(groupCfast, fDelay); - sim.run(4); BEAST_EXPECT(sim.synchronized()); - BEAST_EXPECT(sim.branches() == 1); - } else // old approach caused a fork { diff --git a/src/test/consensus/LedgerTrie_test.cpp b/src/test/consensus/LedgerTrie_test.cpp index f149bb45b4d..6c102b1bd6b 100644 --- a/src/test/consensus/LedgerTrie_test.cpp +++ b/src/test/consensus/LedgerTrie_test.cpp @@ -294,13 +294,17 @@ class LedgerTrie_test : public beast::unit_test::suite } void - testTipAndBranchSupport() + testSupport() { using namespace csf; + using Seq = Ledger::Seq; + + LedgerTrie t; LedgerHistoryHelper h; BEAST_EXPECT(t.tipSupport(h["a"]) == 0); BEAST_EXPECT(t.tipSupport(h["axy"]) == 0); + BEAST_EXPECT(t.branchSupport(h["a"]) == 0); BEAST_EXPECT(t.branchSupport(h["axy"]) == 0); @@ -309,6 +313,7 @@ class LedgerTrie_test : public beast::unit_test::suite 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); @@ -324,24 +329,38 @@ class LedgerTrie_test : public beast::unit_test::suite BEAST_EXPECT(t.branchSupport(h["ab"]) == 2); BEAST_EXPECT(t.branchSupport(h["abc"]) == 1); BEAST_EXPECT(t.branchSupport(h["abe"]) == 1); + + t.remove(h["abc"]); + BEAST_EXPECT(t.tipSupport(h["a"]) == 0); + BEAST_EXPECT(t.tipSupport(h["ab"]) == 0); + BEAST_EXPECT(t.tipSupport(h["abc"]) == 0); + BEAST_EXPECT(t.tipSupport(h["abe"]) == 1); + + BEAST_EXPECT(t.branchSupport(h["a"]) == 1); + BEAST_EXPECT(t.branchSupport(h["ab"]) == 1); + BEAST_EXPECT(t.branchSupport(h["abc"]) == 0); + BEAST_EXPECT(t.branchSupport(h["abe"]) == 1); + } void testGetPreferred() { using namespace csf; + using Seq = Ledger::Seq; // Empty { LedgerTrie t; LedgerHistoryHelper h; - BEAST_EXPECT(t.getPreferred().second == Ledger::ID{0}); + BEAST_EXPECT(t.getPreferred(Seq{0}).second == h[""].id()); + BEAST_EXPECT(t.getPreferred(Seq{2}).second == h[""].id()); } // Single node no children { LedgerTrie t; LedgerHistoryHelper h; t.insert(h["abc"]); - BEAST_EXPECT(t.getPreferred().second == h["abc"].id()); + BEAST_EXPECT(t.getPreferred(Seq{3}).second == h["abc"].id()); } // Single node smaller child support { @@ -349,10 +368,8 @@ class LedgerTrie_test : public beast::unit_test::suite LedgerHistoryHelper h; t.insert(h["abc"]); t.insert(h["abcd"]); - BEAST_EXPECT(t.getPreferred().second == h["abc"].id()); - - t.insert(h["abc"]); - BEAST_EXPECT(t.getPreferred().second == h["abc"].id()); + BEAST_EXPECT(t.getPreferred(Seq{3}).second == h["abc"].id()); + BEAST_EXPECT(t.getPreferred(Seq{4}).second == h["abc"].id()); } // Single node larger child { @@ -360,7 +377,8 @@ class LedgerTrie_test : public beast::unit_test::suite LedgerHistoryHelper h; t.insert(h["abc"]); t.insert(h["abcd"],2); - BEAST_EXPECT(t.getPreferred().second == h["abcd"].id()); + BEAST_EXPECT(t.getPreferred(Seq{3}).second == h["abcd"].id()); + BEAST_EXPECT(t.getPreferred(Seq{4}).second == h["abcd"].id()); } // Single node smaller children support { @@ -369,10 +387,12 @@ class LedgerTrie_test : public beast::unit_test::suite t.insert(h["abc"]); t.insert(h["abcd"]); t.insert(h["abce"]); - BEAST_EXPECT(t.getPreferred().second == h["abc"].id()); + BEAST_EXPECT(t.getPreferred(Seq{3}).second == h["abc"].id()); + BEAST_EXPECT(t.getPreferred(Seq{4}).second == h["abc"].id()); t.insert(h["abc"]); - BEAST_EXPECT(t.getPreferred().second == h["abc"].id()); + BEAST_EXPECT(t.getPreferred(Seq{3}).second == h["abc"].id()); + BEAST_EXPECT(t.getPreferred(Seq{4}).second == h["abc"].id()); } // Single node larger children { @@ -381,9 +401,12 @@ class LedgerTrie_test : public beast::unit_test::suite t.insert(h["abc"]); t.insert(h["abcd"],2); t.insert(h["abce"]); - BEAST_EXPECT(t.getPreferred().second == h["abc"].id()); + BEAST_EXPECT(t.getPreferred(Seq{3}).second == h["abc"].id()); + BEAST_EXPECT(t.getPreferred(Seq{4}).second == h["abc"].id()); + t.insert(h["abcd"]); - BEAST_EXPECT(t.getPreferred().second == h["abcd"].id()); + BEAST_EXPECT(t.getPreferred(Seq{3}).second == h["abcd"].id()); + BEAST_EXPECT(t.getPreferred(Seq{4}).second == h["abcd"].id()); } // Tie-breaker by id { @@ -393,11 +416,11 @@ class LedgerTrie_test : public beast::unit_test::suite t.insert(h["abce"],2); BEAST_EXPECT(h["abce"].id() > h["abcd"].id()); - BEAST_EXPECT(t.getPreferred().second == h["abce"].id()); + BEAST_EXPECT(t.getPreferred(Seq{4}).second == h["abce"].id()); t.insert(h["abcd"]); BEAST_EXPECT(h["abce"].id() > h["abcd"].id()); - BEAST_EXPECT(t.getPreferred().second == h["abcd"].id()); + BEAST_EXPECT(t.getPreferred(Seq{4}).second == h["abcd"].id()); } // Tie-breaker not needed @@ -409,12 +432,14 @@ class LedgerTrie_test : public beast::unit_test::suite 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().second == h["abce"].id()); + BEAST_EXPECT(t.getPreferred(Seq{3}).second == h["abce"].id()); + BEAST_EXPECT(t.getPreferred(Seq{4}).second == 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().second == h["abc"].id()); + BEAST_EXPECT(t.getPreferred(Seq{3}).second == h["abc"].id()); + BEAST_EXPECT(t.getPreferred(Seq{4}).second == h["abc"].id()); } // Single node larger grand child @@ -424,7 +449,9 @@ class LedgerTrie_test : public beast::unit_test::suite t.insert(h["abc"]); t.insert(h["abcd"],2); t.insert(h["abcde"],4); - BEAST_EXPECT(t.getPreferred().second == h["abcde"].id()); + BEAST_EXPECT(t.getPreferred(Seq{3}).second == h["abcde"].id()); + BEAST_EXPECT(t.getPreferred(Seq{4}).second == h["abcde"].id()); + BEAST_EXPECT(t.getPreferred(Seq{5}).second == h["abcde"].id()); } // Too much prefix support from competing branches @@ -435,20 +462,90 @@ class LedgerTrie_test : public beast::unit_test::suite t.insert(h["abcde"],2); t.insert(h["abcfg"],2); // 'de' and 'fg' are tied without 'abc' vote - BEAST_EXPECT(t.getPreferred().second == h["abc"].id()); + BEAST_EXPECT(t.getPreferred(Seq{3}).second == h["abc"].id()); + BEAST_EXPECT(t.getPreferred(Seq{4}).second == h["abc"].id()); + BEAST_EXPECT(t.getPreferred(Seq{5}).second == h["abc"].id()); + t.remove(h["abc"]); t.insert(h["abcd"]); - // 'de' branch has 3 votes to 2, but not enough suport for 'e' - // since the node on 'd' and the 2 on 'fg' could go in a - // different direction - BEAST_EXPECT(t.getPreferred().second == h["abcd"].id()); + + // 'de' branch has 3 votes to 2, so earlier sequences see it as + // preferred + BEAST_EXPECT(t.getPreferred(Seq{3}).second == h["abcde"].id()); + BEAST_EXPECT(t.getPreferred(Seq{4}).second == 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}).second == h["abc"].id()); + } + + // Changing largestSeq perspective changes preferred branch + { + /** Build the tree below with tip support annotated + A + / \ + B(1) C(1) + / | | + H D F(1) + | + E(2) + | + G + */ + LedgerTrie t; + LedgerHistoryHelper h; + t.insert(h["ab"]); + t.insert(h["ac"]); + t.insert(h["acf"]); + t.insert(h["abde"],2); + + // B has more branch support + BEAST_EXPECT(t.getPreferred(Seq{1}).second == h["ab"].id()); + BEAST_EXPECT(t.getPreferred(Seq{2}).second == 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}).second == h["a"].id()); + BEAST_EXPECT(t.getPreferred(Seq{4}).second == h["a"].id()); + + // One of E advancing to G doesn't change anything + t.remove(h["abde"]); + t.insert(h["abdeg"]); + + BEAST_EXPECT(t.getPreferred(Seq{1}).second == h["ab"].id()); + BEAST_EXPECT(t.getPreferred(Seq{2}).second == h["ab"].id()); + BEAST_EXPECT(t.getPreferred(Seq{3}).second == h["a"].id()); + BEAST_EXPECT(t.getPreferred(Seq{4}).second == h["a"].id()); + BEAST_EXPECT(t.getPreferred(Seq{5}).second == h["a"].id()); + + // C advancing to H does advance the seq 3 preferred ledger + t.remove(h["ac"]); + t.insert(h["abh"]); + BEAST_EXPECT(t.getPreferred(Seq{1}).second == h["ab"].id()); + BEAST_EXPECT(t.getPreferred(Seq{2}).second == h["ab"].id()); + BEAST_EXPECT(t.getPreferred(Seq{3}).second == h["ab"].id()); + BEAST_EXPECT(t.getPreferred(Seq{4}).second == h["a"].id()); + BEAST_EXPECT(t.getPreferred(Seq{5}).second == h["a"].id()); + + // F advancing to E also moves the preferred ledger forward + t.remove(h["acf"]); + t.insert(h["abde"]); + BEAST_EXPECT(t.getPreferred(Seq{1}).second == h["abde"].id()); + BEAST_EXPECT(t.getPreferred(Seq{2}).second == h["abde"].id()); + BEAST_EXPECT(t.getPreferred(Seq{3}).second == h["abde"].id()); + BEAST_EXPECT(t.getPreferred(Seq{4}).second == h["ab"].id()); + BEAST_EXPECT(t.getPreferred(Seq{5}).second == 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. @@ -524,7 +621,7 @@ class LedgerTrie_test : public beast::unit_test::suite { testInsert(); testRemove(); - testTipAndBranchSupport(); + testSupport(); testGetPreferred(); testRootRelated(); testStress(); diff --git a/src/test/consensus/Validations_test.cpp b/src/test/consensus/Validations_test.cpp index d377f5f06d0..8178aaeb997 100644 --- a/src/test/consensus/Validations_test.cpp +++ b/src/test/consensus/Validations_test.cpp @@ -870,10 +870,11 @@ class Validations_test : public beast::unit_test::suite for (auto const& node : {a, b, c, d}) BEAST_EXPECT( ValStatus::current == harness.add(node.validate(ledgerAC))); + // Parent of preferred stays put BEAST_EXPECT(harness.vals().getPreferred(ledgerA) == pref(ledgerA)); // Earlier different chain, switch BEAST_EXPECT(harness.vals().getPreferred(ledgerB) == pref(ledgerAC)); - // Earlier same chain stays where it is + // Later on chain, stays where it is BEAST_EXPECT(harness.vals().getPreferred(ledgerACD) == pref(ledgerACD)); // Any later grandchild or different chain is preferred diff --git a/src/test/csf/Peer.h b/src/test/csf/Peer.h index 3b711e0ad00..b66712493d4 100644 --- a/src/test/csf/Peer.h +++ b/src/test/csf/Peer.h @@ -613,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 From 0ccf0a82a38cc387dcd697ec068bc480af38734d Mon Sep 17 00:00:00 2001 From: Brad Chase Date: Thu, 25 Jan 2018 08:24:58 -0500 Subject: [PATCH 31/38] [FOLD] Reduce log warnings: Re-orders the comparison in mismatch of RCLValidations to avoid trying to lookup the ID of the sequence 0 ledger. --- src/ripple/app/consensus/RCLValidations.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ripple/app/consensus/RCLValidations.cpp b/src/ripple/app/consensus/RCLValidations.cpp index 3bbb3eeed19..451d7c27a01 100644 --- a/src/ripple/app/consensus/RCLValidations.cpp +++ b/src/ripple/app/consensus/RCLValidations.cpp @@ -105,7 +105,7 @@ mismatch(RCLValidatedLedger const& a, RCLValidatedLedger const& b) Seq const upper = std::min(a.seq(), b.seq()); Seq curr = upper; - while (a[curr] != b[curr] && curr >= lower && curr != Seq{0}) + while (curr != Seq{0} && a[curr] != b[curr] && curr >= lower) --curr; // If the searchable interval mismatches entirely, then we have to From 277134ba626e013290d4acd1432e6e9f4f9b7d9c Mon Sep 17 00:00:00 2001 From: Brad Chase Date: Fri, 26 Jan 2018 10:27:30 -0500 Subject: [PATCH 32/38] [FOLD] Address PR comments --- src/ripple/app/consensus/RCLValidations.cpp | 30 ++++++++++++++++----- src/ripple/app/consensus/RCLValidations.h | 4 +-- src/ripple/consensus/Consensus.h | 2 +- src/ripple/consensus/Validations.h | 28 +++++++++---------- src/test/app/RCLValidations_test.cpp | 25 ++++++++--------- src/test/consensus/Consensus_test.cpp | 11 ++++---- src/test/consensus/LedgerTrie_test.cpp | 12 ++++----- src/test/consensus/Validations_test.cpp | 22 +++++++++------ src/test/csf/Peer.h | 2 +- src/test/csf/Sim.h | 12 ++++----- src/test/csf/Validation.h | 11 ++++++-- src/test/csf/ledgers.h | 4 +-- 12 files changed, 91 insertions(+), 72 deletions(-) diff --git a/src/ripple/app/consensus/RCLValidations.cpp b/src/ripple/app/consensus/RCLValidations.cpp index 451d7c27a01..e5000eedbfe 100644 --- a/src/ripple/app/consensus/RCLValidations.cpp +++ b/src/ripple/app/consensus/RCLValidations.cpp @@ -100,7 +100,7 @@ mismatch(RCLValidatedLedger const& a, RCLValidatedLedger const& b) { using Seq = RCLValidatedLedger::Seq; - // Find overlapping interval for known sequence for the the ledgers + // 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()); @@ -297,12 +297,7 @@ handleNewValidation(Application& app, RCLValidations& validations = app.getValidations(); beast::Journal j = validations.adaptor().journal(); - // masterKey is seated only if validator is trusted or listed - if (masterKey) - { - ValStatus const outcome = validations.add(*masterKey, val); - - auto dmp = [&](beast::Journal::Stream s, std::string const& msg) { + auto dmp = [&](beast::Journal::Stream s, std::string const& msg) { s << "Val for " << hash << (val->isTrusted() ? " trusted/" : " UNtrusted/") << (val->isFull() ? " full" : "partial") << " from " @@ -313,6 +308,20 @@ handleNewValidation(Application& app, << " src=" << source; }; + if(!val->isFieldPresent(sfLedgerSequence)) + { + if(j.error()) + dmp(j.error(), " missing ledger seqeuence field"); + return false; + } + + // masterKey is seated only if validator is trusted or listed + if (masterKey) + { + ValStatus const outcome = validations.add(*masterKey, val); + + + if(j.debug()) dmp(j.debug(), to_string(outcome)); @@ -321,6 +330,13 @@ handleNewValidation(Application& app, auto const seq = val->getFieldU32(sfLedgerSequence); dmp(j.warn(), " already validated sequence past " + to_string(seq)); } + else if(outcome == ValStatus::repeat && j.warn()) + { + auto const seq = val->getFieldU32(sfLedgerSequence); + dmp(j.warn(), + " already validated ledger with same id but different seq " + "than" + to_string(seq)); + } if (val->isTrusted() && outcome == ValStatus::current) { diff --git a/src/ripple/app/consensus/RCLValidations.h b/src/ripple/app/consensus/RCLValidations.h index 86e50a1a52a..ea7a5b31776 100644 --- a/src/ripple/app/consensus/RCLValidations.h +++ b/src/ripple/app/consensus/RCLValidations.h @@ -63,9 +63,7 @@ class RCLValidation std::uint32_t seq() const { - if(auto res = (*val_)[~sfLedgerSequence]) - return *res; - return 0; + return val_->getFieldU32(sfLedgerSequence); } /// Validation's signing time diff --git a/src/ripple/consensus/Consensus.h b/src/ripple/consensus/Consensus.h index d535bee5710..820fd9c3ce3 100644 --- a/src/ripple/consensus/Consensus.h +++ b/src/ripple/consensus/Consensus.h @@ -227,7 +227,7 @@ checkConsensus( // Number of proposers that have validated a ledger descended from the // given ledger; if prevLedger.id() != prevLedgerID, use prevLedgerID // for the determination - std::size_t proposersFinished(Ledger conost & prev, + std::size_t proposersFinished(Ledger const & prevLedger, Ledger::ID const & prevLedger) const; // Return the ID of the last closed (and validated) ledger that the diff --git a/src/ripple/consensus/Validations.h b/src/ripple/consensus/Validations.h index 257e7839e04..ba411a9125b 100644 --- a/src/ripple/consensus/Validations.h +++ b/src/ripple/consensus/Validations.h @@ -103,7 +103,7 @@ class SeqEnforcer { if(now > (when_ + p.validationSET_EXPIRES)) seq_ = Seq{0}; - if(seq_ != Seq{0} && s <= seq_) + if(s <= seq_) return false; seq_ = s; when_ = now; @@ -151,11 +151,11 @@ isCurrent( enum class ValStatus { /// This was a new validation and was added current, - /// Already had this exact same validation + /// Already had this validation for this ID but different seq repeat, /// Not current or was older than current from this node stale, - /// A validation was marked full but it violates increasing seq requirement + /// A validation violates the increasing seq requirement badSeq }; @@ -377,7 +377,7 @@ class Validations @param lock Existing lock of mutex_ @param key The master public key identifying the validating node @param val The trusted validation issued by the node - @param priorID If not none, the ID of the last current validated ledger. + @param priorID If not none, the last current validated ledger from key */ void updateTrie( @@ -413,7 +413,7 @@ class Validations Accessing the trie through this helper ensures acquiring validations are checked and any stale validations are flushed from the trie. - @param lock Existing locked of mutex_ + @param lock Existing lock of mutex_ @param f Invokable with signature (LedgerTrie &) @warning The invokable `f` is expected to be a simple transformation of @@ -551,7 +551,7 @@ class Validations Attempt to add a new validation. @param key The master key associated with this validation - @param val The validationo to store + @param val The validation to store @return The outcome @note The provided key may differ from the validations's key() @@ -568,13 +568,10 @@ class Validations // Check that validation sequence is greater than any non-expired // validations sequence from that validator - if (val.seq() != Seq{0}) - { - auto const now = byLedger_.clock().now(); - SeqEnforcer& enforcer = seqEnforcers_[key]; - if (!enforcer(now, val.seq(), parms_)) - return ValStatus::badSeq; - } + auto const now = byLedger_.clock().now(); + SeqEnforcer& enforcer = seqEnforcers_[key]; + if (!enforcer(now, val.seq(), parms_)) + return ValStatus::badSeq; // This validation is a repeat if we already have // one with the same id for this key @@ -631,7 +628,7 @@ class Validations and is *not* an ancestor of the current working ledger; otherwise it remains the current working ledger. - @param currLedger The local nodes current working ledger + @param currLedger The local node's current working ledger @return The sequence and id of the preferred working ledger, or Seq{0},ID{0} if no trusted validations are available to @@ -674,7 +671,8 @@ class Validations if (preferredSeq > currSeq) return std::make_pair(preferredSeq, preferredID); - // Only switch to earlier sequence numbers if it is a different chain. + // Only switch to earlier or same sequence number + // if it is a different chain. if (currLedger[preferredSeq] != preferredID) return std::make_pair(preferredSeq, preferredID); diff --git a/src/test/app/RCLValidations_test.cpp b/src/test/app/RCLValidations_test.cpp index 6581994e6c7..0feeb2b7a80 100644 --- a/src/test/app/RCLValidations_test.cpp +++ b/src/test/app/RCLValidations_test.cpp @@ -71,7 +71,7 @@ class RCLValidations_test : public beast::unit_test::suite Seq const diverge = history.size()/2; std::vector> altHistory( history.begin(), history.begin() + diverge); - // advance clock too get new ledgers + // advance clock to get new ledgers env.timeKeeper().set(env.timeKeeper().now() + 1200s); prev = altHistory.back(); bool forceHash = true; @@ -179,27 +179,24 @@ class RCLValidations_test : public beast::unit_test::suite // Different chains, different seqs { // Compare around the divergence point - for(auto ledger : {history[diverge]}) + RCLValidatedLedger a{history[diverge], j}; + for(Seq offset = diverge/2; offset < 3*diverge/2; ++offset) { - RCLValidatedLedger a{ledger, j}; - for(Seq offset = diverge/2; offset < 3*diverge/2; ++offset) + RCLValidatedLedger b{altHistory[offset-1], j}; + if(offset <= diverge) { - 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_EXPECT(mismatch(a,b) == b.seq() + 1); + } + else + { + BEAST_EXPECT(mismatch(a,b) == diverge + 1); } } } } -}; // namespace test +}; BEAST_DEFINE_TESTSUITE(RCLValidations, app, ripple); diff --git a/src/test/consensus/Consensus_test.cpp b/src/test/consensus/Consensus_test.cpp index f5e751acc92..93bf57bd076 100644 --- a/src/test/consensus/Consensus_test.cpp +++ b/src/test/consensus/Consensus_test.cpp @@ -869,7 +869,7 @@ class Consensus_test : public beast::unit_test::suite 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 validation make it through + // network so those validations make it through if (!reconnected && e.ledger.seq() == csf::Ledger::Seq{3}) { reconnected = true; @@ -902,12 +902,11 @@ class Consensus_test : public beast::unit_test::suite // - 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 disconnect split at just - // the right time such that they never see any C validations but - // their own. + // 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 C. + // 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' @@ -938,7 +937,7 @@ class Consensus_test : public beast::unit_test::suite // other nodes network.connect(groupCfast, fDelay); // The rest of the network is connected at the same speed - (network - groupCfast).connect(network - groupCfast, delay); + groupNotFastC.connect(groupNotFastC, delay); Disruptor dc(network, groupCfast, groupCsplit, delay); sim.collectors.add(dc); diff --git a/src/test/consensus/LedgerTrie_test.cpp b/src/test/consensus/LedgerTrie_test.cpp index 6c102b1bd6b..e8cab4b6b62 100644 --- a/src/test/consensus/LedgerTrie_test.cpp +++ b/src/test/consensus/LedgerTrie_test.cpp @@ -73,13 +73,13 @@ class LedgerTrie_test : public beast::unit_test::suite BEAST_EXPECT(t.tipSupport(h["abce"]) == 1); BEAST_EXPECT(t.branchSupport(h["abce"]) == 1); } - // Prefix of existing node + // uncommitted of existing node { LedgerTrie t; LedgerHistoryHelper h; t.insert(h["abcd"]); BEAST_EXPECT(t.checkInvariants()); - // prefix with no siblings + // uncommitted with no siblings t.insert(h["abcdf"]); BEAST_EXPECT(t.checkInvariants()); @@ -88,7 +88,7 @@ class LedgerTrie_test : public beast::unit_test::suite BEAST_EXPECT(t.tipSupport(h["abcdf"]) == 1); BEAST_EXPECT(t.branchSupport(h["abcdf"]) == 1); - // prefix with existing child + // uncommitted with existing child t.insert(h["abc"]); BEAST_EXPECT(t.checkInvariants()); @@ -99,7 +99,7 @@ class LedgerTrie_test : public beast::unit_test::suite BEAST_EXPECT(t.tipSupport(h["abcdf"]) == 1); BEAST_EXPECT(t.branchSupport(h["abcdf"]) == 1); } - // Suffix + prefix of existing node + // Suffix + uncommitted of existing node { LedgerTrie t; LedgerHistoryHelper h; @@ -115,7 +115,7 @@ class LedgerTrie_test : public beast::unit_test::suite BEAST_EXPECT(t.tipSupport(h["abce"]) == 1); BEAST_EXPECT(t.branchSupport(h["abce"]) == 1); } - // Suffix + prefix with existing child + // Suffix + uncommitted with existing child { // abcd : abcde, abcf @@ -454,7 +454,7 @@ class LedgerTrie_test : public beast::unit_test::suite BEAST_EXPECT(t.getPreferred(Seq{5}).second == h["abcde"].id()); } - // Too much prefix support from competing branches + // Too much uncommitted support from competing branches { LedgerTrie t; LedgerHistoryHelper h; diff --git a/src/test/consensus/Validations_test.cpp b/src/test/consensus/Validations_test.cpp index 8178aaeb997..8e8d8878bed 100644 --- a/src/test/consensus/Validations_test.cpp +++ b/src/test/consensus/Validations_test.cpp @@ -363,14 +363,14 @@ class Validations_test : public beast::unit_test::suite // Processing validations out of order should ignore the older // validation harness.clock().advance(2s); - auto const val3 = n.validate(ledgerABCDE); + auto const valABCDE = n.validate(ledgerABCDE); harness.clock().advance(4s); - auto const val4 = n.validate(ledgerABCD); + auto const valABCD = n.validate(ledgerABCD); - BEAST_EXPECT(ValStatus::current == harness.add(val4)); + BEAST_EXPECT(ValStatus::current == harness.add(valABCD)); - BEAST_EXPECT(ValStatus::stale == harness.add(val3)); + BEAST_EXPECT(ValStatus::stale == harness.add(valABCDE)); } { @@ -388,7 +388,7 @@ class Validations_test : public beast::unit_test::suite ValStatus::stale == harness.add(n.validate(ledgerAB, -1s, -1s))); - // Process a validation that has an "leter" seq and later sign + // Process a validation that has a later seq and later sign // time BEAST_EXPECT( ValStatus::current == @@ -473,7 +473,7 @@ class Validations_test : public beast::unit_test::suite BEAST_EXPECT( ValStatus::current == harness.add(n.validate(ledgerAB))); - harness.vals().currentTrusted(); + trigger(harness.vals()); BEAST_EXPECT( harness.vals().getNodesAfter(ledgerA, ledgerA.id()) == 1); BEAST_EXPECT( @@ -930,11 +930,17 @@ class Validations_test : public beast::unit_test::suite // Single trusted always wins over peer counts BEAST_EXPECT(ValStatus::current == harness.add(a.validate(ledgerA))); + BEAST_EXPECT( + harness.vals().getPreferredLCL(ledgerA, Seq{0}, peerCounts) == + ledgerA.id()); BEAST_EXPECT( harness.vals().getPreferredLCL(ledgerB, Seq{0}, peerCounts) == ledgerA.id()); + BEAST_EXPECT( + harness.vals().getPreferredLCL(ledgerC, Seq{0}, peerCounts) == + ledgerA.id()); - // Stick with current ledger if trusted validation ledger has to old + // Stick with current ledger if trusted validation ledger has too old // of a sequence BEAST_EXPECT( harness.vals().getPreferredLCL(ledgerB, Seq{2}, peerCounts) == @@ -1017,8 +1023,8 @@ class Validations_test : public beast::unit_test::suite BEAST_EXPECT(enforcer(clock.now(), Seq{1}, p)); BEAST_EXPECT(enforcer(clock.now(), Seq{10}, p)); - BEAST_EXPECT(!enforcer(clock.now(), Seq{9}, p)); BEAST_EXPECT(!enforcer(clock.now(), Seq{5}, p)); + BEAST_EXPECT(!enforcer(clock.now(), Seq{9}, p)); clock.advance(p.validationSET_EXPIRES - 1ms); BEAST_EXPECT(!enforcer(clock.now(), Seq{1}, p)); clock.advance(2ms); diff --git a/src/test/csf/Peer.h b/src/test/csf/Peer.h index b66712493d4..fcbd8aa6bc7 100644 --- a/src/test/csf/Peer.h +++ b/src/test/csf/Peer.h @@ -587,7 +587,7 @@ struct Peer key, id, isFull}; - // share thew new validation; it is trusted by the receiver + // share the new validation; it is trusted by the receiver share(v); // we trust ourselves addTrustedValidation(v); diff --git a/src/test/csf/Sim.h b/src/test/csf/Sim.h index 6ecad0604fd..1ec2c12485c 100644 --- a/src/test/csf/Sim.h +++ b/src/test/csf/Sim.h @@ -141,26 +141,24 @@ class Sim /** 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; - + synchronized(PeerGroup const& g) const; /** Check whether all peers in the network are synchronized */ bool synchronized() const; - /** 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; + branches(PeerGroup const& g) const; /** Calculate the number of branches in the network */ diff --git a/src/test/csf/Validation.h b/src/test/csf/Validation.h index 14f11d4ea3d..80b74e4b844 100644 --- a/src/test/csf/Validation.h +++ b/src/test/csf/Validation.h @@ -145,8 +145,15 @@ class Validation asTie() const { // 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_, - trusted_, loadFee_, full_); + return std::tie( + ledgerID_, + seq_, + signTime_, + seenTime_, + key_, + nodeID_, + loadFee_, + full_); } bool operator==(Validation const& o) const diff --git a/src/test/csf/ledgers.h b/src/test/csf/ledgers.h index 9c0e8334a77..8ae8a38584e 100644 --- a/src/test/csf/ledgers.h +++ b/src/test/csf/ledgers.h @@ -96,7 +96,7 @@ class Ledger 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 foor any + //! ancestors based on the parentID, this member is not needed for any //! of the operators below. std::vector ancestors; @@ -323,7 +323,7 @@ struct LedgerHistoryHelper /** Get or create the ledger with the given string history. - Creates an necessary intermediate ledgers, but asserts if + 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) From 83d5d55c7a28bfc6aa332e5d0402c845e69a6476 Mon Sep 17 00:00:00 2001 From: Brad Chase Date: Wed, 31 Jan 2018 09:58:54 -0500 Subject: [PATCH 33/38] [FOLD] Fix typos --- src/ripple/app/consensus/RCLValidations.cpp | 15 +++++++-------- src/ripple/app/consensus/RCLValidations.h | 2 ++ src/ripple/consensus/LedgerTrie.h | 10 +++++----- src/ripple/consensus/Validations.h | 16 ++++++++-------- src/test/csf/Peer.h | 2 +- src/test/csf/Sim.h | 2 +- 6 files changed, 24 insertions(+), 23 deletions(-) diff --git a/src/ripple/app/consensus/RCLValidations.cpp b/src/ripple/app/consensus/RCLValidations.cpp index e5000eedbfe..86c42ad7325 100644 --- a/src/ripple/app/consensus/RCLValidations.cpp +++ b/src/ripple/app/consensus/RCLValidations.cpp @@ -83,8 +83,7 @@ auto RCLValidatedLedger::operator[](Seq const& s) const -> ID if (s == seq()) return ledgerID_; Seq const diff = seq() - s; - if (ancestors_.size() >= diff) - return ancestors_[ancestors_.size() - diff]; + return ancestors_[ancestors_.size() - diff]; } JLOG(j_.warn()) << "Unable to determine hash of ancestor seq=" << s @@ -300,9 +299,9 @@ handleNewValidation(Application& app, auto dmp = [&](beast::Journal::Stream s, std::string const& msg) { s << "Val for " << hash << (val->isTrusted() ? " trusted/" : " UNtrusted/") - << (val->isFull() ? " full" : "partial") << " from " + << (val->isFull() ? "full" : "partial") << " from " << toBase58(TokenType::TOKEN_NODE_PUBLIC, *masterKey) - << "signing key " + << " signing key " << toBase58(TokenType::TOKEN_NODE_PUBLIC, signingKey) << " " << msg << " src=" << source; @@ -311,7 +310,7 @@ handleNewValidation(Application& app, if(!val->isFieldPresent(sfLedgerSequence)) { if(j.error()) - dmp(j.error(), " missing ledger seqeuence field"); + dmp(j.error(), "missing ledger sequence field"); return false; } @@ -328,13 +327,13 @@ handleNewValidation(Application& app, if(outcome == ValStatus::badSeq && j.warn()) { auto const seq = val->getFieldU32(sfLedgerSequence); - dmp(j.warn(), " already validated sequence past " + to_string(seq)); + dmp(j.warn(), "already validated sequence past " + to_string(seq)); } - else if(outcome == ValStatus::repeat && j.warn()) + 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 " + "already validated ledger with same id but different seq " "than" + to_string(seq)); } diff --git a/src/ripple/app/consensus/RCLValidations.h b/src/ripple/app/consensus/RCLValidations.h index ea7a5b31776..16b15a1828c 100644 --- a/src/ripple/app/consensus/RCLValidations.h +++ b/src/ripple/app/consensus/RCLValidations.h @@ -101,11 +101,13 @@ class RCLValidation return val_->isTrusted(); } + /// Whether the validatioon is full (not-partial) bool full() const { return val_->isFull(); } + /// Get the load fee of the validation if it exists boost::optional loadFee() const diff --git a/src/ripple/consensus/LedgerTrie.h b/src/ripple/consensus/LedgerTrie.h index 2cd20133b6c..6e19008ccfb 100644 --- a/src/ripple/consensus/LedgerTrie.h +++ b/src/ripple/consensus/LedgerTrie.h @@ -605,7 +605,7 @@ class LedgerTrie not have heard from those nodes yet. The preferred ledger for this sequence number is then the ledger - with relative majority of support, where uncommited support + 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. @@ -621,7 +621,7 @@ class LedgerTrie @param largestIssued The sequence number of the largest validation issued by this node. - @return Pair with the seqeuence number and ID of the preferred ledger + @return Pair with the sequence number and ID of the preferred ledger */ std::pair getPreferred(Seq const largestIssued) const @@ -637,9 +637,9 @@ class LedgerTrie { // 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 uncommited support for that ledger. + // the next ledger exceeds the uncommitted support for that ledger. { - // Add any initial uncommited support prior for ledgers + // 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() && @@ -704,7 +704,7 @@ class LedgerTrie margin++; } - // If the best child has margin exceeding the uncommited support, + // 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; diff --git a/src/ripple/consensus/Validations.h b/src/ripple/consensus/Validations.h index ba411a9125b..d9776a9192f 100644 --- a/src/ripple/consensus/Validations.h +++ b/src/ripple/consensus/Validations.h @@ -152,7 +152,7 @@ enum class ValStatus { /// This was a new validation and was added current, /// Already had this validation for this ID but different seq - repeat, + repeatID, /// Not current or was older than current from this node stale, /// A validation violates the increasing seq requirement @@ -166,8 +166,8 @@ to_string(ValStatus m) { case ValStatus::current: return "current"; - case ValStatus::repeat: - return "repeat"; + case ValStatus::repeatID: + return "repeatID"; case ValStatus::stale: return "stale"; case ValStatus::badSeq: @@ -370,7 +370,7 @@ class Validations /** Process a new validation Process a new trusted validation from a validator. This will be - reflected only after the validated ledger is succesfully acquired by + reflected only after the validated ledger is successfully acquired by the local node. In the interim, the prior validated ledger from this node remains. @@ -554,7 +554,7 @@ class Validations @param val The validation to store @return The outcome - @note The provided key may differ from the validations's key() + @note The provided key may differ from the validation's key() member if the validator is using ephemeral signing keys. */ ValStatus @@ -577,7 +577,7 @@ class Validations // one with the same id for this key auto const ret = byLedger_[val.ledgerID()].emplace(key, val); if (!ret.second && ret.first->second.key() == val.key()) - return ValStatus::repeat; + return ValStatus::repeatID; auto const ins = current_.emplace(key, val); if (!ins.second) @@ -667,7 +667,7 @@ class Validations } // A ledger ahead of us is preferred regardless of whether it is - // a descendent of our working ledger or it is on a different chain + // a descendant of our working ledger or it is on a different chain if (preferredSeq > currSeq) return std::make_pair(preferredSeq, preferredID); @@ -795,7 +795,7 @@ class Validations /** Get the set of known public keys associated with current validations - @return The set of of knowns keys for current listed validators + @return The set of knows keys for current listed validators */ hash_set getCurrentPublicKeys() diff --git a/src/test/csf/Peer.h b/src/test/csf/Peer.h index fcbd8aa6bc7..19c94c82bd3 100644 --- a/src/test/csf/Peer.h +++ b/src/test/csf/Peer.h @@ -683,7 +683,7 @@ struct Peer v.setSeen(now()); ValStatus const res = validations.add(v.key(), v); - if(res == ValStatus::stale || res == ValStatus::repeat) + if(res == ValStatus::stale || res == ValStatus::repeatID) return false; // Acquire will try to get from network if not already local diff --git a/src/test/csf/Sim.h b/src/test/csf/Sim.h index 1ec2c12485c..02295d750ea 100644 --- a/src/test/csf/Sim.h +++ b/src/test/csf/Sim.h @@ -155,7 +155,7 @@ class Sim /** Calculate the number of branches in the group. A branch occurs if two nodes in the group have fullyValidatedLedgers - that are not on the same chain of ledgers. + that are not on the same chain of ledgers. */ std::size_t branches(PeerGroup const& g) const; From 944527dbf9d151dff2e47a02170937f6dc341150 Mon Sep 17 00:00:00 2001 From: Brad Chase Date: Wed, 31 Jan 2018 09:59:48 -0500 Subject: [PATCH 34/38] [FOLD] Only acquire ledgers we are not yet acquiring --- src/ripple/consensus/Validations.h | 15 ++++++++++++--- src/test/consensus/Validations_test.cpp | 10 ++++++++++ 2 files changed, 22 insertions(+), 3 deletions(-) diff --git a/src/ripple/consensus/Validations.h b/src/ripple/consensus/Validations.h index d9776a9192f..66b28d4c55a 100644 --- a/src/ripple/consensus/Validations.h +++ b/src/ripple/consensus/Validations.h @@ -402,10 +402,19 @@ class Validations checkAcquired(lock); - if (boost::optional ledger = adaptor_.acquire(val.ledgerID())) - updateTrie(lock, key, *ledger); + auto it = acquiring_.find(val.ledgerID()); + if(it != acquiring_.end()) + { + it->second.insert(key); + } else - acquiring_[val.ledgerID()].insert(key); + { + if (boost::optional ledger = adaptor_.acquire(val.ledgerID())) + updateTrie(lock, key, *ledger); + else + acquiring_[val.ledgerID()].insert(key); + } + } /** Use the trie for a calculation diff --git a/src/test/consensus/Validations_test.cpp b/src/test/consensus/Validations_test.cpp index 8e8d8878bed..334add806cb 100644 --- a/src/test/consensus/Validations_test.cpp +++ b/src/test/consensus/Validations_test.cpp @@ -956,6 +956,7 @@ class Validations_test : public beast::unit_test::suite LedgerHistoryHelper h; TestHarness harness(h.oracle); Node a = harness.makeNode(); + Node b = harness.makeNode(); using ID = Ledger::ID; using Seq = Ledger::Seq; @@ -983,10 +984,19 @@ class Validations_test : public beast::unit_test::suite harness.vals().getPreferred(genesisLedger) == std::make_pair(ledgerAB.seq(), ledgerAB.id())); + // Another node requesting that ledger still doesn't change things + Validation val3 = b.validate(ID{4}, Seq{4}, 0s, 0s, true); + BEAST_EXPECT(ValStatus::current == harness.add(val3)); + BEAST_EXPECT(harness.vals().numTrustedForLedger(ID{4}) == 2); + BEAST_EXPECT( + harness.vals().getPreferred(genesisLedger) == + std::make_pair(ledgerAB.seq(), ledgerAB.id())); + // Switch to validation that is available harness.clock().advance(5s); Ledger ledgerABCDE = h["abcde"]; BEAST_EXPECT(ValStatus::current == harness.add(a.partial(ledgerABCDE))); + BEAST_EXPECT(ValStatus::current == harness.add(b.partial(ledgerABCDE))); BEAST_EXPECT( harness.vals().getPreferred(genesisLedger) == std::make_pair(ledgerABCDE.seq(), ledgerABCDE.id())); From ea18089f75c62881588b3f096826f2627fdec8b6 Mon Sep 17 00:00:00 2001 From: Brad Chase Date: Wed, 31 Jan 2018 10:56:52 -0500 Subject: [PATCH 35/38] [FOLD] Add SpanTip: Use the SpanTip class to allow finding the ID of the ancestor of a tip of ledger history. The Validations::getPreferred function can use this to more directly check if the current ledger is the parent of the preferred ledger. --- src/ripple/consensus/LedgerTrie.h | 54 +++++++++++--- src/ripple/consensus/Validations.h | 49 +++++-------- src/test/consensus/LedgerTrie_test.cpp | 99 +++++++++++++------------- 3 files changed, 113 insertions(+), 89 deletions(-) diff --git a/src/ripple/consensus/LedgerTrie.h b/src/ripple/consensus/LedgerTrie.h index 6e19008ccfb..5afca9e90c3 100644 --- a/src/ripple/consensus/LedgerTrie.h +++ b/src/ripple/consensus/LedgerTrie.h @@ -28,7 +28,46 @@ namespace ripple { +/** The tip of a span of ledger ancestry +*/ +template +class SpanTip +{ +public: + using Seq = typename Ledger::Seq; + using ID = typename Ledger::ID; + + SpanTip(Seq s, ID i, Ledger const lgr) + : seq{s}, id{i}, ledger{std::move(lgr)} + { + } + + // The sequence number 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 + preferred ledger + */ + ID + ancestor(Seq const& s) const + { + assert(s <= seq); + return ledger[s]; + } + +private: + Ledger const ledger; +}; + namespace ledger_trie_detail { + // Represents a span of ancestry of a ledger template class Span @@ -101,12 +140,12 @@ class Span return clamp(mismatch(ledger_, o)); } - // The Seq and ID of the end of the span - std::pair + // The tip of this span + SpanTip tip() const { Seq tipSeq{end_ - Seq{1}}; - return {tipSeq, ledger_[tipSeq]}; + return SpanTip{tipSeq, ledger_[tipSeq], ledger_}; } private: @@ -137,7 +176,7 @@ class Span friend std::ostream& operator<<(std::ostream& o, Span const& s) { - return o << s.tip().second << "[" << s.start_ << "," << s.end_ << ")"; + return o << s.tip().id << "[" << s.start_ << "," << s.end_ << ")"; } friend Span @@ -203,8 +242,8 @@ struct Node getJson() const { Json::Value res; - res["id"] = to_string(span.tip().second); - res["seq"] = static_cast(span.tip().first); + res["id"] = to_string(span.tip().id); + res["seq"] = static_cast(span.tip().seq); res["tipSupport"] = tipSupport; res["branchSupport"] = branchSupport; if (!children.empty()) @@ -407,7 +446,6 @@ class LedgerTrie boost::optional oldSuffix = loc->span.from(diffSeq); boost::optional newSuffix = Span{ledger}.from(diffSeq); - if (oldSuffix) { // Have @@ -623,7 +661,7 @@ class LedgerTrie issued by this node. @return Pair with the sequence number and ID of the preferred ledger */ - std::pair + SpanTip getPreferred(Seq const largestIssued) const { Node* curr = root.get(); diff --git a/src/ripple/consensus/Validations.h b/src/ripple/consensus/Validations.h index 66b28d4c55a..0a33011bb76 100644 --- a/src/ripple/consensus/Validations.h +++ b/src/ripple/consensus/Validations.h @@ -637,74 +637,61 @@ class Validations and is *not* an ancestor of the current working ledger; otherwise it remains the current working ledger. - @param currLedger The local node's current working ledger + @param curr The local node's current working ledger @return The sequence and id of the preferred working ledger, or Seq{0},ID{0} if no trusted validations are available to determine the preferred ledger. */ std::pair - getPreferred(Ledger const& currLedger) + getPreferred(Ledger const& curr) { - Seq preferredSeq{0}; - ID preferredID{0}; - ScopedLock lock{mutex_}; - std::tie(preferredSeq, preferredID) = + SpanTip preferred = withTrie(lock, [this](LedgerTrie& trie) { return trie.getPreferred(localSeqEnforcer_.largest()); }); // No trusted validations to determine branch - if (preferredSeq == Seq{0}) - return std::make_pair(preferredSeq, preferredID); - - Seq currSeq = currLedger.seq(); - ID currID = currLedger.id(); + if (preferred.seq == Seq{0}) + return std::make_pair(preferred.seq, preferred.id); // If we are the parent of the preferred ledger, stick with our // current ledger since we might be about to generate it - if (preferredSeq == currSeq + Seq{1}) - { - for (auto const& it : lastLedger_) - { - Ledger const& ledger = it.second; - if (ledger.seq() == preferredSeq && - ledger.id() == preferredID && ledger[currSeq] == currID) - return std::make_pair(currSeq, currID); - } - } + if (preferred.seq == curr.seq() + Seq{1} && + preferred.ancestor(curr.seq()) == curr.id()) + return std::make_pair(curr.seq(), curr.id()); // A ledger ahead of us is preferred regardless of whether it is // a descendant of our working ledger or it is on a different chain - if (preferredSeq > currSeq) - return std::make_pair(preferredSeq, preferredID); + if (preferred.seq > curr.seq()) + return std::make_pair(preferred.seq, preferred.id); // Only switch to earlier or same sequence number // if it is a different chain. - if (currLedger[preferredSeq] != preferredID) - return std::make_pair(preferredSeq, preferredID); + if (curr[preferred.seq] != preferred.id) + return std::make_pair(preferred.seq, preferred.id); // Stick with current ledger - return std::make_pair(currSeq, currID); + return std::make_pair(curr.seq(), curr.id()); } /** Get the ID of the preferred working ledger that exceeds a minimum valid ledger sequence number - @param currLedger Current working ledger + @param curr Current working ledger @param minValidSeq Minimum allowed sequence number - @return ID Of the preferred ledger, or currLedger if the preferred ledger + @return ID Of the preferred ledger, or curr if the preferred ledger is not valid */ ID - getPreferred(Ledger const& currLedger, Seq minValidSeq) + getPreferred(Ledger const& curr, Seq minValidSeq) { - std::pair preferred = getPreferred(currLedger); + std::pair preferred = getPreferred(curr); if(preferred.first >= minValidSeq && preferred.second != ID{0}) return preferred.second; - return currLedger.id(); + return curr.id(); } diff --git a/src/test/consensus/LedgerTrie_test.cpp b/src/test/consensus/LedgerTrie_test.cpp index e8cab4b6b62..76590d11751 100644 --- a/src/test/consensus/LedgerTrie_test.cpp +++ b/src/test/consensus/LedgerTrie_test.cpp @@ -352,15 +352,15 @@ class LedgerTrie_test : public beast::unit_test::suite { LedgerTrie t; LedgerHistoryHelper h; - BEAST_EXPECT(t.getPreferred(Seq{0}).second == h[""].id()); - BEAST_EXPECT(t.getPreferred(Seq{2}).second == h[""].id()); + BEAST_EXPECT(t.getPreferred(Seq{0}).id == h[""].id()); + BEAST_EXPECT(t.getPreferred(Seq{2}).id == h[""].id()); } // Single node no children { LedgerTrie t; LedgerHistoryHelper h; t.insert(h["abc"]); - BEAST_EXPECT(t.getPreferred(Seq{3}).second == h["abc"].id()); + BEAST_EXPECT(t.getPreferred(Seq{3}).id == h["abc"].id()); } // Single node smaller child support { @@ -368,8 +368,8 @@ class LedgerTrie_test : public beast::unit_test::suite LedgerHistoryHelper h; t.insert(h["abc"]); t.insert(h["abcd"]); - BEAST_EXPECT(t.getPreferred(Seq{3}).second == h["abc"].id()); - BEAST_EXPECT(t.getPreferred(Seq{4}).second == h["abc"].id()); + BEAST_EXPECT(t.getPreferred(Seq{3}).id == h["abc"].id()); + BEAST_EXPECT(t.getPreferred(Seq{4}).id == h["abc"].id()); } // Single node larger child { @@ -377,8 +377,8 @@ class LedgerTrie_test : public beast::unit_test::suite LedgerHistoryHelper h; t.insert(h["abc"]); t.insert(h["abcd"],2); - BEAST_EXPECT(t.getPreferred(Seq{3}).second == h["abcd"].id()); - BEAST_EXPECT(t.getPreferred(Seq{4}).second == h["abcd"].id()); + 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 { @@ -387,12 +387,12 @@ class LedgerTrie_test : public beast::unit_test::suite t.insert(h["abc"]); t.insert(h["abcd"]); t.insert(h["abce"]); - BEAST_EXPECT(t.getPreferred(Seq{3}).second == h["abc"].id()); - BEAST_EXPECT(t.getPreferred(Seq{4}).second == h["abc"].id()); + 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}).second == h["abc"].id()); - BEAST_EXPECT(t.getPreferred(Seq{4}).second == h["abc"].id()); + BEAST_EXPECT(t.getPreferred(Seq{3}).id == h["abc"].id()); + BEAST_EXPECT(t.getPreferred(Seq{4}).id == h["abc"].id()); } // Single node larger children { @@ -401,12 +401,12 @@ class LedgerTrie_test : public beast::unit_test::suite t.insert(h["abc"]); t.insert(h["abcd"],2); t.insert(h["abce"]); - BEAST_EXPECT(t.getPreferred(Seq{3}).second == h["abc"].id()); - BEAST_EXPECT(t.getPreferred(Seq{4}).second == h["abc"].id()); + 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}).second == h["abcd"].id()); - BEAST_EXPECT(t.getPreferred(Seq{4}).second == h["abcd"].id()); + BEAST_EXPECT(t.getPreferred(Seq{3}).id == h["abcd"].id()); + BEAST_EXPECT(t.getPreferred(Seq{4}).id == h["abcd"].id()); } // Tie-breaker by id { @@ -416,11 +416,11 @@ class LedgerTrie_test : public beast::unit_test::suite t.insert(h["abce"],2); BEAST_EXPECT(h["abce"].id() > h["abcd"].id()); - BEAST_EXPECT(t.getPreferred(Seq{4}).second == h["abce"].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}).second == h["abcd"].id()); + BEAST_EXPECT(t.getPreferred(Seq{4}).id == h["abcd"].id()); } // Tie-breaker not needed @@ -432,14 +432,14 @@ class LedgerTrie_test : public beast::unit_test::suite 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}).second == h["abce"].id()); - BEAST_EXPECT(t.getPreferred(Seq{4}).second == h["abce"].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}).second == h["abc"].id()); - BEAST_EXPECT(t.getPreferred(Seq{4}).second == h["abc"].id()); + 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 @@ -449,9 +449,9 @@ class LedgerTrie_test : public beast::unit_test::suite t.insert(h["abc"]); t.insert(h["abcd"],2); t.insert(h["abcde"],4); - BEAST_EXPECT(t.getPreferred(Seq{3}).second == h["abcde"].id()); - BEAST_EXPECT(t.getPreferred(Seq{4}).second == h["abcde"].id()); - BEAST_EXPECT(t.getPreferred(Seq{5}).second == h["abcde"].id()); + 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 @@ -462,22 +462,22 @@ class LedgerTrie_test : public beast::unit_test::suite t.insert(h["abcde"],2); t.insert(h["abcfg"],2); // 'de' and 'fg' are tied without 'abc' vote - BEAST_EXPECT(t.getPreferred(Seq{3}).second == h["abc"].id()); - BEAST_EXPECT(t.getPreferred(Seq{4}).second == h["abc"].id()); - BEAST_EXPECT(t.getPreferred(Seq{5}).second == h["abc"].id()); + 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}).second == h["abcde"].id()); - BEAST_EXPECT(t.getPreferred(Seq{4}).second == h["abcde"].id()); + 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}).second == h["abc"].id()); + BEAST_EXPECT(t.getPreferred(Seq{5}).id == h["abc"].id()); } // Changing largestSeq perspective changes preferred branch @@ -501,40 +501,40 @@ class LedgerTrie_test : public beast::unit_test::suite t.insert(h["abde"],2); // B has more branch support - BEAST_EXPECT(t.getPreferred(Seq{1}).second == h["ab"].id()); - BEAST_EXPECT(t.getPreferred(Seq{2}).second == h["ab"].id()); + 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}).second == h["a"].id()); - BEAST_EXPECT(t.getPreferred(Seq{4}).second == h["a"].id()); + 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 t.remove(h["abde"]); t.insert(h["abdeg"]); - BEAST_EXPECT(t.getPreferred(Seq{1}).second == h["ab"].id()); - BEAST_EXPECT(t.getPreferred(Seq{2}).second == h["ab"].id()); - BEAST_EXPECT(t.getPreferred(Seq{3}).second == h["a"].id()); - BEAST_EXPECT(t.getPreferred(Seq{4}).second == h["a"].id()); - BEAST_EXPECT(t.getPreferred(Seq{5}).second == h["a"].id()); + 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 t.remove(h["ac"]); t.insert(h["abh"]); - BEAST_EXPECT(t.getPreferred(Seq{1}).second == h["ab"].id()); - BEAST_EXPECT(t.getPreferred(Seq{2}).second == h["ab"].id()); - BEAST_EXPECT(t.getPreferred(Seq{3}).second == h["ab"].id()); - BEAST_EXPECT(t.getPreferred(Seq{4}).second == h["a"].id()); - BEAST_EXPECT(t.getPreferred(Seq{5}).second == h["a"].id()); + 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 t.remove(h["acf"]); t.insert(h["abde"]); - BEAST_EXPECT(t.getPreferred(Seq{1}).second == h["abde"].id()); - BEAST_EXPECT(t.getPreferred(Seq{2}).second == h["abde"].id()); - BEAST_EXPECT(t.getPreferred(Seq{3}).second == h["abde"].id()); - BEAST_EXPECT(t.getPreferred(Seq{4}).second == h["ab"].id()); - BEAST_EXPECT(t.getPreferred(Seq{5}).second == h["ab"].id()); + 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()); } @@ -625,7 +625,6 @@ class LedgerTrie_test : public beast::unit_test::suite testGetPreferred(); testRootRelated(); testStress(); - } }; From 2a97f830887661b990b16b6ad8a660b17bb23eb8 Mon Sep 17 00:00:00 2001 From: Brad Chase Date: Fri, 2 Feb 2018 13:09:51 -0500 Subject: [PATCH 36/38] [FOLD] Address PR comments --- src/ripple/app/consensus/RCLValidations.cpp | 25 ++++++-------- src/ripple/app/consensus/RCLValidations.h | 2 +- src/ripple/consensus/LedgerTrie.h | 4 +-- src/ripple/consensus/Validations.h | 2 +- src/test/consensus/LedgerTrie_test.cpp | 38 ++++++++++++++++++--- src/test/consensus/Validations_test.cpp | 3 +- 6 files changed, 51 insertions(+), 23 deletions(-) diff --git a/src/ripple/app/consensus/RCLValidations.cpp b/src/ripple/app/consensus/RCLValidations.cpp index 86c42ad7325..85bf7fd69f7 100644 --- a/src/ripple/app/consensus/RCLValidations.cpp +++ b/src/ripple/app/consensus/RCLValidations.cpp @@ -297,15 +297,15 @@ handleNewValidation(Application& app, beast::Journal j = validations.adaptor().journal(); auto dmp = [&](beast::Journal::Stream s, std::string const& msg) { - s << "Val for " << hash - << (val->isTrusted() ? " trusted/" : " UNtrusted/") - << (val->isFull() ? "full" : "partial") << " from " - << toBase58(TokenType::TOKEN_NODE_PUBLIC, *masterKey) - << " signing key " - << toBase58(TokenType::TOKEN_NODE_PUBLIC, signingKey) << " " - << msg - << " src=" << source; - }; + 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)) { @@ -318,16 +318,14 @@ handleNewValidation(Application& app, if (masterKey) { ValStatus const outcome = validations.add(*masterKey, val); - - - if(j.debug()) dmp(j.debug(), to_string(outcome)); if(outcome == ValStatus::badSeq && j.warn()) { auto const seq = val->getFieldU32(sfLedgerSequence); - dmp(j.warn(), "already validated sequence past " + to_string(seq)); + dmp(j.warn(), + "already validated sequence at or past " + to_string(seq)); } else if(outcome == ValStatus::repeatID && j.warn()) { @@ -341,7 +339,6 @@ handleNewValidation(Application& app, { app.getLedgerMaster().checkAccept( hash, val->getFieldU32(sfLedgerSequence)); - shouldRelay = true; } diff --git a/src/ripple/app/consensus/RCLValidations.h b/src/ripple/app/consensus/RCLValidations.h index 16b15a1828c..1bd3978eded 100644 --- a/src/ripple/app/consensus/RCLValidations.h +++ b/src/ripple/app/consensus/RCLValidations.h @@ -178,7 +178,7 @@ class RCLValidatedLedger beast::Journal j_; }; -/** Generic validations adaptor classs for RCL +/** Generic validations adaptor class for RCL Manages storing and writing stale RCLValidations to the sqlite DB and acquiring validated ledgers from the network. diff --git a/src/ripple/consensus/LedgerTrie.h b/src/ripple/consensus/LedgerTrie.h index 5afca9e90c3..a80c0471d3e 100644 --- a/src/ripple/consensus/LedgerTrie.h +++ b/src/ripple/consensus/LedgerTrie.h @@ -42,7 +42,7 @@ class SpanTip { } - // The sequence number the tip ledger + // The sequence number of the tip ledger Seq seq; // The ID of the tip ledger ID id; @@ -53,7 +53,7 @@ class SpanTip @return The ID of the ancestor with that sequence number @note s must be less than or equal to the sequence number of the - preferred ledger + tip ledger */ ID ancestor(Seq const& s) const diff --git a/src/ripple/consensus/Validations.h b/src/ripple/consensus/Validations.h index 0a33011bb76..59c8c473d19 100644 --- a/src/ripple/consensus/Validations.h +++ b/src/ripple/consensus/Validations.h @@ -791,7 +791,7 @@ class Validations /** Get the set of known public keys associated with current validations - @return The set of knows keys for current listed validators + @return The set of known keys for current listed validators */ hash_set getCurrentPublicKeys() diff --git a/src/test/consensus/LedgerTrie_test.cpp b/src/test/consensus/LedgerTrie_test.cpp index 76590d11751..651bf10383d 100644 --- a/src/test/consensus/LedgerTrie_test.cpp +++ b/src/test/consensus/LedgerTrie_test.cpp @@ -482,7 +482,7 @@ class LedgerTrie_test : public beast::unit_test::suite // Changing largestSeq perspective changes preferred branch { - /** Build the tree below with tip support annotated + /** Build the tree below with initial tip support annotated A / \ B(1) C(1) @@ -508,7 +508,17 @@ class LedgerTrie_test : public beast::unit_test::suite 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 + /** 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"]); @@ -518,7 +528,17 @@ class LedgerTrie_test : public beast::unit_test::suite 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 + /** 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()); @@ -527,7 +547,17 @@ class LedgerTrie_test : public beast::unit_test::suite 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 + /** 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()); diff --git a/src/test/consensus/Validations_test.cpp b/src/test/consensus/Validations_test.cpp index 334add806cb..ff921102ca2 100644 --- a/src/test/consensus/Validations_test.cpp +++ b/src/test/consensus/Validations_test.cpp @@ -462,6 +462,7 @@ class Validations_test : public beast::unit_test::suite std::vector triggers = { [&](TestValidations& vals) { vals.currentTrusted(); }, + [&](TestValidations& vals) { vals.getCurrentPublicKeys(); }, [&](TestValidations& vals) { vals.getPreferred(genesisLedger); }, [&](TestValidations& vals) { vals.getNodesAfter(ledgerA, ledgerA.id()); @@ -516,7 +517,7 @@ class Validations_test : public beast::unit_test::suite c = harness.makeNode(), d = harness.makeNode(); c.untrust(); - // first round a,b,c agree, d has differing id + // first round a,b,c agree, d has is partial BEAST_EXPECT(ValStatus::current == harness.add(a.validate(ledgerA))); BEAST_EXPECT(ValStatus::current == harness.add(b.validate(ledgerA))); BEAST_EXPECT(ValStatus::current == harness.add(c.validate(ledgerA))); From 5fd14dea0148da09e35a0e6a2a2551bb11721f5d Mon Sep 17 00:00:00 2001 From: Brad Chase Date: Fri, 2 Feb 2018 19:52:41 -0500 Subject: [PATCH 37/38] [FOLD] Track seq and ID in acquiring ledgers --- src/ripple/consensus/Validations.h | 24 +++++++++++++----------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/src/ripple/consensus/Validations.h b/src/ripple/consensus/Validations.h index 59c8c473d19..b7f69cf8b16 100644 --- a/src/ripple/consensus/Validations.h +++ b/src/ripple/consensus/Validations.h @@ -303,7 +303,7 @@ class Validations hash_map lastLedger_; // Set of ledgers being acquired from the network - hash_map> acquiring_; + hash_map, hash_set> acquiring_; // Parameters to determine validation staleness ValidationParms const parms_; @@ -318,7 +318,7 @@ class Validations removeTrie(ScopedLock const&, NodeKey const& key, Validation const& val) { { - auto it = acquiring_.find(val.ledgerID()); + auto it = acquiring_.find(std::make_pair(val.seq(), val.ledgerID())); if (it != acquiring_.end()) { it->second.erase(key); @@ -342,7 +342,8 @@ class Validations { for (auto it = acquiring_.begin(); it != acquiring_.end();) { - if (boost::optional ledger = adaptor_.acquire(it->first)) + if (boost::optional ledger = + adaptor_.acquire(it->first.second)) { for (NodeKey const& key : it->second) updateTrie(lock, key, *ledger); @@ -377,21 +378,21 @@ class Validations @param lock Existing lock of mutex_ @param key The master public key identifying the validating node @param val The trusted validation issued by the node - @param priorID If not none, the last current validated ledger from key + @param prior If not none, the last current validated ledger Seq,ID of key */ void updateTrie( ScopedLock const& lock, NodeKey const& key, Validation const& val, - boost::optional priorID) + boost::optional> prior) { assert(val.trusted()); // Clear any prior acquiring ledger for this node - if (priorID) + if (prior) { - auto it = acquiring_.find(*priorID); + auto it = acquiring_.find(*prior); if (it != acquiring_.end()) { it->second.erase(key); @@ -402,7 +403,8 @@ class Validations checkAcquired(lock); - auto it = acquiring_.find(val.ledgerID()); + std::pair valPair{val.seq(),val.ledgerID()}; + auto it = acquiring_.find(valPair); if(it != acquiring_.end()) { it->second.insert(key); @@ -412,7 +414,7 @@ class Validations if (boost::optional ledger = adaptor_.acquire(val.ledgerID())) updateTrie(lock, key, *ledger); else - acquiring_[val.ledgerID()].insert(key); + acquiring_[valPair].insert(key); } } @@ -595,11 +597,11 @@ class Validations Validation& oldVal = ins.first->second; if (val.signTime() > oldVal.signTime()) { - ID oldID = oldVal.ledgerID(); + std::pair old(oldVal.seq(),oldVal.ledgerID()); adaptor_.onStale(std::move(oldVal)); ins.first->second = val; if (val.trusted()) - updateTrie(lock, key, val, oldID); + updateTrie(lock, key, val, old); } else return ValStatus::stale; From f0bdfe1f761ac30bc5cada5953634d6d2d74f95f Mon Sep 17 00:00:00 2001 From: Brad Chase Date: Fri, 2 Feb 2018 20:13:20 -0500 Subject: [PATCH 38/38] [FOLD] Fallback to acquiring ledgers: When first starting up, a node may still be working to acquire the ledgers for validations it has received. When calculating the preferred ledger in this case, we should still rely on these validations over peer counts until we acquire our first validated ledger. This should fix the speed at which a newly started node syncs with the network. --- src/ripple/consensus/Validations.h | 20 ++++++++++++++++++++ src/test/consensus/Validations_test.cpp | 14 ++++++++++++++ 2 files changed, 34 insertions(+) diff --git a/src/ripple/consensus/Validations.h b/src/ripple/consensus/Validations.h index b7f69cf8b16..52a06185266 100644 --- a/src/ripple/consensus/Validations.h +++ b/src/ripple/consensus/Validations.h @@ -656,7 +656,27 @@ class Validations // No trusted validations to determine branch if (preferred.seq == Seq{0}) + { + // fall back to majority over acquiring ledgers + auto it = std::max_element( + acquiring_.begin(), + acquiring_.end(), + [](auto const& a, auto const& b) { + std::pair const& aKey = a.first; + typename hash_set::size_type const& aSize = + a.second.size(); + std::pair const& bKey = b.first; + typename hash_set::size_type const& bSize = + b.second.size(); + // order by number of trusted peers validating that ledger + // break ties with ledger ID + return std::tie(aSize, aKey.second) < + std::tie(bSize, bKey.second); + }); + if(it != acquiring_.end()) + return it->first; return std::make_pair(preferred.seq, preferred.id); + } // If we are the parent of the preferred ledger, stick with our // current ledger since we might be about to generate it diff --git a/src/test/consensus/Validations_test.cpp b/src/test/consensus/Validations_test.cpp index ff921102ca2..24821fd47d3 100644 --- a/src/test/consensus/Validations_test.cpp +++ b/src/test/consensus/Validations_test.cpp @@ -970,6 +970,20 @@ class Validations_test : public beast::unit_test::suite BEAST_EXPECT(harness.vals().numTrustedForLedger(ID{2}) == 1); // but ledger based data is not BEAST_EXPECT(harness.vals().getNodesAfter(genesisLedger, ID{0}) == 0); + // Initial preferred branch falls back to the ledger we are trying to + // acquire + BEAST_EXPECT( + harness.vals().getPreferred(genesisLedger) == + std::make_pair(Seq{2}, ID{2})); + + // After adding another unavailable validation, the preferred ledger + // breaks ties via higher ID + BEAST_EXPECT( + ValStatus::current == + harness.add(b.validate(ID{3}, Seq{2}, 0s, 0s, true))); + BEAST_EXPECT( + harness.vals().getPreferred(genesisLedger) == + std::make_pair(Seq{2}, ID{3})); // Create the ledger Ledger ledgerAB = h["ab"];