Skip to content

Commit a0e75bd

Browse files
committed
Merge #15937: Add loadwallet and createwallet load_on_startup options
642ad31 Add loadwallet and createwallet RPC load_on_startup options (Russell Yanofsky) Pull request description: This maintains a persistent list of wallets stored in settings that will automatically be loaded on startup. Being able to load a wallet automatically on startup will be more useful in the GUI, but it's reasonable to expose this feature by RPC as well. ACKs for top commit: achow101: re-ACK 642ad31 Only change is the test meshcollider: re-utACK 642ad31 Tree-SHA512: cca0b71bf1a83ad071830e6c459f1cd979b4add7144e899ec560da72b5910dd9bf9426e5c7d125ae96fad8990fbf81a76bc83c0459486c16086ada6cbde5eaa3
2 parents f269165 + 642ad31 commit a0e75bd

File tree

11 files changed

+168
-8
lines changed

11 files changed

+168
-8
lines changed

doc/release-notes-15937.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
Configuration
2+
-------------
3+
4+
The `createwallet`, `loadwallet`, and `unloadwallet` RPCs now accept
5+
`load_on_startup` options that modify bitcoin's dynamic configuration in
6+
`\<datadir\>/settings.json`, and can add or remove a wallet from the list of
7+
wallets automatically loaded at startup. Unless these options are explicitly
8+
set to true or false, the load on startup wallet list is not modified, so this
9+
change is backwards compatible.
10+
11+
In the future, the GUI will start updating the same startup wallet list as the
12+
RPCs to automatically reopen wallets previously opened in the GUI.

src/interfaces/chain.cpp

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -372,6 +372,27 @@ class ChainImpl : public Chain
372372
RPCRunLater(name, std::move(fn), seconds);
373373
}
374374
int rpcSerializationFlags() override { return RPCSerializationFlags(); }
375+
util::SettingsValue getRwSetting(const std::string& name) override
376+
{
377+
util::SettingsValue result;
378+
gArgs.LockSettings([&](const util::Settings& settings) {
379+
if (const util::SettingsValue* value = util::FindKey(settings.rw_settings, name)) {
380+
result = *value;
381+
}
382+
});
383+
return result;
384+
}
385+
bool updateRwSetting(const std::string& name, const util::SettingsValue& value) override
386+
{
387+
gArgs.LockSettings([&](util::Settings& settings) {
388+
if (value.isNull()) {
389+
settings.rw_settings.erase(name);
390+
} else {
391+
settings.rw_settings[name] = value;
392+
}
393+
});
394+
return gArgs.WriteSettingsFile();
395+
}
375396
void requestMempoolTransactions(Notifications& notifications) override
376397
{
377398
LOCK2(::cs_main, ::mempool.cs);

src/interfaces/chain.h

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77

88
#include <optional.h> // For Optional and nullopt
99
#include <primitives/transaction.h> // For CTransactionRef
10+
#include <util/settings.h> // For util::SettingsValue
1011

1112
#include <functional>
1213
#include <memory>
@@ -269,6 +270,12 @@ class Chain
269270
//! Current RPC serialization flags.
270271
virtual int rpcSerializationFlags() = 0;
271272

273+
//! Return <datadir>/settings.json setting value.
274+
virtual util::SettingsValue getRwSetting(const std::string& name) = 0;
275+
276+
//! Write a setting to <datadir>/settings.json.
277+
virtual bool updateRwSetting(const std::string& name, const util::SettingsValue& value) = 0;
278+
272279
//! Synchronously send transactionAddedToMempool notifications about all
273280
//! current mempool transactions to the specified handler and return after
274281
//! the last one is sent. These notifications aren't coordinated with async

src/rpc/client.cpp

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -173,6 +173,9 @@ static const CRPCConvertParam vRPCConvertParams[] =
173173
{ "createwallet", 2, "blank"},
174174
{ "createwallet", 4, "avoid_reuse"},
175175
{ "createwallet", 5, "descriptors"},
176+
{ "createwallet", 6, "load_on_startup"},
177+
{ "loadwallet", 1, "load_on_startup"},
178+
{ "unloadwallet", 1, "load_on_startup"},
176179
{ "getnodeaddresses", 0, "count"},
177180
{ "addpeeraddress", 1, "port"},
178181
{ "stop", 0, "wait" },

src/wallet/init.cpp

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
#include <node/context.h>
1010
#include <node/ui_interface.h>
1111
#include <outputtype.h>
12+
#include <univalue.h>
1213
#include <util/check.h>
1314
#include <util/moneystr.h>
1415
#include <util/system.h>
@@ -118,6 +119,14 @@ void WalletInit::Construct(NodeContext& node) const
118119
LogPrintf("Wallet disabled!\n");
119120
return;
120121
}
121-
args.SoftSetArg("-wallet", "");
122+
// If there's no -wallet setting with a list of wallets to load, set it to
123+
// load the default "" wallet.
124+
if (!args.IsArgSet("wallet")) {
125+
args.LockSettings([&](util::Settings& settings) {
126+
util::SettingsValue wallets(util::SettingsValue::VARR);
127+
wallets.push_back(""); // Default wallet name is ""
128+
settings.rw_settings["wallet"] = wallets;
129+
});
130+
}
122131
node.chain_clients.emplace_back(interfaces::MakeWalletClient(*node.chain, args, args.GetArgs("-wallet")));
123132
}

