blockchain.cpp raw

   1  // Copyright (c) 2010 Satoshi Nakamoto
   2  // Copyright (c) 2009-2022 The Limenka developers
   3  // Distributed under the MIT software license, see the accompanying
   4  // file COPYING or http://www.opensource.org/licenses/mit-license.php.
   5  
   6  #include <limenka-build-config.h> // IWYU pragma: keep
   7  
   8  #include <rpc/blockchain.h>
   9  
  10  #include <blockfilter.h>
  11  #include <chain.h>
  12  #include <chainparams.h>
  13  #include <chainparamsbase.h>
  14  #include <clientversion.h>
  15  #include <coins.h>
  16  #include <common/args.h>
  17  #include <consensus/amount.h>
  18  #include <consensus/params.h>
  19  #include <consensus/validation.h>
  20  #include <core_io.h>
  21  #include <deploymentinfo.h>
  22  #include <deploymentstatus.h>
  23  #include <flatfile.h>
  24  #include <hash.h>
  25  #include <index/blockfilterindex.h>
  26  #include <index/coinstatsindex.h>
  27  #include <interfaces/mining.h>
  28  #include <kernel/coinstats.h>
  29  #include <key_io.h>
  30  #include <logging/timer.h>
  31  #include <net.h>
  32  #include <net_processing.h>
  33  #include <node/blockstorage.h>
  34  #include <node/context.h>
  35  #include <node/transaction.h>
  36  #include <node/utxo_snapshot.h>
  37  #include <node/warnings.h>
  38  #include <primitives/transaction.h>
  39  #include <policy/settings.h>
  40  #include <rpc/server.h>
  41  #include <rpc/server_util.h>
  42  #include <rpc/util.h>
  43  #include <script/descriptor.h>
  44  #include <script/sign.h>
  45  #include <serialize.h>
  46  #include <streams.h>
  47  #include <sync.h>
  48  #include <txdb.h>
  49  #include <txmempool.h>
  50  #include <undo.h>
  51  #include <univalue.h>
  52  #include <util/check.h>
  53  #include <util/fs.h>
  54  #include <util/strencodings.h>
  55  #include <util/string.h>
  56  #include <util/syserror.h>
  57  #include <validation.h>
  58  
  59  #ifdef ENABLE_WALLET
  60  #include <interfaces/wallet.h>
  61  #include <wallet/coincontrol.h>
  62  #include <wallet/fees.h>
  63  #include <wallet/rpc/util.h>
  64  #include <wallet/types.h>
  65  #include <wallet/wallet.h>
  66  #endif
  67  
  68  #include <util/translation.h>
  69  #include <validation.h>
  70  #include <validationinterface.h>
  71  #include <versionbits.h>
  72  
  73  #include <stdint.h>
  74  
  75  #include <condition_variable>
  76  #include <iterator>
  77  #include <memory>
  78  #include <mutex>
  79  #include <optional>
  80  #include <vector>
  81  
  82  using kernel::CCoinsStats;
  83  using kernel::CoinStatsHashType;
  84  
  85  using interfaces::BlockRef;
  86  using interfaces::Mining;
  87  using node::BlockManager;
  88  using node::NodeContext;
  89  using node::SnapshotMetadata;
  90  using util::MakeUnorderedList;
  91  
  92  std::tuple<std::unique_ptr<CCoinsViewCursor>, CCoinsStats, const CBlockIndex*>
  93  PrepareUTXOSnapshot(
  94      Chainstate& chainstate,
  95      const std::function<void()>& interruption_point = {})
  96      EXCLUSIVE_LOCKS_REQUIRED(::cs_main);
  97  
  98  UniValue WriteUTXOSnapshot(
  99      const bool is_human_readable,
 100      const bool show_header,
 101      const Span<const std::byte>& separator,
 102      const std::vector<std::pair<std::string, coinascii_cb_t>>& requested,
 103      Chainstate& chainstate,
 104      CCoinsViewCursor* pcursor,
 105      CCoinsStats* maybe_stats,
 106      const CBlockIndex* tip,
 107      AutoFile&& afile,
 108      const fs::path& path,
 109      const fs::path& temppath,
 110      const std::function<void()>& interruption_point = {});
 111  
 112  /* Calculate the difficulty for a given block index.
 113   */
 114  double GetDifficulty(const CBlockIndex& blockindex)
 115  {
 116      int nShift = (blockindex.nBits >> 24) & 0xff;
 117      double dDiff =
 118          (double)0x0000ffff / (double)(blockindex.nBits & 0x00ffffff);
 119  
 120      while (nShift < 29)
 121      {
 122          dDiff *= 256.0;
 123          nShift++;
 124      }
 125      while (nShift > 29)
 126      {
 127          dDiff /= 256.0;
 128          nShift--;
 129      }
 130  
 131      return dDiff;
 132  }
 133  
 134  static int ComputeNextBlockAndDepth(const CBlockIndex& tip, const CBlockIndex& blockindex, const CBlockIndex*& next)
 135  {
 136      next = tip.GetAncestor(blockindex.nHeight + 1);
 137      if (next && next->pprev == &blockindex) {
 138          return tip.nHeight - blockindex.nHeight + 1;
 139      }
 140      next = nullptr;
 141      return &blockindex == &tip ? 1 : -1;
 142  }
 143  
 144  static const CBlockIndex* ParseHashOrHeight(const UniValue& param, ChainstateManager& chainman)
 145  {
 146      LOCK(::cs_main);
 147      CChain& active_chain = chainman.ActiveChain();
 148  
 149      if (param.isNum()) {
 150          const int height{param.getInt<int>()};
 151          if (height < 0) {
 152              throw JSONRPCError(RPC_INVALID_PARAMETER, strprintf("Target block height %d is negative", height));
 153          }
 154          const int current_tip{active_chain.Height()};
 155          if (height > current_tip) {
 156              throw JSONRPCError(RPC_INVALID_PARAMETER, strprintf("Target block height %d after current tip %d", height, current_tip));
 157          }
 158  
 159          return active_chain[height];
 160      } else {
 161          const uint256 hash{ParseHashV(param, "hash_or_height")};
 162          const CBlockIndex* pindex = chainman.m_blockman.LookupBlockIndex(hash);
 163  
 164          if (!pindex) {
 165              throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Block not found");
 166          }
 167  
 168          return pindex;
 169      }
 170  }
 171  
 172  UniValue blockheaderToJSON(const CBlockIndex& tip, const CBlockIndex& blockindex, const uint256 pow_limit)
 173  {
 174      // Serialize passed information without accessing chain state of the active chain!
 175      AssertLockNotHeld(cs_main); // For performance reasons
 176  
 177      UniValue result(UniValue::VOBJ);
 178      result.pushKV("hash", blockindex.GetBlockHash().GetHex());
 179      const CBlockIndex* pnext;
 180      int confirmations = ComputeNextBlockAndDepth(tip, blockindex, pnext);
 181      result.pushKV("confirmations", confirmations);
 182      result.pushKV("height", blockindex.nHeight);
 183      result.pushKV("version", blockindex.nVersion);
 184      result.pushKV("versionHex", strprintf("%08x", blockindex.nVersion));
 185      result.pushKV("merkleroot", blockindex.hashMerkleRoot.GetHex());
 186      result.pushKV("time", blockindex.nTime);
 187      result.pushKV("mediantime", blockindex.GetMedianTimePast());
 188      result.pushKV("nonce", blockindex.nNonce);
 189      result.pushKV("bits", strprintf("%08x", blockindex.nBits));
 190      result.pushKV("target", GetTarget(blockindex, pow_limit).GetHex());
 191      result.pushKV("difficulty", GetDifficulty(blockindex));
 192      result.pushKV("chainwork", blockindex.nChainWork.GetHex());
 193      result.pushKV("nTx", blockindex.nTx);
 194  
 195      if (blockindex.pprev)
 196          result.pushKV("previousblockhash", blockindex.pprev->GetBlockHash().GetHex());
 197      if (pnext)
 198          result.pushKV("nextblockhash", pnext->GetBlockHash().GetHex());
 199      return result;
 200  }
 201  
 202  UniValue blockToJSON(BlockManager& blockman, const CBlock& block, const CBlockIndex& tip, const CBlockIndex& blockindex, TxVerbosity verbosity, const uint256 pow_limit)
 203  {
 204      UniValue result = blockheaderToJSON(tip, blockindex, pow_limit);
 205  
 206      result.pushKV("strippedsize", (int)::GetSerializeSize(TX_NO_WITNESS(block)));
 207      result.pushKV("size", (int)::GetSerializeSize(TX_WITH_WITNESS(block)));
 208      result.pushKV("weight", (int)::GetBlockWeight(block));
 209      UniValue txs(UniValue::VARR);
 210      txs.reserve(block.vtx.size());
 211  
 212      switch (verbosity) {
 213          case TxVerbosity::SHOW_TXID:
 214              for (const CTransactionRef& tx : block.vtx) {
 215                  txs.push_back(tx->GetHash().GetHex());
 216              }
 217              break;
 218  
 219          case TxVerbosity::SHOW_DETAILS:
 220          case TxVerbosity::SHOW_DETAILS_AND_PREVOUT:
 221              CBlockUndo blockUndo;
 222              const bool is_not_pruned{WITH_LOCK(::cs_main, return !blockman.IsBlockPruned(blockindex))};
 223              bool have_undo{is_not_pruned && WITH_LOCK(::cs_main, return blockindex.nStatus & BLOCK_HAVE_UNDO)};
 224              if (have_undo && !blockman.ReadBlockUndo(blockUndo, blockindex)) {
 225                  throw JSONRPCError(RPC_INTERNAL_ERROR, "Undo data expected but can't be read. This could be due to disk corruption or a conflict with a pruning event.");
 226              }
 227              for (size_t i = 0; i < block.vtx.size(); ++i) {
 228                  const CTransactionRef& tx = block.vtx.at(i);
 229                  // coinbase transaction (i.e. i == 0) doesn't have undo data
 230                  const CTxUndo* txundo = (have_undo && i > 0) ? &blockUndo.vtxundo.at(i - 1) : nullptr;
 231                  UniValue objTx(UniValue::VOBJ);
 232                  TxToUniv(*tx, /*block_hash=*/uint256(), /*entry=*/objTx, /*include_hex=*/true, txundo, verbosity);
 233                  txs.push_back(std::move(objTx));
 234              }
 235              break;
 236      }
 237  
 238      result.pushKV("tx", std::move(txs));
 239  
 240      return result;
 241  }
 242  
 243  static RPCHelpMan getblockcount()
 244  {
 245      return RPCHelpMan{"getblockcount",
 246                  "\nReturns the height of the most-work fully-validated chain.\n"
 247                  "The genesis block has height 0.\n",
 248                  {},
 249                  RPCResult{
 250                      RPCResult::Type::NUM, "", "The current block count"},
 251                  RPCExamples{
 252                      HelpExampleCli("getblockcount", "")
 253              + HelpExampleRpc("getblockcount", "")
 254                  },
 255          [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
 256  {
 257      ChainstateManager& chainman = EnsureAnyChainman(request.context);
 258      LOCK(cs_main);
 259      return chainman.ActiveChain().Height();
 260  },
 261      };
 262  }
 263  
 264  static RPCHelpMan getbestblockhash()
 265  {
 266      return RPCHelpMan{"getbestblockhash",
 267                  "\nReturns the hash of the best (tip) block in the most-work fully-validated chain.\n",
 268                  {},
 269                  RPCResult{
 270                      RPCResult::Type::STR_HEX, "", "the block hash, hex-encoded"},
 271                  RPCExamples{
 272                      HelpExampleCli("getbestblockhash", "")
 273              + HelpExampleRpc("getbestblockhash", "")
 274                  },
 275          [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
 276  {
 277      ChainstateManager& chainman = EnsureAnyChainman(request.context);
 278      LOCK(cs_main);
 279      return chainman.ActiveChain().Tip()->GetBlockHash().GetHex();
 280  },
 281      };
 282  }
 283  
 284  static RPCHelpMan waitfornewblock()
 285  {
 286      return RPCHelpMan{"waitfornewblock",
 287                  "\nWaits for any new block and returns useful info about it.\n"
 288                  "\nReturns the current block on timeout or exit.\n"
 289                  "\nMake sure to use no RPC timeout (limenka-cli -rpcclienttimeout=0)",
 290                  {
 291                      {"timeout", RPCArg::Type::NUM, RPCArg::Default{0}, "Time in milliseconds to wait for a response. 0 indicates no timeout."},
 292                      {"current_tip", RPCArg::Type::STR_HEX, RPCArg::Optional::OMITTED, "Method waits for the chain tip to differ from this."},
 293                  },
 294                  RPCResult{
 295                      RPCResult::Type::OBJ, "", "",
 296                      {
 297                          {RPCResult::Type::STR_HEX, "hash", "The blockhash"},
 298                          {RPCResult::Type::NUM, "height", "Block height"},
 299                      }},
 300                  RPCExamples{
 301                      HelpExampleCli("waitfornewblock", "1000")
 302              + HelpExampleRpc("waitfornewblock", "1000")
 303                  },
 304          [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
 305  {
 306      int timeout = 0;
 307      if (!request.params[0].isNull())
 308          timeout = request.params[0].getInt<int>();
 309      if (timeout < 0) throw JSONRPCError(RPC_MISC_ERROR, "Negative timeout");
 310  
 311      NodeContext& node = EnsureAnyNodeContext(request.context);
 312      Mining& miner = EnsureMining(node);
 313  
 314      // If the caller provided a current_tip value, pass it to waitTipChanged().
 315      //
 316      // If the caller did not provide a current tip hash, call getTip() to get
 317      // one and wait for the tip to be different from this value. This mode is
 318      // less reliable because if the tip changed between waitfornewblock calls,
 319      // it will need to change a second time before this call returns.
 320      auto block{CHECK_NONFATAL(miner.getTip()).value()};
 321  
 322      uint256 tip_hash{request.params[1].isNull()
 323          ? block.hash
 324          : ParseHashV(request.params[1], "current_tip")};
 325  
 326      // If the user provided an invalid current_tip then this call immediately
 327      // returns the current tip.
 328      std::optional<BlockRef> new_block = timeout ? miner.waitTipChanged(tip_hash, std::chrono::milliseconds(timeout)) :
 329                                                miner.waitTipChanged(tip_hash);
 330  
 331      // Return current block upon shutdown
 332      if (new_block) block = *new_block;
 333  
 334      UniValue ret(UniValue::VOBJ);
 335      ret.pushKV("hash", block.hash.GetHex());
 336      ret.pushKV("height", block.height);
 337      return ret;
 338  },
 339      };
 340  }
 341  
 342  static RPCHelpMan waitforblock()
 343  {
 344      return RPCHelpMan{"waitforblock",
 345                  "\nWaits for a specific new block and returns useful info about it.\n"
 346                  "\nReturns the current block on timeout or exit.\n"
 347                  "\nMake sure to use no RPC timeout (limenka-cli -rpcclienttimeout=0)",
 348                  {
 349                      {"blockhash", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "Block hash to wait for."},
 350                      {"timeout", RPCArg::Type::NUM, RPCArg::Default{0}, "Time in milliseconds to wait for a response. 0 indicates no timeout."},
 351                  },
 352                  RPCResult{
 353                      RPCResult::Type::OBJ, "", "",
 354                      {
 355                          {RPCResult::Type::STR_HEX, "hash", "The blockhash"},
 356                          {RPCResult::Type::NUM, "height", "Block height"},
 357                      }},
 358                  RPCExamples{
 359                      HelpExampleCli("waitforblock", "\"0000000000079f8ef3d2c688c244eb7a4570b24c9ed7b4a8c619eb02596f8862\" 1000")
 360              + HelpExampleRpc("waitforblock", "\"0000000000079f8ef3d2c688c244eb7a4570b24c9ed7b4a8c619eb02596f8862\", 1000")
 361                  },
 362          [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
 363  {
 364      int timeout = 0;
 365  
 366      uint256 hash(ParseHashV(request.params[0], "blockhash"));
 367  
 368      if (!request.params[1].isNull())
 369          timeout = request.params[1].getInt<int>();
 370      if (timeout < 0) throw JSONRPCError(RPC_MISC_ERROR, "Negative timeout");
 371  
 372      NodeContext& node = EnsureAnyNodeContext(request.context);
 373      Mining& miner = EnsureMining(node);
 374  
 375      auto block{CHECK_NONFATAL(miner.getTip()).value()};
 376      const auto deadline{std::chrono::steady_clock::now() + 1ms * timeout};
 377      while (block.hash != hash) {
 378          std::optional<BlockRef> new_block;
 379          if (timeout) {
 380              auto now{std::chrono::steady_clock::now()};
 381              if (now >= deadline) break;
 382              const MillisecondsDouble remaining{deadline - now};
 383              new_block = miner.waitTipChanged(block.hash, remaining);
 384          } else {
 385              new_block = miner.waitTipChanged(block.hash);
 386          }
 387          // Return current block upon shutdown
 388          if (!new_block) break;
 389          block = *new_block;
 390      }
 391  
 392      UniValue ret(UniValue::VOBJ);
 393      ret.pushKV("hash", block.hash.GetHex());
 394      ret.pushKV("height", block.height);
 395      return ret;
 396  },
 397      };
 398  }
 399  
 400  static RPCHelpMan waitforblockheight()
 401  {
 402      return RPCHelpMan{"waitforblockheight",
 403                  "\nWaits for (at least) block height and returns the height and hash\n"
 404                  "of the current tip.\n"
 405                  "\nReturns the current block on timeout or exit.\n"
 406                  "\nMake sure to use no RPC timeout (limenka-cli -rpcclienttimeout=0)",
 407                  {
 408                      {"height", RPCArg::Type::NUM, RPCArg::Optional::NO, "Block height to wait for."},
 409                      {"timeout", RPCArg::Type::NUM, RPCArg::Default{0}, "Time in milliseconds to wait for a response. 0 indicates no timeout."},
 410                  },
 411                  RPCResult{
 412                      RPCResult::Type::OBJ, "", "",
 413                      {
 414                          {RPCResult::Type::STR_HEX, "hash", "The blockhash"},
 415                          {RPCResult::Type::NUM, "height", "Block height"},
 416                      }},
 417                  RPCExamples{
 418                      HelpExampleCli("waitforblockheight", "100 1000")
 419              + HelpExampleRpc("waitforblockheight", "100, 1000")
 420                  },
 421          [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
 422  {
 423      int timeout = 0;
 424  
 425      int height = request.params[0].getInt<int>();
 426  
 427      if (!request.params[1].isNull())
 428          timeout = request.params[1].getInt<int>();
 429      if (timeout < 0) throw JSONRPCError(RPC_MISC_ERROR, "Negative timeout");
 430  
 431      NodeContext& node = EnsureAnyNodeContext(request.context);
 432      Mining& miner = EnsureMining(node);
 433  
 434      auto block{CHECK_NONFATAL(miner.getTip()).value()};
 435      const auto deadline{std::chrono::steady_clock::now() + 1ms * timeout};
 436  
 437      while (block.height < height) {
 438          std::optional<BlockRef> new_block;
 439          if (timeout) {
 440              auto now{std::chrono::steady_clock::now()};
 441              if (now >= deadline) break;
 442              const MillisecondsDouble remaining{deadline - now};
 443              new_block = miner.waitTipChanged(block.hash, remaining);
 444          } else {
 445              new_block = miner.waitTipChanged(block.hash);
 446          }
 447          // Return current block on shutdown
 448          if (!new_block) break;
 449          block = *new_block;
 450      }
 451  
 452      UniValue ret(UniValue::VOBJ);
 453      ret.pushKV("hash", block.hash.GetHex());
 454      ret.pushKV("height", block.height);
 455      return ret;
 456  },
 457      };
 458  }
 459  
 460  static RPCHelpMan syncwithvalidationinterfacequeue()
 461  {
 462      return RPCHelpMan{"syncwithvalidationinterfacequeue",
 463                  "\nWaits for the validation interface queue to catch up on everything that was there when we entered this function.\n",
 464                  {},
 465                  RPCResult{RPCResult::Type::NONE, "", ""},
 466                  RPCExamples{
 467                      HelpExampleCli("syncwithvalidationinterfacequeue","")
 468              + HelpExampleRpc("syncwithvalidationinterfacequeue","")
 469                  },
 470          [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
 471  {
 472      NodeContext& node = EnsureAnyNodeContext(request.context);
 473      CHECK_NONFATAL(node.validation_signals)->SyncWithValidationInterfaceQueue();
 474      return UniValue::VNULL;
 475  },
 476      };
 477  }
 478  
 479  static RPCHelpMan getdifficulty()
 480  {
 481      return RPCHelpMan{"getdifficulty",
 482                  "\nReturns the proof-of-work difficulty as a multiple of the minimum difficulty.\n",
 483                  {},
 484                  RPCResult{
 485                      RPCResult::Type::NUM, "", "the proof-of-work difficulty as a multiple of the minimum difficulty."},
 486                  RPCExamples{
 487                      HelpExampleCli("getdifficulty", "")
 488              + HelpExampleRpc("getdifficulty", "")
 489                  },
 490          [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
 491  {
 492      ChainstateManager& chainman = EnsureAnyChainman(request.context);
 493      LOCK(cs_main);
 494      return GetDifficulty(*CHECK_NONFATAL(chainman.ActiveChain().Tip()));
 495  },
 496      };
 497  }
 498  
 499  static RPCHelpMan getblockfrompeer()
 500  {
 501      return RPCHelpMan{
 502          "getblockfrompeer",
 503          "Attempt to fetch block from a given peer.\n\n"
 504          "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"
 505          "Subsequent calls for the same block may cause the response from the previous peer to be ignored.\n"
 506          "Peers generally ignore requests for a stale block that they never fully verified, or one that is more than a month old.\n"
 507          "When a peer does not respond with a block, we will disconnect.\n"
 508          "Note: The block could be re-pruned as soon as it is received.\n\n"
 509          "Returns an empty JSON object if the request was successfully scheduled.",
 510          {
 511              {"blockhash", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "The block hash to try to fetch"},
 512              {"peer_id|nodeid", RPCArg::Type::NUM, RPCArg::Optional::NO, "The peer to fetch it from (see getpeerinfo for peer IDs)"},
 513          },
 514          RPCResult{RPCResult::Type::OBJ, "", /*optional=*/false, "", {}},
 515          RPCExamples{
 516              HelpExampleCli("getblockfrompeer", "\"00000000c937983704a73af28acdec37b049d214adbda81d7e2a3dd146f6ed09\" 0")
 517              + HelpExampleRpc("getblockfrompeer", "\"00000000c937983704a73af28acdec37b049d214adbda81d7e2a3dd146f6ed09\" 0")
 518          },
 519          [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
 520  {
 521      const NodeContext& node = EnsureAnyNodeContext(request.context);
 522      ChainstateManager& chainman = EnsureChainman(node);
 523      PeerManager& peerman = EnsurePeerman(node);
 524  
 525      const uint256& block_hash{ParseHashV(request.params[0], "blockhash")};
 526      const NodeId peer_id{request.params[1].getInt<int64_t>()};
 527  
 528      const CBlockIndex* const index = WITH_LOCK(cs_main, return chainman.m_blockman.LookupBlockIndex(block_hash););
 529  
 530  #if 0
 531      // Fetching blocks before the node has syncing past their height can prevent block files from
 532      // being pruned, so we avoid it if the node is in prune mode.
 533      if (chainman.m_blockman.IsPruneMode() && index->nHeight > WITH_LOCK(chainman.GetMutex(), return chainman.ActiveTip()->nHeight)) {
 534          throw JSONRPCError(RPC_MISC_ERROR, "In prune mode, only blocks that the node has already synced previously can be fetched from a peer");
 535      }
 536  #endif
 537  
 538      const bool block_has_data = index && WITH_LOCK(::cs_main, return index->nStatus & BLOCK_HAVE_DATA);
 539      if (block_has_data) {
 540          throw JSONRPCError(RPC_MISC_ERROR, "Block already downloaded");
 541      }
 542  
 543      if (const auto err{peerman.FetchBlock(peer_id, block_hash, index)}) {
 544          throw JSONRPCError(RPC_MISC_ERROR, err.value());
 545      }
 546      return UniValue::VOBJ;
 547  },
 548      };
 549  }
 550  
 551  static RPCHelpMan getblockhash()
 552  {
 553      return RPCHelpMan{"getblockhash",
 554                  "\nReturns hash of block in best-block-chain at height provided.\n",
 555                  {
 556                      {"height", RPCArg::Type::NUM, RPCArg::Optional::NO, "The height index"},
 557                  },
 558                  RPCResult{
 559                      RPCResult::Type::STR_HEX, "", "The block hash"},
 560                  RPCExamples{
 561                      HelpExampleCli("getblockhash", "1000")
 562              + HelpExampleRpc("getblockhash", "1000")
 563                  },
 564          [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
 565  {
 566      ChainstateManager& chainman = EnsureAnyChainman(request.context);
 567      LOCK(cs_main);
 568      const CChain& active_chain = chainman.ActiveChain();
 569  
 570      int nHeight = request.params[0].getInt<int>();
 571      if (nHeight < 0 || nHeight > active_chain.Height())
 572          throw JSONRPCError(RPC_INVALID_PARAMETER, "Block height out of range");
 573  
 574      const CBlockIndex* pblockindex = active_chain[nHeight];
 575      return pblockindex->GetBlockHash().GetHex();
 576  },
 577      };
 578  }
 579  
 580  #ifdef ENABLE_WALLET
 581  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);
 582  
 583  static RPCHelpMan sweepprivkeys()
 584  {
 585      return RPCHelpMan{"sweepprivkeys",
 586          "\nSends limenkas controlled by private key to specified destinations.\n",
 587          {
 588              {"options", RPCArg::Type::OBJ_NAMED_PARAMS, RPCArg::Optional::NO, "",
 589                  {
 590                      {"privkeys", RPCArg::Type::ARR, RPCArg::Optional::NO, "An array of WIF private key(s)",
 591                          {
 592                              {"privkey", RPCArg::Type::STR_HEX, RPCArg::Optional::OMITTED, ""},
 593                          },
 594                          },
 595  
 596                      {"label", RPCArg::Type::STR, RPCArg::Optional::OMITTED, "Label for received limenkas"},
 597                  },
 598                  RPCArgOptions{.oneline_description="options"}},
 599          },
 600          RPCResult{RPCResult::Type::STR_HEX, "", "The transaction id."},
 601          RPCExamples{""},
 602          [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
 603  {
 604      NodeContext& node = EnsureAnyNodeContext(request.context);
 605  
 606      JSONRPCRequest wallet_req = request;
 607      CHECK_NONFATAL(node.wallet_loader && node.wallet_loader->context());
 608      node.wallet_loader->assignContextHACK(wallet_req.context);
 609      std::shared_ptr<wallet::CWallet> const wallet = wallet::GetWalletForJSONRPCRequest(wallet_req);
 610      if (!wallet) return NullUniValue;
 611      wallet::CWallet* const pwallet = wallet.get();
 612  
 613      // NOTE: It isn't safe to sweep-and-send in a single action, since this would leave the send missing from the transaction history
 614  
 615      // Parse options
 616      std::set<CScript> needles;
 617      wallet::CCoinControl coin_control;
 618      FlatSigningProvider temp_keystore;
 619      CMutableTransaction tx;
 620      std::string label;
 621      CAmount total_in = 0;
 622      for (const std::string& optname : request.params[0].getKeys()) {
 623          const UniValue& optval = request.params[0][optname];
 624          if (optname == "privkeys") {
 625              const UniValue& privkeys_a = optval.get_array();
 626              for (size_t privkey_i = 0; privkey_i < privkeys_a.size(); ++privkey_i) {
 627                  const UniValue& privkey_wif = privkeys_a[privkey_i];
 628                  std::string wif_secret = privkey_wif.get_str();
 629                  CKey key = DecodeSecret(wif_secret);
 630                  if (!key.IsValid()) throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid private key encoding");
 631                  CPubKey pubkey = key.GetPubKey();
 632                  CHECK_NONFATAL(key.VerifyPubKey(pubkey));
 633  
 634                  temp_keystore.keys[pubkey.GetID()] = key;
 635                  temp_keystore.pubkeys[pubkey.GetID()] = pubkey;
 636                  CKeyID address = pubkey.GetID();
 637                  CScript script = GetScriptForDestination(PKHash(address));
 638                  if (!script.empty()) {
 639                      needles.insert(script);
 640                  }
 641                  script = GetScriptForRawPubKey(pubkey);
 642                  if (!script.empty()) {
 643                      needles.insert(script);
 644                  }
 645                  if (pubkey.IsCompressed()) {
 646                      CScript p2wpkh_script = GetScriptForDestination(WitnessV0KeyHash(pubkey));
 647                      if (!p2wpkh_script.empty()) {
 648                          needles.insert(p2wpkh_script);
 649                      }
 650                      script = GetScriptForDestination(ScriptHash(p2wpkh_script));
 651                      if (!script.empty()) {
 652                          needles.insert(script);
 653                          temp_keystore.scripts[CScriptID(p2wpkh_script)] = p2wpkh_script;
 654                      }
 655                      auto tap_tweak = XOnlyPubKey(pubkey).CreateTapTweak(nullptr);
 656                      if (tap_tweak) {
 657                          WitnessV1Taproot output_key{tap_tweak->first};
 658                          needles.insert(GetScriptForDestination(output_key));
 659                          TaprootBuilder builder;
 660                          builder.Finalize(XOnlyPubKey(pubkey));
 661                          temp_keystore.tr_trees[output_key] = builder;
 662                      }
 663                  }
 664              }
 665          } else if (optname == "label") {
 666              label = wallet::LabelFromValue(optval.get_str());
 667          } else {
 668              throw JSONRPCError(RPC_INVALID_PARAMETER, strprintf("Unrecognised option '%s'", optname));
 669          }
 670      }
 671  
 672      std::unique_ptr<wallet::ReserveDestination> reservedest;
 673      CTxDestination dest;
 674      {
 675          LOCK(pwallet->cs_wallet);
 676  
 677          // Reserve the key we will be using
 678          reservedest.reset(new wallet::ReserveDestination(pwallet, pwallet->TransactionChangeType(pwallet->m_default_change_type, std::vector<wallet::CRecipient>())));
 679          auto op_dest = reservedest->GetReservedDestination(false);
 680          if (!op_dest) {
 681              throw JSONRPCError(RPC_WALLET_KEYPOOL_RAN_OUT, util::ErrorString(op_dest).original);
 682          }
 683          dest = *op_dest;
 684      }
 685  
 686      // Scan UTXO set for inputs
 687      std::vector<CTxOut> input_txos;
 688      {
 689          // Collect all possible inputs
 690          std::map<COutPoint, Coin> coins;
 691          {
 692              std::unique_ptr<CCoinsViewCursor> pcursor;
 693              {
 694                  ChainstateManager& chainman = EnsureAnyChainman(request.context);
 695                  LOCK(cs_main);
 696                  if (node.mempool) {
 697                      node.mempool->FindScriptPubKey(needles, coins);
 698                  }
 699                  Chainstate& active_chainstate = chainman.ActiveChainstate();
 700                  active_chainstate.ForceFlushStateToDisk();
 701                  pcursor = std::unique_ptr<CCoinsViewCursor>(active_chainstate.CoinsDB().Cursor());
 702                  CHECK_NONFATAL(pcursor);
 703              }
 704              std::atomic<int> scan_progress;
 705              const std::atomic<bool> should_abort{false};
 706              int64_t count;
 707              if (!FindScriptPubKey(scan_progress, should_abort, count, pcursor.get(), needles, coins, node.rpc_interruption_point)) {
 708                  throw JSONRPCError(RPC_MISC_ERROR, "UTXO FindScriptPubKey failed");
 709              }
 710          }
 711  
 712          // Add them as inputs to the transaction, and count the total value
 713          for (auto& it : coins) {
 714              const COutPoint& outpoint = it.first;
 715              const Coin& coin = it.second;
 716              const CTxOut& txo = coin.out;
 717              tx.vin.emplace_back(outpoint.hash, outpoint.n);
 718              input_txos.push_back(txo);
 719              total_in += txo.nValue;
 720          }
 721      }
 722  
 723      if (total_in == 0) {
 724          throw JSONRPCError(RPC_WALLET_INSUFFICIENT_FUNDS, "No value to sweep");
 725      }
 726  
 727      tx.vout.emplace_back(total_in, GetScriptForDestination(dest));
 728  
 729      while (true) {
 730          if (IsDust(tx.vout[0], pwallet->chain().relayDustFee())) {
 731              throw JSONRPCError(RPC_VERIFY_REJECTED, "Swept value would be dust");
 732          }
 733          PrecomputedTransactionData txdata;
 734          txdata.Init(tx, std::vector<CTxOut>(input_txos.begin(), input_txos.end()), true);
 735          for (size_t input_index = 0; input_index < tx.vin.size(); ++input_index) {
 736              const auto& utxo = input_txos[input_index];
 737              SignatureData sig_data;
 738              MutableTransactionSignatureCreator creator(tx, input_index, utxo.nValue, &txdata, SIGHASH_ALL);
 739              if (!ProduceSignature(temp_keystore, creator, utxo.scriptPubKey, sig_data)) {
 740                  throw JSONRPCError(RPC_MISC_ERROR, "Failed to sign");
 741              }
 742              UpdateInput(tx.vin.at(input_index), sig_data);
 743          }
 744          int64_t tx_vsize = GetVirtualTransactionSize(CTransaction(tx));
 745          CAmount fee_needed = GetMinimumFee(*wallet, tx_vsize, coin_control, nullptr /* FeeCalculation */);
 746          const CAmount total_out = tx.vout[0].nValue;
 747          if (fee_needed <= total_in - total_out) {
 748              break;
 749          }
 750          tx.vout[0].nValue = total_in - fee_needed;
 751      }
 752  
 753      CTransactionRef final_tx(MakeTransactionRef(std::move(tx)));
 754      pwallet->SetAddressBook(dest, label, wallet::AddressPurpose::RECEIVE);
 755  
 756      std::string err_string;
 757      const node::TransactionError err = BroadcastTransaction(node, final_tx, err_string, pwallet->m_default_max_tx_fee, true /* relay */, true /* wait_callback */);
 758      if (node::TransactionError::OK != err) {
 759          pwallet->DelAddressBook(dest);
 760          throw JSONRPCTransactionError(err, err_string);
 761      }
 762      reservedest->KeepDestination();
 763  
 764      return final_tx->GetHash().GetHex();
 765  },
 766      };
 767  }
 768  #endif // ENABLE_WALLET
 769  
 770  static RPCHelpMan getblockheader()
 771  {
 772      return RPCHelpMan{"getblockheader",
 773                  "\nIf verbose is false, returns a string that is serialized, hex-encoded data for blockheader 'hash'.\n"
 774                  "If verbose is true, returns an Object with information about blockheader <hash>.\n",
 775                  {
 776                      {"blockhash", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "The block hash"},
 777                      {"verbose", RPCArg::Type::BOOL, RPCArg::Default{true}, "true for a json object, false for the hex-encoded data"},
 778                  },
 779                  {
 780                      RPCResult{"for verbose = true",
 781                          RPCResult::Type::OBJ, "", "",
 782                          {
 783                              {RPCResult::Type::STR_HEX, "hash", "the block hash (same as provided)"},
 784                              {RPCResult::Type::NUM, "confirmations", "The number of confirmations, or -1 if the block is not on the main chain"},
 785                              {RPCResult::Type::NUM, "height", "The block height or index"},
 786                              {RPCResult::Type::NUM, "version", "The block version"},
 787                              {RPCResult::Type::STR_HEX, "versionHex", "The block version formatted in hexadecimal"},
 788                              {RPCResult::Type::STR_HEX, "merkleroot", "The merkle root"},
 789                              {RPCResult::Type::NUM_TIME, "time", "The block time expressed in " + UNIX_EPOCH_TIME},
 790                              {RPCResult::Type::NUM_TIME, "mediantime", "The median block time expressed in " + UNIX_EPOCH_TIME},
 791                              {RPCResult::Type::NUM, "nonce", "The nonce"},
 792                              {RPCResult::Type::STR_HEX, "bits", "nBits: compact representation of the block difficulty target"},
 793                              {RPCResult::Type::STR_HEX, "target", "The difficulty target"},
 794                              {RPCResult::Type::NUM, "difficulty", "The difficulty"},
 795                              {RPCResult::Type::STR_HEX, "chainwork", "Expected number of hashes required to produce the current chain"},
 796                              {RPCResult::Type::NUM, "nTx", "The number of transactions in the block"},
 797                              {RPCResult::Type::STR_HEX, "previousblockhash", /*optional=*/true, "The hash of the previous block (if available)"},
 798                              {RPCResult::Type::STR_HEX, "nextblockhash", /*optional=*/true, "The hash of the next block (if available)"},
 799                          }},
 800                      RPCResult{"for verbose=false",
 801                          RPCResult::Type::STR_HEX, "", "A string that is serialized, hex-encoded data for block 'hash'"},
 802                  },
 803                  RPCExamples{
 804                      HelpExampleCli("getblockheader", "\"00000000c937983704a73af28acdec37b049d214adbda81d7e2a3dd146f6ed09\"")
 805              + HelpExampleRpc("getblockheader", "\"00000000c937983704a73af28acdec37b049d214adbda81d7e2a3dd146f6ed09\"")
 806                  },
 807          [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
 808  {
 809      uint256 hash(ParseHashV(request.params[0], "hash"));
 810  
 811      bool fVerbose = true;
 812      if (!request.params[1].isNull())
 813          fVerbose = request.params[1].get_bool();
 814  
 815      const CBlockIndex* pblockindex;
 816      const CBlockIndex* tip;
 817      ChainstateManager& chainman = EnsureAnyChainman(request.context);
 818      {
 819          LOCK(cs_main);
 820          pblockindex = chainman.m_blockman.LookupBlockIndex(hash);
 821          tip = chainman.ActiveChain().Tip();
 822      }
 823  
 824      if (!pblockindex) {
 825          throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Block not found");
 826      }
 827  
 828      if (!fVerbose)
 829      {
 830          DataStream ssBlock{};
 831          ssBlock << pblockindex->GetBlockHeader();
 832          std::string strHex = HexStr(ssBlock);
 833          return strHex;
 834      }
 835  
 836      return blockheaderToJSON(*tip, *pblockindex, chainman.GetConsensus().powLimit);
 837  },
 838      };
 839  }
 840  
 841  void CheckBlockDataAvailability(BlockManager& blockman, const CBlockIndex& blockindex, bool check_for_undo)
 842  {
 843      AssertLockHeld(cs_main);
 844      uint32_t flag = check_for_undo ? BLOCK_HAVE_UNDO : BLOCK_HAVE_DATA;
 845      if (!(blockindex.nStatus & flag)) {
 846          if (blockman.IsBlockPruned(blockindex)) {
 847              throw JSONRPCError(RPC_MISC_ERROR, strprintf("%s not available (pruned data)", check_for_undo ? "Undo data" : "Block"));
 848          }
 849          if (check_for_undo) {
 850              throw JSONRPCError(RPC_MISC_ERROR, "Undo data not available");
 851          }
 852          throw JSONRPCError(RPC_MISC_ERROR, "Block not available (not fully downloaded)");
 853      }
 854  }
 855  
 856  static CBlock GetBlockChecked(BlockManager& blockman, const CBlockIndex& blockindex)
 857  {
 858      CBlock block;
 859      {
 860          LOCK(cs_main);
 861          CheckBlockDataAvailability(blockman, blockindex, /*check_for_undo=*/false);
 862      }
 863  
 864      if (!blockman.ReadBlock(block, blockindex)) {
 865          // Block not found on disk. This shouldn't normally happen unless the block was
 866          // pruned right after we released the lock above.
 867          throw JSONRPCError(RPC_MISC_ERROR, "Block not found on disk");
 868      }
 869  
 870      return block;
 871  }
 872  
 873  static std::vector<uint8_t> GetRawBlockChecked(BlockManager& blockman, const CBlockIndex& blockindex)
 874  {
 875      std::vector<uint8_t> data{};
 876      FlatFilePos pos{};
 877      {
 878          LOCK(cs_main);
 879          CheckBlockDataAvailability(blockman, blockindex, /*check_for_undo=*/false);
 880          pos = blockindex.GetBlockPos();
 881      }
 882  
 883      if (!blockman.ReadRawBlock(data, pos)) {
 884          // Block not found on disk. This shouldn't normally happen unless the block was
 885          // pruned right after we released the lock above.
 886          throw JSONRPCError(RPC_MISC_ERROR, "Block not found on disk");
 887      }
 888  
 889      return data;
 890  }
 891  
 892  static CBlockUndo GetUndoChecked(BlockManager& blockman, const CBlockIndex& blockindex)
 893  {
 894      CBlockUndo blockUndo;
 895  
 896      // The Genesis block does not have undo data
 897      if (blockindex.nHeight == 0) return blockUndo;
 898  
 899      {
 900          LOCK(cs_main);
 901          CheckBlockDataAvailability(blockman, blockindex, /*check_for_undo=*/true);
 902      }
 903  
 904      if (!blockman.ReadBlockUndo(blockUndo, blockindex)) {
 905          throw JSONRPCError(RPC_MISC_ERROR, "Can't read undo data from disk");
 906      }
 907  
 908      return blockUndo;
 909  }
 910  
 911  const RPCResult& GetBlockVin()
 912  {
 913      static const RPCResult getblock_vin{
 914      RPCResult::Type::ARR, "vin", "",
 915      {
 916          {RPCResult::Type::OBJ, "", "",
 917          {
 918              {RPCResult::Type::ELISION, "", "The same output as verbosity = 2"},
 919              {RPCResult::Type::OBJ, "prevout", "(Only if undo information is available)",
 920              {
 921                  {RPCResult::Type::BOOL, "generated", "Coinbase or not"},
 922                  {RPCResult::Type::NUM, "height", "The height of the prevout"},
 923                  {RPCResult::Type::STR_AMOUNT, "value", "The value in " + CURRENCY_UNIT},
 924                  {RPCResult::Type::OBJ, "scriptPubKey", "",
 925                  {
 926                      {RPCResult::Type::STR, "asm", "Disassembly of the output script"},
 927                      {RPCResult::Type::STR, "desc", "Inferred descriptor for the output"},
 928                      {RPCResult::Type::STR_HEX, "hex", "The raw output script bytes, hex-encoded"},
 929                      {RPCResult::Type::STR, "address", /*optional=*/true, "The Limenka address (only if a well-defined address exists)"},
 930                      {RPCResult::Type::STR, "type", "The type (one of: " + GetAllOutputTypes() + ")"},
 931                  }},
 932              }},
 933          }},
 934      }
 935  };
 936      return getblock_vin;
 937  }
 938  
 939  static RPCHelpMan getblock()
 940  {
 941      return RPCHelpMan{"getblock",
 942                  "\nIf verbosity is 0, returns a string that is serialized, hex-encoded data for block 'hash'.\n"
 943                  "If verbosity is 1, returns an Object with information about block <hash>.\n"
 944                  "If verbosity is 2, returns an Object with information about block <hash> and information about each transaction.\n"
 945                  "If verbosity is 3, returns an Object with information about block <hash> and information about each transaction, including prevout information for inputs (only for unpruned blocks in the current best chain).\n",
 946                  {
 947                      {"blockhash", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "The block hash"},
 948                      {"verbosity|verbose", RPCArg::Type::NUM, RPCArg::Default{1}, "0 for hex-encoded data, 1 for a JSON object, 2 for JSON object with transaction data, and 3 for JSON object with transaction data including prevout information for inputs",
 949                       RPCArgOptions{.skip_type_check = true}},
 950                  },
 951                  {
 952                      RPCResult{"for verbosity = 0",
 953                  RPCResult::Type::STR_HEX, "", "A string that is serialized, hex-encoded data for block 'hash'"},
 954                      RPCResult{"for verbosity = 1",
 955                  RPCResult::Type::OBJ, "", "",
 956                  {
 957                      {RPCResult::Type::STR_HEX, "hash", "the block hash (same as provided)"},
 958                      {RPCResult::Type::NUM, "confirmations", "The number of confirmations, or -1 if the block is not on the main chain"},
 959                      {RPCResult::Type::NUM, "size", "The block size"},
 960                      {RPCResult::Type::NUM, "strippedsize", "The block size excluding witness data"},
 961                      {RPCResult::Type::NUM, "weight", "The block weight as defined in BIP 141"},
 962                      {RPCResult::Type::NUM, "height", "The block height or index"},
 963                      {RPCResult::Type::NUM, "version", "The block version"},
 964                      {RPCResult::Type::STR_HEX, "versionHex", "The block version formatted in hexadecimal"},
 965                      {RPCResult::Type::STR_HEX, "merkleroot", "The merkle root"},
 966                      {RPCResult::Type::ARR, "tx", "The transaction ids",
 967                          {{RPCResult::Type::STR_HEX, "", "The transaction id"}}},
 968                      {RPCResult::Type::NUM_TIME, "time",       "The block time expressed in " + UNIX_EPOCH_TIME},
 969                      {RPCResult::Type::NUM_TIME, "mediantime", "The median block time expressed in " + UNIX_EPOCH_TIME},
 970                      {RPCResult::Type::NUM, "nonce", "The nonce"},
 971                      {RPCResult::Type::STR_HEX, "bits", "nBits: compact representation of the block difficulty target"},
 972                      {RPCResult::Type::STR_HEX, "target", "The difficulty target"},
 973                      {RPCResult::Type::NUM, "difficulty", "The difficulty"},
 974                      {RPCResult::Type::STR_HEX, "chainwork", "Expected number of hashes required to produce the chain up to this block (in hex)"},
 975                      {RPCResult::Type::NUM, "nTx", "The number of transactions in the block"},
 976                      {RPCResult::Type::STR_HEX, "previousblockhash", /*optional=*/true, "The hash of the previous block (if available)"},
 977                      {RPCResult::Type::STR_HEX, "nextblockhash", /*optional=*/true, "The hash of the next block (if available)"},
 978                  }},
 979                      RPCResult{"for verbosity = 2",
 980                  RPCResult::Type::OBJ, "", "",
 981                  {
 982                      {RPCResult::Type::ELISION, "", "Same output as verbosity = 1"},
 983                      {RPCResult::Type::ARR, "tx", "",
 984                      {
 985                          {RPCResult::Type::OBJ, "", "",
 986                          {
 987                              {RPCResult::Type::ELISION, "", "The transactions in the format of the getrawtransaction RPC. Different from verbosity = 1 \"tx\" result"},
 988                              {RPCResult::Type::NUM, "fee", /*optional=*/true, "The transaction fee in " + CURRENCY_UNIT + ", omitted if block undo data is not available"},
 989                          }},
 990                      }},
 991                  }},
 992                      RPCResult{"for verbosity = 3",
 993                  RPCResult::Type::OBJ, "", "",
 994                  {
 995                      {RPCResult::Type::ELISION, "", "Same output as verbosity = 2"},
 996                      {RPCResult::Type::ARR, "tx", "",
 997                      {
 998                          {RPCResult::Type::OBJ, "", "",
 999                          {
1000                              GetBlockVin(),
1001                          }},
1002                      }},
1003                  }},
1004          },
1005                  RPCExamples{
1006                      HelpExampleCli("getblock", "\"00000000c937983704a73af28acdec37b049d214adbda81d7e2a3dd146f6ed09\"")
1007              + HelpExampleRpc("getblock", "\"00000000c937983704a73af28acdec37b049d214adbda81d7e2a3dd146f6ed09\"")
1008                  },
1009          [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
1010  {
1011      uint256 hash(ParseHashV(request.params[0], "blockhash"));
1012  
1013      int verbosity{ParseVerbosity(request.params[1], /*default_verbosity=*/1, /*allow_bool=*/true)};
1014  
1015      const CBlockIndex* pblockindex;
1016      const CBlockIndex* tip;
1017      ChainstateManager& chainman = EnsureAnyChainman(request.context);
1018      {
1019          LOCK(cs_main);
1020          pblockindex = chainman.m_blockman.LookupBlockIndex(hash);
1021          tip = chainman.ActiveChain().Tip();
1022  
1023          if (!pblockindex) {
1024              throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Block not found");
1025          }
1026      }
1027  
1028      const std::vector<uint8_t> block_data{GetRawBlockChecked(chainman.m_blockman, *pblockindex)};
1029  
1030      if (verbosity <= 0) {
1031          return HexStr(block_data);
1032      }
1033  
1034      DataStream block_stream{block_data};
1035      CBlock block{};
1036      block_stream >> TX_WITH_WITNESS(block);
1037  
1038      TxVerbosity tx_verbosity;
1039      if (verbosity == 1) {
1040          tx_verbosity = TxVerbosity::SHOW_TXID;
1041      } else if (verbosity == 2) {
1042          tx_verbosity = TxVerbosity::SHOW_DETAILS;
1043      } else {
1044          tx_verbosity = TxVerbosity::SHOW_DETAILS_AND_PREVOUT;
1045      }
1046  
1047      return blockToJSON(chainman.m_blockman, block, *tip, *pblockindex, tx_verbosity, chainman.GetConsensus().powLimit);
1048  },
1049      };
1050  }
1051  
1052  //! Return height of highest block that has been pruned, or std::nullopt if no blocks have been pruned
1053  std::optional<int> GetPruneHeight(const BlockManager& blockman, const CChain& chain) {
1054      AssertLockHeld(::cs_main);
1055  
1056      // Search for the last block missing block data or undo data. Don't let the
1057      // search consider the genesis block, because the genesis block does not
1058      // have undo data, but should not be considered pruned.
1059      const CBlockIndex* first_block{chain[1]};
1060      const CBlockIndex* chain_tip{chain.Tip()};
1061  
1062      // If there are no blocks after the genesis block, or no blocks at all, nothing is pruned.
1063      if (!first_block || !chain_tip) return std::nullopt;
1064  
1065      // If the chain tip is pruned, everything is pruned.
1066      if (!((chain_tip->nStatus & BLOCK_HAVE_MASK) == BLOCK_HAVE_MASK)) return chain_tip->nHeight;
1067  
1068      const auto& first_unpruned{*CHECK_NONFATAL(blockman.GetFirstBlock(*chain_tip, /*status_mask=*/BLOCK_HAVE_MASK, first_block))};
1069      if (&first_unpruned == first_block) {
1070          // All blocks between first_block and chain_tip have data, so nothing is pruned.
1071          return std::nullopt;
1072      }
1073  
1074      // Block before the first unpruned block is the last pruned block.
1075      return CHECK_NONFATAL(first_unpruned.pprev)->nHeight;
1076  }
1077  
1078  static RPCHelpMan listprunelocks()
1079  {
1080      return RPCHelpMan{"listprunelocks",
1081          "\nReturns a list of pruning locks.\n",
1082          {},
1083          RPCResult{
1084              RPCResult::Type::OBJ, "", "",
1085              {
1086                  {RPCResult::Type::ARR, "prune_locks", "",
1087                  {
1088                      {RPCResult::Type::OBJ, "", "",
1089                      {
1090                          {RPCResult::Type::STR, "id", "A unique identifier for the lock"},
1091                          {RPCResult::Type::STR, "desc", "A description of the lock's purpose"},
1092                          {RPCResult::Type::ARR_FIXED, "height", "Range of blocks prevented from being pruned",
1093                          {
1094                              {RPCResult::Type::NUM, "height_first", "Height of first block that may not be pruned"},
1095                              {RPCResult::Type::NUM, "height_last", "Height of last block that may not be pruned (omitted if unbounded)"},
1096                          }},
1097                          {RPCResult::Type::BOOL, "temporary", "Indicates the lock will not remain after a restart of the node"},
1098                      }},
1099                  }},
1100              }
1101          },
1102          RPCExamples{
1103              HelpExampleCli("listprunelocks", "")
1104            + HelpExampleRpc("listprunelocks", "")
1105          },
1106      [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
1107  {
1108      NodeContext& node = EnsureAnyNodeContext(request.context);
1109      ChainstateManager& chainman = EnsureChainman(node);
1110      Chainstate& active_chainstate = chainman.ActiveChainstate();
1111  
1112      UniValue locks_uv(UniValue::VARR);
1113      {
1114          LOCK(::cs_main);
1115          BlockManager * const blockman = &active_chainstate.m_blockman;
1116          for (const auto& prune_lock : blockman->m_prune_locks) {
1117              UniValue prune_lock_uv(UniValue::VOBJ);
1118              const auto& lock_info = prune_lock.second;
1119              prune_lock_uv.pushKV("id", prune_lock.first);
1120              prune_lock_uv.pushKV("desc", lock_info.desc);
1121              UniValue heights_uv(UniValue::VARR);
1122              heights_uv.push_back(lock_info.height_first);
1123              if (lock_info.height_last < std::numeric_limits<uint64_t>::max()) {
1124                  heights_uv.push_back(lock_info.height_last);
1125              }
1126              prune_lock_uv.pushKV("height", heights_uv);
1127              prune_lock_uv.pushKV("temporary", lock_info.temporary);
1128              locks_uv.push_back(prune_lock_uv);
1129          }
1130      }
1131  
1132      UniValue result(UniValue::VOBJ);
1133      result.pushKV("prune_locks", locks_uv);
1134      return result;
1135  },
1136      };
1137  }
1138  
1139  static RPCHelpMan setprunelock()
1140  {
1141      return RPCHelpMan{"setprunelock",
1142          "\nManipulate pruning locks.\n",
1143          {
1144              {"id", RPCArg::Type::STR, RPCArg::Optional::NO, "The unique id of the manipulated prune lock (or \"*\" if deleting all)"},
1145              {"lock_info", RPCArg::Type::OBJ, RPCArg::Optional::NO, "An object describing the desired lock",
1146                  {
1147                      {"desc", RPCArg::Type::STR, RPCArg::Optional::NO, "Description of the lock"},
1148                      {"height", RPCArg::Type::RANGE, RPCArg::DefaultHint("deletes the lock"), "The range of block heights to prevent pruning"},
1149                      {"sync", RPCArg::Type::BOOL, RPCArg::Default(false), "If true, success indicates the lock change was stored to disk (if non-temporary). If false, it is possible for a subsequent node crash to lose the lock."},
1150                      {"temporary", RPCArg::Type::BOOL, RPCArg::Default(false), "If true, the lock will not persist across node restart."},
1151                  },
1152              },
1153          },
1154          RPCResult{
1155              RPCResult::Type::OBJ, "", "",
1156              {
1157                  {RPCResult::Type::BOOL, "success", "Whether the change was successful"},
1158              }},
1159          RPCExamples{
1160              HelpExampleCli("setprunelock", "\"test\" \"{\\\"desc\\\": \\\"Just a test\\\", \\\"height\\\": [0,100]}\"")
1161            + HelpExampleCli("setprunelock", "\"test-2\" \"{\\\"desc\\\": \\\"Second RPC-created prunelock test\\\", \\\"height\\\": [100]}\"")
1162            + HelpExampleRpc("setprunelock", "\"test\", {\"desc\": \"Just a test\", \"height\": [0,100]}")
1163          },
1164      [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
1165  {
1166      NodeContext& node = EnsureAnyNodeContext(request.context);
1167      ChainstateManager& chainman = EnsureChainman(node);
1168      Chainstate& active_chainstate = chainman.ActiveChainstate();
1169  
1170      const auto& lock_info_json = request.params[1];
1171      RPCTypeCheckObj(lock_info_json,
1172          {
1173              {"desc", UniValueType(UniValue::VSTR)},
1174              {"height", UniValueType()}, // will be checked below
1175              {"sync", UniValueType(UniValue::VBOOL)},
1176              {"temporary", UniValueType(UniValue::VBOOL)},
1177          },
1178          /*fAllowNull=*/ true, /*fStrict=*/ true);
1179  
1180      const auto& lockid = request.params[0].get_str();
1181  
1182      node::PruneLockInfo lock_info;
1183  
1184      auto height_param = lock_info_json["height"];
1185      if (!height_param.isArray()) {
1186          UniValue new_height_param(UniValue::VARR);
1187          new_height_param.push_back(std::move(height_param));
1188          height_param = std::move(new_height_param);
1189      }
1190      bool success;
1191      if (height_param[0].isNull() && height_param[1].isNull()) {
1192          // Delete
1193          LOCK(::cs_main);
1194          BlockManager * const blockman = &active_chainstate.m_blockman;
1195          if (lockid == "*") {
1196              // Delete all
1197              success = true;
1198              std::vector<std::string> all_ids;
1199              all_ids.reserve(blockman->m_prune_locks.size());
1200              for (const auto& prune_lock : blockman->m_prune_locks) {
1201                  all_ids.push_back(prune_lock.first);
1202              }
1203              for (auto& lockid : all_ids) {
1204                  success |= blockman->DeletePruneLock(lockid);
1205              }
1206          } else {
1207              success = blockman->PruneLockExists(lockid) && blockman->DeletePruneLock(lockid);
1208          }
1209      } else {
1210          if (lockid == "*") throw JSONRPCError(RPC_INVALID_PARAMETER, "id \"*\" only makes sense when deleting");
1211          if (!height_param[0].isNum()) throw JSONRPCError(RPC_TYPE_ERROR, "Invalid start height");
1212          lock_info.height_first = height_param[0].getInt<uint64_t>();
1213          if (!height_param[1].isNull()) {
1214              if (!height_param[1].isNum()) throw JSONRPCError(RPC_TYPE_ERROR, "Invalid end height");
1215              lock_info.height_last = height_param[1].getInt<uint64_t>();
1216          }
1217          lock_info.desc = lock_info_json["desc"].get_str();
1218          if (lock_info_json["temporary"].isNull()) {
1219              lock_info.temporary = false;
1220          } else {
1221              lock_info.temporary = lock_info_json["temporary"].get_bool();
1222          }
1223          bool sync = false;
1224          if (!lock_info_json["sync"].isNull()) {
1225              sync = lock_info_json["sync"].get_bool();
1226          }
1227          LOCK(::cs_main);
1228          BlockManager * const blockman = &active_chainstate.m_blockman;
1229          success = blockman->UpdatePruneLock(lockid, lock_info, sync);
1230      }
1231  
1232      UniValue result(UniValue::VOBJ);
1233      result.pushKV("success", success);
1234      return result;
1235  },
1236      };
1237  }
1238  
1239  static RPCHelpMan pruneblockchain()
1240  {
1241      return RPCHelpMan{"pruneblockchain",
1242                  "Attempts to delete block and undo data up to a specified height or timestamp, if eligible for pruning.\n"
1243                  "Requires `-prune` to be enabled at startup. While pruned data may be re-fetched in some cases (e.g., via `getblockfrompeer`), local deletion is irreversible.\n",
1244                  {
1245                      {"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"
1246              "                  to prune blocks whose block time is at least 2 hours older than the provided timestamp."},
1247                  },
1248                  RPCResult{
1249                      RPCResult::Type::NUM, "", "Height of the last block pruned"},
1250                  RPCExamples{
1251                      HelpExampleCli("pruneblockchain", "1000")
1252              + HelpExampleRpc("pruneblockchain", "1000")
1253                  },
1254          [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
1255  {
1256      ChainstateManager& chainman = EnsureAnyChainman(request.context);
1257      if (!chainman.m_blockman.IsPruneMode()) {
1258          throw JSONRPCError(RPC_MISC_ERROR, "Cannot prune blocks because node is not in prune mode.");
1259      }
1260  
1261      LOCK(cs_main);
1262      Chainstate& active_chainstate = chainman.ActiveChainstate();
1263      CChain& active_chain = active_chainstate.m_chain;
1264  
1265      int heightParam = request.params[0].getInt<int>();
1266      if (heightParam < 0) {
1267          throw JSONRPCError(RPC_INVALID_PARAMETER, "Negative block height.");
1268      }
1269      if (heightParam == 0) {
1270          // Nothing to do here
1271          return uint64_t(0);
1272      }
1273  
1274      // Height value more than a billion is too high to be a block height, and
1275      // too low to be a block time (corresponds to timestamp from Sep 2001).
1276      if (heightParam > 1000000000) {
1277          // Add a 2 hour buffer to include blocks which might have had old timestamps
1278          const CBlockIndex* pindex = active_chain.FindEarliestAtLeast(heightParam - TIMESTAMP_WINDOW, 0);
1279          if (!pindex) {
1280              throw JSONRPCError(RPC_INVALID_PARAMETER, "Could not find block with at least the specified timestamp.");
1281          }
1282          heightParam = pindex->nHeight;
1283      }
1284  
1285      unsigned int height = (unsigned int) heightParam;
1286      unsigned int chainHeight = (unsigned int) active_chain.Height();
1287      if (chainHeight < chainman.GetParams().PruneAfterHeight()) {
1288          throw JSONRPCError(RPC_MISC_ERROR, "Blockchain is too short for pruning.");
1289      } else if (height > chainHeight) {
1290          throw JSONRPCError(RPC_INVALID_PARAMETER, "Blockchain is shorter than the attempted prune height.");
1291      } else if (height > chainHeight - MIN_BLOCKS_TO_KEEP) {
1292          LogDebug(BCLog::RPC, "Attempt to prune blocks close to the tip.  Retaining the minimum number of blocks.\n");
1293          height = chainHeight - MIN_BLOCKS_TO_KEEP;
1294      }
1295  
1296      PruneBlockFilesManual(active_chainstate, height);
1297      return GetPruneHeight(chainman.m_blockman, active_chain).value_or(-1);
1298  },
1299      };
1300  }
1301  
1302  CoinStatsHashType ParseHashType(const std::string& hash_type_input)
1303  {
1304      if (hash_type_input == "hash_serialized_3") {
1305          return CoinStatsHashType::HASH_SERIALIZED;
1306      } else if (hash_type_input == "muhash") {
1307          return CoinStatsHashType::MUHASH;
1308      } else if (hash_type_input == "none") {
1309          return CoinStatsHashType::NONE;
1310      } else {
1311          throw JSONRPCError(RPC_INVALID_PARAMETER, strprintf("'%s' is not a valid hash_type", hash_type_input));
1312      }
1313  }
1314  
1315  /**
1316   * Calculate statistics about the unspent transaction output set
1317   *
1318   * @param[in] index_requested Signals if the coinstatsindex should be used (when available).
1319   */
1320  static std::optional<kernel::CCoinsStats> GetUTXOStats(CCoinsView* view, node::BlockManager& blockman,
1321                                                         kernel::CoinStatsHashType hash_type,
1322                                                         const std::function<void()>& interruption_point = {},
1323                                                         const CBlockIndex* pindex = nullptr,
1324                                                         bool index_requested = true)
1325  {
1326      // Use CoinStatsIndex if it is requested and available and a hash_type of Muhash or None was requested
1327      if ((hash_type == kernel::CoinStatsHashType::MUHASH || hash_type == kernel::CoinStatsHashType::NONE) && g_coin_stats_index && index_requested) {
1328          if (pindex) {
1329              return g_coin_stats_index->LookUpStats(*pindex);
1330          } else {
1331              CBlockIndex& block_index = *CHECK_NONFATAL(WITH_LOCK(::cs_main, return blockman.LookupBlockIndex(view->GetBestBlock())));
1332              return g_coin_stats_index->LookUpStats(block_index);
1333          }
1334      }
1335  
1336      // If the coinstats index isn't requested or is otherwise not usable, the
1337      // pindex should either be null or equal to the view's best block. This is
1338      // because without the coinstats index we can only get coinstats about the
1339      // best block.
1340      CHECK_NONFATAL(!pindex || pindex->GetBlockHash() == view->GetBestBlock());
1341  
1342      return kernel::ComputeUTXOStats(hash_type, view, blockman, interruption_point);
1343  }
1344  
1345  static RPCHelpMan gettxoutsetinfo()
1346  {
1347      return RPCHelpMan{"gettxoutsetinfo",
1348                  "\nReturns statistics about the unspent transaction output set.\n"
1349                  "Note this call may take some time if you are not using coinstatsindex.\n",
1350                  {
1351                      {"hash_type", RPCArg::Type::STR, RPCArg::Default{"hash_serialized_3"}, "Which UTXO set hash should be calculated. Options: 'hash_serialized_3' (the legacy algorithm), 'muhash', 'none'."},
1352                      {"hash_or_height", RPCArg::Type::NUM, RPCArg::DefaultHint{"the current best block"}, "The block hash or height of the target height (only available with coinstatsindex).",
1353                       RPCArgOptions{
1354                           .skip_type_check = true,
1355                           .type_str = {"", "string or numeric"},
1356                       }},
1357                      {"use_index", RPCArg::Type::BOOL, RPCArg::Default{true}, "Use coinstatsindex, if available."},
1358                  },
1359                  RPCResult{
1360                      RPCResult::Type::OBJ, "", "",
1361                      {
1362                          {RPCResult::Type::NUM, "height", "The block height (index) of the returned statistics"},
1363                          {RPCResult::Type::STR_HEX, "bestblock", "The hash of the block at which these statistics are calculated"},
1364                          {RPCResult::Type::NUM, "txouts", "The number of unspent transaction outputs"},
1365                          {RPCResult::Type::NUM, "bogosize", "Database-independent, meaningless metric indicating the UTXO set size"},
1366                          {RPCResult::Type::STR_HEX, "hash_serialized_3", /*optional=*/true, "The serialized hash (only present if 'hash_serialized_3' hash_type is chosen)"},
1367                          {RPCResult::Type::STR_HEX, "muhash", /*optional=*/true, "The serialized hash (only present if 'muhash' hash_type is chosen)"},
1368                          {RPCResult::Type::NUM, "transactions", /*optional=*/true, "The number of transactions with unspent outputs (not available when coinstatsindex is used)"},
1369                          {RPCResult::Type::NUM, "disk_size", /*optional=*/true, "The estimated size of the chainstate on disk (not available when coinstatsindex is used)"},
1370                          {RPCResult::Type::STR_AMOUNT, "total_amount", "The total amount of coins in the UTXO set"},
1371                          {RPCResult::Type::STR_AMOUNT, "total_unspendable_amount", /*optional=*/true, "The total amount of coins permanently excluded from the UTXO set (only available if coinstatsindex is used)"},
1372                          {RPCResult::Type::OBJ, "block_info", /*optional=*/true, "Info on amounts in the block at this block height (only available if coinstatsindex is used)",
1373                          {
1374                              {RPCResult::Type::STR_AMOUNT, "prevout_spent", "Total amount of all prevouts spent in this block"},
1375                              {RPCResult::Type::STR_AMOUNT, "coinbase", "Coinbase subsidy amount of this block"},
1376                              {RPCResult::Type::STR_AMOUNT, "new_outputs_ex_coinbase", "Total amount of new outputs created by this block"},
1377                              {RPCResult::Type::STR_AMOUNT, "unspendable", "Total amount of unspendable outputs created in this block"},
1378                              {RPCResult::Type::OBJ, "unspendables", "Detailed view of the unspendable categories",
1379                              {
1380                                  {RPCResult::Type::STR_AMOUNT, "genesis_block", "The unspendable amount of the Genesis block subsidy"},
1381                                  {RPCResult::Type::STR_AMOUNT, "bip30", "Transactions overridden by duplicates (no longer possible with BIP30)"},
1382                                  {RPCResult::Type::STR_AMOUNT, "scripts", "Amounts sent to scripts that are unspendable (for example OP_RETURN outputs)"},
1383                                  {RPCResult::Type::STR_AMOUNT, "unclaimed_rewards", "Fee rewards that miners did not claim in their coinbase transaction"},
1384                              }}
1385                          }},
1386                      }},
1387                  RPCExamples{
1388                      HelpExampleCli("gettxoutsetinfo", "") +
1389                      HelpExampleCli("gettxoutsetinfo", R"("none")") +
1390                      HelpExampleCli("gettxoutsetinfo", R"("none" 1000)") +
1391                      HelpExampleCli("gettxoutsetinfo", R"("none" '"00000000c937983704a73af28acdec37b049d214adbda81d7e2a3dd146f6ed09"')") +
1392                      HelpExampleCli("-named gettxoutsetinfo", R"(hash_type='muhash' use_index='false')") +
1393                      HelpExampleRpc("gettxoutsetinfo", "") +
1394                      HelpExampleRpc("gettxoutsetinfo", R"("none")") +
1395                      HelpExampleRpc("gettxoutsetinfo", R"("none", 1000)") +
1396                      HelpExampleRpc("gettxoutsetinfo", R"("none", "00000000c937983704a73af28acdec37b049d214adbda81d7e2a3dd146f6ed09")")
1397                  },
1398          [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
1399  {
1400      UniValue ret(UniValue::VOBJ);
1401  
1402      const CBlockIndex* pindex{nullptr};
1403      const CoinStatsHashType hash_type{request.params[0].isNull() ? CoinStatsHashType::HASH_SERIALIZED : ParseHashType(request.params[0].get_str())};
1404      bool index_requested = request.params[2].isNull() || request.params[2].get_bool();
1405  
1406      NodeContext& node = EnsureAnyNodeContext(request.context);
1407      ChainstateManager& chainman = EnsureChainman(node);
1408      Chainstate& active_chainstate = chainman.ActiveChainstate();
1409      active_chainstate.ForceFlushStateToDisk();
1410  
1411      CCoinsView* coins_view;
1412      BlockManager* blockman;
1413      {
1414          LOCK(::cs_main);
1415          coins_view = &active_chainstate.CoinsDB();
1416          blockman = &active_chainstate.m_blockman;
1417          pindex = blockman->LookupBlockIndex(coins_view->GetBestBlock());
1418      }
1419  
1420      if (!request.params[1].isNull()) {
1421          if (!g_coin_stats_index) {
1422              throw JSONRPCError(RPC_INVALID_PARAMETER, "Querying specific block heights requires coinstatsindex");
1423          }
1424  
1425          if (hash_type == CoinStatsHashType::HASH_SERIALIZED) {
1426              throw JSONRPCError(RPC_INVALID_PARAMETER, "hash_serialized_3 hash type cannot be queried for a specific block");
1427          }
1428  
1429          if (!index_requested) {
1430              throw JSONRPCError(RPC_INVALID_PARAMETER, "Cannot set use_index to false when querying for a specific block");
1431          }
1432          pindex = ParseHashOrHeight(request.params[1], chainman);
1433      }
1434  
1435      if (index_requested && g_coin_stats_index) {
1436          if (!g_coin_stats_index->BlockUntilSyncedToCurrentChain()) {
1437              const IndexSummary summary{g_coin_stats_index->GetSummary()};
1438  
1439              // If a specific block was requested and the index has already synced past that height, we can return the
1440              // data already even though the index is not fully synced yet.
1441              if (pindex->nHeight > summary.best_block_height) {
1442                  throw JSONRPCError(RPC_INTERNAL_ERROR, strprintf("Unable to get data because coinstatsindex is still syncing. Current height: %d", summary.best_block_height));
1443              }
1444          }
1445      }
1446  
1447      const std::optional<CCoinsStats> maybe_stats = GetUTXOStats(coins_view, *blockman, hash_type, node.rpc_interruption_point, pindex, index_requested);
1448      if (maybe_stats.has_value()) {
1449          const CCoinsStats& stats = maybe_stats.value();
1450          ret.pushKV("height", (int64_t)stats.nHeight);
1451          ret.pushKV("bestblock", stats.hashBlock.GetHex());
1452          ret.pushKV("txouts", (int64_t)stats.nTransactionOutputs);
1453          ret.pushKV("bogosize", (int64_t)stats.nBogoSize);
1454          if (hash_type == CoinStatsHashType::HASH_SERIALIZED) {
1455              ret.pushKV("hash_serialized_3", stats.hashSerialized.GetHex());
1456          }
1457          if (hash_type == CoinStatsHashType::MUHASH) {
1458              ret.pushKV("muhash", stats.hashSerialized.GetHex());
1459          }
1460          CHECK_NONFATAL(stats.total_amount.has_value());
1461          ret.pushKV("total_amount", ValueFromAmount(stats.total_amount.value()));
1462          if (!stats.index_used) {
1463              ret.pushKV("transactions", static_cast<int64_t>(stats.nTransactions));
1464              ret.pushKV("disk_size", stats.nDiskSize);
1465          } else {
1466              ret.pushKV("total_unspendable_amount", ValueFromAmount(stats.total_unspendable_amount));
1467  
1468              CCoinsStats prev_stats{};
1469              if (pindex->nHeight > 0) {
1470                  const std::optional<CCoinsStats> maybe_prev_stats = GetUTXOStats(coins_view, *blockman, hash_type, node.rpc_interruption_point, pindex->pprev, index_requested);
1471                  if (!maybe_prev_stats) {
1472                      throw JSONRPCError(RPC_INTERNAL_ERROR, "Unable to read UTXO set");
1473                  }
1474                  prev_stats = maybe_prev_stats.value();
1475              }
1476  
1477              UniValue block_info(UniValue::VOBJ);
1478              block_info.pushKV("prevout_spent", ValueFromAmount(stats.total_prevout_spent_amount - prev_stats.total_prevout_spent_amount));
1479              block_info.pushKV("coinbase", ValueFromAmount(stats.total_coinbase_amount - prev_stats.total_coinbase_amount));
1480              block_info.pushKV("new_outputs_ex_coinbase", ValueFromAmount(stats.total_new_outputs_ex_coinbase_amount - prev_stats.total_new_outputs_ex_coinbase_amount));
1481              block_info.pushKV("unspendable", ValueFromAmount(stats.total_unspendable_amount - prev_stats.total_unspendable_amount));
1482  
1483              UniValue unspendables(UniValue::VOBJ);
1484              unspendables.pushKV("genesis_block", ValueFromAmount(stats.total_unspendables_genesis_block - prev_stats.total_unspendables_genesis_block));
1485              unspendables.pushKV("bip30", ValueFromAmount(stats.total_unspendables_bip30 - prev_stats.total_unspendables_bip30));
1486              unspendables.pushKV("scripts", ValueFromAmount(stats.total_unspendables_scripts - prev_stats.total_unspendables_scripts));
1487              unspendables.pushKV("unclaimed_rewards", ValueFromAmount(stats.total_unspendables_unclaimed_rewards - prev_stats.total_unspendables_unclaimed_rewards));
1488              block_info.pushKV("unspendables", std::move(unspendables));
1489  
1490              ret.pushKV("block_info", std::move(block_info));
1491          }
1492      } else {
1493          throw JSONRPCError(RPC_INTERNAL_ERROR, "Unable to read UTXO set");
1494      }
1495      return ret;
1496  },
1497      };
1498  }
1499  
1500  static RPCHelpMan gettxout()
1501  {
1502      return RPCHelpMan{"gettxout",
1503          "\nReturns details about an unspent transaction output.\n",
1504          {
1505              {"txid", RPCArg::Type::STR, RPCArg::Optional::NO, "The transaction id"},
1506              {"n", RPCArg::Type::NUM, RPCArg::Optional::NO, "vout number"},
1507              {"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."},
1508          },
1509          {
1510              RPCResult{"If the UTXO was not found", RPCResult::Type::NONE, "", ""},
1511              RPCResult{"Otherwise", RPCResult::Type::OBJ, "", "", {
1512                  {RPCResult::Type::STR_HEX, "bestblock", "The hash of the block at the tip of the chain"},
1513                  {RPCResult::Type::NUM, "confirmations", "The number of confirmations"},
1514                  {RPCResult::Type::NUM, "confirmations_assumed", /*optional=*/true, "The number of unverified confirmations (eg, in an assumed-valid UTXO set)"},
1515                  {RPCResult::Type::STR_AMOUNT, "value", "The transaction value in " + CURRENCY_UNIT},
1516                  {RPCResult::Type::OBJ, "scriptPubKey", "", {
1517                      {RPCResult::Type::STR, "asm", "Disassembly of the output script"},
1518                      {RPCResult::Type::STR, "desc", "Inferred descriptor for the output"},
1519                      {RPCResult::Type::STR_HEX, "hex", "The raw output script bytes, hex-encoded"},
1520                      {RPCResult::Type::STR, "type", "The type (one of: " + GetAllOutputTypes() + ")"},
1521                      {RPCResult::Type::STR, "address", /*optional=*/true, "The Limenka address (only if a well-defined address exists)"},
1522                  }},
1523                  {RPCResult::Type::BOOL, "coinbase", "Coinbase or not"},
1524              }},
1525          },
1526          RPCExamples{
1527              "\nGet unspent transactions\n"
1528              + HelpExampleCli("listunspent", "") +
1529              "\nView the details\n"
1530              + HelpExampleCli("gettxout", "\"txid\" 1") +
1531              "\nAs a JSON-RPC call\n"
1532              + HelpExampleRpc("gettxout", "\"txid\", 1")
1533                  },
1534          [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
1535  {
1536      NodeContext& node = EnsureAnyNodeContext(request.context);
1537      ChainstateManager& chainman = EnsureChainman(node);
1538      LOCK(cs_main);
1539  
1540      UniValue ret(UniValue::VOBJ);
1541  
1542      auto hash{Txid::FromUint256(ParseHashV(request.params[0], "txid"))};
1543      COutPoint out{hash, request.params[1].getInt<uint32_t>()};
1544      bool fMempool = true;
1545      if (!request.params[2].isNull())
1546          fMempool = request.params[2].get_bool();
1547  
1548      Chainstate& active_chainstate = chainman.ActiveChainstate();
1549      CCoinsViewCache* coins_view = &active_chainstate.CoinsTip();
1550  
1551      std::optional<Coin> coin;
1552      if (fMempool) {
1553          const CTxMemPool& mempool = EnsureMemPool(node);
1554          LOCK(mempool.cs);
1555          CCoinsViewMemPool view(coins_view, mempool);
1556          if (!mempool.isSpent(out)) coin = view.GetCoin(out);
1557      } else {
1558          coin = coins_view->GetCoin(out);
1559      }
1560      if (!coin) return UniValue::VNULL;
1561  
1562      const CBlockIndex* pindex = active_chainstate.m_blockman.LookupBlockIndex(coins_view->GetBestBlock());
1563      ret.pushKV("bestblock", pindex->GetBlockHash().GetHex());
1564      if (coin->nHeight == MEMPOOL_HEIGHT) {
1565          ret.pushKV("confirmations", 0);
1566      } else {
1567          const auto assumed_base_height = chainman.GetSnapshotBaseHeight();
1568          if (assumed_base_height && coin->nHeight < *assumed_base_height) {
1569              ret.pushKV("confirmations", 0);
1570              ret.pushKV("confirmations_assumed", (int64_t)(pindex->nHeight - coin->nHeight + 1));
1571          } else {
1572          ret.pushKV("confirmations", (int64_t)(pindex->nHeight - coin->nHeight + 1));
1573          }
1574      }
1575      ret.pushKV("value", ValueFromAmount(coin->out.nValue));
1576      UniValue o(UniValue::VOBJ);
1577      ScriptToUniv(coin->out.scriptPubKey, /*out=*/o, /*include_hex=*/true, /*include_address=*/true);
1578      ret.pushKV("scriptPubKey", std::move(o));
1579      ret.pushKV("coinbase", (bool)coin->fCoinBase);
1580  
1581      return ret;
1582  },
1583      };
1584  }
1585  
1586  static RPCHelpMan verifychain()
1587  {
1588      return RPCHelpMan{"verifychain",
1589                  "\nVerifies blockchain database.\n",
1590                  {
1591                      {"checklevel", RPCArg::Type::NUM, RPCArg::DefaultHint{strprintf("%d, range=0-4", DEFAULT_CHECKLEVEL)},
1592                          strprintf("How thorough the block verification is:\n%s", MakeUnorderedList(CHECKLEVEL_DOC))},
1593                      {"nblocks", RPCArg::Type::NUM, RPCArg::DefaultHint{strprintf("%d, 0=all", DEFAULT_CHECKBLOCKS)}, "The number of blocks to check."},
1594                  },
1595                  RPCResult{
1596                      RPCResult::Type::BOOL, "", "Verification finished successfully. If false, check debug log for reason."},
1597                  RPCExamples{
1598                      HelpExampleCli("verifychain", "")
1599              + HelpExampleRpc("verifychain", "")
1600                  },
1601          [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
1602  {
1603      const int check_level{request.params[0].isNull() ? DEFAULT_CHECKLEVEL : request.params[0].getInt<int>()};
1604      const int check_depth{request.params[1].isNull() ? DEFAULT_CHECKBLOCKS : request.params[1].getInt<int>()};
1605  
1606      ChainstateManager& chainman = EnsureAnyChainman(request.context);
1607      LOCK(cs_main);
1608  
1609      Chainstate& active_chainstate = chainman.ActiveChainstate();
1610      return CVerifyDB(chainman.GetNotifications()).VerifyDB(
1611                 active_chainstate, chainman.GetParams().GetConsensus(), active_chainstate.CoinsTip(), check_level, check_depth) == VerifyDBResult::SUCCESS;
1612  },
1613      };
1614  }
1615  
1616  static RPCHelpMan scriptthreadsinfo()
1617  {
1618      return RPCHelpMan{"scriptthreadsinfo",
1619                  "\nShow information about the script verification threads.\n",
1620                  {},
1621                  RPCResult{
1622                      RPCResult::Type::OBJ, "", "",
1623                      {
1624                          {RPCResult::Type::BOOL, "enabled", "true if script verification threads are enabled (see setscriptthreadsenabled)."},
1625                          {RPCResult::Type::NUM, "num_script_check_threads", "The total number of script verification threads, when enabled."},
1626                      },
1627                  },
1628                  RPCExamples{
1629                      HelpExampleCli("scriptthreadsinfo", "")
1630              + HelpExampleRpc("scriptthreadsinfo", "")
1631                  },
1632          [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
1633  {
1634      ChainstateManager& chainman = EnsureAnyChainman(request.context);
1635      UniValue ret(UniValue::VOBJ);
1636      size_t thread_count{chainman.m_script_check_queue_enabled ? chainman.GetCheckQueue().ThreadCount() : 0};
1637      ret.pushKV("enabled", (bool)thread_count);
1638      ret.pushKV("num_script_check_threads", (int64_t)thread_count + 1);
1639      return ret;
1640  },
1641      };
1642  }
1643  
1644  static RPCHelpMan setscriptthreadsenabled()
1645  {
1646      return RPCHelpMan{"setscriptthreadsenabled",
1647                  "\nDisable/enable script verification threads, thereby reducing CPU usage on multicore systems on demand.\n"
1648                  "Disabling script verification threads may result in a significant slow-down during synchronisation.\n"
1649                  "Has no effect on single core machines or if started with -par=<-<numcores>\n",
1650                  {
1651                      {"state", RPCArg::Type::BOOL, RPCArg::Optional::NO, "false if script verification threads should be disabled (true for re-enabling)"},
1652                  },
1653                  RPCResult{RPCResult::Type::NONE, "", ""},
1654                  RPCExamples{
1655                      HelpExampleCli("setscriptthreadsenabled", "false")
1656              + HelpExampleRpc("setscriptthreadsenabled", "false")
1657                  },
1658          [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
1659  {
1660      ChainstateManager& chainman = EnsureAnyChainman(request.context);
1661      LOCK(cs_main);
1662  
1663      const bool parallel_script_checks{request.params[0].get_bool()};
1664      if (parallel_script_checks) {
1665          if (!chainman.GetCheckQueue().HasThreads()) {
1666              throw JSONRPCError(RPC_MISC_ERROR, "Script verification threads are disabled (single core machine or -par=<-<numcores>)");
1667          }
1668  
1669          chainman.m_script_check_queue_enabled = true;
1670      } else {
1671          chainman.m_script_check_queue_enabled = false;
1672      }
1673  
1674      return NullUniValue;
1675  },
1676      };
1677  }
1678  
1679  static void SoftForkDescPushBack(const CBlockIndex* blockindex, UniValue& softforks, const ChainstateManager& chainman, Consensus::BuriedDeployment dep)
1680  {
1681      // For buried deployments.
1682  
1683      if (!DeploymentEnabled(chainman, dep)) return;
1684  
1685      UniValue rv(UniValue::VOBJ);
1686      rv.pushKV("type", "buried");
1687      // getdeploymentinfo reports the softfork as active from when the chain height is
1688      // one below the activation height
1689      rv.pushKV("active", DeploymentActiveAfter(blockindex, chainman, dep));
1690      rv.pushKV("height", chainman.GetConsensus().DeploymentHeight(dep));
1691      softforks.pushKV(DeploymentName(dep), std::move(rv));
1692  }
1693  
1694  static void SoftForkDescPushBack(const CBlockIndex* blockindex, UniValue& softforks, const ChainstateManager& chainman, Consensus::DeploymentPos id)
1695  {
1696      // For BIP9 deployments.
1697  
1698      if (!DeploymentEnabled(chainman, id)) return;
1699      if (blockindex == nullptr) return;
1700  
1701      auto get_state_name = [](const ThresholdState state) -> std::string {
1702          switch (state) {
1703          case ThresholdState::DEFINED: return "defined";
1704          case ThresholdState::STARTED: return "started";
1705          case ThresholdState::LOCKED_IN: return "locked_in";
1706          case ThresholdState::ACTIVE: return "active";
1707          case ThresholdState::FAILED: return "failed";
1708          case ThresholdState::EXPIRED: return "expired";
1709          }
1710          return "invalid";
1711      };
1712  
1713      UniValue bip9(UniValue::VOBJ);
1714  
1715      const ThresholdState next_state = chainman.m_versionbitscache.State(blockindex, chainman.GetConsensus(), id);
1716      const ThresholdState current_state = chainman.m_versionbitscache.State(blockindex->pprev, chainman.GetConsensus(), id);
1717  
1718      const bool has_signal = (ThresholdState::STARTED == current_state || ThresholdState::LOCKED_IN == current_state);
1719  
1720      // BIP9 parameters
1721      if (has_signal) {
1722          bip9.pushKV("bit", chainman.GetConsensus().vDeployments[id].bit);
1723      }
1724      bip9.pushKV("start_time", chainman.GetConsensus().vDeployments[id].nStartTime);
1725      bip9.pushKV("timeout", chainman.GetConsensus().vDeployments[id].nTimeout);
1726      bip9.pushKV("min_activation_height", chainman.GetConsensus().vDeployments[id].min_activation_height);
1727      if (chainman.GetConsensus().vDeployments[id].max_activation_height < std::numeric_limits<int>::max()) {
1728          bip9.pushKV("max_activation_height", chainman.GetConsensus().vDeployments[id].max_activation_height);
1729      }
1730  
1731      // BIP9 status
1732      bip9.pushKV("status", get_state_name(current_state));
1733      bip9.pushKV("since", chainman.m_versionbitscache.StateSinceHeight(blockindex->pprev, chainman.GetConsensus(), id));
1734      bip9.pushKV("status_next", get_state_name(next_state));
1735  
1736      // BIP9 signalling status, if applicable
1737      if (has_signal) {
1738          UniValue statsUV(UniValue::VOBJ);
1739          std::vector<bool> signals;
1740          BIP9Stats statsStruct = chainman.m_versionbitscache.Statistics(blockindex, chainman.GetConsensus(), id, &signals);
1741          statsUV.pushKV("period", statsStruct.period);
1742          statsUV.pushKV("period_start", blockindex->nHeight + 1 - statsStruct.elapsed);
1743          statsUV.pushKV("elapsed", statsStruct.elapsed);
1744          statsUV.pushKV("count", statsStruct.count);
1745          if (ThresholdState::LOCKED_IN != current_state) {
1746              statsUV.pushKV("threshold", statsStruct.threshold);
1747              statsUV.pushKV("possible", statsStruct.possible);
1748          }
1749          bip9.pushKV("statistics", std::move(statsUV));
1750  
1751          std::string sig;
1752          sig.reserve(signals.size());
1753          for (const bool s : signals) {
1754              sig.push_back(s ? '#' : '-');
1755          }
1756          bip9.pushKV("signalling", sig);
1757      }
1758  
1759      UniValue rv(UniValue::VOBJ);
1760      rv.pushKV("type", "bip9");
1761      if (ThresholdState::ACTIVE == next_state) {
1762          const int activation_height = chainman.m_versionbitscache.StateSinceHeight(blockindex, chainman.GetConsensus(), id);
1763          rv.pushKV("height", activation_height);
1764          // Add height_end for temporary softforks
1765          const auto& deployment = chainman.GetConsensus().vDeployments[id];
1766          if (deployment.active_duration < std::numeric_limits<int>::max()) {
1767              rv.pushKV("height_end", activation_height + deployment.active_duration - 1);
1768          }
1769      }
1770      rv.pushKV("active", ThresholdState::ACTIVE == next_state);
1771      rv.pushKV("bip9", std::move(bip9));
1772  
1773      softforks.pushKV(DeploymentName(id), std::move(rv));
1774  }
1775  
1776  // used by rest.cpp:rest_chaininfo, so cannot be static
1777  RPCHelpMan getblockchaininfo()
1778  {
1779      return RPCHelpMan{"getblockchaininfo",
1780          "Returns an object containing various state info regarding blockchain processing.\n",
1781          {},
1782          RPCResult{
1783              RPCResult::Type::OBJ, "", "",
1784              {
1785                  {RPCResult::Type::STR, "chain", "current network name (" LIST_CHAIN_NAMES ")"},
1786                  {RPCResult::Type::NUM, "blocks", "the height of the most-work fully-validated chain. The genesis block has height 0"},
1787                  {RPCResult::Type::NUM, "headers", "the current number of headers we have validated"},
1788                  {RPCResult::Type::STR, "bestblockhash", "the hash of the currently best block"},
1789                  {RPCResult::Type::STR_HEX, "bits", "nBits: compact representation of the block difficulty target"},
1790                  {RPCResult::Type::STR_HEX, "target", "The difficulty target"},
1791                  {RPCResult::Type::NUM, "difficulty", "the current difficulty"},
1792                  {RPCResult::Type::NUM_TIME, "time", "The block time expressed in " + UNIX_EPOCH_TIME},
1793                  {RPCResult::Type::NUM_TIME, "mediantime", "The median block time expressed in " + UNIX_EPOCH_TIME},
1794                  {RPCResult::Type::NUM, "verificationprogress", "estimate of verification progress [0..1]"},
1795                  {RPCResult::Type::BOOL, "initialblockdownload", "(debug information) estimate of whether this node is in Initial Block Download mode"},
1796                  {RPCResult::Type::STR_HEX, "chainwork", "total amount of work in active chain, in hexadecimal"},
1797                  {RPCResult::Type::NUM, "size_on_disk", "the estimated size of the block and undo files on disk"},
1798                  {RPCResult::Type::BOOL, "pruned", "if the blocks are subject to pruning"},
1799                  {RPCResult::Type::NUM, "pruneheight", /*optional=*/true, "height of the last block pruned, plus one (only present if pruning is enabled)"},
1800                  {RPCResult::Type::BOOL, "automatic_pruning", /*optional=*/true, "whether automatic pruning is enabled (only present if pruning is enabled)"},
1801                  {RPCResult::Type::NUM, "prune_target_size", /*optional=*/true, "the target size used by pruning (only present if automatic pruning is enabled)"},
1802                  {RPCResult::Type::STR_HEX, "signet_challenge", /*optional=*/true, "the block challenge (aka. block script), in hexadecimal (only present if the current network is a signet)"},
1803                  {RPCResult::Type::NUM, "fork_min_interval", /*optional=*/true, "rolling minimum fork block interval in seconds over the last 2016 fork blocks - the empirical sequential-delay floor (only present on the fork chain)"},
1804                  {RPCResult::Type::NUM, "fork_delay_steps", /*optional=*/true, "the current sequential-delay step count (only present on the fork chain)"},
1805                  (IsDeprecatedRPCEnabled("warnings") ?
1806                      RPCResult{RPCResult::Type::STR, "warnings", "any network and blockchain warnings (DEPRECATED)"} :
1807                      RPCResult{RPCResult::Type::ARR, "warnings", "any network and blockchain warnings (run with `-deprecatedrpc=warnings` to return the latest warning as a single string)",
1808                      {
1809                          {RPCResult::Type::STR, "", "warning"},
1810                      }
1811                      }
1812                  ),
1813              }},
1814          RPCExamples{
1815              HelpExampleCli("getblockchaininfo", "")
1816              + HelpExampleRpc("getblockchaininfo", "")
1817          },
1818          [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
1819  {
1820      ChainstateManager& chainman = EnsureAnyChainman(request.context);
1821      LOCK(cs_main);
1822      Chainstate& active_chainstate = chainman.ActiveChainstate();
1823  
1824      const CBlockIndex& tip{*CHECK_NONFATAL(active_chainstate.m_chain.Tip())};
1825      const int height{tip.nHeight};
1826      UniValue obj(UniValue::VOBJ);
1827      obj.pushKV("chain", chainman.GetParams().GetChainTypeString());
1828      obj.pushKV("blocks", height);
1829      obj.pushKV("headers", chainman.m_best_header ? chainman.m_best_header->nHeight : -1);
1830      obj.pushKV("bestblockhash", tip.GetBlockHash().GetHex());
1831      obj.pushKV("bits", strprintf("%08x", tip.nBits));
1832      obj.pushKV("target", GetTarget(tip, chainman.GetConsensus().powLimit).GetHex());
1833      obj.pushKV("difficulty", GetDifficulty(tip));
1834      obj.pushKV("time", tip.GetBlockTime());
1835      obj.pushKV("mediantime", tip.GetMedianTimePast());
1836      obj.pushKV("verificationprogress", chainman.GuessVerificationProgress(&tip));
1837      obj.pushKV("initialblockdownload", chainman.IsInitialBlockDownload());
1838      obj.pushKV("chainwork", tip.nChainWork.GetHex());
1839      obj.pushKV("size_on_disk", chainman.m_blockman.CalculateCurrentUsage());
1840      obj.pushKV("pruned", chainman.m_blockman.IsPruneMode());
1841      if (chainman.m_blockman.IsPruneMode()) {
1842          const auto prune_height{GetPruneHeight(chainman.m_blockman, active_chainstate.m_chain)};
1843          obj.pushKV("pruneheight", prune_height ? prune_height.value() + 1 : 0);
1844  
1845          const bool automatic_pruning{chainman.m_blockman.GetPruneTarget() != BlockManager::PRUNE_TARGET_MANUAL};
1846          obj.pushKV("automatic_pruning",  automatic_pruning);
1847          if (automatic_pruning) {
1848              obj.pushKV("prune_target_size", chainman.m_blockman.GetPruneTarget());
1849          }
1850      }
1851      if (chainman.GetParams().GetChainType() == ChainType::SIGNET) {
1852          const std::vector<uint8_t>& signet_challenge =
1853              chainman.GetParams().GetConsensus().signet_challenge;
1854          obj.pushKV("signet_challenge", HexStr(signet_challenge));
1855      }
1856      if (chainman.GetParams().GetChainType() == ChainType::FORK) {
1857          // Empirical delay-floor measurement: the rolling minimum fork
1858          // block interval maps the fastest delay hardware in service.
1859          // When this drops far below the 60s nominal, a consensus K
1860          // revision is indicated (see PLAN_LOCKED.md).
1861          const auto min_interval = chainman.GetForkMinInterval(2016);
1862          if (min_interval.has_value()) {
1863              obj.pushKV("fork_min_interval", *min_interval);
1864          }
1865          obj.pushKV("fork_delay_steps", uint64_t{chainman.GetConsensus().nForkDelaySteps});
1866      }
1867  
1868      NodeContext& node = EnsureAnyNodeContext(request.context);
1869      obj.pushKV("warnings", node::GetWarningsForRpc(*CHECK_NONFATAL(node.warnings), IsDeprecatedRPCEnabled("warnings")));
1870      return obj;
1871  },
1872      };
1873  }
1874  
1875  namespace {
1876  const std::vector<RPCResult> RPCHelpForDeployment{
1877      {RPCResult::Type::STR, "type", "one of \"buried\", \"bip9\""},
1878      {RPCResult::Type::NUM, "height", /*optional=*/true, "height of the first block which enforces the rules (only for \"buried\" type, or \"bip9\" type with \"active\" status)"},
1879      {RPCResult::Type::NUM, "height_end", /*optional=*/true, "height of the last block which enforces the rules (only for \"bip9\" type with \"active\" status and temporary deployments)"},
1880      {RPCResult::Type::BOOL, "active", "true if the rules are enforced for the mempool and the next block"},
1881      {RPCResult::Type::OBJ, "bip9", /*optional=*/true, "status of bip9 softforks (only for \"bip9\" type)",
1882      {
1883          {RPCResult::Type::NUM, "bit", /*optional=*/true, "the bit (0-28) in the block version field used to signal this softfork (only for \"started\" and \"locked_in\" status)"},
1884          {RPCResult::Type::NUM_TIME, "start_time", "the minimum median time past of a block at which the bit gains its meaning"},
1885          {RPCResult::Type::NUM_TIME, "timeout", "the median time past of a block at which the deployment is considered failed if not yet locked in"},
1886          {RPCResult::Type::NUM, "min_activation_height", "minimum height of blocks for which the rules may be enforced"},
1887          {RPCResult::Type::NUM, "max_activation_height", /*optional=*/true, "height at which the deployment will unconditionally activate (absent for miner-vetoable deployments)"},
1888          {RPCResult::Type::STR, "status", "status of deployment at specified block (one of \"defined\", \"started\", \"locked_in\", \"active\", \"failed\", \"expired\")"},
1889          {RPCResult::Type::NUM, "since", "height of the first block to which the status applies"},
1890          {RPCResult::Type::STR, "status_next", "status of deployment at the next block"},
1891          {RPCResult::Type::OBJ, "statistics", /*optional=*/true, "numeric statistics about signalling for a softfork (only for \"started\" and \"locked_in\" status)",
1892          {
1893              {RPCResult::Type::NUM, "period", "the length in blocks of the signalling period"},
1894              {RPCResult::Type::NUM, "period_start", "height of the first block of this signalling period"},
1895              {RPCResult::Type::NUM, "threshold", /*optional=*/true, "the number of blocks with the version bit set required to activate the feature (only for \"started\" status)"},
1896              {RPCResult::Type::NUM, "elapsed", "the number of blocks elapsed since the beginning of the current period"},
1897              {RPCResult::Type::NUM, "count", "the number of blocks with the version bit set in the current period"},
1898              {RPCResult::Type::BOOL, "possible", /*optional=*/true, "returns false if there are not enough blocks left in this period to pass activation threshold (only for \"started\" status)"},
1899          }},
1900          {RPCResult::Type::STR, "signalling", /*optional=*/true, "indicates blocks that signalled with a # and blocks that did not with a -"},
1901      }},
1902  };
1903  
1904  UniValue DeploymentInfo(const CBlockIndex* blockindex, const ChainstateManager& chainman)
1905  {
1906      UniValue softforks(UniValue::VOBJ);
1907      SoftForkDescPushBack(blockindex, softforks, chainman, Consensus::DEPLOYMENT_HEIGHTINCB);
1908      SoftForkDescPushBack(blockindex, softforks, chainman, Consensus::DEPLOYMENT_DERSIG);
1909      SoftForkDescPushBack(blockindex, softforks, chainman, Consensus::DEPLOYMENT_CLTV);
1910      SoftForkDescPushBack(blockindex, softforks, chainman, Consensus::DEPLOYMENT_CSV);
1911      SoftForkDescPushBack(blockindex, softforks, chainman, Consensus::DEPLOYMENT_SEGWIT);
1912      SoftForkDescPushBack(blockindex, softforks, chainman, Consensus::DEPLOYMENT_TESTDUMMY);
1913      SoftForkDescPushBack(blockindex, softforks, chainman, Consensus::DEPLOYMENT_TAPROOT);
1914      SoftForkDescPushBack(blockindex, softforks, chainman, Consensus::DEPLOYMENT_REDUCED_DATA);
1915      return softforks;
1916  }
1917  } // anon namespace
1918  
1919  RPCHelpMan getdeploymentinfo()
1920  {
1921      return RPCHelpMan{"getdeploymentinfo",
1922          "Returns an object containing various state info regarding deployments of consensus changes.",
1923          {
1924              {"blockhash", RPCArg::Type::STR_HEX, RPCArg::Default{"hash of current chain tip"}, "The block hash at which to query deployment state"},
1925          },
1926          RPCResult{
1927              RPCResult::Type::OBJ, "", "", {
1928                  {RPCResult::Type::STR, "hash", "requested block hash (or tip)"},
1929                  {RPCResult::Type::NUM, "height", "requested block height (or tip)"},
1930                  {RPCResult::Type::OBJ_DYN, "deployments", "", {
1931                      {RPCResult::Type::OBJ, "xxxx", "name of the deployment", RPCHelpForDeployment}
1932                  }},
1933              }
1934          },
1935          RPCExamples{ HelpExampleCli("getdeploymentinfo", "") + HelpExampleRpc("getdeploymentinfo", "") },
1936          [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
1937          {
1938              const ChainstateManager& chainman = EnsureAnyChainman(request.context);
1939              LOCK(cs_main);
1940              const Chainstate& active_chainstate = chainman.ActiveChainstate();
1941  
1942              const CBlockIndex* blockindex;
1943              if (request.params[0].isNull()) {
1944                  blockindex = CHECK_NONFATAL(active_chainstate.m_chain.Tip());
1945              } else {
1946                  const uint256 hash(ParseHashV(request.params[0], "blockhash"));
1947                  blockindex = chainman.m_blockman.LookupBlockIndex(hash);
1948                  if (!blockindex) {
1949                      throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Block not found");
1950                  }
1951              }
1952  
1953              UniValue deploymentinfo(UniValue::VOBJ);
1954              deploymentinfo.pushKV("hash", blockindex->GetBlockHash().ToString());
1955              deploymentinfo.pushKV("height", blockindex->nHeight);
1956              deploymentinfo.pushKV("deployments", DeploymentInfo(blockindex, chainman));
1957              return deploymentinfo;
1958          },
1959      };
1960  }
1961  
1962  /** Comparison function for sorting the getchaintips heads.  */
1963  struct CompareBlocksByHeight
1964  {
1965      bool operator()(const CBlockIndex* a, const CBlockIndex* b) const
1966      {
1967          /* Make sure that unequal blocks with the same height do not compare
1968             equal. Use the pointers themselves to make a distinction. */
1969  
1970          if (a->nHeight != b->nHeight)
1971            return (a->nHeight > b->nHeight);
1972  
1973          return a < b;
1974      }
1975  };
1976  
1977  static RPCHelpMan getchaintips()
1978  {
1979      return RPCHelpMan{"getchaintips",
1980                  "Return information about all known tips in the block tree,"
1981                  " including the main chain as well as orphaned branches.\n",
1982                  {},
1983                  RPCResult{
1984                      RPCResult::Type::ARR, "", "",
1985                      {{RPCResult::Type::OBJ, "", "",
1986                          {
1987                              {RPCResult::Type::NUM, "height", "height of the chain tip"},
1988                              {RPCResult::Type::STR_HEX, "hash", "block hash of the tip"},
1989                              {RPCResult::Type::NUM, "branchlen", "zero for main chain, otherwise length of branch connecting the tip to the main chain"},
1990                              {RPCResult::Type::STR, "status", "status of the chain, \"active\" for the main chain\n"
1991              "Possible values for status:\n"
1992              "1.  \"invalid\"               This branch contains at least one invalid block\n"
1993              "2.  \"headers-only\"          Not all blocks for this branch are available, but the headers are valid\n"
1994              "3.  \"valid-headers\"         All blocks are available for this branch, but they were never fully validated\n"
1995              "4.  \"valid-fork\"            This branch is not part of the active chain, but is fully validated\n"
1996              "5.  \"active\"                This is the tip of the active main chain, which is certainly valid"},
1997                          }}}},
1998                  RPCExamples{
1999                      HelpExampleCli("getchaintips", "")
2000              + HelpExampleRpc("getchaintips", "")
2001                  },
2002          [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
2003  {
2004      ChainstateManager& chainman = EnsureAnyChainman(request.context);
2005      LOCK(cs_main);
2006      CChain& active_chain = chainman.ActiveChain();
2007  
2008      /*
2009       * Idea: The set of chain tips is the active chain tip, plus orphan blocks which do not have another orphan building off of them.
2010       * Algorithm:
2011       *  - Make one pass through BlockIndex(), picking out the orphan blocks, and also storing a set of the orphan block's pprev pointers.
2012       *  - Iterate through the orphan blocks. If the block isn't pointed to by another orphan, it is a chain tip.
2013       *  - Add the active chain tip
2014       */
2015      std::set<const CBlockIndex*, CompareBlocksByHeight> setTips;
2016      std::set<const CBlockIndex*> setOrphans;
2017      std::set<const CBlockIndex*> setPrevs;
2018  
2019      for (const auto& [_, block_index] : chainman.BlockIndex()) {
2020          if (!active_chain.Contains(&block_index)) {
2021              setOrphans.insert(&block_index);
2022              setPrevs.insert(block_index.pprev);
2023          }
2024      }
2025  
2026      for (std::set<const CBlockIndex*>::iterator it = setOrphans.begin(); it != setOrphans.end(); ++it) {
2027          if (setPrevs.erase(*it) == 0) {
2028              setTips.insert(*it);
2029          }
2030      }
2031  
2032      // Always report the currently active tip.
2033      setTips.insert(active_chain.Tip());
2034  
2035      /* Construct the output array.  */
2036      UniValue res(UniValue::VARR);
2037      for (const CBlockIndex* block : setTips) {
2038          UniValue obj(UniValue::VOBJ);
2039          obj.pushKV("height", block->nHeight);
2040          obj.pushKV("hash", block->phashBlock->GetHex());
2041  
2042          const int branchLen = block->nHeight - active_chain.FindFork(block)->nHeight;
2043          obj.pushKV("branchlen", branchLen);
2044  
2045          std::string status;
2046          if (active_chain.Contains(block)) {
2047              // This block is part of the currently active chain.
2048              status = "active";
2049          } else if (block->nStatus & BLOCK_FAILED_MASK) {
2050              // This block or one of its ancestors is invalid.
2051              status = "invalid";
2052          } else if (!block->HaveNumChainTxs()) {
2053              // This block cannot be connected because full block data for it or one of its parents is missing.
2054              status = "headers-only";
2055          } else if (block->IsValid(BLOCK_VALID_SCRIPTS)) {
2056              // This block is fully validated, but no longer part of the active chain. It was probably the active block once, but was reorganized.
2057              status = "valid-fork";
2058          } else if (block->IsValid(BLOCK_VALID_TREE)) {
2059              // The headers for this block are valid, but it has not been validated. It was probably never part of the most-work chain.
2060              status = "valid-headers";
2061          } else {
2062              // No clue.
2063              status = "unknown";
2064          }
2065          obj.pushKV("status", status);
2066  
2067          res.push_back(std::move(obj));
2068      }
2069  
2070      return res;
2071  },
2072      };
2073  }
2074  
2075  static RPCHelpMan preciousblock()
2076  {
2077      return RPCHelpMan{"preciousblock",
2078                  "\nTreats a block as if it were received before others with the same work.\n"
2079                  "\nA later preciousblock call can override the effect of an earlier one.\n"
2080                  "\nThe effects of preciousblock are not retained across restarts.\n",
2081                  {
2082                      {"blockhash", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "the hash of the block to mark as precious"},
2083                  },
2084                  RPCResult{RPCResult::Type::NONE, "", ""},
2085                  RPCExamples{
2086                      HelpExampleCli("preciousblock", "\"blockhash\"")
2087              + HelpExampleRpc("preciousblock", "\"blockhash\"")
2088                  },
2089          [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
2090  {
2091      uint256 hash(ParseHashV(request.params[0], "blockhash"));
2092      CBlockIndex* pblockindex;
2093  
2094      ChainstateManager& chainman = EnsureAnyChainman(request.context);
2095      {
2096          LOCK(cs_main);
2097          pblockindex = chainman.m_blockman.LookupBlockIndex(hash);
2098          if (!pblockindex) {
2099              throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Block not found");
2100          }
2101      }
2102  
2103      BlockValidationState state;
2104      chainman.ActiveChainstate().PreciousBlock(state, pblockindex);
2105  
2106      if (!state.IsValid()) {
2107          throw JSONRPCError(RPC_DATABASE_ERROR, state.ToString());
2108      }
2109  
2110      return UniValue::VNULL;
2111  },
2112      };
2113  }
2114  
2115  void InvalidateBlock(ChainstateManager& chainman, const uint256 block_hash) {
2116      BlockValidationState state;
2117      CBlockIndex* pblockindex;
2118      {
2119          LOCK(chainman.GetMutex());
2120          pblockindex = chainman.m_blockman.LookupBlockIndex(block_hash);
2121          if (!pblockindex) {
2122              throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Block not found");
2123          }
2124      }
2125      chainman.ActiveChainstate().InvalidateBlock(state, pblockindex);
2126  
2127      if (state.IsValid()) {
2128          chainman.ActiveChainstate().ActivateBestChain(state);
2129      }
2130  
2131      if (!state.IsValid()) {
2132          throw JSONRPCError(RPC_DATABASE_ERROR, state.ToString());
2133      }
2134  }
2135  
2136  static RPCHelpMan invalidateblock()
2137  {
2138      return RPCHelpMan{"invalidateblock",
2139                  "\nPermanently marks a block as invalid, as if it violated a consensus rule.\n",
2140                  {
2141                      {"blockhash", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "the hash of the block to mark as invalid"},
2142                  },
2143                  RPCResult{RPCResult::Type::NONE, "", ""},
2144                  RPCExamples{
2145                      HelpExampleCli("invalidateblock", "\"blockhash\"")
2146              + HelpExampleRpc("invalidateblock", "\"blockhash\"")
2147                  },
2148          [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
2149  {
2150      ChainstateManager& chainman = EnsureAnyChainman(request.context);
2151      uint256 hash(ParseHashV(request.params[0], "blockhash"));
2152  
2153      InvalidateBlock(chainman, hash);
2154  
2155      return UniValue::VNULL;
2156  },
2157      };
2158  }
2159  
2160  void ReconsiderBlock(ChainstateManager& chainman, uint256 block_hash) {
2161      {
2162          LOCK(chainman.GetMutex());
2163          CBlockIndex* pblockindex = chainman.m_blockman.LookupBlockIndex(block_hash);
2164          if (!pblockindex) {
2165              throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Block not found");
2166          }
2167  
2168          chainman.ActiveChainstate().ResetBlockFailureFlags(pblockindex);
2169          chainman.RecalculateBestHeader();
2170      }
2171  
2172      BlockValidationState state;
2173      chainman.ActiveChainstate().ActivateBestChain(state);
2174  
2175      if (!state.IsValid()) {
2176          throw JSONRPCError(RPC_DATABASE_ERROR, state.ToString());
2177      }
2178  }
2179  
2180  static RPCHelpMan reconsiderblock()
2181  {
2182      return RPCHelpMan{"reconsiderblock",
2183                  "\nRemoves invalidity status of a block, its ancestors and its descendants, reconsider them for activation.\n"
2184                  "This can be used to undo the effects of invalidateblock.\n",
2185                  {
2186                      {"blockhash", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "the hash of the block to reconsider"},
2187                  },
2188                  RPCResult{RPCResult::Type::NONE, "", ""},
2189                  RPCExamples{
2190                      HelpExampleCli("reconsiderblock", "\"blockhash\"")
2191              + HelpExampleRpc("reconsiderblock", "\"blockhash\"")
2192                  },
2193          [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
2194  {
2195      ChainstateManager& chainman = EnsureAnyChainman(request.context);
2196      uint256 hash(ParseHashV(request.params[0], "blockhash"));
2197  
2198      ReconsiderBlock(chainman, hash);
2199  
2200      return UniValue::VNULL;
2201  },
2202      };
2203  }
2204  
2205  static RPCHelpMan getchaintxstats()
2206  {
2207      return RPCHelpMan{"getchaintxstats",
2208                  "\nCompute statistics about the total number and rate of transactions in the chain.\n",
2209                  {
2210                      {"nblocks", RPCArg::Type::NUM, RPCArg::DefaultHint{"one month"}, "Size of the window in number of blocks"},
2211                      {"blockhash", RPCArg::Type::STR_HEX, RPCArg::DefaultHint{"chain tip"}, "The hash of the block that ends the window."},
2212                  },
2213                  RPCResult{
2214                      RPCResult::Type::OBJ, "", "",
2215                      {
2216                          {RPCResult::Type::NUM_TIME, "time", "The timestamp for the final block in the window, expressed in " + UNIX_EPOCH_TIME},
2217                          {RPCResult::Type::NUM, "txcount", /*optional=*/true,
2218                           "The total number of transactions in the chain up to that point, if known. "
2219                           "It may be unknown when using assumeutxo."},
2220                          {RPCResult::Type::STR_HEX, "window_final_block_hash", "The hash of the final block in the window"},
2221                          {RPCResult::Type::NUM, "window_final_block_height", "The height of the final block in the window."},
2222                          {RPCResult::Type::NUM, "window_block_count", "Size of the window in number of blocks"},
2223                          {RPCResult::Type::NUM, "window_interval", /*optional=*/true, "The elapsed time in the window in seconds. Only returned if \"window_block_count\" is > 0"},
2224                          {RPCResult::Type::NUM, "window_tx_count", /*optional=*/true,
2225                           "The number of transactions in the window. "
2226                           "Only returned if \"window_block_count\" is > 0 and if txcount exists for the start and end of the window."},
2227                          {RPCResult::Type::NUM, "txrate", /*optional=*/true,
2228                           "The average rate of transactions per second in the window. "
2229                           "Only returned if \"window_interval\" is > 0 and if window_tx_count exists."},
2230                      }},
2231                  RPCExamples{
2232                      HelpExampleCli("getchaintxstats", "")
2233              + HelpExampleRpc("getchaintxstats", "2016")
2234                  },
2235          [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
2236  {
2237      ChainstateManager& chainman = EnsureAnyChainman(request.context);
2238      const CBlockIndex* pindex;
2239      int blockcount = 30 * 24 * 60 * 60 / chainman.GetParams().GetConsensus().nPowTargetSpacing; // By default: 1 month
2240  
2241      if (request.params[1].isNull()) {
2242          LOCK(cs_main);
2243          pindex = chainman.ActiveChain().Tip();
2244      } else {
2245          uint256 hash(ParseHashV(request.params[1], "blockhash"));
2246          LOCK(cs_main);
2247          pindex = chainman.m_blockman.LookupBlockIndex(hash);
2248          if (!pindex) {
2249              throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Block not found");
2250          }
2251          if (!chainman.ActiveChain().Contains(pindex)) {
2252              throw JSONRPCError(RPC_INVALID_PARAMETER, "Block is not in main chain");
2253          }
2254      }
2255  
2256      CHECK_NONFATAL(pindex != nullptr);
2257  
2258      if (request.params[0].isNull()) {
2259          blockcount = std::max(0, std::min(blockcount, pindex->nHeight - 1));
2260      } else {
2261          blockcount = request.params[0].getInt<int>();
2262  
2263          if (blockcount < 0 || (blockcount > 0 && blockcount >= pindex->nHeight)) {
2264              throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid block count: should be between 0 and the block's height - 1");
2265          }
2266      }
2267  
2268      const CBlockIndex& past_block{*CHECK_NONFATAL(pindex->GetAncestor(pindex->nHeight - blockcount))};
2269      const int64_t nTimeDiff{pindex->GetMedianTimePast() - past_block.GetMedianTimePast()};
2270  
2271      UniValue ret(UniValue::VOBJ);
2272      ret.pushKV("time", (int64_t)pindex->nTime);
2273      if (pindex->m_chain_tx_count) {
2274          ret.pushKV("txcount", pindex->m_chain_tx_count);
2275      }
2276      ret.pushKV("window_final_block_hash", pindex->GetBlockHash().GetHex());
2277      ret.pushKV("window_final_block_height", pindex->nHeight);
2278      ret.pushKV("window_block_count", blockcount);
2279      if (blockcount > 0) {
2280          ret.pushKV("window_interval", nTimeDiff);
2281          if (pindex->m_chain_tx_count != 0 && past_block.m_chain_tx_count != 0) {
2282              const auto window_tx_count = pindex->m_chain_tx_count - past_block.m_chain_tx_count;
2283              ret.pushKV("window_tx_count", window_tx_count);
2284              if (nTimeDiff > 0) {
2285                  ret.pushKV("txrate", double(window_tx_count) / nTimeDiff);
2286              }
2287          }
2288      }
2289  
2290      return ret;
2291  },
2292      };
2293  }
2294  
2295  template<typename T>
2296  static T CalculateTruncatedMedian(std::vector<T>& scores)
2297  {
2298      size_t size = scores.size();
2299      if (size == 0) {
2300          return 0;
2301      }
2302  
2303      std::sort(scores.begin(), scores.end());
2304      if (size % 2 == 0) {
2305          return (scores[size / 2 - 1] + scores[size / 2]) / 2;
2306      } else {
2307          return scores[size / 2];
2308      }
2309  }
2310  
2311  void CalculatePercentilesByWeight(CAmount result[NUM_GETBLOCKSTATS_PERCENTILES], std::vector<std::pair<CAmount, int64_t>>& scores, int64_t total_weight)
2312  {
2313      if (scores.empty()) {
2314          return;
2315      }
2316  
2317      std::sort(scores.begin(), scores.end());
2318  
2319      // 10th, 25th, 50th, 75th, and 90th percentile weight units.
2320      const double weights[NUM_GETBLOCKSTATS_PERCENTILES] = {
2321          total_weight / 10.0, total_weight / 4.0, total_weight / 2.0, (total_weight * 3.0) / 4.0, (total_weight * 9.0) / 10.0
2322      };
2323  
2324      int64_t next_percentile_index = 0;
2325      int64_t cumulative_weight = 0;
2326      for (const auto& element : scores) {
2327          cumulative_weight += element.second;
2328          while (next_percentile_index < NUM_GETBLOCKSTATS_PERCENTILES && cumulative_weight >= weights[next_percentile_index]) {
2329              result[next_percentile_index] = element.first;
2330              ++next_percentile_index;
2331          }
2332      }
2333  
2334      // Fill any remaining percentiles with the last value.
2335      for (int64_t i = next_percentile_index; i < NUM_GETBLOCKSTATS_PERCENTILES; i++) {
2336          result[i] = scores.back().first;
2337      }
2338  }
2339  
2340  template<typename T>
2341  static inline bool SetHasKeys(const std::set<T>& set) {return false;}
2342  template<typename T, typename Tk, typename... Args>
2343  static inline bool SetHasKeys(const std::set<T>& set, const Tk& key, const Args&... args)
2344  {
2345      return (set.count(key) != 0) || SetHasKeys(set, args...);
2346  }
2347  
2348  // outpoint (needed for the utxo index) + nHeight + fCoinBase
2349  static constexpr size_t PER_UTXO_OVERHEAD = sizeof(COutPoint) + sizeof(uint32_t) + sizeof(bool);
2350  
2351  static RPCHelpMan getblockstats()
2352  {
2353      return RPCHelpMan{"getblockstats",
2354                  "\nCompute per block statistics for a given window. All amounts are in satoshis.\n"
2355                  "It won't work for some heights with pruning.\n",
2356                  {
2357                      {"hash_or_height", RPCArg::Type::NUM, RPCArg::Optional::NO, "The block hash or height of the target block",
2358                       RPCArgOptions{
2359                           .skip_type_check = true,
2360                           .type_str = {"", "string or numeric"},
2361                       }},
2362                      {"stats", RPCArg::Type::ARR, RPCArg::DefaultHint{"all values"}, "Values to plot (see result below)",
2363                          {
2364                              {"height", RPCArg::Type::STR, RPCArg::Optional::OMITTED, "Selected statistic"},
2365                              {"time", RPCArg::Type::STR, RPCArg::Optional::OMITTED, "Selected statistic"},
2366                          },
2367                          RPCArgOptions{.oneline_description="stats"}},
2368                  },
2369                  RPCResult{
2370              RPCResult::Type::OBJ, "", "",
2371              {
2372                  {RPCResult::Type::NUM, "avgfee", /*optional=*/true, "Average fee in the block"},
2373                  {RPCResult::Type::NUM, "avgfeerate", /*optional=*/true, "Average feerate (in satoshis per virtual byte)"},
2374                  {RPCResult::Type::NUM, "avgtxsize", /*optional=*/true, "Average transaction size"},
2375                  {RPCResult::Type::STR_HEX, "blockhash", /*optional=*/true, "The block hash (to check for potential reorgs)"},
2376                  {RPCResult::Type::ARR_FIXED, "feerate_percentiles", /*optional=*/true, "Feerates at the 10th, 25th, 50th, 75th, and 90th percentile weight unit (in satoshis per virtual byte)",
2377                  {
2378                      {RPCResult::Type::NUM, "10th_percentile_feerate", "The 10th percentile feerate"},
2379                      {RPCResult::Type::NUM, "25th_percentile_feerate", "The 25th percentile feerate"},
2380                      {RPCResult::Type::NUM, "50th_percentile_feerate", "The 50th percentile feerate"},
2381                      {RPCResult::Type::NUM, "75th_percentile_feerate", "The 75th percentile feerate"},
2382                      {RPCResult::Type::NUM, "90th_percentile_feerate", "The 90th percentile feerate"},
2383                  }},
2384                  {RPCResult::Type::NUM, "height", /*optional=*/true, "The height of the block"},
2385                  {RPCResult::Type::NUM, "ins", /*optional=*/true, "The number of inputs (excluding coinbase)"},
2386                  {RPCResult::Type::NUM, "maxfee", /*optional=*/true, "Maximum fee in the block"},
2387                  {RPCResult::Type::NUM, "maxfeerate", /*optional=*/true, "Maximum feerate (in satoshis per virtual byte)"},
2388                  {RPCResult::Type::NUM, "maxtxsize", /*optional=*/true, "Maximum transaction size"},
2389                  {RPCResult::Type::NUM, "medianfee", /*optional=*/true, "Truncated median fee in the block"},
2390                  {RPCResult::Type::NUM, "mediantime", /*optional=*/true, "The block median time past"},
2391                  {RPCResult::Type::NUM, "mediantxsize", /*optional=*/true, "Truncated median transaction size"},
2392                  {RPCResult::Type::NUM, "minfee", /*optional=*/true, "Minimum fee in the block"},
2393                  {RPCResult::Type::NUM, "minfeerate", /*optional=*/true, "Minimum feerate (in satoshis per virtual byte)"},
2394                  {RPCResult::Type::NUM, "mintxsize", /*optional=*/true, "Minimum transaction size"},
2395                  {RPCResult::Type::NUM, "outs", /*optional=*/true, "The number of outputs"},
2396                  {RPCResult::Type::NUM, "subsidy", /*optional=*/true, "The block subsidy"},
2397                  {RPCResult::Type::NUM, "swtotal_size", /*optional=*/true, "Total size of all segwit transactions"},
2398                  {RPCResult::Type::NUM, "swtotal_weight", /*optional=*/true, "Total weight of all segwit transactions"},
2399                  {RPCResult::Type::NUM, "swtxs", /*optional=*/true, "The number of segwit transactions"},
2400                  {RPCResult::Type::NUM, "time", /*optional=*/true, "The block time"},
2401                  {RPCResult::Type::NUM, "total_out", /*optional=*/true, "Total amount in all outputs (excluding coinbase and thus reward [ie subsidy + totalfee])"},
2402                  {RPCResult::Type::NUM, "total_size", /*optional=*/true, "Total size of all non-coinbase transactions"},
2403                  {RPCResult::Type::NUM, "total_weight", /*optional=*/true, "Total weight of all non-coinbase transactions"},
2404                  {RPCResult::Type::NUM, "totalfee", /*optional=*/true, "The fee total"},
2405                  {RPCResult::Type::NUM, "txs", /*optional=*/true, "The number of transactions (including coinbase)"},
2406                  {RPCResult::Type::NUM, "utxo_increase", /*optional=*/true, "The increase/decrease in the number of unspent outputs (not discounting op_return and similar)"},
2407                  {RPCResult::Type::NUM, "utxo_size_inc", /*optional=*/true, "The increase/decrease in size for the utxo index (not discounting op_return and similar)"},
2408                  {RPCResult::Type::NUM, "utxo_increase_actual", /*optional=*/true, "The increase/decrease in the number of unspent outputs, not counting unspendables"},
2409                  {RPCResult::Type::NUM, "utxo_size_inc_actual", /*optional=*/true, "The increase/decrease in size for the utxo index, not counting unspendables"},
2410              }},
2411                  RPCExamples{
2412                      HelpExampleCli("getblockstats", R"('"00000000c937983704a73af28acdec37b049d214adbda81d7e2a3dd146f6ed09"' '["minfeerate","avgfeerate"]')") +
2413                      HelpExampleCli("getblockstats", R"(1000 '["minfeerate","avgfeerate"]')") +
2414                      HelpExampleRpc("getblockstats", R"("00000000c937983704a73af28acdec37b049d214adbda81d7e2a3dd146f6ed09", ["minfeerate","avgfeerate"])") +
2415                      HelpExampleRpc("getblockstats", R"(1000, ["minfeerate","avgfeerate"])")
2416                  },
2417          [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
2418  {
2419      ChainstateManager& chainman = EnsureAnyChainman(request.context);
2420      const CBlockIndex& pindex{*CHECK_NONFATAL(ParseHashOrHeight(request.params[0], chainman))};
2421  
2422      std::set<std::string> stats;
2423      if (!request.params[1].isNull()) {
2424          const UniValue stats_univalue = request.params[1].get_array();
2425          for (unsigned int i = 0; i < stats_univalue.size(); i++) {
2426              const std::string stat = stats_univalue[i].get_str();
2427              stats.insert(stat);
2428          }
2429      }
2430  
2431      const CBlock& block = GetBlockChecked(chainman.m_blockman, pindex);
2432      const CBlockUndo& blockUndo = GetUndoChecked(chainman.m_blockman, pindex);
2433  
2434      const bool do_all = stats.size() == 0; // Calculate everything if nothing selected (default)
2435      const bool do_mediantxsize = do_all || stats.count("mediantxsize") != 0;
2436      const bool do_medianfee = do_all || stats.count("medianfee") != 0;
2437      const bool do_feerate_percentiles = do_all || stats.count("feerate_percentiles") != 0;
2438      const bool loop_inputs = do_all || do_medianfee || do_feerate_percentiles ||
2439          SetHasKeys(stats, "utxo_increase", "utxo_increase_actual", "utxo_size_inc", "utxo_size_inc_actual", "totalfee", "avgfee", "avgfeerate", "minfee", "maxfee", "minfeerate", "maxfeerate");
2440      const bool loop_outputs = do_all || loop_inputs || stats.count("total_out");
2441      const bool do_calculate_size = do_mediantxsize ||
2442          SetHasKeys(stats, "total_size", "avgtxsize", "mintxsize", "maxtxsize", "swtotal_size");
2443      const bool do_calculate_weight = do_all || SetHasKeys(stats, "total_weight", "avgfeerate", "swtotal_weight", "avgfeerate", "feerate_percentiles", "minfeerate", "maxfeerate");
2444      const bool do_calculate_sw = do_all || SetHasKeys(stats, "swtxs", "swtotal_size", "swtotal_weight");
2445  
2446      CAmount maxfee = 0;
2447      CAmount maxfeerate = 0;
2448      CAmount minfee = MAX_MONEY;
2449      CAmount minfeerate = MAX_MONEY;
2450      CAmount total_out = 0;
2451      CAmount totalfee = 0;
2452      int64_t inputs = 0;
2453      int64_t maxtxsize = 0;
2454      int64_t mintxsize = MAX_BLOCK_SERIALIZED_SIZE;
2455      int64_t outputs = 0;
2456      int64_t swtotal_size = 0;
2457      int64_t swtotal_weight = 0;
2458      int64_t swtxs = 0;
2459      int64_t total_size = 0;
2460      int64_t total_weight = 0;
2461      int64_t utxos = 0;
2462      int64_t utxo_size_inc = 0;
2463      int64_t utxo_size_inc_actual = 0;
2464      std::vector<CAmount> fee_array;
2465      std::vector<std::pair<CAmount, int64_t>> feerate_array;
2466      std::vector<int64_t> txsize_array;
2467  
2468      for (size_t i = 0; i < block.vtx.size(); ++i) {
2469          const auto& tx = block.vtx.at(i);
2470          outputs += tx->vout.size();
2471  
2472          CAmount tx_total_out = 0;
2473          if (loop_outputs) {
2474              for (const CTxOut& out : tx->vout) {
2475                  tx_total_out += out.nValue;
2476  
2477                  size_t out_size = GetSerializeSize(out) + PER_UTXO_OVERHEAD;
2478                  utxo_size_inc += out_size;
2479  
2480                  // The Genesis block and the repeated BIP30 block coinbases don't change the UTXO
2481                  // set counts, so they have to be excluded from the statistics
2482                  if (pindex.nHeight == 0 || (IsBIP30Repeat(pindex) && tx->IsCoinBase())) continue;
2483                  // Skip unspendable outputs since they are not included in the UTXO set
2484                  if (out.scriptPubKey.IsUnspendable()) continue;
2485  
2486                  ++utxos;
2487                  utxo_size_inc_actual += out_size;
2488              }
2489          }
2490  
2491          if (tx->IsCoinBase()) {
2492              continue;
2493          }
2494  
2495          inputs += tx->vin.size(); // Don't count coinbase's fake input
2496          total_out += tx_total_out; // Don't count coinbase reward
2497  
2498          int64_t tx_size = 0;
2499          if (do_calculate_size) {
2500  
2501              tx_size = tx->GetTotalSize();
2502              if (do_mediantxsize) {
2503                  txsize_array.push_back(tx_size);
2504              }
2505              maxtxsize = std::max(maxtxsize, tx_size);
2506              mintxsize = std::min(mintxsize, tx_size);
2507              total_size += tx_size;
2508          }
2509  
2510          int64_t weight = 0;
2511          if (do_calculate_weight) {
2512              weight = GetTransactionWeight(*tx);
2513              total_weight += weight;
2514          }
2515  
2516          if (do_calculate_sw && tx->HasWitness()) {
2517              ++swtxs;
2518              swtotal_size += tx_size;
2519              swtotal_weight += weight;
2520          }
2521  
2522          if (loop_inputs) {
2523              CAmount tx_total_in = 0;
2524              const auto& txundo = blockUndo.vtxundo.at(i - 1);
2525              for (const Coin& coin: txundo.vprevout) {
2526                  const CTxOut& prevoutput = coin.out;
2527  
2528                  tx_total_in += prevoutput.nValue;
2529                  size_t prevout_size = GetSerializeSize(prevoutput) + PER_UTXO_OVERHEAD;
2530                  utxo_size_inc -= prevout_size;
2531                  utxo_size_inc_actual -= prevout_size;
2532              }
2533  
2534              CAmount txfee = tx_total_in - tx_total_out;
2535              CHECK_NONFATAL(MoneyRange(txfee));
2536              if (do_medianfee) {
2537                  fee_array.push_back(txfee);
2538              }
2539              maxfee = std::max(maxfee, txfee);
2540              minfee = std::min(minfee, txfee);
2541              totalfee += txfee;
2542  
2543              // New feerate uses satoshis per virtual byte instead of per serialized byte
2544              CAmount feerate = weight ? (txfee * WITNESS_SCALE_FACTOR) / weight : 0;
2545              if (do_feerate_percentiles) {
2546                  feerate_array.emplace_back(feerate, weight);
2547              }
2548              maxfeerate = std::max(maxfeerate, feerate);
2549              minfeerate = std::min(minfeerate, feerate);
2550          }
2551      }
2552  
2553      CAmount feerate_percentiles[NUM_GETBLOCKSTATS_PERCENTILES] = { 0 };
2554      CalculatePercentilesByWeight(feerate_percentiles, feerate_array, total_weight);
2555  
2556      UniValue feerates_res(UniValue::VARR);
2557      for (int64_t i = 0; i < NUM_GETBLOCKSTATS_PERCENTILES; i++) {
2558          feerates_res.push_back(feerate_percentiles[i]);
2559      }
2560  
2561      UniValue ret_all(UniValue::VOBJ);
2562      ret_all.pushKV("avgfee", (block.vtx.size() > 1) ? totalfee / (block.vtx.size() - 1) : 0);
2563      ret_all.pushKV("avgfeerate", total_weight ? (totalfee * WITNESS_SCALE_FACTOR) / total_weight : 0); // Unit: sat/vbyte
2564      ret_all.pushKV("avgtxsize", (block.vtx.size() > 1) ? total_size / (block.vtx.size() - 1) : 0);
2565      ret_all.pushKV("blockhash", pindex.GetBlockHash().GetHex());
2566      ret_all.pushKV("feerate_percentiles", std::move(feerates_res));
2567      ret_all.pushKV("height", (int64_t)pindex.nHeight);
2568      ret_all.pushKV("ins", inputs);
2569      ret_all.pushKV("maxfee", maxfee);
2570      ret_all.pushKV("maxfeerate", maxfeerate);
2571      ret_all.pushKV("maxtxsize", maxtxsize);
2572      ret_all.pushKV("medianfee", CalculateTruncatedMedian(fee_array));
2573      ret_all.pushKV("mediantime", pindex.GetMedianTimePast());
2574      ret_all.pushKV("mediantxsize", CalculateTruncatedMedian(txsize_array));
2575      ret_all.pushKV("minfee", (minfee == MAX_MONEY) ? 0 : minfee);
2576      ret_all.pushKV("minfeerate", (minfeerate == MAX_MONEY) ? 0 : minfeerate);
2577      ret_all.pushKV("mintxsize", mintxsize == MAX_BLOCK_SERIALIZED_SIZE ? 0 : mintxsize);
2578      ret_all.pushKV("outs", outputs);
2579      ret_all.pushKV("subsidy", GetBlockSubsidy(pindex.nHeight, chainman.GetParams().GetConsensus()));
2580      ret_all.pushKV("swtotal_size", swtotal_size);
2581      ret_all.pushKV("swtotal_weight", swtotal_weight);
2582      ret_all.pushKV("swtxs", swtxs);
2583      ret_all.pushKV("time", pindex.GetBlockTime());
2584      ret_all.pushKV("total_out", total_out);
2585      ret_all.pushKV("total_size", total_size);
2586      ret_all.pushKV("total_weight", total_weight);
2587      ret_all.pushKV("totalfee", totalfee);
2588      ret_all.pushKV("txs", (int64_t)block.vtx.size());
2589      ret_all.pushKV("utxo_increase", outputs - inputs);
2590      ret_all.pushKV("utxo_size_inc", utxo_size_inc);
2591      ret_all.pushKV("utxo_increase_actual", utxos - inputs);
2592      ret_all.pushKV("utxo_size_inc_actual", utxo_size_inc_actual);
2593  
2594      if (do_all) {
2595          return ret_all;
2596      }
2597  
2598      UniValue ret(UniValue::VOBJ);
2599      for (const std::string& stat : stats) {
2600          const UniValue& value = ret_all[stat];
2601          if (value.isNull()) {
2602              throw JSONRPCError(RPC_INVALID_PARAMETER, strprintf("Invalid selected statistic '%s'", stat));
2603          }
2604          ret.pushKV(stat, value);
2605      }
2606      return ret;
2607  },
2608      };
2609  }
2610  
2611  //! Search for a given set of pubkey scripts
2612  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)
2613  {
2614      scan_progress = 0;
2615      count = 0;
2616      while (cursor->Valid()) {
2617          COutPoint key;
2618          Coin coin;
2619          if (!cursor->GetKey(key) || !cursor->GetValue(coin)) return false;
2620          if (++count % 8192 == 0) {
2621              interruption_point();
2622              if (should_abort) {
2623                  // allow to abort the scan via the abort reference
2624                  return false;
2625              }
2626          }
2627          if (count % 256 == 0) {
2628              // update progress reference every 256 item
2629              uint32_t high = 0x100 * *UCharCast(key.hash.begin()) + *(UCharCast(key.hash.begin()) + 1);
2630              scan_progress = (int)(high * 100.0 / 65536.0 + 0.5);
2631          }
2632          if (needles.count(coin.out.scriptPubKey)) {
2633              out_results.emplace(key, coin);
2634          }
2635          cursor->Next();
2636      }
2637      scan_progress = 100;
2638      return true;
2639  }
2640  
2641  /** RAII object to prevent concurrency issue when scanning the txout set */
2642  static std::atomic<int> g_scan_progress;
2643  static std::atomic<bool> g_scan_in_progress;
2644  static std::atomic<bool> g_should_abort_scan;
2645  class CoinsViewScanReserver
2646  {
2647  private:
2648      bool m_could_reserve{false};
2649  public:
2650      explicit CoinsViewScanReserver() = default;
2651  
2652      bool reserve() {
2653          CHECK_NONFATAL(!m_could_reserve);
2654          if (g_scan_in_progress.exchange(true)) {
2655              return false;
2656          }
2657          CHECK_NONFATAL(g_scan_progress == 0);
2658          m_could_reserve = true;
2659          return true;
2660      }
2661  
2662      ~CoinsViewScanReserver() {
2663          if (m_could_reserve) {
2664              g_scan_in_progress = false;
2665              g_scan_progress = 0;
2666          }
2667      }
2668  };
2669  
2670  static const auto scan_action_arg_desc = RPCArg{
2671      "action", RPCArg::Type::STR, RPCArg::Optional::NO, "The action to execute\n"
2672          "\"start\" for starting a scan\n"
2673          "\"abort\" for aborting the current scan (returns true when abort was successful)\n"
2674          "\"status\" for progress report (in %) of the current scan"
2675  };
2676  
2677  static const auto output_descriptor_obj = RPCArg{
2678      "", RPCArg::Type::OBJ, RPCArg::Optional::OMITTED, "An object with output descriptor and metadata",
2679      {
2680          {"desc", RPCArg::Type::STR, RPCArg::Optional::NO, "An output descriptor"},
2681          {"range", RPCArg::Type::RANGE, RPCArg::Default{1000}, "The range of HD chain indexes to explore (either end or [begin,end])"},
2682      }
2683  };
2684  
2685  static const auto scan_objects_arg_desc = RPCArg{
2686      "scanobjects", RPCArg::Type::ARR, RPCArg::Optional::OMITTED, "Array of scan objects. Required for \"start\" action\n"
2687          "Every scan object is either a string descriptor or an object:",
2688      {
2689          {"descriptor", RPCArg::Type::STR, RPCArg::Optional::OMITTED, "An output descriptor"},
2690          output_descriptor_obj,
2691      },
2692      RPCArgOptions{.oneline_description="[scanobjects,...]"},
2693  };
2694  
2695  static const auto scan_result_abort = RPCResult{
2696      "when action=='abort'", RPCResult::Type::BOOL, "success",
2697      "True if scan will be aborted (not necessarily before this RPC returns), or false if there is no scan to abort"
2698  };
2699  static const auto scan_result_status_none = RPCResult{
2700      "when action=='status' and no scan is in progress - possibly already completed", RPCResult::Type::NONE, "", ""
2701  };
2702  static const auto scan_result_status_some = RPCResult{
2703      "when action=='status' and a scan is currently in progress", RPCResult::Type::OBJ, "", "",
2704      {{RPCResult::Type::NUM, "progress", "Approximate percent complete"},}
2705  };
2706  
2707  
2708  static RPCHelpMan scantxoutset()
2709  {
2710      // raw() descriptor corresponding to mainnet address 12cbQLTFMXRnSzktFkuoG3eHoMeFtpTu3S
2711      const std::string EXAMPLE_DESCRIPTOR_RAW = "raw(76a91411b366edfc0a8b66feebae5c2e25a7b6a5d1cf3188ac)#fm24fxxy";
2712  
2713      return RPCHelpMan{"scantxoutset",
2714          "\nScans the unspent transaction output set for entries that match certain output descriptors.\n"
2715          "Examples of output descriptors are:\n"
2716          "    addr(<address>)                      Outputs whose output script corresponds to the specified address (does not include P2PK)\n"
2717          "    raw(<hex script>)                    Outputs whose output script equals the specified hex-encoded bytes\n"
2718          "    combo(<pubkey>)                      P2PK, P2PKH, P2WPKH, and P2SH-P2WPKH outputs for the given pubkey\n"
2719          "    pkh(<pubkey>)                        P2PKH outputs for the given pubkey\n"
2720          "    sh(multi(<n>,<pubkey>,<pubkey>,...)) P2SH-multisig outputs for the given threshold and pubkeys\n"
2721          "    tr(<pubkey>)                         P2TR\n"
2722          "    tr(<pubkey>,{pk(<pubkey>)})          P2TR with single fallback pubkey in tapscript\n"
2723          "    rawtr(<pubkey>)                      P2TR with the specified key as output key rather than inner\n"
2724          "    wsh(and_v(v:pk(<pubkey>),after(2)))  P2WSH miniscript with mandatory pubkey and a timelock\n"
2725          "\nIn the above, <pubkey> either refers to a fixed public key in hexadecimal notation, or to an xpub/xprv optionally followed by one\n"
2726          "or more path elements separated by \"/\", and optionally ending in \"/*\" (unhardened), or \"/*'\" or \"/*h\" (hardened) to specify all\n"
2727          "unhardened or hardened child keys.\n"
2728          "In the latter case, a range needs to be specified by below if different from 1000.\n"
2729          "For more information on output descriptors, see the documentation in the doc/descriptors.md file.\n",
2730          {
2731              scan_action_arg_desc,
2732              scan_objects_arg_desc,
2733          },
2734          {
2735              RPCResult{"when action=='start'; only returns after scan completes", RPCResult::Type::OBJ, "", "", {
2736                  {RPCResult::Type::BOOL, "success", "Whether the scan was completed"},
2737                  {RPCResult::Type::NUM, "txouts", "The number of unspent transaction outputs scanned"},
2738                  {RPCResult::Type::NUM, "height", "The block height at which the scan was done"},
2739                  {RPCResult::Type::STR_HEX, "bestblock", "The hash of the block at the tip of the chain"},
2740                  {RPCResult::Type::ARR, "unspents", "",
2741                  {
2742                      {RPCResult::Type::OBJ, "", "",
2743                      {
2744                          {RPCResult::Type::STR_HEX, "txid", "The transaction id"},
2745                          {RPCResult::Type::NUM, "vout", "The vout value"},
2746                          {RPCResult::Type::STR_HEX, "scriptPubKey", "The output script"},
2747                          {RPCResult::Type::STR, "desc", "A specialized descriptor for the matched output script"},
2748                          {RPCResult::Type::STR_AMOUNT, "amount", "The total amount in " + CURRENCY_UNIT + " of the unspent output"},
2749                          {RPCResult::Type::BOOL, "coinbase", "Whether this is a coinbase output"},
2750                          {RPCResult::Type::NUM, "height", "Height of the unspent transaction output"},
2751                          {RPCResult::Type::STR_HEX, "blockhash", "Blockhash of the unspent transaction output"},
2752                          {RPCResult::Type::NUM, "confirmations", "Number of confirmations of the unspent transaction output when the scan was done"},
2753                      }},
2754                  }},
2755                  {RPCResult::Type::STR_AMOUNT, "total_amount", "The total amount of all found unspent outputs in " + CURRENCY_UNIT},
2756              }},
2757              scan_result_abort,
2758              scan_result_status_some,
2759              scan_result_status_none,
2760          },
2761          RPCExamples{
2762              HelpExampleCli("scantxoutset", "start \'[\"" + EXAMPLE_DESCRIPTOR_RAW + "\"]\'") +
2763              HelpExampleCli("scantxoutset", "status") +
2764              HelpExampleCli("scantxoutset", "abort") +
2765              HelpExampleRpc("scantxoutset", "\"start\", [\"" + EXAMPLE_DESCRIPTOR_RAW + "\"]") +
2766              HelpExampleRpc("scantxoutset", "\"status\"") +
2767              HelpExampleRpc("scantxoutset", "\"abort\"")
2768          },
2769          [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
2770  {
2771      UniValue result(UniValue::VOBJ);
2772      const auto action{self.Arg<std::string>("action")};
2773      if (action == "status") {
2774          CoinsViewScanReserver reserver;
2775          if (reserver.reserve()) {
2776              // no scan in progress
2777              return UniValue::VNULL;
2778          }
2779          result.pushKV("progress", g_scan_progress.load());
2780          return result;
2781      } else if (action == "abort") {
2782          CoinsViewScanReserver reserver;
2783          if (reserver.reserve()) {
2784              // reserve was possible which means no scan was running
2785              return false;
2786          }
2787          // set the abort flag
2788          g_should_abort_scan = true;
2789          return true;
2790      } else if (action == "start") {
2791          CoinsViewScanReserver reserver;
2792          if (!reserver.reserve()) {
2793              throw JSONRPCError(RPC_INVALID_PARAMETER, "Scan already in progress, use action \"abort\" or \"status\"");
2794          }
2795  
2796          if (request.params.size() < 2) {
2797              throw JSONRPCError(RPC_MISC_ERROR, "scanobjects argument is required for the start action");
2798          }
2799  
2800          std::set<CScript> needles;
2801          std::map<CScript, std::string> descriptors;
2802          CAmount total_in = 0;
2803  
2804          // loop through the scan objects
2805          for (const UniValue& scanobject : request.params[1].get_array().getValues()) {
2806              FlatSigningProvider provider;
2807              auto scripts = EvalDescriptorStringOrObject(scanobject, provider);
2808              for (CScript& script : scripts) {
2809                  std::string inferred = InferDescriptor(script, provider)->ToString();
2810                  needles.emplace(script);
2811                  descriptors.emplace(std::move(script), std::move(inferred));
2812              }
2813          }
2814  
2815          // Scan the unspent transaction output set for inputs
2816          UniValue unspents(UniValue::VARR);
2817          std::vector<CTxOut> input_txos;
2818          std::map<COutPoint, Coin> coins;
2819          g_should_abort_scan = false;
2820          int64_t count = 0;
2821          std::unique_ptr<CCoinsViewCursor> pcursor;
2822          const CBlockIndex* tip;
2823          NodeContext& node = EnsureAnyNodeContext(request.context);
2824          {
2825              ChainstateManager& chainman = EnsureChainman(node);
2826              LOCK(cs_main);
2827              Chainstate& active_chainstate = chainman.ActiveChainstate();
2828              active_chainstate.ForceFlushStateToDisk();
2829              pcursor = CHECK_NONFATAL(active_chainstate.CoinsDB().Cursor());
2830              tip = CHECK_NONFATAL(active_chainstate.m_chain.Tip());
2831          }
2832          bool res = FindScriptPubKey(g_scan_progress, g_should_abort_scan, count, pcursor.get(), needles, coins, node.rpc_interruption_point);
2833          result.pushKV("success", res);
2834          result.pushKV("txouts", count);
2835          result.pushKV("height", tip->nHeight);
2836          result.pushKV("bestblock", tip->GetBlockHash().GetHex());
2837  
2838          for (const auto& it : coins) {
2839              const COutPoint& outpoint = it.first;
2840              const Coin& coin = it.second;
2841              const CTxOut& txo = coin.out;
2842              const CBlockIndex& coinb_block{*CHECK_NONFATAL(tip->GetAncestor(coin.nHeight))};
2843              input_txos.push_back(txo);
2844              total_in += txo.nValue;
2845  
2846              UniValue unspent(UniValue::VOBJ);
2847              unspent.pushKV("txid", outpoint.hash.GetHex());
2848              unspent.pushKV("vout", outpoint.n);
2849              unspent.pushKV("scriptPubKey", HexStr(txo.scriptPubKey));
2850              unspent.pushKV("desc", descriptors[txo.scriptPubKey]);
2851              unspent.pushKV("amount", ValueFromAmount(txo.nValue));
2852              unspent.pushKV("coinbase", coin.IsCoinBase());
2853              unspent.pushKV("height", coin.nHeight);
2854              unspent.pushKV("blockhash", coinb_block.GetBlockHash().GetHex());
2855              unspent.pushKV("confirmations", tip->nHeight - coin.nHeight + 1);
2856  
2857              unspents.push_back(std::move(unspent));
2858          }
2859          result.pushKV("unspents", std::move(unspents));
2860          result.pushKV("total_amount", ValueFromAmount(total_in));
2861      } else {
2862          throw JSONRPCError(RPC_INVALID_PARAMETER, strprintf("Invalid action '%s'", action));
2863      }
2864      return result;
2865  },
2866      };
2867  }
2868  
2869  /** RAII object to prevent concurrency issue when scanning blockfilters */
2870  static std::atomic<int> g_scanfilter_progress;
2871  static std::atomic<int> g_scanfilter_progress_height;
2872  static std::atomic<bool> g_scanfilter_in_progress;
2873  static std::atomic<bool> g_scanfilter_should_abort_scan;
2874  class BlockFiltersScanReserver
2875  {
2876  private:
2877      bool m_could_reserve{false};
2878  public:
2879      explicit BlockFiltersScanReserver() = default;
2880  
2881      bool reserve() {
2882          CHECK_NONFATAL(!m_could_reserve);
2883          if (g_scanfilter_in_progress.exchange(true)) {
2884              return false;
2885          }
2886          m_could_reserve = true;
2887          return true;
2888      }
2889  
2890      void release() {
2891          if (!m_could_reserve) {
2892              throw std::runtime_error("Attempt to release unreserved BlockFiltersScanReserver");
2893          }
2894          g_scanfilter_in_progress = false;
2895          m_could_reserve = false;
2896      }
2897  
2898      ~BlockFiltersScanReserver() {
2899          if (m_could_reserve) {
2900              release();
2901          }
2902      }
2903  };
2904  
2905  static bool CheckBlockFilterMatches(BlockManager& blockman, const CBlockIndex& blockindex, const GCSFilter::ElementSet& needles)
2906  {
2907      const CBlock block{GetBlockChecked(blockman, blockindex)};
2908      const CBlockUndo block_undo{GetUndoChecked(blockman, blockindex)};
2909  
2910      // Check if any of the outputs match the scriptPubKey
2911      for (const auto& tx : block.vtx) {
2912          if (std::any_of(tx->vout.cbegin(), tx->vout.cend(), [&](const auto& txout) {
2913                  return needles.count(std::vector<unsigned char>(txout.scriptPubKey.begin(), txout.scriptPubKey.end())) != 0;
2914              })) {
2915              return true;
2916          }
2917      }
2918      // Check if any of the inputs match the scriptPubKey
2919      for (const auto& txundo : block_undo.vtxundo) {
2920          if (std::any_of(txundo.vprevout.cbegin(), txundo.vprevout.cend(), [&](const auto& coin) {
2921                  return needles.count(std::vector<unsigned char>(coin.out.scriptPubKey.begin(), coin.out.scriptPubKey.end())) != 0;
2922              })) {
2923              return true;
2924          }
2925      }
2926  
2927      return false;
2928  }
2929  
2930  static RPCHelpMan scanblocks()
2931  {
2932      return RPCHelpMan{"scanblocks",
2933          "\nReturn relevant blockhashes for given descriptors (requires blockfilterindex).\n"
2934          "This call may take several minutes. Make sure to use no RPC timeout (limenka-cli -rpcclienttimeout=0)",
2935          {
2936              scan_action_arg_desc,
2937              scan_objects_arg_desc,
2938              RPCArg{"start_height", RPCArg::Type::NUM, RPCArg::Default{0}, "Height to start to scan from"},
2939              RPCArg{"stop_height", RPCArg::Type::NUM, RPCArg::DefaultHint{"chain tip"}, "Height to stop to scan"},
2940              RPCArg{"filtertype", RPCArg::Type::STR, RPCArg::Default{BlockFilterTypeName(BlockFilterType::BASIC)}, "The type name of the filter"},
2941              RPCArg{"options", RPCArg::Type::OBJ_NAMED_PARAMS, RPCArg::Optional::OMITTED, "",
2942                  {
2943                      {"filter_false_positives", RPCArg::Type::BOOL, RPCArg::Default{false}, "Filter false positives (slower and may fail on pruned nodes). Otherwise they may occur at a rate of 1/M"},
2944                  },
2945                  RPCArgOptions{.oneline_description="options"}},
2946          },
2947          {
2948              scan_result_status_none,
2949              RPCResult{"When action=='start'; only returns after scan completes", RPCResult::Type::OBJ, "", "", {
2950                  {RPCResult::Type::NUM, "from_height", "The height we started the scan from"},
2951                  {RPCResult::Type::NUM, "to_height", "The height we ended the scan at"},
2952                  {RPCResult::Type::ARR, "relevant_blocks", "Blocks that may have matched a scanobject.", {
2953                      {RPCResult::Type::STR_HEX, "blockhash", "A relevant blockhash"},
2954                  }},
2955                  {RPCResult::Type::BOOL, "completed", "true if the scan process was not aborted"}
2956              }},
2957              RPCResult{"when action=='status' and a scan is currently in progress", RPCResult::Type::OBJ, "", "", {
2958                      {RPCResult::Type::NUM, "progress", "Approximate percent complete"},
2959                      {RPCResult::Type::NUM, "current_height", "Height of the block currently being scanned"},
2960                      {RPCResult::Type::ARR, "relevant_blocks", "Blocks that may have matched a scanobject.", {
2961                          {RPCResult::Type::STR_HEX, "blockhash", "A relevant blockhash"},
2962                      }},
2963                  },
2964              },
2965              scan_result_abort,
2966          },
2967          RPCExamples{
2968              HelpExampleCli("scanblocks", "start '[\"addr(bcrt1q4u4nsgk6ug0sqz7r3rj9tykjxrsl0yy4d0wwte)\"]' 300000") +
2969              HelpExampleCli("scanblocks", "start '[\"addr(bcrt1q4u4nsgk6ug0sqz7r3rj9tykjxrsl0yy4d0wwte)\"]' 100 150 basic") +
2970              HelpExampleCli("scanblocks", "status") +
2971              HelpExampleRpc("scanblocks", "\"start\", [\"addr(bcrt1q4u4nsgk6ug0sqz7r3rj9tykjxrsl0yy4d0wwte)\"], 300000") +
2972              HelpExampleRpc("scanblocks", "\"start\", [\"addr(bcrt1q4u4nsgk6ug0sqz7r3rj9tykjxrsl0yy4d0wwte)\"], 100, 150, \"basic\"") +
2973              HelpExampleRpc("scanblocks", "\"status\"")
2974          },
2975          [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
2976  {
2977      static GlobalMutex cs_relevant_blocks;
2978      static UniValue relevant_blocks GUARDED_BY(cs_relevant_blocks);
2979  
2980      UniValue ret(UniValue::VOBJ);
2981      if (request.params[0].get_str() == "status") {
2982          BlockFiltersScanReserver reserver;
2983          LOCK(cs_relevant_blocks);
2984          if (reserver.reserve()) {
2985              // no scan in progress
2986              return NullUniValue;
2987          }
2988          ret.pushKV("progress", g_scanfilter_progress.load());
2989          ret.pushKV("current_height", g_scanfilter_progress_height.load());
2990          ret.pushKV("relevant_blocks", relevant_blocks);
2991          return ret;
2992      } else if (request.params[0].get_str() == "abort") {
2993          BlockFiltersScanReserver reserver;
2994          if (reserver.reserve()) {
2995              // reserve was possible which means no scan was running
2996              return false;
2997          }
2998          // set the abort flag
2999          g_scanfilter_should_abort_scan = true;
3000          return true;
3001      } else if (request.params[0].get_str() == "start") {
3002          {
3003              LOCK(cs_relevant_blocks);
3004              relevant_blocks = UniValue(UniValue::VARR);
3005          }
3006  
3007          BlockFiltersScanReserver reserver;
3008          if (!reserver.reserve()) {
3009              throw JSONRPCError(RPC_INVALID_PARAMETER, "Scan already in progress, use action \"abort\" or \"status\"");
3010          }
3011          const std::string filtertype_name{request.params[4].isNull() ? "basic" : request.params[4].get_str()};
3012  
3013          BlockFilterType filtertype;
3014          if (!BlockFilterTypeByName(filtertype_name, filtertype)) {
3015              throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Unknown filtertype");
3016          }
3017  
3018          UniValue options{request.params[5].isNull() ? UniValue::VOBJ : request.params[5]};
3019          bool filter_false_positives{options.exists("filter_false_positives") ? options["filter_false_positives"].get_bool() : false};
3020  
3021          BlockFilterIndex* index = GetBlockFilterIndex(filtertype);
3022          if (!index) {
3023              throw JSONRPCError(RPC_MISC_ERROR, "Index is not enabled for filtertype " + filtertype_name);
3024          }
3025  
3026          NodeContext& node = EnsureAnyNodeContext(request.context);
3027          ChainstateManager& chainman = EnsureChainman(node);
3028  
3029          // set the start-height
3030          const CBlockIndex* start_index = nullptr;
3031          const CBlockIndex* stop_block = nullptr;
3032          {
3033              LOCK(cs_main);
3034              CChain& active_chain = chainman.ActiveChain();
3035              start_index = active_chain.Genesis();
3036              stop_block = active_chain.Tip(); // If no stop block is provided, stop at the chain tip.
3037              if (!request.params[2].isNull()) {
3038                  start_index = active_chain[request.params[2].getInt<int>()];
3039                  if (!start_index) {
3040                      throw JSONRPCError(RPC_MISC_ERROR, "Invalid start_height");
3041                  }
3042              }
3043              if (!request.params[3].isNull()) {
3044                  stop_block = active_chain[request.params[3].getInt<int>()];
3045                  if (!stop_block || stop_block->nHeight < start_index->nHeight) {
3046                      throw JSONRPCError(RPC_MISC_ERROR, "Invalid stop_height");
3047                  }
3048              }
3049          }
3050          CHECK_NONFATAL(start_index);
3051          CHECK_NONFATAL(stop_block);
3052  
3053          // loop through the scan objects, add scripts to the needle_set
3054          GCSFilter::ElementSet needle_set;
3055          for (const UniValue& scanobject : request.params[1].get_array().getValues()) {
3056              FlatSigningProvider provider;
3057              std::vector<CScript> scripts = EvalDescriptorStringOrObject(scanobject, provider);
3058              for (const CScript& script : scripts) {
3059                  needle_set.emplace(script.begin(), script.end());
3060              }
3061          }
3062  
3063          const int amount_per_chunk = 10000;
3064          std::vector<BlockFilter> filters;
3065          int start_block_height = start_index->nHeight; // for progress reporting
3066          const int total_blocks_to_process = stop_block->nHeight - start_block_height;
3067  
3068          g_scanfilter_should_abort_scan = false;
3069          g_scanfilter_progress = 0;
3070          g_scanfilter_progress_height = start_block_height;
3071          bool completed = true;
3072  
3073          const CBlockIndex* end_range = nullptr;
3074          do {
3075              node.rpc_interruption_point(); // allow a clean shutdown
3076              if (g_scanfilter_should_abort_scan) {
3077                  completed = false;
3078                  break;
3079              }
3080  
3081              // split the lookup range in chunks if we are deeper than 'amount_per_chunk' blocks from the stopping block
3082              int start_block = !end_range ? start_index->nHeight : start_index->nHeight + 1; // to not include the previous round 'end_range' block
3083              end_range = (start_block + amount_per_chunk < stop_block->nHeight) ?
3084                      WITH_LOCK(::cs_main, return chainman.ActiveChain()[start_block + amount_per_chunk]) :
3085                      stop_block;
3086  
3087              if (index->LookupFilterRange(start_block, end_range, filters)) {
3088                  for (const BlockFilter& filter : filters) {
3089                      // compare the elements-set with each filter
3090                      if (filter.GetFilter().MatchAny(needle_set)) {
3091                          if (filter_false_positives) {
3092                              // Double check the filter matches by scanning the block
3093                              const CBlockIndex& blockindex = *CHECK_NONFATAL(WITH_LOCK(cs_main, return chainman.m_blockman.LookupBlockIndex(filter.GetBlockHash())));
3094  
3095                              if (!CheckBlockFilterMatches(chainman.m_blockman, blockindex, needle_set)) {
3096                                  continue;
3097                              }
3098                          }
3099  
3100                          LOCK(cs_relevant_blocks);
3101                          relevant_blocks.push_back(filter.GetBlockHash().GetHex());
3102                      }
3103                  }
3104              }
3105              start_index = end_range;
3106  
3107              // update progress
3108              int blocks_processed = end_range->nHeight - start_block_height;
3109              if (total_blocks_to_process > 0) { // avoid division by zero
3110                  g_scanfilter_progress = (int)(100.0 / total_blocks_to_process * blocks_processed);
3111              } else {
3112                  g_scanfilter_progress = 100;
3113              }
3114              g_scanfilter_progress_height = end_range->nHeight;
3115  
3116          // Finish if we reached the stop block
3117          } while (start_index != stop_block);
3118  
3119          ret.pushKV("from_height", start_block_height);
3120          ret.pushKV("to_height", start_index->nHeight); // start_index is always the last scanned block here
3121          LOCK(cs_relevant_blocks);
3122          ret.pushKV("relevant_blocks", std::move(relevant_blocks));
3123          ret.pushKV("completed", completed);
3124          reserver.release(); // ensure this is before cs_relevant_blocks is released, so status doesn't try to use moved relevant_blocks
3125      }
3126      else {
3127          throw JSONRPCError(RPC_INVALID_PARAMETER, strprintf("Invalid action '%s'", request.params[0].get_str()));
3128      }
3129      return ret;
3130  },
3131      };
3132  }
3133  
3134  static RPCHelpMan getdescriptoractivity()
3135  {
3136      return RPCHelpMan{"getdescriptoractivity",
3137          "\nGet spend and receive activity associated with a set of descriptors for a set of blocks. "
3138          "This command pairs well with the `relevant_blocks` output of `scanblocks()`.\n"
3139          "This call may take several minutes. If you encounter timeouts, try specifying no RPC timeout (limenka-cli -rpcclienttimeout=0)",
3140          {
3141              RPCArg{"blockhashes", RPCArg::Type::ARR, RPCArg::Optional::NO, "The list of blockhashes to examine for activity. Order doesn't matter. Must be along main chain or an error is thrown.\n", {
3142                  {"blockhash", RPCArg::Type::STR_HEX, RPCArg::Optional::OMITTED, "A valid blockhash"},
3143              }},
3144              RPCArg{"scanobjects", RPCArg::Type::ARR, RPCArg::Optional::NO, "The list of descriptors (scan objects) to examine for activity. Every scan object is either a string descriptor or an object:",
3145                  {
3146                      {"descriptor", RPCArg::Type::STR, RPCArg::Optional::OMITTED, "An output descriptor"},
3147                      output_descriptor_obj,
3148                  },
3149                  RPCArgOptions{.oneline_description="[scanobjects,...]"},
3150              },
3151              {"include_mempool", RPCArg::Type::BOOL, RPCArg::Default{true}, "Whether to include unconfirmed activity"},
3152          },
3153          RPCResult{
3154              RPCResult::Type::OBJ, "", "", {
3155                  {RPCResult::Type::ARR, "activity", "events", {
3156                      {RPCResult::Type::OBJ, "", "", {
3157                          {RPCResult::Type::STR, "type", "always 'spend'"},
3158                          {RPCResult::Type::STR_AMOUNT, "amount", "The total amount in " + CURRENCY_UNIT + " of the spent output"},
3159                          {RPCResult::Type::STR_HEX, "blockhash", /*optional=*/true, "The blockhash this spend appears in (omitted if unconfirmed)"},
3160                          {RPCResult::Type::NUM, "height", /*optional=*/true, "Height of the spend (omitted if unconfirmed)"},
3161                          {RPCResult::Type::STR_HEX, "spend_txid", "The txid of the spending transaction"},
3162                          {RPCResult::Type::NUM, "spend_vin", "The input index of the spend"},
3163                          {RPCResult::Type::STR_HEX, "prevout_txid", "The txid of the prevout"},
3164                          {RPCResult::Type::NUM, "prevout_vout", "The vout of the prevout"},
3165                          {RPCResult::Type::OBJ, "prevout_spk", "", ScriptPubKeyDoc()},
3166                      }},
3167                      {RPCResult::Type::OBJ, "", "", {
3168                          {RPCResult::Type::STR, "type", "always 'receive'"},
3169                          {RPCResult::Type::STR_AMOUNT, "amount", "The total amount in " + CURRENCY_UNIT + " of the new output"},
3170                          {RPCResult::Type::STR_HEX, "blockhash", /*optional=*/true, "The block that this receive is in (omitted if unconfirmed)"},
3171                          {RPCResult::Type::NUM, "height", /*optional=*/true, "The height of the receive (omitted if unconfirmed)"},
3172                          {RPCResult::Type::STR_HEX, "txid", "The txid of the receiving transaction"},
3173                          {RPCResult::Type::NUM, "vout", "The vout of the receiving output"},
3174                          {RPCResult::Type::OBJ, "output_spk", "", ScriptPubKeyDoc()},
3175                      }},
3176                      // TODO is the skip_type_check avoidable with a heterogeneous ARR?
3177                  }, /*skip_type_check=*/true},
3178              },
3179          },
3180          RPCExamples{
3181              HelpExampleCli("getdescriptoractivity", "'[\"000000000000000000001347062c12fded7c528943c8ce133987e2e2f5a840ee\"]' '[\"addr(bc1qzl6nsgqzu89a66l50cvwapnkw5shh23zarqkw9)\"]'")
3182          },
3183          [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
3184  {
3185      UniValue ret(UniValue::VOBJ);
3186      UniValue activity(UniValue::VARR);
3187      NodeContext& node = EnsureAnyNodeContext(request.context);
3188      ChainstateManager& chainman = EnsureChainman(node);
3189  
3190      struct CompareByHeightAscending {
3191          bool operator()(const CBlockIndex* a, const CBlockIndex* b) const {
3192              return a->nHeight < b->nHeight;
3193          }
3194      };
3195  
3196      std::set<const CBlockIndex*, CompareByHeightAscending> blockindexes_sorted;
3197  
3198      {
3199          // Validate all given blockhashes, and ensure blocks are along a single chain.
3200          LOCK(::cs_main);
3201          for (const UniValue& blockhash : request.params[0].get_array().getValues()) {
3202              uint256 bhash = ParseHashV(blockhash, "blockhash");
3203              CBlockIndex* pindex = chainman.m_blockman.LookupBlockIndex(bhash);
3204              if (!pindex) {
3205                  throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Block not found");
3206              }
3207              if (!chainman.ActiveChain().Contains(pindex)) {
3208                  throw JSONRPCError(RPC_INVALID_PARAMETER, "Block is not in main chain");
3209              }
3210              blockindexes_sorted.insert(pindex);
3211          }
3212      }
3213  
3214      std::set<CScript> scripts_to_watch;
3215  
3216      // Determine scripts to watch.
3217      for (const UniValue& scanobject : request.params[1].get_array().getValues()) {
3218          FlatSigningProvider provider;
3219          std::vector<CScript> scripts = EvalDescriptorStringOrObject(scanobject, provider);
3220  
3221          for (const CScript& script : scripts) {
3222              scripts_to_watch.insert(script);
3223          }
3224      }
3225  
3226      const auto AddSpend = [&](
3227              const CScript& spk,
3228              const CAmount val,
3229              const CTransactionRef& tx,
3230              int vin,
3231              const CTxIn& txin,
3232              const CBlockIndex* index
3233              ) {
3234          UniValue event(UniValue::VOBJ);
3235          UniValue spkUv(UniValue::VOBJ);
3236          ScriptToUniv(spk, /*out=*/spkUv, /*include_hex=*/true, /*include_address=*/true);
3237  
3238          event.pushKV("type", "spend");
3239          event.pushKV("amount", ValueFromAmount(val));
3240          if (index) {
3241              event.pushKV("blockhash", index->GetBlockHash().ToString());
3242              event.pushKV("height", index->nHeight);
3243          }
3244          event.pushKV("spend_txid", tx->GetHash().ToString());
3245          event.pushKV("spend_vin", vin);
3246          event.pushKV("prevout_txid", txin.prevout.hash.ToString());
3247          event.pushKV("prevout_vout", txin.prevout.n);
3248          event.pushKV("prevout_spk", spkUv);
3249  
3250          return event;
3251      };
3252  
3253      const auto AddReceive = [&](const CTxOut& txout, const CBlockIndex* index, int vout, const CTransactionRef& tx) {
3254          UniValue event(UniValue::VOBJ);
3255          UniValue spkUv(UniValue::VOBJ);
3256          ScriptToUniv(txout.scriptPubKey, /*out=*/spkUv, /*include_hex=*/true, /*include_address=*/true);
3257  
3258          event.pushKV("type", "receive");
3259          event.pushKV("amount", ValueFromAmount(txout.nValue));
3260          if (index) {
3261              event.pushKV("blockhash", index->GetBlockHash().ToString());
3262              event.pushKV("height", index->nHeight);
3263          }
3264          event.pushKV("txid", tx->GetHash().ToString());
3265          event.pushKV("vout", vout);
3266          event.pushKV("output_spk", spkUv);
3267  
3268          return event;
3269      };
3270  
3271      BlockManager* blockman;
3272      Chainstate& active_chainstate = chainman.ActiveChainstate();
3273      {
3274          LOCK(::cs_main);
3275          blockman = CHECK_NONFATAL(&active_chainstate.m_blockman);
3276      }
3277  
3278      for (const CBlockIndex* blockindex : blockindexes_sorted) {
3279          const CBlock block{GetBlockChecked(chainman.m_blockman, *blockindex)};
3280          const CBlockUndo block_undo{GetUndoChecked(*blockman, *blockindex)};
3281  
3282          for (size_t i = 0; i < block.vtx.size(); ++i) {
3283              const auto& tx = block.vtx.at(i);
3284  
3285              if (!tx->IsCoinBase()) {
3286                  // skip coinbase; spends can't happen there.
3287                  const auto& txundo = block_undo.vtxundo.at(i - 1);
3288  
3289                  for (size_t vin_idx = 0; vin_idx < tx->vin.size(); ++vin_idx) {
3290                      const auto& coin = txundo.vprevout.at(vin_idx);
3291                      const auto& txin = tx->vin.at(vin_idx);
3292                      if (scripts_to_watch.contains(coin.out.scriptPubKey)) {
3293                          activity.push_back(AddSpend(
3294                                      coin.out.scriptPubKey, coin.out.nValue, tx, vin_idx, txin, blockindex));
3295                      }
3296                  }
3297              }
3298  
3299              for (size_t vout_idx = 0; vout_idx < tx->vout.size(); ++vout_idx) {
3300                  const auto& vout = tx->vout.at(vout_idx);
3301                  if (scripts_to_watch.contains(vout.scriptPubKey)) {
3302                      activity.push_back(AddReceive(vout, blockindex, vout_idx, tx));
3303                  }
3304              }
3305          }
3306      }
3307  
3308      bool search_mempool = true;
3309      if (!request.params[2].isNull()) {
3310          search_mempool = request.params[2].get_bool();
3311      }
3312  
3313      if (search_mempool) {
3314          const CTxMemPool& mempool = EnsureMemPool(node);
3315          LOCK(::cs_main);
3316          LOCK(mempool.cs);
3317          const CCoinsViewCache& coins_view = &active_chainstate.CoinsTip();
3318  
3319          for (const CTxMemPoolEntry& e : mempool.entryAll()) {
3320              const auto& tx = e.GetSharedTx();
3321  
3322              for (size_t vin_idx = 0; vin_idx < tx->vin.size(); ++vin_idx) {
3323                  CScript scriptPubKey;
3324                  CAmount value;
3325                  const auto& txin = tx->vin.at(vin_idx);
3326                  std::optional<Coin> coin = coins_view.GetCoin(txin.prevout);
3327  
3328                  // Check if the previous output is in the chain
3329                  if (!coin) {
3330                      // If not found in the chain, check the mempool. Likely, this is a
3331                      // child transaction of another transaction in the mempool.
3332                      CTransactionRef prev_tx = CHECK_NONFATAL(mempool.get(txin.prevout.hash));
3333  
3334                      if (txin.prevout.n >= prev_tx->vout.size()) {
3335                          throw std::runtime_error("Invalid output index");
3336                      }
3337                      const CTxOut& out = prev_tx->vout[txin.prevout.n];
3338                      scriptPubKey = out.scriptPubKey;
3339                      value = out.nValue;
3340                  } else {
3341                      // Coin found in the chain
3342                      const CTxOut& out = coin->out;
3343                      scriptPubKey = out.scriptPubKey;
3344                      value = out.nValue;
3345                  }
3346  
3347                  if (scripts_to_watch.contains(scriptPubKey)) {
3348                      UniValue event(UniValue::VOBJ);
3349                      activity.push_back(AddSpend(
3350                                  scriptPubKey, value, tx, vin_idx, txin, nullptr));
3351                  }
3352              }
3353  
3354              for (size_t vout_idx = 0; vout_idx < tx->vout.size(); ++vout_idx) {
3355                  const auto& vout = tx->vout.at(vout_idx);
3356                  if (scripts_to_watch.contains(vout.scriptPubKey)) {
3357                      activity.push_back(AddReceive(vout, nullptr, vout_idx, tx));
3358                  }
3359              }
3360          }
3361      }
3362  
3363      ret.pushKV("activity", activity);
3364      return ret;
3365  },
3366      };
3367  }
3368  
3369  static RPCHelpMan getblockfilter()
3370  {
3371      return RPCHelpMan{"getblockfilter",
3372                  "\nRetrieve a BIP 157 content filter for a particular block.\n",
3373                  {
3374                      {"blockhash", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "The hash of the block"},
3375                      {"filtertype", RPCArg::Type::STR, RPCArg::Default{BlockFilterTypeName(BlockFilterType::BASIC)}, "The type name of the filter, values: " + ListBlockFilterTypes()},
3376                  },
3377                  RPCResult{
3378                      RPCResult::Type::OBJ, "", "",
3379                      {
3380                          {RPCResult::Type::STR_HEX, "filter", "the hex-encoded filter data"},
3381                          {RPCResult::Type::STR_HEX, "header", "the hex-encoded filter header"},
3382                      }},
3383                  RPCExamples{
3384                      HelpExampleCli("getblockfilter", "\"00000000c937983704a73af28acdec37b049d214adbda81d7e2a3dd146f6ed09\" \"basic\"") +
3385                      HelpExampleRpc("getblockfilter", "\"00000000c937983704a73af28acdec37b049d214adbda81d7e2a3dd146f6ed09\", \"basic\"")
3386                  },
3387          [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
3388  {
3389      uint256 block_hash = ParseHashV(request.params[0], "blockhash");
3390      std::string filtertype_name = BlockFilterTypeName(BlockFilterType::BASIC);
3391      if (!request.params[1].isNull()) {
3392          filtertype_name = request.params[1].get_str();
3393      }
3394  
3395      BlockFilterType filtertype;
3396      if (!BlockFilterTypeByName(filtertype_name, filtertype)) {
3397          throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Unknown filtertype");
3398      }
3399  
3400      BlockFilterIndex* index = GetBlockFilterIndex(filtertype);
3401      if (!index) {
3402          throw JSONRPCError(RPC_MISC_ERROR, "Index is not enabled for filtertype " + filtertype_name);
3403      }
3404  
3405      const CBlockIndex* block_index;
3406      bool block_was_connected;
3407      {
3408          ChainstateManager& chainman = EnsureAnyChainman(request.context);
3409          LOCK(cs_main);
3410          block_index = chainman.m_blockman.LookupBlockIndex(block_hash);
3411          if (!block_index) {
3412              throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Block not found");
3413          }
3414          block_was_connected = block_index->IsValid(BLOCK_VALID_SCRIPTS);
3415      }
3416  
3417      bool index_ready = index->BlockUntilSyncedToCurrentChain();
3418  
3419      BlockFilter filter;
3420      uint256 filter_header;
3421      if (!index->LookupFilter(block_index, filter) ||
3422          !index->LookupFilterHeader(block_index, filter_header)) {
3423          int err_code;
3424          std::string errmsg = "Filter not found.";
3425  
3426          if (!block_was_connected) {
3427              err_code = RPC_INVALID_ADDRESS_OR_KEY;
3428              errmsg += " Block was not connected to active chain.";
3429          } else if (!index_ready) {
3430              err_code = RPC_MISC_ERROR;
3431              errmsg += " Block filters are still in the process of being indexed.";
3432          } else {
3433              err_code = RPC_INTERNAL_ERROR;
3434              errmsg += " This error is unexpected and indicates index corruption.";
3435          }
3436  
3437          throw JSONRPCError(err_code, errmsg);
3438      }
3439  
3440      UniValue ret(UniValue::VOBJ);
3441      ret.pushKV("filter", HexStr(filter.GetEncodedFilter()));
3442      ret.pushKV("header", filter_header.GetHex());
3443      return ret;
3444  },
3445      };
3446  }
3447  
3448  /**
3449   * RAII class that disables the network in its constructor and enables it in its
3450   * destructor.
3451   */
3452  class NetworkDisable
3453  {
3454      CConnman& m_connman;
3455  public:
3456      NetworkDisable(CConnman& connman) : m_connman(connman) {
3457          m_connman.SetNetworkActive(false);
3458          if (m_connman.GetNetworkActive()) {
3459              throw JSONRPCError(RPC_MISC_ERROR, "Network activity could not be suspended.");
3460          }
3461      };
3462      ~NetworkDisable() {
3463          m_connman.SetNetworkActive(true);
3464      };
3465  };
3466  
3467  /**
3468   * RAII class that temporarily rolls back the local chain in it's constructor
3469   * and rolls it forward again in it's destructor.
3470   */
3471  class TemporaryRollback
3472  {
3473      ChainstateManager& m_chainman;
3474      const CBlockIndex& m_invalidate_index;
3475  public:
3476      TemporaryRollback(ChainstateManager& chainman, const CBlockIndex& index) : m_chainman(chainman), m_invalidate_index(index) {
3477          InvalidateBlock(m_chainman, m_invalidate_index.GetBlockHash());
3478      };
3479      ~TemporaryRollback() {
3480          ReconsiderBlock(m_chainman, m_invalidate_index.GetBlockHash());
3481      };
3482  };
3483  
3484  /**
3485   * Serialize the UTXO set to a file for loading elsewhere.
3486   *
3487   * @see SnapshotMetadata
3488   */
3489  static RPCHelpMan dumptxoutset()
3490  {
3491      static const std::vector<std::pair<std::string, coinascii_cb_t>> ascii_types{
3492          {"txid",         [](const COutPoint& k, const Coin& c) { return k.hash.GetHex(); }},
3493          {"vout",         [](const COutPoint& k, const Coin& c) { return util::ToString(static_cast<int32_t>(k.n)); }},
3494          {"value",        [](const COutPoint& k, const Coin& c) { return util::ToString(c.out.nValue); }},
3495          {"coinbase",     [](const COutPoint& k, const Coin& c) { return util::ToString(c.fCoinBase); }},
3496          {"height",       [](const COutPoint& k, const Coin& c) { return util::ToString(static_cast<uint32_t>(c.nHeight)); }},
3497          {"scriptPubKey", [](const COutPoint& k, const Coin& c) { return HexStr(c.out.scriptPubKey); }},
3498          // add any other desired items here
3499      };
3500  
3501      std::vector<RPCArg> ascii_args;
3502      std::transform(std::begin(ascii_types), std::end(ascii_types), std::back_inserter(ascii_args),
3503              [](const std::pair<std::string, coinascii_cb_t>& t) { return RPCArg{t.first, RPCArg::Type::STR, RPCArg::Optional::OMITTED, "Info to write for a given UTXO"}; });
3504  
3505      return RPCHelpMan{
3506          "dumptxoutset",
3507          "Write the UTXO set to a file. This can be used in loadtxoutset afterwards if this snapshot height is supported in the chainparams as well.\n\n"
3508          "Unless the \"latest\" type is requested, the node will roll back to the requested height and network activity will be suspended during this process. "
3509          "Because of this it is discouraged to interact with the node in any other way during the execution of this call to avoid inconsistent results and race conditions, particularly RPCs that interact with blockstorage.\n\n"
3510          "This call may take several minutes. Make sure to use no RPC timeout (limenka-cli -rpcclienttimeout=0)",
3511          {
3512              {"path", RPCArg::Type::STR, RPCArg::Optional::NO, "Path to the output file. If relative, will be prefixed by datadir."},
3513              {"type|format", {RPCArg::Type::STR, RPCArg::Type::ARR}, 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 can currently be loaded with loadtxoutset."},
3514              {"options|show_header", {RPCArg::Type::OBJ_NAMED_PARAMS, RPCArg::Type::BOOL}, RPCArg::Optional::OMITTED, "",
3515                  {
3516                      {"format", RPCArg::Type::ARR, RPCArg::DefaultHint{"compact serialized format"},
3517                                                  "If no argument is provided, a compact binary serialized format is used; otherwise only requested items "
3518                                                  "available below are written in ASCII format (if an empty array is provided, all items are written in ASCII).",
3519                                                  ascii_args,
3520                                                  RPCArgOptions{.oneline_description="format", .also_positional = true}},
3521                      {"rollback", RPCArg::Type::NUM, RPCArg::Optional::OMITTED,
3522                          "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.",
3523                      RPCArgOptions{.skip_type_check = true, .type_str = {"", "string or numeric"}}},
3524                      {"show_header", RPCArg::Type::BOOL, RPCArg::Default{true}, "Whether to include the header line in non-serialized (ASCII) mode", RPCArgOptions{.also_positional = true}},
3525                      {"separator", RPCArg::Type::STR, RPCArg::Default{","}, "Field separator to use in non-serialized (ASCII) mode", RPCArgOptions{.also_positional = true}},
3526                  },
3527              },
3528              {"separator", RPCArg::Type::STR, RPCArg::Optional::OMITTED, "", RPCArgOptions{.hidden=true}},
3529          },
3530          RPCResult{
3531              RPCResult::Type::OBJ, "", "",
3532                  {
3533                      {RPCResult::Type::NUM, "coins_written", "the number of coins written in the snapshot"},
3534                      {RPCResult::Type::STR_HEX, "base_hash", "the hash of the base of the snapshot"},
3535                      {RPCResult::Type::NUM, "base_height", "the height of the base of the snapshot"},
3536                      {RPCResult::Type::STR, "path", "the absolute path that the snapshot was written to"},
3537                      {RPCResult::Type::STR_HEX, "txoutset_hash", "the hash of the UTXO set contents"},
3538                      {RPCResult::Type::NUM, "nchaintx", "the number of transactions in the chain up to and including the base block"},
3539                  }
3540          },
3541          RPCExamples{
3542              HelpExampleCli("-rpcclienttimeout=0 dumptxoutset", "utxo.dat latest") +
3543              HelpExampleCli("-rpcclienttimeout=0 dumptxoutset", "utxo.dat rollback") +
3544              HelpExampleCli("-rpcclienttimeout=0 -named dumptxoutset", R"(utxo.dat rollback=853456)")
3545              +
3546              HelpExampleCli("-rpcclienttimeout=0 -named dumptxoutset", "utxo.dat type=latest format='[]'") +
3547              HelpExampleCli("-rpcclienttimeout=0 -named dumptxoutset", "utxo.dat type=latest format='[\"txid\", \"vout\"]' show_header=false separator=':'")
3548          },
3549          [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
3550  {
3551      EnsureNotWalletRestricted(request);
3552  
3553      NodeContext& node = EnsureAnyNodeContext(request.context);
3554      const CBlockIndex* tip{WITH_LOCK(::cs_main, return node.chainman->ActiveChain().Tip())};
3555      const CBlockIndex* target_index{nullptr};
3556      std::string snapshot_type;
3557      UniValue options{request.params[2].isObject() ? request.params[2] : UniValue::VOBJ};
3558  
3559      const UniValue& hr_format = [&]() -> const UniValue& {
3560          if (options["format"].isNull() && request.params[1].isArray()) {
3561              // Knots 0.20.0-28.1 compatibility
3562              snapshot_type = "latest";
3563              return request.params[1];
3564          }
3565          snapshot_type = self.Arg<std::string>("type");
3566          return options["format"];
3567      }();
3568      const bool is_human_readable = !hr_format.isNull();
3569      const bool show_header = [&] {
3570          if (!options["show_header"].isNull()) {  // only possible of options is an Object
3571              return options["show_header"].get_bool();
3572          }
3573          if (is_human_readable && request.params[2].isBool()) {
3574              // Knots 0.20.0-28.1 compatibility
3575              return request.params[2].get_bool();
3576          }
3577          if (!request.params[2].isNull()) request.params[2].get_obj();  // type check skipped earlier
3578          return true;
3579      }();
3580      const auto separator = [&] {
3581          const bool null_separator_in_options{options["separator"].isNull()};
3582          if (null_separator_in_options && is_human_readable && request.params[3].isStr()) {
3583              return MakeByteSpan(request.params[3].get_str());
3584          }
3585          if (!request.params[3].isNull()) {
3586              throw std::runtime_error(self.ToString());
3587          }
3588          if (null_separator_in_options) {
3589              return MakeByteSpan(",").first(1);
3590          }
3591          return MakeByteSpan(options["separator"].get_str());
3592      }();
3593  
3594      if (options.exists("rollback")) {
3595          if (!snapshot_type.empty() && snapshot_type != "rollback") {
3596              throw JSONRPCError(RPC_INVALID_PARAMETER, strprintf("Invalid snapshot type \"%s\" specified with rollback option", snapshot_type));
3597          }
3598          target_index = ParseHashOrHeight(options["rollback"], *node.chainman);
3599      } else if (snapshot_type == "rollback") {
3600          auto snapshot_heights = node.chainman->GetParams().GetAvailableSnapshotHeights();
3601          CHECK_NONFATAL(snapshot_heights.size() > 0);
3602          auto max_height = std::max_element(snapshot_heights.begin(), snapshot_heights.end());
3603          target_index = ParseHashOrHeight(*max_height, *node.chainman);
3604      } else if (snapshot_type == "latest") {
3605          target_index = tip;
3606      } else {
3607          throw JSONRPCError(RPC_INVALID_PARAMETER, strprintf("Invalid snapshot type \"%s\" specified. Please specify \"rollback\" or \"latest\"", snapshot_type));
3608      }
3609  
3610      // handle optional ASCII parameters
3611      std::vector<std::pair<std::string, coinascii_cb_t>> requested;
3612      if (is_human_readable) {
3613          const auto& arr = hr_format.get_array();
3614          const std::unordered_map<std::string, coinascii_cb_t> ascii_map(std::begin(ascii_types), std::end(ascii_types));
3615          for (size_t i = 0; i < arr.size(); ++i) {
3616              const auto it = ascii_map.find(arr[i].get_str());
3617              if (it == std::end(ascii_map))
3618                  throw JSONRPCError(RPC_INVALID_PARAMETER, "unable to find item '"+arr[i].get_str()+"'");
3619  
3620              requested.emplace_back(*it);
3621          }
3622  
3623          // if nothing was found, shows everything by default
3624          if (requested.size() == 0)
3625              requested = ascii_types;
3626      }
3627  
3628      const ArgsManager& args{EnsureAnyArgsman(request.context)};
3629      const fs::path path = fsbridge::AbsPathJoin(args.GetDataDirNet(), fs::u8path(request.params[0].get_str()));
3630      const auto path_info{fs::status(path)};
3631      // Write to a temporary path and then move into `path` on completion
3632      // to avoid confusion due to an interruption.
3633      const fs::path temppath = fs::is_fifo(path_info) ? path : // If a named pipe is passed, write directly to it
3634          fsbridge::AbsPathJoin(args.GetDataDirNet(), fs::u8path(request.params[0].get_str() + ".incomplete"));
3635  
3636      if (fs::exists(path_info) && !fs::is_fifo(path_info)) {
3637          throw JSONRPCError(
3638              RPC_INVALID_PARAMETER,
3639              path.utf8string() + " already exists. If you are sure this is what you want, "
3640              "move it out of the way first");
3641      }
3642  
3643      FILE* file{fsbridge::fopen(temppath, !is_human_readable ? "wb" : "w")};
3644      AutoFile afile{file};
3645      if (afile.IsNull()) {
3646          throw JSONRPCError(
3647              RPC_INVALID_PARAMETER,
3648              "Couldn't open file " + temppath.utf8string() + " for writing.");
3649      }
3650  
3651      CConnman& connman = EnsureConnman(node);
3652      const CBlockIndex* invalidate_index{nullptr};
3653      std::optional<NetworkDisable> disable_network;
3654      std::optional<TemporaryRollback> temporary_rollback;
3655  
3656      // If the user wants to dump the txoutset of the current tip, we don't have
3657      // to roll back at all
3658      if (target_index != tip) {
3659          // If the node is running in pruned mode we ensure all necessary block
3660          // data is available before starting to roll back.
3661          if (node.chainman->m_blockman.IsPruneMode()) {
3662              LOCK(node.chainman->GetMutex());
3663              const CBlockIndex* current_tip{node.chainman->ActiveChain().Tip()};
3664              const CBlockIndex* first_block{node.chainman->m_blockman.GetFirstBlock(*current_tip, /*status_mask=*/BLOCK_HAVE_MASK)};
3665              if (first_block->nHeight > target_index->nHeight) {
3666                  throw JSONRPCError(RPC_MISC_ERROR, "Could not roll back to requested height since necessary block data is already pruned.");
3667              }
3668          }
3669  
3670          // Suspend network activity for the duration of the process when we are
3671          // rolling back the chain to get a utxo set from a past height. We do
3672          // this so we don't punish peers that send us that send us data that
3673          // seems wrong in this temporary state. For example a normal new block
3674          // would be classified as a block connecting an invalid block.
3675          // Skip if the network is already disabled because this
3676          // automatically re-enables the network activity at the end of the
3677          // process which may not be what the user wants.
3678          if (connman.GetNetworkActive()) {
3679              disable_network.emplace(connman);
3680          }
3681  
3682          invalidate_index = WITH_LOCK(::cs_main, return node.chainman->ActiveChain().Next(target_index));
3683          temporary_rollback.emplace(*node.chainman, *invalidate_index);
3684      }
3685  
3686      Chainstate* chainstate;
3687      std::unique_ptr<CCoinsViewCursor> cursor;
3688      CCoinsStats stats;
3689      {
3690          // Lock the chainstate before calling PrepareUtxoSnapshot, to be able
3691          // to get a UTXO database cursor while the chain is pointing at the
3692          // target block. After that, release the lock while calling
3693          // WriteUTXOSnapshot. The cursor will remain valid and be used by
3694          // WriteUTXOSnapshot to write a consistent snapshot even if the
3695          // chainstate changes.
3696          LOCK(node.chainman->GetMutex());
3697          chainstate = &node.chainman->ActiveChainstate();
3698          // In case there is any issue with a block being read from disk we need
3699          // to stop here, otherwise the dump could still be created for the wrong
3700          // height.
3701          // The new tip could also not be the target block if we have a stale
3702          // sister block of invalidate_index. This block (or a descendant) would
3703          // be activated as the new tip and we would not get to new_tip_index.
3704          if (target_index != chainstate->m_chain.Tip()) {
3705              LogWarning("dumptxoutset failed to roll back to requested height, reverting to tip.\n");
3706              throw JSONRPCError(RPC_MISC_ERROR, "Could not roll back to requested height.");
3707          } else {
3708              std::tie(cursor, stats, tip) = PrepareUTXOSnapshot(*chainstate, node.rpc_interruption_point);
3709          }
3710      }
3711  
3712      UniValue result = WriteUTXOSnapshot(
3713                                          is_human_readable,
3714                                          show_header, separator, requested,
3715                                          *chainstate,
3716                                          cursor.get(),
3717                                          &stats,
3718                                          tip,
3719                                          std::move(afile),
3720                                          path,
3721                                          temppath,
3722                                          node.rpc_interruption_point);
3723      if (!fs::is_fifo(path_info)) fs::rename(temppath, path);
3724  
3725      result.pushKV("path", path.utf8string());
3726      return result;
3727  },
3728      };
3729  }
3730  
3731  std::tuple<std::unique_ptr<CCoinsViewCursor>, CCoinsStats, const CBlockIndex*>
3732  PrepareUTXOSnapshot(
3733      Chainstate& chainstate,
3734      const std::function<void()>& interruption_point)
3735  {
3736      std::unique_ptr<CCoinsViewCursor> pcursor;
3737      std::optional<CCoinsStats> maybe_stats;
3738      const CBlockIndex* tip;
3739  
3740      {
3741          // We need to lock cs_main to ensure that the coinsdb isn't written to
3742          // between (i) flushing coins cache to disk (coinsdb), (ii) getting stats
3743          // based upon the coinsdb, and (iii) constructing a cursor to the
3744          // coinsdb for use in WriteUTXOSnapshot.
3745          //
3746          // Cursors returned by leveldb iterate over snapshots, so the contents
3747          // of the pcursor will not be affected by simultaneous writes during
3748          // use below this block.
3749          //
3750          // See discussion here:
3751          //   https://github.com/limenka/limenka/pull/15606#discussion_r274479369
3752          //
3753          AssertLockHeld(::cs_main);
3754  
3755          chainstate.ForceFlushStateToDisk();
3756  
3757          maybe_stats = GetUTXOStats(&chainstate.CoinsDB(), chainstate.m_blockman, CoinStatsHashType::HASH_SERIALIZED, interruption_point);
3758          if (!maybe_stats) {
3759              throw JSONRPCError(RPC_INTERNAL_ERROR, "Unable to read UTXO set");
3760          }
3761  
3762          pcursor = chainstate.CoinsDB().Cursor();
3763          tip = CHECK_NONFATAL(chainstate.m_blockman.LookupBlockIndex(maybe_stats->hashBlock));
3764      }
3765  
3766      return {std::move(pcursor), *CHECK_NONFATAL(maybe_stats), tip};
3767  }
3768  
3769  UniValue WriteUTXOSnapshot(
3770      const bool is_human_readable,
3771      const bool show_header,
3772      const Span<const std::byte>& separator,
3773      const std::vector<std::pair<std::string, coinascii_cb_t>>& requested,
3774      Chainstate& chainstate,
3775      CCoinsViewCursor* pcursor,
3776      CCoinsStats* maybe_stats,
3777      const CBlockIndex* tip,
3778      AutoFile&& afile,
3779      const fs::path& path,
3780      const fs::path& temppath,
3781      const std::function<void()>& interruption_point)
3782  {
3783      LOG_TIME_SECONDS(strprintf("writing UTXO snapshot at height %s (%s) to file %s (via %s)",
3784          tip->nHeight, tip->GetBlockHash().ToString(),
3785          fs::PathToString(path), fs::PathToString(temppath)));
3786  
3787      // used when human readable format is requested
3788      const auto line_separator = MakeByteSpan("\n").first(1);
3789  
3790      if (!is_human_readable) {
3791      SnapshotMetadata metadata{chainstate.m_chainman.GetParams().MessageStart(), tip->GetBlockHash(), maybe_stats->coins_count};
3792  
3793      afile << metadata;
3794      } else if (show_header) {
3795          afile.write(MakeByteSpan("#(blockhash " + tip->GetBlockHash().ToString() + " ) "));
3796          for (auto it = std::begin(requested); it != std::end(requested); ++it) {
3797              if (it != std::begin(requested)) {
3798                  afile.write(separator);
3799              }
3800              afile.write(MakeByteSpan(it->first));
3801          }
3802          afile.write(line_separator);
3803      }
3804  
3805      COutPoint key;
3806      Txid last_hash;
3807      Coin coin;
3808      unsigned int iter{0};
3809      size_t written_coins_count{0};
3810      std::vector<std::pair<uint32_t, Coin>> coins;
3811  
3812      // To reduce space the serialization format of the snapshot avoids
3813      // duplication of tx hashes. The code takes advantage of the guarantee by
3814      // leveldb that keys are lexicographically sorted.
3815      // In the coins vector we collect all coins that belong to a certain tx hash
3816      // (key.hash) and when we have them all (key.hash != last_hash) we write
3817      // them to file using the below lambda function.
3818      // See also https://github.com/limenka/limenka/issues/25675
3819      auto write_coins_to_file = [&](AutoFile& afile, const Txid& last_hash, const std::vector<std::pair<uint32_t, Coin>>& coins, size_t& written_coins_count) {
3820          afile << last_hash;
3821          WriteCompactSize(afile, coins.size());
3822          for (const auto& [n, coin] : coins) {
3823              WriteCompactSize(afile, n);
3824              afile << coin;
3825              ++written_coins_count;
3826          }
3827      };
3828  
3829      pcursor->GetKey(key);
3830      last_hash = key.hash;
3831      while (pcursor->Valid()) {
3832          if (iter % 5000 == 0) interruption_point();
3833          ++iter;
3834          if (pcursor->GetKey(key) && pcursor->GetValue(coin)) {
3835              if (!is_human_readable) {
3836              if (key.hash != last_hash) {
3837                  write_coins_to_file(afile, last_hash, coins, written_coins_count);
3838                  last_hash = key.hash;
3839                  coins.clear();
3840              }
3841              coins.emplace_back(key.n, coin);
3842              } else {
3843                  for (auto it = std::begin(requested); it != std::end(requested); ++it) {
3844                      if (it != std::begin(requested))
3845                          afile.write(separator);
3846                      afile.write(MakeByteSpan(it->second(key, coin)));
3847                  }
3848                  afile.write(line_separator);
3849                  ++written_coins_count;
3850              }
3851          }
3852          pcursor->Next();
3853      }
3854  
3855      if (!coins.empty()) {
3856          write_coins_to_file(afile, last_hash, coins, written_coins_count);
3857      }
3858  
3859      CHECK_NONFATAL(written_coins_count == maybe_stats->coins_count);
3860  
3861      if (afile.fclose() != 0) {
3862          throw std::ios_base::failure(
3863              strprintf("Error closing %s: %s", fs::PathToString(temppath), SysErrorString(errno)));
3864      }
3865  
3866      UniValue result(UniValue::VOBJ);
3867      result.pushKV("coins_written", written_coins_count);
3868      result.pushKV("base_hash", tip->GetBlockHash().ToString());
3869      result.pushKV("base_height", tip->nHeight);
3870      result.pushKV("path", path.utf8string());
3871      result.pushKV("txoutset_hash", maybe_stats->hashSerialized.ToString());
3872      result.pushKV("nchaintx", tip->m_chain_tx_count);
3873      return result;
3874  }
3875  
3876  UniValue CreateUTXOSnapshot(
3877      node::NodeContext& node,
3878      Chainstate& chainstate,
3879      AutoFile&& afile,
3880      const fs::path& path,
3881      const fs::path& tmppath)
3882  {
3883      auto [cursor, stats, tip]{WITH_LOCK(::cs_main, return PrepareUTXOSnapshot(chainstate, node.rpc_interruption_point))};
3884      return WriteUTXOSnapshot(
3885                               false, false, Span<std::byte>(), {},
3886                               chainstate,
3887                               cursor.get(),
3888                               &stats,
3889                               tip,
3890                               std::move(afile),
3891                               path,
3892                               tmppath,
3893                               node.rpc_interruption_point);
3894  }
3895  
3896  static RPCHelpMan loadtxoutset()
3897  {
3898      return RPCHelpMan{
3899          "loadtxoutset",
3900          "Load the serialized UTXO set from a file.\n"
3901          "Once this snapshot is loaded, its contents will be "
3902          "deserialized into a second chainstate data structure, which is then used to sync to "
3903          "the network's tip. "
3904          "Meanwhile, the original chainstate will complete the initial block download process in "
3905          "the background, eventually validating up to the block that the snapshot is based upon.\n\n"
3906  
3907          "The result is a usable limenkad instance that is current with the network tip in a "
3908          "matter of minutes rather than hours. UTXO snapshot are typically obtained from "
3909          "third-party sources (HTTP, torrent, etc.) which is reasonable since their "
3910          "contents are always checked by hash.\n\n"
3911  
3912          "You can find more information on this process in the `assumeutxo` design "
3913          "document (<https://github.com/limenka/limenka/blob/master/doc/design/assumeutxo.md>).",
3914          {
3915              {"path",
3916                  RPCArg::Type::STR,
3917                  RPCArg::Optional::NO,
3918                  "path to the snapshot file. If relative, will be prefixed by datadir."},
3919          },
3920          RPCResult{
3921              RPCResult::Type::OBJ, "", "",
3922                  {
3923                      {RPCResult::Type::NUM, "coins_loaded", "the number of coins loaded from the snapshot"},
3924                      {RPCResult::Type::STR_HEX, "tip_hash", "the hash of the base of the snapshot"},
3925                      {RPCResult::Type::NUM, "base_height", "the height of the base of the snapshot"},
3926                      {RPCResult::Type::STR, "path", "the absolute path that the snapshot was loaded from"},
3927                  }
3928          },
3929          RPCExamples{
3930              HelpExampleCli("-rpcclienttimeout=0 loadtxoutset", "utxo.dat")
3931          },
3932          [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
3933  {
3934      EnsureNotWalletRestricted(request);
3935  
3936      NodeContext& node = EnsureAnyNodeContext(request.context);
3937      ChainstateManager& chainman = EnsureChainman(node);
3938      const fs::path path{AbsPathForConfigVal(EnsureArgsman(node), fs::u8path(self.Arg<std::string>("path")))};
3939  
3940      FILE* file{fsbridge::fopen(path, "rb")};
3941      AutoFile afile{file};
3942      if (afile.IsNull()) {
3943          throw JSONRPCError(
3944              RPC_INVALID_PARAMETER,
3945              "Couldn't open file " + path.utf8string() + " for reading.");
3946      }
3947  
3948      SnapshotMetadata metadata{chainman.GetParams().MessageStart()};
3949      try {
3950          afile >> metadata;
3951      } catch (const std::ios_base::failure& e) {
3952          throw JSONRPCError(RPC_DESERIALIZATION_ERROR, strprintf("Unable to parse metadata: %s", e.what()));
3953      }
3954  
3955      auto activation_result{chainman.ActivateSnapshot(afile, metadata, false)};
3956      if (!activation_result) {
3957          throw JSONRPCError(RPC_INTERNAL_ERROR, strprintf("Unable to load UTXO snapshot: %s. (%s)", util::ErrorString(activation_result).original, path.utf8string()));
3958      }
3959  
3960      // Because we can't provide historical blocks during tip or background sync.
3961      // Update local services to reflect we are a limited peer until we are fully sync.
3962      node.connman->RemoveLocalServices(NODE_NETWORK);
3963      // Setting the limited state is usually redundant because the node can always
3964      // provide the last 288 blocks, but it doesn't hurt to set it.
3965      node.connman->AddLocalServices(NODE_NETWORK_LIMITED);
3966  
3967      CBlockIndex& snapshot_index{*CHECK_NONFATAL(*activation_result)};
3968  
3969      UniValue result(UniValue::VOBJ);
3970      result.pushKV("coins_loaded", metadata.m_coins_count);
3971      result.pushKV("tip_hash", snapshot_index.GetBlockHash().ToString());
3972      result.pushKV("base_height", snapshot_index.nHeight);
3973      result.pushKV("path", fs::PathToString(path));
3974      return result;
3975  },
3976      };
3977  }
3978  
3979  const std::vector<RPCResult> RPCHelpForChainstate{
3980      {RPCResult::Type::NUM, "blocks", "number of blocks in this chainstate"},
3981      {RPCResult::Type::STR_HEX, "bestblockhash", "blockhash of the tip"},
3982      {RPCResult::Type::STR_HEX, "bits", "nBits: compact representation of the block difficulty target"},
3983      {RPCResult::Type::STR_HEX, "target", "The difficulty target"},
3984      {RPCResult::Type::NUM, "difficulty", "difficulty of the tip"},
3985      {RPCResult::Type::NUM, "verificationprogress", "progress towards the network tip"},
3986      {RPCResult::Type::STR_HEX, "snapshot_blockhash", /*optional=*/true, "the base block of the snapshot this chainstate is based on, if any"},
3987      {RPCResult::Type::NUM, "coins_db_cache_bytes", "size of the coinsdb cache"},
3988      {RPCResult::Type::NUM, "coins_tip_cache_bytes", "size of the coinstip cache"},
3989      {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."},
3990  };
3991  
3992  static RPCHelpMan getchainstates()
3993  {
3994  return RPCHelpMan{
3995          "getchainstates",
3996          "\nReturn information about chainstates.\n",
3997          {},
3998          RPCResult{
3999              RPCResult::Type::OBJ, "", "", {
4000                  {RPCResult::Type::NUM, "headers", "the number of headers seen so far"},
4001                  {RPCResult::Type::ARR, "chainstates", "list of the chainstates ordered by work, with the most-work (active) chainstate last", {{RPCResult::Type::OBJ, "", "", RPCHelpForChainstate},}},
4002              }
4003          },
4004          RPCExamples{
4005              HelpExampleCli("getchainstates", "")
4006      + HelpExampleRpc("getchainstates", "")
4007          },
4008          [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
4009  {
4010      LOCK(cs_main);
4011      UniValue obj(UniValue::VOBJ);
4012  
4013      ChainstateManager& chainman = EnsureAnyChainman(request.context);
4014  
4015      auto make_chain_data = [&](const Chainstate& cs, bool validated) EXCLUSIVE_LOCKS_REQUIRED(::cs_main) {
4016          AssertLockHeld(::cs_main);
4017          UniValue data(UniValue::VOBJ);
4018          if (!cs.m_chain.Tip()) {
4019              return data;
4020          }
4021          const CChain& chain = cs.m_chain;
4022          const CBlockIndex* tip = chain.Tip();
4023  
4024          data.pushKV("blocks",                (int)chain.Height());
4025          data.pushKV("bestblockhash",         tip->GetBlockHash().GetHex());
4026          data.pushKV("bits", strprintf("%08x", tip->nBits));
4027          data.pushKV("target", GetTarget(*tip, chainman.GetConsensus().powLimit).GetHex());
4028          data.pushKV("difficulty", GetDifficulty(*tip));
4029          data.pushKV("verificationprogress", chainman.GuessVerificationProgress(tip));
4030          data.pushKV("coins_db_cache_bytes",  cs.m_coinsdb_cache_size_bytes);
4031          data.pushKV("coins_tip_cache_bytes", cs.m_coinstip_cache_size_bytes);
4032          if (cs.m_from_snapshot_blockhash) {
4033              data.pushKV("snapshot_blockhash", cs.m_from_snapshot_blockhash->ToString());
4034          }
4035          data.pushKV("validated", validated);
4036          return data;
4037      };
4038  
4039      obj.pushKV("headers", chainman.m_best_header ? chainman.m_best_header->nHeight : -1);
4040  
4041      const auto& chainstates = chainman.GetAll();
4042      UniValue obj_chainstates{UniValue::VARR};
4043      for (Chainstate* cs : chainstates) {
4044        obj_chainstates.push_back(make_chain_data(*cs, !cs->m_from_snapshot_blockhash || chainstates.size() == 1));
4045      }
4046      obj.pushKV("chainstates", std::move(obj_chainstates));
4047      return obj;
4048  }
4049      };
4050  }
4051  
4052  static RPCHelpMan getblockfileinfo()
4053  {
4054      return RPCHelpMan{
4055              "getblockfileinfo",
4056              "Retrieves information about a certain block file.",
4057              {
4058                  {"file_number", RPCArg::Type::NUM, RPCArg::Optional::NO, "block file number"},
4059              },
4060              RPCResult{
4061                      RPCResult::Type::OBJ, "", "",
4062                      {
4063                          {RPCResult::Type::NUM, "blocks_num", "the number of blocks stored in the file"},
4064                          {RPCResult::Type::NUM, "lowest_block", "the height of the lowest block inside the file"},
4065                          {RPCResult::Type::NUM, "highest_block", "the height of the highest block inside the file"},
4066                          {RPCResult::Type::NUM, "data_size", "the number of used bytes in the block file"},
4067                          {RPCResult::Type::NUM, "undo_size", "the number of used bytes in the undo file"},
4068                      }
4069              },
4070              RPCExamples{ HelpExampleCli("getblockfileinfo", "0") },
4071              [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue {
4072                  NodeContext& node = EnsureAnyNodeContext(request.context);
4073  
4074                  int block_num = request.params[0].getInt<int>();
4075                  if (block_num < 0) throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid block number");
4076  
4077                  CBlockFileInfo* info = node.chainman->m_blockman.GetBlockFileInfo(block_num);
4078                  if (!info) throw JSONRPCError(RPC_INVALID_PARAMETER, "block file not found");
4079  
4080                  UniValue result(UniValue::VOBJ);
4081                  result.pushKV("blocks_num", info->nBlocks);
4082                  result.pushKV("lowest_block", info->nHeightFirst);
4083                  result.pushKV("highest_block", info->nHeightLast);
4084                  result.pushKV("data_size", info->nSize);
4085                  result.pushKV("undo_size", info->nUndoSize);
4086  
4087                  return result;
4088              }
4089      };
4090  }
4091  
4092  static RPCHelpMan getblocklocations()
4093  {
4094      return RPCHelpMan{"getblocklocations",
4095                  "\nEXPERIMENTAL warning: this call may be removed or changed in future releases.\n"
4096                  "\nReturns a JSON for the file system location of 'blockhash' block and undo data.\n"
4097                  "\nIt is possible to return also the locations of previous blocks, by specifying 'nblocks' > 1.\n",
4098                  {
4099                      {"blockhash", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "The block hash"},
4100                      {"nblocks", RPCArg::Type::NUM, RPCArg::Optional::NO, "Maximum number locations to return (up to genesis block)"},
4101                  },
4102                  {
4103                      RPCResult{
4104                          RPCResult::Type::ARR, "", "",
4105                          {
4106                              {RPCResult::Type::OBJ, "", "",
4107                              {
4108                                  {RPCResult::Type::NUM, "file", "blk*.dat/rev*.dat file index"},
4109                                  {RPCResult::Type::NUM, "data", "block data file offset"},
4110                                  {RPCResult::Type::NUM, "undo", /*optional=*/true, "undo data file offset (if exists)"},
4111                                  {RPCResult::Type::STR_HEX, "prev", "previous block hash"},
4112                              }},
4113                          }
4114                      },
4115                  },
4116                  RPCExamples{
4117                      HelpExampleCli("getblocklocations", "\"00000000c937983704a73af28acdec37b049d214adbda81d7e2a3dd146f6ed09\" 10")
4118                  },
4119                  [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue {
4120  
4121      ChainstateManager& chainman = EnsureAnyChainman(request.context);
4122      if (chainman.m_blockman.IsPruneMode()) {
4123          throw JSONRPCError(RPC_MISC_ERROR, "Block locations are not available in prune mode");
4124      }
4125  
4126      uint256 hash(ParseHashV(request.params[0], "blockhash"));
4127      size_t nblocks = request.params[1].getInt<size_t>();
4128  
4129      const CBlockIndex* pblockindex = WITH_LOCK(cs_main, return chainman.m_blockman.LookupBlockIndex(hash));
4130      if (!pblockindex) {
4131          throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Block not found");
4132      }
4133  
4134      UniValue result(UniValue::VARR);
4135      do {
4136          int64_t file_num;
4137          uint64_t data_pos, undo_pos;
4138          {
4139              LOCK(::cs_main);
4140              file_num = pblockindex->nFile;
4141              data_pos = pblockindex->nDataPos;
4142              undo_pos = pblockindex->nUndoPos;
4143          }
4144          UniValue location(UniValue::VOBJ);
4145          location.pushKV("file", file_num);
4146          location.pushKV("data", data_pos);
4147          if (undo_pos) {
4148              location.pushKV("undo", undo_pos);
4149          }
4150          if (pblockindex->pprev) {
4151              location.pushKV("prev", pblockindex->pprev->GetBlockHash().GetHex());
4152          } else {
4153              location.pushKV("prev", uint256().GetHex());
4154          }
4155          result.push_back(location);
4156          pblockindex = pblockindex->pprev;
4157      } while (result.size() < nblocks && pblockindex);
4158      return result;
4159  },
4160      };
4161  }
4162  
4163  
4164  void RegisterBlockchainRPCCommands(CRPCTable& t)
4165  {
4166      static const CRPCCommand commands[]{
4167          {"blockchain", &getblockchaininfo},
4168          {"blockchain", &getchaintxstats},
4169          {"blockchain", &getblockstats},
4170          {"blockchain", &getbestblockhash},
4171          {"blockchain", &getblockcount},
4172          {"blockchain", &getblock},
4173          {"blockchain", &getblockfrompeer},
4174          {"blockchain", &getblockhash},
4175          {"blockchain", &getblockheader},
4176          {"blockchain", &getchaintips},
4177          {"blockchain", &getdifficulty},
4178          {"blockchain", &getdeploymentinfo},
4179          {"blockchain", &gettxout},
4180          {"blockchain", &gettxoutsetinfo},
4181          {"blockchain", &listprunelocks},
4182          {"blockchain", &setprunelock},
4183          {"blockchain", &pruneblockchain},
4184          {"blockchain", &verifychain},
4185          {"blockchain", &scriptthreadsinfo},
4186          {"blockchain", &setscriptthreadsenabled},
4187          {"blockchain", &preciousblock},
4188          {"blockchain", &scantxoutset},
4189          {"blockchain", &scanblocks},
4190          {"blockchain", &getdescriptoractivity},
4191          {"blockchain", &getblockfilter},
4192          {"blockchain", &dumptxoutset},
4193          {"blockchain", &loadtxoutset},
4194          {"blockchain", &getchainstates},
4195          {"hidden", &getblockfileinfo},
4196          {"hidden", &invalidateblock},
4197          {"hidden", &reconsiderblock},
4198          {"blockchain", &waitfornewblock},
4199          {"blockchain", &waitforblock},
4200          {"blockchain", &waitforblockheight},
4201          {"hidden", &syncwithvalidationinterfacequeue},
4202          {"hidden", &getblocklocations},
4203  
4204  #ifdef ENABLE_WALLET
4205          {"wallet", &sweepprivkeys},
4206  #endif
4207      };
4208      for (const auto& c : commands) {
4209          t.appendCommand(c.name, &c);
4210      }
4211  }
4212