Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
264 changes: 141 additions & 123 deletions qa/rpc-tests/data/rpc_getblockstats.json

Large diffs are not rendered by default.

3 changes: 2 additions & 1 deletion src/Makefile.am
Original file line number Diff line number Diff line change
Expand Up @@ -125,10 +125,11 @@ BITCOIN_CORE_H = \
protocol.h \
random.h \
reverselock.h \
rpc/blockchain.h \
rpc/client.h \
rpc/protocol.h \
rpc/server.h \
rpc/register.h \
rpc/server.h \
scheduler.h \
script/generic.hpp \
script/sigcache.h \
Expand Down
65 changes: 55 additions & 10 deletions src/rpc/blockchain.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
// Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.

#include "blockchain.h"

#include "amount.h"
#include "chain.h"
#include "chainparams.h"
Expand Down Expand Up @@ -73,7 +75,7 @@ UniValue blockheaderToJSON(const CBlockIndex* blockindex)
return result;
}

UniValue blockToJSON(const CBlock& block, const CBlockIndex* blockindex, bool txDetails = false)
UniValue blockToJSON(const CBlock& block, const CBlockIndex* blockindex, bool txDetails)
{
UniValue result(UniValue::VOBJ);
result.push_back(Pair("hash", blockindex->GetBlockHash().GetHex()));
Expand Down Expand Up @@ -340,7 +342,7 @@ void entryToJSON(UniValue &info, const CTxMemPoolEntry &e)
info.push_back(Pair("depends", depends));
}

UniValue mempoolToJSON(bool fVerbose = false)
UniValue mempoolToJSON(bool fVerbose)
{
if (fVerbose)
{
Expand Down Expand Up @@ -1421,6 +1423,35 @@ static T CalculateTruncatedMedian(std::vector<T>& scores)
}
}

void CalculatePercentilesByWeight(CAmount result[NUM_GETBLOCKSTATS_PERCENTILES], std::vector<std::pair<CAmount, int64_t>>& scores, int64_t total_weight)
{
if (scores.empty()) {
return;
}

std::sort(scores.begin(), scores.end());

// 10th, 25th, 50th, 75th, and 90th percentile weight units.
const double weights[NUM_GETBLOCKSTATS_PERCENTILES] = {
total_weight / 10.0, total_weight / 4.0, total_weight / 2.0, (total_weight * 3.0) / 4.0, (total_weight * 9.0) / 10.0
};

int64_t next_percentile_index = 0;
int64_t cumulative_weight = 0;
for (const auto& element : scores) {
cumulative_weight += element.second;
while (next_percentile_index < NUM_GETBLOCKSTATS_PERCENTILES && cumulative_weight >= weights[next_percentile_index]) {
result[next_percentile_index] = element.first;
++next_percentile_index;
}
}

// Fill any remaining percentiles with the last value.
for (int64_t i = next_percentile_index; i < NUM_GETBLOCKSTATS_PERCENTILES; i++) {
result[i] = scores.back().first;
}
}

template<typename T>
static inline bool SetHasKeys(const std::set<T>& set) {return false;}
template<typename T, typename Tk, typename... Args>
Expand Down Expand Up @@ -1454,13 +1485,19 @@ static UniValue getblockstats(const JSONRPCRequest& request)
" \"avgfeerate\": xxxxx, (numeric) Average feerate (in satoshis per virtual byte)\n"
" \"avgtxsize\": xxxxx, (numeric) Average transaction size\n"
" \"blockhash\": xxxxx, (string) The block hash (to check for potential reorgs)\n"
" \"feerate_percentiles\": [ (array of numeric) Feerates at the 10th, 25th, 50th, 75th, and 90th percentile weight unit (in satoshis per virtual byte)\n"
" \"10th_percentile_feerate\", (numeric) The 10th percentile feerate\n"
" \"25th_percentile_feerate\", (numeric) The 25th percentile feerate\n"
" \"50th_percentile_feerate\", (numeric) The 50th percentile feerate\n"
" \"75th_percentile_feerate\", (numeric) The 75th percentile feerate\n"
" \"90th_percentile_feerate\", (numeric) The 90th percentile feerate\n"
" ],\n"
" \"height\": xxxxx, (numeric) The height of the block\n"
" \"ins\": xxxxx, (numeric) The number of inputs (excluding coinbase)\n"
" \"maxfee\": xxxxx, (numeric) Maximum fee in the block\n"
" \"maxfeerate\": xxxxx, (numeric) Maximum feerate (in satoshis per virtual byte)\n"
" \"maxtxsize\": xxxxx, (numeric) Maximum transaction size\n"
" \"medianfee\": xxxxx, (numeric) Truncated median fee in the block\n"
" \"medianfeerate\": xxxxx, (numeric) Truncated median feerate (in satoshis per virtual byte)\n"
" \"mediantime\": xxxxx, (numeric) The block median time past\n"
" \"mediantxsize\": xxxxx, (numeric) Truncated median transaction size\n"
" \"minfee\": xxxxx, (numeric) Minimum fee in the block\n"
Expand Down Expand Up @@ -1529,13 +1566,13 @@ static UniValue getblockstats(const JSONRPCRequest& request)
const bool do_all = stats.size() == 0; // Calculate everything if nothing selected (default)
const bool do_mediantxsize = do_all || stats.count("mediantxsize") != 0;
const bool do_medianfee = do_all || stats.count("medianfee") != 0;
const bool do_medianfeerate = do_all || stats.count("medianfeerate") != 0;
const bool do_feerate_percentiles = do_all || stats.count("feerate_percentiles") != 0;
const bool loop_inputs = do_all || stats.count("utxo_size_inc");
const bool loop_outputs = do_all || do_medianfee || do_medianfeerate || loop_inputs ||
const bool loop_outputs = do_all || do_medianfee || do_feerate_percentiles || loop_inputs ||
SetHasKeys(stats, "totalfee", "avgfee", "avgfeerate", "minfee", "maxfee", "minfeerate", "maxfeerate", "total_out");
const bool do_calculate_size = do_mediantxsize ||
SetHasKeys(stats, "total_size", "avgtxsize", "mintxsize", "maxtxsize", "swtotal_size");
const bool do_calculate_weight = do_all || SetHasKeys(stats, "total_weight", "avgfeerate", "swtotal_weight", "avgfeerate", "medianfeerate", "minfeerate", "maxfeerate");
const bool do_calculate_weight = do_all || SetHasKeys(stats, "total_weight", "avgfeerate", "swtotal_weight", "avgfeerate", "feerate_percentiles", "minfeerate", "maxfeerate");
const bool do_calculate_sw = do_all || SetHasKeys(stats, "swtxs", "swtotal_size", "swtotal_weight");

