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