aboutsummaryrefslogtreecommitdiff
path: root/src/wallet
diff options
context:
space:
mode:
Diffstat (limited to 'src/wallet')
-rw-r--r--src/wallet/interfaces.cpp6
-rw-r--r--src/wallet/rpcdump.cpp17
-rw-r--r--src/wallet/rpcwallet.cpp2
-rw-r--r--src/wallet/scriptpubkeyman.cpp106
-rw-r--r--src/wallet/scriptpubkeyman.h6
-rw-r--r--src/wallet/wallet.cpp105
-rw-r--r--src/wallet/wallet.h24
-rw-r--r--src/wallet/walletdb.cpp55
-rw-r--r--src/wallet/walletdb.h3
-rw-r--r--src/wallet/walletutil.h3
10 files changed, 230 insertions, 97 deletions
diff --git a/src/wallet/interfaces.cpp b/src/wallet/interfaces.cpp
index 5a832d020b..e33adf94c9 100644
--- a/src/wallet/interfaces.cpp
+++ b/src/wallet/interfaces.cpp
@@ -475,13 +475,13 @@ public:
std::unique_ptr<Handler> handleAddressBookChanged(AddressBookChangedFn fn) override
{
return MakeHandler(m_wallet->NotifyAddressBookChanged.connect(
- [fn](CWallet*, const CTxDestination& address, const std::string& label, bool is_mine,
- const std::string& purpose, ChangeType status) { fn(address, label, is_mine, purpose, status); }));
+ [fn](const CTxDestination& address, const std::string& label, bool is_mine,
+ const std::string& purpose, ChangeType status) { fn(address, label, is_mine, purpose, status); }));
}
std::unique_ptr<Handler> handleTransactionChanged(TransactionChangedFn fn) override
{
return MakeHandler(m_wallet->NotifyTransactionChanged.connect(
- [fn](CWallet*, const uint256& txid, ChangeType status) { fn(txid, status); }));
+ [fn](const uint256& txid, ChangeType status) { fn(txid, status); }));
}
std::unique_ptr<Handler> handleWatchOnlyChanged(WatchOnlyChangedFn fn) override
{
diff --git a/src/wallet/rpcdump.cpp b/src/wallet/rpcdump.cpp
index 35649ab02c..ea97b339cf 100644
--- a/src/wallet/rpcdump.cpp
+++ b/src/wallet/rpcdump.cpp
@@ -1566,9 +1566,8 @@ static UniValue ProcessDescriptorImport(CWallet& wallet, const UniValue& data, c
// Check if the wallet already contains the descriptor
auto existing_spk_manager = wallet.GetDescriptorScriptPubKeyMan(w_desc);
if (existing_spk_manager) {
- LOCK(existing_spk_manager->cs_desc_man);
- if (range_start > existing_spk_manager->GetWalletDescriptor().range_start) {
- throw JSONRPCError(RPC_INVALID_PARAMS, strprintf("range_start can only decrease; current range = [%d,%d]", existing_spk_manager->GetWalletDescriptor().range_start, existing_spk_manager->GetWalletDescriptor().range_end));
+ if (!existing_spk_manager->CanUpdateToWalletDescriptor(w_desc, error)) {
+ throw JSONRPCError(RPC_INVALID_PARAMETER, error);
}
}
@@ -1585,16 +1584,16 @@ static UniValue ProcessDescriptorImport(CWallet& wallet, const UniValue& data, c
} else {
wallet.AddActiveScriptPubKeyMan(spk_manager->GetID(), *w_desc.descriptor->GetOutputType(), internal);
}
+ } else {
+ if (w_desc.descriptor->GetOutputType()) {
+ wallet.DeactivateScriptPubKeyMan(spk_manager->GetID(), *w_desc.descriptor->GetOutputType(), internal);
+ }
}
result.pushKV("success", UniValue(true));
} catch (const UniValue& e) {
result.pushKV("success", UniValue(false));
result.pushKV("error", e);
- } catch (...) {
- result.pushKV("success", UniValue(false));
-
- result.pushKV("error", JSONRPCError(RPC_MISC_ERROR, "Missing required fields"));
}
if (warnings.size()) result.pushKV("warnings", warnings);
return result;
@@ -1787,8 +1786,6 @@ RPCHelpMan listdescriptors()
throw JSONRPCError(RPC_WALLET_ERROR, "listdescriptors is not available for non-descriptor wallets");
}
- EnsureWalletIsUnlocked(*wallet);
-
LOCK(wallet->cs_wallet);
UniValue descriptors(UniValue::VARR);
@@ -1802,7 +1799,7 @@ RPCHelpMan listdescriptors()
LOCK(desc_spk_man->cs_desc_man);
const auto& wallet_descriptor = desc_spk_man->GetWalletDescriptor();
std::string descriptor;
- if (!desc_spk_man->GetDescriptorString(descriptor, false)) {
+ if (!desc_spk_man->GetDescriptorString(descriptor)) {
throw JSONRPCError(RPC_WALLET_ERROR, "Can't get normalized descriptor string.");
}
spk.pushKV("desc", descriptor);
diff --git a/src/wallet/rpcwallet.cpp b/src/wallet/rpcwallet.cpp
index bc5d771b6e..f1d5117415 100644
--- a/src/wallet/rpcwallet.cpp
+++ b/src/wallet/rpcwallet.cpp
@@ -3872,7 +3872,7 @@ RPCHelpMan getaddressinfo()
DescriptorScriptPubKeyMan* desc_spk_man = dynamic_cast<DescriptorScriptPubKeyMan*>(pwallet->GetScriptPubKeyMan(scriptPubKey));
if (desc_spk_man) {
std::string desc_str;
- if (desc_spk_man->GetDescriptorString(desc_str, false)) {
+ if (desc_spk_man->GetDescriptorString(desc_str)) {
ret.pushKV("parent_desc", desc_str);
}
}
diff --git a/src/wallet/scriptpubkeyman.cpp b/src/wallet/scriptpubkeyman.cpp
index 5467495fff..73433214f1 100644
--- a/src/wallet/scriptpubkeyman.cpp
+++ b/src/wallet/scriptpubkeyman.cpp
@@ -1803,34 +1803,10 @@ bool DescriptorScriptPubKeyMan::TopUp(unsigned int size)
}
m_map_pubkeys[pubkey] = i;
}
- // Write the cache
- for (const auto& parent_xpub_pair : temp_cache.GetCachedParentExtPubKeys()) {
- CExtPubKey xpub;
- if (m_wallet_descriptor.cache.GetCachedParentExtPubKey(parent_xpub_pair.first, xpub)) {
- if (xpub != parent_xpub_pair.second) {
- throw std::runtime_error(std::string(__func__) + ": New cached parent xpub does not match already cached parent xpub");
- }
- continue;
- }
- if (!batch.WriteDescriptorParentCache(parent_xpub_pair.second, id, parent_xpub_pair.first)) {
- throw std::runtime_error(std::string(__func__) + ": writing cache item failed");
- }
- m_wallet_descriptor.cache.CacheParentExtPubKey(parent_xpub_pair.first, parent_xpub_pair.second);
- }
- for (const auto& derived_xpub_map_pair : temp_cache.GetCachedDerivedExtPubKeys()) {
- for (const auto& derived_xpub_pair : derived_xpub_map_pair.second) {
- CExtPubKey xpub;
- if (m_wallet_descriptor.cache.GetCachedDerivedExtPubKey(derived_xpub_map_pair.first, derived_xpub_pair.first, xpub)) {
- if (xpub != derived_xpub_pair.second) {
- throw std::runtime_error(std::string(__func__) + ": New cached derived xpub does not match already cached derived xpub");
- }
- continue;
- }
- if (!batch.WriteDescriptorDerivedCache(derived_xpub_pair.second, id, derived_xpub_map_pair.first, derived_xpub_pair.first)) {
- throw std::runtime_error(std::string(__func__) + ": writing cache item failed");
- }
- m_wallet_descriptor.cache.CacheDerivedExtPubKey(derived_xpub_map_pair.first, derived_xpub_pair.first, derived_xpub_pair.second);
- }
+ // Merge and write the cache
+ DescriptorCache new_items = m_wallet_descriptor.cache.MergeAndDiff(temp_cache);
+ if (!batch.WriteDescriptorCacheItems(id, new_items)) {
+ throw std::runtime_error(std::string(__func__) + ": writing cache items failed");
}
m_max_cached_index++;
}
@@ -1873,6 +1849,12 @@ bool DescriptorScriptPubKeyMan::AddDescriptorKeyWithDB(WalletBatch& batch, const
AssertLockHeld(cs_desc_man);
assert(!m_storage.IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS));
+ // Check if provided key already exists
+ if (m_map_keys.find(pubkey.GetID()) != m_map_keys.end() ||
+ m_map_crypted_keys.find(pubkey.GetID()) != m_map_crypted_keys.end()) {
+ return true;
+ }
+
if (m_storage.HasEncryptionKeys()) {
if (m_storage.IsLocked()) {
return false;
@@ -2276,15 +2258,75 @@ const std::vector<CScript> DescriptorScriptPubKeyMan::GetScriptPubKeys() const
return script_pub_keys;
}
-bool DescriptorScriptPubKeyMan::GetDescriptorString(std::string& out, bool priv) const
+bool DescriptorScriptPubKeyMan::GetDescriptorString(std::string& out) const
{
LOCK(cs_desc_man);
- if (m_storage.IsLocked()) {
- return false;
+
+ FlatSigningProvider provider;
+ provider.keys = GetKeys();
+
+ return m_wallet_descriptor.descriptor->ToNormalizedString(provider, out, &m_wallet_descriptor.cache);
+}
+
+void DescriptorScriptPubKeyMan::UpgradeDescriptorCache()
+{
+ LOCK(cs_desc_man);
+ if (m_storage.IsLocked() || m_storage.IsWalletFlagSet(WALLET_FLAG_LAST_HARDENED_XPUB_CACHED)) {
+ return;
}
+ // Skip if we have the last hardened xpub cache
+ if (m_wallet_descriptor.cache.GetCachedLastHardenedExtPubKeys().size() > 0) {
+ return;
+ }
+
+ // Expand the descriptor
FlatSigningProvider provider;
provider.keys = GetKeys();
+ FlatSigningProvider out_keys;
+ std::vector<CScript> scripts_temp;
+ DescriptorCache temp_cache;
+ if (!m_wallet_descriptor.descriptor->Expand(0, provider, scripts_temp, out_keys, &temp_cache)){
+ throw std::runtime_error("Unable to expand descriptor");
+ }
+
+ // Cache the last hardened xpubs
+ DescriptorCache diff = m_wallet_descriptor.cache.MergeAndDiff(temp_cache);
+ if (!WalletBatch(m_storage.GetDatabase()).WriteDescriptorCacheItems(GetID(), diff)) {
+ throw std::runtime_error(std::string(__func__) + ": writing cache items failed");
+ }
+}
+
+void DescriptorScriptPubKeyMan::UpdateWalletDescriptor(WalletDescriptor& descriptor)
+{
+ LOCK(cs_desc_man);
+ std::string error;
+ if (!CanUpdateToWalletDescriptor(descriptor, error)) {
+ throw std::runtime_error(std::string(__func__) + ": " + error);
+ }
- return m_wallet_descriptor.descriptor->ToNormalizedString(provider, out, priv);
+ m_map_pubkeys.clear();
+ m_map_script_pub_keys.clear();
+ m_max_cached_index = -1;
+ m_wallet_descriptor = descriptor;
+}
+
+bool DescriptorScriptPubKeyMan::CanUpdateToWalletDescriptor(const WalletDescriptor& descriptor, std::string& error)
+{
+ LOCK(cs_desc_man);
+ if (!HasWalletDescriptor(descriptor)) {
+ error = "can only update matching descriptor";
+ return false;
+ }
+
+ if (descriptor.range_start > m_wallet_descriptor.range_start ||
+ descriptor.range_end < m_wallet_descriptor.range_end) {
+ // Use inclusive range for error
+ error = strprintf("new range must include current range = [%d,%d]",
+ m_wallet_descriptor.range_start,
+ m_wallet_descriptor.range_end - 1);
+ return false;
+ }
+
+ return true;
}
diff --git a/src/wallet/scriptpubkeyman.h b/src/wallet/scriptpubkeyman.h
index 128197e567..572a695662 100644
--- a/src/wallet/scriptpubkeyman.h
+++ b/src/wallet/scriptpubkeyman.h
@@ -613,13 +613,17 @@ public:
bool AddCryptedKey(const CKeyID& key_id, const CPubKey& pubkey, const std::vector<unsigned char>& crypted_key);
bool HasWalletDescriptor(const WalletDescriptor& desc) const;
+ void UpdateWalletDescriptor(WalletDescriptor& descriptor);
+ bool CanUpdateToWalletDescriptor(const WalletDescriptor& descriptor, std::string& error);
void AddDescriptorKey(const CKey& key, const CPubKey &pubkey);
void WriteDescriptor();
const WalletDescriptor GetWalletDescriptor() const EXCLUSIVE_LOCKS_REQUIRED(cs_desc_man);
const std::vector<CScript> GetScriptPubKeys() const;
- bool GetDescriptorString(std::string& out, bool priv) const;
+ bool GetDescriptorString(std::string& out) const;
+
+ void UpgradeDescriptorCache();
};
#endif // BITCOIN_WALLET_SCRIPTPUBKEYMAN_H
diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp
index 5b108a5069..1f736f8baf 100644
--- a/src/wallet/wallet.cpp
+++ b/src/wallet/wallet.cpp
@@ -374,6 +374,19 @@ void CWallet::UpgradeKeyMetadata()
SetWalletFlag(WALLET_FLAG_KEY_ORIGIN_METADATA);
}
+void CWallet::UpgradeDescriptorCache()
+{
+ if (!IsWalletFlagSet(WALLET_FLAG_DESCRIPTORS) || IsLocked() || IsWalletFlagSet(WALLET_FLAG_LAST_HARDENED_XPUB_CACHED)) {
+ return;
+ }
+
+ for (ScriptPubKeyMan* spkm : GetAllScriptPubKeyMans()) {
+ DescriptorScriptPubKeyMan* desc_spkm = dynamic_cast<DescriptorScriptPubKeyMan*>(spkm);
+ desc_spkm->UpgradeDescriptorCache();
+ }
+ SetWalletFlag(WALLET_FLAG_LAST_HARDENED_XPUB_CACHED);
+}
+
bool CWallet::Unlock(const SecureString& strWalletPassphrase, bool accept_no_keys)
{
CCrypter crypter;
@@ -390,6 +403,8 @@ bool CWallet::Unlock(const SecureString& strWalletPassphrase, bool accept_no_key
if (Unlock(_vMasterKey, accept_no_keys)) {
// Now that we've unlocked, upgrade the key metadata
UpgradeKeyMetadata();
+ // Now that we've unlocked, upgrade the descriptor cache
+ UpgradeDescriptorCache();
return true;
}
}
@@ -801,7 +816,7 @@ bool CWallet::MarkReplaced(const uint256& originalHash, const uint256& newHash)
success = false;
}
- NotifyTransactionChanged(this, originalHash, CT_UPDATED);
+ NotifyTransactionChanged(originalHash, CT_UPDATED);
return success;
}
@@ -930,7 +945,7 @@ CWalletTx* CWallet::AddToWallet(CTransactionRef tx, const CWalletTx::Confirmatio
wtx.MarkDirty();
// Notify UI of new or updated transaction
- NotifyTransactionChanged(this, hash, fInsertedNew ? CT_NEW : CT_UPDATED);
+ NotifyTransactionChanged(hash, fInsertedNew ? CT_NEW : CT_UPDATED);
#if HAVE_SYSTEM
// notify an external script when a wallet transaction comes in or is updated
@@ -1104,7 +1119,7 @@ bool CWallet::AbandonTransaction(const uint256& hashTx)
wtx.setAbandoned();
wtx.MarkDirty();
batch.WriteTx(wtx);
- NotifyTransactionChanged(this, wtx.GetHash(), CT_UPDATED);
+ NotifyTransactionChanged(wtx.GetHash(), CT_UPDATED);
// Iterate over all its outputs, and mark transactions in the wallet that spend them abandoned too
TxSpends::const_iterator iter = mapTxSpends.lower_bound(COutPoint(now, 0));
while (iter != mapTxSpends.end() && iter->first.hash == now) {
@@ -1944,7 +1959,7 @@ void CWallet::CommitTransaction(CTransactionRef tx, mapValue_t mapValue, std::ve
for (const CTxIn& txin : tx->vin) {
CWalletTx &coin = mapWallet.at(txin.prevout.hash);
coin.MarkDirty();
- NotifyTransactionChanged(this, coin.GetHash(), CT_UPDATED);
+ NotifyTransactionChanged(coin.GetHash(), CT_UPDATED);
}
// Get the inserted-CWalletTx from mapWallet so that the
@@ -1999,7 +2014,7 @@ DBErrors CWallet::ZapSelectTx(std::vector<uint256>& vHashIn, std::vector<uint256
for (const auto& txin : it->second.tx->vin)
mapTxSpends.erase(txin.prevout);
mapWallet.erase(it);
- NotifyTransactionChanged(this, hash, CT_DELETED);
+ NotifyTransactionChanged(hash, CT_DELETED);
}
if (nZapSelectTxRet == DBErrors::NEED_REWRITE)
@@ -2033,8 +2048,8 @@ bool CWallet::SetAddressBookWithDB(WalletBatch& batch, const CTxDestination& add
m_address_book[address].purpose = strPurpose;
is_mine = IsMine(address) != ISMINE_NO;
}
- NotifyAddressBookChanged(this, address, strName, is_mine,
- strPurpose, (fUpdated ? CT_UPDATED : CT_NEW) );
+ NotifyAddressBookChanged(address, strName, is_mine,
+ strPurpose, (fUpdated ? CT_UPDATED : CT_NEW));
if (!strPurpose.empty() && !batch.WritePurpose(EncodeDestination(address), strPurpose))
return false;
return batch.WriteName(EncodeDestination(address), strName);
@@ -2069,7 +2084,7 @@ bool CWallet::DelAddressBook(const CTxDestination& address)
is_mine = IsMine(address) != ISMINE_NO;
}
- NotifyAddressBookChanged(this, address, "", is_mine, "", CT_DELETED);
+ NotifyAddressBookChanged(address, "", is_mine, "", CT_DELETED);
batch.ErasePurpose(EncodeDestination(address));
return batch.EraseName(EncodeDestination(address));
@@ -3158,11 +3173,37 @@ void CWallet::AddActiveScriptPubKeyMan(uint256 id, OutputType type, bool interna
void CWallet::LoadActiveScriptPubKeyMan(uint256 id, OutputType type, bool internal)
{
+ // Activating ScriptPubKeyManager for a given output and change type is incompatible with legacy wallets.
+ // Legacy wallets have only one ScriptPubKeyManager and it's active for all output and change types.
+ Assert(IsWalletFlagSet(WALLET_FLAG_DESCRIPTORS));
+
WalletLogPrintf("Setting spkMan to active: id = %s, type = %d, internal = %d\n", id.ToString(), static_cast<int>(type), static_cast<int>(internal));
auto& spk_mans = internal ? m_internal_spk_managers : m_external_spk_managers;
+ auto& spk_mans_other = internal ? m_external_spk_managers : m_internal_spk_managers;
auto spk_man = m_spk_managers.at(id).get();
spk_mans[type] = spk_man;
+ if (spk_mans_other[type] == spk_man) {
+ spk_mans_other[type] = nullptr;
+ }
+
+ NotifyCanGetAddressesChanged();
+}
+
+void CWallet::DeactivateScriptPubKeyMan(uint256 id, OutputType type, bool internal)
+{
+ auto spk_man = GetScriptPubKeyMan(type, internal);
+ if (spk_man != nullptr && spk_man->GetID() == id) {
+ WalletLogPrintf("Deactivate spkMan: id = %s, type = %d, internal = %d\n", id.ToString(), static_cast<int>(type), static_cast<int>(internal));
+ WalletBatch batch(GetDatabase());
+ if (!batch.EraseActiveScriptPubKeyMan(static_cast<uint8_t>(type), internal)) {
+ throw std::runtime_error(std::string(__func__) + ": erasing active ScriptPubKeyMan id failed");
+ }
+
+ auto& spk_mans = internal ? m_internal_spk_managers : m_external_spk_managers;
+ spk_mans[type] = nullptr;
+ }
+
NotifyCanGetAddressesChanged();
}
@@ -3196,44 +3237,26 @@ ScriptPubKeyMan* CWallet::AddWalletDescriptor(WalletDescriptor& desc, const Flat
}
LOCK(cs_wallet);
- auto new_spk_man = std::unique_ptr<DescriptorScriptPubKeyMan>(new DescriptorScriptPubKeyMan(*this, desc));
-
- // If we already have this descriptor, remove it from the maps but add the existing cache to desc
- auto old_spk_man = GetDescriptorScriptPubKeyMan(desc);
- if (old_spk_man) {
+ auto spk_man = GetDescriptorScriptPubKeyMan(desc);
+ if (spk_man) {
WalletLogPrintf("Update existing descriptor: %s\n", desc.descriptor->ToString());
+ spk_man->UpdateWalletDescriptor(desc);
+ } else {
+ auto new_spk_man = std::unique_ptr<DescriptorScriptPubKeyMan>(new DescriptorScriptPubKeyMan(*this, desc));
+ spk_man = new_spk_man.get();
- {
- LOCK(old_spk_man->cs_desc_man);
- new_spk_man->SetCache(old_spk_man->GetWalletDescriptor().cache);
- }
-
- // Remove from maps of active spkMans
- auto old_spk_man_id = old_spk_man->GetID();
- for (bool internal : {false, true}) {
- for (OutputType t : OUTPUT_TYPES) {
- auto active_spk_man = GetScriptPubKeyMan(t, internal);
- if (active_spk_man && active_spk_man->GetID() == old_spk_man_id) {
- if (internal) {
- m_internal_spk_managers.erase(t);
- } else {
- m_external_spk_managers.erase(t);
- }
- break;
- }
- }
- }
- m_spk_managers.erase(old_spk_man_id);
+ // Save the descriptor to memory
+ m_spk_managers[new_spk_man->GetID()] = std::move(new_spk_man);
}
// Add the private keys to the descriptor
for (const auto& entry : signing_provider.keys) {
const CKey& key = entry.second;
- new_spk_man->AddDescriptorKey(key, key.GetPubKey());
+ spk_man->AddDescriptorKey(key, key.GetPubKey());
}
// Top up key pool, the manager will generate new scriptPubKeys internally
- if (!new_spk_man->TopUp()) {
+ if (!spk_man->TopUp()) {
WalletLogPrintf("Could not top up scriptPubKeys\n");
return nullptr;
}
@@ -3241,7 +3264,7 @@ ScriptPubKeyMan* CWallet::AddWalletDescriptor(WalletDescriptor& desc, const Flat
// Apply the label if necessary
// Note: we disable labels for ranged descriptors
if (!desc.descriptor->IsRange()) {
- auto script_pub_keys = new_spk_man->GetScriptPubKeys();
+ auto script_pub_keys = spk_man->GetScriptPubKeys();
if (script_pub_keys.empty()) {
WalletLogPrintf("Could not generate scriptPubKeys (cache is empty)\n");
return nullptr;
@@ -3253,12 +3276,8 @@ ScriptPubKeyMan* CWallet::AddWalletDescriptor(WalletDescriptor& desc, const Flat
}
}
- // Save the descriptor to memory
- auto ret = new_spk_man.get();
- m_spk_managers[new_spk_man->GetID()] = std::move(new_spk_man);
-
// Save the descriptor to DB
- ret->WriteDescriptor();
+ spk_man->WriteDescriptor();
- return ret;
+ return spk_man;
}
diff --git a/src/wallet/wallet.h b/src/wallet/wallet.h
index b63938c5f1..3997751f52 100644
--- a/src/wallet/wallet.h
+++ b/src/wallet/wallet.h
@@ -117,6 +117,7 @@ static constexpr uint64_t KNOWN_WALLET_FLAGS =
WALLET_FLAG_AVOID_REUSE
| WALLET_FLAG_BLANK_WALLET
| WALLET_FLAG_KEY_ORIGIN_METADATA
+ | WALLET_FLAG_LAST_HARDENED_XPUB_CACHED
| WALLET_FLAG_DISABLE_PRIVATE_KEYS
| WALLET_FLAG_DESCRIPTORS
| WALLET_FLAG_EXTERNAL_SIGNER;
@@ -128,6 +129,7 @@ static const std::map<std::string,WalletFlags> WALLET_FLAG_MAP{
{"avoid_reuse", WALLET_FLAG_AVOID_REUSE},
{"blank", WALLET_FLAG_BLANK_WALLET},
{"key_origin_metadata", WALLET_FLAG_KEY_ORIGIN_METADATA},
+ {"last_hardened_xpub_cached", WALLET_FLAG_LAST_HARDENED_XPUB_CACHED},
{"disable_private_keys", WALLET_FLAG_DISABLE_PRIVATE_KEYS},
{"descriptor_wallet", WALLET_FLAG_DESCRIPTORS},
{"external_signer", WALLET_FLAG_EXTERNAL_SIGNER}
@@ -476,6 +478,9 @@ public:
//! Upgrade stored CKeyMetadata objects to store key origin info as KeyOriginInfo
void UpgradeKeyMetadata() EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
+ //! Upgrade DescriptorCaches
+ void UpgradeDescriptorCache() EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
+
bool LoadMinVersion(int nVersion) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet) { AssertLockHeld(cs_wallet); nWalletVersion = nVersion; return true; }
//! Adds a destination data tuple to the store, without saving it to disk
@@ -724,19 +729,18 @@ public:
/**
* Address book entry changed.
- * @note called with lock cs_wallet held.
+ * @note called without lock cs_wallet held.
*/
- boost::signals2::signal<void (CWallet *wallet, const CTxDestination
- &address, const std::string &label, bool isMine,
- const std::string &purpose,
- ChangeType status)> NotifyAddressBookChanged;
+ boost::signals2::signal<void(const CTxDestination& address,
+ const std::string& label, bool isMine,
+ const std::string& purpose, ChangeType status)>
+ NotifyAddressBookChanged;
/**
* Wallet transaction added, removed or updated.
* @note called with lock cs_wallet held.
*/
- boost::signals2::signal<void (CWallet *wallet, const uint256 &hashTx,
- ChangeType status)> NotifyTransactionChanged;
+ boost::signals2::signal<void(const uint256& hashTx, ChangeType status)> NotifyTransactionChanged;
/** Show progress e.g. for rescan */
boost::signals2::signal<void (const std::string &title, int nProgress)> ShowProgress;
@@ -895,6 +899,12 @@ public:
//! @param[in] internal Whether this ScriptPubKeyMan provides change addresses
void LoadActiveScriptPubKeyMan(uint256 id, OutputType type, bool internal);
+ //! Remove specified ScriptPubKeyMan from set of active SPK managers. Writes the change to the wallet file.
+ //! @param[in] id The unique id for the ScriptPubKeyMan
+ //! @param[in] type The OutputType this ScriptPubKeyMan provides addresses for
+ //! @param[in] internal Whether this ScriptPubKeyMan provides change addresses
+ void DeactivateScriptPubKeyMan(uint256 id, OutputType type, bool internal);
+
//! Create new DescriptorScriptPubKeyMans and add them to the wallet
void SetupDescriptorScriptPubKeyMans() EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
diff --git a/src/wallet/walletdb.cpp b/src/wallet/walletdb.cpp
index 24d5351945..1e5d8dfa3a 100644
--- a/src/wallet/walletdb.cpp
+++ b/src/wallet/walletdb.cpp
@@ -52,6 +52,7 @@ const std::string TX{"tx"};
const std::string VERSION{"version"};
const std::string WALLETDESCRIPTOR{"walletdescriptor"};
const std::string WALLETDESCRIPTORCACHE{"walletdescriptorcache"};
+const std::string WALLETDESCRIPTORLHCACHE{"walletdescriptorlhcache"};
const std::string WALLETDESCRIPTORCKEY{"walletdescriptorckey"};
const std::string WALLETDESCRIPTORKEY{"walletdescriptorkey"};
const std::string WATCHMETA{"watchmeta"};
@@ -209,6 +210,12 @@ bool WalletBatch::WriteActiveScriptPubKeyMan(uint8_t type, const uint256& id, bo
return WriteIC(make_pair(key, type), id);
}
+bool WalletBatch::EraseActiveScriptPubKeyMan(uint8_t type, bool internal)
+{
+ const std::string key{internal ? DBKeys::ACTIVEINTERNALSPK : DBKeys::ACTIVEEXTERNALSPK};
+ return EraseIC(make_pair(key, type));
+}
+
bool WalletBatch::WriteDescriptorKey(const uint256& desc_id, const CPubKey& pubkey, const CPrivKey& privkey)
{
// hash pubkey/privkey to accelerate wallet load
@@ -248,6 +255,35 @@ bool WalletBatch::WriteDescriptorParentCache(const CExtPubKey& xpub, const uint2
return WriteIC(std::make_pair(std::make_pair(DBKeys::WALLETDESCRIPTORCACHE, desc_id), key_exp_index), ser_xpub);
}
+bool WalletBatch::WriteDescriptorLastHardenedCache(const CExtPubKey& xpub, const uint256& desc_id, uint32_t key_exp_index)
+{
+ std::vector<unsigned char> ser_xpub(BIP32_EXTKEY_SIZE);
+ xpub.Encode(ser_xpub.data());
+ return WriteIC(std::make_pair(std::make_pair(DBKeys::WALLETDESCRIPTORLHCACHE, desc_id), key_exp_index), ser_xpub);
+}
+
+bool WalletBatch::WriteDescriptorCacheItems(const uint256& desc_id, const DescriptorCache& cache)
+{
+ for (const auto& parent_xpub_pair : cache.GetCachedParentExtPubKeys()) {
+ if (!WriteDescriptorParentCache(parent_xpub_pair.second, desc_id, parent_xpub_pair.first)) {
+ return false;
+ }
+ }
+ for (const auto& derived_xpub_map_pair : cache.GetCachedDerivedExtPubKeys()) {
+ for (const auto& derived_xpub_pair : derived_xpub_map_pair.second) {
+ if (!WriteDescriptorDerivedCache(derived_xpub_pair.second, desc_id, derived_xpub_map_pair.first, derived_xpub_pair.first)) {
+ return false;
+ }
+ }
+ }
+ for (const auto& lh_xpub_pair : cache.GetCachedLastHardenedExtPubKeys()) {
+ if (!WriteDescriptorLastHardenedCache(lh_xpub_pair.second, desc_id, lh_xpub_pair.first)) {
+ return false;
+ }
+ }
+ return true;
+}
+
class CWalletScanState {
public:
unsigned int nKeys{0};
@@ -602,6 +638,17 @@ ReadKeyValue(CWallet* pwallet, CDataStream& ssKey, CDataStream& ssValue,
} else {
wss.m_descriptor_caches[desc_id].CacheDerivedExtPubKey(key_exp_index, der_index, xpub);
}
+ } else if (strType == DBKeys::WALLETDESCRIPTORLHCACHE) {
+ uint256 desc_id;
+ uint32_t key_exp_index;
+ ssKey >> desc_id;
+ ssKey >> key_exp_index;
+
+ std::vector<unsigned char> ser_xpub(BIP32_EXTKEY_SIZE);
+ ssValue >> ser_xpub;
+ CExtPubKey xpub;
+ xpub.Decode(ser_xpub.data());
+ wss.m_descriptor_caches[desc_id].CacheLastHardenedExtPubKey(key_exp_index, xpub);
} else if (strType == DBKeys::WALLETDESCRIPTORKEY) {
uint256 desc_id;
CPubKey pubkey;
@@ -843,6 +890,14 @@ DBErrors WalletBatch::LoadWallet(CWallet* pwallet)
result = DBErrors::CORRUPT;
}
+ // Upgrade all of the descriptor caches to cache the last hardened xpub
+ // This operation is not atomic, but if it fails, only new entries are added so it is backwards compatible
+ try {
+ pwallet->UpgradeDescriptorCache();
+ } catch (...) {
+ result = DBErrors::CORRUPT;
+ }
+
// Set the inactive chain
if (wss.m_hd_chains.size() > 0) {
LegacyScriptPubKeyMan* legacy_spkm = pwallet->GetLegacyScriptPubKeyMan();
diff --git a/src/wallet/walletdb.h b/src/wallet/walletdb.h
index e7b2d7d780..9b775eb481 100644
--- a/src/wallet/walletdb.h
+++ b/src/wallet/walletdb.h
@@ -246,6 +246,8 @@ public:
bool WriteDescriptor(const uint256& desc_id, const WalletDescriptor& descriptor);
bool WriteDescriptorDerivedCache(const CExtPubKey& xpub, const uint256& desc_id, uint32_t key_exp_index, uint32_t der_index);
bool WriteDescriptorParentCache(const CExtPubKey& xpub, const uint256& desc_id, uint32_t key_exp_index);
+ bool WriteDescriptorLastHardenedCache(const CExtPubKey& xpub, const uint256& desc_id, uint32_t key_exp_index);
+ bool WriteDescriptorCacheItems(const uint256& desc_id, const DescriptorCache& cache);
/// Write destination data key,value tuple to database
bool WriteDestData(const std::string &address, const std::string &key, const std::string &value);
@@ -253,6 +255,7 @@ public:
bool EraseDestData(const std::string &address, const std::string &key);
bool WriteActiveScriptPubKeyMan(uint8_t type, const uint256& id, bool internal);
+ bool EraseActiveScriptPubKeyMan(uint8_t type, bool internal);
DBErrors LoadWallet(CWallet* pwallet);
DBErrors FindWalletTx(std::vector<uint256>& vTxHash, std::list<CWalletTx>& vWtx);
diff --git a/src/wallet/walletutil.h b/src/wallet/walletutil.h
index 0713f768c1..c75e1759bc 100644
--- a/src/wallet/walletutil.h
+++ b/src/wallet/walletutil.h
@@ -43,6 +43,9 @@ enum WalletFlags : uint64_t {
// Indicates that the metadata has already been upgraded to contain key origins
WALLET_FLAG_KEY_ORIGIN_METADATA = (1ULL << 1),
+ // Indicates that the descriptor cache has been upgraded to cache last hardened xpubs
+ WALLET_FLAG_LAST_HARDENED_XPUB_CACHED = (1ULL << 2),
+
// will enforce the rule that the wallet can't contain any private keys (only watch-only/pubkeys)
WALLET_FLAG_DISABLE_PRIVATE_KEYS = (1ULL << 32),