Merge #15930: rpc: Add balances RPC

facfb4111d rpc: Deprecate getunconfirmedbalance and getwalletinfo balances (MarcoFalke)
999931cf8f rpc: Add getbalances RPC (MarcoFalke)
fad13e925e rpcwallet: Make helper methods const on CWallet (MarcoFalke)
fad40ec915 wallet: Use IsValidNumArgs in getwalletinfo rpc (MarcoFalke)

Pull request description:

  This exposes the `CWallet::GetBalance()` struct over RPC.

  In the future, incorrectly named rpcs such as `getunconfirmedbalance` or rpcs redundant to this such as `getbalance` could be removed.

ACKs for commit facfb4:
  jnewbery:
    utACK facfb4111d

Tree-SHA512: 1f54fedce55df9a8ea82d2b6265354b39a956072621876ebaee2355aac0e23c7b64340c3279502415598c095858529e18b50789be956250aafda1cd3a8d948a5
This commit is contained in:
Wladimir J. van der Laan 2019-05-06 10:12:20 +02:00
commit a3d2d6b067
No known key found for this signature in database
GPG key ID: 1E4AED62986CD25D
4 changed files with 105 additions and 18 deletions

View file

@ -61,6 +61,15 @@ platform.
Notable changes Notable changes
=============== ===============
New RPCs
--------
- `getbalances` returns an object with all balances (`mine`,
`untrusted_pending` and `immature`). Please refer to the RPC help of
`getbalances` for details. The new RPC is intended to replace
`getunconfirmedbalance` and the balance fields in `getwalletinfo`, as well as
`getbalance`. The old calls may be removed in a future version.
Updated RPCs Updated RPCs
------------ ------------

View file

