// DragonX Wallet - ImGui Edition // Copyright 2024-2026 The Hush Developers // Released under the GPLv3 // // provision_state.h - state of the startup "node files" job (App::startStartupFilesProvision): on Windows it // extracts the bundled Sapling params / asmap / node / miner from the .exe, on Linux/macOS it copies them // from the executable's directory, and on full-node builds it then verifies the Sapling params. The job runs // on a worker thread; the UI thread only polls this. No app dependencies, so tests include it directly. #pragma once #include #include #include #include #include namespace dragonx { namespace resources { enum class ProvisionPhase : int { Idle = 0, Extracting, Copying, Verifying }; // Lock-free progress: written by the worker, read by the UI every frame. struct ProvisionProgress { std::atomic bytesDone{0}; std::atomic bytesTotal{0}; std::atomic phase{static_cast(ProvisionPhase::Idle)}; void setPhase(ProvisionPhase p) { phase.store(static_cast(p), std::memory_order_relaxed); } ProvisionPhase getPhase() const { return static_cast(phase.load(std::memory_order_relaxed)); } }; // 0..100; 0 while the total is unknown. inline int provisionPercent(std::uint64_t done, std::uint64_t total) { if (total == 0) return 0; if (done >= total) return 100; return static_cast((done * 100u) / total); } // The chip stays hidden for a short grace period so a job that ends in a few ms never flashes. inline bool provisionChipVisible(bool running, double elapsedSeconds, double delaySeconds) { return running && elapsedSeconds >= delaySeconds; } // What App::startEmbeddedDaemon() does while the job may still be writing the node files: never launch the // node over half-written files, never block the UI thread (defer + replay), workers simply wait. enum class DaemonStartGate { Proceed, Defer, Wait }; inline DaemonStartGate daemonStartGate(bool jobRunning, bool onUiThread) { if (!jobRunning) return DaemonStartGate::Proceed; return onUiThread ? DaemonStartGate::Defer : DaemonStartGate::Wait; } class StartupProvision { public: struct Result { bool extractOk = true; // every needed file written/copied (false: disk full / permissions / AV) bool verifyOk = true; // Sapling SHA-256 check (full node only) bool cancelled = false; // shutdown stopped it; nothing is wrong }; enum class State : int { Idle, Running, Finished, Reaped }; ProvisionProgress progress; // The Sapling param repair copy (replace the daemon-dir params from the bundle, ~51.5 MB) ran this session: // set by the job and by App::startEmbeddedDaemon(), so a still-failing verify never repeats it on every // connect retry (the UI thread). std::atomic paramRepairTried{false}; void begin() { std::lock_guard lk(m_); state_ = State::Running; startedAt_ = std::chrono::steady_clock::now(); } // Nothing needed doing (the steady-state launch): no thread was started. void markNothingToDo() { std::lock_guard lk(m_); state_ = State::Reaped; } void finish(const Result& r) { { std::lock_guard lk(m_); result_ = r; state_ = State::Finished; } cv_.notify_all(); } bool isRunning() const { std::lock_guard lk(m_); return state_ == State::Running; } // Non-reaping peek: the job has ended and shutdown cancelled it (synchronized with finish() through m_). bool endedCancelled() const { std::lock_guard lk(m_); return state_ != State::Running && result_.cancelled; } // App::hasPendingBackgroundResults(): finished and not yet reaped (the reap needs one update() tick). // NOT while Running: nothing is reapable then, and the chip already requests progress frames when visible. bool hasCompletion() const { std::lock_guard lk(m_); return state_ == State::Finished; } // UI thread: true exactly once, after the job finished. bool takeCompletion(Result* out) { std::lock_guard lk(m_); if (state_ != State::Finished) return false; if (out) *out = result_; state_ = State::Reaped; return true; } // Worker threads: true when the job is no longer running. bool waitUntilDone(int timeoutMs) { std::unique_lock lk(m_); cv_.wait_for(lk, std::chrono::milliseconds(timeoutMs), [&] { return state_ != State::Running; }); return state_ != State::Running; } double elapsedSeconds() const { std::lock_guard lk(m_); if (state_ != State::Running) return 0.0; return std::chrono::duration(std::chrono::steady_clock::now() - startedAt_).count(); } private: mutable std::mutex m_; std::condition_variable cv_; State state_ = State::Idle; Result result_; std::chrono::steady_clock::time_point startedAt_{}; }; } // namespace resources } // namespace dragonx