txoutproof.cpp raw

   1  // Copyright (c) 2010 Satoshi Nakamoto
   2  // Copyright (c) 2009-2022 The Limenka 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 <consensus/validation.h>
  10  #include <index/txindex.h>
  11  #include <merkleblock.h>
  12  #include <node/blockstorage.h>
  13  #include <primitives/transaction.h>
  14  #include <rpc/blockchain.h>
  15  #include <rpc/server.h>
  16  #include <rpc/server_util.h>
  17  #include <rpc/util.h>
  18  #include <univalue.h>
  19  #include <util/strencodings.h>
  20  #include <validation.h>
  21  
  22  using node::GetTransaction;
  23  
  24  static RPCHelpMan gettxoutproof()
  25  {
  26      return RPCHelpMan{"gettxoutproof",
  27          "\nReturns 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 id"},
  36                  },
  37              },
  38              {"blockhash", RPCArg::Type::STR_HEX, RPCArg::Optional::OMITTED, "If specified, looks for txid in the block with this hash"},
  39              {"options", RPCArg::Type::OBJ_NAMED_PARAMS, RPCArg::Optional::OMITTED, "",
  40                  {
  41                      {"prove_witness", RPCArg::Type::BOOL, RPCArg::Default{false}, "If true, proves the associated wtxid/hash of the specified transactions instead of txid"},
  42                  },
  43              },
  44          },
  45          {
  46          RPCResult{
  47                  "If prove_witness is false or unspecified",
  48              RPCResult::Type::STR, "data", "A string that is a serialized, hex-encoded data for the proof."
  49          },
  50              RPCResult{
  51                  "If prove_witness is true", RPCResult::Type::OBJ, "", "",
  52                  {
  53                      {RPCResult::Type::STR, "proof", "The produced txout proof, hex-encoded."},
  54                      {RPCResult::Type::OBJ, "proven", "Information about the proof.", {
  55                          {RPCResult::Type::STR_HEX, "blockhash", "The block hash the proof links to"},
  56                          {RPCResult::Type::NUM, "blockheight", "The height of the block the proof links to"},
  57                          {RPCResult::Type::ARR, "tx", "Information about transactions", {
  58                              {RPCResult::Type::OBJ, "", "Information about a transaction", {
  59                                  {RPCResult::Type::STR_HEX, "txid", "Transaction id this is for (parameter; NOT proven by proof)"},
  60                                  {RPCResult::Type::STR_HEX, "wtxid", "Wtxid/hash of a transaction"},
  61                                  {RPCResult::Type::NUM, "blockindex", "Index of transaction in block"},
  62                              }},
  63                          }},
  64                      }},
  65                  }
  66              },
  67          },
  68          RPCExamples{""},
  69          [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
  70          {
  71              std::set<Txid> setTxids;
  72              UniValue txids = request.params[0].get_array();
  73              if (txids.empty()) {
  74                  throw JSONRPCError(RPC_INVALID_PARAMETER, "Parameter 'txids' cannot be empty");
  75              }
  76              for (unsigned int idx = 0; idx < txids.size(); idx++) {
  77                  auto ret{setTxids.insert(Txid::FromUint256(ParseHashV(txids[idx], "txid")))};
  78                  if (!ret.second) {
  79                      throw JSONRPCError(RPC_INVALID_PARAMETER, std::string("Invalid parameter, duplicated txid: ") + txids[idx].get_str());
  80                  }
  81              }
  82              const UniValue options{request.params[2].isNull() ? UniValue::VOBJ : request.params[2]};
  83              bool prove_witness = options["prove_witness"].isNull() ? false : options["prove_witness"].get_bool();
  84  
  85              const CBlockIndex* pblockindex = nullptr;
  86              uint256 hashBlock;
  87              ChainstateManager& chainman = EnsureAnyChainman(request.context);
  88              if (!request.params[1].isNull()) {
  89                  LOCK(cs_main);
  90                  hashBlock = ParseHashV(request.params[1], "blockhash");
  91                  pblockindex = chainman.m_blockman.LookupBlockIndex(hashBlock);
  92                  if (!pblockindex) {
  93                      throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Block not found");
  94                  }
  95              } else {
  96                  LOCK(cs_main);
  97                  Chainstate& active_chainstate = chainman.ActiveChainstate();
  98  
  99                  // Loop through txids and try to find which block they're in. Exit loop once a block is found.
 100                  for (const auto& tx : setTxids) {
 101                      const Coin& coin{AccessByTxid(active_chainstate.CoinsTip(), tx)};
 102                      if (!coin.IsSpent()) {
 103                          pblockindex = active_chainstate.m_chain[coin.nHeight];
 104                          break;
 105                      }
 106                  }
 107              }
 108  
 109  
 110              // Allow txindex to catch up if we need to query it and before we acquire cs_main.
 111              if (g_txindex && !pblockindex) {
 112                  g_txindex->BlockUntilSyncedToCurrentChain();
 113              }
 114  
 115              if (pblockindex == nullptr) {
 116                  const CTransactionRef tx = GetTransaction(/*block_index=*/nullptr, /*mempool=*/nullptr, *setTxids.begin(), hashBlock, chainman.m_blockman);
 117                  if (!tx || hashBlock.IsNull()) {
 118                      throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Transaction not yet in block");
 119                  }
 120  
 121                  LOCK(cs_main);
 122                  pblockindex = chainman.m_blockman.LookupBlockIndex(hashBlock);
 123                  if (!pblockindex) {
 124                      throw JSONRPCError(RPC_INTERNAL_ERROR, "Transaction index corrupt");
 125                  }
 126              }
 127  
 128              {
 129                  LOCK(cs_main);
 130                  CheckBlockDataAvailability(chainman.m_blockman, *pblockindex, /*check_for_undo=*/false);
 131              }
 132              CBlock block;
 133              if (!chainman.m_blockman.ReadBlock(block, *pblockindex)) {
 134                  throw JSONRPCError(RPC_INTERNAL_ERROR, "Can't read block from disk");
 135              }
 136  
 137              unsigned int ntxFound = 0;
 138              UniValue txs{UniValue::VARR};
 139              for (size_t i{0}; i < block.vtx.size(); ++i) {
 140                  const auto& tx = block.vtx.at(i);
 141                  if (setTxids.count(tx->GetHash())) {
 142                      ntxFound++;
 143                      if (prove_witness) {
 144                          UniValue txinfo{UniValue::VOBJ};
 145                          txinfo.pushKV("txid", tx->GetHash().GetHex());
 146                          txinfo.pushKV("wtxid", tx->GetWitnessHash().GetHex());
 147                          txinfo.pushKV("blockindex", i);
 148                          txs.push_back(txinfo);
 149                      }
 150                  }
 151              }
 152              if (ntxFound != setTxids.size()) {
 153                  throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Not all transactions found in specified or retrieved block");
 154              }
 155  
 156              DataStream ssMB{};
 157              CMerkleBlock mb(block, setTxids, /*prove_witness=*/ prove_witness);
 158              if (prove_witness) {
 159                  mb.SerializeWithWitness(ssMB);
 160              } else {
 161              ssMB << mb;
 162              }
 163              std::string strHex = HexStr(ssMB);
 164  
 165              if (prove_witness) {
 166                  UniValue proven{UniValue::VOBJ};
 167                  proven.pushKV("blockhash", block.GetHash().GetHex());
 168                  proven.pushKV("blockheight", pblockindex->nHeight);
 169                  proven.pushKV("tx", txs);
 170                  UniValue res{UniValue::VOBJ};
 171                  res.pushKV("proof", strHex);
 172                  res.pushKV("proven", proven);
 173                  return res;
 174              }
 175  
 176              return strHex;
 177          },
 178      };
 179  }
 180  
 181  static RPCHelpMan verifytxoutproof()
 182  {
 183      return RPCHelpMan{"verifytxoutproof",
 184          "\nVerifies that a proof points to a transaction in a block, returning the transaction it commits to\n"
 185          "and throwing an RPC error if the block is not in our best chain\n",
 186          {
 187              {"proof", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "The hex-encoded proof generated by gettxoutproof"},
 188              {"options", RPCArg::Type::OBJ_NAMED_PARAMS, RPCArg::Optional::OMITTED, "",
 189                  {
 190                      {"verify_witness", RPCArg::Type::BOOL, RPCArg::Default{false}, "If true, also verifies the associated wtxid/hash of the specified transactions (if included in proof)"},
 191                  },
 192              },
 193          },
 194          {
 195          RPCResult{
 196              "If verify_witness is false or unspecified",
 197              RPCResult::Type::ARR, "", "",
 198              {
 199                  {RPCResult::Type::STR_HEX, "txid", "The txid(s) which the proof commits to, or empty array if the proof cannot be validated."},
 200              }
 201          },
 202              RPCResult{
 203                  "If verify_witness is true and the proof valid", RPCResult::Type::OBJ, "", "",
 204                  {
 205                      {RPCResult::Type::STR_HEX, "blockhash", "The block hash this proof links to"},
 206                      {RPCResult::Type::NUM, "blockheight", "The height of the block this proof links to"},
 207                      {RPCResult::Type::NUM, "confirmations", /*optional=*/true, "Number of blocks (including the one with the transactions) confirming these transactions"},
 208                      {RPCResult::Type::NUM, "confirmations_assumed", /*optional=*/true, "The number of unverified blocks confirming these transactions (eg, in an assumed-valid UTXO set)"},
 209                      {RPCResult::Type::ARR, "tx", "Information about transactions", {
 210                          {RPCResult::Type::OBJ, "", "Information about a transaction", {
 211                              {RPCResult::Type::STR_HEX, "wtxid", "Wtxid/hash of a transaction"},
 212                              {RPCResult::Type::NUM, "blockindex", "Index of transaction in block"},
 213                          }},
 214                      }},
 215                  }
 216              },
 217              RPCResult{"If verify_witness is true and the proof invalid", RPCResult::Type::OBJ, "", ""},
 218          },
 219          RPCExamples{""},
 220          [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
 221          {
 222              const UniValue options{request.params[1].isNull() ? UniValue::VOBJ : request.params[1].get_obj()};
 223              const bool verify_witness = options["verify_witness"].isNull() ? false : options["verify_witness"].get_bool();
 224  
 225              DataStream ssMB{ParseHexV(request.params[0], "proof")};
 226              CMerkleBlock merkleBlock;
 227              if (verify_witness) {
 228                  merkleBlock.UnserializeWithWitness(ssMB);
 229              } else {
 230              ssMB >> merkleBlock;
 231              }
 232  
 233              UniValue res(verify_witness ? UniValue::VOBJ : UniValue::VARR);
 234  
 235              std::vector<uint256> vMatch;
 236              std::vector<unsigned int> vIndex;
 237              if (merkleBlock.txn.ExtractMatches(vMatch, vIndex) != merkleBlock.header.hashMerkleRoot)
 238                  return res;
 239  
 240              if (vMatch.empty()) {
 241                  return res;
 242              }
 243  
 244              int witness_commit_outidx{NO_WITNESS_COMMITMENT};
 245              if (verify_witness) {
 246                  if (vIndex.at(0) != 0) return res;
 247                  if (!merkleBlock.m_gentx) return res;
 248                  if (merkleBlock.m_gentx->GetHash() != vMatch[0]) return res;
 249                  if (!merkleBlock.m_gentx->IsCoinBase()) return res;
 250                  witness_commit_outidx = GetWitnessCommitmentIndex(*merkleBlock.m_gentx);
 251                  if (witness_commit_outidx == NO_WITNESS_COMMITMENT) {
 252                      if (!merkleBlock.m_prove_gentx) {
 253                          // We must always prove the gentx to either reveal the wtxid root or prove it has none
 254                          // But the user may not care to prove the gentx itself, and in this case, we need some way to disambiguate
 255                          vMatch.erase(vMatch.begin());
 256                          vIndex.erase(vIndex.begin());
 257                      }
 258                  } else {
 259                      vIndex.clear();
 260                      uint256 wtxid_root = merkleBlock.m_wtxid_tree.ExtractMatches(vMatch, vIndex);
 261                      if (vMatch.empty()) return res;
 262                      const auto& gentx_witness_stack{merkleBlock.m_gentx->vin[0].scriptWitness.stack};
 263                      if (gentx_witness_stack.size() != 1 || gentx_witness_stack[0].size() != 32) return res;
 264                      CHash256().Write(wtxid_root).Write(gentx_witness_stack[0]).Finalize(wtxid_root);
 265                      if (memcmp(wtxid_root.begin(), &merkleBlock.m_gentx->vout[witness_commit_outidx].scriptPubKey[6], 32)) {
 266                          return res;
 267                      }
 268                      if (vIndex.at(0) == 0) {
 269                          auto& gentx_match = vMatch[0];
 270                          if (!gentx_match.IsNull()) return res;
 271                          gentx_match = merkleBlock.m_gentx->GetHash();
 272                      }
 273                  }
 274              }
 275  
 276              {
 277              ChainstateManager& chainman = EnsureAnyChainman(request.context);
 278              LOCK(cs_main);
 279  
 280              const CBlockIndex* pindex = chainman.m_blockman.LookupBlockIndex(merkleBlock.header.GetHash());
 281              if (!pindex || !chainman.ActiveChain().Contains(pindex) || pindex->nTx == 0) {
 282                  throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Block not found in chain");
 283              }
 284  
 285              // Check if proof is valid, only add results if so
 286                  if (pindex->nTx != merkleBlock.txn.GetNumTransactions()) {
 287                      return res;
 288                  }
 289  
 290                  if (verify_witness) {
 291                      res.pushKV("blockheight", pindex->nHeight);
 292  
 293                      const auto pindex_tip = chainman.ActiveChain().Tip();
 294                      CHECK_NONFATAL(pindex_tip);
 295                      const auto assumed_base_height = chainman.GetSnapshotBaseHeight();
 296                      if (assumed_base_height && pindex->nHeight < *assumed_base_height) {
 297                          res.pushKV("confirmations", 0);
 298                          res.pushKV("confirmations_assumed", (int64_t)(pindex_tip->nHeight - pindex->nHeight + 1));
 299                      } else {
 300                          res.pushKV("confirmations", (int64_t)(pindex_tip->nHeight - pindex->nHeight + 1));
 301                      }
 302                  }
 303              }
 304  
 305              if (verify_witness) {
 306                  res.pushKV("blockhash", merkleBlock.header.GetHash().GetHex());
 307                  UniValue txs{UniValue::VARR};
 308                  for (size_t i{0}; i < vMatch.size(); ++i) {
 309                      UniValue tx{UniValue::VOBJ};
 310                      tx.pushKV("wtxid", vMatch.at(i).GetHex());
 311                      tx.pushKV("blockindex", vIndex.at(i));
 312                      txs.push_back(tx);
 313                  }
 314                  res.pushKV("tx", txs);
 315                  return res;
 316              }
 317  
 318                  for (const uint256& hash : vMatch) {
 319                      res.push_back(hash.GetHex());
 320                  }
 321              return res;
 322          },
 323      };
 324  }
 325  
 326  void RegisterTxoutProofRPCCommands(CRPCTable& t)
 327  {
 328      static const CRPCCommand commands[]{
 329          {"blockchain", &gettxoutproof},
 330          {"blockchain", &verifytxoutproof},
 331      };
 332      for (const auto& c : commands) {
 333          t.appendCommand(c.name, &c);
 334      }
 335  }
 336