1683 lines
74 KiB
C++
1683 lines
74 KiB
C++
// Copyright (c) 2010 Satoshi Nakamoto
|
|
// Copyright (c) 2009-2014 The Bitcoin Core developers
|
|
// Copyright (c) 2019-2023 The Zcash developers
|
|
// Distributed under the MIT software license, see the accompanying
|
|
// file COPYING or https://www.opensource.org/licenses/mit-license.php .
|
|
|
|
#include "amount.h"
|
|
#include "chain.h"
|
|
#include "chainparams.h"
|
|
#include "checkpoints.h"
|
|
#include "consensus/validation.h"
|
|
#include "experimental_features.h"
|
|
#include "key_io.h"
|
|
#include "main.h"
|
|
#include "metrics.h"
|
|
#include "primitives/transaction.h"
|
|
#include "rpc/server.h"
|
|
#include "streams.h"
|
|
#include "sync.h"
|
|
#include "util/system.h"
|
|
|
|
#include <stdint.h>
|
|
|
|
#include <univalue.h>
|
|
|
|
#include <optional>
|
|
#include <regex>
|
|
|
|
using namespace std;
|
|
|
|
extern void TxToJSON(const CTransaction& tx, const uint256 hashBlock, UniValue& entry);
|
|
void ScriptPubKeyToJSON(const CScript& scriptPubKey, UniValue& out, bool fIncludeHex);
|
|
|
|
double GetDifficultyINTERNAL(const CBlockIndex* blockindex, bool networkDifficulty)
|
|
{
|
|
// Floating point number that is a multiple of the minimum difficulty,
|
|
// minimum difficulty = 1.0.
|
|
if (blockindex == NULL)
|
|
{
|
|
if (chainActive.Tip() == NULL)
|
|
return 1.0;
|
|
else
|
|
blockindex = chainActive.Tip();
|
|
}
|
|
|
|
uint32_t bits;
|
|
if (networkDifficulty) {
|
|
bits = GetNextWorkRequired(blockindex, nullptr, Params().GetConsensus());
|
|
} else {
|
|
bits = blockindex->nBits;
|
|
}
|
|
|
|
uint32_t powLimit =
|
|
UintToArith256(Params().GetConsensus().powLimit).GetCompact();
|
|
int nShift = (bits >> 24) & 0xff;
|
|
int nShiftAmount = (powLimit >> 24) & 0xff;
|
|
|
|
double dDiff =
|
|
(double)(powLimit & 0x00ffffff) /
|
|
(double)(bits & 0x00ffffff);
|
|
|
|
while (nShift < nShiftAmount)
|
|
{
|
|
dDiff *= 256.0;
|
|
nShift++;
|
|
}
|
|
while (nShift > nShiftAmount)
|
|
{
|
|
dDiff /= 256.0;
|
|
nShift--;
|
|
}
|
|
|
|
return dDiff;
|
|
}
|
|
|
|
double GetDifficulty(const CBlockIndex* blockindex)
|
|
{
|
|
return GetDifficultyINTERNAL(blockindex, false);
|
|
}
|
|
|
|
double GetNetworkDifficulty(const CBlockIndex* blockindex)
|
|
{
|
|
return GetDifficultyINTERNAL(blockindex, true);
|
|
}
|
|
|
|
static UniValue ValuePoolDesc(
|
|
const std::optional<std::string> name,
|
|
const std::optional<CAmount> chainValue,
|
|
const std::optional<CAmount> valueDelta)
|
|
{
|
|
UniValue rv(UniValue::VOBJ);
|
|
if (name.has_value()) {
|
|
rv.pushKV("id", name.value());
|
|
}
|
|
rv.pushKV("monitored", (bool)chainValue);
|
|
if (chainValue) {
|
|
rv.pushKV("chainValue", ValueFromAmount(*chainValue));
|
|
rv.pushKV("chainValueZat", *chainValue);
|
|
}
|
|
if (valueDelta) {
|
|
rv.pushKV("valueDelta", ValueFromAmount(*valueDelta));
|
|
rv.pushKV("valueDeltaZat", *valueDelta);
|
|
}
|
|
return rv;
|
|
}
|
|
|
|
UniValue blockheaderToJSON(const CBlockIndex* blockindex)
|
|
{
|
|
AssertLockHeld(cs_main);
|
|
UniValue result(UniValue::VOBJ);
|
|
result.pushKV("hash", blockindex->GetBlockHash().GetHex());
|
|
int confirmations = -1;
|
|
// Only report confirmations if the block is on the main chain
|
|
if (chainActive.Contains(blockindex))
|
|
confirmations = chainActive.Height() - blockindex->nHeight + 1;
|
|
result.pushKV("confirmations", confirmations);
|
|
result.pushKV("height", blockindex->nHeight);
|
|
result.pushKV("version", blockindex->nVersion);
|
|
result.pushKV("merkleroot", blockindex->hashMerkleRoot.GetHex());
|
|
result.pushKV("finalsaplingroot", blockindex->hashFinalSaplingRoot.GetHex());
|
|
result.pushKV("time", (int64_t)blockindex->nTime);
|
|
result.pushKV("nonce", blockindex->nNonce.GetHex());
|
|
result.pushKV("solution", HexStr(blockindex->GetBlockHeader().nSolution));
|
|
result.pushKV("bits", strprintf("%08x", blockindex->nBits));
|
|
result.pushKV("difficulty", GetDifficulty(blockindex));
|
|
result.pushKV("chainwork", blockindex->nChainWork.GetHex());
|
|
|
|
if (blockindex->pprev)
|
|
result.pushKV("previousblockhash", blockindex->pprev->GetBlockHash().GetHex());
|
|
CBlockIndex *pnext = chainActive.Next(blockindex);
|
|
if (pnext)
|
|
result.pushKV("nextblockhash", pnext->GetBlockHash().GetHex());
|
|
return result;
|
|
}
|
|
|
|
// insightexplorer
|
|
UniValue blockToDeltasJSON(const CBlock& block, const CBlockIndex* blockindex)
|
|
{
|
|
UniValue result(UniValue::VOBJ);
|
|
result.pushKV("hash", block.GetHash().GetHex());
|
|
// Only report confirmations if the block is on the main chain
|
|
if (!chainActive.Contains(blockindex))
|
|
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Block is an orphan");
|
|
int confirmations = chainActive.Height() - blockindex->nHeight + 1;
|
|
result.pushKV("confirmations", confirmations);
|
|
result.pushKV("size", (int)::GetSerializeSize(block, SER_NETWORK, PROTOCOL_VERSION));
|
|
result.pushKV("height", blockindex->nHeight);
|
|
result.pushKV("version", block.nVersion);
|
|
result.pushKV("merkleroot", block.hashMerkleRoot.GetHex());
|
|
|
|
KeyIO keyIO(Params());
|
|
UniValue deltas(UniValue::VARR);
|
|
for (unsigned int i = 0; i < block.vtx.size(); i++) {
|
|
const CTransaction &tx = block.vtx[i];
|
|
const uint256 txhash = tx.GetHash();
|
|
|
|
UniValue entry(UniValue::VOBJ);
|
|
entry.pushKV("txid", txhash.GetHex());
|
|
entry.pushKV("index", (int)i);
|
|
|
|
UniValue inputs(UniValue::VARR);
|
|
if (!tx.IsCoinBase()) {
|
|
for (size_t j = 0; j < tx.vin.size(); j++) {
|
|
const CTxIn input = tx.vin[j];
|
|
UniValue delta(UniValue::VOBJ);
|
|
CSpentIndexValue spentInfo;
|
|
CSpentIndexKey spentKey(input.prevout.hash, input.prevout.n);
|
|
|
|
if (!GetSpentIndex(spentKey, spentInfo)) {
|
|
throw JSONRPCError(RPC_INTERNAL_ERROR, "Spent information not available");
|
|
}
|
|
CTxDestination dest = DestFromAddressHash(spentInfo.addressType, spentInfo.addressHash);
|
|
if (IsValidDestination(dest)) {
|
|
delta.pushKV("address", keyIO.EncodeDestination(dest));
|
|
}
|
|
delta.pushKV("satoshis", -1 * spentInfo.satoshis);
|
|
delta.pushKV("index", (int)j);
|
|
delta.pushKV("prevtxid", input.prevout.hash.GetHex());
|
|
delta.pushKV("prevout", (int)input.prevout.n);
|
|
|
|
inputs.push_back(delta);
|
|
}
|
|
}
|
|
entry.pushKV("inputs", inputs);
|
|
|
|
UniValue outputs(UniValue::VARR);
|
|
for (unsigned int k = 0; k < tx.vout.size(); k++) {
|
|
const CTxOut &out = tx.vout[k];
|
|
UniValue delta(UniValue::VOBJ);
|
|
const uint160 addrhash = out.scriptPubKey.AddressHash();
|
|
CTxDestination dest;
|
|
|
|
if (out.scriptPubKey.IsPayToScriptHash()) {
|
|
dest = CScriptID(addrhash);
|
|
} else if (out.scriptPubKey.IsPayToPublicKeyHash()) {
|
|
dest = CKeyID(addrhash);
|
|
}
|
|
if (IsValidDestination(dest)) {
|
|
delta.pushKV("address", keyIO.EncodeDestination(dest));
|
|
}
|
|
delta.pushKV("satoshis", out.nValue);
|
|
delta.pushKV("index", (int)k);
|
|
|
|
outputs.push_back(delta);
|
|
}
|
|
entry.pushKV("outputs", outputs);
|
|
deltas.push_back(entry);
|
|
}
|
|
result.pushKV("deltas", deltas);
|
|
result.pushKV("time", block.GetBlockTime());
|
|
result.pushKV("mediantime", (int64_t)blockindex->GetMedianTimePast());
|
|
result.pushKV("nonce", block.nNonce.GetHex());
|
|
result.pushKV("bits", strprintf("%08x", block.nBits));
|
|
result.pushKV("difficulty", GetDifficulty(blockindex));
|
|
result.pushKV("chainwork", blockindex->nChainWork.GetHex());
|
|
|
|
if (blockindex->pprev)
|
|
result.pushKV("previousblockhash", blockindex->pprev->GetBlockHash().GetHex());
|
|
CBlockIndex *pnext = chainActive.Next(blockindex);
|
|
if (pnext)
|
|
result.pushKV("nextblockhash", pnext->GetBlockHash().GetHex());
|
|
return result;
|
|
}
|
|
|
|
UniValue blockToJSON(const CBlock& block, const CBlockIndex* blockindex, bool txDetails = false)
|
|
{
|
|
AssertLockHeld(cs_main);
|
|
bool nu5Active = Params().GetConsensus().NetworkUpgradeActive(
|
|
blockindex->nHeight, Consensus::UPGRADE_NU5);
|
|
|
|
UniValue result(UniValue::VOBJ);
|
|
result.pushKV("hash", block.GetHash().GetHex());
|
|
int confirmations = -1;
|
|
// Only report confirmations if the block is on the main chain
|
|
if (chainActive.Contains(blockindex))
|
|
confirmations = chainActive.Height() - blockindex->nHeight + 1;
|
|
result.pushKV("confirmations", confirmations);
|
|
result.pushKV("size", (int)::GetSerializeSize(block, SER_NETWORK, PROTOCOL_VERSION));
|
|
result.pushKV("height", blockindex->nHeight);
|
|
result.pushKV("version", block.nVersion);
|
|
result.pushKV("merkleroot", block.hashMerkleRoot.GetHex());
|
|
result.pushKV("blockcommitments", blockindex->hashBlockCommitments.GetHex());
|
|
result.pushKV("authdataroot", blockindex->hashAuthDataRoot.GetHex());
|
|
result.pushKV("finalsaplingroot", blockindex->hashFinalSaplingRoot.GetHex());
|
|
if (nu5Active) {
|
|
auto finalOrchardRootBytes = blockindex->hashFinalOrchardRoot;
|
|
result.pushKV("finalorchardroot", HexStr(finalOrchardRootBytes.begin(), finalOrchardRootBytes.end()));
|
|
}
|
|
result.pushKV("chainhistoryroot", blockindex->hashChainHistoryRoot.GetHex());
|
|
UniValue txs(UniValue::VARR);
|
|
for (const CTransaction&tx : block.vtx)
|
|
{
|
|
if(txDetails)
|
|
{
|
|
UniValue objTx(UniValue::VOBJ);
|
|
TxToJSON(tx, uint256(), objTx);
|
|
txs.push_back(objTx);
|
|
}
|
|
else
|
|
txs.push_back(tx.GetHash().GetHex());
|
|
}
|
|
result.pushKV("tx", txs);
|
|
result.pushKV("time", block.GetBlockTime());
|
|
result.pushKV("nonce", block.nNonce.GetHex());
|
|
result.pushKV("solution", HexStr(block.nSolution));
|
|
result.pushKV("bits", strprintf("%08x", block.nBits));
|
|
result.pushKV("difficulty", GetDifficulty(blockindex));
|
|
result.pushKV("chainwork", blockindex->nChainWork.GetHex());
|
|
result.pushKV("anchor", blockindex->hashFinalSproutRoot.GetHex());
|
|
result.pushKV("chainSupply", ValuePoolDesc(std::nullopt, blockindex->nChainTotalSupply, blockindex->nChainSupplyDelta));
|
|
UniValue valuePools(UniValue::VARR);
|
|
valuePools.push_back(ValuePoolDesc("transparent", blockindex->nChainTransparentValue, blockindex->nTransparentValue));
|
|
valuePools.push_back(ValuePoolDesc("sprout", blockindex->nChainSproutValue, blockindex->nSproutValue));
|
|
valuePools.push_back(ValuePoolDesc("sapling", blockindex->nChainSaplingValue, blockindex->nSaplingValue));
|
|
valuePools.push_back(ValuePoolDesc("orchard", blockindex->nChainOrchardValue, blockindex->nOrchardValue));
|
|
result.pushKV("valuePools", valuePools);
|
|
|
|
{
|
|
UniValue trees(UniValue::VOBJ);
|
|
|
|
SaplingMerkleTree saplingTree;
|
|
if (pcoinsTip != nullptr && pcoinsTip->GetSaplingAnchorAt(blockindex->hashFinalSaplingRoot, saplingTree)) {
|
|
UniValue sapling(UniValue::VOBJ);
|
|
sapling.pushKV("size", (uint64_t)saplingTree.size());
|
|
trees.pushKV("sapling", sapling);
|
|
}
|
|
|
|
OrchardMerkleFrontier orchardTree;
|
|
if (pcoinsTip != nullptr && pcoinsTip->GetOrchardAnchorAt(blockindex->hashFinalOrchardRoot, orchardTree)) {
|
|
UniValue orchard(UniValue::VOBJ);
|
|
orchard.pushKV("size", (uint64_t)orchardTree.size());
|
|
trees.pushKV("orchard", orchard);
|
|
}
|
|
|
|
result.pushKV("trees", trees);
|
|
}
|
|
|
|
if (blockindex->pprev)
|
|
result.pushKV("previousblockhash", blockindex->pprev->GetBlockHash().GetHex());
|
|
CBlockIndex *pnext = chainActive.Next(blockindex);
|
|
if (pnext)
|
|
result.pushKV("nextblockhash", pnext->GetBlockHash().GetHex());
|
|
return result;
|
|
}
|
|
|
|
UniValue getblockcount(const UniValue& params, bool fHelp)
|
|
{
|
|
if (fHelp || params.size() != 0)
|
|
throw runtime_error(
|
|
"getblockcount\n"
|
|
"\nReturns the height of the most recent block in the best valid block chain (equivalently,\n"
|
|
"the number of blocks in this chain excluding the genesis block).\n"
|
|
"\nResult:\n"
|
|
"n (numeric) The height of the most recent block.\n"
|
|
"\nExamples:\n"
|
|
+ HelpExampleCli("getblockcount", "")
|
|
+ HelpExampleRpc("getblockcount", "")
|
|
);
|
|
|
|
LOCK(cs_main);
|
|
return chainActive.Height();
|
|
}
|
|
|
|
UniValue getbestblockhash(const UniValue& params, bool fHelp)
|
|
{
|
|
if (fHelp || params.size() != 0)
|
|
throw runtime_error(
|
|
"getbestblockhash\n"
|
|
"\nReturns the hash of the best (tip) block in the longest block chain.\n"
|
|
"\nResult\n"
|
|
"\"hex\" (string) the block hash hex encoded\n"
|
|
"\nExamples\n"
|
|
+ HelpExampleCli("getbestblockhash", "")
|
|
+ HelpExampleRpc("getbestblockhash", "")
|
|
);
|
|
|
|
LOCK(cs_main);
|
|
return chainActive.Tip()->GetBlockHash().GetHex();
|
|
}
|
|
|
|
UniValue getdifficulty(const UniValue& params, bool fHelp)
|
|
{
|
|
if (fHelp || params.size() != 0)
|
|
throw runtime_error(
|
|
"getdifficulty\n"
|
|
"\nReturns the proof-of-work difficulty as a multiple of the minimum difficulty.\n"
|
|
"\nResult:\n"
|
|
"n.nnn (numeric) the proof-of-work difficulty as a multiple of the minimum difficulty.\n"
|
|
"\nExamples:\n"
|
|
+ HelpExampleCli("getdifficulty", "")
|
|
+ HelpExampleRpc("getdifficulty", "")
|
|
);
|
|
|
|
LOCK(cs_main);
|
|
return GetNetworkDifficulty();
|
|
}
|
|
|
|
UniValue mempoolToJSON(bool fVerbose = false)
|
|
{
|
|
if (fVerbose)
|
|
{
|
|
LOCK(mempool.cs);
|
|
UniValue o(UniValue::VOBJ);
|
|
for (const CTxMemPoolEntry& e : mempool.mapTx)
|
|
{
|
|
const uint256& hash = e.GetTx().GetHash();
|
|
UniValue info(UniValue::VOBJ);
|
|
info.pushKV("size", (int)e.GetTxSize());
|
|
info.pushKV("fee", ValueFromAmount(e.GetFee()));
|
|
info.pushKV("modifiedfee", ValueFromAmount(e.GetModifiedFee()));
|
|
info.pushKV("time", e.GetTime());
|
|
info.pushKV("height", (int)e.GetHeight());
|
|
info.pushKV("descendantcount", e.GetCountWithDescendants());
|
|
info.pushKV("descendantsize", e.GetSizeWithDescendants());
|
|
info.pushKV("descendantfees", e.GetModFeesWithDescendants());
|
|
const CTransaction& tx = e.GetTx();
|
|
set<string> setDepends;
|
|
for (const CTxIn& txin : tx.vin)
|
|
{
|
|
if (mempool.exists(txin.prevout.hash))
|
|
setDepends.insert(txin.prevout.hash.ToString());
|
|
}
|
|
|
|
UniValue depends(UniValue::VARR);
|
|
for (const string& dep : setDepends)
|
|
{
|
|
depends.push_back(dep);
|
|
}
|
|
|
|
info.pushKV("depends", depends);
|
|
o.pushKV(hash.ToString(), info);
|
|
}
|
|
return o;
|
|
}
|
|
else
|
|
{
|
|
vector<uint256> vtxid;
|
|
mempool.queryHashes(vtxid);
|
|
|
|
UniValue a(UniValue::VARR);
|
|
for (const uint256& hash : vtxid)
|
|
a.push_back(hash.ToString());
|
|
|
|
return a;
|
|
}
|
|
}
|
|
|
|
UniValue getrawmempool(const UniValue& params, bool fHelp)
|
|
{
|
|
if (fHelp || params.size() > 1)
|
|
throw runtime_error(
|
|
"getrawmempool ( verbose )\n"
|
|
"\nReturns all transaction ids in memory pool as a json array of string transaction ids.\n"
|
|
"\nArguments:\n"
|
|
"1. verbose (boolean, optional, default=false) true for a json object, false for array of transaction ids\n"
|
|
"\nResult: (for verbose = false):\n"
|
|
"[ (json array of string)\n"
|
|
" \"transactionid\" (string) The transaction id\n"
|
|
" ,...\n"
|
|
"]\n"
|
|
"\nResult: (for verbose = true):\n"
|
|
"{ (json object)\n"
|
|
" \"transactionid\" : { (json object)\n"
|
|
" \"size\" : n, (numeric) transaction size in bytes\n"
|
|
" \"fee\" : n, (numeric) transaction fee in " + CURRENCY_UNIT + "\n"
|
|
" \"modifiedfee\" : n, (numeric) transaction fee with fee deltas used for mining priority\n"
|
|
" \"time\" : n, (numeric) local time transaction entered pool in seconds since 1 Jan 1970 GMT\n"
|
|
" \"height\" : n, (numeric) block height when transaction entered pool\n"
|
|
" \"descendantcount\" : n, (numeric) number of in-mempool descendant transactions (including this one)\n"
|
|
" \"descendantsize\" : n, (numeric) size of in-mempool descendants (including this one)\n"
|
|
" \"descendantfees\" : n, (numeric) modified fees (see \"modifiedfee\" above) of in-mempool descendants (including this one)\n"
|
|
" \"depends\" : [ (array) unconfirmed transactions used as inputs for this transaction\n"
|
|
" \"transactionid\", (string) parent transaction id\n"
|
|
" ... ]\n"
|
|
" }, ...\n"
|
|
"}\n"
|
|
"\nExamples\n"
|
|
+ HelpExampleCli("getrawmempool", "true")
|
|
+ HelpExampleRpc("getrawmempool", "true")
|
|
);
|
|
|
|
LOCK(cs_main);
|
|
|
|
bool fVerbose = false;
|
|
if (params.size() > 0)
|
|
fVerbose = params[0].get_bool();
|
|
|
|
return mempoolToJSON(fVerbose);
|
|
}
|
|
|
|
// insightexplorer
|
|
UniValue getblockdeltas(const UniValue& params, bool fHelp)
|
|
{
|
|
std::string disabledMsg = "";
|
|
if (!(fExperimentalInsightExplorer || fExperimentalLightWalletd)) {
|
|
disabledMsg = experimentalDisabledHelpMsg("getblockdeltas", {"insightexplorer", "lightwalletd"});
|
|
}
|
|
if (fHelp || params.size() != 1)
|
|
throw runtime_error(
|
|
"getblockdeltas \"blockhash\"\n"
|
|
"\nReturns information about the given block and its transactions.\n"
|
|
+ disabledMsg +
|
|
"\nArguments:\n"
|
|
"1. \"hash\" (string, required) The block hash\n"
|
|
"\nResult:\n"
|
|
"{\n"
|
|
" \"hash\": \"hash\", (string) block ID\n"
|
|
" \"confirmations\": n, (numeric) number of confirmations\n"
|
|
" \"size\": n, (numeric) block size in bytes\n"
|
|
" \"height\": n, (numeric) block height\n"
|
|
" \"version\": n, (numeric) block version (e.g. 4)\n"
|
|
" \"merkleroot\": \"hash\", (hexstring) block Merkle root\n"
|
|
" \"deltas\": [\n"
|
|
" {\n"
|
|
" \"txid\": \"hash\", (hexstring) transaction ID\n"
|
|
" \"index\": n, (numeric) The offset of the tx in the block\n"
|
|
" \"inputs\": [ (array of json objects)\n"
|
|
" {\n"
|
|
" \"address\": \"taddr\", (string) transparent address\n"
|
|
" \"satoshis\": n, (numeric) negative of spend amount\n"
|
|
" \"index\": n, (numeric) vin index\n"
|
|
" \"prevtxid\": \"hash\", (string) source utxo tx ID\n"
|
|
" \"prevout\": n (numeric) source utxo index\n"
|
|
" }, ...\n"
|
|
" ],\n"
|
|
" \"outputs\": [ (array of json objects)\n"
|
|
" {\n"
|
|
" \"address\": \"taddr\", (string) transparent address\n"
|
|
" \"satoshis\": n, (numeric) amount\n"
|
|
" \"index\": n (numeric) vout index\n"
|
|
" }, ...\n"
|
|
" ]\n"
|
|
" }, ...\n"
|
|
" ],\n"
|
|
" \"time\" : n, (numeric) The block version\n"
|
|
" \"mediantime\": n, (numeric) The most recent blocks' ave time\n"
|
|
" \"nonce\" : \"nonce\", (hex string) The nonce\n"
|
|
" \"bits\" : \"1d00ffff\", (hex string) The bits\n"
|
|
" \"difficulty\": n, (numeric) the current difficulty\n"
|
|
" \"chainwork\": \"xxxx\" (hex string) total amount of work in active chain\n"
|
|
" \"previousblockhash\" : \"hash\",(hex string) The hash of the previous block\n"
|
|
" \"nextblockhash\" : \"hash\" (hex string) The hash of the next block\n"
|
|
"}\n"
|
|
"\nExamples:\n"
|
|
+ HelpExampleCli("getblockdeltas", "00227e566682aebd6a7a5b772c96d7a999cadaebeaf1ce96f4191a3aad58b00b")
|
|
+ HelpExampleRpc("getblockdeltas", "\"00227e566682aebd6a7a5b772c96d7a999cadaebeaf1ce96f4191a3aad58b00b\"")
|
|
);
|
|
|
|
if (!(fExperimentalInsightExplorer || fExperimentalLightWalletd)) {
|
|
throw JSONRPCError(RPC_MISC_ERROR, "Error: getblockdeltas is disabled. "
|
|
"Run './zcash-cli help getblockdeltas' for instructions on how to enable this feature.");
|
|
}
|
|
|
|
std::string strHash = params[0].get_str();
|
|
uint256 hash(uint256S(strHash));
|
|
|
|
LOCK(cs_main);
|
|
|
|
if (mapBlockIndex.count(hash) == 0)
|
|
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Block not found");
|
|
|
|
CBlock block;
|
|
CBlockIndex* pblockindex = mapBlockIndex[hash];
|
|
|
|
if (fHavePruned && !(pblockindex->nStatus & BLOCK_HAVE_DATA) && pblockindex->nTx > 0)
|
|
throw JSONRPCError(RPC_INTERNAL_ERROR, "Block not available (pruned data)");
|
|
|
|
if (!ReadBlockFromDisk(block, pblockindex, Params().GetConsensus()))
|
|
throw JSONRPCError(RPC_INTERNAL_ERROR, "Can't read block from disk");
|
|
|
|
return blockToDeltasJSON(block, pblockindex);
|
|
}
|
|
|
|
// insightexplorer
|
|
UniValue getblockhashes(const UniValue& params, bool fHelp)
|
|
{
|
|
std::string disabledMsg = "";
|
|
if (!(fExperimentalInsightExplorer || fExperimentalLightWalletd)) {
|
|
disabledMsg = experimentalDisabledHelpMsg("getblockhashes", {"insightexplorer", "lightwalletd"});
|
|
}
|
|
if (fHelp || params.size() < 2)
|
|
throw runtime_error(
|
|
"getblockhashes high low ( {\"noOrphans\": true|false, \"logicalTimes\": true|false} )\n"
|
|
"\nReturns array of hashes of blocks within the timestamp range provided,\n"
|
|
"\ngreater or equal to low, less than high.\n"
|
|
+ disabledMsg +
|
|
"\nArguments:\n"
|
|
"1. high (numeric, required) The newer block timestamp\n"
|
|
"2. low (numeric, required) The older block timestamp\n"
|
|
"3. options (string, optional) A json object\n"
|
|
" {\n"
|
|
" \"noOrphans\": true|false (boolean) will only include blocks on the main chain\n"
|
|
" \"logicalTimes\": true|false (boolean) will include logical timestamps with hashes\n"
|
|
" }\n"
|
|
"\nResult:\n"
|
|
"[\n"
|
|
" \"xxxx\" (hex string) The block hash\n"
|
|
"]\n"
|
|
"or\n"
|
|
"[\n"
|
|
" {\n"
|
|
" \"blockhash\": \"xxxx\" (hex string) The block hash\n"
|
|
" \"logicalts\": n (numeric) The logical timestamp\n"
|
|
" }\n"
|
|
"]\n"
|
|
"\nExamples:\n"
|
|
+ HelpExampleCli("getblockhashes", "1558141697 1558141576")
|
|
+ HelpExampleRpc("getblockhashes", "1558141697, 1558141576")
|
|
+ HelpExampleCli("getblockhashes", "1558141697 1558141576 '{\"noOrphans\":false, \"logicalTimes\":true}'")
|
|
);
|
|
|
|
if (!(fExperimentalInsightExplorer || fExperimentalLightWalletd)) {
|
|
throw JSONRPCError(RPC_MISC_ERROR, "Error: getblockhashes is disabled. "
|
|
"Run './zcash-cli help getblockhashes' for instructions on how to enable this feature.");
|
|
}
|
|
|
|
unsigned int high = params[0].get_int();
|
|
unsigned int low = params[1].get_int();
|
|
bool fActiveOnly = false;
|
|
bool fLogicalTS = false;
|
|
|
|
if (params.size() > 2) {
|
|
UniValue noOrphans = find_value(params[2].get_obj(), "noOrphans");
|
|
if (!noOrphans.isNull())
|
|
fActiveOnly = noOrphans.get_bool();
|
|
|
|
UniValue returnLogical = find_value(params[2].get_obj(), "logicalTimes");
|
|
if (!returnLogical.isNull())
|
|
fLogicalTS = returnLogical.get_bool();
|
|
}
|
|
|
|
std::vector<std::pair<uint256, unsigned int> > blockHashes;
|
|
{
|
|
LOCK(cs_main);
|
|
if (!GetTimestampIndex(high, low, fActiveOnly, blockHashes)) {
|
|
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY,
|
|
"No information available for block hashes");
|
|
}
|
|
}
|
|
UniValue result(UniValue::VARR);
|
|
for (std::vector<std::pair<uint256, unsigned int> >::const_iterator it=blockHashes.begin();
|
|
it!=blockHashes.end(); it++) {
|
|
if (fLogicalTS) {
|
|
UniValue item(UniValue::VOBJ);
|
|
item.pushKV("blockhash", it->first.GetHex());
|
|
item.pushKV("logicalts", (int)it->second);
|
|
result.push_back(item);
|
|
} else {
|
|
result.push_back(it->first.GetHex());
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
//! Sanity-check a height argument and interpret negative values.
|
|
int interpretHeightArg(int nHeight, int currentHeight)
|
|
{
|
|
if (nHeight < 0) {
|
|
nHeight += currentHeight + 1;
|
|
}
|
|
if (nHeight < 0 || nHeight > currentHeight) {
|
|
throw JSONRPCError(RPC_INVALID_PARAMETER, "Block height out of range");
|
|
}
|
|
return nHeight;
|
|
}
|
|
|
|
//! Parse and sanity-check a height argument, return its integer representation.
|
|
int parseHeightArg(const std::string& strHeight, int currentHeight)
|
|
{
|
|
// std::stoi allows (locale-dependent) whitespace and optional '+' sign,
|
|
// whereas we want to be strict.
|
|
regex r("(?:(-?)[1-9][0-9]*|[0-9]+)");
|
|
if (!regex_match(strHeight, r)) {
|
|
throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid block height parameter");
|
|
}
|
|
int nHeight;
|
|
try {
|
|
nHeight = std::stoi(strHeight);
|
|
}
|
|
catch (const std::exception &e) {
|
|
throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid block height parameter");
|
|
}
|
|
return interpretHeightArg(nHeight, currentHeight);
|
|
}
|
|
|
|
UniValue getblockhash(const UniValue& params, bool fHelp)
|
|
{
|
|
if (fHelp || params.size() != 1)
|
|
throw runtime_error(
|
|
"getblockhash index\n"
|
|
"\nReturns hash of block in best-block-chain at index provided.\n"
|
|
"\nArguments:\n"
|
|
"1. index (numeric, required) The block index. If negative then -1 is the last known valid block\n"
|
|
"\nResult:\n"
|
|
"\"hash\" (string) The block hash\n"
|
|
"\nExamples:\n"
|
|
+ HelpExampleCli("getblockhash", "1000")
|
|
+ HelpExampleRpc("getblockhash", "1000")
|
|
);
|
|
|
|
LOCK(cs_main);
|
|
|
|
const CBlockIndex* pblockindex = chainActive[interpretHeightArg(params[0].get_int(), chainActive.Height())];
|
|
return pblockindex->GetBlockHash().GetHex();
|
|
}
|
|
|
|
UniValue getblockheader(const UniValue& params, bool fHelp)
|
|
{
|
|
if (fHelp || params.size() < 1 || params.size() > 2)
|
|
throw runtime_error(
|
|
"getblockheader \"hash\" ( verbose )\n"
|
|
"\nIf 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"
|
|
"\nArguments:\n"
|
|
"1. \"hash\" (string, required) The block hash\n"
|
|
"2. verbose (boolean, optional, default=true) true for a json object, false for the hex encoded data\n"
|
|
"\nResult (for verbose = true):\n"
|
|
"{\n"
|
|
" \"hash\" : \"hash\", (string) the block hash (same as provided)\n"
|
|
" \"confirmations\" : n, (numeric) The number of confirmations, or -1 if the block is not on the main chain\n"
|
|
" \"height\" : n, (numeric) The block height or index\n"
|
|
" \"version\" : n, (numeric) The block version\n"
|
|
" \"merkleroot\" : \"xxxx\", (string) The merkle root\n"
|
|
" \"finalsaplingroot\" : \"xxxx\", (string) The root of the Sapling commitment tree after applying this block\n"
|
|
" \"time\" : ttt, (numeric) The block time in seconds since epoch (Jan 1 1970 GMT)\n"
|
|
" \"nonce\" : n, (numeric) The nonce\n"
|
|
" \"bits\" : \"1d00ffff\", (string) The bits\n"
|
|
" \"difficulty\" : x.xxx, (numeric) The difficulty\n"
|
|
" \"previousblockhash\" : \"hash\", (string) The hash of the previous block\n"
|
|
" \"nextblockhash\" : \"hash\" (string) The hash of the next block\n"
|
|
"}\n"
|
|
"\nResult (for verbose=false):\n"
|
|
"\"data\" (string) A string that is serialized, hex-encoded data for block 'hash'.\n"
|
|
"\nExamples:\n"
|
|
+ HelpExampleCli("getblockheader", "\"00000000c937983704a73af28acdec37b049d214adbda81d7e2a3dd146f6ed09\"")
|
|
+ HelpExampleRpc("getblockheader", "\"00000000c937983704a73af28acdec37b049d214adbda81d7e2a3dd146f6ed09\"")
|
|
);
|
|
|
|
LOCK(cs_main);
|
|
|
|
std::string strHash = params[0].get_str();
|
|
uint256 hash(uint256S(strHash));
|
|
|
|
bool fVerbose = true;
|
|
if (params.size() > 1)
|
|
fVerbose = params[1].get_bool();
|
|
|
|
if (mapBlockIndex.count(hash) == 0)
|
|
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Block not found");
|
|
|
|
CBlockIndex* pblockindex = mapBlockIndex[hash];
|
|
|
|
try {
|
|
if (!fVerbose) {
|
|
CDataStream ssBlock(SER_NETWORK, PROTOCOL_VERSION);
|
|
ssBlock << pblockindex->GetBlockHeader();
|
|
std::string strHex = HexStr(ssBlock.begin(), ssBlock.end());
|
|
return strHex;
|
|
} else {
|
|
return blockheaderToJSON(pblockindex);
|
|
}
|
|
} catch (const runtime_error&) {
|
|
throw JSONRPCError(RPC_DATABASE_ERROR, "Failed to read index entry");
|
|
}
|
|
}
|
|
|
|
UniValue getblock(const UniValue& params, bool fHelp)
|
|
{
|
|
if (fHelp || params.size() < 1 || params.size() > 2)
|
|
throw runtime_error(
|
|
"getblock \"hash|height\" ( verbosity )\n"
|
|
"\nIf verbosity is 0, returns a string that is serialized, hex-encoded data for the block.\n"
|
|
"If verbosity is 1, returns an Object with information about the block.\n"
|
|
"If verbosity is 2, returns an Object with information about the block and information about each transaction. \n"
|
|
"\nArguments:\n"
|
|
"1. \"hash|height\" (string, required) The block hash or height. Height can be negative where -1 is the last known valid block\n"
|
|
"2. verbosity (numeric, optional, default=1) 0 for hex encoded data, 1 for a json object, and 2 for json object with transaction data\n"
|
|
"\nResult (for verbosity = 0):\n"
|
|
"\"data\" (string) A string that is serialized, hex-encoded data for the block.\n"
|
|
"\nResult (for verbosity = 1):\n"
|
|
"{\n"
|
|
" \"hash\" : \"hash\", (string) the block hash (same as provided hash)\n"
|
|
" \"confirmations\" : n, (numeric) The number of confirmations, or -1 if the block is not on the main chain\n"
|
|
" \"size\" : n, (numeric) The block size\n"
|
|
" \"height\" : n, (numeric) The block height or index (same as provided height)\n"
|
|
" \"version\" : n, (numeric) The block version\n"
|
|
" \"merkleroot\" : \"xxxx\", (string) The merkle root\n"
|
|
" \"finalsaplingroot\" : \"xxxx\", (string) The root of the Sapling commitment tree after applying this block\n"
|
|
" \"finalorchardroot\" : \"xxxx\", (string, optional) The root of the Orchard commitment tree after\n"
|
|
" applying this block. Omitted for blocks prior to NU5 activation. This\n"
|
|
" will be the null hash if this block has never been connected to a\n"
|
|
" main chain.\n"
|
|
" NB: The serialized representation of this field returned by this method\n"
|
|
" was byte-flipped relative to its representation in the `getrawtransaction`\n"
|
|
" output in prior releases up to and including v5.2.0. This has now been\n"
|
|
" rectified.\n"
|
|
" \"tx\" : [ (array of string) The transaction ids\n"
|
|
" \"transactionid\" (string) The transaction id\n"
|
|
" ,...\n"
|
|
" ],\n"
|
|
" \"time\" : ttt, (numeric) The block time in seconds since epoch (Jan 1 1970 GMT)\n"
|
|
" \"nonce\" : n, (numeric) The nonce\n"
|
|
" \"bits\" : \"1d00ffff\", (string) The bits\n"
|
|
" \"difficulty\" : x.xxx, (numeric) The difficulty\n"
|
|
" \"chainSupply\": { (object) information about the total supply\n"
|
|
" \"monitored\": xx, (boolean) true if the total supply is being monitored\n"
|
|
" \"chainValue\": xxxxxx, (numeric, optional) total chain supply after this block, in " + CURRENCY_UNIT + "\n"
|
|
" \"chainValueZat\": xxxxxx, (numeric, optional) total chain supply after this block, in " + MINOR_CURRENCY_UNIT + "\n"
|
|
" \"valueDelta\": xxxxxx, (numeric, optional) change to the chain supply produced by this block, in " + CURRENCY_UNIT + "\n"
|
|
" \"valueDeltaZat\": xxxxxx, (numeric, optional) change to the chain supply produced by this block, in " + MINOR_CURRENCY_UNIT + "\n"
|
|
" },\n"
|
|
" \"valuePools\": [ (array) information about each value pool\n"
|
|
" {\n"
|
|
" \"id\": \"xxxx\", (string) name of the pool\n"
|
|
" \"monitored\": xx, (boolean) true if the pool is being monitored\n"
|
|
" \"chainValue\": xxxxxx, (numeric, optional) total amount in the pool, in " + CURRENCY_UNIT + "\n"
|
|
" \"chainValueZat\": xxxxxx, (numeric, optional) total amount in the pool, in " + MINOR_CURRENCY_UNIT + "\n"
|
|
" \"valueDelta\": xxxxxx, (numeric, optional) change to the amount in the pool produced by this block, in " + CURRENCY_UNIT + "\n"
|
|
" \"valueDeltaZat\": xxxxxx, (numeric, optional) change to the amount in the pool produced by this block, in " + MINOR_CURRENCY_UNIT + "\n"
|
|
" }, ...\n"
|
|
" ],\n"
|
|
" \"trees\": { (object) information about the note commitment trees\n"
|
|
" \"sapling\": { (object, optional)\n"
|
|
" \"size\": n, (numeric) the total number of Sapling note commitments as of the end of this block\n"
|
|
" },\n"
|
|
" \"orchard\": { (object, optional)\n"
|
|
" \"size\": n, (numeric) the total number of Orchard note commitments as of the end of this block\n"
|
|
" },\n"
|
|
" },\n"
|
|
" \"previousblockhash\" : \"hash\", (string) The hash of the previous block\n"
|
|
" \"nextblockhash\" : \"hash\" (string) The hash of the next block\n"
|
|
"}\n"
|
|
"\nResult (for verbosity = 2):\n"
|
|
"{\n"
|
|
" ..., Same output as verbosity = 1.\n"
|
|
" \"tx\" : [ (array of Objects) The transactions in the format of the getrawtransaction RPC. Different from verbosity = 1 \"tx\" result.\n"
|
|
" ,...\n"
|
|
" ],\n"
|
|
" ,... Same output as verbosity = 1.\n"
|
|
"}\n"
|
|
"\nExamples:\n"
|
|
+ HelpExampleCli("getblock", "\"00000000febc373a1da2bd9f887b105ad79ddc26ac26c2b28652d64e5207c5b5\"")
|
|
+ HelpExampleRpc("getblock", "\"00000000febc373a1da2bd9f887b105ad79ddc26ac26c2b28652d64e5207c5b5\"")
|
|
+ HelpExampleCli("getblock", "12800")
|
|
+ HelpExampleRpc("getblock", "12800")
|
|
);
|
|
|
|
LOCK(cs_main);
|
|
|
|
std::string strHash = params[0].get_str();
|
|
|
|
// If height is supplied, find the hash
|
|
if (strHash.size() < (2 * sizeof(uint256))) {
|
|
strHash = chainActive[parseHeightArg(strHash, chainActive.Height())]->GetBlockHash().GetHex();
|
|
}
|
|
|
|
uint256 hash(uint256S(strHash));
|
|
|
|
int verbosity = 1;
|
|
if (params.size() > 1) {
|
|
if(params[1].isNum()) {
|
|
verbosity = params[1].get_int();
|
|
} else {
|
|
verbosity = params[1].get_bool() ? 1 : 0;
|
|
}
|
|
}
|
|
|
|
if (verbosity < 0 || verbosity > 2) {
|
|
throw JSONRPCError(RPC_INVALID_PARAMETER, "Verbosity must be in range from 0 to 2");
|
|
}
|
|
|
|
if (mapBlockIndex.count(hash) == 0)
|
|
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Block not found");
|
|
|
|
CBlock block;
|
|
CBlockIndex* pblockindex = mapBlockIndex[hash];
|
|
|
|
if (fHavePruned && !(pblockindex->nStatus & BLOCK_HAVE_DATA) && pblockindex->nTx > 0)
|
|
throw JSONRPCError(RPC_INTERNAL_ERROR, "Block not available (pruned data)");
|
|
|
|
if(!ReadBlockFromDisk(block, pblockindex, Params().GetConsensus()))
|
|
throw JSONRPCError(RPC_INTERNAL_ERROR, "Can't read block from disk");
|
|
|
|
if (verbosity == 0)
|
|
{
|
|
CDataStream ssBlock(SER_NETWORK, PROTOCOL_VERSION);
|
|
ssBlock << block;
|
|
std::string strHex = HexStr(ssBlock.begin(), ssBlock.end());
|
|
return strHex;
|
|
}
|
|
|
|
return blockToJSON(block, pblockindex, verbosity >= 2);
|
|
}
|
|
|
|
UniValue gettxoutsetinfo(const UniValue& params, bool fHelp)
|
|
{
|
|
if (fHelp || params.size() != 0)
|
|
throw runtime_error(
|
|
"gettxoutsetinfo\n"
|
|
"\nReturns statistics about the unspent transaction output set.\n"
|
|
"Note this call may take some time.\n"
|
|
"\nResult:\n"
|
|
"{\n"
|
|
" \"height\":n, (numeric) The current block height (index)\n"
|
|
" \"bestblock\": \"hex\", (string) the best block hash hex\n"
|
|
" \"transactions\": n, (numeric) The number of transactions\n"
|
|
" \"txouts\": n, (numeric) The number of output transactions\n"
|
|
" \"bytes_serialized\": n, (numeric) The serialized size\n"
|
|
" \"hash_serialized\": \"hash\", (string) The serialized hash\n"
|
|
" \"total_amount\": x.xxx (numeric) The total amount\n"
|
|
"}\n"
|
|
"\nExamples:\n"
|
|
+ HelpExampleCli("gettxoutsetinfo", "")
|
|
+ HelpExampleRpc("gettxoutsetinfo", "")
|
|
);
|
|
|
|
UniValue ret(UniValue::VOBJ);
|
|
|
|
CCoinsStats stats;
|
|
FlushStateToDisk();
|
|
if (pcoinsTip->GetStats(stats)) {
|
|
ret.pushKV("height", (int64_t)stats.nHeight);
|
|
ret.pushKV("bestblock", stats.hashBlock.GetHex());
|
|
ret.pushKV("transactions", (int64_t)stats.nTransactions);
|
|
ret.pushKV("txouts", (int64_t)stats.nTransactionOutputs);
|
|
ret.pushKV("bytes_serialized", (int64_t)stats.nSerializedSize);
|
|
ret.pushKV("hash_serialized", stats.hashSerialized.GetHex());
|
|
ret.pushKV("total_amount", ValueFromAmount(stats.nTotalAmount));
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
UniValue gettxout(const UniValue& params, bool fHelp)
|
|
{
|
|
if (fHelp || params.size() < 2 || params.size() > 3)
|
|
throw runtime_error(
|
|
"gettxout \"txid\" n ( includemempool )\n"
|
|
"\nReturns details about an unspent transaction output.\n"
|
|
"\nArguments:\n"
|
|
"1. \"txid\" (string, required) The transaction id\n"
|
|
"2. n (numeric, required) vout value\n"
|
|
"3. includemempool (boolean, optional) Whether to include the mempool\n"
|
|
"\nResult:\n"
|
|
"{\n"
|
|
" \"bestblock\" : \"hash\", (string) the block hash\n"
|
|
" \"confirmations\" : n, (numeric) The number of confirmations\n"
|
|
" \"value\" : x.xxx, (numeric) The transaction value in " + CURRENCY_UNIT + "\n"
|
|
" \"scriptPubKey\" : { (json object)\n"
|
|
" \"asm\" : \"code\", (string) \n"
|
|
" \"hex\" : \"hex\", (string) \n"
|
|
" \"reqSigs\" : n, (numeric) Number of required signatures\n"
|
|
" \"type\" : \"pubkeyhash\", (string) The type, eg pubkeyhash\n"
|
|
" \"addresses\" : [ (array of string) array of Zcash addresses\n"
|
|
" \"zcashaddress\" (string) Zcash address\n"
|
|
" ,...\n"
|
|
" ]\n"
|
|
" },\n"
|
|
" \"version\" : n, (numeric) The version\n"
|
|
" \"coinbase\" : true|false (boolean) Coinbase or not\n"
|
|
"}\n"
|
|
|
|
"\nExamples:\n"
|
|
"\nGet unspent transactions\n"
|
|
+ HelpExampleCli("listunspent", "") +
|
|
"\nView the details\n"
|
|
+ HelpExampleCli("gettxout", "\"txid\" 1") +
|
|
"\nAs a json rpc call\n"
|
|
+ HelpExampleRpc("gettxout", "\"txid\", 1")
|
|
);
|
|
|
|
LOCK(cs_main);
|
|
|
|
UniValue ret(UniValue::VOBJ);
|
|
|
|
std::string strHash = params[0].get_str();
|
|
uint256 hash(uint256S(strHash));
|
|
int n = params[1].get_int();
|
|
bool fMempool = true;
|
|
if (params.size() > 2)
|
|
fMempool = params[2].get_bool();
|
|
|
|
CCoins coins;
|
|
if (fMempool) {
|
|
LOCK(mempool.cs);
|
|
CCoinsViewMemPool view(pcoinsTip, mempool);
|
|
if (!view.GetCoins(hash, coins))
|
|
return NullUniValue;
|
|
mempool.pruneSpent(hash, coins); // TODO: this should be done by the CCoinsViewMemPool
|
|
} else {
|
|
if (!pcoinsTip->GetCoins(hash, coins))
|
|
return NullUniValue;
|
|
}
|
|
if (n<0 || (unsigned int)n>=coins.vout.size() || coins.vout[n].IsNull())
|
|
return NullUniValue;
|
|
|
|
BlockMap::iterator it = mapBlockIndex.find(pcoinsTip->GetBestBlock());
|
|
CBlockIndex *pindex = it->second;
|
|
ret.pushKV("bestblock", pindex->GetBlockHash().GetHex());
|
|
if ((unsigned int)coins.nHeight == MEMPOOL_HEIGHT)
|
|
ret.pushKV("confirmations", 0);
|
|
else
|
|
ret.pushKV("confirmations", pindex->nHeight - coins.nHeight + 1);
|
|
ret.pushKV("value", ValueFromAmount(coins.vout[n].nValue));
|
|
UniValue o(UniValue::VOBJ);
|
|
ScriptPubKeyToJSON(coins.vout[n].scriptPubKey, o, true);
|
|
ret.pushKV("scriptPubKey", o);
|
|
ret.pushKV("version", coins.nVersion);
|
|
ret.pushKV("coinbase", coins.fCoinBase);
|
|
|
|
return ret;
|
|
}
|
|
|
|
UniValue verifychain(const UniValue& params, bool fHelp)
|
|
{
|
|
int nCheckLevel = GetArg("-checklevel", DEFAULT_CHECKLEVEL);
|
|
int nCheckDepth = GetArg("-checkblocks", DEFAULT_CHECKBLOCKS);
|
|
if (fHelp || params.size() > 2)
|
|
throw runtime_error(
|
|
"verifychain ( checklevel numblocks )\n"
|
|
"\nVerifies blockchain database.\n"
|
|
"\nArguments:\n"
|
|
"1. checklevel (numeric, optional, 0-4, default=" + strprintf("%d", nCheckLevel) + ") How thorough the block verification is.\n"
|
|
"2. numblocks (numeric, optional, default=" + strprintf("%d", nCheckDepth) + ", 0=all) The number of blocks to check.\n"
|
|
"\nResult:\n"
|
|
"true|false (boolean) Verified or not\n"
|
|
"\nExamples:\n"
|
|
+ HelpExampleCli("verifychain", "")
|
|
+ HelpExampleRpc("verifychain", "")
|
|
);
|
|
|
|
LOCK(cs_main);
|
|
|
|
if (params.size() > 0)
|
|
nCheckLevel = params[0].get_int();
|
|
if (params.size() > 1)
|
|
nCheckDepth = params[1].get_int();
|
|
|
|
return CVerifyDB().VerifyDB(Params(), pcoinsTip, nCheckLevel, nCheckDepth);
|
|
}
|
|
|
|
/** Implementation of IsSuperMajority with better feedback */
|
|
static UniValue SoftForkMajorityDesc(int minVersion, CBlockIndex* pindex, int nRequired, const Consensus::Params& consensusParams)
|
|
{
|
|
int nFound = 0;
|
|
CBlockIndex* pstart = pindex;
|
|
for (int i = 0; i < consensusParams.nMajorityWindow && pstart != NULL; i++)
|
|
{
|
|
if (pstart->nVersion >= minVersion)
|
|
++nFound;
|
|
pstart = pstart->pprev;
|
|
}
|
|
|
|
UniValue rv(UniValue::VOBJ);
|
|
rv.pushKV("status", nFound >= nRequired);
|
|
rv.pushKV("found", nFound);
|
|
rv.pushKV("required", nRequired);
|
|
rv.pushKV("window", consensusParams.nMajorityWindow);
|
|
return rv;
|
|
}
|
|
|
|
static UniValue SoftForkDesc(const std::string &name, int version, CBlockIndex* pindex, const Consensus::Params& consensusParams)
|
|
{
|
|
UniValue rv(UniValue::VOBJ);
|
|
rv.pushKV("id", name);
|
|
rv.pushKV("version", version);
|
|
rv.pushKV("enforce", SoftForkMajorityDesc(version, pindex, consensusParams.nMajorityEnforceBlockUpgrade, consensusParams));
|
|
rv.pushKV("reject", SoftForkMajorityDesc(version, pindex, consensusParams.nMajorityRejectBlockOutdated, consensusParams));
|
|
return rv;
|
|
}
|
|
|
|
static UniValue NetworkUpgradeDesc(const Consensus::Params& consensusParams, Consensus::UpgradeIndex idx, int height)
|
|
{
|
|
UniValue rv(UniValue::VOBJ);
|
|
auto upgrade = NetworkUpgradeInfo[idx];
|
|
rv.pushKV("name", upgrade.strName);
|
|
rv.pushKV("activationheight", consensusParams.vUpgrades[idx].nActivationHeight);
|
|
switch (NetworkUpgradeState(height, consensusParams, idx)) {
|
|
case UPGRADE_DISABLED: rv.pushKV("status", "disabled"); break;
|
|
case UPGRADE_PENDING: rv.pushKV("status", "pending"); break;
|
|
case UPGRADE_ACTIVE: rv.pushKV("status", "active"); break;
|
|
}
|
|
rv.pushKV("info", upgrade.strInfo);
|
|
return rv;
|
|
}
|
|
|
|
void NetworkUpgradeDescPushBack(
|
|
UniValue& networkUpgrades,
|
|
const Consensus::Params& consensusParams,
|
|
Consensus::UpgradeIndex idx,
|
|
int height)
|
|
{
|
|
// Network upgrades with an activation height of NO_ACTIVATION_HEIGHT are
|
|
// hidden. This is used when network upgrade implementations are merged
|
|
// without specifying the activation height.
|
|
if (consensusParams.vUpgrades[idx].nActivationHeight != Consensus::NetworkUpgrade::NO_ACTIVATION_HEIGHT) {
|
|
networkUpgrades.pushKV(
|
|
HexInt(NetworkUpgradeInfo[idx].nBranchId),
|
|
NetworkUpgradeDesc(consensusParams, idx, height));
|
|
}
|
|
}
|
|
|
|
|
|
UniValue getblockchaininfo(const UniValue& params, bool fHelp)
|
|
{
|
|
if (fHelp || params.size() != 0)
|
|
throw runtime_error(
|
|
"getblockchaininfo\n"
|
|
"Returns an object containing various state info regarding block chain processing.\n"
|
|
"\nNote that when the chain tip is at the last block before a network upgrade activation,\n"
|
|
"consensus.chaintip != consensus.nextblock.\n"
|
|
"\nResult:\n"
|
|
"{\n"
|
|
" \"chain\": \"xxxx\", (string) current network name as defined in BIP70 (main, test, regtest)\n"
|
|
" \"blocks\": xxxxxx, (numeric) the current number of blocks processed in the server\n"
|
|
" \"initial_block_download_complete\": xx, (boolean) true if the initial download of the blockchain is complete\n"
|
|
" \"headers\": xxxxxx, (numeric) the current number of headers we have validated\n"
|
|
" \"bestblockhash\": \"...\", (string) the hash of the currently best block\n"
|
|
" \"difficulty\": xxxxxx, (numeric) the current difficulty\n"
|
|
" \"verificationprogress\": xxxx, (numeric) estimate of verification progress [0..1]\n"
|
|
" \"estimatedheight\": xxxx, (numeric) if syncing, the estimated height of the chain, else the current best height\n"
|
|
" \"chainwork\": \"xxxx\" (string) total amount of work in active chain, in hexadecimal\n"
|
|
" \"size_on_disk\": xxxxxx, (numeric) the estimated size of the block and undo files on disk\n"
|
|
" \"commitments\": xxxxxx, (numeric) the current number of note commitments in the commitment tree\n"
|
|
" \"chainSupply\": { (object) information about the total supply\n"
|
|
" \"monitored\": xx, (boolean) true if the total supply is being monitored\n"
|
|
" \"chainValue\": xxxxxx, (numeric, optional) total chain supply after this block, in " + CURRENCY_UNIT + "\n"
|
|
" \"chainValueZat\": xxxxxx, (numeric, optional) total chain supply after this block, in " + MINOR_CURRENCY_UNIT + "\n"
|
|
" }\n"
|
|
" \"valuePools\": [ (array) information about each value pool\n"
|
|
" {\n"
|
|
" \"id\": \"xxxx\", (string) name of the pool\n"
|
|
" \"monitored\": xx, (boolean) true if the pool is being monitored\n"
|
|
" \"chainValue\": xxxxxx, (numeric, optional) total amount in the pool, in " + CURRENCY_UNIT + "\n"
|
|
" \"chainValueZat\": xxxxxx, (numeric, optional) total amount in the pool, in " + MINOR_CURRENCY_UNIT + "\n"
|
|
" }, ...\n"
|
|
" ]\n"
|
|
" \"softforks\": [ (array) status of softforks in progress\n"
|
|
" {\n"
|
|
" \"id\": \"xxxx\", (string) name of softfork\n"
|
|
" \"version\": xx, (numeric) block version\n"
|
|
" \"enforce\": { (object) progress toward enforcing the softfork rules for new-version blocks\n"
|
|
" \"status\": xx, (boolean) true if threshold reached\n"
|
|
" \"found\": xx, (numeric) number of blocks with the new version found\n"
|
|
" \"required\": xx, (numeric) number of blocks required to trigger\n"
|
|
" \"window\": xx, (numeric) maximum size of examined window of recent blocks\n"
|
|
" },\n"
|
|
" \"reject\": { ... } (object) progress toward rejecting pre-softfork blocks (same fields as \"enforce\")\n"
|
|
" }, ...\n"
|
|
" ],\n"
|
|
" \"upgrades\": { (object) status of network upgrades\n"
|
|
" \"xxxx\" : { (string) branch ID of the upgrade\n"
|
|
" \"name\": \"xxxx\", (string) name of upgrade\n"
|
|
" \"activationheight\": xxxxxx, (numeric) block height of activation\n"
|
|
" \"status\": \"xxxx\", (string) status of upgrade\n"
|
|
" \"info\": \"xxxx\", (string) additional information about upgrade\n"
|
|
" }, ...\n"
|
|
" },\n"
|
|
" \"consensus\": { (object) branch IDs of the current and upcoming consensus rules\n"
|
|
" \"chaintip\": \"xxxxxxxx\", (string) branch ID used to validate the current chain tip\n"
|
|
" \"nextblock\": \"xxxxxxxx\" (string) branch ID that the next block will be validated under\n"
|
|
" }\n"
|
|
"}\n"
|
|
"\nExamples:\n"
|
|
+ HelpExampleCli("getblockchaininfo", "")
|
|
+ HelpExampleRpc("getblockchaininfo", "")
|
|
);
|
|
|
|
LOCK(cs_main);
|
|
|
|
UniValue obj(UniValue::VOBJ);
|
|
obj.pushKV("chain", Params().NetworkIDString());
|
|
obj.pushKV("blocks", (int)chainActive.Height());
|
|
obj.pushKV("initial_block_download_complete", !IsInitialBlockDownload(Params().GetConsensus()));
|
|
obj.pushKV("headers", pindexBestHeader ? pindexBestHeader->nHeight : -1);
|
|
obj.pushKV("bestblockhash", chainActive.Tip()->GetBlockHash().GetHex());
|
|
obj.pushKV("difficulty", (double)GetNetworkDifficulty());
|
|
obj.pushKV("verificationprogress", Checkpoints::GuessVerificationProgress(Params().Checkpoints(), chainActive.Tip()));
|
|
obj.pushKV("chainwork", chainActive.Tip()->nChainWork.GetHex());
|
|
obj.pushKV("pruned", fPruneMode);
|
|
obj.pushKV("size_on_disk", CalculateCurrentUsage());
|
|
|
|
if (IsInitialBlockDownload(Params().GetConsensus()))
|
|
obj.pushKV("estimatedheight", EstimateNetHeight(Params().GetConsensus(), (int)chainActive.Height(), chainActive.Tip()->GetMedianTimePast()));
|
|
else
|
|
obj.pushKV("estimatedheight", (int)chainActive.Height());
|
|
|
|
SproutMerkleTree tree;
|
|
pcoinsTip->GetSproutAnchorAt(pcoinsTip->GetBestAnchor(SPROUT), tree);
|
|
obj.pushKV("commitments", static_cast<uint64_t>(tree.size()));
|
|
|
|
CBlockIndex* tip = chainActive.Tip();
|
|
obj.pushKV("chainSupply", ValuePoolDesc(std::nullopt, tip->nChainTotalSupply, std::nullopt));
|
|
UniValue valuePools(UniValue::VARR);
|
|
valuePools.push_back(ValuePoolDesc("transparent", tip->nChainTransparentValue, std::nullopt));
|
|
valuePools.push_back(ValuePoolDesc("sprout", tip->nChainSproutValue, std::nullopt));
|
|
valuePools.push_back(ValuePoolDesc("sapling", tip->nChainSaplingValue, std::nullopt));
|
|
valuePools.push_back(ValuePoolDesc("orchard", tip->nChainOrchardValue, std::nullopt));
|
|
obj.pushKV("valuePools", valuePools);
|
|
|
|
const CChainParams& chainparams = Params();
|
|
const Consensus::Params& consensusParams = chainparams.GetConsensus();
|
|
|
|
UniValue softforks(UniValue::VARR);
|
|
softforks.push_back(SoftForkDesc("bip34", 2, tip, consensusParams));
|
|
softforks.push_back(SoftForkDesc("bip66", 3, tip, consensusParams));
|
|
softforks.push_back(SoftForkDesc("bip65", 4, tip, consensusParams));
|
|
obj.pushKV("softforks", softforks);
|
|
|
|
UniValue upgrades(UniValue::VOBJ);
|
|
for (int i = Consensus::UPGRADE_OVERWINTER; i < Consensus::MAX_NETWORK_UPGRADES; i++) {
|
|
NetworkUpgradeDescPushBack(upgrades, consensusParams, Consensus::UpgradeIndex(i), tip->nHeight);
|
|
}
|
|
obj.pushKV("upgrades", upgrades);
|
|
|
|
UniValue consensus(UniValue::VOBJ);
|
|
consensus.pushKV("chaintip", HexInt(CurrentEpochBranchId(tip->nHeight, consensusParams)));
|
|
consensus.pushKV("nextblock", HexInt(CurrentEpochBranchId(tip->nHeight + 1, consensusParams)));
|
|
obj.pushKV("consensus", consensus);
|
|
|
|
if (fPruneMode)
|
|
{
|
|
CBlockIndex *block = chainActive.Tip();
|
|
while (block && block->pprev && (block->pprev->nStatus & BLOCK_HAVE_DATA))
|
|
block = block->pprev;
|
|
|
|
obj.pushKV("pruneheight", block->nHeight);
|
|
}
|
|
|
|
if (Params().NetworkIDString() == "regtest") {
|
|
obj.pushKV("fullyNotified", ChainIsFullyNotified(chainparams));
|
|
}
|
|
|
|
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;
|
|
}
|
|
};
|
|
|
|
UniValue getchaintips(const UniValue& params, bool fHelp)
|
|
{
|
|
if (fHelp || params.size() != 0)
|
|
throw runtime_error(
|
|
"getchaintips\n"
|
|
"Return information about all known tips in the block tree,"
|
|
" including the main chain as well as orphaned branches.\n"
|
|
"\nResult:\n"
|
|
"[\n"
|
|
" {\n"
|
|
" \"height\": xxxx, (numeric) height of the chain tip\n"
|
|
" \"hash\": \"xxxx\", (string) block hash of the tip\n"
|
|
" \"branchlen\": 0 (numeric) zero for main chain\n"
|
|
" \"status\": \"active\" (string) \"active\" for the main chain\n"
|
|
" },\n"
|
|
" {\n"
|
|
" \"height\": xxxx,\n"
|
|
" \"hash\": \"xxxx\",\n"
|
|
" \"branchlen\": 1 (numeric) length of branch connecting the tip to the main chain\n"
|
|
" \"status\": \"xxxx\" (string) status of the chain (active, valid-fork, valid-headers, headers-only, invalid)\n"
|
|
" }\n"
|
|
"]\n"
|
|
"Possible values for status:\n"
|
|
"1. \"invalid\" This branch contains at least one invalid block\n"
|
|
"2. \"headers-only\" Not all blocks for this branch are available, but the headers are valid\n"
|
|
"3. \"valid-headers\" All blocks are available for this branch, but they were never fully validated\n"
|
|
"4. \"valid-fork\" This branch is not part of the active chain, but is fully validated\n"
|
|
"5. \"active\" This is the tip of the active main chain, which is certainly valid\n"
|
|
"\nExamples:\n"
|
|
+ HelpExampleCli("getchaintips", "")
|
|
+ HelpExampleRpc("getchaintips", "")
|
|
);
|
|
|
|
LOCK(cs_main);
|
|
|
|
/* Build up a list of chain tips. We start with the list of all
|
|
known blocks, and successively remove blocks that appear as pprev
|
|
of another block. */
|
|
std::set<const CBlockIndex*, CompareBlocksByHeight> setTips;
|
|
for (const std::pair<const uint256, CBlockIndex*>& item : mapBlockIndex)
|
|
setTips.insert(item.second);
|
|
for (const std::pair<const uint256, CBlockIndex*>& item : mapBlockIndex)
|
|
{
|
|
const CBlockIndex* pprev = item.second->pprev;
|
|
if (pprev)
|
|
setTips.erase(pprev);
|
|
}
|
|
|
|
// Always report the currently active tip.
|
|
setTips.insert(chainActive.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 - chainActive.FindFork(block)->nHeight;
|
|
obj.pushKV("branchlen", branchLen);
|
|
|
|
string status;
|
|
if (chainActive.Contains(block)) {
|
|
// This block is part of the currently active chain.
|
|
status = "active";
|
|
} else if (block->nStatus & BLOCK_FAILED_MASK) {
|
|
// This block or one of its ancestors is invalid.
|
|
status = "invalid";
|
|
} else if (block->nChainTx == 0) {
|
|
// 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(BLOCK_VALID_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(BLOCK_VALID_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;
|
|
}
|
|
|
|
UniValue z_gettreestate(const UniValue& params, bool fHelp)
|
|
{
|
|
if (fHelp || params.size() != 1)
|
|
throw runtime_error(
|
|
"z_gettreestate \"hash|height\"\n"
|
|
"Return information about the given block's tree state.\n"
|
|
"\nArguments:\n"
|
|
"1. \"hash|height\" (string, required) The block hash or height. Height can be negative where -1 is the last known valid block\n"
|
|
"\nResult:\n"
|
|
"{\n"
|
|
" \"hash\": \"hash\", (string) hex block hash\n"
|
|
" \"height\": n, (numeric) block height\n"
|
|
" \"time\": n, (numeric) block time: UTC seconds since the Unix 1970-01-01 epoch\n"
|
|
" \"sprout\": {\n"
|
|
" \"skipHash\": \"hash\", (string) hash of most recent block with more information\n"
|
|
" \"commitments\": {\n"
|
|
" \"finalRoot\": \"hex\", (string)\n"
|
|
" \"finalState\": \"hex\" (string)\n"
|
|
" }\n"
|
|
" },\n"
|
|
" \"sapling\": {\n"
|
|
" \"skipHash\": \"hash\", (string) hash of most recent block with more information\n"
|
|
" \"commitments\": {\n"
|
|
" \"finalRoot\": \"hex\", (string)\n"
|
|
" NB: The serialized representation of this field returned by this method\n"
|
|
" was byte-flipped relative to its representation in the `getrawtransaction`\n"
|
|
" output in prior releases up to and including v5.2.0. This has now been\n"
|
|
" rectified.\n"
|
|
" \"finalState\": \"hex\" (string)\n"
|
|
" }\n"
|
|
" },\n"
|
|
" \"orchard\": {\n"
|
|
" \"skipHash\": \"hash\", (string) hash of most recent block with more information\n"
|
|
" \"commitments\": {\n"
|
|
" \"finalRoot\": \"hex\", (string)\n"
|
|
" \"finalState\": \"hex\" (string)\n"
|
|
" }\n"
|
|
" }\n"
|
|
"}\n"
|
|
"\nExamples:\n"
|
|
+ HelpExampleCli("z_gettreestate", "\"00000000febc373a1da2bd9f887b105ad79ddc26ac26c2b28652d64e5207c5b5\"")
|
|
+ HelpExampleRpc("z_gettreestate", "\"00000000febc373a1da2bd9f887b105ad79ddc26ac26c2b28652d64e5207c5b5\"")
|
|
+ HelpExampleCli("z_gettreestate", "12800")
|
|
+ HelpExampleRpc("z_gettreestate", "12800")
|
|
);
|
|
|
|
LOCK(cs_main);
|
|
|
|
std::string strHash = params[0].get_str();
|
|
|
|
// If height is supplied, find the hash
|
|
if (strHash.size() < (2 * sizeof(uint256))) {
|
|
strHash = chainActive[parseHeightArg(strHash, chainActive.Height())]->GetBlockHash().GetHex();
|
|
}
|
|
uint256 hash(uint256S(strHash));
|
|
|
|
if (mapBlockIndex.count(hash) == 0)
|
|
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Block not found");
|
|
const CBlockIndex* const pindex = mapBlockIndex[hash];
|
|
if (!chainActive.Contains(pindex)) {
|
|
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Requested block is not part of the main chain");
|
|
}
|
|
|
|
UniValue res(UniValue::VOBJ);
|
|
res.pushKV("hash", pindex->GetBlockHash().GetHex());
|
|
res.pushKV("height", pindex->nHeight);
|
|
res.pushKV("time", int64_t(pindex->nTime));
|
|
|
|
// sprout
|
|
{
|
|
UniValue sprout_result(UniValue::VOBJ);
|
|
UniValue sprout_commitments(UniValue::VOBJ);
|
|
sprout_commitments.pushKV("finalRoot", pindex->hashFinalSproutRoot.GetHex());
|
|
SproutMerkleTree tree;
|
|
if (pcoinsTip->GetSproutAnchorAt(pindex->hashFinalSproutRoot, tree)) {
|
|
CDataStream s(SER_NETWORK, PROTOCOL_VERSION);
|
|
s << tree;
|
|
sprout_commitments.pushKV("finalState", HexStr(s.begin(), s.end()));
|
|
} else {
|
|
// Set skipHash to the most recent block that has a finalState.
|
|
const CBlockIndex* pindex_skip = pindex->pprev;
|
|
while (pindex_skip && !pcoinsTip->GetSproutAnchorAt(pindex_skip->hashFinalSproutRoot, tree)) {
|
|
pindex_skip = pindex_skip->pprev;
|
|
}
|
|
if (pindex_skip) {
|
|
sprout_result.pushKV("skipHash", pindex_skip->GetBlockHash().GetHex());
|
|
}
|
|
}
|
|
sprout_result.pushKV("commitments", sprout_commitments);
|
|
res.pushKV("sprout", sprout_result);
|
|
}
|
|
|
|
// sapling
|
|
auto sapling_activation_height = Params().GetConsensus().GetActivationHeight(Consensus::UPGRADE_SAPLING);
|
|
if (sapling_activation_height.has_value()) {
|
|
UniValue sapling_result(UniValue::VOBJ);
|
|
UniValue sapling_commitments(UniValue::VOBJ);
|
|
sapling_commitments.pushKV("finalRoot", pindex->hashFinalSaplingRoot.GetHex());
|
|
bool need_skiphash = false;
|
|
SaplingMerkleTree tree;
|
|
if (pcoinsTip->GetSaplingAnchorAt(pindex->hashFinalSaplingRoot, tree)) {
|
|
CDataStream s(SER_NETWORK, PROTOCOL_VERSION);
|
|
s << tree;
|
|
sapling_commitments.pushKV("finalState", HexStr(s.begin(), s.end()));
|
|
} else {
|
|
// Set skipHash to the most recent block that has a finalState.
|
|
const CBlockIndex* pindex_skip = pindex->pprev;
|
|
auto saplingActive = [&](const CBlockIndex* pindex_cur) -> bool {
|
|
return pindex_cur && pindex_cur->nHeight >= sapling_activation_height.value();
|
|
};
|
|
while (saplingActive(pindex_skip) && !pcoinsTip->GetSaplingAnchorAt(pindex_skip->hashFinalSaplingRoot, tree)) {
|
|
pindex_skip = pindex_skip->pprev;
|
|
}
|
|
if (saplingActive(pindex_skip)) {
|
|
sapling_result.pushKV("skipHash", pindex_skip->GetBlockHash().GetHex());
|
|
}
|
|
}
|
|
sapling_result.pushKV("commitments", sapling_commitments);
|
|
res.pushKV("sapling", sapling_result);
|
|
}
|
|
|
|
// orchard
|
|
auto nu5_activation_height = Params().GetConsensus().GetActivationHeight(Consensus::UPGRADE_NU5);
|
|
if (nu5_activation_height.has_value()) {
|
|
UniValue orchard_result(UniValue::VOBJ);
|
|
UniValue orchard_commitments(UniValue::VOBJ);
|
|
auto finalOrchardRootBytes = pindex->hashFinalOrchardRoot;
|
|
orchard_commitments.pushKV("finalRoot", HexStr(finalOrchardRootBytes.begin(), finalOrchardRootBytes.end()));
|
|
bool need_skiphash = false;
|
|
OrchardMerkleFrontier tree;
|
|
if (pcoinsTip->GetOrchardAnchorAt(pindex->hashFinalOrchardRoot, tree)) {
|
|
CDataStream s(SER_NETWORK, PROTOCOL_VERSION);
|
|
s << OrchardMerkleFrontierLegacySer(tree);
|
|
orchard_commitments.pushKV("finalState", HexStr(s.begin(), s.end()));
|
|
} else {
|
|
// Set skipHash to the most recent block that has a finalState.
|
|
const CBlockIndex* pindex_skip = pindex->pprev;
|
|
auto orchardActive = [&](const CBlockIndex* pindex_cur) -> bool {
|
|
return pindex_cur && pindex_cur->nHeight >= nu5_activation_height.value();
|
|
};
|
|
while (orchardActive(pindex_skip) && !pcoinsTip->GetOrchardAnchorAt(pindex_skip->hashFinalOrchardRoot, tree)) {
|
|
pindex_skip = pindex_skip->pprev;
|
|
}
|
|
if (orchardActive(pindex_skip)) {
|
|
orchard_result.pushKV("skipHash", pindex_skip->GetBlockHash().GetHex());
|
|
}
|
|
}
|
|
orchard_result.pushKV("commitments", orchard_commitments);
|
|
res.pushKV("orchard", orchard_result);
|
|
}
|
|
|
|
return res;
|
|
}
|
|
|
|
UniValue z_getsubtreesbyindex(const UniValue& params, bool fHelp)
|
|
{
|
|
std::string disabledMsg = "";
|
|
if (!fExperimentalLightWalletd) {
|
|
disabledMsg = experimentalDisabledHelpMsg("z_getsubtreesbyindex", {"lightwalletd"});
|
|
}
|
|
if (fHelp || params.size() < 2 || params.size() > 3) {
|
|
auto strHeight = strprintf("%d", libzcash::TRACKED_SUBTREE_HEIGHT);
|
|
throw runtime_error(
|
|
"z_getsubtreesbyindex \"pool\" start_index ( limit )\n"
|
|
"Returns roots of subtrees of the given pool's note commitment tree. Each value returned\n"
|
|
"in the `subtrees` field is the Merkle root of a subtree containing 2^"+strHeight+" leaves.\n"
|
|
+ disabledMsg +
|
|
"\nArguments:\n"
|
|
"1. \"pool\" (string, required) The pool from which subtrees should be returned. Either \"sapling\" or \"orchard\".\n"
|
|
"2. start_index (numeric, required) The index of the first 2^"+strHeight+"-leaf subtree to return.\n"
|
|
"2. limit (numeric, optional) The maximum number of subtree values to return.\n"
|
|
"\nResult:\n"
|
|
"{\n"
|
|
" \"pool\" : \"sapling|orchard\", (string) The shielded pool to which the subtrees belong\n"
|
|
" \"start_index\": n, (numeric) The index of the first subtree\n"
|
|
" \"subtrees\": [ (array) A sequential list of complete subtrees\n"
|
|
" {\n"
|
|
" \"root\": \"hash\", (string) Merkle root of the 2^"+strHeight+"-leaf subtree\n"
|
|
" \"end_height\": n, (numeric) height of the block containing the note that completed this subtree\n"
|
|
" }, ...\n"
|
|
" ]\n"
|
|
"}\n"
|
|
"\nExamples:\n"
|
|
+ HelpExampleCli("z_getsubtreesbyindex", "\"sapling\", 0")
|
|
+ HelpExampleRpc("z_getsubtreesbyindex", "\"orchard\", 3, 7")
|
|
);
|
|
}
|
|
|
|
if (!fExperimentalLightWalletd) {
|
|
throw JSONRPCError(RPC_MISC_ERROR, "Error: z_getsubtreesbyindex is disabled. "
|
|
"Run './zcash-cli help z_getsubtreesbyindex' for instructions on how to enable this feature.");
|
|
}
|
|
|
|
auto strPool = params[0].get_str();
|
|
ShieldedType pool;
|
|
if (strPool == "sapling") {
|
|
pool = SAPLING;
|
|
} else if (strPool == "orchard") {
|
|
pool = ORCHARD;
|
|
} else {
|
|
throw JSONRPCError(RPC_INVALID_PARAMETER, "Requested pool must be \"sapling\" or \"orchard\"");
|
|
}
|
|
|
|
libzcash::SubtreeIndex startIndex = params[1].get_int();
|
|
std::optional<uint64_t> limit = std::nullopt;
|
|
if (params.size() > 2) {
|
|
limit = params[2].get_int();
|
|
}
|
|
|
|
LOCK(cs_main);
|
|
|
|
UniValue subtrees(UniValue::VARR);
|
|
uint64_t count = 0;
|
|
for (libzcash::SubtreeIndex index = startIndex; ; index++) {
|
|
if (limit.has_value() && count >= limit.value()) {
|
|
break;
|
|
}
|
|
|
|
auto subtreeData = pcoinsTip->GetSubtreeData(pool, index);
|
|
if (!subtreeData.has_value()) {
|
|
break;
|
|
}
|
|
|
|
UniValue subtree(UniValue::VOBJ);
|
|
subtree.pushKV("root", HexStr(subtreeData->root));
|
|
subtree.pushKV("end_height", subtreeData->nHeight);
|
|
subtrees.push_back(subtree);
|
|
count++;
|
|
}
|
|
|
|
UniValue res(UniValue::VOBJ);
|
|
res.pushKV("pool", strPool);
|
|
res.pushKV("start_index", startIndex);
|
|
res.pushKV("subtrees", subtrees);
|
|
|
|
return res;
|
|
}
|
|
|
|
UniValue mempoolInfoToJSON()
|
|
{
|
|
UniValue ret(UniValue::VOBJ);
|
|
ret.pushKV("size", (int64_t) mempool.size());
|
|
ret.pushKV("bytes", (int64_t) mempool.GetTotalTxSize());
|
|
ret.pushKV("usage", (int64_t) mempool.DynamicMemoryUsage());
|
|
|
|
if (Params().NetworkIDString() == "regtest") {
|
|
ret.pushKV("fullyNotified", mempool.IsFullyNotified());
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
UniValue getmempoolinfo(const UniValue& params, bool fHelp)
|
|
{
|
|
if (fHelp || params.size() != 0)
|
|
throw runtime_error(
|
|
"getmempoolinfo\n"
|
|
"\nReturns details on the active state of the TX memory pool.\n"
|
|
"\nResult:\n"
|
|
"{\n"
|
|
" \"size\": xxxxx (numeric) Current tx count\n"
|
|
" \"bytes\": xxxxx (numeric) Sum of all tx sizes\n"
|
|
" \"usage\": xxxxx (numeric) Total memory usage for the mempool\n"
|
|
"}\n"
|
|
"\nExamples:\n"
|
|
+ HelpExampleCli("getmempoolinfo", "")
|
|
+ HelpExampleRpc("getmempoolinfo", "")
|
|
);
|
|
|
|
return mempoolInfoToJSON();
|
|
}
|
|
|
|
UniValue invalidateblock(const UniValue& params, bool fHelp)
|
|
{
|
|
if (fHelp || params.size() != 1)
|
|
throw runtime_error(
|
|
"invalidateblock \"hash\"\n"
|
|
"\nPermanently marks a block as invalid, as if it violated a consensus rule.\n"
|
|
"\nArguments:\n"
|
|
"1. hash (string, required) the hash of the block to mark as invalid\n"
|
|
"\nResult:\n"
|
|
"\nExamples:\n"
|
|
+ HelpExampleCli("invalidateblock", "\"blockhash\"")
|
|
+ HelpExampleRpc("invalidateblock", "\"blockhash\"")
|
|
);
|
|
|
|
std::string strHash = params[0].get_str();
|
|
uint256 hash(uint256S(strHash));
|
|
CValidationState state;
|
|
|
|
{
|
|
LOCK(cs_main);
|
|
if (mapBlockIndex.count(hash) == 0)
|
|
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Block not found");
|
|
|
|
CBlockIndex* pblockindex = mapBlockIndex[hash];
|
|
InvalidateBlock(state, Params(), pblockindex);
|
|
}
|
|
|
|
if (state.IsValid()) {
|
|
ActivateBestChain(state, Params());
|
|
}
|
|
|
|
if (!state.IsValid()) {
|
|
throw JSONRPCError(RPC_DATABASE_ERROR, state.GetRejectReason());
|
|
}
|
|
|
|
return NullUniValue;
|
|
}
|
|
|
|
UniValue reconsiderblock(const UniValue& params, bool fHelp)
|
|
{
|
|
if (fHelp || params.size() != 1)
|
|
throw runtime_error(
|
|
"reconsiderblock \"hash\"\n"
|
|
"\nRemoves invalidity status of a block and its descendants, reconsider them for activation.\n"
|
|
"This can be used to undo the effects of invalidateblock.\n"
|
|
"\nArguments:\n"
|
|
"1. hash (string, required) the hash of the block to reconsider\n"
|
|
"\nResult:\n"
|
|
"\nExamples:\n"
|
|
+ HelpExampleCli("reconsiderblock", "\"blockhash\"")
|
|
+ HelpExampleRpc("reconsiderblock", "\"blockhash\"")
|
|
);
|
|
|
|
std::string strHash = params[0].get_str();
|
|
uint256 hash(uint256S(strHash));
|
|
CValidationState state;
|
|
|
|
{
|
|
LOCK(cs_main);
|
|
if (mapBlockIndex.count(hash) == 0)
|
|
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Block not found");
|
|
|
|
CBlockIndex* pblockindex = mapBlockIndex[hash];
|
|
ReconsiderBlock(state, pblockindex);
|
|
}
|
|
|
|
if (state.IsValid()) {
|
|
ActivateBestChain(state, Params());
|
|
}
|
|
|
|
if (!state.IsValid()) {
|
|
throw JSONRPCError(RPC_DATABASE_ERROR, state.GetRejectReason());
|
|
}
|
|
|
|
return NullUniValue;
|
|
}
|
|
|
|
static const CRPCCommand commands[] =
|
|
{ // category name actor (function) okSafeMode
|
|
// --------------------- ------------------------ ----------------------- ----------
|
|
{ "blockchain", "getblockchaininfo", &getblockchaininfo, true },
|
|
{ "blockchain", "getbestblockhash", &getbestblockhash, true },
|
|
{ "blockchain", "getblockcount", &getblockcount, true },
|
|
{ "blockchain", "getblock", &getblock, true },
|
|
{ "blockchain", "getblockhash", &getblockhash, true },
|
|
{ "blockchain", "getblockheader", &getblockheader, true },
|
|
{ "blockchain", "getchaintips", &getchaintips, true },
|
|
{ "blockchain", "z_gettreestate", &z_gettreestate, true },
|
|
{ "blockchain", "z_getsubtreesbyindex", &z_getsubtreesbyindex, true },
|
|
{ "blockchain", "getdifficulty", &getdifficulty, true },
|
|
{ "blockchain", "getmempoolinfo", &getmempoolinfo, true },
|
|
{ "blockchain", "getrawmempool", &getrawmempool, true },
|
|
{ "blockchain", "gettxout", &gettxout, true },
|
|
{ "blockchain", "gettxoutsetinfo", &gettxoutsetinfo, true },
|
|
{ "blockchain", "verifychain", &verifychain, true },
|
|
|
|
// insightexplorer
|
|
{ "blockchain", "getblockdeltas", &getblockdeltas, false },
|
|
{ "blockchain", "getblockhashes", &getblockhashes, true },
|
|
|
|
/* Not shown in help */
|
|
{ "hidden", "invalidateblock", &invalidateblock, true },
|
|
{ "hidden", "reconsiderblock", &reconsiderblock, true },
|
|
};
|
|
|
|
void RegisterBlockchainRPCCommands(CRPCTable &tableRPC)
|
|
{
|
|
for (unsigned int vcidx = 0; vcidx < ARRAYLEN(commands); vcidx++)
|
|
tableRPC.appendCommand(commands[vcidx].name, &commands[vcidx]);
|
|
}
|