aboutsummaryrefslogtreecommitdiff
path: root/src/wallet/rpcwallet.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/wallet/rpcwallet.cpp')
-rw-r--r--src/wallet/rpcwallet.cpp214
1 files changed, 176 insertions, 38 deletions
diff --git a/src/wallet/rpcwallet.cpp b/src/wallet/rpcwallet.cpp
index 0d804dcc10..96a74a338a 100644
--- a/src/wallet/rpcwallet.cpp
+++ b/src/wallet/rpcwallet.cpp
@@ -1,5 +1,5 @@
// Copyright (c) 2010 Satoshi Nakamoto
-// Copyright (c) 2009-2018 The Bitcoin Core developers
+// Copyright (c) 2009-2019 The Bitcoin Core developers
// Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
@@ -9,7 +9,6 @@
#include <core_io.h>
#include <init.h>
#include <interfaces/chain.h>
-#include <validation.h>
#include <key_io.h>
#include <net.h>
#include <node/transaction.h>
@@ -27,10 +26,11 @@
#include <timedata.h>
#include <util/bip32.h>
#include <util/fees.h>
-#include <util/system.h>
#include <util/moneystr.h>
+#include <util/system.h>
#include <util/url.h>
#include <util/validation.h>
+#include <validation.h>
#include <wallet/coincontrol.h>
#include <wallet/feebumper.h>
#include <wallet/psbtwallet.h>
@@ -70,14 +70,14 @@ std::shared_ptr<CWallet> GetWalletForJSONRPCRequest(const JSONRPCRequest& reques
return wallets.size() == 1 || (request.fHelp && wallets.size() > 0) ? wallets[0] : nullptr;
}
-std::string HelpRequiringPassphrase(CWallet * const pwallet)
+std::string HelpRequiringPassphrase(const CWallet* pwallet)
{
return pwallet && pwallet->IsCrypted()
? "\nRequires wallet passphrase to be set with walletpassphrase call."
: "";
}
-bool EnsureWalletIsAvailable(CWallet * const pwallet, bool avoidException)
+bool EnsureWalletIsAvailable(const CWallet* pwallet, bool avoidException)
{
if (pwallet) return true;
if (avoidException) return false;
@@ -89,7 +89,7 @@ bool EnsureWalletIsAvailable(CWallet * const pwallet, bool avoidException)
"Wallet file not specified (must request wallet RPC through /wallet/<filename> uri-path).");
}
-void EnsureWalletIsUnlocked(CWallet * const pwallet)
+void EnsureWalletIsUnlocked(const CWallet* pwallet)
{
if (pwallet->IsLocked()) {
throw JSONRPCError(RPC_WALLET_UNLOCK_NEEDED, "Error: Please enter the wallet passphrase with walletpassphrase first.");
@@ -550,13 +550,14 @@ static UniValue signmessage(const JSONRPCRequest& request)
throw JSONRPCError(RPC_TYPE_ERROR, "Invalid address");
}
- const CKeyID *keyID = boost::get<CKeyID>(&dest);
- if (!keyID) {
+ const PKHash *pkhash = boost::get<PKHash>(&dest);
+ if (!pkhash) {
throw JSONRPCError(RPC_TYPE_ERROR, "Address does not refer to key");
}
CKey key;
- if (!pwallet->GetKey(*keyID, key)) {
+ CKeyID keyID(*pkhash);
+ if (!pwallet->GetKey(keyID, key)) {
throw JSONRPCError(RPC_WALLET_ERROR, "Private key not available");
}
@@ -785,7 +786,7 @@ static UniValue getunconfirmedbalance(const JSONRPCRequest &request)
if (request.fHelp || request.params.size() > 0)
throw std::runtime_error(
RPCHelpMan{"getunconfirmedbalance",
- "Returns the server's total unconfirmed balance\n",
+ "DEPRECATED\nIdentical to getbalances().mine.untrusted_pending\n",
{},
RPCResults{},
RPCExamples{""},
@@ -2373,6 +2374,68 @@ static UniValue settxfee(const JSONRPCRequest& request)
return true;
}
+static UniValue getbalances(const JSONRPCRequest& request)
+{
+ std::shared_ptr<CWallet> const rpc_wallet = GetWalletForJSONRPCRequest(request);
+ if (!EnsureWalletIsAvailable(rpc_wallet.get(), request.fHelp)) {
+ return NullUniValue;
+ }
+ CWallet& wallet = *rpc_wallet;
+
+ const RPCHelpMan help{
+ "getbalances",
+ "Returns an object with all balances in " + CURRENCY_UNIT + ".\n",
+ {},
+ RPCResult{
+ "{\n"
+ " \"mine\": { (object) balances from outputs that the wallet can sign\n"
+ " \"trusted\": xxx (numeric) trusted balance (outputs created by the wallet or confirmed outputs)\n"
+ " \"untrusted_pending\": xxx (numeric) untrusted pending balance (outputs created by others that are in the mempool)\n"
+ " \"immature\": xxx (numeric) balance from immature coinbase outputs\n"
+ " },\n"
+ " \"watchonly\": { (object) watchonly balances (not present if wallet does not watch anything)\n"
+ " \"trusted\": xxx (numeric) trusted balance (outputs created by the wallet or confirmed outputs)\n"
+ " \"untrusted_pending\": xxx (numeric) untrusted pending balance (outputs created by others that are in the mempool)\n"
+ " \"immature\": xxx (numeric) balance from immature coinbase outputs\n"
+ " },\n"
+ "}\n"},
+ RPCExamples{
+ HelpExampleCli("getbalances", "") +
+ HelpExampleRpc("getbalances", "")},
+ };
+
+ if (request.fHelp || !help.IsValidNumArgs(request.params.size())) {
+ throw std::runtime_error(help.ToString());
+ }
+
+ // 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
+ wallet.BlockUntilSyncedToCurrentChain();
+
+ auto locked_chain = wallet.chain().lock();
+ LOCK(wallet.cs_wallet);
+
+ UniValue obj(UniValue::VOBJ);
+
+ const auto bal = wallet.GetBalance();
+ UniValue balances{UniValue::VOBJ};
+ {
+ UniValue balances_mine{UniValue::VOBJ};
+ balances_mine.pushKV("trusted", ValueFromAmount(bal.m_mine_trusted));
+ balances_mine.pushKV("untrusted_pending", ValueFromAmount(bal.m_mine_untrusted_pending));
+ balances_mine.pushKV("immature", ValueFromAmount(bal.m_mine_immature));
+ balances.pushKV("mine", balances_mine);
+ }
+ if (wallet.HaveWatchOnly()) {
+ UniValue balances_watchonly{UniValue::VOBJ};
+ balances_watchonly.pushKV("trusted", ValueFromAmount(bal.m_watchonly_trusted));
+ balances_watchonly.pushKV("untrusted_pending", ValueFromAmount(bal.m_watchonly_untrusted_pending));
+ balances_watchonly.pushKV("immature", ValueFromAmount(bal.m_watchonly_immature));
+ balances.pushKV("watchonly", balances_watchonly);
+ }
+ return balances;
+}
+
static UniValue getwalletinfo(const JSONRPCRequest& request)
{
std::shared_ptr<CWallet> const wallet = GetWalletForJSONRPCRequest(request);
@@ -2382,18 +2445,16 @@ static UniValue getwalletinfo(const JSONRPCRequest& request)
return NullUniValue;
}
- if (request.fHelp || request.params.size() != 0)
- throw std::runtime_error(
- RPCHelpMan{"getwalletinfo",
+ const RPCHelpMan help{"getwalletinfo",
"Returns an object containing various wallet state info.\n",
{},
RPCResult{
"{\n"
" \"walletname\": xxxxx, (string) the wallet name\n"
" \"walletversion\": xxxxx, (numeric) the wallet version\n"
- " \"balance\": xxxxxxx, (numeric) the total confirmed balance of the wallet in " + CURRENCY_UNIT + "\n"
- " \"unconfirmed_balance\": xxx, (numeric) the total unconfirmed balance of the wallet in " + CURRENCY_UNIT + "\n"
- " \"immature_balance\": xxxxxx, (numeric) the total immature balance of the wallet in " + CURRENCY_UNIT + "\n"
+ " \"balance\": xxxxxxx, (numeric) DEPRECATED. Identical to getbalances().mine.trusted\n"
+ " \"unconfirmed_balance\": xxx, (numeric) DEPRECATED. Identical to getbalances().mine.untrusted_pending\n"
+ " \"immature_balance\": xxxxxx, (numeric) DEPRECATED. Identical to getbalances().mine.immature\n"
" \"txcount\": xxxxxxx, (numeric) the total number of transactions in the wallet\n"
" \"keypoololdest\": xxxxxx, (numeric) the timestamp (seconds since Unix epoch) of the oldest pre-generated key in the key pool\n"
" \"keypoolsize\": xxxx, (numeric) how many new keys are pre-generated (only counts external keys)\n"
@@ -2402,13 +2463,22 @@ static UniValue getwalletinfo(const JSONRPCRequest& request)
" \"paytxfee\": x.xxxx, (numeric) the transaction fee configuration, set in " + CURRENCY_UNIT + "/kB\n"
" \"hdseedid\": \"<hash160>\" (string, optional) the Hash160 of the HD seed (only present when HD is enabled)\n"
" \"private_keys_enabled\": true|false (boolean) false if privatekeys are disabled for this wallet (enforced watch-only wallet)\n"
+ " \"scanning\": (json object) current scanning details, or false if no scan is in progress\n"
+ " {\n"
+ " \"duration\" : xxxx (numeric) elapsed seconds since scan start\n"
+ " \"progress\" : x.xxxx, (numeric) scanning progress percentage [0.0, 1.0]\n"
+ " }\n"
"}\n"
},
RPCExamples{
HelpExampleCli("getwalletinfo", "")
+ HelpExampleRpc("getwalletinfo", "")
},
- }.ToString());
+ };
+
+ if (request.fHelp || !help.IsValidNumArgs(request.params.size())) {
+ throw std::runtime_error(help.ToString());
+ }
// 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
@@ -2441,6 +2511,14 @@ static UniValue getwalletinfo(const JSONRPCRequest& request)
obj.pushKV("hdseedid", seed_id.GetHex());
}
obj.pushKV("private_keys_enabled", !pwallet->IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS));
+ if (pwallet->IsScanning()) {
+ UniValue scanning(UniValue::VOBJ);
+ scanning.pushKV("duration", pwallet->ScanningDuration() / 1000);
+ scanning.pushKV("progress", pwallet->ScanningProgress());
+ obj.pushKV("scanning", scanning);
+ } else {
+ obj.pushKV("scanning", false);
+ }
return obj;
}
@@ -2563,26 +2641,29 @@ static UniValue loadwallet(const JSONRPCRequest& request)
static UniValue createwallet(const JSONRPCRequest& request)
{
- if (request.fHelp || request.params.size() < 1 || request.params.size() > 3) {
- throw std::runtime_error(
- RPCHelpMan{"createwallet",
- "\nCreates and loads a new wallet.\n",
- {
- {"wallet_name", RPCArg::Type::STR, RPCArg::Optional::NO, "The name for the new wallet. If this is a path, the wallet will be created at the path location."},
- {"disable_private_keys", RPCArg::Type::BOOL, /* default */ "false", "Disable the possibility of private keys (only watchonlys are possible in this mode)."},
- {"blank", RPCArg::Type::BOOL, /* default */ "false", "Create a blank wallet. A blank wallet has no keys or HD seed. One can be set using sethdseed."},
- },
- RPCResult{
+ const RPCHelpMan help{
+ "createwallet",
+ "\nCreates and loads a new wallet.\n",
+ {
+ {"wallet_name", RPCArg::Type::STR, RPCArg::Optional::NO, "The name for the new wallet. If this is a path, the wallet will be created at the path location."},
+ {"disable_private_keys", RPCArg::Type::BOOL, /* default */ "false", "Disable the possibility of private keys (only watchonlys are possible in this mode)."},
+ {"blank", RPCArg::Type::BOOL, /* default */ "false", "Create a blank wallet. A blank wallet has no keys or HD seed. One can be set using sethdseed."},
+ {"passphrase", RPCArg::Type::STR, RPCArg::Optional::OMITTED, "Encrypt the wallet with this passphrase."},
+ },
+ RPCResult{
"{\n"
" \"name\" : <wallet_name>, (string) The wallet name if created successfully. If the wallet was created using a full path, the wallet_name will be the full path.\n"
" \"warning\" : <warning>, (string) Warning message if wallet was not loaded cleanly.\n"
"}\n"
- },
- RPCExamples{
- HelpExampleCli("createwallet", "\"testwallet\"")
+ },
+ RPCExamples{
+ HelpExampleCli("createwallet", "\"testwallet\"")
+ HelpExampleRpc("createwallet", "\"testwallet\"")
- },
- }.ToString());
+ },
+ };
+
+ if (request.fHelp || !help.IsValidNumArgs(request.params.size())) {
+ throw std::runtime_error(help.ToString());
}
std::string error;
std::string warning;
@@ -2592,7 +2673,20 @@ static UniValue createwallet(const JSONRPCRequest& request)
flags |= WALLET_FLAG_DISABLE_PRIVATE_KEYS;
}
+ bool create_blank = false; // Indicate that the wallet is actually supposed to be blank and not just blank to make it encrypted
if (!request.params[2].isNull() && request.params[2].get_bool()) {
+ create_blank = true;
+ flags |= WALLET_FLAG_BLANK_WALLET;
+ }
+ SecureString passphrase;
+ passphrase.reserve(100);
+ if (!request.params[3].isNull()) {
+ passphrase = request.params[3].get_str().c_str();
+ if (passphrase.empty()) {
+ // Empty string is invalid
+ throw JSONRPCError(RPC_WALLET_ENCRYPTION_FAILED, "Cannot encrypt a wallet with a blank password");
+ }
+ // Born encrypted wallets need to be blank first so that wallet creation doesn't make any unencrypted keys
flags |= WALLET_FLAG_BLANK_WALLET;
}
@@ -2610,6 +2704,29 @@ static UniValue createwallet(const JSONRPCRequest& request)
if (!wallet) {
throw JSONRPCError(RPC_WALLET_ERROR, "Wallet creation failed.");
}
+
+ // Encrypt the wallet if there's a passphrase
+ if (!passphrase.empty() && !(flags & WALLET_FLAG_DISABLE_PRIVATE_KEYS)) {
+ if (!wallet->EncryptWallet(passphrase)) {
+ throw JSONRPCError(RPC_WALLET_ENCRYPTION_FAILED, "Error: Wallet created but failed to encrypt.");
+ }
+
+ if (!create_blank) {
+ // Unlock the wallet
+ if (!wallet->Unlock(passphrase)) {
+ throw JSONRPCError(RPC_WALLET_ENCRYPTION_FAILED, "Error: Wallet was encrypted but could not be unlocked");
+ }
+
+ // Set a seed for the wallet
+ CPubKey master_pub_key = wallet->GenerateNewSeed();
+ wallet->SetHDSeed(master_pub_key);
+ wallet->NewKeyPool();
+
+ // Relock the wallet
+ wallet->Lock();
+ }
+ }
+
AddWallet(wallet);
wallet->postInitProcess();
@@ -2820,7 +2937,7 @@ static UniValue listunspent(const JSONRPCRequest& request)
}
if (scriptPubKey.IsPayToScriptHash()) {
- const CScriptID& hash = boost::get<CScriptID>(address);
+ const CScriptID& hash = CScriptID(boost::get<ScriptHash>(address));
CScript redeemScript;
if (pwallet->GetCScript(hash, redeemScript)) {
entry.pushKV("redeemScript", HexStr(redeemScript.begin(), redeemScript.end()));
@@ -3150,7 +3267,14 @@ UniValue signrawtransactionwithwallet(const JSONRPCRequest& request)
LOCK(pwallet->cs_wallet);
EnsureWalletIsUnlocked(pwallet);
- return SignTransaction(pwallet->chain(), mtx, request.params[1], pwallet, false, request.params[2]);
+ // Fetch previous transactions (inputs):
+ std::map<COutPoint, Coin> coins;
+ for (const CTxIn& txin : mtx.vin) {
+ coins[txin.prevout]; // Create empty map entry keyed by prevout.
+ }
+ pwallet->chain().findCoins(coins);
+
+ return SignTransaction(mtx, request.params[1], pwallet, coins, false, request.params[2]);
}
static UniValue bumpfee(const JSONRPCRequest& request)
@@ -3453,8 +3577,9 @@ public:
UniValue operator()(const CNoDestination& dest) const { return UniValue(UniValue::VOBJ); }
- UniValue operator()(const CKeyID& keyID) const
+ UniValue operator()(const PKHash& pkhash) const
{
+ CKeyID keyID(pkhash);
UniValue obj(UniValue::VOBJ);
CPubKey vchPubKey;
if (pwallet && pwallet->GetPubKey(keyID, vchPubKey)) {
@@ -3464,8 +3589,9 @@ public:
return obj;
}
- UniValue operator()(const CScriptID& scriptID) const
+ UniValue operator()(const ScriptHash& scripthash) const
{
+ CScriptID scriptID(scripthash);
UniValue obj(UniValue::VOBJ);
CScript subscript;
if (pwallet && pwallet->GetCScript(scriptID, subscript)) {
@@ -3683,9 +3809,20 @@ static UniValue getaddressesbylabel(const JSONRPCRequest& request)
// Find all addresses that have the given label
UniValue ret(UniValue::VOBJ);
+ std::set<std::string> addresses;
for (const std::pair<const CTxDestination, CAddressBookData>& item : pwallet->mapAddressBook) {
if (item.second.name == label) {
- ret.pushKV(EncodeDestination(item.first), AddressBookDataToJSON(item.second, false));
+ std::string address = EncodeDestination(item.first);
+ // CWallet::mapAddressBook is not expected to contain duplicate
+ // address strings, but build a separate set as a precaution just in
+ // case it does.
+ bool unique = addresses.emplace(address).second;
+ assert(unique);
+ // UniValue::pushKV checks if the key exists in O(N)
+ // and since duplicate addresses are unexpected (checked with
+ // std::set in O(log(N))), UniValue::__pushKV is used instead,
+ // which currently is O(1).
+ ret.__pushKV(address, AddressBookDataToJSON(item.second, false));
}
}
@@ -4042,7 +4179,7 @@ static const CRPCCommand commands[] =
{ "wallet", "addmultisigaddress", &addmultisigaddress, {"nrequired","keys","label","address_type"} },
{ "wallet", "backupwallet", &backupwallet, {"destination"} },
{ "wallet", "bumpfee", &bumpfee, {"txid", "options"} },
- { "wallet", "createwallet", &createwallet, {"wallet_name", "disable_private_keys", "blank"} },
+ { "wallet", "createwallet", &createwallet, {"wallet_name", "disable_private_keys", "blank", "passphrase"} },
{ "wallet", "dumpprivkey", &dumpprivkey, {"address"} },
{ "wallet", "dumpwallet", &dumpwallet, {"filename"} },
{ "wallet", "encryptwallet", &encryptwallet, {"passphrase"} },
@@ -4055,6 +4192,7 @@ static const CRPCCommand commands[] =
{ "wallet", "getreceivedbylabel", &getreceivedbylabel, {"label","minconf"} },
{ "wallet", "gettransaction", &gettransaction, {"txid","include_watchonly"} },
{ "wallet", "getunconfirmedbalance", &getunconfirmedbalance, {} },
+ { "wallet", "getbalances", &getbalances, {} },
{ "wallet", "getwalletinfo", &getwalletinfo, {} },
{ "wallet", "importaddress", &importaddress, {"address","label","rescan","p2sh"} },
{ "wallet", "importmulti", &importmulti, {"requests","options"} },