fix(wallet): stamp syncedHere only after identity verified + guard the startup wallet file (W1-3)

- W1-3 (Med): updateWalletIndexForActiveWallet stamped syncedHere in the markOpened block
  at bare connect (idHash still empty), letting a freshly-restored wallet skip its needed
  rescan. syncedHere is now stamped only once the wallet's identity is verified (idHash
  non-empty), so it takes effect at the post-address-refresh index update; lastOpenedEpoch
  still records at open.

- Startup guard (the W1-1 launch counterpart): App::init now exists()-checks the recorded
  active wallet before the daemon is configured. A non-default active wallet moved/deleted
  between sessions falls back to the default wallet.dat with a warning, instead of the
  daemon silently auto-creating an empty wallet under the missing name. Runs before the PIN
  vault init so the vault is scoped to the wallet actually opened.

Completes P1-B. Remaining P1: W3-3 (sweep opid persistence) deferred for careful
adversarially-reviewed work — re-tracking a stale opid could hang the migration if the op
poller doesn't time out; the existing balance/mined gates already prevent fund loss. See
docs/wallet-hardening.md.

Build-clean; ctest 1/1.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-08-02 15:18:09 -05:00
parent de1ae736de
commit f9ddab059e
3 changed files with 37 additions and 3 deletions

View File

@@ -1096,9 +1096,16 @@ void App::updateWalletIndexForActiveWallet(bool markOpened)
if (!ec) e.sizeBytesAtLastOpen = static_cast<long long>(sz);
}
// W1-3: record "synced here" only once the wallet's identity is actually verified (its addresses are
// known -> idHash non-empty). Stamping it on the bare connect (before any address readback) would let
// a freshly-restored wallet skip its needed rescan. It's idempotent, so the post-refresh update
// (updateWalletIndexForActiveWallet after addresses load) sets it once; lastOpenedEpoch is still
// recorded at open time here.
if (!idHash.empty()) {
e.syncedHere = true; // loaded + identity-verified in this datadir -> catch-up (no full rescan)
}
if (markOpened) {
e.lastOpenedEpoch = static_cast<long long>(std::time(nullptr));
e.syncedHere = true; // we've loaded it in this datadir -> catch-up (no full rescan) on switch
}
if (wallet_index_.upsert(e)) wallet_index_.save();