/ src / rpc / txoutproof.cpp
txoutproof.cpp
  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  
 22  static RPCHelpMan gettxoutproof()
 23  {
 24      return RPCHelpMan{"gettxoutproof",
 25          "\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          "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          {
 31              {"txids", RPCArg::Type::ARR, RPCArg::Optional::NO, "The txids to filter",
 32                  {
 33                      {"txid", RPCArg::Type::STR_HEX, RPCArg::Optional::OMITTED, "A transaction hash"},
 34                  },
 35              },
 36              {"blockhash", RPCArg::Type::STR_HEX, RPCArg::Optional::OMITTED, "If specified, looks for txid in the block with this hash"},
 37          },
 38          RPCResult{
 39              RPCResult::Type::STR, "data", "A string that is a serialized, hex-encoded data for the proof."
 40          },
 41          RPCExamples{""},
 42          [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
 43          {
 44              std::set<Txid> setTxids;
 45              UniValue txids = request.params[0].get_array();
 46              if (txids.empty()) {
 47                  throw JSONRPCError(RPC_INVALID_PARAMETER, "Parameter 'txids' cannot be empty");
 48              }
 49              for (unsigned int idx = 0; idx < txids.size(); idx++) {
 50                  auto ret{setTxids.insert(Txid::FromUint256(ParseHashV(txids[idx], "txid")))};
 51                  if (!ret.second) {
 52                      throw JSONRPCError(RPC_INVALID_PARAMETER, std::string("Invalid parameter, duplicated txid: ") + txids[idx].get_str());
 53                  }
 54              }
 55  
 56              const CBlockIndex* pblockindex = nullptr;
 57              uint256 hashBlock;
 58              ChainstateManager& chainman = EnsureAnyChainman(request.context);
 59              if (!request.params[1].isNull()) {
 60                  LOCK(cs_main);
 61                  hashBlock = ParseHashV(request.params[1], "blockhash");
 62                  pblockindex = chainman.m_blockman.LookupBlockIndex(hashBlock);
 63                  if (!pblockindex) {
 64                      throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Block not found");
 65                  }
 66              } else {
 67                  LOCK(cs_main);
 68                  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                  for (const auto& tx : setTxids) {
 72                      const Coin& coin{AccessByTxid(active_chainstate.CoinsTip(), tx)};
 73                      if (!coin.IsSpent()) {
 74                          pblockindex = active_chainstate.m_chain[coin.nHeight];
 75                          break;
 76                      }
 77                  }
 78              }
 79  
 80  
 81              // Allow txindex to catch up if we need to query it and before we acquire cs_main.
 82              if (g_txindex && !pblockindex) {
 83                  g_txindex->BlockUntilSyncedToCurrentChain();
 84              }
 85  
 86              if (pblockindex == nullptr) {
 87                  const CTransactionRef tx = GetTransaction(/*block_index=*/nullptr, /*mempool=*/nullptr, *setTxids.begin(), hashBlock, chainman.m_blockman);
 88                  if (!tx || hashBlock.IsNull()) {
 89                      throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Transaction not yet in block");
 90                  }
 91  
 92                  LOCK(cs_main);
 93                  pblockindex = chainman.m_blockman.LookupBlockIndex(hashBlock);
 94                  if (!pblockindex) {
 95                      throw JSONRPCError(RPC_INTERNAL_ERROR, "Transaction index corrupt");
 96                  }
 97              }
 98  
 99              CBlock block;
100              if (!chainman.m_blockman.ReadBlockFromDisk(block, *pblockindex)) {
101                  throw JSONRPCError(RPC_INTERNAL_ERROR, "Can't read block from disk");
102              }
103  
104              unsigned int ntxFound = 0;
105              for (const auto& tx : block.vtx) {
106                  if (setTxids.count(tx->GetHash())) {
107                      ntxFound++;
108                  }
109              }
110              if (ntxFound != setTxids.size()) {
111                  throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Not all transactions found in specified or retrieved block");
112              }
113  
114              DataStream ssMB{};
115              CMerkleBlock mb(block, setTxids);
116              ssMB << mb;
117              std::string strHex = HexStr(ssMB);
118              return strHex;
119          },
120      };
121  }
122  
123  static RPCHelpMan verifytxoutproof()
124  {
125      return RPCHelpMan{"verifytxoutproof",
126          "\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          {
129              {"proof", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "The hex-encoded proof generated by gettxoutproof"},
130          },
131          RPCResult{
132              RPCResult::Type::ARR, "", "",
133              {
134                  {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          RPCExamples{""},
138          [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
139          {
140              DataStream ssMB{ParseHexV(request.params[0], "proof")};
141              CMerkleBlock merkleBlock;
142              ssMB >> merkleBlock;
143  
144              UniValue res(UniValue::VARR);
145  
146              std::vector<uint256> vMatch;
147              std::vector<unsigned int> vIndex;
148              if (merkleBlock.txn.ExtractMatches(vMatch, vIndex) != merkleBlock.header.hashMerkleRoot)
149                  return res;
150  
151              ChainstateManager& chainman = EnsureAnyChainman(request.context);
152              LOCK(cs_main);
153  
154              const CBlockIndex* pindex = chainman.m_blockman.LookupBlockIndex(merkleBlock.header.GetHash());
155              if (!pindex || !chainman.ActiveChain().Contains(pindex) || pindex->nTx == 0) {
156                  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              if (pindex->nTx == merkleBlock.txn.GetNumTransactions()) {
161                  for (const uint256& hash : vMatch) {
162                      res.push_back(hash.GetHex());
163                  }
164              }
165  
166              return res;
167          },
168      };
169  }
170  
171  void RegisterTxoutProofRPCCommands(CRPCTable& t)
172  {
173      static const CRPCCommand commands[]{
174          {"blockchain", &gettxoutproof},
175          {"blockchain", &verifytxoutproof},
176      };
177      for (const auto& c : commands) {
178          t.appendCommand(c.name, &c);
179      }
180  }