#include #include #include #include namespace xrpl { namespace test { class LedgerTrie_test : public beast::unit_test::suite { void testInsert() { using namespace csf; // Single entry by itself { LedgerTrie t; LedgerHistoryHelper h; t.insert(h["abc"]); BEAST_EXPECT(t.checkInvariants()); BEAST_EXPECT(t.tipSupport(h["abc"]) == 1); BEAST_EXPECT(t.branchSupport(h["abc"]) == 1); t.insert(h["abc"]); BEAST_EXPECT(t.checkInvariants()); BEAST_EXPECT(t.tipSupport(h["abc"]) == 2); BEAST_EXPECT(t.branchSupport(h["abc"]) == 2); } // Suffix of existing (extending tree) { LedgerTrie t; LedgerHistoryHelper h; t.insert(h["abc"]); BEAST_EXPECT(t.checkInvariants()); // extend with no siblings t.insert(h["abcd"]); BEAST_EXPECT(t.checkInvariants()); BEAST_EXPECT(t.tipSupport(h["abc"]) == 1); BEAST_EXPECT(t.branchSupport(h["abc"]) == 2); BEAST_EXPECT(t.tipSupport(h["abcd"]) == 1); BEAST_EXPECT(t.branchSupport(h["abcd"]) == 1); // extend with existing sibling t.insert(h["abce"]); BEAST_EXPECT(t.tipSupport(h["abc"]) == 1); BEAST_EXPECT(t.branchSupport(h["abc"]) == 3); BEAST_EXPECT(t.tipSupport(h["abcd"]) == 1); BEAST_EXPECT(t.branchSupport(h["abcd"]) == 1); BEAST_EXPECT(t.tipSupport(h["abce"]) == 1); BEAST_EXPECT(t.branchSupport(h["abce"]) == 1); } // uncommitted of existing node { LedgerTrie t; LedgerHistoryHelper h; t.insert(h["abcd"]); BEAST_EXPECT(t.checkInvariants()); // uncommitted with no siblings t.insert(h["abcdf"]); BEAST_EXPECT(t.checkInvariants()); BEAST_EXPECT(t.tipSupport(h["abcd"]) == 1); BEAST_EXPECT(t.branchSupport(h["abcd"]) == 2); BEAST_EXPECT(t.tipSupport(h["abcdf"]) == 1); BEAST_EXPECT(t.branchSupport(h["abcdf"]) == 1); // uncommitted with existing child t.insert(h["abc"]); BEAST_EXPECT(t.checkInvariants()); BEAST_EXPECT(t.tipSupport(h["abc"]) == 1); BEAST_EXPECT(t.branchSupport(h["abc"]) == 3); BEAST_EXPECT(t.tipSupport(h["abcd"]) == 1); BEAST_EXPECT(t.branchSupport(h["abcd"]) == 2); BEAST_EXPECT(t.tipSupport(h["abcdf"]) == 1); BEAST_EXPECT(t.branchSupport(h["abcdf"]) == 1); } // Suffix + uncommitted of existing node { LedgerTrie t; LedgerHistoryHelper h; t.insert(h["abcd"]); BEAST_EXPECT(t.checkInvariants()); t.insert(h["abce"]); BEAST_EXPECT(t.checkInvariants()); BEAST_EXPECT(t.tipSupport(h["abc"]) == 0); BEAST_EXPECT(t.branchSupport(h["abc"]) == 2); BEAST_EXPECT(t.tipSupport(h["abcd"]) == 1); BEAST_EXPECT(t.branchSupport(h["abcd"]) == 1); BEAST_EXPECT(t.tipSupport(h["abce"]) == 1); BEAST_EXPECT(t.branchSupport(h["abce"]) == 1); } // Suffix + uncommitted with existing child { // abcd : abcde, abcf LedgerTrie t; LedgerHistoryHelper h; t.insert(h["abcd"]); BEAST_EXPECT(t.checkInvariants()); t.insert(h["abcde"]); BEAST_EXPECT(t.checkInvariants()); t.insert(h["abcf"]); BEAST_EXPECT(t.checkInvariants()); BEAST_EXPECT(t.tipSupport(h["abc"]) == 0); BEAST_EXPECT(t.branchSupport(h["abc"]) == 3); BEAST_EXPECT(t.tipSupport(h["abcd"]) == 1); BEAST_EXPECT(t.branchSupport(h["abcd"]) == 2); BEAST_EXPECT(t.tipSupport(h["abcf"]) == 1); BEAST_EXPECT(t.branchSupport(h["abcf"]) == 1); BEAST_EXPECT(t.tipSupport(h["abcde"]) == 1); BEAST_EXPECT(t.branchSupport(h["abcde"]) == 1); } // Multiple counts { LedgerTrie t; LedgerHistoryHelper h; t.insert(h["ab"], 4); BEAST_EXPECT(t.tipSupport(h["ab"]) == 4); BEAST_EXPECT(t.branchSupport(h["ab"]) == 4); BEAST_EXPECT(t.tipSupport(h["a"]) == 0); BEAST_EXPECT(t.branchSupport(h["a"]) == 4); t.insert(h["abc"], 2); BEAST_EXPECT(t.tipSupport(h["abc"]) == 2); BEAST_EXPECT(t.branchSupport(h["abc"]) == 2); BEAST_EXPECT(t.tipSupport(h["ab"]) == 4); BEAST_EXPECT(t.branchSupport(h["ab"]) == 6); BEAST_EXPECT(t.tipSupport(h["a"]) == 0); BEAST_EXPECT(t.branchSupport(h["a"]) == 6); } } void testRemove() { using namespace csf; // Not in trie { LedgerTrie t; LedgerHistoryHelper h; t.insert(h["abc"]); BEAST_EXPECT(!t.remove(h["ab"])); BEAST_EXPECT(t.checkInvariants()); BEAST_EXPECT(!t.remove(h["a"])); BEAST_EXPECT(t.checkInvariants()); } // In trie but with 0 tip support { LedgerTrie t; LedgerHistoryHelper h; t.insert(h["abcd"]); t.insert(h["abce"]); BEAST_EXPECT(t.tipSupport(h["abc"]) == 0); BEAST_EXPECT(t.branchSupport(h["abc"]) == 2); BEAST_EXPECT(!t.remove(h["abc"])); BEAST_EXPECT(t.checkInvariants()); BEAST_EXPECT(t.tipSupport(h["abc"]) == 0); BEAST_EXPECT(t.branchSupport(h["abc"]) == 2); } // In trie with > 1 tip support { LedgerTrie t; LedgerHistoryHelper h; t.insert(h["abc"], 2); BEAST_EXPECT(t.tipSupport(h["abc"]) == 2); BEAST_EXPECT(t.remove(h["abc"])); BEAST_EXPECT(t.checkInvariants()); BEAST_EXPECT(t.tipSupport(h["abc"]) == 1); t.insert(h["abc"], 1); BEAST_EXPECT(t.tipSupport(h["abc"]) == 2); BEAST_EXPECT(t.remove(h["abc"], 2)); BEAST_EXPECT(t.checkInvariants()); BEAST_EXPECT(t.tipSupport(h["abc"]) == 0); t.insert(h["abc"], 3); BEAST_EXPECT(t.tipSupport(h["abc"]) == 3); BEAST_EXPECT(t.remove(h["abc"], 300)); BEAST_EXPECT(t.checkInvariants()); BEAST_EXPECT(t.tipSupport(h["abc"]) == 0); } // In trie with = 1 tip support, no children { LedgerTrie t; LedgerHistoryHelper h; t.insert(h["ab"]); t.insert(h["abc"]); BEAST_EXPECT(t.tipSupport(h["ab"]) == 1); BEAST_EXPECT(t.branchSupport(h["ab"]) == 2); BEAST_EXPECT(t.tipSupport(h["abc"]) == 1); BEAST_EXPECT(t.branchSupport(h["abc"]) == 1); BEAST_EXPECT(t.remove(h["abc"])); BEAST_EXPECT(t.checkInvariants()); BEAST_EXPECT(t.tipSupport(h["ab"]) == 1); BEAST_EXPECT(t.branchSupport(h["ab"]) == 1); BEAST_EXPECT(t.tipSupport(h["abc"]) == 0); BEAST_EXPECT(t.branchSupport(h["abc"]) == 0); } // In trie with = 1 tip support, 1 child { LedgerTrie t; LedgerHistoryHelper h; t.insert(h["ab"]); t.insert(h["abc"]); t.insert(h["abcd"]); BEAST_EXPECT(t.tipSupport(h["abc"]) == 1); BEAST_EXPECT(t.branchSupport(h["abc"]) == 2); BEAST_EXPECT(t.tipSupport(h["abcd"]) == 1); BEAST_EXPECT(t.branchSupport(h["abcd"]) == 1); BEAST_EXPECT(t.remove(h["abc"])); BEAST_EXPECT(t.checkInvariants()); BEAST_EXPECT(t.tipSupport(h["abc"]) == 0); BEAST_EXPECT(t.branchSupport(h["abc"]) == 1); BEAST_EXPECT(t.tipSupport(h["abcd"]) == 1); BEAST_EXPECT(t.branchSupport(h["abcd"]) == 1); } // In trie with = 1 tip support, > 1 children { LedgerTrie t; LedgerHistoryHelper h; t.insert(h["ab"]); t.insert(h["abc"]); t.insert(h["abcd"]); t.insert(h["abce"]); BEAST_EXPECT(t.tipSupport(h["abc"]) == 1); BEAST_EXPECT(t.branchSupport(h["abc"]) == 3); BEAST_EXPECT(t.remove(h["abc"])); BEAST_EXPECT(t.checkInvariants()); BEAST_EXPECT(t.tipSupport(h["abc"]) == 0); BEAST_EXPECT(t.branchSupport(h["abc"]) == 2); } // In trie with = 1 tip support, parent compaction { LedgerTrie t; LedgerHistoryHelper h; t.insert(h["ab"]); t.insert(h["abc"]); t.insert(h["abd"]); BEAST_EXPECT(t.checkInvariants()); t.remove(h["ab"]); BEAST_EXPECT(t.checkInvariants()); BEAST_EXPECT(t.tipSupport(h["abc"]) == 1); BEAST_EXPECT(t.tipSupport(h["abd"]) == 1); BEAST_EXPECT(t.tipSupport(h["ab"]) == 0); BEAST_EXPECT(t.branchSupport(h["ab"]) == 2); t.remove(h["abd"]); BEAST_EXPECT(t.checkInvariants()); BEAST_EXPECT(t.tipSupport(h["abc"]) == 1); BEAST_EXPECT(t.branchSupport(h["ab"]) == 1); } } void testEmpty() { using namespace csf; LedgerTrie t; LedgerHistoryHelper h; BEAST_EXPECT(t.empty()); Ledger genesis = h[""]; t.insert(genesis); BEAST_EXPECT(!t.empty()); t.remove(genesis); BEAST_EXPECT(t.empty()); t.insert(h["abc"]); BEAST_EXPECT(!t.empty()); t.remove(h["abc"]); BEAST_EXPECT(t.empty()); } void testSupport() { 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); 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; BEAST_EXPECT(t.getPreferred(Seq{0}) == std::nullopt); BEAST_EXPECT(t.getPreferred(Seq{2}) == std::nullopt); } // Genesis support is NOT empty { LedgerTrie t; LedgerHistoryHelper h; Ledger genesis = h[""]; t.insert(genesis); BEAST_EXPECT(t.getPreferred(Seq{0})->id == genesis.id()); BEAST_EXPECT(t.remove(genesis)); BEAST_EXPECT(t.getPreferred(Seq{0}) == std::nullopt); BEAST_EXPECT(!t.remove(genesis)); } // Single node no children { LedgerTrie t; LedgerHistoryHelper h; t.insert(h["abc"]); BEAST_EXPECT(t.getPreferred(Seq{3})->id == h["abc"].id()); } // Single node smaller child support { LedgerTrie t; LedgerHistoryHelper h; t.insert(h["abc"]); t.insert(h["abcd"]); BEAST_EXPECT(t.getPreferred(Seq{3})->id == h["abc"].id()); BEAST_EXPECT(t.getPreferred(Seq{4})->id == h["abc"].id()); } // Single node larger child { LedgerTrie t; LedgerHistoryHelper h; t.insert(h["abc"]); t.insert(h["abcd"], 2); BEAST_EXPECT(t.getPreferred(Seq{3})->id == h["abcd"].id()); BEAST_EXPECT(t.getPreferred(Seq{4})->id == h["abcd"].id()); } // Single node smaller children support { LedgerTrie t; LedgerHistoryHelper h; t.insert(h["abc"]); t.insert(h["abcd"]); t.insert(h["abce"]); BEAST_EXPECT(t.getPreferred(Seq{3})->id == h["abc"].id()); BEAST_EXPECT(t.getPreferred(Seq{4})->id == h["abc"].id()); t.insert(h["abc"]); BEAST_EXPECT(t.getPreferred(Seq{3})->id == h["abc"].id()); BEAST_EXPECT(t.getPreferred(Seq{4})->id == h["abc"].id()); } // Single node larger children { LedgerTrie t; LedgerHistoryHelper h; t.insert(h["abc"]); t.insert(h["abcd"], 2); t.insert(h["abce"]); BEAST_EXPECT(t.getPreferred(Seq{3})->id == h["abc"].id()); BEAST_EXPECT(t.getPreferred(Seq{4})->id == h["abc"].id()); t.insert(h["abcd"]); BEAST_EXPECT(t.getPreferred(Seq{3})->id == h["abcd"].id()); BEAST_EXPECT(t.getPreferred(Seq{4})->id == h["abcd"].id()); } // Tie-breaker by id { LedgerTrie t; LedgerHistoryHelper h; t.insert(h["abcd"], 2); t.insert(h["abce"], 2); BEAST_EXPECT(h["abce"].id() > h["abcd"].id()); BEAST_EXPECT(t.getPreferred(Seq{4})->id == h["abce"].id()); t.insert(h["abcd"]); BEAST_EXPECT(h["abce"].id() > h["abcd"].id()); BEAST_EXPECT(t.getPreferred(Seq{4})->id == h["abcd"].id()); } // Tie-breaker not needed { LedgerTrie t; LedgerHistoryHelper h; t.insert(h["abc"]); t.insert(h["abcd"]); t.insert(h["abce"], 2); // abce only has a margin of 1, but it owns the tie-breaker BEAST_EXPECT(h["abce"].id() > h["abcd"].id()); BEAST_EXPECT(t.getPreferred(Seq{3})->id == h["abce"].id()); BEAST_EXPECT(t.getPreferred(Seq{4})->id == h["abce"].id()); // Switch support from abce to abcd, tie-breaker now needed t.remove(h["abce"]); t.insert(h["abcd"]); BEAST_EXPECT(t.getPreferred(Seq{3})->id == h["abc"].id()); BEAST_EXPECT(t.getPreferred(Seq{4})->id == h["abc"].id()); } // Single node larger grand child { LedgerTrie t; LedgerHistoryHelper h; t.insert(h["abc"]); t.insert(h["abcd"], 2); t.insert(h["abcde"], 4); BEAST_EXPECT(t.getPreferred(Seq{3})->id == h["abcde"].id()); BEAST_EXPECT(t.getPreferred(Seq{4})->id == h["abcde"].id()); BEAST_EXPECT(t.getPreferred(Seq{5})->id == h["abcde"].id()); } // Too much uncommitted support from competing branches { LedgerTrie t; LedgerHistoryHelper h; t.insert(h["abc"]); t.insert(h["abcde"], 2); t.insert(h["abcfg"], 2); // 'de' and 'fg' are tied without 'abc' vote BEAST_EXPECT(t.getPreferred(Seq{3})->id == h["abc"].id()); BEAST_EXPECT(t.getPreferred(Seq{4})->id == h["abc"].id()); BEAST_EXPECT(t.getPreferred(Seq{5})->id == h["abc"].id()); t.remove(h["abc"]); t.insert(h["abcd"]); // 'de' branch has 3 votes to 2, so earlier sequences see it as // preferred BEAST_EXPECT(t.getPreferred(Seq{3})->id == h["abcde"].id()); BEAST_EXPECT(t.getPreferred(Seq{4})->id == h["abcde"].id()); // However, if you validated a ledger with Seq 5, potentially on // a different branch, you do not yet know if they chose abcd // or abcf because of you, so abc remains preferred BEAST_EXPECT(t.getPreferred(Seq{5})->id == h["abc"].id()); } // Changing largestSeq perspective changes preferred branch { /** Build the tree below with initial tip support annotated A / \ B(1) C(1) / | | H D F(1) | E(2) | G */ LedgerTrie t; LedgerHistoryHelper h; t.insert(h["ab"]); t.insert(h["ac"]); t.insert(h["acf"]); t.insert(h["abde"], 2); // B has more branch support BEAST_EXPECT(t.getPreferred(Seq{1})->id == h["ab"].id()); BEAST_EXPECT(t.getPreferred(Seq{2})->id == h["ab"].id()); // But if you last validated D,F or E, you do not yet know // if someone used that validation to commit to B or C BEAST_EXPECT(t.getPreferred(Seq{3})->id == h["a"].id()); BEAST_EXPECT(t.getPreferred(Seq{4})->id == h["a"].id()); /** One of E advancing to G doesn't change anything A / \ B(1) C(1) / | | H D F(1) | E(1) | G(1) */ t.remove(h["abde"]); t.insert(h["abdeg"]); BEAST_EXPECT(t.getPreferred(Seq{1})->id == h["ab"].id()); BEAST_EXPECT(t.getPreferred(Seq{2})->id == h["ab"].id()); BEAST_EXPECT(t.getPreferred(Seq{3})->id == h["a"].id()); BEAST_EXPECT(t.getPreferred(Seq{4})->id == h["a"].id()); BEAST_EXPECT(t.getPreferred(Seq{5})->id == h["a"].id()); /** C advancing to H does advance the seq 3 preferred ledger A / \ B(1) C / | | H(1)D F(1) | E(1) | G(1) */ t.remove(h["ac"]); t.insert(h["abh"]); BEAST_EXPECT(t.getPreferred(Seq{1})->id == h["ab"].id()); BEAST_EXPECT(t.getPreferred(Seq{2})->id == h["ab"].id()); BEAST_EXPECT(t.getPreferred(Seq{3})->id == h["ab"].id()); BEAST_EXPECT(t.getPreferred(Seq{4})->id == h["a"].id()); BEAST_EXPECT(t.getPreferred(Seq{5})->id == h["a"].id()); /** F advancing to E also moves the preferred ledger forward A / \ B(1) C / | | H(1)D F | E(2) | G(1) */ t.remove(h["acf"]); t.insert(h["abde"]); BEAST_EXPECT(t.getPreferred(Seq{1})->id == h["abde"].id()); BEAST_EXPECT(t.getPreferred(Seq{2})->id == h["abde"].id()); BEAST_EXPECT(t.getPreferred(Seq{3})->id == h["abde"].id()); BEAST_EXPECT(t.getPreferred(Seq{4})->id == h["ab"].id()); BEAST_EXPECT(t.getPreferred(Seq{5})->id == h["ab"].id()); } } void testRootRelated() { using namespace csf; // 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 depthConst = 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, depthConst - 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() override { testInsert(); testRemove(); testEmpty(); testSupport(); testGetPreferred(); testRootRelated(); testStress(); } }; BEAST_DEFINE_TESTSUITE(LedgerTrie, consensus, xrpl); } // namespace test } // namespace xrpl