aboutsummaryrefslogtreecommitdiff
path: root/src/wallet/wallet.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/wallet/wallet.cpp')
-rw-r--r--src/wallet/wallet.cpp409
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);
}
}