diff --git a/src/wallet/scriptpubkeyman.cpp b/src/wallet/scriptpubkeyman.cpp index c64aff5fe2..e4632777cc 100644 --- a/src/wallet/scriptpubkeyman.cpp +++ b/src/wallet/scriptpubkeyman.cpp @@ -84,7 +84,7 @@ bool PermitsUncompressed(IsMineSigVersion sigversion) return sigversion == IsMineSigVersion::TOP || sigversion == IsMineSigVersion::P2SH; } -bool HaveKeys(const std::vector& pubkeys, const LegacyScriptPubKeyMan& keystore) +bool HaveKeys(const std::vector& pubkeys, const LegacyDataSPKM& keystore) { for (const valtype& pubkey : pubkeys) { CKeyID keyID = CPubKey(pubkey).GetID(); @@ -102,7 +102,7 @@ bool HaveKeys(const std::vector& pubkeys, const LegacyScriptPubKeyMan& //! scripts or simply treat any script that has been //! stored in the keystore as spendable // NOLINTNEXTLINE(misc-no-recursion) -IsMineResult IsMineInner(const LegacyScriptPubKeyMan& keystore, const CScript& scriptPubKey, IsMineSigVersion sigversion, bool recurse_scripthash=true) +IsMineResult IsMineInner(const LegacyDataSPKM& keystore, const CScript& scriptPubKey, IsMineSigVersion sigversion, bool recurse_scripthash=true) { IsMineResult ret = IsMineResult::NO; @@ -217,7 +217,7 @@ IsMineResult IsMineInner(const LegacyScriptPubKeyMan& keystore, const CScript& s } // namespace -isminetype LegacyScriptPubKeyMan::IsMine(const CScript& script) const +isminetype LegacyDataSPKM::IsMine(const CScript& script) const { switch (IsMineInner(*this, script, IsMineSigVersion::TOP)) { case IsMineResult::INVALID: @@ -231,7 +231,7 @@ isminetype LegacyScriptPubKeyMan::IsMine(const CScript& script) const assert(false); } -bool LegacyScriptPubKeyMan::CheckDecryptionKey(const CKeyingMaterial& master_key) +bool LegacyDataSPKM::CheckDecryptionKey(const CKeyingMaterial& master_key) { { LOCK(cs_KeyStore); @@ -585,7 +585,7 @@ int64_t LegacyScriptPubKeyMan::GetTimeFirstKey() const return nTimeFirstKey; } -std::unique_ptr LegacyScriptPubKeyMan::GetSolvingProvider(const CScript& script) const +std::unique_ptr LegacyDataSPKM::GetSolvingProvider(const CScript& script) const { return std::make_unique(*this); } @@ -721,7 +721,7 @@ void LegacyScriptPubKeyMan::UpdateTimeFirstKey(int64_t nCreateTime) NotifyFirstKeyTimeChanged(this, nTimeFirstKey); } -bool LegacyScriptPubKeyMan::LoadKey(const CKey& key, const CPubKey &pubkey) +bool LegacyDataSPKM::LoadKey(const CKey& key, const CPubKey &pubkey) { return AddKeyPubKeyInner(key, pubkey); } @@ -773,7 +773,7 @@ bool LegacyScriptPubKeyMan::AddKeyPubKeyWithDB(WalletBatch& batch, const CKey& s return true; } -bool LegacyScriptPubKeyMan::LoadCScript(const CScript& redeemScript) +bool LegacyDataSPKM::LoadCScript(const CScript& redeemScript) { /* A sanity check was added in pull #3843 to avoid adding redeemScripts * that never can be redeemed. However, old wallets may still contain @@ -788,18 +788,36 @@ bool LegacyScriptPubKeyMan::LoadCScript(const CScript& redeemScript) return FillableSigningProvider::AddCScript(redeemScript); } +void LegacyDataSPKM::LoadKeyMetadata(const CKeyID& keyID, const CKeyMetadata& meta) +{ + LOCK(cs_KeyStore); + mapKeyMetadata[keyID] = meta; +} + void LegacyScriptPubKeyMan::LoadKeyMetadata(const CKeyID& keyID, const CKeyMetadata& meta) { LOCK(cs_KeyStore); + LegacyDataSPKM::LoadKeyMetadata(keyID, meta); UpdateTimeFirstKey(meta.nCreateTime); - mapKeyMetadata[keyID] = meta; +} + +void LegacyDataSPKM::LoadScriptMetadata(const CScriptID& script_id, const CKeyMetadata& meta) +{ + LOCK(cs_KeyStore); + m_script_metadata[script_id] = meta; } void LegacyScriptPubKeyMan::LoadScriptMetadata(const CScriptID& script_id, const CKeyMetadata& meta) { LOCK(cs_KeyStore); + LegacyDataSPKM::LoadScriptMetadata(script_id, meta); UpdateTimeFirstKey(meta.nCreateTime); - m_script_metadata[script_id] = meta; +} + +bool LegacyDataSPKM::AddKeyPubKeyInner(const CKey& key, const CPubKey& pubkey) +{ + LOCK(cs_KeyStore); + return FillableSigningProvider::AddKeyPubKey(key, pubkey); } bool LegacyScriptPubKeyMan::AddKeyPubKeyInner(const CKey& key, const CPubKey &pubkey) @@ -827,7 +845,7 @@ bool LegacyScriptPubKeyMan::AddKeyPubKeyInner(const CKey& key, const CPubKey &pu return true; } -bool LegacyScriptPubKeyMan::LoadCryptedKey(const CPubKey &vchPubKey, const std::vector &vchCryptedSecret, bool checksum_valid) +bool LegacyDataSPKM::LoadCryptedKey(const CPubKey &vchPubKey, const std::vector &vchCryptedSecret, bool checksum_valid) { // Set fDecryptionThoroughlyChecked to false when the checksum is invalid if (!checksum_valid) { @@ -837,7 +855,7 @@ bool LegacyScriptPubKeyMan::LoadCryptedKey(const CPubKey &vchPubKey, const std:: return AddCryptedKeyInner(vchPubKey, vchCryptedSecret); } -bool LegacyScriptPubKeyMan::AddCryptedKeyInner(const CPubKey &vchPubKey, const std::vector &vchCryptedSecret) +bool LegacyDataSPKM::AddCryptedKeyInner(const CPubKey &vchPubKey, const std::vector &vchCryptedSecret) { LOCK(cs_KeyStore); assert(mapKeys.empty()); @@ -865,13 +883,13 @@ bool LegacyScriptPubKeyMan::AddCryptedKey(const CPubKey &vchPubKey, } } -bool LegacyScriptPubKeyMan::HaveWatchOnly(const CScript &dest) const +bool LegacyDataSPKM::HaveWatchOnly(const CScript &dest) const { LOCK(cs_KeyStore); return setWatchOnly.count(dest) > 0; } -bool LegacyScriptPubKeyMan::HaveWatchOnly() const +bool LegacyDataSPKM::HaveWatchOnly() const { LOCK(cs_KeyStore); return (!setWatchOnly.empty()); @@ -905,12 +923,12 @@ bool LegacyScriptPubKeyMan::RemoveWatchOnly(const CScript &dest) return true; } -bool LegacyScriptPubKeyMan::LoadWatchOnly(const CScript &dest) +bool LegacyDataSPKM::LoadWatchOnly(const CScript &dest) { return AddWatchOnlyInMem(dest); } -bool LegacyScriptPubKeyMan::AddWatchOnlyInMem(const CScript &dest) +bool LegacyDataSPKM::AddWatchOnlyInMem(const CScript &dest) { LOCK(cs_KeyStore); setWatchOnly.insert(dest); @@ -954,7 +972,7 @@ bool LegacyScriptPubKeyMan::AddWatchOnly(const CScript& dest, int64_t nCreateTim return AddWatchOnly(dest); } -void LegacyScriptPubKeyMan::LoadHDChain(const CHDChain& chain) +void LegacyDataSPKM::LoadHDChain(const CHDChain& chain) { LOCK(cs_KeyStore); m_hd_chain = chain; @@ -975,14 +993,14 @@ void LegacyScriptPubKeyMan::AddHDChain(const CHDChain& chain) m_hd_chain = chain; } -void LegacyScriptPubKeyMan::AddInactiveHDChain(const CHDChain& chain) +void LegacyDataSPKM::AddInactiveHDChain(const CHDChain& chain) { LOCK(cs_KeyStore); assert(!chain.seed_id.IsNull()); m_inactive_hd_chains[chain.seed_id] = chain; } -bool LegacyScriptPubKeyMan::HaveKey(const CKeyID &address) const +bool LegacyDataSPKM::HaveKey(const CKeyID &address) const { LOCK(cs_KeyStore); if (!m_storage.HasEncryptionKeys()) { @@ -991,7 +1009,7 @@ bool LegacyScriptPubKeyMan::HaveKey(const CKeyID &address) const return mapCryptedKeys.count(address) > 0; } -bool LegacyScriptPubKeyMan::GetKey(const CKeyID &address, CKey& keyOut) const +bool LegacyDataSPKM::GetKey(const CKeyID &address, CKey& keyOut) const { LOCK(cs_KeyStore); if (!m_storage.HasEncryptionKeys()) { @@ -1010,7 +1028,7 @@ bool LegacyScriptPubKeyMan::GetKey(const CKeyID &address, CKey& keyOut) const return false; } -bool LegacyScriptPubKeyMan::GetKeyOrigin(const CKeyID& keyID, KeyOriginInfo& info) const +bool LegacyDataSPKM::GetKeyOrigin(const CKeyID& keyID, KeyOriginInfo& info) const { CKeyMetadata meta; { @@ -1030,7 +1048,7 @@ bool LegacyScriptPubKeyMan::GetKeyOrigin(const CKeyID& keyID, KeyOriginInfo& inf return true; } -bool LegacyScriptPubKeyMan::GetWatchPubKey(const CKeyID &address, CPubKey &pubkey_out) const +bool LegacyDataSPKM::GetWatchPubKey(const CKeyID &address, CPubKey &pubkey_out) const { LOCK(cs_KeyStore); WatchKeyMap::const_iterator it = mapWatchKeys.find(address); @@ -1041,7 +1059,7 @@ bool LegacyScriptPubKeyMan::GetWatchPubKey(const CKeyID &address, CPubKey &pubke return false; } -bool LegacyScriptPubKeyMan::GetPubKey(const CKeyID &address, CPubKey& vchPubKeyOut) const +bool LegacyDataSPKM::GetPubKey(const CKeyID &address, CPubKey& vchPubKeyOut) const { LOCK(cs_KeyStore); if (!m_storage.HasEncryptionKeys()) { @@ -1160,7 +1178,7 @@ void LegacyScriptPubKeyMan::DeriveNewChildKey(WalletBatch &batch, CKeyMetadata& throw std::runtime_error(std::string(__func__) + ": writing HD chain model failed"); } -void LegacyScriptPubKeyMan::LoadKeyPool(int64_t nIndex, const CKeyPool &keypool) +void LegacyDataSPKM::LoadKeyPool(int64_t nIndex, const CKeyPool &keypool) { LOCK(cs_KeyStore); if (keypool.m_pre_split) { @@ -1681,7 +1699,7 @@ std::set LegacyScriptPubKeyMan::GetKeys() const return set_address; } -std::unordered_set LegacyScriptPubKeyMan::GetScriptPubKeys() const +std::unordered_set LegacyDataSPKM::GetScriptPubKeys() const { LOCK(cs_KeyStore); std::unordered_set spks; @@ -1739,7 +1757,7 @@ std::unordered_set LegacyScriptPubKeyMan::GetScriptPub return spks; } -std::unordered_set LegacyScriptPubKeyMan::GetNotMineScriptPubKeys() const +std::unordered_set LegacyDataSPKM::GetNotMineScriptPubKeys() const { LOCK(cs_KeyStore); std::unordered_set spks; @@ -1749,7 +1767,7 @@ std::unordered_set LegacyScriptPubKeyMan::GetNotMineSc return spks; } -std::optional LegacyScriptPubKeyMan::MigrateToDescriptor() +std::optional LegacyDataSPKM::MigrateToDescriptor() { LOCK(cs_KeyStore); if (m_storage.IsLocked()) { @@ -1816,7 +1834,7 @@ std::optional LegacyScriptPubKeyMan::MigrateToDescriptor() WalletDescriptor w_desc(std::move(desc), creation_time, 0, 0, 0); // Make the DescriptorScriptPubKeyMan and get the scriptPubKeys - auto desc_spk_man = std::unique_ptr(new DescriptorScriptPubKeyMan(m_storage, w_desc, m_keypool_size)); + auto desc_spk_man = std::make_unique(m_storage, w_desc, /*keypool_size=*/0); desc_spk_man->AddDescriptorKey(key, key.GetPubKey()); desc_spk_man->TopUp(); auto desc_spks = desc_spk_man->GetScriptPubKeys(); @@ -1861,7 +1879,7 @@ std::optional LegacyScriptPubKeyMan::MigrateToDescriptor() WalletDescriptor w_desc(std::move(desc), 0, 0, chain_counter, 0); // Make the DescriptorScriptPubKeyMan and get the scriptPubKeys - auto desc_spk_man = std::unique_ptr(new DescriptorScriptPubKeyMan(m_storage, w_desc, m_keypool_size)); + auto desc_spk_man = std::make_unique(m_storage, w_desc, /*keypool_size=*/0); desc_spk_man->AddDescriptorKey(master_key.key, master_key.key.GetPubKey()); desc_spk_man->TopUp(); auto desc_spks = desc_spk_man->GetScriptPubKeys(); @@ -1923,7 +1941,7 @@ std::optional LegacyScriptPubKeyMan::MigrateToDescriptor() } else { // Make the DescriptorScriptPubKeyMan and get the scriptPubKeys WalletDescriptor w_desc(std::move(desc), creation_time, 0, 0, 0); - auto desc_spk_man = std::unique_ptr(new DescriptorScriptPubKeyMan(m_storage, w_desc, m_keypool_size)); + auto desc_spk_man = std::make_unique(m_storage, w_desc, /*keypool_size=*/0); for (const auto& keyid : privkeyids) { CKey key; if (!GetKey(keyid, key)) { @@ -2001,7 +2019,7 @@ std::optional LegacyScriptPubKeyMan::MigrateToDescriptor() return out; } -bool LegacyScriptPubKeyMan::DeleteRecords() +bool LegacyDataSPKM::DeleteRecords() { LOCK(cs_KeyStore); WalletBatch batch(m_storage.GetDatabase()); diff --git a/src/wallet/scriptpubkeyman.h b/src/wallet/scriptpubkeyman.h index 4d9f7bb1fa..bb0294b22e 100644 --- a/src/wallet/scriptpubkeyman.h +++ b/src/wallet/scriptpubkeyman.h @@ -278,31 +278,111 @@ static const std::unordered_set LEGACY_OUTPUT_TYPES { class DescriptorScriptPubKeyMan; -class LegacyScriptPubKeyMan : public ScriptPubKeyMan, public FillableSigningProvider +// Manages the data for a LegacyScriptPubKeyMan. +// This is the minimum necessary to load a legacy wallet so that it can be migrated. +class LegacyDataSPKM : public ScriptPubKeyMan, public FillableSigningProvider { -private: - //! keeps track of whether Unlock has run a thorough check before - bool fDecryptionThoroughlyChecked = true; - +protected: using WatchOnlySet = std::set; using WatchKeyMap = std::map; - - WalletBatch *encrypted_batch GUARDED_BY(cs_KeyStore) = nullptr; - using CryptedKeyMap = std::map>>; CryptedKeyMap mapCryptedKeys GUARDED_BY(cs_KeyStore); WatchOnlySet setWatchOnly GUARDED_BY(cs_KeyStore); WatchKeyMap mapWatchKeys GUARDED_BY(cs_KeyStore); + /* the HD chain data model (external chain counters) */ + CHDChain m_hd_chain; + std::unordered_map m_inactive_hd_chains; + + //! keeps track of whether Unlock has run a thorough check before + bool fDecryptionThoroughlyChecked = true; + + bool AddWatchOnlyInMem(const CScript &dest); + virtual bool AddKeyPubKeyInner(const CKey& key, const CPubKey &pubkey); + bool AddCryptedKeyInner(const CPubKey &vchPubKey, const std::vector &vchCryptedSecret); + +public: + using ScriptPubKeyMan::ScriptPubKeyMan; + + // Map from Key ID to key metadata. + std::map mapKeyMetadata GUARDED_BY(cs_KeyStore); + + // Map from Script ID to key metadata (for watch-only keys). + std::map m_script_metadata GUARDED_BY(cs_KeyStore); + + // ScriptPubKeyMan overrides + bool CheckDecryptionKey(const CKeyingMaterial& master_key) override; + std::unordered_set GetScriptPubKeys() const override; + std::unique_ptr GetSolvingProvider(const CScript& script) const override; + uint256 GetID() const override { return uint256::ONE; } + // TODO: Remove IsMine when deleting LegacyScriptPubKeyMan + isminetype IsMine(const CScript& script) const override; + + // FillableSigningProvider overrides + bool HaveKey(const CKeyID &address) const override; + bool GetKey(const CKeyID &address, CKey& keyOut) const override; + bool GetPubKey(const CKeyID &address, CPubKey& vchPubKeyOut) const override; + bool GetKeyOrigin(const CKeyID& keyid, KeyOriginInfo& info) const override; + + std::set setInternalKeyPool GUARDED_BY(cs_KeyStore); + std::set setExternalKeyPool GUARDED_BY(cs_KeyStore); + std::set set_pre_split_keypool GUARDED_BY(cs_KeyStore); + int64_t m_max_keypool_index GUARDED_BY(cs_KeyStore) = 0; + std::map m_pool_key_to_index; + + //! Load metadata (used by LoadWallet) + virtual void LoadKeyMetadata(const CKeyID& keyID, const CKeyMetadata &metadata); + virtual void LoadScriptMetadata(const CScriptID& script_id, const CKeyMetadata &metadata); + + //! Adds a watch-only address to the store, without saving it to disk (used by LoadWallet) + bool LoadWatchOnly(const CScript &dest); + //! Returns whether the watch-only script is in the wallet + bool HaveWatchOnly(const CScript &dest) const; + //! Returns whether there are any watch-only things in the wallet + bool HaveWatchOnly() const; + //! Adds a key to the store, without saving it to disk (used by LoadWallet) + bool LoadKey(const CKey& key, const CPubKey &pubkey); + //! Adds an encrypted key to the store, without saving it to disk (used by LoadWallet) + bool LoadCryptedKey(const CPubKey &vchPubKey, const std::vector &vchCryptedSecret, bool checksum_valid); + //! Adds a CScript to the store + bool LoadCScript(const CScript& redeemScript); + //! Load a HD chain model (used by LoadWallet) + void LoadHDChain(const CHDChain& chain); + void AddInactiveHDChain(const CHDChain& chain); + const CHDChain& GetHDChain() const { return m_hd_chain; } + //! Load a keypool entry + void LoadKeyPool(int64_t nIndex, const CKeyPool &keypool); + + //! Fetches a pubkey from mapWatchKeys if it exists there + bool GetWatchPubKey(const CKeyID &address, CPubKey &pubkey_out) const; + + /** + * Retrieves scripts that were imported by bugs into the legacy spkm and are + * simply invalid, such as a sh(sh(pkh())) script, or not watched. + */ + std::unordered_set GetNotMineScriptPubKeys() const; + + /** Get the DescriptorScriptPubKeyMans (with private keys) that have the same scriptPubKeys as this LegacyScriptPubKeyMan. + * Does not modify this ScriptPubKeyMan. */ + std::optional MigrateToDescriptor(); + /** Delete all the records ofthis LegacyScriptPubKeyMan from disk*/ + bool DeleteRecords(); +}; + +// Implements the full legacy wallet behavior +class LegacyScriptPubKeyMan : public LegacyDataSPKM +{ +private: + WalletBatch *encrypted_batch GUARDED_BY(cs_KeyStore) = nullptr; + // By default, do not scan any block until keys/scripts are generated/imported int64_t nTimeFirstKey GUARDED_BY(cs_KeyStore) = UNKNOWN_TIME; //! Number of pre-generated keys/scripts (part of the look-ahead process, used to detect payments) int64_t m_keypool_size GUARDED_BY(cs_KeyStore){DEFAULT_KEYPOOL_SIZE}; - bool AddKeyPubKeyInner(const CKey& key, const CPubKey &pubkey); - bool AddCryptedKeyInner(const CPubKey &vchPubKey, const std::vector &vchCryptedSecret); + bool AddKeyPubKeyInner(const CKey& key, const CPubKey &pubkey) override; /** * Private version of AddWatchOnly method which does not accept a @@ -315,7 +395,6 @@ private: */ bool AddWatchOnly(const CScript& dest) EXCLUSIVE_LOCKS_REQUIRED(cs_KeyStore); bool AddWatchOnlyWithDB(WalletBatch &batch, const CScript& dest) EXCLUSIVE_LOCKS_REQUIRED(cs_KeyStore); - bool AddWatchOnlyInMem(const CScript &dest); //! Adds a watch-only address to the store, and saves it to disk. bool AddWatchOnlyWithDB(WalletBatch &batch, const CScript& dest, int64_t create_time) EXCLUSIVE_LOCKS_REQUIRED(cs_KeyStore); @@ -330,18 +409,9 @@ private: /** Add a KeyOriginInfo to the wallet */ bool AddKeyOriginWithDB(WalletBatch& batch, const CPubKey& pubkey, const KeyOriginInfo& info); - /* the HD chain data model (external chain counters) */ - CHDChain m_hd_chain; - std::unordered_map m_inactive_hd_chains; - /* HD derive new child key (on internal or external chain) */ void DeriveNewChildKey(WalletBatch& batch, CKeyMetadata& metadata, CKey& secret, CHDChain& hd_chain, bool internal = false) EXCLUSIVE_LOCKS_REQUIRED(cs_KeyStore); - std::set setInternalKeyPool GUARDED_BY(cs_KeyStore); - std::set setExternalKeyPool GUARDED_BY(cs_KeyStore); - std::set set_pre_split_keypool GUARDED_BY(cs_KeyStore); - int64_t m_max_keypool_index GUARDED_BY(cs_KeyStore) = 0; - std::map m_pool_key_to_index; // Tracks keypool indexes to CKeyIDs of keys that have been taken out of the keypool but may be returned to it std::map m_index_to_reserved_key; @@ -378,12 +448,10 @@ private: bool TopUpChain(WalletBatch& batch, CHDChain& chain, unsigned int size); public: - LegacyScriptPubKeyMan(WalletStorage& storage, int64_t keypool_size) : ScriptPubKeyMan(storage), m_keypool_size(keypool_size) {} + LegacyScriptPubKeyMan(WalletStorage& storage, int64_t keypool_size) : LegacyDataSPKM(storage), m_keypool_size(keypool_size) {} util::Result GetNewDestination(const OutputType type) override; - isminetype IsMine(const CScript& script) const override; - bool CheckDecryptionKey(const CKeyingMaterial& master_key) override; bool Encrypt(const CKeyingMaterial& master_key, WalletBatch* batch) override; util::Result GetReservedDestination(const OutputType type, bool internal, int64_t& index, CKeyPool& keypool) override; @@ -417,8 +485,6 @@ public: bool CanGetAddresses(bool internal = false) const override; - std::unique_ptr GetSolvingProvider(const CScript& script) const override; - bool CanProvide(const CScript& script, SignatureData& sigdata) override; bool SignTransaction(CMutableTransaction& tx, const std::map& coins, int sighash, std::map& input_errors) const override; @@ -427,58 +493,27 @@ public: uint256 GetID() const override; - // Map from Key ID to key metadata. - std::map mapKeyMetadata GUARDED_BY(cs_KeyStore); - - // Map from Script ID to key metadata (for watch-only keys). - std::map m_script_metadata GUARDED_BY(cs_KeyStore); - //! Adds a key to the store, and saves it to disk. bool AddKeyPubKey(const CKey& key, const CPubKey &pubkey) override; - //! Adds a key to the store, without saving it to disk (used by LoadWallet) - bool LoadKey(const CKey& key, const CPubKey &pubkey); //! Adds an encrypted key to the store, and saves it to disk. bool AddCryptedKey(const CPubKey &vchPubKey, const std::vector &vchCryptedSecret); - //! Adds an encrypted key to the store, without saving it to disk (used by LoadWallet) - bool LoadCryptedKey(const CPubKey &vchPubKey, const std::vector &vchCryptedSecret, bool checksum_valid); void UpdateTimeFirstKey(int64_t nCreateTime) EXCLUSIVE_LOCKS_REQUIRED(cs_KeyStore); - //! Adds a CScript to the store - bool LoadCScript(const CScript& redeemScript); //! Load metadata (used by LoadWallet) - void LoadKeyMetadata(const CKeyID& keyID, const CKeyMetadata &metadata); - void LoadScriptMetadata(const CScriptID& script_id, const CKeyMetadata &metadata); + void LoadKeyMetadata(const CKeyID& keyID, const CKeyMetadata &metadata) override; + void LoadScriptMetadata(const CScriptID& script_id, const CKeyMetadata &metadata) override; //! Generate a new key CPubKey GenerateNewKey(WalletBatch& batch, CHDChain& hd_chain, bool internal = false) EXCLUSIVE_LOCKS_REQUIRED(cs_KeyStore); /* Set the HD chain model (chain child index counters) and writes it to the database */ void AddHDChain(const CHDChain& chain); - //! Load a HD chain model (used by LoadWallet) - void LoadHDChain(const CHDChain& chain); - const CHDChain& GetHDChain() const { return m_hd_chain; } - void AddInactiveHDChain(const CHDChain& chain); - //! Adds a watch-only address to the store, without saving it to disk (used by LoadWallet) - bool LoadWatchOnly(const CScript &dest); - //! Returns whether the watch-only script is in the wallet - bool HaveWatchOnly(const CScript &dest) const; - //! Returns whether there are any watch-only things in the wallet - bool HaveWatchOnly() const; //! Remove a watch only script from the keystore bool RemoveWatchOnly(const CScript &dest); bool AddWatchOnly(const CScript& dest, int64_t nCreateTime) EXCLUSIVE_LOCKS_REQUIRED(cs_KeyStore); - //! Fetches a pubkey from mapWatchKeys if it exists there - bool GetWatchPubKey(const CKeyID &address, CPubKey &pubkey_out) const; - /* SigningProvider overrides */ - bool HaveKey(const CKeyID &address) const override; - bool GetKey(const CKeyID &address, CKey& keyOut) const override; - bool GetPubKey(const CKeyID &address, CPubKey& vchPubKeyOut) const override; bool AddCScript(const CScript& redeemScript) override; - bool GetKeyOrigin(const CKeyID& keyid, KeyOriginInfo& info) const override; - //! Load a keypool entry - void LoadKeyPool(int64_t nIndex, const CKeyPool &keypool); bool NewKeyPool(); void MarkPreSplitKeys() EXCLUSIVE_LOCKS_REQUIRED(cs_KeyStore); @@ -527,28 +562,15 @@ public: const std::map& GetAllReserveKeys() const { return m_pool_key_to_index; } std::set GetKeys() const override; - std::unordered_set GetScriptPubKeys() const override; - - /** - * Retrieves scripts that were imported by bugs into the legacy spkm and are - * simply invalid, such as a sh(sh(pkh())) script, or not watched. - */ - std::unordered_set GetNotMineScriptPubKeys() const; - - /** Get the DescriptorScriptPubKeyMans (with private keys) that have the same scriptPubKeys as this LegacyScriptPubKeyMan. - * Does not modify this ScriptPubKeyMan. */ - std::optional MigrateToDescriptor(); - /** Delete all the records ofthis LegacyScriptPubKeyMan from disk*/ - bool DeleteRecords(); }; /** Wraps a LegacyScriptPubKeyMan so that it can be returned in a new unique_ptr. Does not provide privkeys */ class LegacySigningProvider : public SigningProvider { private: - const LegacyScriptPubKeyMan& m_spk_man; + const LegacyDataSPKM& m_spk_man; public: - explicit LegacySigningProvider(const LegacyScriptPubKeyMan& spk_man) : m_spk_man(spk_man) {} + explicit LegacySigningProvider(const LegacyDataSPKM& spk_man) : m_spk_man(spk_man) {} bool GetCScript(const CScriptID &scriptid, CScript& script) const override { return m_spk_man.GetCScript(scriptid, script); } bool HaveCScript(const CScriptID &scriptid) const override { return m_spk_man.HaveCScript(scriptid); } diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp index d569c64b43..1e98cb0771 100644 --- a/src/wallet/wallet.cpp +++ b/src/wallet/wallet.cpp @@ -2929,7 +2929,7 @@ bool CWallet::EraseAddressReceiveRequest(WalletBatch& batch, const CTxDestinatio return true; } -std::unique_ptr MakeWalletDatabase(const std::string& name, const DatabaseOptions& options, DatabaseStatus& status, bilingual_str& error_string) +static util::Result GetWalletPath(const std::string& name) { // Do some checking on wallet path. It should be either a: // @@ -2942,15 +2942,24 @@ std::unique_ptr MakeWalletDatabase(const std::string& name, cons if (!(path_type == fs::file_type::not_found || path_type == fs::file_type::directory || (path_type == fs::file_type::symlink && fs::is_directory(wallet_path)) || (path_type == fs::file_type::regular && fs::PathFromString(name).filename() == fs::PathFromString(name)))) { - error_string = Untranslated(strprintf( + return util::Error{Untranslated(strprintf( "Invalid -wallet path '%s'. -wallet path should point to a directory where wallet.dat and " "database/log.?????????? files can be stored, a location where such a directory could be created, " "or (for backwards compatibility) the name of an existing data file in -walletdir (%s)", - name, fs::quoted(fs::PathToString(GetWalletDir())))); + name, fs::quoted(fs::PathToString(GetWalletDir()))))}; + } + return wallet_path; +} + +std::unique_ptr MakeWalletDatabase(const std::string& name, const DatabaseOptions& options, DatabaseStatus& status, bilingual_str& error_string) +{ + const auto& wallet_path = GetWalletPath(name); + if (!wallet_path) { + error_string = util::ErrorString(wallet_path); status = DatabaseStatus::FAILED_BAD_PATH; return nullptr; } - return MakeDatabase(wallet_path, options, status, error_string); + return MakeDatabase(*wallet_path, options, status, error_string); } std::shared_ptr CWallet::Create(WalletContext& context, const std::string& name, std::unique_ptr database, uint64_t wallet_creation_flags, bilingual_str& error, std::vector& warnings) @@ -3608,6 +3617,16 @@ LegacyScriptPubKeyMan* CWallet::GetLegacyScriptPubKeyMan() const return dynamic_cast(it->second); } +LegacyDataSPKM* CWallet::GetLegacyDataSPKM() const +{ + if (IsWalletFlagSet(WALLET_FLAG_DESCRIPTORS)) { + return nullptr; + } + auto it = m_internal_spk_managers.find(OutputType::LEGACY); + if (it == m_internal_spk_managers.end()) return nullptr; + return dynamic_cast(it->second); +} + LegacyScriptPubKeyMan* CWallet::GetOrCreateLegacyScriptPubKeyMan() { SetupLegacyScriptPubKeyMan(); @@ -3624,13 +3643,22 @@ void CWallet::AddScriptPubKeyMan(const uint256& id, std::unique_ptrGetTimeFirstKey()); } +LegacyDataSPKM* CWallet::GetOrCreateLegacyDataSPKM() +{ + SetupLegacyScriptPubKeyMan(); + return GetLegacyDataSPKM(); +} + void CWallet::SetupLegacyScriptPubKeyMan() { if (!m_internal_spk_managers.empty() || !m_external_spk_managers.empty() || !m_spk_managers.empty() || IsWalletFlagSet(WALLET_FLAG_DESCRIPTORS)) { return; } - auto spk_manager = std::unique_ptr(new LegacyScriptPubKeyMan(*this, m_keypool_size)); + std::unique_ptr spk_manager = m_database->Format() == "bdb_ro" ? + std::make_unique(*this) : + std::make_unique(*this, m_keypool_size); + for (const auto& type : LEGACY_OUTPUT_TYPES) { m_internal_spk_managers[type] = spk_manager.get(); m_external_spk_managers[type] = spk_manager.get(); @@ -3998,7 +4026,7 @@ std::optional CWallet::GetDescriptorsForLegacy(bilingual_str& err { AssertLockHeld(cs_wallet); - LegacyScriptPubKeyMan* legacy_spkm = GetLegacyScriptPubKeyMan(); + LegacyDataSPKM* legacy_spkm = GetLegacyDataSPKM(); if (!Assume(legacy_spkm)) { // This shouldn't happen error = Untranslated(STR_INTERNAL_BUG("Error: Legacy wallet data missing")); @@ -4017,7 +4045,7 @@ bool CWallet::ApplyMigrationData(MigrationData& data, bilingual_str& error) { AssertLockHeld(cs_wallet); - LegacyScriptPubKeyMan* legacy_spkm = GetLegacyScriptPubKeyMan(); + LegacyDataSPKM* legacy_spkm = GetLegacyDataSPKM(); if (!Assume(legacy_spkm)) { // This shouldn't happen error = Untranslated(STR_INTERNAL_BUG("Error: Legacy wallet data missing")); @@ -4352,11 +4380,24 @@ util::Result MigrateLegacyToDescriptor(const std::string& walle // 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 (wallet->IsWalletFlagSet(WALLET_FLAG_DESCRIPTORS)) { + return util::Error{_("Error: This wallet is already a descriptor wallet")}; + } + 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; + } else { + // Check if the wallet is BDB + const auto& wallet_path = GetWalletPath(wallet_name); + if (!wallet_path) { + return util::Error{util::ErrorString(wallet_path)}; + } + if (!IsBDBFile(BDBDataFile(*wallet_path))) { + return util::Error{_("Error: This wallet is already a descriptor wallet")}; + } } // Load the wallet but only in the context of this function. @@ -4365,6 +4406,7 @@ util::Result MigrateLegacyToDescriptor(const std::string& walle empty_context.args = context.args; DatabaseOptions options; options.require_existing = true; + options.require_format = DatabaseFormat::BERKELEY_RO; DatabaseStatus status; std::unique_ptr database = MakeWalletDatabase(wallet_name, options, status, error); if (!database) { @@ -4379,6 +4421,8 @@ util::Result MigrateLegacyToDescriptor(const std::string& walle // Helper to reload as normal for some of our exit scenarios const auto& reload_wallet = [&](std::shared_ptr& to_reload) { + // Reset options.require_format as wallets of any format may be reloaded. + options.require_format = std::nullopt; assert(to_reload.use_count() == 1); std::string name = to_reload->GetName(); to_reload.reset(); diff --git a/src/wallet/wallet.h b/src/wallet/wallet.h index 5bc888462f..984a2d9c48 100644 --- a/src/wallet/wallet.h +++ b/src/wallet/wallet.h @@ -963,8 +963,10 @@ public: //! Get the LegacyScriptPubKeyMan which is used for all types, internal, and external. LegacyScriptPubKeyMan* GetLegacyScriptPubKeyMan() const; LegacyScriptPubKeyMan* GetOrCreateLegacyScriptPubKeyMan(); + LegacyDataSPKM* GetLegacyDataSPKM() const; + LegacyDataSPKM* GetOrCreateLegacyDataSPKM(); - //! Make a LegacyScriptPubKeyMan and set it for all types, internal, and external. + //! Make a Legacy(Data)SPKM and set it for all types, internal, and external. void SetupLegacyScriptPubKeyMan(); bool WithEncryptionKey(std::function cb) const override; diff --git a/src/wallet/walletdb.cpp b/src/wallet/walletdb.cpp index f34fcfc3fd..61cc9dbc78 100644 --- a/src/wallet/walletdb.cpp +++ b/src/wallet/walletdb.cpp @@ -354,9 +354,9 @@ bool LoadKey(CWallet* pwallet, DataStream& ssKey, DataStream& ssValue, std::stri strErr = "Error reading wallet database: CPrivKey corrupt"; return false; } - if (!pwallet->GetOrCreateLegacyScriptPubKeyMan()->LoadKey(key, vchPubKey)) + if (!pwallet->GetOrCreateLegacyDataSPKM()->LoadKey(key, vchPubKey)) { - strErr = "Error reading wallet database: LegacyScriptPubKeyMan::LoadKey failed"; + strErr = "Error reading wallet database: LegacyDataSPKM::LoadKey failed"; return false; } } catch (const std::exception& e) { @@ -393,9 +393,9 @@ bool LoadCryptedKey(CWallet* pwallet, DataStream& ssKey, DataStream& ssValue, st } } - if (!pwallet->GetOrCreateLegacyScriptPubKeyMan()->LoadCryptedKey(vchPubKey, vchPrivKey, checksum_valid)) + if (!pwallet->GetOrCreateLegacyDataSPKM()->LoadCryptedKey(vchPubKey, vchPrivKey, checksum_valid)) { - strErr = "Error reading wallet database: LegacyScriptPubKeyMan::LoadCryptedKey failed"; + strErr = "Error reading wallet database: LegacyDataSPKM::LoadCryptedKey failed"; return false; } } catch (const std::exception& e) { @@ -440,7 +440,7 @@ bool LoadHDChain(CWallet* pwallet, DataStream& ssValue, std::string& strErr) try { CHDChain chain; ssValue >> chain; - pwallet->GetOrCreateLegacyScriptPubKeyMan()->LoadHDChain(chain); + pwallet->GetOrCreateLegacyDataSPKM()->LoadHDChain(chain); } catch (const std::exception& e) { if (strErr.empty()) { strErr = e.what(); @@ -584,9 +584,9 @@ static DBErrors LoadLegacyWalletRecords(CWallet* pwallet, DatabaseBatch& batch, key >> hash; CScript script; value >> script; - if (!pwallet->GetOrCreateLegacyScriptPubKeyMan()->LoadCScript(script)) + if (!pwallet->GetOrCreateLegacyDataSPKM()->LoadCScript(script)) { - strErr = "Error reading wallet database: LegacyScriptPubKeyMan::LoadCScript failed"; + strErr = "Error reading wallet database: LegacyDataSPKM::LoadCScript failed"; return DBErrors::NONCRITICAL_ERROR; } return DBErrors::LOAD_OK; @@ -607,7 +607,7 @@ static DBErrors LoadLegacyWalletRecords(CWallet* pwallet, DatabaseBatch& batch, key >> vchPubKey; CKeyMetadata keyMeta; value >> keyMeta; - pwallet->GetOrCreateLegacyScriptPubKeyMan()->LoadKeyMetadata(vchPubKey.GetID(), keyMeta); + pwallet->GetOrCreateLegacyDataSPKM()->LoadKeyMetadata(vchPubKey.GetID(), keyMeta); // Extract some CHDChain info from this metadata if it has any if (keyMeta.nVersion >= CKeyMetadata::VERSION_WITH_HDDATA && !keyMeta.hd_seed_id.IsNull() && keyMeta.hdKeypath.size() > 0) { @@ -674,7 +674,7 @@ static DBErrors LoadLegacyWalletRecords(CWallet* pwallet, DatabaseBatch& batch, // Set inactive chains if (!hd_chains.empty()) { - LegacyScriptPubKeyMan* legacy_spkm = pwallet->GetLegacyScriptPubKeyMan(); + LegacyDataSPKM* legacy_spkm = pwallet->GetLegacyDataSPKM(); if (legacy_spkm) { for (const auto& [hd_seed_id, chain] : hd_chains) { if (hd_seed_id != legacy_spkm->GetHDChain().seed_id) { @@ -695,7 +695,7 @@ static DBErrors LoadLegacyWalletRecords(CWallet* pwallet, DatabaseBatch& batch, uint8_t fYes; value >> fYes; if (fYes == '1') { - pwallet->GetOrCreateLegacyScriptPubKeyMan()->LoadWatchOnly(script); + pwallet->GetOrCreateLegacyDataSPKM()->LoadWatchOnly(script); } return DBErrors::LOAD_OK; }); @@ -708,7 +708,7 @@ static DBErrors LoadLegacyWalletRecords(CWallet* pwallet, DatabaseBatch& batch, key >> script; CKeyMetadata keyMeta; value >> keyMeta; - pwallet->GetOrCreateLegacyScriptPubKeyMan()->LoadScriptMetadata(CScriptID(script), keyMeta); + pwallet->GetOrCreateLegacyDataSPKM()->LoadScriptMetadata(CScriptID(script), keyMeta); return DBErrors::LOAD_OK; }); result = std::max(result, watch_meta_res.m_result); @@ -720,7 +720,7 @@ static DBErrors LoadLegacyWalletRecords(CWallet* pwallet, DatabaseBatch& batch, key >> nIndex; CKeyPool keypool; value >> keypool; - pwallet->GetOrCreateLegacyScriptPubKeyMan()->LoadKeyPool(nIndex, keypool); + pwallet->GetOrCreateLegacyDataSPKM()->LoadKeyPool(nIndex, keypool); return DBErrors::LOAD_OK; }); result = std::max(result, pool_res.m_result); @@ -763,7 +763,7 @@ static DBErrors LoadLegacyWalletRecords(CWallet* pwallet, DatabaseBatch& batch, // nTimeFirstKey is only reliable if all keys have metadata if (pwallet->IsLegacy() && (key_res.m_records + ckey_res.m_records + watch_script_res.m_records) != (keymeta_res.m_records + watch_meta_res.m_records)) { - auto spk_man = pwallet->GetOrCreateLegacyScriptPubKeyMan(); + auto spk_man = pwallet->GetLegacyScriptPubKeyMan(); if (spk_man) { LOCK(spk_man->cs_KeyStore); spk_man->UpdateTimeFirstKey(1); diff --git a/test/functional/wallet_migration.py b/test/functional/wallet_migration.py index 890b6a5c1b..8fdc284d24 100755 --- a/test/functional/wallet_migration.py +++ b/test/functional/wallet_migration.py @@ -205,9 +205,13 @@ class WalletMigrationTest(BitcoinTestFramework): self.assert_list_txs_equal(basic2.listtransactions(), basic2_txs) # Now test migration on a descriptor wallet - self.log.info("Test \"nothing to migrate\" when the user tries to migrate a wallet with no legacy data") + self.log.info("Test \"nothing to migrate\" when the user tries to migrate a loaded wallet with no legacy data") assert_raises_rpc_error(-4, "Error: This wallet is already a descriptor wallet", basic2.migratewallet) + self.log.info("Test \"nothing to migrate\" when the user tries to migrate an unloaded wallet with no legacy data") + basic2.unloadwallet() + assert_raises_rpc_error(-4, "Error: This wallet is already a descriptor wallet", self.nodes[0].migratewallet, "basic2") + def test_multisig(self): default = self.nodes[0].get_wallet_rpc(self.default_wallet_name) @@ -467,6 +471,12 @@ class WalletMigrationTest(BitcoinTestFramework): assert_raises_rpc_error(-4, "Error: Wallet decryption failed, the wallet passphrase was not provided or was incorrect", wallet.migratewallet, None, "badpass") assert_raises_rpc_error(-4, "The passphrase contains a null character", wallet.migratewallet, None, "pass\0with\0null") + # Check the wallet is still active post-migration failure. + # If not, it will throw an exception and abort the test. + wallet.walletpassphrase("pass", 99999) + wallet.getnewaddress() + + # Verify we can properly migrate the encrypted wallet self.migrate_wallet(wallet, passphrase="pass") info = wallet.getwalletinfo()