Skip to content

Commit 3004126

Browse files
committed
Merge bitcoin/bitcoin#24582: Move txoutproof RPCs to txoutproof.cpp
fa2d176 Move txoutproof RPCs to txoutproof.cpp (MarcoFalke) Pull request description: The txoutproof RPCs don't really fit into `rawtransaction.cpp`, as they deal with txids, not with raw transactions. As they are placed in the `blockchain` RPC category, they could be moved there. However, `blockchain.cpp` already takes about 20 seconds to compile (and `rawtransaction.cpp` even longer), so move them to a separate file. Can be reviewed with `--color-moved=dimmed-zebra --color-moved-ws=ignore-all-space`. ACKs for top commit: achow101: ACK fa2d176 theStack: Concept and code-review ACK fa2d176 Tree-SHA512: 6250e5f87b6237f604d69643f9a809b238702d73f041792c537aeadeafdb60ab8e0dca1d83347d0d6c85900ce179df14365ae303ca3930ed33a528a862f85aa3
2 parents 3ab96f2 + fa2d176 commit 3004126

File tree

4 files changed

+186
-160
lines changed

4 files changed

+186
-160
lines changed

src/Makefile.am

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -378,6 +378,7 @@ libbitcoin_node_a_SOURCES = \
378378
rpc/rawtransaction.cpp \
379379
rpc/server.cpp \
380380
rpc/server_util.cpp \
381+
rpc/txoutproof.cpp \
381382
script/sigcache.cpp \
382383
shutdown.cpp \
383384
signet.cpp \

src/rpc/rawtransaction.cpp

Lines changed: 0 additions & 153 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@
1111
#include <core_io.h>
1212
#include <index/txindex.h>
1313
#include <key_io.h>
14-
#include <merkleblock.h>
1514
#include <node/blockstorage.h>
1615
#include <node/coin.h>
1716
#include <node/context.h>
@@ -268,155 +267,6 @@ static RPCHelpMan getrawtransaction()
268267
};
269268
}
270269

