Skip to content
Closed
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
11 changes: 11 additions & 0 deletions src/primitives/transaction.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -201,6 +201,17 @@ CAmountMap CTransaction::GetFee() const
return fee;
}

CAmount CTransaction::GetFee(const CAsset& asset) const
{
CAmount fee = 0;
for (unsigned int i = 0; i < vout.size(); i++) {
if (vout[i].IsFee() && vout[i].nAsset.GetAsset() == asset) {
fee += vout[i].nValue.GetAmount();
}
}
return fee;
}

CTransaction::CTransaction() : nVersion(CTransaction::CURRENT_VERSION), vin(), vout(), nLockTime(0) { }

CTransaction::CTransaction(const CMutableTransaction &tx) : nVersion(tx.nVersion), vin(tx.vin), vout(tx.vout), wit(tx.wit), nLockTime(tx.nLockTime) {
Expand Down
1 change: 1 addition & 0 deletions src/primitives/transaction.h
Original file line number Diff line number Diff line change
Expand Up @@ -763,6 +763,7 @@ class CTransaction

// Compute the fee from the explicit fee outputs. Must call HasValidFee first
CAmountMap GetFee() const;
CAmount GetFee(const CAsset& asset) const;

// Compute priority, given priority of inputs and (optionally) tx size
double ComputePriority(double dPriorityInputs, unsigned int nTxSize=0) const;
Expand Down
170 changes: 164 additions & 6 deletions src/rpc/blockchain.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@

#include <univalue.h>

#include <boost/algorithm/string.hpp>
#include <boost/thread/thread.hpp> // boost::thread::interrupt

using namespace std;
Expand Down Expand Up @@ -527,6 +528,15 @@ UniValue getblockheader(const UniValue& params, bool fHelp)
return blockheaderToJSON(pblockindex);
}

static void ReadBlockCheckPruned(CBlockIndex* pblockindex, CBlock& block)
{
if (fHavePruned && !(pblockindex->nStatus & BLOCK_HAVE_DATA) && pblockindex->nTx > 0)
throw JSONRPCError(RPC_INTERNAL_ERROR, "Block not available (pruned data)");

if(!ReadBlockFromDisk(block, pblockindex, Params().GetConsensus()))
throw JSONRPCError(RPC_INTERNAL_ERROR, "Can't read block from disk");
}

