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