#include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace xrpl::test { class LedgerMaster_test : public beast::unit_test::Suite { static std::unique_ptr makeNetworkConfig(uint32_t networkID) { using namespace jtx; return envconfig([&](std::unique_ptr cfg) { cfg->networkId = networkID; // This test relies on ledger hash so must lock it to fee 10. cfg->fees.referenceFee = 10; return cfg; }); } void testTxnIdFromIndex(FeatureBitset features) { testcase("tx_id_from_index"); using namespace test::jtx; using namespace std::literals; test::jtx::Env env{*this, makeNetworkConfig(11111)}; auto const alice = Account("alice"); env.fund(XRP(1000), alice); env.close(); // build ledgers std::vector> txns; std::vector> metas; auto const startLegSeq = env.current()->header().seq; for (int i = 0; i < 2; ++i) { env(noop(alice)); txns.emplace_back(env.tx()); env.close(); metas.emplace_back(env.closed()->txRead(env.tx()->getTransactionID()).second); } // add last (empty) ledger env.close(); auto const endLegSeq = env.closed()->header().seq; // test invalid range { std::uint32_t const ledgerSeq = -1; std::uint32_t const txnIndex = 0; auto result = env.app().getLedgerMaster().txnIdFromIndex(ledgerSeq, txnIndex); BEAST_EXPECT(!result); } // test not in ledger { uint32_t const txnIndex = metas[0]->getFieldU32(sfTransactionIndex); auto result = env.app().getLedgerMaster().txnIdFromIndex(0, txnIndex); BEAST_EXPECT(!result); } // test empty ledger { auto result = env.app().getLedgerMaster().txnIdFromIndex(endLegSeq, 0); BEAST_EXPECT(!result); } // ended without result { uint32_t const txnIndex = metas[0]->getFieldU32(sfTransactionIndex); auto result = env.app().getLedgerMaster().txnIdFromIndex(endLegSeq + 1, txnIndex); BEAST_EXPECT(!result); } // success (first tx) { uint32_t const txnIndex = metas[0]->getFieldU32(sfTransactionIndex); auto result = env.app().getLedgerMaster().txnIdFromIndex(startLegSeq, txnIndex); BEAST_EXPECT( // NOLINTNEXTLINE(bugprone-unchecked-optional-access) *result == uint256( "277F4FD89C20B92457FEF05FF63F6405563AD0563C73D967A29727" "72679ADC65")); } // success (second tx) { uint32_t const txnIndex = metas[1]->getFieldU32(sfTransactionIndex); auto result = env.app().getLedgerMaster().txnIdFromIndex(startLegSeq + 1, txnIndex); BEAST_EXPECT( // NOLINTNEXTLINE(bugprone-unchecked-optional-access) *result == uint256( "293DF7335EBBAF4420D52E70ABF470EB4C5792CAEA2F91F76193C2" "819F538FDE")); } } void testCompleteLedgerRange(FeatureBitset features) { // Note that this test is intentionally very similar to // SHAMapStore_test::testLedgerGaps, but has a different // focus. testcase("Complete Ledger operations"); using namespace test::jtx; auto const deleteInterval = 8; Env env{*this, envconfig([](auto cfg) { return online_delete(std::move(cfg), deleteInterval); })}; auto const alice = Account("alice"); env.fund(XRP(1000), alice); env.close(); auto& lm = env.app().getLedgerMaster(); LedgerIndex minSeq = 2; LedgerIndex maxSeq = env.closed()->header().seq; auto& store = env.app().getSHAMapStore(); store.rendezvous(); LedgerIndex lastRotated = store.getLastRotated(); BEAST_EXPECTS(maxSeq == 3, to_string(maxSeq)); BEAST_EXPECTS(lm.getCompleteLedgers() == "2-3", lm.getCompleteLedgers()); BEAST_EXPECTS(lastRotated == 3, to_string(lastRotated)); BEAST_EXPECT(lm.missingFromCompleteLedgerRange(minSeq, maxSeq) == 0); BEAST_EXPECT(lm.missingFromCompleteLedgerRange(minSeq + 1, maxSeq - 1) == 0); BEAST_EXPECT(lm.missingFromCompleteLedgerRange(minSeq - 1, maxSeq + 1) == 2); BEAST_EXPECT(lm.missingFromCompleteLedgerRange(minSeq - 2, maxSeq - 2) == 2); BEAST_EXPECT(lm.missingFromCompleteLedgerRange(minSeq + 2, maxSeq + 2) == 2); // Close enough ledgers to rotate a few times for (int i = 0; i < 24; ++i) { for (int t = 0; t < 3; ++t) { env(noop(alice)); } env.close(); store.rendezvous(); ++maxSeq; if (maxSeq == lastRotated + deleteInterval) { minSeq = lastRotated; lastRotated = maxSeq; } BEAST_EXPECTS( env.closed()->header().seq == maxSeq, to_string(env.closed()->header().seq)); BEAST_EXPECTS(store.getLastRotated() == lastRotated, to_string(store.getLastRotated())); std::stringstream expectedRange; expectedRange << minSeq << "-" << maxSeq; BEAST_EXPECTS(lm.getCompleteLedgers() == expectedRange.str(), lm.getCompleteLedgers()); BEAST_EXPECT(lm.missingFromCompleteLedgerRange(minSeq, maxSeq) == 0); BEAST_EXPECT(lm.missingFromCompleteLedgerRange(minSeq + 1, maxSeq - 1) == 0); BEAST_EXPECT(lm.missingFromCompleteLedgerRange(minSeq - 1, maxSeq + 1) == 2); BEAST_EXPECT(lm.missingFromCompleteLedgerRange(minSeq - 2, maxSeq - 2) == 2); BEAST_EXPECT(lm.missingFromCompleteLedgerRange(minSeq + 2, maxSeq + 2) == 2); } } public: void run() override { using namespace test::jtx; FeatureBitset const all{testableAmendments()}; testWithFeats(all); } void testWithFeats(FeatureBitset features) { testTxnIdFromIndex(features); testCompleteLedgerRange(features); } }; BEAST_DEFINE_TESTSUITE(LedgerMaster, app, xrpl); } // namespace xrpl::test