mirror of
https://github.com/bitcoin/bitcoin.git
synced 2025-06-23 23:33:47 +02:00
Move txoutproof RPCs to txoutproof.cpp
This commit is contained in:
parent
bf2c0fb2a2
commit
fa2d176016
@ -378,6 +378,7 @@ libbitcoin_node_a_SOURCES = \
|
|||||||
rpc/rawtransaction.cpp \
|
rpc/rawtransaction.cpp \
|
||||||
rpc/server.cpp \
|
rpc/server.cpp \
|
||||||
rpc/server_util.cpp \
|
rpc/server_util.cpp \
|
||||||
|
rpc/txoutproof.cpp \
|
||||||
script/sigcache.cpp \
|
script/sigcache.cpp \
|
||||||
shutdown.cpp \
|
shutdown.cpp \
|
||||||
signet.cpp \
|
signet.cpp \
|
||||||
|
@ -11,7 +11,6 @@
|
|||||||
#include <core_io.h>
|
#include <core_io.h>
|
||||||
#include <index/txindex.h>
|
#include <index/txindex.h>
|
||||||
#include <key_io.h>
|
#include <key_io.h>
|
||||||
#include <merkleblock.h>
|
|
||||||
#include <node/blockstorage.h>
|
#include <node/blockstorage.h>
|
||||||
#include <node/coin.h>
|
#include <node/coin.h>
|
||||||
#include <node/context.h>
|
#include <node/context.h>
|
||||||
@ -268,155 +267,6 @@ static RPCHelpMan getrawtransaction()
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
static RPCHelpMan gettxoutproof()
|
|
||||||
{
|
|
||||||
return RPCHelpMan{"gettxoutproof",
|
|
||||||
"\nReturns a hex-encoded proof that \"txid\" was included in a block.\n"
|
|
||||||
"\nNOTE: By default this function only works sometimes. This is when there is an\n"
|
|
||||||
"unspent output in the utxo for this transaction. To make it always work,\n"
|
|
||||||
"you need to maintain a transaction index, using the -txindex command line option or\n"
|
|
||||||
"specify the block in which the transaction is included manually (by blockhash).\n",
|
|
||||||
{
|
|
||||||
{"txids", RPCArg::Type::ARR, RPCArg::Optional::NO, "The txids to filter",
|
|
||||||
{
|
|
||||||
{"txid", RPCArg::Type::STR_HEX, RPCArg::Optional::OMITTED, "A transaction hash"},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{"blockhash", RPCArg::Type::STR_HEX, RPCArg::Optional::OMITTED_NAMED_ARG, "If specified, looks for txid in the block with this hash"},
|
|
||||||
},
|
|
||||||
RPCResult{
|
|
||||||
RPCResult::Type::STR, "data", "A string that is a serialized, hex-encoded data for the proof."
|
|
||||||
},
|
|
||||||
RPCExamples{""},
|
|
||||||
[&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
|
|
||||||
{
|
|
||||||
std::set<uint256> setTxids;
|
|
||||||
UniValue txids = request.params[0].get_array();
|
|
||||||
if (txids.empty()) {
|
|
||||||
throw JSONRPCError(RPC_INVALID_PARAMETER, "Parameter 'txids' cannot be empty");
|
|
||||||
}
|
|
||||||
for (unsigned int idx = 0; idx < txids.size(); idx++) {
|
|
||||||
auto ret = setTxids.insert(ParseHashV(txids[idx], "txid"));
|
|
||||||
if (!ret.second) {
|
|
||||||
throw JSONRPCError(RPC_INVALID_PARAMETER, std::string("Invalid parameter, duplicated txid: ") + txids[idx].get_str());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const CBlockIndex* pblockindex = nullptr;
|
|
||||||
uint256 hashBlock;
|
|
||||||
ChainstateManager& chainman = EnsureAnyChainman(request.context);
|
|
||||||
if (!request.params[1].isNull()) {
|
|
||||||
LOCK(cs_main);
|
|
||||||
hashBlock = ParseHashV(request.params[1], "blockhash");
|
|
||||||
pblockindex = chainman.m_blockman.LookupBlockIndex(hashBlock);
|
|
||||||
if (!pblockindex) {
|
|
||||||
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Block not found");
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
LOCK(cs_main);
|
|
||||||
CChainState& active_chainstate = chainman.ActiveChainstate();
|
|
||||||
|
|
||||||
// Loop through txids and try to find which block they're in. Exit loop once a block is found.
|
|
||||||
for (const auto& tx : setTxids) {
|
|
||||||
const Coin& coin = AccessByTxid(active_chainstate.CoinsTip(), tx);
|
|
||||||
if (!coin.IsSpent()) {
|
|
||||||
pblockindex = active_chainstate.m_chain[coin.nHeight];
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// Allow txindex to catch up if we need to query it and before we acquire cs_main.
|
|
||||||
if (g_txindex && !pblockindex) {
|
|
||||||
g_txindex->BlockUntilSyncedToCurrentChain();
|
|
||||||
}
|
|
||||||
|
|
||||||
LOCK(cs_main);
|
|
||||||
|
|
||||||
if (pblockindex == nullptr) {
|
|
||||||
const CTransactionRef tx = GetTransaction(/* block_index */ nullptr, /* mempool */ nullptr, *setTxids.begin(), Params().GetConsensus(), hashBlock);
|
|
||||||
if (!tx || hashBlock.IsNull()) {
|
|
||||||
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Transaction not yet in block");
|
|
||||||
}
|
|
||||||
pblockindex = chainman.m_blockman.LookupBlockIndex(hashBlock);
|
|
||||||
if (!pblockindex) {
|
|
||||||
throw JSONRPCError(RPC_INTERNAL_ERROR, "Transaction index corrupt");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
CBlock block;
|
|
||||||
if (!ReadBlockFromDisk(block, pblockindex, Params().GetConsensus())) {
|
|
||||||
throw JSONRPCError(RPC_INTERNAL_ERROR, "Can't read block from disk");
|
|
||||||
}
|
|
||||||
|
|
||||||
unsigned int ntxFound = 0;
|
|
||||||
for (const auto& tx : block.vtx) {
|
|
||||||
if (setTxids.count(tx->GetHash())) {
|
|
||||||
ntxFound++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (ntxFound != setTxids.size()) {
|
|
||||||
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Not all transactions found in specified or retrieved block");
|
|
||||||
}
|
|
||||||
|
|
||||||
CDataStream ssMB(SER_NETWORK, PROTOCOL_VERSION | SERIALIZE_TRANSACTION_NO_WITNESS);
|
|
||||||
CMerkleBlock mb(block, setTxids);
|
|
||||||
ssMB << mb;
|
|
||||||
std::string strHex = HexStr(ssMB);
|
|
||||||
return strHex;
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
static RPCHelpMan verifytxoutproof()
|
|
||||||
{
|
|
||||||
return RPCHelpMan{"verifytxoutproof",
|
|
||||||
"\nVerifies that a proof points to a transaction in a block, returning the transaction it commits to\n"
|
|
||||||
"and throwing an RPC error if the block is not in our best chain\n",
|
|
||||||
{
|
|
||||||
{"proof", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "The hex-encoded proof generated by gettxoutproof"},
|
|
||||||
},
|
|
||||||
RPCResult{
|
|
||||||
RPCResult::Type::ARR, "", "",
|
|
||||||
{
|
|
||||||
{RPCResult::Type::STR_HEX, "txid", "The txid(s) which the proof commits to, or empty array if the proof cannot be validated."},
|
|
||||||
}
|
|
||||||
},
|
|
||||||
RPCExamples{""},
|
|
||||||
[&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
|
|
||||||
{
|
|
||||||
CDataStream ssMB(ParseHexV(request.params[0], "proof"), SER_NETWORK, PROTOCOL_VERSION | SERIALIZE_TRANSACTION_NO_WITNESS);
|
|
||||||
CMerkleBlock merkleBlock;
|
|
||||||
ssMB >> merkleBlock;
|
|
||||||
|
|
||||||
UniValue res(UniValue::VARR);
|
|
||||||
|
|
||||||
std::vector<uint256> vMatch;
|
|
||||||
std::vector<unsigned int> vIndex;
|
|
||||||
if (merkleBlock.txn.ExtractMatches(vMatch, vIndex) != merkleBlock.header.hashMerkleRoot)
|
|
||||||
return res;
|
|
||||||
|
|
||||||
ChainstateManager& chainman = EnsureAnyChainman(request.context);
|
|
||||||
LOCK(cs_main);
|
|
||||||
|
|
||||||
const CBlockIndex* pindex = chainman.m_blockman.LookupBlockIndex(merkleBlock.header.GetHash());
|
|
||||||
if (!pindex || !chainman.ActiveChain().Contains(pindex) || pindex->nTx == 0) {
|
|
||||||
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Block not found in chain");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if proof is valid, only add results if so
|
|
||||||
if (pindex->nTx == merkleBlock.txn.GetNumTransactions()) {
|
|
||||||
for (const uint256& hash : vMatch) {
|
|
||||||
res.push_back(hash.GetHex());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return res;
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
static RPCHelpMan createrawtransaction()
|
static RPCHelpMan createrawtransaction()
|
||||||
{
|
{
|
||||||
return RPCHelpMan{"createrawtransaction",
|
return RPCHelpMan{"createrawtransaction",
|
||||||
@ -2089,9 +1939,6 @@ static const CRPCCommand commands[] =
|
|||||||
{ "rawtransactions", &utxoupdatepsbt, },
|
{ "rawtransactions", &utxoupdatepsbt, },
|
||||||
{ "rawtransactions", &joinpsbts, },
|
{ "rawtransactions", &joinpsbts, },
|
||||||
{ "rawtransactions", &analyzepsbt, },
|
{ "rawtransactions", &analyzepsbt, },
|
||||||
|
|
||||||
{ "blockchain", &gettxoutproof, },
|
|
||||||
{ "blockchain", &verifytxoutproof, },
|
|
||||||
};
|
};
|
||||||
// clang-format on
|
// clang-format on
|
||||||
for (const auto& c : commands) {
|
for (const auto& c : commands) {
|
||||||
|
@ -9,25 +9,20 @@
|
|||||||
* headers for everything under src/rpc/ */
|
* headers for everything under src/rpc/ */
|
||||||
class CRPCTable;
|
class CRPCTable;
|
||||||
|
|
||||||
/** Register block chain RPC commands */
|
|
||||||
void RegisterBlockchainRPCCommands(CRPCTable &tableRPC);
|
void RegisterBlockchainRPCCommands(CRPCTable &tableRPC);
|
||||||
/** Register mempool RPC commands */
|
|
||||||
void RegisterMempoolRPCCommands(CRPCTable&);
|
void RegisterMempoolRPCCommands(CRPCTable&);
|
||||||
/** Register P2P networking RPC commands */
|
void RegisterTxoutProofRPCCommands(CRPCTable&);
|
||||||
void RegisterNetRPCCommands(CRPCTable &tableRPC);
|
void RegisterNetRPCCommands(CRPCTable &tableRPC);
|
||||||
/** Register miscellaneous RPC commands */
|
|
||||||
void RegisterMiscRPCCommands(CRPCTable &tableRPC);
|
void RegisterMiscRPCCommands(CRPCTable &tableRPC);
|
||||||
/** Register mining RPC commands */
|
|
||||||
void RegisterMiningRPCCommands(CRPCTable &tableRPC);
|
void RegisterMiningRPCCommands(CRPCTable &tableRPC);
|
||||||
/** Register raw transaction RPC commands */
|
|
||||||
void RegisterRawTransactionRPCCommands(CRPCTable &tableRPC);
|
void RegisterRawTransactionRPCCommands(CRPCTable &tableRPC);
|
||||||
/** Register raw transaction RPC commands */
|
|
||||||
void RegisterSignerRPCCommands(CRPCTable &tableRPC);
|
void RegisterSignerRPCCommands(CRPCTable &tableRPC);
|
||||||
|
|
||||||
static inline void RegisterAllCoreRPCCommands(CRPCTable &t)
|
static inline void RegisterAllCoreRPCCommands(CRPCTable &t)
|
||||||
{
|
{
|
||||||
RegisterBlockchainRPCCommands(t);
|
RegisterBlockchainRPCCommands(t);
|
||||||
RegisterMempoolRPCCommands(t);
|
RegisterMempoolRPCCommands(t);
|
||||||
|
RegisterTxoutProofRPCCommands(t);
|
||||||
RegisterNetRPCCommands(t);
|
RegisterNetRPCCommands(t);
|
||||||
RegisterMiscRPCCommands(t);
|
RegisterMiscRPCCommands(t);
|
||||||
RegisterMiningRPCCommands(t);
|
RegisterMiningRPCCommands(t);
|
||||||
|
183
src/rpc/txoutproof.cpp
Normal file
183
src/rpc/txoutproof.cpp
Normal file
@ -0,0 +1,183 @@
|
|||||||
|
// Copyright (c) 2010 Satoshi Nakamoto
|
||||||
|
// Copyright (c) 2009-2022 The Bitcoin Core developers
|
||||||
|
// Distributed under the MIT software license, see the accompanying
|
||||||
|
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
|
||||||
|
|
||||||
|
#include <chain.h>
|
||||||
|
#include <chainparams.h>
|
||||||
|
#include <coins.h>
|
||||||
|
#include <index/txindex.h>
|
||||||
|
#include <merkleblock.h>
|
||||||
|
#include <node/blockstorage.h>
|
||||||
|
#include <primitives/transaction.h>
|
||||||
|
#include <rpc/server.h>
|
||||||
|
#include <rpc/server_util.h>
|
||||||
|
#include <rpc/util.h>
|
||||||
|
#include <univalue.h>
|
||||||
|
#include <util/strencodings.h>
|
||||||
|
#include <validation.h>
|
||||||
|
|
||||||
|
using node::GetTransaction;
|
||||||
|
using node::ReadBlockFromDisk;
|
||||||
|
|
||||||
|
static RPCHelpMan gettxoutproof()
|
||||||
|
{
|
||||||
|
return RPCHelpMan{"gettxoutproof",
|
||||||
|
"\nReturns a hex-encoded proof that \"txid\" was included in a block.\n"
|
||||||
|
"\nNOTE: By default this function only works sometimes. This is when there is an\n"
|
||||||
|
"unspent output in the utxo for this transaction. To make it always work,\n"
|
||||||
|
"you need to maintain a transaction index, using the -txindex command line option or\n"
|
||||||
|
"specify the block in which the transaction is included manually (by blockhash).\n",
|
||||||
|
{
|
||||||
|
{"txids", RPCArg::Type::ARR, RPCArg::Optional::NO, "The txids to filter",
|
||||||
|
{
|
||||||
|
{"txid", RPCArg::Type::STR_HEX, RPCArg::Optional::OMITTED, "A transaction hash"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{"blockhash", RPCArg::Type::STR_HEX, RPCArg::Optional::OMITTED_NAMED_ARG, "If specified, looks for txid in the block with this hash"},
|
||||||
|
},
|
||||||
|
RPCResult{
|
||||||
|
RPCResult::Type::STR, "data", "A string that is a serialized, hex-encoded data for the proof."
|
||||||
|
},
|
||||||
|
RPCExamples{""},
|
||||||
|
[&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
|
||||||
|
{
|
||||||
|
std::set<uint256> setTxids;
|
||||||
|
UniValue txids = request.params[0].get_array();
|
||||||
|
if (txids.empty()) {
|
||||||
|
throw JSONRPCError(RPC_INVALID_PARAMETER, "Parameter 'txids' cannot be empty");
|
||||||
|
}
|
||||||
|
for (unsigned int idx = 0; idx < txids.size(); idx++) {
|
||||||
|
auto ret = setTxids.insert(ParseHashV(txids[idx], "txid"));
|
||||||
|
if (!ret.second) {
|
||||||
|
throw JSONRPCError(RPC_INVALID_PARAMETER, std::string("Invalid parameter, duplicated txid: ") + txids[idx].get_str());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const CBlockIndex* pblockindex = nullptr;
|
||||||
|
uint256 hashBlock;
|
||||||
|
ChainstateManager& chainman = EnsureAnyChainman(request.context);
|
||||||
|
if (!request.params[1].isNull()) {
|
||||||
|
LOCK(cs_main);
|
||||||
|
hashBlock = ParseHashV(request.params[1], "blockhash");
|
||||||
|
pblockindex = chainman.m_blockman.LookupBlockIndex(hashBlock);
|
||||||
|
if (!pblockindex) {
|
||||||
|
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Block not found");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
LOCK(cs_main);
|
||||||
|
CChainState& active_chainstate = chainman.ActiveChainstate();
|
||||||
|
|
||||||
|
// Loop through txids and try to find which block they're in. Exit loop once a block is found.
|
||||||
|
for (const auto& tx : setTxids) {
|
||||||
|
const Coin& coin = AccessByTxid(active_chainstate.CoinsTip(), tx);
|
||||||
|
if (!coin.IsSpent()) {
|
||||||
|
pblockindex = active_chainstate.m_chain[coin.nHeight];
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Allow txindex to catch up if we need to query it and before we acquire cs_main.
|
||||||
|
if (g_txindex && !pblockindex) {
|
||||||
|
g_txindex->BlockUntilSyncedToCurrentChain();
|
||||||
|
}
|
||||||
|
|
||||||
|
LOCK(cs_main);
|
||||||
|
|
||||||
|
if (pblockindex == nullptr) {
|
||||||
|
const CTransactionRef tx = GetTransaction(/* block_index */ nullptr, /* mempool */ nullptr, *setTxids.begin(), Params().GetConsensus(), hashBlock);
|
||||||
|
if (!tx || hashBlock.IsNull()) {
|
||||||
|
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Transaction not yet in block");
|
||||||
|
}
|
||||||
|
pblockindex = chainman.m_blockman.LookupBlockIndex(hashBlock);
|
||||||
|
if (!pblockindex) {
|
||||||
|
throw JSONRPCError(RPC_INTERNAL_ERROR, "Transaction index corrupt");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
CBlock block;
|
||||||
|
if (!ReadBlockFromDisk(block, pblockindex, Params().GetConsensus())) {
|
||||||
|
throw JSONRPCError(RPC_INTERNAL_ERROR, "Can't read block from disk");
|
||||||
|
}
|
||||||
|
|
||||||
|
unsigned int ntxFound = 0;
|
||||||
|
for (const auto& tx : block.vtx) {
|
||||||
|
if (setTxids.count(tx->GetHash())) {
|
||||||
|
ntxFound++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (ntxFound != setTxids.size()) {
|
||||||
|
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Not all transactions found in specified or retrieved block");
|
||||||
|
}
|
||||||
|
|
||||||
|
CDataStream ssMB(SER_NETWORK, PROTOCOL_VERSION | SERIALIZE_TRANSACTION_NO_WITNESS);
|
||||||
|
CMerkleBlock mb(block, setTxids);
|
||||||
|
ssMB << mb;
|
||||||
|
std::string strHex = HexStr(ssMB);
|
||||||
|
return strHex;
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
static RPCHelpMan verifytxoutproof()
|
||||||
|
{
|
||||||
|
return RPCHelpMan{"verifytxoutproof",
|
||||||
|
"\nVerifies that a proof points to a transaction in a block, returning the transaction it commits to\n"
|
||||||
|
"and throwing an RPC error if the block is not in our best chain\n",
|
||||||
|
{
|
||||||
|
{"proof", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "The hex-encoded proof generated by gettxoutproof"},
|
||||||
|
},
|
||||||
|
RPCResult{
|
||||||
|
RPCResult::Type::ARR, "", "",
|
||||||
|
{
|
||||||
|
{RPCResult::Type::STR_HEX, "txid", "The txid(s) which the proof commits to, or empty array if the proof cannot be validated."},
|
||||||
|
}
|
||||||
|
},
|
||||||
|
RPCExamples{""},
|
||||||
|
[&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
|
||||||
|
{
|
||||||
|
CDataStream ssMB(ParseHexV(request.params[0], "proof"), SER_NETWORK, PROTOCOL_VERSION | SERIALIZE_TRANSACTION_NO_WITNESS);
|
||||||
|
CMerkleBlock merkleBlock;
|
||||||
|
ssMB >> merkleBlock;
|
||||||
|
|
||||||
|
UniValue res(UniValue::VARR);
|
||||||
|
|
||||||
|
std::vector<uint256> vMatch;
|
||||||
|
std::vector<unsigned int> vIndex;
|
||||||
|
if (merkleBlock.txn.ExtractMatches(vMatch, vIndex) != merkleBlock.header.hashMerkleRoot)
|
||||||
|
return res;
|
||||||
|
|
||||||
|
ChainstateManager& chainman = EnsureAnyChainman(request.context);
|
||||||
|
LOCK(cs_main);
|
||||||
|
|
||||||
|
const CBlockIndex* pindex = chainman.m_blockman.LookupBlockIndex(merkleBlock.header.GetHash());
|
||||||
|
if (!pindex || !chainman.ActiveChain().Contains(pindex) || pindex->nTx == 0) {
|
||||||
|
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Block not found in chain");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if proof is valid, only add results if so
|
||||||
|
if (pindex->nTx == merkleBlock.txn.GetNumTransactions()) {
|
||||||
|
for (const uint256& hash : vMatch) {
|
||||||
|
res.push_back(hash.GetHex());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return res;
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
void RegisterTxoutProofRPCCommands(CRPCTable& t)
|
||||||
|
{
|
||||||
|
static const CRPCCommand commands[]{
|
||||||
|
// category actor (function)
|
||||||
|
// -------- ----------------
|
||||||
|
{"blockchain", &gettxoutproof},
|
||||||
|
{"blockchain", &verifytxoutproof},
|
||||||
|
};
|
||||||
|
for (const auto& c : commands) {
|
||||||
|
t.appendCommand(c.name, &c);
|
||||||
|
}
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user