diff options
Diffstat (limited to 'src/wallet/wallet.cpp')
-rw-r--r-- | src/wallet/wallet.cpp | 409 |
1 files changed, 256 insertions, 153 deletions
diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp index 4e0fea156c..11203ca3a4 100644 --- a/src/wallet/wallet.cpp +++ b/src/wallet/wallet.cpp @@ -555,7 +555,7 @@ void CWallet::UpgradeDescriptorCache() SetWalletFlag(WALLET_FLAG_LAST_HARDENED_XPUB_CACHED); } -bool CWallet::Unlock(const SecureString& strWalletPassphrase, bool accept_no_keys) +bool CWallet::Unlock(const SecureString& strWalletPassphrase) { CCrypter crypter; CKeyingMaterial _vMasterKey; @@ -568,7 +568,7 @@ bool CWallet::Unlock(const SecureString& strWalletPassphrase, bool accept_no_key return false; if (!crypter.Decrypt(pMasterKey.second.vchCryptedKey, _vMasterKey)) continue; // try another master key - if (Unlock(_vMasterKey, accept_no_keys)) { + if (Unlock(_vMasterKey)) { // Now that we've unlocked, upgrade the key metadata UpgradeKeyMetadata(); // Now that we've unlocked, upgrade the descriptor cache @@ -1080,6 +1080,9 @@ CWalletTx* CWallet::AddToWallet(CTransactionRef tx, const TxState& state, const wtx.m_it_wtxOrdered = wtxOrdered.insert(std::make_pair(wtx.nOrderPos, &wtx)); wtx.nTimeSmart = ComputeTimeSmart(wtx, rescanning_old_block); AddToSpends(wtx, &batch); + + // Update birth time when tx time is older than it. + MaybeUpdateBirthTime(wtx.GetTxTime()); } if (!fInsertedNew) @@ -1199,6 +1202,10 @@ bool CWallet::LoadToWallet(const uint256& hash, const UpdateWalletTxFn& fill_wtx } } } + + // Update birth time when tx time is older than it. + MaybeUpdateBirthTime(wtx.GetTxTime()); + return true; } @@ -1747,11 +1754,11 @@ bool CWallet::ImportScriptPubKeys(const std::string& label, const std::set<CScri return true; } -void CWallet::FirstKeyTimeChanged(const ScriptPubKeyMan* spkm, int64_t new_birth_time) +void CWallet::MaybeUpdateBirthTime(int64_t time) { int64_t birthtime = m_birth_time.load(); - if (new_birth_time < birthtime) { - m_birth_time = new_birth_time; + if (time < birthtime) { + m_birth_time = time; } } @@ -2292,6 +2299,8 @@ DBErrors CWallet::LoadWallet() { LOCK(cs_wallet); + Assert(m_spk_managers.empty()); + Assert(m_wallet_flags == 0); DBErrors nLoadWalletRet = WalletBatch(GetDatabase()).LoadWallet(this); if (nLoadWalletRet == DBErrors::NEED_REWRITE) { @@ -2366,22 +2375,32 @@ bool CWallet::SetAddressBookWithDB(WalletBatch& batch, const CTxDestination& add { LOCK(cs_wallet); std::map<CTxDestination, CAddressBookData>::iterator mi = m_address_book.find(address); - fUpdated = (mi != m_address_book.end() && !mi->second.IsChange()); - m_address_book[address].SetLabel(strName); + fUpdated = mi != m_address_book.end() && !mi->second.IsChange(); + + CAddressBookData& record = mi != m_address_book.end() ? mi->second : m_address_book[address]; + record.SetLabel(strName); is_mine = IsMine(address) != ISMINE_NO; if (new_purpose) { /* update purpose only if requested */ - purpose = m_address_book[address].purpose = new_purpose; - } else { - purpose = m_address_book[address].purpose; + record.purpose = new_purpose; } + purpose = record.purpose; } + + const std::string& encoded_dest = EncodeDestination(address); + if (new_purpose && !batch.WritePurpose(encoded_dest, PurposeToString(*new_purpose))) { + WalletLogPrintf("Error: fail to write address book 'purpose' entry\n"); + return false; + } + if (!batch.WriteName(encoded_dest, strName)) { + WalletLogPrintf("Error: fail to write address book 'name' entry\n"); + return false; + } + // In very old wallets, address purpose may not be recorded so we derive it from IsMine NotifyAddressBookChanged(address, strName, is_mine, purpose.value_or(is_mine ? AddressPurpose::RECEIVE : AddressPurpose::SEND), (fUpdated ? CT_UPDATED : CT_NEW)); - if (new_purpose && !batch.WritePurpose(EncodeDestination(address), PurposeToString(*new_purpose))) - return false; - return batch.WriteName(EncodeDestination(address), strName); + return true; } bool CWallet::SetAddressBook(const CTxDestination& address, const std::string& strName, const std::optional<AddressPurpose>& purpose) @@ -2393,6 +2412,12 @@ bool CWallet::SetAddressBook(const CTxDestination& address, const std::string& s bool CWallet::DelAddressBook(const CTxDestination& address) { WalletBatch batch(GetDatabase()); + return DelAddressBookWithDB(batch, address); +} + +bool CWallet::DelAddressBookWithDB(WalletBatch& batch, const CTxDestination& address) +{ + const std::string& dest = EncodeDestination(address); { LOCK(cs_wallet); // If we want to delete receiving addresses, we should avoid calling EraseAddressData because it will delete the previously_spent value. Could instead just erase the label so it becomes a change address, and keep the data. @@ -2403,14 +2428,30 @@ bool CWallet::DelAddressBook(const CTxDestination& address) return false; } // Delete data rows associated with this address - batch.EraseAddressData(address); + if (!batch.EraseAddressData(address)) { + WalletLogPrintf("Error: cannot erase address book entry data\n"); + return false; + } + + // Delete purpose entry + if (!batch.ErasePurpose(dest)) { + WalletLogPrintf("Error: cannot erase address book entry purpose\n"); + return false; + } + + // Delete name entry + if (!batch.EraseName(dest)) { + WalletLogPrintf("Error: cannot erase address book entry name\n"); + return false; + } + + // finally, remove it from the map m_address_book.erase(address); } + // All good, signal changes NotifyAddressBookChanged(address, "", /*is_mine=*/false, AddressPurpose::SEND, CT_DELETED); - - batch.ErasePurpose(EncodeDestination(address)); - return batch.EraseName(EncodeDestination(address)); + return true; } size_t CWallet::KeypoolCountExternalKeys() const @@ -3103,9 +3144,10 @@ std::shared_ptr<CWallet> CWallet::Create(WalletContext& context, const std::stri int64_t time = spk_man->GetTimeFirstKey(); if (!time_first_key || time < *time_first_key) time_first_key = time; } - if (time_first_key) walletInstance->m_birth_time = *time_first_key; + if (time_first_key) walletInstance->MaybeUpdateBirthTime(*time_first_key); if (chain && !AttachChain(walletInstance, *chain, rescan_required, error, warnings)) { + walletInstance->m_chain_notifications_handler.reset(); // Reset this pointer so that the wallet will actually be unloaded return nullptr; } @@ -3380,12 +3422,12 @@ bool CWallet::Lock() return true; } -bool CWallet::Unlock(const CKeyingMaterial& vMasterKeyIn, bool accept_no_keys) +bool CWallet::Unlock(const CKeyingMaterial& vMasterKeyIn) { { LOCK(cs_wallet); for (const auto& spk_man_pair : m_spk_managers) { - if (!spk_man_pair.second->CheckDecryptionKey(vMasterKeyIn, accept_no_keys)) { + if (!spk_man_pair.second->CheckDecryptionKey(vMasterKeyIn)) { return false; } } @@ -3496,10 +3538,12 @@ LegacyScriptPubKeyMan* CWallet::GetOrCreateLegacyScriptPubKeyMan() void CWallet::AddScriptPubKeyMan(const uint256& id, std::unique_ptr<ScriptPubKeyMan> spkm_man) { + // Add spkm_man to m_spk_managers before calling any method + // that might access it. const auto& spkm = m_spk_managers[id] = std::move(spkm_man); // Update birth time if needed - FirstKeyTimeChanged(spkm.get(), spkm->GetTimeFirstKey()); + MaybeUpdateBirthTime(spkm->GetTimeFirstKey()); } void CWallet::SetupLegacyScriptPubKeyMan() @@ -3517,9 +3561,10 @@ void CWallet::SetupLegacyScriptPubKeyMan() AddScriptPubKeyMan(id, std::move(spk_manager)); } -const CKeyingMaterial& CWallet::GetEncryptionKey() const +bool CWallet::WithEncryptionKey(std::function<bool (const CKeyingMaterial&)> cb) const { - return vMasterKey; + LOCK(cs_wallet); + return cb(vMasterKey); } bool CWallet::HasEncryptionKeys() const @@ -3532,7 +3577,7 @@ void CWallet::ConnectScriptPubKeyManNotifiers() for (const auto& spk_man : GetActiveScriptPubKeyMans()) { spk_man->NotifyWatchonlyChanged.connect(NotifyWatchonlyChanged); spk_man->NotifyCanGetAddressesChanged.connect(NotifyCanGetAddressesChanged); - spk_man->NotifyFirstKeyTimeChanged.connect(std::bind(&CWallet::FirstKeyTimeChanged, this, std::placeholders::_1, std::placeholders::_2)); + spk_man->NotifyFirstKeyTimeChanged.connect(std::bind(&CWallet::MaybeUpdateBirthTime, this, std::placeholders::_2)); } } @@ -3551,6 +3596,10 @@ void CWallet::SetupDescriptorScriptPubKeyMans(const CExtKey& master_key) { AssertLockHeld(cs_wallet); + // Create single batch txn + WalletBatch batch(GetDatabase()); + if (!batch.TxnBegin()) throw std::runtime_error("Error: cannot create db transaction for descriptors setup"); + for (bool internal : {false, true}) { for (OutputType t : OUTPUT_TYPES) { auto spk_manager = std::unique_ptr<DescriptorScriptPubKeyMan>(new DescriptorScriptPubKeyMan(*this, m_keypool_size)); @@ -3558,16 +3607,19 @@ void CWallet::SetupDescriptorScriptPubKeyMans(const CExtKey& master_key) if (IsLocked()) { throw std::runtime_error(std::string(__func__) + ": Wallet is locked, cannot setup new descriptors"); } - if (!spk_manager->CheckDecryptionKey(vMasterKey) && !spk_manager->Encrypt(vMasterKey, nullptr)) { + if (!spk_manager->CheckDecryptionKey(vMasterKey) && !spk_manager->Encrypt(vMasterKey, &batch)) { throw std::runtime_error(std::string(__func__) + ": Could not encrypt new descriptors"); } } - spk_manager->SetupDescriptorGeneration(master_key, t, internal); + spk_manager->SetupDescriptorGeneration(batch, master_key, t, internal); uint256 id = spk_manager->GetID(); AddScriptPubKeyMan(id, std::move(spk_manager)); - AddActiveScriptPubKeyMan(id, t, internal); + AddActiveScriptPubKeyManWithDb(batch, id, t, internal); } } + + // Ensure information is committed to disk + if (!batch.TxnCommit()) throw std::runtime_error("Error: cannot commit db transaction for descriptors setup"); } void CWallet::SetupDescriptorScriptPubKeyMans() @@ -3576,8 +3628,7 @@ void CWallet::SetupDescriptorScriptPubKeyMans() if (!IsWalletFlagSet(WALLET_FLAG_EXTERNAL_SIGNER)) { // Make a seed - CKey seed_key; - seed_key.MakeNewKey(true); + CKey seed_key = GenerateRandomKey(); CPubKey seed = seed_key.GetPubKey(); assert(seed_key.VerifyPubKey(seed)); @@ -3594,6 +3645,10 @@ void CWallet::SetupDescriptorScriptPubKeyMans() UniValue signer_res = signer.GetDescriptors(account); if (!signer_res.isObject()) throw std::runtime_error(std::string(__func__) + ": Unexpected result"); + + WalletBatch batch(GetDatabase()); + if (!batch.TxnBegin()) throw std::runtime_error("Error: cannot create db transaction for descriptors import"); + for (bool internal : {false, true}) { const UniValue& descriptor_vals = signer_res.find_value(internal ? "internal" : "receive"); if (!descriptor_vals.isArray()) throw std::runtime_error(std::string(__func__) + ": Unexpected result"); @@ -3610,18 +3665,26 @@ void CWallet::SetupDescriptorScriptPubKeyMans() } OutputType t = *desc->GetOutputType(); auto spk_manager = std::unique_ptr<ExternalSignerScriptPubKeyMan>(new ExternalSignerScriptPubKeyMan(*this, m_keypool_size)); - spk_manager->SetupDescriptor(std::move(desc)); + spk_manager->SetupDescriptor(batch, std::move(desc)); uint256 id = spk_manager->GetID(); AddScriptPubKeyMan(id, std::move(spk_manager)); - AddActiveScriptPubKeyMan(id, t, internal); + AddActiveScriptPubKeyManWithDb(batch, id, t, internal); } } + + // Ensure imported descriptors are committed to disk + if (!batch.TxnCommit()) throw std::runtime_error("Error: cannot commit db transaction for descriptors import"); } } void CWallet::AddActiveScriptPubKeyMan(uint256 id, OutputType type, bool internal) { WalletBatch batch(GetDatabase()); + return AddActiveScriptPubKeyManWithDb(batch, id, type, internal); +} + +void CWallet::AddActiveScriptPubKeyManWithDb(WalletBatch& batch, uint256 id, OutputType type, bool internal) +{ if (!batch.WriteActiveScriptPubKeyMan(static_cast<uint8_t>(type), id, internal)) { throw std::runtime_error(std::string(__func__) + ": writing active ScriptPubKeyMan id failed"); } @@ -3849,7 +3912,11 @@ std::optional<MigrationData> CWallet::GetDescriptorsForLegacy(bilingual_str& err AssertLockHeld(cs_wallet); LegacyScriptPubKeyMan* legacy_spkm = GetLegacyScriptPubKeyMan(); - assert(legacy_spkm); + if (!Assume(legacy_spkm)) { + // This shouldn't happen + error = Untranslated(STR_INTERNAL_BUG("Error: Legacy wallet data missing")); + return std::nullopt; + } std::optional<MigrationData> res = legacy_spkm->MigrateToDescriptor(); if (res == std::nullopt) { @@ -3864,8 +3931,9 @@ bool CWallet::ApplyMigrationData(MigrationData& data, bilingual_str& error) AssertLockHeld(cs_wallet); LegacyScriptPubKeyMan* legacy_spkm = GetLegacyScriptPubKeyMan(); - if (!legacy_spkm) { - error = _("Error: This wallet is already a descriptor wallet"); + if (!Assume(legacy_spkm)) { + // This shouldn't happen + error = Untranslated(STR_INTERNAL_BUG("Error: Legacy wallet data missing")); return false; } @@ -3907,6 +3975,13 @@ bool CWallet::ApplyMigrationData(MigrationData& data, bilingual_str& error) } } + // Get best block locator so that we can copy it to the watchonly and solvables + CBlockLocator best_block_locator; + if (!WalletBatch(GetDatabase()).ReadBestBlock(best_block_locator)) { + error = _("Error: Unable to read wallet's best block locator record"); + return false; + } + // Check if the transactions in the wallet are still ours. Either they belong here, or they belong in the watchonly wallet. // We need to go through these in the tx insertion order so that lookups to spends works. std::vector<uint256> txids_to_delete; @@ -3917,32 +3992,47 @@ bool CWallet::ApplyMigrationData(MigrationData& data, bilingual_str& error) LOCK(data.watchonly_wallet->cs_wallet); data.watchonly_wallet->nOrderPosNext = nOrderPosNext; watchonly_batch->WriteOrderPosNext(data.watchonly_wallet->nOrderPosNext); + // Write the best block locator to avoid rescanning on reload + if (!watchonly_batch->WriteBestBlock(best_block_locator)) { + error = _("Error: Unable to write watchonly wallet best block locator record"); + return false; + } + } + if (data.solvable_wallet) { + // Write the best block locator to avoid rescanning on reload + if (!WalletBatch(data.solvable_wallet->GetDatabase()).WriteBestBlock(best_block_locator)) { + error = _("Error: Unable to write solvable wallet best block locator record"); + return false; + } } for (const auto& [_pos, wtx] : wtxOrdered) { - if (!IsMine(*wtx->tx) && !IsFromMe(*wtx->tx)) { - // Check it is the watchonly wallet's - // solvable_wallet doesn't need to be checked because transactions for those scripts weren't being watched for - if (data.watchonly_wallet) { - LOCK(data.watchonly_wallet->cs_wallet); - if (data.watchonly_wallet->IsMine(*wtx->tx) || data.watchonly_wallet->IsFromMe(*wtx->tx)) { - // Add to watchonly wallet - const uint256& hash = wtx->GetHash(); - const CWalletTx& to_copy_wtx = *wtx; - if (!data.watchonly_wallet->LoadToWallet(hash, [&](CWalletTx& ins_wtx, bool new_tx) EXCLUSIVE_LOCKS_REQUIRED(data.watchonly_wallet->cs_wallet) { - if (!new_tx) return false; - ins_wtx.SetTx(to_copy_wtx.tx); - ins_wtx.CopyFrom(to_copy_wtx); - return true; - })) { - error = strprintf(_("Error: Could not add watchonly tx %s to watchonly wallet"), wtx->GetHash().GetHex()); - return false; - } - watchonly_batch->WriteTx(data.watchonly_wallet->mapWallet.at(hash)); - // Mark as to remove from this wallet + // Check it is the watchonly wallet's + // solvable_wallet doesn't need to be checked because transactions for those scripts weren't being watched for + bool is_mine = IsMine(*wtx->tx) || IsFromMe(*wtx->tx); + if (data.watchonly_wallet) { + LOCK(data.watchonly_wallet->cs_wallet); + if (data.watchonly_wallet->IsMine(*wtx->tx) || data.watchonly_wallet->IsFromMe(*wtx->tx)) { + // Add to watchonly wallet + const uint256& hash = wtx->GetHash(); + const CWalletTx& to_copy_wtx = *wtx; + if (!data.watchonly_wallet->LoadToWallet(hash, [&](CWalletTx& ins_wtx, bool new_tx) EXCLUSIVE_LOCKS_REQUIRED(data.watchonly_wallet->cs_wallet) { + if (!new_tx) return false; + ins_wtx.SetTx(to_copy_wtx.tx); + ins_wtx.CopyFrom(to_copy_wtx); + return true; + })) { + error = strprintf(_("Error: Could not add watchonly tx %s to watchonly wallet"), wtx->GetHash().GetHex()); + return false; + } + watchonly_batch->WriteTx(data.watchonly_wallet->mapWallet.at(hash)); + // Mark as to remove from the migrated wallet only if it does not also belong to it + if (!is_mine) { txids_to_delete.push_back(hash); - continue; } + continue; } + } + if (!is_mine) { // Both not ours and not in the watchonly wallet error = strprintf(_("Error: Transaction %s in wallet cannot be identified to belong to migrated wallets"), wtx->GetHash().GetHex()); return false; @@ -3957,97 +4047,89 @@ bool CWallet::ApplyMigrationData(MigrationData& data, bilingual_str& error) } } + // Pair external wallets with their corresponding db handler + std::vector<std::pair<std::shared_ptr<CWallet>, std::unique_ptr<WalletBatch>>> wallets_vec; + for (const auto& ext_wallet : {data.watchonly_wallet, data.solvable_wallet}) { + if (!ext_wallet) continue; + + std::unique_ptr<WalletBatch> batch = std::make_unique<WalletBatch>(ext_wallet->GetDatabase()); + if (!batch->TxnBegin()) { + error = strprintf(_("Error: database transaction cannot be executed for wallet %s"), ext_wallet->GetName()); + return false; + } + wallets_vec.emplace_back(ext_wallet, std::move(batch)); + } + + // Write address book entry to disk + auto func_store_addr = [](WalletBatch& batch, const CTxDestination& dest, const CAddressBookData& entry) { + auto address{EncodeDestination(dest)}; + if (entry.purpose) batch.WritePurpose(address, PurposeToString(*entry.purpose)); + if (entry.label) batch.WriteName(address, *entry.label); + for (const auto& [id, request] : entry.receive_requests) { + batch.WriteAddressReceiveRequest(dest, id, request); + } + if (entry.previously_spent) batch.WriteAddressPreviouslySpent(dest, true); + }; + // Check the address book data in the same way we did for transactions std::vector<CTxDestination> dests_to_delete; - for (const auto& addr_pair : m_address_book) { - // Labels applied to receiving addresses should go based on IsMine - if (addr_pair.second.purpose == AddressPurpose::RECEIVE) { - if (!IsMine(addr_pair.first)) { - // Check the address book data is the watchonly wallet's - if (data.watchonly_wallet) { - LOCK(data.watchonly_wallet->cs_wallet); - if (data.watchonly_wallet->IsMine(addr_pair.first)) { - // Add to the watchonly. Preserve the labels, purpose, and change-ness - std::string label = addr_pair.second.GetLabel(); - data.watchonly_wallet->m_address_book[addr_pair.first].purpose = addr_pair.second.purpose; - if (!addr_pair.second.IsChange()) { - data.watchonly_wallet->m_address_book[addr_pair.first].SetLabel(label); - } - dests_to_delete.push_back(addr_pair.first); - continue; - } - } - if (data.solvable_wallet) { - LOCK(data.solvable_wallet->cs_wallet); - if (data.solvable_wallet->IsMine(addr_pair.first)) { - // Add to the solvable. Preserve the labels, purpose, and change-ness - std::string label = addr_pair.second.GetLabel(); - data.solvable_wallet->m_address_book[addr_pair.first].purpose = addr_pair.second.purpose; - if (!addr_pair.second.IsChange()) { - data.solvable_wallet->m_address_book[addr_pair.first].SetLabel(label); - } - dests_to_delete.push_back(addr_pair.first); - continue; - } - } + for (const auto& [dest, record] : m_address_book) { + // Ensure "receive" entries that are no longer part of the original wallet are transferred to another wallet + // Entries for everything else ("send") will be cloned to all wallets. + bool require_transfer = record.purpose == AddressPurpose::RECEIVE && !IsMine(dest); + bool copied = false; + for (auto& [wallet, batch] : wallets_vec) { + LOCK(wallet->cs_wallet); + if (require_transfer && !wallet->IsMine(dest)) continue; + + // Copy the entire address book entry + wallet->m_address_book[dest] = record; + func_store_addr(*batch, dest, record); + + copied = true; + // Only delete 'receive' records that are no longer part of the original wallet + if (require_transfer) { + dests_to_delete.push_back(dest); + break; + } + } - // Skip invalid/non-watched scripts that will not be migrated - if (not_migrated_dests.count(addr_pair.first) > 0) { - dests_to_delete.push_back(addr_pair.first); - continue; - } + // Fail immediately if we ever found an entry that was ours and cannot be transferred + // to any of the created wallets (watch-only, solvable). + // Means that no inferred descriptor maps to the stored entry. Which mustn't happen. + if (require_transfer && !copied) { - // Not ours, not in watchonly wallet, and not in solvable - error = _("Error: Address book data in wallet cannot be identified to belong to migrated wallets"); - return false; - } - } else { - // Labels for everything else ("send") should be cloned to all - if (data.watchonly_wallet) { - LOCK(data.watchonly_wallet->cs_wallet); - // Add to the watchonly. Preserve the labels, purpose, and change-ness - std::string label = addr_pair.second.GetLabel(); - data.watchonly_wallet->m_address_book[addr_pair.first].purpose = addr_pair.second.purpose; - if (!addr_pair.second.IsChange()) { - data.watchonly_wallet->m_address_book[addr_pair.first].SetLabel(label); - } - } - if (data.solvable_wallet) { - LOCK(data.solvable_wallet->cs_wallet); - // Add to the solvable. Preserve the labels, purpose, and change-ness - std::string label = addr_pair.second.GetLabel(); - data.solvable_wallet->m_address_book[addr_pair.first].purpose = addr_pair.second.purpose; - if (!addr_pair.second.IsChange()) { - data.solvable_wallet->m_address_book[addr_pair.first].SetLabel(label); - } + // Skip invalid/non-watched scripts that will not be migrated + if (not_migrated_dests.count(dest) > 0) { + dests_to_delete.push_back(dest); + continue; } + + error = _("Error: Address book data in wallet cannot be identified to belong to migrated wallets"); + return false; } } - // Persist added address book entries (labels, purpose) for watchonly and solvable wallets - auto persist_address_book = [](const CWallet& wallet) { - LOCK(wallet.cs_wallet); - WalletBatch batch{wallet.GetDatabase()}; - for (const auto& [destination, addr_book_data] : wallet.m_address_book) { - auto address{EncodeDestination(destination)}; - std::optional<std::string> label = addr_book_data.IsChange() ? std::nullopt : std::make_optional(addr_book_data.GetLabel()); - // don't bother writing default values (unknown purpose) - if (addr_book_data.purpose) batch.WritePurpose(address, PurposeToString(*addr_book_data.purpose)); - if (label) batch.WriteName(address, *label); + // Persist external wallets address book entries + for (auto& [wallet, batch] : wallets_vec) { + if (!batch->TxnCommit()) { + error = strprintf(_("Error: address book copy failed for wallet %s"), wallet->GetName()); + return false; } - }; - if (data.watchonly_wallet) persist_address_book(*data.watchonly_wallet); - if (data.solvable_wallet) persist_address_book(*data.solvable_wallet); + } - // Remove the things to delete + // Remove the things to delete in this wallet + WalletBatch local_wallet_batch(GetDatabase()); + local_wallet_batch.TxnBegin(); if (dests_to_delete.size() > 0) { for (const auto& dest : dests_to_delete) { - if (!DelAddressBook(dest)) { + if (!DelAddressBookWithDB(local_wallet_batch, dest)) { error = _("Error: Unable to remove watchonly address book data"); return false; } } } + local_wallet_batch.TxnCommit(); // Connect the SPKM signals ConnectScriptPubKeyManNotifiers(); @@ -4179,11 +4261,13 @@ util::Result<MigrationResult> MigrateLegacyToDescriptor(const std::string& walle std::vector<bilingual_str> warnings; // If the wallet is still loaded, unload it so that nothing else tries to use it while we're changing it + bool was_loaded = false; if (auto wallet = GetWallet(context, wallet_name)) { if (!RemoveWallet(context, wallet, /*load_on_start=*/std::nullopt, warnings)) { return util::Error{_("Unable to unload the wallet before migrating")}; } UnloadWallet(std::move(wallet)); + was_loaded = true; } // Load the wallet but only in the context of this function. @@ -4204,8 +4288,20 @@ util::Result<MigrationResult> MigrateLegacyToDescriptor(const std::string& walle return util::Error{Untranslated("Wallet loading failed.") + Untranslated(" ") + error}; } + // Helper to reload as normal for some of our exit scenarios + const auto& reload_wallet = [&](std::shared_ptr<CWallet>& to_reload) { + assert(to_reload.use_count() == 1); + std::string name = to_reload->GetName(); + to_reload.reset(); + to_reload = LoadWallet(context, name, /*load_on_start=*/std::nullopt, options, status, error, warnings); + return to_reload != nullptr; + }; + // Before anything else, check if there is something to migrate. - if (!local_wallet->GetLegacyScriptPubKeyMan()) { + if (local_wallet->IsWalletFlagSet(WALLET_FLAG_DESCRIPTORS)) { + if (was_loaded) { + reload_wallet(local_wallet); + } return util::Error{_("Error: This wallet is already a descriptor wallet")}; } @@ -4214,32 +4310,44 @@ util::Result<MigrationResult> MigrateLegacyToDescriptor(const std::string& walle fs::path backup_filename = fs::PathFromString(strprintf("%s-%d.legacy.bak", wallet_name, GetTime())); fs::path backup_path = this_wallet_dir / backup_filename; if (!local_wallet->BackupWallet(fs::PathToString(backup_path))) { + if (was_loaded) { + reload_wallet(local_wallet); + } return util::Error{_("Error: Unable to make a backup of your wallet")}; } res.backup_path = backup_path; bool success = false; - { - LOCK(local_wallet->cs_wallet); - // Unlock the wallet if needed - if (local_wallet->IsLocked() && !local_wallet->Unlock(passphrase)) { - if (passphrase.find('\0') == std::string::npos) { - return util::Error{Untranslated("Error: Wallet decryption failed, the wallet passphrase was not provided or was incorrect.")}; - } else { - return util::Error{Untranslated("Error: Wallet decryption failed, the wallet passphrase entered was incorrect. " - "The passphrase contains a null character (ie - a zero byte). " - "If this passphrase was set with a version of this software prior to 25.0, " - "please try again with only the characters up to — but not including — " - "the first null character.")}; - } + // Unlock the wallet if needed + if (local_wallet->IsLocked() && !local_wallet->Unlock(passphrase)) { + if (was_loaded) { + reload_wallet(local_wallet); + } + if (passphrase.find('\0') == std::string::npos) { + return util::Error{Untranslated("Error: Wallet decryption failed, the wallet passphrase was not provided or was incorrect.")}; + } else { + return util::Error{Untranslated("Error: Wallet decryption failed, the wallet passphrase entered was incorrect. " + "The passphrase contains a null character (ie - a zero byte). " + "If this passphrase was set with a version of this software prior to 25.0, " + "please try again with only the characters up to — but not including — " + "the first null character.")}; } + } + { + LOCK(local_wallet->cs_wallet); // First change to using SQLite if (!local_wallet->MigrateToSQLite(error)) return util::Error{error}; - // Do the migration, and cleanup if it fails - success = DoMigration(*local_wallet, context, error, res); + // Do the migration of keys and scripts for non-blank wallets, and cleanup if it fails + success = local_wallet->IsWalletFlagSet(WALLET_FLAG_BLANK_WALLET); + if (!success) { + success = DoMigration(*local_wallet, context, error, res); + } else { + // Make sure that descriptors flag is actually set + local_wallet->SetWalletFlag(WALLET_FLAG_DESCRIPTORS); + } } // In case of reloading failure, we need to remember the wallet dirs to remove @@ -4249,24 +4357,19 @@ util::Result<MigrationResult> MigrateLegacyToDescriptor(const std::string& walle std::set<fs::path> wallet_dirs; if (success) { // Migration successful, unload all wallets locally, then reload them. - const auto& reload_wallet = [&](std::shared_ptr<CWallet>& to_reload) { - assert(to_reload.use_count() == 1); - std::string name = to_reload->GetName(); - wallet_dirs.insert(fs::PathFromString(to_reload->GetDatabase().Filename()).parent_path()); - to_reload.reset(); - to_reload = LoadWallet(context, name, /*load_on_start=*/std::nullopt, options, status, error, warnings); - return to_reload != nullptr; - }; // Reload the main wallet + wallet_dirs.insert(fs::PathFromString(local_wallet->GetDatabase().Filename()).parent_path()); success = reload_wallet(local_wallet); res.wallet = local_wallet; res.wallet_name = wallet_name; if (success && res.watchonly_wallet) { // Reload watchonly + wallet_dirs.insert(fs::PathFromString(res.watchonly_wallet->GetDatabase().Filename()).parent_path()); success = reload_wallet(res.watchonly_wallet); } if (success && res.solvables_wallet) { // Reload solvables + wallet_dirs.insert(fs::PathFromString(res.solvables_wallet->GetDatabase().Filename()).parent_path()); success = reload_wallet(res.solvables_wallet); } } |