Coverage Report

Created: 2025-06-10 13:21

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