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 }