271-
static RPCHelpMan gettxoutproof()
272-
{
273-
return RPCHelpMan{"gettxoutproof",
274-
"\nReturns a hex-encoded proof that \"txid\" was included in a block.\n"
275-
"\nNOTE: By default this function only works sometimes. This is when there is an\n"
276-
"unspent output in the utxo for this transaction. To make it always work,\n"
277-
"you need to maintain a transaction index, using the -txindex command line option or\n"
278-
"specify the block in which the transaction is included manually (by blockhash).\n",
279-
{
280-
{"txids", RPCArg::Type::ARR, RPCArg::Optional::NO, "The txids to filter",
281-
{
282-
{"txid", RPCArg::Type::STR_HEX, RPCArg::Optional::OMITTED, "A transaction hash"},
283-
},
284-
},
285-
{"blockhash", RPCArg::Type::STR_HEX, RPCArg::Optional::OMITTED_NAMED_ARG, "If specified, looks for txid in the block with this hash"},
286-
},
287-
RPCResult{
288-
RPCResult::Type::STR, "data", "A string that is a serialized, hex-encoded data for the proof."
289-
},
290-
RPCExamples{""},
291-
[&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
292-
{
293-
std::set<uint256> setTxids;
294-
UniValue txids = request.params[0].get_array();
295-
if (txids.empty()) {
296-
throw JSONRPCError(RPC_INVALID_PARAMETER, "Parameter 'txids' cannot be empty");
297-
}
298-
for (unsigned int idx = 0; idx < txids.size(); idx++) {
299-
auto ret = setTxids.insert(ParseHashV(txids[idx], "txid"));
300-
if (!ret.second) {
301-
throw JSONRPCError(RPC_INVALID_PARAMETER, std::string("Invalid parameter, duplicated txid: ") + txids[idx].get_str());
302-
}
303-
}
304-
305-
const CBlockIndex* pblockindex = nullptr;
306-
uint256 hashBlock;
307-
ChainstateManager& chainman = EnsureAnyChainman(request.context);
308-
if (!request.params[1].isNull()) {
309-
LOCK(cs_main);
310-
hashBlock = ParseHashV(request.params[1], "blockhash");
311-
pblockindex = chainman.m_blockman.LookupBlockIndex(hashBlock);
312-
if (!pblockindex) {
313-
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Block not found");
314-
}
315-
} else {
316-
LOCK(cs_main);
317-
CChainState& active_chainstate = chainman.ActiveChainstate();
318-
319-
// Loop through txids and try to find which block they're in. Exit loop once a block is found.
320-
for (const auto& tx : setTxids) {
321-
const Coin& coin = AccessByTxid(active_chainstate.CoinsTip(), tx);
322-
if (!coin.IsSpent()) {
323-
pblockindex = active_chainstate.m_chain[coin.nHeight];
324-
break;
325-
}
326-
}
327-
}
328-
329-
330-
// Allow txindex to catch up if we need to query it and before we acquire cs_main.
331-
if (g_txindex && !pblockindex) {
332-
g_txindex->BlockUntilSyncedToCurrentChain();
333-
}
334-
335-
LOCK(cs_main);
336-
337-
if (pblockindex == nullptr) {
338-
const CTransactionRef tx = GetTransaction(/* block_index */ nullptr, /* mempool */ nullptr, *setTxids.begin(), Params().GetConsensus(), hashBlock);
339-
if (!tx || hashBlock.IsNull()) {
340-
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Transaction not yet in block");
341-
}
342-
pblockindex = chainman.m_blockman.LookupBlockIndex(hashBlock);
343-
if (!pblockindex) {
344-
throw JSONRPCError(RPC_INTERNAL_ERROR, "Transaction index corrupt");
345-
}
346-
}
347-
348-
CBlock block;
349-
if (!ReadBlockFromDisk(block, pblockindex, Params().GetConsensus())) {
350-
throw JSONRPCError(RPC_INTERNAL_ERROR, "Can't read block from disk");
351-
}
352-
353-
unsigned int ntxFound = 0;
354-
for (const auto& tx : block.vtx) {
355-
if (setTxids.count(tx->GetHash())) {
356-
ntxFound++;
357-
}
358-
}
359-
if (ntxFound != setTxids.size()) {
360-
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Not all transactions found in specified or retrieved block");
361-
}
362-
363-
CDataStream ssMB(SER_NETWORK, PROTOCOL_VERSION | SERIALIZE_TRANSACTION_NO_WITNESS);
364-
CMerkleBlock mb(block, setTxids);
365-
ssMB << mb;
366-
std::string strHex = HexStr(ssMB);
367-
return strHex;
368-
},
369-
};
370-
}
371-
372-
static RPCHelpMan verifytxoutproof()
373-
{
374-
return RPCHelpMan{"verifytxoutproof",
375-
"\nVerifies that a proof points to a transaction in a block, returning the transaction it commits to\n"
376-
"and throwing an RPC error if the block is not in our best chain\n",
377-
{
378-
{"proof", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "The hex-encoded proof generated by gettxoutproof"},
379-
},
380-
RPCResult{
381-
RPCResult::Type::ARR, "", "",
382-
{
383-
{RPCResult::Type::STR_HEX, "txid", "The txid(s) which the proof commits to, or empty array if the proof cannot be validated."},
384-
}
385-
},
386-
RPCExamples{""},
387-
[&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
388-
{
389-
CDataStream ssMB(ParseHexV(request.params[0], "proof"), SER_NETWORK, PROTOCOL_VERSION | SERIALIZE_TRANSACTION_NO_WITNESS);
390-
CMerkleBlock merkleBlock;
391-
ssMB >> merkleBlock;
392-
393-
UniValue res(UniValue::VARR);
394-
395-
std::vector<uint256> vMatch;
396-
std::vector<unsigned int> vIndex;
397-
if (merkleBlock.txn.ExtractMatches(vMatch, vIndex) != merkleBlock.header.hashMerkleRoot)
398-
return res;
399-
400-
ChainstateManager& chainman = EnsureAnyChainman(request.context);
401-
LOCK(cs_main);
402-
403-
const CBlockIndex* pindex = chainman.m_blockman.LookupBlockIndex(merkleBlock.header.GetHash());
404-
if (!pindex || !chainman.ActiveChain().Contains(pindex) || pindex->nTx == 0) {
405-
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Block not found in chain");
406-
}
407-
408-
// Check if proof is valid, only add results if so
409-
if (pindex->nTx == merkleBlock.txn.GetNumTransactions()) {
410-
for (const uint256& hash : vMatch) {
411-
res.push_back(hash.GetHex());
412-
}
413-
}
414-
415-
return res;
416-
},
417-
};
418-
}
419-
420270
static RPCHelpMan createrawtransaction()
421271
{
422272
return RPCHelpMan{"createrawtransaction",
@@ -2089,9 +1939,6 @@ static const CRPCCommand commands[] =
20891939
{ "rawtransactions", &utxoupdatepsbt, },
20901940
{ "rawtransactions", &joinpsbts, },
20911941
{ "rawtransactions", &analyzepsbt, },
2092-
2093-
{ "blockchain", &gettxoutproof, },
2094-
{ "blockchain", &verifytxoutproof, },
20951942
};
20961943
// clang-format on
20971944
for (const auto& c : commands) {

src/rpc/register.h

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -9,25 +9,20 @@
99
* headers for everything under src/rpc/ */
1010
class CRPCTable;
1111

12-
/** Register block chain RPC commands */
1312
void RegisterBlockchainRPCCommands(CRPCTable &tableRPC);
14-
/** Register mempool RPC commands */
1513
void RegisterMempoolRPCCommands(CRPCTable&);
16-
/** Register P2P networking RPC commands */
14+
void RegisterTxoutProofRPCCommands(CRPCTable&);
1715
void RegisterNetRPCCommands(CRPCTable &tableRPC);
18-
/** Register miscellaneous RPC commands */
1916
void RegisterMiscRPCCommands(CRPCTable &tableRPC);
20-
/** Register mining RPC commands */
2117
void RegisterMiningRPCCommands(CRPCTable &tableRPC);
22-
/** Register raw transaction RPC commands */
2318
void RegisterRawTransactionRPCCommands(CRPCTable &tableRPC);
24-
/** Register raw transaction RPC commands */
2519
void RegisterSignerRPCCommands(CRPCTable &tableRPC);
2620

2721
static inline void RegisterAllCoreRPCCommands(CRPCTable &t)
2822
{
2923
RegisterBlockchainRPCCommands(t);
3024
RegisterMempoolRPCCommands(t);
25+
RegisterTxoutProofRPCCommands(t);
3126
RegisterNetRPCCommands(t);
3227
RegisterMiscRPCCommands(t);
3328
RegisterMiningRPCCommands(t);

src/rpc/txoutproof.cpp

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

0 commit comments

Comments
 (0)