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 }