@ -1,5 +1,5 @@
// Copyright (c) 2010 Satoshi Nakamoto // 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 // Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php. // file COPYING or http://www.opensource.org/licenses/mit-license.php.
@ -9,7 +9,6 @@
#include <core_io.h> #include <core_io.h>
#include <init.h> #include <init.h>
#include <interfaces/chain.h> #include <interfaces/chain.h>
#include <validation.h>
#include <key_io.h> #include <key_io.h>
#include <net.h> #include <net.h>
#include <node/transaction.h> #include <node/transaction.h>
@ -27,10 +26,11 @@
#include <timedata.h> #include <timedata.h>
#include <util/bip32.h> #include <util/bip32.h>
#include <util/fees.h> #include <util/fees.h>
#include <util/system.h>
#include <util/moneystr.h> #include <util/moneystr.h>
#include <util/system.h>
#include <util/url.h> #include <util/url.h>
#include <util/validation.h> #include <util/validation.h>
#include <validation.h>
#include <wallet/coincontrol.h> #include <wallet/coincontrol.h>
#include <wallet/feebumper.h> #include <wallet/feebumper.h>
#include <wallet/psbtwallet.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; 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() return pwallet && pwallet->IsCrypted()
? "\nRequires wallet passphrase to be set with walletpassphrase call." ? "\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 (pwallet) return true;
if (avoidException) return false; 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)."); "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()) { if (pwallet->IsLocked()) {
throw JSONRPCError(RPC_WALLET_UNLOCK_NEEDED, "Error: Please enter the wallet passphrase with walletpassphrase first."); throw JSONRPCError(RPC_WALLET_UNLOCK_NEEDED, "Error: Please enter the wallet passphrase with walletpassphrase first.");
@ -785,7 +785,7 @@ static UniValue getunconfirmedbalance(const JSONRPCRequest &request)
if (request.fHelp || request.params.size() > 0) if (request.fHelp || request.params.size() > 0)
throw std::runtime_error( throw std::runtime_error(
RPCHelpMan{"getunconfirmedbalance", RPCHelpMan{"getunconfirmedbalance",
"Returns the server's total unconfirmed balance\n", "DEPRECATED\nIdentical to getbalances().mine.untrusted_pending\n",
{}, {},
RPCResults{}, RPCResults{},
RPCExamples{""}, RPCExamples{""},
@ -2373,6 +2373,68 @@ static UniValue settxfee(const JSONRPCRequest& request)
return true; 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) static UniValue getwalletinfo(const JSONRPCRequest& request)
{ {
std::shared_ptr<CWallet> const wallet = GetWalletForJSONRPCRequest(request); std::shared_ptr<CWallet> const wallet = GetWalletForJSONRPCRequest(request);
@ -2382,18 +2444,16 @@ static UniValue getwalletinfo(const JSONRPCRequest& request)
return NullUniValue; return NullUniValue;
} }
if (request.fHelp || request.params.size() != 0) const RPCHelpMan help{"getwalletinfo",
throw std::runtime_error(
RPCHelpMan{"getwalletinfo",
"Returns an object containing various wallet state info.\n", "Returns an object containing various wallet state info.\n",
{}, {},
RPCResult{ RPCResult{
"{\n" "{\n"
" \"walletname\": xxxxx, (string) the wallet name\n" " \"walletname\": xxxxx, (string) the wallet name\n"
" \"walletversion\": xxxxx, (numeric) the wallet version\n" " \"walletversion\": xxxxx, (numeric) the wallet version\n"
" \"balance\": xxxxxxx, (numeric) the total confirmed balance of the wallet in " + CURRENCY_UNIT + "\n" " \"balance\": xxxxxxx, (numeric) DEPRECATED. Identical to getbalances().mine.trusted\n"
" \"unconfirmed_balance\": xxx, (numeric) the total unconfirmed balance of the wallet in " + CURRENCY_UNIT + "\n" " \"unconfirmed_balance\": xxx, (numeric) DEPRECATED. Identical to getbalances().mine.untrusted_pending\n"
" \"immature_balance\": xxxxxx, (numeric) the total immature balance of the wallet in " + CURRENCY_UNIT + "\n" " \"immature_balance\": xxxxxx, (numeric) DEPRECATED. Identical to getbalances().mine.immature\n"
" \"txcount\": xxxxxxx, (numeric) the total number of transactions in the wallet\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" " \"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" " \"keypoolsize\": xxxx, (numeric) how many new keys are pre-generated (only counts external keys)\n"
@ -2408,7 +2468,11 @@ static UniValue getwalletinfo(const JSONRPCRequest& request)
HelpExampleCli("getwalletinfo", "") HelpExampleCli("getwalletinfo", "")
+ HelpExampleRpc("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 // 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 // the user could have gotten from another RPC command prior to now
@ -4073,6 +4137,7 @@ static const CRPCCommand commands[] =
{ "wallet", "getreceivedbylabel", &getreceivedbylabel, {"label","minconf"} }, { "wallet", "getreceivedbylabel", &getreceivedbylabel, {"label","minconf"} },
{ "wallet", "gettransaction", &gettransaction, {"txid","include_watchonly"} }, { "wallet", "gettransaction", &gettransaction, {"txid","include_watchonly"} },
{ "wallet", "getunconfirmedbalance", &getunconfirmedbalance, {} }, { "wallet", "getunconfirmedbalance", &getunconfirmedbalance, {} },
{ "wallet", "getbalances", &getbalances, {} },
{ "wallet", "getwalletinfo", &getwalletinfo, {} }, { "wallet", "getwalletinfo", &getwalletinfo, {} },
{ "wallet", "importaddress", &importaddress, {"address","label","rescan","p2sh"} }, { "wallet", "importaddress", &importaddress, {"address","label","rescan","p2sh"} },
{ "wallet", "importmulti", &importmulti, {"requests","options"} }, { "wallet", "importmulti", &importmulti, {"requests","options"} },

View file

@ -31,9 +31,9 @@ void RegisterWalletRPCCommands(interfaces::Chain& chain, std::vector<std::unique
*/ */
std::shared_ptr<CWallet> GetWalletForJSONRPCRequest(const JSONRPCRequest& request); std::shared_ptr<CWallet> GetWalletForJSONRPCRequest(const JSONRPCRequest& request);
std::string HelpRequiringPassphrase(CWallet *); std::string HelpRequiringPassphrase(const CWallet*);
void EnsureWalletIsUnlocked(CWallet *); void EnsureWalletIsUnlocked(const CWallet*);
bool EnsureWalletIsAvailable(CWallet *, bool avoidException); bool EnsureWalletIsAvailable(const CWallet*, bool avoidException);
UniValue getaddressinfo(const JSONRPCRequest& request); UniValue getaddressinfo(const JSONRPCRequest& request);
UniValue signrawtransactionwithwallet(const JSONRPCRequest& request); UniValue signrawtransactionwithwallet(const JSONRPCRequest& request);

View file

@ -59,14 +59,24 @@ class WalletTest(BitcoinTestFramework):
assert_equal(len(self.nodes[0].listunspent()), 0) assert_equal(len(self.nodes[0].listunspent()), 0)
assert_equal(len(self.nodes[1].listunspent()), 0) assert_equal(len(self.nodes[1].listunspent()), 0)
self.log.info("Mining blocks ...") self.log.info("Check that only node 0 is watching an address")
assert 'watchonly' in self.nodes[0].getbalances()
assert 'watchonly' not in self.nodes[1].getbalances()
self.log.info("Mining blocks ...")
self.nodes[0].generate(1) self.nodes[0].generate(1)
self.sync_all() self.sync_all()
self.nodes[1].generate(1) self.nodes[1].generate(1)
self.nodes[1].generatetoaddress(101, ADDRESS_WATCHONLY) self.nodes[1].generatetoaddress(101, ADDRESS_WATCHONLY)
self.sync_all() self.sync_all()
assert_equal(self.nodes[0].getbalances()['mine']['trusted'], 50)
assert_equal(self.nodes[0].getwalletinfo()['balance'], 50)
assert_equal(self.nodes[1].getbalances()['mine']['trusted'], 50)
assert_equal(self.nodes[0].getbalances()['watchonly']['immature'], 5000)
assert 'watchonly' not in self.nodes[1].getbalances()
assert_equal(self.nodes[0].getbalance(), 50) assert_equal(self.nodes[0].getbalance(), 50)
assert_equal(self.nodes[1].getbalance(), 50) assert_equal(self.nodes[1].getbalance(), 50)
@ -107,8 +117,11 @@ class WalletTest(BitcoinTestFramework):
assert_equal(self.nodes[1].getbalance(minconf=1), Decimal('0')) assert_equal(self.nodes[1].getbalance(minconf=1), Decimal('0'))
# getunconfirmedbalance # getunconfirmedbalance
assert_equal(self.nodes[0].getunconfirmedbalance(), Decimal('60')) # output of node 1's spend assert_equal(self.nodes[0].getunconfirmedbalance(), Decimal('60')) # output of node 1's spend
assert_equal(self.nodes[0].getbalances()['mine']['untrusted_pending'], Decimal('60'))
assert_equal(self.nodes[0].getwalletinfo()["unconfirmed_balance"], Decimal('60')) assert_equal(self.nodes[0].getwalletinfo()["unconfirmed_balance"], Decimal('60'))
assert_equal(self.nodes[1].getunconfirmedbalance(), Decimal('0')) # Doesn't include output of node 0's send since it was spent assert_equal(self.nodes[1].getunconfirmedbalance(), Decimal('0')) # Doesn't include output of node 0's send since it was spent
assert_equal(self.nodes[1].getbalances()['mine']['untrusted_pending'], Decimal('0'))
assert_equal(self.nodes[1].getwalletinfo()["unconfirmed_balance"], Decimal('0')) assert_equal(self.nodes[1].getwalletinfo()["unconfirmed_balance"], Decimal('0'))
test_balances(fee_node_1=Decimal('0.01')) test_balances(fee_node_1=Decimal('0.01'))