CAmount maxfee = 0;
Expand All @@ -1555,7 +1592,7 @@ static UniValue getblockstats(const JSONRPCRequest& request)
int64_t total_weight = 0;
int64_t utxo_size_inc = 0;
std::vector<CAmount> fee_array;
std::vector<CAmount> feerate_array;
std::vector<std::pair<CAmount, int64_t>> feerate_array;
std::vector<int64_t> txsize_array;

for (const auto& tx : block.vtx) {
Expand Down Expand Up @@ -1642,26 +1679,34 @@ static UniValue getblockstats(const JSONRPCRequest& request)

// New feerate uses satoshis per virtual byte instead of per serialized byte
CAmount feerate = weight ? (txfee * WITNESS_SCALE_FACTOR) / weight : 0;
if (do_medianfeerate) {
feerate_array.push_back(feerate);
if (do_feerate_percentiles) {
feerate_array.emplace_back(std::make_pair(feerate, weight));
}
maxfeerate = std::max(maxfeerate, feerate);
minfeerate = std::min(minfeerate, feerate);
}
}

CAmount feerate_percentiles[NUM_GETBLOCKSTATS_PERCENTILES] = { 0 };
CalculatePercentilesByWeight(feerate_percentiles, feerate_array, total_weight);

UniValue feerates_res(UniValue::VARR);
for (int64_t i = 0; i < NUM_GETBLOCKSTATS_PERCENTILES; i++) {
feerates_res.push_back(feerate_percentiles[i]);
}

UniValue ret_all(UniValue::VOBJ);
ret_all.pushKV("avgfee", (block.vtx.size() > 1) ? totalfee / (block.vtx.size() - 1) : 0);
ret_all.pushKV("avgfeerate", total_weight ? (totalfee * WITNESS_SCALE_FACTOR) / total_weight : 0); // Unit: sat/vbyte
ret_all.pushKV("avgtxsize", (block.vtx.size() > 1) ? total_size / (block.vtx.size() - 1) : 0);
ret_all.pushKV("blockhash", pindex->GetBlockHash().GetHex());
ret_all.pushKV("feerate_percentiles", feerates_res);
ret_all.pushKV("height", (int64_t)pindex->nHeight);
ret_all.pushKV("ins", inputs);
ret_all.pushKV("maxfee", maxfee);
ret_all.pushKV("maxfeerate", maxfeerate);
ret_all.pushKV("maxtxsize", maxtxsize);
ret_all.pushKV("medianfee", CalculateTruncatedMedian(fee_array));
ret_all.pushKV("medianfeerate", CalculateTruncatedMedian(feerate_array));
ret_all.pushKV("mediantime", pindex->GetMedianTimePast());
ret_all.pushKV("mediantxsize", CalculateTruncatedMedian(txsize_array));
ret_all.pushKV("minfee", (minfee == MAX_MONEY) ? 0 : minfee);
Expand Down
46 changes: 46 additions & 0 deletions src/rpc/blockchain.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
// Copyright (c) 2017 The Bitcoin Core developers
// Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.

#ifndef BITCOIN_RPC_BLOCKCHAIN_H
#define BITCOIN_RPC_BLOCKCHAIN_H

#include <vector>
#include <stdint.h>
#include <amount.h>

class CBlock;
class CBlockIndex;
class UniValue;

static constexpr int NUM_GETBLOCKSTATS_PERCENTILES = 5;

