Skip to content

Commit ae23fab

Browse files
committed
Add a new RPC command: restorewallet
1 parent 0b5344b commit ae23fab

File tree

2 files changed

+96
-24
lines changed

2 files changed

+96
-24
lines changed

src/rpc/client.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -187,6 +187,7 @@ static const CRPCConvertParam vRPCConvertParams[] =
187187
{ "createwallet", 5, "descriptors"},
188188
{ "createwallet", 6, "load_on_startup"},
189189
{ "createwallet", 7, "external_signer"},
190+
{ "restorewallet", 2, "load_on_startup"},
190191
{ "loadwallet", 1, "load_on_startup"},
191192
{ "unloadwallet", 1, "load_on_startup"},
192193
{ "getnodeaddresses", 0, "count"},

src/wallet/rpcwallet.cpp

Lines changed: 95 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -2572,6 +2572,37 @@ static RPCHelpMan listwallets()
25722572
};
25732573
}
25742574

2575+
static std::tuple<std::shared_ptr<CWallet>, std::vector<bilingual_str>> LoadWalletHelper(WalletContext& context, UniValue load_on_start_param, const std::string wallet_name)
2576+
{
2577+
DatabaseOptions options;
2578+
DatabaseStatus status;
2579+
options.require_existing = true;
2580+
bilingual_str error;
2581+
std::vector<bilingual_str> warnings;
2582+
std::optional<bool> load_on_start = load_on_start_param.isNull() ? std::nullopt : std::optional<bool>(load_on_start_param.get_bool());
2583+
std::shared_ptr<CWallet> const wallet = LoadWallet(*context.chain, wallet_name, load_on_start, options, status, error, warnings);
2584+
2585+
if (!wallet) {
2586+
// Map bad format to not found, since bad format is returned when the
2587+
// wallet directory exists, but doesn't contain a data file.
2588+
RPCErrorCode code = RPC_WALLET_ERROR;
2589+
switch (status) {
2590+
case DatabaseStatus::FAILED_NOT_FOUND:
2591+
case DatabaseStatus::FAILED_BAD_FORMAT:
2592+
code = RPC_WALLET_NOT_FOUND;
2593+
break;
2594+
case DatabaseStatus::FAILED_ALREADY_LOADED:
2595+
code = RPC_WALLET_ALREADY_LOADED;
2596+
break;
2597+
default: // RPC_WALLET_ERROR is returned for all other cases.
2598+
break;
2599+
}
2600+
throw JSONRPCError(code, error.original);
2601+
}
2602+
2603+
return { wallet, warnings };
2604+
}
2605+
25752606
static RPCHelpMan loadwallet()
25762607
{
25772608
return RPCHelpMan{"loadwallet",
@@ -2598,30 +2629,7 @@ static RPCHelpMan loadwallet()
25982629
WalletContext& context = EnsureWalletContext(request.context);
25992630
const std::string name(request.params[0].get_str());
26002631

2601-
DatabaseOptions options;
2602-
DatabaseStatus status;
2603-
options.require_existing = true;
2604-
bilingual_str error;
2605-
std::vector<bilingual_str> warnings;
2606-
std::optional<bool> load_on_start = request.params[1].isNull() ? std::nullopt : std::optional<bool>(request.params[1].get_bool());
2607-
std::shared_ptr<CWallet> const wallet = LoadWallet(*context.chain, name, load_on_start, options, status, error, warnings);
2608-
if (!wallet) {
2609-
// Map bad format to not found, since bad format is returned when the
2610-
// wallet directory exists, but doesn't contain a data file.
2611-
RPCErrorCode code = RPC_WALLET_ERROR;
2612-
switch (status) {
2613-
case DatabaseStatus::FAILED_NOT_FOUND:
2614-
case DatabaseStatus::FAILED_BAD_FORMAT:
2615-
code = RPC_WALLET_NOT_FOUND;
2616-
break;
2617-
case DatabaseStatus::FAILED_ALREADY_LOADED:
2618-
code = RPC_WALLET_ALREADY_LOADED;
2619-
break;
2620-
default: // RPC_WALLET_ERROR is returned for all other cases.
2621-
break;
2622-
}
2623-
throw JSONRPCError(code, error.original);
2624-
}
2632+
auto [wallet, warnings] = LoadWalletHelper(context, request.params[1], name);
26252633

26262634
UniValue obj(UniValue::VOBJ);
26272635
obj.pushKV("name", wallet->GetName());
@@ -2795,6 +2803,68 @@ static RPCHelpMan createwallet()
27952803
};
27962804
}
27972805

