LCOV - code coverage report
Current view: top level - src/rpc - txoutproof.cpp (source / functions) Coverage Total Hit
Test: total_coverage.info Lines: 97.2 % 106 103
Test Date: 2026-10-23 06:14:09 Functions: 100.0 % 5 5
Branches: 54.7 % 278 152

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

Generated by: LCOV version 2.0-1