src/wallet/load.cpp

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@
1313
#include <wallet/wallet.h>
1414
#include <wallet/walletdb.h>
1515

16+
#include <univalue.h>
17+
1618
bool VerifyWallets(interfaces::Chain& chain, const std::vector<std::string>& wallet_files)
1719
{
1820
if (gArgs.IsArgSet("-walletdir")) {
@@ -120,3 +122,26 @@ void UnloadWallets()
120122
UnloadWallet(std::move(wallet));
121123
}
122124
}
125+
126+
bool AddWalletSetting(interfaces::Chain& chain, const std::string& wallet_name)
127+
{
128+
util::SettingsValue setting_value = chain.getRwSetting("wallet");
129+
if (!setting_value.isArray()) setting_value.setArray();
130+
for (const util::SettingsValue& value : setting_value.getValues()) {
131+
if (value.isStr() && value.get_str() == wallet_name) return true;
132+
}
133+
setting_value.push_back(wallet_name);
134+
return chain.updateRwSetting("wallet", setting_value);
135+
}
136+
137+
bool RemoveWalletSetting(interfaces::Chain& chain, const std::string& wallet_name)
138+
{
139+
util::SettingsValue setting_value = chain.getRwSetting("wallet");
140+
if (!setting_value.isArray()) return true;
141+
util::SettingsValue new_value(util::SettingsValue::VARR);
142+
for (const util::SettingsValue& value : setting_value.getValues()) {
143+
if (!value.isStr() || value.get_str() != wallet_name) new_value.push_back(value);
144+
}
145+
if (new_value.size() == setting_value.size()) return true;
146+
return chain.updateRwSetting("wallet", new_value);
147+
}

src/wallet/load.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,4 +34,10 @@ void StopWallets();
3434
//! Close all wallets.
3535
void UnloadWallets();
3636

37+
//! Add wallet name to persistent configuration so it will be loaded on startup.
38+
bool AddWalletSetting(interfaces::Chain& chain, const std::string& wallet_name);
39+
40+
//! Remove wallet name from persistent configuration so it will not be loaded on startup.
41+
bool RemoveWalletSetting(interfaces::Chain& chain, const std::string& wallet_name);
42+
3743
#endif // BITCOIN_WALLET_LOAD_H

src/wallet/rpcwallet.cpp

Lines changed: 33 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
#include <wallet/coincontrol.h>
3131
#include <wallet/context.h>
3232
#include <wallet/feebumper.h>
33+
#include <wallet/load.h>
3334
#include <wallet/rpcwallet.h>
3435
#include <wallet/wallet.h>
3536
#include <wallet/walletdb.h>
@@ -229,6 +230,18 @@ static void SetFeeEstimateMode(const CWallet* pwallet, CCoinControl& cc, const U
229230
}
230231
}
231232

