diff --git a/docs/software/commands.md b/docs/software/commands.md index c4dde5790..dc73ec4ba 100644 --- a/docs/software/commands.md +++ b/docs/software/commands.md @@ -148,10 +148,12 @@ Command options can only by placed after command. * **load-xdr **: Load an XDR bucket file, for testing. * **new-db**: Clears the local database and resets it to the genesis ledger. If you connect to the network after that it will catch up from scratch. -* **new-hist ...**: Initialize the named history archives - HISTORY-LABEL. HISTORY-LABEL should be one of the history archives you have - specified in the stellar-core.cfg. This will write a - `.well-known/stellar-history.json` file in the archive root. +* **new-hist [--idempotent] ...**: Initialize the named + history archives HISTORY-LABEL. HISTORY-LABEL should be one of the history + archives you have specified in the stellar-core.cfg. This will write a + `.well-known/stellar-history.json` file in the archive root. With + **--idempotent**, the command succeeds even if the archive is already + initialized. * **offline-info**: Returns an output similar to `--c info` for an offline instance, but written directly to standard output (ignoring log levels). * **print-xdr **: Pretty-print a binary file containing an XDR diff --git a/src/history/HistoryArchiveManager.cpp b/src/history/HistoryArchiveManager.cpp index 20e69a070..7aefee3a8 100644 --- a/src/history/HistoryArchiveManager.cpp +++ b/src/history/HistoryArchiveManager.cpp @@ -197,7 +197,8 @@ HistoryArchiveManager::getCheckLedgerHeaderWork( } bool -HistoryArchiveManager::initializeHistoryArchive(std::string const& arch) const +HistoryArchiveManager::initializeHistoryArchive(std::string const& arch, + bool idempotent) const { auto archive = getHistoryArchive(arch); if (!archive) @@ -215,6 +216,13 @@ HistoryArchiveManager::initializeHistoryArchive(std::string const& arch) const ws.executeWork(0, archive, "hist-init", 0); if (getHas->getState() == BasicWork::State::WORK_SUCCESS) { + if (idempotent) + { + CLOG_INFO(History, + "History archive '{}' already initialized; skipping", + arch); + return true; + } CLOG_ERROR(History, "History archive '{}' already initialized!", arch); return false; } diff --git a/src/history/HistoryArchiveManager.h b/src/history/HistoryArchiveManager.h index 0b310a9cf..da1e8727d 100644 --- a/src/history/HistoryArchiveManager.h +++ b/src/history/HistoryArchiveManager.h @@ -39,7 +39,8 @@ class HistoryArchiveManager // Initialize a named history archive by writing // .well-known/stellar-history.json to it. - bool initializeHistoryArchive(std::string const& arch) const; + bool initializeHistoryArchive(std::string const& arch, + bool idempotent = false) const; // Returns whether or not the HistoryManager has any writable history // archives (those configured with both a `get` and `put` command). diff --git a/src/history/test/HistoryTests.cpp b/src/history/test/HistoryTests.cpp index 0e811557f..d5bd9e97b 100644 --- a/src/history/test/HistoryTests.cpp +++ b/src/history/test/HistoryTests.cpp @@ -2190,6 +2190,28 @@ TEST_CASE("initialize existing history store fails", "[history]") } } +TEST_CASE("initialize existing history store idempotently succeeds", + "[history]") +{ + Config cfg(getTestConfig(0, Config::TESTDB_BUCKET_DB_PERSISTENT)); + TmpDirHistoryConfigurator tcfg; + cfg = tcfg.configure(cfg, true); + + { + VirtualClock clock; + Application::pointer app = createTestApplication(clock, cfg); + REQUIRE(app->getHistoryArchiveManager().initializeHistoryArchive( + tcfg.getArchiveDirName())); + } + + { + VirtualClock clock; + Application::pointer app = createTestApplication(clock, cfg); + REQUIRE(app->getHistoryArchiveManager().initializeHistoryArchive( + tcfg.getArchiveDirName(), true)); + } +} + TEST_CASE("Catchup failure recovery with buffered checkpoint", "[history][catchup]") { diff --git a/src/main/ApplicationUtils.cpp b/src/main/ApplicationUtils.cpp index cc6a2cc4c..fc512824b 100644 --- a/src/main/ApplicationUtils.cpp +++ b/src/main/ApplicationUtils.cpp @@ -1030,7 +1030,8 @@ genSeed() } int -initializeHistories(Config cfg, std::vector const& newHistories) +initializeHistories(Config cfg, std::vector const& newHistories, + bool idempotent) { VirtualClock clock; cfg.setNoListen(); @@ -1038,7 +1039,8 @@ initializeHistories(Config cfg, std::vector const& newHistories) for (auto const& arch : newHistories) { - if (!app->getHistoryArchiveManager().initializeHistoryArchive(arch)) + if (!app->getHistoryArchiveManager().initializeHistoryArchive( + arch, idempotent)) return 1; } return 0; diff --git a/src/main/ApplicationUtils.h b/src/main/ApplicationUtils.h index c8a3e8a99..970f2f3de 100644 --- a/src/main/ApplicationUtils.h +++ b/src/main/ApplicationUtils.h @@ -52,7 +52,8 @@ int rebuildLedgerFromBuckets(Config cfg); #endif void genSeed(); int initializeHistories(Config cfg, - std::vector const& newHistories); + std::vector const& newHistories, + bool idempotent = false); void writeCatchupInfo(Json::Value const& catchupInfo, std::string const& outputFile); int catchup(Application::pointer app, CatchupConfiguration cc, diff --git a/src/main/CommandLine.cpp b/src/main/CommandLine.cpp index 2c870bed8..a16a93b4e 100644 --- a/src/main/CommandLine.cpp +++ b/src/main/CommandLine.cpp @@ -1501,13 +1501,18 @@ runNewHist(CommandLineArgs const& args) { CommandLine::ConfigOption configOption; std::vector newHistories; + bool idempotent = false; return runWithHelp(args, {configurationParser(configOption), + clara::Opt{idempotent}["--idempotent"]( + "succeed if the history archive is already " + "initialized"), requiredArgParser(newHistories, "HISTORY-LABEL")}, [&] { return initializeHistories(configOption.getConfig(), - newHistories); + newHistories, + idempotent); }); }