Security audit remediation (15 confirmed findings from the codebase audit): - H-02: scrub+delete the decrypt-flow plaintext key export on ALL exit paths (RAII guard) and purge stale obsidiandecryptexport* files at startup. - M-01/L-03/L-04/L-05/L-07: sodium_memzero the Set-PIN and encrypt-PIN worker passphrase/PIN copies, the RPC Basic-auth string (auth_), the exported/imported key buffers (App::wipeSecrets, called from ~App and before main's _Exit), and the first-run wizard "Skip" buffers. - M-03/M-04/M-05/L-06: return locked COPIES from XmrigManager/EmbeddedDaemon getters (dedicated error_mutex_; DaemonController::lastError now by value), route xmrig last_error_ writes through a locked setter, and wrap shutdown_status_/wizard_stop_status_ in a locking GuardedStatus (wizard_stopping_external_ -> std::atomic). - M-02: persist after a console send/shield/import in the lite backend. - L-01: require the confirm click for z_shieldcoinbase/z_mergetoaddress. - L-02: quote/escape each Windows daemon argv per the MSDN CommandLineToArgvW rules. - L-08: pin json/tomlplusplus/libwebp FetchContent to immutable commit SHAs. - I-01: extract updater archives from the already-verified in-memory buffer (no disk re-read TOCTOU). Feature: warn once (full-node) when the active wallet loads empty while a sibling wallet file in the datadir holds keys. A funded salvage wallet.<ts>.bak routes to the recovery/Restore flow; a funded sibling .dat routes to the wallet manager. Per-wallet-file dismissal; gated on synced + address-list-loaded to avoid false positives on warm reconnect / spent-down wallets. UX fixes: - send: show the TOTAL balance (with a spendable "available" note) in the source dropdown and keep pending-change addresses visible. - chat: insert emoji at the cursor position; restrict new-chat recipients to shielded (z) addresses. - console: optional auto-focus of the command input on tab open (off by default). - shutdown: when "stop external daemon" is on, keep the shutdown screen up until the external node actually exits, showing live status. Adversarially reviewed; verified across full-node, lite, and Windows builds; tests pass. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
242 lines
9.3 KiB
C++
242 lines
9.3 KiB
C++
#pragma once
|
|
|
|
#include "embedded_daemon.h"
|
|
|
|
#include <algorithm>
|
|
#include <cstddef>
|
|
#include <memory>
|
|
#include <string>
|
|
#include <vector>
|
|
|
|
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;
|
|
void clearExternalDaemonDetected();
|
|
State state() const;
|
|
std::string lastError() const; // by value: EmbeddedDaemon::getLastError() returns a locked copy (M-04)
|
|
int crashCount() const;
|
|
int lastBlockHeight() const;
|
|
double memoryUsageMB() const;
|
|
std::vector<std::string> 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;
|
|
void setSalvageOnNextStart(bool enabled);
|
|
void setReindexOnNextStart(bool enabled); // -reindex: rebuild the block DB from raw blocks on next start
|
|
|
|
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<EmbeddedDaemon> daemon_;
|
|
};
|
|
|
|
} // namespace daemon
|
|
} // namespace dragonx
|