fuzz coverage

Coverage Report

Created: 2025-06-01 19:34

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