diff --git a/src/app.cpp b/src/app.cpp index 89e19c0..cb1c13c 100644 --- a/src/app.cpp +++ b/src/app.cpp @@ -6222,6 +6222,14 @@ bool App::startEmbeddedDaemon() }); } + // Compact-via-switch: the user chose to compact after switching from an external node to this built-in + // one. Arm -salvagewallet on this start so the fresh node rewrites wallet.dat into a compact file + // (-salvagewallet implies a rescan). One-shot: it fires only for the start that follows the switch. + if (compact_on_embedded_start_.exchange(false)) { + daemon_controller_->setSalvageOnNextStart(true); + user_initiated_rescan_ = true; // the implied rescan is wallet-triggered → its completion should toast + } + // Only a process we actually launched starts a new story: a start that finds a node already running (and // launches nothing) must not wipe what the tracker knows about that node from its debug.log. const bool launched = daemon_controller_->start( diff --git a/src/app.h b/src/app.h index 7c6982b..760517e 100644 --- a/src/app.h +++ b/src/app.h @@ -810,6 +810,11 @@ public: bool canCompactWallet() const { return supportsFullNodeLifecycleActions() && isUsingEmbeddedDaemon() && daemon_controller_ != nullptr; } + // Compact when connected to an EXTERNAL node: stop it (graceful RPC "stop"), switch the wallet to its + // built-in node, and start that with -salvagewallet so it compacts. Needs a full-node build with + // embedded-daemon support. A one-shot flag arms the salvage on the resulting embedded start. + void compactViaEmbeddedSwitch(); + std::atomic compact_on_embedded_start_{false}; // A newer daemon binary is installed but the running node is still the old one until a restart. void markDaemonRestartPending() { daemon_restart_pending_.store(true); } bool daemonRestartPending() const { return daemon_restart_pending_.load(); } diff --git a/src/app_network.cpp b/src/app_network.cpp index 664efcf..5b88a5b 100644 --- a/src/app_network.cpp +++ b/src/app_network.cpp @@ -6525,6 +6525,52 @@ void App::compactWallet() daemon_controller_->setSalvageOnNextStart(false); // the restart was refused — disarm the stray salvage } +void App::compactViaEmbeddedSwitch() +{ + auto& n = ui::Notifications::instance(); + if (!supportsFullNodeLifecycleActions() || !supportsEmbeddedDaemon()) { + n.warning(TR("appx_fullnode_lifecycle_unavailable_lite")); + return; + } + if (canCompactWallet()) { compactWallet(); return; } // already on the built-in node — just compact + if (daemon_restarting_.load() || isWalletSwitchInProgress() || show_seed_migration_ || + (settings_ && settings_->getSeedMigrationPending())) { + n.warning(TR("merge_run_busy")); + return; + } + if (!worker_) return; + + // Gate the connect loop / auto-start while we swap nodes, then drop the RPC connection to the external + // node (we're about to tell it to stop). onDisconnected shows the loading overlay. + daemon_restarting_ = true; + connection_status_ = TR("compact_switching"); + if (rpc_ && rpc_->isConnected()) { abortMaintenanceCalls(); rpc_->disconnect(); } + onDisconnected("Compact: switch to the built-in node"); + n.info(TR("compact_switching"), 12.0f); + + worker_->post([this]() -> rpc::RPCWorker::MainCb { + // Graceful RPC "stop" to the external node + wait (up to ~20 s) for its RPC port to free. Blocking, + // so it runs on the worker; no RPC is needed meanwhile (we just disconnected). + const bool stopped = stopDaemonForWalletSwitch(); + return [this, stopped]() { // back on the main thread + daemon_restarting_ = false; + if (shutting_down_.load()) return; + if (!stopped) { + ui::Notifications::instance().error(TR("compact_switch_stop_failed")); + return; // the external node is still up; nothing was changed + } + // Switch the wallet to its built-in node, arm the compacting start, and bring it up. The fresh + // node starts with -salvagewallet (rewrite + rescan); the connect loop reconnects to it. (Embedded + // mode is a runtime flag that defaults on for a full-node build, so this just ensures it's set — + // after the switch our own process is the running node.) + setUseEmbeddedDaemon(true); + compact_on_embedded_start_.store(true); + ui::Notifications::instance().info(TR("compact_started")); + startEmbeddedDaemon(); + }; + }); +} + // Complementary on-disk safety net (full-node) for a wallet salvage we did NOT witness this launch — it // happened on a prior run, or under an external daemon whose startup output we never captured, so // detectWalletAutoRecovery() never fired. If the active wallet loads EMPTY while a sibling wallet file in diff --git a/src/ui/pages/settings_page.cpp b/src/ui/pages/settings_page.cpp index 251e877..b79198b 100644 --- a/src/ui/pages/settings_page.cpp +++ b/src/ui/pages/settings_page.cpp @@ -2388,14 +2388,16 @@ void RenderSettingsPage(App* app) { // Compact reclaims the file space consolidation frees (BDB won't shrink in place). // It rewrites wallet.dat by restarting the node with -salvagewallet, so it only works // with the embedded node — disable it for an external dragonxd and explain on hover. - const bool canCompact = app->canCompactWallet(); - ImGui::BeginDisabled(!canCompact); + // Enabled whenever this build can run the built-in node. If connected to an external + // node, the confirmation offers to switch to the built-in one first (compactViaEmbeddedSwitch). + const bool compactPossible = app->supportsEmbeddedDaemon(); + ImGui::BeginDisabled(!compactPossible); if (material::ActionButton("##walletcompact", TR("wallet_size_compact"), ICON_MD_COMPRESS, material::ActionTier::Secondary)) s_settingsState.confirm_compact_wallet = true; ImGui::EndDisabled(); if (ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled)) - material::Tooltip("%s", TR(canCompact ? "tt_wallet_size_compact" : "compact_needs_embedded")); + material::Tooltip("%s", TR(compactPossible ? "tt_wallet_size_compact" : "compact_needs_embedded")); } // Row 3: folder buttons (their own row so the path gets the full width). @@ -3482,11 +3484,14 @@ void RenderSettingsPage(App* app) { // Confirm: compact wallet.dat to reclaim the space consolidation freed (rewrite via -salvagewallet, // which restarts the node and re-scans the chain). A backup of the current wallet is kept. if (s_settingsState.confirm_compact_wallet) { + // Direct compact when the wallet already runs the built-in node; otherwise the dialog explains it + // will stop the external node and switch to the built-in one first. + const bool embeddedNow = app->canCompactWallet(); if (BeginOverlayDialog(TR("confirm_compact_wallet_title"), &s_settingsState.confirm_compact_wallet, 500.0f, 0.94f)) { material::DialogWarningHeader(TR("warning"), material::WarningVec4()); ImGui::Spacing(); - ImGui::TextWrapped("%s", TR("confirm_compact_wallet_msg")); + ImGui::TextWrapped("%s", TR(embeddedNow ? "confirm_compact_wallet_msg" : "confirm_compact_switch_msg")); ImGui::Spacing(); ImGui::TextColored(material::SuccessVec4(), "%s", TR("confirm_compact_wallet_safe")); ImGui::Spacing(); @@ -3501,7 +3506,8 @@ void RenderSettingsPage(App* app) { s_settingsState.confirm_compact_wallet = false; } if (doConfirm) { - app->compactWallet(); + if (embeddedNow) app->compactWallet(); + else app->compactViaEmbeddedSwitch(); s_settingsState.confirm_compact_wallet = false; } EndOverlayDialog(); diff --git a/src/util/i18n.cpp b/src/util/i18n.cpp index ee66886..50dd96a 100644 --- a/src/util/i18n.cpp +++ b/src/util/i18n.cpp @@ -886,6 +886,9 @@ void I18n::loadBuiltinEnglish() strings_["confirm_compact_wallet_safe"] = "Your funds and balance are safe \xE2\x80\x94 a timestamped backup of the current wallet is kept."; strings_["compact_wallet_confirm"] = "Compact"; strings_["compact_needs_embedded"] = "Compacting rewrites wallet.dat, which the wallet can only do with its built-in node. You're connected to a dragonxd you started yourself \xE2\x80\x94 turn on \"Use embedded dragonxd\" in Settings, or restart your dragonxd with -salvagewallet to reclaim the space."; + strings_["confirm_compact_switch_msg"] = "You're connected to a dragonxd you started yourself. To compact, the wallet will stop that node, switch to its own built-in node, and rewrite wallet.dat on it \xE2\x80\x94 restarting and re-scanning the blockchain, which can take a while. Note: the built-in node is the dragonxd in the wallet's own folder, so this changes which node you run."; + strings_["compact_switching"] = "Stopping your node and switching to the built-in one\xE2\x80\xA6"; + strings_["compact_switch_stop_failed"] = "Couldn't stop your external node \xE2\x80\x94 it's still running, so nothing was changed."; strings_["settings_debug_changed"] = "Debug categories changed \xe2\x80\x94 restart daemon to apply"; strings_["settings_auto_detected"] = "Auto-detected from DRAGONX.conf"; strings_["settings_visual_effects"] = "Visual Effects";