#include #include #include #include #include #include #include #include #include namespace xrpl { namespace test { class View_test : public beast::unit_test::suite { // Convert a small integer to a key static Keylet k(std::uint64_t id) { return Keylet{ltACCOUNT_ROOT, uint256(id)}; } // Create SLE with key and payload static std::shared_ptr sle(std::uint64_t id, std::uint32_t seq = 1) { auto const le = std::make_shared(k(id)); le->setFieldU32(sfSequence, seq); return le; } // Return payload for SLE template static std::uint32_t seq(std::shared_ptr const& le) { return le->getFieldU32(sfSequence); } // Set payload on SLE static void seq(std::shared_ptr const& le, std::uint32_t seq) { le->setFieldU32(sfSequence, seq); } // Erase all state items static void wipe(OpenLedger& openLedger) { openLedger.modify([](OpenView& view, beast::Journal) { // HACK! std::optional next; next.emplace(0); for (;;) { next = view.succ(*next); if (!next) break; view.rawErase(std::make_shared( *view.read(keylet::unchecked(*next)))); } return true; }); } static void wipe(Ledger& ledger) { // HACK! std::optional next; next.emplace(0); for (;;) { next = ledger.succ(*next); if (!next) break; ledger.rawErase( std::make_shared(*ledger.read(keylet::unchecked(*next)))); } } // Test succ correctness void succ( ReadView const& v, std::uint32_t id, std::optional answer) { auto const next = v.succ(k(id).key); if (answer) { if (BEAST_EXPECT(next)) BEAST_EXPECT(*next == k(*answer).key); } else { BEAST_EXPECT(!next); } } template static std::shared_ptr> copy(std::shared_ptr const& sp) { return std::make_shared>(*sp); } // Exercise Ledger implementation of ApplyView void testLedger() { testcase("Ledger"); using namespace jtx; Env env(*this); Config config; std::shared_ptr const genesis = std::make_shared( create_genesis, config, std::vector{}, env.app().getNodeFamily()); auto const ledger = std::make_shared( *genesis, env.app().timeKeeper().closeTime()); wipe(*ledger); ReadView& v = *ledger; succ(v, 0, std::nullopt); ledger->rawInsert(sle(1, 1)); BEAST_EXPECT(v.exists(k(1))); BEAST_EXPECT(seq(v.read(k(1))) == 1); succ(v, 0, 1); succ(v, 1, std::nullopt); ledger->rawInsert(sle(2, 2)); BEAST_EXPECT(seq(v.read(k(2))) == 2); ledger->rawInsert(sle(3, 3)); BEAST_EXPECT(seq(v.read(k(3))) == 3); auto s = copy(v.read(k(2))); seq(s, 4); ledger->rawReplace(s); BEAST_EXPECT(seq(v.read(k(2))) == 4); ledger->rawErase(sle(2)); BEAST_EXPECT(!v.exists(k(2))); BEAST_EXPECT(v.exists(k(1))); BEAST_EXPECT(v.exists(k(3))); } void testMeta() { testcase("Meta"); using namespace jtx; Env env(*this); wipe(env.app().openLedger()); auto const open = env.current(); ApplyViewImpl v(&*open, tapNONE); succ(v, 0, std::nullopt); v.insert(sle(1)); BEAST_EXPECT(v.exists(k(1))); BEAST_EXPECT(seq(v.read(k(1))) == 1); BEAST_EXPECT(seq(v.peek(k(1))) == 1); succ(v, 0, 1); succ(v, 1, std::nullopt); v.insert(sle(2, 2)); BEAST_EXPECT(seq(v.read(k(2))) == 2); v.insert(sle(3, 3)); auto s = v.peek(k(3)); BEAST_EXPECT(seq(s) == 3); s = v.peek(k(2)); seq(s, 4); v.update(s); BEAST_EXPECT(seq(v.read(k(2))) == 4); v.erase(s); BEAST_EXPECT(!v.exists(k(2))); BEAST_EXPECT(v.exists(k(1))); BEAST_EXPECT(v.exists(k(3))); } // Exercise all succ paths void testMetaSucc() { testcase("Meta succ"); using namespace jtx; Env env(*this); wipe(env.app().openLedger()); auto const open = env.current(); ApplyViewImpl v0(&*open, tapNONE); v0.insert(sle(1)); v0.insert(sle(2)); v0.insert(sle(4)); v0.insert(sle(7)); { Sandbox v1(&v0); v1.insert(sle(3)); v1.insert(sle(5)); v1.insert(sle(6)); // v0: 12-4--7 // v1: --3-56- succ(v0, 0, 1); succ(v0, 1, 2); succ(v0, 2, 4); succ(v0, 3, 4); succ(v0, 4, 7); succ(v0, 5, 7); succ(v0, 6, 7); succ(v0, 7, std::nullopt); succ(v1, 0, 1); succ(v1, 1, 2); succ(v1, 2, 3); succ(v1, 3, 4); succ(v1, 4, 5); succ(v1, 5, 6); succ(v1, 6, 7); succ(v1, 7, std::nullopt); v1.erase(v1.peek(k(4))); succ(v1, 3, 5); v1.erase(v1.peek(k(6))); succ(v1, 5, 7); succ(v1, 6, 7); // v0: 12----7 // v1: --3-5-- v1.apply(v0); } // v0: 123-5-7 succ(v0, 0, 1); succ(v0, 1, 2); succ(v0, 2, 3); succ(v0, 3, 5); succ(v0, 4, 5); succ(v0, 5, 7); succ(v0, 6, 7); succ(v0, 7, std::nullopt); } void testStacked() { testcase("Stacked"); using namespace jtx; Env env(*this); wipe(env.app().openLedger()); auto const open = env.current(); ApplyViewImpl v0(&*open, tapNONE); v0.rawInsert(sle(1, 1)); v0.rawInsert(sle(2, 2)); v0.rawInsert(sle(4, 4)); { Sandbox v1(&v0); v1.erase(v1.peek(k(2))); v1.insert(sle(3, 3)); auto s = v1.peek(k(4)); seq(s, 5); v1.update(s); BEAST_EXPECT(seq(v1.read(k(1))) == 1); BEAST_EXPECT(!v1.exists(k(2))); BEAST_EXPECT(seq(v1.read(k(3))) == 3); BEAST_EXPECT(seq(v1.read(k(4))) == 5); { Sandbox v2(&v1); auto s2 = v2.peek(k(3)); seq(s2, 6); v2.update(s2); v2.erase(v2.peek(k(4))); BEAST_EXPECT(seq(v2.read(k(1))) == 1); BEAST_EXPECT(!v2.exists(k(2))); BEAST_EXPECT(seq(v2.read(k(3))) == 6); BEAST_EXPECT(!v2.exists(k(4))); // discard v2 } BEAST_EXPECT(seq(v1.read(k(1))) == 1); BEAST_EXPECT(!v1.exists(k(2))); BEAST_EXPECT(seq(v1.read(k(3))) == 3); BEAST_EXPECT(seq(v1.read(k(4))) == 5); { Sandbox v2(&v1); auto s2 = v2.peek(k(3)); seq(s2, 6); v2.update(s2); v2.erase(v2.peek(k(4))); BEAST_EXPECT(seq(v2.read(k(1))) == 1); BEAST_EXPECT(!v2.exists(k(2))); BEAST_EXPECT(seq(v2.read(k(3))) == 6); BEAST_EXPECT(!v2.exists(k(4))); v2.apply(v1); } BEAST_EXPECT(seq(v1.read(k(1))) == 1); BEAST_EXPECT(!v1.exists(k(2))); BEAST_EXPECT(seq(v1.read(k(3))) == 6); BEAST_EXPECT(!v1.exists(k(4))); v1.apply(v0); } BEAST_EXPECT(seq(v0.read(k(1))) == 1); BEAST_EXPECT(!v0.exists(k(2))); BEAST_EXPECT(seq(v0.read(k(3))) == 6); BEAST_EXPECT(!v0.exists(k(4))); } // Verify contextual information void testContext() { testcase("Context"); using namespace jtx; using namespace std::chrono; { Env env(*this); wipe(env.app().openLedger()); auto const open = env.current(); OpenView v0(open.get()); BEAST_EXPECT(v0.seq() != 98); BEAST_EXPECT(v0.seq() == open->seq()); BEAST_EXPECT(v0.parentCloseTime() != NetClock::time_point{99s}); BEAST_EXPECT(v0.parentCloseTime() == open->parentCloseTime()); { // shallow copy OpenView v1(v0); BEAST_EXPECT(v1.seq() == v0.seq()); BEAST_EXPECT(v1.parentCloseTime() == v1.parentCloseTime()); ApplyViewImpl v2(&v1, tapRETRY); BEAST_EXPECT(v2.parentCloseTime() == v1.parentCloseTime()); BEAST_EXPECT(v2.seq() == v1.seq()); BEAST_EXPECT(v2.flags() == tapRETRY); Sandbox v3(&v2); BEAST_EXPECT(v3.seq() == v2.seq()); BEAST_EXPECT(v3.parentCloseTime() == v2.parentCloseTime()); BEAST_EXPECT(v3.flags() == tapRETRY); } { ApplyViewImpl v1(&v0, tapRETRY); PaymentSandbox v2(&v1); BEAST_EXPECT(v2.seq() == v0.seq()); BEAST_EXPECT(v2.parentCloseTime() == v0.parentCloseTime()); BEAST_EXPECT(v2.flags() == tapRETRY); PaymentSandbox v3(&v2); BEAST_EXPECT(v3.seq() == v2.seq()); BEAST_EXPECT(v3.parentCloseTime() == v2.parentCloseTime()); BEAST_EXPECT(v3.flags() == v2.flags()); } } } // Return a list of keys found via sles static std::vector sles(ReadView const& ledger) { std::vector v; v.reserve(32); for (auto const& sle : ledger.sles) v.push_back(sle->key()); return v; } template static std::vector list(Args... args) { return std::vector({uint256(args)...}); } void testUpperAndLowerBound() { testcase("Upper and lower bound"); using namespace jtx; Env env(*this); Config config; std::shared_ptr const genesis = std::make_shared( create_genesis, config, std::vector{}, env.app().getNodeFamily()); auto const ledger = std::make_shared( *genesis, env.app().timeKeeper().closeTime()); auto setup = [&ledger](std::vector const& vec) { wipe(*ledger); for (auto x : vec) { ledger->rawInsert(sle(x)); } }; { setup({1, 2, 3}); BEAST_EXPECT(sles(*ledger) == list(1, 2, 3)); auto e = ledger->stateMap().end(); auto b1 = ledger->stateMap().begin(); BEAST_EXPECT(ledger->stateMap().lower_bound(uint256(1)) == e); BEAST_EXPECT(ledger->stateMap().lower_bound(uint256(2)) == b1); ++b1; BEAST_EXPECT(ledger->stateMap().lower_bound(uint256(3)) == b1); ++b1; BEAST_EXPECT(ledger->stateMap().lower_bound(uint256(4)) == b1); BEAST_EXPECT(ledger->stateMap().lower_bound(uint256(5)) == b1); b1 = ledger->stateMap().begin(); BEAST_EXPECT(ledger->stateMap().upper_bound(uint256(0)) == b1); ++b1; BEAST_EXPECT(ledger->stateMap().upper_bound(uint256(1)) == b1); ++b1; BEAST_EXPECT(ledger->stateMap().upper_bound(uint256(2)) == b1); BEAST_EXPECT(ledger->stateMap().upper_bound(uint256(3)) == e); } { setup({2, 4, 6}); BEAST_EXPECT(sles(*ledger) == list(2, 4, 6)); auto e = ledger->stateMap().end(); auto b1 = ledger->stateMap().begin(); BEAST_EXPECT(ledger->stateMap().lower_bound(uint256(1)) == e); BEAST_EXPECT(ledger->stateMap().lower_bound(uint256(2)) == e); BEAST_EXPECT(ledger->stateMap().lower_bound(uint256(3)) == b1); BEAST_EXPECT(ledger->stateMap().lower_bound(uint256(4)) == b1); ++b1; BEAST_EXPECT(ledger->stateMap().lower_bound(uint256(5)) == b1); BEAST_EXPECT(ledger->stateMap().lower_bound(uint256(6)) == b1); ++b1; BEAST_EXPECT(ledger->stateMap().lower_bound(uint256(7)) == b1); b1 = ledger->stateMap().begin(); BEAST_EXPECT(ledger->stateMap().upper_bound(uint256(1)) == b1); ++b1; BEAST_EXPECT(ledger->stateMap().upper_bound(uint256(2)) == b1); BEAST_EXPECT(ledger->stateMap().upper_bound(uint256(3)) == b1); ++b1; BEAST_EXPECT(ledger->stateMap().upper_bound(uint256(4)) == b1); BEAST_EXPECT(ledger->stateMap().upper_bound(uint256(5)) == b1); BEAST_EXPECT(ledger->stateMap().upper_bound(uint256(6)) == e); BEAST_EXPECT(ledger->stateMap().upper_bound(uint256(7)) == e); } { setup({2, 3, 5, 6, 10, 15}); BEAST_EXPECT(sles(*ledger) == list(2, 3, 5, 6, 10, 15)); auto e = ledger->stateMap().end(); auto b = ledger->stateMap().begin(); BEAST_EXPECT(ledger->stateMap().lower_bound(uint256(1)) == e); BEAST_EXPECT(ledger->stateMap().lower_bound(uint256(2)) == e); BEAST_EXPECT(ledger->stateMap().lower_bound(uint256(3)) == b); ++b; BEAST_EXPECT(ledger->stateMap().lower_bound(uint256(4)) == b); BEAST_EXPECT(ledger->stateMap().lower_bound(uint256(5)) == b); ++b; BEAST_EXPECT(ledger->stateMap().lower_bound(uint256(6)) == b); ++b; BEAST_EXPECT(ledger->stateMap().lower_bound(uint256(7)) == b); BEAST_EXPECT(ledger->stateMap().lower_bound(uint256(8)) == b); BEAST_EXPECT(ledger->stateMap().lower_bound(uint256(9)) == b); BEAST_EXPECT(ledger->stateMap().lower_bound(uint256(10)) == b); ++b; BEAST_EXPECT(ledger->stateMap().lower_bound(uint256(11)) == b); BEAST_EXPECT(ledger->stateMap().lower_bound(uint256(12)) == b); BEAST_EXPECT(ledger->stateMap().lower_bound(uint256(13)) == b); BEAST_EXPECT(ledger->stateMap().lower_bound(uint256(14)) == b); BEAST_EXPECT(ledger->stateMap().lower_bound(uint256(15)) == b); ++b; BEAST_EXPECT(ledger->stateMap().lower_bound(uint256(16)) == b); b = ledger->stateMap().begin(); BEAST_EXPECT(ledger->stateMap().upper_bound(uint256(0)) == b); BEAST_EXPECT(ledger->stateMap().upper_bound(uint256(1)) == b); ++b; BEAST_EXPECT(ledger->stateMap().upper_bound(uint256(2)) == b); ++b; BEAST_EXPECT(ledger->stateMap().upper_bound(uint256(3)) == b); BEAST_EXPECT(ledger->stateMap().upper_bound(uint256(4)) == b); ++b; BEAST_EXPECT(ledger->stateMap().upper_bound(uint256(5)) == b); ++b; BEAST_EXPECT(ledger->stateMap().upper_bound(uint256(6)) == b); BEAST_EXPECT(ledger->stateMap().upper_bound(uint256(7)) == b); BEAST_EXPECT(ledger->stateMap().upper_bound(uint256(8)) == b); BEAST_EXPECT(ledger->stateMap().upper_bound(uint256(9)) == b); ++b; BEAST_EXPECT(ledger->stateMap().upper_bound(uint256(10)) == b); BEAST_EXPECT(ledger->stateMap().upper_bound(uint256(11)) == b); BEAST_EXPECT(ledger->stateMap().upper_bound(uint256(12)) == b); BEAST_EXPECT(ledger->stateMap().upper_bound(uint256(13)) == b); BEAST_EXPECT(ledger->stateMap().upper_bound(uint256(14)) == b); ++b; BEAST_EXPECT(ledger->stateMap().upper_bound(uint256(15)) == e); BEAST_EXPECT(ledger->stateMap().upper_bound(uint256(16)) == e); } { // some full trees, some empty trees, etc setup({0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 20, 25, 30, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 66, 100}); BEAST_EXPECT( sles(*ledger) == list( 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 20, 25, 30, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 66, 100)); auto b = ledger->stateMap().begin(); auto e = ledger->stateMap().end(); BEAST_EXPECT(ledger->stateMap().lower_bound(uint256(0)) == e); BEAST_EXPECT(ledger->stateMap().lower_bound(uint256(1)) == b); BEAST_EXPECT( ledger->stateMap().lower_bound(uint256(5))->key() == uint256(4)); BEAST_EXPECT( ledger->stateMap().lower_bound(uint256(15))->key() == uint256(14)); BEAST_EXPECT( ledger->stateMap().lower_bound(uint256(16))->key() == uint256(15)); BEAST_EXPECT( ledger->stateMap().lower_bound(uint256(19))->key() == uint256(16)); BEAST_EXPECT( ledger->stateMap().lower_bound(uint256(20))->key() == uint256(16)); BEAST_EXPECT( ledger->stateMap().lower_bound(uint256(24))->key() == uint256(20)); BEAST_EXPECT( ledger->stateMap().lower_bound(uint256(31))->key() == uint256(30)); BEAST_EXPECT( ledger->stateMap().lower_bound(uint256(32))->key() == uint256(30)); BEAST_EXPECT( ledger->stateMap().lower_bound(uint256(40))->key() == uint256(39)); BEAST_EXPECT( ledger->stateMap().lower_bound(uint256(47))->key() == uint256(46)); BEAST_EXPECT( ledger->stateMap().lower_bound(uint256(48))->key() == uint256(47)); BEAST_EXPECT( ledger->stateMap().lower_bound(uint256(64))->key() == uint256(48)); BEAST_EXPECT( ledger->stateMap().lower_bound(uint256(90))->key() == uint256(66)); BEAST_EXPECT( ledger->stateMap().lower_bound(uint256(96))->key() == uint256(66)); BEAST_EXPECT( ledger->stateMap().lower_bound(uint256(100))->key() == uint256(66)); BEAST_EXPECT( ledger->stateMap().upper_bound(uint256(0))->key() == uint256(1)); BEAST_EXPECT( ledger->stateMap().upper_bound(uint256(5))->key() == uint256(6)); BEAST_EXPECT( ledger->stateMap().upper_bound(uint256(15))->key() == uint256(16)); BEAST_EXPECT( ledger->stateMap().upper_bound(uint256(16))->key() == uint256(20)); BEAST_EXPECT( ledger->stateMap().upper_bound(uint256(18))->key() == uint256(20)); BEAST_EXPECT( ledger->stateMap().upper_bound(uint256(20))->key() == uint256(25)); BEAST_EXPECT( ledger->stateMap().upper_bound(uint256(31))->key() == uint256(32)); BEAST_EXPECT( ledger->stateMap().upper_bound(uint256(32))->key() == uint256(33)); BEAST_EXPECT( ledger->stateMap().upper_bound(uint256(47))->key() == uint256(48)); BEAST_EXPECT( ledger->stateMap().upper_bound(uint256(48))->key() == uint256(66)); BEAST_EXPECT( ledger->stateMap().upper_bound(uint256(53))->key() == uint256(66)); BEAST_EXPECT( ledger->stateMap().upper_bound(uint256(66))->key() == uint256(100)); BEAST_EXPECT( ledger->stateMap().upper_bound(uint256(70))->key() == uint256(100)); BEAST_EXPECT( ledger->stateMap().upper_bound(uint256(85))->key() == uint256(100)); BEAST_EXPECT( ledger->stateMap().upper_bound(uint256(98))->key() == uint256(100)); BEAST_EXPECT(ledger->stateMap().upper_bound(uint256(100)) == e); BEAST_EXPECT(ledger->stateMap().upper_bound(uint256(155)) == e); } } void testSles() { testcase("Sles"); using namespace jtx; Env env(*this); Config config; std::shared_ptr const genesis = std::make_shared( create_genesis, config, std::vector{}, env.app().getNodeFamily()); auto const ledger = std::make_shared( *genesis, env.app().timeKeeper().closeTime()); auto setup123 = [&ledger, this]() { // erase middle element wipe(*ledger); ledger->rawInsert(sle(1)); ledger->rawInsert(sle(2)); ledger->rawInsert(sle(3)); BEAST_EXPECT(sles(*ledger) == list(1, 2, 3)); }; { setup123(); OpenView view(ledger.get()); view.rawErase(sle(1)); view.rawInsert(sle(4)); view.rawInsert(sle(5)); BEAST_EXPECT(sles(view) == list(2, 3, 4, 5)); auto b = view.sles.begin(); BEAST_EXPECT(view.sles.upper_bound(uint256(1)) == b); ++b; BEAST_EXPECT(view.sles.upper_bound(uint256(2)) == b); ++b; BEAST_EXPECT(view.sles.upper_bound(uint256(3)) == b); ++b; BEAST_EXPECT(view.sles.upper_bound(uint256(4)) == b); ++b; BEAST_EXPECT(view.sles.upper_bound(uint256(5)) == b); } { setup123(); OpenView view(ledger.get()); view.rawErase(sle(1)); view.rawErase(sle(2)); view.rawInsert(sle(4)); view.rawInsert(sle(5)); BEAST_EXPECT(sles(view) == list(3, 4, 5)); auto b = view.sles.begin(); BEAST_EXPECT(view.sles.upper_bound(uint256(1)) == b); BEAST_EXPECT(view.sles.upper_bound(uint256(2)) == b); ++b; BEAST_EXPECT(view.sles.upper_bound(uint256(3)) == b); ++b; BEAST_EXPECT(view.sles.upper_bound(uint256(4)) == b); ++b; BEAST_EXPECT(view.sles.upper_bound(uint256(5)) == b); } { setup123(); OpenView view(ledger.get()); view.rawErase(sle(1)); view.rawErase(sle(2)); view.rawErase(sle(3)); view.rawInsert(sle(4)); view.rawInsert(sle(5)); BEAST_EXPECT(sles(view) == list(4, 5)); auto b = view.sles.begin(); BEAST_EXPECT(view.sles.upper_bound(uint256(1)) == b); BEAST_EXPECT(view.sles.upper_bound(uint256(2)) == b); BEAST_EXPECT(view.sles.upper_bound(uint256(3)) == b); ++b; BEAST_EXPECT(view.sles.upper_bound(uint256(4)) == b); ++b; BEAST_EXPECT(view.sles.upper_bound(uint256(5)) == b); } { setup123(); OpenView view(ledger.get()); view.rawErase(sle(3)); view.rawInsert(sle(4)); view.rawInsert(sle(5)); BEAST_EXPECT(sles(view) == list(1, 2, 4, 5)); auto b = view.sles.begin(); ++b; BEAST_EXPECT(view.sles.upper_bound(uint256(1)) == b); ++b; BEAST_EXPECT(view.sles.upper_bound(uint256(2)) == b); BEAST_EXPECT(view.sles.upper_bound(uint256(3)) == b); ++b; BEAST_EXPECT(view.sles.upper_bound(uint256(4)) == b); ++b; BEAST_EXPECT(view.sles.upper_bound(uint256(5)) == b); } { setup123(); OpenView view(ledger.get()); view.rawReplace(sle(1, 10)); view.rawReplace(sle(3, 30)); BEAST_EXPECT(sles(view) == list(1, 2, 3)); BEAST_EXPECT(seq(view.read(k(1))) == 10); BEAST_EXPECT(seq(view.read(k(2))) == 1); BEAST_EXPECT(seq(view.read(k(3))) == 30); view.rawErase(sle(3)); BEAST_EXPECT(sles(view) == list(1, 2)); auto b = view.sles.begin(); ++b; BEAST_EXPECT(view.sles.upper_bound(uint256(1)) == b); ++b; BEAST_EXPECT(view.sles.upper_bound(uint256(2)) == b); BEAST_EXPECT(view.sles.upper_bound(uint256(3)) == b); BEAST_EXPECT(view.sles.upper_bound(uint256(4)) == b); BEAST_EXPECT(view.sles.upper_bound(uint256(5)) == b); view.rawInsert(sle(5)); view.rawInsert(sle(4)); view.rawInsert(sle(3)); BEAST_EXPECT(sles(view) == list(1, 2, 3, 4, 5)); b = view.sles.begin(); ++b; BEAST_EXPECT(view.sles.upper_bound(uint256(1)) == b); ++b; BEAST_EXPECT(view.sles.upper_bound(uint256(2)) == b); ++b; BEAST_EXPECT(view.sles.upper_bound(uint256(3)) == b); ++b; BEAST_EXPECT(view.sles.upper_bound(uint256(4)) == b); ++b; BEAST_EXPECT(view.sles.upper_bound(uint256(5)) == b); } } void testFlags() { testcase("Flags"); using namespace jtx; Env env(*this); auto const alice = Account("alice"); auto const bob = Account("bob"); auto const carol = Account("carol"); auto const gw = Account("gateway"); auto const USD = gw["USD"]; auto const EUR = gw["EUR"]; env.fund(XRP(10000), alice, bob, carol, gw); env.close(); env.trust(USD(100), alice, bob, carol); { // Global freezing. env(pay(gw, alice, USD(50))); env(offer(alice, XRP(5), USD(5))); // Now freeze gw. env(fset(gw, asfGlobalFreeze)); env.close(); env(offer(alice, XRP(4), USD(5)), ter(tecFROZEN)); env.close(); // Alice's USD balance should be zero if frozen. BEAST_EXPECT( USD(0) == accountHolds( *env.closed(), alice, USD.currency, gw, fhZERO_IF_FROZEN, env.journal)); // Thaw gw and try again. env(fclear(gw, asfGlobalFreeze)); env.close(); env(offer("alice", XRP(4), USD(5))); } { // Local freezing. env(pay(gw, bob, USD(50))); env.close(); // Now gw freezes bob's USD trust line. env(trust(gw, USD(100), bob, tfSetFreeze)); env.close(); // Bob's balance should be zero if frozen. BEAST_EXPECT( USD(0) == accountHolds( *env.closed(), bob, USD.currency, gw, fhZERO_IF_FROZEN, env.journal)); // gw thaws bob's trust line. bob gets his money back. env(trust(gw, USD(100), bob, tfClearFreeze)); env.close(); BEAST_EXPECT( USD(50) == accountHolds( *env.closed(), bob, USD.currency, gw, fhZERO_IF_FROZEN, env.journal)); } { // accountHolds(). env(pay(gw, carol, USD(50))); env.close(); // carol has no EUR. BEAST_EXPECT( EUR(0) == accountHolds( *env.closed(), carol, EUR.currency, gw, fhZERO_IF_FROZEN, env.journal)); // But carol does have USD. BEAST_EXPECT( USD(50) == accountHolds( *env.closed(), carol, USD.currency, gw, fhZERO_IF_FROZEN, env.journal)); // carol's XRP balance should be her holdings minus her reserve. auto const carolsXRP = accountHolds( *env.closed(), carol, xrpCurrency(), xrpAccount(), fhZERO_IF_FROZEN, env.journal); // carol's XRP balance: 10000 // base reserve: -200 // 1 trust line times its reserve: 1 * -50 // ------- // carol's available balance: 9750 BEAST_EXPECT(carolsXRP == XRP(9750)); // carol should be able to spend *more* than her XRP balance on // a fee by eating into her reserve. env(noop(carol), fee(carolsXRP + XRP(10))); env.close(); // carol's XRP balance should now show as zero. BEAST_EXPECT( XRP(0) == accountHolds( *env.closed(), carol, xrpCurrency(), gw, fhZERO_IF_FROZEN, env.journal)); } { // accountFunds(). // Gateways have whatever funds they claim to have. auto const gwUSD = accountFunds( *env.closed(), gw, USD(314159), fhZERO_IF_FROZEN, env.journal); BEAST_EXPECT(gwUSD == USD(314159)); // carol has funds from the gateway. auto carolsUSD = accountFunds( *env.closed(), carol, USD(0), fhZERO_IF_FROZEN, env.journal); BEAST_EXPECT(carolsUSD == USD(50)); // If carol's funds are frozen she has no funds... env(fset(gw, asfGlobalFreeze)); env.close(); carolsUSD = accountFunds( *env.closed(), carol, USD(0), fhZERO_IF_FROZEN, env.journal); BEAST_EXPECT(carolsUSD == USD(0)); // ... unless the query ignores the FROZEN state. carolsUSD = accountFunds( *env.closed(), carol, USD(0), fhIGNORE_FREEZE, env.journal); BEAST_EXPECT(carolsUSD == USD(50)); // Just to be tidy, thaw gw. env(fclear(gw, asfGlobalFreeze)); env.close(); } } void testTransferRate() { testcase("Transfer rate"); using namespace jtx; Env env(*this); auto const gw1 = Account("gw1"); env.fund(XRP(10000), gw1); env.close(); auto rdView = env.closed(); // Test with no rate set on gw1. BEAST_EXPECT(transferRate(*rdView, gw1) == parityRate); env(rate(gw1, 1.02)); env.close(); rdView = env.closed(); BEAST_EXPECT(transferRate(*rdView, gw1) == Rate{1020000000}); } void testAreCompatible() { // This test requires incompatible ledgers. The good news we can // construct and manage two different Env instances at the same // time. So we can use two Env instances to produce mutually // incompatible ledgers. testcase("Are compatible"); using namespace jtx; auto const alice = Account("alice"); auto const bob = Account("bob"); // The first Env. Env eA(*this, envconfig(), nullptr, beast::severities::kDisabled); eA.fund(XRP(10000), alice); eA.close(); auto const rdViewA3 = eA.closed(); eA.fund(XRP(10000), bob); eA.close(); auto const rdViewA4 = eA.closed(); // The two Env's can't share the same ports, so modify the config // of the second Env to use higher port numbers Env eB{*this, envconfig(), nullptr, beast::severities::kDisabled}; // Make ledgers that are incompatible with the first ledgers. Note // that bob is funded before alice. eB.fund(XRP(10000), bob); eB.close(); auto const rdViewB3 = eB.closed(); eB.fund(XRP(10000), alice); eB.close(); auto const rdViewB4 = eB.closed(); // Check for compatibility. auto jStream = eA.journal.error(); BEAST_EXPECT(areCompatible(*rdViewA3, *rdViewA4, jStream, "")); BEAST_EXPECT(areCompatible(*rdViewA4, *rdViewA3, jStream, "")); BEAST_EXPECT(areCompatible(*rdViewA4, *rdViewA4, jStream, "")); BEAST_EXPECT(!areCompatible(*rdViewA3, *rdViewB4, jStream, "")); BEAST_EXPECT(!areCompatible(*rdViewA4, *rdViewB3, jStream, "")); BEAST_EXPECT(!areCompatible(*rdViewA4, *rdViewB4, jStream, "")); // Try the other interface. // Note that the different interface has different outcomes. auto const& iA3 = rdViewA3->header(); auto const& iA4 = rdViewA4->header(); BEAST_EXPECT(areCompatible(iA3.hash, iA3.seq, *rdViewA4, jStream, "")); BEAST_EXPECT(areCompatible(iA4.hash, iA4.seq, *rdViewA3, jStream, "")); BEAST_EXPECT(areCompatible(iA4.hash, iA4.seq, *rdViewA4, jStream, "")); BEAST_EXPECT(!areCompatible(iA3.hash, iA3.seq, *rdViewB4, jStream, "")); BEAST_EXPECT(areCompatible(iA4.hash, iA4.seq, *rdViewB3, jStream, "")); BEAST_EXPECT(!areCompatible(iA4.hash, iA4.seq, *rdViewB4, jStream, "")); } void testRegressions() { testcase("Regressions"); using namespace jtx; // Create a ledger with 1 item, put a // ApplyView on that, then another ApplyView, // erase the item, apply. { Env env(*this); Config config; std::shared_ptr const genesis = std::make_shared( create_genesis, config, std::vector{}, env.app().getNodeFamily()); auto const ledger = std::make_shared( *genesis, env.app().timeKeeper().closeTime()); wipe(*ledger); ledger->rawInsert(sle(1)); ReadView& v0 = *ledger; ApplyViewImpl v1(&v0, tapNONE); { Sandbox v2(&v1); v2.erase(v2.peek(k(1))); v2.apply(v1); } BEAST_EXPECT(!v1.exists(k(1))); } // Make sure OpenLedger::empty works { Env env(*this); BEAST_EXPECT(env.app().openLedger().empty()); env.fund(XRP(10000), Account("test")); BEAST_EXPECT(!env.app().openLedger().empty()); } } void run() override { // This had better work, or else BEAST_EXPECT(k(0).key < k(1).key); testLedger(); testMeta(); testMetaSucc(); testStacked(); testContext(); testSles(); testUpperAndLowerBound(); testFlags(); testTransferRate(); testAreCompatible(); testRegressions(); } }; class GetAmendments_test : public beast::unit_test::suite { void testGetAmendments() { using namespace jtx; Env env{*this, envconfig(validator, "")}; // Start out with no amendments. auto majorities = getMajorityAmendments(*env.closed()); BEAST_EXPECT(majorities.empty()); // Now close ledgers until the amendments show up. int i = 0; for (i = 0; i <= 256; ++i) { env.close(); majorities = getMajorityAmendments(*env.closed()); if (!majorities.empty()) break; } // There should be at least 3 amendments. Don't do exact comparison // to avoid maintenance as more amendments are added in the future. BEAST_EXPECT(i == 254); BEAST_EXPECT(majorities.size() >= 2); // None of the amendments should be enabled yet. auto enableds = getEnabledAmendments(*env.closed()); BEAST_EXPECT(enableds.empty()); // Now wait 2 weeks modulo 256 ledgers for the amendments to be // enabled. Speed the process by closing ledgers every 80 minutes, // which should get us to just past 2 weeks after 256 ledgers. for (i = 0; i <= 256; ++i) { using namespace std::chrono_literals; env.close(80min); enableds = getEnabledAmendments(*env.closed()); if (!enableds.empty()) break; } BEAST_EXPECT(i == 255); BEAST_EXPECT(enableds.size() >= 2); } void run() override { testGetAmendments(); } }; BEAST_DEFINE_TESTSUITE(View, ledger, xrpl); BEAST_DEFINE_TESTSUITE(GetAmendments, ledger, xrpl); } // namespace test } // namespace xrpl