Merge bitcoin/bitcoin#23647: MOVEONLY: Move wallet backup and encryption RPCs out of rpcwallet

5b2167fd30 MOVEONLY: Move LoadWalletHelper to wallet/rpc/util (Samuel Dobson)
8b73640152 MOVEONLY: Move wallet encryption RPCs to encrypt.cpp (Samuel Dobson)
803b30502b MOVEONLY: Move backupwallet and restorewallet to rpc/backup.cpp (Samuel Dobson)
3a9d39324e MOVEONLY: Move rpcdump.cpp to wallet/rpc/backup.cpp (Samuel Dobson)

Pull request description:

  As part of an effort to split rpcwallet as per #23622, this moves `rpcdump.cpp` into the new wallet/rpc directory as well as moving backup and encryption RPCs out of rpcwallet.

ACKs for top commit:
  MarcoFalke:
    ACK 5b2167fd30 🎭

Tree-SHA512: aa8054767927fa56b5c51edc91a2d94fe9f1cca198e1b2cac1ebd464f6956a89c782a7b6de4409361adca6ca1377272b6e2af660b737c4849ee323f899945ad9
This commit is contained in:
MarcoFalke 2021-12-03 09:17:04 +01:00
commit 8b4d53e4d6
No known key found for this signature in database
GPG key ID: CE2B75697E69A548
6 changed files with 390 additions and 370 deletions

View file

@ -410,9 +410,10 @@ libbitcoin_wallet_a_SOURCES = \
wallet/interfaces.cpp \
wallet/load.cpp \
wallet/receive.cpp \
wallet/rpc/backup.cpp \
wallet/rpc/encrypt.cpp \
wallet/rpc/signmessage.cpp \
wallet/rpc/util.cpp \
wallet/rpcdump.cpp \
wallet/rpcwallet.cpp \
wallet/scriptpubkeyman.cpp \
wallet/spend.cpp \

View file

