diff --git a/src/rpc/client.cpp b/src/rpc/client.cpp index fc36fd1dcfc47..4fb93be805114 100644 --- a/src/rpc/client.cpp +++ b/src/rpc/client.cpp @@ -182,6 +182,7 @@ static const CRPCConvertParam vRPCConvertParams[] = { "getspecialtxes", 3, "skip" }, { "getspecialtxes", 4, "verbosity" }, { "disconnectnode", 1, "nodeid" }, + { "upgradewallet", 0, "version" }, // Echo with conversion (For testing only) { "echojson", 0, "arg0" }, { "echojson", 1, "arg1" }, diff --git a/src/wallet/init.cpp b/src/wallet/init.cpp index 71de74d92b82b..686d867dfb935 100644 --- a/src/wallet/init.cpp +++ b/src/wallet/init.cpp @@ -62,7 +62,6 @@ void WalletInit::AddWalletOptions(ArgsManager& argsman) const argsman.AddArg("-rescan=", "Rescan the block chain for missing wallet transactions on startup" " (1 = start from wallet creation time, 2 = start from genesis block)", ArgsManager::ALLOW_ANY, OptionsCategory::WALLET); argsman.AddArg("-spendzeroconfchange", strprintf("Spend unconfirmed change when sending transactions (default: %u)", DEFAULT_SPEND_ZEROCONF_CHANGE), ArgsManager::ALLOW_ANY, OptionsCategory::WALLET); - argsman.AddArg("-upgradewallet", "Upgrade wallet to latest format on startup", ArgsManager::ALLOW_ANY, OptionsCategory::WALLET); argsman.AddArg("-wallet=", "Specify wallet path to load at startup. Can be used multiple times to load multiple wallets. Path is to a directory containing wallet data and log files. If the path is not absolute, it is interpreted relative to . This only loads existing wallets and does not create new ones. For backwards compatibility this also accepts names of existing top-level data files in .", ArgsManager::ALLOW_ANY | ArgsManager::NETWORK_ONLY, OptionsCategory::WALLET); argsman.AddArg("-walletbackupsdir=", "Specify full path to directory for automatic wallet backups (must exist)", ArgsManager::ALLOW_ANY, OptionsCategory::WALLET); argsman.AddArg("-walletbroadcast", strprintf("Make the wallet broadcast transactions (default: %u)", DEFAULT_WALLETBROADCAST), ArgsManager::ALLOW_ANY, OptionsCategory::WALLET); @@ -129,8 +128,6 @@ bool WalletInit::ParameterInteraction() const return InitError(_("You can not start a masternode with wallet enabled.")); } - const bool is_multiwallet = gArgs.GetArgs("-wallet").size() > 1; - if (gArgs.GetBoolArg("-blocksonly", DEFAULT_BLOCKSONLY) && gArgs.SoftSetBoolArg("-walletbroadcast", false)) { LogPrintf("%s: parameter interaction: -blocksonly=1 -> setting -walletbroadcast=0\n", __func__); } @@ -146,12 +143,6 @@ bool WalletInit::ParameterInteraction() const gArgs.ForceRemoveArg("rescan"); } - if (is_multiwallet) { - if (gArgs.GetBoolArg("-upgradewallet", false)) { - return InitError(strprintf(_("%s is only allowed with a single wallet file"), "-upgradewallet")); - } - } - if (gArgs.GetBoolArg("-sysperms", false)) return InitError(Untranslated("-sysperms is not allowed in combination with enabled wallet functionality")); diff --git a/src/wallet/rpcwallet.cpp b/src/wallet/rpcwallet.cpp index be1075faa2fc7..5118f7401f4c0 100644 --- a/src/wallet/rpcwallet.cpp +++ b/src/wallet/rpcwallet.cpp @@ -2748,7 +2748,7 @@ static UniValue loadwallet(const JSONRPCRequest& request) RPCHelpMan{"loadwallet", "\nLoads a wallet from a wallet file or directory." "\nNote that all wallet command-line options used when starting dashd will be" - "\napplied to the new wallet (eg -upgradewallet, rescan, etc).\n", + "\napplied to the new wallet (eg, rescan, etc).\n", { {"filename", RPCArg::Type::STR, RPCArg::Optional::NO, "The wallet directory or .dat file."}, {"load_on_startup", RPCArg::Type::BOOL, /* default */ "null", "Save wallet name to persistent settings and load on startup. True to add wallet to startup list, false to remove, null to leave unchanged."}, @@ -4064,6 +4064,42 @@ UniValue walletcreatefundedpsbt(const JSONRPCRequest& request) return result; } +static UniValue upgradewallet(const JSONRPCRequest& request) +{ + RPCHelpMan{"upgradewallet", + "\nUpgrade the wallet. Upgrades to the latest version if no version number is specified\n" + "New keys may be generated and a new wallet backup will need to be made.", + { + {"version", RPCArg::Type::NUM, /* default */ strprintf("%d", FEATURE_LATEST), "The version number to upgrade to. Default is the latest wallet version"} + }, + RPCResults{}, + RPCExamples{ + HelpExampleCli("upgradewallet", "120200") + + HelpExampleRpc("upgradewallet", "120200") + } + }.Check(request); + + std::shared_ptr const wallet = GetWalletForJSONRPCRequest(request); + if (!wallet) return NullUniValue; + CWallet* const pwallet = wallet.get(); + + RPCTypeCheck(request.params, {UniValue::VNUM}, true); + + EnsureWalletIsUnlocked(pwallet); + + int version = 0; + if (!request.params[0].isNull()) { + version = request.params[0].get_int(); + } + + bilingual_str error; + std::vector warnings; + if (!pwallet->UpgradeWallet(version, error, warnings)) { + throw JSONRPCError(RPC_WALLET_ERROR, error.original); + } + return error.original; +} + // clang-format off static const CRPCCommand commands[] = { // category name actor (function) argNames @@ -4123,6 +4159,7 @@ static const CRPCCommand commands[] = { "wallet", "signmessage", &signmessage, {"address","message"} }, { "wallet", "signrawtransactionwithwallet", &signrawtransactionwithwallet, {"hexstring","prevtxs","sighashtype"} }, { "wallet", "unloadwallet", &unloadwallet, {"wallet_name", "load_on_startup"} }, + { "wallet", "upgradewallet", &upgradewallet, {"version"} }, { "wallet", "upgradetohd", &upgradetohd, {"mnemonic", "mnemonicpassphrase", "walletpassphrase", "rescan"} }, { "wallet", "walletlock", &walletlock, {} }, { "wallet", "walletpassphrasechange", &walletpassphrasechange, {"oldpassphrase","newpassphrase"} }, diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp index a58f57d36db24..1d316586374be 100644 --- a/src/wallet/wallet.cpp +++ b/src/wallet/wallet.cpp @@ -4306,27 +4306,9 @@ std::shared_ptr CWallet::Create(interfaces::Chain& chain, const std::st } } - if (gArgs.GetBoolArg("-upgradewallet", fFirstRun)) - { - int nMaxVersion = gArgs.GetArg("-upgradewallet", 0); - auto nMinVersion = DEFAULT_USE_HD_WALLET ? FEATURE_LATEST : FEATURE_COMPRPUBKEY; - if (nMaxVersion == 0) // the -upgradewallet without argument case - { - walletInstance->WalletLogPrintf("Performing wallet upgrade to %i\n", nMinVersion); - nMaxVersion = FEATURE_LATEST; - walletInstance->SetMinVersion(nMinVersion); // permanently upgrade the wallet immediately - } - else - walletInstance->WalletLogPrintf("Allowing wallet upgrade up to %i\n", nMaxVersion); - if (nMaxVersion < walletInstance->GetVersion()) - { - return unload_wallet(_("Cannot downgrade wallet")); - } - walletInstance->SetMaxVersion(nMaxVersion); - } - if (fFirstRun) { + walletInstance->SetMaxVersion(FEATURE_LATEST); walletInstance->SetWalletFlags(wallet_creation_flags, false); if (!(wallet_creation_flags & (WALLET_FLAG_DISABLE_PRIVATE_KEYS | WALLET_FLAG_BLANK_WALLET))) { // Create new HD chain @@ -4596,6 +4578,27 @@ std::shared_ptr CWallet::Create(interfaces::Chain& chain, const std::st return walletInstance; } +bool CWallet::UpgradeWallet(int version, bilingual_str& error, std::vector& warnings) +{ + int nMaxVersion = version; + auto nMinVersion = DEFAULT_USE_HD_WALLET ? FEATURE_LATEST : FEATURE_COMPRPUBKEY; + if (nMaxVersion == 0) { + WalletLogPrintf("Performing wallet upgrade to %i\n", nMinVersion); + nMaxVersion = FEATURE_LATEST; + SetMinVersion(nMinVersion); // permanently upgrade the wallet immediately + } else { + WalletLogPrintf("Allowing wallet upgrade up to %i\n", nMaxVersion); + } + + if (nMaxVersion < GetVersion()) { + error = Untranslated("Cannot downgrade wallet"); + return false; + } + + SetMaxVersion(nMaxVersion); + return true; +} + void CWallet::postInitProcess() { LOCK(cs_wallet); diff --git a/src/wallet/wallet.h b/src/wallet/wallet.h index 2b7b5dfe597ed..6848c5cc572e5 100644 --- a/src/wallet/wallet.h +++ b/src/wallet/wallet.h @@ -1241,6 +1241,9 @@ class CWallet final : public WalletStorage, public interfaces::Chain::Notificati LogPrintf(("%s " + fmt).c_str(), GetDisplayName(), parameters...); }; + /** Upgrade the wallet */ + bool UpgradeWallet(int version, bilingual_str& error, std::vector& warnings); + /** Get last block processed height */ int GetLastBlockHeight() const EXCLUSIVE_LOCKS_REQUIRED(cs_wallet) { diff --git a/test/functional/wallet_multiwallet.py b/test/functional/wallet_multiwallet.py index 2212e424467e7..9ff85945a5160 100755 --- a/test/functional/wallet_multiwallet.py +++ b/test/functional/wallet_multiwallet.py @@ -157,10 +157,6 @@ def wallet_file(name): open(not_a_dir, 'a', encoding="utf8").close() self.nodes[0].assert_start_raises_init_error(['-walletdir=' + not_a_dir], 'Error: Specified -walletdir "' + not_a_dir + '" is not a directory') - self.log.info("Do not allow -upgradewallet with multiwallet") - self.nodes[0].assert_start_raises_init_error(['-upgradewallet', '-wallet=w1', '-wallet=w2'], "Error: -upgradewallet is only allowed with a single wallet file") - self.nodes[0].assert_start_raises_init_error(['-upgradewallet=1', '-wallet=w1', '-wallet=w2'], "Error: -upgradewallet is only allowed with a single wallet file") - # if wallets/ doesn't exist, datadir should be the default wallet dir wallet_dir2 = data_dir('walletdir') os.rename(wallet_dir(), wallet_dir2) @@ -401,18 +397,5 @@ def wallet_file(name): self.nodes[0].unloadwallet(wallet) self.nodes[1].loadwallet(wallet) - # Fail to load if wallet is downgraded - shutil.copytree(os.path.join(self.options.data_wallets_dir, 'high_minversion'), wallet_dir('high_minversion')) - self.restart_node(0, extra_args=['-upgradewallet={}'.format(FEATURE_LATEST)]) - assert {'name': 'high_minversion'} in self.nodes[0].listwalletdir()['wallets'] - self.log.info("Fail -upgradewallet that results in downgrade") - assert_raises_rpc_error( - -4, - 'Wallet loading failed. Error loading {}: Wallet requires newer version of {}'.format( - wallet_dir('high_minversion', 'wallet.dat'), "Dash Core"), - lambda: self.nodes[0].loadwallet(filename='high_minversion'), - ) - - if __name__ == '__main__': MultiWalletTest().main()