Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement getutxostats RPC method for total unique funded addresses. #531

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
3 changes: 3 additions & 0 deletions src/kernel/coinstats.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,9 @@ static bool ComputeUTXOStats(CCoinsView* view, CCoinsStats& stats, T hash_obj, c
prevkey = key.hash;
outputs[key.n] = std::move(coin);
stats.coins_count++;

// Add the scriptPubKey to the set of unique scriptPubKeys
stats.uniqueScriptPubKeys.insert(coin.out.scriptPubKey);
} else {
return error("%s: unable to read value", __func__);
}
Expand Down
3 changes: 3 additions & 0 deletions src/kernel/coinstats.h
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,9 @@ struct CCoinsStats {
//! The total amount, or nullopt if an overflow occurred calculating it
std::optional<CAmount> total_amount{0};

//! The set of unique scriptPubKeys in the UTXO set. Used to estimate the number of addresses holding coins.
std::set<CScript> uniqueScriptPubKeys;

//! The number of coins contained.
uint64_t coins_count{0};

Expand Down
60 changes: 60 additions & 0 deletions src/rpc/blockchain.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1033,6 +1033,65 @@ static std::optional<kernel::CCoinsStats> GetUTXOStats(CCoinsView* view, node::B
return kernel::ComputeUTXOStats(hash_type, view, blockman, interruption_point);
}

static RPCHelpMan getutxostats()
{
return RPCHelpMan{"getutxostats",
"\nReturns statistics about the unspent transaction output (UTXO) set\n",
{},
RPCResult{
RPCResult::Type::OBJ, "", "",
{
{RPCResult::Type::NUM, "height", "The current block height (index)"},
{RPCResult::Type::NUM, "transactions", "The number of transactions with unspent outputs"},
{RPCResult::Type::NUM, "txouts", "The number of unspent transaction outputs"},
{RPCResult::Type::NUM, "bogosize", "A meaningless metric for UTXO set size"},
{RPCResult::Type::STR_AMOUNT, "total_amount", "The total amount"},
{RPCResult::Type::NUM, "total_addresses", "The total number of addresses holding coins"},
},
},
RPCExamples{
HelpExampleCli("getutxostats", "")
+ HelpExampleRpc("getutxostats", "")
},
[&](const RPCHelpMan& self, const node::JSONRPCRequest& request) -> UniValue
{
NodeContext& node = EnsureAnyNodeContext(request.context);
ChainstateManager& chainman = EnsureChainman(node);
Chainstate& active_chainstate = chainman.ActiveChainstate();
active_chainstate.ForceFlushStateToDisk();

CCoinsView* coins_view;
node::BlockManager* blockman;
const CBlockIndex* pindex{nullptr};

{
LOCK(::cs_main);
coins_view = &active_chainstate.CoinsDB();
blockman = &active_chainstate.m_blockman;
pindex = blockman->LookupBlockIndex(coins_view->GetBestBlock());
}

const std::optional<CCoinsStats> maybe_stats = GetUTXOStats(coins_view, *blockman, CoinStatsHashType::HASH_SERIALIZED, node.rpc_interruption_point, pindex, true);
if (!maybe_stats.has_value()) {
throw JSONRPCError(RPC_INTERNAL_ERROR, "Unable to read UTXO set");
}

const CCoinsStats& stats = maybe_stats.value();
UniValue ret(UniValue::VOBJ);
ret.pushKV("height", int(stats.nHeight));
ret.pushKV("transactions", int64_t(stats.nTransactions));
ret.pushKV("txouts", int64_t(stats.nTransactionOutputs));
ret.pushKV("bogosize", int64_t(stats.nBogoSize));
if (stats.total_amount.has_value()) {
ret.pushKV("total_amount", ValueFromAmount(stats.total_amount.value()));
}
ret.pushKV("total_addresses", int64_t(stats.uniqueScriptPubKeys.size()));

return ret;
},
};
}

static RPCHelpMan gettxoutsetinfo()
{
return RPCHelpMan{"gettxoutsetinfo",
Expand Down Expand Up @@ -2925,6 +2984,7 @@ void RegisterBlockchainRPCCommands(CRPCTable& t)
{"blockchain", &getblockfilter},
// SYSCOIN
{"blockchain", &getchainlocks},
{"blockchain", &getutxostats},
{"hidden", &invalidateblock},
{"hidden", &reconsiderblock},
{"hidden", &waitfornewblock},
Expand Down
2 changes: 2 additions & 0 deletions src/rpc/blockchain.h
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
#ifndef SYSCOIN_RPC_BLOCKCHAIN_H
#define SYSCOIN_RPC_BLOCKCHAIN_H

#include <kernel/coinstats.h>

#include <consensus/amount.h>
#include <core_io.h>
#include <streams.h>
Expand Down
20 changes: 20 additions & 0 deletions test/functional/rpc_blockchain.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
- getblock
- getblockhash
- getbestblockhash
- getutxostats
- verifychain

Tests correspond to code in rpc/blockchain.cpp.
Expand Down Expand Up @@ -86,6 +87,7 @@ def run_test(self):
self._test_getblockheader()
self._test_getdifficulty()
self._test_getnetworkhashps()
self._test_getutxostats()
self._test_stopatheight()
self._test_waitforblockheight()
self._test_getblock()
Expand Down Expand Up @@ -595,6 +597,24 @@ def move_block_file(old, new):
assert 'previousblockhash' not in node.getblock(node.getblockhash(0))
assert 'nextblockhash' not in node.getblock(node.getbestblockhash())

def _test_getutxostats(self):
self.log.info("Test getutxostats")
node = self.nodes[0]

# Call getutxostats with default arguments
res = node.getutxostats()

# Check that the result includes the expected keys
expected_keys = ['height', 'transactions', 'txouts', 'bogosize', 'total_amount', 'total_addresses']
assert_equal(sorted(res.keys()), sorted(expected_keys))

# Check that the values are of the correct types
assert isinstance(res['height'], int)
assert isinstance(res['transactions'], int)
assert isinstance(res['txouts'], int)
assert isinstance(res['bogosize'], int)
assert isinstance(res['total_amount'], Decimal)
assert isinstance(res['total_addresses'], int)

if __name__ == '__main__':
BlockchainTest().main()