LCOV - code coverage report
Current view: top level - src/rpc - txoutproof.cpp (source / functions) Hit Total Coverage
Test: total_coverage.info Lines: 104 110 94.5 %
Date: 2026-06-25 07:23:43 Functions: 7 7 100.0 %

          Line data    Source code
       1             : // Copyright (c) 2010 Satoshi Nakamoto
       2             : // Copyright (c) 2009-2022 The Bitcoin Core developers
       3             : // Distributed under the MIT software license, see the accompanying
       4             : // file COPYING or http://www.opensource.org/licenses/mit-license.php.
       5             : 
       6             : #include <chain.h>
       7             : #include <chainparams.h>
       8             : #include <coins.h>
       9             : #include <index/txindex.h>
      10             : #include <merkleblock.h>
      11             : #include <node/blockstorage.h>
      12             : #include <primitives/transaction.h>
      13             : #include <rpc/server.h>
      14             : #include <rpc/server_util.h>
      15             : #include <rpc/util.h>
      16             : #include <univalue.h>
      17             : #include <util/strencodings.h>
      18             : #include <validation.h>
      19             : 
      20             : using node::GetTransaction;
      21             : using node::ReadBlockFromDisk;
      22             : 
      23        6204 : static RPCHelpMan gettxoutproof()
      24             : {
      25       12408 :     return RPCHelpMan{"gettxoutproof",
      26        6204 :         "\nReturns a hex-encoded proof that \"txid\" was included in a block.\n"
      27             :         "\nNOTE: By default this function only works sometimes. This is when there is an\n"
      28             :         "unspent output in the utxo for this transaction. To make it always work,\n"
      29             :         "you need to maintain a transaction index, using the -txindex command line option or\n"
      30             :         "specify the block in which the transaction is included manually (by blockhash).\n",
      31       18612 :         {
      32       12408 :             {"txids", RPCArg::Type::ARR, RPCArg::Optional::NO, "The txids to filter",
      33       12408 :                 {
      34        6204 :                     {"txid", RPCArg::Type::STR_HEX, RPCArg::Optional::OMITTED, "A transaction hash"},
      35             :                 },
      36             :             },
      37        6204 :             {"blockhash", RPCArg::Type::STR_HEX, RPCArg::Optional::OMITTED_NAMED_ARG, "If specified, looks for txid in the block with this hash"},
      38             :         },
      39        6204 :         RPCResult{
      40        6204 :             RPCResult::Type::STR, "data", "A string that is a serialized, hex-encoded data for the proof."
      41             :         },
      42        6204 :         RPCExamples{
      43        6204 :             HelpExampleCli("gettxoutproof", "'[\"mytxid\",...]'")
      44        6204 :             + HelpExampleCli("gettxoutproof", "'[\"mytxid\",...]' \"blockhash\"")
      45        6204 :             + HelpExampleRpc("gettxoutproof", "[\"mytxid\",...], \"blockhash\"")
      46             :         },
      47        6252 :         [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
      48             :         {
      49          48 :             std::set<uint256> setTxids;
      50          48 :             UniValue txids = request.params[0].get_array();
      51          48 :             if (txids.empty()) {
      52           2 :                 throw JSONRPCError(RPC_INVALID_PARAMETER, "Parameter 'txids' cannot be empty");
      53             :             }
      54         100 :             for (unsigned int idx = 0; idx < txids.size(); idx++) {
      55          60 :                 auto ret = setTxids.insert(ParseHashV(txids[idx], "txid"));
      56          56 :                 if (!ret.second) {
      57           2 :                     throw JSONRPCError(RPC_INVALID_PARAMETER, std::string("Invalid parameter, duplicated txid: ") + txids[idx].get_str());
      58             :                 }
      59          54 :             }
      60             : 
      61          40 :             const CBlockIndex* pblockindex = nullptr;
      62          40 :             uint256 hashBlock;
      63          40 :             ChainstateManager& chainman = EnsureAnyChainman(request.context);
      64          40 :             if (!request.params[1].isNull()) {
      65          10 :                 LOCK(cs_main);
      66          10 :                 hashBlock = ParseHashV(request.params[1], "blockhash");
      67           6 :                 pblockindex = chainman.m_blockman.LookupBlockIndex(hashBlock);
      68           6 :                 if (!pblockindex) {
      69           2 :                     throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Block not found");
      70             :                 }
      71          10 :             } else {
      72          30 :                 LOCK(cs_main);
      73          30 :                 CChainState& active_chainstate = chainman.ActiveChainstate();
      74             : 
      75             :                 // Loop through txids and try to find which block they're in. Exit loop once a block is found.
      76          40 :                 for (const auto& tx : setTxids) {
      77          36 :                     const Coin& coin = AccessByTxid(active_chainstate.CoinsTip(), tx);
      78          36 :                     if (!coin.IsSpent()) {
      79          26 :                         pblockindex = active_chainstate.m_chain[coin.nHeight];
      80          26 :                         break;
      81             :                     }
      82             :                 }
      83          30 :             }
      84             : 
      85             : 
      86             :             // Allow txindex to catch up if we need to query it and before we acquire cs_main.
      87          34 :             if (g_txindex && !pblockindex) {
      88           4 :                 g_txindex->BlockUntilSyncedToCurrentChain();
      89           4 :             }
      90             : 
      91          34 :             if (pblockindex == nullptr) {
      92           4 :                 const CTransactionRef tx = GetTransaction(/*block_index=*/nullptr, /*mempool=*/nullptr, *setTxids.begin(), Params().GetConsensus(), hashBlock);
      93           4 :                 if (!tx || hashBlock.IsNull()) {
      94           2 :                     throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Transaction not yet in block");
      95             :                 }
      96           4 :                 pblockindex = WITH_LOCK(::cs_main, return chainman.m_blockman.LookupBlockIndex(hashBlock));
      97           2 :                 if (!pblockindex) {
      98           0 :                     throw JSONRPCError(RPC_INTERNAL_ERROR, "Transaction index corrupt");
      99             :                 }
     100           4 :             }
     101             : 
     102          32 :             CBlock block;
     103          32 :             if (!ReadBlockFromDisk(block, pblockindex, Params().GetConsensus())) {
     104           0 :                 throw JSONRPCError(RPC_INTERNAL_ERROR, "Can't read block from disk");
     105             :             }
     106             : 
     107          32 :             unsigned int ntxFound = 0;
     108         114 :             for (const auto& tx : block.vtx) {
     109          82 :                 if (setTxids.count(tx->GetHash())) {
     110          42 :                     ntxFound++;
     111          42 :                 }
     112             :             }
     113          32 :             if (ntxFound != setTxids.size()) {
     114           2 :                 throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Not all transactions found in specified or retrieved block");
     115             :             }
     116             : 
     117          30 :             CDataStream ssMB(SER_NETWORK, PROTOCOL_VERSION);
     118          30 :             CMerkleBlock mb(block, setTxids);
     119          30 :             ssMB << mb;
     120          30 :             std::string strHex = HexStr(ssMB);
     121          30 :             return strHex;
     122          62 :         },
     123             :     };
     124           0 : }
     125             : 
     126        6231 : static RPCHelpMan verifytxoutproof()
     127             : {
     128       12462 :     return RPCHelpMan{"verifytxoutproof",
     129        6231 :         "\nVerifies that a proof points to a transaction in a block, returning the transaction it commits to\n"
     130             :         "and throwing an RPC error if the block is not in our best chain\n",
     131       12462 :         {
     132        6231 :             {"proof", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "The hex-encoded proof generated by gettxoutproof"},
     133             :         },
     134        6231 :         RPCResult{
     135        6231 :             RPCResult::Type::ARR, "", "",
     136       12462 :             {
     137        6231 :                 {RPCResult::Type::STR_HEX, "txid", "The txid(s) which the proof commits to, or empty array if the proof cannot be validated."},
     138             :             }
     139             :         },
     140        6231 :         RPCExamples{
     141        6231 :             HelpExampleCli("verifytxoutproof", "\"proof\"")
     142        6231 :             + HelpExampleRpc("gettxoutproof", "\"proof\"")
     143             :         },
     144        6306 :         [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
     145             :         {
     146          75 :             CDataStream ssMB(ParseHexV(request.params[0], "proof"), SER_NETWORK, PROTOCOL_VERSION);
     147          75 :             CMerkleBlock merkleBlock;
     148          75 :             ssMB >> merkleBlock;
     149             : 
     150          75 :             UniValue res(UniValue::VARR);
     151             : 
     152          75 :             std::vector<uint256> vMatch;
     153          75 :             std::vector<unsigned int> vIndex;
     154          75 :             if (merkleBlock.txn.ExtractMatches(vMatch, vIndex) != merkleBlock.header.hashMerkleRoot)
     155           0 :                 return res;
     156             : 
     157          75 :             ChainstateManager& chainman = EnsureAnyChainman(request.context);
     158          75 :             LOCK(cs_main);
     159             : 
     160          75 :             const CBlockIndex* pindex = chainman.m_blockman.LookupBlockIndex(merkleBlock.header.GetHash());
     161          75 :             if (!pindex || !chainman.ActiveChain().Contains(pindex) || pindex->nTx == 0) {
     162           0 :                 throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Block not found in chain");
     163             :             }
     164             : 
     165             :             // Check if proof is valid, only add results if so
     166          75 :             if (pindex->nTx == merkleBlock.txn.GetNumTransactions()) {
     167         156 :                 for (const uint256& hash : vMatch) {
     168          85 :                     res.push_back(hash.GetHex());
     169             :                 }
     170          71 :             }
     171             : 
     172          75 :             return res;
     173          75 :         },
     174             :     };
     175           0 : }
     176             : 
     177        3201 : void RegisterTxoutProofRPCCommands(CRPCTable& t)
     178             : {
     179        9339 :     static const CRPCCommand commands[]{
     180        3069 :         {"blockchain", &gettxoutproof},
     181        3069 :         {"blockchain", &verifytxoutproof},
     182             :     };
     183        9603 :     for (const auto& c : commands) {
     184        6402 :         t.appendCommand(c.name, &c);
     185             :     }
     186        3201 : }

Generated by: LCOV version 1.16