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.cpp649
1 files changed, 431 insertions, 218 deletions
diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp
index 70fb375efa..8a79cf730b 100644
--- a/src/wallet/wallet.cpp
+++ b/src/wallet/wallet.cpp
@@ -5,9 +5,7 @@
#include <wallet/wallet.h>
-#if defined(HAVE_CONFIG_H)
-#include <config/bitcoin-config.h>
-#endif
+#include <config/bitcoin-config.h> // IWYU pragma: keep
#include <addresstype.h>
#include <blockfilter.h>
#include <chain.h>
@@ -375,7 +373,12 @@ std::shared_ptr<CWallet> CreateWallet(WalletContext& context, const std::string&
uint64_t wallet_creation_flags = options.create_flags;
const SecureString& passphrase = options.create_passphrase;
+ ArgsManager& args = *Assert(context.args);
+
if (wallet_creation_flags & WALLET_FLAG_DESCRIPTORS) options.require_format = DatabaseFormat::SQLITE;
+ else if (args.GetBoolArg("-swapbdbendian", false)) {
+ options.require_format = DatabaseFormat::BERKELEY_SWAP;
+ }
// Indicate that the wallet is actually supposed to be blank and not just blank to make it encrypted
bool create_blank = (wallet_creation_flags & WALLET_FLAG_BLANK_WALLET);
@@ -555,7 +558,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 +571,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
@@ -752,8 +755,8 @@ bool CWallet::IsSpent(const COutPoint& outpoint) const
const uint256& wtxid = it->second;
const auto mit = mapWallet.find(wtxid);
if (mit != mapWallet.end()) {
- int depth = GetTxDepthInMainChain(mit->second);
- if (depth > 0 || (depth == 0 && !mit->second.isAbandoned()))
+ const auto& wtx = mit->second;
+ if (!wtx.isAbandoned() && !wtx.isBlockConflicted() && !wtx.isMempoolConflicted())
return true; // Spent
}
}
@@ -1197,7 +1200,7 @@ bool CWallet::LoadToWallet(const uint256& hash, const UpdateWalletTxFn& fill_wtx
auto it = mapWallet.find(txin.prevout.hash);
if (it != mapWallet.end()) {
CWalletTx& prevtx = it->second;
- if (auto* prev = prevtx.state<TxStateConflicted>()) {
+ if (auto* prev = prevtx.state<TxStateBlockConflicted>()) {
MarkConflicted(prev->conflicting_block_hash, prev->conflicting_block_height, wtx.GetHash());
}
}
@@ -1309,7 +1312,7 @@ bool CWallet::AbandonTransaction(const uint256& hashTx)
assert(!wtx.isConfirmed());
assert(!wtx.InMempool());
// If already conflicted or abandoned, no need to set abandoned
- if (!wtx.isConflicted() && !wtx.isAbandoned()) {
+ if (!wtx.isBlockConflicted() && !wtx.isAbandoned()) {
wtx.m_state = TxStateInactive{/*abandoned=*/true};
return TxUpdate::NOTIFY_CHANGED;
}
@@ -1346,7 +1349,7 @@ void CWallet::MarkConflicted(const uint256& hashBlock, int conflicting_height, c
if (conflictconfirms < GetTxDepthInMainChain(wtx)) {
// Block is 'more conflicted' than current confirm; update.
// Mark transaction as conflicted with this block.
- wtx.m_state = TxStateConflicted{hashBlock, conflicting_height};
+ wtx.m_state = TxStateBlockConflicted{hashBlock, conflicting_height};
return TxUpdate::CHANGED;
}
return TxUpdate::UNCHANGED;
@@ -1360,7 +1363,10 @@ void CWallet::MarkConflicted(const uint256& hashBlock, int conflicting_height, c
void CWallet::RecursiveUpdateTxState(const uint256& tx_hash, const TryUpdatingStateFn& try_updating_state) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet) {
// Do not flush the wallet here for performance reasons
WalletBatch batch(GetDatabase(), false);
+ RecursiveUpdateTxState(&batch, tx_hash, try_updating_state);
+}
+void CWallet::RecursiveUpdateTxState(WalletBatch* batch, const uint256& tx_hash, const TryUpdatingStateFn& try_updating_state) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet) {
std::set<uint256> todo;
std::set<uint256> done;
@@ -1377,7 +1383,7 @@ void CWallet::RecursiveUpdateTxState(const uint256& tx_hash, const TryUpdatingSt
TxUpdate update_state = try_updating_state(wtx);
if (update_state != TxUpdate::UNCHANGED) {
wtx.MarkDirty();
- batch.WriteTx(wtx);
+ if (batch) batch->WriteTx(wtx);
// Iterate over all its outputs, and update those tx states as well (if applicable)
for (unsigned int i = 0; i < wtx.tx->vout.size(); ++i) {
std::pair<TxSpends::const_iterator, TxSpends::const_iterator> range = mapTxSpends.equal_range(COutPoint(Txid::FromUint256(now), i));
@@ -1418,6 +1424,20 @@ void CWallet::transactionAddedToMempool(const CTransactionRef& tx) {
if (it != mapWallet.end()) {
RefreshMempoolStatus(it->second, chain());
}
+
+ const Txid& txid = tx->GetHash();
+
+ for (const CTxIn& tx_in : tx->vin) {
+ // For each wallet transaction spending this prevout..
+ for (auto range = mapTxSpends.equal_range(tx_in.prevout); range.first != range.second; range.first++) {
+ const uint256& spent_id = range.first->second;
+ // Skip the recently added tx
+ if (spent_id == txid) continue;
+ RecursiveUpdateTxState(/*batch=*/nullptr, spent_id, [&txid](CWalletTx& wtx) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet) {
+ return wtx.mempool_conflicts.insert(txid).second ? TxUpdate::CHANGED : TxUpdate::UNCHANGED;
+ });
+ }
+ }
}
void CWallet::transactionRemovedFromMempool(const CTransactionRef& tx, MemPoolRemovalReason reason) {
@@ -1455,6 +1475,21 @@ void CWallet::transactionRemovedFromMempool(const CTransactionRef& tx, MemPoolRe
// https://github.com/bitcoin-core/bitcoin-devwiki/wiki/Wallet-Transaction-Conflict-Tracking
SyncTransaction(tx, TxStateInactive{});
}
+
+ const Txid& txid = tx->GetHash();
+
+ for (const CTxIn& tx_in : tx->vin) {
+ // Iterate over all wallet transactions spending txin.prev
+ // and recursively mark them as no longer conflicting with
+ // txid
+ for (auto range = mapTxSpends.equal_range(tx_in.prevout); range.first != range.second; range.first++) {
+ const uint256& spent_id = range.first->second;
+
+ RecursiveUpdateTxState(/*batch=*/nullptr, spent_id, [&txid](CWalletTx& wtx) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet) {
+ return wtx.mempool_conflicts.erase(txid) ? TxUpdate::CHANGED : TxUpdate::UNCHANGED;
+ });
+ }
+ }
}
void CWallet::blockConnected(ChainstateRole role, const interfaces::BlockInfo& block)
@@ -1506,11 +1541,11 @@ void CWallet::blockDisconnected(const interfaces::BlockInfo& block)
for (TxSpends::const_iterator _it = range.first; _it != range.second; ++_it) {
CWalletTx& wtx = mapWallet.find(_it->second)->second;
- if (!wtx.isConflicted()) continue;
+ if (!wtx.isBlockConflicted()) continue;
auto try_updating_state = [&](CWalletTx& tx) {
- if (!tx.isConflicted()) return TxUpdate::UNCHANGED;
- if (tx.state<TxStateConflicted>()->conflicting_block_height >= disconnect_height) {
+ if (!tx.isBlockConflicted()) return TxUpdate::UNCHANGED;
+ if (tx.state<TxStateBlockConflicted>()->conflicting_block_height >= disconnect_height) {
tx.m_state = TxStateInactive{};
return TxUpdate::CHANGED;
}
@@ -1571,11 +1606,22 @@ isminetype CWallet::IsMine(const CTxDestination& dest) const
isminetype CWallet::IsMine(const CScript& script) const
{
AssertLockHeld(cs_wallet);
- isminetype result = ISMINE_NO;
- for (const auto& spk_man_pair : m_spk_managers) {
- result = std::max(result, spk_man_pair.second->IsMine(script));
+
+ // Search the cache so that IsMine is called only on the relevant SPKMs instead of on everything in m_spk_managers
+ const auto& it = m_cached_spks.find(script);
+ if (it != m_cached_spks.end()) {
+ isminetype res = ISMINE_NO;
+ for (const auto& spkm : it->second) {
+ res = std::max(res, spkm->IsMine(script));
+ }
+ Assume(res == ISMINE_SPENDABLE);
+ return res;
}
- return result;
+
+ // Legacy wallet
+ if (IsLegacy()) return GetLegacyScriptPubKeyMan()->IsMine(script);
+
+ return ISMINE_NO;
}
bool CWallet::IsMine(const CTransaction& tx) const
@@ -2320,12 +2366,41 @@ DBErrors CWallet::LoadWallet()
return nLoadWalletRet;
}
-DBErrors CWallet::ZapSelectTx(std::vector<uint256>& vHashIn, std::vector<uint256>& vHashOut)
+util::Result<void> CWallet::RemoveTxs(std::vector<uint256>& txs_to_remove)
{
AssertLockHeld(cs_wallet);
- DBErrors nZapSelectTxRet = WalletBatch(GetDatabase()).ZapSelectTx(vHashIn, vHashOut);
- for (const uint256& hash : vHashOut) {
- const auto& it = mapWallet.find(hash);
+ WalletBatch batch(GetDatabase());
+ if (!batch.TxnBegin()) return util::Error{_("Error starting db txn for wallet transactions removal")};
+
+ // Check for transaction existence and remove entries from disk
+ using TxIterator = std::unordered_map<uint256, CWalletTx, SaltedTxidHasher>::const_iterator;
+ std::vector<TxIterator> erased_txs;
+ bilingual_str str_err;
+ for (const uint256& hash : txs_to_remove) {
+ auto it_wtx = mapWallet.find(hash);
+ if (it_wtx == mapWallet.end()) {
+ str_err = strprintf(_("Transaction %s does not belong to this wallet"), hash.GetHex());
+ break;
+ }
+ if (!batch.EraseTx(hash)) {
+ str_err = strprintf(_("Failure removing transaction: %s"), hash.GetHex());
+ break;
+ }
+ erased_txs.emplace_back(it_wtx);
+ }
+
+ // Roll back removals in case of an error
+ if (!str_err.empty()) {
+ batch.TxnAbort();
+ return util::Error{str_err};
+ }
+
+ // Dump changes to disk
+ if (!batch.TxnCommit()) return util::Error{_("Error committing db txn for wallet transactions removal")};
+
+ // Update the in-memory state and notify upper layers about the removals
+ for (const auto& it : erased_txs) {
+ const uint256 hash{it->first};
wtxOrdered.erase(it->second.m_it_wtxOrdered);
for (const auto& txin : it->second.tx->vin)
mapTxSpends.erase(txin.prevout);
@@ -2333,22 +2408,9 @@ DBErrors CWallet::ZapSelectTx(std::vector<uint256>& vHashIn, std::vector<uint256
NotifyTransactionChanged(hash, CT_DELETED);
}
- if (nZapSelectTxRet == DBErrors::NEED_REWRITE)
- {
- if (GetDatabase().Rewrite("\x04pool"))
- {
- for (const auto& spk_man_pair : m_spk_managers) {
- spk_man_pair.second->RewriteDB();
- }
- }
- }
-
- if (nZapSelectTxRet != DBErrors::LOAD_OK)
- return nZapSelectTxRet;
-
MarkDirty();
- return DBErrors::LOAD_OK;
+ return {}; // all good
}
bool CWallet::SetAddressBookWithDB(WalletBatch& batch, const CTxDestination& address, const std::string& strName, const std::optional<AddressPurpose>& new_purpose)
@@ -2359,22 +2421,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)
@@ -2385,7 +2457,14 @@ bool CWallet::SetAddressBook(const CTxDestination& address, const std::string& s
bool CWallet::DelAddressBook(const CTxDestination& address)
{
- WalletBatch batch(GetDatabase());
+ return RunWithinTxn(GetDatabase(), /*process_desc=*/"address book entry removal", [&](WalletBatch& batch){
+ 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.
@@ -2396,14 +2475,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
@@ -2547,8 +2642,10 @@ util::Result<CTxDestination> ReserveDestination::GetReservedDestination(bool int
if (nIndex == -1) {
CKeyPool keypool;
- auto op_address = m_spk_man->GetReservedDestination(type, internal, nIndex, keypool);
+ int64_t index;
+ auto op_address = m_spk_man->GetReservedDestination(type, internal, index, keypool);
if (!op_address) return op_address;
+ nIndex = index;
address = *op_address;
fInternal = keypool.fInternal;
}
@@ -2573,7 +2670,7 @@ void ReserveDestination::ReturnDestination()
address = CNoDestination();
}
-bool CWallet::DisplayAddress(const CTxDestination& dest)
+util::Result<void> CWallet::DisplayAddress(const CTxDestination& dest)
{
CScript scriptPubKey = GetScriptForDestination(dest);
for (const auto& spk_man : GetScriptPubKeyMans(scriptPubKey)) {
@@ -2582,9 +2679,9 @@ bool CWallet::DisplayAddress(const CTxDestination& dest)
continue;
}
ExternalSigner signer = ExternalSignerScriptPubKeyMan::GetExternalSigner();
- return signer_spk_man->DisplayAddress(scriptPubKey, signer);
+ return signer_spk_man->DisplayAddress(dest, signer);
}
- return false;
+ return util::Error{_("There is no ScriptPubKeyManager for this address")};
}
bool CWallet::LockCoin(const COutPoint& output, WalletBatch* batch)
@@ -2725,7 +2822,7 @@ unsigned int CWallet::ComputeTimeSmart(const CWalletTx& wtx, bool rescanning_old
std::optional<uint256> block_hash;
if (auto* conf = wtx.state<TxStateConfirmed>()) {
block_hash = conf->confirmed_block_hash;
- } else if (auto* conf = wtx.state<TxStateConflicted>()) {
+ } else if (auto* conf = wtx.state<TxStateBlockConflicted>()) {
block_hash = conf->conflicting_block_hash;
}
@@ -3099,6 +3196,7 @@ std::shared_ptr<CWallet> CWallet::Create(WalletContext& context, const std::stri
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;
}
@@ -3314,7 +3412,7 @@ int CWallet::GetTxDepthInMainChain(const CWalletTx& wtx) const
if (auto* conf = wtx.state<TxStateConfirmed>()) {
assert(conf->confirmed_block_height >= 0);
return GetLastBlockHeight() - conf->confirmed_block_height + 1;
- } else if (auto* conf = wtx.state<TxStateConflicted>()) {
+ } else if (auto* conf = wtx.state<TxStateBlockConflicted>()) {
assert(conf->conflicting_block_height >= 0);
return -1 * (GetLastBlockHeight() - conf->conflicting_block_height + 1);
} else {
@@ -3373,12 +3471,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;
}
}
@@ -3402,6 +3500,17 @@ std::set<ScriptPubKeyMan*> CWallet::GetActiveScriptPubKeyMans() const
return spk_mans;
}
+bool CWallet::IsActiveScriptPubKeyMan(const ScriptPubKeyMan& spkm) const
+{
+ for (const auto& [_, ext_spkm] : m_external_spk_managers) {
+ if (ext_spkm == &spkm) return true;
+ }
+ for (const auto& [_, int_spkm] : m_internal_spk_managers) {
+ if (int_spkm == &spkm) return true;
+ }
+ return false;
+}
+
std::set<ScriptPubKeyMan*> CWallet::GetAllScriptPubKeyMans() const
{
std::set<ScriptPubKeyMan*> spk_mans;
@@ -3424,12 +3533,18 @@ ScriptPubKeyMan* CWallet::GetScriptPubKeyMan(const OutputType& type, bool intern
std::set<ScriptPubKeyMan*> CWallet::GetScriptPubKeyMans(const CScript& script) const
{
std::set<ScriptPubKeyMan*> spk_mans;
- SignatureData sigdata;
- for (const auto& spk_man_pair : m_spk_managers) {
- if (spk_man_pair.second->CanProvide(script, sigdata)) {
- spk_mans.insert(spk_man_pair.second.get());
- }
+
+ // Search the cache for relevant SPKMs instead of iterating m_spk_managers
+ const auto& it = m_cached_spks.find(script);
+ if (it != m_cached_spks.end()) {
+ spk_mans.insert(it->second.begin(), it->second.end());
}
+ SignatureData sigdata;
+ Assume(std::all_of(spk_mans.begin(), spk_mans.end(), [&script, &sigdata](ScriptPubKeyMan* spkm) { return spkm->CanProvide(script, sigdata); }));
+
+ // Legacy wallet
+ if (IsLegacy() && GetLegacyScriptPubKeyMan()->CanProvide(script, sigdata)) spk_mans.insert(GetLegacyScriptPubKeyMan());
+
return spk_mans;
}
@@ -3449,11 +3564,17 @@ std::unique_ptr<SigningProvider> CWallet::GetSolvingProvider(const CScript& scri
std::unique_ptr<SigningProvider> CWallet::GetSolvingProvider(const CScript& script, SignatureData& sigdata) const
{
- for (const auto& spk_man_pair : m_spk_managers) {
- if (spk_man_pair.second->CanProvide(script, sigdata)) {
- return spk_man_pair.second->GetSolvingProvider(script);
- }
+ // Search the cache for relevant SPKMs instead of iterating m_spk_managers
+ const auto& it = m_cached_spks.find(script);
+ if (it != m_cached_spks.end()) {
+ // All spkms for a given script must already be able to make a SigningProvider for the script, so just return the first one.
+ Assume(it->second.at(0)->CanProvide(script, sigdata));
+ return it->second.at(0)->GetSolvingProvider(script);
}
+
+ // Legacy wallet
+ if (IsLegacy() && GetLegacyScriptPubKeyMan()->CanProvide(script, sigdata)) return GetLegacyScriptPubKeyMan()->GetSolvingProvider(script);
+
return nullptr;
}
@@ -3512,9 +3633,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
@@ -3531,15 +3653,36 @@ void CWallet::ConnectScriptPubKeyManNotifiers()
}
}
-void CWallet::LoadDescriptorScriptPubKeyMan(uint256 id, WalletDescriptor& desc)
+DescriptorScriptPubKeyMan& CWallet::LoadDescriptorScriptPubKeyMan(uint256 id, WalletDescriptor& desc)
{
+ DescriptorScriptPubKeyMan* spk_manager;
if (IsWalletFlagSet(WALLET_FLAG_EXTERNAL_SIGNER)) {
- auto spk_manager = std::unique_ptr<ScriptPubKeyMan>(new ExternalSignerScriptPubKeyMan(*this, desc, m_keypool_size));
- AddScriptPubKeyMan(id, std::move(spk_manager));
+ spk_manager = new ExternalSignerScriptPubKeyMan(*this, desc, m_keypool_size);
} else {
- auto spk_manager = std::unique_ptr<ScriptPubKeyMan>(new DescriptorScriptPubKeyMan(*this, desc, m_keypool_size));
- AddScriptPubKeyMan(id, std::move(spk_manager));
+ spk_manager = new DescriptorScriptPubKeyMan(*this, desc, m_keypool_size);
+ }
+ AddScriptPubKeyMan(id, std::unique_ptr<ScriptPubKeyMan>(spk_manager));
+ return *spk_manager;
+}
+
+DescriptorScriptPubKeyMan& CWallet::SetupDescriptorScriptPubKeyMan(WalletBatch& batch, const CExtKey& master_key, const OutputType& output_type, bool internal)
+{
+ AssertLockHeld(cs_wallet);
+ auto spk_manager = std::unique_ptr<DescriptorScriptPubKeyMan>(new DescriptorScriptPubKeyMan(*this, m_keypool_size));
+ if (IsCrypted()) {
+ 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, &batch)) {
+ throw std::runtime_error(std::string(__func__) + ": Could not encrypt new descriptors");
+ }
}
+ spk_manager->SetupDescriptorGeneration(batch, master_key, output_type, internal);
+ DescriptorScriptPubKeyMan* out = spk_manager.get();
+ uint256 id = spk_manager->GetID();
+ AddScriptPubKeyMan(id, std::move(spk_manager));
+ AddActiveScriptPubKeyManWithDb(batch, id, output_type, internal);
+ return *out;
}
void CWallet::SetupDescriptorScriptPubKeyMans(const CExtKey& master_key)
@@ -3552,19 +3695,7 @@ void CWallet::SetupDescriptorScriptPubKeyMans(const CExtKey& master_key)
for (bool internal : {false, true}) {
for (OutputType t : OUTPUT_TYPES) {
- auto spk_manager = std::unique_ptr<DescriptorScriptPubKeyMan>(new DescriptorScriptPubKeyMan(*this, m_keypool_size));
- if (IsCrypted()) {
- 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, &batch)) {
- throw std::runtime_error(std::string(__func__) + ": Could not encrypt new descriptors");
- }
- }
- spk_manager->SetupDescriptorGeneration(batch, master_key, t, internal);
- uint256 id = spk_manager->GetID();
- AddScriptPubKeyMan(id, std::move(spk_manager));
- AddActiveScriptPubKeyManWithDb(batch, id, t, internal);
+ SetupDescriptorScriptPubKeyMan(batch, master_key, t, internal);
}
}
@@ -3862,7 +3993,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) {
@@ -3877,8 +4012,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;
}
@@ -3889,6 +4025,8 @@ bool CWallet::ApplyMigrationData(MigrationData& data, bilingual_str& error)
if (ExtractDestination(script, dest)) not_migrated_dests.emplace(dest);
}
+ Assume(!m_cached_spks.empty());
+
for (auto& desc_spkm : data.desc_spkms) {
if (m_spk_managers.count(desc_spkm->GetID()) > 0) {
error = _("Error: Duplicate descriptors created during migration. Your wallet may be corrupted.");
@@ -3920,6 +4058,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;
@@ -3930,32 +4075,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;
@@ -3964,112 +4124,95 @@ bool CWallet::ApplyMigrationData(MigrationData& data, bilingual_str& error)
watchonly_batch.reset(); // Flush
// Do the removes
if (txids_to_delete.size() > 0) {
- std::vector<uint256> deleted_txids;
- if (ZapSelectTx(txids_to_delete, deleted_txids) != DBErrors::LOAD_OK) {
- error = _("Error: Could not delete watchonly transactions");
+ if (auto res = RemoveTxs(txids_to_delete); !res) {
+ error = _("Error: Could not delete watchonly transactions. ") + util::ErrorString(res);
return false;
}
- if (deleted_txids != txids_to_delete) {
- error = _("Error: Not all watchonly txs could be deleted");
+ }
+
+ // 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;
}
- // Tell the GUI of each tx
- for (const uint256& txid : deleted_txids) {
- NotifyTransactionChanged(txid, CT_UPDATED);
- }
+ 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();
@@ -4201,11 +4344,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.
@@ -4226,42 +4371,66 @@ 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")};
}
// Make a backup of the DB
fs::path this_wallet_dir = fs::absolute(fs::PathFromString(local_wallet->GetDatabase().Filename())).parent_path();
- fs::path backup_filename = fs::PathFromString(strprintf("%s-%d.legacy.bak", wallet_name, GetTime()));
+ fs::path backup_filename = fs::PathFromString(strprintf("%s_%d.legacy.bak", (wallet_name.empty() ? "default_wallet" : 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
@@ -4271,24 +4440,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);
}
}
@@ -4346,4 +4510,53 @@ util::Result<MigrationResult> MigrateLegacyToDescriptor(const std::string& walle
}
return res;
}
+
+void CWallet::CacheNewScriptPubKeys(const std::set<CScript>& spks, ScriptPubKeyMan* spkm)
+{
+ for (const auto& script : spks) {
+ m_cached_spks[script].push_back(spkm);
+ }
+}
+
+void CWallet::TopUpCallback(const std::set<CScript>& spks, ScriptPubKeyMan* spkm)
+{
+ // Update scriptPubKey cache
+ CacheNewScriptPubKeys(spks, spkm);
+}
+
+std::set<CExtPubKey> CWallet::GetActiveHDPubKeys() const
+{
+ AssertLockHeld(cs_wallet);
+
+ Assert(IsWalletFlagSet(WALLET_FLAG_DESCRIPTORS));
+
+ std::set<CExtPubKey> active_xpubs;
+ for (const auto& spkm : GetActiveScriptPubKeyMans()) {
+ const DescriptorScriptPubKeyMan* desc_spkm = dynamic_cast<DescriptorScriptPubKeyMan*>(spkm);
+ assert(desc_spkm);
+ LOCK(desc_spkm->cs_desc_man);
+ WalletDescriptor w_desc = desc_spkm->GetWalletDescriptor();
+
+ std::set<CPubKey> desc_pubkeys;
+ std::set<CExtPubKey> desc_xpubs;
+ w_desc.descriptor->GetPubKeys(desc_pubkeys, desc_xpubs);
+ active_xpubs.merge(std::move(desc_xpubs));
+ }
+ return active_xpubs;
+}
+
+std::optional<CKey> CWallet::GetKey(const CKeyID& keyid) const
+{
+ Assert(IsWalletFlagSet(WALLET_FLAG_DESCRIPTORS));
+
+ for (const auto& spkm : GetAllScriptPubKeyMans()) {
+ const DescriptorScriptPubKeyMan* desc_spkm = dynamic_cast<DescriptorScriptPubKeyMan*>(spkm);
+ assert(desc_spkm);
+ LOCK(desc_spkm->cs_desc_man);
+ if (std::optional<CKey> key = desc_spkm->GetKey(keyid)) {
+ return key;
+ }
+ }
+ return std::nullopt;
+}
} // namespace wallet