feat(wallet): per-wallet data isolation foundation (P1)
First phase of multi-wallet support: keep each wallet's data separate so loading
a different wallet no longer shows the previous one's data, and lay the metadata
groundwork for the wallet-files list.
- Address book scoped per wallet: AddressBookEntry gains a "scope" ("global" or a
wallet-identity hash); the Contacts tab shows global + current-wallet contacts
(App::activeWalletIdentityHash) with a "show in every wallet" toggle + globe
badge. Legacy entries migrate to "global". Scope-aware de-dup allows the same
address across different wallets.
- Wallet metadata index (data/wallet_index -> wallets.json): file-keyed cache of
balance / address count / identity / size / last-opened / synced-here, since
those can't be read off a wallet.dat without loading it. Populated on connect +
address refresh (change-detecting upsert). Plus an active_wallet_file setting
for the -wallet=<name> switch coming in P2.
Unit-tested (testAddressBookScope, testWalletIndex): migration, visibility filter,
scope-aware de-dup, upsert change-detection, save/reload round-trip.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -499,7 +499,11 @@ void App::onConnected()
|
||||
daemon_start_error_shown_ = false;
|
||||
daemon_last_seen_crashes_ = 0; // (onConnected resets the daemon's crash count too)
|
||||
connection_status_ = TR("connected");
|
||||
|
||||
|
||||
// Stamp the active wallet as opened in the index (last-opened + size + synced-here). Balance +
|
||||
// address count fill in on the first address refresh (addresses aren't loaded yet here).
|
||||
updateWalletIndexForActiveWallet(/*markOpened=*/true);
|
||||
|
||||
// Reset crash counter on successful connection
|
||||
if (daemon_controller_) {
|
||||
daemon_controller_->resetCrashCount();
|
||||
@@ -959,6 +963,50 @@ std::string App::transactionHistoryCacheWalletIdentity() const
|
||||
shieldedAddresses, transparentAddresses);
|
||||
}
|
||||
|
||||
std::string App::activeWalletIdentityHash() const
|
||||
{
|
||||
const std::string identity = transactionHistoryCacheWalletIdentity();
|
||||
if (identity.empty()) return std::string();
|
||||
return data::TransactionHistoryCache::walletIdentityHash(identity);
|
||||
}
|
||||
|
||||
void App::updateWalletIndexForActiveWallet(bool markOpened)
|
||||
{
|
||||
if (!settings_) return;
|
||||
const std::string file = settings_->getActiveWalletFile();
|
||||
if (file.empty()) return;
|
||||
|
||||
// Start from the existing entry so we preserve fields we're not updating this call.
|
||||
data::WalletIndexEntry e;
|
||||
if (const auto* existing = wallet_index_.find(file)) e = *existing;
|
||||
e.fileName = file;
|
||||
if (e.displayName.empty()) e.displayName = file;
|
||||
|
||||
// Balance + address count are only meaningful once the wallet's addresses are known (identity
|
||||
// non-empty); at connect time they're still 0, so don't cache bogus zeros then.
|
||||
const std::string idHash = state_.connected ? activeWalletIdentityHash() : std::string();
|
||||
if (!idHash.empty()) {
|
||||
e.walletIdentityHash = idHash;
|
||||
e.cachedBalance = state_.totalBalance;
|
||||
e.cachedAddressCount = static_cast<long long>(state_.z_addresses.size() + state_.t_addresses.size());
|
||||
}
|
||||
|
||||
// File size is always readable from disk.
|
||||
std::error_code ec;
|
||||
const std::string path = util::Platform::getDragonXDataDir() + "/" + file;
|
||||
if (std::filesystem::exists(path, ec)) {
|
||||
auto sz = std::filesystem::file_size(path, ec);
|
||||
if (!ec) e.sizeBytesAtLastOpen = static_cast<long long>(sz);
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
void App::wipePendingTransactionHistoryCachePassphrase()
|
||||
{
|
||||
if (!pending_transaction_history_cache_passphrase_.empty()) {
|
||||
@@ -1337,6 +1385,9 @@ void App::refreshAddressData()
|
||||
if (state_.transactions.empty()) loadTransactionHistoryCacheIfAvailable();
|
||||
else storeTransactionHistoryCacheIfAvailable();
|
||||
maybeFinishTransactionSendProgress();
|
||||
// Addresses (hence identity + count) are now known — refresh the wallet index's cached
|
||||
// metadata for the active wallet. Throttled: upsert() only writes on a real change.
|
||||
updateWalletIndexForActiveWallet(/*markOpened=*/false);
|
||||
};
|
||||
}, 3);
|
||||
if (!enqueued.enqueued) return;
|
||||
|
||||
Reference in New Issue
Block a user