@ -1831,3 +1831,99 @@ RPCHelpMan listdescriptors()
},
};
}
RPCHelpMan backupwallet()
{
return RPCHelpMan{"backupwallet",
"\nSafely copies current wallet file to destination, which can be a directory or a path with filename.\n",
{
{"destination", RPCArg::Type::STR, RPCArg::Optional::NO, "The destination directory or file"},
},
RPCResult{RPCResult::Type::NONE, "", ""},
RPCExamples{
HelpExampleCli("backupwallet", "\"backup.dat\"")
+ HelpExampleRpc("backupwallet", "\"backup.dat\"")
},
[&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
{
const std::shared_ptr<const CWallet> pwallet = GetWalletForJSONRPCRequest(request);
if (!pwallet) return NullUniValue;
// Make sure the results are valid at least up to the most recent block
// the user could have gotten from another RPC command prior to now
pwallet->BlockUntilSyncedToCurrentChain();
LOCK(pwallet->cs_wallet);
std::string strDest = request.params[0].get_str();
if (!pwallet->BackupWallet(strDest)) {
throw JSONRPCError(RPC_WALLET_ERROR, "Error: Wallet backup failed!");
}
return NullUniValue;
},
};
}
RPCHelpMan restorewallet()
{
return RPCHelpMan{
"restorewallet",
"\nRestore and loads a wallet from backup.\n",
{
{"wallet_name", RPCArg::Type::STR, RPCArg::Optional::NO, "The name that will be applied to the restored wallet"},
{"backup_file", RPCArg::Type::STR, RPCArg::Optional::NO, "The backup file that will be used to restore the wallet."},
{"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."},
},
RPCResult{
RPCResult::Type::OBJ, "", "",
{
{RPCResult::Type::STR, "name", "The wallet name if restored successfully."},
{RPCResult::Type::STR, "warning", "Warning message if wallet was not loaded cleanly."},
}
},
RPCExamples{
HelpExampleCli("restorewallet", "\"testwallet\" \"home\\backups\\backup-file.bak\"")
+ HelpExampleRpc("restorewallet", "\"testwallet\" \"home\\backups\\backup-file.bak\"")
+ HelpExampleCliNamed("restorewallet", {{"wallet_name", "testwallet"}, {"backup_file", "home\\backups\\backup-file.bak\""}, {"load_on_startup", true}})
+ HelpExampleRpcNamed("restorewallet", {{"wallet_name", "testwallet"}, {"backup_file", "home\\backups\\backup-file.bak\""}, {"load_on_startup", true}})
},
[&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
{
WalletContext& context = EnsureWalletContext(request.context);
auto backup_file = fs::u8path(request.params[1].get_str());
if (!fs::exists(backup_file)) {
throw JSONRPCError(RPC_INVALID_PARAMETER, "Backup file does not exist");
}
std::string wallet_name = request.params[0].get_str();
const fs::path wallet_path = fsbridge::AbsPathJoin(GetWalletDir(), fs::u8path(wallet_name));
if (fs::exists(wallet_path)) {
throw JSONRPCError(RPC_INVALID_PARAMETER, "Wallet name already exists.");
}
if (!TryCreateDirectories(wallet_path)) {
throw JSONRPCError(RPC_WALLET_ERROR, strprintf("Failed to create database path '%s'. Database already exists.", wallet_path.u8string()));
}
auto wallet_file = wallet_path / "wallet.dat";
fs::copy_file(backup_file, wallet_file, fs::copy_option::fail_if_exists);
auto [wallet, warnings] = LoadWalletHelper(context, request.params[2], wallet_name);
UniValue obj(UniValue::VOBJ);
obj.pushKV("name", wallet->GetName());
obj.pushKV("warning", Join(warnings, Untranslated("\n")).original);
return obj;
},
};
}

248
src/wallet/rpc/encrypt.cpp Normal file
View file

@ -0,0 +1,248 @@
// Copyright (c) 2011-2021 The Bitcoin Core developers
// Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
#include <rpc/util.h>
#include <wallet/rpc/util.h>
#include <wallet/wallet.h>
RPCHelpMan walletpassphrase()
{
return RPCHelpMan{"walletpassphrase",
"\nStores the wallet decryption key in memory for 'timeout' seconds.\n"
"This is needed prior to performing transactions related to private keys such as sending bitcoins\n"
"\nNote:\n"
"Issuing the walletpassphrase command while the wallet is already unlocked will set a new unlock\n"
"time that overrides the old one.\n",
{
{"passphrase", RPCArg::Type::STR, RPCArg::Optional::NO, "The wallet passphrase"},
{"timeout", RPCArg::Type::NUM, RPCArg::Optional::NO, "The time to keep the decryption key in seconds; capped at 100000000 (~3 years)."},
},
RPCResult{RPCResult::Type::NONE, "", ""},
RPCExamples{
"\nUnlock the wallet for 60 seconds\n"
+ HelpExampleCli("walletpassphrase", "\"my pass phrase\" 60") +
"\nLock the wallet again (before 60 seconds)\n"
+ HelpExampleCli("walletlock", "") +
"\nAs a JSON-RPC call\n"
+ HelpExampleRpc("walletpassphrase", "\"my pass phrase\", 60")
},
[&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
{
std::shared_ptr<CWallet> const wallet = GetWalletForJSONRPCRequest(request);
if (!wallet) return NullUniValue;
CWallet* const pwallet = wallet.get();
int64_t nSleepTime;
int64_t relock_time;
// Prevent concurrent calls to walletpassphrase with the same wallet.
LOCK(pwallet->m_unlock_mutex);
{
LOCK(pwallet->cs_wallet);
if (!pwallet->IsCrypted()) {
throw JSONRPCError(RPC_WALLET_WRONG_ENC_STATE, "Error: running with an unencrypted wallet, but walletpassphrase was called.");
}
// Note that the walletpassphrase is stored in request.params[0] which is not mlock()ed
SecureString strWalletPass;
strWalletPass.reserve(100);
// TODO: get rid of this .c_str() by implementing SecureString::operator=(std::string)
// Alternately, find a way to make request.params[0] mlock()'d to begin with.
strWalletPass = request.params[0].get_str().c_str();
// Get the timeout
nSleepTime = request.params[1].get_int64();
// Timeout cannot be negative, otherwise it will relock immediately
if (nSleepTime < 0) {
throw JSONRPCError(RPC_INVALID_PARAMETER, "Timeout cannot be negative.");
}
// Clamp timeout
constexpr int64_t MAX_SLEEP_TIME = 100000000; // larger values trigger a macos/libevent bug?
if (nSleepTime > MAX_SLEEP_TIME) {
nSleepTime = MAX_SLEEP_TIME;
}
if (strWalletPass.empty()) {
throw JSONRPCError(RPC_INVALID_PARAMETER, "passphrase can not be empty");
}
if (!pwallet->Unlock(strWalletPass)) {
throw JSONRPCError(RPC_WALLET_PASSPHRASE_INCORRECT, "Error: The wallet passphrase entered was incorrect.");
}
pwallet->TopUpKeyPool();
pwallet->nRelockTime = GetTime() + nSleepTime;
relock_time = pwallet->nRelockTime;
}
// rpcRunLater must be called without cs_wallet held otherwise a deadlock
// can occur. The deadlock would happen when RPCRunLater removes the
// previous timer (and waits for the callback to finish if already running)
// and the callback locks cs_wallet.
AssertLockNotHeld(wallet->cs_wallet);
// Keep a weak pointer to the wallet so that it is possible to unload the
// wallet before the following callback is called. If a valid shared pointer
// is acquired in the callback then the wallet is still loaded.
std::weak_ptr<CWallet> weak_wallet = wallet;
pwallet->chain().rpcRunLater(strprintf("lockwallet(%s)", pwallet->GetName()), [weak_wallet, relock_time] {
if (auto shared_wallet = weak_wallet.lock()) {
LOCK(shared_wallet->cs_wallet);
// Skip if this is not the most recent rpcRunLater callback.
if (shared_wallet->nRelockTime != relock_time) return;
shared_wallet->Lock();
shared_wallet->nRelockTime = 0;
}
}, nSleepTime);
return NullUniValue;
},
};
}
RPCHelpMan walletpassphrasechange()
{
return RPCHelpMan{"walletpassphrasechange",
"\nChanges the wallet passphrase from 'oldpassphrase' to 'newpassphrase'.\n",
{
{"oldpassphrase", RPCArg::Type::STR, RPCArg::Optional::NO, "The current passphrase"},
{"newpassphrase", RPCArg::Type::STR, RPCArg::Optional::NO, "The new passphrase"},
},
RPCResult{RPCResult::Type::NONE, "", ""},
RPCExamples{
HelpExampleCli("walletpassphrasechange", "\"old one\" \"new one\"")
+ HelpExampleRpc("walletpassphrasechange", "\"old one\", \"new one\"")
},
[&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
{
std::shared_ptr<CWallet> const pwallet = GetWalletForJSONRPCRequest(request);
if (!pwallet) return NullUniValue;
LOCK(pwallet->cs_wallet);
if (!pwallet->IsCrypted()) {
throw JSONRPCError(RPC_WALLET_WRONG_ENC_STATE, "Error: running with an unencrypted wallet, but walletpassphrasechange was called.");
}
// TODO: get rid of these .c_str() calls by implementing SecureString::operator=(std::string)
// Alternately, find a way to make request.params[0] mlock()'d to begin with.
SecureString strOldWalletPass;
strOldWalletPass.reserve(100);
strOldWalletPass = request.params[0].get_str().c_str();
SecureString strNewWalletPass;
strNewWalletPass.reserve(100);
strNewWalletPass = request.params[1].get_str().c_str();
if (strOldWalletPass.empty() || strNewWalletPass.empty()) {
throw JSONRPCError(RPC_INVALID_PARAMETER, "passphrase can not be empty");
}
if (!pwallet->ChangeWalletPassphrase(strOldWalletPass, strNewWalletPass)) {
throw JSONRPCError(RPC_WALLET_PASSPHRASE_INCORRECT, "Error: The wallet passphrase entered was incorrect.");
}
return NullUniValue;
},
};
}
RPCHelpMan walletlock()
{
return RPCHelpMan{"walletlock",
"\nRemoves the wallet encryption key from memory, locking the wallet.\n"
"After calling this method, you will need to call walletpassphrase again\n"
"before being able to call any methods which require the wallet to be unlocked.\n",
{},
RPCResult{RPCResult::Type::NONE, "", ""},
RPCExamples{
"\nSet the passphrase for 2 minutes to perform a transaction\n"
+ HelpExampleCli("walletpassphrase", "\"my pass phrase\" 120") +
"\nPerform a send (requires passphrase set)\n"
+ HelpExampleCli("sendtoaddress", "\"" + EXAMPLE_ADDRESS[0] + "\" 1.0") +
"\nClear the passphrase since we are done before 2 minutes is up\n"
+ HelpExampleCli("walletlock", "") +
"\nAs a JSON-RPC call\n"
+ HelpExampleRpc("walletlock", "")
},
[&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
{
std::shared_ptr<CWallet> const pwallet = GetWalletForJSONRPCRequest(request);
if (!pwallet) return NullUniValue;
LOCK(pwallet->cs_wallet);
if (!pwallet->IsCrypted()) {
throw JSONRPCError(RPC_WALLET_WRONG_ENC_STATE, "Error: running with an unencrypted wallet, but walletlock was called.");
}
pwallet->Lock();
pwallet->nRelockTime = 0;
return NullUniValue;
},
};
}
RPCHelpMan encryptwallet()
{
return RPCHelpMan{"encryptwallet",
"\nEncrypts the wallet with 'passphrase'. This is for first time encryption.\n"
"After this, any calls that interact with private keys such as sending or signing \n"
"will require the passphrase to be set prior the making these calls.\n"
"Use the walletpassphrase call for this, and then walletlock call.\n"
"If the wallet is already encrypted, use the walletpassphrasechange call.\n",
{
{"passphrase", RPCArg::Type::STR, RPCArg::Optional::NO, "The pass phrase to encrypt the wallet with. It must be at least 1 character, but should be long."},
},
RPCResult{RPCResult::Type::STR, "", "A string with further instructions"},
RPCExamples{
"\nEncrypt your wallet\n"
+ HelpExampleCli("encryptwallet", "\"my pass phrase\"") +
"\nNow set the passphrase to use the wallet, such as for signing or sending bitcoin\n"
+ HelpExampleCli("walletpassphrase", "\"my pass phrase\"") +
"\nNow we can do something like sign\n"
+ HelpExampleCli("signmessage", "\"address\" \"test message\"") +
"\nNow lock the wallet again by removing the passphrase\n"
+ HelpExampleCli("walletlock", "") +
"\nAs a JSON-RPC call\n"
+ HelpExampleRpc("encryptwallet", "\"my pass phrase\"")
},
[&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
{
std::shared_ptr<CWallet> const pwallet = GetWalletForJSONRPCRequest(request);
if (!pwallet) return NullUniValue;
LOCK(pwallet->cs_wallet);
if (pwallet->IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS)) {
throw JSONRPCError(RPC_WALLET_ENCRYPTION_FAILED, "Error: wallet does not contain private keys, nothing to encrypt.");
}
if (pwallet->IsCrypted()) {
throw JSONRPCError(RPC_WALLET_WRONG_ENC_STATE, "Error: running with an encrypted wallet, but encryptwallet was called.");
}
// TODO: get rid of this .c_str() by implementing SecureString::operator=(std::string)
// Alternately, find a way to make request.params[0] mlock()'d to begin with.
SecureString strWalletPass;
strWalletPass.reserve(100);
strWalletPass = request.params[0].get_str().c_str();
if (strWalletPass.empty()) {
throw JSONRPCError(RPC_INVALID_PARAMETER, "passphrase can not be empty");
}
if (!pwallet->EncryptWallet(strWalletPass)) {
throw JSONRPCError(RPC_WALLET_ENCRYPTION_FAILED, "Error: Failed to encrypt the wallet.");
}
return "wallet encrypted; The keypool has been flushed and a new HD seed was generated (if you are using HD). You need to make a new backup.";
},
};
}

View file

@ -5,6 +5,7 @@
#include <wallet/rpc/util.h>
#include <rpc/util.h>
#include <util/translation.h>
#include <util/url.h>
#include <wallet/context.h>
#include <wallet/wallet.h>
@ -120,3 +121,34 @@ std::string LabelFromValue(const UniValue& value)
throw JSONRPCError(RPC_WALLET_INVALID_LABEL_NAME, "Invalid label name");
return label;
}
std::tuple<std::shared_ptr<CWallet>, std::vector<bilingual_str>> LoadWalletHelper(WalletContext& context, UniValue load_on_start_param, const std::string wallet_name)
{
DatabaseOptions options;
DatabaseStatus status;
options.require_existing = true;
bilingual_str error;
std::vector<bilingual_str> warnings;
std::optional<bool> load_on_start = load_on_start_param.isNull() ? std::nullopt : std::optional<bool>(load_on_start_param.get_bool());
std::shared_ptr<CWallet> const wallet = LoadWallet(context, wallet_name, load_on_start, options, status, error, warnings);
if (!wallet) {
// Map bad format to not found, since bad format is returned when the
// wallet directory exists, but doesn't contain a data file.
RPCErrorCode code = RPC_WALLET_ERROR;
switch (status) {
case DatabaseStatus::FAILED_NOT_FOUND:
case DatabaseStatus::FAILED_BAD_FORMAT:
code = RPC_WALLET_NOT_FOUND;
break;
case DatabaseStatus::FAILED_ALREADY_LOADED:
code = RPC_WALLET_ALREADY_LOADED;
break;
default: // RPC_WALLET_ERROR is returned for all other cases.
break;
}
throw JSONRPCError(code, error.original);
}
return { wallet, warnings };
}

View file

@ -8,7 +8,9 @@
#include <any>
#include <memory>
#include <string>
#include <vector>
struct bilingual_str;
class CWallet;
class JSONRPCRequest;
class LegacyScriptPubKeyMan;
@ -35,4 +37,6 @@ bool GetAvoidReuseFlag(const CWallet& wallet, const UniValue& param);
bool ParseIncludeWatchonly(const UniValue& include_watchonly, const CWallet& wallet);
std::string LabelFromValue(const UniValue& value);
std::tuple<std::shared_ptr<CWallet>, std::vector<bilingual_str>> LoadWalletHelper(WalletContext& context, UniValue load_on_start_param, const std::string wallet_name);
#endif // BITCOIN_WALLET_RPC_UTIL_H

View file

@ -1656,41 +1656,6 @@ static RPCHelpMan abandontransaction()
};
}
static RPCHelpMan backupwallet()
{
return RPCHelpMan{"backupwallet",
"\nSafely copies current wallet file to destination, which can be a directory or a path with filename.\n",
{
{"destination", RPCArg::Type::STR, RPCArg::Optional::NO, "The destination directory or file"},
},
RPCResult{RPCResult::Type::NONE, "", ""},
RPCExamples{
HelpExampleCli("backupwallet", "\"backup.dat\"")
+ HelpExampleRpc("backupwallet", "\"backup.dat\"")
},
[&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
{
const std::shared_ptr<const CWallet> pwallet = GetWalletForJSONRPCRequest(request);
if (!pwallet) return NullUniValue;
// Make sure the results are valid at least up to the most recent block
// the user could have gotten from another RPC command prior to now
pwallet->BlockUntilSyncedToCurrentChain();
LOCK(pwallet->cs_wallet);
std::string strDest = request.params[0].get_str();
if (!pwallet->BackupWallet(strDest)) {
throw JSONRPCError(RPC_WALLET_ERROR, "Error: Wallet backup failed!");
}
return NullUniValue;
},
};
}
static RPCHelpMan keypoolrefill()
{
return RPCHelpMan{"keypoolrefill",
@ -1762,247 +1727,6 @@ static RPCHelpMan newkeypool()
};
}
static RPCHelpMan walletpassphrase()
{
return RPCHelpMan{"walletpassphrase",
"\nStores the wallet decryption key in memory for 'timeout' seconds.\n"
"This is needed prior to performing transactions related to private keys such as sending bitcoins\n"
"\nNote:\n"
"Issuing the walletpassphrase command while the wallet is already unlocked will set a new unlock\n"
"time that overrides the old one.\n",
{
{"passphrase", RPCArg::Type::STR, RPCArg::Optional::NO, "The wallet passphrase"},
{"timeout", RPCArg::Type::NUM, RPCArg::Optional::NO, "The time to keep the decryption key in seconds; capped at 100000000 (~3 years)."},
},
RPCResult{RPCResult::Type::NONE, "", ""},
RPCExamples{
"\nUnlock the wallet for 60 seconds\n"
+ HelpExampleCli("walletpassphrase", "\"my pass phrase\" 60") +
"\nLock the wallet again (before 60 seconds)\n"
+ HelpExampleCli("walletlock", "") +
"\nAs a JSON-RPC call\n"
+ HelpExampleRpc("walletpassphrase", "\"my pass phrase\", 60")
},
[&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
{
std::shared_ptr<CWallet> const wallet = GetWalletForJSONRPCRequest(request);
if (!wallet) return NullUniValue;
CWallet* const pwallet = wallet.get();
int64_t nSleepTime;
int64_t relock_time;
// Prevent concurrent calls to walletpassphrase with the same wallet.
LOCK(pwallet->m_unlock_mutex);
{
LOCK(pwallet->cs_wallet);
if (!pwallet->IsCrypted()) {
throw JSONRPCError(RPC_WALLET_WRONG_ENC_STATE, "Error: running with an unencrypted wallet, but walletpassphrase was called.");
}
// Note that the walletpassphrase is stored in request.params[0] which is not mlock()ed
SecureString strWalletPass;
strWalletPass.reserve(100);
// TODO: get rid of this .c_str() by implementing SecureString::operator=(std::string)
// Alternately, find a way to make request.params[0] mlock()'d to begin with.
strWalletPass = request.params[0].get_str().c_str();
// Get the timeout
nSleepTime = request.params[1].get_int64();
// Timeout cannot be negative, otherwise it will relock immediately
if (nSleepTime < 0) {
throw JSONRPCError(RPC_INVALID_PARAMETER, "Timeout cannot be negative.");
}
// Clamp timeout
constexpr int64_t MAX_SLEEP_TIME = 100000000; // larger values trigger a macos/libevent bug?
if (nSleepTime > MAX_SLEEP_TIME) {
nSleepTime = MAX_SLEEP_TIME;
}
if (strWalletPass.empty()) {
throw JSONRPCError(RPC_INVALID_PARAMETER, "passphrase can not be empty");
}
if (!pwallet->Unlock(strWalletPass)) {
throw JSONRPCError(RPC_WALLET_PASSPHRASE_INCORRECT, "Error: The wallet passphrase entered was incorrect.");
}
pwallet->TopUpKeyPool();
pwallet->nRelockTime = GetTime() + nSleepTime;
relock_time = pwallet->nRelockTime;
}
// rpcRunLater must be called without cs_wallet held otherwise a deadlock
// can occur. The deadlock would happen when RPCRunLater removes the
// previous timer (and waits for the callback to finish if already running)
// and the callback locks cs_wallet.
AssertLockNotHeld(wallet->cs_wallet);
// Keep a weak pointer to the wallet so that it is possible to unload the
// wallet before the following callback is called. If a valid shared pointer
// is acquired in the callback then the wallet is still loaded.
std::weak_ptr<CWallet> weak_wallet = wallet;
pwallet->chain().rpcRunLater(strprintf("lockwallet(%s)", pwallet->GetName()), [weak_wallet, relock_time] {
if (auto shared_wallet = weak_wallet.lock()) {
LOCK(shared_wallet->cs_wallet);
// Skip if this is not the most recent rpcRunLater callback.
if (shared_wallet->nRelockTime != relock_time) return;
shared_wallet->Lock();
shared_wallet->nRelockTime = 0;
}
}, nSleepTime);
return NullUniValue;
},
};
}
static RPCHelpMan walletpassphrasechange()
{
return RPCHelpMan{"walletpassphrasechange",
"\nChanges the wallet passphrase from 'oldpassphrase' to 'newpassphrase'.\n",
{
{"oldpassphrase", RPCArg::Type::STR, RPCArg::Optional::NO, "The current passphrase"},
{"newpassphrase", RPCArg::Type::STR, RPCArg::Optional::NO, "The new passphrase"},
},
RPCResult{RPCResult::Type::NONE, "", ""},
RPCExamples{
HelpExampleCli("walletpassphrasechange", "\"old one\" \"new one\"")
+ HelpExampleRpc("walletpassphrasechange", "\"old one\", \"new one\"")
},
[&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
{
std::shared_ptr<CWallet> const pwallet = GetWalletForJSONRPCRequest(request);
if (!pwallet) return NullUniValue;
LOCK(pwallet->cs_wallet);
if (!pwallet->IsCrypted()) {
throw JSONRPCError(RPC_WALLET_WRONG_ENC_STATE, "Error: running with an unencrypted wallet, but walletpassphrasechange was called.");
}
// TODO: get rid of these .c_str() calls by implementing SecureString::operator=(std::string)
// Alternately, find a way to make request.params[0] mlock()'d to begin with.
SecureString strOldWalletPass;
strOldWalletPass.reserve(100);
strOldWalletPass = request.params[0].get_str().c_str();
SecureString strNewWalletPass;
strNewWalletPass.reserve(100);
strNewWalletPass = request.params[1].get_str().c_str();
if (strOldWalletPass.empty() || strNewWalletPass.empty()) {
throw JSONRPCError(RPC_INVALID_PARAMETER, "passphrase can not be empty");
}
if (!pwallet->ChangeWalletPassphrase(strOldWalletPass, strNewWalletPass)) {
throw JSONRPCError(RPC_WALLET_PASSPHRASE_INCORRECT, "Error: The wallet passphrase entered was incorrect.");
}
return NullUniValue;
},
};
}
static RPCHelpMan walletlock()
{
return RPCHelpMan{"walletlock",
"\nRemoves the wallet encryption key from memory, locking the wallet.\n"
"After calling this method, you will need to call walletpassphrase again\n"
"before being able to call any methods which require the wallet to be unlocked.\n",
{},
RPCResult{RPCResult::Type::NONE, "", ""},
RPCExamples{
"\nSet the passphrase for 2 minutes to perform a transaction\n"
+ HelpExampleCli("walletpassphrase", "\"my pass phrase\" 120") +
"\nPerform a send (requires passphrase set)\n"
+ HelpExampleCli("sendtoaddress", "\"" + EXAMPLE_ADDRESS[0] + "\" 1.0") +
"\nClear the passphrase since we are done before 2 minutes is up\n"
+ HelpExampleCli("walletlock", "") +
"\nAs a JSON-RPC call\n"
+ HelpExampleRpc("walletlock", "")
},
[&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
{
std::shared_ptr<CWallet> const pwallet = GetWalletForJSONRPCRequest(request);
if (!pwallet) return NullUniValue;
LOCK(pwallet->cs_wallet);
if (!pwallet->IsCrypted()) {
throw JSONRPCError(RPC_WALLET_WRONG_ENC_STATE, "Error: running with an unencrypted wallet, but walletlock was called.");
}
pwallet->Lock();
pwallet->nRelockTime = 0;
return NullUniValue;
},
};
}
static RPCHelpMan encryptwallet()
{
return RPCHelpMan{"encryptwallet",
"\nEncrypts the wallet with 'passphrase'. This is for first time encryption.\n"
"After this, any calls that interact with private keys such as sending or signing \n"
"will require the passphrase to be set prior the making these calls.\n"
"Use the walletpassphrase call for this, and then walletlock call.\n"
"If the wallet is already encrypted, use the walletpassphrasechange call.\n",
{
{"passphrase", RPCArg::Type::STR, RPCArg::Optional::NO, "The pass phrase to encrypt the wallet with. It must be at least 1 character, but should be long."},
},
RPCResult{RPCResult::Type::STR, "", "A string with further instructions"},
RPCExamples{
"\nEncrypt your wallet\n"
+ HelpExampleCli("encryptwallet", "\"my pass phrase\"") +
"\nNow set the passphrase to use the wallet, such as for signing or sending bitcoin\n"
+ HelpExampleCli("walletpassphrase", "\"my pass phrase\"") +
"\nNow we can do something like sign\n"
+ HelpExampleCli("signmessage", "\"address\" \"test message\"") +
"\nNow lock the wallet again by removing the passphrase\n"
+ HelpExampleCli("walletlock", "") +
"\nAs a JSON-RPC call\n"
+ HelpExampleRpc("encryptwallet", "\"my pass phrase\"")
},
[&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
{
std::shared_ptr<CWallet> const pwallet = GetWalletForJSONRPCRequest(request);
if (!pwallet) return NullUniValue;
LOCK(pwallet->cs_wallet);
if (pwallet->IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS)) {
throw JSONRPCError(RPC_WALLET_ENCRYPTION_FAILED, "Error: wallet does not contain private keys, nothing to encrypt.");
}
if (pwallet->IsCrypted()) {
throw JSONRPCError(RPC_WALLET_WRONG_ENC_STATE, "Error: running with an encrypted wallet, but encryptwallet was called.");
}
// TODO: get rid of this .c_str() by implementing SecureString::operator=(std::string)
// Alternately, find a way to make request.params[0] mlock()'d to begin with.
SecureString strWalletPass;
strWalletPass.reserve(100);
strWalletPass = request.params[0].get_str().c_str();
if (strWalletPass.empty()) {
throw JSONRPCError(RPC_INVALID_PARAMETER, "passphrase can not be empty");
}
if (!pwallet->EncryptWallet(strWalletPass)) {
throw JSONRPCError(RPC_WALLET_ENCRYPTION_FAILED, "Error: Failed to encrypt the wallet.");
}
return "wallet encrypted; The keypool has been flushed and a new HD seed was generated (if you are using HD). You need to make a new backup.";
},
};
}
static RPCHelpMan lockunspent()
{
return RPCHelpMan{"lockunspent",
@ -2467,37 +2191,6 @@ static RPCHelpMan listwallets()
};
}
static std::tuple<std::shared_ptr<CWallet>, std::vector<bilingual_str>> LoadWalletHelper(WalletContext& context, UniValue load_on_start_param, const std::string wallet_name)
{
DatabaseOptions options;
DatabaseStatus status;
options.require_existing = true;
bilingual_str error;
std::vector<bilingual_str> warnings;
std::optional<bool> load_on_start = load_on_start_param.isNull() ? std::nullopt : std::optional<bool>(load_on_start_param.get_bool());
std::shared_ptr<CWallet> const wallet = LoadWallet(context, wallet_name, load_on_start, options, status, error, warnings);
if (!wallet) {
// Map bad format to not found, since bad format is returned when the
// wallet directory exists, but doesn't contain a data file.
RPCErrorCode code = RPC_WALLET_ERROR;
switch (status) {
case DatabaseStatus::FAILED_NOT_FOUND:
case DatabaseStatus::FAILED_BAD_FORMAT:
code = RPC_WALLET_NOT_FOUND;
break;
case DatabaseStatus::FAILED_ALREADY_LOADED:
code = RPC_WALLET_ALREADY_LOADED;
break;
default: // RPC_WALLET_ERROR is returned for all other cases.
break;
}
throw JSONRPCError(code, error.original);
}
return { wallet, warnings };
}
static RPCHelpMan loadwallet()
{
return RPCHelpMan{"loadwallet",
@ -2697,68 +2390,6 @@ static RPCHelpMan createwallet()
};
}
static RPCHelpMan restorewallet()
{
return RPCHelpMan{
"restorewallet",
"\nRestore and loads a wallet from backup.\n",
{
{"wallet_name", RPCArg::Type::STR, RPCArg::Optional::NO, "The name that will be applied to the restored wallet"},
{"backup_file", RPCArg::Type::STR, RPCArg::Optional::NO, "The backup file that will be used to restore the wallet."},
{"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."},
},
RPCResult{
RPCResult::Type::OBJ, "", "",
{
{RPCResult::Type::STR, "name", "The wallet name if restored successfully."},
{RPCResult::Type::STR, "warning", "Warning message if wallet was not loaded cleanly."},
}
},
RPCExamples{
HelpExampleCli("restorewallet", "\"testwallet\" \"home\\backups\\backup-file.bak\"")
+ HelpExampleRpc("restorewallet", "\"testwallet\" \"home\\backups\\backup-file.bak\"")
+ HelpExampleCliNamed("restorewallet", {{"wallet_name", "testwallet"}, {"backup_file", "home\\backups\\backup-file.bak\""}, {"load_on_startup", true}})
+ HelpExampleRpcNamed("restorewallet", {{"wallet_name", "testwallet"}, {"backup_file", "home\\backups\\backup-file.bak\""}, {"load_on_startup", true}})
},
[&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
{
WalletContext& context = EnsureWalletContext(request.context);
auto backup_file = fs::u8path(request.params[1].get_str());
if (!fs::exists(backup_file)) {
throw JSONRPCError(RPC_INVALID_PARAMETER, "Backup file does not exist");
}
std::string wallet_name = request.params[0].get_str();
const fs::path wallet_path = fsbridge::AbsPathJoin(GetWalletDir(), fs::u8path(wallet_name));
if (fs::exists(wallet_path)) {
throw JSONRPCError(RPC_INVALID_PARAMETER, "Wallet name already exists.");
}
if (!TryCreateDirectories(wallet_path)) {
throw JSONRPCError(RPC_WALLET_ERROR, strprintf("Failed to create database path '%s'. Database already exists.", wallet_path.u8string()));
}
auto wallet_file = wallet_path / "wallet.dat";
fs::copy_file(backup_file, wallet_file, fs::copy_option::fail_if_exists);
auto [wallet, warnings] = LoadWalletHelper(context, request.params[2], wallet_name);
UniValue obj(UniValue::VOBJ);
obj.pushKV("name", wallet->GetName());
obj.pushKV("warning", Join(warnings, Untranslated("\n")).original);
return obj;
},
};
}
static RPCHelpMan unloadwallet()
{
return RPCHelpMan{"unloadwallet",
@ -4699,6 +4330,14 @@ RPCHelpMan importmulti();
RPCHelpMan importdescriptors();
RPCHelpMan listdescriptors();
RPCHelpMan signmessage();
RPCHelpMan backupwallet();
RPCHelpMan restorewallet();
// encryption
RPCHelpMan walletpassphrase();
RPCHelpMan walletpassphrasechange();
RPCHelpMan walletlock();
RPCHelpMan encryptwallet();
Span<const CRPCCommand> GetWalletRPCCommands()
{