diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp index a7492b305..f24bb1676 100644 --- a/src/wallet/wallet.cpp +++ b/src/wallet/wallet.cpp @@ -822,51 +822,11 @@ WalletUAGenerationResult CWallet::GenerateUnifiedAddress( } } -// NOTE: There is an important relationship between `LoadUnifiedFullViewingKey` -// and `LoadUnifiedAddressMetadata`. In all cases, by the end of the -// wallet-loading process, `mapUfvkAddressMetadata` needs to be fully populated -// for each unified full viewing key. However, we cannot guarantee in what -// order UFVKs and unified address and account metadata are loaded from disk. -// Therefore: -// * When we load a unified full viewing key from disk, we repopulate -// the cache of generated addresses by regenerating the address for each -// (diversifierIndex, {receiverType...}) pair in `mapUfvkAddressMetadata` -// that we have loaded so far. -// * When we load a unified address metadata record from disk: -// - if we have not yet loaded the unified full viewing key, simply add -// an entry `mapUfvkAddressMetadata` so that we can restore the address -// once the UFVK has been loaded. -// - if we have already loaded the unified full viewing key from disk, -// regenerate the address from its metadata and add it to the keystore -// -// While this is somewhat complicated, it has the benefit of making each -// piece of unified full viewing key and address metadata write-once in -// the wallet database; we never need to update ufvk or address metadata -// once written. bool CWallet::LoadUnifiedFullViewingKey(const libzcash::UnifiedFullViewingKey &key) { - auto zufvk = ZcashdUnifiedFullViewingKey::FromUnifiedFullViewingKey(Params(), key); - auto metadata = mapUfvkAddressMetadata.find(zufvk.GetKeyID()); - if (metadata != mapUfvkAddressMetadata.end()) { - // restore unified addresses that have been previously generated to the - // keystore - for (const auto &[j, receiverTypes] : metadata->second.GetKnownReceiverSetsByDiversifierIndex()) { - bool restored = std::visit(match { - [&](const UnifiedAddressGenerationError& err) { - return false; - }, - [&](const std::pair& addr) { - return CCryptoKeyStore::AddTransparentReceiverForUnifiedAddress( - zufvk.GetKeyID(), addr.second, addr.first); - } - }, zufvk.Address(j, receiverTypes)); - - // failure to restore the generated address is an error - if (!restored) return false; - } - } - - return CCryptoKeyStore::AddUnifiedFullViewingKey(zufvk); + return CCryptoKeyStore::AddUnifiedFullViewingKey( + ZcashdUnifiedFullViewingKey::FromUnifiedFullViewingKey(Params(), key) + ); } bool CWallet::LoadUnifiedAccountMetadata(const ZcashdUnifiedAccountMetadata &skmeta) @@ -880,23 +840,47 @@ bool CWallet::LoadUnifiedAccountMetadata(const ZcashdUnifiedAccountMetadata &skm bool CWallet::LoadUnifiedAddressMetadata(const ZcashdUnifiedAddressMetadata &addrmeta) { AssertLockHeld(cs_wallet); - if (!mapUfvkAddressMetadata[addrmeta.GetKeyID()].SetReceivers( + + return mapUfvkAddressMetadata[addrmeta.GetKeyID()].SetReceivers( addrmeta.GetDiversifierIndex(), - addrmeta.GetReceiverTypes())) { - return false; - } + addrmeta.GetReceiverTypes()); +} - auto ufvk = GetUnifiedFullViewingKey(addrmeta.GetKeyID()); - if (ufvk.has_value()) { - // Regenerate the unified address and add it to the keystore. - auto j = addrmeta.GetDiversifierIndex(); - auto addr = ufvk.value().Address(j, addrmeta.GetReceiverTypes()); - auto addrPtr = std::get_if>(&addr); +bool CWallet::LoadUnifiedCaches() +{ + AssertLockHeld(cs_wallet); - if (addrPtr == nullptr) { - return false; + for (auto account = mapUnifiedAccountKeys.begin(); account != mapUnifiedAccountKeys.end(); ++account) { + auto ufvkId = account->second; + auto ufvk = GetUnifiedFullViewingKey(ufvkId); + if (ufvk.has_value()) { + auto metadata = mapUfvkAddressMetadata.find(ufvkId); + if (metadata != mapUfvkAddressMetadata.end()) { + // restore unified addresses that have been previously generated to the + // keystore + for (const auto &[j, receiverTypes] : metadata->second.GetKnownReceiverSetsByDiversifierIndex()) { + bool restored = std::visit(match { + [&](const UnifiedAddressGenerationError& err) { + return false; + }, + [&](const std::pair& addr) { + return CCryptoKeyStore::AddTransparentReceiverForUnifiedAddress( + ufvkId, addr.second, addr.first); + } + }, ufvk.value().Address(j, receiverTypes)); + + // failure to restore the generated address is an error + if (!restored) return false; + } + } else { + // Loaded an account, but didn't initialize + // `mapUfvkAddressMetadata` for the corresponding viewing key. + return false; + } } else { - return CCryptoKeyStore::AddTransparentReceiverForUnifiedAddress(addrmeta.GetKeyID(), j, addrPtr->first); + // Loaded an account, but the corresponding viewing key could not be + // found. + return false; } } diff --git a/src/wallet/wallet.h b/src/wallet/wallet.h index 4db777bd6..c4e656c2f 100644 --- a/src/wallet/wallet.h +++ b/src/wallet/wallet.h @@ -1534,6 +1534,14 @@ public: bool LoadUnifiedAccountMetadata(const ZcashdUnifiedAccountMetadata &skmeta); bool LoadUnifiedAddressMetadata(const ZcashdUnifiedAddressMetadata &addrmeta); + //! Reconstructs (in memory) caches and mappings for unified accounts, + //! addresses and keying material. This should be called once, after the + //! remainder of the on-disk wallet data has been loaded. + //! + //! Returns true if and only if there were no detected inconsistencies or + //! failures in reconstructing the cache. + bool LoadUnifiedCaches(); + std::optional FindUnifiedFullViewingKey(const libzcash::UnifiedAddress& addr) const; std::optional GetUnifiedAccountId(const libzcash::UFVKId& ufvkId) const; diff --git a/src/wallet/walletdb.cpp b/src/wallet/walletdb.cpp index e90e72e3e..a907fa2c4 100644 --- a/src/wallet/walletdb.cpp +++ b/src/wallet/walletdb.cpp @@ -963,6 +963,14 @@ DBErrors CWalletDB::LoadWallet(CWallet* pwallet) } pcursor->close(); + // Load unified address/account/key caches based on what was loaded + if (!pwallet->LoadUnifiedCaches()) { + // We can be more permissive of certain kinds of failures during + // loading; for now we'll interpret failure to reconstruct the + // caches to be "as bad" as losing keys. + result = DB_CORRUPT; + } + // Run the Orchard batch validator; if it fails, treat it like a bad transaction record. if (!wss.orchardAuth.Validate()) { fNoncriticalErrors = true; @@ -1283,6 +1291,13 @@ bool CWalletDB::Recover(CDBEnv& dbenv, const std::string& filename, bool fOnlyKe ptxn->commit(0); pdbCopy->close(0); + // Try to load the unified caches, uncovering inconsistencies in wallet + // records like missing viewing key records despite existing account + // records. + if (!dummyWallet.LoadUnifiedCaches()) { + LogPrintf("WARNING: unified caches could not be reconstructed; salvaged wallet file may have omissions"); + } + return fSuccess; }