#pragma once #include "embedded_daemon.h" #include #include #include #include #include namespace dragonx { namespace config { class Settings; } namespace daemon { class DaemonController { public: using State = EmbeddedDaemon::State; using StateCallback = EmbeddedDaemon::StateCallback; enum class ShutdownAction { DisconnectOnly, StopDaemon }; struct ShutdownDecision { ShutdownAction action = ShutdownAction::DisconnectOnly; const char* logReason = "no embedded daemon"; const char* status = "Disconnecting..."; }; enum class LifecycleOperation { ManualRestart, Rescan, RepairWallet, // restart with -zapwallettxes=2 (wipe & rebuild wallet tx records) DeleteBlockchainData, BootstrapStop }; struct LifecycleDecision { LifecycleOperation operation = LifecycleOperation::ManualRestart; bool allowed = false; bool wasRunning = false; const char* taskName = ""; const char* status = ""; const char* warning = ""; bool resetCrashCount = false; bool setRescanOnNextStart = false; bool disconnectRpc = false; int restartDelayMs = 0; bool setZapOnNextStart = false; }; class LifecycleTaskContext { public: virtual ~LifecycleTaskContext() = default; virtual bool cancelled() const = 0; virtual bool shuttingDown() const = 0; virtual void sleepForMs(int milliseconds) = 0; }; class LifecycleRuntime { public: virtual ~LifecycleRuntime() = default; virtual void stopDaemonWithPolicy() = 0; virtual bool startDaemon() = 0; virtual int deleteBlockchainData() = 0; virtual void resetOutputOffset() = 0; virtual void requestRpcStopAndDisconnect(const char* context, const char* reason) = 0; }; struct LifecycleExecutionResult { bool completed = false; bool cancelled = false; bool stopped = false; bool started = false; int deletedItems = 0; }; DaemonController(); ~DaemonController(); DaemonController(const DaemonController&) = delete; DaemonController& operator=(const DaemonController&) = delete; EmbeddedDaemon* daemon() { return daemon_.get(); } const EmbeddedDaemon* daemon() const { return daemon_.get(); } void setStateCallback(StateCallback callback); void syncSettings(const config::Settings* settings); bool start(const config::Settings* settings); void stop(int waitMs); bool isRunning() const; bool externalDaemonDetected() const; State state() const; const std::string& lastError() const; int crashCount() const; int lastBlockHeight() const; double memoryUsageMB() const; std::vector recentLines(std::size_t count) const; std::string outputSince(std::size_t& offset) const; void resetCrashCount(); void setRescanOnNextStart(bool enabled); bool rescanOnNextStart() const; void setZapOnNextStart(bool enabled); bool zapOnNextStart() const; static ShutdownDecision evaluateShutdownPolicy(bool hasDaemon, bool externalDaemonDetected, bool keepDaemonRunning, bool stopExternalDaemon) { if (!hasDaemon) { return {}; } if (keepDaemonRunning) { return {ShutdownAction::DisconnectOnly, "keep_daemon_running enabled", "Disconnecting (daemon stays running)..."}; } if (externalDaemonDetected && !stopExternalDaemon) { return {ShutdownAction::DisconnectOnly, "external daemon (not ours to stop)", "Disconnecting (daemon stays running)..."}; } return {ShutdownAction::StopDaemon, "stopping managed daemon", "Sending stop command to daemon..."}; } static LifecycleDecision evaluateLifecycleOperation(LifecycleOperation operation, bool usingEmbeddedDaemon, bool hasDaemon, bool daemonRunning, bool restartInProgress = false) { switch (operation) { case LifecycleOperation::ManualRestart: if (!usingEmbeddedDaemon || restartInProgress) return {}; return {operation, true, daemonRunning, "daemon-restart", "Restarting daemon...", "", true, false, true, 500}; case LifecycleOperation::Rescan: if (!usingEmbeddedDaemon || !hasDaemon) { return {operation, false, daemonRunning, "", "", "Rescan requires embedded daemon. Restart your daemon with -rescan manually."}; } return {operation, true, daemonRunning, "rescan-blockchain", "Starting rescan...", "", false, true, false, 3000}; case LifecycleOperation::RepairWallet: if (!usingEmbeddedDaemon || !hasDaemon) { return {operation, false, daemonRunning, "", "", "Wallet repair requires embedded daemon. Restart your daemon with -zapwallettxes=2 manually."}; } return {operation, true, daemonRunning, "repair-wallet", "Repairing wallet...", "", false, false, false, 3000, true}; case LifecycleOperation::DeleteBlockchainData: if (!usingEmbeddedDaemon || !hasDaemon) { return {operation, false, daemonRunning, "", "", "Delete blockchain requires embedded daemon. Stop your daemon manually and delete the data directory."}; } return {operation, true, daemonRunning, "delete-blockchain-data", "Deleting blockchain data...", "", false, false, false, 3000}; case LifecycleOperation::BootstrapStop: return {operation, true, daemonRunning, "bootstrap-stop-daemon", "Stopping daemon for bootstrap...", "", false, false, true, 0}; } return {}; } void prepareLifecycleOperation(const LifecycleDecision& decision, const config::Settings* settings = nullptr); static inline LifecycleExecutionResult executeLifecycleOperation(const LifecycleDecision& decision, LifecycleRuntime& runtime, LifecycleTaskContext& task) { LifecycleExecutionResult result; if (!decision.allowed) return result; auto waitForDelay = [&]() { int waitTicks = std::max(0, decision.restartDelayMs / 100); for (int i = 0; i < waitTicks && !task.cancelled() && !task.shuttingDown(); ++i) { task.sleepForMs(100); } }; auto cancelled = [&]() { result.cancelled = task.cancelled() || task.shuttingDown(); return result.cancelled; }; switch (decision.operation) { case LifecycleOperation::BootstrapStop: if (decision.wasRunning) { runtime.requestRpcStopAndDisconnect("Bootstrap daemon stop", "Bootstrap"); result.stopped = true; } result.completed = true; return result; case LifecycleOperation::ManualRestart: if (decision.wasRunning) { runtime.stopDaemonWithPolicy(); result.stopped = true; } break; case LifecycleOperation::Rescan: case LifecycleOperation::RepairWallet: case LifecycleOperation::DeleteBlockchainData: runtime.stopDaemonWithPolicy(); result.stopped = true; break; } if (cancelled()) return result; waitForDelay(); if (cancelled()) return result; if (decision.operation == LifecycleOperation::DeleteBlockchainData) { result.deletedItems = runtime.deleteBlockchainData(); if (cancelled()) return result; } if (decision.operation == LifecycleOperation::Rescan || decision.operation == LifecycleOperation::RepairWallet || decision.operation == LifecycleOperation::DeleteBlockchainData) { runtime.resetOutputOffset(); } result.started = runtime.startDaemon(); result.completed = !cancelled(); return result; } ShutdownDecision shutdownDecision(bool keepDaemonRunning, bool stopExternalDaemon) const; private: std::unique_ptr daemon_; }; } // namespace daemon } // namespace dragonx