fix(lite): honor the backend sync result with retry and a failed state
A failed backend sync was silently swallowed: the reply was never inspected, so a sync that errored out left the wallet showing a "syncing" state that never completed and never reported a problem. - The sync and rescan replies are now parsed. On failure the partial progress is saved and the sync is retried with exponential back-off (5s doubling up to 60s, six attempts in all), then an explicit failed state is published: done, sync not complete, and the backend's error text carried through. - A failed rescan is retried as a plain sync, because a rescan has already cleared the wallet and re-running it would throw away the partial progress just saved. - A terminal error, such as a reorg deeper than the wallet can unwind, stops the retries immediately instead of waiting out the attempts. - Recovery is observed, not guessed: once a later refresh's server-bound reads all succeed, the failed state clears itself. Behaviour change: a sync failure now surfaces to the user as a failed state with a message, instead of a frozen "syncing", and the client retries on its own with back-off. Tests: a parser case for the success, failure, and terminal replies, and fake-backend controller cases for retrying after a failure, giving up after the attempt cap, a terminal reorg stopping at once, a failed rescan retrying as sync, cancel stopping the back-off, and the failed state self-healing on a good refresh. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -153,6 +153,19 @@ inline bool liteServerSwitchMayProceed(bool walletOpInFlight, double deferredSec
|
||||
return !walletOpInFlight || deferredSeconds >= kLiteServerSwitchMaxDeferSeconds;
|
||||
}
|
||||
|
||||
// Outcome of the background sync/rescan thread, shared with it by shared_ptr (the thread never touches the controller).
|
||||
struct LiteScanState {
|
||||
std::atomic<bool> cancel{false}; // controller destroyed / retired: stop retrying
|
||||
std::atomic<bool> failed{false}; // gave up (terminal error or too many attempts); cleared by recovery/rescan
|
||||
std::atomic<int> retryBaseMs{5000}; // first retry delay (tests shrink it)
|
||||
mutable std::mutex mutex;
|
||||
std::string error; // last failure text (guarded by mutex); empty = none
|
||||
|
||||
void setError(const std::string& e) { std::lock_guard<std::mutex> l(mutex); error = e; }
|
||||
std::string errorCopy() const { std::lock_guard<std::mutex> l(mutex); return error; }
|
||||
};
|
||||
constexpr int kLiteScanMaxAttempts = 6; // first try + 5 retries (the backend already retries 6x62 s inside each)
|
||||
|
||||
class LiteWalletController {
|
||||
public:
|
||||
LiteWalletController(WalletCapabilities capabilities,
|
||||
@@ -235,6 +248,13 @@ public:
|
||||
bool syncStarted() const { return syncStarted_; }
|
||||
bool syncComplete() const { return syncDone_ && syncDone_->load(); }
|
||||
|
||||
// True after the background sync/rescan gave up (see LiteScanState); scanError() is the backend's text (also set
|
||||
// while it is still retrying). Neither is persisted. Safe from any thread.
|
||||
bool scanFailed() const { return scan_->failed.load(); }
|
||||
std::string scanError() const { return scan_->errorCopy(); }
|
||||
// Tests only: first sync-retry delay in ms (default 5000); call before createWallet()/startSync().
|
||||
void setSyncRetryBaseMsForTest(int ms) { scan_->retryBaseMs.store(ms); }
|
||||
|
||||
// Launch the backend sync on a detached background thread (NON-blocking; the backend's
|
||||
// `sync` command runs a full, uninterruptible chain scan). Auto-invoked when a lifecycle
|
||||
// op produces a ready wallet; safe to call once.
|
||||
@@ -394,6 +414,7 @@ private:
|
||||
std::thread syncThread_;
|
||||
std::atomic<bool> syncLaunched_{false}; // startSync() guard (set on the main thread)
|
||||
std::shared_ptr<std::atomic<bool>> syncDone_ = std::make_shared<std::atomic<bool>>(false);
|
||||
std::shared_ptr<LiteScanState> scan_ = std::make_shared<LiteScanState>();
|
||||
|
||||
// Detached background broadcast (send/shield): proving can take seconds. Mirrors the sync
|
||||
// thread's shared-lifetime pattern (shared running flag + result slot captured by the
|
||||
|
||||
Reference in New Issue
Block a user