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