Skip to content

Commit

Permalink
wallet: Add scan_utxo option to getbalances RPC
Browse files Browse the repository at this point in the history
Add the option to getbalances by scanning the utxo set
  • Loading branch information
BrandonOdiwuor committed Nov 28, 2023
1 parent d752349 commit b210d3b
Show file tree
Hide file tree
Showing 6 changed files with 69 additions and 4 deletions.
3 changes: 3 additions & 0 deletions src/interfaces/chain.h
Original file line number Diff line number Diff line change
Expand Up @@ -188,6 +188,9 @@ class Chain
//! populates the values.
virtual void findCoins(std::map<COutPoint, Coin>& coins) = 0;

//! Scan UTXO set from coins belonging to the output_scripts
virtual void getCoinsByScript(std::set<CScript>& output_scripts, std::map<COutPoint, Coin>& coins) = 0;

//! Estimate fraction of total transactions verified if blocks up to
//! the specified block hash are verified.
virtual double guessVerificationProgress(const uint256& block_hash) = 0;
Expand Down
17 changes: 17 additions & 0 deletions src/node/coin.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -23,4 +23,21 @@ void FindCoins(const NodeContext& node, std::map<COutPoint, Coin>& coins)
}
}
}

void GetCoins(const NodeContext& node, std::set<CScript>& output_scripts, std::map<COutPoint, Coin>& coins)
{
assert(node.chainman);
LOCK(cs_main);
std::unique_ptr<CCoinsViewCursor> cursor = node.chainman->ActiveChainstate().CoinsDB().Cursor();
while (cursor->Valid()) {
COutPoint key;
Coin coin;
if (cursor->GetKey(key) && cursor->GetValue(coin)) {
if (output_scripts.count(coin.out.scriptPubKey)) {
coins.emplace(key, coin);
}
}
cursor->Next();
}
}
} // namespace node
11 changes: 11 additions & 0 deletions src/node/coin.h
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,11 @@
#define BITCOIN_NODE_COIN_H

#include <map>
#include <set>

class COutPoint;
class Coin;
class CScript;

namespace node {
struct NodeContext;
Expand All @@ -22,6 +24,15 @@ struct NodeContext;
* @param[in,out] coins map to fill
*/
void FindCoins(const node::NodeContext& node, std::map<COutPoint, Coin>& coins);

/**
* Scans the UTXO set for coins belonging to output_scripts.
*
* @param[in] node The node context to use for lookup
* @param[in] output_scripts The scripts to scan for coins
* @param[in,out] coins map to fill
*/
void GetCoins(const NodeContext& node, std::set<CScript>& output_scripts, std::map<COutPoint, Coin>& coins);
} // namespace node

#endif // BITCOIN_NODE_COIN_H
4 changes: 4 additions & 0 deletions src/node/interfaces.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -610,6 +610,10 @@ class ChainImpl : public Chain
int{FillBlock(block2, block2_out, lock, active, chainman().m_blockman)};
}
void findCoins(std::map<COutPoint, Coin>& coins) override { return FindCoins(m_node, coins); }
void getCoinsByScript(std::set<CScript>& output_scripts, std::map<COutPoint, Coin>& coins) override
{
return GetCoins(m_node, output_scripts, coins);
}
double guessVerificationProgress(const uint256& block_hash) override
{
LOCK(::cs_main);
Expand Down
1 change: 1 addition & 0 deletions src/rpc/client.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -185,6 +185,7 @@ static const CRPCConvertParam vRPCConvertParams[] =
{ "lockunspent", 0, "unlock" },
{ "lockunspent", 1, "transactions" },
{ "lockunspent", 2, "persistent" },
{ "getbalances", 0, "scan_utxoset" },
{ "send", 0, "outputs" },
{ "send", 1, "conf_target" },
{ "send", 3, "fee_rate"},
Expand Down
37 changes: 33 additions & 4 deletions src/wallet/rpc/coins.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -432,7 +432,9 @@ RPCHelpMan getbalances()
return RPCHelpMan{
"getbalances",
"Returns an object with all balances in " + CURRENCY_UNIT + ".\n",
{},
{
{"scan_utxoset", RPCArg::Type::BOOL, RPCArg::Default{false}, "Whether to scan (true) or not to scan (false) the unspent transactions output set"},
},
RPCResult{
RPCResult::Type::OBJ, "", "",
{
Expand All @@ -453,8 +455,10 @@ RPCHelpMan getbalances()
}
},
RPCExamples{
HelpExampleCli("getbalances", "") +
HelpExampleRpc("getbalances", "")},
HelpExampleCli("getbalances", "")
+ HelpExampleRpc("getbalances", "")
+ HelpExampleCli("getbalances", "true")
},
[&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
{
const std::shared_ptr<const CWallet> rpc_wallet = GetWalletForJSONRPCRequest(request);
Expand All @@ -468,10 +472,35 @@ RPCHelpMan getbalances()
LOCK(wallet.cs_wallet);

const auto bal = GetBalance(wallet);

CAmount utxo_balance = 0;

bool scan_utxoset = false;
if (!request.params[0].isNull()) {
scan_utxoset = request.params[0].get_bool();
}
if (scan_utxoset) {
std::set<CScript> output_scripts;
for (const auto spkm : wallet.GetAllScriptPubKeyMans()) {
for (const auto& script : spkm->GetScriptPubKeys()) {
output_scripts.emplace(script);
}
}

// Get coins belonging to wallet scripts from utxo set
std::map<COutPoint, Coin> coins;
wallet.chain().getCoinsByScript(output_scripts, coins);

for (const auto& it : coins) {
const Coin& coin = it.second;
utxo_balance += coin.out.nValue;
}
}

UniValue balances{UniValue::VOBJ};
{
UniValue balances_mine{UniValue::VOBJ};
balances_mine.pushKV("trusted", ValueFromAmount(bal.m_mine_trusted));
balances_mine.pushKV("trusted", ValueFromAmount(scan_utxoset ? utxo_balance : bal.m_mine_trusted));
balances_mine.pushKV("untrusted_pending", ValueFromAmount(bal.m_mine_untrusted_pending));
balances_mine.pushKV("immature", ValueFromAmount(bal.m_mine_immature));
if (wallet.IsWalletFlagSet(WALLET_FLAG_AVOID_REUSE)) {
Expand Down

0 comments on commit b210d3b

Please sign in to comment.