diff --git a/doc/release-notes.md b/doc/release-notes.md --- a/doc/release-notes.md +++ b/doc/release-notes.md @@ -9,3 +9,15 @@ `true` with sub-fields `ancestor`, `base`, `modified` and `descendant` denominated in BCH. This new field deprecates previous fee fields, such a `fee`, `modifiedfee`, `ancestorfee` and `descendantfee`. + +Dynamic creation of wallets +--------------------------------------- + - Previously, wallets could only be loaded or created at startup, by + specifying `-wallet` parameters on the command line or in the bitcoin.conf + file. It is now possible to create wallets dynamically at runtime: + + - New wallets can be created (and loaded) by calling the `createwallet` RPC. + The provided name must not match a wallet file in the `walletdir` directory + or the name of a wallet that is currently loaded. + + - This feature is currently only available through the RPC interface. diff --git a/src/wallet/rpcwallet.cpp b/src/wallet/rpcwallet.cpp --- a/src/wallet/rpcwallet.cpp +++ b/src/wallet/rpcwallet.cpp @@ -3409,6 +3409,63 @@ return obj; } +UniValue createwallet(const Config &config, const JSONRPCRequest &request) { + if (request.fHelp || request.params.size() != 1) { + throw std::runtime_error( + "createwallet \"wallet_name\"\n" + "\nCreates and loads a new wallet.\n" + "\nArguments:\n" + "1. \"wallet_name\" (string, required) The name for the new " + "wallet. If this is a path, the wallet will be created at the path " + "location.\n" + "\nResult:\n" + "{\n" + " \"name\" : , (string) The wallet name if " + "created successfully. If the wallet was created using a full " + "path, the wallet_name will be the full path.\n" + " \"warning\" : , (string) Warning message if " + "wallet was not loaded cleanly.\n" + "}\n" + "\nExamples:\n" + + HelpExampleCli("createwallet", "\"testwallet\"") + + HelpExampleRpc("createwallet", "\"testwallet\"")); + } + + const CChainParams &chainParams = config.GetChainParams(); + + std::string wallet_name = request.params[0].get_str(); + std::string error; + std::string warning; + + fs::path wallet_path = fs::absolute(wallet_name, GetWalletDir()); + if (fs::symlink_status(wallet_path).type() != fs::file_not_found) { + throw JSONRPCError(RPC_WALLET_ERROR, + "Wallet " + wallet_name + " already exists."); + } + + // Wallet::Verify will check if we're trying to create a wallet with a + // duplicate name. + if (!CWallet::Verify(chainParams, wallet_name, false, error, warning)) { + throw JSONRPCError(RPC_WALLET_ERROR, + "Wallet file verification failed: " + error); + } + + std::shared_ptr const wallet = CWallet::CreateWalletFromFile( + chainParams, wallet_name, fs::absolute(wallet_name, GetWalletDir())); + if (!wallet) { + throw JSONRPCError(RPC_WALLET_ERROR, "Wallet creation failed."); + } + AddWallet(wallet); + + wallet->postInitProcess(); + + UniValue obj(UniValue::VOBJ); + obj.pushKV("name", wallet->GetName()); + obj.pushKV("warning", warning); + + return obj; +} + static UniValue resendwallettransactions(const Config &config, const JSONRPCRequest &request) { std::shared_ptr const wallet = GetWalletForJSONRPCRequest(request); @@ -4461,6 +4518,7 @@ { "wallet", "abandontransaction", abandontransaction, {"txid"} }, { "wallet", "addmultisigaddress", addmultisigaddress, {"nrequired","keys","label|account"} }, { "wallet", "backupwallet", backupwallet, {"destination"} }, + { "wallet", "createwallet", createwallet, {"wallet_name"} }, { "wallet", "encryptwallet", encryptwallet, {"passphrase"} }, { "wallet", "getaccountaddress", getlabeladdress, {"account"} }, { "wallet", "getlabeladdress", getlabeladdress, {"label"} }, diff --git a/test/functional/wallet_multiwallet.py b/test/functional/wallet_multiwallet.py --- a/test/functional/wallet_multiwallet.py +++ b/test/functional/wallet_multiwallet.py @@ -222,6 +222,30 @@ assert_raises_rpc_error(-4, "Wallet file verification failed: Invalid -wallet path 'w8_symlink'", self.nodes[0].loadwallet, 'w8_symlink') + self.log.info("Test dynamic wallet creation.") + + # Fail to create a wallet if it already exists. + assert_raises_rpc_error(-4, "Wallet w2 already exists.", + self.nodes[0].createwallet, 'w2') + + # Successfully create a wallet with a new name + loadwallet_name = self.nodes[0].createwallet('w9') + assert_equal(loadwallet_name['name'], 'w9') + w9 = node.get_wallet_rpc('w9') + assert_equal(w9.getwalletinfo()['walletname'], 'w9') + + assert 'w9' in self.nodes[0].listwallets() + + # Successfully create a wallet using a full path + new_wallet_dir = os.path.join(self.options.tmpdir, 'new_walletdir') + new_wallet_name = os.path.join(new_wallet_dir, 'w10') + loadwallet_name = self.nodes[0].createwallet(new_wallet_name) + assert_equal(loadwallet_name['name'], new_wallet_name) + w10 = node.get_wallet_rpc(new_wallet_name) + assert_equal(w10.getwalletinfo()['walletname'], new_wallet_name) + + assert new_wallet_name in self.nodes[0].listwallets() + if __name__ == '__main__': MultiWalletTest().main()