Coverage Report

Created: 2026-04-29 19:21

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