233+
static void UpdateWalletSetting(interfaces::Chain& chain,
234+
const std::string& wallet_name,
235+
const UniValue& load_on_startup,
236+
std::vector<bilingual_str>& warnings)
237+
{
238+
if (load_on_startup.isTrue() && !AddWalletSetting(chain, wallet_name)) {
239+
warnings.emplace_back(Untranslated("Wallet load on startup setting could not be updated, so wallet may not be loaded next node startup."));
240+
} else if (load_on_startup.isFalse() && !RemoveWalletSetting(chain, wallet_name)) {
241+
warnings.emplace_back(Untranslated("Wallet load on startup setting could not be updated, so wallet may still be loaded next node startup."));
242+
}
243+
}
244+
232245
static UniValue getnewaddress(const JSONRPCRequest& request)
233246
{
234247
RPCHelpMan{"getnewaddress",
@@ -2484,6 +2497,7 @@ static UniValue loadwallet(const JSONRPCRequest& request)
24842497
"\napplied to the new wallet (eg -zapwallettxes, rescan, etc).\n",
24852498
{
24862499
{"filename", RPCArg::Type::STR, RPCArg::Optional::NO, "The wallet directory or .dat file."},
2500+
{"load_on_startup", RPCArg::Type::BOOL, /* default */ "null", "Save wallet name to persistent settings and load on startup. True to add wallet to startup list, false to remove, null to leave unchanged."},
24872501
},
24882502
RPCResult{
24892503
RPCResult::Type::OBJ, "", "",
@@ -2516,6 +2530,8 @@ static UniValue loadwallet(const JSONRPCRequest& request)
25162530
std::shared_ptr<CWallet> const wallet = LoadWallet(*context.chain, location, error, warnings);
25172531
if (!wallet) throw JSONRPCError(RPC_WALLET_ERROR, error.original);
25182532

2533+
UpdateWalletSetting(*context.chain, location.GetName(), request.params[1], warnings);
2534+
25192535
UniValue obj(UniValue::VOBJ);
25202536
obj.pushKV("name", wallet->GetName());
25212537
obj.pushKV("warning", Join(warnings, Untranslated("\n")).original);
@@ -2600,6 +2616,7 @@ static UniValue createwallet(const JSONRPCRequest& request)
26002616
{"passphrase", RPCArg::Type::STR, RPCArg::Optional::OMITTED, "Encrypt the wallet with this passphrase."},
26012617
{"avoid_reuse", RPCArg::Type::BOOL, /* default */ "false", "Keep track of coin reuse, and treat dirty and clean coins differently with privacy considerations in mind."},
26022618
{"descriptors", RPCArg::Type::BOOL, /* default */ "false", "Create a native descriptor wallet. The wallet will use descriptors internally to handle address creation"},
2619+
{"load_on_startup", RPCArg::Type::BOOL, /* default */ "null", "Save wallet name to persistent settings and load on startup. True to add wallet to startup list, false to remove, null to leave unchanged."},
26032620
},
26042621
RPCResult{
26052622
RPCResult::Type::OBJ, "", "",
@@ -2655,6 +2672,8 @@ static UniValue createwallet(const JSONRPCRequest& request)
26552672
// no default case, so the compiler can warn about missing cases
26562673
}
26572674

2675+
UpdateWalletSetting(*context.chain, request.params[0].get_str(), request.params[6], warnings);
2676+
26582677
UniValue obj(UniValue::VOBJ);
26592678
obj.pushKV("name", wallet->GetName());
26602679
obj.pushKV("warning", Join(warnings, Untranslated("\n")).original);
@@ -2669,8 +2688,11 @@ static UniValue unloadwallet(const JSONRPCRequest& request)
26692688
"Specifying the wallet name on a wallet endpoint is invalid.",
26702689
{
26712690
{"wallet_name", RPCArg::Type::STR, /* default */ "the wallet name from the RPC request", "The name of the wallet to unload."},
2691+
{"load_on_startup", RPCArg::Type::BOOL, /* default */ "null", "Save wallet name to persistent settings and load on startup. True to add wallet to startup list, false to remove, null to leave unchanged."},
26722692
},
2673-
RPCResult{RPCResult::Type::NONE, "", ""},
2693+
RPCResult{RPCResult::Type::OBJ, "", "", {
2694+
{RPCResult::Type::STR, "warning", "Warning message if wallet was not unloaded cleanly."},
2695+
}},
26742696
RPCExamples{
26752697
HelpExampleCli("unloadwallet", "wallet_name")
26762698
+ HelpExampleRpc("unloadwallet", "wallet_name")
@@ -2698,9 +2720,15 @@ static UniValue unloadwallet(const JSONRPCRequest& request)
26982720
throw JSONRPCError(RPC_MISC_ERROR, "Requested wallet already unloaded");
26992721
}
27002722

2723+
interfaces::Chain& chain = wallet->chain();
2724+
std::vector<bilingual_str> warnings;
2725+
27012726
UnloadWallet(std::move(wallet));
2727+
UpdateWalletSetting(chain, wallet_name, request.params[1], warnings);
27022728

2703-
return NullUniValue;
2729+
UniValue result(UniValue::VOBJ);
2730+
result.pushKV("warning", Join(warnings, Untranslated("\n")).original);
2731+
return result;
27042732
}
27052733

27062734
static UniValue listunspent(const JSONRPCRequest& request)
@@ -4158,7 +4186,7 @@ static const CRPCCommand commands[] =
41584186
{ "wallet", "backupwallet", &backupwallet, {"destination"} },
41594187
{ "wallet", "bumpfee", &bumpfee, {"txid", "options"} },
41604188
{ "wallet", "psbtbumpfee", &psbtbumpfee, {"txid", "options"} },
4161-
{ "wallet", "createwallet", &createwallet, {"wallet_name", "disable_private_keys", "blank", "passphrase", "avoid_reuse", "descriptors"} },
4189+
{ "wallet", "createwallet", &createwallet, {"wallet_name", "disable_private_keys", "blank", "passphrase", "avoid_reuse", "descriptors", "load_on_startup"} },
41624190
{ "wallet", "dumpprivkey", &dumpprivkey, {"address"} },
41634191
{ "wallet", "dumpwallet", &dumpwallet, {"filename"} },
41644192
{ "wallet", "encryptwallet", &encryptwallet, {"passphrase"} },
@@ -4191,7 +4219,7 @@ static const CRPCCommand commands[] =
41914219
{ "wallet", "listunspent", &listunspent, {"minconf","maxconf","addresses","include_unsafe","query_options"} },
41924220
{ "wallet", "listwalletdir", &listwalletdir, {} },
41934221
{ "wallet", "listwallets", &listwallets, {} },
4194-
{ "wallet", "loadwallet", &loadwallet, {"filename"} },
4222+
{ "wallet", "loadwallet", &loadwallet, {"filename", "load_on_startup"} },
41954223
{ "wallet", "lockunspent", &lockunspent, {"unlock","transactions"} },
41964224
{ "wallet", "removeprunedfunds", &removeprunedfunds, {"txid"} },
41974225
{ "wallet", "rescanblockchain", &rescanblockchain, {"start_height", "stop_height"} },
@@ -4203,7 +4231,7 @@ static const CRPCCommand commands[] =
42034231
{ "wallet", "setwalletflag", &setwalletflag, {"flag","value"} },
42044232
{ "wallet", "signmessage", &signmessage, {"address","message"} },
42054233
{ "wallet", "signrawtransactionwithwallet", &signrawtransactionwithwallet, {"hexstring","prevtxs","sighashtype"} },
4206-
{ "wallet", "unloadwallet", &unloadwallet, {"wallet_name"} },
4234+
{ "wallet", "unloadwallet", &unloadwallet, {"wallet_name", "load_on_startup"} },
42074235
{ "wallet", "upgradewallet", &upgradewallet, {"version"} },
42084236
{ "wallet", "walletcreatefundedpsbt", &walletcreatefundedpsbt, {"inputs","outputs","locktime","options","bip32derivs"} },
42094237
{ "wallet", "walletlock", &walletlock, {} },

test/functional/test_framework/test_node.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -650,10 +650,10 @@ def __init__(self, rpc, cli=False, descriptors=False):
650650
def __getattr__(self, name):
651651
return getattr(self.rpc, name)
652652

653-
def createwallet(self, wallet_name, disable_private_keys=None, blank=None, passphrase='', avoid_reuse=None, descriptors=None):
653+
def createwallet(self, wallet_name, disable_private_keys=None, blank=None, passphrase='', avoid_reuse=None, descriptors=None, load_on_startup=None):
654654
if descriptors is None:
655655
descriptors = self.descriptors
656-
return self.__getattr__('createwallet')(wallet_name, disable_private_keys, blank, passphrase, avoid_reuse, descriptors)
656+
return self.__getattr__('createwallet')(wallet_name, disable_private_keys, blank, passphrase, avoid_reuse, descriptors, load_on_startup)
657657

658658
def importprivkey(self, privkey, label=None, rescan=None):
659659
wallet_info = self.getwalletinfo()

test/functional/test_runner.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -244,6 +244,7 @@
244244
'p2p_node_network_limited.py',
245245
'p2p_permissions.py',
246246
'feature_blocksdir.py',
247+
'wallet_startup.py',
247248
'feature_config_args.py',
248249
'feature_settings.py',
249250
'rpc_getdescriptorinfo.py',

0 commit comments

Comments
 (0)