diff --git a/src/wallet/rpcwallet.cpp b/src/wallet/rpcwallet.cpp index 55d9810745eedf..646a9cd53cdd0c 100644 --- a/src/wallet/rpcwallet.cpp +++ b/src/wallet/rpcwallet.cpp @@ -2681,76 +2681,53 @@ static UniValue upgradetohd(const JSONRPCRequest& request) }, }.Check(request); - std::shared_ptr const wallet = GetWalletForJSONRPCRequest(request); - if (!wallet) return NullUniValue; - CWallet* const pwallet = wallet.get(); - LegacyScriptPubKeyMan* spk_man = pwallet->GetLegacyScriptPubKeyMan(); - if (!spk_man) { - throw JSONRPCError(RPC_WALLET_ERROR, "This type of wallet does not support this command"); - } + std::shared_ptr const pwallet = GetWalletForJSONRPCRequest(request); + if (!pwallet) return NullUniValue; bool generate_mnemonic = request.params[0].isNull() || request.params[0].get_str().empty(); - - { - LOCK(pwallet->cs_wallet); - - // Do not do anything to HD wallets - if (pwallet->IsHDEnabled()) { - throw JSONRPCError(RPC_WALLET_ERROR, "Cannot upgrade a wallet to HD if it is already upgraded to HD."); - } - - if (!pwallet->SetMaxVersion(FEATURE_HD)) { - throw JSONRPCError(RPC_WALLET_ERROR, "Cannot downgrade wallet"); + SecureString secureWalletPassphrase; + secureWalletPassphrase.reserve(100); + // TODO: get rid of this .c_str() by implementing SecureString::operator=(std::string) + // Alternately, find a way to make request.params[0] mlock()'d to begin with. + if (!request.params[2].isNull()) { + secureWalletPassphrase = request.params[2].get_str().c_str(); + if (!pwallet->Unlock(secureWalletPassphrase)) { + throw JSONRPCError(RPC_WALLET_PASSPHRASE_INCORRECT, "The wallet passphrase entered was incorrect"); } + } - if (pwallet->IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS)) { - throw JSONRPCError(RPC_WALLET_ERROR, "Error: Private keys are disabled for this wallet"); - } + EnsureWalletIsUnlocked(pwallet.get()); - bool prev_encrypted = pwallet->IsCrypted(); + SecureString secureMnemonic; + secureMnemonic.reserve(256); + if (!generate_mnemonic) { + secureMnemonic = request.params[0].get_str().c_str(); + } - SecureString secureWalletPassphrase; - secureWalletPassphrase.reserve(100); - // TODO: get rid of this .c_str() by implementing SecureString::operator=(std::string) - // Alternately, find a way to make request.params[0] mlock()'d to begin with. - if (request.params[2].isNull()) { - if (prev_encrypted) { - throw JSONRPCError(RPC_WALLET_PASSPHRASE_INCORRECT, "Cannot upgrade encrypted wallet to HD without the wallet passphrase"); - } - } else { - secureWalletPassphrase = request.params[2].get_str().c_str(); - if (!pwallet->Unlock(secureWalletPassphrase)) { - throw JSONRPCError(RPC_WALLET_PASSPHRASE_INCORRECT, "The wallet passphrase entered was incorrect"); - } - } + SecureString secureMnemonicPassphrase; + secureMnemonicPassphrase.reserve(256); + if (!request.params[1].isNull()) { + secureMnemonicPassphrase = request.params[1].get_str().c_str(); + } + // TODO: breaking changes kept for v21! + // instead upgradetohd let's use more straightforward 'sethdseed' + constexpr bool is_v21 = false; + const int previous_version{pwallet->GetVersion()}; + if (is_v21 && previous_version >= FEATURE_HD) { + return JSONRPCError(RPC_WALLET_ERROR, "Already at latest version. Wallet version unchanged."); + } - SecureString secureMnemonic; - secureMnemonic.reserve(256); - if (!generate_mnemonic) { - secureMnemonic = request.params[0].get_str().c_str(); - } + bilingual_str error; + const bool wallet_upgraded{pwallet->UpgradeToHD(secureMnemonic, secureMnemonicPassphrase, secureWalletPassphrase, error)}; - SecureString secureMnemonicPassphrase; - secureMnemonicPassphrase.reserve(256); - if (!request.params[1].isNull()) { - secureMnemonicPassphrase = request.params[1].get_str().c_str(); + if (!secureWalletPassphrase.empty() && !pwallet->IsCrypted()) { + if (!pwallet->EncryptWallet(secureWalletPassphrase)) { + throw JSONRPCError(RPC_WALLET_ENCRYPTION_FAILED, "Failed to encrypt HD wallet"); } + } - pwallet->WalletLogPrintf("Upgrading wallet to HD\n"); - pwallet->SetMinVersion(FEATURE_HD); - - if (prev_encrypted) { - if (!pwallet->GenerateNewHDChainEncrypted(secureMnemonic, secureMnemonicPassphrase, secureWalletPassphrase)) { - throw JSONRPCError(RPC_WALLET_ERROR, "Failed to generate encrypted HD wallet"); - } - } else { - spk_man->GenerateNewHDChain(secureMnemonic, secureMnemonicPassphrase); - if (!secureWalletPassphrase.empty()) { - if (!pwallet->EncryptWallet(secureWalletPassphrase)) { - throw JSONRPCError(RPC_WALLET_ENCRYPTION_FAILED, "Failed to encrypt HD wallet"); - } - } - } + if (!wallet_upgraded) { + throw JSONRPCError(RPC_WALLET_ERROR, error.original); } // If you are generating new mnemonic it is assumed that the addresses have never gotten a transaction before, so you don't need to rescan for transactions diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp index 094ca70149686d..1c31084065498a 100644 --- a/src/wallet/wallet.cpp +++ b/src/wallet/wallet.cpp @@ -4908,6 +4908,37 @@ bool CWallet::UpgradeWallet(int version, bilingual_str& error) return true; } +bool CWallet::UpgradeToHD(const SecureString& secureMnemonic, const SecureString& secureMnemonicPassphrase, const SecureString& secureWalletPassphrase, bilingual_str& error) +{ + LOCK(cs_wallet); + + // Do not do anything to HD wallets + if (IsHDEnabled()) { + error = Untranslated("Cannot upgrade a wallet to HD if it is already upgraded to HD."); + return false; + } + + if (IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS)) { + error = Untranslated("Private keys are disabled for this wallet"); + return false; + } + + WalletLogPrintf("Upgrading wallet to HD\n"); + SetMinVersion(FEATURE_HD); + + auto spk_man = GetOrCreateLegacyScriptPubKeyMan(); + bool prev_encrypted = IsCrypted(); + if (prev_encrypted) { + if (!GenerateNewHDChainEncrypted(secureMnemonic, secureMnemonicPassphrase, secureWalletPassphrase)) { + error = Untranslated("Failed to generate encrypted HD wallet"); + return false; + } + } else { + spk_man->GenerateNewHDChain(secureMnemonic, secureMnemonicPassphrase); + } + return true; +} + const CAddressBookData* CWallet::FindAddressBookEntry(const CTxDestination& dest, bool allow_change) const { const auto& address_book_it = m_address_book.find(dest); diff --git a/src/wallet/wallet.h b/src/wallet/wallet.h index 990ed07819f30f..b737d2978917dd 100644 --- a/src/wallet/wallet.h +++ b/src/wallet/wallet.h @@ -1331,6 +1331,9 @@ class CWallet final : public WalletStorage, public interfaces::Chain::Notificati /** Upgrade the wallet */ bool UpgradeWallet(int version, bilingual_str& error); + /** Upgrade non-HD wallet to HD wallet */ + bool UpgradeToHD(const SecureString& secureMnemonic, const SecureString& secureMnemonicPassphrase, const SecureString& secureWalletPassphrase, bilingual_str& error); + //! Returns all unique ScriptPubKeyMans in m_internal_spk_managers and m_external_spk_managers std::set GetActiveScriptPubKeyMans() const; diff --git a/test/functional/wallet_upgradetohd.py b/test/functional/wallet_upgradetohd.py index b7c80d05f8a345..64cf39cf57a399 100755 --- a/test/functional/wallet_upgradetohd.py +++ b/test/functional/wallet_upgradetohd.py @@ -190,7 +190,7 @@ def run_test(self): node.stop() node.wait_until_stopped() self.start_node(0, extra_args=['-rescan']) - assert_raises_rpc_error(-14, "Cannot upgrade encrypted wallet to HD without the wallet passphrase", node.upgradetohd, mnemonic) + assert_raises_rpc_error(-13, "Error: Please enter the wallet passphrase with walletpassphrase first.", node.upgradetohd, mnemonic) assert_raises_rpc_error(-14, "The wallet passphrase entered was incorrect", node.upgradetohd, mnemonic, "", "wrongpass") assert node.upgradetohd(mnemonic, "", walletpass) assert_raises_rpc_error(-13, "Error: Please enter the wallet passphrase with walletpassphrase first.", node.dumphdinfo)