// DragonX Wallet - ImGui Edition // Copyright 2024-2026 The Hush Developers // Released under the GPLv3 #pragma once #include #include #include #include #include namespace dragonx { namespace util { /** * @brief Bootstrap downloader + extractor for DRAGONX blockchain data. * * Downloads a blockchain snapshot zip from bootstrap.dragonx.is, verifies * SHA-256 / MD5 checksums, extracts it into the DRAGONX data directory * (skipping wallet.dat), and cleans old chain data before extraction. * All work runs on a background thread; progress is queried thread-safely * from the UI thread. */ class Bootstrap { public: enum class State { Idle, Downloading, Verifying, Extracting, Completed, Failed }; struct Progress { State state = State::Idle; double downloaded_bytes = 0; double total_bytes = 0; // 0 if server doesn't send Content-Length float percent = 0.0f; // 0..100 std::string status_text; // human-readable status std::string error; // non-empty on failure }; Bootstrap() = default; ~Bootstrap(); // Non-copyable Bootstrap(const Bootstrap&) = delete; Bootstrap& operator=(const Bootstrap&) = delete; /// Base URL for bootstrap downloads (zip + checksum files). static constexpr const char* kBaseUrl = "https://bootstrap.dragonx.is"; static constexpr const char* kMirrorUrl = "https://bootstrap2.dragonx.is"; static constexpr const char* kZipName = "DRAGONX.zip"; /// Start the bootstrap process on a background thread. /// @param dataDir Path to DRAGONX data dir (e.g. ~/.hush/DRAGONX/) /// @param url Bootstrap zip URL void start(const std::string& dataDir, const std::string& url = "https://bootstrap.dragonx.is/DRAGONX.zip"); /// Cancel an in-progress download/extract. void cancel(); /// Thread-safe read of current progress. Progress getProgress() const; /// Whether the operation is finished (success or failure). bool isDone() const; /// Format a byte count as human-readable string (e.g. "1.24 GB") static std::string formatSize(double bytes); private: mutable std::mutex mutex_; Progress progress_; std::atomic cancel_requested_{false}; std::thread worker_; std::atomic worker_running_{false}; bool download(const std::string& url, const std::string& destZip); bool extract(const std::string& zipPath, const std::string& dataDir); void cleanChainData(const std::string& dataDir); void setProgress(State state, const std::string& text, double downloaded = 0, double total = 0); /// Download a small text file (checksum) into a string. Returns empty on failure. std::string downloadSmallFile(const std::string& url); /// Compute SHA-256 of a file, return lowercase hex digest. /// Updates progress with "Verifying SHA-256..." status during computation. std::string computeSHA256(const std::string& filePath); /// Compute MD5 of a file, return lowercase hex digest. /// Updates progress with "Verifying MD5..." status during computation. std::string computeMD5(const std::string& filePath); /// Parse the first hex token from a checksum file (handles " " format). static std::string parseChecksumFile(const std::string& content); /// Verify downloaded zip against remote checksums. Returns true if valid. bool verifyChecksums(const std::string& zipPath, const std::string& baseUrl); /// Perform a HEAD request to get remote file size. /// Returns -1 on failure. long long getRemoteFileSize(const std::string& url); /// Byte offset of the partial file when resuming a download. /// Added to dlnow in progress reports so the bar reflects true total. double resume_offset_{0}; // libcurl progress callback (static → calls into instance via clientp) static int progressCallback(void* clientp, long long dltotal, long long dlnow, long long ultotal, long long ulnow); }; } // namespace util } // namespace dragonx