Coverage Report

Created: 2026-09-15 16:03

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