fix(lite): lock/unlock parity — shared attempt-lockout, immediate unlock state, no dead PIN, surfaced encrypt failure
Lite-parity audit, FIX C (gating/validation/presentation only — no encrypt/unlock/send backend call changed): - Lite unlock went straight to the backend with no lockout. New App::unlockLiteWallet() runs the SAME escalating, persisted attempt-lockout as the full node (restoreLockoutState/applyUnlockFailure), wired into the Settings unlock, the send-time prompt and the lock screen. A wrong-passphrase failure counts; an already-unlocked/not-encrypted result does not. - state_.locked is now cleared immediately on a successful unlock (the ~2s status poll lagged, so the UI stayed "locked" after a correct passphrase). - PIN quick-unlock controls are hidden in lite Settings — lite has no PIN vault, so they were dead. - A create/restore whose passphrase encryption FAILS now raises a one-shot visible error (takeEncryptionFailureNotice -> lite_encrypt_failed_unencrypted) instead of only a log line; the wallet is left open but unencrypted. Fixed the flag on BOTH the lifecycle and the direct create/restore encrypt paths. - A background sync save is skipped while an encrypted wallet is unlocked (the backend save re-locks it), resuming once locked. Compile- and unit-verified (testLiteWalletControllerLockUnlockParity covers the save-deferral + encrypt-failure notice, run twice for the flaky guard); the lock-screen lockout/PIN-hiding UX itself is runtime/GUI-only and not exercised here — worth a manual lite GUI pass. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
+7
-3
@@ -965,6 +965,9 @@ void App::update()
|
||||
lite_wallet_->pumpAsyncOpen();
|
||||
// Likewise finalize any completed async lifecycle request (Settings-page create/open/restore).
|
||||
lite_wallet_->pumpLifecycleResult();
|
||||
// A create/restore whose passphrase encryption failed left the wallet UNENCRYPTED (previously log-only): say so.
|
||||
if (lite_wallet_->takeEncryptionFailureNotice())
|
||||
ui::Notifications::instance().error(TR("lite_encrypt_failed_unencrypted"), 15.0f);
|
||||
// Deliver finished wallet ops (new address / import / encrypt / decrypt) to their UI callbacks.
|
||||
lite_wallet_->pumpWalletOps();
|
||||
// A server switch deferred behind an in-flight wallet op: retry it. This may replace lite_wallet_; the rest of
|
||||
@@ -3829,12 +3832,13 @@ void App::renderLiteUnlockPrompt()
|
||||
const float btnW = 130.0f * dp;
|
||||
bool doUnlock = ui::material::TactileButton(TR("lite_unlock_btn"), ImVec2(btnW, 0)) || entered;
|
||||
if (doUnlock) {
|
||||
const bool ok = lite_wallet_->unlockWallet(pass);
|
||||
std::string unlockErr;
|
||||
const bool ok = unlockLiteWallet(std::string(pass), &unlockErr);
|
||||
sodium_memzero(pass, sizeof(pass));
|
||||
if (ok) ui::Notifications::instance().success(TR("lite_unlock_ok"), 5.0f);
|
||||
else {
|
||||
wallet::liteLog(std::string("Unlock failed: ") + TR("lite_unlock_failed"));
|
||||
ui::Notifications::instance().error(TR("lite_unlock_failed"));
|
||||
wallet::liteLog(std::string("Unlock failed: ") + unlockErr);
|
||||
ui::Notifications::instance().error(unlockErr);
|
||||
}
|
||||
lite_unlock_prompt_ = false;
|
||||
ImGui::CloseCurrentPopup();
|
||||
|
||||
Reference in New Issue
Block a user