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