Setting a PIN always unlocked the wallet with a 5 s probe and then re-locked it, even when the wallet was already unlocked, so a PIN check silently re-locked an unlocked wallet. And once the daemon's absolute unlock window ran out the GUI still showed "unlocked", so the next send failed with nothing on screen explaining it. - The Set-PIN check now decides on the main thread: an unlocked wallet is verified with the time it has left (capped at seven days) and is not re-locked, so the GUI and the daemon keep agreeing; a locked wallet still uses the 5 s probe and re-locks. Either way the daemon's lock state is re-read afterwards, which self-heals any drift. - When the daemon's unlock window has expired but the GUI still says "unlocked", a bounded reconcile (once per expiry, 30 s back-off) re-reads getwalletinfo so the lock screen and the transaction-cache lock follow the daemon. Lite wallets, which have no daemon timer, are left alone. Behaviour change: with auto-lock disabled the daemon still re-locks at its window, and the GUI now lands on the lock screen instead of appearing unlocked until a send fails. Tests: the Set-PIN verify plan (unlocked keeps its remaining time and skips the re-lock, locked or near-expiry falls back to the 5 s probe and re-lock, long windows are capped) and the unlock-expiry predicate across encrypted, locked, no-deadline and not-yet-expired cases. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
51 lines
2.3 KiB
C++
51 lines
2.3 KiB
C++
#pragma once
|
|
#include <cstdint>
|
|
|
|
namespace dragonx {
|
|
namespace data {
|
|
|
|
enum class EncryptionPendingAction { None, Clear, Warn };
|
|
|
|
// What to do with the persisted "encryption requested in the wizard" flag, given ONE parsed getwalletinfo
|
|
// result. rpcOk must come from the parsed reply (a -28 warm-up failure or a throw is rpcOk=false => None);
|
|
// never derive it from WalletState (a 15 s timeout forces encrypted=false there).
|
|
inline EncryptionPendingAction decideEncryptionPending(bool flagSet, bool rpcOk, bool walletEncrypted,
|
|
bool deferredPending, bool encryptInProgress,
|
|
bool alreadyWarned)
|
|
{
|
|
if (!flagSet || !rpcOk) return EncryptionPendingAction::None;
|
|
if (walletEncrypted) return EncryptionPendingAction::Clear;
|
|
if (deferredPending || encryptInProgress || alreadyWarned) return EncryptionPendingAction::None;
|
|
return EncryptionPendingAction::Warn;
|
|
}
|
|
|
|
// ---- C90 ----
|
|
constexpr int kSetPinProbeSecs = 5; // the verification unlock used when the wallet is locked
|
|
|
|
struct SetPinVerifyPlan {
|
|
int timeoutSecs; // walletpassphrase timeout for the passphrase check
|
|
bool relockAfter; // call walletlock afterwards (only if the wallet was locked before)
|
|
};
|
|
|
|
// Set PIN verifies the passphrase with walletpassphrase. If the wallet is unlocked now, re-unlock with the time
|
|
// it has left (replaces the daemon's timer with the same deadline) and DO NOT relock; otherwise a 5 s probe + relock.
|
|
inline SetPinVerifyPlan planSetPinVerify(bool guiUnlocked, std::int64_t unlockedUntil, std::int64_t now)
|
|
{
|
|
const std::int64_t left = unlockedUntil - now;
|
|
if (guiUnlocked && left > kSetPinProbeSecs) {
|
|
const std::int64_t capped = left > 86400 * 7 ? 86400 * 7 : left;
|
|
return {static_cast<int>(capped), false};
|
|
}
|
|
return {kSetPinProbeSecs, true};
|
|
}
|
|
|
|
// The daemon's absolute unlock window has run out but the GUI still says "unlocked". Wall clock, because the
|
|
// deadline is an absolute unix time. unlockedUntil == 0 means "no deadline known" => never expired here.
|
|
inline bool unlockWindowExpired(bool encrypted, bool locked, std::int64_t unlockedUntil, std::int64_t now)
|
|
{
|
|
return encrypted && !locked && unlockedUntil > 0 && now >= unlockedUntil;
|
|
}
|
|
|
|
} // namespace data
|
|
} // namespace dragonx
|