UniValue getblock(const UniValue& params, bool fHelp)
{
if (fHelp || params.size() < 1 || params.size() > 2)
Expand Down Expand Up @@ -582,12 +592,7 @@ UniValue getblock(const UniValue& params, bool fHelp)

CBlock block;
CBlockIndex* pblockindex = mapBlockIndex[hash];

if (fHavePruned && !(pblockindex->nStatus & BLOCK_HAVE_DATA) && pblockindex->nTx > 0)
throw JSONRPCError(RPC_INTERNAL_ERROR, "Block not available (pruned data)");

if(!ReadBlockFromDisk(block, pblockindex, Params().GetConsensus()))
throw JSONRPCError(RPC_INTERNAL_ERROR, "Can't read block from disk");
ReadBlockCheckPruned(pblockindex, block);

if (!fVerbose)
{
Expand Down Expand Up @@ -1137,6 +1142,158 @@ UniValue reconsiderblock(const UniValue& params, bool fHelp)
return NullUniValue;
}

static void UpdateBlockStats(const CBlock& block, const CAsset& asset, std::vector<std::string>& plot_values, std::map<std::string, UniValue>& map_stats)
{
CAmount min = MAX_MONEY;
CAmount max = 0;
CAmount total = 0;
CFeeRate minfeerate = CFeeRate(MAX_MONEY, 1);
CFeeRate maxfeerate = CFeeRate(0);
int64_t total_weight = 0;

for (const CTransaction& tx : block.vtx) {
if (tx.IsCoinBase()) {
continue;
}

CAmount txfee = tx.GetFee(asset);
int64_t weight = GetTransactionWeight(tx);
total_weight += weight;
CFeeRate feerate = CFeeRate(txfee, weight);
for (const std::string& plot_value : plot_values) {
if (plot_value == "minfee") {
min = std::min(min, txfee);
} else if (plot_value == "maxfee") {
max = std::max(max, txfee);
} else if (plot_value == "totalfee") {
total += txfee;
} else if (plot_value == "minfeerate") {
minfeerate = std::min(minfeerate, feerate);
} else if (plot_value == "maxfeerate") {
maxfeerate = std::min(maxfeerate, feerate);
}
}
}

for (const std::string& plot_value : plot_values) {
// Update map_stats
if (plot_value == "minfee") {
map_stats[plot_value].push_back((min == MAX_MONEY) ? 0 : min);
} else if (plot_value == "maxfee") {
map_stats[plot_value].push_back(max);
} else if (plot_value == "totalfee") {
map_stats[plot_value].push_back(total);
} else if (plot_value == "minfeerate") {
map_stats[plot_value].push_back((minfeerate == CFeeRate(MAX_MONEY, 1)) ? CFeeRate(0).GetFee(1) : minfeerate.GetFee(1));
} else if (plot_value == "maxfeerate") {
map_stats[plot_value].push_back(maxfeerate.GetFee(1));
} else if (plot_value == "avgfee") {
map_stats[plot_value].push_back((block.vtx.size() > 1) ? total / (block.vtx.size() - 1) : 0);
} else if (plot_value == "avgfeeperweightunit") {
map_stats[plot_value].push_back(CFeeRate(total, total_weight).GetFee(1));
} else if (plot_value == "avgfeerate") {
map_stats[plot_value].push_back((block.vtx.size() > 1) ? CFeeRate(total, total_weight).GetFee(1) / (block.vtx.size() - 1) : 0);
}
}
}

static bool IsAllowedPlotValue(const std::string& plot_value, std::vector<std::string>& allowed_plot_values)
{
for (const std::string& allowed_plot_value : allowed_plot_values) {
if (allowed_plot_value == plot_value) {
return true;
}
}
return false;
}

UniValue getperblockstats(const UniValue& params, bool fHelp)
{
std::string str_allowed_plot_values = "minfee,maxfee,totalfee,minfeerate,maxfeerate,avgfee,avgfeeperweightunit,avgfeerate";
std::vector<std::string> allowed_plot_values;
boost::split(allowed_plot_values, str_allowed_plot_values, boost::is_any_of(","));

if (fHelp || params.size() > 2)
throw std::runtime_error(
"getperblockstats ( nStart nEnd plotValues )\n"
"\nCompute per block statistics for a given window.\n"
"\nArguments:\n"
"1. \"nStart\" (numeric, required) The height of the block that starts the window.\n"
"2. \"nEnd\" (numeric, optional) The height of the block that ends the window (default: current tip).\n"
"3. \"plotValues\" (string, optional) Values to plot (comma separated), default(all): " + str_allowed_plot_values +
"4. \"asset\" (string, optional) Hex asset id or asset label for fees.\n"
"\nResult:\n"
"{\n"
"}\n"
"\nExamples:\n"
+ HelpExampleCli("getperblockstats", "1000 1000 \"minfeerate,avgfeerate\"")
+ HelpExampleRpc("getperblockstats", "1000 1000 \"maxfeerate,avgfeerate\"")
);

LOCK(cs_main);

int start = params[0].get_int();
if (start < 1 || start > chainActive.Height()) {
throw JSONRPCError(RPC_INVALID_PARAMETER, "Start block height out of range");
}

int end;
if (params.size() > 1) {
end = params[1].get_int();
} else {
end = chainActive.Height();
}
if (end < 0 || end > chainActive.Height()) {
throw JSONRPCError(RPC_INVALID_PARAMETER, "End block height out of range");
}

std::string str_plot_values = str_allowed_plot_values;
if (params.size() > 2) {
str_plot_values = params[1].get_str();
}
std::vector<std::string> plot_values;
boost::split(plot_values, str_plot_values, boost::is_any_of(","));
for (const std::string plot_value : plot_values) {
if (!IsAllowedPlotValue(plot_value, allowed_plot_values)) {
throw JSONRPCError(RPC_INVALID_PARAMETER, strprintf("Invalid plot value %s", plot_value));
}
}

// TODO use GetAssetFromString() to leverage gAssetsDir (ie support labels, rpc doc is lying)
CAsset asset = policyAsset;
std::string strasset = "";
if (params.size() > 3) {
strasset = params[3].get_str();
asset = CAsset(uint256S(strasset));
}

std::map<std::string, UniValue> map_stats;
for (const std::string& allowed_plot_value : allowed_plot_values) {
map_stats[allowed_plot_value] = UniValue(UniValue::VARR);
}
map_stats["height"] = UniValue(UniValue::VARR);
map_stats["time"] = UniValue(UniValue::VARR);

CBlockIndex* pindex = chainActive[end];
CBlock block;
for (int i = end; i >= start; i--) {
ReadBlockCheckPruned(pindex, block);
UpdateBlockStats(block, asset, plot_values, map_stats);
// For the X axis:
map_stats["height"].push_back(i);
map_stats["time"].push_back(block.GetBlockTime());
pindex = pindex->pprev;
}

UniValue ret(UniValue::VOBJ);
for (const std::string plot_value : plot_values) {
ret.push_back(Pair(plot_value, map_stats[plot_value]));
}
ret.push_back(Pair("height", map_stats["height"]));
ret.push_back(Pair("time", map_stats["time"]));
return ret;
}

static const CRPCCommand commands[] =
{ // category name actor (function) okSafeMode
// --------------------- ------------------------ ----------------------- ----------
Expand All @@ -1156,6 +1313,7 @@ static const CRPCCommand commands[] =
{ "blockchain", "gettxout", &gettxout, true },
{ "blockchain", "gettxoutsetinfo", &gettxoutsetinfo, true },
{ "blockchain", "verifychain", &verifychain, true },
{ "blockchain", "getperblockstats", &getperblockstats, true },

/* Not shown in help */
{ "hidden", "invalidateblock", &invalidateblock, true },
Expand Down