fix(startup): surface a "taking too long" notice when the daemon won't come up
F3: the daemon connect loop retried forever with only an animated spinner when the daemon was reachable-but-never-ready (stuck in RPC warmup / -28, or an external daemon that never finishes init) -- no error, no guidance, no escape. It now stamps connect_stall_since_ the moment the daemon first goes "reachable but not ready" (the warmup branch + applyDaemonInitStatus) and clears it on connect / disconnect / warmup-complete. A pure, unit-testable util::connectHasStalled() helper (new util/connect_stall.h, 45s default from ui.toml [screens.loading].stall-timeout-sec) drives a "Taking longer than expected" notice in renderLoadingOverlay(): a title, a reassuring body with elapsed seconds, and a full-node hint to Settings > Restart Daemon or the Console. The background retry keeps running underneath, so the notice self-clears the instant it connects. Guarded off while the daemon is in State::Error (that case is owned by the existing crash-count hint). The overlay is a pure draw-list layer with no interactive widgets, so this follows the existing crash-hint idiom (guidance text, not injected buttons); the stalled state is computed locally in the overlay, so the only new App member is connect_stall_since_. Adds testConnectHasStalled to test_phase4.cpp and three i18n keys to i18n.cpp (English source of truth; the res/lang/*.json back-fill is deferred to a single add_missing_translations.py run at the end of the batch). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -31,7 +31,7 @@ around a single shared helper. The connectivity-breaking security flip lands las
|
|||||||
| 3 | **F4** | `embedded_daemon.cpp` · `start()` | After F1/F2 so crash-count semantics are settled; its bail deliberately stays out of the crash path. | ☑ |
|
| 3 | **F4** | `embedded_daemon.cpp` · `start()` | After F1/F2 so crash-count semantics are settled; its bail deliberately stays out of the crash path. | ☑ |
|
||||||
| 4 | **F7** | `util/platform` · `connection.cpp` | Structural owner of the fs-error idiom + `ConnectionConfig` that F5/F6/F8 reuse. | ☑ |
|
| 4 | **F7** | `util/platform` · `connection.cpp` | Structural owner of the fs-error idiom + `ConnectionConfig` that F5/F6/F8 reuse. | ☑ |
|
||||||
| 5 | **F6 + F5** | `app.cpp` · `verifySaplingParams()` | Same `startEmbeddedDaemon` / `verifySaplingParams` block; land together. | ☑ |
|
| 5 | **F6 + F5** | `app.cpp` · `verifySaplingParams()` | Same `startEmbeddedDaemon` / `verifySaplingParams` block; land together. | ☑ |
|
||||||
| 6 | **F3** | `app.cpp` · `renderLoadingOverlay()` | After F1 — panel is guarded off during `State::Error` (owned by the crash-count hint). | ☐ |
|
| 6 | **F3** | `app.cpp` · `renderLoadingOverlay()` | After F1 — panel is guarded off during `State::Error` (owned by the crash-count hint). | ☑ |
|
||||||
| 7 | **F8** | `connection.cpp` · `tryConnect()` | Largest; only connectivity-breaking default flip — land last, with release notes. | ☐ |
|
| 7 | **F8** | `connection.cpp` · `tryConnect()` | Largest; only connectivity-breaking default flip — land last, with release notes. | ☐ |
|
||||||
|
|
||||||
---
|
---
|
||||||
@@ -461,7 +461,16 @@ once). Wire `renderPlaintextRemoteRpcDialog` into the app modal-dispatch list.
|
|||||||
|
|
||||||
## F3 — Unbounded connect spinner (deferred to step 6)
|
## F3 — Unbounded connect spinner (deferred to step 6)
|
||||||
|
|
||||||
**Severity:** Medium · **Effort:** S (~3–5h) · **Status:** ☐
|
**Severity:** Medium · **Effort:** S (~3–5h) · **Status:** ☑ landed & verified
|
||||||
|
|
||||||
|
> **As-built note.** `renderLoadingOverlay()` is a pure draw-list overlay with **no interactive
|
||||||
|
> widgets** (the existing crash case at ~5289 already communicates via guidance *text*, relying on
|
||||||
|
> the sidebar staying reachable). So rather than inject `ActionButton`s — which would fight the
|
||||||
|
> non-interactive overlay — the stall notice follows that same idiom: a "Taking longer than
|
||||||
|
> expected" title + a reassuring body (with elapsed seconds) + a full-node-gated hint ("Open
|
||||||
|
> Settings → Restart Daemon, or check the Console"). This let me drop the planned
|
||||||
|
> `WalletState::connect_stalled` flag too: the stalled state is computed locally in the overlay
|
||||||
|
> from `connect_stall_since_`, so the only new member is `App::connect_stall_since_`.
|
||||||
|
|
||||||
The connect loop retries forever while `!state_.connected` (`app.cpp:1239`);
|
The connect loop retries forever while `!state_.connected` (`app.cpp:1239`);
|
||||||
`loading_timer_` only animates the spinner. Stamp `connect_stall_since_` when
|
`loading_timer_` only animates the spinner. Stamp `connect_stall_since_` when
|
||||||
@@ -503,6 +512,7 @@ design record; see the shared-helper table above.
|
|||||||
|
|
||||||
- **F1** — ☑ landed: `isRunning()` (POSIX) now reads the atomic `state_` (predicate `Running || Stopping`) instead of calling `waitpid`, leaving `monitorProcess()` the sole reaper. Clean build (all targets link); `ctest` 1/1 passing. Not unit-testable — needs the manual `kill -SEGV` repro before release.
|
- **F1** — ☑ landed: `isRunning()` (POSIX) now reads the atomic `state_` (predicate `Running || Stopping`) instead of calling `waitpid`, leaving `monitorProcess()` the sole reaper. Clean build (all targets link); `ctest` 1/1 passing. Not unit-testable — needs the manual `kill -SEGV` repro before release.
|
||||||
- **F2** — ☑ landed: `startProcess()` (POSIX) now creates a `FD_CLOEXEC` self-pipe before `fork()`; the child writes `errno` to it on `execv` failure, the parent reads EOF-vs-errno and, on failure, reaps the zombie + sets a precise `last_error_` ("not executable or wrong architecture") + returns `false` (so `start()` no longer reports `Running` for a daemon that never started). Parent-side `setpgid` is now best-effort with a `DEBUG_LOGF` on failure. Clean build; `ctest` 1/1 passing. Not unit-testable — needs the manual non-executable / wrong-arch-binary repro before release.
|
- **F2** — ☑ landed: `startProcess()` (POSIX) now creates a `FD_CLOEXEC` self-pipe before `fork()`; the child writes `errno` to it on `execv` failure, the parent reads EOF-vs-errno and, on failure, reaps the zombie + sets a precise `last_error_` ("not executable or wrong architecture") + returns `false` (so `start()` no longer reports `Running` for a daemon that never started). Parent-side `setpgid` is now best-effort with a `DEBUG_LOGF` on failure. Clean build; `ctest` 1/1 passing. Not unit-testable — needs the manual non-executable / wrong-arch-binary repro before release.
|
||||||
|
- **F3** — ☑ landed: the connect loop now stamps `connect_stall_since_ = ImGui::GetTime()` the moment the daemon first goes "reachable but not ready" (warmup branch + `applyDaemonInitStatus`), and clears it in `onConnected` / `onDisconnected` / warmup-complete — all in `app_network.cpp`. The pure `util::connectHasStalled(stallSince, now, threshold)` helper (new `util/connect_stall.h`, default 45 s from `ui.toml`) drives a draw-list "Taking longer than expected" notice in `renderLoadingOverlay()` (title + elapsed-seconds body + full-node hint), guarded off while the daemon is in `State::Error`. Background retry continues, so the notice self-clears on connect. New `testConnectHasStalled` unit test (7 assertions). Clean build; `ctest` 1/1 passing. (Draw-list text, not buttons — see as-built note above.)
|
||||||
- **F6** — ☑ landed: `verifySaplingParams()` now hash-verifies each Sapling param against its pinned canonical SHA-256 (from `build-lite-backend-artifact.sh`), replacing the existence-only check, so a truncated/corrupt-but-present param is rejected instead of failing later on a shielded op. A `<params_dir>/.sapling_verified` marker keyed on `size:mtime` skips re-hashing ~48 MB on every startup. Logic extracted to the injectable `verifySaplingParamsIn(dir, digests)`; new `testVerifySaplingParams` unit test (valid / marker fast-path / wrong-hash / truncated / missing). Clean build; `ctest` 1/1 passing.
|
- **F6** — ☑ landed: `verifySaplingParams()` now hash-verifies each Sapling param against its pinned canonical SHA-256 (from `build-lite-backend-artifact.sh`), replacing the existence-only check, so a truncated/corrupt-but-present param is rejected instead of failing later on a shielded op. A `<params_dir>/.sapling_verified` marker keyed on `size:mtime` skips re-hashing ~48 MB on every startup. Logic extracted to the injectable `verifySaplingParamsIn(dir, digests)`; new `testVerifySaplingParams` unit test (valid / marker fast-path / wrong-hash / truncated / missing). Clean build; `ctest` 1/1 passing.
|
||||||
- **F5** — ☑ landed: `startEmbeddedDaemon()` now checks `extractEmbeddedResources()`'s return (abort with `sb_daemon_extract_failed` on failure) and the previously-dropped `copy_file` `error_code` in the daemon-binary fallback loop (abort with `sb_daemon_files_failed` incl. the dir), so a disk-full / truncated `dragonxd` write is surfaced up front instead of failing opaquely at spawn. An absent source file stays non-fatal. Two i18n keys added to `i18n.cpp`. Clean build; `ctest` 1/1 passing.
|
- **F5** — ☑ landed: `startEmbeddedDaemon()` now checks `extractEmbeddedResources()`'s return (abort with `sb_daemon_extract_failed` on failure) and the previously-dropped `copy_file` `error_code` in the daemon-binary fallback loop (abort with `sb_daemon_files_failed` incl. the dir), so a disk-full / truncated `dragonxd` write is surfaced up front instead of failing opaquely at spawn. An absent source file stays non-fatal. Two i18n keys added to `i18n.cpp`. Clean build; `ctest` 1/1 passing.
|
||||||
- **F7** — ☑ landed: new non-throwing `Platform::ensureDirectory(dir, outError)` in `util/platform.{h,cpp}` with one consistent message. Replaces the unchecked/throwing directory-create sites at `main.cpp:730` (pre-init: now logs + `MessageBoxA` on Windows + `return 1`), `connection.cpp:216` (autoDetectConfig now uses the ec overload — **no more uncaught `filesystem_error`** — and sets the new `ConnectionConfig::dir_error`), and both `app.cpp` daemon-dir sites (surface via `daemon_status_` + `return false`). Primary connect path (`app_network.cpp:243`) checks `dir_error` and bails to the status line instead of mislabelling it "waiting for config". `embedded_resources.cpp:270` left as-is (already correct). New `testPlatformEnsureDirectory` unit test (existing-dir / fresh-nested / empty / parent-is-file). Clean build; `ctest` 1/1 passing.
|
- **F7** — ☑ landed: new non-throwing `Platform::ensureDirectory(dir, outError)` in `util/platform.{h,cpp}` with one consistent message. Replaces the unchecked/throwing directory-create sites at `main.cpp:730` (pre-init: now logs + `MessageBoxA` on Windows + `return 1`), `connection.cpp:216` (autoDetectConfig now uses the ec overload — **no more uncaught `filesystem_error`** — and sets the new `ConnectionConfig::dir_error`), and both `app.cpp` daemon-dir sites (surface via `daemon_status_` + `return false`). Primary connect path (`app_network.cpp:243`) checks `dir_error` and bails to the status line instead of mislabelling it "waiting for config". `embedded_resources.cpp:270` left as-is (already correct). New `testPlatformEnsureDirectory` unit test (existing-dir / fresh-nested / empty / parent-is-file). Clean build; `ctest` 1/1 passing.
|
||||||
|
|||||||
@@ -1503,6 +1503,7 @@ progress-bar = { height = 6.0, radius = 3.0 }
|
|||||||
progress-width = { size = 260.0 }
|
progress-width = { size = 260.0 }
|
||||||
backdrop-alpha = { opacity = 0.80 }
|
backdrop-alpha = { opacity = 0.80 }
|
||||||
vertical-gap = { size = 8.0 }
|
vertical-gap = { size = 8.0 }
|
||||||
|
stall-timeout-sec = { size = 45.0 }
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
# First-Run Wizard Screens
|
# First-Run Wizard Screens
|
||||||
|
|||||||
50
src/app.cpp
50
src/app.cpp
@@ -69,6 +69,7 @@
|
|||||||
#include "ui/widgets/copy_field.h"
|
#include "ui/widgets/copy_field.h"
|
||||||
#include "ui/notifications.h"
|
#include "ui/notifications.h"
|
||||||
#include "util/i18n.h"
|
#include "util/i18n.h"
|
||||||
|
#include "util/connect_stall.h"
|
||||||
#include "util/platform.h"
|
#include "util/platform.h"
|
||||||
#include "util/text_format.h"
|
#include "util/text_format.h"
|
||||||
#include "util/payment_uri.h"
|
#include "util/payment_uri.h"
|
||||||
@@ -5296,6 +5297,55 @@ void App::renderLoadingOverlay(float contentH)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// -------------------------------------------------------------------
|
||||||
|
// 3d. "Taking longer than expected" notice — the daemon is reachable/launching but
|
||||||
|
// hasn't become ready within the stall threshold. The connect loop keeps retrying
|
||||||
|
// underneath (this notice clears itself the instant it connects); it just stops the
|
||||||
|
// user staring at a silent spinner forever. Guarded off while the daemon is in the
|
||||||
|
// Error state — that case is owned by the crash block (3c) above.
|
||||||
|
// -------------------------------------------------------------------
|
||||||
|
if (connect_stall_since_ > 0.0 &&
|
||||||
|
!(daemon_controller_ &&
|
||||||
|
daemon_controller_->state() == daemon::EmbeddedDaemon::State::Error) &&
|
||||||
|
util::connectHasStalled(connect_stall_since_, ImGui::GetTime(),
|
||||||
|
loadElem("stall-timeout-sec", util::kConnectStallDefaultSeconds))) {
|
||||||
|
curY += gap;
|
||||||
|
ImFont* bodyFont2 = Type().body2();
|
||||||
|
if (!bodyFont2) bodyFont2 = ImGui::GetFont();
|
||||||
|
ImFont* capFont = Type().caption();
|
||||||
|
if (!capFont) capFont = ImGui::GetFont();
|
||||||
|
|
||||||
|
// Title
|
||||||
|
const char* title = TR("loading_stall_title");
|
||||||
|
ImVec2 ts = bodyFont2->CalcTextSizeA(bodyFont2->LegacySize, FLT_MAX, 0.0f, title);
|
||||||
|
dl->AddText(bodyFont2, bodyFont2->LegacySize,
|
||||||
|
ImVec2(wp.x + cx - ts.x * 0.5f, curY),
|
||||||
|
IM_COL32(255, 210, 90, 235), title);
|
||||||
|
curY += ts.y + gap * 0.5f;
|
||||||
|
|
||||||
|
// Body (wrapped) — reassure + show elapsed seconds
|
||||||
|
char stallBody[256];
|
||||||
|
snprintf(stallBody, sizeof(stallBody), TR("loading_stall_body"),
|
||||||
|
(float)(ImGui::GetTime() - connect_stall_since_));
|
||||||
|
float wrapW = ws.x * 0.8f;
|
||||||
|
if (wrapW > 640.0f) wrapW = 640.0f;
|
||||||
|
ImVec2 bs = capFont->CalcTextSizeA(capFont->LegacySize, FLT_MAX, wrapW, stallBody);
|
||||||
|
dl->AddText(capFont, capFont->LegacySize,
|
||||||
|
ImVec2(wp.x + cx - wrapW * 0.5f, curY),
|
||||||
|
IM_COL32(200, 200, 200, 210), stallBody, nullptr, wrapW);
|
||||||
|
curY += bs.y + gap * 0.5f;
|
||||||
|
|
||||||
|
// Actionable guidance (full-node only — lite has no daemon to restart)
|
||||||
|
if (supportsFullNodeLifecycleActions()) {
|
||||||
|
const char* hint = TR("loading_stall_hint");
|
||||||
|
ImVec2 hs = capFont->CalcTextSizeA(capFont->LegacySize, FLT_MAX, 0.0f, hint);
|
||||||
|
dl->AddText(capFont, capFont->LegacySize,
|
||||||
|
ImVec2(wp.x + cx - hs.x * 0.5f, curY),
|
||||||
|
IM_COL32(180, 180, 180, 190), hint);
|
||||||
|
curY += hs.y + gap;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// -------------------------------------------------------------------
|
// -------------------------------------------------------------------
|
||||||
// 4. Daemon output snippet (last few lines, if embedded)
|
// 4. Daemon output snippet (last few lines, if embedded)
|
||||||
// -------------------------------------------------------------------
|
// -------------------------------------------------------------------
|
||||||
|
|||||||
@@ -1023,6 +1023,7 @@ private:
|
|||||||
std::uint64_t clipboard_secret_hash_ = 0;
|
std::uint64_t clipboard_secret_hash_ = 0;
|
||||||
double clipboard_clear_deadline_ = 0.0;
|
double clipboard_clear_deadline_ = 0.0;
|
||||||
float loading_timer_ = 0.0f; // spinner animation for loading overlay
|
float loading_timer_ = 0.0f; // spinner animation for loading overlay
|
||||||
|
double connect_stall_since_ = 0.0; // ImGui::GetTime() when the daemon first went "reachable but not ready"; 0 = not stalling (see util/connect_stall.h)
|
||||||
|
|
||||||
// Current page (sidebar navigation)
|
// Current page (sidebar navigation)
|
||||||
ui::NavPage current_page_ = ui::NavPage::Overview;
|
ui::NavPage current_page_ = ui::NavPage::Overview;
|
||||||
|
|||||||
@@ -396,6 +396,7 @@ void App::tryConnect()
|
|||||||
// fail until warmup completes. Set the warmup state so
|
// fail until warmup completes. Set the warmup state so
|
||||||
// the UI shows status instead of a blocking overlay.
|
// the UI shows status instead of a blocking overlay.
|
||||||
state_.warming_up = true;
|
state_.warming_up = true;
|
||||||
|
if (connect_stall_since_ <= 0.0) connect_stall_since_ = ImGui::GetTime(); // start the "taking too long" clock
|
||||||
auto wt = translateWarmup(warmupStatus);
|
auto wt = translateWarmup(warmupStatus);
|
||||||
state_.warmup_status = wt.title;
|
state_.warmup_status = wt.title;
|
||||||
state_.warmup_description = wt.description;
|
state_.warmup_description = wt.description;
|
||||||
@@ -537,6 +538,7 @@ void App::onConnected()
|
|||||||
}
|
}
|
||||||
state_.daemon_initializing = false; // RPC is answering now; clear the "initializing" overlay
|
state_.daemon_initializing = false; // RPC is answering now; clear the "initializing" overlay
|
||||||
daemon_wait_attempts_ = 0; // re-arm the port-busy / start-failure notifications
|
daemon_wait_attempts_ = 0; // re-arm the port-busy / start-failure notifications
|
||||||
|
connect_stall_since_ = 0.0; // connected — clear the "taking too long" clock
|
||||||
daemon_start_error_shown_ = false;
|
daemon_start_error_shown_ = false;
|
||||||
daemon_last_seen_crashes_ = 0; // (onConnected resets the daemon's crash count too)
|
daemon_last_seen_crashes_ = 0; // (onConnected resets the daemon's crash count too)
|
||||||
connection_status_ = TR("connected");
|
connection_status_ = TR("connected");
|
||||||
@@ -617,6 +619,7 @@ void App::onDisconnected(const std::string& reason)
|
|||||||
state_.connected = false;
|
state_.connected = false;
|
||||||
state_.warming_up = false;
|
state_.warming_up = false;
|
||||||
state_.warmup_status.clear();
|
state_.warmup_status.clear();
|
||||||
|
connect_stall_since_ = 0.0; // reset the "taking too long" clock (App member, untouched by state_.clear())
|
||||||
state_.clear();
|
state_.clear();
|
||||||
connection_status_ = reason;
|
connection_status_ = reason;
|
||||||
|
|
||||||
@@ -671,6 +674,7 @@ void App::onDisconnected(const std::string& reason)
|
|||||||
std::string App::applyDaemonInitStatus(bool reachableButBusy)
|
std::string App::applyDaemonInitStatus(bool reachableButBusy)
|
||||||
{
|
{
|
||||||
state_.daemon_initializing = true;
|
state_.daemon_initializing = true;
|
||||||
|
if (connect_stall_since_ <= 0.0) connect_stall_since_ = ImGui::GetTime(); // start the "taking too long" clock
|
||||||
|
|
||||||
// Find the most recent console line that names an init phase, so we can tell the user exactly
|
// Find the most recent console line that names an init phase, so we can tell the user exactly
|
||||||
// what the node is doing (loading the block index, verifying, activating best chain, …).
|
// what the node is doing (loading the block index, verifying, activating best chain, …).
|
||||||
@@ -1499,6 +1503,7 @@ void App::refreshCoreData()
|
|||||||
state_.warming_up = false;
|
state_.warming_up = false;
|
||||||
state_.warmup_status.clear();
|
state_.warmup_status.clear();
|
||||||
state_.warmup_description.clear();
|
state_.warmup_description.clear();
|
||||||
|
connect_stall_since_ = 0.0; // warmup finished — clear the "taking too long" clock
|
||||||
connection_status_ = TR("connected");
|
connection_status_ = TR("connected");
|
||||||
VERBOSE_LOGF("[warmup] Daemon ready, warmup complete\n");
|
VERBOSE_LOGF("[warmup] Daemon ready, warmup complete\n");
|
||||||
|
|
||||||
|
|||||||
27
src/util/connect_stall.h
Normal file
27
src/util/connect_stall.h
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
// DragonX Wallet - ImGui Edition
|
||||||
|
// Copyright 2024-2026 The Hush Developers
|
||||||
|
// Released under the GPLv3
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
namespace dragonx {
|
||||||
|
namespace util {
|
||||||
|
|
||||||
|
// Default "taking longer than expected" threshold (seconds) for the daemon connect loop,
|
||||||
|
// overridable via ui.toml [screens.loading].stall-timeout-sec. Kept as a free function with
|
||||||
|
// no ImGui/App dependency so it is directly unit-testable from tests/test_phase4.cpp.
|
||||||
|
constexpr float kConnectStallDefaultSeconds = 45.0f;
|
||||||
|
|
||||||
|
// True once a daemon that is reachable-but-not-ready has stayed that way past the threshold.
|
||||||
|
// stallSince : timestamp (same clock as `now`) when the stall began; <= 0 means "not stalling".
|
||||||
|
// now : current time in the same units as stallSince.
|
||||||
|
// thresholdSec: how long to wait before considering it stalled; <= 0 disables the feature.
|
||||||
|
inline bool connectHasStalled(double stallSince, double now, float thresholdSec)
|
||||||
|
{
|
||||||
|
if (stallSince <= 0.0) return false; // not currently in a stall-tracked state
|
||||||
|
if (thresholdSec <= 0.0f) return false; // 0/negative disables the notice defensively
|
||||||
|
return (now - stallSince) >= static_cast<double>(thresholdSec);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace util
|
||||||
|
} // namespace dragonx
|
||||||
@@ -1318,6 +1318,9 @@ void I18n::loadBuiltinEnglish()
|
|||||||
strings_["sb_sapling_not_found"] = "Sapling parameters not found.";
|
strings_["sb_sapling_not_found"] = "Sapling parameters not found.";
|
||||||
strings_["sb_daemon_extract_failed"] = "Failed to write daemon files — check free disk space and permissions.";
|
strings_["sb_daemon_extract_failed"] = "Failed to write daemon files — check free disk space and permissions.";
|
||||||
strings_["sb_daemon_files_failed"] = "Failed to write daemon files to %s — check free disk space and permissions.";
|
strings_["sb_daemon_files_failed"] = "Failed to write daemon files to %s — check free disk space and permissions.";
|
||||||
|
strings_["loading_stall_title"] = "Taking longer than expected";
|
||||||
|
strings_["loading_stall_body"] = "The daemon has been initializing for %.0fs. This can be normal after an update or on first launch (loading the block index or rescanning) — it will connect automatically once ready.";
|
||||||
|
strings_["loading_stall_hint"] = "Still stuck? Open Settings and use Restart Daemon, or check the Console for details.";
|
||||||
strings_["sb_dragonxd_running"] = "dragonxd running";
|
strings_["sb_dragonxd_running"] = "dragonxd running";
|
||||||
strings_["sb_dragonxd_stopping"] = "Stopping dragonxd...";
|
strings_["sb_dragonxd_stopping"] = "Stopping dragonxd...";
|
||||||
strings_["sb_dragonxd_stopped"] = "dragonxd stopped";
|
strings_["sb_dragonxd_stopped"] = "dragonxd stopped";
|
||||||
|
|||||||
@@ -4,6 +4,7 @@
|
|||||||
#include "chat/chat_database.h"
|
#include "chat/chat_database.h"
|
||||||
#include "daemon/daemon_controller.h"
|
#include "daemon/daemon_controller.h"
|
||||||
#include "daemon/embedded_daemon.h"
|
#include "daemon/embedded_daemon.h"
|
||||||
|
#include "util/connect_stall.h"
|
||||||
#include "data/transaction_history_cache.h"
|
#include "data/transaction_history_cache.h"
|
||||||
#include "data/address_book.h"
|
#include "data/address_book.h"
|
||||||
#include "data/wallet_index.h"
|
#include "data/wallet_index.h"
|
||||||
@@ -2478,6 +2479,18 @@ void testDaemonShutdownPolicy()
|
|||||||
EXPECT_TRUE(bootstrap.disconnectRpc);
|
EXPECT_TRUE(bootstrap.disconnectRpc);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void testConnectHasStalled()
|
||||||
|
{
|
||||||
|
using dragonx::util::connectHasStalled;
|
||||||
|
EXPECT_TRUE(connectHasStalled(100.0, 145.0, 45.0f)); // exactly at threshold
|
||||||
|
EXPECT_TRUE(connectHasStalled(100.0, 300.0, 45.0f)); // well over
|
||||||
|
EXPECT_TRUE(!connectHasStalled(100.0, 144.0, 45.0f)); // just under
|
||||||
|
EXPECT_TRUE(!connectHasStalled(0.0, 1000.0, 45.0f)); // sentinel: not stalling
|
||||||
|
EXPECT_TRUE(!connectHasStalled(-1.0, 1000.0, 45.0f)); // sentinel: not stalling
|
||||||
|
EXPECT_TRUE(!connectHasStalled(10.0, 20.0, 0.0f)); // disabled: threshold 0
|
||||||
|
EXPECT_TRUE(!connectHasStalled(10.0, 20.0, -5.0f)); // disabled: negative threshold
|
||||||
|
}
|
||||||
|
|
||||||
void testVerifySaplingParams()
|
void testVerifySaplingParams()
|
||||||
{
|
{
|
||||||
using dragonx::rpc::Connection;
|
using dragonx::rpc::Connection;
|
||||||
@@ -6744,6 +6757,7 @@ int main()
|
|||||||
testDatadirLockGate();
|
testDatadirLockGate();
|
||||||
testPlatformEnsureDirectory();
|
testPlatformEnsureDirectory();
|
||||||
testVerifySaplingParams();
|
testVerifySaplingParams();
|
||||||
|
testConnectHasStalled();
|
||||||
testDaemonLifecycleExecution();
|
testDaemonLifecycleExecution();
|
||||||
testDaemonLifecycleAdapters();
|
testDaemonLifecycleAdapters();
|
||||||
testConsoleTextLayout();
|
testConsoleTextLayout();
|
||||||
|
|||||||
Reference in New Issue
Block a user