Changeset View
Changeset View
Standalone View
Standalone View
src/rpc/blockchain.cpp
// Copyright (c) 2010 Satoshi Nakamoto | // Copyright (c) 2010 Satoshi Nakamoto | ||||
// Copyright (c) 2009-2016 The Bitcoin Core developers | // Copyright (c) 2009-2016 The Bitcoin Core developers | ||||
// Distributed under the MIT software license, see the accompanying | // Distributed under the MIT software license, see the accompanying | ||||
// file COPYING or http://www.opensource.org/licenses/mit-license.php. | // file COPYING or http://www.opensource.org/licenses/mit-license.php. | ||||
#include <rpc/blockchain.h> | #include <rpc/blockchain.h> | ||||
#include <amount.h> | #include <amount.h> | ||||
#include <chain.h> | #include <chain.h> | ||||
#include <chainparams.h> | #include <chainparams.h> | ||||
#include <checkpoints.h> | #include <checkpoints.h> | ||||
#include <coins.h> | #include <coins.h> | ||||
#include <config.h> | #include <config.h> | ||||
#include <consensus/validation.h> | #include <consensus/validation.h> | ||||
#include <core_io.h> | #include <core_io.h> | ||||
#include <hash.h> | #include <hash.h> | ||||
#include <index/txindex.h> | |||||
#include <policy/policy.h> | #include <policy/policy.h> | ||||
#include <primitives/transaction.h> | #include <primitives/transaction.h> | ||||
#include <rpc/server.h> | #include <rpc/server.h> | ||||
#include <streams.h> | #include <streams.h> | ||||
#include <sync.h> | #include <sync.h> | ||||
#include <txdb.h> | #include <txdb.h> | ||||
#include <txmempool.h> | #include <txmempool.h> | ||||
#include <util.h> | #include <util.h> | ||||
#include <utilstrencodings.h> | #include <utilstrencodings.h> | ||||
#include <validation.h> | #include <validation.h> | ||||
#include <validationinterface.h> | #include <validationinterface.h> | ||||
#include <warnings.h> | #include <warnings.h> | ||||
#include <boost/algorithm/string.hpp> | |||||
#include <boost/thread/thread.hpp> // boost::thread::interrupt | #include <boost/thread/thread.hpp> // boost::thread::interrupt | ||||
#include <condition_variable> | #include <condition_variable> | ||||
#include <cstdint> | #include <cstdint> | ||||
#include <mutex> | #include <mutex> | ||||
struct CUpdatedBlock { | struct CUpdatedBlock { | ||||
uint256 hash; | uint256 hash; | ||||
▲ Show 20 Lines • Show All 1,856 Lines • ▼ Show 20 Lines | if (blockcount > 0) { | ||||
if (nTimeDiff > 0) { | if (nTimeDiff > 0) { | ||||
ret.pushKV("txrate", double(nTxDiff) / nTimeDiff); | ret.pushKV("txrate", double(nTxDiff) / nTimeDiff); | ||||
} | } | ||||
} | } | ||||
return ret; | return ret; | ||||
} | } | ||||
template <typename T> | |||||
static T CalculateTruncatedMedian(std::vector<T> &scores) { | |||||
size_t size = scores.size(); | |||||
if (size == 0) { | |||||
return T(); | |||||
} | |||||
std::sort(scores.begin(), scores.end()); | |||||
if (size % 2 == 0) { | |||||
return (scores[size / 2 - 1] + scores[size / 2]) / 2; | |||||
} else { | |||||
return scores[size / 2]; | |||||
} | |||||
} | |||||
template <typename T> static inline bool SetHasKeys(const std::set<T> &set) { | |||||
return false; | |||||
} | |||||
template <typename T, typename Tk, typename... Args> | |||||
static inline bool SetHasKeys(const std::set<T> &set, const Tk &key, | |||||
const Args &... args) { | |||||
return (set.count(key) != 0) || SetHasKeys(set, args...); | |||||
} | |||||
// outpoint (needed for the utxo index) + nHeight + fCoinBase | |||||
static constexpr size_t PER_UTXO_OVERHEAD = | |||||
sizeof(COutPoint) + sizeof(uint32_t) + sizeof(bool); | |||||
static UniValue getblockstats(const Config &config, | |||||
const JSONRPCRequest &request) { | |||||
if (request.fHelp || request.params.size() < 1 || | |||||
request.params.size() > 4) { | |||||
throw std::runtime_error( | |||||
"getblockstats hash_or_height ( stats )\n" | |||||
"\nCompute per block statistics for a given window. All amounts " | |||||
"are in " + | |||||
CURRENCY_UNIT + | |||||
".\n" | |||||
"It won't work for some heights with pruning.\n" | |||||
"It won't work without -txindex for utxo_size_inc, *fee or " | |||||
"*feerate stats.\n" | |||||
"\nArguments:\n" | |||||
"1. \"hash_or_height\" (string or numeric, required) The block " | |||||
"hash or height of the target block\n" | |||||
"2. \"stats\" (array, optional) Values to plot, by " | |||||
"default all values (see result below)\n" | |||||
" [\n" | |||||
" \"height\", (string, optional) Selected statistic\n" | |||||
" \"time\", (string, optional) Selected statistic\n" | |||||
" ,...\n" | |||||
" ]\n" | |||||
"\nResult:\n" | |||||
"{ (json object)\n" | |||||
" \"avgfee\": x.xxx, (numeric) Average fee in the block\n" | |||||
" \"avgfeerate\": x.xxx, (numeric) Average feerate (in " + | |||||
CURRENCY_UNIT + | |||||
" per byte)\n" | |||||
" \"avgtxsize\": xxxxx, (numeric) Average transaction size\n" | |||||
" \"blockhash\": xxxxx, (string) The block hash (to check " | |||||
"for potential reorgs)\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 " + | |||||
CURRENCY_UNIT + | |||||
" per byte)\n" | |||||
" \"maxtxsize\": xxxxx, (numeric) Maximum transaction size\n" | |||||
" \"medianfee\": x.xxx, (numeric) Truncated median fee in " | |||||
"the block\n" | |||||
" \"medianfeerate\": x.xxx, (numeric) Truncated median feerate " | |||||
"(in " + | |||||
CURRENCY_UNIT + | |||||
" per byte)\n" | |||||
" \"mediantime\": xxxxx, (numeric) The block median time " | |||||
"past\n" | |||||
" \"mediantxsize\": xxxxx, (numeric) Truncated median " | |||||
"transaction size\n" | |||||
" \"minfee\": x.xxx, (numeric) Minimum fee in the block\n" | |||||
" \"minfeerate\": xx.xx, (numeric) Minimum feerate (in " + | |||||
CURRENCY_UNIT + | |||||
" per byte)\n" | |||||
" \"mintxsize\": xxxxx, (numeric) Minimum transaction size\n" | |||||
" \"outs\": xxxxx, (numeric) The number of outputs\n" | |||||
" \"subsidy\": x.xxx, (numeric) The block subsidy\n" | |||||
" \"time\": xxxxx, (numeric) The block time\n" | |||||
" \"total_out\": x.xxx, (numeric) Total amount in all " | |||||
"outputs (excluding coinbase and thus reward [ie subsidy + " | |||||
"totalfee])\n" | |||||
" \"total_size\": xxxxx, (numeric) Total size of all " | |||||
"non-coinbase transactions\n" | |||||
" \"totalfee\": x.xxx, (numeric) The fee total\n" | |||||
" \"txs\": xxxxx, (numeric) The number of " | |||||
"transactions (excluding coinbase)\n" | |||||
" \"utxo_increase\": xxxxx, (numeric) The increase/decrease in " | |||||
"the number of unspent outputs\n" | |||||
" \"utxo_size_inc\": xxxxx, (numeric) The increase/decrease in " | |||||
"size for the utxo index (not discounting op_return and similar)\n" | |||||
"}\n" | |||||
"\nExamples:\n" + | |||||
HelpExampleCli("getblockstats", | |||||
"1000 '[\"minfeerate\",\"avgfeerate\"]'") + | |||||
HelpExampleRpc("getblockstats", | |||||
"1000 '[\"minfeerate\",\"avgfeerate\"]'")); | |||||
} | |||||
LOCK(cs_main); | |||||
CBlockIndex *pindex; | |||||
if (request.params[0].isNum()) { | |||||
const int height = request.params[0].get_int(); | |||||
const int current_tip = chainActive.Height(); | |||||
if (height < 0) { | |||||
throw JSONRPCError( | |||||
RPC_INVALID_PARAMETER, | |||||
strprintf("Target block height %d is negative", height)); | |||||
} | |||||
if (height > current_tip) { | |||||
throw JSONRPCError( | |||||
RPC_INVALID_PARAMETER, | |||||
strprintf("Target block height %d after current tip %d", height, | |||||
current_tip)); | |||||
} | |||||
pindex = chainActive[height]; | |||||
} else { | |||||
const std::string strHash = request.params[0].get_str(); | |||||
const uint256 hash(uint256S(strHash)); | |||||
pindex = LookupBlockIndex(hash); | |||||
if (!pindex) { | |||||
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Block not found"); | |||||
} | |||||
if (!chainActive.Contains(pindex)) { | |||||
throw JSONRPCError(RPC_INVALID_PARAMETER, | |||||
strprintf("Block is not in chain %s", | |||||
Params().NetworkIDString())); | |||||
} | |||||
} | |||||
assert(pindex != nullptr); | |||||
std::set<std::string> stats; | |||||
if (!request.params[1].isNull()) { | |||||
const UniValue stats_univalue = request.params[1].get_array(); | |||||
for (unsigned int i = 0; i < stats_univalue.size(); i++) { | |||||
const std::string stat = stats_univalue[i].get_str(); | |||||
stats.insert(stat); | |||||
} | |||||
} | |||||
const CBlock block = GetBlockChecked(config, pindex); | |||||
// Calculate everything if nothing selected (default) | |||||
const bool do_all = stats.size() == 0; | |||||
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 || | |||||
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 || loop_inputs || | |||||
SetHasKeys(stats, "total_size", "avgtxsize", "mintxsize", "maxtxsize"); | |||||
const int64_t blockMaxSize = config.GetMaxBlockSize(); | |||||
Amount maxfee = Amount::zero(); | |||||
Amount maxfeerate = Amount::zero(); | |||||
Amount minfee = MAX_MONEY; | |||||
Amount minfeerate = MAX_MONEY; | |||||
Amount total_out = Amount::zero(); | |||||
Amount totalfee = Amount::zero(); | |||||
int64_t inputs = 0; | |||||
int64_t maxtxsize = 0; | |||||
int64_t mintxsize = blockMaxSize; | |||||
int64_t outputs = 0; | |||||
int64_t total_size = 0; | |||||
int64_t utxo_size_inc = 0; | |||||
std::vector<Amount> fee_array; | |||||
std::vector<Amount> feerate_array; | |||||
std::vector<int64_t> txsize_array; | |||||
for (const auto &tx : block.vtx) { | |||||
outputs += tx->vout.size(); | |||||
Amount tx_total_out = Amount::zero(); | |||||
if (loop_outputs) { | |||||
for (const CTxOut &out : tx->vout) { | |||||
tx_total_out += out.nValue; | |||||
utxo_size_inc += | |||||
GetSerializeSize(out, SER_NETWORK, PROTOCOL_VERSION) + | |||||
PER_UTXO_OVERHEAD; | |||||
} | |||||
} | |||||
if (tx->IsCoinBase()) { | |||||
continue; | |||||
} | |||||
// Don't count coinbase's fake input | |||||
inputs += tx->vin.size(); | |||||
// Don't count coinbase reward | |||||
total_out += tx_total_out; | |||||
int64_t tx_size = 0; | |||||
if (do_calculate_size) { | |||||
tx_size = tx->GetTotalSize(); | |||||
if (do_mediantxsize) { | |||||
txsize_array.push_back(tx_size); | |||||
} | |||||
maxtxsize = std::max(maxtxsize, tx_size); | |||||
mintxsize = std::min(mintxsize, tx_size); | |||||
total_size += tx_size; | |||||
} | |||||
if (loop_inputs) { | |||||
if (!g_txindex) { | |||||
throw JSONRPCError(RPC_INVALID_PARAMETER, | |||||
"One or more of the selected stats requires " | |||||
"-txindex enabled"); | |||||
} | |||||
Amount tx_total_in = Amount::zero(); | |||||
for (const CTxIn &in : tx->vin) { | |||||
CTransactionRef tx_in; | |||||
uint256 hashBlock; | |||||
if (!GetTransaction(config, in.prevout.GetTxId(), tx_in, | |||||
hashBlock, false)) { | |||||
throw JSONRPCError(RPC_INTERNAL_ERROR, | |||||
std::string("Unexpected internal error " | |||||
"(tx index seems corrupt)")); | |||||
} | |||||
CTxOut prevoutput = tx_in->vout[in.prevout.GetN()]; | |||||
tx_total_in += prevoutput.nValue; | |||||
utxo_size_inc -= GetSerializeSize(prevoutput, SER_NETWORK, | |||||
PROTOCOL_VERSION) + | |||||
PER_UTXO_OVERHEAD; | |||||
} | |||||
Amount txfee = tx_total_in - tx_total_out; | |||||
assert(MoneyRange(txfee)); | |||||
if (do_medianfee) { | |||||
fee_array.push_back(txfee); | |||||
} | |||||
maxfee = std::max(maxfee, txfee); | |||||
minfee = std::min(minfee, txfee); | |||||
totalfee += txfee; | |||||
Amount feerate = txfee / tx_size; | |||||
if (do_medianfeerate) { | |||||
feerate_array.push_back(feerate); | |||||
} | |||||
maxfeerate = std::max(maxfeerate, feerate); | |||||
minfeerate = std::min(minfeerate, feerate); | |||||
} | |||||
} | |||||
UniValue ret_all(UniValue::VOBJ); | |||||
ret_all.pushKV("avgfee", | |||||
ValueFromAmount((block.vtx.size() > 1) | |||||
? totalfee / int((block.vtx.size() - 1)) | |||||
: Amount::zero())); | |||||
ret_all.pushKV("avgfeerate", | |||||
ValueFromAmount((total_size > 0) ? totalfee / total_size | |||||
: Amount::zero())); | |||||
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("height", (int64_t)pindex->nHeight); | |||||
ret_all.pushKV("ins", inputs); | |||||
ret_all.pushKV("maxfee", ValueFromAmount(maxfee)); | |||||
ret_all.pushKV("maxfeerate", ValueFromAmount(maxfeerate)); | |||||
ret_all.pushKV("maxtxsize", maxtxsize); | |||||
ret_all.pushKV("medianfee", | |||||
ValueFromAmount(CalculateTruncatedMedian(fee_array))); | |||||
ret_all.pushKV("medianfeerate", | |||||
ValueFromAmount(CalculateTruncatedMedian(feerate_array))); | |||||
ret_all.pushKV("mediantime", pindex->GetMedianTimePast()); | |||||
ret_all.pushKV("mediantxsize", CalculateTruncatedMedian(txsize_array)); | |||||
ret_all.pushKV( | |||||
"minfee", | |||||
ValueFromAmount((minfee == MAX_MONEY) ? Amount::zero() : minfee)); | |||||
ret_all.pushKV("minfeerate", | |||||
ValueFromAmount((minfeerate == MAX_MONEY) ? Amount::zero() | |||||
: minfeerate)); | |||||
ret_all.pushKV("mintxsize", mintxsize == blockMaxSize ? 0 : mintxsize); | |||||
ret_all.pushKV("outs", outputs); | |||||
ret_all.pushKV("subsidy", ValueFromAmount(GetBlockSubsidy( | |||||
pindex->nHeight, Params().GetConsensus()))); | |||||
ret_all.pushKV("time", pindex->GetBlockTime()); | |||||
ret_all.pushKV("total_out", ValueFromAmount(total_out)); | |||||
ret_all.pushKV("total_size", total_size); | |||||
ret_all.pushKV("totalfee", ValueFromAmount(totalfee)); | |||||
ret_all.pushKV("txs", (int64_t)block.vtx.size()); | |||||
ret_all.pushKV("utxo_increase", outputs - inputs); | |||||
ret_all.pushKV("utxo_size_inc", utxo_size_inc); | |||||
if (do_all) { | |||||
return ret_all; | |||||
} | |||||
UniValue ret(UniValue::VOBJ); | |||||
for (const std::string &stat : stats) { | |||||
const UniValue &value = ret_all[stat]; | |||||
if (value.isNull()) { | |||||
throw JSONRPCError( | |||||
RPC_INVALID_PARAMETER, | |||||
strprintf("Invalid selected statistic %s", stat)); | |||||
} | |||||
ret.pushKV(stat, value); | |||||
} | |||||
return ret; | |||||
} | |||||
static UniValue savemempool(const Config &config, | static UniValue savemempool(const Config &config, | ||||
const JSONRPCRequest &request) { | const JSONRPCRequest &request) { | ||||
if (request.fHelp || request.params.size() != 0) { | if (request.fHelp || request.params.size() != 0) { | ||||
throw std::runtime_error("savemempool\n" | throw std::runtime_error("savemempool\n" | ||||
"\nDumps the mempool to disk. It will fail " | "\nDumps the mempool to disk. It will fail " | ||||
"until the previous dump is fully loaded.\n" | "until the previous dump is fully loaded.\n" | ||||
"\nExamples:\n" + | "\nExamples:\n" + | ||||
HelpExampleCli("savemempool", "") + | HelpExampleCli("savemempool", "") + | ||||
Show All 16 Lines | static const ContextFreeRPCCommand commands[] = { | ||||
// category name actor (function) argNames | // category name actor (function) argNames | ||||
// ------------------- ------------------------ ---------------------- ---------- | // ------------------- ------------------------ ---------------------- ---------- | ||||
{ "blockchain", "getbestblockhash", getbestblockhash, {} }, | { "blockchain", "getbestblockhash", getbestblockhash, {} }, | ||||
{ "blockchain", "getblock", getblock, {"blockhash","verbosity|verbose"} }, | { "blockchain", "getblock", getblock, {"blockhash","verbosity|verbose"} }, | ||||
{ "blockchain", "getblockchaininfo", getblockchaininfo, {} }, | { "blockchain", "getblockchaininfo", getblockchaininfo, {} }, | ||||
{ "blockchain", "getblockcount", getblockcount, {} }, | { "blockchain", "getblockcount", getblockcount, {} }, | ||||
{ "blockchain", "getblockhash", getblockhash, {"height"} }, | { "blockchain", "getblockhash", getblockhash, {"height"} }, | ||||
{ "blockchain", "getblockheader", getblockheader, {"blockhash","verbose"} }, | { "blockchain", "getblockheader", getblockheader, {"blockhash","verbose"} }, | ||||
{ "blockchain", "getblockstats", getblockstats, {"hash_or_height","stats"} }, | |||||
{ "blockchain", "getchaintips", getchaintips, {} }, | { "blockchain", "getchaintips", getchaintips, {} }, | ||||
{ "blockchain", "getchaintxstats", getchaintxstats, {"nblocks", "blockhash"} }, | { "blockchain", "getchaintxstats", getchaintxstats, {"nblocks", "blockhash"} }, | ||||
{ "blockchain", "getdifficulty", getdifficulty, {} }, | { "blockchain", "getdifficulty", getdifficulty, {} }, | ||||
{ "blockchain", "getmempoolancestors", getmempoolancestors, {"txid","verbose"} }, | { "blockchain", "getmempoolancestors", getmempoolancestors, {"txid","verbose"} }, | ||||
{ "blockchain", "getmempooldescendants", getmempooldescendants, {"txid","verbose"} }, | { "blockchain", "getmempooldescendants", getmempooldescendants, {"txid","verbose"} }, | ||||
{ "blockchain", "getmempoolentry", getmempoolentry, {"txid"} }, | { "blockchain", "getmempoolentry", getmempoolentry, {"txid"} }, | ||||
{ "blockchain", "getmempoolinfo", getmempoolinfo, {} }, | { "blockchain", "getmempoolinfo", getmempoolinfo, {} }, | ||||
{ "blockchain", "getrawmempool", getrawmempool, {"verbose"} }, | { "blockchain", "getrawmempool", getrawmempool, {"verbose"} }, | ||||
Show All 26 Lines |