Page Menu
Home
Phabricator
Search
Configure Global Search
Log In
Files
F14864316
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Award Token
Flag For Later
Size
144 KB
Subscribers
None
View Options
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 ¶m,
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
Details
Attached
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)
Attached To
rABC Bitcoin ABC
Event Timeline
Log In to Comment