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