LCOV - code coverage report
Current view: top level - src/rpc - txoutproof.cpp (source / functions) Hit Total Coverage
Test: fuzz_coverage.info Lines: 33 110 30.0 %
Date: 2023-11-06 23:13:05 Functions: 3 8 37.5 %
Branches: 47 322 14.6 %

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

Generated by: LCOV version 1.14