Skip to content

Commit 9b27fda

Browse files
jonasschnellijonspock
authored andcommitted
Merge #13058: [wallet] createwallet RPC - create new wallet at runtime
Summary: f7e153e95 [wallets] [docs] Add release notes for createwallet RPC. (John Newbery) 32167e830 [wallet] [tests] Add tests for `createwallet` RPC. (John Newbery) 942131774 [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 Backport of Core PR13058 bitcoin/bitcoin#13058 Test Plan: make check test_runner.py Reviewers: deadalnix, Fabien, jasonbcox, O1 Bitcoin ABC, #bitcoin_abc, fpelliccioni Reviewed By: deadalnix, O1 Bitcoin ABC, #bitcoin_abc Subscribers: fpelliccioni Differential Revision: https://reviews.bitcoinabc.org/D4220
1 parent a86ee70 commit 9b27fda

File tree

3 files changed

+81
-70
lines changed

3 files changed

+81
-70
lines changed

doc/release-notes.md

Lines changed: 18 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,21 @@
1-
This release includes the following features and fixes since 1.1.3 release:
1+
This release includes the following features and fixes since 1.1.4 release:
22

3-
Miscellaneous changes
4-
CMake build updates
5-
Speed up unit test builds
3+
This release includes the following features and fixes:
4+
- Minor bug fixes and improvements.
5+
- New `fees` field introduced in `getrawmempool`, `getmempoolancestors`,
6+
`getmempooldescendants` and `getmempoolentry` when verbosity is set to
7+
`true` with sub-fields `ancestor`, `base`, `modified` and `descendant`
8+
denominated in BCH. This new field deprecates previous fee fields, such a
9+
`fee`, `modifiedfee`, `ancestorfee` and `descendantfee`.
610

7-
About 100 upstream bitcoin-abc updates
11+
Dynamic creation of wallets
12+
---------------------------------------
13+
- Previously, wallets could only be loaded or created at startup, by
14+
specifying `-wallet` parameters on the command line or in the bitcoin.conf
15+
file. It is now possible to create wallets dynamically at runtime:
816

17+
- New wallets can be created (and loaded) by calling the `createwallet` RPC.
18+
The provided name must not match a wallet file in the `walletdir` directory
19+
or the name of a wallet that is currently loaded.
20+
21+
- This feature is currently only available through the RPC interface.

src/wallet/rpcwallet.cpp

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3445,6 +3445,68 @@ UniValue loadwallet(const Config &config, const JSONRPCRequest &request) {
34453445
return obj;
34463446
}
34473447

3448+
UniValue createwallet(const Config &config, const JSONRPCRequest &request) {
3449+
if (request.fHelp || request.params.size() != 1) {
3450+
throw std::runtime_error(
3451+
"createwallet \"wallet_name\"\n"
3452+
"\nCreates and loads a new wallet.\n"
3453+
"\nArguments:\n"
3454+
"1. \"wallet_name\" (string, required) The name for the new wallet. "
3455+
"If this is a path, the wallet will be created at the path location.\n"
3456+
"2. \"password\" (string, optional) The password for the encrypted wallet or blank if empty. "
3457+
"\nResult:\n"
3458+
"{\n"
3459+
" \"name\" : <wallet_name>, (string) The wallet name if "
3460+
"created successfully. If the wallet was created using a full "
3461+
"path, the wallet_name will be the full path.\n"
3462+
" \"warning\" : <warning>, (string) Warning message if "
3463+
"wallet was not loaded cleanly.\n"
3464+
"}\n"
3465+
"\nExamples:\n" +
3466+
HelpExampleCli("createwallet", "\"testwallet\"") +
3467+
HelpExampleRpc("createwallet", "\"testwallet\""));
3468+
}
3469+
3470+
const CChainParams &chainParams = config.GetChainParams();
3471+
3472+
std::string wallet_name = request.params[0].get_str();
3473+
std::string password = request.params[1].get_str();
3474+
std::string error;
3475+
std::string warning;
3476+
3477+
WalletLocation location(wallet_name);
3478+
3479+
if (location.Exists()) {
3480+
throw JSONRPCError(RPC_WALLET_ERROR,
3481+
"Wallet " + wallet_name + " already exists.");
3482+
}
3483+
3484+
// Wallet::Verify will check if we're trying to create a wallet with a
3485+
// duplicate name.
3486+
if (!CWallet::Verify(chainParams, location, false, error, warning)) {
3487+
throw JSONRPCError(RPC_WALLET_ERROR,
3488+
"Wallet file verification failed: " + error);
3489+
}
3490+
3491+
std::vector<std::string> words;
3492+
bool use_bls = false;
3493+
SecureString passphrase(password);
3494+
std::shared_ptr<CWallet> const wallet = CWallet::CreateWalletFromFile(
3495+
chainParams, location, passphrase, words, use_bls);
3496+
if (!wallet) {
3497+
throw JSONRPCError(RPC_WALLET_ERROR, "Wallet creation failed.");
3498+
}
3499+
AddWallet(wallet);
3500+
3501+
wallet->postInitProcess();
3502+
3503+
UniValue obj(UniValue::VOBJ);
3504+
obj.pushKV("name", wallet->GetName());
3505+
obj.pushKV("warning", warning);
3506+
3507+
return obj;
3508+
}
3509+
34483510
static UniValue resendwallettransactions(const Config &config,
34493511
const JSONRPCRequest &request) {
34503512
std::shared_ptr<CWallet> const wallet = GetWalletForJSONRPCRequest(request);
@@ -4236,6 +4298,7 @@ static const ContextFreeRPCCommand commands[] = {
42364298
{ "wallet", "abandontransaction", abandontransaction, {"txid"} },
42374299
{ "wallet", "addmultisigaddress", addmultisigaddress, {"nrequired","keys","label|account"} },
42384300
{ "wallet", "backupwallet", backupwallet, {"destination"} },
4301+
{ "wallet", "createwallet", createwallet, {"wallet_name"} },
42394302
{ "wallet", "getlabeladdress", getlabeladdress, {"label"} },
42404303
{ "wallet", "getaddressesbylabels", getaddressesbylabels, {} },
42414304
{ "wallet", "getbalance", getbalance, {"account","minconf","include_watchonly"} },

test/functional/wallet_multiwallet.py

Lines changed: 0 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -220,11 +220,6 @@ def wallet(name): return node.get_wallet_rpc(name)
220220
assert_raises_rpc_error(-4, "Wallet file verification failed: Invalid -wallet path 'w8_symlink'",
221221
self.nodes[0].loadwallet, 'w8_symlink')
222222

223-
# Fail to load if a directory is specified that doesn't contain a wallet
224-
os.mkdir(wallet_dir('empty_wallet_dir'))
225-
assert_raises_rpc_error(-18, "Directory empty_wallet_dir does not contain a wallet.dat file",
226-
self.nodes[0].loadwallet, 'empty_wallet_dir')
227-
228223
self.log.info("Test dynamic wallet creation.")
229224

230225
# Fail to create a wallet if it already exists.
@@ -249,66 +244,6 @@ def wallet(name): return node.get_wallet_rpc(name)
249244

250245
assert new_wallet_name in self.nodes[0].listwallets()
251246

252-
self.log.info("Test dynamic wallet unloading")
253-
254-
# Test `unloadwallet` errors
255-
assert_raises_rpc_error(-1, "JSON value is not a string as expected",
256-
self.nodes[0].unloadwallet)
257-
assert_raises_rpc_error(-18, "Requested wallet does not exist or is not loaded",
258-
self.nodes[0].unloadwallet, "dummy")
259-
assert_raises_rpc_error(-18, "Requested wallet does not exist or is not loaded",
260-
node.get_wallet_rpc("dummy").unloadwallet)
261-
assert_raises_rpc_error(-8, "Cannot unload the requested wallet",
262-
w1.unloadwallet, "w2"),
263-
264-
# Successfully unload the specified wallet name
265-
self.nodes[0].unloadwallet("w1")
266-
assert 'w1' not in self.nodes[0].listwallets()
267-
268-
# Successfully unload the wallet referenced by the request endpoint
269-
w2.unloadwallet()
270-
assert 'w2' not in self.nodes[0].listwallets()
271-
272-
# Successfully unload all wallets
273-
for wallet_name in self.nodes[0].listwallets():
274-
self.nodes[0].unloadwallet(wallet_name)
275-
assert_equal(self.nodes[0].listwallets(), [])
276-
assert_raises_rpc_error(-32601, "Method not found (wallet method is disabled because no wallet is loaded)",
277-
self.nodes[0].getwalletinfo)
278-
279-
# Successfully load a previously unloaded wallet
280-
self.nodes[0].loadwallet('w1')
281-
assert_equal(self.nodes[0].listwallets(), ['w1'])
282-
assert_equal(w1.getwalletinfo()['walletname'], 'w1')
283-
284-
# Test backing up and restoring wallets
285-
self.log.info("Test wallet backup")
286-
self.restart_node(0, ['-nowallet'])
287-
for wallet_name in wallet_names:
288-
self.nodes[0].loadwallet(wallet_name)
289-
for wallet_name in wallet_names:
290-
rpc = self.nodes[0].get_wallet_rpc(wallet_name)
291-
addr = rpc.getnewaddress()
292-
backup = os.path.join(self.options.tmpdir, 'backup.dat')
293-
rpc.backupwallet(backup)
294-
self.nodes[0].unloadwallet(wallet_name)
295-
shutil.copyfile(empty_wallet, wallet_file(wallet_name))
296-
self.nodes[0].loadwallet(wallet_name)
297-
assert_equal(rpc.getaddressinfo(addr)['ismine'], False)
298-
self.nodes[0].unloadwallet(wallet_name)
299-
shutil.copyfile(backup, wallet_file(wallet_name))
300-
self.nodes[0].loadwallet(wallet_name)
301-
assert_equal(rpc.getaddressinfo(addr)['ismine'], True)
302-
303-
# Test .walletlock file is closed
304-
self.start_node(1)
305-
wallet = os.path.join(self.options.tmpdir, 'my_wallet')
306-
self.nodes[0].createwallet(wallet)
307-
assert_raises_rpc_error(-4, "Error initializing wallet database environment",
308-
self.nodes[1].loadwallet, wallet)
309-
self.nodes[0].unloadwallet(wallet)
310-
self.nodes[1].loadwallet(wallet)
311-
312247

313248
if __name__ == '__main__':
314249
MultiWalletTest().main()

0 commit comments

Comments
 (0)