feat(lite): Console tab with connection + open/create diagnostics
The lite variant had no visibility into why a wallet failed to open — just a "disconnected" spinner. Add a lite-only Console tab (full-node keeps its RPC console) that shows a live diagnostic log. - LiteDiagnostics: a small thread-safe, bounded ring buffer (header-only). The controller writes to it from its background threads: each failover server attempt and result, wallet open/create/restore outcomes, sync start, and blocked-open reasons. The App logs controller (re)builds with the preferred server. - lite_console_tab: a terminal-styled, read-only view of the log (newest at the bottom, error/success lines coloured) with Clear / Copy / Auto-scroll. Reachable even when the wallet is locked (it's diagnostics, no secrets). Registered as NavPage::LiteConsole, gated lite-only via WalletUiSurface::LiteConsole. A unit test drives an open-with-failover and asserts the log records the connection attempt and the successful open. Built clean for full-node, lite, and Windows cross-compile. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -4,6 +4,7 @@
|
||||
|
||||
#include "lite_wallet_controller.h"
|
||||
|
||||
#include "lite_diagnostics.h"
|
||||
#include "../data/wallet_state.h"
|
||||
|
||||
#include <algorithm>
|
||||
@@ -279,11 +280,16 @@ std::unique_ptr<LiteWalletController> LiteWalletController::createLinked(
|
||||
void LiteWalletController::onLifecycleResult(const LiteWalletLifecycleResult& result)
|
||||
{
|
||||
status_ = result.status;
|
||||
const std::string op = liteWalletLifecycleOperationName(result.operation);
|
||||
if (result.walletReady) {
|
||||
liteLog(op + ": wallet ready");
|
||||
walletOpen_ = true;
|
||||
if (persist_) persist_();
|
||||
startSync(); // begin background sync on the backend
|
||||
startWorker(); // begin periodic refresh -> WalletState (via takeRefreshedModel)
|
||||
} else if (result.attempted) {
|
||||
liteLog(op + " failed: " +
|
||||
(result.error.empty() ? result.status.message : result.error));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -312,6 +318,7 @@ bool LiteWalletController::beginOpenExisting()
|
||||
status_ = WalletBackendStatus{WalletBackendState::Error,
|
||||
reason.empty() ? "lite wallet is not available" : reason, {}, {}, 0.0};
|
||||
lastOpenError_ = status_.message;
|
||||
liteLog("Open blocked: " + lastOpenError_);
|
||||
return false;
|
||||
}
|
||||
auto servers = failoverServerUrls();
|
||||
@@ -319,6 +326,7 @@ bool LiteWalletController::beginOpenExisting()
|
||||
status_ = WalletBackendStatus{WalletBackendState::Error,
|
||||
"no usable lite servers are configured", {}, {}, 0.0};
|
||||
lastOpenError_ = status_.message;
|
||||
liteLog("Open blocked: " + lastOpenError_);
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -329,6 +337,7 @@ bool LiteWalletController::beginOpenExisting()
|
||||
}
|
||||
openRunning_->store(true);
|
||||
status_ = WalletBackendStatus{WalletBackendState::Connecting, "opening wallet", {}, {}, 0.0};
|
||||
liteLog("Opening wallet — trying " + std::to_string(servers.size()) + " server(s)");
|
||||
|
||||
// Capture only shared refs + value copies (never `this`) so the thread can safely outlive us.
|
||||
auto bridge = bridge_;
|
||||
@@ -340,14 +349,18 @@ bool LiteWalletController::beginOpenExisting()
|
||||
outcome.error = "could not reach any lite server";
|
||||
for (const auto& url : servers) {
|
||||
if (!bridge) break;
|
||||
liteLog(" connecting to " + url + " ...");
|
||||
// initialize_existing loads the wallet file but contacts the server to start the
|
||||
// light client; ok && non-empty value == ready (mirrors the lifecycle's success test).
|
||||
const auto call = bridge->initializeExisting(/*dangerous=*/false, url);
|
||||
if (call.ok && !call.value.empty()) {
|
||||
liteLog(" " + url + ": connected");
|
||||
outcome.ok = true;
|
||||
outcome.serverUrl = url;
|
||||
break;
|
||||
}
|
||||
const std::string why = call.error.empty() ? "unreachable" : call.error;
|
||||
liteLog(" " + url + ": " + why);
|
||||
if (!call.error.empty()) outcome.error = call.error;
|
||||
}
|
||||
{
|
||||
@@ -374,12 +387,14 @@ void LiteWalletController::pumpAsyncOpen()
|
||||
walletOpen_ = true;
|
||||
lastOpenError_.clear();
|
||||
status_ = WalletBackendStatus{WalletBackendState::Ready, "wallet open", {}, {}, 0.0};
|
||||
liteLog("Wallet opened via " + outcome.serverUrl);
|
||||
if (persist_) persist_();
|
||||
startSync(); // begin background sync on the backend
|
||||
startWorker(); // begin periodic refresh -> WalletState
|
||||
} else {
|
||||
lastOpenError_ = outcome.error;
|
||||
status_ = WalletBackendStatus{WalletBackendState::Error, outcome.error, {}, {}, 0.0};
|
||||
liteLog("Open failed: " + outcome.error);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -387,6 +402,7 @@ void LiteWalletController::startSync()
|
||||
{
|
||||
if (syncLaunched_.exchange(true)) return;
|
||||
syncStarted_ = true;
|
||||
liteLog("Background sync started");
|
||||
// The backend `sync` command is a blocking, uninterruptible full chain scan, so run it on
|
||||
// a detached thread. Capture shared refs (not the controller) so it is safe to outlive us.
|
||||
auto bridge = bridge_;
|
||||
|
||||
Reference in New Issue
Block a user