Replace median fee rate with feerate percentiles
Removes medianfeerate result from getblockstats. Adds feerate_percentiles which give the feerate of the 10th, 25th, 50th, 75th, and 90th percentile weight unit in the block.
This commit is contained in:
parent
df9f712746
commit
4b7091a842
5 changed files with 162 additions and 12 deletions
|
@ -1640,6 +1640,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>
|
||||
|
@ -1673,13 +1702,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"
|
||||
|
@ -1747,13 +1782,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 loop_inputs = do_all || do_medianfee || do_medianfeerate ||
|
||||
const bool do_feerate_percentiles = do_all || stats.count("feerate_percentiles") != 0;
|
||||
const bool loop_inputs = do_all || do_medianfee || do_feerate_percentiles ||
|
||||
SetHasKeys(stats, "utxo_size_inc", "totalfee", "avgfee", "avgfeerate", "minfee", "maxfee", "minfeerate", "maxfeerate");
|
||||
const bool loop_outputs = do_all || loop_inputs || stats.count("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;
|
||||
|
@ -1773,7 +1808,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) {
|
||||
|
@ -1848,26 +1883,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);
|
||||
|
|
|
@ -5,10 +5,16 @@
|
|||
#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.
|
||||
|
@ -33,4 +39,7 @@ 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
|
||||
|
|
|
@ -16,6 +16,8 @@
|
|||
|
||||
#include <univalue.h>
|
||||
|
||||
#include <rpc/blockchain.h>
|
||||
|
||||
UniValue CallRPC(std::string args)
|
||||
{
|
||||
std::vector<std::string> vArgs;
|
||||
|
@ -336,4 +338,82 @@ 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()
|
||||
|
|
|
@ -112,13 +112,19 @@
|
|||
"avgfeerate": 0,
|
||||
"avgtxsize": 0,
|
||||
"blockhash": "1d7fe80f19d28b8e712af0399ac84006db753441f3033111b3a8d610afab364f",
|
||||
"feerate_percentiles": [
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0
|
||||
],
|
||||
"height": 101,
|
||||
"ins": 0,
|
||||
"maxfee": 0,
|
||||
"maxfeerate": 0,
|
||||
"maxtxsize": 0,
|
||||
"medianfee": 0,
|
||||
"medianfeerate": 0,
|
||||
"mediantime": 1525107242,
|
||||
"mediantxsize": 0,
|
||||
"minfee": 0,
|
||||
|
@ -144,12 +150,18 @@
|
|||
"avgtxsize": 187,
|
||||
"blockhash": "4e21a43675d7a41cb6b944e068c5bcd0a677baf658d9ebe021ae2d2f99397ccc",
|
||||
"height": 102,
|
||||
"feerate_percentiles": [
|
||||
20,
|
||||
20,
|
||||
20,
|
||||
20,
|
||||
20
|
||||
],
|
||||
"ins": 1,
|
||||
"maxfee": 3760,
|
||||
"maxfeerate": 20,
|
||||
"maxtxsize": 187,
|
||||
"medianfee": 3760,
|
||||
"medianfeerate": 20,
|
||||
"mediantime": 1525107242,
|
||||
"mediantxsize": 187,
|
||||
"minfee": 3760,
|
||||
|
@ -174,13 +186,19 @@
|
|||
"avgfeerate": 109,
|
||||
"avgtxsize": 228,
|
||||
"blockhash": "22d9b8b9c2a37c81515f3fc84f7241f6c07dbcea85ef16b00bcc33ae400a030f",
|
||||
"feerate_percentiles": [
|
||||
20,
|
||||
20,
|
||||
20,
|
||||
300,
|
||||
300
|
||||
],
|
||||
"height": 103,
|
||||
"ins": 3,
|
||||
"maxfee": 49800,
|
||||
"maxfeerate": 300,
|
||||
"maxtxsize": 248,
|
||||
"medianfee": 3760,
|
||||
"medianfeerate": 20,
|
||||
"mediantime": 1525107243,
|
||||
"mediantxsize": 248,
|
||||
"minfee": 3320,
|
||||
|
|
|
@ -27,7 +27,7 @@ class GetblockstatsTest(BitcoinTestFramework):
|
|||
'maxfee',
|
||||
'maxfeerate',
|
||||
'medianfee',
|
||||
'medianfeerate',
|
||||
'feerate_percentiles',
|
||||
'minfee',
|
||||
'minfeerate',
|
||||
'totalfee',
|
||||
|
|
Loading…
Reference in a new issue