Inside Bitcoin
Opening the selected file…
Opening the selected file…
The code behind the network, with room to understand it.
Release published 2026-10-28 · Snapshot fetched 2026-11-01. Latest stable release at fetch time; development branches and Git history are excluded. Only the selected code window and visible folders load here.
1// Copyright (c) 2010 Satoshi Nakamoto2// Copyright (c) 2009-present The Bitcoin Core developers3// Distributed under the MIT software license, see the accompanying4// file COPYING or http://www.opensource.org/licenses/mit-license.php.6#include <rpc/register.h> // IWYU pragma: associated8#include <chain.h>9#include <coins.h>10#include <crypto/hex_base.h>11#include <index/txindex.h>12#include <merkleblock.h>13#include <node/blockstorage.h>14#include <node/transaction.h>15#include <primitives/block.h>16#include <primitives/transaction.h>17#include <rpc/blockchain.h>18#include <rpc/protocol.h>19#include <rpc/request.h>20#include <rpc/server.h>21#include <rpc/server_util.h>22#include <rpc/util.h>23#include <streams.h>24#include <sync.h>25#include <uint256.h>26#include <univalue.h>27#include <validation.h>29#include <memory>30#include <set>31#include <span>32#include <string>33#include <utility>34#include <vector>36using node::GetTransaction;38static RPCMethod gettxoutproof()39{40return RPCMethod{41"gettxoutproof",42"Returns a hex-encoded proof that \"txid\" was included in a block.\n"43"\nNOTE: By default this function only works sometimes. This is when there is an\n"44"unspent output in the utxo for this transaction. To make it always work,\n"45"you need to maintain a transaction index, using the -txindex command line option or\n"46"specify the block in which the transaction is included manually (by blockhash).\n",47{48{"txids", RPCArg::Type::ARR, RPCArg::Optional::NO, "The txids to filter",49{50{"txid", RPCArg::Type::STR_HEX, RPCArg::Optional::OMITTED, "A transaction hash"},51},52},53{"blockhash", RPCArg::Type::STR_HEX, RPCArg::Optional::OMITTED, "If specified, looks for txid in the block with this hash"},54},55RPCResult{56RPCResult::Type::STR, "data", "A string that is a serialized, hex-encoded data for the proof."57},58RPCExamples{""},59[](const RPCMethod& self, const JSONRPCRequest& request) -> UniValue60{61std::set<Txid> setTxids;62UniValue txids = request.params[0].get_array();63if (txids.empty()) {64throw JSONRPCError(RPC_INVALID_PARAMETER, "Parameter 'txids' cannot be empty");65}66for (unsigned int idx = 0; idx < txids.size(); idx++) {67auto ret{setTxids.insert(Txid::FromUint256(ParseHashV(txids[idx], "txid")))};68if (!ret.second) {69throw JSONRPCError(RPC_INVALID_PARAMETER, std::string("Invalid parameter, duplicated txid: ") + txids[idx].get_str());70}71}73const CBlockIndex* pblockindex = nullptr;74uint256 hashBlock;75ChainstateManager& chainman = EnsureAnyChainman(request.context);76if (!request.params[1].isNull()) {77LOCK(cs_main);78hashBlock = ParseHashV(request.params[1], "blockhash");79pblockindex = chainman.m_blockman.LookupBlockIndex(hashBlock);80if (!pblockindex) {81throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Block not found");82}83} else {84LOCK(cs_main);85Chainstate& active_chainstate = chainman.ActiveChainstate();87// Loop through txids and try to find which block they're in. Exit loop once a block is found.88for (const auto& tx : setTxids) {89const Coin& coin{AccessByTxid(active_chainstate.CoinsTip(), tx)};90if (!coin.IsSpent()) {91pblockindex = active_chainstate.m_chain[coin.nHeight];92break;93}94}95}98// Allow txindex to catch up if we need to query it and before we acquire cs_main.99if (g_txindex && !pblockindex) {100g_txindex->BlockUntilSyncedToCurrentChain();101}103if (pblockindex == nullptr) {104const CTransactionRef tx = GetTransaction(/*block_index=*/nullptr, /*mempool=*/nullptr, *setTxids.begin(), chainman.m_blockman, hashBlock);105if (!tx || hashBlock.IsNull()) {106throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Transaction not yet in block");107}109LOCK(cs_main);110pblockindex = chainman.m_blockman.LookupBlockIndex(hashBlock);111if (!pblockindex) {112throw JSONRPCError(RPC_INTERNAL_ERROR, "Transaction index corrupt");113}114}116{117LOCK(cs_main);118CheckBlockDataAvailability(chainman.m_blockman, *pblockindex, /*check_for_undo=*/false);119}120CBlock block;121if (!chainman.m_blockman.ReadBlock(block, *pblockindex)) {122throw JSONRPCError(RPC_INTERNAL_ERROR, "Can't read block from disk");123}125unsigned int ntxFound = 0;126for (const auto& tx : block.vtx) {127if (setTxids.contains(tx->GetHash())) {128ntxFound++;129}130}131if (ntxFound != setTxids.size()) {132throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Not all transactions found in specified or retrieved block");133}135DataStream ssMB{};136CMerkleBlock mb(block, setTxids);137ssMB << mb;138std::string strHex = HexStr(ssMB);139return strHex;140},141};142}144static RPCMethod verifytxoutproof()145{146return RPCMethod{147"verifytxoutproof",148"Verifies that a proof points to a transaction in a block, returning the transaction it commits to\n"149"and throwing an RPC error if the block is not in our best chain\n",150{151{"proof", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "The hex-encoded proof generated by gettxoutproof"},152},153RPCResult{154RPCResult::Type::ARR, "", "",155{156{RPCResult::Type::STR_HEX, "txid", "The txid(s) which the proof commits to, or empty array if the proof cannot be validated."},157}158},159RPCExamples{""},160[](const RPCMethod& self, const JSONRPCRequest& request) -> UniValue161{162CMerkleBlock merkleBlock;163SpanReader{ParseHexV(request.params[0], "proof")} >> merkleBlock;165UniValue res(UniValue::VARR);167std::vector<Txid> vMatch;168std::vector<unsigned int> vIndex;169if (merkleBlock.txn.ExtractMatches(vMatch, vIndex) != merkleBlock.header.hashMerkleRoot)170return res;172ChainstateManager& chainman = EnsureAnyChainman(request.context);173LOCK(cs_main);175const CBlockIndex* pindex = chainman.m_blockman.LookupBlockIndex(merkleBlock.header.GetHash());176if (!pindex || !chainman.ActiveChain().Contains(*pindex) || pindex->nTx == 0) {177throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Block not found in chain");178}180// Check if proof is valid, only add results if so181if (pindex->nTx == merkleBlock.txn.GetNumTransactions()) {182for (const auto& txid : vMatch) {183res.push_back(txid.GetHex());184}185}187return res;188},189};190}192void RegisterTxoutProofRPCCommands(CRPCTable& t)193{194static const CRPCCommand commands[]{195{"blockchain", &gettxoutproof},196{"blockchain", &verifytxoutproof},197};198for (const auto& c : commands) {199t.appendCommand(c.name, &c);200}201}