diff --git a/doc/release-notes-pr10740.md b/doc/release-notes-pr10740.md new file mode 100644 index 000000000..a57cbc793 --- /dev/null +++ b/doc/release-notes-pr10740.md @@ -0,0 +1,8 @@ +Dynamic loading of wallets +-------------------------- + +Previously, wallets could only be loaded at startup, by specifying `-wallet` parameters on the command line or in the bitcoin.conf file. It is now possible to load wallets dynamically at runtime by calling the `loadwallet` RPC. + +The wallet can be specified as file/directory basename (which must be located in the `walletdir` directory), or as an absolute path to a file/directory. + +This feature is currently only available through the RPC interface. Wallets loaded in this way will not display in the bitcoin-qt GUI. diff --git a/src/wallet/init.cpp b/src/wallet/init.cpp index 6c5522e4b..5cfa86451 100644 --- a/src/wallet/init.cpp +++ b/src/wallet/init.cpp @@ -6,6 +6,7 @@ #include #include #include +#include #include #include #include @@ -189,55 +190,29 @@ bool WalletInit::Verify() const uiInterface.InitMessage(_("Verifying wallet(s)...")); + std::vector wallet_files = gArgs.GetArgs("-wallet"); + + // Parameter interaction code should have thrown an error if -salvagewallet + // was enabled with more than wallet file, so the wallet_files size check + // here should have no effect. + bool salvage_wallet = gArgs.GetBoolArg("-salvagewallet", false) && wallet_files.size() <= 1; + // Keep track of each wallet absolute path to detect duplicates. std::set wallet_paths; - for (const std::string& walletFile : gArgs.GetArgs("-wallet")) { - // Do some checking on wallet path. It should be either a: - // - // 1. Path where a directory can be created. - // 2. Path to an existing directory. - // 3. Path to a symlink to a directory. - // 4. For backwards compatibility, the name of a data file in -walletdir. - fs::path wallet_path = fs::absolute(walletFile, GetWalletDir()); - fs::file_type path_type = fs::symlink_status(wallet_path).type(); - if (!(path_type == fs::file_not_found || path_type == fs::directory_file || - (path_type == fs::symlink_file && fs::is_directory(wallet_path)) || - (path_type == fs::regular_file && fs::path(walletFile).filename() == walletFile))) { - return InitError(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)"), - walletFile, GetWalletDir())); - } + for (const auto wallet_file : wallet_files) { + fs::path wallet_path = fs::absolute(wallet_file, GetWalletDir()); if (!wallet_paths.insert(wallet_path).second) { - return InitError(strprintf(_("Error loading wallet %s. Duplicate -wallet filename specified."), walletFile)); + return InitError(strprintf(_("Error loading wallet %s. Duplicate -wallet filename specified."), wallet_file)); } - std::string strError; - if (!WalletBatch::VerifyEnvironment(wallet_path, strError)) { - return InitError(strError); - } - - if (gArgs.GetBoolArg("-salvagewallet", false)) { - // Recover readable keypairs: - CWallet dummyWallet("dummy", WalletDatabase::CreateDummy()); - std::string backup_filename; - if (!WalletBatch::Recover(wallet_path, (void *)&dummyWallet, WalletBatch::RecoverKeysOnlyFilter, backup_filename)) { - return false; - } - } - - std::string strWarning; - bool dbV = WalletBatch::VerifyDatabaseFile(wallet_path, strWarning, strError); - if (!strWarning.empty()) { - InitWarning(strWarning); - } - if (!dbV) { - InitError(strError); - return false; - } + std::string error_string; + std::string warning_string; + bool verify_success = CWallet::Verify(wallet_file, salvage_wallet, error_string, warning_string); + if (!error_string.empty()) InitError(error_string); + if (!warning_string.empty()) InitWarning(warning_string); + if (!verify_success) return false; } return true; @@ -264,8 +239,11 @@ bool WalletInit::Open() const void WalletInit::Start(CScheduler& scheduler) const { for (CWallet* pwallet : GetWallets()) { - pwallet->postInitProcess(scheduler); + pwallet->postInitProcess(); } + + // Run a thread to flush wallet periodically + scheduler.scheduleEvery(MaybeCompactWalletDB, 500); } void WalletInit::Flush() const diff --git a/src/wallet/rpcwallet.cpp b/src/wallet/rpcwallet.cpp index a7b2b05d9..8cd395283 100644 --- a/src/wallet/rpcwallet.cpp +++ b/src/wallet/rpcwallet.cpp @@ -2994,6 +2994,53 @@ static UniValue listwallets(const JSONRPCRequest& request) return obj; } +UniValue loadwallet(const JSONRPCRequest& request) +{ + if (request.fHelp || request.params.size() != 1) + throw std::runtime_error( + "loadwallet \"filename\"\n" + "\nLoads a wallet from a wallet file or directory." + "\nNote that all wallet command-line options used when starting bitcoind will be" + "\napplied to the new wallet (eg -zapwallettxes, upgradewallet, rescan, etc).\n" + "\nArguments:\n" + "1. \"filename\" (string, required) The wallet directory or .dat file.\n" + "\nResult:\n" + "{\n" + " \"name\" : , (string) The wallet name if loaded successfully.\n" + " \"warning\" : , (string) Warning message if wallet was not loaded cleanly.\n" + "}\n" + "\nExamples:\n" + + HelpExampleCli("loadwallet", "\"test.dat\"") + + HelpExampleRpc("loadwallet", "\"test.dat\"") + ); + std::string wallet_file = request.params[0].get_str(); + std::string error; + + fs::path wallet_path = fs::absolute(wallet_file, GetWalletDir()); + if (fs::symlink_status(wallet_path).type() == fs::file_not_found) { + throw JSONRPCError(RPC_WALLET_NOT_FOUND, "Wallet " + wallet_file + " not found."); + } + + std::string warning; + if (!CWallet::Verify(wallet_file, false, error, warning)) { + throw JSONRPCError(RPC_WALLET_ERROR, "Wallet file verification failed: " + error); + } + + CWallet * const wallet = CWallet::CreateWalletFromFile(wallet_file, fs::absolute(wallet_file, GetWalletDir())); + if (!wallet) { + throw JSONRPCError(RPC_WALLET_ERROR, "Wallet loading failed."); + } + AddWallet(wallet); + + wallet->postInitProcess(); + + UniValue obj(UniValue::VOBJ); + obj.pushKV("name", wallet->GetName()); + obj.pushKV("warning", warning); + + return obj; +} + static UniValue resendwallettransactions(const JSONRPCRequest& request) { CWallet * const pwallet = GetWalletForJSONRPCRequest(request); @@ -4197,6 +4244,7 @@ static const CRPCCommand commands[] = { "wallet", "listtransactions", &listtransactions, {"account|dummy","count","skip","include_watchonly"} }, { "wallet", "listunspent", &listunspent, {"minconf","maxconf","addresses","include_unsafe","query_options"} }, { "wallet", "listwallets", &listwallets, {} }, + { "wallet", "loadwallet", &loadwallet, {"filename"} }, { "wallet", "lockunspent", &lockunspent, {"unlock","transactions"} }, { "wallet", "sendfrom", &sendfrom, {"fromaccount","toaddress","amount","minconf","comment","comment_to"} }, { "wallet", "sendmany", &sendmany, {"fromaccount|dummy","amounts","minconf","comment","subtractfeefrom","replaceable","conf_target","estimate_mode"} }, diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp index 5be9f4a29..74f36e9ab 100644 --- a/src/wallet/wallet.cpp +++ b/src/wallet/wallet.cpp @@ -23,11 +23,11 @@ #include #include #include