aboutsummaryrefslogtreecommitdiff
path: root/src/rpc/blockchain.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/rpc/blockchain.cpp')
-rw-r--r--src/rpc/blockchain.cpp691
1 files changed, 632 insertions, 59 deletions
diff --git a/src/rpc/blockchain.cpp b/src/rpc/blockchain.cpp
index 03c097197f..012e3e3ac1 100644
--- a/src/rpc/blockchain.cpp
+++ b/src/rpc/blockchain.cpp
@@ -6,6 +6,7 @@
#include <rpc/blockchain.h>
#include <amount.h>
+#include <base58.h>
#include <chain.h>
#include <chainparams.h>
#include <checkpoints.h>
@@ -13,6 +14,8 @@
#include <consensus/validation.h>
#include <validation.h>
#include <core_io.h>
+#include <index/txindex.h>
+#include <key_io.h>
#include <policy/feerate.h>
#include <policy/policy.h>
#include <primitives/transaction.h>
@@ -27,10 +30,12 @@
#include <validationinterface.h>
#include <warnings.h>
+#include <assert.h>
#include <stdint.h>
#include <univalue.h>
+#include <boost/algorithm/string.hpp>
#include <boost/thread/thread.hpp> // boost::thread::interrupt
#include <memory>
@@ -47,17 +52,13 @@ static std::mutex cs_blockchange;
static std::condition_variable cond_blockchange;
static CUpdatedBlock latestblock;
-/* Calculate the difficulty for a given block index,
- * or the block index of the given chain.
+/* Calculate the difficulty for a given block index.
*/
-double GetDifficulty(const CChain& chain, const CBlockIndex* blockindex)
+double GetDifficulty(const CBlockIndex* blockindex)
{
if (blockindex == nullptr)
{
- if (chain.Tip() == nullptr)
- return 1.0;
- else
- blockindex = chain.Tip();
+ return 1.0;
}
int nShift = (blockindex->nBits >> 24) & 0xff;
@@ -78,11 +79,6 @@ double GetDifficulty(const CChain& chain, const CBlockIndex* blockindex)
return dDiff;
}
-double GetDifficulty(const CBlockIndex* blockindex)
-{
- return GetDifficulty(chainActive, blockindex);
-}
-
UniValue blockheaderToJSON(const CBlockIndex* blockindex)
{
AssertLockHeld(cs_main);
@@ -103,6 +99,7 @@ UniValue blockheaderToJSON(const CBlockIndex* blockindex)
result.pushKV("bits", strprintf("%08x", blockindex->nBits));
result.pushKV("difficulty", GetDifficulty(blockindex));
result.pushKV("chainwork", blockindex->nChainWork.GetHex());
+ result.pushKV("nTx", (uint64_t)blockindex->nTx);
if (blockindex->pprev)
result.pushKV("previousblockhash", blockindex->pprev->GetBlockHash().GetHex());
@@ -148,6 +145,7 @@ UniValue blockToJSON(const CBlock& block, const CBlockIndex* blockindex, bool tx
result.pushKV("bits", strprintf("%08x", block.nBits));
result.pushKV("difficulty", GetDifficulty(blockindex));
result.pushKV("chainwork", blockindex->nChainWork.GetHex());
+ result.pushKV("nTx", (uint64_t)blockindex->nTx);
if (blockindex->pprev)
result.pushKV("previousblockhash", blockindex->pprev->GetBlockHash().GetHex());
@@ -157,7 +155,7 @@ UniValue blockToJSON(const CBlock& block, const CBlockIndex* blockindex, bool tx
return result;
}
-UniValue getblockcount(const JSONRPCRequest& request)
+static UniValue getblockcount(const JSONRPCRequest& request)
{
if (request.fHelp || request.params.size() != 0)
throw std::runtime_error(
@@ -174,7 +172,7 @@ UniValue getblockcount(const JSONRPCRequest& request)
return chainActive.Height();
}
-UniValue getbestblockhash(const JSONRPCRequest& request)
+static UniValue getbestblockhash(const JSONRPCRequest& request)
{
if (request.fHelp || request.params.size() != 0)
throw std::runtime_error(
@@ -201,7 +199,7 @@ void RPCNotifyBlockChange(bool ibd, const CBlockIndex * pindex)
cond_blockchange.notify_all();
}
-UniValue waitfornewblock(const JSONRPCRequest& request)
+static UniValue waitfornewblock(const JSONRPCRequest& request)
{
if (request.fHelp || request.params.size() > 1)
throw std::runtime_error(
@@ -239,7 +237,7 @@ UniValue waitfornewblock(const JSONRPCRequest& request)
return ret;
}
-UniValue waitforblock(const JSONRPCRequest& request)
+static UniValue waitforblock(const JSONRPCRequest& request)
{
if (request.fHelp || request.params.size() < 1 || request.params.size() > 2)
throw std::runtime_error(
@@ -281,7 +279,7 @@ UniValue waitforblock(const JSONRPCRequest& request)
return ret;
}
-UniValue waitforblockheight(const JSONRPCRequest& request)
+static UniValue waitforblockheight(const JSONRPCRequest& request)
{
if (request.fHelp || request.params.size() < 1 || request.params.size() > 2)
throw std::runtime_error(
@@ -323,7 +321,7 @@ UniValue waitforblockheight(const JSONRPCRequest& request)
return ret;
}
-UniValue syncwithvalidationinterfacequeue(const JSONRPCRequest& request)
+static UniValue syncwithvalidationinterfacequeue(const JSONRPCRequest& request)
{
if (request.fHelp || request.params.size() > 0) {
throw std::runtime_error(
@@ -338,7 +336,7 @@ UniValue syncwithvalidationinterfacequeue(const JSONRPCRequest& request)
return NullUniValue;
}
-UniValue getdifficulty(const JSONRPCRequest& request)
+static UniValue getdifficulty(const JSONRPCRequest& request)
{
if (request.fHelp || request.params.size() != 0)
throw std::runtime_error(
@@ -352,10 +350,10 @@ UniValue getdifficulty(const JSONRPCRequest& request)
);
LOCK(cs_main);
- return GetDifficulty();
+ return GetDifficulty(chainActive.Tip());
}
-std::string EntryDescriptionString()
+static std::string EntryDescriptionString()
{
return " \"size\" : n, (numeric) virtual transaction size as defined in BIP 141. This is different from actual serialized size for witness transactions as witness data is discounted.\n"
" \"fee\" : n, (numeric) transaction fee in " + CURRENCY_UNIT + " (DEPRECATED)\n"
@@ -373,7 +371,7 @@ std::string EntryDescriptionString()
" \"base\" : n, (numeric) transaction fee in " + CURRENCY_UNIT + "\n"
" \"modified\" : n, (numeric) transaction fee with fee deltas used for mining priority in " + CURRENCY_UNIT + "\n"
" \"ancestor\" : n, (numeric) modified fees (see above) of in-mempool ancestors (including this one) in " + CURRENCY_UNIT + "\n"
- " \"descendent\" : n, (numeric) number of in-mempool ancestor transactions (including this one) in " + CURRENCY_UNIT + "\n"
+ " \"descendant\" : n, (numeric) modified fees (see above) of in-mempool descendants (including this one) in " + CURRENCY_UNIT + "\n"
" }\n"
" \"depends\" : [ (array) unconfirmed transactions used as inputs for this transaction\n"
" \"transactionid\", (string) parent transaction id\n"
@@ -383,7 +381,7 @@ std::string EntryDescriptionString()
" ... ]\n";
}
-void entryToJSON(UniValue &info, const CTxMemPoolEntry &e)
+static void entryToJSON(UniValue &info, const CTxMemPoolEntry &e) EXCLUSIVE_LOCKS_REQUIRED(::mempool.cs)
{
AssertLockHeld(mempool.cs);
@@ -460,7 +458,7 @@ UniValue mempoolToJSON(bool fVerbose)
}
}
-UniValue getrawmempool(const JSONRPCRequest& request)
+static UniValue getrawmempool(const JSONRPCRequest& request)
{
if (request.fHelp || request.params.size() > 1)
throw std::runtime_error(
@@ -492,7 +490,7 @@ UniValue getrawmempool(const JSONRPCRequest& request)
return mempoolToJSON(fVerbose);
}
-UniValue getmempoolancestors(const JSONRPCRequest& request)
+static UniValue getmempoolancestors(const JSONRPCRequest& request)
{
if (request.fHelp || request.params.size() < 1 || request.params.size() > 2) {
throw std::runtime_error(
@@ -556,7 +554,7 @@ UniValue getmempoolancestors(const JSONRPCRequest& request)
}
}
-UniValue getmempooldescendants(const JSONRPCRequest& request)
+static UniValue getmempooldescendants(const JSONRPCRequest& request)
{
if (request.fHelp || request.params.size() < 1 || request.params.size() > 2) {
throw std::runtime_error(
@@ -620,7 +618,7 @@ UniValue getmempooldescendants(const JSONRPCRequest& request)
}
}
-UniValue getmempoolentry(const JSONRPCRequest& request)
+static UniValue getmempoolentry(const JSONRPCRequest& request)
{
if (request.fHelp || request.params.size() != 1) {
throw std::runtime_error(
@@ -653,7 +651,7 @@ UniValue getmempoolentry(const JSONRPCRequest& request)
return info;
}
-UniValue getblockhash(const JSONRPCRequest& request)
+static UniValue getblockhash(const JSONRPCRequest& request)
{
if (request.fHelp || request.params.size() != 1)
throw std::runtime_error(
@@ -678,7 +676,7 @@ UniValue getblockhash(const JSONRPCRequest& request)
return pblockindex->GetBlockHash().GetHex();
}
-UniValue getblockheader(const JSONRPCRequest& request)
+static UniValue getblockheader(const JSONRPCRequest& request)
{
if (request.fHelp || request.params.size() < 1 || request.params.size() > 2)
throw std::runtime_error(
@@ -702,6 +700,7 @@ UniValue getblockheader(const JSONRPCRequest& request)
" \"bits\" : \"1d00ffff\", (string) The bits\n"
" \"difficulty\" : x.xxx, (numeric) The difficulty\n"
" \"chainwork\" : \"0000...1f3\" (string) Expected number of hashes required to produce the current chain (in hex)\n"
+ " \"nTx\" : n, (numeric) The number of transactions in the block.\n"
" \"previousblockhash\" : \"hash\", (string) The hash of the previous block\n"
" \"nextblockhash\" : \"hash\", (string) The hash of the next block\n"
"}\n"
@@ -737,7 +736,26 @@ UniValue getblockheader(const JSONRPCRequest& request)
return blockheaderToJSON(pblockindex);
}
-UniValue getblock(const JSONRPCRequest& request)
+static CBlock GetBlockChecked(const CBlockIndex* pblockindex)
+{
+ CBlock block;
+ if (IsBlockPruned(pblockindex)) {
+ throw JSONRPCError(RPC_MISC_ERROR, "Block not available (pruned data)");
+ }
+
+ if (!ReadBlockFromDisk(block, pblockindex, Params().GetConsensus())) {
+ // Block not found on disk. This could be because we have the block
+ // header in our index but don't have the block (for example if a
+ // non-whitelisted node sends us an unrequested long chain of valid
+ // blocks, we add the headers to our index, but don't accept the
+ // block).
+ throw JSONRPCError(RPC_MISC_ERROR, "Block not found on disk");
+ }
+
+ return block;
+}
+
+static UniValue getblock(const JSONRPCRequest& request)
{
if (request.fHelp || request.params.size() < 1 || request.params.size() > 2)
throw std::runtime_error(
@@ -771,6 +789,7 @@ UniValue getblock(const JSONRPCRequest& request)
" \"bits\" : \"1d00ffff\", (string) The bits\n"
" \"difficulty\" : x.xxx, (numeric) The difficulty\n"
" \"chainwork\" : \"xxxx\", (string) Expected number of hashes required to produce the chain up to this block (in hex)\n"
+ " \"nTx\" : n, (numeric) The number of transactions in the block.\n"
" \"previousblockhash\" : \"hash\", (string) The hash of the previous block\n"
" \"nextblockhash\" : \"hash\" (string) The hash of the next block\n"
"}\n"
@@ -805,17 +824,7 @@ UniValue getblock(const JSONRPCRequest& request)
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Block not found");
}
- CBlock block;
- if (fHavePruned && !(pblockindex->nStatus & BLOCK_HAVE_DATA) && pblockindex->nTx > 0)
- throw JSONRPCError(RPC_MISC_ERROR, "Block not available (pruned data)");
-
- if (!ReadBlockFromDisk(block, pblockindex, Params().GetConsensus()))
- // Block not found on disk. This could be because we have the block
- // header in our index but don't have the block (for example if a
- // non-whitelisted node sends us an unrequested long chain of valid
- // blocks, we add the headers to our index, but don't accept the
- // block).
- throw JSONRPCError(RPC_MISC_ERROR, "Block not found on disk");
+ const CBlock block = GetBlockChecked(pblockindex);
if (verbosity <= 0)
{
@@ -848,7 +857,7 @@ static void ApplyStats(CCoinsStats &stats, CHashWriter& ss, const uint256& hash,
ss << hash;
ss << VARINT(outputs.begin()->second.nHeight * 2 + outputs.begin()->second.fCoinBase ? 1u : 0u);
stats.nTransactions++;
- for (const auto output : outputs) {
+ for (const auto& output : outputs) {
ss << VARINT(output.first + 1);
ss << output.second.out.scriptPubKey;
ss << VARINT(output.second.out.nValue, VarIntMode::NONNEGATIVE_SIGNED);
@@ -899,7 +908,7 @@ static bool GetUTXOStats(CCoinsView *view, CCoinsStats &stats)
return true;
}
-UniValue pruneblockchain(const JSONRPCRequest& request)
+static UniValue pruneblockchain(const JSONRPCRequest& request)
{
if (request.fHelp || request.params.size() != 1)
throw std::runtime_error(
@@ -940,7 +949,7 @@ UniValue pruneblockchain(const JSONRPCRequest& request)
else if (height > chainHeight)
throw JSONRPCError(RPC_INVALID_PARAMETER, "Blockchain is shorter than the attempted prune height.");
else if (height > chainHeight - MIN_BLOCKS_TO_KEEP) {
- LogPrint(BCLog::RPC, "Attempt to prune blocks close to the tip. Retaining the minimum number of blocks.");
+ LogPrint(BCLog::RPC, "Attempt to prune blocks close to the tip. Retaining the minimum number of blocks.\n");
height = chainHeight - MIN_BLOCKS_TO_KEEP;
}
@@ -948,7 +957,7 @@ UniValue pruneblockchain(const JSONRPCRequest& request)
return uint64_t(height);
}
-UniValue gettxoutsetinfo(const JSONRPCRequest& request)
+static UniValue gettxoutsetinfo(const JSONRPCRequest& request)
{
if (request.fHelp || request.params.size() != 0)
throw std::runtime_error(
@@ -958,9 +967,9 @@ UniValue gettxoutsetinfo(const JSONRPCRequest& request)
"\nResult:\n"
"{\n"
" \"height\":n, (numeric) The current block height (index)\n"
- " \"bestblock\": \"hex\", (string) the best block hash hex\n"
- " \"transactions\": n, (numeric) The number of transactions\n"
- " \"txouts\": n, (numeric) The number of output transactions\n"
+ " \"bestblock\": \"hex\", (string) The hash of the block at the tip of the chain\n"
+ " \"transactions\": n, (numeric) The number of transactions with unspent outputs\n"
+ " \"txouts\": n, (numeric) The number of unspent transaction outputs\n"
" \"bogosize\": n, (numeric) A meaningless metric for UTXO set size\n"
" \"hash_serialized_2\": \"hash\", (string) The serialized hash\n"
" \"disk_size\": n, (numeric) The estimated size of the chainstate on disk\n"
@@ -1003,7 +1012,7 @@ UniValue gettxout(const JSONRPCRequest& request)
" Note that an unspent output that is spent in the mempool won't appear.\n"
"\nResult:\n"
"{\n"
- " \"bestblock\" : \"hash\", (string) the block hash\n"
+ " \"bestblock\": \"hash\", (string) The hash of the block at the tip of the chain\n"
" \"confirmations\" : n, (numeric) The number of confirmations\n"
" \"value\" : x.xxx, (numeric) The transaction value in " + CURRENCY_UNIT + "\n"
" \"scriptPubKey\" : { (json object)\n"
@@ -1069,7 +1078,7 @@ UniValue gettxout(const JSONRPCRequest& request)
return ret;
}
-UniValue verifychain(const JSONRPCRequest& request)
+static UniValue verifychain(const JSONRPCRequest& request)
{
int nCheckLevel = gArgs.GetArg("-checklevel", DEFAULT_CHECKLEVEL);
int nCheckDepth = gArgs.GetArg("-checkblocks", DEFAULT_CHECKBLOCKS);
@@ -1159,7 +1168,7 @@ static UniValue BIP9SoftForkDesc(const Consensus::Params& consensusParams, Conse
return rv;
}
-void BIP9SoftForkDescPushBack(UniValue& bip9_softforks, const Consensus::Params& consensusParams, Consensus::DeploymentPos id)
+static void BIP9SoftForkDescPushBack(UniValue& bip9_softforks, const Consensus::Params& consensusParams, Consensus::DeploymentPos id)
{
// Deployments with timeout value of 0 are hidden.
// A timeout value of 0 guarantees a softfork will never be activated.
@@ -1229,7 +1238,7 @@ UniValue getblockchaininfo(const JSONRPCRequest& request)
obj.pushKV("blocks", (int)chainActive.Height());
obj.pushKV("headers", pindexBestHeader ? pindexBestHeader->nHeight : -1);
obj.pushKV("bestblockhash", chainActive.Tip()->GetBlockHash().GetHex());
- obj.pushKV("difficulty", (double)GetDifficulty());
+ obj.pushKV("difficulty", (double)GetDifficulty(chainActive.Tip()));
obj.pushKV("mediantime", (int64_t)chainActive.Tip()->GetMedianTimePast());
obj.pushKV("verificationprogress", GuessVerificationProgress(Params().TxData(), chainActive.Tip()));
obj.pushKV("initialblockdownload", IsInitialBlockDownload());
@@ -1285,7 +1294,7 @@ struct CompareBlocksByHeight
}
};
-UniValue getchaintips(const JSONRPCRequest& request)
+static UniValue getchaintips(const JSONRPCRequest& request)
{
if (request.fHelp || request.params.size() != 0)
throw std::runtime_error(
@@ -1402,7 +1411,7 @@ UniValue mempoolInfoToJSON()
return ret;
}
-UniValue getmempoolinfo(const JSONRPCRequest& request)
+static UniValue getmempoolinfo(const JSONRPCRequest& request)
{
if (request.fHelp || request.params.size() != 0)
throw std::runtime_error(
@@ -1425,7 +1434,7 @@ UniValue getmempoolinfo(const JSONRPCRequest& request)
return mempoolInfoToJSON();
}
-UniValue preciousblock(const JSONRPCRequest& request)
+static UniValue preciousblock(const JSONRPCRequest& request)
{
if (request.fHelp || request.params.size() != 1)
throw std::runtime_error(
@@ -1463,7 +1472,7 @@ UniValue preciousblock(const JSONRPCRequest& request)
return NullUniValue;
}
-UniValue invalidateblock(const JSONRPCRequest& request)
+static UniValue invalidateblock(const JSONRPCRequest& request)
{
if (request.fHelp || request.params.size() != 1)
throw std::runtime_error(
@@ -1502,7 +1511,7 @@ UniValue invalidateblock(const JSONRPCRequest& request)
return NullUniValue;
}
-UniValue reconsiderblock(const JSONRPCRequest& request)
+static UniValue reconsiderblock(const JSONRPCRequest& request)
{
if (request.fHelp || request.params.size() != 1)
throw std::runtime_error(
@@ -1540,7 +1549,7 @@ UniValue reconsiderblock(const JSONRPCRequest& request)
return NullUniValue;
}
-UniValue getchaintxstats(const JSONRPCRequest& request)
+static UniValue getchaintxstats(const JSONRPCRequest& request)
{
if (request.fHelp || request.params.size() > 2)
throw std::runtime_error(
@@ -1614,7 +1623,285 @@ UniValue getchaintxstats(const JSONRPCRequest& request)
return ret;
}
-UniValue savemempool(const JSONRPCRequest& request)
+template<typename T>
+static T CalculateTruncatedMedian(std::vector<T>& scores)
+{
+ size_t size = scores.size();
+ if (size == 0) {
+ return 0;
+ }
+
+ 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 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 satoshis.\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\": xxxxx, (numeric) Average fee in the block\n"
+ " \"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"
+ " \"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"
+ " \"minfeerate\": xxxxx, (numeric) Minimum feerate (in satoshis per virtual byte)\n"
+ " \"mintxsize\": xxxxx, (numeric) Minimum transaction size\n"
+ " \"outs\": xxxxx, (numeric) The number of outputs\n"
+ " \"subsidy\": xxxxx, (numeric) The block subsidy\n"
+ " \"swtotal_size\": xxxxx, (numeric) Total size of all segwit transactions\n"
+ " \"swtotal_weight\": xxxxx, (numeric) Total weight of all segwit transactions divided by segwit scale factor (4)\n"
+ " \"swtxs\": xxxxx, (numeric) The number of segwit transactions\n"
+ " \"time\": xxxxx, (numeric) The block time\n"
+ " \"total_out\": xxxxx, (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"
+ " \"total_weight\": xxxxx, (numeric) Total weight of all non-coinbase transactions divided by segwit scale factor (4)\n"
+ " \"totalfee\": xxxxx, (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(pindex);
+
+ 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 ||
+ 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_sw = do_all || SetHasKeys(stats, "swtxs", "swtotal_size", "swtotal_weight");
+
+ CAmount maxfee = 0;
+ CAmount maxfeerate = 0;
+ CAmount minfee = MAX_MONEY;
+ CAmount minfeerate = MAX_MONEY;
+ CAmount total_out = 0;
+ CAmount totalfee = 0;
+ int64_t inputs = 0;
+ int64_t maxtxsize = 0;
+ int64_t mintxsize = MAX_BLOCK_SERIALIZED_SIZE;
+ int64_t outputs = 0;
+ int64_t swtotal_size = 0;
+ int64_t swtotal_weight = 0;
+ int64_t swtxs = 0;
+ int64_t total_size = 0;
+ int64_t total_weight = 0;
+ int64_t utxo_size_inc = 0;
+ std::vector<CAmount> fee_array;
+ std::vector<CAmount> feerate_array;
+ std::vector<int64_t> txsize_array;
+
+ for (const auto& tx : block.vtx) {
+ outputs += tx->vout.size();
+
+ CAmount tx_total_out = 0;
+ 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;
+ }
+
+ inputs += tx->vin.size(); // Don't count coinbase's fake input
+ total_out += tx_total_out; // Don't count coinbase reward
+
+ 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;
+ }
+
+ int64_t weight = 0;
+ if (do_calculate_weight) {
+ weight = GetTransactionWeight(*tx);
+ total_weight += weight;
+ }
+
+ if (do_calculate_sw && tx->HasWitness()) {
+ ++swtxs;
+ swtotal_size += tx_size;
+ swtotal_weight += weight;
+ }
+
+ if (loop_inputs) {
+
+ if (!g_txindex) {
+ throw JSONRPCError(RPC_INVALID_PARAMETER, "One or more of the selected stats requires -txindex enabled");
+ }
+ CAmount tx_total_in = 0;
+ for (const CTxIn& in : tx->vin) {
+ CTransactionRef tx_in;
+ uint256 hashBlock;
+ if (!GetTransaction(in.prevout.hash, tx_in, Params().GetConsensus(), hashBlock, false)) {
+ throw JSONRPCError(RPC_INTERNAL_ERROR, std::string("Unexpected internal error (tx index seems corrupt)"));
+ }
+
+ CTxOut prevoutput = tx_in->vout[in.prevout.n];
+
+ tx_total_in += prevoutput.nValue;
+ utxo_size_inc -= GetSerializeSize(prevoutput, SER_NETWORK, PROTOCOL_VERSION) + PER_UTXO_OVERHEAD;
+ }
+
+ CAmount 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;
+
+ // 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);
+ }
+ maxfeerate = std::max(maxfeerate, feerate);
+ minfeerate = std::min(minfeerate, feerate);
+ }
+ }
+
+ 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("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);
+ ret_all.pushKV("minfeerate", (minfeerate == MAX_MONEY) ? 0 : minfeerate);
+ ret_all.pushKV("mintxsize", mintxsize == MAX_BLOCK_SERIALIZED_SIZE ? 0 : mintxsize);
+ ret_all.pushKV("outs", outputs);
+ ret_all.pushKV("subsidy", GetBlockSubsidy(pindex->nHeight, Params().GetConsensus()));
+ ret_all.pushKV("swtotal_size", swtotal_size);
+ ret_all.pushKV("swtotal_weight", swtotal_weight);
+ ret_all.pushKV("swtxs", swtxs);
+ ret_all.pushKV("time", pindex->GetBlockTime());
+ ret_all.pushKV("total_out", total_out);
+ ret_all.pushKV("total_size", total_size);
+ ret_all.pushKV("total_weight", total_weight);
+ ret_all.pushKV("totalfee", 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 JSONRPCRequest& request)
{
if (request.fHelp || request.params.size() != 0) {
throw std::runtime_error(
@@ -1637,11 +1924,296 @@ UniValue savemempool(const JSONRPCRequest& request)
return NullUniValue;
}
+//! Search for a given set of pubkey scripts
+bool FindScriptPubKey(std::atomic<int>& scan_progress, const std::atomic<bool>& should_abort, int64_t& count, CCoinsViewCursor* cursor, const std::set<CScript>& needles, std::map<COutPoint, Coin>& out_results) {
+ scan_progress = 0;
+ count = 0;
+ while (cursor->Valid()) {
+ COutPoint key;
+ Coin coin;
+ if (!cursor->GetKey(key) || !cursor->GetValue(coin)) return false;
+ if (++count % 8192 == 0) {
+ boost::this_thread::interruption_point();
+ if (should_abort) {
+ // allow to abort the scan via the abort reference
+ return false;
+ }
+ }
+ if (count % 256 == 0) {
+ // update progress reference every 256 item
+ uint32_t high = 0x100 * *key.hash.begin() + *(key.hash.begin() + 1);
+ scan_progress = (int)(high * 100.0 / 65536.0 + 0.5);
+ }
+ if (needles.count(coin.out.scriptPubKey)) {
+ out_results.emplace(key, coin);
+ }
+ cursor->Next();
+ }
+ scan_progress = 100;
+ return true;
+}
+
+/** RAII object to prevent concurrency issue when scanning the txout set */
+static std::mutex g_utxosetscan;
+static std::atomic<int> g_scan_progress;
+static std::atomic<bool> g_scan_in_progress;
+static std::atomic<bool> g_should_abort_scan;
+class CoinsViewScanReserver
+{
+private:
+ bool m_could_reserve;
+public:
+ explicit CoinsViewScanReserver() : m_could_reserve(false) {}
+
+ bool reserve() {
+ assert (!m_could_reserve);
+ std::lock_guard<std::mutex> lock(g_utxosetscan);
+ if (g_scan_in_progress) {
+ return false;
+ }
+ g_scan_in_progress = true;
+ m_could_reserve = true;
+ return true;
+ }
+
+ ~CoinsViewScanReserver() {
+ if (m_could_reserve) {
+ std::lock_guard<std::mutex> lock(g_utxosetscan);
+ g_scan_in_progress = false;
+ }
+ }
+};
+
+static const char *g_default_scantxoutset_script_types[] = { "P2PKH", "P2SH_P2WPKH", "P2WPKH" };
+
+enum class OutputScriptType {
+ UNKNOWN,
+ P2PK,
+ P2PKH,
+ P2SH_P2WPKH,
+ P2WPKH
+};
+
+static inline OutputScriptType GetOutputScriptTypeFromString(const std::string& outputtype)
+{
+ if (outputtype == "P2PK") return OutputScriptType::P2PK;
+ else if (outputtype == "P2PKH") return OutputScriptType::P2PKH;
+ else if (outputtype == "P2SH_P2WPKH") return OutputScriptType::P2SH_P2WPKH;
+ else if (outputtype == "P2WPKH") return OutputScriptType::P2WPKH;
+ else return OutputScriptType::UNKNOWN;
+}
+
+CTxDestination GetDestinationForKey(const CPubKey& key, OutputScriptType type)
+{
+ switch (type) {
+ case OutputScriptType::P2PKH: return key.GetID();
+ case OutputScriptType::P2SH_P2WPKH:
+ case OutputScriptType::P2WPKH: {
+ if (!key.IsCompressed()) return key.GetID();
+ CTxDestination witdest = WitnessV0KeyHash(key.GetID());
+ if (type == OutputScriptType::P2SH_P2WPKH) {
+ CScript witprog = GetScriptForDestination(witdest);
+ return CScriptID(witprog);
+ } else {
+ return witdest;
+ }
+ }
+ default: assert(false);
+ }
+}
+
+UniValue scantxoutset(const JSONRPCRequest& request)
+{
+ if (request.fHelp || request.params.size() < 1 || request.params.size() > 2)
+ throw std::runtime_error(
+ "scantxoutset <action> ( <scanobjects> )\n"
+ "\nScans the unspent transaction output set for possible entries that matches common scripts of given public keys.\n"
+ "Using addresses as scanobjects will _not_ detect unspent P2PK txouts\n"
+ "\nArguments:\n"
+ "1. \"action\" (string, required) The action to execute\n"
+ " \"start\" for starting a scan\n"
+ " \"abort\" for aborting the current scan (returns true when abort was successful)\n"
+ " \"status\" for progress report (in %) of the current scan\n"
+ "2. \"scanobjects\" (array, optional) Array of scan objects (only one object type per scan object allowed)\n"
+ " [\n"
+ " { \"address\" : \"<address>\" }, (string, optional) Bitcoin address\n"
+ " { \"script\" : \"<scriptPubKey>\" }, (string, optional) HEX encoded script (scriptPubKey)\n"
+ " { \"pubkey\" : (object, optional) Public key\n"
+ " {\n"
+ " \"pubkey\" : \"<pubkey\">, (string, required) HEX encoded public key\n"
+ " \"script_types\" : [ ... ], (array, optional) Array of script-types to derive from the pubkey (possible values: \"P2PK\", \"P2PKH\", \"P2SH-P2WPKH\", \"P2WPKH\")\n"
+ " }\n"
+ " },\n"
+ " ]\n"
+ "\nResult:\n"
+ "{\n"
+ " \"unspents\": [\n"
+ " {\n"
+ " \"txid\" : \"transactionid\", (string) The transaction id\n"
+ " \"vout\": n, (numeric) the vout value\n"
+ " \"scriptPubKey\" : \"script\", (string) the script key\n"
+ " \"amount\" : x.xxx, (numeric) The total amount in " + CURRENCY_UNIT + " of the unspent output\n"
+ " \"height\" : n, (numeric) Height of the unspent transaction output\n"
+ " }\n"
+ " ,...], \n"
+ " \"total_amount\" : x.xxx, (numeric) The total amount of all found unspent outputs in " + CURRENCY_UNIT + "\n"
+ "]\n"
+ );
+
+ RPCTypeCheck(request.params, {UniValue::VSTR, UniValue::VARR});
+
+ UniValue result(UniValue::VOBJ);
+ if (request.params[0].get_str() == "status") {
+ CoinsViewScanReserver reserver;
+ if (reserver.reserve()) {
+ // no scan in progress
+ return NullUniValue;
+ }
+ result.pushKV("progress", g_scan_progress);
+ return result;
+ } else if (request.params[0].get_str() == "abort") {
+ CoinsViewScanReserver reserver;
+ if (reserver.reserve()) {
+ // reserve was possible which means no scan was running
+ return false;
+ }
+ // set the abort flag
+ g_should_abort_scan = true;
+ return true;
+ } else if (request.params[0].get_str() == "start") {
+ CoinsViewScanReserver reserver;
+ if (!reserver.reserve()) {
+ throw JSONRPCError(RPC_INVALID_PARAMETER, "Scan already in progress, use action \"abort\" or \"status\"");
+ }
+ std::set<CScript> needles;
+ CAmount total_in = 0;
+
+ // loop through the scan objects
+ for (const UniValue& scanobject : request.params[1].get_array().getValues()) {
+ if (!scanobject.isObject()) {
+ throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid scan object");
+ }
+ UniValue address_uni = find_value(scanobject, "address");
+ UniValue pubkey_uni = find_value(scanobject, "pubkey");
+ UniValue script_uni = find_value(scanobject, "script");
+
+ // make sure only one object type is present
+ if (1 != !address_uni.isNull() + !pubkey_uni.isNull() + !script_uni.isNull()) {
+ throw JSONRPCError(RPC_INVALID_PARAMETER, "Only one object type is allowed per scan object");
+ } else if (!address_uni.isNull() && !address_uni.isStr()) {
+ throw JSONRPCError(RPC_INVALID_PARAMETER, "Scanobject \"address\" must contain a single string as value");
+ } else if (!pubkey_uni.isNull() && !pubkey_uni.isObject()) {
+ throw JSONRPCError(RPC_INVALID_PARAMETER, "Scanobject \"pubkey\" must contain an object as value");
+ } else if (!script_uni.isNull() && !script_uni.isStr()) {
+ throw JSONRPCError(RPC_INVALID_PARAMETER, "Scanobject \"script\" must contain a single string as value");
+ } else if (address_uni.isStr()) {
+ // type: address
+ // decode destination and derive the scriptPubKey
+ // add the script to the scan containers
+ CTxDestination dest = DecodeDestination(address_uni.get_str());
+ if (!IsValidDestination(dest)) {
+ throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid address");
+ }
+ CScript script = GetScriptForDestination(dest);
+ assert(!script.empty());
+ needles.insert(script);
+ } else if (pubkey_uni.isObject()) {
+ // type: pubkey
+ // derive script(s) according to the script_type parameter
+ UniValue script_types_uni = find_value(pubkey_uni, "script_types");
+ UniValue pubkeydata_uni = find_value(pubkey_uni, "pubkey");
+
+ // check the script types and use the default if not provided
+ if (!script_types_uni.isNull() && !script_types_uni.isArray()) {
+ throw JSONRPCError(RPC_INVALID_PARAMETER, "script_types must be an array");
+ } else if (script_types_uni.isNull()) {
+ // use the default script types
+ script_types_uni = UniValue(UniValue::VARR);
+ for (const char *t : g_default_scantxoutset_script_types) {
+ script_types_uni.push_back(t);
+ }
+ }
+
+ // check the acctual pubkey
+ if (!pubkeydata_uni.isStr() || !IsHex(pubkeydata_uni.get_str())) {
+ throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Public key must be hex encoded");
+ }
+ CPubKey pubkey(ParseHexV(pubkeydata_uni, "pubkey"));
+ if (!pubkey.IsFullyValid()) {
+ throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid public key");
+ }
+
+ // loop through the script types and derive the script
+ for (const UniValue& script_type_uni : script_types_uni.get_array().getValues()) {
+ OutputScriptType script_type = GetOutputScriptTypeFromString(script_type_uni.get_str());
+ if (script_type == OutputScriptType::UNKNOWN) throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid script type");
+ CScript script;
+ if (script_type == OutputScriptType::P2PK) {
+ // support legacy P2PK scripts
+ script << ToByteVector(pubkey) << OP_CHECKSIG;
+ } else {
+ script = GetScriptForDestination(GetDestinationForKey(pubkey, script_type));
+ }
+ assert(!script.empty());
+ needles.insert(script);
+ }
+ } else if (script_uni.isStr()) {
+ // type: script
+ // check and add the script to the scan containers (needles array)
+ CScript script(ParseHexV(script_uni, "script"));
+ // TODO: check script: max length, has OP, is unspenable etc.
+ needles.insert(script);
+ }
+ }
+
+ // Scan the unspent transaction output set for inputs
+ UniValue unspents(UniValue::VARR);
+ std::vector<CTxOut> input_txos;
+ std::map<COutPoint, Coin> coins;
+ g_should_abort_scan = false;
+ g_scan_progress = 0;
+ int64_t count = 0;
+ std::unique_ptr<CCoinsViewCursor> pcursor;
+ {
+ LOCK(cs_main);
+ FlushStateToDisk();
+ pcursor = std::unique_ptr<CCoinsViewCursor>(pcoinsdbview->Cursor());
+ assert(pcursor);
+ }
+ bool res = FindScriptPubKey(g_scan_progress, g_should_abort_scan, count, pcursor.get(), needles, coins);
+ result.pushKV("success", res);
+ result.pushKV("searched_items", count);
+
+ for (const auto& it : coins) {
+ const COutPoint& outpoint = it.first;
+ const Coin& coin = it.second;
+ const CTxOut& txo = coin.out;
+ input_txos.push_back(txo);
+ total_in += txo.nValue;
+
+ UniValue unspent(UniValue::VOBJ);
+ unspent.pushKV("txid", outpoint.hash.GetHex());
+ unspent.pushKV("vout", (int32_t)outpoint.n);
+ unspent.pushKV("scriptPubKey", HexStr(txo.scriptPubKey.begin(), txo.scriptPubKey.end()));
+ unspent.pushKV("amount", ValueFromAmount(txo.nValue));
+ unspent.pushKV("height", (int32_t)coin.nHeight);
+
+ unspents.push_back(unspent);
+ }
+ result.pushKV("unspents", unspents);
+ result.pushKV("total_amount", ValueFromAmount(total_in));
+ } else {
+ throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid command");
+ }
+ return result;
+}
+
static const CRPCCommand commands[] =
{ // category name actor (function) argNames
// --------------------- ------------------------ ----------------------- ----------
{ "blockchain", "getblockchaininfo", &getblockchaininfo, {} },
{ "blockchain", "getchaintxstats", &getchaintxstats, {"nblocks", "blockhash"} },
+ { "blockchain", "getblockstats", &getblockstats, {"hash_or_height", "stats"} },
{ "blockchain", "getbestblockhash", &getbestblockhash, {} },
{ "blockchain", "getblockcount", &getblockcount, {} },
{ "blockchain", "getblock", &getblock, {"blockhash","verbosity|verbose"} },
@@ -1661,6 +2233,7 @@ static const CRPCCommand commands[] =
{ "blockchain", "verifychain", &verifychain, {"checklevel","nblocks"} },
{ "blockchain", "preciousblock", &preciousblock, {"blockhash"} },
+ { "blockchain", "scantxoutset", &scantxoutset, {"action", "scanobjects"} },
/* Not shown in help */
{ "hidden", "invalidateblock", &invalidateblock, {"blockhash"} },