Files
ObsidianDragon/src/services/wallet_security_workflow_executor.h
T
DanSandClaude Opus 5.5 e20b70d73f fix(daemon): never restart, delete or swap under a node that hasn't exited
Every operation that stops the node (restart, rescan, repair, delete
chain data, reinstall, bootstrap, wallet switch/adopt/restore/rebuild,
wizard restart, encrypt, decrypt) now:

- refuses up front while the node is rebuilding witnesses or has not
  answered for 30s, with a "try again when it's done" warning, and sends
  no stop;
- goes ahead only once the node's PROCESS has exited, not its RPC port.
  A stop that doesn't finish within 120s cancels the operation (warning;
  a cancelled rescan/repair disarms its flags); nothing is started over
  the old node, chain data isn't deleted, wallet.dat isn't swapped.

Specifics:
- stopDaemonForWalletSwitch() no longer reports "stopped" when the app
  is quitting (adopt could swap wallet.dat under a live node).
- Decrypt waited a fixed 3s after "stop" before renaming wallet.dat; it
  now waits for the process to exit, and scrubs the key export if not.
- Bootstrap waits for the process before cleanChainData and stops the
  node through the stop routine instead of the shared RPC client.
- StopScope::Connected lets switch/reinstall/decrypt stop an adopted or
  external node through the same never-kill routine.
- A bounded stop that can't reach the node (a foreign node refusing our
  credentials) gives up after 5s instead of 120s.
- macOS: isDaemonProcessRunning() checks processes (libproc), not the
  RPC port.
- rpcBusyWhileAlive() also reads the busy gate: since the gate, the
  first timeout usually lands on another lane and the 30s clock never
  started (also weakened the quit prompt for a silent rebuild).
- Notifications render during the first-run wizard.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 11:22:39 -05:00

83 lines
3.0 KiB
C++

#pragma once
#include "wallet_security_workflow.h"
#include <cstdint>
#include <functional>
#include <string>
namespace dragonx {
namespace services {
class WalletSecurityWorkflowExecutor {
public:
using WalletFilePlan = WalletSecurityWorkflow::WalletFilePlan;
struct Outcome {
bool ok = false;
std::string error;
bool passphraseRejected = false;
};
struct ExportOutcome : Outcome {
WalletFilePlan filePlan;
};
class RpcGateway {
public:
virtual ~RpcGateway() = default;
virtual bool unlockWallet(const std::string& passphrase, int timeoutSeconds, std::string& error) = 0;
virtual bool exportWallet(const std::string& fileName, long timeoutSeconds, std::string& error) = 0;
virtual bool requestDaemonStop(std::string& error) = 0;
virtual bool probeDaemon(std::string& error) = 0;
};
class ImportGateway {
public:
virtual ~ImportGateway() = default;
virtual bool importWallet(const std::string& exportPath, long timeoutSeconds, std::string& error) = 0;
};
class FileGateway {
public:
virtual ~FileGateway() = default;
virtual std::string dataDir() = 0;
virtual bool backupEncryptedWallet(const WalletFilePlan& filePlan, std::string& error) = 0;
};
class DaemonGateway {
public:
virtual ~DaemonGateway() = default;
virtual bool isUsingEmbeddedDaemon() const = 0;
virtual bool stopEmbeddedDaemon() = 0; // true: the node exited (false: still running; never killed)
virtual bool startEmbeddedDaemon() = 0;
virtual bool cancelled() const = 0;
virtual bool shuttingDown() const = 0;
virtual void sleepForMs(int milliseconds) = 0;
};
using VaultCleanupGateway = std::function<void()>;
static Outcome unlockWallet(const std::string& passphrase,
RpcGateway& rpc,
int timeoutSeconds = 600);
static ExportOutcome exportWallet(RpcGateway& rpc,
FileGateway& files,
std::uint64_t timestampSeconds,
long timeoutSeconds = 300L);
static Outcome stopDaemon(RpcGateway& rpc);
static Outcome backupEncryptedWallet(FileGateway& files,
const WalletFilePlan& filePlan);
static Outcome restartDaemonAndWait(DaemonGateway& daemon,
RpcGateway& rpc,
int preRestartDelayMs = 2000,
int embeddedRestartSettleMs = 1000,
int maxProbeSeconds = 60);
static Outcome importWallet(ImportGateway& importer,
const std::string& exportPath,
long timeoutSeconds = 1200L);
static void cleanupVaultAndPin(const VaultCleanupGateway& cleanup);
};
} // namespace services
} // namespace dragonx