Skip to content

Commit 343d4e4

Browse files
committed
Merge #13058: [wallet] createwallet RPC - create new wallet at runtime
f7e153e [wallets] [docs] Add release notes for createwallet RPC. (John Newbery) 32167e8 [wallet] [tests] Add tests for `createwallet` RPC. (John Newbery) 9421317 [wallet] [rpc] Add `createwallet` RPC (John Newbery) Pull request description: Adds a `createwallet` RPC to dynamically create a new wallet at runtime. Includes tests and release notes. Tree-SHA512: e0d89e3ae498234e9db5b827c56804cbab64f18a1875e2b5e676172c110278ea1b9e93a8a61b8dd80e2f2a691490bf229e923e4ccb284a1d3e420b8317815866
2 parents 0b1c0c4 + f7e153e commit 343d4e4

File tree

3 files changed

+77
-5
lines changed

3 files changed

+77
-5
lines changed

doc/release-notes-pr10740.md

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
1-
Dynamic loading of wallets
2-
--------------------------
1+
Dynamic loading and creation of wallets
2+
---------------------------------------
33

4-
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.
4+
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 load and create wallets dynamically at runtime:
55

6-
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.
6+
- Existing wallets can be loaded 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.
7+
- 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.
78

8-
This feature is currently only available through the RPC interface. Wallets loaded in this way will display in the bitcoin-qt GUI.
9+
This feature is currently only available through the RPC interface.

src/wallet/rpcwallet.cpp

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3114,6 +3114,53 @@ UniValue loadwallet(const JSONRPCRequest& request)
31143114
return obj;
31153115
}
31163116

3117+
UniValue createwallet(const JSONRPCRequest& request)
3118+
{
3119+
if (request.fHelp || request.params.size() != 1) {
3120+
throw std::runtime_error(
3121+
"createwallet \"wallet_name\"\n"
3122+
"\nCreates and loads a new wallet.\n"
3123+
"\nArguments:\n"
3124+
"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"
3125+
"\nResult:\n"
3126+
"{\n"
3127+
" \"name\" : <wallet_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"
3128+
" \"warning\" : <warning>, (string) Warning message if wallet was not loaded cleanly.\n"
3129+
"}\n"
3130+
"\nExamples:\n"
3131+
+ HelpExampleCli("createwallet", "\"testwallet\"")
3132+
+ HelpExampleRpc("createwallet", "\"testwallet\"")
3133+
);
3134+
}
3135+
std::string wallet_name = request.params[0].get_str();
3136+
std::string error;
3137+
std::string warning;
3138+
3139+
fs::path wallet_path = fs::absolute(wallet_name, GetWalletDir());
3140+
if (fs::symlink_status(wallet_path).type() != fs::file_not_found) {
3141+
throw JSONRPCError(RPC_WALLET_ERROR, "Wallet " + wallet_name + " already exists.");
3142+
}
3143+
3144+
// Wallet::Verify will check if we're trying to create a wallet with a duplication name.
3145+
if (!CWallet::Verify(wallet_name, false, error, warning)) {
3146+
throw JSONRPCError(RPC_WALLET_ERROR, "Wallet file verification failed: " + error);
3147+
}
3148+
3149+
std::shared_ptr<CWallet> const wallet = CWallet::CreateWalletFromFile(wallet_name, fs::absolute(wallet_name, GetWalletDir()));
3150+
if (!wallet) {
3151+
throw JSONRPCError(RPC_WALLET_ERROR, "Wallet creation failed.");
3152+
}
3153+
AddWallet(wallet);
3154+
3155+
wallet->postInitProcess();
3156+
3157+
UniValue obj(UniValue::VOBJ);
3158+
obj.pushKV("name", wallet->GetName());
3159+
obj.pushKV("warning", warning);
3160+
3161+
return obj;
3162+
}
3163+
31173164
static UniValue resendwallettransactions(const JSONRPCRequest& request)
31183165
{
31193166
std::shared_ptr<CWallet> const wallet = GetWalletForJSONRPCRequest(request);
@@ -4315,6 +4362,7 @@ static const CRPCCommand commands[] =
43154362
{ "hidden", "addwitnessaddress", &addwitnessaddress, {"address","p2sh"} },
43164363
{ "wallet", "backupwallet", &backupwallet, {"destination"} },
43174364
{ "wallet", "bumpfee", &bumpfee, {"txid", "options"} },
4365+
{ "wallet", "createwallet", &createwallet, {"wallet_name"} },
43184366
{ "wallet", "dumpprivkey", &dumpprivkey, {"address"} },
43194367
{ "wallet", "dumpwallet", &dumpwallet, {"filename"} },
43204368
{ "wallet", "encryptwallet", &encryptwallet, {"passphrase"} },

test/functional/wallet_multiwallet.py

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -211,5 +211,28 @@ def run_test(self):
211211
# Fail to load if wallet file is a symlink
212212
assert_raises_rpc_error(-4, "Wallet file verification failed: Invalid -wallet path 'w8_symlink'", self.nodes[0].loadwallet, 'w8_symlink')
213213

214+
self.log.info("Test dynamic wallet creation.")
215+
216+
# Fail to create a wallet if it already exists.
217+
assert_raises_rpc_error(-4, "Wallet w2 already exists.", self.nodes[0].createwallet, 'w2')
218+
219+
# Successfully create a wallet with a new name
220+
loadwallet_name = self.nodes[0].createwallet('w9')
221+
assert_equal(loadwallet_name['name'], 'w9')
222+
w9 = node.get_wallet_rpc('w9')
223+
assert_equal(w9.getwalletinfo()['walletname'], 'w9')
224+
225+
assert 'w9' in self.nodes[0].listwallets()
226+
227+
# Successfully create a wallet using a full path
228+
new_wallet_dir = os.path.join(self.options.tmpdir, 'new_walletdir')
229+
new_wallet_name = os.path.join(new_wallet_dir, 'w10')
230+
loadwallet_name = self.nodes[0].createwallet(new_wallet_name)
231+
assert_equal(loadwallet_name['name'], new_wallet_name)
232+
w10 = node.get_wallet_rpc(new_wallet_name)
233+
assert_equal(w10.getwalletinfo()['walletname'], new_wallet_name)
234+
235+
assert new_wallet_name in self.nodes[0].listwallets()
236+
214237
if __name__ == '__main__':
215238
MultiWalletTest().main()

0 commit comments

Comments
 (0)