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/server.h>
14 : #include <rpc/server_util.h>
15 : #include <rpc/util.h>
16 : #include <univalue.h>
17 : #include <util/strencodings.h>
18 : #include <validation.h>
19 :
20 : using node::GetTransaction;
21 : using node::ReadBlockFromDisk;
22 :
23 6204 : static RPCHelpMan gettxoutproof()
24 : {
25 12408 : return RPCHelpMan{"gettxoutproof",
26 6204 : "\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 18612 : {
32 12408 : {"txids", RPCArg::Type::ARR, RPCArg::Optional::NO, "The txids to filter",
33 12408 : {
34 6204 : {"txid", RPCArg::Type::STR_HEX, RPCArg::Optional::OMITTED, "A transaction hash"},
35 : },
36 : },
37 6204 : {"blockhash", RPCArg::Type::STR_HEX, RPCArg::Optional::OMITTED_NAMED_ARG, "If specified, looks for txid in the block with this hash"},
38 : },
39 6204 : RPCResult{
40 6204 : RPCResult::Type::STR, "data", "A string that is a serialized, hex-encoded data for the proof."
41 : },
42 6204 : RPCExamples{
43 6204 : HelpExampleCli("gettxoutproof", "'[\"mytxid\",...]'")
44 6204 : + HelpExampleCli("gettxoutproof", "'[\"mytxid\",...]' \"blockhash\"")
45 6204 : + HelpExampleRpc("gettxoutproof", "[\"mytxid\",...], \"blockhash\"")
46 : },
47 6252 : [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
48 : {
49 48 : std::set<uint256> setTxids;
50 48 : UniValue txids = request.params[0].get_array();
51 48 : if (txids.empty()) {
52 2 : throw JSONRPCError(RPC_INVALID_PARAMETER, "Parameter 'txids' cannot be empty");
53 : }
54 100 : for (unsigned int idx = 0; idx < txids.size(); idx++) {
55 60 : auto ret = setTxids.insert(ParseHashV(txids[idx], "txid"));
56 56 : if (!ret.second) {
57 2 : throw JSONRPCError(RPC_INVALID_PARAMETER, std::string("Invalid parameter, duplicated txid: ") + txids[idx].get_str());
58 : }
59 54 : }
60 :
61 40 : const CBlockIndex* pblockindex = nullptr;
62 40 : uint256 hashBlock;
63 40 : ChainstateManager& chainman = EnsureAnyChainman(request.context);
64 40 : if (!request.params[1].isNull()) {
65 10 : LOCK(cs_main);
66 10 : hashBlock = ParseHashV(request.params[1], "blockhash");
67 6 : pblockindex = chainman.m_blockman.LookupBlockIndex(hashBlock);
68 6 : if (!pblockindex) {
69 2 : throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Block not found");
70 : }
71 10 : } else {
72 30 : LOCK(cs_main);
73 30 : CChainState& active_chainstate = chainman.ActiveChainstate();
74 :
75 : // Loop through txids and try to find which block they're in. Exit loop once a block is found.
76 40 : for (const auto& tx : setTxids) {
77 36 : const Coin& coin = AccessByTxid(active_chainstate.CoinsTip(), tx);
78 36 : if (!coin.IsSpent()) {
79 26 : pblockindex = active_chainstate.m_chain[coin.nHeight];
80 26 : break;
81 : }
82 : }
83 30 : }
84 :
85 :
86 : // Allow txindex to catch up if we need to query it and before we acquire cs_main.
87 34 : if (g_txindex && !pblockindex) {
88 4 : g_txindex->BlockUntilSyncedToCurrentChain();
89 4 : }
90 :
91 34 : if (pblockindex == nullptr) {
92 4 : const CTransactionRef tx = GetTransaction(/*block_index=*/nullptr, /*mempool=*/nullptr, *setTxids.begin(), Params().GetConsensus(), hashBlock);
93 4 : if (!tx || hashBlock.IsNull()) {
94 2 : throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Transaction not yet in block");
95 : }
96 4 : pblockindex = WITH_LOCK(::cs_main, return chainman.m_blockman.LookupBlockIndex(hashBlock));
97 2 : if (!pblockindex) {
98 0 : throw JSONRPCError(RPC_INTERNAL_ERROR, "Transaction index corrupt");
99 : }
100 4 : }
101 :
102 32 : CBlock block;
103 32 : if (!ReadBlockFromDisk(block, pblockindex, Params().GetConsensus())) {
104 0 : throw JSONRPCError(RPC_INTERNAL_ERROR, "Can't read block from disk");
105 : }
106 :
107 32 : unsigned int ntxFound = 0;
108 114 : for (const auto& tx : block.vtx) {
109 82 : if (setTxids.count(tx->GetHash())) {
110 42 : ntxFound++;
111 42 : }
112 : }
113 32 : if (ntxFound != setTxids.size()) {
114 2 : throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Not all transactions found in specified or retrieved block");
115 : }
116 :
117 30 : CDataStream ssMB(SER_NETWORK, PROTOCOL_VERSION);
118 30 : CMerkleBlock mb(block, setTxids);
119 30 : ssMB << mb;
120 30 : std::string strHex = HexStr(ssMB);
121 30 : return strHex;
122 62 : },
123 : };
124 0 : }
125 :
126 6231 : static RPCHelpMan verifytxoutproof()
127 : {
128 12462 : return RPCHelpMan{"verifytxoutproof",
129 6231 : "\nVerifies that a proof points to a transaction in a block, returning the transaction it commits to\n"
130 : "and throwing an RPC error if the block is not in our best chain\n",
131 12462 : {
132 6231 : {"proof", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "The hex-encoded proof generated by gettxoutproof"},
133 : },
134 6231 : RPCResult{
135 6231 : RPCResult::Type::ARR, "", "",
136 12462 : {
137 6231 : {RPCResult::Type::STR_HEX, "txid", "The txid(s) which the proof commits to, or empty array if the proof cannot be validated."},
138 : }
139 : },
140 6231 : RPCExamples{
141 6231 : HelpExampleCli("verifytxoutproof", "\"proof\"")
142 6231 : + HelpExampleRpc("gettxoutproof", "\"proof\"")
143 : },
144 6306 : [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
145 : {
146 75 : CDataStream ssMB(ParseHexV(request.params[0], "proof"), SER_NETWORK, PROTOCOL_VERSION);
147 75 : CMerkleBlock merkleBlock;
148 75 : ssMB >> merkleBlock;
149 :
150 75 : UniValue res(UniValue::VARR);
151 :
152 75 : std::vector<uint256> vMatch;
153 75 : std::vector<unsigned int> vIndex;
154 75 : if (merkleBlock.txn.ExtractMatches(vMatch, vIndex) != merkleBlock.header.hashMerkleRoot)
155 0 : return res;
156 :
157 75 : ChainstateManager& chainman = EnsureAnyChainman(request.context);
158 75 : LOCK(cs_main);
159 :
160 75 : const CBlockIndex* pindex = chainman.m_blockman.LookupBlockIndex(merkleBlock.header.GetHash());
161 75 : if (!pindex || !chainman.ActiveChain().Contains(pindex) || pindex->nTx == 0) {
162 0 : throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Block not found in chain");
163 : }
164 :
165 : // Check if proof is valid, only add results if so
166 75 : if (pindex->nTx == merkleBlock.txn.GetNumTransactions()) {
167 156 : for (const uint256& hash : vMatch) {
168 85 : res.push_back(hash.GetHex());
169 : }
170 71 : }
171 :
172 75 : return res;
173 75 : },
174 : };
175 0 : }
176 :
177 3201 : void RegisterTxoutProofRPCCommands(CRPCTable& t)
178 : {
179 9339 : static const CRPCCommand commands[]{
180 3069 : {"blockchain", &gettxoutproof},
181 3069 : {"blockchain", &verifytxoutproof},
182 : };
183 9603 : for (const auto& c : commands) {
184 6402 : t.appendCommand(c.name, &c);
185 : }
186 3201 : }
|