#include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace xrpl::test::csf { class Validations_test : public beast::unit_test::Suite { using clock_type = beast::AbstractClock const; // Helper to convert steady_clock to a reasonable NetClock // This allows a single manual clock in the unit tests static NetClock::time_point toNetClock(clock_type const& c) { // We don't care about the actual epochs, but do want the // generated NetClock time to be well past its epoch to ensure // any subtractions are positive using namespace std::chrono; return NetClock::time_point( duration_cast(c.now().time_since_epoch() + 86400s)); } // Represents a node that can issue validations class Node { clock_type const& c_; PeerID nodeID_; bool trusted_ = true; std::size_t signIdx_{1}; std::optional loadFee_; public: Node(PeerID nodeID, clock_type const& c) : c_(c), nodeID_(nodeID) { } void untrust() { trusted_ = false; } void trust() { trusted_ = true; } void setLoadFee(std::uint32_t fee) { loadFee_ = fee; } [[nodiscard]] PeerID nodeID() const { return nodeID_; } void advanceKey() { signIdx_++; } [[nodiscard]] PeerKey currKey() const { return std::make_pair(nodeID_, signIdx_); } [[nodiscard]] PeerKey masterKey() const { return std::make_pair(nodeID_, 0); } [[nodiscard]] NetClock::time_point now() const { return toNetClock(c_); } // Issue a new validation with given sequence number and id and // with signing and seen times offset from the common clock [[nodiscard]] Validation validate( Ledger::ID id, Ledger::Seq seq, NetClock::duration signOffset, NetClock::duration seenOffset, bool full) const { Validation v{ id, seq, now() + signOffset, now() + seenOffset, currKey(), nodeID_, full, loadFee_}; if (trusted_) v.setTrusted(); return v; } [[nodiscard]] Validation validate(Ledger ledger, NetClock::duration signOffset, NetClock::duration seenOffset) const { return validate(ledger.id(), ledger.seq(), signOffset, seenOffset, true); } [[nodiscard]] Validation validate(Ledger ledger) const { return validate( ledger.id(), ledger.seq(), NetClock::duration{0}, NetClock::duration{0}, true); } [[nodiscard]] Validation partial(Ledger ledger) const { return validate( ledger.id(), ledger.seq(), NetClock::duration{0}, NetClock::duration{0}, false); } }; // Generic Validations adaptor class Adaptor { clock_type& c_; LedgerOracle& oracle_; public: // Non-locking mutex to avoid locks in generic Validations struct Mutex { void lock() { } void unlock() { } }; using Validation = csf::Validation; using Ledger = csf::Ledger; Adaptor(clock_type& c, LedgerOracle& o) : c_{c}, oracle_{o} { } [[nodiscard]] NetClock::time_point now() const { return toNetClock(c_); } std::optional acquire(Ledger::ID const& id) { return oracle_.lookup(id); } }; // Specialize generic Validations using the above types using TestValidations = Validations; // Gather the dependencies of TestValidations in a single class and provide // accessors for simplifying test logic class TestHarness { ValidationParms p_; beast::ManualClock clock_; TestValidations tv_; PeerID nextNodeId_{0}; public: explicit TestHarness(LedgerOracle& o) : tv_(p_, clock_, clock_, o) { } ValStatus add(Validation const& v) { return tv_.add(v.nodeID(), v); } TestValidations& vals() { return tv_; } Node makeNode() { return Node(nextNodeId_++, clock_); } ValidationParms parms() const { return p_; } auto& clock() { return clock_; } }; Ledger const genesisLedger_{Ledger::MakeGenesis{}}; void testAddValidation() { using namespace std::chrono_literals; testcase("Add validation"); LedgerHistoryHelper h; Ledger const ledgerA = h["a"]; Ledger ledgerAB = h["ab"]; Ledger ledgerAZ = h["az"]; Ledger ledgerABC = h["abc"]; Ledger const ledgerABCD = h["abcd"]; Ledger const ledgerABCDE = h["abcde"]; { TestHarness harness(h.oracle); Node n = harness.makeNode(); auto const v = n.validate(ledgerA); // Add a current validation BEAST_EXPECT(ValStatus::Current == harness.add(v)); // Re-adding violates the increasing seq requirement for full // validations BEAST_EXPECT(ValStatus::BadSeq == harness.add(v)); harness.clock().advance(1s); BEAST_EXPECT(ValStatus::Current == harness.add(n.validate(ledgerAB))); // Test the node changing signing key // 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); // Rotate signing keys n.advanceKey(); harness.clock().advance(1s); // Cannot re-do the same full validation sequence BEAST_EXPECT(ValStatus::Conflicting == harness.add(n.validate(ledgerAB))); // Cannot send the same partial validation sequence BEAST_EXPECT(ValStatus::Conflicting == harness.add(n.partial(ledgerAB))); // Now trusts the newest ledger too harness.clock().advance(1s); BEAST_EXPECT(ValStatus::Current == harness.add(n.validate(ledgerABC))); BEAST_EXPECT(harness.vals().numTrustedForLedger(ledgerAB.id()) == 1); BEAST_EXPECT(harness.vals().numTrustedForLedger(ledgerABC.id()) == 1); // Processing validations out of order should ignore the older // validation harness.clock().advance(2s); auto const valABCDE = n.validate(ledgerABCDE); harness.clock().advance(4s); auto const valABCD = n.validate(ledgerABCD); BEAST_EXPECT(ValStatus::Current == harness.add(valABCD)); BEAST_EXPECT(ValStatus::Stale == harness.add(valABCDE)); } { // Process validations out of order with shifted times TestHarness harness(h.oracle); Node const n = harness.makeNode(); // 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))); // Process a validation that has a later seq and later sign // time BEAST_EXPECT(ValStatus::Current == harness.add(n.validate(ledgerABC, 1s, 1s))); } { // Test stale on arrival validations TestHarness harness(h.oracle); Node const n = harness.makeNode(); BEAST_EXPECT( ValStatus::Stale == harness.add(n.validate(ledgerA, -harness.parms().validationCurrentEarly, 0s))); BEAST_EXPECT( ValStatus::Stale == harness.add(n.validate(ledgerA, harness.parms().validationCurrentWall, 0s))); BEAST_EXPECT( ValStatus::Stale == harness.add(n.validate(ledgerA, 0s, harness.parms().validationCurrentLocal))); } { // 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::Conflicting == process(ledgerAZ)); harness.clock().advance(harness.parms().validationSetExpires + 1ms); BEAST_EXPECT(ValStatus::Current == process(ledgerAZ)); } } } void testOnStale() { testcase("Stale validation"); // Verify validation becomes stale based solely on time passing, but // use different functions to trigger the check for staleness LedgerHistoryHelper h; Ledger ledgerA = h["a"]; Ledger const ledgerAB = h["ab"]; using Trigger = std::function; std::vector const triggers = { [&](TestValidations& vals) { vals.currentTrusted(); }, [&](TestValidations& vals) { vals.getCurrentNodeIDs(); }, [&](TestValidations& vals) { vals.getPreferred(genesisLedger_); }, [&](TestValidations& vals) { vals.getNodesAfter(ledgerA, ledgerA.id()); }}; for (Trigger const& trigger : triggers) { TestHarness harness(h.oracle); Node const n = harness.makeNode(); BEAST_EXPECT(ValStatus::Current == harness.add(n.validate(ledgerAB))); trigger(harness.vals()); BEAST_EXPECT(harness.vals().getNodesAfter(ledgerA, ledgerA.id()) == 1); BEAST_EXPECT( harness.vals().getPreferred(genesisLedger_) == std::make_pair(ledgerAB.seq(), ledgerAB.id())); harness.clock().advance(harness.parms().validationCurrentLocal); // trigger check for stale trigger(harness.vals()); BEAST_EXPECT(harness.vals().getNodesAfter(ledgerA, ledgerA.id()) == 0); BEAST_EXPECT(harness.vals().getPreferred(genesisLedger_) == std::nullopt); } } void testGetNodesAfter() { // 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 const ledgerA = h["a"]; Ledger const ledgerAB = h["ab"]; Ledger const ledgerABC = h["abc"]; Ledger const ledgerAD = h["ad"]; TestHarness harness(h.oracle); Node const trustedNode1 = harness.makeNode(); Node const trustedNode2 = harness.makeNode(); Node const trustedNode3 = harness.makeNode(); Node notTrustedNode = harness.makeNode(); notTrustedNode.untrust(); // first round a,b,c agree, d has is partial BEAST_EXPECT(ValStatus::Current == harness.add(trustedNode1.validate(ledgerA))); BEAST_EXPECT(ValStatus::Current == harness.add(trustedNode2.validate(ledgerA))); BEAST_EXPECT(ValStatus::Current == harness.add(notTrustedNode.validate(ledgerA))); BEAST_EXPECT(ValStatus::Current == harness.add(trustedNode3.partial(ledgerA))); for (Ledger const& ledger : {ledgerA, ledgerAB, ledgerABC, ledgerAD}) BEAST_EXPECT(harness.vals().getNodesAfter(ledger, ledger.id()) == 0); harness.clock().advance(5s); BEAST_EXPECT(ValStatus::Current == harness.add(trustedNode1.validate(ledgerAB))); BEAST_EXPECT(ValStatus::Current == harness.add(trustedNode2.validate(ledgerABC))); BEAST_EXPECT(ValStatus::Current == harness.add(notTrustedNode.validate(ledgerAB))); BEAST_EXPECT(ValStatus::Current == harness.add(trustedNode3.partial(ledgerABC))); BEAST_EXPECT(harness.vals().getNodesAfter(ledgerA, ledgerA.id()) == 3); BEAST_EXPECT(harness.vals().getNodesAfter(ledgerAB, ledgerAB.id()) == 2); BEAST_EXPECT(harness.vals().getNodesAfter(ledgerABC, ledgerABC.id()) == 0); BEAST_EXPECT(harness.vals().getNodesAfter(ledgerAD, ledgerAD.id()) == 0); // If given a ledger inconsistent with the id, is still able to check using slower method BEAST_EXPECT(harness.vals().getNodesAfter(ledgerAD, ledgerA.id()) == 1); BEAST_EXPECT(harness.vals().getNodesAfter(ledgerAD, ledgerAB.id()) == 2); } void testCurrentTrusted() { using namespace std::chrono_literals; testcase("Current trusted validations"); LedgerHistoryHelper h; Ledger const ledgerA = h["a"]; Ledger const ledgerB = h["b"]; Ledger const ledgerAC = h["ac"]; TestHarness harness(h.oracle); Node const a = harness.makeNode(); Node b = harness.makeNode(); b.untrust(); 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() == 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(ledgerAC))); // New validation for a BEAST_EXPECT(harness.vals().currentTrusted().size() == 1); BEAST_EXPECT(harness.vals().currentTrusted()[0].ledgerID() == ledgerAC.id()); BEAST_EXPECT(harness.vals().currentTrusted()[0].seq() == ledgerAC.seq()); // Pass enough time for it to go stale harness.clock().advance(harness.parms().validationCurrentLocal); BEAST_EXPECT(harness.vals().currentTrusted().empty()); } void testGetCurrentPublicKeys() { using namespace std::chrono_literals; testcase("Current public keys"); LedgerHistoryHelper h; Ledger const ledgerA = h["a"]; Ledger const ledgerAC = h["ac"]; 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(ledgerA))); { hash_set const expectedKeys = {a.nodeID(), b.nodeID()}; BEAST_EXPECT(harness.vals().getCurrentNodeIDs() == expectedKeys); } harness.clock().advance(3s); // Change keys and issue partials a.advanceKey(); b.advanceKey(); for (auto const& node : {a, b}) BEAST_EXPECT(ValStatus::Current == harness.add(node.partial(ledgerAC))); { hash_set const expectedKeys = {a.nodeID(), b.nodeID()}; BEAST_EXPECT(harness.vals().getCurrentNodeIDs() == expectedKeys); } // Pass enough time for them to go stale harness.clock().advance(harness.parms().validationCurrentLocal); BEAST_EXPECT(harness.vals().getCurrentNodeIDs().empty()); } void testTrustedByLedgerFunctions() { // Test the Validations functions that calculate a value by ledger ID using namespace std::chrono_literals; testcase("By ledger functions"); // Several Validations functions return a set of values associated // with trusted ledgers sharing the same ledger ID. The tests below // exercise this logic by saving the set of trusted Validations, and // verifying that the Validations member functions all calculate the // proper transformation of the available ledgers. LedgerHistoryHelper h; TestHarness harness(h.oracle); Node a = harness.makeNode(), b = 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, std::vector> trustedValidations; //---------------------------------------------------------------------- // checkers auto sorted = [](auto vec) { std::sort(vec.begin(), vec.end()); return vec; }; auto compare = [&]() { for (auto& it : trustedValidations) { auto const& id = it.first.first; auto const& seq = it.first.second; auto const& expectedValidations = it.second; BEAST_EXPECT(harness.vals().numTrustedForLedger(id) == expectedValidations.size()); BEAST_EXPECT( sorted(harness.vals().getTrustedForLedger(id, seq)) == sorted(expectedValidations)); std::uint32_t const baseFee = 0; std::vector expectedFees; expectedFees.reserve(expectedValidations.size()); for (auto const& val : expectedValidations) { expectedFees.push_back(val.loadFee().value_or(baseFee)); } BEAST_EXPECT(sorted(harness.vals().fees(id, baseFee)) == sorted(expectedFees)); } }; //---------------------------------------------------------------------- Ledger const ledgerA = h["a"]; Ledger const ledgerB = h["b"]; Ledger const ledgerAC = h["ac"]; // Add a dummy ID to cover unknown ledger identifiers trustedValidations[{Ledger::ID{100}, Ledger::Seq{100}}] = {}; // first round a,b,c agree for (auto const& node : {a, b, c}) { auto const val = node.validate(ledgerA); BEAST_EXPECT(ValStatus::Current == harness.add(val)); if (val.trusted()) trustedValidations[{val.ledgerID(), val.seq()}].emplace_back(val); } // d disagrees { auto const val = d.validate(ledgerB); BEAST_EXPECT(ValStatus::Current == harness.add(val)); trustedValidations[{val.ledgerID(), val.seq()}].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 for (auto const& node : {a, b, c}) { auto const val = node.validate(ledgerAC); BEAST_EXPECT(ValStatus::Current == harness.add(val)); if (val.trusted()) trustedValidations[{val.ledgerID(), val.seq()}].emplace_back(val); } // d now thinks ledger 1, but cannot re-issue a previously used seq // and attempting it should generate a conflict. { BEAST_EXPECT(ValStatus::Conflicting == harness.add(d.partial(ledgerA))); } // e only issues partials { BEAST_EXPECT(ValStatus::Current == harness.add(e.partial(ledgerAC))); } compare(); } void testExpire() { // Verify expiring clears out validations stored by ledger testcase("Expire validations"); SuiteJournal j("Validations_test", *this); LedgerHistoryHelper h; TestHarness harness(h.oracle); Node const a = harness.makeNode(); constexpr Ledger::Seq kOne(1); constexpr Ledger::Seq kTwo(2); // simple cases Ledger const ledgerA = h["a"]; BEAST_EXPECT(ValStatus::Current == harness.add(a.validate(ledgerA))); BEAST_EXPECT(harness.vals().numTrustedForLedger(ledgerA.id()) == 1); harness.vals().expire(j); BEAST_EXPECT(harness.vals().numTrustedForLedger(ledgerA.id()) == 1); harness.clock().advance(harness.parms().validationSetExpires); harness.vals().expire(j); BEAST_EXPECT(harness.vals().numTrustedForLedger(ledgerA.id()) == 0); // use setSeqToKeep to keep the validation from expire Ledger const ledgerB = h["ab"]; BEAST_EXPECT(ValStatus::Current == harness.add(a.validate(ledgerB))); BEAST_EXPECT(harness.vals().numTrustedForLedger(ledgerB.id()) == 1); harness.vals().setSeqToKeep(ledgerB.seq(), ledgerB.seq() + kOne); harness.clock().advance(harness.parms().validationSetExpires); harness.vals().expire(j); BEAST_EXPECT(harness.vals().numTrustedForLedger(ledgerB.id()) == 1); // change toKeep harness.vals().setSeqToKeep(ledgerB.seq() + kOne, ledgerB.seq() + kTwo); // advance clock slowly int const loops = harness.parms().validationSetExpires / harness.parms().validationFRESHNESS + 1; for (int i = 0; i < loops; ++i) { harness.clock().advance(harness.parms().validationFRESHNESS); harness.vals().expire(j); } BEAST_EXPECT(harness.vals().numTrustedForLedger(ledgerB.id()) == 0); // Allow the validation with high seq to expire Ledger const ledgerC = h["abc"]; BEAST_EXPECT(ValStatus::Current == harness.add(a.validate(ledgerC))); BEAST_EXPECT(harness.vals().numTrustedForLedger(ledgerC.id()) == 1); harness.vals().setSeqToKeep(ledgerC.seq() - kOne, ledgerC.seq()); harness.clock().advance(harness.parms().validationSetExpires); harness.vals().expire(j); BEAST_EXPECT(harness.vals().numTrustedForLedger(ledgerC.id()) == 0); } void testFlush() { // Test final flush of validations using namespace std::chrono_literals; testcase("Flush validations"); LedgerHistoryHelper h; TestHarness harness(h.oracle); Node const trustedNode1 = harness.makeNode(); Node const trustedNode2 = harness.makeNode(); Node notTrustedNode = harness.makeNode(); notTrustedNode.untrust(); Ledger const ledgerA = h["a"]; Ledger const ledgerAB = h["ab"]; hash_map expected; for (auto const& node : {trustedNode1, trustedNode2, notTrustedNode}) { auto const val = node.validate(ledgerA); BEAST_EXPECT(ValStatus::Current == harness.add(val)); expected.emplace(node.nodeID(), val); } // 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 = trustedNode1.validate(ledgerAB); BEAST_EXPECT(ValStatus::Current == harness.add(newVal)); expected.find(trustedNode1.nodeID())->second = newVal; } void testGetPreferredLedger() { using namespace std::chrono_literals; testcase("Preferred Ledger"); LedgerHistoryHelper h; TestHarness harness(h.oracle); Node const trustedNode1 = harness.makeNode(); Node const trustedNode2 = harness.makeNode(); Node const trustedNode3 = harness.makeNode(); Node notTrustedNode = harness.makeNode(); notTrustedNode.untrust(); Ledger const ledgerA = h["a"]; Ledger const ledgerB = h["b"]; Ledger const ledgerAC = h["ac"]; Ledger const ledgerACD = h["acd"]; using Seq = Ledger::Seq; auto pref = [](Ledger ledger) { return std::make_pair(ledger.seq(), ledger.id()); }; // Empty (no ledgers) BEAST_EXPECT(harness.vals().getPreferred(ledgerA) == std::nullopt); // Single ledger BEAST_EXPECT(ValStatus::Current == harness.add(trustedNode1.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(trustedNode2.validate(ledgerA))); BEAST_EXPECT(ValStatus::Current == harness.add(notTrustedNode.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(trustedNode3.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 : {trustedNode1, trustedNode2, notTrustedNode, trustedNode3}) BEAST_EXPECT(ValStatus::Current == harness.add(node.validate(ledgerAC))); // Parent of preferred stays put BEAST_EXPECT(harness.vals().getPreferred(ledgerA) == pref(ledgerA)); // Earlier different chain, switch BEAST_EXPECT(harness.vals().getPreferred(ledgerB) == pref(ledgerAC)); // Later on chain, stays where it is BEAST_EXPECT(harness.vals().getPreferred(ledgerACD) == pref(ledgerACD)); // Any later grandchild or different chain is preferred harness.clock().advance(5s); for (auto const& node : {trustedNode1, trustedNode2, notTrustedNode, trustedNode3}) 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 const a = harness.makeNode(); Ledger const ledgerA = h["a"]; Ledger const ledgerB = h["b"]; Ledger const ledgerC = h["c"]; using ID = Ledger::ID; using Seq = Ledger::Seq; hash_map peerCounts; // No trusted validations or counts sticks with current ledger BEAST_EXPECT(harness.vals().getPreferredLCL(ledgerA, Seq{0}, peerCounts) == ledgerA.id()); ++peerCounts[ledgerB.id()]; // No trusted validations, rely on peer counts BEAST_EXPECT(harness.vals().getPreferredLCL(ledgerA, Seq{0}, peerCounts) == ledgerB.id()); ++peerCounts[ledgerC.id()]; // No trusted validations, tied peers goes with larger ID BEAST_EXPECT(ledgerC.id() > ledgerB.id()); BEAST_EXPECT(harness.vals().getPreferredLCL(ledgerA, Seq{0}, peerCounts) == ledgerC.id()); peerCounts[ledgerC.id()] += 1000; // Single trusted always wins over peer counts BEAST_EXPECT(ValStatus::Current == harness.add(a.validate(ledgerA))); BEAST_EXPECT(harness.vals().getPreferredLCL(ledgerA, Seq{0}, peerCounts) == ledgerA.id()); BEAST_EXPECT(harness.vals().getPreferredLCL(ledgerB, Seq{0}, peerCounts) == ledgerA.id()); BEAST_EXPECT(harness.vals().getPreferredLCL(ledgerC, Seq{0}, peerCounts) == ledgerA.id()); // Stick with current ledger if trusted validation ledger has too old // of a sequence BEAST_EXPECT(harness.vals().getPreferredLCL(ledgerB, Seq{2}, peerCounts) == ledgerB.id()); } void testAcquireValidatedLedger() { using namespace std::chrono_literals; testcase("Acquire validated ledger"); LedgerHistoryHelper h; TestHarness harness(h.oracle); Node const a = harness.makeNode(); Node const b = harness.makeNode(); using ID = Ledger::ID; using Seq = Ledger::Seq; // Validate the ledger before it is actually available Validation const val = a.validate(ID{2}, Seq{2}, 0s, 0s, true); BEAST_EXPECT(ValStatus::Current == harness.add(val)); // Validation is available BEAST_EXPECT(harness.vals().numTrustedForLedger(ID{2}) == 1); // but ledger based data is not BEAST_EXPECT(harness.vals().getNodesAfter(genesisLedger_, ID{0}) == 0); // Initial preferred branch falls back to the ledger we are trying to // acquire BEAST_EXPECT(harness.vals().getPreferred(genesisLedger_) == std::make_pair(Seq{2}, ID{2})); // After adding another unavailable validation, the preferred ledger // breaks ties via higher ID BEAST_EXPECT(ValStatus::Current == harness.add(b.validate(ID{3}, Seq{2}, 0s, 0s, true))); BEAST_EXPECT(harness.vals().getPreferred(genesisLedger_) == std::make_pair(Seq{2}, ID{3})); // Create the ledger Ledger const ledgerAB = h["ab"]; // Now it should be available BEAST_EXPECT(harness.vals().getNodesAfter(genesisLedger_, ID{0}) == 1); // Create a validation that is not available harness.clock().advance(5s); Validation const val2 = a.validate(ID{4}, Seq{4}, 0s, 0s, true); BEAST_EXPECT(ValStatus::Current == harness.add(val2)); BEAST_EXPECT(harness.vals().numTrustedForLedger(ID{4}) == 1); BEAST_EXPECT( harness.vals().getPreferred(genesisLedger_) == std::make_pair(ledgerAB.seq(), ledgerAB.id())); // Another node requesting that ledger still doesn't change things Validation const 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 const ledgerABCDE = h["abcde"]; BEAST_EXPECT(ValStatus::Current == harness.add(a.partial(ledgerABCDE))); BEAST_EXPECT(ValStatus::Current == harness.add(b.partial(ledgerABCDE))); BEAST_EXPECT( harness.vals().getPreferred(genesisLedger_) == std::make_pair(ledgerABCDE.seq(), ledgerABCDE.id())); } void testNumTrustedForLedger() { testcase("NumTrustedForLedger"); LedgerHistoryHelper h; TestHarness harness(h.oracle); Node const a = harness.makeNode(); Node const b = harness.makeNode(); Ledger const ledgerA = h["a"]; BEAST_EXPECT(ValStatus::Current == harness.add(a.partial(ledgerA))); BEAST_EXPECT(harness.vals().numTrustedForLedger(ledgerA.id()) == 0); BEAST_EXPECT(ValStatus::Current == harness.add(b.validate(ledgerA))); BEAST_EXPECT(harness.vals().numTrustedForLedger(ledgerA.id()) == 1); } void testSeqEnforcer() { testcase("SeqEnforcer"); using Seq = Ledger::Seq; using namespace std::chrono; beast::ManualClock clock; SeqEnforcer enforcer; ValidationParms const p; BEAST_EXPECT(enforcer(clock.now(), Seq{1}, p)); BEAST_EXPECT(enforcer(clock.now(), Seq{10}, p)); BEAST_EXPECT(!enforcer(clock.now(), Seq{5}, p)); BEAST_EXPECT(!enforcer(clock.now(), Seq{9}, p)); clock.advance(p.validationSetExpires - 1ms); BEAST_EXPECT(!enforcer(clock.now(), Seq{1}, p)); clock.advance(2ms); BEAST_EXPECT(enforcer(clock.now(), Seq{1}, p)); } void testTrustChanged() { testcase("TrustChanged"); using namespace std::chrono; auto checker = [this]( TestValidations& vals, hash_set const& listed, std::vector const& trustedVals) { Ledger::ID const testID = trustedVals.empty() ? this->genesisLedger_.id() : trustedVals[0].ledgerID(); Ledger::Seq const testSeq = trustedVals.empty() ? this->genesisLedger_.seq() : trustedVals[0].seq(); BEAST_EXPECT(vals.currentTrusted() == trustedVals); BEAST_EXPECT(vals.getCurrentNodeIDs() == listed); BEAST_EXPECT( vals.getNodesAfter(this->genesisLedger_, genesisLedger_.id()) == trustedVals.size()); if (trustedVals.empty()) { BEAST_EXPECT(vals.getPreferred(this->genesisLedger_) == std::nullopt); } else { BEAST_EXPECT(vals.getPreferred(this->genesisLedger_)->second == testID); } BEAST_EXPECT(vals.getTrustedForLedger(testID, testSeq) == trustedVals); BEAST_EXPECT(vals.numTrustedForLedger(testID) == trustedVals.size()); }; { // Trusted to untrusted LedgerHistoryHelper h; TestHarness harness(h.oracle); Node const a = harness.makeNode(); Ledger const ledgerAB = h["ab"]; Validation const v = a.validate(ledgerAB); BEAST_EXPECT(ValStatus::Current == harness.add(v)); hash_set const listed({a.nodeID()}); std::vector trustedVals({v}); checker(harness.vals(), listed, trustedVals); trustedVals.clear(); harness.vals().trustChanged({}, {a.nodeID()}); checker(harness.vals(), listed, trustedVals); } { // Untrusted to trusted LedgerHistoryHelper h; TestHarness harness(h.oracle); Node a = harness.makeNode(); a.untrust(); Ledger const ledgerAB = h["ab"]; Validation const v = a.validate(ledgerAB); BEAST_EXPECT(ValStatus::Current == harness.add(v)); hash_set const listed({a.nodeID()}); std::vector trustedVals; checker(harness.vals(), listed, trustedVals); trustedVals.push_back(v); harness.vals().trustChanged({a.nodeID()}, {}); checker(harness.vals(), listed, trustedVals); } { // Trusted but not acquired -> untrusted LedgerHistoryHelper h; TestHarness harness(h.oracle); Node const a = harness.makeNode(); Validation const v = a.validate(Ledger::ID{2}, Ledger::Seq{2}, 0s, 0s, true); BEAST_EXPECT(ValStatus::Current == harness.add(v)); hash_set const listed({a.nodeID()}); std::vector trustedVals({v}); auto& vals = harness.vals(); BEAST_EXPECT(vals.currentTrusted() == trustedVals); // NOLINTNEXTLINE(bugprone-unchecked-optional-access) BEAST_EXPECT(vals.getPreferred(genesisLedger_)->second == v.ledgerID()); BEAST_EXPECT(vals.getNodesAfter(genesisLedger_, genesisLedger_.id()) == 0); trustedVals.clear(); harness.vals().trustChanged({}, {a.nodeID()}); // make acquiring ledger available h["ab"]; BEAST_EXPECT(vals.currentTrusted() == trustedVals); BEAST_EXPECT(vals.getPreferred(genesisLedger_) == std::nullopt); BEAST_EXPECT(vals.getNodesAfter(genesisLedger_, genesisLedger_.id()) == 0); } } void run() override { testAddValidation(); testOnStale(); testGetNodesAfter(); testCurrentTrusted(); testGetCurrentPublicKeys(); testTrustedByLedgerFunctions(); testExpire(); testFlush(); testGetPreferredLedger(); testGetPreferredLCL(); testAcquireValidatedLedger(); testNumTrustedForLedger(); testSeqEnforcer(); testTrustChanged(); } }; BEAST_DEFINE_TESTSUITE(Validations, consensus, xrpl); } // namespace xrpl::test::csf