Skip to content

Commit

Permalink
Merge bitcoin#15930: rpc: Add balances RPC
Browse files Browse the repository at this point in the history
facfb41 rpc: Deprecate getunconfirmedbalance and getwalletinfo balances (MarcoFalke)
999931c rpc: Add getbalances RPC (MarcoFalke)
fad13e9 rpcwallet: Make helper methods const on CWallet (MarcoFalke)
fad40ec 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 facfb41

Tree-SHA512: 1f54fedce55df9a8ea82d2b6265354b39a956072621876ebaee2355aac0e23c7b64340c3279502415598c095858529e18b50789be956250aafda1cd3a8d948a5
  • Loading branch information
laanwj authored and PastaPastaPasta committed Oct 17, 2022
1 parent ad68327 commit 078094b
Show file tree
Hide file tree
Showing 3 changed files with 86 additions and 10 deletions.
79 changes: 71 additions & 8 deletions src/wallet/rpcwallet.cpp
Original file line number Diff line number Diff line change
@@ -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
// Copyright (c) 2014-2022 The Dash Core developers
// Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
Expand All @@ -20,10 +20,10 @@
#include <util/bip32.h>
#include <util/fees.h>
#include <util/message.h> // For MessageSign()
#include <util/system.h>
#include <util/moneystr.h>
#include <util/ref.h>
#include <util/string.h>
#include <util/system.h>
#include <util/translation.h>
#include <util/url.h>
#include <util/vector.h>
Expand Down Expand Up @@ -123,7 +123,7 @@ std::shared_ptr<CWallet> GetWalletForJSONRPCRequest(const JSONRPCRequest& reques
"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.");
Expand Down Expand Up @@ -801,7 +801,7 @@ static UniValue getbalance(const JSONRPCRequest& request)
static UniValue getunconfirmedbalance(const JSONRPCRequest &request)
{
RPCHelpMan{"getunconfirmedbalance",
"Returns the server's total unconfirmed balance\n",
"DEPRECATED\nIdentical to getbalances().mine.untrusted_pending\n",
{},
RPCResult{RPCResult::Type::NUM, "", "The balance"},
RPCExamples{""},
Expand Down Expand Up @@ -2381,6 +2381,68 @@ static UniValue setcoinjoinamount(const JSONRPCRequest& request)
return NullUniValue;
}

static UniValue getbalances(const JSONRPCRequest& request)
{
RPCHelpMan{"getbalances",
"Returns an object with all balances in " + CURRENCY_UNIT + ".\n",
{},
RPCResult{
RPCResult::Type::OBJ, "", "",
{
{RPCResult::Type::OBJ, "mine", "balances from outputs that the wallet can sign",
{
{RPCResult::Type::STR_AMOUNT, "trusted", " trusted balance (outputs created by the wallet or confirmed outputs)"},
{RPCResult::Type::STR_AMOUNT, "untrusted_pending", " untrusted pending balance (outputs created by others that are in the mempool)"},
{RPCResult::Type::STR_AMOUNT, "immature", " balance from immature coinbase outputs"},
{RPCResult::Type::STR_AMOUNT, "coinjoin", " CoinJoin balance (outputs with enough rounds created by the wallet via mixing)"},
}},
{RPCResult::Type::OBJ, "watchonly", "watchonly balances (not present if wallet does not watch anything)",
{
{RPCResult::Type::STR_AMOUNT, "trusted", " trusted balance (outputs created by the wallet or confirmed outputs)"},
{RPCResult::Type::STR_AMOUNT, "untrusted_pending", " untrusted pending balance (outputs created by others that are in the mempool)"},
{RPCResult::Type::STR_AMOUNT, "immature", " balance from immature coinbase outputs"},
}},
},
},
RPCExamples{
HelpExampleCli("getbalances", "") +
HelpExampleRpc("getbalances", "")
},
}.Check(request);

std::shared_ptr<CWallet> const rpc_wallet = GetWalletForJSONRPCRequest(request);
if (!rpc_wallet) return NullUniValue;
CWallet& wallet = *rpc_wallet;

// 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();

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_mine.pushKV("coinjoin", ValueFromAmount(bal.m_anonymized));
balances.pushKV("mine", balances_mine);
}
auto spk_man = wallet.GetLegacyScriptPubKeyMan();
if (spk_man && spk_man->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)
{
RPCHelpMan{"getwalletinfo",
Expand All @@ -2391,10 +2453,10 @@ static UniValue getwalletinfo(const JSONRPCRequest& request)
{
{RPCResult::Type::STR, "walletname", "the wallet name"},
{RPCResult::Type::NUM, "walletversion", "the wallet version"},
{RPCResult::Type::NUM, "balance", "the total confirmed balance of the wallet in " + CURRENCY_UNIT},
{RPCResult::Type::NUM, "coinjoin_balance", "the CoinJoin balance in " + CURRENCY_UNIT},
{RPCResult::Type::NUM, "unconfirmed_balance", "the total unconfirmed balance of the wallet in " + CURRENCY_UNIT},
{RPCResult::Type::NUM, "immature_balance", "the total immature balance of the wallet in " + CURRENCY_UNIT},
{RPCResult::Type::NUM, "balance", "DEPRECATED. Identical to getbalances().mine.trusted"},
{RPCResult::Type::NUM, "coinjoin_balance", "DEPRECATED. Identical to getbalances().mine.coinjoin"},
{RPCResult::Type::NUM, "unconfirmed_balance", "DEPRECATED. Identical to getbalances().mine.untrusted_pending"},
{RPCResult::Type::NUM, "immature_balance", "DEPRECATED. Identical to getbalances().mine.immature"},
{RPCResult::Type::NUM, "txcount", "the total number of transactions in the wallet"},
{RPCResult::Type::NUM_TIME, "timefirstkey", "the " + UNIX_EPOCH_TIME + " of the oldest known key in the wallet"},
{RPCResult::Type::NUM_TIME, "keypoololdest", "the " + UNIX_EPOCH_TIME + " of the oldest pre-generated key in the key pool"},
Expand Down Expand Up @@ -3985,6 +4047,7 @@ static const CRPCCommand commands[] =
{ "wallet", "getreceivedbylabel", &getreceivedbylabel, {"label","minconf","addlocked"} },
{ "wallet", "gettransaction", &gettransaction, {"txid","include_watchonly"} },
{ "wallet", "getunconfirmedbalance", &getunconfirmedbalance, {} },
{ "wallet", "getbalances", &getbalances, {} },
{ "wallet", "getwalletinfo", &getwalletinfo, {} },
{ "wallet", "importaddress", &importaddress, {"address","label","rescan","p2sh"} },
{ "wallet", "importelectrumwallet", &importelectrumwallet, {"filename", "index"} },
Expand Down
2 changes: 1 addition & 1 deletion src/wallet/rpcwallet.h
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ Span<const CRPCCommand> GetWalletRPCCommands();
*/
std::shared_ptr<CWallet> GetWalletForJSONRPCRequest(const JSONRPCRequest& request);

void EnsureWalletIsUnlocked(CWallet *);
void EnsureWalletIsUnlocked(const CWallet*);
WalletContext& EnsureWalletContext(const util::Ref& context);

UniValue getaddressinfo(const JSONRPCRequest& request);
Expand Down
15 changes: 14 additions & 1 deletion test/functional/wallet_balance.py
Original file line number Diff line number Diff line change
Expand Up @@ -62,14 +62,24 @@ def run_test(self):
assert_equal(len(self.nodes[0].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.sync_all()
self.nodes[1].generate(1)
self.nodes[1].generatetoaddress(101, ADDRESS_WATCHONLY)
self.sync_all()

assert_equal(self.nodes[0].getbalances()['mine']['trusted'], 500)
assert_equal(self.nodes[0].getwalletinfo()['balance'], 500)
assert_equal(self.nodes[1].getbalances()['mine']['trusted'], 500)

assert_equal(self.nodes[0].getbalances()['watchonly']['immature'], 50000)
assert 'watchonly' not in self.nodes[1].getbalances()

assert_equal(self.nodes[0].getbalance(), 500)
assert_equal(self.nodes[1].getbalance(), 500)

Expand Down Expand Up @@ -113,8 +123,11 @@ def test_balances(*, fee_node_1=0):
assert_equal(self.nodes[1].getbalance(minconf=1), Decimal('0'))
# getunconfirmedbalance
assert_equal(self.nodes[0].getunconfirmedbalance(), Decimal('960')) # output of node 1's spend
assert_equal(self.nodes[0].getbalances()['mine']['untrusted_pending'], Decimal('960'))
assert_equal(self.nodes[0].getwalletinfo()["unconfirmed_balance"], Decimal('960'))

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'))

test_balances(fee_node_1=Decimal('0.01'))
Expand Down

0 comments on commit 078094b

Please sign in to comment.