diff --git a/src/rpc/blockchain.cpp b/src/rpc/blockchain.cpp
index 5d28fa583..1dfa7a3ae 100644
--- a/src/rpc/blockchain.cpp
+++ b/src/rpc/blockchain.cpp
@@ -1,3130 +1,3130 @@
 // Copyright (c) 2010 Satoshi Nakamoto
 // Copyright (c) 2009-2019 The Bitcoin Core developers
 // Distributed under the MIT software license, see the accompanying
 // file COPYING or http://www.opensource.org/licenses/mit-license.php.
 
 #include <rpc/blockchain.h>
 
 #include <amount.h>
 #include <blockdb.h>
 #include <blockfilter.h>
 #include <chain.h>
 #include <chainparams.h>
 #include <coins.h>
 #include <config.h>
 #include <consensus/validation.h>
 #include <core_io.h>
 #include <hash.h>
 #include <index/blockfilterindex.h>
 #include <network.h>
 #include <node/coinstats.h>
 #include <node/context.h>
 #include <node/utxo_snapshot.h>
 #include <policy/policy.h>
 #include <primitives/transaction.h>
 #include <rpc/server.h>
 #include <rpc/util.h>
 #include <script/descriptor.h>
 #include <streams.h>
 #include <txdb.h>
 #include <txmempool.h>
 #include <undo.h>
 #include <util/ref.h>
 #include <util/strencodings.h>
 #include <util/system.h>
 #include <util/translation.h>
 #include <validation.h>
 #include <validationinterface.h>
 #include <versionbitsinfo.h> // For VersionBitsDeploymentInfo
 #include <warnings.h>
 
 #include <condition_variable>
 #include <cstdint>
 #include <memory>
 #include <mutex>
 
 struct CUpdatedBlock {
     BlockHash hash;
     int height;
 };
 
 static Mutex cs_blockchange;
 static std::condition_variable cond_blockchange;
 static CUpdatedBlock latestblock GUARDED_BY(cs_blockchange);
 
 NodeContext &EnsureNodeContext(const util::Ref &context) {
     if (!context.Has<NodeContext>()) {
         throw JSONRPCError(RPC_INTERNAL_ERROR, "Node context not found");
     }
     return context.Get<NodeContext>();
 }
 
 CTxMemPool &EnsureMemPool(const util::Ref &context) {
     const NodeContext &node = EnsureNodeContext(context);
     if (!node.mempool) {
         throw JSONRPCError(RPC_CLIENT_MEMPOOL_DISABLED,
                            "Mempool disabled or instance not found");
     }
     return *node.mempool;
 }
 
 ChainstateManager &EnsureChainman(const util::Ref &context) {
     const NodeContext &node = EnsureNodeContext(context);
     if (!node.chainman) {
         throw JSONRPCError(RPC_INTERNAL_ERROR, "Node chainman not found");
     }
     return *node.chainman;
 }
 
 /**
  * Calculate the difficulty for a given block index.
  */
 double GetDifficulty(const CBlockIndex *blockindex) {
     CHECK_NONFATAL(blockindex);
 
     int nShift = (blockindex->nBits >> 24) & 0xff;
     double dDiff = double(0x0000ffff) / double(blockindex->nBits & 0x00ffffff);
 
     while (nShift < 29) {
         dDiff *= 256.0;
         nShift++;
     }
     while (nShift > 29) {
         dDiff /= 256.0;
         nShift--;
     }
 
     return dDiff;
 }
 
 static int ComputeNextBlockAndDepth(const CBlockIndex *tip,
                                     const CBlockIndex *blockindex,
                                     const CBlockIndex *&next) {
     next = tip->GetAncestor(blockindex->nHeight + 1);
     if (next && next->pprev == blockindex) {
         return tip->nHeight - blockindex->nHeight + 1;
     }
     next = nullptr;
     return blockindex == tip ? 1 : -1;
 }
 
 UniValue blockheaderToJSON(const CBlockIndex *tip,
                            const CBlockIndex *blockindex) {
     // Serialize passed information without accessing chain state of the active
     // chain!
     // For performance reasons
     AssertLockNotHeld(cs_main);
 
     UniValue result(UniValue::VOBJ);
     result.pushKV("hash", blockindex->GetBlockHash().GetHex());
     const CBlockIndex *pnext;
     int confirmations = ComputeNextBlockAndDepth(tip, blockindex, pnext);
     result.pushKV("confirmations", confirmations);
     result.pushKV("height", blockindex->nHeight);
     result.pushKV("version", blockindex->nVersion);
     result.pushKV("versionHex", strprintf("%08x", blockindex->nVersion));
     result.pushKV("merkleroot", blockindex->hashMerkleRoot.GetHex());
     result.pushKV("time", int64_t(blockindex->nTime));
     result.pushKV("mediantime", int64_t(blockindex->GetMedianTimePast()));
     result.pushKV("nonce", uint64_t(blockindex->nNonce));
     result.pushKV("bits", strprintf("%08x", blockindex->nBits));
     result.pushKV("difficulty", GetDifficulty(blockindex));
     result.pushKV("chainwork", blockindex->nChainWork.GetHex());
     result.pushKV("nTx", uint64_t(blockindex->nTx));
 
     if (blockindex->pprev) {
         result.pushKV("previousblockhash",
                       blockindex->pprev->GetBlockHash().GetHex());
     }
     if (pnext) {
         result.pushKV("nextblockhash", pnext->GetBlockHash().GetHex());
     }
     return result;
 }
 
 UniValue blockToJSON(const CBlock &block, const CBlockIndex *tip,
                      const CBlockIndex *blockindex, bool txDetails) {
     // Serialize passed information without accessing chain state of the active
     // chain!
     // For performance reasons
     AssertLockNotHeld(cs_main);
 
     UniValue result(UniValue::VOBJ);
     result.pushKV("hash", blockindex->GetBlockHash().GetHex());
     const CBlockIndex *pnext;
     int confirmations = ComputeNextBlockAndDepth(tip, blockindex, pnext);
     result.pushKV("confirmations", confirmations);
     result.pushKV("size", (int)::GetSerializeSize(block, PROTOCOL_VERSION));
     result.pushKV("height", blockindex->nHeight);
     result.pushKV("version", block.nVersion);
     result.pushKV("versionHex", strprintf("%08x", block.nVersion));
     result.pushKV("merkleroot", block.hashMerkleRoot.GetHex());
     UniValue txs(UniValue::VARR);
     for (const auto &tx : block.vtx) {
         if (txDetails) {
             UniValue objTx(UniValue::VOBJ);
             TxToUniv(*tx, uint256(), objTx, true, RPCSerializationFlags());
             txs.push_back(objTx);
         } else {
             txs.push_back(tx->GetId().GetHex());
         }
     }
     result.pushKV("tx", txs);
     result.pushKV("time", block.GetBlockTime());
     result.pushKV("mediantime", int64_t(blockindex->GetMedianTimePast()));
     result.pushKV("nonce", uint64_t(block.nNonce));
     result.pushKV("bits", strprintf("%08x", block.nBits));
     result.pushKV("difficulty", GetDifficulty(blockindex));
     result.pushKV("chainwork", blockindex->nChainWork.GetHex());
     result.pushKV("nTx", uint64_t(blockindex->nTx));
 
     if (blockindex->pprev) {
         result.pushKV("previousblockhash",
                       blockindex->pprev->GetBlockHash().GetHex());
     }
     if (pnext) {
         result.pushKV("nextblockhash", pnext->GetBlockHash().GetHex());
     }
     return result;
 }
 
 static RPCHelpMan getblockcount() {
     return RPCHelpMan{
         "getblockcount",
         "Returns the height of the most-work fully-validated chain.\n"
         "The genesis block has height 0.\n",
         {},
         RPCResult{RPCResult::Type::NUM, "", "The current block count"},
         RPCExamples{HelpExampleCli("getblockcount", "") +
                     HelpExampleRpc("getblockcount", "")},
         [&](const RPCHelpMan &self, const Config &config,
             const JSONRPCRequest &request) -> UniValue {
             LOCK(cs_main);
             return ::ChainActive().Height();
         },
     };
 }
 
 static RPCHelpMan getbestblockhash() {
     return RPCHelpMan{
         "getbestblockhash",
         "Returns the hash of the best (tip) block in the "
         "most-work fully-validated chain.\n",
         {},
         RPCResult{RPCResult::Type::STR_HEX, "", "the block hash, hex-encoded"},
         RPCExamples{HelpExampleCli("getbestblockhash", "") +
                     HelpExampleRpc("getbestblockhash", "")},
         [&](const RPCHelpMan &self, const Config &config,
             const JSONRPCRequest &request) -> UniValue {
             LOCK(cs_main);
             return ::ChainActive().Tip()->GetBlockHash().GetHex();
         },
     };
 }
 
 RPCHelpMan getfinalizedblockhash() {
     return RPCHelpMan{
         "getfinalizedblockhash",
         "Returns the hash of the currently finalized block\n",
         {},
         RPCResult{RPCResult::Type::STR_HEX, "", "the block hash, hex-encoded"},
         RPCExamples{HelpExampleCli("getfinalizedblockhash", "") +
                     HelpExampleRpc("getfinalizedblockhash", "")},
         [&](const RPCHelpMan &self, const Config &config,
             const JSONRPCRequest &request) -> UniValue {
             LOCK(cs_main);
             const CBlockIndex *blockIndexFinalized =
                 ::ChainstateActive().GetFinalizedBlock();
             if (blockIndexFinalized) {
                 return blockIndexFinalized->GetBlockHash().GetHex();
             }
             return UniValue(UniValue::VSTR);
         },
     };
 }
 
 void RPCNotifyBlockChange(const CBlockIndex *pindex) {
     if (pindex) {
         LOCK(cs_blockchange);
         latestblock.hash = pindex->GetBlockHash();
         latestblock.height = pindex->nHeight;
     }
     cond_blockchange.notify_all();
 }
 
 static RPCHelpMan waitfornewblock() {
     return RPCHelpMan{
         "waitfornewblock",
         "Waits for a specific new block and returns useful info about it.\n"
         "\nReturns the current block on timeout or exit.\n",
         {
             {"timeout", RPCArg::Type::NUM, /* default */ "0",
              "Time in milliseconds to wait for a response. 0 indicates no "
              "timeout."},
         },
         RPCResult{RPCResult::Type::OBJ,
                   "",
                   "",
                   {
                       {RPCResult::Type::STR_HEX, "hash", "The blockhash"},
                       {RPCResult::Type::NUM, "height", "Block height"},
                   }},
         RPCExamples{HelpExampleCli("waitfornewblock", "1000") +
                     HelpExampleRpc("waitfornewblock", "1000")},
         [&](const RPCHelpMan &self, const Config &config,
             const JSONRPCRequest &request) -> UniValue {
             int timeout = 0;
             if (!request.params[0].isNull()) {
                 timeout = request.params[0].get_int();
             }
 
             CUpdatedBlock block;
             {
                 WAIT_LOCK(cs_blockchange, lock);
                 block = latestblock;
                 if (timeout) {
                     cond_blockchange.wait_for(
                         lock, std::chrono::milliseconds(timeout),
                         [&block]() EXCLUSIVE_LOCKS_REQUIRED(cs_blockchange) {
                             return latestblock.height != block.height ||
                                    latestblock.hash != block.hash ||
                                    !IsRPCRunning();
                         });
                 } else {
                     cond_blockchange.wait(
                         lock,
                         [&block]() EXCLUSIVE_LOCKS_REQUIRED(cs_blockchange) {
                             return latestblock.height != block.height ||
                                    latestblock.hash != block.hash ||
                                    !IsRPCRunning();
                         });
                 }
                 block = latestblock;
             }
             UniValue ret(UniValue::VOBJ);
             ret.pushKV("hash", block.hash.GetHex());
             ret.pushKV("height", block.height);
             return ret;
         },
     };
 }
 
 static RPCHelpMan waitforblock() {
     return RPCHelpMan{
         "waitforblock",
         "Waits for a specific new block and returns useful info about it.\n"
         "\nReturns the current block on timeout or exit.\n",
         {
             {"blockhash", RPCArg::Type::STR_HEX, RPCArg::Optional::NO,
              "Block hash to wait for."},
             {"timeout", RPCArg::Type::NUM, /* default */ "0",
              "Time in milliseconds to wait for a response. 0 indicates no "
              "timeout."},
         },
         RPCResult{RPCResult::Type::OBJ,
                   "",
                   "",
                   {
                       {RPCResult::Type::STR_HEX, "hash", "The blockhash"},
                       {RPCResult::Type::NUM, "height", "Block height"},
                   }},
         RPCExamples{HelpExampleCli("waitforblock",
                                    "\"0000000000079f8ef3d2c688c244eb7a4570b24c9"
                                    "ed7b4a8c619eb02596f8862\" 1000") +
                     HelpExampleRpc("waitforblock",
                                    "\"0000000000079f8ef3d2c688c244eb7a4570b24c9"
                                    "ed7b4a8c619eb02596f8862\", 1000")},
         [&](const RPCHelpMan &self, const Config &config,
             const JSONRPCRequest &request) -> UniValue {
             int timeout = 0;
 
             BlockHash hash(ParseHashV(request.params[0], "blockhash"));
 
             if (!request.params[1].isNull()) {
                 timeout = request.params[1].get_int();
             }
 
             CUpdatedBlock block;
             {
                 WAIT_LOCK(cs_blockchange, lock);
                 if (timeout) {
                     cond_blockchange.wait_for(
                         lock, std::chrono::milliseconds(timeout),
                         [&hash]() EXCLUSIVE_LOCKS_REQUIRED(cs_blockchange) {
                             return latestblock.hash == hash || !IsRPCRunning();
                         });
                 } else {
                     cond_blockchange.wait(
                         lock,
                         [&hash]() EXCLUSIVE_LOCKS_REQUIRED(cs_blockchange) {
                             return latestblock.hash == hash || !IsRPCRunning();
                         });
                 }
                 block = latestblock;
             }
 
             UniValue ret(UniValue::VOBJ);
             ret.pushKV("hash", block.hash.GetHex());
             ret.pushKV("height", block.height);
             return ret;
         },
     };
 }
 
 static RPCHelpMan waitforblockheight() {
     return RPCHelpMan{
         "waitforblockheight",
         "Waits for (at least) block height and returns the height and "
         "hash\nof the current tip.\n"
         "\nReturns the current block on timeout or exit.\n",
         {
             {"height", RPCArg::Type::NUM, RPCArg::Optional::NO,
              "Block height to wait for."},
             {"timeout", RPCArg::Type::NUM, /* default */ "0",
              "Time in milliseconds to wait for a response. 0 indicates no "
              "timeout."},
         },
         RPCResult{RPCResult::Type::OBJ,
                   "",
                   "",
                   {
                       {RPCResult::Type::STR_HEX, "hash", "The blockhash"},
                       {RPCResult::Type::NUM, "height", "Block height"},
                   }},
         RPCExamples{HelpExampleCli("waitforblockheight", "100 1000") +
                     HelpExampleRpc("waitforblockheight", "100, 1000")},
         [&](const RPCHelpMan &self, const Config &config,
             const JSONRPCRequest &request) -> UniValue {
             int timeout = 0;
 
             int height = request.params[0].get_int();
 
             if (!request.params[1].isNull()) {
                 timeout = request.params[1].get_int();
             }
 
             CUpdatedBlock block;
             {
                 WAIT_LOCK(cs_blockchange, lock);
                 if (timeout) {
                     cond_blockchange.wait_for(
                         lock, std::chrono::milliseconds(timeout),
                         [&height]() EXCLUSIVE_LOCKS_REQUIRED(cs_blockchange) {
                             return latestblock.height >= height ||
                                    !IsRPCRunning();
                         });
                 } else {
                     cond_blockchange.wait(
                         lock,
                         [&height]() EXCLUSIVE_LOCKS_REQUIRED(cs_blockchange) {
                             return latestblock.height >= height ||
                                    !IsRPCRunning();
                         });
                 }
                 block = latestblock;
             }
             UniValue ret(UniValue::VOBJ);
             ret.pushKV("hash", block.hash.GetHex());
             ret.pushKV("height", block.height);
             return ret;
         },
     };
 }
 
 static RPCHelpMan syncwithvalidationinterfacequeue() {
     return RPCHelpMan{
         "syncwithvalidationinterfacequeue",
         "Waits for the validation interface queue to catch up on everything "
         "that was there when we entered this function.\n",
         {},
         RPCResult{RPCResult::Type::NONE, "", ""},
         RPCExamples{HelpExampleCli("syncwithvalidationinterfacequeue", "") +
                     HelpExampleRpc("syncwithvalidationinterfacequeue", "")},
         [&](const RPCHelpMan &self, const Config &config,
             const JSONRPCRequest &request) -> UniValue {
             SyncWithValidationInterfaceQueue();
             return NullUniValue;
         },
     };
 }
 
 static RPCHelpMan getdifficulty() {
     return RPCHelpMan{
         "getdifficulty",
         "Returns the proof-of-work difficulty as a multiple of the minimum "
         "difficulty.\n",
         {},
         RPCResult{RPCResult::Type::NUM, "",
                   "the proof-of-work difficulty as a multiple of the minimum "
                   "difficulty."},
         RPCExamples{HelpExampleCli("getdifficulty", "") +
                     HelpExampleRpc("getdifficulty", "")},
         [&](const RPCHelpMan &self, const Config &config,
             const JSONRPCRequest &request) -> UniValue {
             LOCK(cs_main);
             return GetDifficulty(::ChainActive().Tip());
         },
     };
 }
 
 static std::vector<RPCResult> MempoolEntryDescription() {
     const auto &ticker = Currency::get().ticker;
     return {
         RPCResult{RPCResult::Type::NUM, "size", "transaction size."},
         RPCResult{RPCResult::Type::STR_AMOUNT, "fee",
                   "transaction fee in " + ticker + " (DEPRECATED)"},
         RPCResult{RPCResult::Type::STR_AMOUNT, "modifiedfee",
                   "transaction fee with fee deltas used for mining priority "
                   "(DEPRECATED)"},
         RPCResult{RPCResult::Type::NUM_TIME, "time",
                   "local time transaction entered pool in seconds since 1 Jan "
                   "1970 GMT"},
         RPCResult{RPCResult::Type::NUM, "height",
                   "block height when transaction entered pool"},
         RPCResult{RPCResult::Type::NUM, "descendantcount",
                   "number of in-mempool descendant transactions (including "
                   "this one)"},
         RPCResult{RPCResult::Type::NUM, "descendantsize",
                   "transaction size of in-mempool descendants "
                   "(including this one)"},
         RPCResult{RPCResult::Type::STR_AMOUNT, "descendantfees",
                   "modified fees (see above) of in-mempool descendants "
                   "(including this one) (DEPRECATED)"},
         RPCResult{
             RPCResult::Type::NUM, "ancestorcount",
             "number of in-mempool ancestor transactions (including this one)"},
         RPCResult{
             RPCResult::Type::NUM, "ancestorsize",
             "transaction size of in-mempool ancestors (including this one)"},
         RPCResult{RPCResult::Type::STR_AMOUNT, "ancestorfees",
                   "modified fees (see above) of in-mempool ancestors "
                   "(including this one) (DEPRECATED)"},
         RPCResult{RPCResult::Type::OBJ,
                   "fees",
                   "",
                   {
                       RPCResult{RPCResult::Type::STR_AMOUNT, "base",
                                 "transaction fee in " + ticker},
                       RPCResult{RPCResult::Type::STR_AMOUNT, "modified",
                                 "transaction fee with fee deltas used for "
                                 "mining priority in " +
                                     ticker},
                       RPCResult{RPCResult::Type::STR_AMOUNT, "ancestor",
                                 "modified fees (see above) of in-mempool "
                                 "ancestors (including this one) in " +
                                     ticker},
                       RPCResult{RPCResult::Type::STR_AMOUNT, "descendant",
                                 "modified fees (see above) of in-mempool "
                                 "descendants (including this one) in " +
                                     ticker},
                   }},
         RPCResult{
             RPCResult::Type::ARR,
             "depends",
             "unconfirmed transactions used as inputs for this transaction",
             {RPCResult{RPCResult::Type::STR_HEX, "transactionid",
                        "parent transaction id"}}},
         RPCResult{
             RPCResult::Type::ARR,
             "spentby",
             "unconfirmed transactions spending outputs from this transaction",
             {RPCResult{RPCResult::Type::STR_HEX, "transactionid",
                        "child transaction id"}}},
         RPCResult{RPCResult::Type::BOOL, "unbroadcast",
                   "Whether this transaction is currently unbroadcast (initial "
                   "broadcast not yet acknowledged by any peers)"},
     };
 }
 
 static void entryToJSON(const CTxMemPool &pool, UniValue &info,
                         const CTxMemPoolEntry &e)
     EXCLUSIVE_LOCKS_REQUIRED(pool.cs) {
     AssertLockHeld(pool.cs);
 
     UniValue fees(UniValue::VOBJ);
     fees.pushKV("base", e.GetFee());
     fees.pushKV("modified", e.GetModifiedFee());
     fees.pushKV("ancestor", e.GetModFeesWithAncestors());
     fees.pushKV("descendant", e.GetModFeesWithDescendants());
     info.pushKV("fees", fees);
 
     info.pushKV("size", (int)e.GetTxSize());
     info.pushKV("fee", e.GetFee());
     info.pushKV("modifiedfee", e.GetModifiedFee());
     info.pushKV("time", count_seconds(e.GetTime()));
     info.pushKV("height", (int)e.GetHeight());
     info.pushKV("descendantcount", e.GetCountWithDescendants());
     info.pushKV("descendantsize", e.GetSizeWithDescendants());
     info.pushKV("descendantfees", e.GetModFeesWithDescendants() / SATOSHI);
     info.pushKV("ancestorcount", e.GetCountWithAncestors());
     info.pushKV("ancestorsize", e.GetSizeWithAncestors());
     info.pushKV("ancestorfees", e.GetModFeesWithAncestors() / SATOSHI);
     const CTransaction &tx = e.GetTx();
     std::set<std::string> setDepends;
     for (const CTxIn &txin : tx.vin) {
         if (pool.exists(txin.prevout.GetTxId())) {
             setDepends.insert(txin.prevout.GetTxId().ToString());
         }
     }
 
     UniValue depends(UniValue::VARR);
     for (const std::string &dep : setDepends) {
         depends.push_back(dep);
     }
 
     info.pushKV("depends", depends);
 
     UniValue spent(UniValue::VARR);
     const CTxMemPool::txiter &it = pool.mapTx.find(tx.GetId());
     const CTxMemPoolEntry::Children &children = it->GetMemPoolChildrenConst();
     for (const CTxMemPoolEntry &child : children) {
         spent.push_back(child.GetTx().GetId().ToString());
     }
 
     info.pushKV("spentby", spent);
     info.pushKV("unbroadcast", pool.IsUnbroadcastTx(tx.GetId()));
 }
 
 UniValue MempoolToJSON(const CTxMemPool &pool, bool verbose,
                        bool include_mempool_sequence) {
     if (verbose) {
         if (include_mempool_sequence) {
             throw JSONRPCError(
                 RPC_INVALID_PARAMETER,
                 "Verbose results cannot contain mempool sequence values.");
         }
         LOCK(pool.cs);
         UniValue o(UniValue::VOBJ);
         for (const CTxMemPoolEntry &e : pool.mapTx) {
             const uint256 &txid = e.GetTx().GetId();
             UniValue info(UniValue::VOBJ);
             entryToJSON(pool, info, e);
             // Mempool has unique entries so there is no advantage in using
             // UniValue::pushKV, which checks if the key already exists in O(N).
             // UniValue::__pushKV is used instead which currently is O(1).
             o.__pushKV(txid.ToString(), info);
         }
         return o;
     } else {
         uint64_t mempool_sequence;
         std::vector<uint256> vtxids;
         {
             LOCK(pool.cs);
             pool.queryHashes(vtxids);
             mempool_sequence = pool.GetSequence();
         }
         UniValue a(UniValue::VARR);
         for (const uint256 &txid : vtxids) {
             a.push_back(txid.ToString());
         }
 
         if (!include_mempool_sequence) {
             return a;
         } else {
             UniValue o(UniValue::VOBJ);
             o.pushKV("txids", a);
             o.pushKV("mempool_sequence", mempool_sequence);
             return o;
         }
     }
 }
 
 static RPCHelpMan getrawmempool() {
     return RPCHelpMan{
         "getrawmempool",
         "Returns all transaction ids in memory pool as a json array of "
         "string transaction ids.\n"
         "\nHint: use getmempoolentry to fetch a specific transaction from the "
         "mempool.\n",
         {
             {"verbose", RPCArg::Type::BOOL, /* default */ "false",
              "True for a json object, false for array of transaction ids"},
             {"mempool_sequence", RPCArg::Type::BOOL, /* default */ "false",
              "If verbose=false, returns a json object with transaction list "
              "and mempool sequence number attached."},
         },
         {
             RPCResult{"for verbose = false",
                       RPCResult::Type::ARR,
                       "",
                       "",
                       {
                           {RPCResult::Type::STR_HEX, "", "The transaction id"},
                       }},
             RPCResult{"for verbose = true",
                       RPCResult::Type::OBJ_DYN,
                       "",
                       "",
                       {
                           {RPCResult::Type::OBJ, "transactionid", "",
                            MempoolEntryDescription()},
                       }},
             RPCResult{
                 "for verbose = false and mempool_sequence = true",
                 RPCResult::Type::OBJ,
                 "",
                 "",
                 {
                     {RPCResult::Type::ARR,
                      "txids",
                      "",
                      {
                          {RPCResult::Type::STR_HEX, "", "The transaction id"},
                      }},
                     {RPCResult::Type::NUM, "mempool_sequence",
                      "The mempool sequence value."},
                 }},
         },
         RPCExamples{HelpExampleCli("getrawmempool", "true") +
                     HelpExampleRpc("getrawmempool", "true")},
         [&](const RPCHelpMan &self, const Config &config,
             const JSONRPCRequest &request) -> UniValue {
             bool fVerbose = false;
             if (!request.params[0].isNull()) {
                 fVerbose = request.params[0].get_bool();
             }
 
             bool include_mempool_sequence = false;
             if (!request.params[1].isNull()) {
                 include_mempool_sequence = request.params[1].get_bool();
             }
 
             return MempoolToJSON(EnsureMemPool(request.context), fVerbose,
                                  include_mempool_sequence);
         },
     };
 }
 
 static RPCHelpMan getmempoolancestors() {
     return RPCHelpMan{
         "getmempoolancestors",
         "If txid is in the mempool, returns all in-mempool ancestors.\n",
         {
             {"txid", RPCArg::Type::STR_HEX, RPCArg::Optional::NO,
              "The transaction id (must be in mempool)"},
             {"verbose", RPCArg::Type::BOOL, /* default */ "false",
              "True for a json object, false for array of transaction ids"},
         },
         {
             RPCResult{
                 "for verbose = false",
                 RPCResult::Type::ARR,
                 "",
                 "",
                 {{RPCResult::Type::STR_HEX, "",
                   "The transaction id of an in-mempool ancestor transaction"}}},
             RPCResult{"for verbose = true",
                       RPCResult::Type::OBJ_DYN,
                       "",
                       "",
                       {
                           {RPCResult::Type::OBJ, "transactionid", "",
                            MempoolEntryDescription()},
                       }},
         },
         RPCExamples{HelpExampleCli("getmempoolancestors", "\"mytxid\"") +
                     HelpExampleRpc("getmempoolancestors", "\"mytxid\"")},
         [&](const RPCHelpMan &self, const Config &config,
             const JSONRPCRequest &request) -> UniValue {
             bool fVerbose = false;
             if (!request.params[1].isNull()) {
                 fVerbose = request.params[1].get_bool();
             }
 
             TxId txid(ParseHashV(request.params[0], "parameter 1"));
 
             const CTxMemPool &mempool = EnsureMemPool(request.context);
             LOCK(mempool.cs);
 
             CTxMemPool::txiter it = mempool.mapTx.find(txid);
             if (it == mempool.mapTx.end()) {
                 throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY,
                                    "Transaction not in mempool");
             }
 
             CTxMemPool::setEntries setAncestors;
             uint64_t noLimit = std::numeric_limits<uint64_t>::max();
             std::string dummy;
             mempool.CalculateMemPoolAncestors(*it, setAncestors, noLimit,
                                               noLimit, noLimit, noLimit, dummy,
                                               false);
 
             if (!fVerbose) {
                 UniValue o(UniValue::VARR);
                 for (CTxMemPool::txiter ancestorIt : setAncestors) {
                     o.push_back(ancestorIt->GetTx().GetId().ToString());
                 }
                 return o;
             } else {
                 UniValue o(UniValue::VOBJ);
                 for (CTxMemPool::txiter ancestorIt : setAncestors) {
                     const CTxMemPoolEntry &e = *ancestorIt;
                     const TxId &_txid = e.GetTx().GetId();
                     UniValue info(UniValue::VOBJ);
                     entryToJSON(mempool, info, e);
                     o.pushKV(_txid.ToString(), info);
                 }
                 return o;
             }
         },
     };
 }
 
 static RPCHelpMan getmempooldescendants() {
     return RPCHelpMan{
         "getmempooldescendants",
         "If txid is in the mempool, returns all in-mempool descendants.\n",
         {
             {"txid", RPCArg::Type::STR_HEX, RPCArg::Optional::NO,
              "The transaction id (must be in mempool)"},
             {"verbose", RPCArg::Type::BOOL, /* default */ "false",
              "True for a json object, false for array of transaction ids"},
         },
         {
             RPCResult{"for verbose = false",
                       RPCResult::Type::ARR,
                       "",
                       "",
                       {{RPCResult::Type::STR_HEX, "",
                         "The transaction id of an in-mempool descendant "
                         "transaction"}}},
             RPCResult{"for verbose = true",
                       RPCResult::Type::OBJ_DYN,
                       "",
                       "",
                       {
                           {RPCResult::Type::OBJ, "transactionid", "",
                            MempoolEntryDescription()},
                       }},
         },
         RPCExamples{HelpExampleCli("getmempooldescendants", "\"mytxid\"") +
                     HelpExampleRpc("getmempooldescendants", "\"mytxid\"")},
         [&](const RPCHelpMan &self, const Config &config,
             const JSONRPCRequest &request) -> UniValue {
             bool fVerbose = false;
             if (!request.params[1].isNull()) {
                 fVerbose = request.params[1].get_bool();
             }
 
             TxId txid(ParseHashV(request.params[0], "parameter 1"));
 
             const CTxMemPool &mempool = EnsureMemPool(request.context);
             LOCK(mempool.cs);
 
             CTxMemPool::txiter it = mempool.mapTx.find(txid);
             if (it == mempool.mapTx.end()) {
                 throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY,
                                    "Transaction not in mempool");
             }
 
             CTxMemPool::setEntries setDescendants;
             mempool.CalculateDescendants(it, setDescendants);
             // CTxMemPool::CalculateDescendants will include the given tx
             setDescendants.erase(it);
 
             if (!fVerbose) {
                 UniValue o(UniValue::VARR);
                 for (CTxMemPool::txiter descendantIt : setDescendants) {
                     o.push_back(descendantIt->GetTx().GetId().ToString());
                 }
 
                 return o;
             } else {
                 UniValue o(UniValue::VOBJ);
                 for (CTxMemPool::txiter descendantIt : setDescendants) {
                     const CTxMemPoolEntry &e = *descendantIt;
                     const TxId &_txid = e.GetTx().GetId();
                     UniValue info(UniValue::VOBJ);
                     entryToJSON(mempool, info, e);
                     o.pushKV(_txid.ToString(), info);
                 }
                 return o;
             }
         },
     };
 }
 
 static RPCHelpMan getmempoolentry() {
     return RPCHelpMan{
         "getmempoolentry",
         "Returns mempool data for given transaction\n",
         {
             {"txid", RPCArg::Type::STR_HEX, RPCArg::Optional::NO,
              "The transaction id (must be in mempool)"},
         },
         RPCResult{RPCResult::Type::OBJ, "", "", MempoolEntryDescription()},
         RPCExamples{HelpExampleCli("getmempoolentry", "\"mytxid\"") +
                     HelpExampleRpc("getmempoolentry", "\"mytxid\"")},
         [&](const RPCHelpMan &self, const Config &config,
             const JSONRPCRequest &request) -> UniValue {
             TxId txid(ParseHashV(request.params[0], "parameter 1"));
 
             const CTxMemPool &mempool = EnsureMemPool(request.context);
             LOCK(mempool.cs);
 
             CTxMemPool::txiter it = mempool.mapTx.find(txid);
             if (it == mempool.mapTx.end()) {
                 throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY,
                                    "Transaction not in mempool");
             }
 
             const CTxMemPoolEntry &e = *it;
             UniValue info(UniValue::VOBJ);
             entryToJSON(mempool, info, e);
             return info;
         },
     };
 }
 
 static RPCHelpMan getblockhash() {
     return RPCHelpMan{
         "getblockhash",
         "Returns hash of block in best-block-chain at height provided.\n",
         {
             {"height", RPCArg::Type::NUM, RPCArg::Optional::NO,
              "The height index"},
         },
         RPCResult{RPCResult::Type::STR_HEX, "", "The block hash"},
         RPCExamples{HelpExampleCli("getblockhash", "1000") +
                     HelpExampleRpc("getblockhash", "1000")},
         [&](const RPCHelpMan &self, const Config &config,
             const JSONRPCRequest &request) -> UniValue {
             LOCK(cs_main);
 
             int nHeight = request.params[0].get_int();
             if (nHeight < 0 || nHeight > ::ChainActive().Height()) {
                 throw JSONRPCError(RPC_INVALID_PARAMETER,
                                    "Block height out of range");
             }
 
             CBlockIndex *pblockindex = ::ChainActive()[nHeight];
             return pblockindex->GetBlockHash().GetHex();
         },
     };
 }
 
 static RPCHelpMan getblockheader() {
     return RPCHelpMan{
         "getblockheader",
         "If verbose is false, returns a string that is serialized, hex-encoded "
         "data for blockheader 'hash'.\n"
         "If verbose is true, returns an Object with information about "
         "blockheader <hash>.\n",
         {
             {"blockhash", RPCArg::Type::STR_HEX, RPCArg::Optional::NO,
              "The block hash"},
             {"verbose", RPCArg::Type::BOOL, /* default */ "true",
              "true for a json object, false for the hex-encoded data"},
         },
         {
             RPCResult{
                 "for verbose = true",
                 RPCResult::Type::OBJ,
                 "",
                 "",
                 {
                     {RPCResult::Type::STR_HEX, "hash",
                      "the block hash (same as provided)"},
                     {RPCResult::Type::NUM, "confirmations",
                      "The number of confirmations, or -1 if the block is not "
                      "on the main chain"},
                     {RPCResult::Type::NUM, "height",
                      "The block height or index"},
                     {RPCResult::Type::NUM, "version", "The block version"},
                     {RPCResult::Type::STR_HEX, "versionHex",
                      "The block version formatted in hexadecimal"},
                     {RPCResult::Type::STR_HEX, "merkleroot", "The merkle root"},
                     {RPCResult::Type::NUM_TIME, "time",
                      "The block time expressed in " + UNIX_EPOCH_TIME},
                     {RPCResult::Type::NUM_TIME, "mediantime",
                      "The median block time expressed in " + UNIX_EPOCH_TIME},
                     {RPCResult::Type::NUM, "nonce", "The nonce"},
                     {RPCResult::Type::STR_HEX, "bits", "The bits"},
                     {RPCResult::Type::NUM, "difficulty", "The difficulty"},
                     {RPCResult::Type::STR_HEX, "chainwork",
                      "Expected number of hashes required to produce the "
                      "current chain"},
                     {RPCResult::Type::NUM, "nTx",
                      "The number of transactions in the block"},
                     {RPCResult::Type::STR_HEX, "previousblockhash",
                      "The hash of the previous block"},
                     {RPCResult::Type::STR_HEX, "nextblockhash",
                      "The hash of the next block"},
                 }},
             RPCResult{"for verbose=false", RPCResult::Type::STR_HEX, "",
                       "A string that is serialized, hex-encoded data for block "
                       "'hash'"},
         },
         RPCExamples{HelpExampleCli("getblockheader",
                                    "\"00000000c937983704a73af28acdec37b049d214a"
                                    "dbda81d7e2a3dd146f6ed09\"") +
                     HelpExampleRpc("getblockheader",
                                    "\"00000000c937983704a73af28acdec37b049d214a"
                                    "dbda81d7e2a3dd146f6ed09\"")},
         [&](const RPCHelpMan &self, const Config &config,
             const JSONRPCRequest &request) -> UniValue {
             BlockHash hash(ParseHashV(request.params[0], "hash"));
 
             bool fVerbose = true;
             if (!request.params[1].isNull()) {
                 fVerbose = request.params[1].get_bool();
             }
 
             const CBlockIndex *pblockindex;
             const CBlockIndex *tip;
             {
                 LOCK(cs_main);
                 pblockindex = g_chainman.m_blockman.LookupBlockIndex(hash);
                 tip = ::ChainActive().Tip();
             }
 
             if (!pblockindex) {
                 throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY,
                                    "Block not found");
             }
 
             if (!fVerbose) {
                 CDataStream ssBlock(SER_NETWORK, PROTOCOL_VERSION);
                 ssBlock << pblockindex->GetBlockHeader();
                 std::string strHex = HexStr(ssBlock);
                 return strHex;
             }
 
             return blockheaderToJSON(tip, pblockindex);
         },
     };
 }
 
 static CBlock GetBlockChecked(const Config &config,
                               const CBlockIndex *pblockindex) {
     CBlock block;
     if (IsBlockPruned(pblockindex)) {
         throw JSONRPCError(RPC_MISC_ERROR, "Block not available (pruned data)");
     }
 
     if (!ReadBlockFromDisk(block, pblockindex,
                            config.GetChainParams().GetConsensus())) {
         // Block not found on disk. This could be because we have the block
         // header in our index but not yet have the block or did not accept the
         // block.
         throw JSONRPCError(RPC_MISC_ERROR, "Block not found on disk");
     }
 
     return block;
 }
 
 static CBlockUndo GetUndoChecked(const CBlockIndex *pblockindex) {
     CBlockUndo blockUndo;
     if (IsBlockPruned(pblockindex)) {
         throw JSONRPCError(RPC_MISC_ERROR,
                            "Undo data not available (pruned data)");
     }
 
     if (!UndoReadFromDisk(blockUndo, pblockindex)) {
         throw JSONRPCError(RPC_MISC_ERROR, "Can't read undo data from disk");
     }
 
     return blockUndo;
 }
 
 static RPCHelpMan getblock() {
     return RPCHelpMan{
         "getblock",
         "If verbosity is 0 or false, returns a string that is serialized, "
         "hex-encoded data for block 'hash'.\n"
         "If verbosity is 1 or true, returns an Object with information about "
         "block <hash>.\n"
         "If verbosity is 2, returns an Object with information about block "
         "<hash> and information about each transaction.\n",
         {
             {"blockhash", RPCArg::Type::STR_HEX, RPCArg::Optional::NO,
              "The block hash"},
             {"verbosity|verbose", RPCArg::Type::NUM, /* default */ "1",
              "0 for hex-encoded data, 1 for a json object, and 2 for json "
              "object with transaction data"},
         },
         {
             RPCResult{"for verbosity = 0", RPCResult::Type::STR_HEX, "",
                       "A string that is serialized, hex-encoded data for block "
                       "'hash'"},
             RPCResult{
                 "for verbosity = 1",
                 RPCResult::Type::OBJ,
                 "",
                 "",
                 {
                     {RPCResult::Type::STR_HEX, "hash",
                      "the block hash (same as provided)"},
                     {RPCResult::Type::NUM, "confirmations",
                      "The number of confirmations, or -1 if the block is not "
                      "on the main chain"},
                     {RPCResult::Type::NUM, "size", "The block size"},
                     {RPCResult::Type::NUM, "height",
                      "The block height or index"},
                     {RPCResult::Type::NUM, "version", "The block version"},
                     {RPCResult::Type::STR_HEX, "versionHex",
                      "The block version formatted in hexadecimal"},
                     {RPCResult::Type::STR_HEX, "merkleroot", "The merkle root"},
                     {RPCResult::Type::ARR,
                      "tx",
                      "The transaction ids",
                      {{RPCResult::Type::STR_HEX, "", "The transaction id"}}},
                     {RPCResult::Type::NUM_TIME, "time",
                      "The block time expressed in " + UNIX_EPOCH_TIME},
                     {RPCResult::Type::NUM_TIME, "mediantime",
                      "The median block time expressed in " + UNIX_EPOCH_TIME},
                     {RPCResult::Type::NUM, "nonce", "The nonce"},
                     {RPCResult::Type::STR_HEX, "bits", "The bits"},
                     {RPCResult::Type::NUM, "difficulty", "The difficulty"},
                     {RPCResult::Type::STR_HEX, "chainwork",
                      "Expected number of hashes required to produce the chain "
                      "up to this block (in hex)"},
                     {RPCResult::Type::NUM, "nTx",
                      "The number of transactions in the block"},
                     {RPCResult::Type::STR_HEX, "previousblockhash",
                      "The hash of the previous block"},
                     {RPCResult::Type::STR_HEX, "nextblockhash",
                      "The hash of the next block"},
                 }},
             RPCResult{"for verbosity = 2",
                       RPCResult::Type::OBJ,
                       "",
                       "",
                       {
                           {RPCResult::Type::ELISION, "",
                            "Same output as verbosity = 1"},
                           {RPCResult::Type::ARR,
                            "tx",
                            "",
                            {
                                {RPCResult::Type::OBJ,
                                 "",
                                 "",
                                 {
                                     {RPCResult::Type::ELISION, "",
                                      "The transactions in the format of the "
                                      "getrawtransaction RPC. Different from "
                                      "verbosity = 1 \"tx\" result"},
                                 }},
                            }},
                       }},
         },
         RPCExamples{
             HelpExampleCli("getblock", "\"00000000c937983704a73af28acdec37b049d"
                                        "214adbda81d7e2a3dd146f6ed09\"") +
             HelpExampleRpc("getblock", "\"00000000c937983704a73af28acdec37b049d"
                                        "214adbda81d7e2a3dd146f6ed09\"")},
         [&](const RPCHelpMan &self, const Config &config,
             const JSONRPCRequest &request) -> UniValue {
             BlockHash hash(ParseHashV(request.params[0], "blockhash"));
 
             int verbosity = 1;
             if (!request.params[1].isNull()) {
                 if (request.params[1].isNum()) {
                     verbosity = request.params[1].get_int();
                 } else {
                     verbosity = request.params[1].get_bool() ? 1 : 0;
                 }
             }
 
             CBlock block;
             const CBlockIndex *pblockindex;
             const CBlockIndex *tip;
             {
                 LOCK(cs_main);
                 pblockindex = g_chainman.m_blockman.LookupBlockIndex(hash);
                 tip = ::ChainActive().Tip();
 
                 if (!pblockindex) {
                     throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY,
                                        "Block not found");
                 }
 
                 block = GetBlockChecked(config, pblockindex);
             }
 
             if (verbosity <= 0) {
                 CDataStream ssBlock(SER_NETWORK,
                                     PROTOCOL_VERSION | RPCSerializationFlags());
                 ssBlock << block;
                 std::string strHex = HexStr(ssBlock);
                 return strHex;
             }
 
             return blockToJSON(block, tip, pblockindex, verbosity >= 2);
         },
     };
 }
 
 static RPCHelpMan pruneblockchain() {
     return RPCHelpMan{
         "pruneblockchain",
         "",
         {
             {"height", RPCArg::Type::NUM, RPCArg::Optional::NO,
              "The block height to prune up to. May be set to a discrete "
              "height, or to a " +
                  UNIX_EPOCH_TIME +
                  "\n"
                  "                  to prune blocks whose block time is at "
                  "least 2 hours older than the provided timestamp."},
         },
         RPCResult{RPCResult::Type::NUM, "", "Height of the last block pruned"},
         RPCExamples{HelpExampleCli("pruneblockchain", "1000") +
                     HelpExampleRpc("pruneblockchain", "1000")},
         [&](const RPCHelpMan &self, const Config &config,
             const JSONRPCRequest &request) -> UniValue {
             if (!fPruneMode) {
                 throw JSONRPCError(
                     RPC_MISC_ERROR,
                     "Cannot prune blocks because node is not in prune mode.");
             }
 
             LOCK(cs_main);
 
             int heightParam = request.params[0].get_int();
             if (heightParam < 0) {
                 throw JSONRPCError(RPC_INVALID_PARAMETER,
                                    "Negative block height.");
             }
 
             // Height value more than a billion is too high to be a block
             // height, and too low to be a block time (corresponds to timestamp
             // from Sep 2001).
             if (heightParam > 1000000000) {
                 // Add a 2 hour buffer to include blocks which might have had
                 // old timestamps
                 CBlockIndex *pindex = ::ChainActive().FindEarliestAtLeast(
                     heightParam - TIMESTAMP_WINDOW, 0);
                 if (!pindex) {
                     throw JSONRPCError(RPC_INVALID_PARAMETER,
                                        "Could not find block with at least the "
                                        "specified timestamp.");
                 }
                 heightParam = pindex->nHeight;
             }
 
             unsigned int height = (unsigned int)heightParam;
             unsigned int chainHeight = (unsigned int)::ChainActive().Height();
             if (chainHeight < config.GetChainParams().PruneAfterHeight()) {
                 throw JSONRPCError(RPC_MISC_ERROR,
                                    "Blockchain is too short for pruning.");
             } else if (height > chainHeight) {
                 throw JSONRPCError(
                     RPC_INVALID_PARAMETER,
                     "Blockchain is shorter than the attempted prune height.");
             } else if (height > chainHeight - MIN_BLOCKS_TO_KEEP) {
                 LogPrint(BCLog::RPC,
                          "Attempt to prune blocks close to the tip. "
                          "Retaining the minimum number of blocks.\n");
                 height = chainHeight - MIN_BLOCKS_TO_KEEP;
             }
 
-            PruneBlockFilesManual(height);
+            PruneBlockFilesManual(::ChainstateActive(), height);
             const CBlockIndex *block = ::ChainActive().Tip();
             CHECK_NONFATAL(block);
             while (block->pprev && (block->pprev->nStatus.hasData())) {
                 block = block->pprev;
             }
             return uint64_t(block->nHeight);
         },
     };
 }
 
 static CoinStatsHashType ParseHashType(const std::string &hash_type_input) {
     if (hash_type_input == "hash_serialized") {
         return CoinStatsHashType::HASH_SERIALIZED;
     } else if (hash_type_input == "muhash") {
         return CoinStatsHashType::MUHASH;
     } else if (hash_type_input == "none") {
         return CoinStatsHashType::NONE;
     } else {
         throw JSONRPCError(
             RPC_INVALID_PARAMETER,
             strprintf("%s is not a valid hash_type", hash_type_input));
     }
 }
 
 static RPCHelpMan gettxoutsetinfo() {
     return RPCHelpMan{
         "gettxoutsetinfo",
         "Returns statistics about the unspent transaction output set.\n"
         "Note this call may take some time.\n",
         {
             {"hash_type", RPCArg::Type::STR, /* default */ "hash_serialized",
              "Which UTXO set hash should be calculated. Options: "
              "'hash_serialized' (the legacy algorithm), 'muhash', 'none'."},
         },
         RPCResult{RPCResult::Type::OBJ,
                   "",
                   "",
                   {
                       {RPCResult::Type::NUM, "height",
                        "The current block height (index)"},
                       {RPCResult::Type::STR_HEX, "bestblock",
                        "The hash of the block at the tip of the chain"},
                       {RPCResult::Type::NUM, "transactions",
                        "The number of transactions with unspent outputs"},
                       {RPCResult::Type::NUM, "txouts",
                        "The number of unspent transaction outputs"},
                       {RPCResult::Type::NUM, "bogosize",
                        "A meaningless metric for UTXO set size"},
                       {RPCResult::Type::STR_HEX, "hash_serialized",
                        /* optional */ true,
                        "The serialized hash (only present if 'hash_serialized' "
                        "hash_type is chosen)"},
                       {RPCResult::Type::STR_HEX, "muhash", /* optional */ true,
                        "The serialized hash (only present if 'muhash' "
                        "hash_type is chosen)"},
                       {RPCResult::Type::NUM, "disk_size",
                        "The estimated size of the chainstate on disk"},
                       {RPCResult::Type::STR_AMOUNT, "total_amount",
                        "The total amount"},
                   }},
         RPCExamples{HelpExampleCli("gettxoutsetinfo", "") +
                     HelpExampleRpc("gettxoutsetinfo", "")},
         [&](const RPCHelpMan &self, const Config &config,
             const JSONRPCRequest &request) -> UniValue {
             UniValue ret(UniValue::VOBJ);
 
             CCoinsStats stats;
             ::ChainstateActive().ForceFlushStateToDisk();
 
             const CoinStatsHashType hash_type{
                 request.params[0].isNull()
                     ? CoinStatsHashType::HASH_SERIALIZED
                     : ParseHashType(request.params[0].get_str())};
 
             CCoinsView *coins_view =
                 WITH_LOCK(cs_main, return &ChainstateActive().CoinsDB());
             NodeContext &node = EnsureNodeContext(request.context);
             if (GetUTXOStats(coins_view, stats, hash_type,
                              node.rpc_interruption_point)) {
                 ret.pushKV("height", int64_t(stats.nHeight));
                 ret.pushKV("bestblock", stats.hashBlock.GetHex());
                 ret.pushKV("transactions", int64_t(stats.nTransactions));
                 ret.pushKV("txouts", int64_t(stats.nTransactionOutputs));
                 ret.pushKV("bogosize", int64_t(stats.nBogoSize));
                 if (hash_type == CoinStatsHashType::HASH_SERIALIZED) {
                     ret.pushKV("hash_serialized",
                                stats.hashSerialized.GetHex());
                 }
                 if (hash_type == CoinStatsHashType::MUHASH) {
                     ret.pushKV("muhash", stats.hashSerialized.GetHex());
                 }
                 ret.pushKV("disk_size", stats.nDiskSize);
                 ret.pushKV("total_amount", stats.nTotalAmount);
             } else {
                 throw JSONRPCError(RPC_INTERNAL_ERROR,
                                    "Unable to read UTXO set");
             }
             return ret;
         },
     };
 }
 
 RPCHelpMan gettxout() {
     return RPCHelpMan{
         "gettxout",
         "Returns details about an unspent transaction output.\n",
         {
             {"txid", RPCArg::Type::STR_HEX, RPCArg::Optional::NO,
              "The transaction id"},
             {"n", RPCArg::Type::NUM, RPCArg::Optional::NO, "vout number"},
             {"include_mempool", RPCArg::Type::BOOL, /* default */ "true",
              "Whether to include the mempool. Note that an unspent output that "
              "is spent in the mempool won't appear."},
         },
         RPCResult{
             RPCResult::Type::OBJ,
             "",
             "",
             {
                 {RPCResult::Type::STR_HEX, "bestblock",
                  "The hash of the block at the tip of the chain"},
                 {RPCResult::Type::NUM, "confirmations",
                  "The number of confirmations"},
                 {RPCResult::Type::STR_AMOUNT, "value",
                  "The transaction value in " + Currency::get().ticker},
                 {RPCResult::Type::OBJ,
                  "scriptPubKey",
                  "",
                  {
                      {RPCResult::Type::STR_HEX, "asm", ""},
                      {RPCResult::Type::STR_HEX, "hex", ""},
                      {RPCResult::Type::NUM, "reqSigs",
                       "Number of required signatures"},
                      {RPCResult::Type::STR_HEX, "type",
                       "The type, eg pubkeyhash"},
                      {RPCResult::Type::ARR,
                       "addresses",
                       "array of bitcoin addresses",
                       {{RPCResult::Type::STR, "address", "bitcoin address"}}},
                  }},
                 {RPCResult::Type::BOOL, "coinbase", "Coinbase or not"},
             }},
         RPCExamples{"\nGet unspent transactions\n" +
                     HelpExampleCli("listunspent", "") + "\nView the details\n" +
                     HelpExampleCli("gettxout", "\"txid\" 1") +
                     "\nAs a JSON-RPC call\n" +
                     HelpExampleRpc("gettxout", "\"txid\", 1")},
         [&](const RPCHelpMan &self, const Config &config,
             const JSONRPCRequest &request) -> UniValue {
             LOCK(cs_main);
 
             UniValue ret(UniValue::VOBJ);
 
             TxId txid(ParseHashV(request.params[0], "txid"));
             int n = request.params[1].get_int();
             COutPoint out(txid, n);
             bool fMempool = true;
             if (!request.params[2].isNull()) {
                 fMempool = request.params[2].get_bool();
             }
 
             Coin coin;
             CCoinsViewCache *coins_view = &::ChainstateActive().CoinsTip();
 
             if (fMempool) {
                 const CTxMemPool &mempool = EnsureMemPool(request.context);
                 LOCK(mempool.cs);
                 CCoinsViewMemPool view(coins_view, mempool);
                 if (!view.GetCoin(out, coin) || mempool.isSpent(out)) {
                     return NullUniValue;
                 }
             } else {
                 if (!coins_view->GetCoin(out, coin)) {
                     return NullUniValue;
                 }
             }
 
             const CBlockIndex *pindex = g_chainman.m_blockman.LookupBlockIndex(
                 coins_view->GetBestBlock());
             ret.pushKV("bestblock", pindex->GetBlockHash().GetHex());
             if (coin.GetHeight() == MEMPOOL_HEIGHT) {
                 ret.pushKV("confirmations", 0);
             } else {
                 ret.pushKV("confirmations",
                            int64_t(pindex->nHeight - coin.GetHeight() + 1));
             }
             ret.pushKV("value", coin.GetTxOut().nValue);
             UniValue o(UniValue::VOBJ);
             ScriptPubKeyToUniv(coin.GetTxOut().scriptPubKey, o, true);
             ret.pushKV("scriptPubKey", o);
             ret.pushKV("coinbase", coin.IsCoinBase());
 
             return ret;
         },
     };
 }
 
 static RPCHelpMan verifychain() {
     return RPCHelpMan{
         "verifychain",
         "Verifies blockchain database.\n",
         {
             {"checklevel", RPCArg::Type::NUM,
              /* default */ strprintf("%d, range=0-4", DEFAULT_CHECKLEVEL),
              strprintf("How thorough the block verification is:\n - %s",
                        Join(CHECKLEVEL_DOC, "\n- "))},
             {"nblocks", RPCArg::Type::NUM,
              /* default */ strprintf("%d, 0=all", DEFAULT_CHECKBLOCKS),
              "The number of blocks to check."},
         },
         RPCResult{RPCResult::Type::BOOL, "", "Verified or not"},
         RPCExamples{HelpExampleCli("verifychain", "") +
                     HelpExampleRpc("verifychain", "")},
         [&](const RPCHelpMan &self, const Config &config,
             const JSONRPCRequest &request) -> UniValue {
             const int check_level(request.params[0].isNull()
                                       ? DEFAULT_CHECKLEVEL
                                       : request.params[0].get_int());
             const int check_depth{request.params[1].isNull()
                                       ? DEFAULT_CHECKBLOCKS
                                       : request.params[1].get_int()};
 
             LOCK(cs_main);
 
             return CVerifyDB().VerifyDB(config,
                                         &::ChainstateActive().CoinsTip(),
                                         check_level, check_depth);
         },
     };
 }
 
 static void BIP9SoftForkDescPushBack(UniValue &softforks,
                                      const Consensus::Params &consensusParams,
                                      Consensus::DeploymentPos id)
     EXCLUSIVE_LOCKS_REQUIRED(cs_main) {
     // For BIP9 deployments.
     // Deployments (e.g. testdummy) with timeout value before Jan 1, 2009 are
     // hidden. A timeout value of 0 guarantees a softfork will never be
     // activated. This is used when merging logic to implement a proposed
     // softfork without a specified deployment schedule.
     if (consensusParams.vDeployments[id].nTimeout <= 1230768000) {
         return;
     }
 
     UniValue bip9(UniValue::VOBJ);
     const ThresholdState thresholdState =
         VersionBitsTipState(consensusParams, id);
     switch (thresholdState) {
         case ThresholdState::DEFINED:
             bip9.pushKV("status", "defined");
             break;
         case ThresholdState::STARTED:
             bip9.pushKV("status", "started");
             break;
         case ThresholdState::LOCKED_IN:
             bip9.pushKV("status", "locked_in");
             break;
         case ThresholdState::ACTIVE:
             bip9.pushKV("status", "active");
             break;
         case ThresholdState::FAILED:
             bip9.pushKV("status", "failed");
             break;
     }
     if (ThresholdState::STARTED == thresholdState) {
         bip9.pushKV("bit", consensusParams.vDeployments[id].bit);
     }
     bip9.pushKV("start_time", consensusParams.vDeployments[id].nStartTime);
     bip9.pushKV("timeout", consensusParams.vDeployments[id].nTimeout);
     int64_t since_height = VersionBitsTipStateSinceHeight(consensusParams, id);
     bip9.pushKV("since", since_height);
     if (ThresholdState::STARTED == thresholdState) {
         UniValue statsUV(UniValue::VOBJ);
         BIP9Stats statsStruct = VersionBitsTipStatistics(consensusParams, id);
         statsUV.pushKV("period", statsStruct.period);
         statsUV.pushKV("threshold", statsStruct.threshold);
         statsUV.pushKV("elapsed", statsStruct.elapsed);
         statsUV.pushKV("count", statsStruct.count);
         statsUV.pushKV("possible", statsStruct.possible);
         bip9.pushKV("statistics", statsUV);
     }
 
     UniValue rv(UniValue::VOBJ);
     rv.pushKV("type", "bip9");
     rv.pushKV("bip9", bip9);
     if (ThresholdState::ACTIVE == thresholdState) {
         rv.pushKV("height", since_height);
     }
     rv.pushKV("active", ThresholdState::ACTIVE == thresholdState);
 
     softforks.pushKV(VersionBitsDeploymentInfo[id].name, rv);
 }
 
 RPCHelpMan getblockchaininfo() {
     return RPCHelpMan{
         "getblockchaininfo",
         "Returns an object containing various state info regarding blockchain "
         "processing.\n",
         {},
         RPCResult{
             RPCResult::Type::OBJ,
             "",
             "",
             {
                 {RPCResult::Type::STR, "chain",
                  "current network name (main, test, regtest)"},
                 {RPCResult::Type::NUM, "blocks",
                  "the height of the most-work fully-validated chain. The "
                  "genesis block has height 0"},
                 {RPCResult::Type::NUM, "headers",
                  "the current number of headers we have validated"},
                 {RPCResult::Type::STR, "bestblockhash",
                  "the hash of the currently best block"},
                 {RPCResult::Type::NUM, "difficulty", "the current difficulty"},
                 {RPCResult::Type::NUM, "mediantime",
                  "median time for the current best block"},
                 {RPCResult::Type::NUM, "verificationprogress",
                  "estimate of verification progress [0..1]"},
                 {RPCResult::Type::BOOL, "initialblockdownload",
                  "(debug information) estimate of whether this node is in "
                  "Initial Block Download mode"},
                 {RPCResult::Type::STR_HEX, "chainwork",
                  "total amount of work in active chain, in hexadecimal"},
                 {RPCResult::Type::NUM, "size_on_disk",
                  "the estimated size of the block and undo files on disk"},
                 {RPCResult::Type::BOOL, "pruned",
                  "if the blocks are subject to pruning"},
                 {RPCResult::Type::NUM, "pruneheight",
                  "lowest-height complete block stored (only present if pruning "
                  "is enabled)"},
                 {RPCResult::Type::BOOL, "automatic_pruning",
                  "whether automatic pruning is enabled (only present if "
                  "pruning is enabled)"},
                 {RPCResult::Type::NUM, "prune_target_size",
                  "the target size used by pruning (only present if automatic "
                  "pruning is enabled)"},
                 {RPCResult::Type::OBJ_DYN,
                  "softforks",
                  "status of softforks",
                  {
                      {RPCResult::Type::OBJ,
                       "xxxx",
                       "name of the softfork",
                       {
                           {RPCResult::Type::STR, "type",
                            "one of \"buried\", \"bip9\""},
                           {RPCResult::Type::OBJ,
                            "bip9",
                            "status of bip9 softforks (only for \"bip9\" type)",
                            {
                                {RPCResult::Type::STR, "status",
                                 "one of \"defined\", \"started\", "
                                 "\"locked_in\", \"active\", \"failed\""},
                                {RPCResult::Type::NUM, "bit",
                                 "the bit (0-28) in the block version field "
                                 "used to signal this softfork (only for "
                                 "\"started\" status)"},
                                {RPCResult::Type::NUM_TIME, "start_time",
                                 "the minimum median time past of a block at "
                                 "which the bit gains its meaning"},
                                {RPCResult::Type::NUM_TIME, "timeout",
                                 "the median time past of a block at which the "
                                 "deployment is considered failed if not yet "
                                 "locked in"},
                                {RPCResult::Type::NUM, "since",
                                 "height of the first block to which the status "
                                 "applies"},
                                {RPCResult::Type::OBJ,
                                 "statistics",
                                 "numeric statistics about BIP9 signalling for "
                                 "a softfork",
                                 {
                                     {RPCResult::Type::NUM, "period",
                                      "the length in blocks of the BIP9 "
                                      "signalling period"},
                                     {RPCResult::Type::NUM, "threshold",
                                      "the number of blocks with the version "
                                      "bit set required to activate the "
                                      "feature"},
                                     {RPCResult::Type::NUM, "elapsed",
                                      "the number of blocks elapsed since the "
                                      "beginning of the current period"},
                                     {RPCResult::Type::NUM, "count",
                                      "the number of blocks with the version "
                                      "bit set in the current period"},
                                     {RPCResult::Type::BOOL, "possible",
                                      "returns false if there are not enough "
                                      "blocks left in this period to pass "
                                      "activation threshold"},
                                 }},
                            }},
                           {RPCResult::Type::NUM, "height",
                            "height of the first block which the rules are or "
                            "will be enforced (only for \"buried\" type, or "
                            "\"bip9\" type with \"active\" status)"},
                           {RPCResult::Type::BOOL, "active",
                            "true if the rules are enforced for the mempool and "
                            "the next block"},
                       }},
                  }},
                 {RPCResult::Type::STR, "warnings",
                  "any network and blockchain warnings"},
             }},
         RPCExamples{HelpExampleCli("getblockchaininfo", "") +
                     HelpExampleRpc("getblockchaininfo", "")},
         [&](const RPCHelpMan &self, const Config &config,
             const JSONRPCRequest &request) -> UniValue {
             LOCK(cs_main);
 
             const CChainParams &chainparams = config.GetChainParams();
 
             const CBlockIndex *tip = ::ChainActive().Tip();
             UniValue obj(UniValue::VOBJ);
             obj.pushKV("chain", chainparams.NetworkIDString());
             obj.pushKV("blocks", int(::ChainActive().Height()));
             obj.pushKV("headers",
                        pindexBestHeader ? pindexBestHeader->nHeight : -1);
             obj.pushKV("bestblockhash", tip->GetBlockHash().GetHex());
             obj.pushKV("difficulty", double(GetDifficulty(tip)));
             obj.pushKV("mediantime", int64_t(tip->GetMedianTimePast()));
             obj.pushKV("verificationprogress",
                        GuessVerificationProgress(Params().TxData(), tip));
             obj.pushKV("initialblockdownload",
                        ::ChainstateActive().IsInitialBlockDownload());
             obj.pushKV("chainwork", tip->nChainWork.GetHex());
             obj.pushKV("size_on_disk", CalculateCurrentUsage());
             obj.pushKV("pruned", fPruneMode);
 
             if (fPruneMode) {
                 const CBlockIndex *block = tip;
                 CHECK_NONFATAL(block);
                 while (block->pprev && (block->pprev->nStatus.hasData())) {
                     block = block->pprev;
                 }
 
                 obj.pushKV("pruneheight", block->nHeight);
 
                 // if 0, execution bypasses the whole if block.
                 bool automatic_pruning = (gArgs.GetArg("-prune", 0) != 1);
                 obj.pushKV("automatic_pruning", automatic_pruning);
                 if (automatic_pruning) {
                     obj.pushKV("prune_target_size", nPruneTarget);
                 }
             }
 
             UniValue softforks(UniValue::VOBJ);
             for (int i = 0; i < (int)Consensus::MAX_VERSION_BITS_DEPLOYMENTS;
                  i++) {
                 BIP9SoftForkDescPushBack(softforks, chainparams.GetConsensus(),
                                          Consensus::DeploymentPos(i));
             }
             obj.pushKV("softforks", softforks);
 
             obj.pushKV("warnings", GetWarnings(false).original);
             return obj;
         },
     };
 }
 
 /** Comparison function for sorting the getchaintips heads.  */
 struct CompareBlocksByHeight {
     bool operator()(const CBlockIndex *a, const CBlockIndex *b) const {
         // Make sure that unequal blocks with the same height do not compare
         // equal. Use the pointers themselves to make a distinction.
         if (a->nHeight != b->nHeight) {
             return (a->nHeight > b->nHeight);
         }
 
         return a < b;
     }
 };
 
 static RPCHelpMan getchaintips() {
     return RPCHelpMan{
         "getchaintips",
         "Return information about all known tips in the block tree, including "
         "the main chain as well as orphaned branches.\n",
         {},
         RPCResult{
             RPCResult::Type::ARR,
             "",
             "",
             {{RPCResult::Type::OBJ,
               "",
               "",
               {
                   {RPCResult::Type::NUM, "height", "height of the chain tip"},
                   {RPCResult::Type::STR_HEX, "hash", "block hash of the tip"},
                   {RPCResult::Type::NUM, "branchlen",
                    "zero for main chain, otherwise length of branch connecting "
                    "the tip to the main chain"},
                   {RPCResult::Type::STR, "status",
                    "status of the chain, \"active\" for the main chain\n"
                    "Possible values for status:\n"
                    "1.  \"invalid\"               This branch contains at "
                    "least one invalid block\n"
                    "2.  \"parked\"                This branch contains at "
                    "least one parked block\n"
                    "3.  \"headers-only\"          Not all blocks for this "
                    "branch are available, but the headers are valid\n"
                    "4.  \"valid-headers\"         All blocks are available for "
                    "this branch, but they were never fully validated\n"
                    "5.  \"valid-fork\"            This branch is not part of "
                    "the active chain, but is fully validated\n"
                    "6.  \"active\"                This is the tip of the "
                    "active main chain, which is certainly valid"},
               }}}},
         RPCExamples{HelpExampleCli("getchaintips", "") +
                     HelpExampleRpc("getchaintips", "")},
         [&](const RPCHelpMan &self, const Config &config,
             const JSONRPCRequest &request) -> UniValue {
             ChainstateManager &chainman = EnsureChainman(request.context);
             LOCK(cs_main);
 
             /**
              * Idea: The set of chain tips is the active chain tip, plus orphan
              * blocks which do not have another orphan building off of them.
              * Algorithm:
              *  - Make one pass through BlockIndex(), picking out the orphan
              * blocks, and also storing a set of the orphan block's pprev
              * pointers.
              *  - Iterate through the orphan blocks. If the block isn't pointed
              * to by another orphan, it is a chain tip.
              *  - Add the active chain tip
              */
             std::set<const CBlockIndex *, CompareBlocksByHeight> setTips;
             std::set<const CBlockIndex *> setOrphans;
             std::set<const CBlockIndex *> setPrevs;
 
             for (const std::pair<const BlockHash, CBlockIndex *> &item :
                  chainman.BlockIndex()) {
                 if (!chainman.ActiveChain().Contains(item.second)) {
                     setOrphans.insert(item.second);
                     setPrevs.insert(item.second->pprev);
                 }
             }
 
             for (std::set<const CBlockIndex *>::iterator it =
                      setOrphans.begin();
                  it != setOrphans.end(); ++it) {
                 if (setPrevs.erase(*it) == 0) {
                     setTips.insert(*it);
                 }
             }
 
             // Always report the currently active tip.
             setTips.insert(chainman.ActiveChain().Tip());
 
             /* Construct the output array.  */
             UniValue res(UniValue::VARR);
             for (const CBlockIndex *block : setTips) {
                 UniValue obj(UniValue::VOBJ);
                 obj.pushKV("height", block->nHeight);
                 obj.pushKV("hash", block->phashBlock->GetHex());
 
                 const int branchLen =
                     block->nHeight -
                     chainman.ActiveChain().FindFork(block)->nHeight;
                 obj.pushKV("branchlen", branchLen);
 
                 std::string status;
                 if (chainman.ActiveChain().Contains(block)) {
                     // This block is part of the currently active chain.
                     status = "active";
                 } else if (block->nStatus.isInvalid()) {
                     // This block or one of its ancestors is invalid.
                     status = "invalid";
                 } else if (block->nStatus.isOnParkedChain()) {
                     // This block or one of its ancestors is parked.
                     status = "parked";
                 } else if (!block->HaveTxsDownloaded()) {
                     // This block cannot be connected because full block data
                     // for it or one of its parents is missing.
                     status = "headers-only";
                 } else if (block->IsValid(BlockValidity::SCRIPTS)) {
                     // This block is fully validated, but no longer part of the
                     // active chain. It was probably the active block once, but
                     // was reorganized.
                     status = "valid-fork";
                 } else if (block->IsValid(BlockValidity::TREE)) {
                     // The headers for this block are valid, but it has not been
                     // validated. It was probably never part of the most-work
                     // chain.
                     status = "valid-headers";
                 } else {
                     // No clue.
                     status = "unknown";
                 }
                 obj.pushKV("status", status);
 
                 res.push_back(obj);
             }
 
             return res;
         },
     };
 }
 
 UniValue MempoolInfoToJSON(const CTxMemPool &pool) {
     // Make sure this call is atomic in the pool.
     LOCK(pool.cs);
     UniValue ret(UniValue::VOBJ);
     ret.pushKV("loaded", pool.IsLoaded());
     ret.pushKV("size", (int64_t)pool.size());
     ret.pushKV("bytes", (int64_t)pool.GetTotalTxSize());
     ret.pushKV("usage", (int64_t)pool.DynamicMemoryUsage());
     size_t maxmempool =
         gArgs.GetArg("-maxmempool", DEFAULT_MAX_MEMPOOL_SIZE) * 1000000;
     ret.pushKV("maxmempool", (int64_t)maxmempool);
     ret.pushKV(
         "mempoolminfee",
         std::max(pool.GetMinFee(maxmempool), ::minRelayTxFee).GetFeePerK());
     ret.pushKV("minrelaytxfee", ::minRelayTxFee.GetFeePerK());
     ret.pushKV("unbroadcastcount", uint64_t{pool.GetUnbroadcastTxs().size()});
     return ret;
 }
 
 static RPCHelpMan getmempoolinfo() {
     return RPCHelpMan{
         "getmempoolinfo",
         "Returns details on the active state of the TX memory pool.\n",
         {},
         RPCResult{
             RPCResult::Type::OBJ,
             "",
             "",
             {
                 {RPCResult::Type::BOOL, "loaded",
                  "True if the mempool is fully loaded"},
                 {RPCResult::Type::NUM, "size", "Current tx count"},
                 {RPCResult::Type::NUM, "bytes", "Sum of all transaction sizes"},
                 {RPCResult::Type::NUM, "usage",
                  "Total memory usage for the mempool"},
                 {RPCResult::Type::NUM, "maxmempool",
                  "Maximum memory usage for the mempool"},
                 {RPCResult::Type::STR_AMOUNT, "mempoolminfee",
                  "Minimum fee rate in " + Currency::get().ticker +
                      "/kB for tx to be accepted. Is the maximum of "
                      "minrelaytxfee and minimum mempool fee"},
                 {RPCResult::Type::STR_AMOUNT, "minrelaytxfee",
                  "Current minimum relay fee for transactions"},
                 {RPCResult::Type::NUM, "unbroadcastcount",
                  "Current number of transactions that haven't passed initial "
                  "broadcast yet"},
             }},
         RPCExamples{HelpExampleCli("getmempoolinfo", "") +
                     HelpExampleRpc("getmempoolinfo", "")},
         [&](const RPCHelpMan &self, const Config &config,
             const JSONRPCRequest &request) -> UniValue {
             return MempoolInfoToJSON(EnsureMemPool(request.context));
         },
     };
 }
 
 static RPCHelpMan preciousblock() {
     return RPCHelpMan{
         "preciousblock",
         "Treats a block as if it were received before others with the same "
         "work.\n"
         "\nA later preciousblock call can override the effect of an earlier "
         "one.\n"
         "\nThe effects of preciousblock are not retained across restarts.\n",
         {
             {"blockhash", RPCArg::Type::STR_HEX, RPCArg::Optional::NO,
              "the hash of the block to mark as precious"},
         },
         RPCResult{RPCResult::Type::NONE, "", ""},
         RPCExamples{HelpExampleCli("preciousblock", "\"blockhash\"") +
                     HelpExampleRpc("preciousblock", "\"blockhash\"")},
         [&](const RPCHelpMan &self, const Config &config,
             const JSONRPCRequest &request) -> UniValue {
             BlockHash hash(ParseHashV(request.params[0], "blockhash"));
             CBlockIndex *pblockindex;
 
             {
                 LOCK(cs_main);
                 pblockindex = g_chainman.m_blockman.LookupBlockIndex(hash);
                 if (!pblockindex) {
                     throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY,
                                        "Block not found");
                 }
             }
 
             BlockValidationState state;
             ::ChainstateActive().PreciousBlock(config, state, pblockindex);
 
             if (!state.IsValid()) {
                 throw JSONRPCError(RPC_DATABASE_ERROR, state.GetRejectReason());
             }
 
             return NullUniValue;
         },
     };
 }
 
 RPCHelpMan finalizeblock() {
     return RPCHelpMan{
         "finalizeblock",
         "Treats a block as final. It cannot be reorged. Any chain\n"
         "that does not contain this block is invalid. Used on a less\n"
         "work chain, it can effectively PUT YOU OUT OF CONSENSUS.\n"
         "USE WITH CAUTION!\n",
         {
             {"blockhash", RPCArg::Type::STR_HEX, RPCArg::Optional::NO,
              "the hash of the block to mark as invalid"},
         },
         RPCResult{RPCResult::Type::NONE, "", ""},
         RPCExamples{HelpExampleCli("finalizeblock", "\"blockhash\"") +
                     HelpExampleRpc("finalizeblock", "\"blockhash\"")},
         [&](const RPCHelpMan &self, const Config &config,
             const JSONRPCRequest &request) -> UniValue {
             std::string strHash = request.params[0].get_str();
             BlockHash hash(uint256S(strHash));
             BlockValidationState state;
 
             CBlockIndex *pblockindex = nullptr;
             {
                 LOCK(cs_main);
                 pblockindex = g_chainman.m_blockman.LookupBlockIndex(hash);
                 if (!pblockindex) {
                     throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY,
                                        "Block not found");
                 }
             } // end of locked cs_main scope
 
             ::ChainstateActive().FinalizeBlock(config, state, pblockindex);
 
             if (state.IsValid()) {
                 ::ChainstateActive().ActivateBestChain(config, state);
             }
 
             if (!state.IsValid()) {
                 throw JSONRPCError(RPC_DATABASE_ERROR, state.ToString());
             }
 
             return NullUniValue;
         },
     };
 }
 
 static RPCHelpMan invalidateblock() {
     return RPCHelpMan{
         "invalidateblock",
         "Permanently marks a block as invalid, as if it violated a consensus "
         "rule.\n",
         {
             {"blockhash", RPCArg::Type::STR_HEX, RPCArg::Optional::NO,
              "the hash of the block to mark as invalid"},
         },
         RPCResult{RPCResult::Type::NONE, "", ""},
         RPCExamples{HelpExampleCli("invalidateblock", "\"blockhash\"") +
                     HelpExampleRpc("invalidateblock", "\"blockhash\"")},
         [&](const RPCHelpMan &self, const Config &config,
             const JSONRPCRequest &request) -> UniValue {
             const BlockHash hash(ParseHashV(request.params[0], "blockhash"));
             BlockValidationState state;
 
             CBlockIndex *pblockindex;
             {
                 LOCK(cs_main);
                 pblockindex = g_chainman.m_blockman.LookupBlockIndex(hash);
                 if (!pblockindex) {
                     throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY,
                                        "Block not found");
                 }
             }
             ::ChainstateActive().InvalidateBlock(config, state, pblockindex);
 
             if (state.IsValid()) {
                 ::ChainstateActive().ActivateBestChain(config, state);
             }
 
             if (!state.IsValid()) {
                 throw JSONRPCError(RPC_DATABASE_ERROR, state.ToString());
             }
 
             return NullUniValue;
         },
     };
 }
 
 RPCHelpMan parkblock() {
     return RPCHelpMan{
         "parkblock",
         "Marks a block as parked.\n",
         {
             {"blockhash", RPCArg::Type::STR_HEX, RPCArg::Optional::NO,
              "the hash of the block to park"},
         },
         RPCResult{RPCResult::Type::NONE, "", ""},
         RPCExamples{HelpExampleCli("parkblock", "\"blockhash\"") +
                     HelpExampleRpc("parkblock", "\"blockhash\"")},
         [&](const RPCHelpMan &self, const Config &config,
             const JSONRPCRequest &request) -> UniValue {
             const std::string strHash = request.params[0].get_str();
             const BlockHash hash(uint256S(strHash));
             BlockValidationState state;
 
             CBlockIndex *pblockindex = nullptr;
             {
                 LOCK(cs_main);
                 pblockindex = g_chainman.m_blockman.LookupBlockIndex(hash);
                 if (!pblockindex) {
                     throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY,
                                        "Block not found");
                 }
             }
             ::ChainstateActive().ParkBlock(config, state, pblockindex);
 
             if (state.IsValid()) {
                 ::ChainstateActive().ActivateBestChain(config, state);
             }
 
             if (!state.IsValid()) {
                 throw JSONRPCError(RPC_DATABASE_ERROR, state.GetRejectReason());
             }
 
             return NullUniValue;
         },
     };
 }
 
 static RPCHelpMan reconsiderblock() {
     return RPCHelpMan{
         "reconsiderblock",
         "Removes invalidity status of a block, its ancestors and its"
         "descendants, reconsider them for activation.\n"
         "This can be used to undo the effects of invalidateblock.\n",
         {
             {"blockhash", RPCArg::Type::STR_HEX, RPCArg::Optional::NO,
              "the hash of the block to reconsider"},
         },
         RPCResult{RPCResult::Type::NONE, "", ""},
         RPCExamples{HelpExampleCli("reconsiderblock", "\"blockhash\"") +
                     HelpExampleRpc("reconsiderblock", "\"blockhash\"")},
         [&](const RPCHelpMan &self, const Config &config,
             const JSONRPCRequest &request) -> UniValue {
             const BlockHash hash(ParseHashV(request.params[0], "blockhash"));
 
             {
                 LOCK(cs_main);
                 CBlockIndex *pblockindex =
                     g_chainman.m_blockman.LookupBlockIndex(hash);
                 if (!pblockindex) {
                     throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY,
                                        "Block not found");
                 }
 
                 ::ChainstateActive().ResetBlockFailureFlags(pblockindex);
             }
 
             BlockValidationState state;
             ::ChainstateActive().ActivateBestChain(config, state);
 
             if (!state.IsValid()) {
                 throw JSONRPCError(RPC_DATABASE_ERROR, state.ToString());
             }
 
             return NullUniValue;
         },
     };
 }
 
 RPCHelpMan unparkblock() {
     return RPCHelpMan{
         "unparkblock",
         "Removes parked status of a block and its descendants, reconsider "
         "them for activation.\n"
         "This can be used to undo the effects of parkblock.\n",
         {
             {"blockhash", RPCArg::Type::STR_HEX, RPCArg::Optional::NO,
              "the hash of the block to unpark"},
         },
         RPCResult{RPCResult::Type::NONE, "", ""},
         RPCExamples{HelpExampleCli("unparkblock", "\"blockhash\"") +
                     HelpExampleRpc("unparkblock", "\"blockhash\"")},
         [&](const RPCHelpMan &self, const Config &config,
             const JSONRPCRequest &request) -> UniValue {
             const std::string strHash = request.params[0].get_str();
             const BlockHash hash(uint256S(strHash));
 
             {
                 LOCK(cs_main);
 
                 CBlockIndex *pblockindex =
                     g_chainman.m_blockman.LookupBlockIndex(hash);
                 if (!pblockindex) {
                     throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY,
                                        "Block not found");
                 }
 
                 ::ChainstateActive().UnparkBlockAndChildren(pblockindex);
             }
 
             BlockValidationState state;
             ::ChainstateActive().ActivateBestChain(config, state);
 
             if (!state.IsValid()) {
                 throw JSONRPCError(RPC_DATABASE_ERROR, state.GetRejectReason());
             }
 
             return NullUniValue;
         },
     };
 }
 
 static RPCHelpMan getchaintxstats() {
     return RPCHelpMan{
         "getchaintxstats",
         "Compute statistics about the total number and rate of transactions "
         "in the chain.\n",
         {
             {"nblocks", RPCArg::Type::NUM, /* default */ "one month",
              "Size of the window in number of blocks"},
             {"blockhash", RPCArg::Type::STR_HEX, /* default */ "chain tip",
              "The hash of the block that ends the window."},
         },
         RPCResult{RPCResult::Type::OBJ,
                   "",
                   "",
                   {
                       {RPCResult::Type::NUM_TIME, "time",
                        "The timestamp for the final block in the window, "
                        "expressed in " +
                            UNIX_EPOCH_TIME},
                       {RPCResult::Type::NUM, "txcount",
                        "The total number of transactions in the chain up to "
                        "that point"},
                       {RPCResult::Type::STR_HEX, "window_final_block_hash",
                        "The hash of the final block in the window"},
                       {RPCResult::Type::NUM, "window_final_block_height",
                        "The height of the final block in the window."},
                       {RPCResult::Type::NUM, "window_block_count",
                        "Size of the window in number of blocks"},
                       {RPCResult::Type::NUM, "window_tx_count",
                        "The number of transactions in the window. Only "
                        "returned if \"window_block_count\" is > 0"},
                       {RPCResult::Type::NUM, "window_interval",
                        "The elapsed time in the window in seconds. Only "
                        "returned if \"window_block_count\" is > 0"},
                       {RPCResult::Type::NUM, "txrate",
                        "The average rate of transactions per second in the "
                        "window. Only returned if \"window_interval\" is > 0"},
                   }},
         RPCExamples{HelpExampleCli("getchaintxstats", "") +
                     HelpExampleRpc("getchaintxstats", "2016")},
         [&](const RPCHelpMan &self, const Config &config,
             const JSONRPCRequest &request) -> UniValue {
             const CBlockIndex *pindex;
 
             // By default: 1 month
             int blockcount =
                 30 * 24 * 60 * 60 /
                 config.GetChainParams().GetConsensus().nPowTargetSpacing;
 
             if (request.params[1].isNull()) {
                 LOCK(cs_main);
                 pindex = ::ChainActive().Tip();
             } else {
                 BlockHash hash(ParseHashV(request.params[1], "blockhash"));
                 LOCK(cs_main);
                 pindex = g_chainman.m_blockman.LookupBlockIndex(hash);
                 if (!pindex) {
                     throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY,
                                        "Block not found");
                 }
                 if (!::ChainActive().Contains(pindex)) {
                     throw JSONRPCError(RPC_INVALID_PARAMETER,
                                        "Block is not in main chain");
                 }
             }
 
             CHECK_NONFATAL(pindex != nullptr);
 
             if (request.params[0].isNull()) {
                 blockcount =
                     std::max(0, std::min(blockcount, pindex->nHeight - 1));
             } else {
                 blockcount = request.params[0].get_int();
 
                 if (blockcount < 0 ||
                     (blockcount > 0 && blockcount >= pindex->nHeight)) {
                     throw JSONRPCError(RPC_INVALID_PARAMETER,
                                        "Invalid block count: "
                                        "should be between 0 and "
                                        "the block's height - 1");
                 }
             }
 
             const CBlockIndex *pindexPast =
                 pindex->GetAncestor(pindex->nHeight - blockcount);
             int nTimeDiff =
                 pindex->GetMedianTimePast() - pindexPast->GetMedianTimePast();
             int nTxDiff =
                 pindex->GetChainTxCount() - pindexPast->GetChainTxCount();
 
             UniValue ret(UniValue::VOBJ);
             ret.pushKV("time", pindex->GetBlockTime());
             ret.pushKV("txcount", pindex->GetChainTxCount());
             ret.pushKV("window_final_block_hash",
                        pindex->GetBlockHash().GetHex());
             ret.pushKV("window_final_block_height", pindex->nHeight);
             ret.pushKV("window_block_count", blockcount);
             if (blockcount > 0) {
                 ret.pushKV("window_tx_count", nTxDiff);
                 ret.pushKV("window_interval", nTimeDiff);
                 if (nTimeDiff > 0) {
                     ret.pushKV("txrate", double(nTxDiff) / nTimeDiff);
                 }
             }
 
             return ret;
         },
     };
 }
 
 template <typename T>
 static T CalculateTruncatedMedian(std::vector<T> &scores) {
     size_t size = scores.size();
     if (size == 0) {
         return T();
     }
 
     std::sort(scores.begin(), scores.end());
     if (size % 2 == 0) {
         return (scores[size / 2 - 1] + scores[size / 2]) / 2;
     } else {
         return scores[size / 2];
     }
 }
 
 template <typename T> static inline bool SetHasKeys(const std::set<T> &set) {
     return false;
 }
 template <typename T, typename Tk, typename... Args>
 static inline bool SetHasKeys(const std::set<T> &set, const Tk &key,
                               const Args &...args) {
     return (set.count(key) != 0) || SetHasKeys(set, args...);
 }
 
 // outpoint (needed for the utxo index) + nHeight + fCoinBase
 static constexpr size_t PER_UTXO_OVERHEAD =
     sizeof(COutPoint) + sizeof(uint32_t) + sizeof(bool);
 
 static RPCHelpMan getblockstats() {
     const auto &ticker = Currency::get().ticker;
     return RPCHelpMan{
         "getblockstats",
         "Compute per block statistics for a given window. All amounts are "
         "in " +
             ticker +
             ".\n"
             "It won't work for some heights with pruning.\n",
         {
             {"hash_or_height",
              RPCArg::Type::NUM,
              RPCArg::Optional::NO,
              "The block hash or height of the target block",
              "",
              {"", "string or numeric"}},
             {"stats",
              RPCArg::Type::ARR,
              /* default */ "all values",
              "Values to plot (see result below)",
              {
                  {"height", RPCArg::Type::STR, RPCArg::Optional::OMITTED,
                   "Selected statistic"},
                  {"time", RPCArg::Type::STR, RPCArg::Optional::OMITTED,
                   "Selected statistic"},
              },
              "stats"},
         },
         RPCResult{
             RPCResult::Type::OBJ,
             "",
             "",
             {
                 {RPCResult::Type::NUM, "avgfee", "Average fee in the block"},
                 {RPCResult::Type::NUM, "avgfeerate",
                  "Average feerate (in satoshis per virtual byte)"},
                 {RPCResult::Type::NUM, "avgtxsize", "Average transaction size"},
                 {RPCResult::Type::STR_HEX, "blockhash",
                  "The block hash (to check for potential reorgs)"},
                 {RPCResult::Type::NUM, "height", "The height of the block"},
                 {RPCResult::Type::NUM, "ins",
                  "The number of inputs (excluding coinbase)"},
                 {RPCResult::Type::NUM, "maxfee", "Maximum fee in the block"},
                 {RPCResult::Type::NUM, "maxfeerate",
                  "Maximum feerate (in satoshis per virtual byte)"},
                 {RPCResult::Type::NUM, "maxtxsize", "Maximum transaction size"},
                 {RPCResult::Type::NUM, "medianfee",
                  "Truncated median fee in the block"},
                 {RPCResult::Type::NUM, "medianfeerate",
                  "Truncated median feerate (in " + ticker + " per byte)"},
                 {RPCResult::Type::NUM, "mediantime",
                  "The block median time past"},
                 {RPCResult::Type::NUM, "mediantxsize",
                  "Truncated median transaction size"},
                 {RPCResult::Type::NUM, "minfee", "Minimum fee in the block"},
                 {RPCResult::Type::NUM, "minfeerate",
                  "Minimum feerate (in satoshis per virtual byte)"},
                 {RPCResult::Type::NUM, "mintxsize", "Minimum transaction size"},
                 {RPCResult::Type::NUM, "outs", "The number of outputs"},
                 {RPCResult::Type::NUM, "subsidy", "The block subsidy"},
                 {RPCResult::Type::NUM, "time", "The block time"},
                 {RPCResult::Type::NUM, "total_out",
                  "Total amount in all outputs (excluding coinbase and thus "
                  "reward [ie subsidy + totalfee])"},
                 {RPCResult::Type::NUM, "total_size",
                  "Total size of all non-coinbase transactions"},
                 {RPCResult::Type::NUM, "totalfee", "The fee total"},
                 {RPCResult::Type::NUM, "txs",
                  "The number of transactions (including coinbase)"},
                 {RPCResult::Type::NUM, "utxo_increase",
                  "The increase/decrease in the number of unspent outputs"},
                 {RPCResult::Type::NUM, "utxo_size_inc",
                  "The increase/decrease in size for the utxo index (not "
                  "discounting op_return and similar)"},
             }},
         RPCExamples{
             HelpExampleCli(
                 "getblockstats",
                 R"('"00000000c937983704a73af28acdec37b049d214adbda81d7e2a3dd146f6ed09"' '["minfeerate","avgfeerate"]')") +
             HelpExampleCli("getblockstats",
                            R"(1000 '["minfeerate","avgfeerate"]')") +
             HelpExampleRpc(
                 "getblockstats",
                 R"("00000000c937983704a73af28acdec37b049d214adbda81d7e2a3dd146f6ed09", ["minfeerate","avgfeerate"])") +
             HelpExampleRpc("getblockstats",
                            R"(1000, ["minfeerate","avgfeerate"])")},
         [&](const RPCHelpMan &self, const Config &config,
             const JSONRPCRequest &request) -> UniValue {
             LOCK(cs_main);
 
             CBlockIndex *pindex;
             if (request.params[0].isNum()) {
                 const int height = request.params[0].get_int();
                 const int current_tip = ::ChainActive().Height();
                 if (height < 0) {
                     throw JSONRPCError(
                         RPC_INVALID_PARAMETER,
                         strprintf("Target block height %d is negative",
                                   height));
                 }
                 if (height > current_tip) {
                     throw JSONRPCError(
                         RPC_INVALID_PARAMETER,
                         strprintf("Target block height %d after current tip %d",
                                   height, current_tip));
                 }
 
                 pindex = ::ChainActive()[height];
             } else {
                 const BlockHash hash(
                     ParseHashV(request.params[0], "hash_or_height"));
                 pindex = g_chainman.m_blockman.LookupBlockIndex(hash);
                 if (!pindex) {
                     throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY,
                                        "Block not found");
                 }
                 if (!::ChainActive().Contains(pindex)) {
                     throw JSONRPCError(RPC_INVALID_PARAMETER,
                                        strprintf("Block is not in chain %s",
                                                  Params().NetworkIDString()));
                 }
             }
 
             CHECK_NONFATAL(pindex != nullptr);
 
             std::set<std::string> stats;
             if (!request.params[1].isNull()) {
                 const UniValue stats_univalue = request.params[1].get_array();
                 for (unsigned int i = 0; i < stats_univalue.size(); i++) {
                     const std::string stat = stats_univalue[i].get_str();
                     stats.insert(stat);
                 }
             }
 
             const CBlock block = GetBlockChecked(config, pindex);
             const CBlockUndo blockUndo = GetUndoChecked(pindex);
 
             // Calculate everything if nothing selected (default)
             const bool do_all = stats.size() == 0;
             const bool do_mediantxsize =
                 do_all || stats.count("mediantxsize") != 0;
             const bool do_medianfee = do_all || stats.count("medianfee") != 0;
             const bool do_medianfeerate =
                 do_all || stats.count("medianfeerate") != 0;
             const bool loop_inputs =
                 do_all || do_medianfee || do_medianfeerate ||
                 SetHasKeys(stats, "utxo_size_inc", "totalfee", "avgfee",
                            "avgfeerate", "minfee", "maxfee", "minfeerate",
                            "maxfeerate");
             const bool loop_outputs =
                 do_all || loop_inputs || stats.count("total_out");
             const bool do_calculate_size =
                 do_mediantxsize || loop_inputs ||
                 SetHasKeys(stats, "total_size", "avgtxsize", "mintxsize",
                            "maxtxsize");
 
             const int64_t blockMaxSize = config.GetMaxBlockSize();
             Amount maxfee = Amount::zero();
             Amount maxfeerate = Amount::zero();
             Amount minfee = MAX_MONEY;
             Amount minfeerate = MAX_MONEY;
             Amount total_out = Amount::zero();
             Amount totalfee = Amount::zero();
             int64_t inputs = 0;
             int64_t maxtxsize = 0;
             int64_t mintxsize = blockMaxSize;
             int64_t outputs = 0;
             int64_t total_size = 0;
             int64_t utxo_size_inc = 0;
             std::vector<Amount> fee_array;
             std::vector<Amount> feerate_array;
             std::vector<int64_t> txsize_array;
 
             for (size_t i = 0; i < block.vtx.size(); ++i) {
                 const auto &tx = block.vtx.at(i);
                 outputs += tx->vout.size();
                 Amount tx_total_out = Amount::zero();
                 if (loop_outputs) {
                     for (const CTxOut &out : tx->vout) {
                         tx_total_out += out.nValue;
                         utxo_size_inc +=
                             GetSerializeSize(out, PROTOCOL_VERSION) +
                             PER_UTXO_OVERHEAD;
                     }
                 }
 
                 if (tx->IsCoinBase()) {
                     continue;
                 }
 
                 // Don't count coinbase's fake input
                 inputs += tx->vin.size();
                 // Don't count coinbase reward
                 total_out += tx_total_out;
 
                 int64_t tx_size = 0;
                 if (do_calculate_size) {
                     tx_size = tx->GetTotalSize();
                     if (do_mediantxsize) {
                         txsize_array.push_back(tx_size);
                     }
                     maxtxsize = std::max(maxtxsize, tx_size);
                     mintxsize = std::min(mintxsize, tx_size);
                     total_size += tx_size;
                 }
 
                 if (loop_inputs) {
                     Amount tx_total_in = Amount::zero();
                     const auto &txundo = blockUndo.vtxundo.at(i - 1);
                     for (const Coin &coin : txundo.vprevout) {
                         const CTxOut &prevoutput = coin.GetTxOut();
 
                         tx_total_in += prevoutput.nValue;
                         utxo_size_inc -=
                             GetSerializeSize(prevoutput, PROTOCOL_VERSION) +
                             PER_UTXO_OVERHEAD;
                     }
 
                     Amount txfee = tx_total_in - tx_total_out;
                     CHECK_NONFATAL(MoneyRange(txfee));
                     if (do_medianfee) {
                         fee_array.push_back(txfee);
                     }
                     maxfee = std::max(maxfee, txfee);
                     minfee = std::min(minfee, txfee);
                     totalfee += txfee;
 
                     Amount feerate = txfee / tx_size;
                     if (do_medianfeerate) {
                         feerate_array.push_back(feerate);
                     }
                     maxfeerate = std::max(maxfeerate, feerate);
                     minfeerate = std::min(minfeerate, feerate);
                 }
             }
 
             UniValue ret_all(UniValue::VOBJ);
             ret_all.pushKV("avgfee",
                            block.vtx.size() > 1
                                ? (totalfee / int((block.vtx.size() - 1)))
                                : Amount::zero());
             ret_all.pushKV("avgfeerate", total_size > 0
                                              ? (totalfee / total_size)
                                              : Amount::zero());
             ret_all.pushKV("avgtxsize",
                            (block.vtx.size() > 1)
                                ? total_size / (block.vtx.size() - 1)
                                : 0);
             ret_all.pushKV("blockhash", pindex->GetBlockHash().GetHex());
             ret_all.pushKV("height", (int64_t)pindex->nHeight);
             ret_all.pushKV("ins", inputs);
             ret_all.pushKV("maxfee", maxfee);
             ret_all.pushKV("maxfeerate", maxfeerate);
             ret_all.pushKV("maxtxsize", maxtxsize);
             ret_all.pushKV("medianfee", CalculateTruncatedMedian(fee_array));
             ret_all.pushKV("medianfeerate",
                            CalculateTruncatedMedian(feerate_array));
             ret_all.pushKV("mediantime", pindex->GetMedianTimePast());
             ret_all.pushKV("mediantxsize",
                            CalculateTruncatedMedian(txsize_array));
             ret_all.pushKV("minfee",
                            minfee == MAX_MONEY ? Amount::zero() : minfee);
             ret_all.pushKV("minfeerate", minfeerate == MAX_MONEY
                                              ? Amount::zero()
                                              : minfeerate);
             ret_all.pushKV("mintxsize",
                            mintxsize == blockMaxSize ? 0 : mintxsize);
             ret_all.pushKV("outs", outputs);
             ret_all.pushKV("subsidy", GetBlockSubsidy(pindex->nHeight,
                                                       Params().GetConsensus()));
             ret_all.pushKV("time", pindex->GetBlockTime());
             ret_all.pushKV("total_out", total_out);
             ret_all.pushKV("total_size", total_size);
             ret_all.pushKV("totalfee", totalfee);
             ret_all.pushKV("txs", (int64_t)block.vtx.size());
             ret_all.pushKV("utxo_increase", outputs - inputs);
             ret_all.pushKV("utxo_size_inc", utxo_size_inc);
 
             if (do_all) {
                 return ret_all;
             }
 
             UniValue ret(UniValue::VOBJ);
             for (const std::string &stat : stats) {
                 const UniValue &value = ret_all[stat];
                 if (value.isNull()) {
                     throw JSONRPCError(
                         RPC_INVALID_PARAMETER,
                         strprintf("Invalid selected statistic %s", stat));
                 }
                 ret.pushKV(stat, value);
             }
             return ret;
         },
     };
 }
 
 static RPCHelpMan savemempool() {
     return RPCHelpMan{
         "savemempool",
         "Dumps the mempool to disk. It will fail until the previous dump is "
         "fully loaded.\n",
         {},
         RPCResult{RPCResult::Type::NONE, "", ""},
         RPCExamples{HelpExampleCli("savemempool", "") +
                     HelpExampleRpc("savemempool", "")},
         [&](const RPCHelpMan &self, const Config &config,
             const JSONRPCRequest &request) -> UniValue {
             const CTxMemPool &mempool = EnsureMemPool(request.context);
 
             if (!mempool.IsLoaded()) {
                 throw JSONRPCError(RPC_MISC_ERROR,
                                    "The mempool was not loaded yet");
             }
 
             if (!DumpMempool(mempool)) {
                 throw JSONRPCError(RPC_MISC_ERROR,
                                    "Unable to dump mempool to disk");
             }
 
             return NullUniValue;
         },
     };
 }
 
 namespace {
 //! Search for a given set of pubkey scripts
 static bool FindScriptPubKey(std::atomic<int> &scan_progress,
                              const std::atomic<bool> &should_abort,
                              int64_t &count, CCoinsViewCursor *cursor,
                              const std::set<CScript> &needles,
                              std::map<COutPoint, Coin> &out_results,
                              std::function<void()> &interruption_point) {
     scan_progress = 0;
     count = 0;
     while (cursor->Valid()) {
         COutPoint key;
         Coin coin;
         if (!cursor->GetKey(key) || !cursor->GetValue(coin)) {
             return false;
         }
         if (++count % 8192 == 0) {
             interruption_point();
             if (should_abort) {
                 // allow to abort the scan via the abort reference
                 return false;
             }
         }
         if (count % 256 == 0) {
             // update progress reference every 256 item
             const TxId &txid = key.GetTxId();
             uint32_t high = 0x100 * *txid.begin() + *(txid.begin() + 1);
             scan_progress = int(high * 100.0 / 65536.0 + 0.5);
         }
         if (needles.count(coin.GetTxOut().scriptPubKey)) {
             out_results.emplace(key, coin);
         }
         cursor->Next();
     }
     scan_progress = 100;
     return true;
 }
 } // namespace
 
 /** RAII object to prevent concurrency issue when scanning the txout set */
 static std::atomic<int> g_scan_progress;
 static std::atomic<bool> g_scan_in_progress;
 static std::atomic<bool> g_should_abort_scan;
 class CoinsViewScanReserver {
 private:
     bool m_could_reserve;
 
 public:
     explicit CoinsViewScanReserver() : m_could_reserve(false) {}
 
     bool reserve() {
         CHECK_NONFATAL(!m_could_reserve);
         if (g_scan_in_progress.exchange(true)) {
             return false;
         }
         m_could_reserve = true;
         return true;
     }
 
     ~CoinsViewScanReserver() {
         if (m_could_reserve) {
             g_scan_in_progress = false;
         }
     }
 };
 
 static RPCHelpMan scantxoutset() {
     const auto &ticker = Currency::get().ticker;
     return RPCHelpMan{
         "scantxoutset",
         "EXPERIMENTAL warning: this call may be removed or changed in future "
         "releases.\n"
         "\nScans the unspent transaction output set for entries that match "
         "certain output descriptors.\n"
         "Examples of output descriptors are:\n"
         "    addr(<address>)                      Outputs whose scriptPubKey "
         "corresponds to the specified address (does not include P2PK)\n"
         "    raw(<hex script>)                    Outputs whose scriptPubKey "
         "equals the specified hex scripts\n"
         "    combo(<pubkey>)                      P2PK and P2PKH outputs for "
         "the given pubkey\n"
         "    pkh(<pubkey>)                        P2PKH outputs for the given "
         "pubkey\n"
         "    sh(multi(<n>,<pubkey>,<pubkey>,...)) P2SH-multisig outputs for "
         "the given threshold and pubkeys\n"
         "\nIn the above, <pubkey> either refers to a fixed public key in "
         "hexadecimal notation, or to an xpub/xprv optionally followed by one\n"
         "or more path elements separated by \"/\", and optionally ending in "
         "\"/*\" (unhardened), or \"/*'\" or \"/*h\" (hardened) to specify all\n"
         "unhardened or hardened child keys.\n"
         "In the latter case, a range needs to be specified by below if "
         "different from 1000.\n"
         "For more information on output descriptors, see the documentation in "
         "the doc/descriptors.md file.\n",
         {
             {"action", RPCArg::Type::STR, RPCArg::Optional::NO,
              "The action to execute\n"
              "                                      \"start\" for starting a "
              "scan\n"
              "                                      \"abort\" for aborting the "
              "current scan (returns true when abort was successful)\n"
              "                                      \"status\" for "
              "progress report (in %) of the current scan"},
             {"scanobjects",
              RPCArg::Type::ARR,
              RPCArg::Optional::OMITTED,
              "Array of scan objects. Required for \"start\" action\n"
              "                                  Every scan object is either a "
              "string descriptor or an object:",
              {
                  {"descriptor", RPCArg::Type::STR, RPCArg::Optional::OMITTED,
                   "An output descriptor"},
                  {
                      "",
                      RPCArg::Type::OBJ,
                      RPCArg::Optional::OMITTED,
                      "An object with output descriptor and metadata",
                      {
                          {"desc", RPCArg::Type::STR, RPCArg::Optional::NO,
                           "An output descriptor"},
                          {"range", RPCArg::Type::RANGE, /* default */ "1000",
                           "The range of HD chain indexes to explore (either "
                           "end or [begin,end])"},
                      },
                  },
              },
              "[scanobjects,...]"},
         },
         RPCResult{
             RPCResult::Type::OBJ,
             "",
             "",
             {
                 {RPCResult::Type::BOOL, "success",
                  "Whether the scan was completed"},
                 {RPCResult::Type::NUM, "txouts",
                  "The number of unspent transaction outputs scanned"},
                 {RPCResult::Type::NUM, "height",
                  "The current block height (index)"},
                 {RPCResult::Type::STR_HEX, "bestblock",
                  "The hash of the block at the tip of the chain"},
                 {RPCResult::Type::ARR,
                  "unspents",
                  "",
                  {
                      {RPCResult::Type::OBJ,
                       "",
                       "",
                       {
                           {RPCResult::Type::STR_HEX, "txid",
                            "The transaction id"},
                           {RPCResult::Type::NUM, "vout", "The vout value"},
                           {RPCResult::Type::STR_HEX, "scriptPubKey",
                            "The script key"},
                           {RPCResult::Type::STR, "desc",
                            "A specialized descriptor for the matched "
                            "scriptPubKey"},
                           {RPCResult::Type::STR_AMOUNT, "amount",
                            "The total amount in " + ticker +
                                " of the unspent output"},
                           {RPCResult::Type::NUM, "height",
                            "Height of the unspent transaction output"},
                       }},
                  }},
                 {RPCResult::Type::STR_AMOUNT, "total_amount",
                  "The total amount of all found unspent outputs in " + ticker},
             }},
         RPCExamples{""},
         [&](const RPCHelpMan &self, const Config &config,
             const JSONRPCRequest &request) -> UniValue {
             RPCTypeCheck(request.params, {UniValue::VSTR, UniValue::VARR});
 
             UniValue result(UniValue::VOBJ);
             if (request.params[0].get_str() == "status") {
                 CoinsViewScanReserver reserver;
                 if (reserver.reserve()) {
                     // no scan in progress
                     return NullUniValue;
                 }
                 result.pushKV("progress", g_scan_progress.load());
                 return result;
             } else if (request.params[0].get_str() == "abort") {
                 CoinsViewScanReserver reserver;
                 if (reserver.reserve()) {
                     // reserve was possible which means no scan was running
                     return false;
                 }
                 // set the abort flag
                 g_should_abort_scan = true;
                 return true;
             } else if (request.params[0].get_str() == "start") {
                 CoinsViewScanReserver reserver;
                 if (!reserver.reserve()) {
                     throw JSONRPCError(RPC_INVALID_PARAMETER,
                                        "Scan already in progress, use action "
                                        "\"abort\" or \"status\"");
                 }
 
                 if (request.params.size() < 2) {
                     throw JSONRPCError(RPC_MISC_ERROR,
                                        "scanobjects argument is required for "
                                        "the start action");
                 }
 
                 std::set<CScript> needles;
                 std::map<CScript, std::string> descriptors;
                 Amount total_in = Amount::zero();
 
                 // loop through the scan objects
                 for (const UniValue &scanobject :
                      request.params[1].get_array().getValues()) {
                     FlatSigningProvider provider;
                     auto scripts =
                         EvalDescriptorStringOrObject(scanobject, provider);
                     for (const auto &script : scripts) {
                         std::string inferred =
                             InferDescriptor(script, provider)->ToString();
                         needles.emplace(script);
                         descriptors.emplace(std::move(script),
                                             std::move(inferred));
                     }
                 }
 
                 // Scan the unspent transaction output set for inputs
                 UniValue unspents(UniValue::VARR);
                 std::vector<CTxOut> input_txos;
                 std::map<COutPoint, Coin> coins;
                 g_should_abort_scan = false;
                 g_scan_progress = 0;
                 int64_t count = 0;
                 std::unique_ptr<CCoinsViewCursor> pcursor;
                 CBlockIndex *tip;
                 {
                     LOCK(cs_main);
                     ::ChainstateActive().ForceFlushStateToDisk();
                     pcursor = std::unique_ptr<CCoinsViewCursor>(
                         ::ChainstateActive().CoinsDB().Cursor());
                     CHECK_NONFATAL(pcursor);
                     tip = ::ChainActive().Tip();
                     CHECK_NONFATAL(tip);
                 }
                 NodeContext &node = EnsureNodeContext(request.context);
                 bool res = FindScriptPubKey(
                     g_scan_progress, g_should_abort_scan, count, pcursor.get(),
                     needles, coins, node.rpc_interruption_point);
                 result.pushKV("success", res);
                 result.pushKV("txouts", count);
                 result.pushKV("height", tip->nHeight);
                 result.pushKV("bestblock", tip->GetBlockHash().GetHex());
 
                 for (const auto &it : coins) {
                     const COutPoint &outpoint = it.first;
                     const Coin &coin = it.second;
                     const CTxOut &txo = coin.GetTxOut();
                     input_txos.push_back(txo);
                     total_in += txo.nValue;
 
                     UniValue unspent(UniValue::VOBJ);
                     unspent.pushKV("txid", outpoint.GetTxId().GetHex());
                     unspent.pushKV("vout", int32_t(outpoint.GetN()));
                     unspent.pushKV("scriptPubKey", HexStr(txo.scriptPubKey));
                     unspent.pushKV("desc", descriptors[txo.scriptPubKey]);
                     unspent.pushKV("amount", txo.nValue);
                     unspent.pushKV("height", int32_t(coin.GetHeight()));
 
                     unspents.push_back(unspent);
                 }
                 result.pushKV("unspents", unspents);
                 result.pushKV("total_amount", total_in);
             } else {
                 throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid command");
             }
             return result;
         },
     };
 }
 
 static RPCHelpMan getblockfilter() {
     return RPCHelpMan{
         "getblockfilter",
         "Retrieve a BIP 157 content filter for a particular block.\n",
         {
             {"blockhash", RPCArg::Type::STR_HEX, RPCArg::Optional::NO,
              "The hash of the block"},
             {"filtertype", RPCArg::Type::STR, /*default*/ "basic",
              "The type name of the filter"},
         },
         RPCResult{RPCResult::Type::OBJ,
                   "",
                   "",
                   {
                       {RPCResult::Type::STR_HEX, "filter",
                        "the hex-encoded filter data"},
                       {RPCResult::Type::STR_HEX, "header",
                        "the hex-encoded filter header"},
                   }},
         RPCExamples{
             HelpExampleCli("getblockfilter",
                            "\"00000000c937983704a73af28acdec37b049d214a"
                            "dbda81d7e2a3dd146f6ed09\" \"basic\"") +
             HelpExampleRpc("getblockfilter",
                            "\"00000000c937983704a73af28acdec37b049d214adbda81d7"
                            "e2a3dd146f6ed09\", \"basic\"")},
         [&](const RPCHelpMan &self, const Config &config,
             const JSONRPCRequest &request) -> UniValue {
             const BlockHash block_hash(
                 ParseHashV(request.params[0], "blockhash"));
             std::string filtertype_name = "basic";
             if (!request.params[1].isNull()) {
                 filtertype_name = request.params[1].get_str();
             }
 
             BlockFilterType filtertype;
             if (!BlockFilterTypeByName(filtertype_name, filtertype)) {
                 throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY,
                                    "Unknown filtertype");
             }
 
             BlockFilterIndex *index = GetBlockFilterIndex(filtertype);
             if (!index) {
                 throw JSONRPCError(RPC_MISC_ERROR,
                                    "Index is not enabled for filtertype " +
                                        filtertype_name);
             }
 
             const CBlockIndex *block_index;
             bool block_was_connected;
             {
                 LOCK(cs_main);
                 block_index =
                     g_chainman.m_blockman.LookupBlockIndex(block_hash);
                 if (!block_index) {
                     throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY,
                                        "Block not found");
                 }
                 block_was_connected =
                     block_index->IsValid(BlockValidity::SCRIPTS);
             }
 
             bool index_ready = index->BlockUntilSyncedToCurrentChain();
 
             BlockFilter filter;
             uint256 filter_header;
             if (!index->LookupFilter(block_index, filter) ||
                 !index->LookupFilterHeader(block_index, filter_header)) {
                 int err_code;
                 std::string errmsg = "Filter not found.";
 
                 if (!block_was_connected) {
                     err_code = RPC_INVALID_ADDRESS_OR_KEY;
                     errmsg += " Block was not connected to active chain.";
                 } else if (!index_ready) {
                     err_code = RPC_MISC_ERROR;
                     errmsg += " Block filters are still in the process of "
                               "being indexed.";
                 } else {
                     err_code = RPC_INTERNAL_ERROR;
                     errmsg += " This error is unexpected and indicates index "
                               "corruption.";
                 }
 
                 throw JSONRPCError(err_code, errmsg);
             }
 
             UniValue ret(UniValue::VOBJ);
             ret.pushKV("filter", HexStr(filter.GetEncodedFilter()));
             ret.pushKV("header", filter_header.GetHex());
             return ret;
         },
     };
 }
 
 /**
  * Serialize the UTXO set to a file for loading elsewhere.
  *
  * @see SnapshotMetadata
  */
 static RPCHelpMan dumptxoutset() {
     return RPCHelpMan{
         "dumptxoutset",
         "Write the serialized UTXO set to disk.\n",
         {
             {"path", RPCArg::Type::STR, RPCArg::Optional::NO,
              "path to the output file. If relative, will be prefixed by "
              "datadir."},
         },
         RPCResult{RPCResult::Type::OBJ,
                   "",
                   "",
                   {
                       {RPCResult::Type::NUM, "coins_written",
                        "the number of coins written in the snapshot"},
                       {RPCResult::Type::STR_HEX, "base_hash",
                        "the hash of the base of the snapshot"},
                       {RPCResult::Type::NUM, "base_height",
                        "the height of the base of the snapshot"},
                       {RPCResult::Type::STR, "path",
                        "the absolute path that the snapshot was written to"},
                   }},
         RPCExamples{HelpExampleCli("dumptxoutset", "utxo.dat")},
         [&](const RPCHelpMan &self, const Config &config,
             const JSONRPCRequest &request) -> UniValue {
             fs::path path = fs::absolute(
                 fs::u8path(request.params[0].get_str()), GetDataDir());
             // Write to a temporary path and then move into `path` on completion
             // to avoid confusion due to an interruption.
             fs::path temppath = fs::absolute(
                 fs::u8path(request.params[0].get_str() + ".incomplete"),
                 GetDataDir());
 
             if (fs::exists(path)) {
                 throw JSONRPCError(RPC_INVALID_PARAMETER,
                                    path.u8string() +
                                        " already exists. If you are sure this "
                                        "is what you want, "
                                        "move it out of the way first");
             }
 
             FILE *file{fsbridge::fopen(temppath, "wb")};
             CAutoFile afile{file, SER_DISK, CLIENT_VERSION};
             std::unique_ptr<CCoinsViewCursor> pcursor;
             CCoinsStats stats;
             CBlockIndex *tip;
             NodeContext &node = EnsureNodeContext(request.context);
 
             {
                 // We need to lock cs_main to ensure that the coinsdb isn't
                 // written to between (i) flushing coins cache to disk
                 // (coinsdb), (ii) getting stats based upon the coinsdb, and
                 // (iii) constructing a cursor to the coinsdb for use below this
                 // block.
                 //
                 // Cursors returned by leveldb iterate over snapshots, so the
                 // contents of the pcursor will not be affected by simultaneous
                 // writes during use below this block.
                 //
                 // See discussion here:
                 //   https://github.com/bitcoin/bitcoin/pull/15606#discussion_r274479369
                 //
                 LOCK(::cs_main);
 
                 ::ChainstateActive().ForceFlushStateToDisk();
 
                 if (!GetUTXOStats(&::ChainstateActive().CoinsDB(), stats,
                                   CoinStatsHashType::NONE,
                                   node.rpc_interruption_point)) {
                     throw JSONRPCError(RPC_INTERNAL_ERROR,
                                        "Unable to read UTXO set");
                 }
 
                 pcursor = std::unique_ptr<CCoinsViewCursor>(
                     ::ChainstateActive().CoinsDB().Cursor());
                 tip = g_chainman.m_blockman.LookupBlockIndex(stats.hashBlock);
                 CHECK_NONFATAL(tip);
             }
 
             SnapshotMetadata metadata{tip->GetBlockHash(), stats.coins_count,
                                       uint64_t(tip->GetChainTxCount())};
 
             afile << metadata;
 
             COutPoint key;
             Coin coin;
             unsigned int iter{0};
 
             while (pcursor->Valid()) {
                 if (iter % 5000 == 0) {
                     node.rpc_interruption_point();
                 }
                 ++iter;
                 if (pcursor->GetKey(key) && pcursor->GetValue(coin)) {
                     afile << key;
                     afile << coin;
                 }
 
                 pcursor->Next();
             }
 
             afile.fclose();
             fs::rename(temppath, path);
 
             UniValue result(UniValue::VOBJ);
             result.pushKV("coins_written", stats.coins_count);
             result.pushKV("base_hash", tip->GetBlockHash().ToString());
             result.pushKV("base_height", tip->nHeight);
             result.pushKV("path", path.u8string());
             return result;
         },
     };
 }
 
 void RegisterBlockchainRPCCommands(CRPCTable &t) {
     // clang-format off
     static const CRPCCommand commands[] = {
         //  category            actor (function)
         //  ------------------  ----------------------
         { "blockchain",         getbestblockhash,                  },
         { "blockchain",         getblock,                          },
         { "blockchain",         getblockchaininfo,                 },
         { "blockchain",         getblockcount,                     },
         { "blockchain",         getblockhash,                      },
         { "blockchain",         getblockheader,                    },
         { "blockchain",         getblockstats,                     },
         { "blockchain",         getchaintips,                      },
         { "blockchain",         getchaintxstats,                   },
         { "blockchain",         getdifficulty,                     },
         { "blockchain",         getmempoolancestors,               },
         { "blockchain",         getmempooldescendants,             },
         { "blockchain",         getmempoolentry,                   },
         { "blockchain",         getmempoolinfo,                    },
         { "blockchain",         getrawmempool,                     },
         { "blockchain",         gettxout,                          },
         { "blockchain",         gettxoutsetinfo,                   },
         { "blockchain",         pruneblockchain,                   },
         { "blockchain",         savemempool,                       },
         { "blockchain",         verifychain,                       },
         { "blockchain",         preciousblock,                     },
         { "blockchain",         scantxoutset,                      },
         { "blockchain",         getblockfilter,                    },
 
         /* Not shown in help */
         { "hidden",             getfinalizedblockhash,             },
         { "hidden",             finalizeblock,                     },
         { "hidden",             invalidateblock,                   },
         { "hidden",             parkblock,                         },
         { "hidden",             reconsiderblock,                   },
         { "hidden",             syncwithvalidationinterfacequeue,  },
         { "hidden",             dumptxoutset,                      },
         { "hidden",             unparkblock,                       },
         { "hidden",             waitfornewblock,                   },
         { "hidden",             waitforblock,                      },
         { "hidden",             waitforblockheight,                },
     };
     // clang-format on
     for (const auto &c : commands) {
         t.appendCommand(c.name, &c);
     }
 }
diff --git a/src/validation.cpp b/src/validation.cpp
index df566eee6..a74549e50 100644
--- a/src/validation.cpp
+++ b/src/validation.cpp
@@ -1,6197 +1,6200 @@
 // Copyright (c) 2009-2010 Satoshi Nakamoto
 // Copyright (c) 2009-2018 The Bitcoin Core developers
 // Copyright (c) 2017-2020 The Bitcoin developers
 // Distributed under the MIT software license, see the accompanying
 // file COPYING or http://www.opensource.org/licenses/mit-license.php.
 
 #include <validation.h>
 
 #include <arith_uint256.h>
 #include <avalanche/avalanche.h>
 #include <avalanche/processor.h>
 #include <blockdb.h>
 #include <blockvalidity.h>
 #include <chainparams.h>
 #include <checkpoints.h>
 #include <checkqueue.h>
 #include <config.h>
 #include <consensus/activation.h>
 #include <consensus/merkle.h>
 #include <consensus/tx_check.h>
 #include <consensus/tx_verify.h>
 #include <consensus/validation.h>
 #include <hash.h>
 #include <index/txindex.h>
 #include <logging.h>
 #include <logging/timer.h>
 #include <minerfund.h>
 #include <node/ui_interface.h>
 #include <policy/fees.h>
 #include <policy/mempool.h>
 #include <policy/policy.h>
 #include <policy/settings.h>
 #include <pow/pow.h>
 #include <primitives/block.h>
 #include <primitives/transaction.h>
 #include <random.h>
 #include <reverse_iterator.h>
 #include <script/script.h>
 #include <script/scriptcache.h>
 #include <script/sigcache.h>
 #include <shutdown.h>
 #include <timedata.h>
 #include <tinyformat.h>
 #include <txdb.h>
 #include <txmempool.h>
 #include <undo.h>
 #include <util/check.h> // For NDEBUG compile time check
 #include <util/moneystr.h>
 #include <util/strencodings.h>
 #include <util/system.h>
 #include <util/translation.h>
 #include <validationinterface.h>
 #include <warnings.h>
 
 #include <boost/algorithm/string/replace.hpp>
 
 #include <optional>
 #include <string>
 #include <thread>
 
 #define MICRO 0.000001
 #define MILLI 0.001
 
 /** Time to wait between writing blocks/block index to disk. */
 static constexpr std::chrono::hours DATABASE_WRITE_INTERVAL{1};
 /** Time to wait between flushing chainstate to disk. */
 static constexpr std::chrono::hours DATABASE_FLUSH_INTERVAL{24};
 const std::vector<std::string> CHECKLEVEL_DOC{
     "level 0 reads the blocks from disk",
     "level 1 verifies block validity",
     "level 2 verifies undo data",
     "level 3 checks disconnection of tip blocks",
     "level 4 tries to reconnect the blocks",
     "each level includes the checks of the previous levels",
 };
 
 ChainstateManager g_chainman;
 
 CChainState &ChainstateActive() {
     LOCK(::cs_main);
     assert(g_chainman.m_active_chainstate);
     return *g_chainman.m_active_chainstate;
 }
 
 CChain &ChainActive() {
     LOCK(::cs_main);
     return ::ChainstateActive().m_chain;
 }
 
 /**
  * Global state
  *
  * Mutex to guard access to validation specific variables, such as reading
  * or changing the chainstate.
  *
  * This may also need to be locked when updating the transaction pool, e.g. on
  * AcceptToMemoryPool. See CTxMemPool::cs comment for details.
  *
  * The transaction pool has a separate lock to allow reading from it and the
  * chainstate at the same time.
  */
 RecursiveMutex cs_main;
 
 CBlockIndex *pindexBestHeader = nullptr;
 Mutex g_best_block_mutex;
 std::condition_variable g_best_block_cv;
 uint256 g_best_block;
 std::atomic_bool fImporting(false);
 std::atomic_bool fReindex(false);
 bool fHavePruned = false;
 bool fPruneMode = false;
 bool fRequireStandard = true;
 bool fCheckBlockIndex = false;
 bool fCheckpointsEnabled = DEFAULT_CHECKPOINTS_ENABLED;
 uint64_t nPruneTarget = 0;
 int64_t nMaxTipAge = DEFAULT_MAX_TIP_AGE;
 
 BlockHash hashAssumeValid;
 arith_uint256 nMinimumChainWork;
 
 CFeeRate minRelayTxFee = CFeeRate(DEFAULT_MIN_RELAY_TX_FEE_PER_KB);
 
 // Internal stuff
 namespace {
 CBlockIndex *pindexBestInvalid = nullptr;
 CBlockIndex *pindexBestParked = nullptr;
 
 RecursiveMutex cs_LastBlockFile;
 std::vector<CBlockFileInfo> vinfoBlockFile;
 int nLastBlockFile = 0;
 /**
  * Global flag to indicate we should check to see if there are block/undo files
  * that should be deleted. Set on startup or if we allocate more file space when
  * we're in prune mode.
  */
 bool fCheckForPruning = false;
 
 /** Dirty block index entries. */
 std::set<const CBlockIndex *> setDirtyBlockIndex;
 
 /** Dirty block file entries. */
 std::set<int> setDirtyFileInfo;
 } // namespace
 
 BlockValidationOptions::BlockValidationOptions(const Config &config)
     : excessiveBlockSize(config.GetMaxBlockSize()), checkPoW(true),
       checkMerkleRoot(true) {}
 
 CBlockIndex *BlockManager::LookupBlockIndex(const BlockHash &hash) {
     AssertLockHeld(cs_main);
     assert(std::addressof(g_chainman.BlockIndex()) ==
            std::addressof(m_block_index));
     BlockMap::const_iterator it = m_block_index.find(hash);
     return it == m_block_index.end() ? nullptr : it->second;
 }
 
 CBlockIndex *BlockManager::FindForkInGlobalIndex(const CChain &chain,
                                                  const CBlockLocator &locator) {
     AssertLockHeld(cs_main);
 
     assert(std::addressof(g_chainman.m_blockman) == std::addressof(*this));
     // Find the latest block common to locator and chain - we expect that
     // locator.vHave is sorted descending by height.
     for (const BlockHash &hash : locator.vHave) {
         CBlockIndex *pindex = LookupBlockIndex(hash);
         if (pindex) {
             if (chain.Contains(pindex)) {
                 return pindex;
             }
             if (pindex->GetAncestor(chain.Height()) == chain.Tip()) {
                 return chain.Tip();
             }
         }
     }
     return chain.Genesis();
 }
 
 std::unique_ptr<CBlockTreeDB> pblocktree;
 
 static uint32_t GetNextBlockScriptFlags(const Consensus::Params &params,
                                         const CBlockIndex *pindex);
 
 bool TestLockPointValidity(const CChain &active_chain, const LockPoints *lp) {
     AssertLockHeld(cs_main);
     assert(lp);
     // If there are relative lock times then the maxInputBlock will be set
     // If there are no relative lock times, the LockPoints don't depend on the
     // chain
     if (lp->maxInputBlock) {
         // Check whether ::ChainActive() is an extension of the block at which
         // the LockPoints calculation was valid. If not LockPoints are no longer
         // valid.
         assert(std::addressof(::ChainActive()) == std::addressof(active_chain));
         if (!active_chain.Contains(lp->maxInputBlock)) {
             return false;
         }
     }
 
     // LockPoints still valid
     return true;
 }
 
 bool CheckSequenceLocks(CChainState &active_chainstate, const CTxMemPool &pool,
                         const CTransaction &tx, int flags, LockPoints *lp,
                         bool useExistingLockPoints) {
     AssertLockHeld(cs_main);
     AssertLockHeld(pool.cs);
     assert(std::addressof(::ChainstateActive()) ==
            std::addressof(active_chainstate));
 
     CBlockIndex *tip = active_chainstate.m_chain.Tip();
     assert(tip != nullptr);
 
     CBlockIndex index;
     index.pprev = tip;
     // CheckSequenceLocks() uses active_chainstate.m_chain.Height()+1 to
     // evaluate height based locks because when SequenceLocks() is called within
     // ConnectBlock(), the height of the block *being* evaluated is what is
     // used. Thus if we want to know if a transaction can be part of the *next*
     // block, we need to use one more than active_chainstate.m_chain.Height()
     index.nHeight = tip->nHeight + 1;
 
     std::pair<int, int64_t> lockPair;
     if (useExistingLockPoints) {
         assert(lp);
         lockPair.first = lp->height;
         lockPair.second = lp->time;
     } else {
         // CoinsTip() contains the UTXO set for active_chainstate.m_chain.Tip()
         CCoinsViewMemPool viewMemPool(&active_chainstate.CoinsTip(), pool);
         std::vector<int> prevheights;
         prevheights.resize(tx.vin.size());
         for (size_t txinIndex = 0; txinIndex < tx.vin.size(); txinIndex++) {
             const CTxIn &txin = tx.vin[txinIndex];
             Coin coin;
             if (!viewMemPool.GetCoin(txin.prevout, coin)) {
                 return error("%s: Missing input", __func__);
             }
             if (coin.GetHeight() == MEMPOOL_HEIGHT) {
                 // Assume all mempool transaction confirm in the next block
                 prevheights[txinIndex] = tip->nHeight + 1;
             } else {
                 prevheights[txinIndex] = coin.GetHeight();
             }
         }
         lockPair = CalculateSequenceLocks(tx, flags, prevheights, index);
         if (lp) {
             lp->height = lockPair.first;
             lp->time = lockPair.second;
             // Also store the hash of the block with the highest height of all
             // the blocks which have sequence locked prevouts. This hash needs
             // to still be on the chain for these LockPoint calculations to be
             // valid.
             // Note: It is impossible to correctly calculate a maxInputBlock if
             // any of the sequence locked inputs depend on unconfirmed txs,
             // except in the special case where the relative lock time/height is
             // 0, which is equivalent to no sequence lock. Since we assume input
             // height of tip+1 for mempool txs and test the resulting lockPair
             // from CalculateSequenceLocks against tip+1. We know
             // EvaluateSequenceLocks will fail if there was a non-zero sequence
             // lock on a mempool input, so we can use the return value of
             // CheckSequenceLocks to indicate the LockPoints validity.
             int maxInputHeight = 0;
             for (const int height : prevheights) {
                 // Can ignore mempool inputs since we'll fail if they had
                 // non-zero locks.
                 if (height != tip->nHeight + 1) {
                     maxInputHeight = std::max(maxInputHeight, height);
                 }
             }
             lp->maxInputBlock = tip->GetAncestor(maxInputHeight);
         }
     }
     return EvaluateSequenceLocks(index, lockPair);
 }
 
 // Command-line argument "-replayprotectionactivationtime=<timestamp>" will
 // cause the node to switch to replay protected SigHash ForkID value when the
 // median timestamp of the previous 11 blocks is greater than or equal to
 // <timestamp>. Defaults to the pre-defined timestamp when not set.
 static bool IsReplayProtectionEnabled(const Consensus::Params &params,
                                       int64_t nMedianTimePast) {
     return nMedianTimePast >= gArgs.GetArg("-replayprotectionactivationtime",
                                            params.jeffersonActivationTime);
 }
 
 static bool IsReplayProtectionEnabled(const Consensus::Params &params,
                                       const CBlockIndex *pindexPrev) {
     if (pindexPrev == nullptr) {
         return false;
     }
 
     return IsReplayProtectionEnabled(params, pindexPrev->GetMedianTimePast());
 }
 
 // Used to avoid mempool polluting consensus critical paths if CCoinsViewMempool
 // were somehow broken and returning the wrong scriptPubKeys
 static bool CheckInputsFromMempoolAndCache(
     const CTransaction &tx, TxValidationState &state,
     const CCoinsViewCache &view, const CTxMemPool &pool, const uint32_t flags,
     PrecomputedTransactionData &txdata, int &nSigChecksOut,
     CCoinsViewCache &coins_tip) EXCLUSIVE_LOCKS_REQUIRED(cs_main) {
     AssertLockHeld(cs_main);
 
     // pool.cs should be locked already, but go ahead and re-take the lock here
     // to enforce that mempool doesn't change between when we check the view and
     // when we actually call through to CheckInputScripts
     LOCK(pool.cs);
 
     assert(!tx.IsCoinBase());
     for (const CTxIn &txin : tx.vin) {
         const Coin &coin = view.AccessCoin(txin.prevout);
 
         // AcceptToMemoryPoolWorker has already checked that the coins are
         // available, so this shouldn't fail. If the inputs are not available
         // here then return false.
         if (coin.IsSpent()) {
             return false;
         }
 
         // Check equivalence for available inputs.
         const CTransactionRef &txFrom = pool.get(txin.prevout.GetTxId());
         if (txFrom) {
             assert(txFrom->GetId() == txin.prevout.GetTxId());
             assert(txFrom->vout.size() > txin.prevout.GetN());
             assert(txFrom->vout[txin.prevout.GetN()] == coin.GetTxOut());
         } else {
             assert(std::addressof(::ChainstateActive().CoinsTip()) ==
                    std::addressof(coins_tip));
             const Coin &coinFromDisk = coins_tip.AccessCoin(txin.prevout);
             assert(!coinFromDisk.IsSpent());
             assert(coinFromDisk.GetTxOut() == coin.GetTxOut());
         }
     }
 
     // Call CheckInputScripts() to cache signature and script validity against
     // current tip consensus rules.
     return CheckInputScripts(tx, state, view, flags, /* cacheSigStore = */ true,
                              /* cacheFullScriptStore = */ true, txdata,
                              nSigChecksOut);
 }
 
 namespace {
 
 class MemPoolAccept {
 public:
     MemPoolAccept(CTxMemPool &mempool, CChainState &active_chainstate)
         : m_pool(mempool), m_view(&m_dummy),
           m_viewmempool(&active_chainstate.CoinsTip(), m_pool),
           m_active_chainstate(active_chainstate),
           m_limit_ancestors(
               gArgs.GetArg("-limitancestorcount", DEFAULT_ANCESTOR_LIMIT)),
           m_limit_ancestor_size(
               gArgs.GetArg("-limitancestorsize", DEFAULT_ANCESTOR_SIZE_LIMIT) *
               1000),
           m_limit_descendants(
               gArgs.GetArg("-limitdescendantcount", DEFAULT_DESCENDANT_LIMIT)),
           m_limit_descendant_size(gArgs.GetArg("-limitdescendantsize",
                                                DEFAULT_DESCENDANT_SIZE_LIMIT) *
                                   1000) {
         assert(std::addressof(::ChainstateActive()) ==
                std::addressof(m_active_chainstate));
     }
 
     // We put the arguments we're handed into a struct, so we can pass them
     // around easier.
     struct ATMPArgs {
         const Config &m_config;
         TxValidationState &m_state;
         const int64_t m_accept_time;
         const bool m_bypass_limits;
         /*
          * Return any outpoints which were not previously present in the coins
          * cache, but were added as a result of validating the tx for mempool
          * acceptance. This allows the caller to optionally remove the cache
          * additions if the associated transaction ends up being rejected by
          * the mempool.
          */
         std::vector<COutPoint> &m_coins_to_uncache;
         const bool m_test_accept;
         Amount *m_fee_out;
     };
 
     // Single transaction acceptance
     bool AcceptSingleTransaction(const CTransactionRef &ptx, ATMPArgs &args)
         EXCLUSIVE_LOCKS_REQUIRED(cs_main);
 
 private:
     // All the intermediate state that gets passed between the various levels
     // of checking a given transaction.
     struct Workspace {
         Workspace(const CTransactionRef &ptx,
                   const uint32_t next_block_script_verify_flags)
             : m_ptx(ptx),
               m_next_block_script_verify_flags(next_block_script_verify_flags) {
         }
         CTxMemPool::setEntries m_ancestors;
         std::unique_ptr<CTxMemPoolEntry> m_entry;
 
         Amount m_modified_fees;
 
         const CTransactionRef &m_ptx;
 
         // ABC specific flags that are used in both PreChecks and
         // ConsensusScriptChecks
         const uint32_t m_next_block_script_verify_flags;
         int m_sig_checks_standard;
     };
 
     // Run the policy checks on a given transaction, excluding any script
     // checks. Looks up inputs, calculates feerate, considers replacement,
     // evaluates package limits, etc. As this function can be invoked for "free"
     // by a peer, only tests that are fast should be done here (to avoid CPU
     // DoS).
     bool PreChecks(ATMPArgs &args, Workspace &ws)
         EXCLUSIVE_LOCKS_REQUIRED(cs_main, m_pool.cs);
 
     // Re-run the script checks, using consensus flags, and try to cache the
     // result in the scriptcache. This should be done after
     // PolicyScriptChecks(). This requires that all inputs either be in our
     // utxo set or in the mempool.
     bool ConsensusScriptChecks(ATMPArgs &args, const Workspace &ws,
                                PrecomputedTransactionData &txdata)
         EXCLUSIVE_LOCKS_REQUIRED(cs_main);
 
     // Try to add the transaction to the mempool, removing any conflicts first.
     // Returns true if the transaction is in the mempool after any size
     // limiting is performed, false otherwise.
     bool Finalize(ATMPArgs &args, Workspace &ws)
         EXCLUSIVE_LOCKS_REQUIRED(cs_main, m_pool.cs);
 
 private:
     CTxMemPool &m_pool;
     CCoinsViewCache m_view;
     CCoinsViewMemPool m_viewmempool;
     CCoinsView m_dummy;
 
     CChainState &m_active_chainstate;
 
     // The package limits in effect at the time of invocation.
     const size_t m_limit_ancestors;
     const size_t m_limit_ancestor_size;
     // These may be modified while evaluating a transaction (eg to account for
     // in-mempool conflicts; see below).
     size_t m_limit_descendants;
     size_t m_limit_descendant_size;
 };
 
 bool MemPoolAccept::PreChecks(ATMPArgs &args, Workspace &ws) {
     const CTransactionRef &ptx = ws.m_ptx;
     const CTransaction &tx = *ws.m_ptx;
     const TxId &txid = ws.m_ptx->GetId();
 
     // Copy/alias what we need out of args
     TxValidationState &state = args.m_state;
     const int64_t nAcceptTime = args.m_accept_time;
     const bool bypass_limits = args.m_bypass_limits;
     std::vector<COutPoint> &coins_to_uncache = args.m_coins_to_uncache;
 
     // Alias what we need out of ws
     CTxMemPool::setEntries &setAncestors = ws.m_ancestors;
     std::unique_ptr<CTxMemPoolEntry> &entry = ws.m_entry;
     Amount &nModifiedFees = ws.m_modified_fees;
 
     // Coinbase is only valid in a block, not as a loose transaction.
     if (!CheckRegularTransaction(tx, state)) {
         // state filled in by CheckRegularTransaction.
         return false;
     }
 
     // Rather not work on nonstandard transactions (unless -testnet)
     std::string reason;
     if (fRequireStandard && !IsStandardTx(tx, reason)) {
         return state.Invalid(TxValidationResult::TX_NOT_STANDARD, reason);
     }
 
     // Only accept nLockTime-using transactions that can be mined in the next
     // block; we don't want our mempool filled up with transactions that can't
     // be mined yet.
     assert(std::addressof(::ChainActive()) ==
            std::addressof(m_active_chainstate.m_chain));
     TxValidationState ctxState;
     if (!ContextualCheckTransactionForCurrentBlock(
             m_active_chainstate.m_chain.Tip(),
             args.m_config.GetChainParams().GetConsensus(), tx, ctxState,
             STANDARD_LOCKTIME_VERIFY_FLAGS)) {
         // We copy the state from a dummy to ensure we don't increase the
         // ban score of peer for transaction that could be valid in the future.
         return state.Invalid(TxValidationResult::TX_PREMATURE_SPEND,
                              ctxState.GetRejectReason(),
                              ctxState.GetDebugMessage());
     }
 
     // Is it already in the memory pool?
     if (m_pool.exists(txid)) {
         return state.Invalid(TxValidationResult::TX_CONFLICT,
                              "txn-already-in-mempool");
     }
 
     // Check for conflicts with in-memory transactions
     for (const CTxIn &txin : tx.vin) {
         auto itConflicting = m_pool.mapNextTx.find(txin.prevout);
         if (itConflicting != m_pool.mapNextTx.end()) {
             // Disable replacement feature for good
             return state.Invalid(TxValidationResult::TX_MEMPOOL_POLICY,
                                  "txn-mempool-conflict");
         }
     }
 
     LockPoints lp;
     m_view.SetBackend(m_viewmempool);
 
     assert(std::addressof(::ChainstateActive().CoinsTip()) ==
            std::addressof(m_active_chainstate.CoinsTip()));
     const CCoinsViewCache &coins_cache = m_active_chainstate.CoinsTip();
     // Do all inputs exist?
     for (const CTxIn &txin : tx.vin) {
         if (!coins_cache.HaveCoinInCache(txin.prevout)) {
             coins_to_uncache.push_back(txin.prevout);
         }
 
         // Note: this call may add txin.prevout to the coins cache
         // (coins_cache.cacheCoins) by way of FetchCoin(). It should be
         // removed later (via coins_to_uncache) if this tx turns out to be
         // invalid.
         if (!m_view.HaveCoin(txin.prevout)) {
             // Are inputs missing because we already have the tx?
             for (size_t out = 0; out < tx.vout.size(); out++) {
                 // Optimistically just do efficient check of cache for
                 // outputs.
                 if (coins_cache.HaveCoinInCache(COutPoint(txid, out))) {
                     return state.Invalid(TxValidationResult::TX_CONFLICT,
                                          "txn-already-known");
                 }
             }
 
             // Otherwise assume this might be an orphan tx for which we just
             // haven't seen parents yet.
             return state.Invalid(TxValidationResult::TX_MISSING_INPUTS,
                                  "bad-txns-inputs-missingorspent");
         }
     }
 
     // Are the actual inputs available?
     if (!m_view.HaveInputs(tx)) {
         return state.Invalid(TxValidationResult::TX_MEMPOOL_POLICY,
                              "bad-txns-inputs-spent");
     }
 
     // Bring the best block into scope.
     m_view.GetBestBlock();
 
     // we have all inputs cached now, so switch back to dummy (to protect
     // against bugs where we pull more inputs from disk that miss being
     // added to coins_to_uncache)
     m_view.SetBackend(m_dummy);
 
     // Only accept BIP68 sequence locked transactions that can be mined in
     // the next block; we don't want our mempool filled up with transactions
     // that can't be mined yet. Must keep pool.cs for this unless we change
     // CheckSequenceLocks to take a CoinsViewCache instead of create its
     // own.
     assert(std::addressof(::ChainstateActive()) ==
            std::addressof(m_active_chainstate));
     if (!CheckSequenceLocks(m_active_chainstate, m_pool, tx,
                             STANDARD_LOCKTIME_VERIFY_FLAGS, &lp)) {
         return state.Invalid(TxValidationResult::TX_PREMATURE_SPEND,
                              "non-BIP68-final");
     }
 
     assert(std::addressof(g_chainman.m_blockman) ==
            std::addressof(m_active_chainstate.m_blockman));
     Amount nFees = Amount::zero();
     if (!Consensus::CheckTxInputs(
             tx, state, m_view,
             m_active_chainstate.m_blockman.GetSpendHeight(m_view), nFees)) {
         // state filled in by CheckTxInputs
         return false;
     }
 
     // If fee_out is passed, return the fee to the caller
     if (args.m_fee_out) {
         *args.m_fee_out = nFees;
     }
 
     // Check for non-standard pay-to-script-hash in inputs
     if (fRequireStandard &&
         !AreInputsStandard(tx, m_view, ws.m_next_block_script_verify_flags)) {
         return state.Invalid(TxValidationResult::TX_INPUTS_NOT_STANDARD,
                              "bad-txns-nonstandard-inputs");
     }
 
     // nModifiedFees includes any fee deltas from PrioritiseTransaction
     nModifiedFees = nFees;
     m_pool.ApplyDelta(txid, nModifiedFees);
 
     // Keep track of transactions that spend a coinbase, which we re-scan
     // during reorgs to ensure COINBASE_MATURITY is still met.
     bool fSpendsCoinbase = false;
     for (const CTxIn &txin : tx.vin) {
         const Coin &coin = m_view.AccessCoin(txin.prevout);
         if (coin.IsCoinBase()) {
             fSpendsCoinbase = true;
             break;
         }
     }
 
     unsigned int nSize = tx.GetTotalSize();
 
     // No transactions are allowed below minRelayTxFee except from disconnected
     // blocks.
     // Do not change this to use virtualsize without coordinating a network
     // policy upgrade.
     if (!bypass_limits && nModifiedFees < minRelayTxFee.GetFee(nSize)) {
         return state.Invalid(
             TxValidationResult::TX_MEMPOOL_POLICY, "min relay fee not met",
             strprintf("%d < %d", nModifiedFees, ::minRelayTxFee.GetFee(nSize)));
     }
 
     // Validate input scripts against standard script flags.
     const uint32_t scriptVerifyFlags =
         ws.m_next_block_script_verify_flags | STANDARD_SCRIPT_VERIFY_FLAGS;
     PrecomputedTransactionData txdata(tx);
     if (!CheckInputScripts(tx, state, m_view, scriptVerifyFlags, true, false,
                            txdata, ws.m_sig_checks_standard)) {
         // State filled in by CheckInputScripts
         return false;
     }
 
     assert(std::addressof(::ChainActive()) ==
            std::addressof(m_active_chainstate.m_chain));
     entry.reset(new CTxMemPoolEntry(
         ptx, nFees, nAcceptTime, m_active_chainstate.m_chain.Height(),
         fSpendsCoinbase, ws.m_sig_checks_standard, lp));
 
     unsigned int nVirtualSize = entry->GetTxVirtualSize();
 
     Amount mempoolRejectFee =
         m_pool
             .GetMinFee(gArgs.GetArg("-maxmempool", DEFAULT_MAX_MEMPOOL_SIZE) *
                        1000000)
             .GetFee(nVirtualSize);
     if (!bypass_limits && mempoolRejectFee > Amount::zero() &&
         nModifiedFees < mempoolRejectFee) {
         return state.Invalid(
             TxValidationResult::TX_MEMPOOL_POLICY, "mempool min fee not met",
             strprintf("%d < %d", nModifiedFees, mempoolRejectFee));
     }
 
     // Calculate in-mempool ancestors, up to a limit.
     std::string errString;
     if (!m_pool.CalculateMemPoolAncestors(
             *entry, setAncestors, m_limit_ancestors, m_limit_ancestor_size,
             m_limit_descendants, m_limit_descendant_size, errString)) {
         return state.Invalid(TxValidationResult::TX_MEMPOOL_POLICY,
                              "too-long-mempool-chain", errString);
     }
     return true;
 }
 
 bool MemPoolAccept::ConsensusScriptChecks(ATMPArgs &args, const Workspace &ws,
                                           PrecomputedTransactionData &txdata) {
     const CTransaction &tx = *ws.m_ptx;
     const TxId &txid = tx.GetId();
 
     TxValidationState &state = args.m_state;
 
     // Check again against the next block's script verification flags
     // to cache our script execution flags.
     //
     // This is also useful in case of bugs in the standard flags that cause
     // transactions to pass as valid when they're actually invalid. For
     // instance the STRICTENC flag was incorrectly allowing certain CHECKSIG
     // NOT scripts to pass, even though they were invalid.
     //
     // There is a similar check in CreateNewBlock() to prevent creating
     // invalid blocks (using TestBlockValidity), however allowing such
     // transactions into the mempool can be exploited as a DoS attack.
     assert(std::addressof(::ChainstateActive().CoinsTip()) ==
            std::addressof(m_active_chainstate.CoinsTip()));
     int nSigChecksConsensus;
     if (!CheckInputsFromMempoolAndCache(
             tx, state, m_view, m_pool, ws.m_next_block_script_verify_flags,
             txdata, nSigChecksConsensus, m_active_chainstate.CoinsTip())) {
         // This can occur under some circumstances, if the node receives an
         // unrequested tx which is invalid due to new consensus rules not
         // being activated yet (during IBD).
         return error("%s: BUG! PLEASE REPORT THIS! CheckInputScripts failed "
                      "against next-block but not STANDARD flags %s, %s",
                      __func__, txid.ToString(), state.ToString());
     }
 
     if (ws.m_sig_checks_standard != nSigChecksConsensus) {
         // We can't accept this transaction as we've used the standard count
         // for the mempool/mining, but the consensus count will be enforced
         // in validation (we don't want to produce bad block templates).
         return error(
             "%s: BUG! PLEASE REPORT THIS! SigChecks count differed between "
             "standard and consensus flags in %s",
             __func__, txid.ToString());
     }
     return true;
 }
 
 bool MemPoolAccept::Finalize(ATMPArgs &args, Workspace &ws) {
     const TxId &txid = ws.m_ptx->GetId();
     TxValidationState &state = args.m_state;
     const bool bypass_limits = args.m_bypass_limits;
 
     CTxMemPool::setEntries &setAncestors = ws.m_ancestors;
     std::unique_ptr<CTxMemPoolEntry> &entry = ws.m_entry;
 
     // Store transaction in memory.
     m_pool.addUnchecked(*entry, setAncestors);
 
     // Trim mempool and check if tx was trimmed.
     if (!bypass_limits) {
         assert(std::addressof(::ChainstateActive().CoinsTip()) ==
                std::addressof(m_active_chainstate.CoinsTip()));
         m_pool.LimitSize(
             m_active_chainstate.CoinsTip(),
             gArgs.GetArg("-maxmempool", DEFAULT_MAX_MEMPOOL_SIZE) * 1000000,
             std::chrono::hours{
                 gArgs.GetArg("-mempoolexpiry", DEFAULT_MEMPOOL_EXPIRY)});
         if (!m_pool.exists(txid)) {
             return state.Invalid(TxValidationResult::TX_MEMPOOL_POLICY,
                                  "mempool full");
         }
     }
     return true;
 }
 
 bool MemPoolAccept::AcceptSingleTransaction(const CTransactionRef &ptx,
                                             ATMPArgs &args) {
     AssertLockHeld(cs_main);
     // mempool "read lock" (held through
     // GetMainSignals().TransactionAddedToMempool())
     LOCK(m_pool.cs);
 
     Workspace workspace(ptx, GetNextBlockScriptFlags(
                                  args.m_config.GetChainParams().GetConsensus(),
                                  ::ChainActive().Tip()));
 
     if (!PreChecks(args, workspace)) {
         return false;
     }
 
     // Only compute the precomputed transaction data if we need to verify
     // scripts (ie, other policy checks pass). We perform the inexpensive
     // checks first and avoid hashing and signature verification unless those
     // checks pass, to mitigate CPU exhaustion denial-of-service attacks.
     PrecomputedTransactionData txdata(*ptx);
 
     if (!ConsensusScriptChecks(args, workspace, txdata)) {
         return false;
     }
 
     // Tx was accepted, but not added
     if (args.m_test_accept) {
         return true;
     }
 
     if (!Finalize(args, workspace)) {
         return false;
     }
 
     GetMainSignals().TransactionAddedToMempool(
         ptx, m_pool.GetAndIncrementSequence());
 
     return true;
 }
 
 } // namespace
 
 /**
  * (try to) add transaction to memory pool with a specified acceptance time.
  */
 static bool AcceptToMemoryPoolWithTime(
     const Config &config, CTxMemPool &pool, CChainState &active_chainstate,
     TxValidationState &state, const CTransactionRef &tx, int64_t nAcceptTime,
     bool bypass_limits, bool test_accept, Amount *fee_out = nullptr)
     EXCLUSIVE_LOCKS_REQUIRED(cs_main) {
     AssertLockHeld(cs_main);
     std::vector<COutPoint> coins_to_uncache;
     MemPoolAccept::ATMPArgs args{
         config,           state,       nAcceptTime, bypass_limits,
         coins_to_uncache, test_accept, fee_out};
     assert(std::addressof(::ChainstateActive()) ==
            std::addressof(active_chainstate));
     bool res = MemPoolAccept(pool, active_chainstate)
                    .AcceptSingleTransaction(tx, args);
     if (!res) {
         // Remove coins that were not present in the coins cache before calling
         // ATMPW; this is to prevent memory DoS in case we receive a large
         // number of invalid transactions that attempt to overrun the in-memory
         // coins cache
         // (`CCoinsViewCache::cacheCoins`).
 
         for (const COutPoint &outpoint : coins_to_uncache) {
             active_chainstate.CoinsTip().Uncache(outpoint);
         }
     }
 
     // After we've (potentially) uncached entries, ensure our coins cache is
     // still within its size limits
     BlockValidationState stateDummy;
     active_chainstate.FlushStateToDisk(config.GetChainParams(), stateDummy,
                                        FlushStateMode::PERIODIC);
     return res;
 }
 
 bool AcceptToMemoryPool(CChainState &active_chainstate, const Config &config,
                         CTxMemPool &pool, TxValidationState &state,
                         const CTransactionRef &tx, bool bypass_limits,
                         bool test_accept, Amount *fee_out) {
     return AcceptToMemoryPoolWithTime(config, pool, active_chainstate, state,
                                       tx, GetTime(), bypass_limits, test_accept,
                                       fee_out);
 }
 
 CTransactionRef GetTransaction(const CBlockIndex *const block_index,
                                const CTxMemPool *const mempool,
                                const TxId &txid,
                                const Consensus::Params &consensusParams,
                                BlockHash &hashBlock) {
     LOCK(cs_main);
 
     if (block_index) {
         CBlock block;
         if (ReadBlockFromDisk(block, block_index, consensusParams)) {
             for (const auto &tx : block.vtx) {
                 if (tx->GetId() == txid) {
                     hashBlock = block_index->GetBlockHash();
                     return tx;
                 }
             }
         }
         return nullptr;
     }
     if (mempool) {
         CTransactionRef ptx = mempool->get(txid);
         if (ptx) {
             return ptx;
         }
     }
     if (g_txindex) {
         CTransactionRef tx;
         if (g_txindex->FindTx(txid, hashBlock, tx)) {
             return tx;
         }
     }
     return nullptr;
 }
 
 //////////////////////////////////////////////////////////////////////////////
 //
 // CBlock and CBlockIndex
 //
 
 static bool WriteBlockToDisk(const CBlock &block, FlatFilePos &pos,
                              const CMessageHeader::MessageMagic &messageStart) {
     // Open history file to append
     CAutoFile fileout(OpenBlockFile(pos), SER_DISK, CLIENT_VERSION);
     if (fileout.IsNull()) {
         return error("WriteBlockToDisk: OpenBlockFile failed");
     }
 
     // Write index header
     unsigned int nSize = GetSerializeSize(block, fileout.GetVersion());
     fileout << messageStart << nSize;
 
     // Write block
     long fileOutPos = ftell(fileout.Get());
     if (fileOutPos < 0) {
         return error("WriteBlockToDisk: ftell failed");
     }
 
     pos.nPos = (unsigned int)fileOutPos;
     fileout << block;
 
     return true;
 }
 
 Amount GetBlockSubsidy(int nHeight, const Consensus::Params &consensusParams) {
     int halvings = nHeight / consensusParams.nSubsidyHalvingInterval;
     // Force block reward to zero when right shift is undefined.
     if (halvings >= 64) {
         return Amount::zero();
     }
 
     Amount nSubsidy = 50 * COIN;
     // Subsidy is cut in half every 210,000 blocks which will occur
     // approximately every 4 years.
     return ((nSubsidy / SATOSHI) >> halvings) * SATOSHI;
 }
 
 CoinsViews::CoinsViews(std::string ldb_name, size_t cache_size_bytes,
                        bool in_memory, bool should_wipe)
     : m_dbview(GetDataDir() / ldb_name, cache_size_bytes, in_memory,
                should_wipe),
       m_catcherview(&m_dbview) {}
 
 void CoinsViews::InitCache() {
     m_cacheview = std::make_unique<CCoinsViewCache>(&m_catcherview);
 }
 
 CChainState::CChainState(CTxMemPool &mempool, BlockManager &blockman,
                          BlockHash from_snapshot_blockhash)
     : m_mempool(mempool), m_blockman(blockman),
       m_from_snapshot_blockhash(from_snapshot_blockhash) {}
 
 void CChainState::InitCoinsDB(size_t cache_size_bytes, bool in_memory,
                               bool should_wipe, std::string leveldb_name) {
     if (!m_from_snapshot_blockhash.IsNull()) {
         leveldb_name += "_" + m_from_snapshot_blockhash.ToString();
     }
     m_coins_views = std::make_unique<CoinsViews>(leveldb_name, cache_size_bytes,
                                                  in_memory, should_wipe);
 }
 
 void CChainState::InitCoinsCache(size_t cache_size_bytes) {
     assert(m_coins_views != nullptr);
     m_coinstip_cache_size_bytes = cache_size_bytes;
     m_coins_views->InitCache();
 }
 
 // Note that though this is marked const, we may end up modifying
 // `m_cached_finished_ibd`, which is a performance-related implementation
 // detail. This function must be marked `const` so that `CValidationInterface`
 // clients (which are given a `const CChainState*`) can call it.
 //
 bool CChainState::IsInitialBlockDownload() const {
     // Optimization: pre-test latch before taking the lock.
     if (m_cached_finished_ibd.load(std::memory_order_relaxed)) {
         return false;
     }
 
     LOCK(cs_main);
     if (m_cached_finished_ibd.load(std::memory_order_relaxed)) {
         return false;
     }
     if (fImporting || fReindex) {
         return true;
     }
     if (m_chain.Tip() == nullptr) {
         return true;
     }
     if (m_chain.Tip()->nChainWork < nMinimumChainWork) {
         return true;
     }
     if (m_chain.Tip()->GetBlockTime() < (GetTime() - nMaxTipAge)) {
         return true;
     }
     LogPrintf("Leaving InitialBlockDownload (latching to false)\n");
     m_cached_finished_ibd.store(true, std::memory_order_relaxed);
     return false;
 }
 
 static CBlockIndex const *pindexBestForkTip = nullptr;
 static CBlockIndex const *pindexBestForkBase = nullptr;
 
 static void AlertNotify(const std::string &strMessage) {
     uiInterface.NotifyAlertChanged();
 #if defined(HAVE_SYSTEM)
     std::string strCmd = gArgs.GetArg("-alertnotify", "");
     if (strCmd.empty()) {
         return;
     }
 
     // Alert text should be plain ascii coming from a trusted source, but to be
     // safe we first strip anything not in safeChars, then add single quotes
     // around the whole string before passing it to the shell:
     std::string singleQuote("'");
     std::string safeStatus = SanitizeString(strMessage);
     safeStatus = singleQuote + safeStatus + singleQuote;
     boost::replace_all(strCmd, "%s", safeStatus);
 
     std::thread t(runCommand, strCmd);
     // thread runs free
     t.detach();
 #endif
 }
 
 static void CheckForkWarningConditions() EXCLUSIVE_LOCKS_REQUIRED(cs_main) {
     AssertLockHeld(cs_main);
     // Before we get past initial download, we cannot reliably alert about forks
     // (we assume we don't get stuck on a fork before finishing our initial
     // sync)
     if (::ChainstateActive().IsInitialBlockDownload()) {
         return;
     }
 
     // If our best fork is no longer within 72 blocks (+/- 12 hours if no one
     // mines it) of our head, drop it
     if (pindexBestForkTip &&
         ::ChainActive().Height() - pindexBestForkTip->nHeight >= 72) {
         pindexBestForkTip = nullptr;
     }
 
     if (pindexBestForkTip ||
         (pindexBestInvalid &&
          pindexBestInvalid->nChainWork >
              ::ChainActive().Tip()->nChainWork +
                  (GetBlockProof(*::ChainActive().Tip()) * 6))) {
         if (!GetfLargeWorkForkFound() && pindexBestForkBase) {
             std::string warning =
                 std::string("'Warning: Large-work fork detected, forking after "
                             "block ") +
                 pindexBestForkBase->phashBlock->ToString() + std::string("'");
             AlertNotify(warning);
         }
 
         if (pindexBestForkTip && pindexBestForkBase) {
             LogPrintf("%s: Warning: Large fork found\n  forking the "
                       "chain at height %d (%s)\n  lasting to height %d "
                       "(%s).\nChain state database corruption likely.\n",
                       __func__, pindexBestForkBase->nHeight,
                       pindexBestForkBase->phashBlock->ToString(),
                       pindexBestForkTip->nHeight,
                       pindexBestForkTip->phashBlock->ToString());
             SetfLargeWorkForkFound(true);
         } else {
             LogPrintf("%s: Warning: Found invalid chain at least ~6 blocks "
                       "longer than our best chain.\nChain state database "
                       "corruption likely.\n",
                       __func__);
             SetfLargeWorkInvalidChainFound(true);
         }
     } else {
         SetfLargeWorkForkFound(false);
         SetfLargeWorkInvalidChainFound(false);
     }
 }
 
 static void CheckForkWarningConditionsOnNewFork(CBlockIndex *pindexNewForkTip)
     EXCLUSIVE_LOCKS_REQUIRED(cs_main) {
     AssertLockHeld(cs_main);
     // If we are on a fork that is sufficiently large, set a warning flag.
     const CBlockIndex *pfork = ::ChainActive().FindFork(pindexNewForkTip);
 
     // We define a condition where we should warn the user about as a fork of at
     // least 7 blocks with a tip within 72 blocks (+/- 12 hours if no one mines
     // it) of ours. We use 7 blocks rather arbitrarily as it represents just
     // under 10% of sustained network hash rate operating on the fork, or a
     // chain that is entirely longer than ours and invalid (note that this
     // should be detected by both). We define it this way because it allows us
     // to only store the highest fork tip (+ base) which meets the 7-block
     // condition and from this always have the most-likely-to-cause-warning fork
     if (pfork &&
         (!pindexBestForkTip ||
          pindexNewForkTip->nHeight > pindexBestForkTip->nHeight) &&
         pindexNewForkTip->nChainWork - pfork->nChainWork >
             (GetBlockProof(*pfork) * 7) &&
         ::ChainActive().Height() - pindexNewForkTip->nHeight < 72) {
         pindexBestForkTip = pindexNewForkTip;
         pindexBestForkBase = pfork;
     }
 
     CheckForkWarningConditions();
 }
 
 // Called both upon regular invalid block discovery *and* InvalidateBlock
 void CChainState::InvalidChainFound(CBlockIndex *pindexNew) {
     AssertLockHeld(cs_main);
     if (!pindexBestInvalid ||
         pindexNew->nChainWork > pindexBestInvalid->nChainWork) {
         pindexBestInvalid = pindexNew;
     }
     if (pindexBestHeader != nullptr &&
         pindexBestHeader->GetAncestor(pindexNew->nHeight) == pindexNew) {
         pindexBestHeader = ::ChainActive().Tip();
     }
 
     // If the invalid chain found is supposed to be finalized, we need to move
     // back the finalization point.
     if (IsBlockFinalized(pindexNew)) {
         m_finalizedBlockIndex = pindexNew->pprev;
     }
 
     LogPrintf("%s: invalid block=%s  height=%d  log2_work=%f  date=%s\n",
               __func__, pindexNew->GetBlockHash().ToString(),
               pindexNew->nHeight,
               log(pindexNew->nChainWork.getdouble()) / log(2.0),
               FormatISO8601DateTime(pindexNew->GetBlockTime()));
     CBlockIndex *tip = ::ChainActive().Tip();
     assert(tip);
     LogPrintf("%s:  current best=%s  height=%d  log2_work=%f  date=%s\n",
               __func__, tip->GetBlockHash().ToString(),
               ::ChainActive().Height(),
               log(tip->nChainWork.getdouble()) / log(2.0),
               FormatISO8601DateTime(tip->GetBlockTime()));
 }
 
 // Same as InvalidChainFound, above, except not called directly from
 // InvalidateBlock, which does its own setBlockIndexCandidates management.
 void CChainState::InvalidBlockFound(CBlockIndex *pindex,
                                     const BlockValidationState &state) {
     if (state.GetResult() != BlockValidationResult::BLOCK_MUTATED) {
         pindex->nStatus = pindex->nStatus.withFailed();
         m_blockman.m_failed_blocks.insert(pindex);
         setDirtyBlockIndex.insert(pindex);
         InvalidChainFound(pindex);
     }
 }
 
 void SpendCoins(CCoinsViewCache &view, const CTransaction &tx, CTxUndo &txundo,
                 int nHeight) {
     // Mark inputs spent.
     if (tx.IsCoinBase()) {
         return;
     }
 
     txundo.vprevout.reserve(tx.vin.size());
     for (const CTxIn &txin : tx.vin) {
         txundo.vprevout.emplace_back();
         bool is_spent = view.SpendCoin(txin.prevout, &txundo.vprevout.back());
         assert(is_spent);
     }
 }
 
 void UpdateCoins(CCoinsViewCache &view, const CTransaction &tx, CTxUndo &txundo,
                  int nHeight) {
     SpendCoins(view, tx, txundo, nHeight);
     AddCoins(view, tx, nHeight);
 }
 
 void UpdateCoins(CCoinsViewCache &view, const CTransaction &tx, int nHeight) {
     // Mark inputs spent.
     if (!tx.IsCoinBase()) {
         for (const CTxIn &txin : tx.vin) {
             bool is_spent = view.SpendCoin(txin.prevout);
             assert(is_spent);
         }
     }
 
     // Add outputs.
     AddCoins(view, tx, nHeight);
 }
 
 bool CScriptCheck::operator()() {
     const CScript &scriptSig = ptxTo->vin[nIn].scriptSig;
     if (!VerifyScript(scriptSig, m_tx_out.scriptPubKey, nFlags,
                       CachingTransactionSignatureChecker(
                           ptxTo, nIn, m_tx_out.nValue, cacheStore, txdata),
                       metrics, &error)) {
         return false;
     }
     if ((pTxLimitSigChecks &&
          !pTxLimitSigChecks->consume_and_check(metrics.nSigChecks)) ||
         (pBlockLimitSigChecks &&
          !pBlockLimitSigChecks->consume_and_check(metrics.nSigChecks))) {
         // we can't assign a meaningful script error (since the script
         // succeeded), but remove the ScriptError::OK which could be
         // misinterpreted.
         error = ScriptError::SIGCHECKS_LIMIT_EXCEEDED;
         return false;
     }
     return true;
 }
 
 int BlockManager::GetSpendHeight(const CCoinsViewCache &inputs) {
     AssertLockHeld(cs_main);
     assert(std::addressof(g_chainman.m_blockman) == std::addressof(*this));
     CBlockIndex *pindexPrev = LookupBlockIndex(inputs.GetBestBlock());
     return pindexPrev->nHeight + 1;
 }
 
 bool CheckInputScripts(const CTransaction &tx, TxValidationState &state,
                        const CCoinsViewCache &inputs, const uint32_t flags,
                        bool sigCacheStore, bool scriptCacheStore,
                        const PrecomputedTransactionData &txdata,
                        int &nSigChecksOut, TxSigCheckLimiter &txLimitSigChecks,
                        CheckInputsLimiter *pBlockLimitSigChecks,
                        std::vector<CScriptCheck> *pvChecks) {
     AssertLockHeld(cs_main);
     assert(!tx.IsCoinBase());
 
     if (pvChecks) {
         pvChecks->reserve(tx.vin.size());
     }
 
     // First check if script executions have been cached with the same flags.
     // Note that this assumes that the inputs provided are correct (ie that the
     // transaction hash which is in tx's prevouts properly commits to the
     // scriptPubKey in the inputs view of that transaction).
     ScriptCacheKey hashCacheEntry(tx, flags);
     if (IsKeyInScriptCache(hashCacheEntry, !scriptCacheStore, nSigChecksOut)) {
         if (!txLimitSigChecks.consume_and_check(nSigChecksOut) ||
             (pBlockLimitSigChecks &&
              !pBlockLimitSigChecks->consume_and_check(nSigChecksOut))) {
             return state.Invalid(TxValidationResult::TX_CONSENSUS,
                                  "too-many-sigchecks");
         }
         return true;
     }
 
     int nSigChecksTotal = 0;
 
     for (size_t i = 0; i < tx.vin.size(); i++) {
         const COutPoint &prevout = tx.vin[i].prevout;
         const Coin &coin = inputs.AccessCoin(prevout);
         assert(!coin.IsSpent());
 
         // We very carefully only pass in things to CScriptCheck which are
         // clearly committed to by tx's hash. This provides a sanity
         // check that our caching is not introducing consensus failures through
         // additional data in, eg, the coins being spent being checked as a part
         // of CScriptCheck.
 
         // Verify signature
         CScriptCheck check(coin.GetTxOut(), tx, i, flags, sigCacheStore, txdata,
                            &txLimitSigChecks, pBlockLimitSigChecks);
 
         // If pvChecks is not null, defer the check execution to the caller.
         if (pvChecks) {
             pvChecks->push_back(std::move(check));
             continue;
         }
 
         if (!check()) {
             ScriptError scriptError = check.GetScriptError();
             // Compute flags without the optional standardness flags.
             // This differs from MANDATORY_SCRIPT_VERIFY_FLAGS as it contains
             // additional upgrade flags (see AcceptToMemoryPoolWorker variable
             // extraFlags).
             uint32_t mandatoryFlags =
                 flags & ~STANDARD_NOT_MANDATORY_VERIFY_FLAGS;
             if (flags != mandatoryFlags) {
                 // Check whether the failure was caused by a non-mandatory
                 // script verification check. If so, ensure we return
                 // NOT_STANDARD instead of CONSENSUS to avoid downstream users
                 // splitting the network between upgraded and non-upgraded nodes
                 // by banning CONSENSUS-failing data providers.
                 CScriptCheck check2(coin.GetTxOut(), tx, i, mandatoryFlags,
                                     sigCacheStore, txdata);
                 if (check2()) {
                     return state.Invalid(
                         TxValidationResult::TX_NOT_STANDARD,
                         strprintf("non-mandatory-script-verify-flag (%s)",
                                   ScriptErrorString(scriptError)));
                 }
                 // update the error message to reflect the mandatory violation.
                 scriptError = check2.GetScriptError();
             }
 
             // MANDATORY flag failures correspond to
             // TxValidationResult::TX_CONSENSUS. Because CONSENSUS failures are
             // the most serious case of validation failures, we may need to
             // consider using RECENT_CONSENSUS_CHANGE for any script failure
             // that could be due to non-upgraded nodes which we may want to
             // support, to avoid splitting the network (but this depends on the
             // details of how net_processing handles such errors).
             return state.Invalid(
                 TxValidationResult::TX_CONSENSUS,
                 strprintf("mandatory-script-verify-flag-failed (%s)",
                           ScriptErrorString(scriptError)));
         }
 
         nSigChecksTotal += check.GetScriptExecutionMetrics().nSigChecks;
     }
 
     nSigChecksOut = nSigChecksTotal;
 
     if (scriptCacheStore && !pvChecks) {
         // We executed all of the provided scripts, and were told to cache the
         // result. Do so now.
         AddKeyInScriptCache(hashCacheEntry, nSigChecksTotal);
     }
 
     return true;
 }
 
 static bool UndoWriteToDisk(const CBlockUndo &blockundo, FlatFilePos &pos,
                             const BlockHash &hashBlock,
                             const CMessageHeader::MessageMagic &messageStart) {
     // Open history file to append
     CAutoFile fileout(OpenUndoFile(pos), SER_DISK, CLIENT_VERSION);
     if (fileout.IsNull()) {
         return error("%s: OpenUndoFile failed", __func__);
     }
 
     // Write index header
     unsigned int nSize = GetSerializeSize(blockundo, fileout.GetVersion());
     fileout << messageStart << nSize;
 
     // Write undo data
     long fileOutPos = ftell(fileout.Get());
     if (fileOutPos < 0) {
         return error("%s: ftell failed", __func__);
     }
     pos.nPos = (unsigned int)fileOutPos;
     fileout << blockundo;
 
     // calculate & write checksum
     CHashWriter hasher(SER_GETHASH, PROTOCOL_VERSION);
     hasher << hashBlock;
     hasher << blockundo;
     fileout << hasher.GetHash();
 
     return true;
 }
 
 bool UndoReadFromDisk(CBlockUndo &blockundo, const CBlockIndex *pindex) {
     FlatFilePos pos = pindex->GetUndoPos();
     if (pos.IsNull()) {
         return error("%s: no undo data available", __func__);
     }
 
     // Open history file to read
     CAutoFile filein(OpenUndoFile(pos, true), SER_DISK, CLIENT_VERSION);
     if (filein.IsNull()) {
         return error("%s: OpenUndoFile failed", __func__);
     }
 
     // Read block
     uint256 hashChecksum;
     // We need a CHashVerifier as reserializing may lose data
     CHashVerifier<CAutoFile> verifier(&filein);
     try {
         verifier << pindex->pprev->GetBlockHash();
         verifier >> blockundo;
         filein >> hashChecksum;
     } catch (const std::exception &e) {
         return error("%s: Deserialize or I/O error - %s", __func__, e.what());
     }
 
     // Verify checksum
     if (hashChecksum != verifier.GetHash()) {
         return error("%s: Checksum mismatch", __func__);
     }
 
     return true;
 }
 
 /** Abort with a message */
 static bool AbortNode(const std::string &strMessage,
                       bilingual_str user_message = bilingual_str()) {
     SetMiscWarning(Untranslated(strMessage));
     LogPrintf("*** %s\n", strMessage);
     if (!user_message.empty()) {
         user_message =
             _("A fatal internal error occurred, see debug.log for details");
     }
     AbortError(user_message);
     StartShutdown();
     return false;
 }
 
 static bool AbortNode(BlockValidationState &state,
                       const std::string &strMessage,
                       const bilingual_str &userMessage = bilingual_str()) {
     AbortNode(strMessage, userMessage);
     return state.Error(strMessage);
 }
 
 /** Restore the UTXO in a Coin at a given COutPoint. */
 DisconnectResult UndoCoinSpend(const Coin &undo, CCoinsViewCache &view,
                                const COutPoint &out) {
     bool fClean = true;
 
     if (view.HaveCoin(out)) {
         // Overwriting transaction output.
         fClean = false;
     }
 
     if (undo.GetHeight() == 0) {
         // Missing undo metadata (height and coinbase). Older versions included
         // this information only in undo records for the last spend of a
         // transactions' outputs. This implies that it must be present for some
         // other output of the same tx.
         const Coin &alternate = AccessByTxid(view, out.GetTxId());
         if (alternate.IsSpent()) {
             // Adding output for transaction without known metadata
             return DisconnectResult::FAILED;
         }
 
         // This is somewhat ugly, but hopefully utility is limited. This is only
         // useful when working from legacy on disck data. In any case, putting
         // the correct information in there doesn't hurt.
         const_cast<Coin &>(undo) = Coin(undo.GetTxOut(), alternate.GetHeight(),
                                         alternate.IsCoinBase());
     }
 
     // If the coin already exists as an unspent coin in the cache, then the
     // possible_overwrite parameter to AddCoin must be set to true. We have
     // already checked whether an unspent coin exists above using HaveCoin, so
     // we don't need to guess. When fClean is false, an unspent coin already
     // existed and it is an overwrite.
     view.AddCoin(out, std::move(undo), !fClean);
 
     return fClean ? DisconnectResult::OK : DisconnectResult::UNCLEAN;
 }
 
 /**
  * Undo the effects of this block (with given index) on the UTXO set represented
  * by coins. When FAILED is returned, view is left in an indeterminate state.
  */
 DisconnectResult CChainState::DisconnectBlock(const CBlock &block,
                                               const CBlockIndex *pindex,
                                               CCoinsViewCache &view) {
     CBlockUndo blockUndo;
     if (!UndoReadFromDisk(blockUndo, pindex)) {
         error("DisconnectBlock(): failure reading undo data");
         return DisconnectResult::FAILED;
     }
 
     return ApplyBlockUndo(blockUndo, block, pindex, view);
 }
 
 DisconnectResult ApplyBlockUndo(const CBlockUndo &blockUndo,
                                 const CBlock &block, const CBlockIndex *pindex,
                                 CCoinsViewCache &view) {
     bool fClean = true;
 
     if (blockUndo.vtxundo.size() + 1 != block.vtx.size()) {
         error("DisconnectBlock(): block and undo data inconsistent");
         return DisconnectResult::FAILED;
     }
 
     // First, restore inputs.
     for (size_t i = 1; i < block.vtx.size(); i++) {
         const CTransaction &tx = *(block.vtx[i]);
         const CTxUndo &txundo = blockUndo.vtxundo[i - 1];
         if (txundo.vprevout.size() != tx.vin.size()) {
             error("DisconnectBlock(): transaction and undo data inconsistent");
             return DisconnectResult::FAILED;
         }
 
         for (size_t j = 0; j < tx.vin.size(); j++) {
             const COutPoint &out = tx.vin[j].prevout;
             const Coin &undo = txundo.vprevout[j];
             DisconnectResult res = UndoCoinSpend(undo, view, out);
             if (res == DisconnectResult::FAILED) {
                 return DisconnectResult::FAILED;
             }
             fClean = fClean && res != DisconnectResult::UNCLEAN;
         }
     }
 
     // Second, revert created outputs.
     for (const auto &ptx : block.vtx) {
         const CTransaction &tx = *ptx;
         const TxId &txid = tx.GetId();
         const bool is_coinbase = tx.IsCoinBase();
 
         // Check that all outputs are available and match the outputs in the
         // block itself exactly.
         for (size_t o = 0; o < tx.vout.size(); o++) {
             if (tx.vout[o].scriptPubKey.IsUnspendable()) {
                 continue;
             }
 
             COutPoint out(txid, o);
             Coin coin;
             bool is_spent = view.SpendCoin(out, &coin);
             if (!is_spent || tx.vout[o] != coin.GetTxOut() ||
                 uint32_t(pindex->nHeight) != coin.GetHeight() ||
                 is_coinbase != coin.IsCoinBase()) {
                 // transaction output mismatch
                 fClean = false;
             }
         }
     }
 
     // Move best block pointer to previous block.
     view.SetBestBlock(block.hashPrevBlock);
 
     return fClean ? DisconnectResult::OK : DisconnectResult::UNCLEAN;
 }
 
 static void FlushUndoFile(int block_file, bool finalize = false) {
     FlatFilePos undo_pos_old(block_file, vinfoBlockFile[block_file].nUndoSize);
     if (!UndoFileSeq().Flush(undo_pos_old, finalize)) {
         AbortNode("Flushing undo file to disk failed. This is likely the "
                   "result of an I/O error.");
     }
 }
 
 static void FlushBlockFile(bool fFinalize = false, bool finalize_undo = false) {
     LOCK(cs_LastBlockFile);
     FlatFilePos block_pos_old(nLastBlockFile,
                               vinfoBlockFile[nLastBlockFile].nSize);
     if (!BlockFileSeq().Flush(block_pos_old, fFinalize)) {
         AbortNode("Flushing block file to disk failed. This is likely the "
                   "result of an I/O error.");
     }
     // we do not always flush the undo file, as the chain tip may be lagging
     // behind the incoming blocks,
     // e.g. during IBD or a sync after a node going offline
     if (!fFinalize || finalize_undo) {
         FlushUndoFile(nLastBlockFile, finalize_undo);
     }
 }
 
 static bool FindUndoPos(BlockValidationState &state, int nFile,
                         FlatFilePos &pos, unsigned int nAddSize);
 
 static bool WriteUndoDataForBlock(const CBlockUndo &blockundo,
                                   BlockValidationState &state,
                                   CBlockIndex *pindex,
                                   const CChainParams &chainparams) {
     // Write undo information to disk
     if (pindex->GetUndoPos().IsNull()) {
         FlatFilePos _pos;
         if (!FindUndoPos(state, pindex->nFile, _pos,
                          ::GetSerializeSize(blockundo, CLIENT_VERSION) + 40)) {
             return error("ConnectBlock(): FindUndoPos failed");
         }
         if (!UndoWriteToDisk(blockundo, _pos, pindex->pprev->GetBlockHash(),
                              chainparams.DiskMagic())) {
             return AbortNode(state, "Failed to write undo data");
         }
         // rev files are written in block height order, whereas blk files are
         // written as blocks come in (often out of order) we want to flush the
         // rev (undo) file once we've written the last block, which is indicated
         // by the last height in the block file info as below; note that this
         // does not catch the case where the undo writes are keeping up with the
         // block writes (usually when a synced up node is getting newly mined
         // blocks) -- this case is caught in the FindBlockPos function
         if (_pos.nFile < nLastBlockFile &&
             static_cast<uint32_t>(pindex->nHeight) ==
                 vinfoBlockFile[_pos.nFile].nHeightLast) {
             FlushUndoFile(_pos.nFile, true);
         }
 
         // update nUndoPos in block index
         pindex->nUndoPos = _pos.nPos;
         pindex->nStatus = pindex->nStatus.withUndo();
         setDirtyBlockIndex.insert(pindex);
     }
 
     return true;
 }
 
 static CCheckQueue<CScriptCheck> scriptcheckqueue(128);
 
 void StartScriptCheckWorkerThreads(int threads_num) {
     scriptcheckqueue.StartWorkerThreads(threads_num);
 }
 
 void StopScriptCheckWorkerThreads() {
     scriptcheckqueue.StopWorkerThreads();
 }
 
 VersionBitsCache versionbitscache GUARDED_BY(cs_main);
 
 int32_t ComputeBlockVersion(const CBlockIndex *pindexPrev,
                             const Consensus::Params &params) {
     LOCK(cs_main);
     int32_t nVersion = VERSIONBITS_TOP_BITS;
 
     for (int i = 0; i < (int)Consensus::MAX_VERSION_BITS_DEPLOYMENTS; i++) {
         ThresholdState state = VersionBitsState(
             pindexPrev, params, static_cast<Consensus::DeploymentPos>(i),
             versionbitscache);
         if (state == ThresholdState::LOCKED_IN ||
             state == ThresholdState::STARTED) {
             nVersion |= VersionBitsMask(
                 params, static_cast<Consensus::DeploymentPos>(i));
         }
     }
 
     // Clear the last 4 bits (miner fund activation).
     return nVersion & ~uint32_t(0x0f);
 }
 
 // Returns the script flags which should be checked for the block after
 // the given block.
 static uint32_t GetNextBlockScriptFlags(const Consensus::Params &params,
                                         const CBlockIndex *pindex) {
     uint32_t flags = SCRIPT_VERIFY_NONE;
 
     // Start enforcing P2SH (BIP16)
     if ((pindex->nHeight + 1) >= params.BIP16Height) {
         flags |= SCRIPT_VERIFY_P2SH;
     }
 
     // Start enforcing the DERSIG (BIP66) rule.
     if ((pindex->nHeight + 1) >= params.BIP66Height) {
         flags |= SCRIPT_VERIFY_DERSIG;
     }
 
     // Start enforcing CHECKLOCKTIMEVERIFY (BIP65) rule.
     if ((pindex->nHeight + 1) >= params.BIP65Height) {
         flags |= SCRIPT_VERIFY_CHECKLOCKTIMEVERIFY;
     }
 
     // Start enforcing CSV (BIP68, BIP112 and BIP113) rule.
     if ((pindex->nHeight + 1) >= params.CSVHeight) {
         flags |= SCRIPT_VERIFY_CHECKSEQUENCEVERIFY;
     }
 
     // If the UAHF is enabled, we start accepting replay protected txns
     if (IsUAHFenabled(params, pindex)) {
         flags |= SCRIPT_VERIFY_STRICTENC;
         flags |= SCRIPT_ENABLE_SIGHASH_FORKID;
     }
 
     // If the DAA HF is enabled, we start rejecting transaction that use a high
     // s in their signature. We also make sure that signature that are supposed
     // to fail (for instance in multisig or other forms of smart contracts) are
     // null.
     if (IsDAAEnabled(params, pindex)) {
         flags |= SCRIPT_VERIFY_LOW_S;
         flags |= SCRIPT_VERIFY_NULLFAIL;
     }
 
     // When the magnetic anomaly fork is enabled, we start accepting
     // transactions using the OP_CHECKDATASIG opcode and it's verify
     // alternative. We also start enforcing push only signatures and
     // clean stack.
     if (IsMagneticAnomalyEnabled(params, pindex)) {
         flags |= SCRIPT_VERIFY_SIGPUSHONLY;
         flags |= SCRIPT_VERIFY_CLEANSTACK;
     }
 
     if (IsGravitonEnabled(params, pindex)) {
         flags |= SCRIPT_ENABLE_SCHNORR_MULTISIG;
         flags |= SCRIPT_VERIFY_MINIMALDATA;
     }
 
     if (IsPhononEnabled(params, pindex)) {
         flags |= SCRIPT_ENFORCE_SIGCHECKS;
     }
 
     // We make sure this node will have replay protection during the next hard
     // fork.
     if (IsReplayProtectionEnabled(params, pindex)) {
         flags |= SCRIPT_ENABLE_REPLAY_PROTECTION;
     }
 
     return flags;
 }
 
 static int64_t nTimeCheck = 0;
 static int64_t nTimeForks = 0;
 static int64_t nTimeVerify = 0;
 static int64_t nTimeConnect = 0;
 static int64_t nTimeIndex = 0;
 static int64_t nTimeCallbacks = 0;
 static int64_t nTimeTotal = 0;
 static int64_t nBlocksTotal = 0;
 
 /**
  * Apply the effects of this block (with given index) on the UTXO set
  * represented by coins. Validity checks that depend on the UTXO set are also
  * done; ConnectBlock() can fail if those validity checks fail (among other
  * reasons).
  */
 bool CChainState::ConnectBlock(const CBlock &block, BlockValidationState &state,
                                CBlockIndex *pindex, CCoinsViewCache &view,
                                const CChainParams &params,
                                BlockValidationOptions options,
                                bool fJustCheck) {
     AssertLockHeld(cs_main);
     assert(pindex);
     assert(*pindex->phashBlock == block.GetHash());
     int64_t nTimeStart = GetTimeMicros();
 
     const Consensus::Params &consensusParams = params.GetConsensus();
 
     // Check it again in case a previous version let a bad block in
     // NOTE: We don't currently (re-)invoke ContextualCheckBlock() or
     // ContextualCheckBlockHeader() here. This means that if we add a new
     // consensus rule that is enforced in one of those two functions, then we
     // may have let in a block that violates the rule prior to updating the
     // software, and we would NOT be enforcing the rule here. Fully solving
     // upgrade from one software version to the next after a consensus rule
     // change is potentially tricky and issue-specific.
     // Also, currently the rule against blocks more than 2 hours in the future
     // is enforced in ContextualCheckBlockHeader(); we wouldn't want to
     // re-enforce that rule here (at least until we make it impossible for
     // GetAdjustedTime() to go backward).
     if (!CheckBlock(block, state, consensusParams,
                     options.withCheckPoW(!fJustCheck)
                         .withCheckMerkleRoot(!fJustCheck))) {
         if (state.GetResult() == BlockValidationResult::BLOCK_MUTATED) {
             // We don't write down blocks to disk if they may have been
             // corrupted, so this should be impossible unless we're having
             // hardware problems.
             return AbortNode(state, "Corrupt block found indicating potential "
                                     "hardware failure; shutting down");
         }
         return error("%s: Consensus::CheckBlock: %s", __func__,
                      state.ToString());
     }
 
     // Verify that the view's current state corresponds to the previous block
     BlockHash hashPrevBlock =
         pindex->pprev == nullptr ? BlockHash() : pindex->pprev->GetBlockHash();
     assert(hashPrevBlock == view.GetBestBlock());
 
     nBlocksTotal++;
 
     // Special case for the genesis block, skipping connection of its
     // transactions (its coinbase is unspendable)
     if (block.GetHash() == consensusParams.hashGenesisBlock) {
         if (!fJustCheck) {
             view.SetBestBlock(pindex->GetBlockHash());
         }
 
         return true;
     }
 
     bool fScriptChecks = true;
     if (!hashAssumeValid.IsNull()) {
         // We've been configured with the hash of a block which has been
         // externally verified to have a valid history. A suitable default value
         // is included with the software and updated from time to time. Because
         // validity relative to a piece of software is an objective fact these
         // defaults can be easily reviewed. This setting doesn't force the
         // selection of any particular chain but makes validating some faster by
         // effectively caching the result of part of the verification.
         BlockMap::const_iterator it =
             m_blockman.m_block_index.find(hashAssumeValid);
         if (it != m_blockman.m_block_index.end()) {
             if (it->second->GetAncestor(pindex->nHeight) == pindex &&
                 pindexBestHeader->GetAncestor(pindex->nHeight) == pindex &&
                 pindexBestHeader->nChainWork >= nMinimumChainWork) {
                 // This block is a member of the assumed verified chain and an
                 // ancestor of the best header.
                 // Script verification is skipped when connecting blocks under
                 // the assumevalid block. Assuming the assumevalid block is
                 // valid this is safe because block merkle hashes are still
                 // computed and checked, Of course, if an assumed valid block is
                 // invalid due to false scriptSigs this optimization would allow
                 // an invalid chain to be accepted.
                 // The equivalent time check discourages hash power from
                 // extorting the network via DOS attack into accepting an
                 // invalid block through telling users they must manually set
                 // assumevalid. Requiring a software change or burying the
                 // invalid block, regardless of the setting, makes it hard to
                 // hide the implication of the demand. This also avoids having
                 // release candidates that are hardly doing any signature
                 // verification at all in testing without having to artificially
                 // set the default assumed verified block further back. The test
                 // against nMinimumChainWork prevents the skipping when denied
                 // access to any chain at least as good as the expected chain.
                 fScriptChecks =
                     (GetBlockProofEquivalentTime(
                          *pindexBestHeader, *pindex, *pindexBestHeader,
                          consensusParams) <= 60 * 60 * 24 * 7 * 2);
             }
         }
     }
 
     int64_t nTime1 = GetTimeMicros();
     nTimeCheck += nTime1 - nTimeStart;
     LogPrint(BCLog::BENCH, "    - Sanity checks: %.2fms [%.2fs (%.2fms/blk)]\n",
              MILLI * (nTime1 - nTimeStart), nTimeCheck * MICRO,
              nTimeCheck * MILLI / nBlocksTotal);
 
     // Do not allow blocks that contain transactions which 'overwrite' older
     // transactions, unless those are already completely spent. If such
     // overwrites are allowed, coinbases and transactions depending upon those
     // can be duplicated to remove the ability to spend the first instance --
     // even after being sent to another address.
     // See BIP30, CVE-2012-1909, and http://r6.ca/blog/20120206T005236Z.html
     // for more information. This logic is not necessary for memory pool
     // transactions, as AcceptToMemoryPool already refuses previously-known
     // transaction ids entirely. This rule was originally applied to all blocks
     // with a timestamp after March 15, 2012, 0:00 UTC. Now that the whole
     // chain is irreversibly beyond that time it is applied to all blocks
     // except the two in the chain that violate it. This prevents exploiting
     // the issue against nodes during their initial block download.
     bool fEnforceBIP30 = !((pindex->nHeight == 91842 &&
                             pindex->GetBlockHash() ==
                                 uint256S("0x00000000000a4d0a398161ffc163c503763"
                                          "b1f4360639393e0e4c8e300e0caec")) ||
                            (pindex->nHeight == 91880 &&
                             pindex->GetBlockHash() ==
                                 uint256S("0x00000000000743f190a18c5577a3c2d2a1f"
                                          "610ae9601ac046a38084ccb7cd721")));
 
     // Once BIP34 activated it was not possible to create new duplicate
     // coinbases and thus other than starting with the 2 existing duplicate
     // coinbase pairs, not possible to create overwriting txs. But by the time
     // BIP34 activated, in each of the existing pairs the duplicate coinbase had
     // overwritten the first before the first had been spent. Since those
     // coinbases are sufficiently buried it's no longer possible to create
     // further duplicate transactions descending from the known pairs either. If
     // we're on the known chain at height greater than where BIP34 activated, we
     // can save the db accesses needed for the BIP30 check.
 
     // BIP34 requires that a block at height X (block X) has its coinbase
     // scriptSig start with a CScriptNum of X (indicated height X).  The above
     // logic of no longer requiring BIP30 once BIP34 activates is flawed in the
     // case that there is a block X before the BIP34 height of 227,931 which has
     // an indicated height Y where Y is greater than X.  The coinbase for block
     // X would also be a valid coinbase for block Y, which could be a BIP30
     // violation.  An exhaustive search of all mainnet coinbases before the
     // BIP34 height which have an indicated height greater than the block height
     // reveals many occurrences. The 3 lowest indicated heights found are
     // 209,921, 490,897, and 1,983,702 and thus coinbases for blocks at these 3
     // heights would be the first opportunity for BIP30 to be violated.
 
     // The search reveals a great many blocks which have an indicated height
     // greater than 1,983,702, so we simply remove the optimization to skip
     // BIP30 checking for blocks at height 1,983,702 or higher.  Before we reach
     // that block in another 25 years or so, we should take advantage of a
     // future consensus change to do a new and improved version of BIP34 that
     // will actually prevent ever creating any duplicate coinbases in the
     // future.
     static constexpr int BIP34_IMPLIES_BIP30_LIMIT = 1983702;
 
     // There is no potential to create a duplicate coinbase at block 209,921
     // because this is still before the BIP34 height and so explicit BIP30
     // checking is still active.
 
     // The final case is block 176,684 which has an indicated height of
     // 490,897. Unfortunately, this issue was not discovered until about 2 weeks
     // before block 490,897 so there was not much opportunity to address this
     // case other than to carefully analyze it and determine it would not be a
     // problem. Block 490,897 was, in fact, mined with a different coinbase than
     // block 176,684, but it is important to note that even if it hadn't been or
     // is remined on an alternate fork with a duplicate coinbase, we would still
     // not run into a BIP30 violation.  This is because the coinbase for 176,684
     // is spent in block 185,956 in transaction
     // d4f7fbbf92f4a3014a230b2dc70b8058d02eb36ac06b4a0736d9d60eaa9e8781.  This
     // spending transaction can't be duplicated because it also spends coinbase
     // 0328dd85c331237f18e781d692c92de57649529bd5edf1d01036daea32ffde29.  This
     // coinbase has an indicated height of over 4.2 billion, and wouldn't be
     // duplicatable until that height, and it's currently impossible to create a
     // chain that long. Nevertheless we may wish to consider a future soft fork
     // which retroactively prevents block 490,897 from creating a duplicate
     // coinbase. The two historical BIP30 violations often provide a confusing
     // edge case when manipulating the UTXO and it would be simpler not to have
     // another edge case to deal with.
 
     // testnet3 has no blocks before the BIP34 height with indicated heights
     // post BIP34 before approximately height 486,000,000 and presumably will
     // be reset before it reaches block 1,983,702 and starts doing unnecessary
     // BIP30 checking again.
     assert(pindex->pprev);
     CBlockIndex *pindexBIP34height =
         pindex->pprev->GetAncestor(consensusParams.BIP34Height);
     // Only continue to enforce if we're below BIP34 activation height or the
     // block hash at that height doesn't correspond.
     fEnforceBIP30 =
         fEnforceBIP30 &&
         (!pindexBIP34height ||
          !(pindexBIP34height->GetBlockHash() == consensusParams.BIP34Hash));
 
     // TODO: Remove BIP30 checking from block height 1,983,702 on, once we have
     // a consensus change that ensures coinbases at those heights can not
     // duplicate earlier coinbases.
     if (fEnforceBIP30 || pindex->nHeight >= BIP34_IMPLIES_BIP30_LIMIT) {
         for (const auto &tx : block.vtx) {
             for (size_t o = 0; o < tx->vout.size(); o++) {
                 if (view.HaveCoin(COutPoint(tx->GetId(), o))) {
                     LogPrintf("ERROR: ConnectBlock(): tried to overwrite "
                               "transaction\n");
                     return state.Invalid(BlockValidationResult::BLOCK_CONSENSUS,
                                          "bad-txns-BIP30");
                 }
             }
         }
     }
 
     // Start enforcing BIP68 (sequence locks).
     int nLockTimeFlags = 0;
     if (pindex->nHeight >= consensusParams.CSVHeight) {
         nLockTimeFlags |= LOCKTIME_VERIFY_SEQUENCE;
     }
 
     const uint32_t flags =
         GetNextBlockScriptFlags(consensusParams, pindex->pprev);
 
     int64_t nTime2 = GetTimeMicros();
     nTimeForks += nTime2 - nTime1;
     LogPrint(BCLog::BENCH, "    - Fork checks: %.2fms [%.2fs (%.2fms/blk)]\n",
              MILLI * (nTime2 - nTime1), nTimeForks * MICRO,
              nTimeForks * MILLI / nBlocksTotal);
 
     std::vector<int> prevheights;
     Amount nFees = Amount::zero();
     int nInputs = 0;
 
     // Limit the total executed signature operations in the block, a consensus
     // rule. Tracking during the CPU-consuming part (validation of uncached
     // inputs) is per-input atomic and validation in each thread stops very
     // quickly after the limit is exceeded, so an adversary cannot cause us to
     // exceed the limit by much at all.
     CheckInputsLimiter nSigChecksBlockLimiter(
         GetMaxBlockSigChecksCount(options.getExcessiveBlockSize()));
 
     std::vector<TxSigCheckLimiter> nSigChecksTxLimiters;
     nSigChecksTxLimiters.resize(block.vtx.size() - 1);
 
     CBlockUndo blockundo;
     blockundo.vtxundo.resize(block.vtx.size() - 1);
 
     CCheckQueueControl<CScriptCheck> control(fScriptChecks ? &scriptcheckqueue
                                                            : nullptr);
 
     // Add all outputs
     try {
         for (const auto &ptx : block.vtx) {
             AddCoins(view, *ptx, pindex->nHeight);
         }
     } catch (const std::logic_error &e) {
         // This error will be thrown from AddCoin if we try to connect a block
         // containing duplicate transactions. Such a thing should normally be
         // caught early nowadays (due to ContextualCheckBlock's CTOR
         // enforcement) however some edge cases can escape that:
         // - ContextualCheckBlock does not get re-run after saving the block to
         // disk, and older versions may have saved a weird block.
         // - its checks are not applied to pre-CTOR chains, which we might visit
         // with checkpointing off.
         LogPrintf("ERROR: ConnectBlock(): tried to overwrite transaction\n");
         return state.Invalid(BlockValidationResult::BLOCK_CONSENSUS,
                              "tx-duplicate");
     }
 
     size_t txIndex = 0;
     for (const auto &ptx : block.vtx) {
         const CTransaction &tx = *ptx;
         const bool isCoinBase = tx.IsCoinBase();
         nInputs += tx.vin.size();
 
         {
             Amount txfee = Amount::zero();
             TxValidationState tx_state;
             if (!isCoinBase &&
                 !Consensus::CheckTxInputs(tx, tx_state, view, pindex->nHeight,
                                           txfee)) {
                 // Any transaction validation failure in ConnectBlock is a block
                 // consensus failure.
                 state.Invalid(BlockValidationResult::BLOCK_CONSENSUS,
                               tx_state.GetRejectReason(),
                               tx_state.GetDebugMessage());
 
                 return error("%s: Consensus::CheckTxInputs: %s, %s", __func__,
                              tx.GetId().ToString(), state.ToString());
             }
             nFees += txfee;
         }
 
         if (!MoneyRange(nFees)) {
             LogPrintf("ERROR: %s: accumulated fee in the block out of range.\n",
                       __func__);
             return state.Invalid(BlockValidationResult::BLOCK_CONSENSUS,
                                  "bad-txns-accumulated-fee-outofrange");
         }
 
         // The following checks do not apply to the coinbase.
         if (isCoinBase) {
             continue;
         }
 
         // Check that transaction is BIP68 final BIP68 lock checks (as
         // opposed to nLockTime checks) must be in ConnectBlock because they
         // require the UTXO set.
         prevheights.resize(tx.vin.size());
         for (size_t j = 0; j < tx.vin.size(); j++) {
             prevheights[j] = view.AccessCoin(tx.vin[j].prevout).GetHeight();
         }
 
         if (!SequenceLocks(tx, nLockTimeFlags, prevheights, *pindex)) {
             LogPrintf("ERROR: %s: contains a non-BIP68-final transaction\n",
                       __func__);
             return state.Invalid(BlockValidationResult::BLOCK_CONSENSUS,
                                  "bad-txns-nonfinal");
         }
 
         // Don't cache results if we're actually connecting blocks (still
         // consult the cache, though).
         bool fCacheResults = fJustCheck;
 
         const bool fEnforceSigCheck = flags & SCRIPT_ENFORCE_SIGCHECKS;
         if (!fEnforceSigCheck) {
             // Historically, there has been transactions with a very high
             // sigcheck count, so we need to disable this check for such
             // transactions.
             nSigChecksTxLimiters[txIndex] = TxSigCheckLimiter::getDisabled();
         }
 
         std::vector<CScriptCheck> vChecks;
         // nSigChecksRet may be accurate (found in cache) or 0 (checks were
         // deferred into vChecks).
         int nSigChecksRet;
         TxValidationState tx_state;
         if (fScriptChecks &&
             !CheckInputScripts(tx, tx_state, view, flags, fCacheResults,
                                fCacheResults, PrecomputedTransactionData(tx),
                                nSigChecksRet, nSigChecksTxLimiters[txIndex],
                                &nSigChecksBlockLimiter, &vChecks)) {
             // Any transaction validation failure in ConnectBlock is a block
             // consensus failure
             state.Invalid(BlockValidationResult::BLOCK_CONSENSUS,
                           tx_state.GetRejectReason(),
                           tx_state.GetDebugMessage());
             return error(
                 "ConnectBlock(): CheckInputScripts on %s failed with %s",
                 tx.GetId().ToString(), state.ToString());
         }
 
         control.Add(vChecks);
 
         // Note: this must execute in the same iteration as CheckTxInputs (not
         // in a separate loop) in order to detect double spends. However,
         // this does not prevent double-spending by duplicated transaction
         // inputs in the same transaction (cf. CVE-2018-17144) -- that check is
         // done in CheckBlock (CheckRegularTransaction).
         SpendCoins(view, tx, blockundo.vtxundo.at(txIndex), pindex->nHeight);
         txIndex++;
     }
 
     int64_t nTime3 = GetTimeMicros();
     nTimeConnect += nTime3 - nTime2;
     LogPrint(BCLog::BENCH,
              "      - Connect %u transactions: %.2fms (%.3fms/tx, %.3fms/txin) "
              "[%.2fs (%.2fms/blk)]\n",
              (unsigned)block.vtx.size(), MILLI * (nTime3 - nTime2),
              MILLI * (nTime3 - nTime2) / block.vtx.size(),
              nInputs <= 1 ? 0 : MILLI * (nTime3 - nTime2) / (nInputs - 1),
              nTimeConnect * MICRO, nTimeConnect * MILLI / nBlocksTotal);
 
     Amount blockReward =
         nFees + GetBlockSubsidy(pindex->nHeight, consensusParams);
     if (block.vtx[0]->GetValueOut() > blockReward) {
         LogPrintf("ERROR: ConnectBlock(): coinbase pays too much (actual=%d vs "
                   "limit=%d)\n",
                   block.vtx[0]->GetValueOut(), blockReward);
         return state.Invalid(BlockValidationResult::BLOCK_CONSENSUS,
                              "bad-cb-amount");
     }
 
     const std::vector<CTxDestination> whitelist =
         GetMinerFundWhitelist(consensusParams, pindex->pprev);
     if (!whitelist.empty()) {
         const Amount required = GetMinerFundAmount(blockReward);
 
         for (auto &o : block.vtx[0]->vout) {
             if (o.nValue < required) {
                 // This output doesn't qualify because its amount is too low.
                 continue;
             }
 
             CTxDestination address;
             if (!ExtractDestination(o.scriptPubKey, address)) {
                 // Cannot decode address.
                 continue;
             }
 
             if (std::find(whitelist.begin(), whitelist.end(), address) !=
                 whitelist.end()) {
                 goto MinerFundSuccess;
             }
         }
 
         // We did not find an output that match the miner fund requirements.
         return state.Invalid(BlockValidationResult::BLOCK_CONSENSUS,
                              "bad-cb-minerfund");
     }
 
 MinerFundSuccess:
 
     if (!control.Wait()) {
         return state.Invalid(BlockValidationResult::BLOCK_CONSENSUS,
                              "blk-bad-inputs", "parallel script check failed");
     }
 
     int64_t nTime4 = GetTimeMicros();
     nTimeVerify += nTime4 - nTime2;
     LogPrint(
         BCLog::BENCH,
         "    - Verify %u txins: %.2fms (%.3fms/txin) [%.2fs (%.2fms/blk)]\n",
         nInputs - 1, MILLI * (nTime4 - nTime2),
         nInputs <= 1 ? 0 : MILLI * (nTime4 - nTime2) / (nInputs - 1),
         nTimeVerify * MICRO, nTimeVerify * MILLI / nBlocksTotal);
 
     if (fJustCheck) {
         return true;
     }
 
     if (!WriteUndoDataForBlock(blockundo, state, pindex, params)) {
         return false;
     }
 
     if (!pindex->IsValid(BlockValidity::SCRIPTS)) {
         pindex->RaiseValidity(BlockValidity::SCRIPTS);
         setDirtyBlockIndex.insert(pindex);
     }
 
     assert(pindex->phashBlock);
     // add this block to the view's block chain
     view.SetBestBlock(pindex->GetBlockHash());
 
     int64_t nTime5 = GetTimeMicros();
     nTimeIndex += nTime5 - nTime4;
     LogPrint(BCLog::BENCH, "    - Index writing: %.2fms [%.2fs (%.2fms/blk)]\n",
              MILLI * (nTime5 - nTime4), nTimeIndex * MICRO,
              nTimeIndex * MILLI / nBlocksTotal);
 
     int64_t nTime6 = GetTimeMicros();
     nTimeCallbacks += nTime6 - nTime5;
     LogPrint(BCLog::BENCH, "    - Callbacks: %.2fms [%.2fs (%.2fms/blk)]\n",
              MILLI * (nTime6 - nTime5), nTimeCallbacks * MICRO,
              nTimeCallbacks * MILLI / nBlocksTotal);
 
     return true;
 }
 
 CoinsCacheSizeState
 CChainState::GetCoinsCacheSizeState(const CTxMemPool *tx_pool) {
     return this->GetCoinsCacheSizeState(
         tx_pool, m_coinstip_cache_size_bytes,
         gArgs.GetArg("-maxmempool", DEFAULT_MAX_MEMPOOL_SIZE) * 1000000);
 }
 
 CoinsCacheSizeState
 CChainState::GetCoinsCacheSizeState(const CTxMemPool *tx_pool,
                                     size_t max_coins_cache_size_bytes,
                                     size_t max_mempool_size_bytes) {
     int64_t nMempoolUsage = tx_pool->DynamicMemoryUsage();
     int64_t cacheSize = CoinsTip().DynamicMemoryUsage();
     int64_t nTotalSpace =
         max_coins_cache_size_bytes +
         std::max<int64_t>(max_mempool_size_bytes - nMempoolUsage, 0);
 
     //! No need to periodic flush if at least this much space still available.
     static constexpr int64_t MAX_BLOCK_COINSDB_USAGE_BYTES =
         10 * 1024 * 1024; // 10MB
     int64_t large_threshold = std::max(
         (9 * nTotalSpace) / 10, nTotalSpace - MAX_BLOCK_COINSDB_USAGE_BYTES);
 
     if (cacheSize > nTotalSpace) {
         LogPrintf("Cache size (%s) exceeds total space (%s)\n", cacheSize,
                   nTotalSpace);
         return CoinsCacheSizeState::CRITICAL;
     } else if (cacheSize > large_threshold) {
         return CoinsCacheSizeState::LARGE;
     }
     return CoinsCacheSizeState::OK;
 }
 
 bool CChainState::FlushStateToDisk(const CChainParams &chainparams,
                                    BlockValidationState &state,
                                    FlushStateMode mode,
                                    int nManualPruneHeight) {
     LOCK(cs_main);
     assert(this->CanFlushToDisk());
     static std::chrono::microseconds nLastWrite{0};
     static std::chrono::microseconds nLastFlush{0};
     std::set<int> setFilesToPrune;
     bool full_flush_completed = false;
 
     const size_t coins_count = CoinsTip().GetCacheSize();
     const size_t coins_mem_usage = CoinsTip().DynamicMemoryUsage();
 
     try {
         {
             bool fFlushForPrune = false;
             bool fDoFullFlush = false;
             CoinsCacheSizeState cache_state =
                 GetCoinsCacheSizeState(&m_mempool);
             LOCK(cs_LastBlockFile);
             if (fPruneMode && (fCheckForPruning || nManualPruneHeight > 0) &&
                 !fReindex) {
                 if (nManualPruneHeight > 0) {
                     LOG_TIME_MILLIS_WITH_CATEGORY(
                         "find files to prune (manual)", BCLog::BENCH);
                     m_blockman.FindFilesToPruneManual(
                         setFilesToPrune, nManualPruneHeight, m_chain.Height());
                 } else {
                     LOG_TIME_MILLIS_WITH_CATEGORY("find files to prune",
                                                   BCLog::BENCH);
                     m_blockman.FindFilesToPrune(
                         setFilesToPrune, chainparams.PruneAfterHeight(),
                         m_chain.Height(), IsInitialBlockDownload());
                     fCheckForPruning = false;
                 }
                 if (!setFilesToPrune.empty()) {
                     fFlushForPrune = true;
                     if (!fHavePruned) {
                         pblocktree->WriteFlag("prunedblockfiles", true);
                         fHavePruned = true;
                     }
                 }
             }
             const auto nNow = GetTime<std::chrono::microseconds>();
             // Avoid writing/flushing immediately after startup.
             if (nLastWrite.count() == 0) {
                 nLastWrite = nNow;
             }
             if (nLastFlush.count() == 0) {
                 nLastFlush = nNow;
             }
             // The cache is large and we're within 10% and 10 MiB of the limit,
             // but we have time now (not in the middle of a block processing).
             bool fCacheLarge = mode == FlushStateMode::PERIODIC &&
                                cache_state >= CoinsCacheSizeState::LARGE;
             // The cache is over the limit, we have to write now.
             bool fCacheCritical = mode == FlushStateMode::IF_NEEDED &&
                                   cache_state >= CoinsCacheSizeState::CRITICAL;
             // It's been a while since we wrote the block index to disk. Do this
             // frequently, so we don't need to redownload after a crash.
             bool fPeriodicWrite = mode == FlushStateMode::PERIODIC &&
                                   nNow > nLastWrite + DATABASE_WRITE_INTERVAL;
             // It's been very long since we flushed the cache. Do this
             // infrequently, to optimize cache usage.
             bool fPeriodicFlush = mode == FlushStateMode::PERIODIC &&
                                   nNow > nLastFlush + DATABASE_FLUSH_INTERVAL;
             // Combine all conditions that result in a full cache flush.
             fDoFullFlush = (mode == FlushStateMode::ALWAYS) || fCacheLarge ||
                            fCacheCritical || fPeriodicFlush || fFlushForPrune;
             // Write blocks and block index to disk.
             if (fDoFullFlush || fPeriodicWrite) {
                 // Depend on nMinDiskSpace to ensure we can write block index
                 if (!CheckDiskSpace(gArgs.GetBlocksDirPath())) {
                     return AbortNode(state, "Disk space is too low!",
                                      _("Disk space is too low!"));
                 }
 
                 {
                     LOG_TIME_MILLIS_WITH_CATEGORY(
                         "write block and undo data to disk", BCLog::BENCH);
 
                     // First make sure all block and undo data is flushed to
                     // disk.
                     FlushBlockFile();
                 }
                 // Then update all block file information (which may refer to
                 // block and undo files).
                 {
                     LOG_TIME_MILLIS_WITH_CATEGORY("write block index to disk",
                                                   BCLog::BENCH);
 
                     std::vector<std::pair<int, const CBlockFileInfo *>> vFiles;
                     vFiles.reserve(setDirtyFileInfo.size());
                     for (int i : setDirtyFileInfo) {
                         vFiles.push_back(std::make_pair(i, &vinfoBlockFile[i]));
                     }
 
                     setDirtyFileInfo.clear();
 
                     std::vector<const CBlockIndex *> vBlocks;
                     vBlocks.reserve(setDirtyBlockIndex.size());
                     for (const CBlockIndex *cbi : setDirtyBlockIndex) {
                         vBlocks.push_back(cbi);
                     }
 
                     setDirtyBlockIndex.clear();
 
                     if (!pblocktree->WriteBatchSync(vFiles, nLastBlockFile,
                                                     vBlocks)) {
                         return AbortNode(
                             state, "Failed to write to block index database");
                     }
                 }
 
                 // Finally remove any pruned files
                 if (fFlushForPrune) {
                     LOG_TIME_MILLIS_WITH_CATEGORY("unlink pruned files",
                                                   BCLog::BENCH);
 
                     UnlinkPrunedFiles(setFilesToPrune);
                 }
                 nLastWrite = nNow;
             }
             // Flush best chain related state. This can only be done if the
             // blocks / block index write was also done.
             if (fDoFullFlush && !CoinsTip().GetBestBlock().IsNull()) {
                 LOG_TIME_SECONDS(
                     strprintf("write coins cache to disk (%d coins, %.2fkB)",
                               coins_count, coins_mem_usage / 1000));
 
                 // Typical Coin structures on disk are around 48 bytes in size.
                 // Pushing a new one to the database can cause it to be written
                 // twice (once in the log, and once in the tables). This is
                 // already an overestimation, as most will delete an existing
                 // entry or overwrite one. Still, use a conservative safety
                 // factor of 2.
                 if (!CheckDiskSpace(GetDataDir(),
                                     48 * 2 * 2 * CoinsTip().GetCacheSize())) {
                     return AbortNode(state, "Disk space is too low!",
                                      _("Disk space is too low!"));
                 }
 
                 // Flush the chainstate (which may refer to block index
                 // entries).
                 if (!CoinsTip().Flush()) {
                     return AbortNode(state, "Failed to write to coin database");
                 }
                 nLastFlush = nNow;
                 full_flush_completed = true;
             }
         }
 
         if (full_flush_completed) {
             // Update best block in wallet (so we can detect restored wallets).
             GetMainSignals().ChainStateFlushed(m_chain.GetLocator());
         }
     } catch (const std::runtime_error &e) {
         return AbortNode(state, std::string("System error while flushing: ") +
                                     e.what());
     }
     return true;
 }
 
 void CChainState::ForceFlushStateToDisk() {
     BlockValidationState state;
     const CChainParams &chainparams = Params();
     if (!this->FlushStateToDisk(chainparams, state, FlushStateMode::ALWAYS)) {
         LogPrintf("%s: failed to flush state (%s)\n", __func__,
                   state.ToString());
     }
 }
 
 void CChainState::PruneAndFlush() {
     BlockValidationState state;
     fCheckForPruning = true;
     const CChainParams &chainparams = Params();
     if (!this->FlushStateToDisk(chainparams, state, FlushStateMode::NONE)) {
         LogPrintf("%s: failed to flush state (%s)\n", __func__,
                   state.ToString());
     }
 }
 
 /** Check warning conditions and do some notifications on new chain tip set. */
 static void UpdateTip(CTxMemPool &mempool, CBlockIndex *pindexNew,
                       const CChainParams &params,
                       CChainState &active_chainstate)
     EXCLUSIVE_LOCKS_REQUIRED(::cs_main) {
     // New best block
     mempool.AddTransactionsUpdated(1);
 
     {
         LOCK(g_best_block_mutex);
         g_best_block = pindexNew->GetBlockHash();
         g_best_block_cv.notify_all();
     }
 
     assert(std::addressof(::ChainstateActive()) ==
            std::addressof(active_chainstate));
     LogPrintf(
         "%s: new best=%s height=%d version=0x%08x log2_work=%f tx=%ld "
         "date='%s' progress=%f cache=%.1fMiB(%utxo)\n",
         __func__, pindexNew->GetBlockHash().ToString(), pindexNew->nHeight,
         pindexNew->nVersion, log(pindexNew->nChainWork.getdouble()) / log(2.0),
         pindexNew->GetChainTxCount(),
         FormatISO8601DateTime(pindexNew->GetBlockTime()),
         GuessVerificationProgress(params.TxData(), pindexNew),
         active_chainstate.CoinsTip().DynamicMemoryUsage() * (1.0 / (1 << 20)),
         active_chainstate.CoinsTip().GetCacheSize());
 }
 
 /**
  * Disconnect m_chain's tip.
  * After calling, the mempool will be in an inconsistent state, with
  * transactions from disconnected blocks being added to disconnectpool. You
  * should make the mempool consistent again by calling updateMempoolForReorg.
  * with cs_main held.
  *
  * If disconnectpool is nullptr, then no disconnected transactions are added to
  * disconnectpool (note that the caller is responsible for mempool consistency
  * in any case).
  */
 bool CChainState::DisconnectTip(const CChainParams &params,
                                 BlockValidationState &state,
                                 DisconnectedBlockTransactions *disconnectpool) {
     AssertLockHeld(cs_main);
     AssertLockHeld(m_mempool.cs);
     CBlockIndex *pindexDelete = m_chain.Tip();
     const Consensus::Params &consensusParams = params.GetConsensus();
 
     assert(pindexDelete);
 
     // Read block from disk.
     std::shared_ptr<CBlock> pblock = std::make_shared<CBlock>();
     CBlock &block = *pblock;
     if (!ReadBlockFromDisk(block, pindexDelete, consensusParams)) {
         return error("DisconnectTip(): Failed to read block");
     }
 
     // Apply the block atomically to the chain state.
     int64_t nStart = GetTimeMicros();
     {
         CCoinsViewCache view(&CoinsTip());
         assert(view.GetBestBlock() == pindexDelete->GetBlockHash());
         if (DisconnectBlock(block, pindexDelete, view) !=
             DisconnectResult::OK) {
             return error("DisconnectTip(): DisconnectBlock %s failed",
                          pindexDelete->GetBlockHash().ToString());
         }
 
         bool flushed = view.Flush();
         assert(flushed);
     }
 
     LogPrint(BCLog::BENCH, "- Disconnect block: %.2fms\n",
              (GetTimeMicros() - nStart) * MILLI);
 
     // Write the chain state to disk, if necessary.
     if (!FlushStateToDisk(params, state, FlushStateMode::IF_NEEDED)) {
         return false;
     }
 
     // If this block is deactivating a fork, we move all mempool transactions
     // in front of disconnectpool for reprocessing in a future
     // updateMempoolForReorg call
     if (pindexDelete->pprev != nullptr &&
         GetNextBlockScriptFlags(consensusParams, pindexDelete) !=
             GetNextBlockScriptFlags(consensusParams, pindexDelete->pprev)) {
         LogPrint(BCLog::MEMPOOL,
                  "Disconnecting mempool due to rewind of upgrade block\n");
         if (disconnectpool) {
             disconnectpool->importMempool(m_mempool);
         }
         m_mempool.clear();
     }
 
     if (disconnectpool) {
         disconnectpool->addForBlock(block.vtx, m_mempool);
     }
 
     // If the tip is finalized, then undo it.
     if (m_finalizedBlockIndex == pindexDelete) {
         m_finalizedBlockIndex = pindexDelete->pprev;
     }
 
     m_chain.SetTip(pindexDelete->pprev);
 
     // Update ::ChainActive() and related variables.
     UpdateTip(m_mempool, pindexDelete->pprev, params, *this);
     // Let wallets know transactions went from 1-confirmed to
     // 0-confirmed or conflicted:
     GetMainSignals().BlockDisconnected(pblock, pindexDelete);
     return true;
 }
 
 static int64_t nTimeReadFromDisk = 0;
 static int64_t nTimeConnectTotal = 0;
 static int64_t nTimeFlush = 0;
 static int64_t nTimeChainState = 0;
 static int64_t nTimePostConnect = 0;
 
 struct PerBlockConnectTrace {
     CBlockIndex *pindex = nullptr;
     std::shared_ptr<const CBlock> pblock;
     PerBlockConnectTrace() {}
 };
 
 /**
  * Used to track blocks whose transactions were applied to the UTXO state as a
  * part of a single ActivateBestChainStep call.
  *
  * This class is single-use, once you call GetBlocksConnected() you have to
  * throw it away and make a new one.
  */
 class ConnectTrace {
 private:
     std::vector<PerBlockConnectTrace> blocksConnected;
 
 public:
     explicit ConnectTrace() : blocksConnected(1) {}
 
     void BlockConnected(CBlockIndex *pindex,
                         std::shared_ptr<const CBlock> pblock) {
         assert(!blocksConnected.back().pindex);
         assert(pindex);
         assert(pblock);
         blocksConnected.back().pindex = pindex;
         blocksConnected.back().pblock = std::move(pblock);
         blocksConnected.emplace_back();
     }
 
     std::vector<PerBlockConnectTrace> &GetBlocksConnected() {
         // We always keep one extra block at the end of our list because blocks
         // are added after all the conflicted transactions have been filled in.
         // Thus, the last entry should always be an empty one waiting for the
         // transactions from the next block. We pop the last entry here to make
         // sure the list we return is sane.
         assert(!blocksConnected.back().pindex);
         blocksConnected.pop_back();
         return blocksConnected;
     }
 };
 
 bool CChainState::MarkBlockAsFinal(BlockValidationState &state,
                                    const CBlockIndex *pindex) {
     AssertLockHeld(cs_main);
     if (pindex->nStatus.isInvalid()) {
         // We try to finalize an invalid block.
         LogPrintf("ERROR: %s: Trying to finalize invalid block %s\n", __func__,
                   pindex->GetBlockHash().ToString());
         return state.Invalid(BlockValidationResult::BLOCK_CACHED_INVALID,
                              "finalize-invalid-block");
     }
 
     // Check that the request is consistent with current finalization.
     if (m_finalizedBlockIndex &&
         !AreOnTheSameFork(pindex, m_finalizedBlockIndex)) {
         LogPrintf("ERROR: %s: Trying to finalize block %s which conflicts with "
                   "already finalized block\n",
                   __func__, pindex->GetBlockHash().ToString());
         return state.Invalid(BlockValidationResult::BLOCK_FINALIZATION,
                              "bad-fork-prior-finalized");
     }
 
     if (IsBlockFinalized(pindex)) {
         // The block is already finalized.
         return true;
     }
 
     // We have a new block to finalize.
     m_finalizedBlockIndex = pindex;
     return true;
 }
 
 static const CBlockIndex *FindBlockToFinalize(CBlockIndex *pindexNew)
     EXCLUSIVE_LOCKS_REQUIRED(cs_main) {
     AssertLockHeld(cs_main);
 
     const int32_t maxreorgdepth =
         gArgs.GetArg("-maxreorgdepth", DEFAULT_MAX_REORG_DEPTH);
 
     const int64_t finalizationdelay =
         gArgs.GetArg("-finalizationdelay", DEFAULT_MIN_FINALIZATION_DELAY);
 
     // Find our candidate.
     // If maxreorgdepth is < 0 pindex will be null and auto finalization
     // disabled
     const CBlockIndex *pindex =
         pindexNew->GetAncestor(pindexNew->nHeight - maxreorgdepth);
 
     int64_t now = GetTime();
 
     // If the finalization delay is not expired since the startup time,
     // finalization should be avoided. Header receive time is not saved to disk
     // and so cannot be anterior to startup time.
     if (now < (GetStartupTime() + finalizationdelay)) {
         return nullptr;
     }
 
     // While our candidate is not eligible (finalization delay not expired), try
     // the previous one.
     while (pindex && (pindex != ::ChainstateActive().GetFinalizedBlock())) {
         // Check that the block to finalize is known for a long enough time.
         // This test will ensure that an attacker could not cause a block to
         // finalize by forking the chain with a depth > maxreorgdepth.
         // If the block is loaded from disk, header receive time is 0 and the
         // block will be finalized. This is safe because the delay since the
         // node startup is already expired.
         auto headerReceivedTime = pindex->GetHeaderReceivedTime();
 
         // If finalization delay is <= 0, finalization always occurs immediately
         if (now >= (headerReceivedTime + finalizationdelay)) {
             return pindex;
         }
 
         pindex = pindex->pprev;
     }
 
     return nullptr;
 }
 
 /**
  * Connect a new block to m_chain. pblock is either nullptr or a pointer to
  * a CBlock corresponding to pindexNew, to bypass loading it again from disk.
  *
  * The block is always added to connectTrace (either after loading from disk or
  * by copying pblock) - if that is not intended, care must be taken to remove
  * the last entry in blocksConnected in case of failure.
  */
 bool CChainState::ConnectTip(const Config &config, BlockValidationState &state,
                              CBlockIndex *pindexNew,
                              const std::shared_ptr<const CBlock> &pblock,
                              ConnectTrace &connectTrace,
                              DisconnectedBlockTransactions &disconnectpool) {
     AssertLockHeld(cs_main);
     AssertLockHeld(m_mempool.cs);
 
     const CChainParams &params = config.GetChainParams();
     const Consensus::Params &consensusParams = params.GetConsensus();
 
     assert(pindexNew->pprev == m_chain.Tip());
     // Read block from disk.
     int64_t nTime1 = GetTimeMicros();
     std::shared_ptr<const CBlock> pthisBlock;
     if (!pblock) {
         std::shared_ptr<CBlock> pblockNew = std::make_shared<CBlock>();
         if (!ReadBlockFromDisk(*pblockNew, pindexNew, consensusParams)) {
             return AbortNode(state, "Failed to read block");
         }
         pthisBlock = pblockNew;
     } else {
         pthisBlock = pblock;
     }
 
     const CBlock &blockConnecting = *pthisBlock;
 
     // Apply the block atomically to the chain state.
     int64_t nTime2 = GetTimeMicros();
     nTimeReadFromDisk += nTime2 - nTime1;
     int64_t nTime3;
     LogPrint(BCLog::BENCH, "  - Load block from disk: %.2fms [%.2fs]\n",
              (nTime2 - nTime1) * MILLI, nTimeReadFromDisk * MICRO);
     {
         CCoinsViewCache view(&CoinsTip());
         bool rv = ConnectBlock(blockConnecting, state, pindexNew, view, params,
                                BlockValidationOptions(config));
         GetMainSignals().BlockChecked(blockConnecting, state);
         if (!rv) {
             if (state.IsInvalid()) {
                 InvalidBlockFound(pindexNew, state);
             }
 
             return error("%s: ConnectBlock %s failed, %s", __func__,
                          pindexNew->GetBlockHash().ToString(),
                          state.ToString());
         }
 
         // Update the finalized block.
         const CBlockIndex *pindexToFinalize = FindBlockToFinalize(pindexNew);
         if (pindexToFinalize && !MarkBlockAsFinal(state, pindexToFinalize)) {
             return error("ConnectTip(): MarkBlockAsFinal %s failed (%s)",
                          pindexNew->GetBlockHash().ToString(),
                          state.ToString());
         }
 
         nTime3 = GetTimeMicros();
         nTimeConnectTotal += nTime3 - nTime2;
         assert(nBlocksTotal > 0);
         LogPrint(BCLog::BENCH,
                  "  - Connect total: %.2fms [%.2fs (%.2fms/blk)]\n",
                  (nTime3 - nTime2) * MILLI, nTimeConnectTotal * MICRO,
                  nTimeConnectTotal * MILLI / nBlocksTotal);
         bool flushed = view.Flush();
         assert(flushed);
     }
 
     int64_t nTime4 = GetTimeMicros();
     nTimeFlush += nTime4 - nTime3;
     LogPrint(BCLog::BENCH, "  - Flush: %.2fms [%.2fs (%.2fms/blk)]\n",
              (nTime4 - nTime3) * MILLI, nTimeFlush * MICRO,
              nTimeFlush * MILLI / nBlocksTotal);
 
     // Write the chain state to disk, if necessary.
     if (!FlushStateToDisk(config.GetChainParams(), state,
                           FlushStateMode::IF_NEEDED)) {
         return false;
     }
 
     int64_t nTime5 = GetTimeMicros();
     nTimeChainState += nTime5 - nTime4;
     LogPrint(BCLog::BENCH,
              "  - Writing chainstate: %.2fms [%.2fs (%.2fms/blk)]\n",
              (nTime5 - nTime4) * MILLI, nTimeChainState * MICRO,
              nTimeChainState * MILLI / nBlocksTotal);
 
     // Remove conflicting transactions from the mempool.;
     m_mempool.removeForBlock(blockConnecting.vtx, pindexNew->nHeight);
     disconnectpool.removeForBlock(blockConnecting.vtx);
 
     // If this block is activating a fork, we move all mempool transactions
     // in front of disconnectpool for reprocessing in a future
     // updateMempoolForReorg call
     if (pindexNew->pprev != nullptr &&
         GetNextBlockScriptFlags(consensusParams, pindexNew) !=
             GetNextBlockScriptFlags(consensusParams, pindexNew->pprev)) {
         LogPrint(BCLog::MEMPOOL,
                  "Disconnecting mempool due to acceptance of upgrade block\n");
         disconnectpool.importMempool(m_mempool);
     }
 
     // Update m_chain & related variables.
     m_chain.SetTip(pindexNew);
     UpdateTip(m_mempool, pindexNew, params, *this);
 
     int64_t nTime6 = GetTimeMicros();
     nTimePostConnect += nTime6 - nTime5;
     nTimeTotal += nTime6 - nTime1;
     LogPrint(BCLog::BENCH,
              "  - Connect postprocess: %.2fms [%.2fs (%.2fms/blk)]\n",
              (nTime6 - nTime5) * MILLI, nTimePostConnect * MICRO,
              nTimePostConnect * MILLI / nBlocksTotal);
     LogPrint(BCLog::BENCH, "- Connect block: %.2fms [%.2fs (%.2fms/blk)]\n",
              (nTime6 - nTime1) * MILLI, nTimeTotal * MICRO,
              nTimeTotal * MILLI / nBlocksTotal);
 
     connectTrace.BlockConnected(pindexNew, std::move(pthisBlock));
     return true;
 }
 
 /**
  * Return the tip of the chain with the most work in it, that isn't known to be
  * invalid (it's however far from certain to be valid).
  */
 CBlockIndex *CChainState::FindMostWorkChain() {
     AssertLockHeld(cs_main);
     do {
         CBlockIndex *pindexNew = nullptr;
 
         // Find the best candidate header.
         {
             std::set<CBlockIndex *, CBlockIndexWorkComparator>::reverse_iterator
                 it = setBlockIndexCandidates.rbegin();
             if (it == setBlockIndexCandidates.rend()) {
                 return nullptr;
             }
             pindexNew = *it;
         }
 
         // If this block will cause a finalized block to be reorged, then we
         // mark it as invalid.
         if (m_finalizedBlockIndex &&
             !AreOnTheSameFork(pindexNew, m_finalizedBlockIndex)) {
             LogPrintf("Mark block %s invalid because it forks prior to the "
                       "finalization point %d.\n",
                       pindexNew->GetBlockHash().ToString(),
                       m_finalizedBlockIndex->nHeight);
             pindexNew->nStatus = pindexNew->nStatus.withFailed();
             InvalidChainFound(pindexNew);
         }
 
         const bool fAvalancheEnabled = isAvalancheEnabled(gArgs);
         const bool fAutoUnpark =
             gArgs.GetBoolArg("-automaticunparking", !fAvalancheEnabled);
 
         const CBlockIndex *pindexFork = m_chain.FindFork(pindexNew);
 
         // Check whether all blocks on the path between the currently active
         // chain and the candidate are valid. Just going until the active chain
         // is an optimization, as we know all blocks in it are valid already.
         CBlockIndex *pindexTest = pindexNew;
         bool hasValidAncestor = true;
         while (hasValidAncestor && pindexTest && pindexTest != pindexFork) {
             assert(pindexTest->HaveTxsDownloaded() || pindexTest->nHeight == 0);
 
             // If this is a parked chain, but it has enough PoW, clear the park
             // state.
             bool fParkedChain = pindexTest->nStatus.isOnParkedChain();
             if (fAutoUnpark && fParkedChain) {
                 const CBlockIndex *pindexTip = m_chain.Tip();
 
                 // During initialization, pindexTip and/or pindexFork may be
                 // null. In this case, we just ignore the fact that the chain is
                 // parked.
                 if (!pindexTip || !pindexFork) {
                     UnparkBlock(pindexTest);
                     continue;
                 }
 
                 // A parked chain can be unparked if it has twice as much PoW
                 // accumulated as the main chain has since the fork block.
                 CBlockIndex const *pindexExtraPow = pindexTip;
                 arith_uint256 requiredWork = pindexTip->nChainWork;
                 switch (pindexTip->nHeight - pindexFork->nHeight) {
                     // Limit the penality for depth 1, 2 and 3 to half a block
                     // worth of work to ensure we don't fork accidentally.
                     case 3:
                     case 2:
                         pindexExtraPow = pindexExtraPow->pprev;
                     // FALLTHROUGH
                     case 1: {
                         const arith_uint256 deltaWork =
                             pindexExtraPow->nChainWork - pindexFork->nChainWork;
                         requiredWork += (deltaWork >> 1);
                         break;
                     }
                     default:
                         requiredWork +=
                             pindexExtraPow->nChainWork - pindexFork->nChainWork;
                         break;
                 }
 
                 if (pindexNew->nChainWork > requiredWork) {
                     // We have enough, clear the parked state.
                     LogPrintf("Unpark chain up to block %s as it has "
                               "accumulated enough PoW.\n",
                               pindexNew->GetBlockHash().ToString());
                     fParkedChain = false;
                     UnparkBlock(pindexTest);
                 }
             }
 
             // Pruned nodes may have entries in setBlockIndexCandidates for
             // which block files have been deleted. Remove those as candidates
             // for the most work chain if we come across them; we can't switch
             // to a chain unless we have all the non-active-chain parent blocks.
             bool fInvalidChain = pindexTest->nStatus.isInvalid();
             bool fMissingData = !pindexTest->nStatus.hasData();
             if (!(fInvalidChain || fParkedChain || fMissingData)) {
                 // The current block is acceptable, move to the parent, up to
                 // the fork point.
                 pindexTest = pindexTest->pprev;
                 continue;
             }
 
             // Candidate chain is not usable (either invalid or parked or
             // missing data)
             hasValidAncestor = false;
             setBlockIndexCandidates.erase(pindexTest);
 
             if (fInvalidChain &&
                 (pindexBestInvalid == nullptr ||
                  pindexNew->nChainWork > pindexBestInvalid->nChainWork)) {
                 pindexBestInvalid = pindexNew;
             }
 
             if (fParkedChain &&
                 (pindexBestParked == nullptr ||
                  pindexNew->nChainWork > pindexBestParked->nChainWork)) {
                 pindexBestParked = pindexNew;
             }
 
             LogPrintf("Considered switching to better tip %s but that chain "
                       "contains a%s%s%s block.\n",
                       pindexNew->GetBlockHash().ToString(),
                       fInvalidChain ? "n invalid" : "",
                       fParkedChain ? " parked" : "",
                       fMissingData ? " missing-data" : "");
 
             CBlockIndex *pindexFailed = pindexNew;
             // Remove the entire chain from the set.
             while (pindexTest != pindexFailed) {
                 if (fInvalidChain || fParkedChain) {
                     pindexFailed->nStatus =
                         pindexFailed->nStatus.withFailedParent(fInvalidChain)
                             .withParkedParent(fParkedChain);
                 } else if (fMissingData) {
                     // If we're missing data, then add back to
                     // m_blocks_unlinked, so that if the block arrives in the
                     // future we can try adding to setBlockIndexCandidates
                     // again.
                     m_blockman.m_blocks_unlinked.insert(
                         std::make_pair(pindexFailed->pprev, pindexFailed));
                 }
                 setBlockIndexCandidates.erase(pindexFailed);
                 pindexFailed = pindexFailed->pprev;
             }
 
             if (fInvalidChain || fParkedChain) {
                 // We discovered a new chain tip that is either parked or
                 // invalid, we may want to warn.
                 CheckForkWarningConditionsOnNewFork(pindexNew);
             }
         }
 
         if (fAvalancheEnabled && g_avalanche) {
             g_avalanche->addBlockToReconcile(pindexNew);
         }
 
         // We found a candidate that has valid ancestors. This is our guy.
         if (hasValidAncestor) {
             return pindexNew;
         }
     } while (true);
 }
 
 /**
  * Delete all entries in setBlockIndexCandidates that are worse than the current
  * tip.
  */
 void CChainState::PruneBlockIndexCandidates() {
     // Note that we can't delete the current block itself, as we may need to
     // return to it later in case a reorganization to a better block fails.
     auto it = setBlockIndexCandidates.begin();
     while (it != setBlockIndexCandidates.end() &&
            setBlockIndexCandidates.value_comp()(*it, m_chain.Tip())) {
         setBlockIndexCandidates.erase(it++);
     }
 
     // Either the current tip or a successor of it we're working towards is left
     // in setBlockIndexCandidates.
     assert(!setBlockIndexCandidates.empty());
 }
 
 /**
  * Try to make some progress towards making pindexMostWork the active block.
  * pblock is either nullptr or a pointer to a CBlock corresponding to
  * pindexMostWork.
  *
  * @returns true unless a system error occurred
  */
 bool CChainState::ActivateBestChainStep(
     const Config &config, BlockValidationState &state,
     CBlockIndex *pindexMostWork, const std::shared_ptr<const CBlock> &pblock,
     bool &fInvalidFound, ConnectTrace &connectTrace) {
     AssertLockHeld(cs_main);
     AssertLockHeld(m_mempool.cs);
 
     const CBlockIndex *pindexOldTip = m_chain.Tip();
     const CBlockIndex *pindexFork = m_chain.FindFork(pindexMostWork);
 
     // Disconnect active blocks which are no longer in the best chain.
     bool fBlocksDisconnected = false;
     DisconnectedBlockTransactions disconnectpool;
     while (m_chain.Tip() && m_chain.Tip() != pindexFork) {
         if (!DisconnectTip(config.GetChainParams(), state, &disconnectpool)) {
             // This is likely a fatal error, but keep the mempool consistent,
             // just in case. Only remove from the mempool in this case.
             disconnectpool.updateMempoolForReorg(config, ::ChainstateActive(),
                                                  false, m_mempool);
 
             // If we're unable to disconnect a block during normal operation,
             // then that is a failure of our local system -- we should abort
             // rather than stay on a less work chain.
             AbortNode(state,
                       "Failed to disconnect block; see debug.log for details");
             return false;
         }
 
         fBlocksDisconnected = true;
     }
 
     // Build list of new blocks to connect.
     std::vector<CBlockIndex *> vpindexToConnect;
     bool fContinue = true;
     int nHeight = pindexFork ? pindexFork->nHeight : -1;
     while (fContinue && nHeight != pindexMostWork->nHeight) {
         // Don't iterate the entire list of potential improvements toward the
         // best tip, as we likely only need a few blocks along the way.
         int nTargetHeight = std::min(nHeight + 32, pindexMostWork->nHeight);
         vpindexToConnect.clear();
         vpindexToConnect.reserve(nTargetHeight - nHeight);
         CBlockIndex *pindexIter = pindexMostWork->GetAncestor(nTargetHeight);
         while (pindexIter && pindexIter->nHeight != nHeight) {
             vpindexToConnect.push_back(pindexIter);
             pindexIter = pindexIter->pprev;
         }
 
         nHeight = nTargetHeight;
 
         // Connect new blocks.
         for (CBlockIndex *pindexConnect : reverse_iterate(vpindexToConnect)) {
             if (!ConnectTip(config, state, pindexConnect,
                             pindexConnect == pindexMostWork
                                 ? pblock
                                 : std::shared_ptr<const CBlock>(),
                             connectTrace, disconnectpool)) {
                 if (state.IsInvalid()) {
                     // The block violates a consensus rule.
                     if (state.GetResult() !=
                         BlockValidationResult::BLOCK_MUTATED) {
                         InvalidChainFound(vpindexToConnect.back());
                     }
                     state = BlockValidationState();
                     fInvalidFound = true;
                     fContinue = false;
                     break;
                 }
 
                 // A system error occurred (disk space, database error, ...).
                 // Make the mempool consistent with the current tip, just in
                 // case any observers try to use it before shutdown.
                 disconnectpool.updateMempoolForReorg(
                     config, ::ChainstateActive(), false, m_mempool);
                 return false;
             } else {
                 PruneBlockIndexCandidates();
                 if (!pindexOldTip ||
                     m_chain.Tip()->nChainWork > pindexOldTip->nChainWork) {
                     // We're in a better position than we were. Return
                     // temporarily to release the lock.
                     fContinue = false;
                     break;
                 }
             }
         }
     }
 
     if (fBlocksDisconnected || !disconnectpool.isEmpty()) {
         // If any blocks were disconnected, we need to update the mempool even
         // if disconnectpool is empty. The disconnectpool may also be non-empty
         // if the mempool was imported due to new validation rules being in
         // effect.
         LogPrint(BCLog::MEMPOOL, "Updating mempool due to reorganization or "
                                  "rules upgrade/downgrade\n");
         disconnectpool.updateMempoolForReorg(config, ::ChainstateActive(), true,
                                              m_mempool);
     }
 
     m_mempool.check(&CoinsTip());
 
     // Callbacks/notifications for a new best chain.
     if (fInvalidFound) {
         CheckForkWarningConditionsOnNewFork(pindexMostWork);
     } else {
         CheckForkWarningConditions();
     }
 
     return true;
 }
 
 static SynchronizationState GetSynchronizationState(bool init) {
     if (!init) {
         return SynchronizationState::POST_INIT;
     }
     if (::fReindex) {
         return SynchronizationState::INIT_REINDEX;
     }
     return SynchronizationState::INIT_DOWNLOAD;
 }
 
 static bool NotifyHeaderTip(CChainState &chainstate) LOCKS_EXCLUDED(cs_main) {
     bool fNotify = false;
     bool fInitialBlockDownload = false;
     static CBlockIndex *pindexHeaderOld = nullptr;
     CBlockIndex *pindexHeader = nullptr;
     {
         LOCK(cs_main);
         pindexHeader = pindexBestHeader;
 
         if (pindexHeader != pindexHeaderOld) {
             fNotify = true;
             assert(std::addressof(::ChainstateActive()) ==
                    std::addressof(chainstate));
             fInitialBlockDownload = chainstate.IsInitialBlockDownload();
             pindexHeaderOld = pindexHeader;
         }
     }
 
     // Send block tip changed notifications without cs_main
     if (fNotify) {
         uiInterface.NotifyHeaderTip(
             GetSynchronizationState(fInitialBlockDownload), pindexHeader);
     }
     return fNotify;
 }
 
 static void LimitValidationInterfaceQueue() LOCKS_EXCLUDED(cs_main) {
     AssertLockNotHeld(cs_main);
 
     if (GetMainSignals().CallbacksPending() > 10) {
         SyncWithValidationInterfaceQueue();
     }
 }
 
 bool CChainState::ActivateBestChain(const Config &config,
                                     BlockValidationState &state,
                                     std::shared_ptr<const CBlock> pblock) {
     // Note that while we're often called here from ProcessNewBlock, this is
     // far from a guarantee. Things in the P2P/RPC will often end up calling
     // us in the middle of ProcessNewBlock - do not assume pblock is set
     // sanely for performance or correctness!
     AssertLockNotHeld(cs_main);
 
     const CChainParams &params = config.GetChainParams();
 
     // ABC maintains a fair degree of expensive-to-calculate internal state
     // because this function periodically releases cs_main so that it does not
     // lock up other threads for too long during large connects - and to allow
     // for e.g. the callback queue to drain we use m_cs_chainstate to enforce
     // mutual exclusion so that only one caller may execute this function at a
     // time
     LOCK(m_cs_chainstate);
 
     CBlockIndex *pindexMostWork = nullptr;
     CBlockIndex *pindexNewTip = nullptr;
     int nStopAtHeight = gArgs.GetArg("-stopatheight", DEFAULT_STOPATHEIGHT);
     do {
         // Block until the validation queue drains. This should largely
         // never happen in normal operation, however may happen during
         // reindex, causing memory blowup if we run too far ahead.
         // Note that if a validationinterface callback ends up calling
         // ActivateBestChain this may lead to a deadlock! We should
         // probably have a DEBUG_LOCKORDER test for this in the future.
         LimitValidationInterfaceQueue();
 
         {
             LOCK(cs_main);
             // Lock transaction pool for at least as long as it takes for
             // connectTrace to be consumed
             LOCK(m_mempool.cs);
             CBlockIndex *starting_tip = m_chain.Tip();
             bool blocks_connected = false;
             do {
                 // We absolutely may not unlock cs_main until we've made forward
                 // progress (with the exception of shutdown due to hardware
                 // issues, low disk space, etc).
 
                 // Destructed before cs_main is unlocked
                 ConnectTrace connectTrace;
 
                 if (pindexMostWork == nullptr) {
                     pindexMostWork = FindMostWorkChain();
                 }
 
                 // Whether we have anything to do at all.
                 if (pindexMostWork == nullptr ||
                     pindexMostWork == m_chain.Tip()) {
                     break;
                 }
 
                 bool fInvalidFound = false;
                 std::shared_ptr<const CBlock> nullBlockPtr;
                 if (!ActivateBestChainStep(
                         config, state, pindexMostWork,
                         pblock && pblock->GetHash() ==
                                       pindexMostWork->GetBlockHash()
                             ? pblock
                             : nullBlockPtr,
                         fInvalidFound, connectTrace)) {
                     // A system error occurred
                     return false;
                 }
                 blocks_connected = true;
 
                 if (fInvalidFound) {
                     // Wipe cache, we may need another branch now.
                     pindexMostWork = nullptr;
                 }
 
                 pindexNewTip = m_chain.Tip();
                 for (const PerBlockConnectTrace &trace :
                      connectTrace.GetBlocksConnected()) {
                     assert(trace.pblock && trace.pindex);
                     GetMainSignals().BlockConnected(trace.pblock, trace.pindex);
                 }
             } while (!m_chain.Tip() ||
                      (starting_tip && CBlockIndexWorkComparator()(
                                           m_chain.Tip(), starting_tip)));
 
             // Check the index once we're done with the above loop, since
             // we're going to release cs_main soon. If the index is in a bad
             // state now, then it's better to know immediately rather than
             // randomly have it cause a problem in a race.
             CheckBlockIndex(params.GetConsensus());
 
             if (!blocks_connected) {
                 return true;
             }
 
             const CBlockIndex *pindexFork = m_chain.FindFork(starting_tip);
             bool fInitialDownload = IsInitialBlockDownload();
 
             // Notify external listeners about the new tip.
             // Enqueue while holding cs_main to ensure that UpdatedBlockTip is
             // called in the order in which blocks are connected
             if (pindexFork != pindexNewTip) {
                 // Notify ValidationInterface subscribers
                 GetMainSignals().UpdatedBlockTip(pindexNewTip, pindexFork,
                                                  fInitialDownload);
 
                 // Always notify the UI if a new block tip was connected
                 uiInterface.NotifyBlockTip(
                     GetSynchronizationState(fInitialDownload), pindexNewTip);
             }
         }
         // When we reach this point, we switched to a new tip (stored in
         // pindexNewTip).
 
         if (nStopAtHeight && pindexNewTip &&
             pindexNewTip->nHeight >= nStopAtHeight) {
             StartShutdown();
         }
 
         // We check shutdown only after giving ActivateBestChainStep a chance to
         // run once so that we never shutdown before connecting the genesis
         // block during LoadChainTip(). Previously this caused an assert()
         // failure during shutdown in such cases as the UTXO DB flushing checks
         // that the best block hash is non-null.
         if (ShutdownRequested()) {
             break;
         }
     } while (pindexNewTip != pindexMostWork);
 
     // Write changes periodically to disk, after relay.
     if (!FlushStateToDisk(params, state, FlushStateMode::PERIODIC)) {
         return false;
     }
 
     return true;
 }
 
 bool CChainState::PreciousBlock(const Config &config,
                                 BlockValidationState &state,
                                 CBlockIndex *pindex) {
     {
         LOCK(cs_main);
         if (pindex->nChainWork < m_chain.Tip()->nChainWork) {
             // Nothing to do, this block is not at the tip.
             return true;
         }
 
         if (m_chain.Tip()->nChainWork > nLastPreciousChainwork) {
             // The chain has been extended since the last call, reset the
             // counter.
             nBlockReverseSequenceId = -1;
         }
 
         nLastPreciousChainwork = m_chain.Tip()->nChainWork;
         setBlockIndexCandidates.erase(pindex);
         pindex->nSequenceId = nBlockReverseSequenceId;
         if (nBlockReverseSequenceId > std::numeric_limits<int32_t>::min()) {
             // We can't keep reducing the counter if somebody really wants to
             // call preciousblock 2**31-1 times on the same set of tips...
             nBlockReverseSequenceId--;
         }
 
         // In case this was parked, unpark it.
         UnparkBlock(pindex);
 
         // Make sure it is added to the candidate list if appropriate.
         if (pindex->IsValid(BlockValidity::TRANSACTIONS) &&
             pindex->HaveTxsDownloaded()) {
             setBlockIndexCandidates.insert(pindex);
             PruneBlockIndexCandidates();
         }
     }
 
     return ActivateBestChain(config, state);
 }
 
 bool CChainState::UnwindBlock(const Config &config, BlockValidationState &state,
                               CBlockIndex *pindex, bool invalidate) {
     CBlockIndex *to_mark_failed_or_parked = pindex;
     bool pindex_was_in_chain = false;
     int disconnected = 0;
     const CChainParams &chainparams = config.GetChainParams();
 
     // We do not allow ActivateBestChain() to run while UnwindBlock() is
     // running, as that could cause the tip to change while we disconnect
     // blocks. (Note for backport of Core PR16849: we acquire
     // LOCK(m_cs_chainstate) in the Park, Invalidate and FinalizeBlock functions
     // due to differences in our code)
     AssertLockHeld(m_cs_chainstate);
 
     // We'll be acquiring and releasing cs_main below, to allow the validation
     // callbacks to run. However, we should keep the block index in a
     // consistent state as we disconnect blocks -- in particular we need to
     // add equal-work blocks to setBlockIndexCandidates as we disconnect.
     // To avoid walking the block index repeatedly in search of candidates,
     // build a map once so that we can look up candidate blocks by chain
     // work as we go.
     std::multimap<const arith_uint256, CBlockIndex *> candidate_blocks_by_work;
 
     {
         LOCK(cs_main);
         for (const auto &entry : m_blockman.m_block_index) {
             CBlockIndex *candidate = entry.second;
             // We don't need to put anything in our active chain into the
             // multimap, because those candidates will be found and considered
             // as we disconnect.
             // Instead, consider only non-active-chain blocks that have at
             // least as much work as where we expect the new tip to end up.
             if (!m_chain.Contains(candidate) &&
                 !CBlockIndexWorkComparator()(candidate, pindex->pprev) &&
                 candidate->IsValid(BlockValidity::TRANSACTIONS) &&
                 candidate->HaveTxsDownloaded()) {
                 candidate_blocks_by_work.insert(
                     std::make_pair(candidate->nChainWork, candidate));
             }
         }
     }
 
     // Disconnect (descendants of) pindex, and mark them invalid.
     while (true) {
         if (ShutdownRequested()) {
             break;
         }
 
         // Make sure the queue of validation callbacks doesn't grow unboundedly.
         LimitValidationInterfaceQueue();
 
         LOCK(cs_main);
         // Lock for as long as disconnectpool is in scope to make sure
         // UpdateMempoolForReorg is called after DisconnectTip without unlocking
         // in between
         LOCK(m_mempool.cs);
 
         if (!m_chain.Contains(pindex)) {
             break;
         }
 
         pindex_was_in_chain = true;
         CBlockIndex *invalid_walk_tip = m_chain.Tip();
 
         // ActivateBestChain considers blocks already in m_chain
         // unconditionally valid already, so force disconnect away from it.
 
         DisconnectedBlockTransactions disconnectpool;
 
         bool ret = DisconnectTip(chainparams, state, &disconnectpool);
 
         // DisconnectTip will add transactions to disconnectpool.
         // Adjust the mempool to be consistent with the new tip, adding
         // transactions back to the mempool if disconnecting was successful,
         // and we're not doing a very deep invalidation (in which case
         // keeping the mempool up to date is probably futile anyway).
         disconnectpool.updateMempoolForReorg(
             config, ::ChainstateActive(),
             /* fAddToMempool = */ (++disconnected <= 10) && ret, m_mempool);
 
         if (!ret) {
             return false;
         }
 
         assert(invalid_walk_tip->pprev == m_chain.Tip());
 
         // We immediately mark the disconnected blocks as invalid.
         // This prevents a case where pruned nodes may fail to invalidateblock
         // and be left unable to start as they have no tip candidates (as there
         // are no blocks that meet the "have data and are not invalid per
         // nStatus" criteria for inclusion in setBlockIndexCandidates).
 
         invalid_walk_tip->nStatus =
             invalidate ? invalid_walk_tip->nStatus.withFailed()
                        : invalid_walk_tip->nStatus.withParked();
 
         setDirtyBlockIndex.insert(invalid_walk_tip);
         setBlockIndexCandidates.insert(invalid_walk_tip->pprev);
 
         if (invalid_walk_tip == to_mark_failed_or_parked->pprev &&
             (invalidate ? to_mark_failed_or_parked->nStatus.hasFailed()
                         : to_mark_failed_or_parked->nStatus.isParked())) {
             // We only want to mark the last disconnected block as
             // Failed (or Parked); its children need to be FailedParent (or
             // ParkedParent) instead.
             to_mark_failed_or_parked->nStatus =
                 (invalidate
                      ? to_mark_failed_or_parked->nStatus.withFailed(false)
                            .withFailedParent()
                      : to_mark_failed_or_parked->nStatus.withParked(false)
                            .withParkedParent());
 
             setDirtyBlockIndex.insert(to_mark_failed_or_parked);
         }
 
         // Add any equal or more work headers to setBlockIndexCandidates
         auto candidate_it = candidate_blocks_by_work.lower_bound(
             invalid_walk_tip->pprev->nChainWork);
         while (candidate_it != candidate_blocks_by_work.end()) {
             if (!CBlockIndexWorkComparator()(candidate_it->second,
                                              invalid_walk_tip->pprev)) {
                 setBlockIndexCandidates.insert(candidate_it->second);
                 candidate_it = candidate_blocks_by_work.erase(candidate_it);
             } else {
                 ++candidate_it;
             }
         }
 
         // Track the last disconnected block, so we can correct its
         // FailedParent (or ParkedParent) status in future iterations, or, if
         // it's the last one, call InvalidChainFound on it.
         to_mark_failed_or_parked = invalid_walk_tip;
     }
 
     CheckBlockIndex(chainparams.GetConsensus());
 
     {
         LOCK(cs_main);
         if (m_chain.Contains(to_mark_failed_or_parked)) {
             // If the to-be-marked invalid block is in the active chain,
             // something is interfering and we can't proceed.
             return false;
         }
 
         // Mark pindex (or the last disconnected block) as invalid (or parked),
         // even when it never was in the main chain.
         to_mark_failed_or_parked->nStatus =
             invalidate ? to_mark_failed_or_parked->nStatus.withFailed()
                        : to_mark_failed_or_parked->nStatus.withParked();
         setDirtyBlockIndex.insert(to_mark_failed_or_parked);
         if (invalidate) {
             m_blockman.m_failed_blocks.insert(to_mark_failed_or_parked);
         }
 
         // If any new blocks somehow arrived while we were disconnecting
         // (above), then the pre-calculation of what should go into
         // setBlockIndexCandidates may have missed entries. This would
         // technically be an inconsistency in the block index, but if we clean
         // it up here, this should be an essentially unobservable error.
         // Loop back over all block index entries and add any missing entries
         // to setBlockIndexCandidates.
         for (const std::pair<const BlockHash, CBlockIndex *> &it :
              m_blockman.m_block_index) {
             CBlockIndex *i = it.second;
             if (i->IsValid(BlockValidity::TRANSACTIONS) &&
                 i->HaveTxsDownloaded() &&
                 !setBlockIndexCandidates.value_comp()(i, m_chain.Tip())) {
                 setBlockIndexCandidates.insert(i);
             }
         }
 
         if (invalidate) {
             InvalidChainFound(to_mark_failed_or_parked);
         }
     }
 
     // Only notify about a new block tip if the active chain was modified.
     if (pindex_was_in_chain) {
         uiInterface.NotifyBlockTip(
             GetSynchronizationState(IsInitialBlockDownload()),
             to_mark_failed_or_parked->pprev);
     }
     return true;
 }
 
 bool CChainState::InvalidateBlock(const Config &config,
                                   BlockValidationState &state,
                                   CBlockIndex *pindex) {
     AssertLockNotHeld(m_cs_chainstate);
     // See 'Note for backport of Core PR16849' in CChainState::UnwindBlock
     LOCK(m_cs_chainstate);
 
     return UnwindBlock(config, state, pindex, true);
 }
 
 bool CChainState::ParkBlock(const Config &config, BlockValidationState &state,
                             CBlockIndex *pindex) {
     AssertLockNotHeld(m_cs_chainstate);
     // See 'Note for backport of Core PR16849' in CChainState::UnwindBlock
     LOCK(m_cs_chainstate);
 
     return UnwindBlock(config, state, pindex, false);
 }
 
 bool CChainState::FinalizeBlock(const Config &config,
                                 BlockValidationState &state,
                                 CBlockIndex *pindex) {
     AssertLockNotHeld(m_cs_chainstate);
     // See 'Note for backport of Core PR16849' in CChainState::UnwindBlock
     LOCK(m_cs_chainstate);
 
     AssertLockNotHeld(cs_main);
     CBlockIndex *pindexToInvalidate = nullptr;
     {
         LOCK(cs_main);
         if (!MarkBlockAsFinal(state, pindex)) {
             // state is set by MarkBlockAsFinal.
             return false;
         }
 
         // We have a valid candidate, make sure it is not parked.
         if (pindex->nStatus.isOnParkedChain()) {
             UnparkBlock(pindex);
         }
 
         // If the finalized block is on the active chain, there is no need to
         // rewind.
         if (::ChainActive().Contains(pindex)) {
             return true;
         }
 
         // If the finalized block is not on the active chain, that chain is
         // invalid
         // ...
         const CBlockIndex *pindexFork = ::ChainActive().FindFork(pindex);
         pindexToInvalidate = ::ChainActive().Next(pindexFork);
         if (!pindexToInvalidate) {
             return false;
         }
     } // end of locked cs_main scope
 
     // ... therefore, we invalidate the block on the active chain that comes
     // immediately after it
     return UnwindBlock(config, state, pindexToInvalidate,
                        true /* invalidating */);
 }
 
 template <typename F>
 bool CChainState::UpdateFlagsForBlock(CBlockIndex *pindexBase,
                                       CBlockIndex *pindex, F f) {
     BlockStatus newStatus = f(pindex->nStatus);
     if (pindex->nStatus != newStatus &&
         (!pindexBase ||
          pindex->GetAncestor(pindexBase->nHeight) == pindexBase)) {
         pindex->nStatus = newStatus;
         setDirtyBlockIndex.insert(pindex);
         if (newStatus.isValid()) {
             m_blockman.m_failed_blocks.erase(pindex);
         }
 
         if (pindex->IsValid(BlockValidity::TRANSACTIONS) &&
             pindex->HaveTxsDownloaded() &&
             setBlockIndexCandidates.value_comp()(::ChainActive().Tip(),
                                                  pindex)) {
             setBlockIndexCandidates.insert(pindex);
         }
         return true;
     }
     return false;
 }
 
 template <typename F, typename C, typename AC>
 void CChainState::UpdateFlags(CBlockIndex *pindex, CBlockIndex *&pindexReset,
                               F f, C fChild, AC fAncestorWasChanged) {
     AssertLockHeld(cs_main);
 
     // Update the current block and ancestors; while we're doing this, identify
     // which was the deepest ancestor we changed.
     CBlockIndex *pindexDeepestChanged = pindex;
     for (auto pindexAncestor = pindex; pindexAncestor != nullptr;
          pindexAncestor = pindexAncestor->pprev) {
         if (UpdateFlagsForBlock(nullptr, pindexAncestor, f)) {
             pindexDeepestChanged = pindexAncestor;
         }
     }
 
     if (pindexReset &&
         pindexReset->GetAncestor(pindexDeepestChanged->nHeight) ==
             pindexDeepestChanged) {
         // reset pindexReset if it had a modified ancestor.
         pindexReset = nullptr;
     }
 
     // Update all blocks under modified blocks.
     BlockMap::iterator it = m_blockman.m_block_index.begin();
     while (it != m_blockman.m_block_index.end()) {
         UpdateFlagsForBlock(pindex, it->second, fChild);
         UpdateFlagsForBlock(pindexDeepestChanged, it->second,
                             fAncestorWasChanged);
         it++;
     }
 }
 
 void CChainState::ResetBlockFailureFlags(CBlockIndex *pindex) {
     AssertLockHeld(cs_main);
 
     // In case we are reconsidering something before the finalization point,
     // move the finalization point to the last common ancestor.
     if (m_finalizedBlockIndex) {
         m_finalizedBlockIndex =
             LastCommonAncestor(pindex, m_finalizedBlockIndex);
     }
 
     UpdateFlags(
         pindex, pindexBestInvalid,
         [](const BlockStatus status) {
             return status.withClearedFailureFlags();
         },
         [](const BlockStatus status) {
             return status.withClearedFailureFlags();
         },
         [](const BlockStatus status) {
             return status.withFailedParent(false);
         });
 }
 
 void CChainState::UnparkBlockImpl(CBlockIndex *pindex, bool fClearChildren) {
     AssertLockHeld(cs_main);
 
     UpdateFlags(
         pindex, pindexBestParked,
         [](const BlockStatus status) {
             return status.withClearedParkedFlags();
         },
         [fClearChildren](const BlockStatus status) {
             return fClearChildren ? status.withClearedParkedFlags()
                                   : status.withParkedParent(false);
         },
         [](const BlockStatus status) {
             return status.withParkedParent(false);
         });
 }
 
 void CChainState::UnparkBlockAndChildren(CBlockIndex *pindex) {
     return UnparkBlockImpl(pindex, true);
 }
 
 void CChainState::UnparkBlock(CBlockIndex *pindex) {
     return UnparkBlockImpl(pindex, false);
 }
 
 bool CChainState::IsBlockFinalized(const CBlockIndex *pindex) const {
     AssertLockHeld(cs_main);
     return m_finalizedBlockIndex &&
            m_finalizedBlockIndex->GetAncestor(pindex->nHeight) == pindex;
 }
 
 /** Return the currently finalized block index. */
 const CBlockIndex *CChainState::GetFinalizedBlock() const {
     AssertLockHeld(cs_main);
     return m_finalizedBlockIndex;
 }
 
 CBlockIndex *BlockManager::AddToBlockIndex(const CBlockHeader &block) {
     AssertLockHeld(cs_main);
 
     // Check for duplicate
     BlockHash hash = block.GetHash();
     BlockMap::iterator it = m_block_index.find(hash);
     if (it != m_block_index.end()) {
         return it->second;
     }
 
     // Construct new block index object
     CBlockIndex *pindexNew = new CBlockIndex(block);
     // We assign the sequence id to blocks only when the full data is available,
     // to avoid miners withholding blocks but broadcasting headers, to get a
     // competitive advantage.
     pindexNew->nSequenceId = 0;
     BlockMap::iterator mi =
         m_block_index.insert(std::make_pair(hash, pindexNew)).first;
     pindexNew->phashBlock = &((*mi).first);
     BlockMap::iterator miPrev = m_block_index.find(block.hashPrevBlock);
     if (miPrev != m_block_index.end()) {
         pindexNew->pprev = (*miPrev).second;
         pindexNew->nHeight = pindexNew->pprev->nHeight + 1;
         pindexNew->BuildSkip();
     }
     pindexNew->nTimeReceived = GetTime();
     pindexNew->nTimeMax =
         (pindexNew->pprev
              ? std::max(pindexNew->pprev->nTimeMax, pindexNew->nTime)
              : pindexNew->nTime);
     pindexNew->nChainWork =
         (pindexNew->pprev ? pindexNew->pprev->nChainWork : 0) +
         GetBlockProof(*pindexNew);
     pindexNew->RaiseValidity(BlockValidity::TREE);
     if (pindexBestHeader == nullptr ||
         pindexBestHeader->nChainWork < pindexNew->nChainWork) {
         pindexBestHeader = pindexNew;
     }
 
     setDirtyBlockIndex.insert(pindexNew);
     return pindexNew;
 }
 
 /**
  * Mark a block as having its data received and checked (up to
  * BLOCK_VALID_TRANSACTIONS).
  */
 void CChainState::ReceivedBlockTransactions(const CBlock &block,
                                             CBlockIndex *pindexNew,
                                             const FlatFilePos &pos) {
     pindexNew->nTx = block.vtx.size();
     pindexNew->nSize = ::GetSerializeSize(block, PROTOCOL_VERSION);
     pindexNew->nFile = pos.nFile;
     pindexNew->nDataPos = pos.nPos;
     pindexNew->nUndoPos = 0;
     pindexNew->nStatus = pindexNew->nStatus.withData();
     pindexNew->RaiseValidity(BlockValidity::TRANSACTIONS);
     setDirtyBlockIndex.insert(pindexNew);
 
     if (pindexNew->UpdateChainStats()) {
         // If pindexNew is the genesis block or all parents are
         // BLOCK_VALID_TRANSACTIONS.
         std::deque<CBlockIndex *> queue;
         queue.push_back(pindexNew);
 
         // Recursively process any descendant blocks that now may be eligible to
         // be connected.
         while (!queue.empty()) {
             CBlockIndex *pindex = queue.front();
             queue.pop_front();
             pindex->UpdateChainStats();
             if (pindex->nSequenceId == 0) {
                 // We assign a sequence is when transaction are received to
                 // prevent a miner from being able to broadcast a block but not
                 // its content. However, a sequence id may have been set
                 // manually, for instance via PreciousBlock, in which case, we
                 // don't need to assign one.
                 pindex->nSequenceId = nBlockSequenceId++;
             }
 
             if (m_chain.Tip() == nullptr ||
                 !setBlockIndexCandidates.value_comp()(pindex, m_chain.Tip())) {
                 setBlockIndexCandidates.insert(pindex);
             }
 
             std::pair<std::multimap<CBlockIndex *, CBlockIndex *>::iterator,
                       std::multimap<CBlockIndex *, CBlockIndex *>::iterator>
                 range = m_blockman.m_blocks_unlinked.equal_range(pindex);
             while (range.first != range.second) {
                 std::multimap<CBlockIndex *, CBlockIndex *>::iterator it =
                     range.first;
                 queue.push_back(it->second);
                 range.first++;
                 m_blockman.m_blocks_unlinked.erase(it);
             }
         }
     } else if (pindexNew->pprev &&
                pindexNew->pprev->IsValid(BlockValidity::TREE)) {
         m_blockman.m_blocks_unlinked.insert(
             std::make_pair(pindexNew->pprev, pindexNew));
     }
 }
 
 static bool FindBlockPos(FlatFilePos &pos, unsigned int nAddSize,
                          unsigned int nHeight, uint64_t nTime,
                          bool fKnown = false) {
     LOCK(cs_LastBlockFile);
 
     unsigned int nFile = fKnown ? pos.nFile : nLastBlockFile;
     if (vinfoBlockFile.size() <= nFile) {
         vinfoBlockFile.resize(nFile + 1);
     }
 
     bool finalize_undo = false;
     if (!fKnown) {
         while (vinfoBlockFile[nFile].nSize + nAddSize >= MAX_BLOCKFILE_SIZE) {
             // when the undo file is keeping up with the block file, we want to
             // flush it explicitly when it is lagging behind (more blocks arrive
             // than are being connected), we let the undo block write case
             // handle it
             finalize_undo = (vinfoBlockFile[nFile].nHeightLast ==
                              (unsigned int)ChainActive().Tip()->nHeight);
             nFile++;
             if (vinfoBlockFile.size() <= nFile) {
                 vinfoBlockFile.resize(nFile + 1);
             }
         }
         pos.nFile = nFile;
         pos.nPos = vinfoBlockFile[nFile].nSize;
     }
 
     if ((int)nFile != nLastBlockFile) {
         if (!fKnown) {
             LogPrintf("Leaving block file %i: %s\n", nLastBlockFile,
                       vinfoBlockFile[nLastBlockFile].ToString());
         }
         FlushBlockFile(!fKnown, finalize_undo);
         nLastBlockFile = nFile;
     }
 
     vinfoBlockFile[nFile].AddBlock(nHeight, nTime);
     if (fKnown) {
         vinfoBlockFile[nFile].nSize =
             std::max(pos.nPos + nAddSize, vinfoBlockFile[nFile].nSize);
     } else {
         vinfoBlockFile[nFile].nSize += nAddSize;
     }
 
     if (!fKnown) {
         bool out_of_space;
         size_t bytes_allocated =
             BlockFileSeq().Allocate(pos, nAddSize, out_of_space);
         if (out_of_space) {
             return AbortNode("Disk space is too low!",
                              _("Disk space is too low!"));
         }
         if (bytes_allocated != 0 && fPruneMode) {
             fCheckForPruning = true;
         }
     }
 
     setDirtyFileInfo.insert(nFile);
     return true;
 }
 
 static bool FindUndoPos(BlockValidationState &state, int nFile,
                         FlatFilePos &pos, unsigned int nAddSize) {
     pos.nFile = nFile;
 
     LOCK(cs_LastBlockFile);
 
     pos.nPos = vinfoBlockFile[nFile].nUndoSize;
     vinfoBlockFile[nFile].nUndoSize += nAddSize;
     setDirtyFileInfo.insert(nFile);
 
     bool out_of_space;
     size_t bytes_allocated =
         UndoFileSeq().Allocate(pos, nAddSize, out_of_space);
     if (out_of_space) {
         return AbortNode(state, "Disk space is too low!",
                          _("Disk space is too low!"));
     }
     if (bytes_allocated != 0 && fPruneMode) {
         fCheckForPruning = true;
     }
 
     return true;
 }
 
 /**
  * Return true if the provided block header is valid.
  * Only verify PoW if blockValidationOptions is configured to do so.
  * This allows validation of headers on which the PoW hasn't been done.
  * For example: to validate template handed to mining software.
  * Do not call this for any check that depends on the context.
  * For context-dependent calls, see ContextualCheckBlockHeader.
  */
 static bool CheckBlockHeader(const CBlockHeader &block,
                              BlockValidationState &state,
                              const Consensus::Params &params,
                              BlockValidationOptions validationOptions) {
     // Check proof of work matches claimed amount
     if (validationOptions.shouldValidatePoW() &&
         !CheckProofOfWork(block.GetHash(), block.nBits, params)) {
         return state.Invalid(BlockValidationResult::BLOCK_INVALID_HEADER,
                              "high-hash", "proof of work failed");
     }
 
     return true;
 }
 
 bool CheckBlock(const CBlock &block, BlockValidationState &state,
                 const Consensus::Params &params,
                 BlockValidationOptions validationOptions) {
     // These are checks that are independent of context.
     if (block.fChecked) {
         return true;
     }
 
     // Check that the header is valid (particularly PoW).  This is mostly
     // redundant with the call in AcceptBlockHeader.
     if (!CheckBlockHeader(block, state, params, validationOptions)) {
         return false;
     }
 
     // Check the merkle root.
     if (validationOptions.shouldValidateMerkleRoot()) {
         bool mutated;
         uint256 hashMerkleRoot2 = BlockMerkleRoot(block, &mutated);
         if (block.hashMerkleRoot != hashMerkleRoot2) {
             return state.Invalid(BlockValidationResult::BLOCK_MUTATED,
                                  "bad-txnmrklroot", "hashMerkleRoot mismatch");
         }
 
         // Check for merkle tree malleability (CVE-2012-2459): repeating
         // sequences of transactions in a block without affecting the merkle
         // root of a block, while still invalidating it.
         if (mutated) {
             return state.Invalid(BlockValidationResult::BLOCK_MUTATED,
                                  "bad-txns-duplicate", "duplicate transaction");
         }
     }
 
     // All potential-corruption validation must be done before we do any
     // transaction validation, as otherwise we may mark the header as invalid
     // because we receive the wrong transactions for it.
 
     // First transaction must be coinbase.
     if (block.vtx.empty()) {
         return state.Invalid(BlockValidationResult::BLOCK_CONSENSUS,
                              "bad-cb-missing", "first tx is not coinbase");
     }
 
     // Size limits.
     auto nMaxBlockSize = validationOptions.getExcessiveBlockSize();
 
     // Bail early if there is no way this block is of reasonable size.
     if ((block.vtx.size() * MIN_TRANSACTION_SIZE) > nMaxBlockSize) {
         return state.Invalid(BlockValidationResult::BLOCK_CONSENSUS,
                              "bad-blk-length", "size limits failed");
     }
 
     auto currentBlockSize = ::GetSerializeSize(block, PROTOCOL_VERSION);
     if (currentBlockSize > nMaxBlockSize) {
         return state.Invalid(BlockValidationResult::BLOCK_CONSENSUS,
                              "bad-blk-length", "size limits failed");
     }
 
     // And a valid coinbase.
     TxValidationState tx_state;
     if (!CheckCoinbase(*block.vtx[0], tx_state)) {
         return state.Invalid(BlockValidationResult::BLOCK_CONSENSUS,
                              tx_state.GetRejectReason(),
                              strprintf("Coinbase check failed (txid %s) %s",
                                        block.vtx[0]->GetId().ToString(),
                                        tx_state.GetDebugMessage()));
     }
 
     // Check transactions for regularity, skipping the first. Note that this
     // is the first time we check that all after the first are !IsCoinBase.
     for (size_t i = 1; i < block.vtx.size(); i++) {
         auto *tx = block.vtx[i].get();
         if (!CheckRegularTransaction(*tx, tx_state)) {
             return state.Invalid(
                 BlockValidationResult::BLOCK_CONSENSUS,
                 tx_state.GetRejectReason(),
                 strprintf("Transaction check failed (txid %s) %s",
                           tx->GetId().ToString(), tx_state.GetDebugMessage()));
         }
     }
 
     if (validationOptions.shouldValidatePoW() &&
         validationOptions.shouldValidateMerkleRoot()) {
         block.fChecked = true;
     }
 
     return true;
 }
 
 CBlockIndex *BlockManager::GetLastCheckpoint(const CCheckpointData &data) {
     const MapCheckpoints &checkpoints = data.mapCheckpoints;
 
     for (const MapCheckpoints::value_type &i : reverse_iterate(checkpoints)) {
         const BlockHash &hash = i.second;
         assert(std::addressof(g_chainman.m_blockman) == std::addressof(*this));
         CBlockIndex *pindex = LookupBlockIndex(hash);
         if (pindex) {
             return pindex;
         }
     }
 
     return nullptr;
 }
 
 /**
  * Context-dependent validity checks.
  * By "context", we mean only the previous block headers, but not the UTXO
  * set; UTXO-related validity checks are done in ConnectBlock().
  * NOTE: This function is not currently invoked by ConnectBlock(), so we
  * should consider upgrade issues if we change which consensus rules are
  * enforced in this function (eg by adding a new consensus rule). See comment
  * in ConnectBlock().
  * Note that -reindex-chainstate skips the validation that happens here!
  */
 static bool
 ContextualCheckBlockHeader(const CChainParams &params,
                            const CBlockHeader &block,
                            BlockValidationState &state, BlockManager &blockman,
                            const CBlockIndex *pindexPrev, int64_t nAdjustedTime)
     EXCLUSIVE_LOCKS_REQUIRED(cs_main) {
     assert(pindexPrev != nullptr);
     const int nHeight = pindexPrev->nHeight + 1;
 
     // Check proof of work
     if (block.nBits != GetNextWorkRequired(pindexPrev, &block, params)) {
         LogPrintf("bad bits after height: %d\n", pindexPrev->nHeight);
         return state.Invalid(BlockValidationResult::BLOCK_INVALID_HEADER,
                              "bad-diffbits", "incorrect proof of work");
     }
 
     // Check against checkpoints
     if (fCheckpointsEnabled) {
         const CCheckpointData &checkpoints = params.Checkpoints();
 
         // Check that the block chain matches the known block chain up to a
         // checkpoint.
         if (!Checkpoints::CheckBlock(checkpoints, nHeight, block.GetHash())) {
             LogPrintf("ERROR: %s: rejected by checkpoint lock-in at %d\n",
                       __func__, nHeight);
             return state.Invalid(BlockValidationResult::BLOCK_CHECKPOINT,
                                  "checkpoint mismatch");
         }
 
         // Don't accept any forks from the main chain prior to last checkpoint.
         // GetLastCheckpoint finds the last checkpoint in MapCheckpoints that's
         // in our BlockIndex().
         assert(std::addressof(g_chainman.m_blockman) ==
                std::addressof(blockman));
 
         CBlockIndex *pcheckpoint = blockman.GetLastCheckpoint(checkpoints);
         if (pcheckpoint && nHeight < pcheckpoint->nHeight) {
             LogPrintf("ERROR: %s: forked chain older than last checkpoint "
                       "(height %d)\n",
                       __func__, nHeight);
             return state.Invalid(BlockValidationResult::BLOCK_CHECKPOINT,
                                  "bad-fork-prior-to-checkpoint");
         }
     }
 
     // Check timestamp against prev
     if (block.GetBlockTime() <= pindexPrev->GetMedianTimePast()) {
         return state.Invalid(BlockValidationResult::BLOCK_INVALID_HEADER,
                              "time-too-old", "block's timestamp is too early");
     }
 
     // Check timestamp
     if (block.GetBlockTime() > nAdjustedTime + MAX_FUTURE_BLOCK_TIME) {
         return state.Invalid(BlockValidationResult::BLOCK_TIME_FUTURE,
                              "time-too-new",
                              "block timestamp too far in the future");
     }
 
     // Reject outdated version blocks when 95% (75% on testnet) of the network
     // has upgraded:
     // check for version 2, 3 and 4 upgrades
     const Consensus::Params &consensusParams = params.GetConsensus();
     if ((block.nVersion < 2 && nHeight >= consensusParams.BIP34Height) ||
         (block.nVersion < 3 && nHeight >= consensusParams.BIP66Height) ||
         (block.nVersion < 4 && nHeight >= consensusParams.BIP65Height)) {
         return state.Invalid(
             BlockValidationResult::BLOCK_INVALID_HEADER,
             strprintf("bad-version(0x%08x)", block.nVersion),
             strprintf("rejected nVersion=0x%08x block", block.nVersion));
     }
 
     return true;
 }
 
 bool ContextualCheckTransactionForCurrentBlock(
     const CBlockIndex *active_chain_tip, const Consensus::Params &params,
     const CTransaction &tx, TxValidationState &state, int flags) {
     AssertLockHeld(cs_main);
     // TODO: Make active_chain_tip a reference
     assert(active_chain_tip);
     assert(std::addressof(*::ChainActive().Tip()) ==
            std::addressof(*active_chain_tip));
 
     // By convention a negative value for flags indicates that the current
     // network-enforced consensus rules should be used. In a future soft-fork
     // scenario that would mean checking which rules would be enforced for the
     // next block and setting the appropriate flags. At the present time no
     // soft-forks are scheduled, so no flags are set.
     flags = std::max(flags, 0);
 
     // ContextualCheckTransactionForCurrentBlock() uses
     // active_chain_tip.Height()+1 to evaluate nLockTime because when
     // IsFinalTx() is called within CBlock::AcceptBlock(), the height of the
     // block *being* evaluated is what is used. Thus if we want to know if a
     // transaction can be part of the *next* block, we need to call
     // ContextualCheckTransaction() with one more than
     // active_chain_tip.Height().
     const int nBlockHeight = active_chain_tip->nHeight + 1;
 
     // BIP113 will require that time-locked transactions have nLockTime set to
     // less than the median time of the previous block they're contained in.
     // When the next block is created its previous block will be the current
     // chain tip, so we use that to calculate the median time passed to
     // ContextualCheckTransaction() if LOCKTIME_MEDIAN_TIME_PAST is set.
     const int64_t nMedianTimePast = active_chain_tip->GetMedianTimePast();
     const int64_t nLockTimeCutoff = (flags & LOCKTIME_MEDIAN_TIME_PAST)
                                         ? nMedianTimePast
                                         : GetAdjustedTime();
 
     return ContextualCheckTransaction(params, tx, state, nBlockHeight,
                                       nLockTimeCutoff, nMedianTimePast);
 }
 
 /**
  * NOTE: This function is not currently invoked by ConnectBlock(), so we
  * should consider upgrade issues if we change which consensus rules are
  * enforced in this function (eg by adding a new consensus rule). See comment
  * in ConnectBlock().
  * Note that -reindex-chainstate skips the validation that happens here!
  */
 static bool ContextualCheckBlock(const CBlock &block,
                                  BlockValidationState &state,
                                  const Consensus::Params &params,
                                  const CBlockIndex *pindexPrev) {
     const int nHeight = pindexPrev == nullptr ? 0 : pindexPrev->nHeight + 1;
 
     // Start enforcing BIP113 (Median Time Past).
     int nLockTimeFlags = 0;
     if (nHeight >= params.CSVHeight) {
         assert(pindexPrev != nullptr);
         nLockTimeFlags |= LOCKTIME_MEDIAN_TIME_PAST;
     }
 
     const int64_t nMedianTimePast =
         pindexPrev == nullptr ? 0 : pindexPrev->GetMedianTimePast();
 
     const int64_t nLockTimeCutoff = (nLockTimeFlags & LOCKTIME_MEDIAN_TIME_PAST)
                                         ? nMedianTimePast
                                         : block.GetBlockTime();
 
     const bool fIsMagneticAnomalyEnabled =
         IsMagneticAnomalyEnabled(params, pindexPrev);
 
     // Check transactions:
     // - canonical ordering
     // - ensure they are finalized
     // - perform a preliminary block-sigops count (they will be recounted more
     // strictly during ConnectBlock).
     // - perform a transaction-sigops check (again, a more strict check will
     // happen in ConnectBlock).
     const CTransaction *prevTx = nullptr;
     for (const auto &ptx : block.vtx) {
         const CTransaction &tx = *ptx;
         if (fIsMagneticAnomalyEnabled) {
             if (prevTx && (tx.GetId() <= prevTx->GetId())) {
                 if (tx.GetId() == prevTx->GetId()) {
                     return state.Invalid(BlockValidationResult::BLOCK_CONSENSUS,
                                          "tx-duplicate",
                                          strprintf("Duplicated transaction %s",
                                                    tx.GetId().ToString()));
                 }
 
                 return state.Invalid(
                     BlockValidationResult::BLOCK_CONSENSUS, "tx-ordering",
                     strprintf("Transaction order is invalid (%s < %s)",
                               tx.GetId().ToString(),
                               prevTx->GetId().ToString()));
             }
 
             if (prevTx || !tx.IsCoinBase()) {
                 prevTx = &tx;
             }
         }
 
         TxValidationState tx_state;
         if (!ContextualCheckTransaction(params, tx, tx_state, nHeight,
                                         nLockTimeCutoff, nMedianTimePast)) {
             return state.Invalid(BlockValidationResult::BLOCK_CONSENSUS,
                                  tx_state.GetRejectReason(),
                                  tx_state.GetDebugMessage());
         }
     }
 
     // Enforce rule that the coinbase starts with serialized block height
     if (nHeight >= params.BIP34Height) {
         CScript expect = CScript() << nHeight;
         if (block.vtx[0]->vin[0].scriptSig.size() < expect.size() ||
             !std::equal(expect.begin(), expect.end(),
                         block.vtx[0]->vin[0].scriptSig.begin())) {
             return state.Invalid(BlockValidationResult::BLOCK_CONSENSUS,
                                  "bad-cb-height",
                                  "block height mismatch in coinbase");
         }
     }
 
     return true;
 }
 
 /**
  * If the provided block header is valid, add it to the block index.
  *
  * Returns true if the block is successfully added to the block index.
  */
 bool BlockManager::AcceptBlockHeader(const Config &config,
                                      const CBlockHeader &block,
                                      BlockValidationState &state,
                                      CBlockIndex **ppindex) {
     AssertLockHeld(cs_main);
     const CChainParams &chainparams = config.GetChainParams();
 
     // Check for duplicate
     BlockHash hash = block.GetHash();
     BlockMap::iterator miSelf = m_block_index.find(hash);
     CBlockIndex *pindex = nullptr;
     if (hash != chainparams.GetConsensus().hashGenesisBlock) {
         if (miSelf != m_block_index.end()) {
             // Block header is already known.
             pindex = miSelf->second;
             if (ppindex) {
                 *ppindex = pindex;
             }
 
             if (pindex->nStatus.isInvalid()) {
                 LogPrintf("ERROR: %s: block %s is marked invalid\n", __func__,
                           hash.ToString());
                 return state.Invalid(
                     BlockValidationResult::BLOCK_CACHED_INVALID, "duplicate");
             }
 
             return true;
         }
 
         if (!CheckBlockHeader(block, state, chainparams.GetConsensus(),
                               BlockValidationOptions(config))) {
             LogPrint(BCLog::VALIDATION,
                      "%s: Consensus::CheckBlockHeader: %s, %s\n", __func__,
                      hash.ToString(), state.ToString());
             return false;
         }
 
         // Get prev block index
         BlockMap::iterator mi = m_block_index.find(block.hashPrevBlock);
         if (mi == m_block_index.end()) {
             LogPrintf("ERROR: %s: prev block not found\n", __func__);
             return state.Invalid(BlockValidationResult::BLOCK_MISSING_PREV,
                                  "prev-blk-not-found");
         }
 
         CBlockIndex *pindexPrev = (*mi).second;
         assert(pindexPrev);
         if (pindexPrev->nStatus.isInvalid()) {
             LogPrintf("ERROR: %s: prev block invalid\n", __func__);
             return state.Invalid(BlockValidationResult::BLOCK_INVALID_PREV,
                                  "bad-prevblk");
         }
 
         if (!ContextualCheckBlockHeader(chainparams, block, state, *this,
                                         pindexPrev, GetAdjustedTime())) {
             return error("%s: Consensus::ContextualCheckBlockHeader: %s, %s",
                          __func__, hash.ToString(), state.ToString());
         }
 
         /* Determine if this block descends from any block which has been found
          * invalid (m_failed_blocks), then mark pindexPrev and any blocks
          * between them as failed. For example:
          *
          *                D3
          *              /
          *      B2 - C2
          *    /         \
          *  A             D2 - E2 - F2
          *    \
          *      B1 - C1 - D1 - E1
          *
          * In the case that we attempted to reorg from E1 to F2, only to find
          * C2 to be invalid, we would mark D2, E2, and F2 as BLOCK_FAILED_CHILD
          * but NOT D3 (it was not in any of our candidate sets at the time).
          *
          * In any case D3 will also be marked as BLOCK_FAILED_CHILD at restart
          * in LoadBlockIndex.
          */
         if (!pindexPrev->IsValid(BlockValidity::SCRIPTS)) {
             // The above does not mean "invalid": it checks if the previous
             // block hasn't been validated up to BlockValidity::SCRIPTS. This is
             // a performance optimization, in the common case of adding a new
             // block to the tip, we don't need to iterate over the failed blocks
             // list.
             for (const CBlockIndex *failedit : m_failed_blocks) {
                 if (pindexPrev->GetAncestor(failedit->nHeight) == failedit) {
                     assert(failedit->nStatus.hasFailed());
                     CBlockIndex *invalid_walk = pindexPrev;
                     while (invalid_walk != failedit) {
                         invalid_walk->nStatus =
                             invalid_walk->nStatus.withFailedParent();
                         setDirtyBlockIndex.insert(invalid_walk);
                         invalid_walk = invalid_walk->pprev;
                     }
                     LogPrintf("ERROR: %s: prev block invalid\n", __func__);
                     return state.Invalid(
                         BlockValidationResult::BLOCK_INVALID_PREV,
                         "bad-prevblk");
                 }
             }
         }
     }
 
     if (pindex == nullptr) {
         pindex = AddToBlockIndex(block);
     }
 
     if (ppindex) {
         *ppindex = pindex;
     }
 
     return true;
 }
 
 // Exposed wrapper for AcceptBlockHeader
 bool ChainstateManager::ProcessNewBlockHeaders(
     const Config &config, const std::vector<CBlockHeader> &headers,
     BlockValidationState &state, const CBlockIndex **ppindex) {
     assert(std::addressof(::ChainstateActive()) ==
            std::addressof(ActiveChainstate()));
     AssertLockNotHeld(cs_main);
     {
         LOCK(cs_main);
         for (const CBlockHeader &header : headers) {
             // Use a temp pindex instead of ppindex to avoid a const_cast
             CBlockIndex *pindex = nullptr;
             bool accepted =
                 m_blockman.AcceptBlockHeader(config, header, state, &pindex);
             ActiveChainstate().CheckBlockIndex(
                 config.GetChainParams().GetConsensus());
 
             if (!accepted) {
                 return false;
             }
 
             if (ppindex) {
                 *ppindex = pindex;
             }
         }
     }
 
     if (NotifyHeaderTip(ActiveChainstate())) {
         if (ActiveChainstate().IsInitialBlockDownload() && ppindex &&
             *ppindex) {
             LogPrintf("Synchronizing blockheaders, height: %d (~%.2f%%)\n",
                       (*ppindex)->nHeight,
                       100.0 /
                           ((*ppindex)->nHeight +
                            (GetAdjustedTime() - (*ppindex)->GetBlockTime()) /
                                Params().GetConsensus().nPowTargetSpacing) *
                           (*ppindex)->nHeight);
         }
     }
     return true;
 }
 
 /**
  * Store block on disk. If dbp is non-nullptr, the file is known to already
  * reside on disk.
  */
 static FlatFilePos SaveBlockToDisk(const CBlock &block, int nHeight,
                                    const CChainParams &chainparams,
                                    const FlatFilePos *dbp) {
     unsigned int nBlockSize = ::GetSerializeSize(block, CLIENT_VERSION);
     FlatFilePos blockPos;
     if (dbp != nullptr) {
         blockPos = *dbp;
     }
     if (!FindBlockPos(blockPos, nBlockSize + 8, nHeight, block.GetBlockTime(),
                       dbp != nullptr)) {
         error("%s: FindBlockPos failed", __func__);
         return FlatFilePos();
     }
     if (dbp == nullptr) {
         if (!WriteBlockToDisk(block, blockPos, chainparams.DiskMagic())) {
             AbortNode("Failed to write block");
             return FlatFilePos();
         }
     }
     return blockPos;
 }
 
 /**
  * Store a block on disk.
  *
  * @param[in]     config     The global config.
  * @param[in-out] pblock     The block we want to accept.
  * @param[in]     fRequested A boolean to indicate if this block was requested
  *                           from our peers.
  * @param[in]     dbp        If non-null, the disk position of the block.
  * @param[in-out] fNewBlock  True if block was first received via this call.
  * @return True if the block is accepted as a valid block and written to disk.
  */
 bool CChainState::AcceptBlock(const Config &config,
                               const std::shared_ptr<const CBlock> &pblock,
                               BlockValidationState &state, bool fRequested,
                               const FlatFilePos *dbp, bool *fNewBlock) {
     AssertLockHeld(cs_main);
 
     const CBlock &block = *pblock;
     if (fNewBlock) {
         *fNewBlock = false;
     }
 
     CBlockIndex *pindex = nullptr;
 
     bool accepted_header =
         m_blockman.AcceptBlockHeader(config, block, state, &pindex);
     CheckBlockIndex(config.GetChainParams().GetConsensus());
 
     if (!accepted_header) {
         return false;
     }
 
     // Try to process all requested blocks that we don't have, but only
     // process an unrequested block if it's new and has enough work to
     // advance our tip, and isn't too many blocks ahead.
     bool fAlreadyHave = pindex->nStatus.hasData();
 
     // TODO: deal better with return value and error conditions for duplicate
     // and unrequested blocks.
     if (fAlreadyHave) {
         return true;
     }
 
     // Compare block header timestamps and received times of the block and the
     // chaintip.  If they have the same chain height, use these diffs as a
     // tie-breaker, attempting to pick the more honestly-mined block.
     int64_t newBlockTimeDiff = std::llabs(pindex->GetReceivedTimeDiff());
     int64_t chainTipTimeDiff =
         m_chain.Tip() ? std::llabs(m_chain.Tip()->GetReceivedTimeDiff()) : 0;
 
     bool isSameHeight =
         m_chain.Tip() && (pindex->nChainWork == m_chain.Tip()->nChainWork);
     if (isSameHeight) {
         LogPrintf("Chain tip timestamp-to-received-time difference: hash=%s, "
                   "diff=%d\n",
                   m_chain.Tip()->GetBlockHash().ToString(), chainTipTimeDiff);
         LogPrintf("New block timestamp-to-received-time difference: hash=%s, "
                   "diff=%d\n",
                   pindex->GetBlockHash().ToString(), newBlockTimeDiff);
     }
 
     bool fHasMoreOrSameWork =
         (m_chain.Tip() ? pindex->nChainWork >= m_chain.Tip()->nChainWork
                        : true);
 
     // Blocks that are too out-of-order needlessly limit the effectiveness of
     // pruning, because pruning will not delete block files that contain any
     // blocks which are too close in height to the tip.  Apply this test
     // regardless of whether pruning is enabled; it should generally be safe to
     // not process unrequested blocks.
     bool fTooFarAhead =
         (pindex->nHeight > int(m_chain.Height() + MIN_BLOCKS_TO_KEEP));
 
     // TODO: Decouple this function from the block download logic by removing
     // fRequested
     // This requires some new chain data structure to efficiently look up if a
     // block is in a chain leading to a candidate for best tip, despite not
     // being such a candidate itself.
 
     // If we didn't ask for it:
     if (!fRequested) {
         // This is a previously-processed block that was pruned.
         if (pindex->nTx != 0) {
             return true;
         }
 
         // Don't process less-work chains.
         if (!fHasMoreOrSameWork) {
             return true;
         }
 
         // Block height is too high.
         if (fTooFarAhead) {
             return true;
         }
 
         // Protect against DoS attacks from low-work chains.
         // If our tip is behind, a peer could try to send us
         // low-work blocks on a fake chain that we would never
         // request; don't process these.
         if (pindex->nChainWork < nMinimumChainWork) {
             return true;
         }
     }
 
     const CChainParams &chainparams = config.GetChainParams();
     const Consensus::Params &consensusParams = chainparams.GetConsensus();
 
     if (!CheckBlock(block, state, consensusParams,
                     BlockValidationOptions(config)) ||
         !ContextualCheckBlock(block, state, consensusParams, pindex->pprev)) {
         if (state.IsInvalid() &&
             state.GetResult() != BlockValidationResult::BLOCK_MUTATED) {
             pindex->nStatus = pindex->nStatus.withFailed();
             setDirtyBlockIndex.insert(pindex);
         }
 
         return error("%s: %s (block %s)", __func__, state.ToString(),
                      block.GetHash().ToString());
     }
 
     // If connecting the new block would require rewinding more than one block
     // from the active chain (i.e., a "deep reorg"), then mark the new block as
     // parked. If it has enough work then it will be automatically unparked
     // later, during FindMostWorkChain. We mark the block as parked at the very
     // last minute so we can make sure everything is ready to be reorged if
     // needed.
     if (gArgs.GetBoolArg("-parkdeepreorg", true)) {
         const CBlockIndex *pindexFork = m_chain.FindFork(pindex);
         if (pindexFork && pindexFork->nHeight + 1 < m_chain.Height()) {
             LogPrintf("Park block %s as it would cause a deep reorg.\n",
                       pindex->GetBlockHash().ToString());
             pindex->nStatus = pindex->nStatus.withParked();
             setDirtyBlockIndex.insert(pindex);
         }
     }
 
     // Header is valid/has work and the merkle tree is good.
     // Relay now, but if it does not build on our best tip, let the
     // SendMessages loop relay it.
     if (!IsInitialBlockDownload() && m_chain.Tip() == pindex->pprev) {
         GetMainSignals().NewPoWValidBlock(pindex, pblock);
     }
 
     // Write block to history file
     if (fNewBlock) {
         *fNewBlock = true;
     }
     try {
         FlatFilePos blockPos =
             SaveBlockToDisk(block, pindex->nHeight, chainparams, dbp);
         if (blockPos.IsNull()) {
             state.Error(strprintf(
                 "%s: Failed to find position to write new block to disk",
                 __func__));
             return false;
         }
         ReceivedBlockTransactions(block, pindex, blockPos);
     } catch (const std::runtime_error &e) {
         return AbortNode(state, std::string("System error: ") + e.what());
     }
 
     FlushStateToDisk(chainparams, state, FlushStateMode::NONE);
 
     CheckBlockIndex(consensusParams);
 
     return true;
 }
 
 bool ChainstateManager::ProcessNewBlock(
     const Config &config, const std::shared_ptr<const CBlock> pblock,
     bool fForceProcessing, bool *fNewBlock) {
     AssertLockNotHeld(cs_main);
     assert(std::addressof(::ChainstateActive()) ==
            std::addressof(ActiveChainstate()));
 
     {
         if (fNewBlock) {
             *fNewBlock = false;
         }
 
         BlockValidationState state;
 
         // CheckBlock() does not support multi-threaded block validation
         // because CBlock::fChecked can cause data race.
         // Therefore, the following critical section must include the
         // CheckBlock() call as well.
         LOCK(cs_main);
 
         // Ensure that CheckBlock() passes before calling AcceptBlock, as
         // belt-and-suspenders.
         bool ret =
             CheckBlock(*pblock, state, config.GetChainParams().GetConsensus(),
                        BlockValidationOptions(config));
         if (ret) {
             // Store to disk
             ret = ActiveChainstate().AcceptBlock(
                 config, pblock, state, fForceProcessing, nullptr, fNewBlock);
         }
 
         if (!ret) {
             GetMainSignals().BlockChecked(*pblock, state);
             return error("%s: AcceptBlock FAILED (%s)", __func__,
                          state.ToString());
         }
     }
 
     NotifyHeaderTip(ActiveChainstate());
 
     // Only used to report errors, not invalidity - ignore it
     BlockValidationState state;
     if (!ActiveChainstate().ActivateBestChain(config, state, pblock)) {
         return error("%s: ActivateBestChain failed (%s)", __func__,
                      state.ToString());
     }
 
     return true;
 }
 
 bool TestBlockValidity(BlockValidationState &state, const CChainParams &params,
                        CChainState &chainstate, const CBlock &block,
                        CBlockIndex *pindexPrev,
                        BlockValidationOptions validationOptions) {
     AssertLockHeld(cs_main);
     assert(std::addressof(::ChainstateActive()) == std::addressof(chainstate));
     assert(pindexPrev && pindexPrev == chainstate.m_chain.Tip());
     CCoinsViewCache viewNew(&chainstate.CoinsTip());
     BlockHash block_hash(block.GetHash());
     CBlockIndex indexDummy(block);
     indexDummy.pprev = pindexPrev;
     indexDummy.nHeight = pindexPrev->nHeight + 1;
     indexDummy.phashBlock = &block_hash;
 
     // NOTE: CheckBlockHeader is called by CheckBlock
     assert(std::addressof(g_chainman.m_blockman) ==
            std::addressof(chainstate.m_blockman));
     if (!ContextualCheckBlockHeader(params, block, state, chainstate.m_blockman,
                                     pindexPrev, GetAdjustedTime())) {
         return error("%s: Consensus::ContextualCheckBlockHeader: %s", __func__,
                      state.ToString());
     }
 
     if (!CheckBlock(block, state, params.GetConsensus(), validationOptions)) {
         return error("%s: Consensus::CheckBlock: %s", __func__,
                      state.ToString());
     }
 
     if (!ContextualCheckBlock(block, state, params.GetConsensus(),
                               pindexPrev)) {
         return error("%s: Consensus::ContextualCheckBlock: %s", __func__,
                      state.ToString());
     }
 
     if (!chainstate.ConnectBlock(block, state, &indexDummy, viewNew, params,
                                  validationOptions, true)) {
         return false;
     }
 
     assert(state.IsValid());
     return true;
 }
 
 /**
  * BLOCK PRUNING CODE
  */
 
 /**
  * Calculate the amount of disk space the block & undo files currently use.
  */
 uint64_t CalculateCurrentUsage() {
     LOCK(cs_LastBlockFile);
 
     uint64_t retval = 0;
     for (const CBlockFileInfo &file : vinfoBlockFile) {
         retval += file.nSize + file.nUndoSize;
     }
 
     return retval;
 }
 
 void BlockManager::PruneOneBlockFile(const int fileNumber) {
     AssertLockHeld(cs_main);
     LOCK(cs_LastBlockFile);
 
     for (const auto &entry : m_block_index) {
         CBlockIndex *pindex = entry.second;
         if (pindex->nFile == fileNumber) {
             pindex->nStatus = pindex->nStatus.withData(false).withUndo(false);
             pindex->nFile = 0;
             pindex->nDataPos = 0;
             pindex->nUndoPos = 0;
             setDirtyBlockIndex.insert(pindex);
 
             // Prune from m_blocks_unlinked -- any block we prune would have
             // to be downloaded again in order to consider its chain, at which
             // point it would be considered as a candidate for
             // m_blocks_unlinked or setBlockIndexCandidates.
             auto range = m_blocks_unlinked.equal_range(pindex->pprev);
             while (range.first != range.second) {
                 std::multimap<CBlockIndex *, CBlockIndex *>::iterator _it =
                     range.first;
                 range.first++;
                 if (_it->second == pindex) {
                     m_blocks_unlinked.erase(_it);
                 }
             }
         }
     }
 
     vinfoBlockFile[fileNumber].SetNull();
     setDirtyFileInfo.insert(fileNumber);
 }
 
 void UnlinkPrunedFiles(const std::set<int> &setFilesToPrune) {
     for (const int i : setFilesToPrune) {
         FlatFilePos pos(i, 0);
         fs::remove(BlockFileSeq().FileName(pos));
         fs::remove(UndoFileSeq().FileName(pos));
         LogPrintf("Prune: %s deleted blk/rev (%05u)\n", __func__, i);
     }
 }
 
 void BlockManager::FindFilesToPruneManual(std::set<int> &setFilesToPrune,
                                           int nManualPruneHeight,
                                           int chain_tip_height) {
     assert(fPruneMode && nManualPruneHeight > 0);
 
     LOCK2(cs_main, cs_LastBlockFile);
     if (chain_tip_height < 0) {
         return;
     }
 
     // last block to prune is the lesser of (user-specified height,
     // MIN_BLOCKS_TO_KEEP from the tip)
     unsigned int nLastBlockWeCanPrune = std::min(
         (unsigned)nManualPruneHeight, chain_tip_height - MIN_BLOCKS_TO_KEEP);
     int count = 0;
     for (int fileNumber = 0; fileNumber < nLastBlockFile; fileNumber++) {
         if (vinfoBlockFile[fileNumber].nSize == 0 ||
             vinfoBlockFile[fileNumber].nHeightLast > nLastBlockWeCanPrune) {
             continue;
         }
         PruneOneBlockFile(fileNumber);
         setFilesToPrune.insert(fileNumber);
         count++;
     }
     LogPrintf("Prune (Manual): prune_height=%d removed %d blk/rev pairs\n",
               nLastBlockWeCanPrune, count);
 }
 
 /* This function is called from the RPC code for pruneblockchain */
-void PruneBlockFilesManual(int nManualPruneHeight) {
+void PruneBlockFilesManual(CChainState &active_chainstate,
+                           int nManualPruneHeight) {
     BlockValidationState state;
     const CChainParams &chainparams = Params();
-    if (!::ChainstateActive().FlushStateToDisk(
+    assert(std::addressof(::ChainstateActive()) ==
+           std::addressof(active_chainstate));
+    if (active_chainstate.FlushStateToDisk(
             chainparams, state, FlushStateMode::NONE, nManualPruneHeight)) {
         LogPrintf("%s: failed to flush state (%s)\n", __func__,
                   state.ToString());
     }
 }
 
 void BlockManager::FindFilesToPrune(std::set<int> &setFilesToPrune,
                                     uint64_t nPruneAfterHeight,
                                     int chain_tip_height, bool is_ibd) {
     LOCK2(cs_main, cs_LastBlockFile);
     if (chain_tip_height < 0 || nPruneTarget == 0) {
         return;
     }
     if (uint64_t(chain_tip_height) <= nPruneAfterHeight) {
         return;
     }
 
     unsigned int nLastBlockWeCanPrune = chain_tip_height - MIN_BLOCKS_TO_KEEP;
     uint64_t nCurrentUsage = CalculateCurrentUsage();
     // We don't check to prune until after we've allocated new space for files,
     // so we should leave a buffer under our target to account for another
     // allocation before the next pruning.
     uint64_t nBuffer = BLOCKFILE_CHUNK_SIZE + UNDOFILE_CHUNK_SIZE;
     uint64_t nBytesToPrune;
     int count = 0;
 
     if (nCurrentUsage + nBuffer >= nPruneTarget) {
         // On a prune event, the chainstate DB is flushed.
         // To avoid excessive prune events negating the benefit of high dbcache
         // values, we should not prune too rapidly.
         // So when pruning in IBD, increase the buffer a bit to avoid a re-prune
         // too soon.
         if (is_ibd) {
             // Since this is only relevant during IBD, we use a fixed 10%
             nBuffer += nPruneTarget / 10;
         }
 
         for (int fileNumber = 0; fileNumber < nLastBlockFile; fileNumber++) {
             nBytesToPrune = vinfoBlockFile[fileNumber].nSize +
                             vinfoBlockFile[fileNumber].nUndoSize;
 
             if (vinfoBlockFile[fileNumber].nSize == 0) {
                 continue;
             }
 
             // are we below our target?
             if (nCurrentUsage + nBuffer < nPruneTarget) {
                 break;
             }
 
             // don't prune files that could have a block within
             // MIN_BLOCKS_TO_KEEP of the main chain's tip but keep scanning
             if (vinfoBlockFile[fileNumber].nHeightLast > nLastBlockWeCanPrune) {
                 continue;
             }
 
             PruneOneBlockFile(fileNumber);
             // Queue up the files for removal
             setFilesToPrune.insert(fileNumber);
             nCurrentUsage -= nBytesToPrune;
             count++;
         }
     }
 
     LogPrint(BCLog::PRUNE,
              "Prune: target=%dMiB actual=%dMiB diff=%dMiB "
              "max_prune_height=%d removed %d blk/rev pairs\n",
              nPruneTarget / 1024 / 1024, nCurrentUsage / 1024 / 1024,
              ((int64_t)nPruneTarget - (int64_t)nCurrentUsage) / 1024 / 1024,
              nLastBlockWeCanPrune, count);
 }
 
 CBlockIndex *BlockManager::InsertBlockIndex(const BlockHash &hash) {
     AssertLockHeld(cs_main);
 
     if (hash.IsNull()) {
         return nullptr;
     }
 
     // Return existing
     BlockMap::iterator mi = m_block_index.find(hash);
     if (mi != m_block_index.end()) {
         return (*mi).second;
     }
 
     // Create new
     CBlockIndex *pindexNew = new CBlockIndex();
     mi = m_block_index.insert(std::make_pair(hash, pindexNew)).first;
     pindexNew->phashBlock = &((*mi).first);
 
     return pindexNew;
 }
 
 bool BlockManager::LoadBlockIndex(
     const Consensus::Params &params, CBlockTreeDB &blocktree,
     std::set<CBlockIndex *, CBlockIndexWorkComparator>
         &block_index_candidates) {
     AssertLockHeld(cs_main);
     if (!blocktree.LoadBlockIndexGuts(
             params, [this](const BlockHash &hash) EXCLUSIVE_LOCKS_REQUIRED(
                         cs_main) { return this->InsertBlockIndex(hash); })) {
         return false;
     }
 
     // Calculate nChainWork
     std::vector<std::pair<int, CBlockIndex *>> vSortedByHeight;
     vSortedByHeight.reserve(m_block_index.size());
     for (const std::pair<const BlockHash, CBlockIndex *> &item :
          m_block_index) {
         CBlockIndex *pindex = item.second;
         vSortedByHeight.push_back(std::make_pair(pindex->nHeight, pindex));
     }
 
     sort(vSortedByHeight.begin(), vSortedByHeight.end());
     for (const std::pair<int, CBlockIndex *> &item : vSortedByHeight) {
         if (ShutdownRequested()) {
             return false;
         }
         CBlockIndex *pindex = item.second;
         pindex->nChainWork = (pindex->pprev ? pindex->pprev->nChainWork : 0) +
                              GetBlockProof(*pindex);
         pindex->nTimeMax =
             (pindex->pprev ? std::max(pindex->pprev->nTimeMax, pindex->nTime)
                            : pindex->nTime);
         // We can link the chain of blocks for which we've received transactions
         // at some point. Pruned nodes may have deleted the block.
         if (pindex->nTx > 0) {
             if (!pindex->UpdateChainStats() && pindex->pprev) {
                 m_blocks_unlinked.insert(std::make_pair(pindex->pprev, pindex));
             }
         }
 
         if (!pindex->nStatus.hasFailed() && pindex->pprev &&
             pindex->pprev->nStatus.hasFailed()) {
             pindex->nStatus = pindex->nStatus.withFailedParent();
             setDirtyBlockIndex.insert(pindex);
         }
         if (pindex->IsValid(BlockValidity::TRANSACTIONS) &&
             (pindex->HaveTxsDownloaded() || pindex->pprev == nullptr)) {
             block_index_candidates.insert(pindex);
         }
 
         if (pindex->nStatus.isInvalid() &&
             (!pindexBestInvalid ||
              pindex->nChainWork > pindexBestInvalid->nChainWork)) {
             pindexBestInvalid = pindex;
         }
 
         if (pindex->nStatus.isOnParkedChain() &&
             (!pindexBestParked ||
              pindex->nChainWork > pindexBestParked->nChainWork)) {
             pindexBestParked = pindex;
         }
 
         if (pindex->pprev) {
             pindex->BuildSkip();
         }
 
         if (pindex->IsValid(BlockValidity::TREE) &&
             (pindexBestHeader == nullptr ||
              CBlockIndexWorkComparator()(pindexBestHeader, pindex))) {
             pindexBestHeader = pindex;
         }
     }
 
     return true;
 }
 
 void BlockManager::Unload() {
     m_failed_blocks.clear();
     m_blocks_unlinked.clear();
 
     for (const BlockMap::value_type &entry : m_block_index) {
         delete entry.second;
     }
 
     m_block_index.clear();
 }
 
 static bool LoadBlockIndexDB(ChainstateManager &chainman,
                              const Consensus::Params &params)
     EXCLUSIVE_LOCKS_REQUIRED(cs_main) {
     if (!chainman.m_blockman.LoadBlockIndex(
             params, *pblocktree,
             ::ChainstateActive().setBlockIndexCandidates)) {
         return false;
     }
 
     // Load block file info
     pblocktree->ReadLastBlockFile(nLastBlockFile);
     vinfoBlockFile.resize(nLastBlockFile + 1);
     LogPrintf("%s: last block file = %i\n", __func__, nLastBlockFile);
     for (int nFile = 0; nFile <= nLastBlockFile; nFile++) {
         pblocktree->ReadBlockFileInfo(nFile, vinfoBlockFile[nFile]);
     }
     LogPrintf("%s: last block file info: %s\n", __func__,
               vinfoBlockFile[nLastBlockFile].ToString());
     for (int nFile = nLastBlockFile + 1; true; nFile++) {
         CBlockFileInfo info;
         if (pblocktree->ReadBlockFileInfo(nFile, info)) {
             vinfoBlockFile.push_back(info);
         } else {
             break;
         }
     }
 
     // Check presence of blk files
     LogPrintf("Checking all blk files are present...\n");
     std::set<int> setBlkDataFiles;
     for (const std::pair<const BlockHash, CBlockIndex *> &item :
          chainman.BlockIndex()) {
         CBlockIndex *pindex = item.second;
         if (pindex->nStatus.hasData()) {
             setBlkDataFiles.insert(pindex->nFile);
         }
     }
 
     for (const int i : setBlkDataFiles) {
         FlatFilePos pos(i, 0);
         if (CAutoFile(OpenBlockFile(pos, true), SER_DISK, CLIENT_VERSION)
                 .IsNull()) {
             return false;
         }
     }
 
     // Check whether we have ever pruned block & undo files
     pblocktree->ReadFlag("prunedblockfiles", fHavePruned);
     if (fHavePruned) {
         LogPrintf(
             "LoadBlockIndexDB(): Block files have previously been pruned\n");
     }
 
     // Check whether we need to continue reindexing
     if (pblocktree->IsReindexing()) {
         fReindex = true;
     }
 
     return true;
 }
 
 void CChainState::LoadMempool(const Config &config, const ArgsManager &args) {
     if (args.GetArg("-persistmempool", DEFAULT_PERSIST_MEMPOOL)) {
         assert(std::addressof(::ChainstateActive()) == std::addressof(*this));
         ::LoadMempool(config, m_mempool, *this);
     }
     m_mempool.SetIsLoaded(!ShutdownRequested());
 }
 
 bool CChainState::LoadChainTip(const CChainParams &chainparams) {
     AssertLockHeld(cs_main);
     const CCoinsViewCache &coins_cache = CoinsTip();
     // Never called when the coins view is empty
     assert(!coins_cache.GetBestBlock().IsNull());
     const CBlockIndex *tip = m_chain.Tip();
 
     if (tip && tip->GetBlockHash() == coins_cache.GetBestBlock()) {
         return true;
     }
 
     // Load pointer to end of best chain
     CBlockIndex *pindex =
         m_blockman.LookupBlockIndex(coins_cache.GetBestBlock());
     if (!pindex) {
         return false;
     }
     m_chain.SetTip(pindex);
     PruneBlockIndexCandidates();
 
     tip = m_chain.Tip();
     LogPrintf(
         "Loaded best chain: hashBestChain=%s height=%d date=%s progress=%f\n",
         tip->GetBlockHash().ToString(), m_chain.Height(),
         FormatISO8601DateTime(tip->GetBlockTime()),
         GuessVerificationProgress(chainparams.TxData(), tip));
     return true;
 }
 
 CVerifyDB::CVerifyDB() {
     uiInterface.ShowProgress(_("Verifying blocks...").translated, 0, false);
 }
 
 CVerifyDB::~CVerifyDB() {
     uiInterface.ShowProgress("", 100, false);
 }
 
 bool CVerifyDB::VerifyDB(const Config &config, CCoinsView *coinsview,
                          int nCheckLevel, int nCheckDepth) {
     LOCK(cs_main);
 
     const CChainParams &params = config.GetChainParams();
     const Consensus::Params &consensusParams = params.GetConsensus();
 
     if (::ChainActive().Tip() == nullptr ||
         ::ChainActive().Tip()->pprev == nullptr) {
         return true;
     }
 
     // Verify blocks in the best chain
     if (nCheckDepth <= 0 || nCheckDepth > ::ChainActive().Height()) {
         nCheckDepth = ::ChainActive().Height();
     }
 
     nCheckLevel = std::max(0, std::min(4, nCheckLevel));
     LogPrintf("Verifying last %i blocks at level %i\n", nCheckDepth,
               nCheckLevel);
 
     CCoinsViewCache coins(coinsview);
     CBlockIndex *pindex;
     CBlockIndex *pindexFailure = nullptr;
     int nGoodTransactions = 0;
     BlockValidationState state;
     int reportDone = 0;
     LogPrintfToBeContinued("[0%%]...");
     for (pindex = ::ChainActive().Tip(); pindex && pindex->pprev;
          pindex = pindex->pprev) {
         const int percentageDone =
             std::max(1, std::min(99, (int)(((double)(::ChainActive().Height() -
                                                      pindex->nHeight)) /
                                            (double)nCheckDepth *
                                            (nCheckLevel >= 4 ? 50 : 100))));
         if (reportDone < percentageDone / 10) {
             // report every 10% step
             LogPrintfToBeContinued("[%d%%]...", percentageDone);
             reportDone = percentageDone / 10;
         }
 
         uiInterface.ShowProgress(_("Verifying blocks...").translated,
                                  percentageDone, false);
         if (pindex->nHeight <= ::ChainActive().Height() - nCheckDepth) {
             break;
         }
 
         if (fPruneMode && !pindex->nStatus.hasData()) {
             // If pruning, only go back as far as we have data.
             LogPrintf("VerifyDB(): block verification stopping at height %d "
                       "(pruning, no data)\n",
                       pindex->nHeight);
             break;
         }
 
         CBlock block;
 
         // check level 0: read from disk
         if (!ReadBlockFromDisk(block, pindex, consensusParams)) {
             return error(
                 "VerifyDB(): *** ReadBlockFromDisk failed at %d, hash=%s",
                 pindex->nHeight, pindex->GetBlockHash().ToString());
         }
 
         // check level 1: verify block validity
         if (nCheckLevel >= 1 && !CheckBlock(block, state, consensusParams,
                                             BlockValidationOptions(config))) {
             return error("%s: *** found bad block at %d, hash=%s (%s)\n",
                          __func__, pindex->nHeight,
                          pindex->GetBlockHash().ToString(), state.ToString());
         }
 
         // check level 2: verify undo validity
         if (nCheckLevel >= 2 && pindex) {
             CBlockUndo undo;
             if (!pindex->GetUndoPos().IsNull()) {
                 if (!UndoReadFromDisk(undo, pindex)) {
                     return error(
                         "VerifyDB(): *** found bad undo data at %d, hash=%s\n",
                         pindex->nHeight, pindex->GetBlockHash().ToString());
                 }
             }
         }
         // check level 3: check for inconsistencies during memory-only
         // disconnect of tip blocks
         if (nCheckLevel >= 3 &&
             (coins.DynamicMemoryUsage() +
              ::ChainstateActive().CoinsTip().DynamicMemoryUsage()) <=
                 ::ChainstateActive().m_coinstip_cache_size_bytes) {
             assert(coins.GetBestBlock() == pindex->GetBlockHash());
             DisconnectResult res =
                 ::ChainstateActive().DisconnectBlock(block, pindex, coins);
             if (res == DisconnectResult::FAILED) {
                 return error("VerifyDB(): *** irrecoverable inconsistency in "
                              "block data at %d, hash=%s",
                              pindex->nHeight,
                              pindex->GetBlockHash().ToString());
             }
 
             if (res == DisconnectResult::UNCLEAN) {
                 nGoodTransactions = 0;
                 pindexFailure = pindex;
             } else {
                 nGoodTransactions += block.vtx.size();
             }
         }
 
         if (ShutdownRequested()) {
             return true;
         }
     }
 
     if (pindexFailure) {
         return error("VerifyDB(): *** coin database inconsistencies found "
                      "(last %i blocks, %i good transactions before that)\n",
                      ::ChainActive().Height() - pindexFailure->nHeight + 1,
                      nGoodTransactions);
     }
 
     // store block count as we move pindex at check level >= 4
     int block_count = ::ChainActive().Height() - pindex->nHeight;
 
     // check level 4: try reconnecting blocks
     if (nCheckLevel >= 4) {
         while (pindex != ::ChainActive().Tip()) {
             const int percentageDone = std::max(
                 1, std::min(99, 100 - int(double(::ChainActive().Height() -
                                                  pindex->nHeight) /
                                           double(nCheckDepth) * 50)));
             if (reportDone < percentageDone / 10) {
                 // report every 10% step
                 LogPrintfToBeContinued("[%d%%]...", percentageDone);
                 reportDone = percentageDone / 10;
             }
             uiInterface.ShowProgress(_("Verifying blocks...").translated,
                                      percentageDone, false);
             pindex = ::ChainActive().Next(pindex);
             CBlock block;
             if (!ReadBlockFromDisk(block, pindex, consensusParams)) {
                 return error(
                     "VerifyDB(): *** ReadBlockFromDisk failed at %d, hash=%s",
                     pindex->nHeight, pindex->GetBlockHash().ToString());
             }
             if (!::ChainstateActive().ConnectBlock(
                     block, state, pindex, coins, params,
                     BlockValidationOptions(config))) {
                 return error("VerifyDB(): *** found unconnectable block at %d, "
                              "hash=%s (%s)",
                              pindex->nHeight, pindex->GetBlockHash().ToString(),
                              state.ToString());
             }
             if (ShutdownRequested()) {
                 return true;
             }
         }
     }
 
     LogPrintf("[DONE].\n");
     LogPrintf("No coin database inconsistencies in last %i blocks (%i "
               "transactions)\n",
               block_count, nGoodTransactions);
 
     return true;
 }
 
 /**
  * Apply the effects of a block on the utxo cache, ignoring that it may already
  * have been applied.
  */
 bool CChainState::RollforwardBlock(const CBlockIndex *pindex,
                                    CCoinsViewCache &view,
                                    const Consensus::Params &params) {
     // TODO: merge with ConnectBlock
     CBlock block;
     if (!ReadBlockFromDisk(block, pindex, params)) {
         return error("ReplayBlock(): ReadBlockFromDisk failed at %d, hash=%s",
                      pindex->nHeight, pindex->GetBlockHash().ToString());
     }
 
     for (const CTransactionRef &tx : block.vtx) {
         // Pass check = true as every addition may be an overwrite.
         AddCoins(view, *tx, pindex->nHeight, true);
     }
 
     for (const CTransactionRef &tx : block.vtx) {
         if (tx->IsCoinBase()) {
             continue;
         }
 
         for (const CTxIn &txin : tx->vin) {
             view.SpendCoin(txin.prevout);
         }
     }
 
     return true;
 }
 
 bool CChainState::ReplayBlocks(const Consensus::Params &params) {
     LOCK(cs_main);
 
     CCoinsView &db = this->CoinsDB();
     CCoinsViewCache cache(&db);
 
     std::vector<BlockHash> hashHeads = db.GetHeadBlocks();
     if (hashHeads.empty()) {
         // We're already in a consistent state.
         return true;
     }
     if (hashHeads.size() != 2) {
         return error("ReplayBlocks(): unknown inconsistent state");
     }
 
     uiInterface.ShowProgress(_("Replaying blocks...").translated, 0, false);
     LogPrintf("Replaying blocks\n");
 
     // Old tip during the interrupted flush.
     const CBlockIndex *pindexOld = nullptr;
     // New tip during the interrupted flush.
     const CBlockIndex *pindexNew;
     // Latest block common to both the old and the new tip.
     const CBlockIndex *pindexFork = nullptr;
 
     if (m_blockman.m_block_index.count(hashHeads[0]) == 0) {
         return error(
             "ReplayBlocks(): reorganization to unknown block requested");
     }
 
     pindexNew = m_blockman.m_block_index[hashHeads[0]];
 
     if (!hashHeads[1].IsNull()) {
         // The old tip is allowed to be 0, indicating it's the first flush.
         if (m_blockman.m_block_index.count(hashHeads[1]) == 0) {
             return error(
                 "ReplayBlocks(): reorganization from unknown block requested");
         }
 
         pindexOld = m_blockman.m_block_index[hashHeads[1]];
         pindexFork = LastCommonAncestor(pindexOld, pindexNew);
         assert(pindexFork != nullptr);
     }
 
     // Rollback along the old branch.
     while (pindexOld != pindexFork) {
         if (pindexOld->nHeight > 0) {
             // Never disconnect the genesis block.
             CBlock block;
             if (!ReadBlockFromDisk(block, pindexOld, params)) {
                 return error("RollbackBlock(): ReadBlockFromDisk() failed at "
                              "%d, hash=%s",
                              pindexOld->nHeight,
                              pindexOld->GetBlockHash().ToString());
             }
 
             LogPrintf("Rolling back %s (%i)\n",
                       pindexOld->GetBlockHash().ToString(), pindexOld->nHeight);
             DisconnectResult res = DisconnectBlock(block, pindexOld, cache);
             if (res == DisconnectResult::FAILED) {
                 return error(
                     "RollbackBlock(): DisconnectBlock failed at %d, hash=%s",
                     pindexOld->nHeight, pindexOld->GetBlockHash().ToString());
             }
 
             // If DisconnectResult::UNCLEAN is returned, it means a non-existing
             // UTXO was deleted, or an existing UTXO was overwritten. It
             // corresponds to cases where the block-to-be-disconnect never had
             // all its operations applied to the UTXO set. However, as both
             // writing a UTXO and deleting a UTXO are idempotent operations, the
             // result is still a version of the UTXO set with the effects of
             // that block undone.
         }
         pindexOld = pindexOld->pprev;
     }
 
     // Roll forward from the forking point to the new tip.
     int nForkHeight = pindexFork ? pindexFork->nHeight : 0;
     for (int nHeight = nForkHeight + 1; nHeight <= pindexNew->nHeight;
          ++nHeight) {
         const CBlockIndex *pindex = pindexNew->GetAncestor(nHeight);
         LogPrintf("Rolling forward %s (%i)\n",
                   pindex->GetBlockHash().ToString(), nHeight);
         uiInterface.ShowProgress(_("Replaying blocks...").translated,
                                  (int)((nHeight - nForkHeight) * 100.0 /
                                        (pindexNew->nHeight - nForkHeight)),
                                  false);
         if (!RollforwardBlock(pindex, cache, params)) {
             return false;
         }
     }
 
     cache.SetBestBlock(pindexNew->GetBlockHash());
     cache.Flush();
     uiInterface.ShowProgress("", 100, false);
     return true;
 }
 
 // May NOT be used after any connections are up as much of the peer-processing
 // logic assumes a consistent block index state
 void CChainState::UnloadBlockIndex() {
     nBlockSequenceId = 1;
     setBlockIndexCandidates.clear();
 
     // Do not point to CBlockIndex that will be free'd
     m_finalizedBlockIndex = nullptr;
 }
 
 // May NOT be used after any connections are up as much
 // of the peer-processing logic assumes a consistent
 // block index state
 void UnloadBlockIndex(CTxMemPool *mempool, ChainstateManager &chainman) {
     LOCK(cs_main);
     chainman.Unload();
     pindexBestInvalid = nullptr;
     pindexBestParked = nullptr;
     pindexBestHeader = nullptr;
     pindexBestForkTip = nullptr;
     pindexBestForkBase = nullptr;
     if (mempool) {
         mempool->clear();
     }
     vinfoBlockFile.clear();
     nLastBlockFile = 0;
     setDirtyBlockIndex.clear();
     setDirtyFileInfo.clear();
     fHavePruned = false;
 }
 
 bool ChainstateManager::LoadBlockIndex(const Consensus::Params &params) {
     AssertLockHeld(cs_main);
     // Load block index from databases
     bool needs_init = fReindex;
     if (!fReindex) {
         bool ret = LoadBlockIndexDB(*this, params);
         if (!ret) {
             return false;
         }
 
         needs_init = m_blockman.m_block_index.empty();
     }
 
     if (needs_init) {
         // Everything here is for *new* reindex/DBs. Thus, though
         // LoadBlockIndexDB may have set fReindex if we shut down
         // mid-reindex previously, we don't check fReindex and
         // instead only check it prior to LoadBlockIndexDB to set
         // needs_init.
 
         LogPrintf("Initializing databases...\n");
     }
     return true;
 }
 
 bool CChainState::LoadGenesisBlock(const CChainParams &chainparams) {
     LOCK(cs_main);
 
     // Check whether we're already initialized by checking for genesis in
     // m_blockman.m_block_index. Note that we can't use m_chain here, since it
     // is set based on the coins db, not the block index db, which is the only
     // thing loaded at this point.
     if (m_blockman.m_block_index.count(chainparams.GenesisBlock().GetHash())) {
         return true;
     }
 
     try {
         const CBlock &block = chainparams.GenesisBlock();
         FlatFilePos blockPos = SaveBlockToDisk(block, 0, chainparams, nullptr);
         if (blockPos.IsNull()) {
             return error("%s: writing genesis block to disk failed", __func__);
         }
         CBlockIndex *pindex = m_blockman.AddToBlockIndex(block);
         ReceivedBlockTransactions(block, pindex, blockPos);
     } catch (const std::runtime_error &e) {
         return error("%s: failed to write genesis block: %s", __func__,
                      e.what());
     }
 
     return true;
 }
 
 void CChainState::LoadExternalBlockFile(const Config &config, FILE *fileIn,
                                         FlatFilePos *dbp) {
     // Map of disk positions for blocks with unknown parent (only used for
     // reindex)
     static std::multimap<uint256, FlatFilePos> mapBlocksUnknownParent;
     int64_t nStart = GetTimeMillis();
 
     const CChainParams &chainparams = config.GetChainParams();
 
     int nLoaded = 0;
     try {
         // This takes over fileIn and calls fclose() on it in the CBufferedFile
         // destructor. Make sure we have at least 2*MAX_TX_SIZE space in there
         // so any transaction can fit in the buffer.
         CBufferedFile blkdat(fileIn, 2 * MAX_TX_SIZE, MAX_TX_SIZE + 8, SER_DISK,
                              CLIENT_VERSION);
         uint64_t nRewind = blkdat.GetPos();
         while (!blkdat.eof()) {
             if (ShutdownRequested()) {
                 return;
             }
 
             blkdat.SetPos(nRewind);
             // Start one byte further next time, in case of failure.
             nRewind++;
             // Remove former limit.
             blkdat.SetLimit();
             unsigned int nSize = 0;
             try {
                 // Locate a header.
                 uint8_t buf[CMessageHeader::MESSAGE_START_SIZE];
                 blkdat.FindByte(chainparams.DiskMagic()[0]);
                 nRewind = blkdat.GetPos() + 1;
                 blkdat >> buf;
                 if (memcmp(buf, chainparams.DiskMagic().data(),
                            CMessageHeader::MESSAGE_START_SIZE)) {
                     continue;
                 }
 
                 // Read size.
                 blkdat >> nSize;
                 if (nSize < 80) {
                     continue;
                 }
             } catch (const std::exception &) {
                 // No valid block header found; don't complain.
                 break;
             }
 
             try {
                 // read block
                 uint64_t nBlockPos = blkdat.GetPos();
                 if (dbp) {
                     dbp->nPos = nBlockPos;
                 }
                 blkdat.SetLimit(nBlockPos + nSize);
                 std::shared_ptr<CBlock> pblock = std::make_shared<CBlock>();
                 CBlock &block = *pblock;
                 blkdat >> block;
                 nRewind = blkdat.GetPos();
 
                 const BlockHash hash = block.GetHash();
                 {
                     LOCK(cs_main);
                     // detect out of order blocks, and store them for later
                     assert(std::addressof(g_chainman.m_blockman) ==
                            std::addressof(m_blockman));
                     if (hash != chainparams.GetConsensus().hashGenesisBlock &&
                         !m_blockman.LookupBlockIndex(block.hashPrevBlock)) {
                         LogPrint(
                             BCLog::REINDEX,
                             "%s: Out of order block %s, parent %s not known\n",
                             __func__, hash.ToString(),
                             block.hashPrevBlock.ToString());
                         if (dbp) {
                             mapBlocksUnknownParent.insert(
                                 std::make_pair(block.hashPrevBlock, *dbp));
                         }
                         continue;
                     }
 
                     // process in case the block isn't known yet
                     assert(std::addressof(g_chainman.m_blockman) ==
                            std::addressof(m_blockman));
                     CBlockIndex *pindex = m_blockman.LookupBlockIndex(hash);
                     if (!pindex || !pindex->nStatus.hasData()) {
                         BlockValidationState state;
                         assert(std::addressof(::ChainstateActive()) ==
                                std::addressof(*this));
                         if (AcceptBlock(config, pblock, state, true, dbp,
                                         nullptr)) {
                             nLoaded++;
                         }
                         if (state.IsError()) {
                             break;
                         }
                     } else if (hash != chainparams.GetConsensus()
                                            .hashGenesisBlock &&
                                pindex->nHeight % 1000 == 0) {
                         LogPrint(
                             BCLog::REINDEX,
                             "Block Import: already had block %s at height %d\n",
                             hash.ToString(), pindex->nHeight);
                     }
                 }
 
                 // Activate the genesis block so normal node progress can
                 // continue
                 if (hash == chainparams.GetConsensus().hashGenesisBlock) {
                     BlockValidationState state;
                     assert(std::addressof(::ChainstateActive()) ==
                            std::addressof(*this));
                     if (!ActivateBestChain(config, state, nullptr)) {
                         break;
                     }
                 }
 
                 assert(std::addressof(::ChainstateActive()) ==
                        std::addressof(*this));
                 NotifyHeaderTip(*this);
 
                 // Recursively process earlier encountered successors of this
                 // block
                 std::deque<uint256> queue;
                 queue.push_back(hash);
                 while (!queue.empty()) {
                     uint256 head = queue.front();
                     queue.pop_front();
                     std::pair<std::multimap<uint256, FlatFilePos>::iterator,
                               std::multimap<uint256, FlatFilePos>::iterator>
                         range = mapBlocksUnknownParent.equal_range(head);
                     while (range.first != range.second) {
                         std::multimap<uint256, FlatFilePos>::iterator it =
                             range.first;
                         std::shared_ptr<CBlock> pblockrecursive =
                             std::make_shared<CBlock>();
                         if (ReadBlockFromDisk(*pblockrecursive, it->second,
                                               chainparams.GetConsensus())) {
                             LogPrint(
                                 BCLog::REINDEX,
                                 "%s: Processing out of order child %s of %s\n",
                                 __func__, pblockrecursive->GetHash().ToString(),
                                 head.ToString());
                             LOCK(cs_main);
                             BlockValidationState dummy;
                             assert(std::addressof(::ChainstateActive()) ==
                                    std::addressof(*this));
                             if (AcceptBlock(config, pblockrecursive, dummy,
                                             true, &it->second, nullptr)) {
                                 nLoaded++;
                                 queue.push_back(pblockrecursive->GetHash());
                             }
                         }
                         range.first++;
                         mapBlocksUnknownParent.erase(it);
                         assert(std::addressof(::ChainstateActive()) ==
                                std::addressof(*this));
                         NotifyHeaderTip(*this);
                     }
                 }
             } catch (const std::exception &e) {
                 LogPrintf("%s: Deserialize or I/O error - %s\n", __func__,
                           e.what());
             }
         }
     } catch (const std::runtime_error &e) {
         AbortNode(std::string("System error: ") + e.what());
     }
 
     LogPrintf("Loaded %i blocks from external file in %dms\n", nLoaded,
               GetTimeMillis() - nStart);
 }
 
 void CChainState::CheckBlockIndex(const Consensus::Params &consensusParams) {
     if (!fCheckBlockIndex) {
         return;
     }
 
     LOCK(cs_main);
 
     // During a reindex, we read the genesis block and call CheckBlockIndex
     // before ActivateBestChain, so we have the genesis block in
     // m_blockman.m_block_index but no active chain. (A few of the tests when
     // iterating the block tree require that m_chain has been initialized.)
     if (m_chain.Height() < 0) {
         assert(m_blockman.m_block_index.size() <= 1);
         return;
     }
 
     // Build forward-pointing map of the entire block tree.
     std::multimap<CBlockIndex *, CBlockIndex *> forward;
     for (const auto &entry : m_blockman.m_block_index) {
         forward.emplace(entry.second->pprev, entry.second);
     }
 
     assert(forward.size() == m_blockman.m_block_index.size());
 
     std::pair<std::multimap<CBlockIndex *, CBlockIndex *>::iterator,
               std::multimap<CBlockIndex *, CBlockIndex *>::iterator>
         rangeGenesis = forward.equal_range(nullptr);
     CBlockIndex *pindex = rangeGenesis.first->second;
     rangeGenesis.first++;
     // There is only one index entry with parent nullptr.
     assert(rangeGenesis.first == rangeGenesis.second);
 
     // Iterate over the entire block tree, using depth-first search.
     // Along the way, remember whether there are blocks on the path from genesis
     // block being explored which are the first to have certain properties.
     size_t nNodes = 0;
     int nHeight = 0;
     // Oldest ancestor of pindex which is invalid.
     CBlockIndex *pindexFirstInvalid = nullptr;
     // Oldest ancestor of pindex which is parked.
     CBlockIndex *pindexFirstParked = nullptr;
     // Oldest ancestor of pindex which does not have data available.
     CBlockIndex *pindexFirstMissing = nullptr;
     // Oldest ancestor of pindex for which nTx == 0.
     CBlockIndex *pindexFirstNeverProcessed = nullptr;
     // Oldest ancestor of pindex which does not have BLOCK_VALID_TREE
     // (regardless of being valid or not).
     CBlockIndex *pindexFirstNotTreeValid = nullptr;
     // Oldest ancestor of pindex which does not have BLOCK_VALID_TRANSACTIONS
     // (regardless of being valid or not).
     CBlockIndex *pindexFirstNotTransactionsValid = nullptr;
     // Oldest ancestor of pindex which does not have BLOCK_VALID_CHAIN
     // (regardless of being valid or not).
     CBlockIndex *pindexFirstNotChainValid = nullptr;
     // Oldest ancestor of pindex which does not have BLOCK_VALID_SCRIPTS
     // (regardless of being valid or not).
     CBlockIndex *pindexFirstNotScriptsValid = nullptr;
     while (pindex != nullptr) {
         nNodes++;
         if (pindexFirstInvalid == nullptr && pindex->nStatus.hasFailed()) {
             pindexFirstInvalid = pindex;
         }
         if (pindexFirstParked == nullptr && pindex->nStatus.isParked()) {
             pindexFirstParked = pindex;
         }
         if (pindexFirstMissing == nullptr && !pindex->nStatus.hasData()) {
             pindexFirstMissing = pindex;
         }
         if (pindexFirstNeverProcessed == nullptr && pindex->nTx == 0) {
             pindexFirstNeverProcessed = pindex;
         }
         if (pindex->pprev != nullptr && pindexFirstNotTreeValid == nullptr &&
             pindex->nStatus.getValidity() < BlockValidity::TREE) {
             pindexFirstNotTreeValid = pindex;
         }
         if (pindex->pprev != nullptr &&
             pindexFirstNotTransactionsValid == nullptr &&
             pindex->nStatus.getValidity() < BlockValidity::TRANSACTIONS) {
             pindexFirstNotTransactionsValid = pindex;
         }
         if (pindex->pprev != nullptr && pindexFirstNotChainValid == nullptr &&
             pindex->nStatus.getValidity() < BlockValidity::CHAIN) {
             pindexFirstNotChainValid = pindex;
         }
         if (pindex->pprev != nullptr && pindexFirstNotScriptsValid == nullptr &&
             pindex->nStatus.getValidity() < BlockValidity::SCRIPTS) {
             pindexFirstNotScriptsValid = pindex;
         }
 
         // Begin: actual consistency checks.
         if (pindex->pprev == nullptr) {
             // Genesis block checks.
             // Genesis block's hash must match.
             assert(pindex->GetBlockHash() == consensusParams.hashGenesisBlock);
             // The current active chain's genesis block must be this block.
             assert(pindex == m_chain.Genesis());
         }
         if (!pindex->HaveTxsDownloaded()) {
             // nSequenceId can't be set positive for blocks that aren't linked
             // (negative is used for preciousblock)
             assert(pindex->nSequenceId <= 0);
         }
         // VALID_TRANSACTIONS is equivalent to nTx > 0 for all nodes (whether or
         // not pruning has occurred). HAVE_DATA is only equivalent to nTx > 0
         // (or VALID_TRANSACTIONS) if no pruning has occurred.
         if (!fHavePruned) {
             // If we've never pruned, then HAVE_DATA should be equivalent to nTx
             // > 0
             assert(pindex->nStatus.hasData() == (pindex->nTx > 0));
             assert(pindexFirstMissing == pindexFirstNeverProcessed);
         } else if (pindex->nStatus.hasData()) {
             // If we have pruned, then we can only say that HAVE_DATA implies
             // nTx > 0
             assert(pindex->nTx > 0);
         }
         if (pindex->nStatus.hasUndo()) {
             assert(pindex->nStatus.hasData());
         }
         // This is pruning-independent.
         assert((pindex->nStatus.getValidity() >= BlockValidity::TRANSACTIONS) ==
                (pindex->nTx > 0));
         // All parents having had data (at some point) is equivalent to all
         // parents being VALID_TRANSACTIONS, which is equivalent to
         // HaveTxsDownloaded(). All parents having had data (at some point) is
         // equivalent to all parents being VALID_TRANSACTIONS, which is
         // equivalent to HaveTxsDownloaded().
         assert((pindexFirstNeverProcessed == nullptr) ==
                (pindex->HaveTxsDownloaded()));
         assert((pindexFirstNotTransactionsValid == nullptr) ==
                (pindex->HaveTxsDownloaded()));
         // nHeight must be consistent.
         assert(pindex->nHeight == nHeight);
         // For every block except the genesis block, the chainwork must be
         // larger than the parent's.
         assert(pindex->pprev == nullptr ||
                pindex->nChainWork >= pindex->pprev->nChainWork);
         // The pskip pointer must point back for all but the first 2 blocks.
         assert(nHeight < 2 ||
                (pindex->pskip && (pindex->pskip->nHeight < nHeight)));
         // All m_blockman.m_block_index entries must at least be TREE valid
         assert(pindexFirstNotTreeValid == nullptr);
         if (pindex->nStatus.getValidity() >= BlockValidity::TREE) {
             // TREE valid implies all parents are TREE valid
             assert(pindexFirstNotTreeValid == nullptr);
         }
         if (pindex->nStatus.getValidity() >= BlockValidity::CHAIN) {
             // CHAIN valid implies all parents are CHAIN valid
             assert(pindexFirstNotChainValid == nullptr);
         }
         if (pindex->nStatus.getValidity() >= BlockValidity::SCRIPTS) {
             // SCRIPTS valid implies all parents are SCRIPTS valid
             assert(pindexFirstNotScriptsValid == nullptr);
         }
         if (pindexFirstInvalid == nullptr) {
             // Checks for not-invalid blocks.
             // The failed mask cannot be set for blocks without invalid parents.
             assert(!pindex->nStatus.isInvalid());
         }
         if (pindexFirstParked == nullptr) {
             // Checks for not-parked blocks.
             // The parked mask cannot be set for blocks without parked parents.
             // (i.e., hasParkedParent only if an ancestor is properly parked).
             assert(!pindex->nStatus.isOnParkedChain());
         }
         if (!CBlockIndexWorkComparator()(pindex, m_chain.Tip()) &&
             pindexFirstNeverProcessed == nullptr) {
             if (pindexFirstInvalid == nullptr) {
                 // If this block sorts at least as good as the current tip and
                 // is valid and we have all data for its parents, it must be in
                 // setBlockIndexCandidates or be parked.
                 if (pindexFirstMissing == nullptr) {
                     assert(pindex->nStatus.isOnParkedChain() ||
                            setBlockIndexCandidates.count(pindex));
                 }
                 // m_chain.Tip() must also be there even if some data has
                 // been pruned.
                 if (pindex == m_chain.Tip()) {
                     assert(setBlockIndexCandidates.count(pindex));
                 }
                 // If some parent is missing, then it could be that this block
                 // was in setBlockIndexCandidates but had to be removed because
                 // of the missing data. In this case it must be in
                 // m_blocks_unlinked -- see test below.
             }
         } else {
             // If this block sorts worse than the current tip or some ancestor's
             // block has never been seen, it cannot be in
             // setBlockIndexCandidates.
             assert(setBlockIndexCandidates.count(pindex) == 0);
         }
         // Check whether this block is in m_blocks_unlinked.
         std::pair<std::multimap<CBlockIndex *, CBlockIndex *>::iterator,
                   std::multimap<CBlockIndex *, CBlockIndex *>::iterator>
             rangeUnlinked =
                 m_blockman.m_blocks_unlinked.equal_range(pindex->pprev);
         bool foundInUnlinked = false;
         while (rangeUnlinked.first != rangeUnlinked.second) {
             assert(rangeUnlinked.first->first == pindex->pprev);
             if (rangeUnlinked.first->second == pindex) {
                 foundInUnlinked = true;
                 break;
             }
             rangeUnlinked.first++;
         }
         if (pindex->pprev && pindex->nStatus.hasData() &&
             pindexFirstNeverProcessed != nullptr &&
             pindexFirstInvalid == nullptr) {
             // If this block has block data available, some parent was never
             // received, and has no invalid parents, it must be in
             // m_blocks_unlinked.
             assert(foundInUnlinked);
         }
         if (!pindex->nStatus.hasData()) {
             // Can't be in m_blocks_unlinked if we don't HAVE_DATA
             assert(!foundInUnlinked);
         }
         if (pindexFirstMissing == nullptr) {
             // We aren't missing data for any parent -- cannot be in
             // m_blocks_unlinked.
             assert(!foundInUnlinked);
         }
         if (pindex->pprev && pindex->nStatus.hasData() &&
             pindexFirstNeverProcessed == nullptr &&
             pindexFirstMissing != nullptr) {
             // We HAVE_DATA for this block, have received data for all parents
             // at some point, but we're currently missing data for some parent.
             // We must have pruned.
             assert(fHavePruned);
             // This block may have entered m_blocks_unlinked if:
             //  - it has a descendant that at some point had more work than the
             //    tip, and
             //  - we tried switching to that descendant but were missing
             //    data for some intermediate block between m_chain and the
             //    tip.
             // So if this block is itself better than m_chain.Tip() and it
             // wasn't in
             // setBlockIndexCandidates, then it must be in m_blocks_unlinked.
             if (!CBlockIndexWorkComparator()(pindex, m_chain.Tip()) &&
                 setBlockIndexCandidates.count(pindex) == 0) {
                 if (pindexFirstInvalid == nullptr) {
                     assert(foundInUnlinked);
                 }
             }
         }
         // Perhaps too slow
         // assert(pindex->GetBlockHash() == pindex->GetBlockHeader().GetHash());
         // End: actual consistency checks.
 
         // Try descending into the first subnode.
         std::pair<std::multimap<CBlockIndex *, CBlockIndex *>::iterator,
                   std::multimap<CBlockIndex *, CBlockIndex *>::iterator>
             range = forward.equal_range(pindex);
         if (range.first != range.second) {
             // A subnode was found.
             pindex = range.first->second;
             nHeight++;
             continue;
         }
         // This is a leaf node. Move upwards until we reach a node of which we
         // have not yet visited the last child.
         while (pindex) {
             // We are going to either move to a parent or a sibling of pindex.
             // If pindex was the first with a certain property, unset the
             // corresponding variable.
             if (pindex == pindexFirstInvalid) {
                 pindexFirstInvalid = nullptr;
             }
             if (pindex == pindexFirstParked) {
                 pindexFirstParked = nullptr;
             }
             if (pindex == pindexFirstMissing) {
                 pindexFirstMissing = nullptr;
             }
             if (pindex == pindexFirstNeverProcessed) {
                 pindexFirstNeverProcessed = nullptr;
             }
             if (pindex == pindexFirstNotTreeValid) {
                 pindexFirstNotTreeValid = nullptr;
             }
             if (pindex == pindexFirstNotTransactionsValid) {
                 pindexFirstNotTransactionsValid = nullptr;
             }
             if (pindex == pindexFirstNotChainValid) {
                 pindexFirstNotChainValid = nullptr;
             }
             if (pindex == pindexFirstNotScriptsValid) {
                 pindexFirstNotScriptsValid = nullptr;
             }
             // Find our parent.
             CBlockIndex *pindexPar = pindex->pprev;
             // Find which child we just visited.
             std::pair<std::multimap<CBlockIndex *, CBlockIndex *>::iterator,
                       std::multimap<CBlockIndex *, CBlockIndex *>::iterator>
                 rangePar = forward.equal_range(pindexPar);
             while (rangePar.first->second != pindex) {
                 // Our parent must have at least the node we're coming from as
                 // child.
                 assert(rangePar.first != rangePar.second);
                 rangePar.first++;
             }
             // Proceed to the next one.
             rangePar.first++;
             if (rangePar.first != rangePar.second) {
                 // Move to the sibling.
                 pindex = rangePar.first->second;
                 break;
             } else {
                 // Move up further.
                 pindex = pindexPar;
                 nHeight--;
                 continue;
             }
         }
     }
 
     // Check that we actually traversed the entire map.
     assert(nNodes == forward.size());
 }
 
 std::string CChainState::ToString() {
     CBlockIndex *tip = m_chain.Tip();
     return strprintf("Chainstate [%s] @ height %d (%s)",
                      m_from_snapshot_blockhash.IsNull() ? "ibd" : "snapshot",
                      tip ? tip->nHeight : -1,
                      tip ? tip->GetBlockHash().ToString() : "null");
 }
 
 bool CChainState::ResizeCoinsCaches(size_t coinstip_size, size_t coinsdb_size) {
     if (coinstip_size == m_coinstip_cache_size_bytes &&
         coinsdb_size == m_coinsdb_cache_size_bytes) {
         // Cache sizes are unchanged, no need to continue.
         return true;
     }
     size_t old_coinstip_size = m_coinstip_cache_size_bytes;
     m_coinstip_cache_size_bytes = coinstip_size;
     m_coinsdb_cache_size_bytes = coinsdb_size;
     CoinsDB().ResizeCache(coinsdb_size);
 
     LogPrintf("[%s] resized coinsdb cache to %.1f MiB\n", this->ToString(),
               coinsdb_size * (1.0 / 1024 / 1024));
     LogPrintf("[%s] resized coinstip cache to %.1f MiB\n", this->ToString(),
               coinstip_size * (1.0 / 1024 / 1024));
 
     BlockValidationState state;
     const CChainParams &chainparams = Params();
 
     bool ret;
 
     if (coinstip_size > old_coinstip_size) {
         // Likely no need to flush if cache sizes have grown.
         ret = FlushStateToDisk(chainparams, state, FlushStateMode::IF_NEEDED);
     } else {
         // Otherwise, flush state to disk and deallocate the in-memory coins
         // map.
         ret = FlushStateToDisk(chainparams, state, FlushStateMode::ALWAYS);
         CoinsTip().ReallocateCache();
     }
     return ret;
 }
 
 std::string CBlockFileInfo::ToString() const {
     return strprintf(
         "CBlockFileInfo(blocks=%u, size=%u, heights=%u...%u, time=%s...%s)",
         nBlocks, nSize, nHeightFirst, nHeightLast,
         FormatISO8601DateTime(nTimeFirst), FormatISO8601DateTime(nTimeLast));
 }
 
 CBlockFileInfo *GetBlockFileInfo(size_t n) {
     LOCK(cs_LastBlockFile);
 
     return &vinfoBlockFile.at(n);
 }
 
 static ThresholdState VersionBitsStateImpl(const Consensus::Params &params,
                                            Consensus::DeploymentPos pos,
                                            const CBlockIndex *pindex)
     EXCLUSIVE_LOCKS_REQUIRED(cs_main) {
     return VersionBitsState(pindex, params, pos, versionbitscache);
 }
 
 ThresholdState VersionBitsTipState(const Consensus::Params &params,
                                    Consensus::DeploymentPos pos) {
     LOCK(cs_main);
     return VersionBitsStateImpl(params, pos, ::ChainActive().Tip());
 }
 
 ThresholdState VersionBitsBlockState(const Consensus::Params &params,
                                      Consensus::DeploymentPos pos,
                                      const CBlockIndex *pindex) {
     LOCK(cs_main);
     return VersionBitsStateImpl(params, pos, pindex);
 }
 
 BIP9Stats VersionBitsTipStatistics(const Consensus::Params &params,
                                    Consensus::DeploymentPos pos) {
     LOCK(cs_main);
     return VersionBitsStatistics(::ChainActive().Tip(), params, pos);
 }
 
 int VersionBitsTipStateSinceHeight(const Consensus::Params &params,
                                    Consensus::DeploymentPos pos) {
     LOCK(cs_main);
     return VersionBitsStateSinceHeight(::ChainActive().Tip(), params, pos,
                                        versionbitscache);
 }
 
 static const uint64_t MEMPOOL_DUMP_VERSION = 1;
 
 bool LoadMempool(const Config &config, CTxMemPool &pool,
                  CChainState &active_chainstate) {
     int64_t nExpiryTimeout =
         gArgs.GetArg("-mempoolexpiry", DEFAULT_MEMPOOL_EXPIRY) * 60 * 60;
     FILE *filestr = fsbridge::fopen(GetDataDir() / "mempool.dat", "rb");
     CAutoFile file(filestr, SER_DISK, CLIENT_VERSION);
     if (file.IsNull()) {
         LogPrintf(
             "Failed to open mempool file from disk. Continuing anyway.\n");
         return false;
     }
 
     int64_t count = 0;
     int64_t expired = 0;
     int64_t failed = 0;
     int64_t already_there = 0;
     int64_t unbroadcast = 0;
     int64_t nNow = GetTime();
 
     try {
         uint64_t version;
         file >> version;
         if (version != MEMPOOL_DUMP_VERSION) {
             return false;
         }
 
         uint64_t num;
         file >> num;
         while (num--) {
             CTransactionRef tx;
             int64_t nTime;
             int64_t nFeeDelta;
             file >> tx;
             file >> nTime;
             file >> nFeeDelta;
 
             Amount amountdelta = nFeeDelta * SATOSHI;
             if (amountdelta != Amount::zero()) {
                 pool.PrioritiseTransaction(tx->GetId(), amountdelta);
             }
             TxValidationState state;
             if (nTime > nNow - nExpiryTimeout) {
                 LOCK(cs_main);
                 assert(std::addressof(::ChainstateActive()) ==
                        std::addressof(active_chainstate));
                 AcceptToMemoryPoolWithTime(
                     config, pool, active_chainstate, state, tx, nTime,
                     false /* bypass_limits */, false /* test_accept */);
                 if (state.IsValid()) {
                     ++count;
                 } else {
                     // mempool may contain the transaction already, e.g. from
                     // wallet(s) having loaded it while we were processing
                     // mempool transactions; consider these as valid, instead of
                     // failed, but mark them as 'already there'
                     if (pool.exists(tx->GetId())) {
                         ++already_there;
                     } else {
                         ++failed;
                     }
                 }
             } else {
                 ++expired;
             }
 
             if (ShutdownRequested()) {
                 return false;
             }
         }
         std::map<TxId, Amount> mapDeltas;
         file >> mapDeltas;
 
         for (const auto &i : mapDeltas) {
             pool.PrioritiseTransaction(i.first, i.second);
         }
 
         // TODO: remove this try...catch after May 15th 2021,
         // when no one is running v0.22.11 or lower anymore.
         // This will be done by backporting PR20854.
         try {
             std::set<TxId> unbroadcast_txids;
             file >> unbroadcast_txids;
             unbroadcast = unbroadcast_txids.size();
             for (const auto &txid : unbroadcast_txids) {
                 pool.AddUnbroadcastTx(txid);
             }
         } catch (const std::exception &) {
             // mempool.dat files created prior to v0.22.12 will not have an
             // unbroadcast set. No need to log a failure if parsing fails here.
         }
 
     } catch (const std::exception &e) {
         LogPrintf("Failed to deserialize mempool data on disk: %s. Continuing "
                   "anyway.\n",
                   e.what());
         return false;
     }
 
     LogPrintf("Imported mempool transactions from disk: %i succeeded, %i "
               "failed, %i expired, %i already there, %i waiting for initial "
               "broadcast\n",
               count, failed, expired, already_there, unbroadcast);
     return true;
 }
 
 bool DumpMempool(const CTxMemPool &pool) {
     int64_t start = GetTimeMicros();
 
     std::map<uint256, Amount> mapDeltas;
     std::vector<TxMempoolInfo> vinfo;
     std::set<TxId> unbroadcast_txids;
 
     static Mutex dump_mutex;
     LOCK(dump_mutex);
 
     {
         LOCK(pool.cs);
         for (const auto &i : pool.mapDeltas) {
             mapDeltas[i.first] = i.second;
         }
 
         vinfo = pool.infoAll();
         unbroadcast_txids = pool.GetUnbroadcastTxs();
     }
 
     int64_t mid = GetTimeMicros();
 
     try {
         FILE *filestr = fsbridge::fopen(GetDataDir() / "mempool.dat.new", "wb");
         if (!filestr) {
             return false;
         }
 
         CAutoFile file(filestr, SER_DISK, CLIENT_VERSION);
 
         uint64_t version = MEMPOOL_DUMP_VERSION;
         file << version;
 
         file << uint64_t(vinfo.size());
         for (const auto &i : vinfo) {
             file << *(i.tx);
             file << int64_t(count_seconds(i.m_time));
             file << i.nFeeDelta;
             mapDeltas.erase(i.tx->GetId());
         }
 
         file << mapDeltas;
 
         LogPrintf("Writing %d unbroadcast transactions to disk.\n",
                   unbroadcast_txids.size());
         file << unbroadcast_txids;
 
         if (!FileCommit(file.Get())) {
             throw std::runtime_error("FileCommit failed");
         }
         file.fclose();
         if (!RenameOver(GetDataDir() / "mempool.dat.new",
                         GetDataDir() / "mempool.dat")) {
             throw std::runtime_error("Rename failed");
         }
         int64_t last = GetTimeMicros();
         LogPrintf("Dumped mempool: %gs to copy, %gs to dump\n",
                   (mid - start) * MICRO, (last - mid) * MICRO);
     } catch (const std::exception &e) {
         LogPrintf("Failed to dump mempool: %s. Continuing anyway.\n", e.what());
         return false;
     }
     return true;
 }
 
 bool IsBlockPruned(const CBlockIndex *pblockindex) {
     return (fHavePruned && !pblockindex->nStatus.hasData() &&
             pblockindex->nTx > 0);
 }
 
 //! Guess how far we are in the verification process at the given block index
 //! require cs_main if pindex has not been validated yet (because the chain's
 //! transaction count might be unset) This conditional lock requirement might be
 //! confusing, see: https://github.com/bitcoin/bitcoin/issues/15994
 double GuessVerificationProgress(const ChainTxData &data,
                                  const CBlockIndex *pindex) {
     if (pindex == nullptr) {
         return 0.0;
     }
 
     int64_t nNow = time(nullptr);
 
     double fTxTotal;
     if (pindex->GetChainTxCount() <= data.nTxCount) {
         fTxTotal = data.nTxCount + (nNow - data.nTime) * data.dTxRate;
     } else {
         fTxTotal = pindex->GetChainTxCount() +
                    (nNow - pindex->GetBlockTime()) * data.dTxRate;
     }
 
     return std::min<double>(pindex->GetChainTxCount() / fTxTotal, 1.0);
 }
 
 std::optional<BlockHash> ChainstateManager::SnapshotBlockhash() const {
     // for m_active_chainstate access
     LOCK(::cs_main);
     if (m_active_chainstate != nullptr) {
         // If a snapshot chainstate exists, it will always be our active.
         return m_active_chainstate->m_from_snapshot_blockhash;
     }
     return {};
 }
 
 std::vector<CChainState *> ChainstateManager::GetAll() {
     std::vector<CChainState *> out;
 
     if (!IsSnapshotValidated() && m_ibd_chainstate) {
         out.push_back(m_ibd_chainstate.get());
     }
 
     if (m_snapshot_chainstate) {
         out.push_back(m_snapshot_chainstate.get());
     }
 
     return out;
 }
 
 CChainState &
 ChainstateManager::InitializeChainstate(CTxMemPool &mempool,
                                         const BlockHash &snapshot_blockhash) {
     bool is_snapshot = !snapshot_blockhash.IsNull();
     std::unique_ptr<CChainState> &to_modify =
         is_snapshot ? m_snapshot_chainstate : m_ibd_chainstate;
 
     if (to_modify) {
         throw std::logic_error("should not be overwriting a chainstate");
     }
     to_modify.reset(new CChainState(mempool, m_blockman, snapshot_blockhash));
 
     // Snapshot chainstates and initial IBD chaintates always become active.
     if (is_snapshot || (!is_snapshot && !m_active_chainstate)) {
         LogPrintf("Switching active chainstate to %s\n", to_modify->ToString());
         m_active_chainstate = to_modify.get();
     } else {
         throw std::logic_error("unexpected chainstate activation");
     }
 
     return *to_modify;
 }
 
 CChainState &ChainstateManager::ActiveChainstate() const {
     LOCK(::cs_main);
     assert(m_active_chainstate);
     return *m_active_chainstate;
 }
 
 bool ChainstateManager::IsSnapshotActive() const {
     return m_snapshot_chainstate &&
            WITH_LOCK(::cs_main, return m_active_chainstate) ==
                m_snapshot_chainstate.get();
 }
 
 CChainState &ChainstateManager::ValidatedChainstate() const {
     if (m_snapshot_chainstate && IsSnapshotValidated()) {
         return *m_snapshot_chainstate.get();
     }
     assert(m_ibd_chainstate);
     return *m_ibd_chainstate.get();
 }
 
 bool ChainstateManager::IsBackgroundIBD(CChainState *chainstate) const {
     return (m_snapshot_chainstate && chainstate == m_ibd_chainstate.get());
 }
 
 void ChainstateManager::Unload() {
     for (CChainState *chainstate : this->GetAll()) {
         chainstate->m_chain.SetTip(nullptr);
         chainstate->UnloadBlockIndex();
     }
 
     m_blockman.Unload();
 }
 
 void ChainstateManager::Reset() {
     m_ibd_chainstate.reset();
     m_snapshot_chainstate.reset();
     {
         LOCK(::cs_main);
         m_active_chainstate = nullptr;
     }
     m_snapshot_validated = false;
 }
 
 void ChainstateManager::MaybeRebalanceCaches() {
     if (m_ibd_chainstate && !m_snapshot_chainstate) {
         LogPrintf("[snapshot] allocating all cache to the IBD chainstate\n");
         // Allocate everything to the IBD chainstate.
         m_ibd_chainstate->ResizeCoinsCaches(m_total_coinstip_cache,
                                             m_total_coinsdb_cache);
     } else if (m_snapshot_chainstate && !m_ibd_chainstate) {
         LogPrintf(
             "[snapshot] allocating all cache to the snapshot chainstate\n");
         // Allocate everything to the snapshot chainstate.
         m_snapshot_chainstate->ResizeCoinsCaches(m_total_coinstip_cache,
                                                  m_total_coinsdb_cache);
     } else if (m_ibd_chainstate && m_snapshot_chainstate) {
         // If both chainstates exist, determine who needs more cache based on
         // IBD status.
         //
         // Note: shrink caches first so that we don't inadvertently overwhelm
         // available memory.
         if (m_snapshot_chainstate->IsInitialBlockDownload()) {
             m_ibd_chainstate->ResizeCoinsCaches(m_total_coinstip_cache * 0.05,
                                                 m_total_coinsdb_cache * 0.05);
             m_snapshot_chainstate->ResizeCoinsCaches(
                 m_total_coinstip_cache * 0.95, m_total_coinsdb_cache * 0.95);
         } else {
             m_snapshot_chainstate->ResizeCoinsCaches(
                 m_total_coinstip_cache * 0.05, m_total_coinsdb_cache * 0.05);
             m_ibd_chainstate->ResizeCoinsCaches(m_total_coinstip_cache * 0.95,
                                                 m_total_coinsdb_cache * 0.95);
         }
     }
 }
diff --git a/src/validation.h b/src/validation.h
index 9accdd2fc..0a3d04911 100644
--- a/src/validation.h
+++ b/src/validation.h
@@ -1,1311 +1,1312 @@
 // Copyright (c) 2009-2010 Satoshi Nakamoto
 // Copyright (c) 2009-2019 The Bitcoin Core developers
 // Copyright (c) 2017-2020 The Bitcoin developers
 // Distributed under the MIT software license, see the accompanying
 // file COPYING or http://www.opensource.org/licenses/mit-license.php.
 
 #ifndef BITCOIN_VALIDATION_H
 #define BITCOIN_VALIDATION_H
 
 #if defined(HAVE_CONFIG_H)
 #include <config/bitcoin-config.h>
 #endif
 
 #include <amount.h>
 #include <blockfileinfo.h>
 #include <blockindexworkcomparator.h>
 #include <coins.h>
 #include <consensus/consensus.h>
 #include <disconnectresult.h>
 #include <flatfile.h>
 #include <fs.h>
 #include <protocol.h> // For CMessageHeader::MessageMagic
 #include <script/script_error.h>
 #include <script/script_metrics.h>
 #include <sync.h>
 #include <txdb.h>
 #include <txmempool.h> // For CTxMemPool::cs
 #include <versionbits.h>
 
 #include <atomic>
 #include <cstdint>
 #include <map>
 #include <memory>
 #include <optional>
 #include <set>
 #include <string>
 #include <utility>
 #include <vector>
 
 class BlockValidationState;
 class CBlockIndex;
 class CBlockTreeDB;
 class CBlockUndo;
 class CChainParams;
 class CChain;
 class CChainState;
 class CConnman;
 class CInv;
 class ChainstateManager;
 class Config;
 class CScriptCheck;
 class CTxMemPool;
 class CTxUndo;
 class DisconnectedBlockTransactions;
 class TxValidationState;
 
 struct CCheckpointData;
 struct ChainTxData;
 struct FlatFilePos;
 struct PrecomputedTransactionData;
 struct LockPoints;
 
 namespace Consensus {
 struct Params;
 }
 
 #define MIN_TRANSACTION_SIZE                                                   \
     (::GetSerializeSize(CTransaction(), PROTOCOL_VERSION))
 
 /** Default for -minrelaytxfee, minimum relay fee for transactions */
 static const Amount DEFAULT_MIN_RELAY_TX_FEE_PER_KB(1000 * SATOSHI);
 /** Default for -excessutxocharge for transactions transactions */
 static const Amount DEFAULT_UTXO_FEE = Amount::zero();
 /**
  * Default for -mempoolexpiry, expiration time for mempool transactions in
  * hours.
  */
 static const unsigned int DEFAULT_MEMPOOL_EXPIRY = 336;
 /** The maximum size of a blk?????.dat file (since 0.8) */
 static const unsigned int MAX_BLOCKFILE_SIZE = 0x8000000; // 128 MiB
 /** Maximum number of dedicated script-checking threads allowed */
 static const int MAX_SCRIPTCHECK_THREADS = 15;
 /** -par default (number of script-checking threads, 0 = auto) */
 static const int DEFAULT_SCRIPTCHECK_THREADS = 0;
 static const int64_t DEFAULT_MAX_TIP_AGE = 24 * 60 * 60;
 static const bool DEFAULT_CHECKPOINTS_ENABLED = true;
 static const bool DEFAULT_TXINDEX = false;
 static const char *const DEFAULT_BLOCKFILTERINDEX = "0";
 
 /** Default for -persistmempool */
 static const bool DEFAULT_PERSIST_MEMPOOL = true;
 /** Default for using fee filter */
 static const bool DEFAULT_FEEFILTER = true;
 
 static const bool DEFAULT_PEERBLOOMFILTERS = true;
 
 /** Default for -stopatheight */
 static const int DEFAULT_STOPATHEIGHT = 0;
 /** Default for -maxreorgdepth */
 static const int DEFAULT_MAX_REORG_DEPTH = 10;
 /**
  * Default for -finalizationdelay
  * This is the minimum time between a block header reception and the block
  * finalization.
  * This value should be >> block propagation and validation time
  */
 static const int64_t DEFAULT_MIN_FINALIZATION_DELAY = 2 * 60 * 60;
 /**
  * Block files containing a block-height within MIN_BLOCKS_TO_KEEP of
  * ::ChainActive().Tip() will not be pruned.
  */
 static const unsigned int MIN_BLOCKS_TO_KEEP = 288;
 static const signed int DEFAULT_CHECKBLOCKS = 6;
 static const unsigned int DEFAULT_CHECKLEVEL = 3;
 /**
  * Require that user allocate at least 550 MiB for block & undo files
  * (blk???.dat and rev???.dat)
  * At 1MB per block, 288 blocks = 288MB.
  * Add 15% for Undo data = 331MB
  * Add 20% for Orphan block rate = 397MB
  * We want the low water mark after pruning to be at least 397 MB and since we
  * prune in full block file chunks, we need the high water mark which triggers
  * the prune to be one 128MB block file + added 15% undo data = 147MB greater
  * for a total of 545MB
  * Setting the target to >= 550 MiB will make it likely we can respect the
  * target.
  */
 static const uint64_t MIN_DISK_SPACE_FOR_BLOCK_FILES = 550 * 1024 * 1024;
 
 /** Current sync state passed to tip changed callbacks. */
 enum class SynchronizationState { INIT_REINDEX, INIT_DOWNLOAD, POST_INIT };
 
 extern RecursiveMutex cs_main;
 typedef std::unordered_map<BlockHash, CBlockIndex *, BlockHasher> BlockMap;
 extern Mutex g_best_block_mutex;
 extern std::condition_variable g_best_block_cv;
 extern uint256 g_best_block;
 extern std::atomic_bool fImporting;
 extern std::atomic_bool fReindex;
 extern bool fRequireStandard;
 extern bool fCheckBlockIndex;
 extern bool fCheckpointsEnabled;
 
 /**
  * A fee rate smaller than this is considered zero fee (for relaying, mining and
  * transaction creation)
  */
 extern CFeeRate minRelayTxFee;
 /**
  * If the tip is older than this (in seconds), the node is considered to be in
  * initial block download.
  */
 extern int64_t nMaxTipAge;
 
 /**
  * Block hash whose ancestors we will assume to have valid scripts without
  * checking them.
  */
 extern BlockHash hashAssumeValid;
 
 /**
  * Minimum work we will assume exists on some valid chain.
  */
 extern arith_uint256 nMinimumChainWork;
 
 /**
  * Best header we've seen so far (used for getheaders queries' starting points).
  */
 extern CBlockIndex *pindexBestHeader;
 
 /** Pruning-related variables and constants */
 /** True if any block files have ever been pruned. */
 extern bool fHavePruned;
 /** True if we're running in -prune mode. */
 extern bool fPruneMode;
 /** Number of MiB of block files that we're trying to stay below. */
 extern uint64_t nPruneTarget;
 /** Documentation for argument 'checklevel'. */
 extern const std::vector<std::string> CHECKLEVEL_DOC;
 
 class BlockValidationOptions {
 private:
     uint64_t excessiveBlockSize;
     bool checkPoW : 1;
     bool checkMerkleRoot : 1;
 
 public:
     // Do full validation by default
     explicit BlockValidationOptions(const Config &config);
     explicit BlockValidationOptions(uint64_t _excessiveBlockSize,
                                     bool _checkPow = true,
                                     bool _checkMerkleRoot = true)
         : excessiveBlockSize(_excessiveBlockSize), checkPoW(_checkPow),
           checkMerkleRoot(_checkMerkleRoot) {}
 
     BlockValidationOptions withCheckPoW(bool _checkPoW = true) const {
         BlockValidationOptions ret = *this;
         ret.checkPoW = _checkPoW;
         return ret;
     }
 
     BlockValidationOptions
     withCheckMerkleRoot(bool _checkMerkleRoot = true) const {
         BlockValidationOptions ret = *this;
         ret.checkMerkleRoot = _checkMerkleRoot;
         return ret;
     }
 
     bool shouldValidatePoW() const { return checkPoW; }
     bool shouldValidateMerkleRoot() const { return checkMerkleRoot; }
     uint64_t getExcessiveBlockSize() const { return excessiveBlockSize; }
 };
 
 /**
  * Unload database information.
  */
 void UnloadBlockIndex(CTxMemPool *mempool, ChainstateManager &chainman);
 
 /**
  * Run instances of script checking worker threads
  */
 void StartScriptCheckWorkerThreads(int threads_num);
 
 /**
  * Stop all of the script checking worker threads
  */
 void StopScriptCheckWorkerThreads();
 
 /**
  * Return transaction from the block at block_index.
  * If block_index is not provided, fall back to mempool.
  * If mempool is not provided or the tx couldn't be found in mempool, fall back
  * to g_txindex.
  *
  * @param[in]  block_index     The block to read from disk, or nullptr
  * @param[in]  mempool         If block_index is not provided, look in the
  *                             mempool, if provided
  * @param[in]  txid            The txid
  * @param[in]  consensusParams The params
  * @param[out] hashBlock       The hash of block_index, if the tx was found via
  *                             block_index
  * @returns                    The tx if found, otherwise nullptr
  */
 CTransactionRef GetTransaction(const CBlockIndex *const block_index,
                                const CTxMemPool *const mempool,
                                const TxId &txid,
                                const Consensus::Params &consensusParams,
                                BlockHash &hashBlock);
 Amount GetBlockSubsidy(int nHeight, const Consensus::Params &consensusParams);
 
 /**
  * Guess verification progress (as a fraction between 0.0=genesis and
  * 1.0=current tip).
  */
 double GuessVerificationProgress(const ChainTxData &data,
                                  const CBlockIndex *pindex);
 
 /**
  * Calculate the amount of disk space the block & undo files currently use.
  */
 uint64_t CalculateCurrentUsage();
 
 /**
  * Actually unlink the specified files
  */
 void UnlinkPrunedFiles(const std::set<int> &setFilesToPrune);
 
 /** Prune block files up to a given height */
-void PruneBlockFilesManual(int nManualPruneHeight);
+void PruneBlockFilesManual(CChainState &active_chainstate,
+                           int nManualPruneHeight);
 
 /**
  * (try to) add transaction to memory pool
  * @param[out] fee_out optional argument to return tx fee to the caller
  */
 bool AcceptToMemoryPool(CChainState &active_chainstate, const Config &config,
                         CTxMemPool &pool, TxValidationState &state,
                         const CTransactionRef &tx, bool bypass_limits,
                         bool test_accept = false, Amount *fee_out = nullptr)
     EXCLUSIVE_LOCKS_REQUIRED(cs_main);
 
 /**
  * Simple class for regulating resource usage during CheckInputScripts (and
  * CScriptCheck), atomic so as to be compatible with parallel validation.
  */
 class CheckInputsLimiter {
 protected:
     std::atomic<int64_t> remaining;
 
 public:
     explicit CheckInputsLimiter(int64_t limit) : remaining(limit) {}
 
     bool consume_and_check(int consumed) {
         auto newvalue = (remaining -= consumed);
         return newvalue >= 0;
     }
 
     bool check() { return remaining >= 0; }
 };
 
 class TxSigCheckLimiter : public CheckInputsLimiter {
 public:
     TxSigCheckLimiter() : CheckInputsLimiter(MAX_TX_SIGCHECKS) {}
 
     // Let's make this bad boy copiable.
     TxSigCheckLimiter(const TxSigCheckLimiter &rhs)
         : CheckInputsLimiter(rhs.remaining.load()) {}
 
     TxSigCheckLimiter &operator=(const TxSigCheckLimiter &rhs) {
         remaining = rhs.remaining.load();
         return *this;
     }
 
     static TxSigCheckLimiter getDisabled() {
         TxSigCheckLimiter txLimiter;
         // Historically, there has not been a transaction with more than 20k sig
         // checks on testnet or mainnet, so this effectively disable sigchecks.
         txLimiter.remaining = 20000;
         return txLimiter;
     }
 };
 
 class ConnectTrace;
 
 /**
  * Check whether all of this transaction's input scripts succeed.
  *
  * This involves ECDSA signature checks so can be computationally intensive.
  * This function should only be called after the cheap sanity checks in
  * CheckTxInputs passed.
  *
  * If pvChecks is not nullptr, script checks are pushed onto it instead of being
  * performed inline. Any script checks which are not necessary (eg due to script
  * execution cache hits) are, obviously, not pushed onto pvChecks/run.
  *
  * Upon success nSigChecksOut will be filled in with either:
  * - correct total for all inputs, or,
  * - 0, in the case when checks were pushed onto pvChecks (i.e., a cache miss
  * with pvChecks non-null), in which case the total can be found by executing
  * pvChecks and adding the results.
  *
  * Setting sigCacheStore/scriptCacheStore to false will remove elements from the
  * corresponding cache which are matched. This is useful for checking blocks
  * where we will likely never need the cache entry again.
  *
  * pLimitSigChecks can be passed to limit the sigchecks count either in parallel
  * or serial validation. With pvChecks null (serial validation), breaking the
  * pLimitSigChecks limit will abort evaluation early and return false. With
  * pvChecks not-null (parallel validation): the cached nSigChecks may itself
  * break the limit in which case false is returned, OR, each entry in the
  * returned pvChecks must be executed exactly once in order to probe the limit
  * accurately.
  */
 bool CheckInputScripts(const CTransaction &tx, TxValidationState &state,
                        const CCoinsViewCache &view, const uint32_t flags,
                        bool sigCacheStore, bool scriptCacheStore,
                        const PrecomputedTransactionData &txdata,
                        int &nSigChecksOut, TxSigCheckLimiter &txLimitSigChecks,
                        CheckInputsLimiter *pBlockLimitSigChecks,
                        std::vector<CScriptCheck> *pvChecks)
     EXCLUSIVE_LOCKS_REQUIRED(cs_main);
 
 /**
  * Handy shortcut to full fledged CheckInputScripts call.
  */
 static inline bool
 CheckInputScripts(const CTransaction &tx, TxValidationState &state,
                   const CCoinsViewCache &view, const uint32_t flags,
                   bool sigCacheStore, bool scriptCacheStore,
                   const PrecomputedTransactionData &txdata, int &nSigChecksOut)
     EXCLUSIVE_LOCKS_REQUIRED(cs_main) {
     TxSigCheckLimiter nSigChecksTxLimiter;
     return CheckInputScripts(tx, state, view, flags, sigCacheStore,
                              scriptCacheStore, txdata, nSigChecksOut,
                              nSigChecksTxLimiter, nullptr, nullptr);
 }
 
 /** Get the BIP9 state for a given deployment at the current tip. */
 ThresholdState VersionBitsTipState(const Consensus::Params &params,
                                    Consensus::DeploymentPos pos);
 
 /** Get the BIP9 state for a given deployment at a given block. */
 ThresholdState VersionBitsBlockState(const Consensus::Params &params,
                                      Consensus::DeploymentPos pos,
                                      const CBlockIndex *pindex);
 
 /**
  * Get the numerical statistics for the BIP9 state for a given deployment at the
  * current tip.
  */
 BIP9Stats VersionBitsTipStatistics(const Consensus::Params &params,
                                    Consensus::DeploymentPos pos);
 
 /**
  * Get the block height at which the BIP9 deployment switched into the state for
  * the block building on the current tip.
  */
 int VersionBitsTipStateSinceHeight(const Consensus::Params &params,
                                    Consensus::DeploymentPos pos);
 
 /**
  * Mark all the coins corresponding to a given transaction inputs as spent.
  */
 void SpendCoins(CCoinsViewCache &view, const CTransaction &tx, CTxUndo &txundo,
                 int nHeight);
 
 /**
  * Apply the effects of this transaction on the UTXO set represented by view.
  */
 void UpdateCoins(CCoinsViewCache &view, const CTransaction &tx, int nHeight);
 void UpdateCoins(CCoinsViewCache &view, const CTransaction &tx, CTxUndo &txundo,
                  int nHeight);
 
 /**
  * Test whether the LockPoints height and time are still valid on the current
  * chain.
  */
 bool TestLockPointValidity(const CChain &active_chain, const LockPoints *lp)
     EXCLUSIVE_LOCKS_REQUIRED(cs_main);
 
 /**
  * Check if transaction will be BIP 68 final in the next block to be created.
  *
  * Simulates calling SequenceLocks() with data from the tip of the current
  * active chain. Optionally stores in LockPoints the resulting height and time
  * calculated and the hash of the block needed for calculation or skips the
  * calculation and uses the LockPoints passed in for evaluation. The LockPoints
  * should not be considered valid if CheckSequenceLocks returns false.
  *
  * See consensus/consensus.h for flag definitions.
  */
 bool CheckSequenceLocks(CChainState &active_chainstate, const CTxMemPool &pool,
                         const CTransaction &tx, int flags,
                         LockPoints *lp = nullptr,
                         bool useExistingLockPoints = false)
     EXCLUSIVE_LOCKS_REQUIRED(::cs_main, pool.cs);
 
 /**
  * Closure representing one script verification.
  * Note that this stores references to the spending transaction.
  *
  * Note that if pLimitSigChecks is passed, then failure does not imply that
  * scripts have failed.
  */
 class CScriptCheck {
 private:
     CTxOut m_tx_out;
     const CTransaction *ptxTo;
     unsigned int nIn;
     uint32_t nFlags;
     bool cacheStore;
     ScriptError error;
     ScriptExecutionMetrics metrics;
     PrecomputedTransactionData txdata;
     TxSigCheckLimiter *pTxLimitSigChecks;
     CheckInputsLimiter *pBlockLimitSigChecks;
 
 public:
     CScriptCheck()
         : ptxTo(nullptr), nIn(0), nFlags(0), cacheStore(false),
           error(ScriptError::UNKNOWN), txdata(), pTxLimitSigChecks(nullptr),
           pBlockLimitSigChecks(nullptr) {}
 
     CScriptCheck(const CTxOut &outIn, const CTransaction &txToIn,
                  unsigned int nInIn, uint32_t nFlagsIn, bool cacheIn,
                  const PrecomputedTransactionData &txdataIn,
                  TxSigCheckLimiter *pTxLimitSigChecksIn = nullptr,
                  CheckInputsLimiter *pBlockLimitSigChecksIn = nullptr)
         : m_tx_out(outIn), ptxTo(&txToIn), nIn(nInIn), nFlags(nFlagsIn),
           cacheStore(cacheIn), error(ScriptError::UNKNOWN), txdata(txdataIn),
           pTxLimitSigChecks(pTxLimitSigChecksIn),
           pBlockLimitSigChecks(pBlockLimitSigChecksIn) {}
 
     bool operator()();
 
     void swap(CScriptCheck &check) {
         std::swap(ptxTo, check.ptxTo);
         std::swap(m_tx_out, check.m_tx_out);
         std::swap(nIn, check.nIn);
         std::swap(nFlags, check.nFlags);
         std::swap(cacheStore, check.cacheStore);
         std::swap(error, check.error);
         std::swap(metrics, check.metrics);
         std::swap(txdata, check.txdata);
         std::swap(pTxLimitSigChecks, check.pTxLimitSigChecks);
         std::swap(pBlockLimitSigChecks, check.pBlockLimitSigChecks);
     }
 
     ScriptError GetScriptError() const { return error; }
 
     ScriptExecutionMetrics GetScriptExecutionMetrics() const { return metrics; }
 };
 
 bool UndoReadFromDisk(CBlockUndo &blockundo, const CBlockIndex *pindex);
 
 /** Functions for validating blocks and updating the block tree */
 
 /**
  * Context-independent validity checks.
  *
  * Returns true if the provided block is valid (has valid header,
  * transactions are valid, block is a valid size, etc.)
  */
 bool CheckBlock(const CBlock &block, BlockValidationState &state,
                 const Consensus::Params &params,
                 BlockValidationOptions validationOptions);
 
 /**
  * This is a variant of ContextualCheckTransaction which computes the contextual
  * check for a transaction based on the chain tip.
  *
  * See consensus/consensus.h for flag definitions.
  */
 bool ContextualCheckTransactionForCurrentBlock(
     const CBlockIndex *active_chain_tip, const Consensus::Params &params,
     const CTransaction &tx, TxValidationState &state, int flags = -1)
     EXCLUSIVE_LOCKS_REQUIRED(cs_main);
 
 /**
  * Check a block is completely valid from start to finish (only works on top of
  * our current best block)
  */
 bool TestBlockValidity(BlockValidationState &state, const CChainParams &params,
                        CChainState &chainstate, const CBlock &block,
                        CBlockIndex *pindexPrev,
                        BlockValidationOptions validationOptions)
     EXCLUSIVE_LOCKS_REQUIRED(cs_main);
 
 /**
  * RAII wrapper for VerifyDB: Verify consistency of the block and coin
  * databases.
  */
 class CVerifyDB {
 public:
     CVerifyDB();
     ~CVerifyDB();
     bool VerifyDB(const Config &config, CCoinsView *coinsview, int nCheckLevel,
                   int nCheckDepth);
 };
 
 /** @see CChainState::FlushStateToDisk */
 enum class FlushStateMode { NONE, IF_NEEDED, PERIODIC, ALWAYS };
 
 /** Global variable that points to the active CCoinsView (protected by cs_main)
  */
 extern std::unique_ptr<CCoinsViewCache> pcoinsTip;
 
 /**
  * Maintains a tree of blocks (stored in `m_block_index`) which is consulted
  * to determine where the most-work tip is.
  *
  * This data is used mostly in `CChainState` - information about, e.g.,
  * candidate tips is not maintained here.
  */
 class BlockManager {
     friend CChainState;
 
 private:
     /**
      * Calculate the block/rev files to delete based on height specified
      * by user with RPC command pruneblockchain
      */
     void FindFilesToPruneManual(std::set<int> &setFilesToPrune,
                                 int nManualPruneHeight, int chain_tip_height);
 
     /**
      * Prune block and undo files (blk???.dat and undo???.dat) so that the disk
      * space used is less than a user-defined target. The user sets the target
      * (in MB) on the command line or in config file.  This will be run on
      * startup and whenever new space is allocated in a block or undo file,
      * staying below the target. Changing back to unpruned requires a reindex
      * (which in this case means the blockchain must be re-downloaded.)
      *
      * Pruning functions are called from FlushStateToDisk when the global
      * fCheckForPruning flag has been set. Block and undo files are deleted in
      * lock-step (when blk00003.dat is deleted, so is rev00003.dat.) Pruning
      * cannot take place until the longest chain is at least a certain length
      * (100000 on mainnet, 1000 on testnet, 1000 on regtest). Pruning will never
      * delete a block within a defined distance (currently 288) from the active
      * chain's tip. The block index is updated by unsetting HAVE_DATA and
      * HAVE_UNDO for any blocks that were stored in the deleted files. A db flag
      * records the fact that at least some block files have been pruned.
      *
      * @param[out]   setFilesToPrune   The set of file indices that can be
      *                                 unlinked will be returned
      */
     void FindFilesToPrune(std::set<int> &setFilesToPrune,
                           uint64_t nPruneAfterHeight, int chain_tip_height,
                           bool is_ibd);
 
 public:
     BlockMap m_block_index GUARDED_BY(cs_main);
 
     /**
      * In order to efficiently track invalidity of headers, we keep the set of
      * blocks which we tried to connect and found to be invalid here (ie which
      * were set to BLOCK_FAILED_VALID since the last restart). We can then
      * walk this set and check if a new header is a descendant of something in
      * this set, preventing us from having to walk m_block_index when we try
      * to connect a bad block and fail.
      *
      * While this is more complicated than marking everything which descends
      * from an invalid block as invalid at the time we discover it to be
      * invalid, doing so would require walking all of m_block_index to find all
      * descendants. Since this case should be very rare, keeping track of all
      * BLOCK_FAILED_VALID blocks in a set should be just fine and work just as
      * well.
      *
      * Because we already walk m_block_index in height-order at startup, we go
      * ahead and mark descendants of invalid blocks as FAILED_CHILD at that
      * time, instead of putting things in this set.
      */
     std::set<CBlockIndex *> m_failed_blocks;
 
     /**
      * All pairs A->B, where A (or one of its ancestors) misses transactions,
      * but B has transactions. Pruned nodes may have entries where B is missing
      * data.
      */
     std::multimap<CBlockIndex *, CBlockIndex *> m_blocks_unlinked;
 
     /**
      * Load the blocktree off disk and into memory. Populate certain metadata
      * per index entry (nStatus, nChainWork, nTimeMax, etc.) as well as
      * peripheral collections like setDirtyBlockIndex.
      *
      * @param[out] block_index_candidates  Fill this set with any valid blocks
      * for which we've downloaded all transactions.
      */
     bool LoadBlockIndex(const Consensus::Params &consensus_params,
                         CBlockTreeDB &blocktree,
                         std::set<CBlockIndex *, CBlockIndexWorkComparator>
                             &block_index_candidates)
         EXCLUSIVE_LOCKS_REQUIRED(cs_main);
 
     /** Clear all data members. */
     void Unload() EXCLUSIVE_LOCKS_REQUIRED(cs_main);
 
     CBlockIndex *AddToBlockIndex(const CBlockHeader &block)
         EXCLUSIVE_LOCKS_REQUIRED(cs_main);
     /** Create a new block index entry for a given block hash */
     CBlockIndex *InsertBlockIndex(const BlockHash &hash)
         EXCLUSIVE_LOCKS_REQUIRED(cs_main);
 
     //! Mark one block file as pruned (modify associated database entries)
     void PruneOneBlockFile(const int fileNumber)
         EXCLUSIVE_LOCKS_REQUIRED(cs_main);
 
     /**
      * If a block header hasn't already been seen, call CheckBlockHeader on it,
      * ensure that it doesn't descend from an invalid block, and then add it to
      * m_block_index.
      */
     bool AcceptBlockHeader(const Config &config, const CBlockHeader &block,
                            BlockValidationState &state, CBlockIndex **ppindex)
         EXCLUSIVE_LOCKS_REQUIRED(cs_main);
 
     CBlockIndex *LookupBlockIndex(const BlockHash &hash)
         EXCLUSIVE_LOCKS_REQUIRED(cs_main);
 
     /** Find the last common block between the parameter chain and a locator. */
     CBlockIndex *FindForkInGlobalIndex(const CChain &chain,
                                        const CBlockLocator &locator)
         EXCLUSIVE_LOCKS_REQUIRED(cs_main);
 
     //! Returns last CBlockIndex* that is a checkpoint
     CBlockIndex *GetLastCheckpoint(const CCheckpointData &data)
         EXCLUSIVE_LOCKS_REQUIRED(cs_main);
 
     /**
      * Return the spend height, which is one more than the
      * inputs.GetBestBlock(). While checking, GetBestBlock() refers to the
      * parent block. (protected by cs_main)
      * This is also true for mempool checks.
      */
     int GetSpendHeight(const CCoinsViewCache &inputs)
         EXCLUSIVE_LOCKS_REQUIRED(cs_main);
 
     ~BlockManager() { Unload(); }
 };
 
 /**
  * A convenience class for constructing the CCoinsView* hierarchy used
  * to facilitate access to the UTXO set.
  *
  * This class consists of an arrangement of layered CCoinsView objects,
  * preferring to store and retrieve coins in memory via `m_cacheview` but
  * ultimately falling back on cache misses to the canonical store of UTXOs on
  * disk, `m_dbview`.
  */
 class CoinsViews {
 public:
     //! The lowest level of the CoinsViews cache hierarchy sits in a leveldb
     //! database on disk. All unspent coins reside in this store.
     CCoinsViewDB m_dbview GUARDED_BY(cs_main);
 
     //! This view wraps access to the leveldb instance and handles read errors
     //! gracefully.
     CCoinsViewErrorCatcher m_catcherview GUARDED_BY(cs_main);
 
     //! This is the top layer of the cache hierarchy - it keeps as many coins in
     //! memory as can fit per the dbcache setting.
     std::unique_ptr<CCoinsViewCache> m_cacheview GUARDED_BY(cs_main);
 
     //! This constructor initializes CCoinsViewDB and CCoinsViewErrorCatcher
     //! instances, but it *does not* create a CCoinsViewCache instance by
     //! default. This is done separately because the presence of the cache has
     //! implications on whether or not we're allowed to flush the cache's state
     //! to disk, which should not be done until the health of the database is
     //! verified.
     //!
     //! All arguments forwarded onto CCoinsViewDB.
     CoinsViews(std::string ldb_name, size_t cache_size_bytes, bool in_memory,
                bool should_wipe);
 
     //! Initialize the CCoinsViewCache member.
     void InitCache() EXCLUSIVE_LOCKS_REQUIRED(::cs_main);
 };
 
 enum class CoinsCacheSizeState {
     //! The coins cache is in immediate need of a flush.
     CRITICAL = 2,
     //! The cache is at >= 90% capacity.
     LARGE = 1,
     OK = 0
 };
 
 /**
  * CChainState stores and provides an API to update our local knowledge of the
  * current best chain.
  *
  * Eventually, the API here is targeted at being exposed externally as a
  * consumable libconsensus library, so any functions added must only call
  * other class member functions, pure functions in other parts of the consensus
  * library, callbacks via the validation interface, or read/write-to-disk
  * functions (eventually this will also be via callbacks).
  *
  * Anything that is contingent on the current tip of the chain is stored here,
  * whereas block information and metadata independent of the current tip is
  * kept in `BlockMetadataManager`.
  */
 class CChainState {
 private:
     /**
      * the ChainState CriticalSection
      * A lock that must be held when modifying this ChainState - held in
      * ActivateBestChain()
      */
     RecursiveMutex m_cs_chainstate;
 
     /**
      * Every received block is assigned a unique and increasing identifier, so
      * we know which one to give priority in case of a fork.
      * Blocks loaded from disk are assigned id 0, so start the counter at 1.
      */
     std::atomic<int32_t> nBlockSequenceId{1};
     /** Decreasing counter (used by subsequent preciousblock calls). */
     int32_t nBlockReverseSequenceId = -1;
     /** chainwork for the last block that preciousblock has been applied to. */
     arith_uint256 nLastPreciousChainwork = 0;
 
     /**
      * Whether this chainstate is undergoing initial block download.
      *
      * Mutable because we need to be able to mark IsInitialBlockDownload()
      * const, which latches this for caching purposes.
      */
     mutable std::atomic<bool> m_cached_finished_ibd{false};
 
     //! mempool that is kept in sync with the chain
     CTxMemPool &m_mempool;
 
     //! Manages the UTXO set, which is a reflection of the contents of
     //! `m_chain`.
     std::unique_ptr<CoinsViews> m_coins_views;
 
     /**
      * The best finalized block.
      * This block cannot be reorged in any way except by explicit user action.
      */
     const CBlockIndex *m_finalizedBlockIndex GUARDED_BY(cs_main) = nullptr;
 
 public:
     //! Reference to a BlockManager instance which itself is shared across all
     //! CChainState instances.
     BlockManager &m_blockman;
 
     explicit CChainState(CTxMemPool &mempool, BlockManager &blockman,
                          BlockHash from_snapshot_blockhash = BlockHash());
 
     /**
      * Initialize the CoinsViews UTXO set database management data structures.
      * The in-memory cache is initialized separately.
      *
      * All parameters forwarded to CoinsViews.
      */
     void InitCoinsDB(size_t cache_size_bytes, bool in_memory, bool should_wipe,
                      std::string leveldb_name = "chainstate");
 
     //! Initialize the in-memory coins cache (to be done after the health of the
     //! on-disk database is verified).
     void InitCoinsCache(size_t cache_size_bytes)
         EXCLUSIVE_LOCKS_REQUIRED(::cs_main);
 
     //! @returns whether or not the CoinsViews object has been fully initialized
     //! and we can
     //!          safely flush this object to disk.
     bool CanFlushToDisk() const EXCLUSIVE_LOCKS_REQUIRED(cs_main) {
         return m_coins_views && m_coins_views->m_cacheview;
     }
 
     //! The current chain of blockheaders we consult and build on.
     //! @see CChain, CBlockIndex.
     CChain m_chain;
 
     /**
      * The blockhash which is the base of the snapshot this chainstate was
      * created from.
      *
      * IsNull() if this chainstate was not created from a snapshot.
      */
     const BlockHash m_from_snapshot_blockhash{};
 
     /**
      * The set of all CBlockIndex entries with BLOCK_VALID_TRANSACTIONS (for
      * itself and all ancestors) and as good as our current tip or better.
      * Entries may be failed, though, and pruning nodes may be missing the data
      * for the block.
      */
     std::set<CBlockIndex *, CBlockIndexWorkComparator> setBlockIndexCandidates;
 
     //! @returns A reference to the in-memory cache of the UTXO set.
     CCoinsViewCache &CoinsTip() EXCLUSIVE_LOCKS_REQUIRED(cs_main) {
         assert(m_coins_views->m_cacheview);
         return *m_coins_views->m_cacheview.get();
     }
 
     //! @returns A reference to the on-disk UTXO set database.
     CCoinsViewDB &CoinsDB() { return m_coins_views->m_dbview; }
 
     //! @returns A reference to a wrapped view of the in-memory UTXO set that
     //!     handles disk read errors gracefully.
     CCoinsViewErrorCatcher &CoinsErrorCatcher()
         EXCLUSIVE_LOCKS_REQUIRED(cs_main) {
         return m_coins_views->m_catcherview;
     }
 
     //! Destructs all objects related to accessing the UTXO set.
     void ResetCoinsViews() { m_coins_views.reset(); }
 
     //! The cache size of the on-disk coins view.
     size_t m_coinsdb_cache_size_bytes{0};
 
     //! The cache size of the in-memory coins view.
     size_t m_coinstip_cache_size_bytes{0};
 
     //! Resize the CoinsViews caches dynamically and flush state to disk.
     //! @returns true unless an error occurred during the flush.
     bool ResizeCoinsCaches(size_t coinstip_size, size_t coinsdb_size)
         EXCLUSIVE_LOCKS_REQUIRED(::cs_main);
 
     /** Import blocks from an external file */
     void LoadExternalBlockFile(const Config &config, FILE *fileIn,
                                FlatFilePos *dbp = nullptr);
 
     /**
      * Update the on-disk chain state.
      * The caches and indexes are flushed depending on the mode we're called
      * with if they're too large, if it's been a while since the last write, or
      * always and in all cases if we're in prune mode and are deleting files.
      *
      * If FlushStateMode::NONE is used, then FlushStateToDisk(...) won't do
      * anything besides checking if we need to prune.
      *
      * @returns true unless a system error occurred
      */
     bool FlushStateToDisk(const CChainParams &chainparams,
                           BlockValidationState &state, FlushStateMode mode,
                           int nManualPruneHeight = 0);
 
     //! Unconditionally flush all changes to disk.
     void ForceFlushStateToDisk();
 
     //! Prune blockfiles from the disk if necessary and then flush chainstate
     //! changes if we pruned.
     void PruneAndFlush();
 
     /**
      * Find the best known block, and make it the tip of the block chain. The
      * result is either failure or an activated best chain. pblock is either
      * nullptr or a pointer to a block that is already loaded (to avoid loading
      * it again from disk).
      *
      * ActivateBestChain is split into steps (see ActivateBestChainStep) so that
      * we avoid holding cs_main for an extended period of time; the length of
      * this call may be quite long during reindexing or a substantial reorg.
      *
      * May not be called with cs_main held. May not be called in a
      * validationinterface callback.
      *
      * @returns true unless a system error occurred
      */
     bool ActivateBestChain(const Config &config, BlockValidationState &state,
                            std::shared_ptr<const CBlock> pblock = nullptr)
         LOCKS_EXCLUDED(cs_main);
 
     bool AcceptBlock(const Config &config,
                      const std::shared_ptr<const CBlock> &pblock,
                      BlockValidationState &state, bool fRequested,
                      const FlatFilePos *dbp, bool *fNewBlock)
         EXCLUSIVE_LOCKS_REQUIRED(cs_main);
 
     // Block (dis)connection on a given view:
     DisconnectResult DisconnectBlock(const CBlock &block,
                                      const CBlockIndex *pindex,
                                      CCoinsViewCache &view);
     bool ConnectBlock(const CBlock &block, BlockValidationState &state,
                       CBlockIndex *pindex, CCoinsViewCache &view,
                       const CChainParams &params,
                       BlockValidationOptions options, bool fJustCheck = false)
         EXCLUSIVE_LOCKS_REQUIRED(cs_main);
 
     // Block disconnection on our pcoinsTip:
     bool DisconnectTip(const CChainParams &params, BlockValidationState &state,
                        DisconnectedBlockTransactions *disconnectpool)
         EXCLUSIVE_LOCKS_REQUIRED(cs_main, m_mempool.cs);
 
     // Manual block validity manipulation:
     /**
      * Mark a block as precious and reorganize.
      *
      * May not be called in a validationinterface callback.
      */
     bool PreciousBlock(const Config &config, BlockValidationState &state,
                        CBlockIndex *pindex) LOCKS_EXCLUDED(cs_main);
     /** Mark a block as invalid. */
     bool InvalidateBlock(const Config &config, BlockValidationState &state,
                          CBlockIndex *pindex) LOCKS_EXCLUDED(cs_main)
         EXCLUSIVE_LOCKS_REQUIRED(!m_cs_chainstate);
     /** Park a block. */
     bool ParkBlock(const Config &config, BlockValidationState &state,
                    CBlockIndex *pindex) LOCKS_EXCLUDED(cs_main)
         EXCLUSIVE_LOCKS_REQUIRED(!m_cs_chainstate);
 
     /**
      * Finalize a block.
      * A finalized block can not be reorged in any way.
      */
     bool FinalizeBlock(const Config &config, BlockValidationState &state,
                        CBlockIndex *pindex) LOCKS_EXCLUDED(cs_main)
         EXCLUSIVE_LOCKS_REQUIRED(!m_cs_chainstate);
     /** Return the currently finalized block index. */
     const CBlockIndex *GetFinalizedBlock() const
         EXCLUSIVE_LOCKS_REQUIRED(cs_main);
     /**
      * Checks if a block is finalized.
      */
     bool IsBlockFinalized(const CBlockIndex *pindex) const
         EXCLUSIVE_LOCKS_REQUIRED(cs_main);
 
     /** Remove invalidity status from a block and its descendants. */
     void ResetBlockFailureFlags(CBlockIndex *pindex)
         EXCLUSIVE_LOCKS_REQUIRED(cs_main);
     template <typename F>
     bool UpdateFlagsForBlock(CBlockIndex *pindexBase, CBlockIndex *pindex, F f)
         EXCLUSIVE_LOCKS_REQUIRED(cs_main);
     template <typename F, typename C, typename AC>
     void UpdateFlags(CBlockIndex *pindex, CBlockIndex *&pindexReset, F f,
                      C fChild, AC fAncestorWasChanged)
         EXCLUSIVE_LOCKS_REQUIRED(cs_main);
 
     /** Remove parked status from a block and its descendants. */
     void UnparkBlockAndChildren(CBlockIndex *pindex)
         EXCLUSIVE_LOCKS_REQUIRED(cs_main);
 
     /** Remove parked status from a block. */
     void UnparkBlock(CBlockIndex *pindex) EXCLUSIVE_LOCKS_REQUIRED(cs_main);
 
     /** Replay blocks that aren't fully applied to the database. */
     bool ReplayBlocks(const Consensus::Params &params);
 
     /**
      * Ensures we have a genesis block in the block tree, possibly writing one
      * to disk.
      */
     bool LoadGenesisBlock(const CChainParams &chainparams);
 
     void PruneBlockIndexCandidates();
 
     void UnloadBlockIndex() EXCLUSIVE_LOCKS_REQUIRED(cs_main);
 
     /**
      * Check whether we are doing an initial block download (synchronizing from
      * disk or network)
      */
     bool IsInitialBlockDownload() const;
 
     /**
      * Make various assertions about the state of the block index.
      *
      * By default this only executes fully when using the Regtest chain; see:
      * fCheckBlockIndex.
      */
     void CheckBlockIndex(const Consensus::Params &consensusParams);
 
     /** Load the persisted mempool from disk */
     void LoadMempool(const Config &config, const ArgsManager &args);
 
     /** Update the chain tip based on database information, i.e. CoinsTip()'s
      * best block. */
     bool LoadChainTip(const CChainParams &chainparams)
         EXCLUSIVE_LOCKS_REQUIRED(cs_main);
 
     //! Dictates whether we need to flush the cache to disk or not.
     //!
     //! @return the state of the size of the coins cache.
     CoinsCacheSizeState GetCoinsCacheSizeState(const CTxMemPool *tx_pool)
         EXCLUSIVE_LOCKS_REQUIRED(::cs_main);
 
     CoinsCacheSizeState GetCoinsCacheSizeState(
         const CTxMemPool *tx_pool, size_t max_coins_cache_size_bytes,
         size_t max_mempool_size_bytes) EXCLUSIVE_LOCKS_REQUIRED(::cs_main);
 
     std::string ToString() EXCLUSIVE_LOCKS_REQUIRED(::cs_main);
 
 private:
     bool ActivateBestChainStep(const Config &config,
                                BlockValidationState &state,
                                CBlockIndex *pindexMostWork,
                                const std::shared_ptr<const CBlock> &pblock,
                                bool &fInvalidFound, ConnectTrace &connectTrace)
         EXCLUSIVE_LOCKS_REQUIRED(cs_main, m_mempool.cs);
     bool ConnectTip(const Config &config, BlockValidationState &state,
                     CBlockIndex *pindexNew,
                     const std::shared_ptr<const CBlock> &pblock,
                     ConnectTrace &connectTrace,
                     DisconnectedBlockTransactions &disconnectpool)
         EXCLUSIVE_LOCKS_REQUIRED(cs_main, m_mempool.cs);
     void InvalidBlockFound(CBlockIndex *pindex,
                            const BlockValidationState &state)
         EXCLUSIVE_LOCKS_REQUIRED(cs_main);
     void InvalidChainFound(CBlockIndex *pindexNew)
         EXCLUSIVE_LOCKS_REQUIRED(cs_main);
     CBlockIndex *FindMostWorkChain() EXCLUSIVE_LOCKS_REQUIRED(cs_main);
     bool MarkBlockAsFinal(BlockValidationState &state,
                           const CBlockIndex *pindex)
         EXCLUSIVE_LOCKS_REQUIRED(cs_main);
     void ReceivedBlockTransactions(const CBlock &block, CBlockIndex *pindexNew,
                                    const FlatFilePos &pos)
         EXCLUSIVE_LOCKS_REQUIRED(cs_main);
 
     bool RollforwardBlock(const CBlockIndex *pindex, CCoinsViewCache &inputs,
                           const Consensus::Params &params)
         EXCLUSIVE_LOCKS_REQUIRED(cs_main);
 
     void UnparkBlockImpl(CBlockIndex *pindex, bool fClearChildren)
         EXCLUSIVE_LOCKS_REQUIRED(cs_main);
 
     bool UnwindBlock(const Config &config, BlockValidationState &state,
                      CBlockIndex *pindex, bool invalidate)
         EXCLUSIVE_LOCKS_REQUIRED(m_cs_chainstate);
 
     friend ChainstateManager;
 };
 
 /**
  * Provides an interface for creating and interacting with one or two
  * chainstates: an IBD chainstate generated by downloading blocks, and
  * an optional snapshot chainstate loaded from a UTXO snapshot. Managed
  * chainstates can be maintained at different heights simultaneously.
  *
  * This class provides abstractions that allow the retrieval of the current
  * most-work chainstate ("Active") as well as chainstates which may be in
  * background use to validate UTXO snapshots.
  *
  * Definitions:
  *
  * *IBD chainstate*: a chainstate whose current state has been "fully"
  *   validated by the initial block download process.
  *
  * *Snapshot chainstate*: a chainstate populated by loading in an
  *    assumeutxo UTXO snapshot.
  *
  * *Active chainstate*: the chainstate containing the current most-work
  *    chain. Consulted by most parts of the system (net_processing,
  *    wallet) as a reflection of the current chain and UTXO set.
  *    This may either be an IBD chainstate or a snapshot chainstate.
  *
  * *Background IBD chainstate*: an IBD chainstate for which the
  *    IBD process is happening in the background while use of the
  *    active (snapshot) chainstate allows the rest of the system to function.
  *
  * *Validated chainstate*: the most-work chainstate which has been validated
  *   locally via initial block download. This will be the snapshot chainstate
  *   if a snapshot was loaded and all blocks up to the snapshot starting point
  *   have been downloaded and validated (via background validation), otherwise
  *   it will be the IBD chainstate.
  */
 class ChainstateManager {
 private:
     //! The chainstate used under normal operation (i.e. "regular" IBD) or, if
     //! a snapshot is in use, for background validation.
     //!
     //! Its contents (including on-disk data) will be deleted *upon shutdown*
     //! after background validation of the snapshot has completed. We do not
     //! free the chainstate contents immediately after it finishes validation
     //! to cautiously avoid a case where some other part of the system is still
     //! using this pointer (e.g. net_processing).
     //!
     //! Once this pointer is set to a corresponding chainstate, it will not
     //! be reset until init.cpp:Shutdown(). This means it is safe to acquire
     //! the contents of this pointer with ::cs_main held, release the lock,
     //! and then use the reference without concern of it being deconstructed.
     //!
     //! This is especially important when, e.g., calling ActivateBestChain()
     //! on all chainstates because we are not able to hold ::cs_main going into
     //! that call.
     std::unique_ptr<CChainState> m_ibd_chainstate;
 
     //! A chainstate initialized on the basis of a UTXO snapshot. If this is
     //! non-null, it is always our active chainstate.
     //!
     //! Once this pointer is set to a corresponding chainstate, it will not
     //! be reset until init.cpp:Shutdown(). This means it is safe to acquire
     //! the contents of this pointer with ::cs_main held, release the lock,
     //! and then use the reference without concern of it being deconstructed.
     //!
     //! This is especially important when, e.g., calling ActivateBestChain()
     //! on all chainstates because we are not able to hold ::cs_main going into
     //! that call.
     std::unique_ptr<CChainState> m_snapshot_chainstate;
 
     //! Points to either the ibd or snapshot chainstate; indicates our
     //! most-work chain.
     //!
     //! Once this pointer is set to a corresponding chainstate, it will not
     //! be reset until init.cpp:Shutdown(). This means it is safe to acquire
     //! the contents of this pointer with ::cs_main held, release the lock,
     //! and then use the reference without concern of it being deconstructed.
     //!
     //! This is especially important when, e.g., calling ActivateBestChain()
     //! on all chainstates because we are not able to hold ::cs_main going into
     //! that call.
     CChainState *m_active_chainstate GUARDED_BY(::cs_main){nullptr};
 
     //! If true, the assumed-valid chainstate has been fully validated
     //! by the background validation chainstate.
     bool m_snapshot_validated{false};
 
     // For access to m_active_chainstate.
     friend CChainState &ChainstateActive();
     friend CChain &ChainActive();
 
 public:
     //! A single BlockManager instance is shared across each constructed
     //! chainstate to avoid duplicating block metadata.
     BlockManager m_blockman GUARDED_BY(::cs_main);
 
     //! The total number of bytes available for us to use across all in-memory
     //! coins caches. This will be split somehow across chainstates.
     int64_t m_total_coinstip_cache{0};
     //
     //! The total number of bytes available for us to use across all leveldb
     //! coins databases. This will be split somehow across chainstates.
     int64_t m_total_coinsdb_cache{0};
 
     //! Instantiate a new chainstate and assign it based upon whether it is
     //! from a snapshot.
     //!
     //! @param[in] mempool              The mempool to pass to the chainstate
     //                                  constructor
     //! @param[in] snapshot_blockhash   If given, signify that this chainstate
     //!                                 is based on a snapshot.
     CChainState &
     InitializeChainstate(CTxMemPool &mempool,
                          const BlockHash &snapshot_blockhash = BlockHash())
         EXCLUSIVE_LOCKS_REQUIRED(::cs_main);
 
     //! Get all chainstates currently being used.
     std::vector<CChainState *> GetAll();
 
     //! The most-work chain.
     CChainState &ActiveChainstate() const;
     CChain &ActiveChain() const { return ActiveChainstate().m_chain; }
     int ActiveHeight() const { return ActiveChain().Height(); }
     CBlockIndex *ActiveTip() const { return ActiveChain().Tip(); }
 
     BlockMap &BlockIndex() EXCLUSIVE_LOCKS_REQUIRED(::cs_main) {
         return m_blockman.m_block_index;
     }
 
     bool IsSnapshotActive() const;
 
     std::optional<BlockHash> SnapshotBlockhash() const;
 
     //! Is there a snapshot in use and has it been fully validated?
     bool IsSnapshotValidated() const { return m_snapshot_validated; }
 
     //! @returns true if this chainstate is being used to validate an active
     //!          snapshot in the background.
     bool IsBackgroundIBD(CChainState *chainstate) const;
 
     //! Return the most-work chainstate that has been fully validated.
     //!
     //! During background validation of a snapshot, this is the IBD chain. After
     //! background validation has completed, this is the snapshot chain.
     CChainState &ValidatedChainstate() const;
 
     CChain &ValidatedChain() const { return ValidatedChainstate().m_chain; }
     CBlockIndex *ValidatedTip() const { return ValidatedChain().Tip(); }
 
     /**
      * Process an incoming block. This only returns after the best known valid
      * block is made active. Note that it does not, however, guarantee that the
      * specific block passed to it has been checked for validity!
      *
      * If you want to *possibly* get feedback on whether pblock is valid, you
      * must install a CValidationInterface (see validationinterface.h) - this
      * will have its BlockChecked method called whenever *any* block completes
      * validation.
      *
      * Note that we guarantee that either the proof-of-work is valid on pblock,
      * or (and possibly also) BlockChecked will have been called.
      *
      * May not be called in a validationinterface callback.
      *
      * @param[in]   config  The global config.
      * @param[in]   pblock  The block we want to process.
      * @param[in]   fForceProcessing Process this block even if unrequested;
      *              used for non-network block sources.
      * @param[out]  fNewBlock A boolean which is set to indicate if the block
      * was first received via this call.
      * @returns     If the block was processed, independently of block validity
      */
     bool ProcessNewBlock(const Config &config,
                          const std::shared_ptr<const CBlock> pblock,
                          bool fForceProcessing, bool *fNewBlock)
         LOCKS_EXCLUDED(cs_main);
 
     /**
      * Process incoming block headers.
      *
      * May not be called in a validationinterface callback.
      *
      * @param[in]  config        The config.
      * @param[in]  block         The block headers themselves.
      * @param[out] state         This may be set to an Error state if any error
      *                           occurred processing them.
      * @param[out] ppindex       If set, the pointer will be set to point to the
      *                           last new block index object for the given
      * headers.
      * @return True if block headers were accepted as valid.
      */
     bool ProcessNewBlockHeaders(const Config &config,
                                 const std::vector<CBlockHeader> &block,
                                 BlockValidationState &state,
                                 const CBlockIndex **ppindex = nullptr)
         LOCKS_EXCLUDED(cs_main);
 
     //! Load the block tree and coins database from disk, initializing state if
     //! we're running with -reindex
     bool LoadBlockIndex(const Consensus::Params &params)
         EXCLUSIVE_LOCKS_REQUIRED(cs_main);
 
     //! Unload block index and chain data before shutdown.
     void Unload() EXCLUSIVE_LOCKS_REQUIRED(::cs_main);
 
     //! Clear (deconstruct) chainstate data.
     void Reset();
 
     //! Check to see if caches are out of balance and if so, call
     //! ResizeCoinsCaches() as needed.
     void MaybeRebalanceCaches() EXCLUSIVE_LOCKS_REQUIRED(::cs_main);
 };
 
 /**
  * DEPRECATED! Please use node.chainman instead. May only be used in
  * validation.cpp internally
  */
 extern ChainstateManager g_chainman GUARDED_BY(::cs_main);
 
 /** Please prefer the identical ChainstateManager::ActiveChainstate */
 CChainState &ChainstateActive();
 
 /** Please prefer the identical ChainstateManager::ActiveChain */
 CChain &ChainActive();
 
 /**
  * Global variable that points to the active block tree (protected by cs_main)
  */
 extern std::unique_ptr<CBlockTreeDB> pblocktree;
 
 /**
  * Determine what nVersion a new block should use.
  */
 int32_t ComputeBlockVersion(const CBlockIndex *pindexPrev,
                             const Consensus::Params &params);
 
 /** Get block file info entry for one block file */
 CBlockFileInfo *GetBlockFileInfo(size_t n);
 
 /** Dump the mempool to disk. */
 bool DumpMempool(const CTxMemPool &pool);
 
 /** Load the mempool from disk. */
 bool LoadMempool(const Config &config, CTxMemPool &pool,
                  CChainState &active_chainstate);
 
 //! Check whether the block associated with this index entry is pruned or not.
 bool IsBlockPruned(const CBlockIndex *pblockindex);
 
 #endif // BITCOIN_VALIDATION_H