Contacts were scoped by activeWalletIdentityHash() — a hash of the wallet's
ENTIRE address set. Creating a new receive address grows the set, changing the
hash, so every contact stamped with the old hash falls out of the scope filter
(contacts_tab.cpp:915) while still being counted — the "3 saved, 1 showing"
symptom, where only the one set to global (which bypasses the scope) survives.
It also hid scoped contacts on every startup before the daemon connected (hash
empty until addresses load).
Introduce a stable per-wallet scope id: WalletIndexEntry.scopeId ("w:"+random
hex), generated once and persisted in the wallet index (keyed by wallet file),
never recomputed from the mutable address set — so creating addresses, locking,
or disconnecting never changes it. App::activeWalletScopeId() establishes it on
first use. Contacts now scope + filter on this instead of the drifting hash. The
tx-history-cache identity (the hash's real purpose) is untouched.
Recovery for already-orphaned contacts:
- AddressBook::reattachLegacyScopes() re-attaches non-global, non-"w:" contacts
to the active wallet's stable id; run once when there's a single known wallet
(unambiguous attribution). Idempotent.
- The scope filter fails OPEN for legacy scopes (multi-wallet case where recovery
can't attribute them) so no contact is ever hidden; stable "w:" scopes still
match strictly.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
71 lines
2.8 KiB
C++
71 lines
2.8 KiB
C++
// DragonX Wallet - ImGui Edition
|
|
// Copyright 2024-2026 The Hush Developers
|
|
// Released under the GPLv3
|
|
|
|
#pragma once
|
|
|
|
#include <string>
|
|
#include <vector>
|
|
|
|
namespace dragonx {
|
|
namespace data {
|
|
|
|
/**
|
|
* @brief Cached, file-keyed metadata for one wallet file.
|
|
*
|
|
* A wallet.dat on disk only reveals its size + mtime; balance and address count require the daemon
|
|
* to have loaded it. So those are cached here after each load and shown in the wallet-files list
|
|
* (P2) even when the wallet isn't the active one. walletIdentityHash bridges file -> per-wallet data
|
|
* (address book, tx history) so the right caches can be selected before/after a switch.
|
|
*/
|
|
struct WalletIndexEntry {
|
|
std::string fileName; // plain wallet filename in the datadir (e.g. "wallet.dat")
|
|
std::string displayName; // user-facing name (defaults to fileName)
|
|
std::string walletIdentityHash; // address-derived identity of the last load; "" = unknown
|
|
std::string scopeId; // stable per-wallet id ("w:"+hex) for scoping contacts etc.;
|
|
// generated once, never recomputed from the (mutable) address set
|
|
double cachedBalance = -1.0; // last-known total balance; < 0 = unknown (never opened)
|
|
long long cachedAddressCount = -1;// < 0 = unknown
|
|
long long lastOpenedEpoch = 0; // unix seconds of last open; 0 = never opened
|
|
long long sizeBytesAtLastOpen = 0;
|
|
bool syncedHere = false; // loaded in this datadir before -> catch-up on switch (no full rescan)
|
|
};
|
|
|
|
/**
|
|
* @brief Persistent wallet-files metadata index (wallets.json, in the config dir).
|
|
*
|
|
* Keyed by wallet file name. Populated after each load (P1b) and read by the wallet-files list (P2).
|
|
* Also remembers extra folders the user added to scan for wallet files. Not encrypted — it holds
|
|
* only file names + coarse metadata (no keys/addresses).
|
|
*/
|
|
class WalletIndex {
|
|
public:
|
|
bool load();
|
|
bool save();
|
|
static std::string getDefaultPath();
|
|
|
|
const std::vector<WalletIndexEntry>& entries() const { return entries_; }
|
|
const std::vector<std::string>& extraFolders() const { return extra_folders_; }
|
|
|
|
/**
|
|
* @brief Insert or update the entry for e.fileName.
|
|
* @return true if a new entry was added or any field changed (so the caller can skip save()).
|
|
*/
|
|
bool upsert(const WalletIndexEntry& e);
|
|
|
|
/** @brief Find the entry for a wallet file, or nullptr. */
|
|
const WalletIndexEntry* find(const std::string& fileName) const;
|
|
|
|
/** @brief Add/remove an extra scan folder. Returns true if the set changed. */
|
|
bool addExtraFolder(const std::string& dir);
|
|
bool removeExtraFolder(const std::string& dir);
|
|
|
|
private:
|
|
std::vector<WalletIndexEntry> entries_;
|
|
std::vector<std::string> extra_folders_;
|
|
std::string file_path_;
|
|
};
|
|
|
|
} // namespace data
|
|
} // namespace dragonx
|