Adds a createwallet RPC to dynamically create a new wallet at runtime.
Includes tests and release notes.
Adds a createwallet RPC to dynamically create a new wallet at runtime.
Includes tests and release notes.
3989 | + wallet_file, GetWalletDir()); 3990 | + return false; 3991 | + } 3992 | + 3993 | + // Make sure that the wallet path doesn't clash with an existing wallet path 3994 | + std::set<fs::path> wallet_paths;
As mentioned in #13028, I think this does not have sufficient concurrency protection (maybe okay since edge case). AFAIK, callers calling CWallet::Verify as well as inside the call, there is no lock being held, thus, I think, it is possible that there could be a race in loadwallet and createwallet bypassing the filename check.
2954 | + 2955 | + UniValue obj(UniValue::VSTR); 2956 | + obj = wallet_name; 2957 | + 2958 | + return obj; 2959 | +
Empty line.
Concept ACK. Needs rebase.
rebased
3057 | + ); 3058 | + std::string wallet_name = request.params[0].get_str(); 3059 | + std::string error; 3060 | + 3061 | + fs::path wallet_path = fs::absolute(wallet_name, GetWalletDir()); 3062 | + if (fs::symlink_status(wallet_path).type() != fs::file_not_found) {
This doesn't prevent concurrency which is very unlikely and so can be improved later.
CWallet::Verify() below takes the cs_wallets lock, so I think the worst that can happen is the first wallet creation succeeds and the second fails Verify().
218 | + 219 | + # Successfully create a wallet with a new name 220 | + self.nodes[0].createwallet('w9') 221 | + 222 | + assert 'w9' in self.nodes[0].listwallets() 223 | +
Also call some method in w9, like get_wallet_rpc('w9').getwalletinfo()?
sure. Added
210 | @@ -211,5 +211,15 @@ def run_test(self): 211 | # Fail to load if wallet file is a symlink 212 | assert_raises_rpc_error(-4, "Wallet file verification failed: Invalid -wallet path 'w8_symlink'", self.nodes[0].loadwallet, 'w8_symlink') 213 | 214 | + self.log.info("Test dynamic wallet creation.") 215 | +
Could test invalid argument.
We don't exhaustively test invalid args for RPCs (since it's just hitting the univalue type checking code).
3048 | + "createwallet \"filename\"\n" 3049 | + "\nCreates and loads a new wallet.\n" 3050 | + "\nArguments:\n" 3051 | + "1. \"wallet_name\" (string, required) The name for the new wallet.\n" 3052 | + "\nResult:\n" 3053 | + "<walletname> (string) The wallet name if created successfully.\n"
Return same format as loadwallet?
Done
Tested ACK, but left a couple of comments for your appreciation.
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 | + self.nodes[0].createwallet('w9')
Assert result.
Done
Needs rebase.
Tested ACK 53e71dc4474db083f99ca0d30ca3abd5a492b1a6. Conceptually, I think adding an option for load/not-load could make sense,... or is the use care of pure wallet creation useless? Eventually create should never load since it should not hurt on RPC level to call a chain of commands to complete a task.
I think adding an option for load/not-load could make sense,... or is the use care of pure wallet creation useless? Eventually create should never load.
My first thoughts would be that walletcreate should always load the wallet on creation. It seems unintuitive to have to call loadwallet immediately after creating a new wallet.
If there's a requirement for 'create without load', then I think that should be provided by a bitcoin-wallet-tool. There's no need to have a full node running if you just want to create a new wallet file.
My first thoughts would be that walletcreate should always load the wallet on creation. It seems unintuitive to have to call loadwallet immediately after creating a new wallet.
Not sure here. We do design an RPC interface/API and not end user function representations. An additional step seems acceptable if it widens the usability of the API.... though the question of what use cases a pure create could fulfil is one that may not have concrete answers today.
@jonasschnelli - I'm happy to take wider feedback on this. My preference would be to keep the current branch's behaviour (always load when creating) and decide whether to add create-without-load later with a new argument. I'm happy to change that though if others think we should have create-without-load now.
Yes. Lets try to get more opinions... I guess it could also be added later (an option to make create not load). However, this is a great PR and I'd like to underline my tested ACK #13058 (comment)
Well if createwallet creates and loads the wallet and there is no use case for just create, then a valid alternative would be to add an option to loadwallet to create in case it doesn't exist?
a valid alternative would be to add an option to loadwallet to create in case it doesn't exist.
I'd prefer to keep these two methods separate. It seems to me that reloading an existing wallet and creating a new wallet are completely different operations and should have their own RPC methods.
3086 | + UniValue obj(UniValue::VOBJ); 3087 | + obj.pushKV("name", wallet->GetName()); 3088 | + obj.pushKV("warning", warning); 3089 | + 3090 | + return obj; 3091 | +
nit, remove empty line.
removed
3049 | + if (request.fHelp || request.params.size() != 1) 3050 | + throw std::runtime_error( 3051 | + "createwallet \"filename\"\n" 3052 | + "\nCreates and loads a new wallet.\n" 3053 | + "\nArguments:\n" 3054 | + "1. \"wallet_name\" (string, required) The name for the new wallet.\n"
Here wallet_name, above filename 🙄
fixed
Add a `createwallet` RPC to allow wallets to be created dynamically at
runtime. This functionality is currently only available through RPC and
newly created wallets will not be displayed in the GUI.
3073 | + // Wallet::Verify will check if we're trying to create a wallet with a duplication name. 3074 | + if (!CWallet::Verify(wallet_name, false, error, warning)) { 3075 | + throw JSONRPCError(RPC_WALLET_ERROR, "Wallet file verification failed: " + error); 3076 | + } 3077 | + 3078 | + CWallet * const wallet = CWallet::CreateWalletFromFile(wallet_name, fs::absolute(wallet_name, GetWalletDir()));
std::shared_ptr<CWallet> wallet = CWallet::CreateWalletFromFile(...);
Thanks promag. Rebased and fixed
6 | -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. 7 | +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: 8 | 9 | -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. 10 | +- 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. 11 | +- New wallets can be created 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.
nit, can be created (and loaded) by calling ...?
Sure. Changed
Tested ACK 8266c84.
3127 | + " \"name\" : <wallet_name>, (string) The wallet name if created successfully.\n" 3128 | + " \"warning\" : <warning>, (string) Warning message if wallet was not loaded cleanly.\n" 3129 | + "}\n" 3130 | + "\nExamples:\n" 3131 | + + HelpExampleCli("createwallet", "\"test.dat\"") 3132 | + + HelpExampleRpc("createwallet", "\"test.dat\"")
Are these .dat suffixes neccessary? It would create a folder named test.dat on my Windows PC.
You're right. I've changed these to testwallet
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.\n"
It seems confusing to call this wallet_name and instead accept also accept a path. Something in the named arg that indicates that this is a file would help. Also please make sure the doc string matches the named arg and then test for the name of the argument in the functional tests.
I can't think of a better argument name, so I've just updated the help text.
Also added a test to create a wallet using a full path.
3122 | + "\nCreates and loads a new wallet.\n" 3123 | + "\nArguments:\n" 3124 | + "1. \"wallet_name\" (string, required) The name for the new wallet.\n" 3125 | + "\nResult:\n" 3126 | + "{\n" 3127 | + " \"name\" : <wallet_name>, (string) The wallet name if created successfully.\n"
Same here
help text updated.
3113 | @@ -3114,6 +3114,52 @@ UniValue loadwallet(const JSONRPCRequest& request) 3114 | return obj; 3115 | } 3116 | 3117 | +UniValue createwallet(const JSONRPCRequest& request) 3118 | +{ 3119 | + if (request.fHelp || request.params.size() != 1)
Style-nit: Curly braces for multi-line-then statements
Changed.
some doc-nits (Can safely be ignored, I volunteer to fix them up post-merge)
Addressed all @promag and @MarcoFalke comments.
utACK f7e153e
utACK f7e153e.
utACK f7e153e95f
Tested ACK f7e153e95f08e662427e8e4abcac8f53ab58281b
Milestone
0.17.0