aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorglozow <gloriajzhao@gmail.com>2024-07-11 16:22:14 +0100
committerglozow <gloriajzhao@gmail.com>2024-07-11 16:47:02 +0100
commitd9aa7b23e438fc381c6bddecfe53943d63b1a1fd (patch)
treed6b7667dd610ad8df0e4c82c38ff7e70ea4f7043
parent9b480f7a25a737c9c4ebc33401e94d66c2da9ec3 (diff)
parent8ce3739edbcf6437bf2695087e0ebe8c633df19b (diff)
downloadbitcoin-d9aa7b23e438fc381c6bddecfe53943d63b1a1fd.tar.xz
Merge bitcoin/bitcoin#26596: wallet: Migrate legacy wallets to descriptor wallets without requiring BDB
8ce3739edbcf6437bf2695087e0ebe8c633df19b test: verify wallet is still active post-migration failure (furszy) 771bc60f134c002a027e799639f0fed59ae8123d wallet: Use LegacyDataSPKM when loading (Ava Chow) 61d872f1b3d0dbfd0de4ff9b7deff40eeffa6a14 wallet: Move MigrateToDescriptor and DeleteRecords to LegacyDataSPKM (Ava Chow) b231f4d556876ae70305e8710e31d53525ded8ae wallet: Move LegacyScriptPubKeyMan::IsMine to LegacyDataSPKM (Ava Chow) 7461d0c006c92ede2f2595b79a5509eaf3509fb7 wallet: Move LegacySPKM data storage and handling to LegacyDataSPKM (Ava Chow) 517e204bacd9dcea6612aae57d5a2813b89cd01d Change MigrateLegacyToDescriptor to reopen wallet as BERKELEY_RO (Ava Chow) Pull request description: #26606 introduced `BerkeleyRODatabase` which is an independent parser for BDB files. This PR uses this in legacy wallet migration so that migration will continue to work once the legacy wallet and BDB are removed. `LegacyDataSPKM` is introduced to have the minimum data and functions necessary for a legacy wallet to be loaded for migration. ACKs for top commit: cbergqvist: ACK 8ce3739edbcf6437bf2695087e0ebe8c633df19b theStack: Code-review ACK 8ce3739edbcf6437bf2695087e0ebe8c633df19b furszy: Code review ACK 8ce3739edbcf6437bf2695087e0ebe8c633df19b Tree-SHA512: dccea12d6c597de15e3e42f97ab483cfd069e103611200279a177e021e8e9c4e74387c4f45d2e58b3a1e7e2bdb32a1d2d2060b1f8086c03eeaa0c68579d9d54e
-rw-r--r--src/wallet/scriptpubkeyman.cpp78
-rw-r--r--src/wallet/scriptpubkeyman.h168
-rw-r--r--src/wallet/wallet.cpp58
-rw-r--r--src/wallet/wallet.h4
-rw-r--r--src/wallet/walletdb.cpp26
-rwxr-xr-xtest/functional/wallet_migration.py12
6 files changed, 221 insertions, 125 deletions
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<valtype>& pubkeys, const LegacyScriptPubKeyMan& keystore)
+bool HaveKeys(const std::vector<valtype>& pubkeys, const LegacyDataSPKM& keystore)
{
for (const valtype& pubkey : pubkeys) {
CKeyID keyID = CPubKey(pubkey).GetID();
@@ -102,7 +102,7 @@ bool HaveKeys(const std::vector<valtype>& 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<SigningProvider> LegacyScriptPubKeyMan::GetSolvingProvider(const CScript& script) const
+std::unique_ptr<SigningProvider> LegacyDataSPKM::GetSolvingProvider(const CScript& script) const
{
return std::make_unique<LegacySigningProvider>(*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<unsigned char> &vchCryptedSecret, bool checksum_valid)
+bool LegacyDataSPKM::LoadCryptedKey(const CPubKey &vchPubKey, const std::vector<unsigned char> &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<unsigned char> &vchCryptedSecret)
+bool LegacyDataSPKM::AddCryptedKeyInner(const CPubKey &vchPubKey, const std::vector<unsigned char> &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<CKeyID> LegacyScriptPubKeyMan::GetKeys() const
return set_address;
}
-std::unordered_set<CScript, SaltedSipHasher> LegacyScriptPubKeyMan::GetScriptPubKeys() const
+std::unordered_set<CScript, SaltedSipHasher> LegacyDataSPKM::GetScriptPubKeys() const
{
LOCK(cs_KeyStore);
std::unordered_set<CScript, SaltedSipHasher> spks;
@@ -1739,7 +1757,7 @@ std::unordered_set<CScript, SaltedSipHasher> LegacyScriptPubKeyMan::GetScriptPub
return spks;
}
-std::unordered_set<CScript, SaltedSipHasher> LegacyScriptPubKeyMan::GetNotMineScriptPubKeys() const
+std::unordered_set<CScript, SaltedSipHasher> LegacyDataSPKM::GetNotMineScriptPubKeys() const
{
LOCK(cs_KeyStore);
std::unordered_set<CScript, SaltedSipHasher> spks;
@@ -1749,7 +1767,7 @@ std::unordered_set<CScript, SaltedSipHasher> LegacyScriptPubKeyMan::GetNotMineSc
return spks;
}
-std::optional<MigrationData> LegacyScriptPubKeyMan::MigrateToDescriptor()
+std::optional<MigrationData> LegacyDataSPKM::MigrateToDescriptor()
{
LOCK(cs_KeyStore);
if (m_storage.IsLocked()) {
@@ -1816,7 +1834,7 @@ std::optional<MigrationData> 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<DescriptorScriptPubKeyMan>(new DescriptorScriptPubKeyMan(m_storage, w_desc, m_keypool_size));
+ auto desc_spk_man = std::make_unique<DescriptorScriptPubKeyMan>(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<MigrationData> 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<DescriptorScriptPubKeyMan>(new DescriptorScriptPubKeyMan(m_storage, w_desc, m_keypool_size));
+ auto desc_spk_man = std::make_unique<DescriptorScriptPubKeyMan>(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<MigrationData> 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<DescriptorScriptPubKeyMan>(new DescriptorScriptPubKeyMan(m_storage, w_desc, m_keypool_size));
+ auto desc_spk_man = std::make_unique<DescriptorScriptPubKeyMan>(m_storage, w_desc, /*keypool_size=*/0);
for (const auto& keyid : privkeyids) {
CKey key;
if (!GetKey(keyid, key)) {
@@ -2001,7 +2019,7 @@ std::optional<MigrationData> 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<OutputType> 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<CScript>;
using WatchKeyMap = std::map<CKeyID, CPubKey>;
-
- WalletBatch *encrypted_batch GUARDED_BY(cs_KeyStore) = nullptr;
-
using CryptedKeyMap = std::map<CKeyID, std::pair<CPubKey, std::vector<unsigned char>>>;
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<CKeyID, CHDChain, SaltedSipHasher> 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<unsigned char> &vchCryptedSecret);
+
+public:
+ using ScriptPubKeyMan::ScriptPubKeyMan;
+
+ // Map from Key ID to key metadata.
+ std::map<CKeyID, CKeyMetadata> mapKeyMetadata GUARDED_BY(cs_KeyStore);
+
+ // Map from Script ID to key metadata (for watch-only keys).
+ std::map<CScriptID, CKeyMetadata> m_script_metadata GUARDED_BY(cs_KeyStore);
+
+ // ScriptPubKeyMan overrides
+ bool CheckDecryptionKey(const CKeyingMaterial& master_key) override;
+ std::unordered_set<CScript, SaltedSipHasher> GetScriptPubKeys() const override;
+ std::unique_ptr<SigningProvider> 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<int64_t> setInternalKeyPool GUARDED_BY(cs_KeyStore);
+ std::set<int64_t> setExternalKeyPool GUARDED_BY(cs_KeyStore);
+ std::set<int64_t> set_pre_split_keypool GUARDED_BY(cs_KeyStore);
+ int64_t m_max_keypool_index GUARDED_BY(cs_KeyStore) = 0;
+ std::map<CKeyID, int64_t> 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<unsigned char> &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<CScript, SaltedSipHasher> GetNotMineScriptPubKeys() const;
+
+ /** Get the DescriptorScriptPubKeyMans (with private keys) that have the same scriptPubKeys as this LegacyScriptPubKeyMan.
+ * Does not modify this ScriptPubKeyMan. */
+ std::optional<MigrationData> 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<unsigned char> &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<CKeyID, CHDChain, SaltedSipHasher> 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<int64_t> setInternalKeyPool GUARDED_BY(cs_KeyStore);
- std::set<int64_t> setExternalKeyPool GUARDED_BY(cs_KeyStore);
- std::set<int64_t> set_pre_split_keypool GUARDED_BY(cs_KeyStore);
- int64_t m_max_keypool_index GUARDED_BY(cs_KeyStore) = 0;
- std::map<CKeyID, int64_t> 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<int64_t, CKeyID> 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<CTxDestination> 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<CTxDestination> 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<SigningProvider> GetSolvingProvider(const CScript& script) const override;
-
bool CanProvide(const CScript& script, SignatureData& sigdata) override;
bool SignTransaction(CMutableTransaction& tx, const std::map<COutPoint, Coin>& coins, int sighash, std::map<int, bilingual_str>& input_errors) const override;
@@ -427,58 +493,27 @@ public:
uint256 GetID() const override;
- // Map from Key ID to key metadata.
- std::map<CKeyID, CKeyMetadata> mapKeyMetadata GUARDED_BY(cs_KeyStore);
-
- // Map from Script ID to key metadata (for watch-only keys).
- std::map<CScriptID, CKeyMetadata> 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<unsigned char> &vchCryptedSecret);
- //! Adds an encrypted key to the store, without saving it to disk (used by LoadWallet)
- bool LoadCryptedKey(const CPubKey &vchPubKey, const std::vector<unsigned char> &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<CKeyID, int64_t>& GetAllReserveKeys() const { return m_pool_key_to_index; }
std::set<CKeyID> GetKeys() const override;
- std::unordered_set<CScript, SaltedSipHasher> 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<CScript, SaltedSipHasher> GetNotMineScriptPubKeys() const;
-
- /** Get the DescriptorScriptPubKeyMans (with private keys) that have the same scriptPubKeys as this LegacyScriptPubKeyMan.
- * Does not modify this ScriptPubKeyMan. */
- std::optional<MigrationData> 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<WalletDatabase> MakeWalletDatabase(const std::string& name, const DatabaseOptions& options, DatabaseStatus& status, bilingual_str& error_string)
+static util::Result<fs::path> GetWalletPath(const std::string& name)
{
// Do some checking on wallet path. It should be either a:
//
@@ -2942,15 +2942,24 @@ std::unique_ptr<WalletDatabase> 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<WalletDatabase> 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> CWallet::Create(WalletContext& context, const std::string& name, std::unique_ptr<WalletDatabase> database, uint64_t wallet_creation_flags, bilingual_str& error, std::vector<bilingual_str>& warnings)
@@ -3608,6 +3617,16 @@ LegacyScriptPubKeyMan* CWallet::GetLegacyScriptPubKeyMan() const
return dynamic_cast<LegacyScriptPubKeyMan*>(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<LegacyDataSPKM*>(it->second);
+}
+
LegacyScriptPubKeyMan* CWallet::GetOrCreateLegacyScriptPubKeyMan()
{
SetupLegacyScriptPubKeyMan();
@@ -3624,13 +3643,22 @@ void CWallet::AddScriptPubKeyMan(const uint256& id, std::unique_ptr<ScriptPubKey
MaybeUpdateBirthTime(spkm->GetTimeFirstKey());
}
+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<ScriptPubKeyMan>(new LegacyScriptPubKeyMan(*this, m_keypool_size));
+ std::unique_ptr<ScriptPubKeyMan> spk_manager = m_database->Format() == "bdb_ro" ?
+ std::make_unique<LegacyDataSPKM>(*this) :
+ std::make_unique<LegacyScriptPubKeyMan>(*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<MigrationData> 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<MigrationResult> 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<MigrationResult> 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<WalletDatabase> database = MakeWalletDatabase(wallet_name, options, status, error);
if (!database) {
@@ -4379,6 +4421,8 @@ util::Result<MigrationResult> MigrateLegacyToDescriptor(const std::string& walle
// Helper to reload as normal for some of our exit scenarios
const auto& reload_wallet = [&](std::shared_ptr<CWallet>& 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<bool (const CKeyingMaterial&)> 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()