From bf68ebc1cd555f791103f81adc9111e0e55c8003 Mon Sep 17 00:00:00 2001 From: S3RK <1466284+S3RK@users.noreply.github.com> Date: Mon, 28 Jun 2021 21:37:37 +0200 Subject: wallet: allow to import same descriptor twice --- src/wallet/rpcdump.cpp | 5 ++--- src/wallet/scriptpubkeyman.cpp | 40 +++++++++++++++++++++++++++++++++++ src/wallet/scriptpubkeyman.h | 2 ++ src/wallet/wallet.cpp | 48 ++++++++++++------------------------------ 4 files changed, 57 insertions(+), 38 deletions(-) (limited to 'src/wallet') diff --git a/src/wallet/rpcdump.cpp b/src/wallet/rpcdump.cpp index 35649ab02c..3ae36131cc 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); } } diff --git a/src/wallet/scriptpubkeyman.cpp b/src/wallet/scriptpubkeyman.cpp index 44c3912544..8b397fa1f3 100644 --- a/src/wallet/scriptpubkeyman.cpp +++ b/src/wallet/scriptpubkeyman.cpp @@ -1875,6 +1875,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; @@ -2302,3 +2308,37 @@ bool DescriptorScriptPubKeyMan::GetDescriptorString(std::string& out, bool priv) return m_wallet_descriptor.descriptor->ToNormalizedString(provider, out, priv); } + +void DescriptorScriptPubKeyMan::UpdateWalletDescriptor(WalletDescriptor& descriptor) +{ + LOCK(cs_desc_man); + std::string error; + if (!CanUpdateToWalletDescriptor(descriptor, error)) { + throw std::runtime_error(std::string(__func__) + ": " + error); + } + + 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 b2ca354b0a..f2d1d87d55 100644 --- a/src/wallet/scriptpubkeyman.h +++ b/src/wallet/scriptpubkeyman.h @@ -624,6 +624,8 @@ public: bool AddCryptedKey(const CKeyID& key_id, const CPubKey& pubkey, const std::vector& 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(); diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp index c2586b60b8..8750f508a7 100644 --- a/src/wallet/wallet.cpp +++ b/src/wallet/wallet.cpp @@ -3192,44 +3192,26 @@ ScriptPubKeyMan* CWallet::AddWalletDescriptor(WalletDescriptor& desc, const Flat } LOCK(cs_wallet); - auto new_spk_man = std::unique_ptr(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(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; } @@ -3237,7 +3219,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; @@ -3249,12 +3231,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; } -- cgit v1.2.3 From f1b7db14748d9ee04735b4968366d33bc89aea23 Mon Sep 17 00:00:00 2001 From: S3RK <1466284+S3RK@users.noreply.github.com> Date: Mon, 28 Jun 2021 21:37:44 +0200 Subject: wallet: don't mute exceptions in importdescriptors --- src/wallet/rpcdump.cpp | 4 ---- 1 file changed, 4 deletions(-) (limited to 'src/wallet') diff --git a/src/wallet/rpcdump.cpp b/src/wallet/rpcdump.cpp index 3ae36131cc..5f0d88288f 100644 --- a/src/wallet/rpcdump.cpp +++ b/src/wallet/rpcdump.cpp @@ -1590,10 +1590,6 @@ static UniValue ProcessDescriptorImport(CWallet& wallet, const UniValue& data, c } 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; -- cgit v1.2.3 From 586f1d53d60880ea2873d860f95e3390016620d1 Mon Sep 17 00:00:00 2001 From: S3RK <1466284+S3RK@users.noreply.github.com> Date: Mon, 28 Jun 2021 21:37:47 +0200 Subject: wallet: maintain SPK consistency on internal flag change --- src/wallet/wallet.cpp | 9 +++++++++ 1 file changed, 9 insertions(+) (limited to 'src/wallet') diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp index 8750f508a7..5ae490d5cc 100644 --- a/src/wallet/wallet.cpp +++ b/src/wallet/wallet.cpp @@ -3153,12 +3153,21 @@ 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(type), static_cast(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_man->SetInternal(internal); spk_mans[type] = spk_man; + if (spk_mans_other[type] == spk_man) { + spk_mans_other[type] = nullptr; + } + NotifyCanGetAddressesChanged(); } -- cgit v1.2.3 From 3efaf83c75cd8dc2fa084537b8ed6715fb58c04d Mon Sep 17 00:00:00 2001 From: S3RK <1466284+S3RK@users.noreply.github.com> Date: Mon, 28 Jun 2021 21:37:53 +0200 Subject: wallet: deactivate descriptor --- src/wallet/rpcdump.cpp | 4 ++++ src/wallet/wallet.cpp | 17 +++++++++++++++++ src/wallet/wallet.h | 6 ++++++ src/wallet/walletdb.cpp | 6 ++++++ src/wallet/walletdb.h | 1 + 5 files changed, 34 insertions(+) (limited to 'src/wallet') diff --git a/src/wallet/rpcdump.cpp b/src/wallet/rpcdump.cpp index 5f0d88288f..7de12c3a94 100644 --- a/src/wallet/rpcdump.cpp +++ b/src/wallet/rpcdump.cpp @@ -1584,6 +1584,10 @@ 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)); diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp index 5ae490d5cc..521708e69c 100644 --- a/src/wallet/wallet.cpp +++ b/src/wallet/wallet.cpp @@ -3171,6 +3171,23 @@ void CWallet::LoadActiveScriptPubKeyMan(uint256 id, OutputType type, bool intern 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(type), static_cast(internal)); + WalletBatch batch(GetDatabase()); + if (!batch.EraseActiveScriptPubKeyMan(static_cast(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(); +} + bool CWallet::IsLegacy() const { if (m_internal_spk_managers.count(OutputType::LEGACY) == 0) { diff --git a/src/wallet/wallet.h b/src/wallet/wallet.h index b63938c5f1..001b94047a 100644 --- a/src/wallet/wallet.h +++ b/src/wallet/wallet.h @@ -895,6 +895,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..203fca8dd6 100644 --- a/src/wallet/walletdb.cpp +++ b/src/wallet/walletdb.cpp @@ -209,6 +209,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 diff --git a/src/wallet/walletdb.h b/src/wallet/walletdb.h index e7b2d7d780..d740aaadb3 100644 --- a/src/wallet/walletdb.h +++ b/src/wallet/walletdb.h @@ -253,6 +253,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& vTxHash, std::list& vWtx); -- cgit v1.2.3