Branch data Line data Source code
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 : 86 : static RPCMethod gettxoutproof()
39 : : {
40 : 86 : return RPCMethod{
41 : 86 : "gettxoutproof",
42 [ + - ]: 172 : "Returns a hex-encoded proof that \"txid\" was included in a block.\n"
43 : : "\nNOTE: By default this function only works sometimes. This is when there is an\n"
44 : : "unspent output in the utxo for this transaction. To make it always work,\n"
45 : : "you need to maintain a transaction index, using the -txindex command line option or\n"
46 : : "specify the block in which the transaction is included manually (by blockhash).\n",
47 : : {
48 [ + - + - ]: 172 : {"txids", RPCArg::Type::ARR, RPCArg::Optional::NO, "The txids to filter",
49 : : {
50 [ + - + - ]: 172 : {"txid", RPCArg::Type::STR_HEX, RPCArg::Optional::OMITTED, "A transaction hash"},
51 : : },
52 : : },
53 [ + - + - ]: 172 : {"blockhash", RPCArg::Type::STR_HEX, RPCArg::Optional::OMITTED, "If specified, looks for txid in the block with this hash"},
54 : : },
55 [ + - ]: 172 : RPCResult{
56 [ + - + - ]: 172 : RPCResult::Type::STR, "data", "A string that is a serialized, hex-encoded data for the proof."
57 [ + - ]: 172 : },
58 [ + - + - ]: 258 : RPCExamples{""},
59 : 86 : [](const RPCMethod& self, const JSONRPCRequest& request) -> UniValue
60 : : {
61 [ # # ]: 0 : std::set<Txid> setTxids;
62 [ # # # # : 0 : UniValue txids = request.params[0].get_array();
# # ]
63 [ # # # # ]: 0 : if (txids.empty()) {
64 [ # # # # ]: 0 : throw JSONRPCError(RPC_INVALID_PARAMETER, "Parameter 'txids' cannot be empty");
65 : : }
66 [ # # # # ]: 0 : for (unsigned int idx = 0; idx < txids.size(); idx++) {
67 [ # # # # : 0 : auto ret{setTxids.insert(Txid::FromUint256(ParseHashV(txids[idx], "txid")))};
# # ]
68 [ # # ]: 0 : if (!ret.second) {
69 [ # # # # : 0 : throw JSONRPCError(RPC_INVALID_PARAMETER, std::string("Invalid parameter, duplicated txid: ") + txids[idx].get_str());
# # # # #
# ]
70 : : }
71 : : }
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()) {
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) {
81 [ # # # # ]: 0 : throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Block not found");
82 : : }
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) {
89 [ # # # # ]: 0 : const Coin& coin{AccessByTxid(active_chainstate.CoinsTip(), tx)};
90 [ # # ]: 0 : if (!coin.IsSpent()) {
91 [ # # # # ]: 0 : pblockindex = active_chainstate.m_chain[coin.nHeight];
92 : : break;
93 : : }
94 : : }
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) {
100 [ # # ]: 0 : g_txindex->BlockUntilSyncedToCurrentChain();
101 : : }
102 : :
103 [ # # ]: 0 : if (pblockindex == nullptr) {
104 [ # # ]: 0 : const CTransactionRef tx = GetTransaction(/*block_index=*/nullptr, /*mempool=*/nullptr, *setTxids.begin(), chainman.m_blockman, hashBlock);
105 [ # # # # ]: 0 : if (!tx || hashBlock.IsNull()) {
106 [ # # # # ]: 0 : throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Transaction not yet in block");
107 : : }
108 : :
109 [ # # ]: 0 : LOCK(cs_main);
110 [ # # ]: 0 : pblockindex = chainman.m_blockman.LookupBlockIndex(hashBlock);
111 [ # # ]: 0 : if (!pblockindex) {
112 [ # # # # ]: 0 : throw JSONRPCError(RPC_INTERNAL_ERROR, "Transaction index corrupt");
113 : : }
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)) {
122 [ # # # # ]: 0 : throw JSONRPCError(RPC_INTERNAL_ERROR, "Can't read block from disk");
123 : : }
124 : :
125 : 0 : unsigned int ntxFound = 0;
126 [ # # ]: 0 : for (const auto& tx : block.vtx) {
127 [ # # ]: 0 : if (setTxids.contains(tx->GetHash())) {
128 : 0 : ntxFound++;
129 : : }
130 : : }
131 [ # # ]: 0 : if (ntxFound != setTxids.size()) {
132 [ # # # # ]: 0 : throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Not all transactions found in specified or retrieved block");
133 : : }
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 [ + - + - : 774 : };
+ - + + +
+ - - -
- ]
142 [ + - + - : 516 : }
+ - - - ]
143 : :
144 : 86 : static RPCMethod verifytxoutproof()
145 : : {
146 : 86 : return RPCMethod{
147 : 86 : "verifytxoutproof",
148 [ + - ]: 172 : "Verifies that a proof points to a transaction in a block, returning the transaction it commits to\n"
149 : : "and throwing an RPC error if the block is not in our best chain\n",
150 : : {
151 [ + - + - ]: 172 : {"proof", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "The hex-encoded proof generated by gettxoutproof"},
152 : : },
153 [ + - ]: 172 : RPCResult{
154 [ + - + - ]: 172 : RPCResult::Type::ARR, "", "",
155 : : {
156 [ + - + - ]: 172 : {RPCResult::Type::STR_HEX, "txid", "The txid(s) which the proof commits to, or empty array if the proof cannot be validated."},
157 : : }
158 [ + - + - : 344 : },
+ + - - ]
159 [ + - + - ]: 258 : RPCExamples{""},
160 : 86 : [](const RPCMethod& self, const JSONRPCRequest& request) -> UniValue
161 : : {
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)
170 : : 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) {
# # ]
177 [ # # # # ]: 0 : throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Block not found in chain");
178 : : }
179 : :
180 : : // Check if proof is valid, only add results if so
181 [ # # ]: 0 : if (pindex->nTx == merkleBlock.txn.GetNumTransactions()) {
182 [ # # ]: 0 : for (const auto& txid : vMatch) {
183 [ # # # # : 0 : res.push_back(txid.GetHex());
# # ]
184 : : }
185 : : }
186 : :
187 [ # # ]: 0 : return res;
188 : 0 : },
189 [ + - + - : 430 : };
+ + - - ]
190 [ + - + - ]: 344 : }
191 : :
192 : 189 : void RegisterTxoutProofRPCCommands(CRPCTable& t)
193 : : {
194 : 189 : static const CRPCCommand commands[]{
195 [ + - ]: 86 : {"blockchain", &gettxoutproof},
196 [ + - ]: 86 : {"blockchain", &verifytxoutproof},
197 [ + + + - : 275 : };
+ - + - -
- ]
198 [ + + ]: 567 : for (const auto& c : commands) {
199 : 378 : t.appendCommand(c.name, &c);
200 : : }
201 : 189 : }
|