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>
106 lines
3.8 KiB
C++
106 lines
3.8 KiB
C++
#include "wallet_security_workflow_executor.h"
|
|
|
|
namespace dragonx {
|
|
namespace services {
|
|
|
|
WalletSecurityWorkflowExecutor::Outcome WalletSecurityWorkflowExecutor::unlockWallet(
|
|
const std::string& passphrase, RpcGateway& rpc, int timeoutSeconds)
|
|
{
|
|
std::string error;
|
|
if (!rpc.unlockWallet(passphrase, timeoutSeconds, error)) {
|
|
return {false, error.empty() ? "Incorrect passphrase" : error, true};
|
|
}
|
|
return {true, {}, false};
|
|
}
|
|
|
|
WalletSecurityWorkflowExecutor::ExportOutcome WalletSecurityWorkflowExecutor::exportWallet(
|
|
RpcGateway& rpc, FileGateway& files, std::uint64_t timestampSeconds, long timeoutSeconds)
|
|
{
|
|
ExportOutcome outcome;
|
|
outcome.filePlan = WalletSecurityWorkflow::planWalletFiles(files.dataDir(), timestampSeconds);
|
|
|
|
std::string error;
|
|
if (!rpc.exportWallet(outcome.filePlan.exportFile, timeoutSeconds, error)) {
|
|
outcome.ok = false;
|
|
outcome.error = error.empty() ? "Export failed" : "Export failed: " + error;
|
|
return outcome;
|
|
}
|
|
|
|
outcome.ok = true;
|
|
return outcome;
|
|
}
|
|
|
|
WalletSecurityWorkflowExecutor::Outcome WalletSecurityWorkflowExecutor::stopDaemon(RpcGateway& rpc)
|
|
{
|
|
std::string error;
|
|
(void)rpc.requestDaemonStop(error);
|
|
return {true, {}, false};
|
|
}
|
|
|
|
WalletSecurityWorkflowExecutor::Outcome WalletSecurityWorkflowExecutor::backupEncryptedWallet(
|
|
FileGateway& files, const WalletFilePlan& filePlan)
|
|
{
|
|
std::string error;
|
|
if (!files.backupEncryptedWallet(filePlan, error)) {
|
|
return {false, error.empty() ? "Failed to rename wallet.dat" : "Failed to rename wallet.dat: " + error, false};
|
|
}
|
|
return {true, {}, false};
|
|
}
|
|
|
|
WalletSecurityWorkflowExecutor::Outcome WalletSecurityWorkflowExecutor::restartDaemonAndWait(
|
|
DaemonGateway& daemon, RpcGateway& rpc, int preRestartDelayMs,
|
|
int embeddedRestartSettleMs, int maxProbeSeconds)
|
|
{
|
|
auto waitForMs = [&](int milliseconds) -> bool {
|
|
int remaining = milliseconds;
|
|
while (remaining > 0 && !daemon.cancelled() && !daemon.shuttingDown()) {
|
|
int slice = remaining >= 100 ? 100 : remaining;
|
|
daemon.sleepForMs(slice);
|
|
remaining -= slice;
|
|
}
|
|
return !daemon.cancelled() && !daemon.shuttingDown();
|
|
};
|
|
|
|
if (!waitForMs(preRestartDelayMs)) return {false, "", false};
|
|
|
|
if (daemon.isUsingEmbeddedDaemon()) {
|
|
const bool stopped = daemon.stopEmbeddedDaemon();
|
|
if (daemon.cancelled() || daemon.shuttingDown()) return {false, "", false};
|
|
// Never start a second node over one that hasn't exited (it holds wallet.dat and the datadir lock).
|
|
if (!stopped) return {false, "The node did not stop in time, so it was not restarted.", false};
|
|
if (!waitForMs(embeddedRestartSettleMs)) return {false, "", false};
|
|
daemon.startEmbeddedDaemon();
|
|
}
|
|
|
|
bool daemonUp = false;
|
|
std::string lastError;
|
|
for (int i = 0; i < maxProbeSeconds && !daemon.cancelled() && !daemon.shuttingDown(); ++i) {
|
|
daemon.sleepForMs(1000);
|
|
if (rpc.probeDaemon(lastError)) {
|
|
daemonUp = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (daemon.cancelled() || daemon.shuttingDown()) return {false, "", false};
|
|
if (!daemonUp) return {false, "Daemon failed to restart", false};
|
|
return {true, {}, false};
|
|
}
|
|
|
|
WalletSecurityWorkflowExecutor::Outcome WalletSecurityWorkflowExecutor::importWallet(
|
|
ImportGateway& importer, const std::string& exportPath, long timeoutSeconds)
|
|
{
|
|
std::string error;
|
|
if (!importer.importWallet(exportPath, timeoutSeconds, error)) {
|
|
return {false, error.empty() ? "Key import failed" : "Key import failed: " + error, false};
|
|
}
|
|
return {true, {}, false};
|
|
}
|
|
|
|
void WalletSecurityWorkflowExecutor::cleanupVaultAndPin(const VaultCleanupGateway& cleanup)
|
|
{
|
|
if (cleanup) cleanup();
|
|
}
|
|
|
|
} // namespace services
|
|
} // namespace dragonx
|