#include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace xrpl { class LedgerLoad_test : public beast::unit_test::suite { auto static ledgerConfig( std::unique_ptr cfg, std::string const& dbPath, std::string const& ledger, StartUpType type, std::optional trapTxHash) { cfg->START_LEDGER = ledger; cfg->START_UP = type; cfg->TRAP_TX_HASH = trapTxHash; assert(!dbPath.empty()); cfg->legacy("database_path", dbPath); return cfg; } // setup for test cases struct SetupData { std::string const dbPath; // NOLINTBEGIN(readability-redundant-member-init) std::string ledgerFile = {}; Json::Value ledger = {}; Json::Value hashes = {}; uint256 trapTxHash = {}; // NOLINTEND(readability-redundant-member-init) }; SetupData setupLedger(beast::temp_dir const& td) { using namespace test::jtx; SetupData retval = {.dbPath = td.path()}; retval.ledgerFile = td.file("ledgerdata.json"); Env env{*this}; std::optional prev; for (auto i = 0; i < 20; ++i) { Account const acct{"A" + std::to_string(i)}; env.fund(XRP(10000), acct); env.close(); if (i > 0 && BEAST_EXPECT(prev.has_value())) { env.trust(acct["USD"](1000), *prev); // NOLINT(bugprone-unchecked-optional-access) env(pay( acct, *prev, acct["USD"](5))); // NOLINT(bugprone-unchecked-optional-access) } env(offer(acct, XRP(100), acct["USD"](1))); env.close(); prev.emplace(acct); } retval.ledger = env.rpc("ledger", "current", "full")[jss::result]; BEAST_EXPECT(retval.ledger[jss::ledger][jss::accountState].size() == 102); retval.hashes = [&] { for (auto const& it : retval.ledger[jss::ledger][jss::accountState]) { if (it[sfLedgerEntryType.fieldName] == jss::LedgerHashes) return it[sfHashes.fieldName]; } return Json::Value{}; }(); BEAST_EXPECT(retval.hashes.size() == 41); retval.trapTxHash = [&]() { auto const txs = env.rpc( "ledger", std::to_string(41), "tx")[jss::result][jss::ledger][jss::transactions]; BEAST_EXPECT(txs.isArray() && txs.size() > 0); uint256 tmp; BEAST_EXPECT(tmp.parseHex(txs[0u][jss::hash].asString())); return tmp; }(); // write this ledger data to a file. std::ofstream o(retval.ledgerFile, std::ios::out | std::ios::trunc); o << to_string(retval.ledger); o.close(); return retval; } void testLoad(SetupData const& sd) { testcase("Load a saved ledger"); using namespace test::jtx; // create a new env with the ledger file specified for startup Env env( *this, envconfig(ledgerConfig, sd.dbPath, sd.ledgerFile, StartUpType::LoadFile, std::nullopt), nullptr, beast::severities::kDisabled); auto jrb = env.rpc("ledger", "current", "full")[jss::result]; BEAST_EXPECT( sd.ledger[jss::ledger][jss::accountState].size() == jrb[jss::ledger][jss::accountState].size()); } void testBadFiles(SetupData const& sd) { testcase("Load ledger: Bad Files"); using namespace test::jtx; using namespace boost::filesystem; // empty path except([&] { Env const env( *this, envconfig(ledgerConfig, sd.dbPath, "", StartUpType::LoadFile, std::nullopt), nullptr, beast::severities::kDisabled); }); // file does not exist except([&] { Env const env( *this, envconfig( ledgerConfig, sd.dbPath, "badfile.json", StartUpType::LoadFile, std::nullopt), nullptr, beast::severities::kDisabled); }); // make a corrupted version of the ledger file (last 10 bytes removed). boost::system::error_code ec; auto ledgerFileCorrupt = boost::filesystem::path{sd.dbPath} / "ledgerdata_bad.json"; copy_file(sd.ledgerFile, ledgerFileCorrupt, copy_options::overwrite_existing, ec); if (!BEAST_EXPECTS(!ec, ec.message())) return; auto filesize = file_size(ledgerFileCorrupt, ec); if (!BEAST_EXPECTS(!ec, ec.message())) return; resize_file(ledgerFileCorrupt, filesize - 10, ec); if (!BEAST_EXPECTS(!ec, ec.message())) return; except([&] { Env const env( *this, envconfig( ledgerConfig, sd.dbPath, ledgerFileCorrupt.string(), StartUpType::LoadFile, std::nullopt), nullptr, beast::severities::kDisabled); }); } void testLoadByHash(SetupData const& sd) { testcase("Load by hash"); using namespace test::jtx; // create a new env with the ledger hash specified for startup auto ledgerHash = to_string(sd.hashes[sd.hashes.size() - 1]); boost::erase_all(ledgerHash, "\""); Env env( *this, envconfig(ledgerConfig, sd.dbPath, ledgerHash, StartUpType::Load, std::nullopt), nullptr, beast::severities::kDisabled); auto jrb = env.rpc("ledger", "current", "full")[jss::result]; BEAST_EXPECT(jrb[jss::ledger][jss::accountState].size() == 98); BEAST_EXPECT( jrb[jss::ledger][jss::accountState].size() <= sd.ledger[jss::ledger][jss::accountState].size()); } void testReplay(SetupData const& sd) { testcase("Load and replay by hash"); using namespace test::jtx; // create a new env with the ledger hash specified for startup auto ledgerHash = to_string(sd.hashes[sd.hashes.size() - 1]); boost::erase_all(ledgerHash, "\""); Env env( *this, envconfig(ledgerConfig, sd.dbPath, ledgerHash, StartUpType::Replay, std::nullopt), nullptr, beast::severities::kDisabled); auto const jrb = env.rpc("ledger", "current", "full")[jss::result]; BEAST_EXPECT(jrb[jss::ledger][jss::accountState].size() == 97); // in replace mode do not automatically accept the ledger being replayed env.close(); auto const closed = env.rpc("ledger", "current", "full")[jss::result]; BEAST_EXPECT(closed[jss::ledger][jss::accountState].size() == 98); BEAST_EXPECT( closed[jss::ledger][jss::accountState].size() <= sd.ledger[jss::ledger][jss::accountState].size()); } void testReplayTx(SetupData const& sd) { testcase("Load and replay transaction by hash"); using namespace test::jtx; // create a new env with the ledger hash specified for startup auto ledgerHash = to_string(sd.hashes[sd.hashes.size() - 1]); boost::erase_all(ledgerHash, "\""); Env env( *this, envconfig(ledgerConfig, sd.dbPath, ledgerHash, StartUpType::Replay, sd.trapTxHash), nullptr, beast::severities::kDisabled); auto const jrb = env.rpc("ledger", "current", "full")[jss::result]; BEAST_EXPECT(jrb[jss::ledger][jss::accountState].size() == 97); // in replace mode do not automatically accept the ledger being replayed env.close(); auto const closed = env.rpc("ledger", "current", "full")[jss::result]; BEAST_EXPECT(closed[jss::ledger][jss::accountState].size() == 98); BEAST_EXPECT( closed[jss::ledger][jss::accountState].size() <= sd.ledger[jss::ledger][jss::accountState].size()); } void testReplayTxFail(SetupData const& sd) { testcase("Load and replay transaction by hash failure"); using namespace test::jtx; // create a new env with the ledger hash specified for startup auto ledgerHash = to_string(sd.hashes[sd.hashes.size() - 1]); boost::erase_all(ledgerHash, "\""); try { // will throw an exception, because we cannot load a ledger for // replay when trapTxHash is set to an invalid transaction Env const env( *this, envconfig(ledgerConfig, sd.dbPath, ledgerHash, StartUpType::Replay, ~sd.trapTxHash), nullptr, beast::severities::kDisabled); BEAST_EXPECT(false); } catch (std::runtime_error const&) { BEAST_EXPECT(true); } catch (...) { BEAST_EXPECT(false); } } void testLoadLatest(SetupData const& sd) { testcase("Load by keyword"); using namespace test::jtx; // create a new env with the ledger "latest" specified for startup Env env( *this, envconfig(ledgerConfig, sd.dbPath, "latest", StartUpType::Load, std::nullopt), nullptr, beast::severities::kDisabled); auto jrb = env.rpc("ledger", "current", "full")[jss::result]; BEAST_EXPECT( sd.ledger[jss::ledger][jss::accountState].size() == jrb[jss::ledger][jss::accountState].size()); } void testLoadIndex(SetupData const& sd) { testcase("Load by index"); using namespace test::jtx; // create a new env with specific ledger index at startup Env env( *this, envconfig(ledgerConfig, sd.dbPath, "43", StartUpType::Load, std::nullopt), nullptr, beast::severities::kDisabled); auto jrb = env.rpc("ledger", "current", "full")[jss::result]; BEAST_EXPECT( sd.ledger[jss::ledger][jss::accountState].size() == jrb[jss::ledger][jss::accountState].size()); } public: void run() override { beast::temp_dir const td; auto sd = setupLedger(td); // test cases testLoad(sd); testBadFiles(sd); testLoadByHash(sd); testReplay(sd); testReplayTx(sd); testReplayTxFail(sd); testLoadLatest(sd); testLoadIndex(sd); } }; BEAST_DEFINE_TESTSUITE(LedgerLoad, app, xrpl); } // namespace xrpl