2806+
static RPCHelpMan restorewallet()
2807+
{
2808+
return RPCHelpMan{
2809+
"restorewallet",
2810+
"\nRestore and loads a wallet from backup.\n",
2811+
{
2812+
{"wallet_name", RPCArg::Type::STR, RPCArg::Optional::NO, "The name that will be applied to the restored wallet"},
2813+
{"backup_file", RPCArg::Type::STR, RPCArg::Optional::NO, "The backup file that will be used to restore the wallet."},
2814+
{"load_on_startup", RPCArg::Type::BOOL, RPCArg::Optional::OMITTED_NAMED_ARG, "Save wallet name to persistent settings and load on startup. True to add wallet to startup list, false to remove, null to leave unchanged."},
2815+
},
2816+
RPCResult{
2817+
RPCResult::Type::OBJ, "", "",
2818+
{
2819+
{RPCResult::Type::STR, "name", "The wallet name if restored successfully."},
2820+
{RPCResult::Type::STR, "warning", "Warning message if wallet was not loaded cleanly."},
2821+
}
2822+
},
2823+
RPCExamples{
2824+
HelpExampleCli("restorewallet", "\"testwallet\" \"home\\backups\\backup-file.bak\"")
2825+
+ HelpExampleRpc("restorewallet", "\"testwallet\" \"home\\backups\\backup-file.bak\"")
2826+
+ HelpExampleCliNamed("restorewallet", {{"wallet_name", "testwallet"}, {"backup_file", "home\\backups\\backup-file.bak\""}, {"load_on_startup", true}})
2827+
+ HelpExampleRpcNamed("restorewallet", {{"wallet_name", "testwallet"}, {"backup_file", "home\\backups\\backup-file.bak\""}, {"load_on_startup", true}})
2828+
},
2829+
[&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
2830+
{
2831+
2832+
WalletContext& context = EnsureWalletContext(request.context);
2833+
2834+
std::string backup_file = request.params[1].get_str();
2835+
2836+
if (!fs::exists(backup_file)) {
2837+
throw JSONRPCError(RPC_INVALID_PARAMETER, "Backup file does not exist");
2838+
}
2839+
2840+
std::string wallet_name = request.params[0].get_str();
2841+
2842+
const fs::path wallet_path = fsbridge::AbsPathJoin(GetWalletDir(), wallet_name);
2843+
2844+
if (fs::exists(wallet_path)) {
2845+
throw JSONRPCError(RPC_INVALID_PARAMETER, "Wallet name already exists.");
2846+
}
2847+
2848+
if (!TryCreateDirectories(wallet_path)) {
2849+
throw JSONRPCError(RPC_WALLET_ERROR, strprintf("Failed to create database path '%s'. Database already exists.", wallet_path.string()));
2850+
}
2851+
2852+
auto wallet_file = wallet_path / "wallet.dat";
2853+
2854+
fs::copy_file(backup_file, wallet_file, fs::copy_option::fail_if_exists);
2855+
2856+
auto [wallet, warnings] = LoadWalletHelper(context, request.params[2], wallet_name);
2857+
2858+
UniValue obj(UniValue::VOBJ);
2859+
obj.pushKV("name", wallet->GetName());
2860+
obj.pushKV("warning", Join(warnings, Untranslated("\n")).original);
2861+
2862+
return obj;
2863+
2864+
},
2865+
};
2866+
}
2867+
27982868
static RPCHelpMan unloadwallet()
27992869
{
28002870
return RPCHelpMan{"unloadwallet",
@@ -4639,6 +4709,7 @@ static const CRPCCommand commands[] =
46394709
{ "wallet", &bumpfee, },
46404710
{ "wallet", &psbtbumpfee, },
46414711
{ "wallet", &createwallet, },
4712+
{ "wallet", &restorewallet, },
46424713
{ "wallet", &dumpprivkey, },
46434714
{ "wallet", &dumpwallet, },
46444715
{ "wallet", &encryptwallet, },

0 commit comments

Comments
 (0)