Page MenuHomePhabricator

No OneTemporary

diff --git a/src/rpc/blockchain.cpp b/src/rpc/blockchain.cpp
index b370d1729..2dbfc22b6 100644
--- a/src/rpc/blockchain.cpp
+++ b/src/rpc/blockchain.cpp
@@ -1,3268 +1,3283 @@
// Copyright (c) 2010 Satoshi Nakamoto
// Copyright (c) 2009-2019 The Bitcoin Core developers
// Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
#include <rpc/blockchain.h>
#include <avalanche/processor.h>
#include <blockfilter.h>
#include <chain.h>
#include <chainparams.h>
#include <clientversion.h>
#include <coins.h>
#include <common/args.h>
#include <config.h>
#include <consensus/amount.h>
#include <consensus/params.h>
#include <consensus/validation.h>
#include <core_io.h>
#include <hash.h>
#include <index/blockfilterindex.h>
#include <index/coinstatsindex.h>
#include <logging/timer.h>
#include <net.h>
#include <net_processing.h>
#include <node/blockstorage.h>
#include <node/coinstats.h>
#include <node/context.h>
#include <node/utxo_snapshot.h>
#include <primitives/transaction.h>
#include <rpc/server.h>
#include <rpc/server_util.h>
#include <rpc/util.h>
#include <script/descriptor.h>
#include <serialize.h>
#include <streams.h>
#include <txdb.h>
#include <txmempool.h>
#include <undo.h>
#include <util/check.h>
#include <util/fs.h>
#include <util/strencodings.h>
#include <util/translation.h>
#include <validation.h>
#include <validationinterface.h>
#include <warnings.h>
#include <condition_variable>
#include <cstdint>
#include <memory>
#include <mutex>
#include <optional>
using kernel::CCoinsStats;
using kernel::CoinStatsHashType;
using node::BlockManager;
using node::GetUTXOStats;
using node::NodeContext;
using node::SnapshotMetadata;
struct CUpdatedBlock {
BlockHash hash;
int height;
};
static GlobalMutex cs_blockchange;
static std::condition_variable cond_blockchange;
static CUpdatedBlock latestblock GUARDED_BY(cs_blockchange);
std::tuple<std::unique_ptr<CCoinsViewCursor>, CCoinsStats, const CBlockIndex *>
PrepareUTXOSnapshot(Chainstate &chainstate,
const std::function<void()> &interruption_point = {})
EXCLUSIVE_LOCKS_REQUIRED(::cs_main);
UniValue
WriteUTXOSnapshot(Chainstate &chainstate, CCoinsViewCursor *pcursor,
CCoinsStats *maybe_stats, const CBlockIndex *tip,
AutoFile &afile, const fs::path &path,
const fs::path &temppath,
const std::function<void()> &interruption_point = {});
/**
* Calculate the difficulty for a given block index.
*/
double GetDifficulty(const CBlockIndex *blockindex) {
CHECK_NONFATAL(blockindex);
int nShift = (blockindex->nBits >> 24) & 0xff;
double dDiff = double(0x0000ffff) / double(blockindex->nBits & 0x00ffffff);
while (nShift < 29) {
dDiff *= 256.0;
nShift++;
}
while (nShift > 29) {
dDiff /= 256.0;
nShift--;
}
return dDiff;
}
static int ComputeNextBlockAndDepth(const CBlockIndex *tip,
const CBlockIndex *blockindex,
const CBlockIndex *&next) {
next = tip->GetAncestor(blockindex->nHeight + 1);
if (next && next->pprev == blockindex) {
return tip->nHeight - blockindex->nHeight + 1;
}
next = nullptr;
return blockindex == tip ? 1 : -1;
}
static const CBlockIndex *ParseHashOrHeight(const UniValue &param,
ChainstateManager &chainman) {
LOCK(::cs_main);
CChain &active_chain = chainman.ActiveChain();
if (param.isNum()) {
const int height{param.getInt<int>()};
if (height < 0) {
throw JSONRPCError(
RPC_INVALID_PARAMETER,
strprintf("Target block height %d is negative", height));
}
const int current_tip{active_chain.Height()};
if (height > current_tip) {
throw JSONRPCError(
RPC_INVALID_PARAMETER,
strprintf("Target block height %d after current tip %d", height,
current_tip));
}
return active_chain[height];
} else {
const BlockHash hash{ParseHashV(param, "hash_or_height")};
const CBlockIndex *pindex = chainman.m_blockman.LookupBlockIndex(hash);
if (!pindex) {
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Block not found");
}
return pindex;
}
}
UniValue blockheaderToJSON(const CBlockIndex *tip,
const CBlockIndex *blockindex) {
// Serialize passed information without accessing chain state of the active
// chain!
// For performance reasons
AssertLockNotHeld(cs_main);
UniValue result(UniValue::VOBJ);
result.pushKV("hash", blockindex->GetBlockHash().GetHex());
const CBlockIndex *pnext;
int confirmations = ComputeNextBlockAndDepth(tip, blockindex, pnext);
result.pushKV("confirmations", confirmations);
result.pushKV("height", blockindex->nHeight);
result.pushKV("version", blockindex->nVersion);
result.pushKV("versionHex", strprintf("%08x", blockindex->nVersion));
result.pushKV("merkleroot", blockindex->hashMerkleRoot.GetHex());
result.pushKV("time", int64_t(blockindex->nTime));
result.pushKV("mediantime", int64_t(blockindex->GetMedianTimePast()));
result.pushKV("nonce", uint64_t(blockindex->nNonce));
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());
}
if (pnext) {
result.pushKV("nextblockhash", pnext->GetBlockHash().GetHex());
}
return result;
}
UniValue blockToJSON(BlockManager &blockman, const CBlock &block,
const CBlockIndex *tip, const CBlockIndex *blockindex,
bool txDetails) {
UniValue result = blockheaderToJSON(tip, blockindex);
result.pushKV("size", (int)::GetSerializeSize(block, PROTOCOL_VERSION));
UniValue txs(UniValue::VARR);
if (txDetails) {
CBlockUndo blockUndo;
const bool is_not_pruned{
WITH_LOCK(::cs_main, return !blockman.IsBlockPruned(blockindex))};
const bool have_undo{is_not_pruned &&
blockman.UndoReadFromDisk(blockUndo, *blockindex)};
for (size_t i = 0; i < block.vtx.size(); ++i) {
const CTransactionRef &tx = block.vtx.at(i);
// coinbase transaction (i == 0) doesn't have undo data
const CTxUndo *txundo =
(have_undo && i) ? &blockUndo.vtxundo.at(i - 1) : nullptr;
UniValue objTx(UniValue::VOBJ);
TxToUniv(*tx, BlockHash(), objTx, true, RPCSerializationFlags(),
txundo);
txs.push_back(objTx);
}
} else {
for (const CTransactionRef &tx : block.vtx) {
txs.push_back(tx->GetId().GetHex());
}
}
result.pushKV("tx", txs);
return result;
}
static RPCHelpMan getblockcount() {
return RPCHelpMan{
"getblockcount",
"Returns the height of the most-work fully-validated chain.\n"
"The genesis block has height 0.\n",
{},
RPCResult{RPCResult::Type::NUM, "", "The current block count"},
RPCExamples{HelpExampleCli("getblockcount", "") +
HelpExampleRpc("getblockcount", "")},
[&](const RPCHelpMan &self, const Config &config,
const JSONRPCRequest &request) -> UniValue {
ChainstateManager &chainman = EnsureAnyChainman(request.context);
LOCK(cs_main);
return chainman.ActiveHeight();
},
};
}
static RPCHelpMan getbestblockhash() {
return RPCHelpMan{
"getbestblockhash",
"Returns the hash of the best (tip) block in the "
"most-work fully-validated chain.\n",
{},
RPCResult{RPCResult::Type::STR_HEX, "", "the block hash, hex-encoded"},
RPCExamples{HelpExampleCli("getbestblockhash", "") +
HelpExampleRpc("getbestblockhash", "")},
[&](const RPCHelpMan &self, const Config &config,
const JSONRPCRequest &request) -> UniValue {
ChainstateManager &chainman = EnsureAnyChainman(request.context);
LOCK(cs_main);
return chainman.ActiveTip()->GetBlockHash().GetHex();
},
};
}
void RPCNotifyBlockChange(const CBlockIndex *pindex) {
if (pindex) {
LOCK(cs_blockchange);
latestblock.hash = pindex->GetBlockHash();
latestblock.height = pindex->nHeight;
}
cond_blockchange.notify_all();
}
static RPCHelpMan waitfornewblock() {
return RPCHelpMan{
"waitfornewblock",
"Waits for a specific new block and returns useful info about it.\n"
"\nReturns the current block on timeout or exit.\n",
{
{"timeout", RPCArg::Type::NUM, RPCArg::Default{0},
"Time in milliseconds to wait for a response. 0 indicates no "
"timeout."},
},
RPCResult{RPCResult::Type::OBJ,
"",
"",
{
{RPCResult::Type::STR_HEX, "hash", "The blockhash"},
{RPCResult::Type::NUM, "height", "Block height"},
}},
RPCExamples{HelpExampleCli("waitfornewblock", "1000") +
HelpExampleRpc("waitfornewblock", "1000")},
[&](const RPCHelpMan &self, const Config &config,
const JSONRPCRequest &request) -> UniValue {
int timeout = 0;
if (!request.params[0].isNull()) {
timeout = request.params[0].getInt<int>();
}
CUpdatedBlock block;
{
WAIT_LOCK(cs_blockchange, lock);
block = latestblock;
if (timeout) {
cond_blockchange.wait_for(
lock, std::chrono::milliseconds(timeout),
[&block]() EXCLUSIVE_LOCKS_REQUIRED(cs_blockchange) {
return latestblock.height != block.height ||
latestblock.hash != block.hash ||
!IsRPCRunning();
});
} else {
cond_blockchange.wait(
lock,
[&block]() EXCLUSIVE_LOCKS_REQUIRED(cs_blockchange) {
return latestblock.height != block.height ||
latestblock.hash != block.hash ||
!IsRPCRunning();
});
}
block = latestblock;
}
UniValue ret(UniValue::VOBJ);
ret.pushKV("hash", block.hash.GetHex());
ret.pushKV("height", block.height);
return ret;
},
};
}
static RPCHelpMan waitforblock() {
return RPCHelpMan{
"waitforblock",
"Waits for a specific new block and returns useful info about it.\n"
"\nReturns the current block on timeout or exit.\n",
{
{"blockhash", RPCArg::Type::STR_HEX, RPCArg::Optional::NO,
"Block hash to wait for."},
{"timeout", RPCArg::Type::NUM, RPCArg::Default{0},
"Time in milliseconds to wait for a response. 0 indicates no "
"timeout."},
},
RPCResult{RPCResult::Type::OBJ,
"",
"",
{
{RPCResult::Type::STR_HEX, "hash", "The blockhash"},
{RPCResult::Type::NUM, "height", "Block height"},
}},
RPCExamples{HelpExampleCli("waitforblock",
"\"0000000000079f8ef3d2c688c244eb7a4570b24c9"
"ed7b4a8c619eb02596f8862\" 1000") +
HelpExampleRpc("waitforblock",
"\"0000000000079f8ef3d2c688c244eb7a4570b24c9"
"ed7b4a8c619eb02596f8862\", 1000")},
[&](const RPCHelpMan &self, const Config &config,
const JSONRPCRequest &request) -> UniValue {
int timeout = 0;
BlockHash hash(ParseHashV(request.params[0], "blockhash"));
if (!request.params[1].isNull()) {
timeout = request.params[1].getInt<int>();
}
CUpdatedBlock block;
{
WAIT_LOCK(cs_blockchange, lock);
if (timeout) {
cond_blockchange.wait_for(
lock, std::chrono::milliseconds(timeout),
[&hash]() EXCLUSIVE_LOCKS_REQUIRED(cs_blockchange) {
return latestblock.hash == hash || !IsRPCRunning();
});
} else {
cond_blockchange.wait(
lock,
[&hash]() EXCLUSIVE_LOCKS_REQUIRED(cs_blockchange) {
return latestblock.hash == hash || !IsRPCRunning();
});
}
block = latestblock;
}
UniValue ret(UniValue::VOBJ);
ret.pushKV("hash", block.hash.GetHex());
ret.pushKV("height", block.height);
return ret;
},
};
}
static RPCHelpMan waitforblockheight() {
return RPCHelpMan{
"waitforblockheight",
"Waits for (at least) block height and returns the height and "
"hash\nof the current tip.\n"
"\nReturns the current block on timeout or exit.\n",
{
{"height", RPCArg::Type::NUM, RPCArg::Optional::NO,
"Block height to wait for."},
{"timeout", RPCArg::Type::NUM, RPCArg::Default{0},
"Time in milliseconds to wait for a response. 0 indicates no "
"timeout."},
},
RPCResult{RPCResult::Type::OBJ,
"",
"",
{
{RPCResult::Type::STR_HEX, "hash", "The blockhash"},
{RPCResult::Type::NUM, "height", "Block height"},
}},
RPCExamples{HelpExampleCli("waitforblockheight", "100 1000") +
HelpExampleRpc("waitforblockheight", "100, 1000")},
[&](const RPCHelpMan &self, const Config &config,
const JSONRPCRequest &request) -> UniValue {
int timeout = 0;
int height = request.params[0].getInt<int>();
if (!request.params[1].isNull()) {
timeout = request.params[1].getInt<int>();
}
CUpdatedBlock block;
{
WAIT_LOCK(cs_blockchange, lock);
if (timeout) {
cond_blockchange.wait_for(
lock, std::chrono::milliseconds(timeout),
[&height]() EXCLUSIVE_LOCKS_REQUIRED(cs_blockchange) {
return latestblock.height >= height ||
!IsRPCRunning();
});
} else {
cond_blockchange.wait(
lock,
[&height]() EXCLUSIVE_LOCKS_REQUIRED(cs_blockchange) {
return latestblock.height >= height ||
!IsRPCRunning();
});
}
block = latestblock;
}
UniValue ret(UniValue::VOBJ);
ret.pushKV("hash", block.hash.GetHex());
ret.pushKV("height", block.height);
return ret;
},
};
}
static RPCHelpMan syncwithvalidationinterfacequeue() {
return RPCHelpMan{
"syncwithvalidationinterfacequeue",
"Waits for the validation interface queue to catch up on everything "
"that was there when we entered this function.\n",
{},
RPCResult{RPCResult::Type::NONE, "", ""},
RPCExamples{HelpExampleCli("syncwithvalidationinterfacequeue", "") +
HelpExampleRpc("syncwithvalidationinterfacequeue", "")},
[&](const RPCHelpMan &self, const Config &config,
const JSONRPCRequest &request) -> UniValue {
SyncWithValidationInterfaceQueue();
return NullUniValue;
},
};
}
static RPCHelpMan getdifficulty() {
return RPCHelpMan{
"getdifficulty",
"Returns the proof-of-work difficulty as a multiple of the minimum "
"difficulty.\n",
{},
RPCResult{RPCResult::Type::NUM, "",
"the proof-of-work difficulty as a multiple of the minimum "
"difficulty."},
RPCExamples{HelpExampleCli("getdifficulty", "") +
HelpExampleRpc("getdifficulty", "")},
[&](const RPCHelpMan &self, const Config &config,
const JSONRPCRequest &request) -> UniValue {
ChainstateManager &chainman = EnsureAnyChainman(request.context);
LOCK(cs_main);
return GetDifficulty(chainman.ActiveTip());
},
};
}
static RPCHelpMan getblockfrompeer() {
return RPCHelpMan{
"getblockfrompeer",
"Attempt to fetch block from a given peer.\n"
"\nWe must have the header for this block, e.g. using submitheader.\n"
"The block will not have any undo data which can limit the usage of "
"the block data in a context where the undo data is needed.\n"
"Subsequent calls for the same block may cause the response from the "
"previous peer to be ignored.\n"
"\nReturns an empty JSON object if the request was successfully "
"scheduled.",
{
{"blockhash", RPCArg::Type::STR_HEX, RPCArg::Optional::NO,
"The block hash to try to fetch"},
{"peer_id", RPCArg::Type::NUM, RPCArg::Optional::NO,
"The peer to fetch it from (see getpeerinfo for peer IDs)"},
},
RPCResult{RPCResult::Type::OBJ, "", /*optional=*/false, "", {}},
RPCExamples{HelpExampleCli("getblockfrompeer",
"\"00000000c937983704a73af28acdec37b049d214a"
"dbda81d7e2a3dd146f6ed09\" 0") +
HelpExampleRpc("getblockfrompeer",
"\"00000000c937983704a73af28acdec37b049d214a"
"dbda81d7e2a3dd146f6ed09\" 0")},
[&](const RPCHelpMan &self, const Config &config,
const JSONRPCRequest &request) -> UniValue {
const NodeContext &node = EnsureAnyNodeContext(request.context);
ChainstateManager &chainman = EnsureChainman(node);
PeerManager &peerman = EnsurePeerman(node);
const BlockHash block_hash{
ParseHashV(request.params[0], "blockhash")};
const NodeId peer_id{request.params[1].getInt<int64_t>()};
const CBlockIndex *const index = WITH_LOCK(
cs_main,
return chainman.m_blockman.LookupBlockIndex(block_hash););
if (!index) {
throw JSONRPCError(RPC_MISC_ERROR, "Block header missing");
}
if (WITH_LOCK(::cs_main, return index->nStatus.hasData())) {
throw JSONRPCError(RPC_MISC_ERROR, "Block already downloaded");
}
if (const auto err{peerman.FetchBlock(config, peer_id, *index)}) {
throw JSONRPCError(RPC_MISC_ERROR, err.value());
}
return UniValue::VOBJ;
},
};
}
static RPCHelpMan getblockhash() {
return RPCHelpMan{
"getblockhash",
"Returns hash of block in best-block-chain at height provided.\n",
{
{"height", RPCArg::Type::NUM, RPCArg::Optional::NO,
"The height index"},
},
RPCResult{RPCResult::Type::STR_HEX, "", "The block hash"},
RPCExamples{HelpExampleCli("getblockhash", "1000") +
HelpExampleRpc("getblockhash", "1000")},
[&](const RPCHelpMan &self, const Config &config,
const JSONRPCRequest &request) -> UniValue {
ChainstateManager &chainman = EnsureAnyChainman(request.context);
LOCK(cs_main);
const CChain &active_chain = chainman.ActiveChain();
int nHeight = request.params[0].getInt<int>();
if (nHeight < 0 || nHeight > active_chain.Height()) {
throw JSONRPCError(RPC_INVALID_PARAMETER,
"Block height out of range");
}
const CBlockIndex *pblockindex = active_chain[nHeight];
return pblockindex->GetBlockHash().GetHex();
},
};
}
static RPCHelpMan getblockheader() {
return RPCHelpMan{
"getblockheader",
"If verbose is false, returns a string that is serialized, hex-encoded "
"data for blockheader 'hash'.\n"
"If verbose is true, returns an Object with information about "
"blockheader <hash>.\n",
{
{"blockhash", RPCArg::Type::STR_HEX, RPCArg::Optional::NO,
"The block hash"},
{"verbose", RPCArg::Type::BOOL, RPCArg::Default{true},
"true for a json object, false for the hex-encoded data"},
},
{
RPCResult{
"for verbose = true",
RPCResult::Type::OBJ,
"",
"",
{
{RPCResult::Type::STR_HEX, "hash",
"the block hash (same as provided)"},
{RPCResult::Type::NUM, "confirmations",
"The number of confirmations, or -1 if the block is not "
"on the main chain"},
{RPCResult::Type::NUM, "height",
"The block height or index"},
{RPCResult::Type::NUM, "version", "The block version"},
{RPCResult::Type::STR_HEX, "versionHex",
"The block version formatted in hexadecimal"},
{RPCResult::Type::STR_HEX, "merkleroot", "The merkle root"},
{RPCResult::Type::NUM_TIME, "time",
"The block time expressed in " + UNIX_EPOCH_TIME},
{RPCResult::Type::NUM_TIME, "mediantime",
"The median block time expressed in " + UNIX_EPOCH_TIME},
{RPCResult::Type::NUM, "nonce", "The nonce"},
{RPCResult::Type::STR_HEX, "bits", "The bits"},
{RPCResult::Type::NUM, "difficulty", "The difficulty"},
{RPCResult::Type::STR_HEX, "chainwork",
"Expected number of hashes required to produce the "
"current chain"},
{RPCResult::Type::NUM, "nTx",
"The number of transactions in the block"},
{RPCResult::Type::STR_HEX, "previousblockhash",
/* optional */ true,
"The hash of the previous block (if available)"},
{RPCResult::Type::STR_HEX, "nextblockhash",
/* optional */ true,
"The hash of the next block (if available)"},
}},
RPCResult{"for verbose=false", RPCResult::Type::STR_HEX, "",
"A string that is serialized, hex-encoded data for block "
"'hash'"},
},
RPCExamples{HelpExampleCli("getblockheader",
"\"00000000c937983704a73af28acdec37b049d214a"
"dbda81d7e2a3dd146f6ed09\"") +
HelpExampleRpc("getblockheader",
"\"00000000c937983704a73af28acdec37b049d214a"
"dbda81d7e2a3dd146f6ed09\"")},
[&](const RPCHelpMan &self, const Config &config,
const JSONRPCRequest &request) -> UniValue {
BlockHash hash(ParseHashV(request.params[0], "hash"));
bool fVerbose = true;
if (!request.params[1].isNull()) {
fVerbose = request.params[1].get_bool();
}
const CBlockIndex *pblockindex;
const CBlockIndex *tip;
{
ChainstateManager &chainman =
EnsureAnyChainman(request.context);
LOCK(cs_main);
pblockindex = chainman.m_blockman.LookupBlockIndex(hash);
tip = chainman.ActiveTip();
}
if (!pblockindex) {
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY,
"Block not found");
}
if (!fVerbose) {
CDataStream ssBlock(SER_NETWORK, PROTOCOL_VERSION);
ssBlock << pblockindex->GetBlockHeader();
std::string strHex = HexStr(ssBlock);
return strHex;
}
return blockheaderToJSON(tip, pblockindex);
},
};
}
static CBlock GetBlockChecked(BlockManager &blockman,
const CBlockIndex *pblockindex) {
CBlock block;
{
LOCK(cs_main);
if (blockman.IsBlockPruned(pblockindex)) {
throw JSONRPCError(RPC_MISC_ERROR,
"Block not available (pruned data)");
}
}
if (!blockman.ReadBlockFromDisk(block, *pblockindex)) {
// Block not found on disk. This could be because we have the block
// header in our index but not yet have the block or did not accept the
// block. Or if the block was pruned right after we released the lock
// above.
throw JSONRPCError(RPC_MISC_ERROR, "Block not found on disk");
}
return block;
}
static CBlockUndo GetUndoChecked(BlockManager &blockman,
const CBlockIndex *pblockindex) {
CBlockUndo blockUndo;
{
LOCK(cs_main);
if (blockman.IsBlockPruned(pblockindex)) {
throw JSONRPCError(RPC_MISC_ERROR,
"Undo data not available (pruned data)");
}
}
if (!blockman.UndoReadFromDisk(blockUndo, *pblockindex)) {
throw JSONRPCError(RPC_MISC_ERROR, "Can't read undo data from disk");
}
return blockUndo;
}
static RPCHelpMan getblock() {
return RPCHelpMan{
"getblock",
"If verbosity is 0 or false, returns a string that is serialized, "
"hex-encoded data for block 'hash'.\n"
"If verbosity is 1 or true, returns an Object with information about "
"block <hash>.\n"
"If verbosity is 2, returns an Object with information about block "
"<hash> and information about each transaction.\n",
{
{"blockhash", RPCArg::Type::STR_HEX, RPCArg::Optional::NO,
"The block hash"},
{"verbosity|verbose", RPCArg::Type::NUM, RPCArg::Default{1},
"0 for hex-encoded data, 1 for a json object, and 2 for json "
"object with transaction data",
RPCArgOptions{.skip_type_check = true}},
},
{
RPCResult{"for verbosity = 0", RPCResult::Type::STR_HEX, "",
"A string that is serialized, hex-encoded data for block "
"'hash'"},
RPCResult{
"for verbosity = 1",
RPCResult::Type::OBJ,
"",
"",
{
{RPCResult::Type::STR_HEX, "hash",
"the block hash (same as provided)"},
{RPCResult::Type::NUM, "confirmations",
"The number of confirmations, or -1 if the block is not "
"on the main chain"},
{RPCResult::Type::NUM, "size", "The block size"},
{RPCResult::Type::NUM, "height",
"The block height or index"},
{RPCResult::Type::NUM, "version", "The block version"},
{RPCResult::Type::STR_HEX, "versionHex",
"The block version formatted in hexadecimal"},
{RPCResult::Type::STR_HEX, "merkleroot", "The merkle root"},
{RPCResult::Type::ARR,
"tx",
"The transaction ids",
{{RPCResult::Type::STR_HEX, "", "The transaction id"}}},
{RPCResult::Type::NUM_TIME, "time",
"The block time expressed in " + UNIX_EPOCH_TIME},
{RPCResult::Type::NUM_TIME, "mediantime",
"The median block time expressed in " + UNIX_EPOCH_TIME},
{RPCResult::Type::NUM, "nonce", "The nonce"},
{RPCResult::Type::STR_HEX, "bits", "The bits"},
{RPCResult::Type::NUM, "difficulty", "The difficulty"},
{RPCResult::Type::STR_HEX, "chainwork",
"Expected number of hashes required to produce the chain "
"up to this block (in hex)"},
{RPCResult::Type::NUM, "nTx",
"The number of transactions in the block"},
{RPCResult::Type::STR_HEX, "previousblockhash",
/* optional */ true,
"The hash of the previous block (if available)"},
{RPCResult::Type::STR_HEX, "nextblockhash",
/* optional */ true,
"The hash of the next block (if available)"},
}},
RPCResult{"for verbosity = 2",
RPCResult::Type::OBJ,
"",
"",
{
{RPCResult::Type::ELISION, "",
"Same output as verbosity = 1"},
{RPCResult::Type::ARR,
"tx",
"",
{
{RPCResult::Type::OBJ,
"",
"",
{
{RPCResult::Type::ELISION, "",
"The transactions in the format of the "
"getrawtransaction RPC. Different from "
"verbosity = 1 \"tx\" result"},
{RPCResult::Type::STR_AMOUNT, "fee",
"The transaction fee in " +
Currency::get().ticker +
", omitted if block undo data is not "
"available"},
}},
}},
}},
},
RPCExamples{
HelpExampleCli("getblock", "\"00000000c937983704a73af28acdec37b049d"
"214adbda81d7e2a3dd146f6ed09\"") +
HelpExampleRpc("getblock", "\"00000000c937983704a73af28acdec37b049d"
"214adbda81d7e2a3dd146f6ed09\"")},
[&](const RPCHelpMan &self, const Config &config,
const JSONRPCRequest &request) -> UniValue {
BlockHash hash(ParseHashV(request.params[0], "blockhash"));
int verbosity = 1;
if (!request.params[1].isNull()) {
if (request.params[1].isNum()) {
verbosity = request.params[1].getInt<int>();
} else {
verbosity = request.params[1].get_bool() ? 1 : 0;
}
}
const CBlockIndex *pblockindex;
const CBlockIndex *tip;
ChainstateManager &chainman = EnsureAnyChainman(request.context);
{
LOCK(cs_main);
pblockindex = chainman.m_blockman.LookupBlockIndex(hash);
tip = chainman.ActiveTip();
if (!pblockindex) {
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY,
"Block not found");
}
}
const CBlock block =
GetBlockChecked(chainman.m_blockman, pblockindex);
if (verbosity <= 0) {
CDataStream ssBlock(SER_NETWORK,
PROTOCOL_VERSION | RPCSerializationFlags());
ssBlock << block;
std::string strHex = HexStr(ssBlock);
return strHex;
}
return blockToJSON(chainman.m_blockman, block, tip, pblockindex,
verbosity >= 2);
},
};
}
std::optional<int> GetPruneHeight(const BlockManager &blockman,
const CChain &chain) {
AssertLockHeld(::cs_main);
// Search for the last block missing block data or undo data. Don't let the
// search consider the genesis block, because the genesis block does not
// have undo data, but should not be considered pruned.
const CBlockIndex *first_block{chain[1]};
const CBlockIndex *chain_tip{chain.Tip()};
// If there are no blocks after the genesis block, or no blocks at all,
// nothing is pruned.
if (!first_block || !chain_tip) {
return std::nullopt;
}
// If the chain tip is pruned, everything is pruned.
if (!(chain_tip->nStatus.hasData() && chain_tip->nStatus.hasUndo())) {
return chain_tip->nHeight;
}
// Get first block with data, after the last block without data.
// This is the start of the unpruned range of blocks.
const CBlockIndex *first_unpruned{CHECK_NONFATAL(
blockman.GetFirstBlock(*chain_tip,
/*status_test=*/[](const BlockStatus &status) {
return status.hasData() && status.hasUndo();
}))};
if (first_unpruned == first_block) {
// All blocks between first_block and chain_tip have data, so nothing is
// pruned.
return std::nullopt;
}
// Block before the first unpruned block is the last pruned block.
return CHECK_NONFATAL(first_unpruned->pprev)->nHeight;
}
static RPCHelpMan pruneblockchain() {
return RPCHelpMan{
"pruneblockchain",
"",
{
{"height", RPCArg::Type::NUM, RPCArg::Optional::NO,
"The block height to prune up to. May be set to a discrete "
"height, or to a " +
UNIX_EPOCH_TIME +
"\n"
" to prune blocks whose block time is at "
"least 2 hours older than the provided timestamp."},
},
RPCResult{RPCResult::Type::NUM, "", "Height of the last block pruned"},
RPCExamples{HelpExampleCli("pruneblockchain", "1000") +
HelpExampleRpc("pruneblockchain", "1000")},
[&](const RPCHelpMan &self, const Config &config,
const JSONRPCRequest &request) -> UniValue {
ChainstateManager &chainman = EnsureAnyChainman(request.context);
if (!chainman.m_blockman.IsPruneMode()) {
throw JSONRPCError(
RPC_MISC_ERROR,
"Cannot prune blocks because node is not in prune mode.");
}
LOCK(cs_main);
Chainstate &active_chainstate = chainman.ActiveChainstate();
CChain &active_chain = active_chainstate.m_chain;
int heightParam = request.params[0].getInt<int>();
if (heightParam < 0) {
throw JSONRPCError(RPC_INVALID_PARAMETER,
"Negative block height.");
}
// Height value more than a billion is too high to be a block
// height, and too low to be a block time (corresponds to timestamp
// from Sep 2001).
if (heightParam > 1000000000) {
// Add a 2 hour buffer to include blocks which might have had
// old timestamps
const CBlockIndex *pindex = active_chain.FindEarliestAtLeast(
heightParam - TIMESTAMP_WINDOW, 0);
if (!pindex) {
throw JSONRPCError(RPC_INVALID_PARAMETER,
"Could not find block with at least the "
"specified timestamp.");
}
heightParam = pindex->nHeight;
}
unsigned int height = (unsigned int)heightParam;
unsigned int chainHeight = (unsigned int)active_chain.Height();
if (chainHeight < config.GetChainParams().PruneAfterHeight()) {
throw JSONRPCError(RPC_MISC_ERROR,
"Blockchain is too short for pruning.");
} 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.\n");
height = chainHeight - MIN_BLOCKS_TO_KEEP;
}
PruneBlockFilesManual(active_chainstate, height);
return GetPruneHeight(chainman.m_blockman, active_chain)
.value_or(-1);
},
};
}
static CoinStatsHashType ParseHashType(const std::string &hash_type_input) {
if (hash_type_input == "hash_serialized") {
return CoinStatsHashType::HASH_SERIALIZED;
} else if (hash_type_input == "muhash") {
return CoinStatsHashType::MUHASH;
} else if (hash_type_input == "none") {
return CoinStatsHashType::NONE;
} else {
throw JSONRPCError(
RPC_INVALID_PARAMETER,
strprintf("%s is not a valid hash_type", hash_type_input));
}
}
static RPCHelpMan gettxoutsetinfo() {
return RPCHelpMan{
"gettxoutsetinfo",
"Returns statistics about the unspent transaction output set.\n"
"Note this call may take some time if you are not using "
"coinstatsindex.\n",
{
{"hash_type", RPCArg::Type::STR, RPCArg::Default{"hash_serialized"},
"Which UTXO set hash should be calculated. Options: "
"'hash_serialized' (the legacy algorithm), 'muhash', 'none'."},
{"hash_or_height", RPCArg::Type::NUM, RPCArg::Optional::OMITTED,
"The block hash or height of the target height (only available "
"with coinstatsindex).",
RPCArgOptions{.skip_type_check = true,
.type_str = {"", "string or numeric"}}},
{"use_index", RPCArg::Type::BOOL, RPCArg::Default{true},
"Use coinstatsindex, if available."},
},
RPCResult{
RPCResult::Type::OBJ,
"",
"",
{
{RPCResult::Type::NUM, "height",
"The current block height (index)"},
{RPCResult::Type::STR_HEX, "bestblock",
"The hash of the block at the tip of the chain"},
{RPCResult::Type::NUM, "txouts",
"The number of unspent transaction outputs"},
{RPCResult::Type::NUM, "bogosize",
"Database-independent, meaningless metric indicating "
"the UTXO set size"},
{RPCResult::Type::STR_HEX, "hash_serialized",
/* optional */ true,
"The serialized hash (only present if 'hash_serialized' "
"hash_type is chosen)"},
{RPCResult::Type::STR_HEX, "muhash", /* optional */ true,
"The serialized hash (only present if 'muhash' "
"hash_type is chosen)"},
{RPCResult::Type::NUM, "transactions",
"The number of transactions with unspent outputs (not "
"available when coinstatsindex is used)"},
{RPCResult::Type::NUM, "disk_size",
"The estimated size of the chainstate on disk (not "
"available when coinstatsindex is used)"},
{RPCResult::Type::STR_AMOUNT, "total_amount",
"The total amount"},
{RPCResult::Type::STR_AMOUNT, "total_unspendable_amount",
"The total amount of coins permanently excluded from the UTXO "
"set (only available if coinstatsindex is used)"},
{RPCResult::Type::OBJ,
"block_info",
"Info on amounts in the block at this block height (only "
"available if coinstatsindex is used)",
{{RPCResult::Type::STR_AMOUNT, "prevout_spent",
"Total amount of all prevouts spent in this block"},
{RPCResult::Type::STR_AMOUNT, "coinbase",
"Coinbase subsidy amount of this block"},
{RPCResult::Type::STR_AMOUNT, "new_outputs_ex_coinbase",
"Total amount of new outputs created by this block"},
{RPCResult::Type::STR_AMOUNT, "unspendable",
"Total amount of unspendable outputs created in this block"},
{RPCResult::Type::OBJ,
"unspendables",
"Detailed view of the unspendable categories",
{
{RPCResult::Type::STR_AMOUNT, "genesis_block",
"The unspendable amount of the Genesis block subsidy"},
{RPCResult::Type::STR_AMOUNT, "bip30",
"Transactions overridden by duplicates (no longer "
"possible with BIP30)"},
{RPCResult::Type::STR_AMOUNT, "scripts",
"Amounts sent to scripts that are unspendable (for "
"example OP_RETURN outputs)"},
{RPCResult::Type::STR_AMOUNT, "unclaimed_rewards",
"Fee rewards that miners did not claim in their "
"coinbase transaction"},
}}}},
}},
RPCExamples{
HelpExampleCli("gettxoutsetinfo", "") +
HelpExampleCli("gettxoutsetinfo", R"("none")") +
HelpExampleCli("gettxoutsetinfo", R"("none" 1000)") +
HelpExampleCli(
"gettxoutsetinfo",
R"("none" '"00000000c937983704a73af28acdec37b049d214adbda81d7e2a3dd146f6ed09"')") +
HelpExampleRpc("gettxoutsetinfo", "") +
HelpExampleRpc("gettxoutsetinfo", R"("none")") +
HelpExampleRpc("gettxoutsetinfo", R"("none", 1000)") +
HelpExampleRpc(
"gettxoutsetinfo",
R"("none", "00000000c937983704a73af28acdec37b049d214adbda81d7e2a3dd146f6ed09")")},
[&](const RPCHelpMan &self, const Config &config,
const JSONRPCRequest &request) -> UniValue {
UniValue ret(UniValue::VOBJ);
const CBlockIndex *pindex{nullptr};
const CoinStatsHashType hash_type{
request.params[0].isNull()
? CoinStatsHashType::HASH_SERIALIZED
: ParseHashType(request.params[0].get_str())};
bool index_requested =
request.params[2].isNull() || request.params[2].get_bool();
NodeContext &node = EnsureAnyNodeContext(request.context);
ChainstateManager &chainman = EnsureChainman(node);
Chainstate &active_chainstate = chainman.ActiveChainstate();
active_chainstate.ForceFlushStateToDisk();
CCoinsView *coins_view;
BlockManager *blockman;
{
LOCK(::cs_main);
coins_view = &active_chainstate.CoinsDB();
blockman = &active_chainstate.m_blockman;
pindex = blockman->LookupBlockIndex(coins_view->GetBestBlock());
}
if (!request.params[1].isNull()) {
if (!g_coin_stats_index) {
throw JSONRPCError(RPC_INVALID_PARAMETER,
"Querying specific block heights "
"requires coinstatsindex");
}
if (hash_type == CoinStatsHashType::HASH_SERIALIZED) {
throw JSONRPCError(RPC_INVALID_PARAMETER,
"hash_serialized hash type cannot be "
"queried for a specific block");
}
pindex = ParseHashOrHeight(request.params[1], chainman);
}
if (index_requested && g_coin_stats_index) {
if (!g_coin_stats_index->BlockUntilSyncedToCurrentChain()) {
const IndexSummary summary{
g_coin_stats_index->GetSummary()};
// If a specific block was requested and the index has
// already synced past that height, we can return the data
// already even though the index is not fully synced yet.
if (pindex->nHeight > summary.best_block_height) {
throw JSONRPCError(
RPC_INTERNAL_ERROR,
strprintf(
"Unable to get data because coinstatsindex is "
"still syncing. Current height: %d",
summary.best_block_height));
}
}
}
const std::optional<CCoinsStats> maybe_stats = GetUTXOStats(
coins_view, *blockman, hash_type, node.rpc_interruption_point,
pindex, index_requested);
if (maybe_stats.has_value()) {
const CCoinsStats &stats = maybe_stats.value();
ret.pushKV("height", int64_t(stats.nHeight));
ret.pushKV("bestblock", stats.hashBlock.GetHex());
ret.pushKV("txouts", int64_t(stats.nTransactionOutputs));
ret.pushKV("bogosize", int64_t(stats.nBogoSize));
if (hash_type == CoinStatsHashType::HASH_SERIALIZED) {
ret.pushKV("hash_serialized",
stats.hashSerialized.GetHex());
}
if (hash_type == CoinStatsHashType::MUHASH) {
ret.pushKV("muhash", stats.hashSerialized.GetHex());
}
ret.pushKV("total_amount", stats.nTotalAmount);
if (!stats.index_used) {
ret.pushKV("transactions",
static_cast<int64_t>(stats.nTransactions));
ret.pushKV("disk_size", stats.nDiskSize);
} else {
ret.pushKV("total_unspendable_amount",
stats.total_unspendable_amount);
CCoinsStats prev_stats{};
if (pindex->nHeight > 0) {
const std::optional<CCoinsStats> maybe_prev_stats =
GetUTXOStats(coins_view, *blockman, hash_type,
node.rpc_interruption_point,
pindex->pprev, index_requested);
if (!maybe_prev_stats) {
throw JSONRPCError(RPC_INTERNAL_ERROR,
"Unable to read UTXO set");
}
prev_stats = maybe_prev_stats.value();
}
UniValue block_info(UniValue::VOBJ);
block_info.pushKV(
"prevout_spent",
stats.total_prevout_spent_amount -
prev_stats.total_prevout_spent_amount);
block_info.pushKV("coinbase",
stats.total_coinbase_amount -
prev_stats.total_coinbase_amount);
block_info.pushKV(
"new_outputs_ex_coinbase",
stats.total_new_outputs_ex_coinbase_amount -
prev_stats.total_new_outputs_ex_coinbase_amount);
block_info.pushKV("unspendable",
stats.total_unspendable_amount -
prev_stats.total_unspendable_amount);
UniValue unspendables(UniValue::VOBJ);
unspendables.pushKV(
"genesis_block",
stats.total_unspendables_genesis_block -
prev_stats.total_unspendables_genesis_block);
unspendables.pushKV(
"bip30", stats.total_unspendables_bip30 -
prev_stats.total_unspendables_bip30);
unspendables.pushKV(
"scripts", stats.total_unspendables_scripts -
prev_stats.total_unspendables_scripts);
unspendables.pushKV(
"unclaimed_rewards",
stats.total_unspendables_unclaimed_rewards -
prev_stats.total_unspendables_unclaimed_rewards);
block_info.pushKV("unspendables", unspendables);
ret.pushKV("block_info", block_info);
}
} else {
throw JSONRPCError(RPC_INTERNAL_ERROR,
"Unable to read UTXO set");
}
return ret;
},
};
}
RPCHelpMan gettxout() {
return RPCHelpMan{
"gettxout",
"Returns details about an unspent transaction output.\n",
{
{"txid", RPCArg::Type::STR_HEX, RPCArg::Optional::NO,
"The transaction id"},
{"n", RPCArg::Type::NUM, RPCArg::Optional::NO, "vout number"},
{"include_mempool", RPCArg::Type::BOOL, RPCArg::Default{true},
"Whether to include the mempool. Note that an unspent output that "
"is spent in the mempool won't appear."},
},
{
RPCResult{"If the UTXO was not found", RPCResult::Type::NONE, "",
""},
RPCResult{
"Otherwise",
RPCResult::Type::OBJ,
"",
"",
{
{RPCResult::Type::STR_HEX, "bestblock",
"The hash of the block at the tip of the chain"},
{RPCResult::Type::NUM, "confirmations",
"The number of confirmations"},
{RPCResult::Type::STR_AMOUNT, "value",
"The transaction value in " + Currency::get().ticker},
{RPCResult::Type::OBJ,
"scriptPubKey",
"",
{
{RPCResult::Type::STR_HEX, "asm", ""},
{RPCResult::Type::STR_HEX, "hex", ""},
{RPCResult::Type::NUM, "reqSigs",
"Number of required signatures"},
{RPCResult::Type::STR_HEX, "type",
"The type, eg pubkeyhash"},
{RPCResult::Type::ARR,
"addresses",
"array of eCash addresses",
{{RPCResult::Type::STR, "address", "eCash address"}}},
}},
{RPCResult::Type::BOOL, "coinbase", "Coinbase or not"},
}},
},
RPCExamples{"\nGet unspent transactions\n" +
HelpExampleCli("listunspent", "") + "\nView the details\n" +
HelpExampleCli("gettxout", "\"txid\" 1") +
"\nAs a JSON-RPC call\n" +
HelpExampleRpc("gettxout", "\"txid\", 1")},
[&](const RPCHelpMan &self, const Config &config,
const JSONRPCRequest &request) -> UniValue {
NodeContext &node = EnsureAnyNodeContext(request.context);
ChainstateManager &chainman = EnsureChainman(node);
LOCK(cs_main);
UniValue ret(UniValue::VOBJ);
TxId txid(ParseHashV(request.params[0], "txid"));
int n = request.params[1].getInt<int>();
COutPoint out(txid, n);
bool fMempool = true;
if (!request.params[2].isNull()) {
fMempool = request.params[2].get_bool();
}
Coin coin;
Chainstate &active_chainstate = chainman.ActiveChainstate();
CCoinsViewCache *coins_view = &active_chainstate.CoinsTip();
if (fMempool) {
const CTxMemPool &mempool = EnsureMemPool(node);
LOCK(mempool.cs);
CCoinsViewMemPool view(coins_view, mempool);
if (!view.GetCoin(out, coin) || mempool.isSpent(out)) {
return NullUniValue;
}
} else {
if (!coins_view->GetCoin(out, coin)) {
return NullUniValue;
}
}
const CBlockIndex *pindex =
active_chainstate.m_blockman.LookupBlockIndex(
coins_view->GetBestBlock());
ret.pushKV("bestblock", pindex->GetBlockHash().GetHex());
if (coin.GetHeight() == MEMPOOL_HEIGHT) {
ret.pushKV("confirmations", 0);
} else {
ret.pushKV("confirmations",
int64_t(pindex->nHeight - coin.GetHeight() + 1));
}
ret.pushKV("value", coin.GetTxOut().nValue);
UniValue o(UniValue::VOBJ);
ScriptPubKeyToUniv(coin.GetTxOut().scriptPubKey, o, true);
ret.pushKV("scriptPubKey", o);
ret.pushKV("coinbase", coin.IsCoinBase());
return ret;
},
};
}
static RPCHelpMan verifychain() {
return RPCHelpMan{
"verifychain",
"Verifies blockchain database.\n",
{
{"checklevel", RPCArg::Type::NUM,
RPCArg::DefaultHint{
strprintf("%d, range=0-4", DEFAULT_CHECKLEVEL)},
strprintf("How thorough the block verification is:\n - %s",
Join(CHECKLEVEL_DOC, "\n- "))},
{"nblocks", RPCArg::Type::NUM,
RPCArg::DefaultHint{strprintf("%d, 0=all", DEFAULT_CHECKBLOCKS)},
"The number of blocks to check."},
},
RPCResult{RPCResult::Type::BOOL, "",
"Verification finished successfully. If false, check "
"debug.log for reason."},
RPCExamples{HelpExampleCli("verifychain", "") +
HelpExampleRpc("verifychain", "")},
[&](const RPCHelpMan &self, const Config &config,
const JSONRPCRequest &request) -> UniValue {
const int check_level{request.params[0].isNull()
? DEFAULT_CHECKLEVEL
: request.params[0].getInt<int>()};
const int check_depth{request.params[1].isNull()
? DEFAULT_CHECKBLOCKS
: request.params[1].getInt<int>()};
ChainstateManager &chainman = EnsureAnyChainman(request.context);
LOCK(cs_main);
Chainstate &active_chainstate = chainman.ActiveChainstate();
return CVerifyDB(chainman.GetNotifications())
.VerifyDB(active_chainstate,
active_chainstate.CoinsTip(), check_level,
check_depth) == VerifyDBResult::SUCCESS;
},
};
}
RPCHelpMan getblockchaininfo() {
return RPCHelpMan{
"getblockchaininfo",
"Returns an object containing various state info regarding blockchain "
"processing.\n",
{},
RPCResult{
RPCResult::Type::OBJ,
"",
"",
{
{RPCResult::Type::STR, "chain",
"current network name (main, test, regtest)"},
{RPCResult::Type::NUM, "blocks",
"the height of the most-work fully-validated chain. The "
"genesis block has height 0"},
{RPCResult::Type::NUM, "headers",
"the current number of headers we have validated"},
{RPCResult::Type::STR, "bestblockhash",
"the hash of the currently best block"},
{RPCResult::Type::NUM, "difficulty", "the current difficulty"},
{RPCResult::Type::NUM_TIME, "time",
"The block time expressed in " + UNIX_EPOCH_TIME},
{RPCResult::Type::NUM_TIME, "mediantime",
"The median block time expressed in " + UNIX_EPOCH_TIME},
{RPCResult::Type::NUM, "verificationprogress",
"estimate of verification progress [0..1]"},
{RPCResult::Type::BOOL, "initialblockdownload",
"(debug information) estimate of whether this node is in "
"Initial Block Download mode"},
{RPCResult::Type::STR_HEX, "chainwork",
"total amount of work in active chain, in hexadecimal"},
{RPCResult::Type::NUM, "size_on_disk",
"the estimated size of the block and undo files on disk"},
{RPCResult::Type::BOOL, "pruned",
"if the blocks are subject to pruning"},
{RPCResult::Type::NUM, "pruneheight",
"lowest-height complete block stored (only present if pruning "
"is enabled)"},
{RPCResult::Type::BOOL, "automatic_pruning",
"whether automatic pruning is enabled (only present if "
"pruning is enabled)"},
{RPCResult::Type::NUM, "prune_target_size",
"the target size used by pruning (only present if automatic "
"pruning is enabled)"},
{RPCResult::Type::STR, "warnings",
"any network and blockchain warnings"},
}},
RPCExamples{HelpExampleCli("getblockchaininfo", "") +
HelpExampleRpc("getblockchaininfo", "")},
[&](const RPCHelpMan &self, const Config &config,
const JSONRPCRequest &request) -> UniValue {
const CChainParams &chainparams = config.GetChainParams();
ChainstateManager &chainman = EnsureAnyChainman(request.context);
LOCK(cs_main);
Chainstate &active_chainstate = chainman.ActiveChainstate();
const CBlockIndex &tip{
*CHECK_NONFATAL(active_chainstate.m_chain.Tip())};
const int height{tip.nHeight};
UniValue obj(UniValue::VOBJ);
obj.pushKV("chain", chainparams.GetChainTypeString());
obj.pushKV("blocks", height);
obj.pushKV("headers", chainman.m_best_header
? chainman.m_best_header->nHeight
: -1);
obj.pushKV("bestblockhash", tip.GetBlockHash().GetHex());
obj.pushKV("difficulty", GetDifficulty(&tip));
obj.pushKV("time", tip.GetBlockTime());
obj.pushKV("mediantime", tip.GetMedianTimePast());
obj.pushKV(
"verificationprogress",
GuessVerificationProgress(chainman.GetParams().TxData(), &tip));
obj.pushKV("initialblockdownload",
chainman.IsInitialBlockDownload());
obj.pushKV("chainwork", tip.nChainWork.GetHex());
obj.pushKV("size_on_disk",
chainman.m_blockman.CalculateCurrentUsage());
obj.pushKV("pruned", chainman.m_blockman.IsPruneMode());
if (chainman.m_blockman.IsPruneMode()) {
const auto prune_height{GetPruneHeight(
chainman.m_blockman, active_chainstate.m_chain)};
obj.pushKV("pruneheight",
prune_height ? prune_height.value() + 1 : 0);
const bool automatic_pruning{
chainman.m_blockman.GetPruneTarget() !=
BlockManager::PRUNE_TARGET_MANUAL};
obj.pushKV("automatic_pruning", automatic_pruning);
if (automatic_pruning) {
obj.pushKV("prune_target_size",
chainman.m_blockman.GetPruneTarget());
}
}
obj.pushKV("warnings", GetWarnings(false).original);
return obj;
},
};
}
/** Comparison function for sorting the getchaintips heads. */
struct CompareBlocksByHeight {
bool operator()(const CBlockIndex *a, const CBlockIndex *b) const {
// Make sure that unequal blocks with the same height do not compare
// equal. Use the pointers themselves to make a distinction.
if (a->nHeight != b->nHeight) {
return (a->nHeight > b->nHeight);
}
return a < b;
}
};
static RPCHelpMan getchaintips() {
return RPCHelpMan{
"getchaintips",
"Return information about all known tips in the block tree, including "
"the main chain as well as orphaned branches.\n",
{},
RPCResult{
RPCResult::Type::ARR,
"",
"",
{{RPCResult::Type::OBJ,
"",
"",
{
{RPCResult::Type::NUM, "height", "height of the chain tip"},
{RPCResult::Type::STR_HEX, "hash", "block hash of the tip"},
{RPCResult::Type::NUM, "branchlen",
"zero for main chain, otherwise length of branch connecting "
"the tip to the main chain"},
{RPCResult::Type::STR, "status",
"status of the chain, \"active\" for the main chain\n"
"Possible values for status:\n"
"1. \"invalid\" This branch contains at "
"least one invalid block\n"
"2. \"parked\" This branch contains at "
"least one parked block\n"
"3. \"headers-only\" Not all blocks for this "
"branch are available, but the headers are valid\n"
"4. \"valid-headers\" All blocks are available for "
"this branch, but they were never fully validated\n"
"5. \"valid-fork\" This branch is not part of "
"the active chain, but is fully validated\n"
"6. \"active\" This is the tip of the "
"active main chain, which is certainly valid"},
}}}},
RPCExamples{HelpExampleCli("getchaintips", "") +
HelpExampleRpc("getchaintips", "")},
[&](const RPCHelpMan &self, const Config &config,
const JSONRPCRequest &request) -> UniValue {
ChainstateManager &chainman = EnsureAnyChainman(request.context);
LOCK(cs_main);
CChain &active_chain = chainman.ActiveChain();
/**
* Idea: The set of chain tips is the active chain tip, plus orphan
* blocks which do not have another orphan building off of them.
* Algorithm:
* - Make one pass through BlockIndex(), picking out the orphan
* blocks, and also storing a set of the orphan block's pprev
* pointers.
* - Iterate through the orphan blocks. If the block isn't pointed
* to by another orphan, it is a chain tip.
* - Add the active chain tip
*/
std::set<const CBlockIndex *, CompareBlocksByHeight> setTips;
std::set<const CBlockIndex *> setOrphans;
std::set<const CBlockIndex *> setPrevs;
for (const auto &[_, block_index] : chainman.BlockIndex()) {
if (!active_chain.Contains(&block_index)) {
setOrphans.insert(&block_index);
setPrevs.insert(block_index.pprev);
}
}
for (std::set<const CBlockIndex *>::iterator it =
setOrphans.begin();
it != setOrphans.end(); ++it) {
if (setPrevs.erase(*it) == 0) {
setTips.insert(*it);
}
}
// Always report the currently active tip.
setTips.insert(active_chain.Tip());
/* Construct the output array. */
UniValue res(UniValue::VARR);
for (const CBlockIndex *block : setTips) {
UniValue obj(UniValue::VOBJ);
obj.pushKV("height", block->nHeight);
obj.pushKV("hash", block->phashBlock->GetHex());
const int branchLen =
block->nHeight - active_chain.FindFork(block)->nHeight;
obj.pushKV("branchlen", branchLen);
std::string status;
if (active_chain.Contains(block)) {
// This block is part of the currently active chain.
status = "active";
} else if (block->nStatus.isInvalid()) {
// This block or one of its ancestors is invalid.
status = "invalid";
} else if (block->nStatus.isOnParkedChain()) {
// This block or one of its ancestors is parked.
status = "parked";
} else if (!block->HaveNumChainTxs()) {
// This block cannot be connected because full block data
// for it or one of its parents is missing.
status = "headers-only";
} else if (block->IsValid(BlockValidity::SCRIPTS)) {
// This block is fully validated, but no longer part of the
// active chain. It was probably the active block once, but
// was reorganized.
status = "valid-fork";
} else if (block->IsValid(BlockValidity::TREE)) {
// The headers for this block are valid, but it has not been
// validated. It was probably never part of the most-work
// chain.
status = "valid-headers";
} else {
// No clue.
status = "unknown";
}
obj.pushKV("status", status);
res.push_back(obj);
}
return res;
},
};
}
static RPCHelpMan preciousblock() {
return RPCHelpMan{
"preciousblock",
"Treats a block as if it were received before others with the same "
"work.\n"
"\nA later preciousblock call can override the effect of an earlier "
"one.\n"
"\nThe effects of preciousblock are not retained across restarts.\n",
{
{"blockhash", RPCArg::Type::STR_HEX, RPCArg::Optional::NO,
"the hash of the block to mark as precious"},
},
RPCResult{RPCResult::Type::NONE, "", ""},
RPCExamples{HelpExampleCli("preciousblock", "\"blockhash\"") +
HelpExampleRpc("preciousblock", "\"blockhash\"")},
[&](const RPCHelpMan &self, const Config &config,
const JSONRPCRequest &request) -> UniValue {
BlockHash hash(ParseHashV(request.params[0], "blockhash"));
CBlockIndex *pblockindex;
NodeContext &node = EnsureAnyNodeContext(request.context);
ChainstateManager &chainman = EnsureChainman(node);
{
LOCK(cs_main);
pblockindex = chainman.m_blockman.LookupBlockIndex(hash);
if (!pblockindex) {
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY,
"Block not found");
}
}
BlockValidationState state;
chainman.ActiveChainstate().PreciousBlock(state, pblockindex,
node.avalanche.get());
if (!state.IsValid()) {
throw JSONRPCError(RPC_DATABASE_ERROR, state.GetRejectReason());
}
// Block to make sure wallet/indexers sync before returning
SyncWithValidationInterfaceQueue();
return NullUniValue;
},
};
}
static void InvalidateBlock(ChainstateManager &chainman,
avalanche::Processor *const avalanche,
const BlockHash &block_hash) {
BlockValidationState state;
CBlockIndex *pblockindex;
{
LOCK(chainman.GetMutex());
pblockindex = chainman.m_blockman.LookupBlockIndex(block_hash);
if (!pblockindex) {
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Block not found");
}
}
chainman.ActiveChainstate().InvalidateBlock(state, pblockindex);
if (state.IsValid()) {
chainman.ActiveChainstate().ActivateBestChain(state, /*pblock=*/nullptr,
avalanche);
}
if (!state.IsValid()) {
throw JSONRPCError(RPC_DATABASE_ERROR, state.ToString());
}
}
static RPCHelpMan invalidateblock() {
return RPCHelpMan{
"invalidateblock",
"Permanently marks a block as invalid, as if it violated a consensus "
"rule.\n",
{
{"blockhash", RPCArg::Type::STR_HEX, RPCArg::Optional::NO,
"the hash of the block to mark as invalid"},
},
RPCResult{RPCResult::Type::NONE, "", ""},
RPCExamples{HelpExampleCli("invalidateblock", "\"blockhash\"") +
HelpExampleRpc("invalidateblock", "\"blockhash\"")},
[&](const RPCHelpMan &self, const Config &config,
const JSONRPCRequest &request) -> UniValue {
NodeContext &node = EnsureAnyNodeContext(request.context);
ChainstateManager &chainman = EnsureChainman(node);
const BlockHash hash(ParseHashV(request.params[0], "blockhash"));
InvalidateBlock(chainman, node.avalanche.get(), hash);
// Block to make sure wallet/indexers sync before returning
SyncWithValidationInterfaceQueue();
return NullUniValue;
},
};
}
RPCHelpMan parkblock() {
return RPCHelpMan{
"parkblock",
"Marks a block as parked.\n",
{
{"blockhash", RPCArg::Type::STR_HEX, RPCArg::Optional::NO,
"the hash of the block to park"},
},
RPCResult{RPCResult::Type::NONE, "", ""},
RPCExamples{HelpExampleCli("parkblock", "\"blockhash\"") +
HelpExampleRpc("parkblock", "\"blockhash\"")},
[&](const RPCHelpMan &self, const Config &config,
const JSONRPCRequest &request) -> UniValue {
const std::string strHash = request.params[0].get_str();
const BlockHash hash(uint256S(strHash));
BlockValidationState state;
NodeContext &node = EnsureAnyNodeContext(request.context);
ChainstateManager &chainman = EnsureChainman(node);
Chainstate &active_chainstate = chainman.ActiveChainstate();
CBlockIndex *pblockindex = nullptr;
{
LOCK(cs_main);
pblockindex = chainman.m_blockman.LookupBlockIndex(hash);
if (!pblockindex) {
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY,
"Block not found");
}
if (active_chainstate.IsBlockAvalancheFinalized(pblockindex)) {
// Reset avalanche finalization if we park a finalized
// block.
active_chainstate.ClearAvalancheFinalizedBlock();
}
}
active_chainstate.ParkBlock(state, pblockindex);
if (state.IsValid()) {
active_chainstate.ActivateBestChain(state, /*pblock=*/nullptr,
node.avalanche.get());
}
if (!state.IsValid()) {
throw JSONRPCError(RPC_DATABASE_ERROR, state.GetRejectReason());
}
// Block to make sure wallet/indexers sync before returning
SyncWithValidationInterfaceQueue();
return NullUniValue;
},
};
}
static void ReconsiderBlock(ChainstateManager &chainman,
avalanche::Processor *const avalanche,
const BlockHash &block_hash) {
{
LOCK(chainman.GetMutex());
CBlockIndex *pblockindex =
chainman.m_blockman.LookupBlockIndex(block_hash);
if (!pblockindex) {
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Block not found");
}
chainman.ActiveChainstate().ResetBlockFailureFlags(pblockindex);
chainman.RecalculateBestHeader();
}
BlockValidationState state;
chainman.ActiveChainstate().ActivateBestChain(state, /*pblock=*/nullptr,
avalanche);
if (!state.IsValid()) {
throw JSONRPCError(RPC_DATABASE_ERROR, state.ToString());
}
}
static RPCHelpMan reconsiderblock() {
return RPCHelpMan{
"reconsiderblock",
"Removes invalidity status of a block, its ancestors and its"
"descendants, reconsider them for activation.\n"
"This can be used to undo the effects of invalidateblock.\n",
{
{"blockhash", RPCArg::Type::STR_HEX, RPCArg::Optional::NO,
"the hash of the block to reconsider"},
},
RPCResult{RPCResult::Type::NONE, "", ""},
RPCExamples{HelpExampleCli("reconsiderblock", "\"blockhash\"") +
HelpExampleRpc("reconsiderblock", "\"blockhash\"")},
[&](const RPCHelpMan &self, const Config &config,
const JSONRPCRequest &request) -> UniValue {
NodeContext &node = EnsureAnyNodeContext(request.context);
ChainstateManager &chainman = EnsureChainman(node);
const BlockHash hash(ParseHashV(request.params[0], "blockhash"));
ReconsiderBlock(chainman, node.avalanche.get(), hash);
// Block to make sure wallet/indexers sync before returning
SyncWithValidationInterfaceQueue();
return NullUniValue;
},
};
}
RPCHelpMan unparkblock() {
return RPCHelpMan{
"unparkblock",
"Removes parked status of a block and its descendants, reconsider "
"them for activation.\n"
"This can be used to undo the effects of parkblock.\n",
{
{"blockhash", RPCArg::Type::STR_HEX, RPCArg::Optional::NO,
"the hash of the block to unpark"},
},
RPCResult{RPCResult::Type::NONE, "", ""},
RPCExamples{HelpExampleCli("unparkblock", "\"blockhash\"") +
HelpExampleRpc("unparkblock", "\"blockhash\"")},
[&](const RPCHelpMan &self, const Config &config,
const JSONRPCRequest &request) -> UniValue {
const std::string strHash = request.params[0].get_str();
NodeContext &node = EnsureAnyNodeContext(request.context);
ChainstateManager &chainman = EnsureChainman(node);
const BlockHash hash(uint256S(strHash));
Chainstate &active_chainstate = chainman.ActiveChainstate();
{
LOCK(cs_main);
CBlockIndex *pblockindex =
chainman.m_blockman.LookupBlockIndex(hash);
if (!pblockindex) {
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY,
"Block not found");
}
if (!pblockindex->nStatus.isOnParkedChain()) {
// Block to unpark is not parked so there is nothing to do.
return NullUniValue;
}
const CBlockIndex *tip = active_chainstate.m_chain.Tip();
if (tip) {
const CBlockIndex *ancestor =
LastCommonAncestor(tip, pblockindex);
if (active_chainstate.IsBlockAvalancheFinalized(ancestor)) {
// Only reset avalanche finalization if we unpark a
// block that might conflict with avalanche finalized
// blocks.
active_chainstate.ClearAvalancheFinalizedBlock();
}
}
active_chainstate.UnparkBlockAndChildren(pblockindex);
}
BlockValidationState state;
active_chainstate.ActivateBestChain(state, /*pblock=*/nullptr,
node.avalanche.get());
if (!state.IsValid()) {
throw JSONRPCError(RPC_DATABASE_ERROR, state.GetRejectReason());
}
// Block to make sure wallet/indexers sync before returning
SyncWithValidationInterfaceQueue();
return NullUniValue;
},
};
}
static RPCHelpMan getchaintxstats() {
return RPCHelpMan{
"getchaintxstats",
"Compute statistics about the total number and rate of transactions "
"in the chain.\n",
{
{"nblocks", RPCArg::Type::NUM, RPCArg::DefaultHint{"one month"},
"Size of the window in number of blocks"},
{"blockhash", RPCArg::Type::STR_HEX,
RPCArg::DefaultHint{"chain tip"},
"The hash of the block that ends the window."},
},
RPCResult{RPCResult::Type::OBJ,
"",
"",
{
{RPCResult::Type::NUM_TIME, "time",
"The timestamp for the final block in the window, "
"expressed in " +
UNIX_EPOCH_TIME},
{RPCResult::Type::NUM, "txcount",
"The total number of transactions in the chain up to "
"that point"},
{RPCResult::Type::STR_HEX, "window_final_block_hash",
"The hash of the final block in the window"},
{RPCResult::Type::NUM, "window_final_block_height",
"The height of the final block in the window."},
{RPCResult::Type::NUM, "window_block_count",
"Size of the window in number of blocks"},
{RPCResult::Type::NUM, "window_tx_count",
"The number of transactions in the window. Only "
"returned if \"window_block_count\" is > 0"},
{RPCResult::Type::NUM, "window_interval",
"The elapsed time in the window in seconds. Only "
"returned if \"window_block_count\" is > 0"},
{RPCResult::Type::NUM, "txrate",
"The average rate of transactions per second in the "
"window. Only returned if \"window_interval\" is > 0"},
}},
RPCExamples{HelpExampleCli("getchaintxstats", "") +
HelpExampleRpc("getchaintxstats", "2016")},
[&](const RPCHelpMan &self, const Config &config,
const JSONRPCRequest &request) -> UniValue {
ChainstateManager &chainman = EnsureAnyChainman(request.context);
const CBlockIndex *pindex;
// By default: 1 month
int blockcount =
30 * 24 * 60 * 60 /
config.GetChainParams().GetConsensus().nPowTargetSpacing;
if (request.params[1].isNull()) {
LOCK(cs_main);
pindex = chainman.ActiveTip();
} else {
BlockHash hash(ParseHashV(request.params[1], "blockhash"));
LOCK(cs_main);
pindex = chainman.m_blockman.LookupBlockIndex(hash);
if (!pindex) {
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY,
"Block not found");
}
if (!chainman.ActiveChain().Contains(pindex)) {
throw JSONRPCError(RPC_INVALID_PARAMETER,
"Block is not in main chain");
}
}
CHECK_NONFATAL(pindex != nullptr);
if (request.params[0].isNull()) {
blockcount =
std::max(0, std::min(blockcount, pindex->nHeight - 1));
} else {
blockcount = request.params[0].getInt<int>();
if (blockcount < 0 ||
(blockcount > 0 && blockcount >= pindex->nHeight)) {
throw JSONRPCError(RPC_INVALID_PARAMETER,
"Invalid block count: "
"should be between 0 and "
"the block's height - 1");
}
}
const CBlockIndex &past_block{*CHECK_NONFATAL(
pindex->GetAncestor(pindex->nHeight - blockcount))};
const int64_t nTimeDiff{pindex->GetMedianTimePast() -
past_block.GetMedianTimePast()};
const int nTxDiff =
pindex->GetChainTxCount() - past_block.GetChainTxCount();
UniValue ret(UniValue::VOBJ);
ret.pushKV("time", pindex->GetBlockTime());
ret.pushKV("txcount", pindex->GetChainTxCount());
ret.pushKV("window_final_block_hash",
pindex->GetBlockHash().GetHex());
ret.pushKV("window_final_block_height", pindex->nHeight);
ret.pushKV("window_block_count", blockcount);
if (blockcount > 0) {
ret.pushKV("window_tx_count", nTxDiff);
ret.pushKV("window_interval", nTimeDiff);
if (nTimeDiff > 0) {
ret.pushKV("txrate", double(nTxDiff) / nTimeDiff);
}
}
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 RPCHelpMan getblockstats() {
const auto &ticker = Currency::get().ticker;
return RPCHelpMan{
"getblockstats",
"Compute per block statistics for a given window. All amounts are "
"in " +
ticker +
".\n"
"It won't work for some heights with pruning.\n",
{
{"hash_or_height", RPCArg::Type::NUM, RPCArg::Optional::NO,
"The block hash or height of the target block",
RPCArgOptions{.skip_type_check = true,
.type_str = {"", "string or numeric"}}},
{"stats",
RPCArg::Type::ARR,
RPCArg::DefaultHint{"all values"},
"Values to plot (see result below)",
{
{"height", RPCArg::Type::STR, RPCArg::Optional::OMITTED,
"Selected statistic"},
{"time", RPCArg::Type::STR, RPCArg::Optional::OMITTED,
"Selected statistic"},
},
RPCArgOptions{.oneline_description = "stats"}},
},
RPCResult{
RPCResult::Type::OBJ,
"",
"",
{
{RPCResult::Type::NUM, "avgfee", "Average fee in the block"},
{RPCResult::Type::NUM, "avgfeerate",
"Average feerate (in satoshis per virtual byte)"},
{RPCResult::Type::NUM, "avgtxsize", "Average transaction size"},
{RPCResult::Type::STR_HEX, "blockhash",
"The block hash (to check for potential reorgs)"},
{RPCResult::Type::NUM, "height", "The height of the block"},
{RPCResult::Type::NUM, "ins",
"The number of inputs (excluding coinbase)"},
{RPCResult::Type::NUM, "maxfee", "Maximum fee in the block"},
{RPCResult::Type::NUM, "maxfeerate",
"Maximum feerate (in satoshis per virtual byte)"},
{RPCResult::Type::NUM, "maxtxsize", "Maximum transaction size"},
{RPCResult::Type::NUM, "medianfee",
"Truncated median fee in the block"},
{RPCResult::Type::NUM, "medianfeerate",
"Truncated median feerate (in " + ticker + " per byte)"},
{RPCResult::Type::NUM, "mediantime",
"The block median time past"},
{RPCResult::Type::NUM, "mediantxsize",
"Truncated median transaction size"},
{RPCResult::Type::NUM, "minfee", "Minimum fee in the block"},
{RPCResult::Type::NUM, "minfeerate",
"Minimum feerate (in satoshis per virtual byte)"},
{RPCResult::Type::NUM, "mintxsize", "Minimum transaction size"},
{RPCResult::Type::NUM, "outs", "The number of outputs"},
{RPCResult::Type::NUM, "subsidy", "The block subsidy"},
{RPCResult::Type::NUM, "time", "The block time"},
{RPCResult::Type::NUM, "total_out",
"Total amount in all outputs (excluding coinbase and thus "
"reward [ie subsidy + totalfee])"},
{RPCResult::Type::NUM, "total_size",
"Total size of all non-coinbase transactions"},
{RPCResult::Type::NUM, "totalfee", "The fee total"},
{RPCResult::Type::NUM, "txs",
"The number of transactions (including coinbase)"},
{RPCResult::Type::NUM, "utxo_increase",
"The increase/decrease in the number of unspent outputs"},
{RPCResult::Type::NUM, "utxo_size_inc",
"The increase/decrease in size for the utxo index (not "
"discounting op_return and similar)"},
}},
RPCExamples{
HelpExampleCli(
"getblockstats",
R"('"00000000c937983704a73af28acdec37b049d214adbda81d7e2a3dd146f6ed09"' '["minfeerate","avgfeerate"]')") +
HelpExampleCli("getblockstats",
R"(1000 '["minfeerate","avgfeerate"]')") +
HelpExampleRpc(
"getblockstats",
R"("00000000c937983704a73af28acdec37b049d214adbda81d7e2a3dd146f6ed09", ["minfeerate","avgfeerate"])") +
HelpExampleRpc("getblockstats",
R"(1000, ["minfeerate","avgfeerate"])")},
[&](const RPCHelpMan &self, const Config &config,
const JSONRPCRequest &request) -> UniValue {
ChainstateManager &chainman = EnsureAnyChainman(request.context);
const CBlockIndex &pindex{*CHECK_NONFATAL(
ParseHashOrHeight(request.params[0], chainman))};
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(chainman.m_blockman, &pindex);
const CBlockUndo &blockUndo =
GetUndoChecked(chainman.m_blockman, &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 (size_t i = 0; i < block.vtx.size(); ++i) {
const auto &tx = block.vtx.at(i);
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, 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) {
Amount tx_total_in = Amount::zero();
const auto &txundo = blockUndo.vtxundo.at(i - 1);
for (const Coin &coin : txundo.vprevout) {
const CTxOut &prevoutput = coin.GetTxOut();
tx_total_in += prevoutput.nValue;
utxo_size_inc -=
GetSerializeSize(prevoutput, PROTOCOL_VERSION) +
PER_UTXO_OVERHEAD;
}
Amount txfee = tx_total_in - tx_total_out;
CHECK_NONFATAL(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",
block.vtx.size() > 1
? (totalfee / int((block.vtx.size() - 1)))
: Amount::zero());
ret_all.pushKV("avgfeerate", 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", 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 ? Amount::zero() : minfee);
ret_all.pushKV("minfeerate", minfeerate == MAX_MONEY
? Amount::zero()
: minfeerate);
ret_all.pushKV("mintxsize",
mintxsize == blockMaxSize ? 0 : mintxsize);
ret_all.pushKV("outs", outputs);
ret_all.pushKV("subsidy", GetBlockSubsidy(pindex.nHeight,
chainman.GetConsensus()));
ret_all.pushKV("time", pindex.GetBlockTime());
ret_all.pushKV("total_out", total_out);
ret_all.pushKV("total_size", total_size);
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;
},
};
}
namespace {
//! Search for a given set of pubkey scripts
static 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,
std::function<void()> &interruption_point) {
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) {
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
const TxId &txid = key.GetTxId();
uint32_t high = 0x100 * *txid.begin() + *(txid.begin() + 1);
scan_progress = int(high * 100.0 / 65536.0 + 0.5);
}
if (needles.count(coin.GetTxOut().scriptPubKey)) {
out_results.emplace(key, coin);
}
cursor->Next();
}
scan_progress = 100;
return true;
}
} // namespace
/** RAII object to prevent concurrency issue when scanning the txout set */
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() {
CHECK_NONFATAL(!m_could_reserve);
if (g_scan_in_progress.exchange(true)) {
return false;
}
m_could_reserve = true;
return true;
}
~CoinsViewScanReserver() {
if (m_could_reserve) {
g_scan_in_progress = false;
}
}
};
static RPCHelpMan scantxoutset() {
const auto &ticker = Currency::get().ticker;
return RPCHelpMan{
"scantxoutset",
"Scans the unspent transaction output set for entries that match "
"certain output descriptors.\n"
"Examples of output descriptors are:\n"
" addr(<address>) Outputs whose scriptPubKey "
"corresponds to the specified address (does not include P2PK)\n"
" raw(<hex script>) Outputs whose scriptPubKey "
"equals the specified hex scripts\n"
" combo(<pubkey>) P2PK and P2PKH outputs for "
"the given pubkey\n"
" pkh(<pubkey>) P2PKH outputs for the given "
"pubkey\n"
" sh(multi(<n>,<pubkey>,<pubkey>,...)) P2SH-multisig outputs for "
"the given threshold and pubkeys\n"
"\nIn the above, <pubkey> either refers to a fixed public key in "
"hexadecimal notation, or to an xpub/xprv optionally followed by one\n"
"or more path elements separated by \"/\", and optionally ending in "
"\"/*\" (unhardened), or \"/*'\" or \"/*h\" (hardened) to specify all\n"
"unhardened or hardened child keys.\n"
"In the latter case, a range needs to be specified by below if "
"different from 1000.\n"
"For more information on output descriptors, see the documentation in "
"the doc/descriptors.md file.\n",
{
{"action", RPCArg::Type::STR, RPCArg::Optional::NO,
"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"},
{"scanobjects",
RPCArg::Type::ARR,
RPCArg::Optional::OMITTED,
"Array of scan objects. Required for \"start\" action\n"
" Every scan object is either a "
"string descriptor or an object:",
{
{"descriptor", RPCArg::Type::STR, RPCArg::Optional::OMITTED,
"An output descriptor"},
{
"",
RPCArg::Type::OBJ,
RPCArg::Optional::OMITTED,
"An object with output descriptor and metadata",
{
{"desc", RPCArg::Type::STR, RPCArg::Optional::NO,
"An output descriptor"},
{"range", RPCArg::Type::RANGE, RPCArg::Default{1000},
"The range of HD chain indexes to explore (either "
"end or [begin,end])"},
},
},
},
RPCArgOptions{.oneline_description = "[scanobjects,...]"}},
},
{
RPCResult{"When action=='abort'", RPCResult::Type::BOOL, "", ""},
RPCResult{"When action=='status' and no scan is in progress",
RPCResult::Type::NONE, "", ""},
RPCResult{
"When action=='status' and scan is in progress",
RPCResult::Type::OBJ,
"",
"",
{
{RPCResult::Type::NUM, "progress", "The scan progress"},
}},
RPCResult{
"When action=='start'",
RPCResult::Type::OBJ,
"",
"",
{
{RPCResult::Type::BOOL, "success",
"Whether the scan was completed"},
{RPCResult::Type::NUM, "txouts",
"The number of unspent transaction outputs scanned"},
{RPCResult::Type::NUM, "height",
"The current block height (index)"},
{RPCResult::Type::STR_HEX, "bestblock",
"The hash of the block at the tip of the chain"},
{RPCResult::Type::ARR,
"unspents",
"",
{
{RPCResult::Type::OBJ,
"",
"",
{
{RPCResult::Type::STR_HEX, "txid",
"The transaction id"},
{RPCResult::Type::NUM, "vout", "The vout value"},
{RPCResult::Type::STR_HEX, "scriptPubKey",
"The script key"},
{RPCResult::Type::STR, "desc",
"A specialized descriptor for the matched "
"scriptPubKey"},
{RPCResult::Type::STR_AMOUNT, "amount",
"The total amount in " + ticker +
" of the unspent output"},
{RPCResult::Type::BOOL, "coinbase",
"Whether this is a coinbase output"},
{RPCResult::Type::NUM, "height",
"Height of the unspent transaction output"},
}},
}},
{RPCResult::Type::STR_AMOUNT, "total_amount",
"The total amount of all found unspent outputs in " +
ticker},
}},
},
RPCExamples{""},
[&](const RPCHelpMan &self, const Config &config,
const JSONRPCRequest &request) -> UniValue {
UniValue result(UniValue::VOBJ);
const auto action{self.Arg<std::string>("action")};
if (action == "status") {
CoinsViewScanReserver reserver;
if (reserver.reserve()) {
// no scan in progress
return NullUniValue;
}
result.pushKV("progress", g_scan_progress.load());
return result;
} else if (action == "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 (action == "start") {
CoinsViewScanReserver reserver;
if (!reserver.reserve()) {
throw JSONRPCError(RPC_INVALID_PARAMETER,
"Scan already in progress, use action "
"\"abort\" or \"status\"");
}
if (request.params.size() < 2) {
throw JSONRPCError(RPC_MISC_ERROR,
"scanobjects argument is required for "
"the start action");
}
std::set<CScript> needles;
std::map<CScript, std::string> descriptors;
Amount total_in = Amount::zero();
// loop through the scan objects
for (const UniValue &scanobject :
request.params[1].get_array().getValues()) {
FlatSigningProvider provider;
auto scripts =
EvalDescriptorStringOrObject(scanobject, provider);
for (CScript &script : scripts) {
std::string inferred =
InferDescriptor(script, provider)->ToString();
needles.emplace(script);
descriptors.emplace(std::move(script),
std::move(inferred));
}
}
// 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;
const CBlockIndex *tip;
NodeContext &node = EnsureAnyNodeContext(request.context);
{
ChainstateManager &chainman = EnsureChainman(node);
LOCK(cs_main);
Chainstate &active_chainstate = chainman.ActiveChainstate();
active_chainstate.ForceFlushStateToDisk();
pcursor = CHECK_NONFATAL(std::unique_ptr<CCoinsViewCursor>(
active_chainstate.CoinsDB().Cursor()));
tip = CHECK_NONFATAL(active_chainstate.m_chain.Tip());
}
bool res = FindScriptPubKey(
g_scan_progress, g_should_abort_scan, count, pcursor.get(),
needles, coins, node.rpc_interruption_point);
result.pushKV("success", res);
result.pushKV("txouts", count);
result.pushKV("height", tip->nHeight);
result.pushKV("bestblock", tip->GetBlockHash().GetHex());
for (const auto &it : coins) {
const COutPoint &outpoint = it.first;
const Coin &coin = it.second;
const CTxOut &txo = coin.GetTxOut();
input_txos.push_back(txo);
total_in += txo.nValue;
UniValue unspent(UniValue::VOBJ);
unspent.pushKV("txid", outpoint.GetTxId().GetHex());
unspent.pushKV("vout", int32_t(outpoint.GetN()));
unspent.pushKV("scriptPubKey", HexStr(txo.scriptPubKey));
unspent.pushKV("desc", descriptors[txo.scriptPubKey]);
unspent.pushKV("amount", txo.nValue);
unspent.pushKV("coinbase", coin.IsCoinBase());
unspent.pushKV("height", int32_t(coin.GetHeight()));
unspents.push_back(unspent);
}
result.pushKV("unspents", unspents);
result.pushKV("total_amount", total_in);
} else {
throw JSONRPCError(RPC_INVALID_PARAMETER,
strprintf("Invalid action '%s'", action));
}
return result;
},
};
}
static RPCHelpMan getblockfilter() {
return RPCHelpMan{
"getblockfilter",
"Retrieve a BIP 157 content filter for a particular block.\n",
{
{"blockhash", RPCArg::Type::STR_HEX, RPCArg::Optional::NO,
"The hash of the block"},
{"filtertype", RPCArg::Type::STR, RPCArg::Default{"basic"},
"The type name of the filter"},
},
RPCResult{RPCResult::Type::OBJ,
"",
"",
{
{RPCResult::Type::STR_HEX, "filter",
"the hex-encoded filter data"},
{RPCResult::Type::STR_HEX, "header",
"the hex-encoded filter header"},
}},
RPCExamples{
HelpExampleCli("getblockfilter",
"\"00000000c937983704a73af28acdec37b049d214a"
"dbda81d7e2a3dd146f6ed09\" \"basic\"") +
HelpExampleRpc("getblockfilter",
"\"00000000c937983704a73af28acdec37b049d214adbda81d7"
"e2a3dd146f6ed09\", \"basic\"")},
[&](const RPCHelpMan &self, const Config &config,
const JSONRPCRequest &request) -> UniValue {
const BlockHash block_hash(
ParseHashV(request.params[0], "blockhash"));
std::string filtertype_name = "basic";
if (!request.params[1].isNull()) {
filtertype_name = request.params[1].get_str();
}
BlockFilterType filtertype;
if (!BlockFilterTypeByName(filtertype_name, filtertype)) {
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY,
"Unknown filtertype");
}
BlockFilterIndex *index = GetBlockFilterIndex(filtertype);
if (!index) {
throw JSONRPCError(RPC_MISC_ERROR,
"Index is not enabled for filtertype " +
filtertype_name);
}
const CBlockIndex *block_index;
bool block_was_connected;
{
ChainstateManager &chainman =
EnsureAnyChainman(request.context);
LOCK(cs_main);
block_index = chainman.m_blockman.LookupBlockIndex(block_hash);
if (!block_index) {
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY,
"Block not found");
}
block_was_connected =
block_index->IsValid(BlockValidity::SCRIPTS);
}
bool index_ready = index->BlockUntilSyncedToCurrentChain();
BlockFilter filter;
uint256 filter_header;
if (!index->LookupFilter(block_index, filter) ||
!index->LookupFilterHeader(block_index, filter_header)) {
int err_code;
std::string errmsg = "Filter not found.";
if (!block_was_connected) {
err_code = RPC_INVALID_ADDRESS_OR_KEY;
errmsg += " Block was not connected to active chain.";
} else if (!index_ready) {
err_code = RPC_MISC_ERROR;
errmsg += " Block filters are still in the process of "
"being indexed.";
} else {
err_code = RPC_INTERNAL_ERROR;
errmsg += " This error is unexpected and indicates index "
"corruption.";
}
throw JSONRPCError(err_code, errmsg);
}
UniValue ret(UniValue::VOBJ);
ret.pushKV("filter", HexStr(filter.GetEncodedFilter()));
ret.pushKV("header", filter_header.GetHex());
return ret;
},
};
}
/**
* RAII class that disables the network in its constructor and enables it in its
* destructor.
*/
class NetworkDisable {
CConnman &m_connman;
public:
NetworkDisable(CConnman &connman) : m_connman(connman) {
m_connman.SetNetworkActive(false);
if (m_connman.GetNetworkActive()) {
throw JSONRPCError(RPC_MISC_ERROR,
"Network activity could not be suspended.");
}
};
~NetworkDisable() { m_connman.SetNetworkActive(true); };
};
+/**
+ * RAII class that temporarily rolls back the local chain in it's constructor
+ * and rolls it forward again in it's destructor.
+ */
+class TemporaryRollback {
+ ChainstateManager &m_chainman;
+ avalanche::Processor *const m_avalanche;
+ const CBlockIndex &m_invalidate_index;
+
+public:
+ TemporaryRollback(ChainstateManager &chainman,
+ avalanche::Processor *const avalanche,
+ const CBlockIndex &index)
+ : m_chainman(chainman), m_avalanche(avalanche),
+ m_invalidate_index(index) {
+ InvalidateBlock(m_chainman, m_avalanche,
+ m_invalidate_index.GetBlockHash());
+ };
+ ~TemporaryRollback() {
+ ReconsiderBlock(m_chainman, m_avalanche,
+ m_invalidate_index.GetBlockHash());
+ };
+};
+
/**
* Serialize the UTXO set to a file for loading elsewhere.
*
* @see SnapshotMetadata
*/
static RPCHelpMan dumptxoutset() {
return RPCHelpMan{
"dumptxoutset",
"Write the serialized UTXO set to a file.\n",
{
{"path", RPCArg::Type::STR, RPCArg::Optional::NO,
"path to the output file. If relative, will be prefixed by "
"datadir."},
{"type", RPCArg::Type::STR, RPCArg::Default(""),
"The type of snapshot to create. Can be \"latest\" to create a "
"snapshot of the current UTXO set or \"rollback\" to temporarily "
"roll back the state of the node to a historical block before "
"creating the snapshot of a historical UTXO set. This parameter "
"can be omitted if a separate \"rollback\" named parameter is "
"specified indicating the height or hash of a specific historical "
"block. If \"rollback\" is specified and separate \"rollback\" "
"named parameter is not specified, this will roll back to the "
"latest valid snapshot block that currently can be loaded with "
"loadtxoutset."},
{
"options",
RPCArg::Type::OBJ_NAMED_PARAMS,
RPCArg::Optional::OMITTED,
"",
{
{"rollback", RPCArg::Type::NUM, RPCArg::Optional::OMITTED,
"Height or hash of the block to roll back to before "
"creating the snapshot. Note: The further this number is "
"from the tip, the longer this process will take. "
"Consider setting a higher -rpcclienttimeout value in "
"this case.",
RPCArgOptions{.skip_type_check = true,
.type_str = {"", "string or numeric"}}},
},
},
},
RPCResult{RPCResult::Type::OBJ,
"",
"",
{
{RPCResult::Type::NUM, "coins_written",
"the number of coins written in the snapshot"},
{RPCResult::Type::STR_HEX, "base_hash",
"the hash of the base of the snapshot"},
{RPCResult::Type::NUM, "base_height",
"the height of the base of the snapshot"},
{RPCResult::Type::STR, "path",
"the absolute path that the snapshot was written to"},
{RPCResult::Type::STR_HEX, "txoutset_hash",
"the hash of the UTXO set contents"},
{RPCResult::Type::NUM, "nchaintx",
"the number of transactions in the chain up to and "
"including the base block"},
}},
RPCExamples{HelpExampleCli("-rpcclienttimeout=0 dumptxoutset",
"utxo.dat latest") +
HelpExampleCli("-rpcclienttimeout=0 dumptxoutset",
"utxo.dat rollback") +
HelpExampleCli("-rpcclienttimeout=0 -named dumptxoutset",
R"(utxo.dat rollback=853456)")},
[&](const RPCHelpMan &self, const Config &config,
const JSONRPCRequest &request) -> UniValue {
NodeContext &node = EnsureAnyNodeContext(request.context);
const CBlockIndex *tip{WITH_LOCK(
::cs_main, return node.chainman->ActiveChain().Tip())};
const CBlockIndex *target_index{nullptr};
const std::string snapshot_type{self.Arg<std::string>("type")};
const UniValue options{request.params[2].isNull()
? UniValue::VOBJ
: request.params[2]};
if (options.exists("rollback")) {
if (!snapshot_type.empty() && snapshot_type != "rollback") {
throw JSONRPCError(
RPC_INVALID_PARAMETER,
strprintf("Invalid snapshot type \"%s\" specified with "
"rollback option",
snapshot_type));
}
target_index =
ParseHashOrHeight(options["rollback"], *node.chainman);
} else if (snapshot_type == "rollback") {
auto snapshot_heights =
node.chainman->GetParams().GetAvailableSnapshotHeights();
CHECK_NONFATAL(snapshot_heights.size() > 0);
auto max_height = std::max_element(snapshot_heights.begin(),
snapshot_heights.end());
target_index = ParseHashOrHeight(*max_height, *node.chainman);
} else if (snapshot_type == "latest") {
target_index = tip;
} else {
throw JSONRPCError(
RPC_INVALID_PARAMETER,
strprintf("Invalid snapshot type \"%s\" specified. Please "
"specify \"rollback\" or \"latest\"",
snapshot_type));
}
const ArgsManager &args{EnsureAnyArgsman(request.context)};
const fs::path path = fsbridge::AbsPathJoin(
args.GetDataDirNet(), fs::u8path(request.params[0].get_str()));
// Write to a temporary path and then move into `path` on completion
// to avoid confusion due to an interruption.
const fs::path temppath = fsbridge::AbsPathJoin(
args.GetDataDirNet(),
fs::u8path(request.params[0].get_str() + ".incomplete"));
if (fs::exists(path)) {
throw JSONRPCError(RPC_INVALID_PARAMETER,
path.u8string() +
" already exists. If you are sure this "
"is what you want, "
"move it out of the way first");
}
FILE *file{fsbridge::fopen(temppath, "wb")};
AutoFile afile{file};
CConnman &connman = EnsureConnman(node);
const CBlockIndex *invalidate_index{nullptr};
std::optional<NetworkDisable> disable_network;
+ std::optional<TemporaryRollback> temporary_rollback;
// If the user wants to dump the txoutset of the current tip, we
// don't have to roll back at all
if (target_index != tip) {
// If the node is running in pruned mode we ensure all necessary
// block data is available before starting to roll back.
if (node.chainman->m_blockman.IsPruneMode()) {
LOCK(node.chainman->GetMutex());
const CBlockIndex *current_tip{
node.chainman->ActiveChain().Tip()};
const CBlockIndex *first_block{
node.chainman->m_blockman.GetFirstBlock(
*current_tip,
/*status_test=*/[](const BlockStatus &status) {
return status.hasData() && status.hasUndo();
})};
if (first_block->nHeight > target_index->nHeight) {
throw JSONRPCError(
RPC_MISC_ERROR,
"Could not roll back to requested height since "
"necessary block data is already pruned.");
}
}
// Suspend network activity for the duration of the process when
// we are rolling back the chain to get a utxo set from a past
// height. We do this so we don't punish peers that send us that
// send us data that seems wrong in this temporary state. For
// example a normal new block would be classified as a block
// connecting an invalid block.
// Skip if the network is already disabled because this
// automatically re-enables the network activity at the end of
// the process which may not be what the user wants.
if (connman.GetNetworkActive()) {
disable_network.emplace(connman);
}
invalidate_index = WITH_LOCK(
::cs_main,
return node.chainman->ActiveChain().Next(target_index));
- InvalidateBlock(*node.chainman, node.avalanche.get(),
- invalidate_index->GetBlockHash());
+ temporary_rollback.emplace(*node.chainman, node.avalanche.get(),
+ *invalidate_index);
}
Chainstate *chainstate;
std::unique_ptr<CCoinsViewCursor> cursor;
CCoinsStats stats;
- UniValue result;
- UniValue error;
{
// Lock the chainstate before calling PrepareUtxoSnapshot, to
// be able to get a UTXO database cursor while the chain is
// pointing at the target block. After that, release the lock
// while calling WriteUTXOSnapshot. The cursor will remain
// valid and be used by WriteUTXOSnapshot to write a consistent
// snapshot even if the chainstate changes.
LOCK(node.chainman->GetMutex());
chainstate = &node.chainman->ActiveChainstate();
// In case there is any issue with a block being read from disk
// we need to stop here, otherwise the dump could still be
// created for the wrong height. The new tip could also not be
// the target block if we have a stale sister block of
// invalidate_index. This block (or a descendant) would be
// activated as the new tip and we would not get to
// new_tip_index.
if (target_index != chainstate->m_chain.Tip()) {
- LogPrintf("Failed to roll back to requested height, "
- "reverting to tip.\n");
- error = JSONRPCError(
+ LogPrintLevel(BCLog::RPC, BCLog::Level::Warning,
+ "Failed to roll back to requested height, "
+ "reverting to tip.\n");
+ throw JSONRPCError(
RPC_MISC_ERROR,
"Could not roll back to requested height.");
} else {
std::tie(cursor, stats, tip) = PrepareUTXOSnapshot(
*chainstate, node.rpc_interruption_point);
}
}
- if (error.isNull()) {
- result = WriteUTXOSnapshot(*chainstate, cursor.get(), &stats,
- tip, afile, path, temppath,
- node.rpc_interruption_point);
- fs::rename(temppath, path);
- }
- if (invalidate_index) {
- ReconsiderBlock(*node.chainman, node.avalanche.get(),
- invalidate_index->GetBlockHash());
- }
- if (!error.isNull()) {
- throw error;
- }
+ UniValue result =
+ WriteUTXOSnapshot(*chainstate, cursor.get(), &stats, tip, afile,
+ path, temppath, node.rpc_interruption_point);
+ fs::rename(temppath, path);
return result;
},
};
}
std::tuple<std::unique_ptr<CCoinsViewCursor>, CCoinsStats, const CBlockIndex *>
PrepareUTXOSnapshot(Chainstate &chainstate,
const std::function<void()> &interruption_point) {
std::unique_ptr<CCoinsViewCursor> pcursor;
std::optional<CCoinsStats> maybe_stats;
const CBlockIndex *tip;
{
// We need to lock cs_main to ensure that the coinsdb isn't
// written to between (i) flushing coins cache to disk
// (coinsdb), (ii) getting stats based upon the coinsdb, and
// (iii) constructing a cursor to the coinsdb for use in
// WriteUTXOSnapshot.
//
// Cursors returned by leveldb iterate over snapshots, so the
// contents of the pcursor will not be affected by simultaneous
// writes during use below this block.
//
// See discussion here:
// https://github.com/bitcoin/bitcoin/pull/15606#discussion_r274479369
//
AssertLockHeld(::cs_main);
chainstate.ForceFlushStateToDisk();
maybe_stats = GetUTXOStats(&chainstate.CoinsDB(), chainstate.m_blockman,
CoinStatsHashType::HASH_SERIALIZED,
interruption_point);
if (!maybe_stats) {
throw JSONRPCError(RPC_INTERNAL_ERROR, "Unable to read UTXO set");
}
pcursor =
std::unique_ptr<CCoinsViewCursor>(chainstate.CoinsDB().Cursor());
tip = CHECK_NONFATAL(
chainstate.m_blockman.LookupBlockIndex(maybe_stats->hashBlock));
}
return {std::move(pcursor), *CHECK_NONFATAL(maybe_stats), tip};
}
UniValue WriteUTXOSnapshot(Chainstate &chainstate, CCoinsViewCursor *pcursor,
CCoinsStats *maybe_stats, const CBlockIndex *tip,
AutoFile &afile, const fs::path &path,
const fs::path &temppath,
const std::function<void()> &interruption_point) {
LOG_TIME_SECONDS(
strprintf("writing UTXO snapshot at height %s (%s) to file %s (via %s)",
tip->nHeight, tip->GetBlockHash().ToString(),
fs::PathToString(path), fs::PathToString(temppath)));
SnapshotMetadata metadata{tip->GetBlockHash(), tip->nHeight,
maybe_stats->coins_count};
afile << metadata;
COutPoint key;
TxId last_txid;
Coin coin;
unsigned int iter{0};
size_t written_coins_count{0};
std::vector<std::pair<uint32_t, Coin>> coins;
// To reduce space the serialization format of the snapshot avoids
// duplication of tx hashes. The code takes advantage of the guarantee by
// leveldb that keys are lexicographically sorted.
// In the coins vector we collect all coins that belong to a certain tx hash
// (key.hash) and when we have them all (key.hash != last_hash) we write
// them to file using the below lambda function.
// See also https://github.com/bitcoin/bitcoin/issues/25675
auto write_coins_to_file =
[&](AutoFile &afile, const TxId &last_txid,
const std::vector<std::pair<uint32_t, Coin>> &coins,
size_t &written_coins_count) {
afile << last_txid;
WriteCompactSize(afile, coins.size());
for (const auto &[n, coin_] : coins) {
WriteCompactSize(afile, n);
afile << coin_;
++written_coins_count;
}
};
pcursor->GetKey(key);
last_txid = key.GetTxId();
while (pcursor->Valid()) {
if (iter % 5000 == 0) {
interruption_point();
}
++iter;
if (pcursor->GetKey(key) && pcursor->GetValue(coin)) {
if (key.GetTxId() != last_txid) {
write_coins_to_file(afile, last_txid, coins,
written_coins_count);
last_txid = key.GetTxId();
coins.clear();
}
coins.emplace_back(key.GetN(), coin);
}
pcursor->Next();
}
if (!coins.empty()) {
write_coins_to_file(afile, last_txid, coins, written_coins_count);
}
CHECK_NONFATAL(written_coins_count == maybe_stats->coins_count);
afile.fclose();
UniValue result(UniValue::VOBJ);
result.pushKV("coins_written", written_coins_count);
result.pushKV("base_hash", tip->GetBlockHash().ToString());
result.pushKV("base_height", tip->nHeight);
result.pushKV("path", path.u8string());
result.pushKV("txoutset_hash", maybe_stats->hashSerialized.ToString());
result.pushKV("nchaintx", tip->nChainTx);
return result;
}
UniValue CreateUTXOSnapshot(node::NodeContext &node, Chainstate &chainstate,
AutoFile &afile, const fs::path &path,
const fs::path &tmppath) {
auto [cursor, stats, tip]{WITH_LOCK(
::cs_main,
return PrepareUTXOSnapshot(chainstate, node.rpc_interruption_point))};
return WriteUTXOSnapshot(chainstate, cursor.get(), &stats, tip, afile, path,
tmppath, node.rpc_interruption_point);
}
static RPCHelpMan loadtxoutset() {
return RPCHelpMan{
"loadtxoutset",
"Load the serialized UTXO set from a file.\n"
"Once this snapshot is loaded, its contents will be deserialized into "
"a second chainstate data structure, which is then used to sync to the "
"network's tip. "
"Meanwhile, the original chainstate will complete the initial block "
"download process in the background, eventually validating up to the "
"block that the snapshot is based upon.\n\n"
"The result is a usable bitcoind instance that is current with the "
"network tip in a matter of minutes rather than hours. UTXO snapshot "
"are typically obtained from third-party sources (HTTP, torrent, etc.) "
"which is reasonable since their contents are always checked by "
"hash.\n\n"
"You can find more information on this process in the `assumeutxo` "
"design document (https://www.bitcoinabc.org/doc/assumeutxo.html).",
{
{"path", RPCArg::Type::STR, RPCArg::Optional::NO,
"path to the snapshot file. If relative, will be prefixed by "
"datadir."},
},
RPCResult{RPCResult::Type::OBJ,
"",
"",
{
{RPCResult::Type::NUM, "coins_loaded",
"the number of coins loaded from the snapshot"},
{RPCResult::Type::STR_HEX, "tip_hash",
"the hash of the base of the snapshot"},
{RPCResult::Type::NUM, "base_height",
"the height of the base of the snapshot"},
{RPCResult::Type::STR, "path",
"the absolute path that the snapshot was loaded from"},
}},
RPCExamples{HelpExampleCli("loadtxoutset", "utxo.dat")},
[&](const RPCHelpMan &self, const Config &config,
const JSONRPCRequest &request) -> UniValue {
NodeContext &node = EnsureAnyNodeContext(request.context);
ChainstateManager &chainman = EnsureChainman(node);
fs::path path{AbsPathForConfigVal(
EnsureArgsman(node), fs::u8path(request.params[0].get_str()))};
FILE *file{fsbridge::fopen(path, "rb")};
AutoFile afile{file};
if (afile.IsNull()) {
throw JSONRPCError(RPC_INVALID_PARAMETER,
"Couldn't open file " + path.u8string() +
" for reading.");
}
SnapshotMetadata metadata;
try {
afile >> metadata;
} catch (const std::ios_base::failure &e) {
throw JSONRPCError(
RPC_DESERIALIZATION_ERROR,
strprintf("Unable to parse metadata: %s", e.what()));
}
BlockHash base_blockhash = metadata.m_base_blockhash;
int base_blockheight = metadata.m_base_blockheight;
if (!chainman.GetParams()
.AssumeutxoForBlockhash(base_blockhash)
.has_value()) {
auto available_heights =
chainman.GetParams().GetAvailableSnapshotHeights();
std::string heights_formatted =
Join(available_heights, ", ",
[&](const auto &i) { return ToString(i); });
throw JSONRPCError(
RPC_INTERNAL_ERROR,
strprintf("Unable to load UTXO snapshot, "
"assumeutxo block hash in snapshot metadata not "
"recognized (hash: %s, height: %s). The "
"following snapshot heights are available: %s.",
base_blockhash.ToString(), base_blockheight,
heights_formatted));
}
CBlockIndex *snapshot_start_block = WITH_LOCK(
::cs_main,
return chainman.m_blockman.LookupBlockIndex(base_blockhash));
if (!snapshot_start_block) {
throw JSONRPCError(
RPC_INTERNAL_ERROR,
strprintf("The base block header (%s) must appear in the "
"headers chain. Make sure all headers are "
"synced, and call this RPC again.",
base_blockhash.ToString()));
}
if (!chainman.ActivateSnapshot(afile, metadata, false)) {
throw JSONRPCError(RPC_INTERNAL_ERROR,
"Unable to load UTXO snapshot " +
fs::PathToString(path));
}
// Because we can't provide historical blocks during tip or
// background sync. Update local services to reflect we are a
// limited peer until we are fully sync.
node.connman->RemoveLocalServices(NODE_NETWORK);
// Setting the limited state is usually redundant because the node
// can always provide the last 288 blocks, but it doesn't hurt to
// set it.
node.connman->AddLocalServices(NODE_NETWORK_LIMITED);
CBlockIndex *new_tip{
WITH_LOCK(::cs_main, return chainman.ActiveTip())};
UniValue result(UniValue::VOBJ);
result.pushKV("coins_loaded", metadata.m_coins_count);
result.pushKV("tip_hash", new_tip->GetBlockHash().ToString());
result.pushKV("base_height", new_tip->nHeight);
result.pushKV("path", fs::PathToString(path));
return result;
},
};
}
const std::vector<RPCResult> RPCHelpForChainstate{
{RPCResult::Type::NUM, "blocks", "number of blocks in this chainstate"},
{RPCResult::Type::STR_HEX, "bestblockhash", "blockhash of the tip"},
{RPCResult::Type::NUM, "difficulty", "difficulty of the tip"},
{RPCResult::Type::NUM, "verificationprogress",
"progress towards the network tip"},
{RPCResult::Type::STR_HEX, "snapshot_blockhash", /*optional=*/true,
"the base block of the snapshot this chainstate is based on, if any"},
{RPCResult::Type::NUM, "coins_db_cache_bytes", "size of the coinsdb cache"},
{RPCResult::Type::NUM, "coins_tip_cache_bytes",
"size of the coinstip cache"},
{RPCResult::Type::BOOL, "validated",
"whether the chainstate is fully validated. True if all blocks in the "
"chainstate were validated, false if the chain is based on a snapshot and "
"the snapshot has not yet been validated."},
};
static RPCHelpMan getchainstates() {
return RPCHelpMan{
"getchainstates",
"\nReturn information about chainstates.\n",
{},
RPCResult{RPCResult::Type::OBJ,
"",
"",
{
{RPCResult::Type::NUM, "headers",
"the number of headers seen so far"},
{RPCResult::Type::ARR,
"chainstates",
"list of the chainstates ordered by work, with the "
"most-work (active) chainstate last",
{
{RPCResult::Type::OBJ, "", "", RPCHelpForChainstate},
}},
}},
RPCExamples{HelpExampleCli("getchainstates", "") +
HelpExampleRpc("getchainstates", "")},
[&](const RPCHelpMan &self, const Config &config,
const JSONRPCRequest &request) -> UniValue {
LOCK(cs_main);
UniValue obj(UniValue::VOBJ);
ChainstateManager &chainman = EnsureAnyChainman(request.context);
auto make_chain_data =
[&](const Chainstate &chainstate,
bool validated) EXCLUSIVE_LOCKS_REQUIRED(::cs_main) {
AssertLockHeld(::cs_main);
UniValue data(UniValue::VOBJ);
if (!chainstate.m_chain.Tip()) {
return data;
}
const CChain &chain = chainstate.m_chain;
const CBlockIndex *tip = chain.Tip();
data.pushKV("blocks", chain.Height());
data.pushKV("bestblockhash", tip->GetBlockHash().GetHex());
data.pushKV("difficulty", GetDifficulty(tip));
data.pushKV(
"verificationprogress",
GuessVerificationProgress(Params().TxData(), tip));
data.pushKV("coins_db_cache_bytes",
chainstate.m_coinsdb_cache_size_bytes);
data.pushKV("coins_tip_cache_bytes",
chainstate.m_coinstip_cache_size_bytes);
if (chainstate.m_from_snapshot_blockhash) {
data.pushKV(
"snapshot_blockhash",
chainstate.m_from_snapshot_blockhash->ToString());
}
data.pushKV("validated", validated);
return data;
};
obj.pushKV("headers", chainman.m_best_header
? chainman.m_best_header->nHeight
: -1);
const auto &chainstates = chainman.GetAll();
UniValue obj_chainstates{UniValue::VARR};
for (Chainstate *cs : chainstates) {
obj_chainstates.push_back(
make_chain_data(*cs, !cs->m_from_snapshot_blockhash ||
chainstates.size() == 1));
}
obj.pushKV("chainstates", std::move(obj_chainstates));
return obj;
}};
}
void RegisterBlockchainRPCCommands(CRPCTable &t) {
// clang-format off
static const CRPCCommand commands[] = {
// category actor (function)
// ------------------ ----------------------
{ "blockchain", getbestblockhash, },
{ "blockchain", getblock, },
{ "blockchain", getblockfrompeer, },
{ "blockchain", getblockchaininfo, },
{ "blockchain", getblockcount, },
{ "blockchain", getblockhash, },
{ "blockchain", getblockheader, },
{ "blockchain", getblockstats, },
{ "blockchain", getchaintips, },
{ "blockchain", getchaintxstats, },
{ "blockchain", getdifficulty, },
{ "blockchain", gettxout, },
{ "blockchain", gettxoutsetinfo, },
{ "blockchain", pruneblockchain, },
{ "blockchain", verifychain, },
{ "blockchain", preciousblock, },
{ "blockchain", scantxoutset, },
{ "blockchain", getblockfilter, },
{ "blockchain", dumptxoutset, },
{ "blockchain", loadtxoutset, },
{ "blockchain", getchainstates, },
/* Not shown in help */
{ "hidden", invalidateblock, },
{ "hidden", parkblock, },
{ "hidden", reconsiderblock, },
{ "hidden", syncwithvalidationinterfacequeue, },
{ "hidden", unparkblock, },
{ "hidden", waitfornewblock, },
{ "hidden", waitforblock, },
{ "hidden", waitforblockheight, },
};
// clang-format on
for (const auto &c : commands) {
t.appendCommand(c.name, &c);
}
}

File Metadata

Mime Type
text/x-diff
Expires
Wed, May 21, 18:44 (22 h, 27 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
5865719
Default Alt Text
(144 KB)

Event Timeline