feat(lite): async + failover for Settings-page create/open/restore
The Settings page drove the controller's synchronous createWallet/openWallet/
restoreWallet, which blocks the UI thread on the (often flaky) lightwalletd and
gives up after the first server. Add a generic async lifecycle path that mirrors
the async-open failover but carries the full request (passphrase, restore seed/
birthday/account/overwrite):
- beginCreateWalletAsync / beginOpenWalletAsync / beginRestoreWalletAsync run
on a detached thread that builds its OWN local LiteWalletLifecycleService
from captured value copies + the shared bridge (never `this`, so it can
safely outlive the controller). Each request type's serverUrl override field
feeds the failover: try the preferred server, then every other usable
default; stop on the first ready wallet or a structural block; keep the
preferred server's error on total failure. The request's secrets are wiped
once the attempt finishes.
- pumpLifecycleResult() finalizes on the main thread (flip walletOpen, persist,
start sync) and caches the result for the UI; wired into App::update next to
pumpAsyncOpen(). beginAsyncLifecycle() now also yields to an in-flight
lifecycle request so the auto-open loop can't race it on the same bridge.
- settings_page kicks off the async op, disables the button while in flight,
and polls the cached result each frame for the status/summary.
Tests: testLiteWalletControllerAsyncLifecycleFailover covers async create (with
passphrase) and restore failing over preferred->fallback, plus all-servers-down.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -178,6 +178,25 @@ public:
|
||||
// and starts sync, or records the failure. Cheap no-op when nothing is pending. Call each tick.
|
||||
void pumpAsyncOpen();
|
||||
bool openInProgress() const { return openRunning_ && openRunning_->load(); }
|
||||
|
||||
// Asynchronous FULL lifecycle (create / open / restore WITH passphrase + restore params) with
|
||||
// the same server failover as beginOpenExisting(). Unlike beginCreateWallet() (plain new seed,
|
||||
// no passphrase), these carry the complete request so the Settings page can create with
|
||||
// encryption, open an encrypted wallet, or restore from a seed without freezing the UI on a
|
||||
// flaky server. Non-blocking; finalized by pumpLifecycleResult() on the main thread. The
|
||||
// request is taken by value and its secret fields are securely wiped once the attempt finishes.
|
||||
// Returns false if a wallet is open, a lifecycle/open is already in progress, or no usable
|
||||
// server exists (the request's secrets are still wiped on that path).
|
||||
bool beginCreateWalletAsync(LiteWalletCreateRequest request);
|
||||
bool beginOpenWalletAsync(LiteWalletOpenRequest request);
|
||||
bool beginRestoreWalletAsync(LiteWalletRestoreRequest request);
|
||||
bool lifecycleRequestInProgress() const { return lifecycleRunning_ && lifecycleRunning_->load(); }
|
||||
// Finalize a completed async lifecycle request on the main thread (flip walletOpen()/status,
|
||||
// persist, start sync) and cache the result for the UI. Cheap no-op when nothing is pending.
|
||||
// Call each tick alongside pumpAsyncOpen().
|
||||
void pumpLifecycleResult();
|
||||
// The most recent finalized async-lifecycle result (create/open/restore), for UI display.
|
||||
const LiteWalletLifecycleResult& lastLifecycleResult() const { return lastLifecycleResult_; }
|
||||
const std::string& lastOpenError() const { return lastOpenError_; }
|
||||
// True if the last failed open hit a server that was merely warming up (JSON-RPC -28 /
|
||||
// "Activating best chain"): the server is healthy and will be ready shortly, so the caller
|
||||
@@ -273,6 +292,8 @@ private:
|
||||
std::shared_ptr<LiteClientBridge> bridge_;
|
||||
std::string chainName_; // backend chain id (for walletExists); from connection settings
|
||||
LiteConnectionSettings connectionSettings_; // kept for the failover candidate-server list
|
||||
WalletCapabilities capabilities_; // for thread-local lifecycle services (async path)
|
||||
LiteWalletLifecycleOptions lifecycleOptions_; // ditto (allowBridgeCalls / rollout gate)
|
||||
LiteWalletLifecycleService lifecycle_;
|
||||
LiteWalletGateway gateway_;
|
||||
LiteSyncService sync_;
|
||||
@@ -311,6 +332,21 @@ private:
|
||||
std::string lastOpenError_; // main-thread only
|
||||
bool lastOpenWarming_ = false; // last failed open hit a warming-up (-28) server
|
||||
|
||||
// Asynchronous FULL lifecycle (create/open/restore with passphrase + restore params). Same
|
||||
// shared-lifetime discipline as the open thread: the detached thread builds its OWN local
|
||||
// LiteWalletLifecycleService from captured value copies + the shared bridge (never `this`), so
|
||||
// it can safely outlive the controller. pumpLifecycleResult() finalizes on the main thread.
|
||||
bool beginLifecycleRequestAsync(
|
||||
const char* verb,
|
||||
std::function<LiteWalletLifecycleResult(LiteWalletLifecycleService&, const std::string&)> exec,
|
||||
std::function<void()> wipeSecrets);
|
||||
std::thread lifecycleThread_;
|
||||
std::shared_ptr<std::atomic<bool>> lifecycleRunning_ = std::make_shared<std::atomic<bool>>(false);
|
||||
std::shared_ptr<std::mutex> lifecycleResultMutex_ = std::make_shared<std::mutex>();
|
||||
std::shared_ptr<std::optional<LiteWalletLifecycleResult>> lifecycleResult_ =
|
||||
std::make_shared<std::optional<LiteWalletLifecycleResult>>();
|
||||
LiteWalletLifecycleResult lastLifecycleResult_; // main-thread only; last finalized request
|
||||
|
||||
// Joinable background refresh worker (fast iterations: syncstatus, plus data once synced).
|
||||
std::thread worker_;
|
||||
std::atomic<bool> running_{false};
|
||||
|
||||
Reference in New Issue
Block a user