/**
* Get the difficulty of the net wrt to the given block index, or the chain tip if
* not provided.
*
* @return A floating point number that is a multiple of the main net minimum
* difficulty (4295032833 hashes).
*/
double GetDifficulty(const CBlockIndex* blockindex = nullptr);

/** Callback for when block tip changed. */
void RPCNotifyBlockChange(bool ibd, const CBlockIndex *);

/** Block description to JSON */
UniValue blockToJSON(const CBlock& block, const CBlockIndex* blockindex, bool txDetails = false);

/** Mempool information to JSON */
UniValue mempoolInfoToJSON();

/** Mempool to JSON */
UniValue mempoolToJSON(bool fVerbose = false);

/** Block header to JSON */
UniValue blockheaderToJSON(const CBlockIndex* blockindex);

/** Used by getblockstats to get feerates at different percentiles by weight */
void CalculatePercentilesByWeight(CAmount result[NUM_GETBLOCKSTATS_PERCENTILES], std::vector<std::pair<CAmount, int64_t>>& scores, int64_t total_weight);

#endif

81 changes: 81 additions & 0 deletions src/test/rpc_tests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@

#include <univalue.h>

#include <rpc/blockchain.h>

UniValue CallRPC(std::string args)
{
std::vector<std::string> vArgs;
Expand Down Expand Up @@ -355,4 +357,83 @@ BOOST_AUTO_TEST_CASE(rpc_convert_values_generatetoaddress)
BOOST_CHECK_EQUAL(result[2].get_int(), 9);
}
*/

BOOST_AUTO_TEST_CASE(rpc_getblockstats_calculate_percentiles_by_weight)
{
int64_t total_weight = 200;
std::vector<std::pair<CAmount, int64_t>> feerates;
CAmount result[NUM_GETBLOCKSTATS_PERCENTILES] = { 0 };

for (int64_t i = 0; i < 100; i++) {
feerates.emplace_back(std::make_pair(1 ,1));
}

for (int64_t i = 0; i < 100; i++) {
feerates.emplace_back(std::make_pair(2 ,1));
}

CalculatePercentilesByWeight(result, feerates, total_weight);
BOOST_CHECK_EQUAL(result[0], 1);
BOOST_CHECK_EQUAL(result[1], 1);
BOOST_CHECK_EQUAL(result[2], 1);
BOOST_CHECK_EQUAL(result[3], 2);
BOOST_CHECK_EQUAL(result[4], 2);

// Test with more pairs, and two pairs overlapping 2 percentiles.
total_weight = 100;
CAmount result2[NUM_GETBLOCKSTATS_PERCENTILES] = { 0 };
feerates.clear();

feerates.emplace_back(std::make_pair(1, 9));
feerates.emplace_back(std::make_pair(2 , 16)); //10th + 25th percentile
feerates.emplace_back(std::make_pair(4 ,50)); //50th + 75th percentile
feerates.emplace_back(std::make_pair(5 ,10));
feerates.emplace_back(std::make_pair(9 ,15)); // 90th percentile

CalculatePercentilesByWeight(result2, feerates, total_weight);

BOOST_CHECK_EQUAL(result2[0], 2);
BOOST_CHECK_EQUAL(result2[1], 2);
BOOST_CHECK_EQUAL(result2[2], 4);
BOOST_CHECK_EQUAL(result2[3], 4);
BOOST_CHECK_EQUAL(result2[4], 9);

// Same test as above, but one of the percentile-overlapping pairs is split in 2.
total_weight = 100;
CAmount result3[NUM_GETBLOCKSTATS_PERCENTILES] = { 0 };
feerates.clear();

feerates.emplace_back(std::make_pair(1, 9));
feerates.emplace_back(std::make_pair(2 , 11)); // 10th percentile
feerates.emplace_back(std::make_pair(2 , 5)); // 25th percentile
feerates.emplace_back(std::make_pair(4 ,50)); //50th + 75th percentile
feerates.emplace_back(std::make_pair(5 ,10));
feerates.emplace_back(std::make_pair(9 ,15)); // 90th percentile

CalculatePercentilesByWeight(result3, feerates, total_weight);

BOOST_CHECK_EQUAL(result3[0], 2);
BOOST_CHECK_EQUAL(result3[1], 2);
BOOST_CHECK_EQUAL(result3[2], 4);
BOOST_CHECK_EQUAL(result3[3], 4);
BOOST_CHECK_EQUAL(result3[4], 9);

// Test with one transaction spanning all percentiles.
total_weight = 104;
CAmount result4[NUM_GETBLOCKSTATS_PERCENTILES] = { 0 };
feerates.clear();

feerates.emplace_back(std::make_pair(1, 100));
feerates.emplace_back(std::make_pair(2, 1));
feerates.emplace_back(std::make_pair(3, 1));
feerates.emplace_back(std::make_pair(3, 1));
feerates.emplace_back(std::make_pair(999999, 1));

CalculatePercentilesByWeight(result4, feerates, total_weight);

for (int64_t i = 0; i < NUM_GETBLOCKSTATS_PERCENTILES; i++) {
BOOST_CHECK_EQUAL(result4[i], 1);
}
}

BOOST_AUTO_TEST_SUITE_END()
Loading