Coverage Report

Created: 2025-09-08 17:06

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-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
6
{
25
6
    return RPCHelpMan{
26
6
        "gettxoutproof",
27
6
        "Returns a hex-encoded proof that \"txid\" was included in a block.\n"
28
6
        "\nNOTE: By default this function only works sometimes. This is when there is an\n"
29
6
        "unspent output in the utxo for this transaction. To make it always work,\n"
30
6
        "you need to maintain a transaction index, using the -txindex command line option or\n"
31
6
        "specify the block in which the transaction is included manually (by blockhash).\n",
32
6
        {
33
6
            {"txids", RPCArg::Type::ARR, RPCArg::Optional::NO, "The txids to filter",
34
6
                {
35
6
                    {"txid", RPCArg::Type::STR_HEX, RPCArg::Optional::OMITTED, "A transaction hash"},
36
6
                },
37
6
            },
38
6
            {"blockhash", RPCArg::Type::STR_HEX, RPCArg::Optional::OMITTED, "If specified, looks for txid in the block with this hash"},
39
6
        },
40
6
        RPCResult{
41
6
            RPCResult::Type::STR, "data", "A string that is a serialized, hex-encoded data for the proof."
42
6
        },
43
6
        RPCExamples{""},
44
6
        [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
45
6
        {
46
0
            std::set<Txid> setTxids;
47
0
            UniValue txids = request.params[0].get_array();
48
0
            if (txids.empty()) {
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++) {
52
0
                auto ret{setTxids.insert(Txid::FromUint256(ParseHashV(txids[idx], "txid")))};
53
0
                if (!ret.second) {
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()) {
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) {
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) {
74
0
                    const Coin& coin{AccessByTxid(active_chainstate.CoinsTip(), tx)};
75
0
                    if (!coin.IsSpent()) {
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) {
85
0
                g_txindex->BlockUntilSyncedToCurrentChain();
86
0
            }
87
88
0
            if (pblockindex == nullptr) {
89
0
                const CTransactionRef tx = GetTransaction(/*block_index=*/nullptr, /*mempool=*/nullptr, *setTxids.begin(), hashBlock, chainman.m_blockman);
90
0
                if (!tx || hashBlock.IsNull()) {
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) {
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)) {
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) {
112
0
                if (setTxids.count(tx->GetHash())) {
113
0
                    ntxFound++;
114
0
                }
115
0
            }
116
0
            if (ntxFound != setTxids.size()) {
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
6
    };
127
6
}
128
129
static RPCHelpMan verifytxoutproof()
130
48
{
131
48
    return RPCHelpMan{
132
48
        "verifytxoutproof",
133
48
        "Verifies that a proof points to a transaction in a block, returning the transaction it commits to\n"
134
48
        "and throwing an RPC error if the block is not in our best chain\n",
135
48
        {
136
48
            {"proof", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "The hex-encoded proof generated by gettxoutproof"},
137
48
        },
138
48
        RPCResult{
139
48
            RPCResult::Type::ARR, "", "",
140
48
            {
141
48
                {RPCResult::Type::STR_HEX, "txid", "The txid(s) which the proof commits to, or empty array if the proof cannot be validated."},
142
48
            }
143
48
        },
144
48
        RPCExamples{""},
145
48
        [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
146
48
        {
147
41
            DataStream ssMB{ParseHexV(request.params[0], "proof")};
148
41
            CMerkleBlock merkleBlock;
149
41
            ssMB >> merkleBlock;
150
151
41
            UniValue res(UniValue::VARR);
152
153
41
            std::vector<Txid> vMatch;
154
41
            std::vector<unsigned int> vIndex;
155
41
            if (merkleBlock.txn.ExtractMatches(vMatch, vIndex) != merkleBlock.header.hashMerkleRoot)
156
35
                return res;
157
158
6
            ChainstateManager& chainman = EnsureAnyChainman(request.context);
159
6
            LOCK(cs_main);
160
161
6
            const CBlockIndex* pindex = chainman.m_blockman.LookupBlockIndex(merkleBlock.header.GetHash());
162
6
            if (!pindex || !chainman.ActiveChain().Contains(pindex) || pindex->nTx == 0) {
163
2
                throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Block not found in chain");
164
2
            }
165
166
            // Check if proof is valid, only add results if so
167
4
            if (pindex->nTx == merkleBlock.txn.GetNumTransactions()) {
168
0
                for (const auto& txid : vMatch) {
169
0
                    res.push_back(txid.GetHex());
170
0
                }
171
0
            }
172
173
4
            return res;
174
6
        },
175
48
    };
176
48
}
177
178
void RegisterTxoutProofRPCCommands(CRPCTable& t)
179
0
{
180
0
    static const CRPCCommand commands[]{
181
0
        {"blockchain", &gettxoutproof},
182
0
        {"blockchain", &verifytxoutproof},
183
0
    };
184
0
    for (const auto& c : commands) {
185
0
        t.appendCommand(c.name, &c);
186
0
    }
187
0
}