aboutsummaryrefslogtreecommitdiff
path: root/src/rpc
diff options
context:
space:
mode:
Diffstat (limited to 'src/rpc')
-rw-r--r--src/rpc/blockchain.cpp939
-rw-r--r--src/rpc/blockchain.h10
-rw-r--r--src/rpc/client.cpp6
-rw-r--r--src/rpc/external_signer.cpp14
-rw-r--r--src/rpc/fees.cpp236
-rw-r--r--src/rpc/mempool.cpp687
-rw-r--r--src/rpc/mempool.h17
-rw-r--r--src/rpc/mining.cpp291
-rw-r--r--src/rpc/misc.cpp833
-rw-r--r--src/rpc/net.cpp63
-rw-r--r--src/rpc/node.cpp440
-rw-r--r--src/rpc/output_script.cpp319
-rw-r--r--src/rpc/rawtransaction.cpp589
-rw-r--r--src/rpc/rawtransaction_util.cpp2
-rw-r--r--src/rpc/register.h24
-rw-r--r--src/rpc/request.cpp11
-rw-r--r--src/rpc/server.cpp22
-rw-r--r--src/rpc/signmessage.cpp113
-rw-r--r--src/rpc/txoutproof.cpp181
-rw-r--r--src/rpc/util.cpp108
-rw-r--r--src/rpc/util.h27
21 files changed, 2577 insertions, 2355 deletions
diff --git a/src/rpc/blockchain.cpp b/src/rpc/blockchain.cpp
index ccc859619d..7bce31c519 100644
--- a/src/rpc/blockchain.cpp
+++ b/src/rpc/blockchain.cpp
@@ -26,10 +26,6 @@
#include <node/coinstats.h>
#include <node/context.h>
#include <node/utxo_snapshot.h>
-#include <policy/feerate.h>
-#include <policy/fees.h>
-#include <policy/policy.h>
-#include <policy/rbf.h>
#include <primitives/transaction.h>
#include <rpc/server.h>
#include <rpc/server_util.h>
@@ -40,8 +36,9 @@
#include <txdb.h>
#include <txmempool.h>
#include <undo.h>
+#include <univalue.h>
+#include <util/check.h>
#include <util/strencodings.h>
-#include <util/string.h>
#include <util/translation.h>
#include <validation.h>
#include <validationinterface.h>
@@ -50,8 +47,6 @@
#include <stdint.h>
-#include <univalue.h>
-
#include <condition_variable>
#include <memory>
#include <mutex>
@@ -60,7 +55,6 @@ using node::BlockManager;
using node::CCoinsStats;
using node::CoinStatsHashType;
using node::GetUTXOStats;
-using node::IsBlockPruned;
using node::NodeContext;
using node::ReadBlockFromDisk;
using node::SnapshotMetadata;
@@ -110,7 +104,8 @@ static int ComputeNextBlockAndDepth(const CBlockIndex* tip, const CBlockIndex* b
return blockindex == tip ? 1 : -1;
}
-CBlockIndex* ParseHashOrHeight(const UniValue& param, ChainstateManager& chainman) {
+static const CBlockIndex* ParseHashOrHeight(const UniValue& param, ChainstateManager& chainman)
+{
LOCK(::cs_main);
CChain& active_chain = chainman.ActiveChain();
@@ -127,7 +122,7 @@ CBlockIndex* ParseHashOrHeight(const UniValue& param, ChainstateManager& chainma
return active_chain[height];
} else {
const uint256 hash{ParseHashV(param, "hash_or_height")};
- CBlockIndex* pindex = chainman.m_blockman.LookupBlockIndex(hash);
+ const CBlockIndex* pindex = chainman.m_blockman.LookupBlockIndex(hash);
if (!pindex) {
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Block not found");
@@ -166,7 +161,7 @@ UniValue blockheaderToJSON(const CBlockIndex* tip, const CBlockIndex* blockindex
return result;
}
-UniValue blockToJSON(const CBlock& block, const CBlockIndex* tip, const CBlockIndex* blockindex, TxVerbosity verbosity)
+UniValue blockToJSON(BlockManager& blockman, const CBlock& block, const CBlockIndex* tip, const CBlockIndex* blockindex, TxVerbosity verbosity)
{
UniValue result = blockheaderToJSON(tip, blockindex);
@@ -185,14 +180,14 @@ UniValue blockToJSON(const CBlock& block, const CBlockIndex* tip, const CBlockIn
case TxVerbosity::SHOW_DETAILS:
case TxVerbosity::SHOW_DETAILS_AND_PREVOUT:
CBlockUndo blockUndo;
- const bool have_undo = !IsBlockPruned(blockindex) && UndoReadFromDisk(blockUndo, blockindex);
+ const bool have_undo{WITH_LOCK(::cs_main, return !blockman.IsBlockPruned(blockindex) && UndoReadFromDisk(blockUndo, blockindex))};
for (size_t i = 0; i < block.vtx.size(); ++i) {
const CTransactionRef& tx = block.vtx.at(i);
// coinbase transaction (i.e. i == 0) doesn't have undo data
const CTxUndo* txundo = (have_undo && i > 0) ? &blockUndo.vtxundo.at(i - 1) : nullptr;
UniValue objTx(UniValue::VOBJ);
- TxToUniv(*tx, uint256(), objTx, true, RPCSerializationFlags(), txundo, verbosity);
+ TxToUniv(*tx, /*block_hash=*/uint256(), /*entry=*/objTx, /*include_hex=*/true, RPCSerializationFlags(), txundo, verbosity);
txs.push_back(objTx);
}
break;
@@ -426,381 +421,19 @@ static RPCHelpMan getdifficulty()
};
}
-static std::vector<RPCResult> MempoolEntryDescription() { return {
- RPCResult{RPCResult::Type::NUM, "vsize", "virtual transaction size as defined in BIP 141. This is different from actual serialized size for witness transactions as witness data is discounted."},
- RPCResult{RPCResult::Type::NUM, "weight", "transaction weight as defined in BIP 141."},
- RPCResult{RPCResult::Type::STR_AMOUNT, "fee", /*optional=*/true,
- "transaction fee, denominated in " + CURRENCY_UNIT + " (DEPRECATED, returned only if config option -deprecatedrpc=fees is passed)"},
- RPCResult{RPCResult::Type::STR_AMOUNT, "modifiedfee", /*optional=*/true,
- "transaction fee with fee deltas used for mining priority, denominated in " + CURRENCY_UNIT +
- " (DEPRECATED, returned only if config option -deprecatedrpc=fees is passed)"},
- RPCResult{RPCResult::Type::NUM_TIME, "time", "local time transaction entered pool in seconds since 1 Jan 1970 GMT"},
- RPCResult{RPCResult::Type::NUM, "height", "block height when transaction entered pool"},
- RPCResult{RPCResult::Type::NUM, "descendantcount", "number of in-mempool descendant transactions (including this one)"},
- RPCResult{RPCResult::Type::NUM, "descendantsize", "virtual transaction size of in-mempool descendants (including this one)"},
- RPCResult{RPCResult::Type::STR_AMOUNT, "descendantfees", /*optional=*/true,
- "transaction fees of in-mempool descendants (including this one) with fee deltas used for mining priority, denominated in " +
- CURRENCY_ATOM + "s (DEPRECATED, returned only if config option -deprecatedrpc=fees is passed)"},
- RPCResult{RPCResult::Type::NUM, "ancestorcount", "number of in-mempool ancestor transactions (including this one)"},
- RPCResult{RPCResult::Type::NUM, "ancestorsize", "virtual transaction size of in-mempool ancestors (including this one)"},
- RPCResult{RPCResult::Type::STR_AMOUNT, "ancestorfees", /*optional=*/true,
- "transaction fees of in-mempool ancestors (including this one) with fee deltas used for mining priority, denominated in " +
- CURRENCY_ATOM + "s (DEPRECATED, returned only if config option -deprecatedrpc=fees is passed)"},
- RPCResult{RPCResult::Type::STR_HEX, "wtxid", "hash of serialized transaction, including witness data"},
- RPCResult{RPCResult::Type::OBJ, "fees", "",
- {
- RPCResult{RPCResult::Type::STR_AMOUNT, "base", "transaction fee, denominated in " + CURRENCY_UNIT},
- RPCResult{RPCResult::Type::STR_AMOUNT, "modified", "transaction fee with fee deltas used for mining priority, denominated in " + CURRENCY_UNIT},
- RPCResult{RPCResult::Type::STR_AMOUNT, "ancestor", "transaction fees of in-mempool ancestors (including this one) with fee deltas used for mining priority, denominated in " + CURRENCY_UNIT},
- RPCResult{RPCResult::Type::STR_AMOUNT, "descendant", "transaction fees of in-mempool descendants (including this one) with fee deltas used for mining priority, denominated in " + CURRENCY_UNIT},
- }},
- RPCResult{RPCResult::Type::ARR, "depends", "unconfirmed transactions used as inputs for this transaction",
- {RPCResult{RPCResult::Type::STR_HEX, "transactionid", "parent transaction id"}}},
- RPCResult{RPCResult::Type::ARR, "spentby", "unconfirmed transactions spending outputs from this transaction",
- {RPCResult{RPCResult::Type::STR_HEX, "transactionid", "child transaction id"}}},
- RPCResult{RPCResult::Type::BOOL, "bip125-replaceable", "Whether this transaction could be replaced due to BIP125 (replace-by-fee)"},
- RPCResult{RPCResult::Type::BOOL, "unbroadcast", "Whether this transaction is currently unbroadcast (initial broadcast not yet acknowledged by any peers)"},
-};}
-
-static void entryToJSON(const CTxMemPool& pool, UniValue& info, const CTxMemPoolEntry& e) EXCLUSIVE_LOCKS_REQUIRED(pool.cs)
-{
- AssertLockHeld(pool.cs);
-
- info.pushKV("vsize", (int)e.GetTxSize());
- info.pushKV("weight", (int)e.GetTxWeight());
- // TODO: top-level fee fields are deprecated. deprecated_fee_fields_enabled blocks should be removed in v24
- const bool deprecated_fee_fields_enabled{IsDeprecatedRPCEnabled("fees")};
- if (deprecated_fee_fields_enabled) {
- info.pushKV("fee", ValueFromAmount(e.GetFee()));
- info.pushKV("modifiedfee", ValueFromAmount(e.GetModifiedFee()));
- }
- info.pushKV("time", count_seconds(e.GetTime()));
- info.pushKV("height", (int)e.GetHeight());
- info.pushKV("descendantcount", e.GetCountWithDescendants());
- info.pushKV("descendantsize", e.GetSizeWithDescendants());
- if (deprecated_fee_fields_enabled) {
- info.pushKV("descendantfees", e.GetModFeesWithDescendants());
- }
- info.pushKV("ancestorcount", e.GetCountWithAncestors());
- info.pushKV("ancestorsize", e.GetSizeWithAncestors());
- if (deprecated_fee_fields_enabled) {
- info.pushKV("ancestorfees", e.GetModFeesWithAncestors());
- }
- info.pushKV("wtxid", pool.vTxHashes[e.vTxHashesIdx].first.ToString());
-
- UniValue fees(UniValue::VOBJ);
- fees.pushKV("base", ValueFromAmount(e.GetFee()));
- fees.pushKV("modified", ValueFromAmount(e.GetModifiedFee()));
- fees.pushKV("ancestor", ValueFromAmount(e.GetModFeesWithAncestors()));
- fees.pushKV("descendant", ValueFromAmount(e.GetModFeesWithDescendants()));
- info.pushKV("fees", fees);
-
- const CTransaction& tx = e.GetTx();
- std::set<std::string> setDepends;
- for (const CTxIn& txin : tx.vin)
- {
- if (pool.exists(GenTxid::Txid(txin.prevout.hash)))
- setDepends.insert(txin.prevout.hash.ToString());
- }
-
- UniValue depends(UniValue::VARR);
- for (const std::string& dep : setDepends)
- {
- depends.push_back(dep);
- }
-
- info.pushKV("depends", depends);
-
- UniValue spent(UniValue::VARR);
- const CTxMemPool::txiter& it = pool.mapTx.find(tx.GetHash());
- const CTxMemPoolEntry::Children& children = it->GetMemPoolChildrenConst();
- for (const CTxMemPoolEntry& child : children) {
- spent.push_back(child.GetTx().GetHash().ToString());
- }
-
- info.pushKV("spentby", spent);
-
- // Add opt-in RBF status
- bool rbfStatus = false;
- RBFTransactionState rbfState = IsRBFOptIn(tx, pool);
- if (rbfState == RBFTransactionState::UNKNOWN) {
- throw JSONRPCError(RPC_MISC_ERROR, "Transaction is not in mempool");
- } else if (rbfState == RBFTransactionState::REPLACEABLE_BIP125) {
- rbfStatus = true;
- }
-
- info.pushKV("bip125-replaceable", rbfStatus);
- info.pushKV("unbroadcast", pool.IsUnbroadcastTx(tx.GetHash()));
-}
-
-UniValue MempoolToJSON(const CTxMemPool& pool, bool verbose, bool include_mempool_sequence)
-{
- if (verbose) {
- if (include_mempool_sequence) {
- throw JSONRPCError(RPC_INVALID_PARAMETER, "Verbose results cannot contain mempool sequence values.");
- }
- LOCK(pool.cs);
- UniValue o(UniValue::VOBJ);
- for (const CTxMemPoolEntry& e : pool.mapTx) {
- const uint256& hash = e.GetTx().GetHash();
- UniValue info(UniValue::VOBJ);
- entryToJSON(pool, info, e);
- // Mempool has unique entries so there is no advantage in using
- // UniValue::pushKV, which checks if the key already exists in O(N).
- // UniValue::__pushKV is used instead which currently is O(1).
- o.__pushKV(hash.ToString(), info);
- }
- return o;
- } else {
- uint64_t mempool_sequence;
- std::vector<uint256> vtxid;
- {
- LOCK(pool.cs);
- pool.queryHashes(vtxid);
- mempool_sequence = pool.GetSequence();
- }
- UniValue a(UniValue::VARR);
- for (const uint256& hash : vtxid)
- a.push_back(hash.ToString());
-
- if (!include_mempool_sequence) {
- return a;
- } else {
- UniValue o(UniValue::VOBJ);
- o.pushKV("txids", a);
- o.pushKV("mempool_sequence", mempool_sequence);
- return o;
- }
- }
-}
-
-static RPCHelpMan getrawmempool()
-{
- return RPCHelpMan{"getrawmempool",
- "\nReturns all transaction ids in memory pool as a json array of string transaction ids.\n"
- "\nHint: use getmempoolentry to fetch a specific transaction from the mempool.\n",
- {
- {"verbose", RPCArg::Type::BOOL, RPCArg::Default{false}, "True for a json object, false for array of transaction ids"},
- {"mempool_sequence", RPCArg::Type::BOOL, RPCArg::Default{false}, "If verbose=false, returns a json object with transaction list and mempool sequence number attached."},
- },
- {
- RPCResult{"for verbose = false",
- RPCResult::Type::ARR, "", "",
- {
- {RPCResult::Type::STR_HEX, "", "The transaction id"},
- }},
- RPCResult{"for verbose = true",
- RPCResult::Type::OBJ_DYN, "", "",
- {
- {RPCResult::Type::OBJ, "transactionid", "", MempoolEntryDescription()},
- }},
- RPCResult{"for verbose = false and mempool_sequence = true",
- RPCResult::Type::OBJ, "", "",
- {
- {RPCResult::Type::ARR, "txids", "",
- {
- {RPCResult::Type::STR_HEX, "", "The transaction id"},
- }},
- {RPCResult::Type::NUM, "mempool_sequence", "The mempool sequence value."},
- }},
- },
- RPCExamples{
- HelpExampleCli("getrawmempool", "true")
- + HelpExampleRpc("getrawmempool", "true")
- },
- [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
-{
- bool fVerbose = false;
- if (!request.params[0].isNull())
- fVerbose = request.params[0].get_bool();
-
- bool include_mempool_sequence = false;
- if (!request.params[1].isNull()) {
- include_mempool_sequence = request.params[1].get_bool();
- }
-
- return MempoolToJSON(EnsureAnyMemPool(request.context), fVerbose, include_mempool_sequence);
-},
- };
-}
-
-static RPCHelpMan getmempoolancestors()
-{
- return RPCHelpMan{"getmempoolancestors",
- "\nIf txid is in the mempool, returns all in-mempool ancestors.\n",
- {
- {"txid", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "The transaction id (must be in mempool)"},
- {"verbose", RPCArg::Type::BOOL, RPCArg::Default{false}, "True for a json object, false for array of transaction ids"},
- },
- {
- RPCResult{"for verbose = false",
- RPCResult::Type::ARR, "", "",
- {{RPCResult::Type::STR_HEX, "", "The transaction id of an in-mempool ancestor transaction"}}},
- RPCResult{"for verbose = true",
- RPCResult::Type::OBJ_DYN, "", "",
- {
- {RPCResult::Type::OBJ, "transactionid", "", MempoolEntryDescription()},
- }},
- },
- RPCExamples{
- HelpExampleCli("getmempoolancestors", "\"mytxid\"")
- + HelpExampleRpc("getmempoolancestors", "\"mytxid\"")
- },
- [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
-{
- bool fVerbose = false;
- if (!request.params[1].isNull())
- fVerbose = request.params[1].get_bool();
-
- uint256 hash = ParseHashV(request.params[0], "parameter 1");
-
- const CTxMemPool& mempool = EnsureAnyMemPool(request.context);
- LOCK(mempool.cs);
-
- CTxMemPool::txiter it = mempool.mapTx.find(hash);
- if (it == mempool.mapTx.end()) {
- throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Transaction not in mempool");
- }
-
- CTxMemPool::setEntries setAncestors;
- uint64_t noLimit = std::numeric_limits<uint64_t>::max();
- std::string dummy;
- mempool.CalculateMemPoolAncestors(*it, setAncestors, noLimit, noLimit, noLimit, noLimit, dummy, false);
-
- if (!fVerbose) {
- UniValue o(UniValue::VARR);
- for (CTxMemPool::txiter ancestorIt : setAncestors) {
- o.push_back(ancestorIt->GetTx().GetHash().ToString());
- }
- return o;
- } else {
- UniValue o(UniValue::VOBJ);
- for (CTxMemPool::txiter ancestorIt : setAncestors) {
- const CTxMemPoolEntry &e = *ancestorIt;
- const uint256& _hash = e.GetTx().GetHash();
- UniValue info(UniValue::VOBJ);
- entryToJSON(mempool, info, e);
- o.pushKV(_hash.ToString(), info);
- }
- return o;
- }
-},
- };
-}
-
-static RPCHelpMan getmempooldescendants()
-{
- return RPCHelpMan{"getmempooldescendants",
- "\nIf txid is in the mempool, returns all in-mempool descendants.\n",
- {
- {"txid", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "The transaction id (must be in mempool)"},
- {"verbose", RPCArg::Type::BOOL, RPCArg::Default{false}, "True for a json object, false for array of transaction ids"},
- },
- {
- RPCResult{"for verbose = false",
- RPCResult::Type::ARR, "", "",
- {{RPCResult::Type::STR_HEX, "", "The transaction id of an in-mempool descendant transaction"}}},
- RPCResult{"for verbose = true",
- RPCResult::Type::OBJ_DYN, "", "",
- {
- {RPCResult::Type::OBJ, "transactionid", "", MempoolEntryDescription()},
- }},
- },
- RPCExamples{
- HelpExampleCli("getmempooldescendants", "\"mytxid\"")
- + HelpExampleRpc("getmempooldescendants", "\"mytxid\"")
- },
- [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
-{
- bool fVerbose = false;
- if (!request.params[1].isNull())
- fVerbose = request.params[1].get_bool();
-
- uint256 hash = ParseHashV(request.params[0], "parameter 1");
-
- const CTxMemPool& mempool = EnsureAnyMemPool(request.context);
- LOCK(mempool.cs);
-
- CTxMemPool::txiter it = mempool.mapTx.find(hash);
- if (it == mempool.mapTx.end()) {
- throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Transaction not in mempool");
- }
-
- CTxMemPool::setEntries setDescendants;
- mempool.CalculateDescendants(it, setDescendants);
- // CTxMemPool::CalculateDescendants will include the given tx
- setDescendants.erase(it);
-
- if (!fVerbose) {
- UniValue o(UniValue::VARR);
- for (CTxMemPool::txiter descendantIt : setDescendants) {
- o.push_back(descendantIt->GetTx().GetHash().ToString());
- }
-
- return o;
- } else {
- UniValue o(UniValue::VOBJ);
- for (CTxMemPool::txiter descendantIt : setDescendants) {
- const CTxMemPoolEntry &e = *descendantIt;
- const uint256& _hash = e.GetTx().GetHash();
- UniValue info(UniValue::VOBJ);
- entryToJSON(mempool, info, e);
- o.pushKV(_hash.ToString(), info);
- }
- return o;
- }
-},
- };
-}
-
-static RPCHelpMan getmempoolentry()
-{
- return RPCHelpMan{"getmempoolentry",
- "\nReturns mempool data for given transaction\n",
- {
- {"txid", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "The transaction id (must be in mempool)"},
- },
- RPCResult{
- RPCResult::Type::OBJ, "", "", MempoolEntryDescription()},
- RPCExamples{
- HelpExampleCli("getmempoolentry", "\"mytxid\"")
- + HelpExampleRpc("getmempoolentry", "\"mytxid\"")
- },
- [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
-{
- uint256 hash = ParseHashV(request.params[0], "parameter 1");
-
- const CTxMemPool& mempool = EnsureAnyMemPool(request.context);
- LOCK(mempool.cs);
-
- CTxMemPool::txiter it = mempool.mapTx.find(hash);
- if (it == mempool.mapTx.end()) {
- throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Transaction not in mempool");
- }
-
- const CTxMemPoolEntry &e = *it;
- UniValue info(UniValue::VOBJ);
- entryToJSON(mempool, info, e);
- return info;
-},
- };
-}
-
static RPCHelpMan getblockfrompeer()
{
return RPCHelpMan{
"getblockfrompeer",
- "\nAttempt to fetch block from a given peer.\n"
- "\nWe must have the header for this block, e.g. using submitheader.\n"
- "\nReturns {} if a block-request was successfully scheduled\n",
+ "Attempt to fetch block from a given peer.\n\n"
+ "We must have the header for this block, e.g. using submitheader.\n"
+ "Subsequent calls for the same block and a new peer will cause the response from the previous peer to be ignored.\n\n"
+ "Returns an empty JSON object if the request was successfully scheduled.",
{
- {"blockhash", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "The block hash"},
- {"nodeid", RPCArg::Type::NUM, RPCArg::Optional::NO, "The node ID (see getpeerinfo for node IDs)"},
+ {"blockhash", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "The block hash to try to fetch"},
+ {"peer_id", RPCArg::Type::NUM, RPCArg::Optional::NO, "The peer to fetch it from (see getpeerinfo for peer IDs)"},
},
- RPCResult{RPCResult::Type::OBJ, "", "",
- {
- {RPCResult::Type::STR, "warnings", /*optional=*/true, "any warnings"},
- }},
+ RPCResult{RPCResult::Type::OBJ, "", /*optional=*/false, "", {}},
RPCExamples{
HelpExampleCli("getblockfrompeer", "\"00000000c937983704a73af28acdec37b049d214adbda81d7e2a3dd146f6ed09\" 0")
+ HelpExampleRpc("getblockfrompeer", "\"00000000c937983704a73af28acdec37b049d214adbda81d7e2a3dd146f6ed09\" 0")
@@ -810,31 +443,25 @@ static RPCHelpMan getblockfrompeer()
const NodeContext& node = EnsureAnyNodeContext(request.context);
ChainstateManager& chainman = EnsureChainman(node);
PeerManager& peerman = EnsurePeerman(node);
- CConnman& connman = EnsureConnman(node);
- uint256 hash(ParseHashV(request.params[0], "hash"));
+ const uint256& block_hash{ParseHashV(request.params[0], "blockhash")};
+ const NodeId peer_id{request.params[1].get_int64()};
- const NodeId nodeid = static_cast<NodeId>(request.params[1].get_int64());
-
- // Check that the peer with nodeid exists
- if (!connman.ForNode(nodeid, [](CNode* node) {return true;})) {
- throw JSONRPCError(RPC_MISC_ERROR, strprintf("Peer nodeid %d does not exist", nodeid));
- }
-
- const CBlockIndex* const index = WITH_LOCK(cs_main, return chainman.m_blockman.LookupBlockIndex(hash););
+ const CBlockIndex* const index = WITH_LOCK(cs_main, return chainman.m_blockman.LookupBlockIndex(block_hash););
if (!index) {
throw JSONRPCError(RPC_MISC_ERROR, "Block header missing");
}
- UniValue result = UniValue::VOBJ;
+ const bool block_has_data = WITH_LOCK(::cs_main, return index->nStatus & BLOCK_HAVE_DATA);
+ if (block_has_data) {
+ throw JSONRPCError(RPC_MISC_ERROR, "Block already downloaded");
+ }
- if (index->nStatus & BLOCK_HAVE_DATA) {
- result.pushKV("warnings", "Block already downloaded");
- } else if (!peerman.FetchBlock(nodeid, hash, *index)) {
- throw JSONRPCError(RPC_MISC_ERROR, "Failed to fetch block from peer");
+ if (const auto err{peerman.FetchBlock(peer_id, *index)}) {
+ throw JSONRPCError(RPC_MISC_ERROR, err.value());
}
- return result;
+ return UniValue::VOBJ;
},
};
}
@@ -862,7 +489,7 @@ static RPCHelpMan getblockhash()
if (nHeight < 0 || nHeight > active_chain.Height())
throw JSONRPCError(RPC_INVALID_PARAMETER, "Block height out of range");
- CBlockIndex* pblockindex = active_chain[nHeight];
+ const CBlockIndex* pblockindex = active_chain[nHeight];
return pblockindex->GetBlockHash().GetHex();
},
};
@@ -938,10 +565,11 @@ static RPCHelpMan getblockheader()
};
}
-static CBlock GetBlockChecked(const CBlockIndex* pblockindex)
+static CBlock GetBlockChecked(BlockManager& blockman, const CBlockIndex* pblockindex) EXCLUSIVE_LOCKS_REQUIRED(::cs_main)
{
+ AssertLockHeld(::cs_main);
CBlock block;
- if (IsBlockPruned(pblockindex)) {
+ if (blockman.IsBlockPruned(pblockindex)) {
throw JSONRPCError(RPC_MISC_ERROR, "Block not available (pruned data)");
}
@@ -955,10 +583,11 @@ static CBlock GetBlockChecked(const CBlockIndex* pblockindex)
return block;
}
-static CBlockUndo GetUndoChecked(const CBlockIndex* pblockindex)
+static CBlockUndo GetUndoChecked(BlockManager& blockman, const CBlockIndex* pblockindex) EXCLUSIVE_LOCKS_REQUIRED(cs_main)
{
+ AssertLockHeld(::cs_main);
CBlockUndo blockUndo;
- if (IsBlockPruned(pblockindex)) {
+ if (blockman.IsBlockPruned(pblockindex)) {
throw JSONRPCError(RPC_MISC_ERROR, "Undo data not available (pruned data)");
}
@@ -1042,8 +671,8 @@ static RPCHelpMan getblock()
{
{RPCResult::Type::STR, "asm", "The asm"},
{RPCResult::Type::STR, "hex", "The hex"},
- {RPCResult::Type::STR, "address", /* optional */ true, "The Bitcoin address (only if a well-defined address exists)"},
- {RPCResult::Type::STR, "type", "The type, eg 'pubkeyhash'"},
+ {RPCResult::Type::STR, "address", /*optional=*/true, "The Bitcoin address (only if a well-defined address exists)"},
+ {RPCResult::Type::STR, "type", "The type (one of: " + GetAllOutputTypes() + ")"},
}},
}},
}},
@@ -1072,8 +701,8 @@ static RPCHelpMan getblock()
CBlock block;
const CBlockIndex* pblockindex;
const CBlockIndex* tip;
+ ChainstateManager& chainman = EnsureAnyChainman(request.context);
{
- ChainstateManager& chainman = EnsureAnyChainman(request.context);
LOCK(cs_main);
pblockindex = chainman.m_blockman.LookupBlockIndex(hash);
tip = chainman.ActiveChain().Tip();
@@ -1082,7 +711,7 @@ static RPCHelpMan getblock()
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Block not found");
}
- block = GetBlockChecked(pblockindex);
+ block = GetBlockChecked(chainman.m_blockman, pblockindex);
}
if (verbosity <= 0)
@@ -1102,7 +731,7 @@ static RPCHelpMan getblock()
tx_verbosity = TxVerbosity::SHOW_DETAILS_AND_PREVOUT;
}
- return blockToJSON(block, tip, pblockindex, tx_verbosity);
+ return blockToJSON(chainman.m_blockman, block, tip, pblockindex, tx_verbosity);
},
};
}
@@ -1131,14 +760,15 @@ static RPCHelpMan pruneblockchain()
CChain& active_chain = active_chainstate.m_chain;
int heightParam = request.params[0].get_int();
- if (heightParam < 0)
+ if (heightParam < 0) {
throw JSONRPCError(RPC_INVALID_PARAMETER, "Negative block height.");
+ }
// Height value more than a billion is too high to be a block height, and
// too low to be a block time (corresponds to timestamp from Sep 2001).
if (heightParam > 1000000000) {
// Add a 2 hour buffer to include blocks which might have had old timestamps
- CBlockIndex* pindex = active_chain.FindEarliestAtLeast(heightParam - TIMESTAMP_WINDOW, 0);
+ const CBlockIndex* pindex = active_chain.FindEarliestAtLeast(heightParam - TIMESTAMP_WINDOW, 0);
if (!pindex) {
throw JSONRPCError(RPC_INVALID_PARAMETER, "Could not find block with at least the specified timestamp.");
}
@@ -1157,12 +787,10 @@ static RPCHelpMan pruneblockchain()
}
PruneBlockFilesManual(active_chainstate, height);
- const CBlockIndex* block = active_chain.Tip();
- CHECK_NONFATAL(block);
- while (block->pprev && (block->pprev->nStatus & BLOCK_HAVE_DATA)) {
- block = block->pprev;
- }
- return uint64_t(block->nHeight);
+ const CBlockIndex& block{*CHECK_NONFATAL(active_chain.Tip())};
+ const CBlockIndex* last_block{active_chainstate.m_blockman.GetFirstStoredBlock(block)};
+
+ return static_cast<uint64_t>(last_block->nHeight);
},
};
}
@@ -1232,7 +860,7 @@ static RPCHelpMan gettxoutsetinfo()
{
UniValue ret(UniValue::VOBJ);
- CBlockIndex* pindex{nullptr};
+ const CBlockIndex* pindex{nullptr};
const CoinStatsHashType hash_type{request.params[0].isNull() ? CoinStatsHashType::HASH_SERIALIZED : ParseHashType(request.params[0].get_str())};
CCoinsStats stats{hash_type};
stats.index_requested = request.params[2].isNull() || request.params[2].get_bool();
@@ -1340,6 +968,7 @@ static RPCHelpMan gettxout()
{RPCResult::Type::STR_AMOUNT, "value", "The transaction value in " + CURRENCY_UNIT},
{RPCResult::Type::OBJ, "scriptPubKey", "", {
{RPCResult::Type::STR, "asm", ""},
+ {RPCResult::Type::STR, "desc", "Inferred descriptor for the output"},
{RPCResult::Type::STR_HEX, "hex", ""},
{RPCResult::Type::STR, "type", "The type, eg pubkeyhash"},
{RPCResult::Type::STR, "address", /*optional=*/true, "The Bitcoin address (only if a well-defined address exists)"},
@@ -1396,7 +1025,7 @@ static RPCHelpMan gettxout()
}
ret.pushKV("value", ValueFromAmount(coin.out.nValue));
UniValue o(UniValue::VOBJ);
- ScriptPubKeyToUniv(coin.out.scriptPubKey, o, true);
+ ScriptToUniv(coin.out.scriptPubKey, /*out=*/o, /*include_hex=*/true, /*include_address=*/true);
ret.pushKV("scriptPubKey", o);
ret.pushKV("coinbase", (bool)coin.fCoinBase);
@@ -1422,7 +1051,7 @@ static RPCHelpMan verifychain()
},
[&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
{
- const int check_level(request.params[0].isNull() ? DEFAULT_CHECKLEVEL : request.params[0].get_int());
+ const int check_level{request.params[0].isNull() ? DEFAULT_CHECKLEVEL : request.params[0].get_int()};
const int check_depth{request.params[1].isNull() ? DEFAULT_CHECKBLOCKS : request.params[1].get_int()};
ChainstateManager& chainman = EnsureAnyChainman(request.context);
@@ -1435,7 +1064,7 @@ static RPCHelpMan verifychain()
};
}
-static void SoftForkDescPushBack(const CBlockIndex* active_chain_tip, UniValue& softforks, const Consensus::Params& params, Consensus::BuriedDeployment dep)
+static void SoftForkDescPushBack(const CBlockIndex* blockindex, UniValue& softforks, const Consensus::Params& params, Consensus::BuriedDeployment dep)
{
// For buried deployments.
@@ -1443,116 +1072,127 @@ static void SoftForkDescPushBack(const CBlockIndex* active_chain_tip, UniValue&
UniValue rv(UniValue::VOBJ);
rv.pushKV("type", "buried");
- // getblockchaininfo reports the softfork as active from when the chain height is
+ // getdeploymentinfo reports the softfork as active from when the chain height is
// one below the activation height
- rv.pushKV("active", DeploymentActiveAfter(active_chain_tip, params, dep));
+ rv.pushKV("active", DeploymentActiveAfter(blockindex, params, dep));
rv.pushKV("height", params.DeploymentHeight(dep));
softforks.pushKV(DeploymentName(dep), rv);
}
-static void SoftForkDescPushBack(const CBlockIndex* active_chain_tip, UniValue& softforks, const Consensus::Params& consensusParams, Consensus::DeploymentPos id)
+static void SoftForkDescPushBack(const CBlockIndex* blockindex, UniValue& softforks, const Consensus::Params& consensusParams, Consensus::DeploymentPos id)
{
// For BIP9 deployments.
if (!DeploymentEnabled(consensusParams, id)) return;
+ if (blockindex == nullptr) return;
+
+ auto get_state_name = [](const ThresholdState state) -> std::string {
+ switch (state) {
+ case ThresholdState::DEFINED: return "defined";
+ case ThresholdState::STARTED: return "started";
+ case ThresholdState::LOCKED_IN: return "locked_in";
+ case ThresholdState::ACTIVE: return "active";
+ case ThresholdState::FAILED: return "failed";
+ }
+ return "invalid";
+ };
UniValue bip9(UniValue::VOBJ);
- const ThresholdState thresholdState = g_versionbitscache.State(active_chain_tip, consensusParams, id);
- switch (thresholdState) {
- case ThresholdState::DEFINED: bip9.pushKV("status", "defined"); break;
- case ThresholdState::STARTED: bip9.pushKV("status", "started"); break;
- case ThresholdState::LOCKED_IN: bip9.pushKV("status", "locked_in"); break;
- case ThresholdState::ACTIVE: bip9.pushKV("status", "active"); break;
- case ThresholdState::FAILED: bip9.pushKV("status", "failed"); break;
- }
- const bool has_signal = (ThresholdState::STARTED == thresholdState || ThresholdState::LOCKED_IN == thresholdState);
+
+ const ThresholdState next_state = g_versionbitscache.State(blockindex, consensusParams, id);
+ const ThresholdState current_state = g_versionbitscache.State(blockindex->pprev, consensusParams, id);
+
+ const bool has_signal = (ThresholdState::STARTED == current_state || ThresholdState::LOCKED_IN == current_state);
+
+ // BIP9 parameters
if (has_signal) {
bip9.pushKV("bit", consensusParams.vDeployments[id].bit);
}
bip9.pushKV("start_time", consensusParams.vDeployments[id].nStartTime);
bip9.pushKV("timeout", consensusParams.vDeployments[id].nTimeout);
- int64_t since_height = g_versionbitscache.StateSinceHeight(active_chain_tip, consensusParams, id);
- bip9.pushKV("since", since_height);
+ bip9.pushKV("min_activation_height", consensusParams.vDeployments[id].min_activation_height);
+
+ // BIP9 status
+ bip9.pushKV("status", get_state_name(current_state));
+ bip9.pushKV("since", g_versionbitscache.StateSinceHeight(blockindex->pprev, consensusParams, id));
+ bip9.pushKV("status_next", get_state_name(next_state));
+
+ // BIP9 signalling status, if applicable
if (has_signal) {
UniValue statsUV(UniValue::VOBJ);
- BIP9Stats statsStruct = g_versionbitscache.Statistics(active_chain_tip, consensusParams, id);
+ std::vector<bool> signals;
+ BIP9Stats statsStruct = g_versionbitscache.Statistics(blockindex, consensusParams, id, &signals);
statsUV.pushKV("period", statsStruct.period);
statsUV.pushKV("elapsed", statsStruct.elapsed);
statsUV.pushKV("count", statsStruct.count);
- if (ThresholdState::LOCKED_IN != thresholdState) {
+ if (ThresholdState::LOCKED_IN != current_state) {
statsUV.pushKV("threshold", statsStruct.threshold);
statsUV.pushKV("possible", statsStruct.possible);
}
bip9.pushKV("statistics", statsUV);
+
+ std::string sig;
+ sig.reserve(signals.size());
+ for (const bool s : signals) {
+ sig.push_back(s ? '#' : '-');
+ }
+ bip9.pushKV("signalling", sig);
}
- bip9.pushKV("min_activation_height", consensusParams.vDeployments[id].min_activation_height);
UniValue rv(UniValue::VOBJ);
rv.pushKV("type", "bip9");
- rv.pushKV("bip9", bip9);
- if (ThresholdState::ACTIVE == thresholdState) {
- rv.pushKV("height", since_height);
+ if (ThresholdState::ACTIVE == next_state) {
+ rv.pushKV("height", g_versionbitscache.StateSinceHeight(blockindex, consensusParams, id));
}
- rv.pushKV("active", ThresholdState::ACTIVE == thresholdState);
+ rv.pushKV("active", ThresholdState::ACTIVE == next_state);
+ rv.pushKV("bip9", bip9);
softforks.pushKV(DeploymentName(id), rv);
}
+namespace {
+/* TODO: when -deprecatedrpc=softforks is removed, drop these */
+UniValue DeploymentInfo(const CBlockIndex* tip, const Consensus::Params& consensusParams);
+extern const std::vector<RPCResult> RPCHelpForDeployment;
+}
+
+// used by rest.cpp:rest_chaininfo, so cannot be static
RPCHelpMan getblockchaininfo()
{
+ /* TODO: from v24, remove -deprecatedrpc=softforks */
return RPCHelpMan{"getblockchaininfo",
- "Returns an object containing various state info regarding blockchain processing.\n",
- {},
- RPCResult{
- RPCResult::Type::OBJ, "", "",
- {
- {RPCResult::Type::STR, "chain", "current network name (main, test, signet, regtest)"},
- {RPCResult::Type::NUM, "blocks", "the height of the most-work fully-validated chain. The genesis block has height 0"},
- {RPCResult::Type::NUM, "headers", "the current number of headers we have validated"},
- {RPCResult::Type::STR, "bestblockhash", "the hash of the currently best block"},
- {RPCResult::Type::NUM, "difficulty", "the current difficulty"},
- {RPCResult::Type::NUM_TIME, "time", "The block time expressed in " + UNIX_EPOCH_TIME},
- {RPCResult::Type::NUM_TIME, "mediantime", "The median block time expressed in " + UNIX_EPOCH_TIME},
- {RPCResult::Type::NUM, "verificationprogress", "estimate of verification progress [0..1]"},
- {RPCResult::Type::BOOL, "initialblockdownload", "(debug information) estimate of whether this node is in Initial Block Download mode"},
- {RPCResult::Type::STR_HEX, "chainwork", "total amount of work in active chain, in hexadecimal"},
- {RPCResult::Type::NUM, "size_on_disk", "the estimated size of the block and undo files on disk"},
- {RPCResult::Type::BOOL, "pruned", "if the blocks are subject to pruning"},
- {RPCResult::Type::NUM, "pruneheight", /*optional=*/true, "lowest-height complete block stored (only present if pruning is enabled)"},
- {RPCResult::Type::BOOL, "automatic_pruning", /*optional=*/true, "whether automatic pruning is enabled (only present if pruning is enabled)"},
- {RPCResult::Type::NUM, "prune_target_size", /*optional=*/true, "the target size used by pruning (only present if automatic pruning is enabled)"},
- {RPCResult::Type::OBJ_DYN, "softforks", "status of softforks",
- {
- {RPCResult::Type::OBJ, "xxxx", "name of the softfork",
- {
- {RPCResult::Type::STR, "type", "one of \"buried\", \"bip9\""},
- {RPCResult::Type::OBJ, "bip9", /*optional=*/true, "status of bip9 softforks (only for \"bip9\" type)",
- {
- {RPCResult::Type::STR, "status", "one of \"defined\", \"started\", \"locked_in\", \"active\", \"failed\""},
- {RPCResult::Type::NUM, "bit", /*optional=*/true, "the bit (0-28) in the block version field used to signal this softfork (only for \"started\" and \"locked_in\" status)"},
- {RPCResult::Type::NUM_TIME, "start_time", "the minimum median time past of a block at which the bit gains its meaning"},
- {RPCResult::Type::NUM_TIME, "timeout", "the median time past of a block at which the deployment is considered failed if not yet locked in"},
- {RPCResult::Type::NUM, "since", "height of the first block to which the status applies"},
- {RPCResult::Type::NUM, "min_activation_height", "minimum height of blocks for which the rules may be enforced"},
- {RPCResult::Type::OBJ, "statistics", /*optional=*/true, "numeric statistics about signalling for a softfork (only for \"started\" and \"locked_in\" status)",
- {
- {RPCResult::Type::NUM, "period", "the length in blocks of the signalling period"},
- {RPCResult::Type::NUM, "threshold", /*optional=*/true, "the number of blocks with the version bit set required to activate the feature (only for \"started\" status)"},
- {RPCResult::Type::NUM, "elapsed", "the number of blocks elapsed since the beginning of the current period"},
- {RPCResult::Type::NUM, "count", "the number of blocks with the version bit set in the current period"},
- {RPCResult::Type::BOOL, "possible", /*optional=*/true, "returns false if there are not enough blocks left in this period to pass activation threshold (only for \"started\" status)"},
- }},
- }},
- {RPCResult::Type::NUM, "height", /*optional=*/true, "height of the first block which the rules are or will be enforced (only for \"buried\" type, or \"bip9\" type with \"active\" status)"},
- {RPCResult::Type::BOOL, "active", "true if the rules are enforced for the mempool and the next block"},
- }},
- }},
- {RPCResult::Type::STR, "warnings", "any network and blockchain warnings"},
- }},
- RPCExamples{
- HelpExampleCli("getblockchaininfo", "")
+ "Returns an object containing various state info regarding blockchain processing.\n",
+ {},
+ RPCResult{
+ RPCResult::Type::OBJ, "", "",
+ {
+ {RPCResult::Type::STR, "chain", "current network name (main, test, signet, regtest)"},
+ {RPCResult::Type::NUM, "blocks", "the height of the most-work fully-validated chain. The genesis block has height 0"},
+ {RPCResult::Type::NUM, "headers", "the current number of headers we have validated"},
+ {RPCResult::Type::STR, "bestblockhash", "the hash of the currently best block"},
+ {RPCResult::Type::NUM, "difficulty", "the current difficulty"},
+ {RPCResult::Type::NUM_TIME, "time", "The block time expressed in " + UNIX_EPOCH_TIME},
+ {RPCResult::Type::NUM_TIME, "mediantime", "The median block time expressed in " + UNIX_EPOCH_TIME},
+ {RPCResult::Type::NUM, "verificationprogress", "estimate of verification progress [0..1]"},
+ {RPCResult::Type::BOOL, "initialblockdownload", "(debug information) estimate of whether this node is in Initial Block Download mode"},
+ {RPCResult::Type::STR_HEX, "chainwork", "total amount of work in active chain, in hexadecimal"},
+ {RPCResult::Type::NUM, "size_on_disk", "the estimated size of the block and undo files on disk"},
+ {RPCResult::Type::BOOL, "pruned", "if the blocks are subject to pruning"},
+ {RPCResult::Type::NUM, "pruneheight", /*optional=*/true, "lowest-height complete block stored (only present if pruning is enabled)"},
+ {RPCResult::Type::BOOL, "automatic_pruning", /*optional=*/true, "whether automatic pruning is enabled (only present if pruning is enabled)"},
+ {RPCResult::Type::NUM, "prune_target_size", /*optional=*/true, "the target size used by pruning (only present if automatic pruning is enabled)"},
+ {RPCResult::Type::OBJ_DYN, "softforks", /*optional=*/true, "(DEPRECATED, returned only if config option -deprecatedrpc=softforks is passed) status of softforks",
+ {
+ {RPCResult::Type::OBJ, "xxxx", "name of the softfork",
+ RPCHelpForDeployment
+ },
+ }},
+ {RPCResult::Type::STR, "warnings", "any network and blockchain warnings"},
+ }},
+ RPCExamples{
+ HelpExampleCli("getblockchaininfo", "")
+ HelpExampleRpc("getblockchaininfo", "")
- },
+ },
[&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
{
const ArgsManager& args{EnsureAnyArgsman(request.context)};
@@ -1560,30 +1200,23 @@ RPCHelpMan getblockchaininfo()
LOCK(cs_main);
CChainState& active_chainstate = chainman.ActiveChainstate();
- const CBlockIndex* tip = active_chainstate.m_chain.Tip();
- CHECK_NONFATAL(tip);
- const int height = tip->nHeight;
+ const CBlockIndex& tip{*CHECK_NONFATAL(active_chainstate.m_chain.Tip())};
+ const int height{tip.nHeight};
UniValue obj(UniValue::VOBJ);
- obj.pushKV("chain", Params().NetworkIDString());
- obj.pushKV("blocks", height);
- obj.pushKV("headers", pindexBestHeader ? pindexBestHeader->nHeight : -1);
- obj.pushKV("bestblockhash", tip->GetBlockHash().GetHex());
- obj.pushKV("difficulty", (double)GetDifficulty(tip));
- obj.pushKV("time", (int64_t)tip->nTime);
- obj.pushKV("mediantime", (int64_t)tip->GetMedianTimePast());
- obj.pushKV("verificationprogress", GuessVerificationProgress(Params().TxData(), tip));
- obj.pushKV("initialblockdownload", active_chainstate.IsInitialBlockDownload());
- obj.pushKV("chainwork", tip->nChainWork.GetHex());
+ obj.pushKV("chain", Params().NetworkIDString());
+ obj.pushKV("blocks", height);
+ obj.pushKV("headers", chainman.m_best_header ? chainman.m_best_header->nHeight : -1);
+ obj.pushKV("bestblockhash", tip.GetBlockHash().GetHex());
+ obj.pushKV("difficulty", GetDifficulty(&tip));
+ obj.pushKV("time", tip.GetBlockTime());
+ obj.pushKV("mediantime", tip.GetMedianTimePast());
+ obj.pushKV("verificationprogress", GuessVerificationProgress(Params().TxData(), &tip));
+ obj.pushKV("initialblockdownload", active_chainstate.IsInitialBlockDownload());
+ obj.pushKV("chainwork", tip.nChainWork.GetHex());
obj.pushKV("size_on_disk", chainman.m_blockman.CalculateCurrentUsage());
- obj.pushKV("pruned", node::fPruneMode);
+ obj.pushKV("pruned", node::fPruneMode);
if (node::fPruneMode) {
- const CBlockIndex* block = tip;
- CHECK_NONFATAL(block);
- while (block->pprev && (block->pprev->nStatus & BLOCK_HAVE_DATA)) {
- block = block->pprev;
- }
-
- obj.pushKV("pruneheight", block->nHeight);
+ obj.pushKV("pruneheight", chainman.m_blockman.GetFirstStoredBlock(tip)->nHeight);
// if 0, execution bypasses the whole if block.
bool automatic_pruning{args.GetIntArg("-prune", 0) != 1};
@@ -1593,16 +1226,10 @@ RPCHelpMan getblockchaininfo()
}
}
- const Consensus::Params& consensusParams = Params().GetConsensus();
- UniValue softforks(UniValue::VOBJ);
- SoftForkDescPushBack(tip, softforks, consensusParams, Consensus::DEPLOYMENT_HEIGHTINCB);
- SoftForkDescPushBack(tip, softforks, consensusParams, Consensus::DEPLOYMENT_DERSIG);
- SoftForkDescPushBack(tip, softforks, consensusParams, Consensus::DEPLOYMENT_CLTV);
- SoftForkDescPushBack(tip, softforks, consensusParams, Consensus::DEPLOYMENT_CSV);
- SoftForkDescPushBack(tip, softforks, consensusParams, Consensus::DEPLOYMENT_SEGWIT);
- SoftForkDescPushBack(tip, softforks, consensusParams, Consensus::DEPLOYMENT_TESTDUMMY);
- SoftForkDescPushBack(tip, softforks, consensusParams, Consensus::DEPLOYMENT_TAPROOT);
- obj.pushKV("softforks", softforks);
+ if (IsDeprecatedRPCEnabled("softforks")) {
+ const Consensus::Params& consensusParams = Params().GetConsensus();
+ obj.pushKV("softforks", DeploymentInfo(&tip, consensusParams));
+ }
obj.pushKV("warnings", GetWarnings(false).original);
return obj;
@@ -1610,6 +1237,91 @@ RPCHelpMan getblockchaininfo()
};
}
+namespace {
+const std::vector<RPCResult> RPCHelpForDeployment{
+ {RPCResult::Type::STR, "type", "one of \"buried\", \"bip9\""},
+ {RPCResult::Type::NUM, "height", /*optional=*/true, "height of the first block which the rules are or will be enforced (only for \"buried\" type, or \"bip9\" type with \"active\" status)"},
+ {RPCResult::Type::BOOL, "active", "true if the rules are enforced for the mempool and the next block"},
+ {RPCResult::Type::OBJ, "bip9", /*optional=*/true, "status of bip9 softforks (only for \"bip9\" type)",
+ {
+ {RPCResult::Type::NUM, "bit", /*optional=*/true, "the bit (0-28) in the block version field used to signal this softfork (only for \"started\" and \"locked_in\" status)"},
+ {RPCResult::Type::NUM_TIME, "start_time", "the minimum median time past of a block at which the bit gains its meaning"},
+ {RPCResult::Type::NUM_TIME, "timeout", "the median time past of a block at which the deployment is considered failed if not yet locked in"},
+ {RPCResult::Type::NUM, "min_activation_height", "minimum height of blocks for which the rules may be enforced"},
+ {RPCResult::Type::STR, "status", "status of deployment at specified block (one of \"defined\", \"started\", \"locked_in\", \"active\", \"failed\")"},
+ {RPCResult::Type::NUM, "since", "height of the first block to which the status applies"},
+ {RPCResult::Type::STR, "status_next", "status of deployment at the next block"},
+ {RPCResult::Type::OBJ, "statistics", /*optional=*/true, "numeric statistics about signalling for a softfork (only for \"started\" and \"locked_in\" status)",
+ {
+ {RPCResult::Type::NUM, "period", "the length in blocks of the signalling period"},
+ {RPCResult::Type::NUM, "threshold", /*optional=*/true, "the number of blocks with the version bit set required to activate the feature (only for \"started\" status)"},
+ {RPCResult::Type::NUM, "elapsed", "the number of blocks elapsed since the beginning of the current period"},
+ {RPCResult::Type::NUM, "count", "the number of blocks with the version bit set in the current period"},
+ {RPCResult::Type::BOOL, "possible", /*optional=*/true, "returns false if there are not enough blocks left in this period to pass activation threshold (only for \"started\" status)"},
+ }},
+ {RPCResult::Type::STR, "signalling", /*optional=*/true, "indicates blocks that signalled with a # and blocks that did not with a -"},
+ }},
+};
+
+UniValue DeploymentInfo(const CBlockIndex* blockindex, const Consensus::Params& consensusParams)
+{
+ UniValue softforks(UniValue::VOBJ);
+ SoftForkDescPushBack(blockindex, softforks, consensusParams, Consensus::DEPLOYMENT_HEIGHTINCB);
+ SoftForkDescPushBack(blockindex, softforks, consensusParams, Consensus::DEPLOYMENT_DERSIG);
+ SoftForkDescPushBack(blockindex, softforks, consensusParams, Consensus::DEPLOYMENT_CLTV);
+ SoftForkDescPushBack(blockindex, softforks, consensusParams, Consensus::DEPLOYMENT_CSV);
+ SoftForkDescPushBack(blockindex, softforks, consensusParams, Consensus::DEPLOYMENT_SEGWIT);
+ SoftForkDescPushBack(blockindex, softforks, consensusParams, Consensus::DEPLOYMENT_TESTDUMMY);
+ SoftForkDescPushBack(blockindex, softforks, consensusParams, Consensus::DEPLOYMENT_TAPROOT);
+ return softforks;
+}
+} // anon namespace
+
+static RPCHelpMan getdeploymentinfo()
+{
+ return RPCHelpMan{"getdeploymentinfo",
+ "Returns an object containing various state info regarding deployments of consensus changes.",
+ {
+ {"blockhash", RPCArg::Type::STR_HEX, RPCArg::Default{"hash of current chain tip"}, "The block hash at which to query deployment state"},
+ },
+ RPCResult{
+ RPCResult::Type::OBJ, "", "", {
+ {RPCResult::Type::STR, "hash", "requested block hash (or tip)"},
+ {RPCResult::Type::NUM, "height", "requested block height (or tip)"},
+ {RPCResult::Type::OBJ_DYN, "deployments", "", {
+ {RPCResult::Type::OBJ, "xxxx", "name of the deployment", RPCHelpForDeployment}
+ }},
+ }
+ },
+ RPCExamples{ HelpExampleCli("getdeploymentinfo", "") + HelpExampleRpc("getdeploymentinfo", "") },
+ [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
+ {
+ const ChainstateManager& chainman = EnsureAnyChainman(request.context);
+ LOCK(cs_main);
+ const CChainState& active_chainstate = chainman.ActiveChainstate();
+
+ const CBlockIndex* blockindex;
+ if (request.params[0].isNull()) {
+ blockindex = CHECK_NONFATAL(active_chainstate.m_chain.Tip());
+ } else {
+ const uint256 hash(ParseHashV(request.params[0], "blockhash"));
+ blockindex = chainman.m_blockman.LookupBlockIndex(hash);
+ if (!blockindex) {
+ throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Block not found");
+ }
+ }
+
+ const Consensus::Params& consensusParams = Params().GetConsensus();
+
+ UniValue deploymentinfo(UniValue::VOBJ);
+ deploymentinfo.pushKV("hash", blockindex->GetBlockHash().ToString());
+ deploymentinfo.pushKV("height", blockindex->nHeight);
+ deploymentinfo.pushKV("deployments", DeploymentInfo(blockindex, consensusParams));
+ return deploymentinfo;
+ },
+ };
+}
+
/** Comparison function for sorting the getchaintips heads. */
struct CompareBlocksByHeight
{
@@ -1667,10 +1379,10 @@ static RPCHelpMan getchaintips()
std::set<const CBlockIndex*> setOrphans;
std::set<const CBlockIndex*> setPrevs;
- for (const std::pair<const uint256, CBlockIndex*>& item : chainman.BlockIndex()) {
- if (!active_chain.Contains(item.second)) {
- setOrphans.insert(item.second);
- setPrevs.insert(item.second->pprev);
+ for (const auto& [_, block_index] : chainman.BlockIndex()) {
+ if (!active_chain.Contains(&block_index)) {
+ setOrphans.insert(&block_index);
+ setPrevs.insert(block_index.pprev);
}
}
@@ -1723,53 +1435,6 @@ static RPCHelpMan getchaintips()
};
}
-UniValue MempoolInfoToJSON(const CTxMemPool& pool)
-{
- // Make sure this call is atomic in the pool.
- LOCK(pool.cs);
- UniValue ret(UniValue::VOBJ);
- ret.pushKV("loaded", pool.IsLoaded());
- ret.pushKV("size", (int64_t)pool.size());
- ret.pushKV("bytes", (int64_t)pool.GetTotalTxSize());
- ret.pushKV("usage", (int64_t)pool.DynamicMemoryUsage());
- ret.pushKV("total_fee", ValueFromAmount(pool.GetTotalFee()));
- size_t maxmempool = gArgs.GetIntArg("-maxmempool", DEFAULT_MAX_MEMPOOL_SIZE) * 1000000;
- ret.pushKV("maxmempool", (int64_t) maxmempool);
- ret.pushKV("mempoolminfee", ValueFromAmount(std::max(pool.GetMinFee(maxmempool), ::minRelayTxFee).GetFeePerK()));
- ret.pushKV("minrelaytxfee", ValueFromAmount(::minRelayTxFee.GetFeePerK()));
- ret.pushKV("unbroadcastcount", uint64_t{pool.GetUnbroadcastTxs().size()});
- return ret;
-}
-
-static RPCHelpMan getmempoolinfo()
-{
- return RPCHelpMan{"getmempoolinfo",
- "\nReturns details on the active state of the TX memory pool.\n",
- {},
- RPCResult{
- RPCResult::Type::OBJ, "", "",
- {
- {RPCResult::Type::BOOL, "loaded", "True if the mempool is fully loaded"},
- {RPCResult::Type::NUM, "size", "Current tx count"},
- {RPCResult::Type::NUM, "bytes", "Sum of all virtual transaction sizes as defined in BIP 141. Differs from actual serialized size because witness data is discounted"},
- {RPCResult::Type::NUM, "usage", "Total memory usage for the mempool"},
- {RPCResult::Type::STR_AMOUNT, "total_fee", "Total fees for the mempool in " + CURRENCY_UNIT + ", ignoring modified fees through prioritisetransaction"},
- {RPCResult::Type::NUM, "maxmempool", "Maximum memory usage for the mempool"},
- {RPCResult::Type::STR_AMOUNT, "mempoolminfee", "Minimum fee rate in " + CURRENCY_UNIT + "/kvB for tx to be accepted. Is the maximum of minrelaytxfee and minimum mempool fee"},
- {RPCResult::Type::STR_AMOUNT, "minrelaytxfee", "Current minimum relay fee for transactions"},
- {RPCResult::Type::NUM, "unbroadcastcount", "Current number of transactions that haven't passed initial broadcast yet"}
- }},
- RPCExamples{
- HelpExampleCli("getmempoolinfo", "")
- + HelpExampleRpc("getmempoolinfo", "")
- },
- [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
-{
- return MempoolInfoToJSON(EnsureAnyMemPool(request.context));
-},
- };
-}
-
static RPCHelpMan preciousblock()
{
return RPCHelpMan{"preciousblock",
@@ -2091,7 +1756,7 @@ static RPCHelpMan getblockstats()
{
ChainstateManager& chainman = EnsureAnyChainman(request.context);
LOCK(cs_main);
- CBlockIndex* pindex{ParseHashOrHeight(request.params[0], chainman)};
+ const CBlockIndex* pindex{ParseHashOrHeight(request.params[0], chainman)};
CHECK_NONFATAL(pindex != nullptr);
std::set<std::string> stats;
@@ -2103,8 +1768,8 @@ static RPCHelpMan getblockstats()
}
}
- const CBlock block = GetBlockChecked(pindex);
- const CBlockUndo blockUndo = GetUndoChecked(pindex);
+ const CBlock block = GetBlockChecked(chainman.m_blockman, pindex);
+ const CBlockUndo blockUndo = GetUndoChecked(chainman.m_blockman, pindex);
const bool do_all = stats.size() == 0; // Calculate everything if nothing selected (default)
const bool do_mediantxsize = do_all || stats.count("mediantxsize") != 0;
@@ -2266,41 +1931,6 @@ static RPCHelpMan getblockstats()
};
}
-static RPCHelpMan savemempool()
-{
- return RPCHelpMan{"savemempool",
- "\nDumps the mempool to disk. It will fail until the previous dump is fully loaded.\n",
- {},
- RPCResult{
- RPCResult::Type::OBJ, "", "",
- {
- {RPCResult::Type::STR, "filename", "the directory and file where the mempool was saved"},
- }},
- RPCExamples{
- HelpExampleCli("savemempool", "")
- + HelpExampleRpc("savemempool", "")
- },
- [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
-{
- const ArgsManager& args{EnsureAnyArgsman(request.context)};
- const CTxMemPool& mempool = EnsureAnyMemPool(request.context);
-
- if (!mempool.IsLoaded()) {
- throw JSONRPCError(RPC_MISC_ERROR, "The mempool was not loaded yet");
- }
-
- if (!DumpMempool(mempool)) {
- throw JSONRPCError(RPC_MISC_ERROR, "Unable to dump mempool to disk");
- }
-
- UniValue ret(UniValue::VOBJ);
- ret.pushKV("filename", fs::path((args.GetDataDirNet() / "mempool.dat")).u8string());
-
- return ret;
-},
- };
-}
-
namespace {
//! Search for a given set of pubkey scripts
bool FindScriptPubKey(std::atomic<int>& scan_progress, const std::atomic<bool>& should_abort, int64_t& count, CCoinsViewCursor* cursor, const std::set<CScript>& needles, std::map<COutPoint, Coin>& out_results, std::function<void()>& interruption_point)
@@ -2486,17 +2116,15 @@ static RPCHelpMan scantxoutset()
g_should_abort_scan = false;
int64_t count = 0;
std::unique_ptr<CCoinsViewCursor> pcursor;
- CBlockIndex* tip;
+ const CBlockIndex* tip;
NodeContext& node = EnsureAnyNodeContext(request.context);
{
ChainstateManager& chainman = EnsureChainman(node);
LOCK(cs_main);
CChainState& active_chainstate = chainman.ActiveChainstate();
active_chainstate.ForceFlushStateToDisk();
- pcursor = active_chainstate.CoinsDB().Cursor();
- CHECK_NONFATAL(pcursor);
- tip = active_chainstate.m_chain.Tip();
- CHECK_NONFATAL(tip);
+ pcursor = CHECK_NONFATAL(active_chainstate.CoinsDB().Cursor());
+ tip = CHECK_NONFATAL(active_chainstate.m_chain.Tip());
}
bool res = FindScriptPubKey(g_scan_progress, g_should_abort_scan, count, pcursor.get(), needles, coins, node.rpc_interruption_point);
result.pushKV("success", res);
@@ -2674,7 +2302,7 @@ UniValue CreateUTXOSnapshot(
{
std::unique_ptr<CCoinsViewCursor> pcursor;
CCoinsStats stats{CoinStatsHashType::HASH_SERIALIZED};
- CBlockIndex* tip;
+ const CBlockIndex* tip;
{
// We need to lock cs_main to ensure that the coinsdb isn't written to
@@ -2698,8 +2326,7 @@ UniValue CreateUTXOSnapshot(
}
pcursor = chainstate.CoinsDB().Cursor();
- tip = chainstate.m_blockman.LookupBlockIndex(stats.hashBlock);
- CHECK_NONFATAL(tip);
+ tip = CHECK_NONFATAL(chainstate.m_blockman.LookupBlockIndex(stats.hashBlock));
}
LOG_TIME_SECONDS(strprintf("writing UTXO snapshot at height %s (%s) to file %s (via %s)",
@@ -2739,48 +2366,36 @@ UniValue CreateUTXOSnapshot(
return result;
}
-void RegisterBlockchainRPCCommands(CRPCTable &t)
-{
-// clang-format off
-static const CRPCCommand commands[] =
-{ // category actor (function)
- // --------------------- ------------------------
- { "blockchain", &getblockchaininfo, },
- { "blockchain", &getchaintxstats, },
- { "blockchain", &getblockstats, },
- { "blockchain", &getbestblockhash, },
- { "blockchain", &getblockcount, },
- { "blockchain", &getblock, },
- { "blockchain", &getblockfrompeer, },
- { "blockchain", &getblockhash, },
- { "blockchain", &getblockheader, },
- { "blockchain", &getchaintips, },
- { "blockchain", &getdifficulty, },
- { "blockchain", &getmempoolancestors, },
- { "blockchain", &getmempooldescendants, },
- { "blockchain", &getmempoolentry, },
- { "blockchain", &getmempoolinfo, },
- { "blockchain", &getrawmempool, },
- { "blockchain", &gettxout, },
- { "blockchain", &gettxoutsetinfo, },
- { "blockchain", &pruneblockchain, },
- { "blockchain", &savemempool, },
- { "blockchain", &verifychain, },
-
- { "blockchain", &preciousblock, },
- { "blockchain", &scantxoutset, },
- { "blockchain", &getblockfilter, },
-
- /* Not shown in help */
- { "hidden", &invalidateblock, },
- { "hidden", &reconsiderblock, },
- { "hidden", &waitfornewblock, },
- { "hidden", &waitforblock, },
- { "hidden", &waitforblockheight, },
- { "hidden", &syncwithvalidationinterfacequeue, },
- { "hidden", &dumptxoutset, },
-};
-// clang-format on
+void RegisterBlockchainRPCCommands(CRPCTable& t)
+{
+ static const CRPCCommand commands[]{
+ {"blockchain", &getblockchaininfo},
+ {"blockchain", &getchaintxstats},
+ {"blockchain", &getblockstats},
+ {"blockchain", &getbestblockhash},
+ {"blockchain", &getblockcount},
+ {"blockchain", &getblock},
+ {"blockchain", &getblockfrompeer},
+ {"blockchain", &getblockhash},
+ {"blockchain", &getblockheader},
+ {"blockchain", &getchaintips},
+ {"blockchain", &getdifficulty},
+ {"blockchain", &getdeploymentinfo},
+ {"blockchain", &gettxout},
+ {"blockchain", &gettxoutsetinfo},
+ {"blockchain", &pruneblockchain},
+ {"blockchain", &verifychain},
+ {"blockchain", &preciousblock},
+ {"blockchain", &scantxoutset},
+ {"blockchain", &getblockfilter},
+ {"hidden", &invalidateblock},
+ {"hidden", &reconsiderblock},
+ {"hidden", &waitfornewblock},
+ {"hidden", &waitforblock},
+ {"hidden", &waitforblockheight},
+ {"hidden", &syncwithvalidationinterfacequeue},
+ {"hidden", &dumptxoutset},
+ };
for (const auto& c : commands) {
t.appendCommand(c.name, &c);
}
diff --git a/src/rpc/blockchain.h b/src/rpc/blockchain.h
index 1f51d7c1ad..5fbd9d5fd3 100644
--- a/src/rpc/blockchain.h
+++ b/src/rpc/blockchain.h
@@ -10,6 +10,7 @@
#include <fs.h>
#include <streams.h>
#include <sync.h>
+#include <validation.h>
#include <any>
#include <stdint.h>
@@ -20,7 +21,6 @@ extern RecursiveMutex cs_main;
class CBlock;
class CBlockIndex;
class CChainState;
-class CTxMemPool;
class UniValue;
namespace node {
struct NodeContext;
@@ -40,13 +40,7 @@ double GetDifficulty(const CBlockIndex* blockindex);
void RPCNotifyBlockChange(const CBlockIndex*);
/** Block description to JSON */
-UniValue blockToJSON(const CBlock& block, const CBlockIndex* tip, const CBlockIndex* blockindex, TxVerbosity verbosity) LOCKS_EXCLUDED(cs_main);
-
-/** Mempool information to JSON */
-UniValue MempoolInfoToJSON(const CTxMemPool& pool);
-
-/** Mempool to JSON */
-UniValue MempoolToJSON(const CTxMemPool& pool, bool verbose = false, bool include_mempool_sequence = false);
+UniValue blockToJSON(node::BlockManager& blockman, const CBlock& block, const CBlockIndex* tip, const CBlockIndex* blockindex, TxVerbosity verbosity) LOCKS_EXCLUDED(cs_main);
/** Block header to JSON */
UniValue blockheaderToJSON(const CBlockIndex* tip, const CBlockIndex* blockindex) LOCKS_EXCLUDED(cs_main);
diff --git a/src/rpc/client.cpp b/src/rpc/client.cpp
index 003ba8bb20..23e9d4074c 100644
--- a/src/rpc/client.cpp
+++ b/src/rpc/client.cpp
@@ -60,7 +60,7 @@ static const CRPCConvertParam vRPCConvertParams[] =
{ "getbalance", 1, "minconf" },
{ "getbalance", 2, "include_watchonly" },
{ "getbalance", 3, "avoid_reuse" },
- { "getblockfrompeer", 1, "nodeid" },
+ { "getblockfrompeer", 1, "peer_id" },
{ "getblockhash", 0, "height" },
{ "waitforblockheight", 0, "height" },
{ "waitforblockheight", 1, "timeout" },
@@ -142,6 +142,10 @@ static const CRPCConvertParam vRPCConvertParams[] =
{ "send", 1, "conf_target" },
{ "send", 3, "fee_rate"},
{ "send", 4, "options" },
+ { "sendall", 0, "recipients" },
+ { "sendall", 1, "conf_target" },
+ { "sendall", 3, "fee_rate"},
+ { "sendall", 4, "options" },
{ "importprivkey", 2, "rescan" },
{ "importaddress", 2, "rescan" },
{ "importaddress", 3, "p2sh" },
diff --git a/src/rpc/external_signer.cpp b/src/rpc/external_signer.cpp
index 60ec15e904..4de7fc4205 100644
--- a/src/rpc/external_signer.cpp
+++ b/src/rpc/external_signer.cpp
@@ -22,7 +22,7 @@ static RPCHelpMan enumeratesigners()
RPCResult{
RPCResult::Type::OBJ, "", "",
{
- {RPCResult::Type::ARR, "signers", /* optional */ false, "",
+ {RPCResult::Type::ARR, "signers", /*optional=*/false, "",
{
{RPCResult::Type::OBJ, "", "",
{
@@ -62,15 +62,11 @@ static RPCHelpMan enumeratesigners()
};
}
-void RegisterSignerRPCCommands(CRPCTable &t)
+void RegisterSignerRPCCommands(CRPCTable& t)
{
-// clang-format off
-static const CRPCCommand commands[] =
-{ // category actor (function)
- // --------------------- ------------------------
- { "signer", &enumeratesigners, },
-};
-// clang-format on
+ static const CRPCCommand commands[]{
+ {"signer", &enumeratesigners},
+ };
for (const auto& c : commands) {
t.appendCommand(c.name, &c);
}
diff --git a/src/rpc/fees.cpp b/src/rpc/fees.cpp
new file mode 100644
index 0000000000..bfec0780aa
--- /dev/null
+++ b/src/rpc/fees.cpp
@@ -0,0 +1,236 @@
+// Copyright (c) 2010 Satoshi Nakamoto
+// Copyright (c) 2009-2021 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 <core_io.h>
+#include <policy/feerate.h>
+#include <policy/fees.h>
+#include <policy/policy.h>
+#include <rpc/protocol.h>
+#include <rpc/request.h>
+#include <rpc/server.h>
+#include <rpc/server_util.h>
+#include <rpc/util.h>
+#include <txmempool.h>
+#include <univalue.h>
+#include <util/fees.h>
+#include <util/system.h>
+#include <validation.h>
+
+#include <algorithm>
+#include <array>
+#include <cmath>
+#include <string>
+
+namespace node {
+struct NodeContext;
+}
+
+using node::NodeContext;
+
+static RPCHelpMan estimatesmartfee()
+{
+ return RPCHelpMan{"estimatesmartfee",
+ "\nEstimates the approximate fee per kilobyte needed for a transaction to begin\n"
+ "confirmation within conf_target blocks if possible and return the number of blocks\n"
+ "for which the estimate is valid. Uses virtual transaction size as defined\n"
+ "in BIP 141 (witness data is discounted).\n",
+ {
+ {"conf_target", RPCArg::Type::NUM, RPCArg::Optional::NO, "Confirmation target in blocks (1 - 1008)"},
+ {"estimate_mode", RPCArg::Type::STR, RPCArg::Default{"conservative"}, "The fee estimate mode.\n"
+ "Whether to return a more conservative estimate which also satisfies\n"
+ "a longer history. A conservative estimate potentially returns a\n"
+ "higher feerate and is more likely to be sufficient for the desired\n"
+ "target, but is not as responsive to short term drops in the\n"
+ "prevailing fee market. Must be one of (case insensitive):\n"
+ "\"" + FeeModes("\"\n\"") + "\""},
+ },
+ RPCResult{
+ RPCResult::Type::OBJ, "", "",
+ {
+ {RPCResult::Type::NUM, "feerate", /*optional=*/true, "estimate fee rate in " + CURRENCY_UNIT + "/kvB (only present if no errors were encountered)"},
+ {RPCResult::Type::ARR, "errors", /*optional=*/true, "Errors encountered during processing (if there are any)",
+ {
+ {RPCResult::Type::STR, "", "error"},
+ }},
+ {RPCResult::Type::NUM, "blocks", "block number where estimate was found\n"
+ "The request target will be clamped between 2 and the highest target\n"
+ "fee estimation is able to return based on how long it has been running.\n"
+ "An error is returned if not enough transactions and blocks\n"
+ "have been observed to make an estimate for any number of blocks."},
+ }},
+ RPCExamples{
+ HelpExampleCli("estimatesmartfee", "6") +
+ HelpExampleRpc("estimatesmartfee", "6")
+ },
+ [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
+ {
+ RPCTypeCheck(request.params, {UniValue::VNUM, UniValue::VSTR});
+ RPCTypeCheckArgument(request.params[0], UniValue::VNUM);
+
+ CBlockPolicyEstimator& fee_estimator = EnsureAnyFeeEstimator(request.context);
+ const NodeContext& node = EnsureAnyNodeContext(request.context);
+ const CTxMemPool& mempool = EnsureMemPool(node);
+
+ unsigned int max_target = fee_estimator.HighestTargetTracked(FeeEstimateHorizon::LONG_HALFLIFE);
+ unsigned int conf_target = ParseConfirmTarget(request.params[0], max_target);
+ bool conservative = true;
+ if (!request.params[1].isNull()) {
+ FeeEstimateMode fee_mode;
+ if (!FeeModeFromString(request.params[1].get_str(), fee_mode)) {
+ throw JSONRPCError(RPC_INVALID_PARAMETER, InvalidEstimateModeErrorMessage());
+ }
+ if (fee_mode == FeeEstimateMode::ECONOMICAL) conservative = false;
+ }
+
+ UniValue result(UniValue::VOBJ);
+ UniValue errors(UniValue::VARR);
+ FeeCalculation feeCalc;
+ CFeeRate feeRate{fee_estimator.estimateSmartFee(conf_target, &feeCalc, conservative)};
+ if (feeRate != CFeeRate(0)) {
+ CFeeRate min_mempool_feerate{mempool.GetMinFee(gArgs.GetIntArg("-maxmempool", DEFAULT_MAX_MEMPOOL_SIZE) * 1000000)};
+ CFeeRate min_relay_feerate{::minRelayTxFee};
+ feeRate = std::max({feeRate, min_mempool_feerate, min_relay_feerate});
+ result.pushKV("feerate", ValueFromAmount(feeRate.GetFeePerK()));
+ } else {
+ errors.push_back("Insufficient data or no feerate found");
+ result.pushKV("errors", errors);
+ }
+ result.pushKV("blocks", feeCalc.returnedTarget);
+ return result;
+ },
+ };
+}
+
+static RPCHelpMan estimaterawfee()
+{
+ return RPCHelpMan{"estimaterawfee",
+ "\nWARNING: This interface is unstable and may disappear or change!\n"
+ "\nWARNING: This is an advanced API call that is tightly coupled to the specific\n"
+ "implementation of fee estimation. The parameters it can be called with\n"
+ "and the results it returns will change if the internal implementation changes.\n"
+ "\nEstimates the approximate fee per kilobyte needed for a transaction to begin\n"
+ "confirmation within conf_target blocks if possible. Uses virtual transaction size as\n"
+ "defined in BIP 141 (witness data is discounted).\n",
+ {
+ {"conf_target", RPCArg::Type::NUM, RPCArg::Optional::NO, "Confirmation target in blocks (1 - 1008)"},
+ {"threshold", RPCArg::Type::NUM, RPCArg::Default{0.95}, "The proportion of transactions in a given feerate range that must have been\n"
+ "confirmed within conf_target in order to consider those feerates as high enough and proceed to check\n"
+ "lower buckets."},
+ },
+ RPCResult{
+ RPCResult::Type::OBJ, "", "Results are returned for any horizon which tracks blocks up to the confirmation target",
+ {
+ {RPCResult::Type::OBJ, "short", /*optional=*/true, "estimate for short time horizon",
+ {
+ {RPCResult::Type::NUM, "feerate", /*optional=*/true, "estimate fee rate in " + CURRENCY_UNIT + "/kvB"},
+ {RPCResult::Type::NUM, "decay", "exponential decay (per block) for historical moving average of confirmation data"},
+ {RPCResult::Type::NUM, "scale", "The resolution of confirmation targets at this time horizon"},
+ {RPCResult::Type::OBJ, "pass", /*optional=*/true, "information about the lowest range of feerates to succeed in meeting the threshold",
+ {
+ {RPCResult::Type::NUM, "startrange", "start of feerate range"},
+ {RPCResult::Type::NUM, "endrange", "end of feerate range"},
+ {RPCResult::Type::NUM, "withintarget", "number of txs over history horizon in the feerate range that were confirmed within target"},
+ {RPCResult::Type::NUM, "totalconfirmed", "number of txs over history horizon in the feerate range that were confirmed at any point"},
+ {RPCResult::Type::NUM, "inmempool", "current number of txs in mempool in the feerate range unconfirmed for at least target blocks"},
+ {RPCResult::Type::NUM, "leftmempool", "number of txs over history horizon in the feerate range that left mempool unconfirmed after target"},
+ }},
+ {RPCResult::Type::OBJ, "fail", /*optional=*/true, "information about the highest range of feerates to fail to meet the threshold",
+ {
+ {RPCResult::Type::ELISION, "", ""},
+ }},
+ {RPCResult::Type::ARR, "errors", /*optional=*/true, "Errors encountered during processing (if there are any)",
+ {
+ {RPCResult::Type::STR, "error", ""},
+ }},
+ }},
+ {RPCResult::Type::OBJ, "medium", /*optional=*/true, "estimate for medium time horizon",
+ {
+ {RPCResult::Type::ELISION, "", ""},
+ }},
+ {RPCResult::Type::OBJ, "long", /*optional=*/true, "estimate for long time horizon",
+ {
+ {RPCResult::Type::ELISION, "", ""},
+ }},
+ }},
+ RPCExamples{
+ HelpExampleCli("estimaterawfee", "6 0.9")
+ },
+ [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
+ {
+ RPCTypeCheck(request.params, {UniValue::VNUM, UniValue::VNUM}, true);
+ RPCTypeCheckArgument(request.params[0], UniValue::VNUM);
+
+ CBlockPolicyEstimator& fee_estimator = EnsureAnyFeeEstimator(request.context);
+
+ unsigned int max_target = fee_estimator.HighestTargetTracked(FeeEstimateHorizon::LONG_HALFLIFE);
+ unsigned int conf_target = ParseConfirmTarget(request.params[0], max_target);
+ double threshold = 0.95;
+ if (!request.params[1].isNull()) {
+ threshold = request.params[1].get_real();
+ }
+ if (threshold < 0 || threshold > 1) {
+ throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid threshold");
+ }
+
+ UniValue result(UniValue::VOBJ);
+
+ for (const FeeEstimateHorizon horizon : ALL_FEE_ESTIMATE_HORIZONS) {
+ CFeeRate feeRate;
+ EstimationResult buckets;
+
+ // Only output results for horizons which track the target
+ if (conf_target > fee_estimator.HighestTargetTracked(horizon)) continue;
+
+ feeRate = fee_estimator.estimateRawFee(conf_target, threshold, horizon, &buckets);
+ UniValue horizon_result(UniValue::VOBJ);
+ UniValue errors(UniValue::VARR);
+ UniValue passbucket(UniValue::VOBJ);
+ passbucket.pushKV("startrange", round(buckets.pass.start));
+ passbucket.pushKV("endrange", round(buckets.pass.end));
+ passbucket.pushKV("withintarget", round(buckets.pass.withinTarget * 100.0) / 100.0);
+ passbucket.pushKV("totalconfirmed", round(buckets.pass.totalConfirmed * 100.0) / 100.0);
+ passbucket.pushKV("inmempool", round(buckets.pass.inMempool * 100.0) / 100.0);
+ passbucket.pushKV("leftmempool", round(buckets.pass.leftMempool * 100.0) / 100.0);
+ UniValue failbucket(UniValue::VOBJ);
+ failbucket.pushKV("startrange", round(buckets.fail.start));
+ failbucket.pushKV("endrange", round(buckets.fail.end));
+ failbucket.pushKV("withintarget", round(buckets.fail.withinTarget * 100.0) / 100.0);
+ failbucket.pushKV("totalconfirmed", round(buckets.fail.totalConfirmed * 100.0) / 100.0);
+ failbucket.pushKV("inmempool", round(buckets.fail.inMempool * 100.0) / 100.0);
+ failbucket.pushKV("leftmempool", round(buckets.fail.leftMempool * 100.0) / 100.0);
+
+ // CFeeRate(0) is used to indicate error as a return value from estimateRawFee
+ if (feeRate != CFeeRate(0)) {
+ horizon_result.pushKV("feerate", ValueFromAmount(feeRate.GetFeePerK()));
+ horizon_result.pushKV("decay", buckets.decay);
+ horizon_result.pushKV("scale", (int)buckets.scale);
+ horizon_result.pushKV("pass", passbucket);
+ // buckets.fail.start == -1 indicates that all buckets passed, there is no fail bucket to output
+ if (buckets.fail.start != -1) horizon_result.pushKV("fail", failbucket);
+ } else {
+ // Output only information that is still meaningful in the event of error
+ horizon_result.pushKV("decay", buckets.decay);
+ horizon_result.pushKV("scale", (int)buckets.scale);
+ horizon_result.pushKV("fail", failbucket);
+ errors.push_back("Insufficient data or no feerate found which meets threshold");
+ horizon_result.pushKV("errors", errors);
+ }
+ result.pushKV(StringForFeeEstimateHorizon(horizon), horizon_result);
+ }
+ return result;
+ },
+ };
+}
+
+void RegisterFeeRPCCommands(CRPCTable& t)
+{
+ static const CRPCCommand commands[]{
+ {"util", &estimatesmartfee},
+ {"hidden", &estimaterawfee},
+ };
+ for (const auto& c : commands) {
+ t.appendCommand(c.name, &c);
+ }
+}
diff --git a/src/rpc/mempool.cpp b/src/rpc/mempool.cpp
new file mode 100644
index 0000000000..27080d3881
--- /dev/null
+++ b/src/rpc/mempool.cpp
@@ -0,0 +1,687 @@
+// 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 <rpc/blockchain.h>
+
+#include <core_io.h>
+#include <fs.h>
+#include <policy/rbf.h>
+#include <primitives/transaction.h>
+#include <rpc/server.h>
+#include <rpc/server_util.h>
+#include <rpc/util.h>
+#include <txmempool.h>
+#include <univalue.h>
+#include <util/moneystr.h>
+#include <validation.h>
+
+using node::DEFAULT_MAX_RAW_TX_FEE_RATE;
+using node::NodeContext;
+
+static RPCHelpMan sendrawtransaction()
+{
+ return RPCHelpMan{"sendrawtransaction",
+ "\nSubmit a raw transaction (serialized, hex-encoded) to local node and network.\n"
+ "\nThe transaction will be sent unconditionally to all peers, so using sendrawtransaction\n"
+ "for manual rebroadcast may degrade privacy by leaking the transaction's origin, as\n"
+ "nodes will normally not rebroadcast non-wallet transactions already in their mempool.\n"
+ "\nA specific exception, RPC_TRANSACTION_ALREADY_IN_CHAIN, may throw if the transaction cannot be added to the mempool.\n"
+ "\nRelated RPCs: createrawtransaction, signrawtransactionwithkey\n",
+ {
+ {"hexstring", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "The hex string of the raw transaction"},
+ {"maxfeerate", RPCArg::Type::AMOUNT, RPCArg::Default{FormatMoney(DEFAULT_MAX_RAW_TX_FEE_RATE.GetFeePerK())},
+ "Reject transactions whose fee rate is higher than the specified value, expressed in " + CURRENCY_UNIT +
+ "/kvB.\nSet to 0 to accept any fee rate.\n"},
+ },
+ RPCResult{
+ RPCResult::Type::STR_HEX, "", "The transaction hash in hex"
+ },
+ RPCExamples{
+ "\nCreate a transaction\n"
+ + HelpExampleCli("createrawtransaction", "\"[{\\\"txid\\\" : \\\"mytxid\\\",\\\"vout\\\":0}]\" \"{\\\"myaddress\\\":0.01}\"") +
+ "Sign the transaction, and get back the hex\n"
+ + HelpExampleCli("signrawtransactionwithwallet", "\"myhex\"") +
+ "\nSend the transaction (signed hex)\n"
+ + HelpExampleCli("sendrawtransaction", "\"signedhex\"") +
+ "\nAs a JSON-RPC call\n"
+ + HelpExampleRpc("sendrawtransaction", "\"signedhex\"")
+ },
+ [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
+ {
+ RPCTypeCheck(request.params, {
+ UniValue::VSTR,
+ UniValueType(), // VNUM or VSTR, checked inside AmountFromValue()
+ });
+
+ CMutableTransaction mtx;
+ if (!DecodeHexTx(mtx, request.params[0].get_str())) {
+ throw JSONRPCError(RPC_DESERIALIZATION_ERROR, "TX decode failed. Make sure the tx has at least one input.");
+ }
+ CTransactionRef tx(MakeTransactionRef(std::move(mtx)));
+
+ const CFeeRate max_raw_tx_fee_rate = request.params[1].isNull() ?
+ DEFAULT_MAX_RAW_TX_FEE_RATE :
+ CFeeRate(AmountFromValue(request.params[1]));
+
+ int64_t virtual_size = GetVirtualTransactionSize(*tx);
+ CAmount max_raw_tx_fee = max_raw_tx_fee_rate.GetFee(virtual_size);
+
+ std::string err_string;
+ AssertLockNotHeld(cs_main);
+ NodeContext& node = EnsureAnyNodeContext(request.context);
+ const TransactionError err = BroadcastTransaction(node, tx, err_string, max_raw_tx_fee, /*relay=*/true, /*wait_callback=*/true);
+ if (TransactionError::OK != err) {
+ throw JSONRPCTransactionError(err, err_string);
+ }
+
+ return tx->GetHash().GetHex();
+ },
+ };
+}
+
+static RPCHelpMan testmempoolaccept()
+{
+ return RPCHelpMan{"testmempoolaccept",
+ "\nReturns result of mempool acceptance tests indicating if raw transaction(s) (serialized, hex-encoded) would be accepted by mempool.\n"
+ "\nIf multiple transactions are passed in, parents must come before children and package policies apply: the transactions cannot conflict with any mempool transactions or each other.\n"
+ "\nIf one transaction fails, other transactions may not be fully validated (the 'allowed' key will be blank).\n"
+ "\nThe maximum number of transactions allowed is " + ToString(MAX_PACKAGE_COUNT) + ".\n"
+ "\nThis checks if transactions violate the consensus or policy rules.\n"
+ "\nSee sendrawtransaction call.\n",
+ {
+ {"rawtxs", RPCArg::Type::ARR, RPCArg::Optional::NO, "An array of hex strings of raw transactions.",
+ {
+ {"rawtx", RPCArg::Type::STR_HEX, RPCArg::Optional::OMITTED, ""},
+ },
+ },
+ {"maxfeerate", RPCArg::Type::AMOUNT, RPCArg::Default{FormatMoney(DEFAULT_MAX_RAW_TX_FEE_RATE.GetFeePerK())},
+ "Reject transactions whose fee rate is higher than the specified value, expressed in " + CURRENCY_UNIT + "/kvB\n"},
+ },
+ RPCResult{
+ RPCResult::Type::ARR, "", "The result of the mempool acceptance test for each raw transaction in the input array.\n"
+ "Returns results for each transaction in the same order they were passed in.\n"
+ "Transactions that cannot be fully validated due to failures in other transactions will not contain an 'allowed' result.\n",
+ {
+ {RPCResult::Type::OBJ, "", "",
+ {
+ {RPCResult::Type::STR_HEX, "txid", "The transaction hash in hex"},
+ {RPCResult::Type::STR_HEX, "wtxid", "The transaction witness hash in hex"},
+ {RPCResult::Type::STR, "package-error", /*optional=*/true, "Package validation error, if any (only possible if rawtxs had more than 1 transaction)."},
+ {RPCResult::Type::BOOL, "allowed", /*optional=*/true, "Whether this tx would be accepted to the mempool and pass client-specified maxfeerate. "
+ "If not present, the tx was not fully validated due to a failure in another tx in the list."},
+ {RPCResult::Type::NUM, "vsize", /*optional=*/true, "Virtual transaction size as defined in BIP 141. This is different from actual serialized size for witness transactions as witness data is discounted (only present when 'allowed' is true)"},
+ {RPCResult::Type::OBJ, "fees", /*optional=*/true, "Transaction fees (only present if 'allowed' is true)",
+ {
+ {RPCResult::Type::STR_AMOUNT, "base", "transaction fee in " + CURRENCY_UNIT},
+ }},
+ {RPCResult::Type::STR, "reject-reason", /*optional=*/true, "Rejection string (only present when 'allowed' is false)"},
+ }},
+ }
+ },
+ RPCExamples{
+ "\nCreate a transaction\n"
+ + HelpExampleCli("createrawtransaction", "\"[{\\\"txid\\\" : \\\"mytxid\\\",\\\"vout\\\":0}]\" \"{\\\"myaddress\\\":0.01}\"") +
+ "Sign the transaction, and get back the hex\n"
+ + HelpExampleCli("signrawtransactionwithwallet", "\"myhex\"") +
+ "\nTest acceptance of the transaction (signed hex)\n"
+ + HelpExampleCli("testmempoolaccept", R"('["signedhex"]')") +
+ "\nAs a JSON-RPC call\n"
+ + HelpExampleRpc("testmempoolaccept", "[\"signedhex\"]")
+ },
+ [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
+ {
+ RPCTypeCheck(request.params, {
+ UniValue::VARR,
+ UniValueType(), // VNUM or VSTR, checked inside AmountFromValue()
+ });
+ const UniValue raw_transactions = request.params[0].get_array();
+ if (raw_transactions.size() < 1 || raw_transactions.size() > MAX_PACKAGE_COUNT) {
+ throw JSONRPCError(RPC_INVALID_PARAMETER,
+ "Array must contain between 1 and " + ToString(MAX_PACKAGE_COUNT) + " transactions.");
+ }
+
+ const CFeeRate max_raw_tx_fee_rate = request.params[1].isNull() ?
+ DEFAULT_MAX_RAW_TX_FEE_RATE :
+ CFeeRate(AmountFromValue(request.params[1]));
+
+ std::vector<CTransactionRef> txns;
+ txns.reserve(raw_transactions.size());
+ for (const auto& rawtx : raw_transactions.getValues()) {
+ CMutableTransaction mtx;
+ if (!DecodeHexTx(mtx, rawtx.get_str())) {
+ throw JSONRPCError(RPC_DESERIALIZATION_ERROR,
+ "TX decode failed: " + rawtx.get_str() + " Make sure the tx has at least one input.");
+ }
+ txns.emplace_back(MakeTransactionRef(std::move(mtx)));
+ }
+
+ NodeContext& node = EnsureAnyNodeContext(request.context);
+ CTxMemPool& mempool = EnsureMemPool(node);
+ ChainstateManager& chainman = EnsureChainman(node);
+ CChainState& chainstate = chainman.ActiveChainstate();
+ const PackageMempoolAcceptResult package_result = [&] {
+ LOCK(::cs_main);
+ if (txns.size() > 1) return ProcessNewPackage(chainstate, mempool, txns, /*test_accept=*/true);
+ return PackageMempoolAcceptResult(txns[0]->GetWitnessHash(),
+ chainman.ProcessTransaction(txns[0], /*test_accept=*/true));
+ }();
+
+ UniValue rpc_result(UniValue::VARR);
+ // We will check transaction fees while we iterate through txns in order. If any transaction fee
+ // exceeds maxfeerate, we will leave the rest of the validation results blank, because it
+ // doesn't make sense to return a validation result for a transaction if its ancestor(s) would
+ // not be submitted.
+ bool exit_early{false};
+ for (const auto& tx : txns) {
+ UniValue result_inner(UniValue::VOBJ);
+ result_inner.pushKV("txid", tx->GetHash().GetHex());
+ result_inner.pushKV("wtxid", tx->GetWitnessHash().GetHex());
+ if (package_result.m_state.GetResult() == PackageValidationResult::PCKG_POLICY) {
+ result_inner.pushKV("package-error", package_result.m_state.GetRejectReason());
+ }
+ auto it = package_result.m_tx_results.find(tx->GetWitnessHash());
+ if (exit_early || it == package_result.m_tx_results.end()) {
+ // Validation unfinished. Just return the txid and wtxid.
+ rpc_result.push_back(result_inner);
+ continue;
+ }
+ const auto& tx_result = it->second;
+ // Package testmempoolaccept doesn't allow transactions to already be in the mempool.
+ CHECK_NONFATAL(tx_result.m_result_type != MempoolAcceptResult::ResultType::MEMPOOL_ENTRY);
+ if (tx_result.m_result_type == MempoolAcceptResult::ResultType::VALID) {
+ const CAmount fee = tx_result.m_base_fees.value();
+ // Check that fee does not exceed maximum fee
+ const int64_t virtual_size = tx_result.m_vsize.value();
+ const CAmount max_raw_tx_fee = max_raw_tx_fee_rate.GetFee(virtual_size);
+ if (max_raw_tx_fee && fee > max_raw_tx_fee) {
+ result_inner.pushKV("allowed", false);
+ result_inner.pushKV("reject-reason", "max-fee-exceeded");
+ exit_early = true;
+ } else {
+ // Only return the fee and vsize if the transaction would pass ATMP.
+ // These can be used to calculate the feerate.
+ result_inner.pushKV("allowed", true);
+ result_inner.pushKV("vsize", virtual_size);
+ UniValue fees(UniValue::VOBJ);
+ fees.pushKV("base", ValueFromAmount(fee));
+ result_inner.pushKV("fees", fees);
+ }
+ } else {
+ result_inner.pushKV("allowed", false);
+ const TxValidationState state = tx_result.m_state;
+ if (state.GetResult() == TxValidationResult::TX_MISSING_INPUTS) {
+ result_inner.pushKV("reject-reason", "missing-inputs");
+ } else {
+ result_inner.pushKV("reject-reason", state.GetRejectReason());
+ }
+ }
+ rpc_result.push_back(result_inner);
+ }
+ return rpc_result;
+ },
+ };
+}
+
+static std::vector<RPCResult> MempoolEntryDescription()
+{
+ return {
+ RPCResult{RPCResult::Type::NUM, "vsize", "virtual transaction size as defined in BIP 141. This is different from actual serialized size for witness transactions as witness data is discounted."},
+ RPCResult{RPCResult::Type::NUM, "weight", "transaction weight as defined in BIP 141."},
+ RPCResult{RPCResult::Type::STR_AMOUNT, "fee", /*optional=*/true,
+ "transaction fee, denominated in " + CURRENCY_UNIT + " (DEPRECATED, returned only if config option -deprecatedrpc=fees is passed)"},
+ RPCResult{RPCResult::Type::STR_AMOUNT, "modifiedfee", /*optional=*/true,
+ "transaction fee with fee deltas used for mining priority, denominated in " + CURRENCY_UNIT +
+ " (DEPRECATED, returned only if config option -deprecatedrpc=fees is passed)"},
+ RPCResult{RPCResult::Type::NUM_TIME, "time", "local time transaction entered pool in seconds since 1 Jan 1970 GMT"},
+ RPCResult{RPCResult::Type::NUM, "height", "block height when transaction entered pool"},
+ RPCResult{RPCResult::Type::NUM, "descendantcount", "number of in-mempool descendant transactions (including this one)"},
+ RPCResult{RPCResult::Type::NUM, "descendantsize", "virtual transaction size of in-mempool descendants (including this one)"},
+ RPCResult{RPCResult::Type::STR_AMOUNT, "descendantfees", /*optional=*/true,
+ "transaction fees of in-mempool descendants (including this one) with fee deltas used for mining priority, denominated in " +
+ CURRENCY_ATOM + "s (DEPRECATED, returned only if config option -deprecatedrpc=fees is passed)"},
+ RPCResult{RPCResult::Type::NUM, "ancestorcount", "number of in-mempool ancestor transactions (including this one)"},
+ RPCResult{RPCResult::Type::NUM, "ancestorsize", "virtual transaction size of in-mempool ancestors (including this one)"},
+ RPCResult{RPCResult::Type::STR_AMOUNT, "ancestorfees", /*optional=*/true,
+ "transaction fees of in-mempool ancestors (including this one) with fee deltas used for mining priority, denominated in " +
+ CURRENCY_ATOM + "s (DEPRECATED, returned only if config option -deprecatedrpc=fees is passed)"},
+ RPCResult{RPCResult::Type::STR_HEX, "wtxid", "hash of serialized transaction, including witness data"},
+ RPCResult{RPCResult::Type::OBJ, "fees", "",
+ {
+ RPCResult{RPCResult::Type::STR_AMOUNT, "base", "transaction fee, denominated in " + CURRENCY_UNIT},
+ RPCResult{RPCResult::Type::STR_AMOUNT, "modified", "transaction fee with fee deltas used for mining priority, denominated in " + CURRENCY_UNIT},
+ RPCResult{RPCResult::Type::STR_AMOUNT, "ancestor", "transaction fees of in-mempool ancestors (including this one) with fee deltas used for mining priority, denominated in " + CURRENCY_UNIT},
+ RPCResult{RPCResult::Type::STR_AMOUNT, "descendant", "transaction fees of in-mempool descendants (including this one) with fee deltas used for mining priority, denominated in " + CURRENCY_UNIT},
+ }},
+ RPCResult{RPCResult::Type::ARR, "depends", "unconfirmed transactions used as inputs for this transaction",
+ {RPCResult{RPCResult::Type::STR_HEX, "transactionid", "parent transaction id"}}},
+ RPCResult{RPCResult::Type::ARR, "spentby", "unconfirmed transactions spending outputs from this transaction",
+ {RPCResult{RPCResult::Type::STR_HEX, "transactionid", "child transaction id"}}},
+ RPCResult{RPCResult::Type::BOOL, "bip125-replaceable", "Whether this transaction could be replaced due to BIP125 (replace-by-fee)"},
+ RPCResult{RPCResult::Type::BOOL, "unbroadcast", "Whether this transaction is currently unbroadcast (initial broadcast not yet acknowledged by any peers)"},
+ };
+}
+
+static void entryToJSON(const CTxMemPool& pool, UniValue& info, const CTxMemPoolEntry& e) EXCLUSIVE_LOCKS_REQUIRED(pool.cs)
+{
+ AssertLockHeld(pool.cs);
+
+ info.pushKV("vsize", (int)e.GetTxSize());
+ info.pushKV("weight", (int)e.GetTxWeight());
+ // TODO: top-level fee fields are deprecated. deprecated_fee_fields_enabled blocks should be removed in v24
+ const bool deprecated_fee_fields_enabled{IsDeprecatedRPCEnabled("fees")};
+ if (deprecated_fee_fields_enabled) {
+ info.pushKV("fee", ValueFromAmount(e.GetFee()));
+ info.pushKV("modifiedfee", ValueFromAmount(e.GetModifiedFee()));
+ }
+ info.pushKV("time", count_seconds(e.GetTime()));
+ info.pushKV("height", (int)e.GetHeight());
+ info.pushKV("descendantcount", e.GetCountWithDescendants());
+ info.pushKV("descendantsize", e.GetSizeWithDescendants());
+ if (deprecated_fee_fields_enabled) {
+ info.pushKV("descendantfees", e.GetModFeesWithDescendants());
+ }
+ info.pushKV("ancestorcount", e.GetCountWithAncestors());
+ info.pushKV("ancestorsize", e.GetSizeWithAncestors());
+ if (deprecated_fee_fields_enabled) {
+ info.pushKV("ancestorfees", e.GetModFeesWithAncestors());
+ }
+ info.pushKV("wtxid", pool.vTxHashes[e.vTxHashesIdx].first.ToString());
+
+ UniValue fees(UniValue::VOBJ);
+ fees.pushKV("base", ValueFromAmount(e.GetFee()));
+ fees.pushKV("modified", ValueFromAmount(e.GetModifiedFee()));
+ fees.pushKV("ancestor", ValueFromAmount(e.GetModFeesWithAncestors()));
+ fees.pushKV("descendant", ValueFromAmount(e.GetModFeesWithDescendants()));
+ info.pushKV("fees", fees);
+
+ const CTransaction& tx = e.GetTx();
+ std::set<std::string> setDepends;
+ for (const CTxIn& txin : tx.vin)
+ {
+ if (pool.exists(GenTxid::Txid(txin.prevout.hash)))
+ setDepends.insert(txin.prevout.hash.ToString());
+ }
+
+ UniValue depends(UniValue::VARR);
+ for (const std::string& dep : setDepends)
+ {
+ depends.push_back(dep);
+ }
+
+ info.pushKV("depends", depends);
+
+ UniValue spent(UniValue::VARR);
+ const CTxMemPool::txiter& it = pool.mapTx.find(tx.GetHash());
+ const CTxMemPoolEntry::Children& children = it->GetMemPoolChildrenConst();
+ for (const CTxMemPoolEntry& child : children) {
+ spent.push_back(child.GetTx().GetHash().ToString());
+ }
+
+ info.pushKV("spentby", spent);
+
+ // Add opt-in RBF status
+ bool rbfStatus = false;
+ RBFTransactionState rbfState = IsRBFOptIn(tx, pool);
+ if (rbfState == RBFTransactionState::UNKNOWN) {
+ throw JSONRPCError(RPC_MISC_ERROR, "Transaction is not in mempool");
+ } else if (rbfState == RBFTransactionState::REPLACEABLE_BIP125) {
+ rbfStatus = true;
+ }
+
+ info.pushKV("bip125-replaceable", rbfStatus);
+ info.pushKV("unbroadcast", pool.IsUnbroadcastTx(tx.GetHash()));
+}
+
+UniValue MempoolToJSON(const CTxMemPool& pool, bool verbose, bool include_mempool_sequence)
+{
+ if (verbose) {
+ if (include_mempool_sequence) {
+ throw JSONRPCError(RPC_INVALID_PARAMETER, "Verbose results cannot contain mempool sequence values.");
+ }
+ LOCK(pool.cs);
+ UniValue o(UniValue::VOBJ);
+ for (const CTxMemPoolEntry& e : pool.mapTx) {
+ const uint256& hash = e.GetTx().GetHash();
+ UniValue info(UniValue::VOBJ);
+ entryToJSON(pool, info, e);
+ // Mempool has unique entries so there is no advantage in using
+ // UniValue::pushKV, which checks if the key already exists in O(N).
+ // UniValue::__pushKV is used instead which currently is O(1).
+ o.__pushKV(hash.ToString(), info);
+ }
+ return o;
+ } else {
+ uint64_t mempool_sequence;
+ std::vector<uint256> vtxid;
+ {
+ LOCK(pool.cs);
+ pool.queryHashes(vtxid);
+ mempool_sequence = pool.GetSequence();
+ }
+ UniValue a(UniValue::VARR);
+ for (const uint256& hash : vtxid)
+ a.push_back(hash.ToString());
+
+ if (!include_mempool_sequence) {
+ return a;
+ } else {
+ UniValue o(UniValue::VOBJ);
+ o.pushKV("txids", a);
+ o.pushKV("mempool_sequence", mempool_sequence);
+ return o;
+ }
+ }
+}
+
+static RPCHelpMan getrawmempool()
+{
+ return RPCHelpMan{"getrawmempool",
+ "\nReturns all transaction ids in memory pool as a json array of string transaction ids.\n"
+ "\nHint: use getmempoolentry to fetch a specific transaction from the mempool.\n",
+ {
+ {"verbose", RPCArg::Type::BOOL, RPCArg::Default{false}, "True for a json object, false for array of transaction ids"},
+ {"mempool_sequence", RPCArg::Type::BOOL, RPCArg::Default{false}, "If verbose=false, returns a json object with transaction list and mempool sequence number attached."},
+ },
+ {
+ RPCResult{"for verbose = false",
+ RPCResult::Type::ARR, "", "",
+ {
+ {RPCResult::Type::STR_HEX, "", "The transaction id"},
+ }},
+ RPCResult{"for verbose = true",
+ RPCResult::Type::OBJ_DYN, "", "",
+ {
+ {RPCResult::Type::OBJ, "transactionid", "", MempoolEntryDescription()},
+ }},
+ RPCResult{"for verbose = false and mempool_sequence = true",
+ RPCResult::Type::OBJ, "", "",
+ {
+ {RPCResult::Type::ARR, "txids", "",
+ {
+ {RPCResult::Type::STR_HEX, "", "The transaction id"},
+ }},
+ {RPCResult::Type::NUM, "mempool_sequence", "The mempool sequence value."},
+ }},
+ },
+ RPCExamples{
+ HelpExampleCli("getrawmempool", "true")
+ + HelpExampleRpc("getrawmempool", "true")
+ },
+ [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
+{
+ bool fVerbose = false;
+ if (!request.params[0].isNull())
+ fVerbose = request.params[0].get_bool();
+
+ bool include_mempool_sequence = false;
+ if (!request.params[1].isNull()) {
+ include_mempool_sequence = request.params[1].get_bool();
+ }
+
+ return MempoolToJSON(EnsureAnyMemPool(request.context), fVerbose, include_mempool_sequence);
+},
+ };
+}
+
+static RPCHelpMan getmempoolancestors()
+{
+ return RPCHelpMan{"getmempoolancestors",
+ "\nIf txid is in the mempool, returns all in-mempool ancestors.\n",
+ {
+ {"txid", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "The transaction id (must be in mempool)"},
+ {"verbose", RPCArg::Type::BOOL, RPCArg::Default{false}, "True for a json object, false for array of transaction ids"},
+ },
+ {
+ RPCResult{"for verbose = false",
+ RPCResult::Type::ARR, "", "",
+ {{RPCResult::Type::STR_HEX, "", "The transaction id of an in-mempool ancestor transaction"}}},
+ RPCResult{"for verbose = true",
+ RPCResult::Type::OBJ_DYN, "", "",
+ {
+ {RPCResult::Type::OBJ, "transactionid", "", MempoolEntryDescription()},
+ }},
+ },
+ RPCExamples{
+ HelpExampleCli("getmempoolancestors", "\"mytxid\"")
+ + HelpExampleRpc("getmempoolancestors", "\"mytxid\"")
+ },
+ [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
+{
+ bool fVerbose = false;
+ if (!request.params[1].isNull())
+ fVerbose = request.params[1].get_bool();
+
+ uint256 hash = ParseHashV(request.params[0], "parameter 1");
+
+ const CTxMemPool& mempool = EnsureAnyMemPool(request.context);
+ LOCK(mempool.cs);
+
+ CTxMemPool::txiter it = mempool.mapTx.find(hash);
+ if (it == mempool.mapTx.end()) {
+ throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Transaction not in mempool");
+ }
+
+ CTxMemPool::setEntries setAncestors;
+ uint64_t noLimit = std::numeric_limits<uint64_t>::max();
+ std::string dummy;
+ mempool.CalculateMemPoolAncestors(*it, setAncestors, noLimit, noLimit, noLimit, noLimit, dummy, false);
+
+ if (!fVerbose) {
+ UniValue o(UniValue::VARR);
+ for (CTxMemPool::txiter ancestorIt : setAncestors) {
+ o.push_back(ancestorIt->GetTx().GetHash().ToString());
+ }
+ return o;
+ } else {
+ UniValue o(UniValue::VOBJ);
+ for (CTxMemPool::txiter ancestorIt : setAncestors) {
+ const CTxMemPoolEntry &e = *ancestorIt;
+ const uint256& _hash = e.GetTx().GetHash();
+ UniValue info(UniValue::VOBJ);
+ entryToJSON(mempool, info, e);
+ o.pushKV(_hash.ToString(), info);
+ }
+ return o;
+ }
+},
+ };
+}
+
+static RPCHelpMan getmempooldescendants()
+{
+ return RPCHelpMan{"getmempooldescendants",
+ "\nIf txid is in the mempool, returns all in-mempool descendants.\n",
+ {
+ {"txid", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "The transaction id (must be in mempool)"},
+ {"verbose", RPCArg::Type::BOOL, RPCArg::Default{false}, "True for a json object, false for array of transaction ids"},
+ },
+ {
+ RPCResult{"for verbose = false",
+ RPCResult::Type::ARR, "", "",
+ {{RPCResult::Type::STR_HEX, "", "The transaction id of an in-mempool descendant transaction"}}},
+ RPCResult{"for verbose = true",
+ RPCResult::Type::OBJ_DYN, "", "",
+ {
+ {RPCResult::Type::OBJ, "transactionid", "", MempoolEntryDescription()},
+ }},
+ },
+ RPCExamples{
+ HelpExampleCli("getmempooldescendants", "\"mytxid\"")
+ + HelpExampleRpc("getmempooldescendants", "\"mytxid\"")
+ },
+ [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
+{
+ bool fVerbose = false;
+ if (!request.params[1].isNull())
+ fVerbose = request.params[1].get_bool();
+
+ uint256 hash = ParseHashV(request.params[0], "parameter 1");
+
+ const CTxMemPool& mempool = EnsureAnyMemPool(request.context);
+ LOCK(mempool.cs);
+
+ CTxMemPool::txiter it = mempool.mapTx.find(hash);
+ if (it == mempool.mapTx.end()) {
+ throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Transaction not in mempool");
+ }
+
+ CTxMemPool::setEntries setDescendants;
+ mempool.CalculateDescendants(it, setDescendants);
+ // CTxMemPool::CalculateDescendants will include the given tx
+ setDescendants.erase(it);
+
+ if (!fVerbose) {
+ UniValue o(UniValue::VARR);
+ for (CTxMemPool::txiter descendantIt : setDescendants) {
+ o.push_back(descendantIt->GetTx().GetHash().ToString());
+ }
+
+ return o;
+ } else {
+ UniValue o(UniValue::VOBJ);
+ for (CTxMemPool::txiter descendantIt : setDescendants) {
+ const CTxMemPoolEntry &e = *descendantIt;
+ const uint256& _hash = e.GetTx().GetHash();
+ UniValue info(UniValue::VOBJ);
+ entryToJSON(mempool, info, e);
+ o.pushKV(_hash.ToString(), info);
+ }
+ return o;
+ }
+},
+ };
+}
+
+static RPCHelpMan getmempoolentry()
+{
+ return RPCHelpMan{"getmempoolentry",
+ "\nReturns mempool data for given transaction\n",
+ {
+ {"txid", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "The transaction id (must be in mempool)"},
+ },
+ RPCResult{
+ RPCResult::Type::OBJ, "", "", MempoolEntryDescription()},
+ RPCExamples{
+ HelpExampleCli("getmempoolentry", "\"mytxid\"")
+ + HelpExampleRpc("getmempoolentry", "\"mytxid\"")
+ },
+ [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
+{
+ uint256 hash = ParseHashV(request.params[0], "parameter 1");
+
+ const CTxMemPool& mempool = EnsureAnyMemPool(request.context);
+ LOCK(mempool.cs);
+
+ CTxMemPool::txiter it = mempool.mapTx.find(hash);
+ if (it == mempool.mapTx.end()) {
+ throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Transaction not in mempool");
+ }
+
+ const CTxMemPoolEntry &e = *it;
+ UniValue info(UniValue::VOBJ);
+ entryToJSON(mempool, info, e);
+ return info;
+},
+ };
+}
+
+UniValue MempoolInfoToJSON(const CTxMemPool& pool)
+{
+ // Make sure this call is atomic in the pool.
+ LOCK(pool.cs);
+ UniValue ret(UniValue::VOBJ);
+ ret.pushKV("loaded", pool.IsLoaded());
+ ret.pushKV("size", (int64_t)pool.size());
+ ret.pushKV("bytes", (int64_t)pool.GetTotalTxSize());
+ ret.pushKV("usage", (int64_t)pool.DynamicMemoryUsage());
+ ret.pushKV("total_fee", ValueFromAmount(pool.GetTotalFee()));
+ int64_t maxmempool{gArgs.GetIntArg("-maxmempool", DEFAULT_MAX_MEMPOOL_SIZE) * 1000000};
+ ret.pushKV("maxmempool", maxmempool);
+ ret.pushKV("mempoolminfee", ValueFromAmount(std::max(pool.GetMinFee(maxmempool), ::minRelayTxFee).GetFeePerK()));
+ ret.pushKV("minrelaytxfee", ValueFromAmount(::minRelayTxFee.GetFeePerK()));
+ ret.pushKV("unbroadcastcount", uint64_t{pool.GetUnbroadcastTxs().size()});
+ return ret;
+}
+
+static RPCHelpMan getmempoolinfo()
+{
+ return RPCHelpMan{"getmempoolinfo",
+ "\nReturns details on the active state of the TX memory pool.\n",
+ {},
+ RPCResult{
+ RPCResult::Type::OBJ, "", "",
+ {
+ {RPCResult::Type::BOOL, "loaded", "True if the mempool is fully loaded"},
+ {RPCResult::Type::NUM, "size", "Current tx count"},
+ {RPCResult::Type::NUM, "bytes", "Sum of all virtual transaction sizes as defined in BIP 141. Differs from actual serialized size because witness data is discounted"},
+ {RPCResult::Type::NUM, "usage", "Total memory usage for the mempool"},
+ {RPCResult::Type::STR_AMOUNT, "total_fee", "Total fees for the mempool in " + CURRENCY_UNIT + ", ignoring modified fees through prioritisetransaction"},
+ {RPCResult::Type::NUM, "maxmempool", "Maximum memory usage for the mempool"},
+ {RPCResult::Type::STR_AMOUNT, "mempoolminfee", "Minimum fee rate in " + CURRENCY_UNIT + "/kvB for tx to be accepted. Is the maximum of minrelaytxfee and minimum mempool fee"},
+ {RPCResult::Type::STR_AMOUNT, "minrelaytxfee", "Current minimum relay fee for transactions"},
+ {RPCResult::Type::NUM, "unbroadcastcount", "Current number of transactions that haven't passed initial broadcast yet"}
+ }},
+ RPCExamples{
+ HelpExampleCli("getmempoolinfo", "")
+ + HelpExampleRpc("getmempoolinfo", "")
+ },
+ [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
+{
+ return MempoolInfoToJSON(EnsureAnyMemPool(request.context));
+},
+ };
+}
+
+static RPCHelpMan savemempool()
+{
+ return RPCHelpMan{"savemempool",
+ "\nDumps the mempool to disk. It will fail until the previous dump is fully loaded.\n",
+ {},
+ RPCResult{
+ RPCResult::Type::OBJ, "", "",
+ {
+ {RPCResult::Type::STR, "filename", "the directory and file where the mempool was saved"},
+ }},
+ RPCExamples{
+ HelpExampleCli("savemempool", "")
+ + HelpExampleRpc("savemempool", "")
+ },
+ [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
+{
+ const ArgsManager& args{EnsureAnyArgsman(request.context)};
+ const CTxMemPool& mempool = EnsureAnyMemPool(request.context);
+
+ if (!mempool.IsLoaded()) {
+ throw JSONRPCError(RPC_MISC_ERROR, "The mempool was not loaded yet");
+ }
+
+ if (!DumpMempool(mempool)) {
+ throw JSONRPCError(RPC_MISC_ERROR, "Unable to dump mempool to disk");
+ }
+
+ UniValue ret(UniValue::VOBJ);
+ ret.pushKV("filename", fs::path((args.GetDataDirNet() / "mempool.dat")).u8string());
+
+ return ret;
+},
+ };
+}
+
+void RegisterMempoolRPCCommands(CRPCTable& t)
+{
+ static const CRPCCommand commands[]{
+ {"rawtransactions", &sendrawtransaction},
+ {"rawtransactions", &testmempoolaccept},
+ {"blockchain", &getmempoolancestors},
+ {"blockchain", &getmempooldescendants},
+ {"blockchain", &getmempoolentry},
+ {"blockchain", &getmempoolinfo},
+ {"blockchain", &getrawmempool},
+ {"blockchain", &savemempool},
+ };
+ for (const auto& c : commands) {
+ t.appendCommand(c.name, &c);
+ }
+}
diff --git a/src/rpc/mempool.h b/src/rpc/mempool.h
new file mode 100644
index 0000000000..229d7d52dd
--- /dev/null
+++ b/src/rpc/mempool.h
@@ -0,0 +1,17 @@
+// Copyright (c) 2017-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.
+
+#ifndef BITCOIN_RPC_MEMPOOL_H
+#define BITCOIN_RPC_MEMPOOL_H
+
+class CTxMemPool;
+class UniValue;
+
+/** Mempool information to JSON */
+UniValue MempoolInfoToJSON(const CTxMemPool& pool);
+
+/** Mempool to JSON */
+UniValue MempoolToJSON(const CTxMemPool& pool, bool verbose = false, bool include_mempool_sequence = false);
+
+#endif // BITCOIN_RPC_MEMPOOL_H
diff --git a/src/rpc/mining.cpp b/src/rpc/mining.cpp
index 0554367672..b552528951 100644
--- a/src/rpc/mining.cpp
+++ b/src/rpc/mining.cpp
@@ -7,6 +7,7 @@
#include <chainparams.h>
#include <consensus/amount.h>
#include <consensus/consensus.h>
+#include <consensus/merkle.h>
#include <consensus/params.h>
#include <consensus/validation.h>
#include <core_io.h>
@@ -16,7 +17,6 @@
#include <net.h>
#include <node/context.h>
#include <node/miner.h>
-#include <policy/fees.h>
#include <pow.h>
#include <rpc/blockchain.h>
#include <rpc/mining.h>
@@ -29,7 +29,6 @@
#include <shutdown.h>
#include <txmempool.h>
#include <univalue.h>
-#include <util/fees.h>
#include <util/strencodings.h>
#include <util/string.h>
#include <util/system.h>
@@ -43,7 +42,6 @@
using node::BlockAssembler;
using node::CBlockTemplate;
-using node::IncrementExtraNonce;
using node::NodeContext;
using node::RegenerateCommitments;
using node::UpdateTime;
@@ -116,14 +114,10 @@ static RPCHelpMan getnetworkhashps()
};
}
-static bool GenerateBlock(ChainstateManager& chainman, CBlock& block, uint64_t& max_tries, unsigned int& extra_nonce, uint256& block_hash)
+static bool GenerateBlock(ChainstateManager& chainman, CBlock& block, uint64_t& max_tries, uint256& block_hash)
{
block_hash.SetNull();
-
- {
- LOCK(cs_main);
- IncrementExtraNonce(&block, chainman.ActiveChain().Tip(), extra_nonce);
- }
+ block.hashMerkleRoot = BlockMerkleRoot(block);
CChainParams chainparams(Params());
@@ -149,30 +143,20 @@ static bool GenerateBlock(ChainstateManager& chainman, CBlock& block, uint64_t&
static UniValue generateBlocks(ChainstateManager& chainman, const CTxMemPool& mempool, const CScript& coinbase_script, int nGenerate, uint64_t nMaxTries)
{
- int nHeightEnd = 0;
- int nHeight = 0;
-
- { // Don't keep cs_main locked
- LOCK(cs_main);
- nHeight = chainman.ActiveChain().Height();
- nHeightEnd = nHeight+nGenerate;
- }
- unsigned int nExtraNonce = 0;
UniValue blockHashes(UniValue::VARR);
- while (nHeight < nHeightEnd && !ShutdownRequested())
- {
+ while (nGenerate > 0 && !ShutdownRequested()) {
std::unique_ptr<CBlockTemplate> pblocktemplate(BlockAssembler(chainman.ActiveChainstate(), mempool, Params()).CreateNewBlock(coinbase_script));
if (!pblocktemplate.get())
throw JSONRPCError(RPC_INTERNAL_ERROR, "Couldn't create new block");
CBlock *pblock = &pblocktemplate->block;
uint256 block_hash;
- if (!GenerateBlock(chainman, *pblock, nMaxTries, nExtraNonce, block_hash)) {
+ if (!GenerateBlock(chainman, *pblock, nMaxTries, block_hash)) {
break;
}
if (!block_hash.IsNull()) {
- ++nHeight;
+ --nGenerate;
blockHashes.push_back(block_hash.GetHex());
}
}
@@ -217,9 +201,9 @@ static RPCHelpMan generatetodescriptor()
{
return RPCHelpMan{
"generatetodescriptor",
- "\nMine blocks immediately to a specified descriptor (before the RPC call returns)\n",
+ "Mine to a specified descriptor and return the block hashes.",
{
- {"num_blocks", RPCArg::Type::NUM, RPCArg::Optional::NO, "How many blocks are generated immediately."},
+ {"num_blocks", RPCArg::Type::NUM, RPCArg::Optional::NO, "How many blocks are generated."},
{"descriptor", RPCArg::Type::STR, RPCArg::Optional::NO, "The descriptor to send the newly generated bitcoin to."},
{"maxtries", RPCArg::Type::NUM, RPCArg::Default{DEFAULT_MAX_TRIES}, "How many iterations to try."},
},
@@ -261,18 +245,18 @@ static RPCHelpMan generate()
static RPCHelpMan generatetoaddress()
{
return RPCHelpMan{"generatetoaddress",
- "\nMine blocks immediately to a specified address (before the RPC call returns)\n",
- {
- {"nblocks", RPCArg::Type::NUM, RPCArg::Optional::NO, "How many blocks are generated immediately."},
- {"address", RPCArg::Type::STR, RPCArg::Optional::NO, "The address to send the newly generated bitcoin to."},
- {"maxtries", RPCArg::Type::NUM, RPCArg::Default{DEFAULT_MAX_TRIES}, "How many iterations to try."},
- },
- RPCResult{
- RPCResult::Type::ARR, "", "hashes of blocks generated",
- {
- {RPCResult::Type::STR_HEX, "", "blockhash"},
- }},
- RPCExamples{
+ "Mine to a specified address and return the block hashes.",
+ {
+ {"nblocks", RPCArg::Type::NUM, RPCArg::Optional::NO, "How many blocks are generated."},
+ {"address", RPCArg::Type::STR, RPCArg::Optional::NO, "The address to send the newly generated bitcoin to."},
+ {"maxtries", RPCArg::Type::NUM, RPCArg::Default{DEFAULT_MAX_TRIES}, "How many iterations to try."},
+ },
+ RPCResult{
+ RPCResult::Type::ARR, "", "hashes of blocks generated",
+ {
+ {RPCResult::Type::STR_HEX, "", "blockhash"},
+ }},
+ RPCExamples{
"\nGenerate 11 blocks to myaddress\n"
+ HelpExampleCli("generatetoaddress", "11 \"myaddress\"")
+ "If you are using the " PACKAGE_NAME " wallet, you can get a new address to send the newly generated bitcoin to with:\n"
@@ -302,7 +286,7 @@ static RPCHelpMan generatetoaddress()
static RPCHelpMan generateblock()
{
return RPCHelpMan{"generateblock",
- "\nMine a block with a set of ordered transactions immediately to a specified address or descriptor (before the RPC call returns)\n",
+ "Mine a set of ordered transactions to a specified address or descriptor and return the block hash.",
{
{"output", RPCArg::Type::STR, RPCArg::Optional::NO, "The address or descriptor to send the newly generated bitcoin to."},
{"transactions", RPCArg::Type::ARR, RPCArg::Optional::NO, "An array of hex strings which are either txids or raw transactions.\n"
@@ -397,9 +381,8 @@ static RPCHelpMan generateblock()
uint256 block_hash;
uint64_t max_tries{DEFAULT_MAX_TRIES};
- unsigned int extra_nonce{0};
- if (!GenerateBlock(chainman, block, max_tries, extra_nonce, block_hash) || block_hash.IsNull()) {
+ if (!GenerateBlock(chainman, block, max_tries, block_hash) || block_hash.IsNull()) {
throw JSONRPCError(RPC_MISC_ERROR, "Failed to make block.");
}
@@ -1069,225 +1052,21 @@ static RPCHelpMan submitheader()
};
}
-static RPCHelpMan estimatesmartfee()
+void RegisterMiningRPCCommands(CRPCTable& t)
{
- return RPCHelpMan{"estimatesmartfee",
- "\nEstimates the approximate fee per kilobyte needed for a transaction to begin\n"
- "confirmation within conf_target blocks if possible and return the number of blocks\n"
- "for which the estimate is valid. Uses virtual transaction size as defined\n"
- "in BIP 141 (witness data is discounted).\n",
- {
- {"conf_target", RPCArg::Type::NUM, RPCArg::Optional::NO, "Confirmation target in blocks (1 - 1008)"},
- {"estimate_mode", RPCArg::Type::STR, RPCArg::Default{"conservative"}, "The fee estimate mode.\n"
- " Whether to return a more conservative estimate which also satisfies\n"
- " a longer history. A conservative estimate potentially returns a\n"
- " higher feerate and is more likely to be sufficient for the desired\n"
- " target, but is not as responsive to short term drops in the\n"
- " prevailing fee market. Must be one of (case insensitive):\n"
- "\"" + FeeModes("\"\n\"") + "\""},
- },
- RPCResult{
- RPCResult::Type::OBJ, "", "",
- {
- {RPCResult::Type::NUM, "feerate", /*optional=*/true, "estimate fee rate in " + CURRENCY_UNIT + "/kvB (only present if no errors were encountered)"},
- {RPCResult::Type::ARR, "errors", /*optional=*/true, "Errors encountered during processing (if there are any)",
- {
- {RPCResult::Type::STR, "", "error"},
- }},
- {RPCResult::Type::NUM, "blocks", "block number where estimate was found\n"
- "The request target will be clamped between 2 and the highest target\n"
- "fee estimation is able to return based on how long it has been running.\n"
- "An error is returned if not enough transactions and blocks\n"
- "have been observed to make an estimate for any number of blocks."},
- }},
- RPCExamples{
- HelpExampleCli("estimatesmartfee", "6") +
- HelpExampleRpc("estimatesmartfee", "6")
- },
- [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
-{
- RPCTypeCheck(request.params, {UniValue::VNUM, UniValue::VSTR});
- RPCTypeCheckArgument(request.params[0], UniValue::VNUM);
-
- CBlockPolicyEstimator& fee_estimator = EnsureAnyFeeEstimator(request.context);
- const NodeContext& node = EnsureAnyNodeContext(request.context);
- const CTxMemPool& mempool = EnsureMemPool(node);
-
- unsigned int max_target = fee_estimator.HighestTargetTracked(FeeEstimateHorizon::LONG_HALFLIFE);
- unsigned int conf_target = ParseConfirmTarget(request.params[0], max_target);
- bool conservative = true;
- if (!request.params[1].isNull()) {
- FeeEstimateMode fee_mode;
- if (!FeeModeFromString(request.params[1].get_str(), fee_mode)) {
- throw JSONRPCError(RPC_INVALID_PARAMETER, InvalidEstimateModeErrorMessage());
- }
- if (fee_mode == FeeEstimateMode::ECONOMICAL) conservative = false;
- }
-
- UniValue result(UniValue::VOBJ);
- UniValue errors(UniValue::VARR);
- FeeCalculation feeCalc;
- CFeeRate feeRate{fee_estimator.estimateSmartFee(conf_target, &feeCalc, conservative)};
- if (feeRate != CFeeRate(0)) {
- CFeeRate min_mempool_feerate{mempool.GetMinFee(gArgs.GetIntArg("-maxmempool", DEFAULT_MAX_MEMPOOL_SIZE) * 1000000)};
- CFeeRate min_relay_feerate{::minRelayTxFee};
- feeRate = std::max({feeRate, min_mempool_feerate, min_relay_feerate});
- result.pushKV("feerate", ValueFromAmount(feeRate.GetFeePerK()));
- } else {
- errors.push_back("Insufficient data or no feerate found");
- result.pushKV("errors", errors);
- }
- result.pushKV("blocks", feeCalc.returnedTarget);
- return result;
-},
+ static const CRPCCommand commands[]{
+ {"mining", &getnetworkhashps},
+ {"mining", &getmininginfo},
+ {"mining", &prioritisetransaction},
+ {"mining", &getblocktemplate},
+ {"mining", &submitblock},
+ {"mining", &submitheader},
+
+ {"hidden", &generatetoaddress},
+ {"hidden", &generatetodescriptor},
+ {"hidden", &generateblock},
+ {"hidden", &generate},
};
-}
-
-static RPCHelpMan estimaterawfee()
-{
- return RPCHelpMan{"estimaterawfee",
- "\nWARNING: This interface is unstable and may disappear or change!\n"
- "\nWARNING: This is an advanced API call that is tightly coupled to the specific\n"
- " implementation of fee estimation. The parameters it can be called with\n"
- " and the results it returns will change if the internal implementation changes.\n"
- "\nEstimates the approximate fee per kilobyte needed for a transaction to begin\n"
- "confirmation within conf_target blocks if possible. Uses virtual transaction size as\n"
- "defined in BIP 141 (witness data is discounted).\n",
- {
- {"conf_target", RPCArg::Type::NUM, RPCArg::Optional::NO, "Confirmation target in blocks (1 - 1008)"},
- {"threshold", RPCArg::Type::NUM, RPCArg::Default{0.95}, "The proportion of transactions in a given feerate range that must have been\n"
- " confirmed within conf_target in order to consider those feerates as high enough and proceed to check\n"
- " lower buckets."},
- },
- RPCResult{
- RPCResult::Type::OBJ, "", "Results are returned for any horizon which tracks blocks up to the confirmation target",
- {
- {RPCResult::Type::OBJ, "short", /*optional=*/true, "estimate for short time horizon",
- {
- {RPCResult::Type::NUM, "feerate", /*optional=*/true, "estimate fee rate in " + CURRENCY_UNIT + "/kvB"},
- {RPCResult::Type::NUM, "decay", "exponential decay (per block) for historical moving average of confirmation data"},
- {RPCResult::Type::NUM, "scale", "The resolution of confirmation targets at this time horizon"},
- {RPCResult::Type::OBJ, "pass", /*optional=*/true, "information about the lowest range of feerates to succeed in meeting the threshold",
- {
- {RPCResult::Type::NUM, "startrange", "start of feerate range"},
- {RPCResult::Type::NUM, "endrange", "end of feerate range"},
- {RPCResult::Type::NUM, "withintarget", "number of txs over history horizon in the feerate range that were confirmed within target"},
- {RPCResult::Type::NUM, "totalconfirmed", "number of txs over history horizon in the feerate range that were confirmed at any point"},
- {RPCResult::Type::NUM, "inmempool", "current number of txs in mempool in the feerate range unconfirmed for at least target blocks"},
- {RPCResult::Type::NUM, "leftmempool", "number of txs over history horizon in the feerate range that left mempool unconfirmed after target"},
- }},
- {RPCResult::Type::OBJ, "fail", /*optional=*/true, "information about the highest range of feerates to fail to meet the threshold",
- {
- {RPCResult::Type::ELISION, "", ""},
- }},
- {RPCResult::Type::ARR, "errors", /*optional=*/true, "Errors encountered during processing (if there are any)",
- {
- {RPCResult::Type::STR, "error", ""},
- }},
- }},
- {RPCResult::Type::OBJ, "medium", /*optional=*/true, "estimate for medium time horizon",
- {
- {RPCResult::Type::ELISION, "", ""},
- }},
- {RPCResult::Type::OBJ, "long", /*optional=*/true, "estimate for long time horizon",
- {
- {RPCResult::Type::ELISION, "", ""},
- }},
- }},
- RPCExamples{
- HelpExampleCli("estimaterawfee", "6 0.9")
- },
- [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
-{
- RPCTypeCheck(request.params, {UniValue::VNUM, UniValue::VNUM}, true);
- RPCTypeCheckArgument(request.params[0], UniValue::VNUM);
-
- CBlockPolicyEstimator& fee_estimator = EnsureAnyFeeEstimator(request.context);
-
- unsigned int max_target = fee_estimator.HighestTargetTracked(FeeEstimateHorizon::LONG_HALFLIFE);
- unsigned int conf_target = ParseConfirmTarget(request.params[0], max_target);
- double threshold = 0.95;
- if (!request.params[1].isNull()) {
- threshold = request.params[1].get_real();
- }
- if (threshold < 0 || threshold > 1) {
- throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid threshold");
- }
-
- UniValue result(UniValue::VOBJ);
-
- for (const FeeEstimateHorizon horizon : ALL_FEE_ESTIMATE_HORIZONS) {
- CFeeRate feeRate;
- EstimationResult buckets;
-
- // Only output results for horizons which track the target
- if (conf_target > fee_estimator.HighestTargetTracked(horizon)) continue;
-
- feeRate = fee_estimator.estimateRawFee(conf_target, threshold, horizon, &buckets);
- UniValue horizon_result(UniValue::VOBJ);
- UniValue errors(UniValue::VARR);
- UniValue passbucket(UniValue::VOBJ);
- passbucket.pushKV("startrange", round(buckets.pass.start));
- passbucket.pushKV("endrange", round(buckets.pass.end));
- passbucket.pushKV("withintarget", round(buckets.pass.withinTarget * 100.0) / 100.0);
- passbucket.pushKV("totalconfirmed", round(buckets.pass.totalConfirmed * 100.0) / 100.0);
- passbucket.pushKV("inmempool", round(buckets.pass.inMempool * 100.0) / 100.0);
- passbucket.pushKV("leftmempool", round(buckets.pass.leftMempool * 100.0) / 100.0);
- UniValue failbucket(UniValue::VOBJ);
- failbucket.pushKV("startrange", round(buckets.fail.start));
- failbucket.pushKV("endrange", round(buckets.fail.end));
- failbucket.pushKV("withintarget", round(buckets.fail.withinTarget * 100.0) / 100.0);
- failbucket.pushKV("totalconfirmed", round(buckets.fail.totalConfirmed * 100.0) / 100.0);
- failbucket.pushKV("inmempool", round(buckets.fail.inMempool * 100.0) / 100.0);
- failbucket.pushKV("leftmempool", round(buckets.fail.leftMempool * 100.0) / 100.0);
-
- // CFeeRate(0) is used to indicate error as a return value from estimateRawFee
- if (feeRate != CFeeRate(0)) {
- horizon_result.pushKV("feerate", ValueFromAmount(feeRate.GetFeePerK()));
- horizon_result.pushKV("decay", buckets.decay);
- horizon_result.pushKV("scale", (int)buckets.scale);
- horizon_result.pushKV("pass", passbucket);
- // buckets.fail.start == -1 indicates that all buckets passed, there is no fail bucket to output
- if (buckets.fail.start != -1) horizon_result.pushKV("fail", failbucket);
- } else {
- // Output only information that is still meaningful in the event of error
- horizon_result.pushKV("decay", buckets.decay);
- horizon_result.pushKV("scale", (int)buckets.scale);
- horizon_result.pushKV("fail", failbucket);
- errors.push_back("Insufficient data or no feerate found which meets threshold");
- horizon_result.pushKV("errors",errors);
- }
- result.pushKV(StringForFeeEstimateHorizon(horizon), horizon_result);
- }
- return result;
-},
- };
-}
-
-void RegisterMiningRPCCommands(CRPCTable &t)
-{
-// clang-format off
-static const CRPCCommand commands[] =
-{ // category actor (function)
- // --------------------- -----------------------
- { "mining", &getnetworkhashps, },
- { "mining", &getmininginfo, },
- { "mining", &prioritisetransaction, },
- { "mining", &getblocktemplate, },
- { "mining", &submitblock, },
- { "mining", &submitheader, },
-
-
- { "generating", &generatetoaddress, },
- { "generating", &generatetodescriptor, },
- { "generating", &generateblock, },
-
- { "util", &estimatesmartfee, },
-
- { "hidden", &estimaterawfee, },
- { "hidden", &generate, },
-};
-// clang-format on
for (const auto& c : commands) {
t.appendCommand(c.name, &c);
}
diff --git a/src/rpc/misc.cpp b/src/rpc/misc.cpp
deleted file mode 100644
index 8d574e0359..0000000000
--- a/src/rpc/misc.cpp
+++ /dev/null
@@ -1,833 +0,0 @@
-// Copyright (c) 2010 Satoshi Nakamoto
-// Copyright (c) 2009-2021 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 <httpserver.h>
-#include <index/blockfilterindex.h>
-#include <index/coinstatsindex.h>
-#include <index/txindex.h>
-#include <interfaces/chain.h>
-#include <interfaces/echo.h>
-#include <interfaces/init.h>
-#include <interfaces/ipc.h>
-#include <key_io.h>
-#include <node/context.h>
-#include <outputtype.h>
-#include <rpc/blockchain.h>
-#include <rpc/server.h>
-#include <rpc/server_util.h>
-#include <rpc/util.h>
-#include <scheduler.h>
-#include <script/descriptor.h>
-#include <util/check.h>
-#include <util/message.h> // For MessageSign(), MessageVerify()
-#include <util/strencodings.h>
-#include <util/syscall_sandbox.h>
-#include <util/system.h>
-
-#include <optional>
-#include <stdint.h>
-#include <tuple>
-#ifdef HAVE_MALLOC_INFO
-#include <malloc.h>
-#endif
-
-#include <univalue.h>
-
-using node::NodeContext;
-
-static RPCHelpMan validateaddress()
-{
- return RPCHelpMan{
- "validateaddress",
- "\nReturn information about the given bitcoin address.\n",
- {
- {"address", RPCArg::Type::STR, RPCArg::Optional::NO, "The bitcoin address to validate"},
- },
- RPCResult{
- RPCResult::Type::OBJ, "", "",
- {
- {RPCResult::Type::BOOL, "isvalid", "If the address is valid or not"},
- {RPCResult::Type::STR, "address", /*optional=*/true, "The bitcoin address validated"},
- {RPCResult::Type::STR_HEX, "scriptPubKey", /*optional=*/true, "The hex-encoded scriptPubKey generated by the address"},
- {RPCResult::Type::BOOL, "isscript", /*optional=*/true, "If the key is a script"},
- {RPCResult::Type::BOOL, "iswitness", /*optional=*/true, "If the address is a witness address"},
- {RPCResult::Type::NUM, "witness_version", /*optional=*/true, "The version number of the witness program"},
- {RPCResult::Type::STR_HEX, "witness_program", /*optional=*/true, "The hex value of the witness program"},
- {RPCResult::Type::STR, "error", /*optional=*/true, "Error message, if any"},
- {RPCResult::Type::ARR, "error_locations", /*optional=*/true, "Indices of likely error locations in address, if known (e.g. Bech32 errors)",
- {
- {RPCResult::Type::NUM, "index", "index of a potential error"},
- }},
- }
- },
- RPCExamples{
- HelpExampleCli("validateaddress", "\"" + EXAMPLE_ADDRESS[0] + "\"") +
- HelpExampleRpc("validateaddress", "\"" + EXAMPLE_ADDRESS[0] + "\"")
- },
- [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
-{
- std::string error_msg;
- std::vector<int> error_locations;
- CTxDestination dest = DecodeDestination(request.params[0].get_str(), error_msg, &error_locations);
- const bool isValid = IsValidDestination(dest);
- CHECK_NONFATAL(isValid == error_msg.empty());
-
- UniValue ret(UniValue::VOBJ);
- ret.pushKV("isvalid", isValid);
- if (isValid) {
- std::string currentAddress = EncodeDestination(dest);
- ret.pushKV("address", currentAddress);
-
- CScript scriptPubKey = GetScriptForDestination(dest);
- ret.pushKV("scriptPubKey", HexStr(scriptPubKey));
-
- UniValue detail = DescribeAddress(dest);
- ret.pushKVs(detail);
- } else {
- UniValue error_indices(UniValue::VARR);
- for (int i : error_locations) error_indices.push_back(i);
- ret.pushKV("error_locations", error_indices);
- ret.pushKV("error", error_msg);
- }
-
- return ret;
-},
- };
-}
-
-static RPCHelpMan createmultisig()
-{
- return RPCHelpMan{"createmultisig",
- "\nCreates a multi-signature address with n signature of m keys required.\n"
- "It returns a json object with the address and redeemScript.\n",
- {
- {"nrequired", RPCArg::Type::NUM, RPCArg::Optional::NO, "The number of required signatures out of the n keys."},
- {"keys", RPCArg::Type::ARR, RPCArg::Optional::NO, "The hex-encoded public keys.",
- {
- {"key", RPCArg::Type::STR_HEX, RPCArg::Optional::OMITTED, "The hex-encoded public key"},
- }},
- {"address_type", RPCArg::Type::STR, RPCArg::Default{"legacy"}, "The address type to use. Options are \"legacy\", \"p2sh-segwit\", and \"bech32\"."},
- },
- RPCResult{
- RPCResult::Type::OBJ, "", "",
- {
- {RPCResult::Type::STR, "address", "The value of the new multisig address."},
- {RPCResult::Type::STR_HEX, "redeemScript", "The string value of the hex-encoded redemption script."},
- {RPCResult::Type::STR, "descriptor", "The descriptor for this multisig"},
- {RPCResult::Type::ARR, "warnings", /* optional */ true, "Any warnings resulting from the creation of this multisig",
- {
- {RPCResult::Type::STR, "", ""},
- }},
- }
- },
- RPCExamples{
- "\nCreate a multisig address from 2 public keys\n"
- + HelpExampleCli("createmultisig", "2 \"[\\\"03789ed0bb717d88f7d321a368d905e7430207ebbd82bd342cf11ae157a7ace5fd\\\",\\\"03dbc6764b8884a92e871274b87583e6d5c2a58819473e17e107ef3f6aa5a61626\\\"]\"") +
- "\nAs a JSON-RPC call\n"
- + HelpExampleRpc("createmultisig", "2, [\"03789ed0bb717d88f7d321a368d905e7430207ebbd82bd342cf11ae157a7ace5fd\",\"03dbc6764b8884a92e871274b87583e6d5c2a58819473e17e107ef3f6aa5a61626\"]")
- },
- [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
-{
- int required = request.params[0].get_int();
-
- // Get the public keys
- const UniValue& keys = request.params[1].get_array();
- std::vector<CPubKey> pubkeys;
- for (unsigned int i = 0; i < keys.size(); ++i) {
- if (IsHex(keys[i].get_str()) && (keys[i].get_str().length() == 66 || keys[i].get_str().length() == 130)) {
- pubkeys.push_back(HexToPubKey(keys[i].get_str()));
- } else {
- throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, strprintf("Invalid public key: %s\n.", keys[i].get_str()));
- }
- }
-
- // Get the output type
- OutputType output_type = OutputType::LEGACY;
- if (!request.params[2].isNull()) {
- std::optional<OutputType> parsed = ParseOutputType(request.params[2].get_str());
- if (!parsed) {
- throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, strprintf("Unknown address type '%s'", request.params[2].get_str()));
- } else if (parsed.value() == OutputType::BECH32M) {
- throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "createmultisig cannot create bech32m multisig addresses");
- }
- output_type = parsed.value();
- }
-
- // Construct using pay-to-script-hash:
- FillableSigningProvider keystore;
- CScript inner;
- const CTxDestination dest = AddAndGetMultisigDestination(required, pubkeys, output_type, keystore, inner);
-
- // Make the descriptor
- std::unique_ptr<Descriptor> descriptor = InferDescriptor(GetScriptForDestination(dest), keystore);
-
- UniValue result(UniValue::VOBJ);
- result.pushKV("address", EncodeDestination(dest));
- result.pushKV("redeemScript", HexStr(inner));
- result.pushKV("descriptor", descriptor->ToString());
-
- UniValue warnings(UniValue::VARR);
- if (!request.params[2].isNull() && OutputTypeFromDestination(dest) != output_type) {
- // Only warns if the user has explicitly chosen an address type we cannot generate
- warnings.push_back("Unable to make chosen address type, please ensure no uncompressed public keys are present.");
- }
- if (warnings.size()) result.pushKV("warnings", warnings);
-
- return result;
-},
- };
-}
-
-static RPCHelpMan getdescriptorinfo()
-{
- const std::string EXAMPLE_DESCRIPTOR = "wpkh([d34db33f/84h/0h/0h]0279be667ef9dcbbac55a06295Ce870b07029Bfcdb2dce28d959f2815b16f81798)";
-
- return RPCHelpMan{"getdescriptorinfo",
- {"\nAnalyses a descriptor.\n"},
- {
- {"descriptor", RPCArg::Type::STR, RPCArg::Optional::NO, "The descriptor."},
- },
- RPCResult{
- RPCResult::Type::OBJ, "", "",
- {
- {RPCResult::Type::STR, "descriptor", "The descriptor in canonical form, without private keys"},
- {RPCResult::Type::STR, "checksum", "The checksum for the input descriptor"},
- {RPCResult::Type::BOOL, "isrange", "Whether the descriptor is ranged"},
- {RPCResult::Type::BOOL, "issolvable", "Whether the descriptor is solvable"},
- {RPCResult::Type::BOOL, "hasprivatekeys", "Whether the input descriptor contained at least one private key"},
- }
- },
- RPCExamples{
- "Analyse a descriptor\n" +
- HelpExampleCli("getdescriptorinfo", "\"" + EXAMPLE_DESCRIPTOR + "\"") +
- HelpExampleRpc("getdescriptorinfo", "\"" + EXAMPLE_DESCRIPTOR + "\"")
- },
- [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
-{
- RPCTypeCheck(request.params, {UniValue::VSTR});
-
- FlatSigningProvider provider;
- std::string error;
- auto desc = Parse(request.params[0].get_str(), provider, error);
- if (!desc) {
- throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, error);
- }
-
- UniValue result(UniValue::VOBJ);
- result.pushKV("descriptor", desc->ToString());
- result.pushKV("checksum", GetDescriptorChecksum(request.params[0].get_str()));
- result.pushKV("isrange", desc->IsRange());
- result.pushKV("issolvable", desc->IsSolvable());
- result.pushKV("hasprivatekeys", provider.keys.size() > 0);
- return result;
-},
- };
-}
-
-static RPCHelpMan deriveaddresses()
-{
- const std::string EXAMPLE_DESCRIPTOR = "wpkh([d34db33f/84h/0h/0h]xpub6DJ2dNUysrn5Vt36jH2KLBT2i1auw1tTSSomg8PhqNiUtx8QX2SvC9nrHu81fT41fvDUnhMjEzQgXnQjKEu3oaqMSzhSrHMxyyoEAmUHQbY/0/*)#cjjspncu";
-
- return RPCHelpMan{"deriveaddresses",
- {"\nDerives one or more addresses corresponding to an output descriptor.\n"
- "Examples of output descriptors are:\n"
- " pkh(<pubkey>) P2PKH outputs for the given pubkey\n"
- " wpkh(<pubkey>) Native segwit P2PKH outputs for the given pubkey\n"
- " sh(multi(<n>,<pubkey>,<pubkey>,...)) P2SH-multisig outputs for the given threshold and pubkeys\n"
- " raw(<hex script>) Outputs whose scriptPubKey equals the specified hex scripts\n"
- "\nIn the above, <pubkey> either refers to a fixed public key in hexadecimal notation, or to an xpub/xprv optionally followed by one\n"
- "or more path elements separated by \"/\", where \"h\" represents a hardened child key.\n"
- "For more information on output descriptors, see the documentation in the doc/descriptors.md file.\n"},
- {
- {"descriptor", RPCArg::Type::STR, RPCArg::Optional::NO, "The descriptor."},
- {"range", RPCArg::Type::RANGE, RPCArg::Optional::OMITTED_NAMED_ARG, "If a ranged descriptor is used, this specifies the end or the range (in [begin,end] notation) to derive."},
- },
- RPCResult{
- RPCResult::Type::ARR, "", "",
- {
- {RPCResult::Type::STR, "address", "the derived addresses"},
- }
- },
- RPCExamples{
- "First three native segwit receive addresses\n" +
- HelpExampleCli("deriveaddresses", "\"" + EXAMPLE_DESCRIPTOR + "\" \"[0,2]\"") +
- HelpExampleRpc("deriveaddresses", "\"" + EXAMPLE_DESCRIPTOR + "\", \"[0,2]\"")
- },
- [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
-{
- RPCTypeCheck(request.params, {UniValue::VSTR, UniValueType()}); // Range argument is checked later
- const std::string desc_str = request.params[0].get_str();
-
- int64_t range_begin = 0;
- int64_t range_end = 0;
-
- if (request.params.size() >= 2 && !request.params[1].isNull()) {
- std::tie(range_begin, range_end) = ParseDescriptorRange(request.params[1]);
- }
-
- FlatSigningProvider key_provider;
- std::string error;
- auto desc = Parse(desc_str, key_provider, error, /* require_checksum = */ true);
- if (!desc) {
- throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, error);
- }
-
- if (!desc->IsRange() && request.params.size() > 1) {
- throw JSONRPCError(RPC_INVALID_PARAMETER, "Range should not be specified for an un-ranged descriptor");
- }
-
- if (desc->IsRange() && request.params.size() == 1) {
- throw JSONRPCError(RPC_INVALID_PARAMETER, "Range must be specified for a ranged descriptor");
- }
-
- UniValue addresses(UniValue::VARR);
-
- for (int i = range_begin; i <= range_end; ++i) {
- FlatSigningProvider provider;
- std::vector<CScript> scripts;
- if (!desc->Expand(i, key_provider, scripts, provider)) {
- throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Cannot derive script without private keys");
- }
-
- for (const CScript &script : scripts) {
- CTxDestination dest;
- if (!ExtractDestination(script, dest)) {
- throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Descriptor does not have a corresponding address");
- }
-
- addresses.push_back(EncodeDestination(dest));
- }
- }
-
- // This should not be possible, but an assert seems overkill:
- if (addresses.empty()) {
- throw JSONRPCError(RPC_MISC_ERROR, "Unexpected empty result");
- }
-
- return addresses;
-},
- };
-}
-
-static RPCHelpMan verifymessage()
-{
- return RPCHelpMan{"verifymessage",
- "Verify a signed message.",
- {
- {"address", RPCArg::Type::STR, RPCArg::Optional::NO, "The bitcoin address to use for the signature."},
- {"signature", RPCArg::Type::STR, RPCArg::Optional::NO, "The signature provided by the signer in base 64 encoding (see signmessage)."},
- {"message", RPCArg::Type::STR, RPCArg::Optional::NO, "The message that was signed."},
- },
- RPCResult{
- RPCResult::Type::BOOL, "", "If the signature is verified or not."
- },
- RPCExamples{
- "\nUnlock the wallet for 30 seconds\n"
- + HelpExampleCli("walletpassphrase", "\"mypassphrase\" 30") +
- "\nCreate the signature\n"
- + HelpExampleCli("signmessage", "\"1D1ZrZNe3JUo7ZycKEYQQiQAWd9y54F4XX\" \"my message\"") +
- "\nVerify the signature\n"
- + HelpExampleCli("verifymessage", "\"1D1ZrZNe3JUo7ZycKEYQQiQAWd9y54F4XX\" \"signature\" \"my message\"") +
- "\nAs a JSON-RPC call\n"
- + HelpExampleRpc("verifymessage", "\"1D1ZrZNe3JUo7ZycKEYQQiQAWd9y54F4XX\", \"signature\", \"my message\"")
- },
- [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
-{
- LOCK(cs_main);
-
- std::string strAddress = request.params[0].get_str();
- std::string strSign = request.params[1].get_str();
- std::string strMessage = request.params[2].get_str();
-
- switch (MessageVerify(strAddress, strSign, strMessage)) {
- case MessageVerificationResult::ERR_INVALID_ADDRESS:
- throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid address");
- case MessageVerificationResult::ERR_ADDRESS_NO_KEY:
- throw JSONRPCError(RPC_TYPE_ERROR, "Address does not refer to key");
- case MessageVerificationResult::ERR_MALFORMED_SIGNATURE:
- throw JSONRPCError(RPC_TYPE_ERROR, "Malformed base64 encoding");
- case MessageVerificationResult::ERR_PUBKEY_NOT_RECOVERED:
- case MessageVerificationResult::ERR_NOT_SIGNED:
- return false;
- case MessageVerificationResult::OK:
- return true;
- }
-
- return false;
-},
- };
-}
-
-static RPCHelpMan signmessagewithprivkey()
-{
- return RPCHelpMan{"signmessagewithprivkey",
- "\nSign a message with the private key of an address\n",
- {
- {"privkey", RPCArg::Type::STR, RPCArg::Optional::NO, "The private key to sign the message with."},
- {"message", RPCArg::Type::STR, RPCArg::Optional::NO, "The message to create a signature of."},
- },
- RPCResult{
- RPCResult::Type::STR, "signature", "The signature of the message encoded in base 64"
- },
- RPCExamples{
- "\nCreate the signature\n"
- + HelpExampleCli("signmessagewithprivkey", "\"privkey\" \"my message\"") +
- "\nVerify the signature\n"
- + HelpExampleCli("verifymessage", "\"1D1ZrZNe3JUo7ZycKEYQQiQAWd9y54F4XX\" \"signature\" \"my message\"") +
- "\nAs a JSON-RPC call\n"
- + HelpExampleRpc("signmessagewithprivkey", "\"privkey\", \"my message\"")
- },
- [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
-{
- std::string strPrivkey = request.params[0].get_str();
- std::string strMessage = request.params[1].get_str();
-
- CKey key = DecodeSecret(strPrivkey);
- if (!key.IsValid()) {
- throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid private key");
- }
-
- std::string signature;
-
- if (!MessageSign(key, strMessage, signature)) {
- throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Sign failed");
- }
-
- return signature;
-},
- };
-}
-
-static RPCHelpMan setmocktime()
-{
- return RPCHelpMan{"setmocktime",
- "\nSet the local time to given timestamp (-regtest only)\n",
- {
- {"timestamp", RPCArg::Type::NUM, RPCArg::Optional::NO, UNIX_EPOCH_TIME + "\n"
- "Pass 0 to go back to using the system time."},
- },
- RPCResult{RPCResult::Type::NONE, "", ""},
- RPCExamples{""},
- [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
-{
- if (!Params().IsMockableChain()) {
- throw std::runtime_error("setmocktime is for regression testing (-regtest mode) only");
- }
-
- // For now, don't change mocktime if we're in the middle of validation, as
- // this could have an effect on mempool time-based eviction, as well as
- // IsCurrentForFeeEstimation() and IsInitialBlockDownload().
- // TODO: figure out the right way to synchronize around mocktime, and
- // ensure all call sites of GetTime() are accessing this safely.
- LOCK(cs_main);
-
- RPCTypeCheck(request.params, {UniValue::VNUM});
- const int64_t time{request.params[0].get_int64()};
- if (time < 0) {
- throw JSONRPCError(RPC_INVALID_PARAMETER, strprintf("Mocktime can not be negative: %s.", time));
- }
- SetMockTime(time);
- auto node_context = util::AnyPtr<NodeContext>(request.context);
- if (node_context) {
- for (const auto& chain_client : node_context->chain_clients) {
- chain_client->setMockTime(time);
- }
- }
-
- return NullUniValue;
-},
- };
-}
-
-#if defined(USE_SYSCALL_SANDBOX)
-static RPCHelpMan invokedisallowedsyscall()
-{
- return RPCHelpMan{
- "invokedisallowedsyscall",
- "\nInvoke a disallowed syscall to trigger a syscall sandbox violation. Used for testing purposes.\n",
- {},
- RPCResult{RPCResult::Type::NONE, "", ""},
- RPCExamples{
- HelpExampleCli("invokedisallowedsyscall", "") + HelpExampleRpc("invokedisallowedsyscall", "")},
- [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue {
- if (!Params().IsTestChain()) {
- throw std::runtime_error("invokedisallowedsyscall is used for testing only.");
- }
- TestDisallowedSandboxCall();
- return NullUniValue;
- },
- };
-}
-#endif // USE_SYSCALL_SANDBOX
-
-static RPCHelpMan mockscheduler()
-{
- return RPCHelpMan{"mockscheduler",
- "\nBump the scheduler into the future (-regtest only)\n",
- {
- {"delta_time", RPCArg::Type::NUM, RPCArg::Optional::NO, "Number of seconds to forward the scheduler into the future." },
- },
- RPCResult{RPCResult::Type::NONE, "", ""},
- RPCExamples{""},
- [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
-{
- if (!Params().IsMockableChain()) {
- throw std::runtime_error("mockscheduler is for regression testing (-regtest mode) only");
- }
-
- // check params are valid values
- RPCTypeCheck(request.params, {UniValue::VNUM});
- int64_t delta_seconds = request.params[0].get_int64();
- if (delta_seconds <= 0 || delta_seconds > 3600) {
- throw std::runtime_error("delta_time must be between 1 and 3600 seconds (1 hr)");
- }
-
- auto node_context = util::AnyPtr<NodeContext>(request.context);
- // protect against null pointer dereference
- CHECK_NONFATAL(node_context);
- CHECK_NONFATAL(node_context->scheduler);
- node_context->scheduler->MockForward(std::chrono::seconds(delta_seconds));
-
- return NullUniValue;
-},
- };
-}
-
-static UniValue RPCLockedMemoryInfo()
-{
- LockedPool::Stats stats = LockedPoolManager::Instance().stats();
- UniValue obj(UniValue::VOBJ);
- obj.pushKV("used", uint64_t(stats.used));
- obj.pushKV("free", uint64_t(stats.free));
- obj.pushKV("total", uint64_t(stats.total));
- obj.pushKV("locked", uint64_t(stats.locked));
- obj.pushKV("chunks_used", uint64_t(stats.chunks_used));
- obj.pushKV("chunks_free", uint64_t(stats.chunks_free));
- return obj;
-}
-
-#ifdef HAVE_MALLOC_INFO
-static std::string RPCMallocInfo()
-{
- char *ptr = nullptr;
- size_t size = 0;
- FILE *f = open_memstream(&ptr, &size);
- if (f) {
- malloc_info(0, f);
- fclose(f);
- if (ptr) {
- std::string rv(ptr, size);
- free(ptr);
- return rv;
- }
- }
- return "";
-}
-#endif
-
-static RPCHelpMan getmemoryinfo()
-{
- /* Please, avoid using the word "pool" here in the RPC interface or help,
- * as users will undoubtedly confuse it with the other "memory pool"
- */
- return RPCHelpMan{"getmemoryinfo",
- "Returns an object containing information about memory usage.\n",
- {
- {"mode", RPCArg::Type::STR, RPCArg::Default{"stats"}, "determines what kind of information is returned.\n"
- " - \"stats\" returns general statistics about memory usage in the daemon.\n"
- " - \"mallocinfo\" returns an XML string describing low-level heap state (only available if compiled with glibc 2.10+)."},
- },
- {
- RPCResult{"mode \"stats\"",
- RPCResult::Type::OBJ, "", "",
- {
- {RPCResult::Type::OBJ, "locked", "Information about locked memory manager",
- {
- {RPCResult::Type::NUM, "used", "Number of bytes used"},
- {RPCResult::Type::NUM, "free", "Number of bytes available in current arenas"},
- {RPCResult::Type::NUM, "total", "Total number of bytes managed"},
- {RPCResult::Type::NUM, "locked", "Amount of bytes that succeeded locking. If this number is smaller than total, locking pages failed at some point and key data could be swapped to disk."},
- {RPCResult::Type::NUM, "chunks_used", "Number allocated chunks"},
- {RPCResult::Type::NUM, "chunks_free", "Number unused chunks"},
- }},
- }
- },
- RPCResult{"mode \"mallocinfo\"",
- RPCResult::Type::STR, "", "\"<malloc version=\"1\">...\""
- },
- },
- RPCExamples{
- HelpExampleCli("getmemoryinfo", "")
- + HelpExampleRpc("getmemoryinfo", "")
- },
- [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
-{
- std::string mode = request.params[0].isNull() ? "stats" : request.params[0].get_str();
- if (mode == "stats") {
- UniValue obj(UniValue::VOBJ);
- obj.pushKV("locked", RPCLockedMemoryInfo());
- return obj;
- } else if (mode == "mallocinfo") {
-#ifdef HAVE_MALLOC_INFO
- return RPCMallocInfo();
-#else
- throw JSONRPCError(RPC_INVALID_PARAMETER, "mallocinfo mode not available");
-#endif
- } else {
- throw JSONRPCError(RPC_INVALID_PARAMETER, "unknown mode " + mode);
- }
-},
- };
-}
-
-static void EnableOrDisableLogCategories(UniValue cats, bool enable) {
- cats = cats.get_array();
- for (unsigned int i = 0; i < cats.size(); ++i) {
- std::string cat = cats[i].get_str();
-
- bool success;
- if (enable) {
- success = LogInstance().EnableCategory(cat);
- } else {
- success = LogInstance().DisableCategory(cat);
- }
-
- if (!success) {
- throw JSONRPCError(RPC_INVALID_PARAMETER, "unknown logging category " + cat);
- }
- }
-}
-
-static RPCHelpMan logging()
-{
- return RPCHelpMan{"logging",
- "Gets and sets the logging configuration.\n"
- "When called without an argument, returns the list of categories with status that are currently being debug logged or not.\n"
- "When called with arguments, adds or removes categories from debug logging and return the lists above.\n"
- "The arguments are evaluated in order \"include\", \"exclude\".\n"
- "If an item is both included and excluded, it will thus end up being excluded.\n"
- "The valid logging categories are: " + LogInstance().LogCategoriesString() + "\n"
- "In addition, the following are available as category names with special meanings:\n"
- " - \"all\", \"1\" : represent all logging categories.\n"
- " - \"none\", \"0\" : even if other logging categories are specified, ignore all of them.\n"
- ,
- {
- {"include", RPCArg::Type::ARR, RPCArg::Optional::OMITTED_NAMED_ARG, "The categories to add to debug logging",
- {
- {"include_category", RPCArg::Type::STR, RPCArg::Optional::OMITTED, "the valid logging category"},
- }},
- {"exclude", RPCArg::Type::ARR, RPCArg::Optional::OMITTED_NAMED_ARG, "The categories to remove from debug logging",
- {
- {"exclude_category", RPCArg::Type::STR, RPCArg::Optional::OMITTED, "the valid logging category"},
- }},
- },
- RPCResult{
- RPCResult::Type::OBJ_DYN, "", "keys are the logging categories, and values indicates its status",
- {
- {RPCResult::Type::BOOL, "category", "if being debug logged or not. false:inactive, true:active"},
- }
- },
- RPCExamples{
- HelpExampleCli("logging", "\"[\\\"all\\\"]\" \"[\\\"http\\\"]\"")
- + HelpExampleRpc("logging", "[\"all\"], [\"libevent\"]")
- },
- [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
-{
- uint32_t original_log_categories = LogInstance().GetCategoryMask();
- if (request.params[0].isArray()) {
- EnableOrDisableLogCategories(request.params[0], true);
- }
- if (request.params[1].isArray()) {
- EnableOrDisableLogCategories(request.params[1], false);
- }
- uint32_t updated_log_categories = LogInstance().GetCategoryMask();
- uint32_t changed_log_categories = original_log_categories ^ updated_log_categories;
-
- // Update libevent logging if BCLog::LIBEVENT has changed.
- // If the library version doesn't allow it, UpdateHTTPServerLogging() returns false,
- // in which case we should clear the BCLog::LIBEVENT flag.
- // Throw an error if the user has explicitly asked to change only the libevent
- // flag and it failed.
- if (changed_log_categories & BCLog::LIBEVENT) {
- if (!UpdateHTTPServerLogging(LogInstance().WillLogCategory(BCLog::LIBEVENT))) {
- LogInstance().DisableCategory(BCLog::LIBEVENT);
- if (changed_log_categories == BCLog::LIBEVENT) {
- throw JSONRPCError(RPC_INVALID_PARAMETER, "libevent logging cannot be updated when using libevent before v2.1.1.");
- }
- }
- }
-
- UniValue result(UniValue::VOBJ);
- for (const auto& logCatActive : LogInstance().LogCategoriesList()) {
- result.pushKV(logCatActive.category, logCatActive.active);
- }
-
- return result;
-},
- };
-}
-
-static RPCHelpMan echo(const std::string& name)
-{
- return RPCHelpMan{name,
- "\nSimply echo back the input arguments. This command is for testing.\n"
- "\nIt will return an internal bug report when arg9='trigger_internal_bug' is passed.\n"
- "\nThe difference between echo and echojson is that echojson has argument conversion enabled in the client-side table in "
- "bitcoin-cli and the GUI. There is no server-side difference.",
- {
- {"arg0", RPCArg::Type::STR, RPCArg::Optional::OMITTED_NAMED_ARG, ""},
- {"arg1", RPCArg::Type::STR, RPCArg::Optional::OMITTED_NAMED_ARG, ""},
- {"arg2", RPCArg::Type::STR, RPCArg::Optional::OMITTED_NAMED_ARG, ""},
- {"arg3", RPCArg::Type::STR, RPCArg::Optional::OMITTED_NAMED_ARG, ""},
- {"arg4", RPCArg::Type::STR, RPCArg::Optional::OMITTED_NAMED_ARG, ""},
- {"arg5", RPCArg::Type::STR, RPCArg::Optional::OMITTED_NAMED_ARG, ""},
- {"arg6", RPCArg::Type::STR, RPCArg::Optional::OMITTED_NAMED_ARG, ""},
- {"arg7", RPCArg::Type::STR, RPCArg::Optional::OMITTED_NAMED_ARG, ""},
- {"arg8", RPCArg::Type::STR, RPCArg::Optional::OMITTED_NAMED_ARG, ""},
- {"arg9", RPCArg::Type::STR, RPCArg::Optional::OMITTED_NAMED_ARG, ""},
- },
- RPCResult{RPCResult::Type::ANY, "", "Returns whatever was passed in"},
- RPCExamples{""},
- [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
-{
- if (request.params[9].isStr()) {
- CHECK_NONFATAL(request.params[9].get_str() != "trigger_internal_bug");
- }
-
- return request.params;
-},
- };
-}
-
-static RPCHelpMan echo() { return echo("echo"); }
-static RPCHelpMan echojson() { return echo("echojson"); }
-
-static RPCHelpMan echoipc()
-{
- return RPCHelpMan{
- "echoipc",
- "\nEcho back the input argument, passing it through a spawned process in a multiprocess build.\n"
- "This command is for testing.\n",
- {{"arg", RPCArg::Type::STR, RPCArg::Optional::NO, "The string to echo",}},
- RPCResult{RPCResult::Type::STR, "echo", "The echoed string."},
- RPCExamples{HelpExampleCli("echo", "\"Hello world\"") +
- HelpExampleRpc("echo", "\"Hello world\"")},
- [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue {
- interfaces::Init& local_init = *EnsureAnyNodeContext(request.context).init;
- std::unique_ptr<interfaces::Echo> echo;
- if (interfaces::Ipc* ipc = local_init.ipc()) {
- // Spawn a new bitcoin-node process and call makeEcho to get a
- // client pointer to a interfaces::Echo instance running in
- // that process. This is just for testing. A slightly more
- // realistic test spawning a different executable instead of
- // the same executable would add a new bitcoin-echo executable,
- // and spawn bitcoin-echo below instead of bitcoin-node. But
- // using bitcoin-node avoids the need to build and install a
- // new executable just for this one test.
- auto init = ipc->spawnProcess("bitcoin-node");
- echo = init->makeEcho();
- ipc->addCleanup(*echo, [init = init.release()] { delete init; });
- } else {
- // IPC support is not available because this is a bitcoind
- // process not a bitcoind-node process, so just create a local
- // interfaces::Echo object and return it so the `echoipc` RPC
- // method will work, and the python test calling `echoipc`
- // can expect the same result.
- echo = local_init.makeEcho();
- }
- return echo->echo(request.params[0].get_str());
- },
- };
-}
-
-static UniValue SummaryToJSON(const IndexSummary&& summary, std::string index_name)
-{
- UniValue ret_summary(UniValue::VOBJ);
- if (!index_name.empty() && index_name != summary.name) return ret_summary;
-
- UniValue entry(UniValue::VOBJ);
- entry.pushKV("synced", summary.synced);
- entry.pushKV("best_block_height", summary.best_block_height);
- ret_summary.pushKV(summary.name, entry);
- return ret_summary;
-}
-
-static RPCHelpMan getindexinfo()
-{
- return RPCHelpMan{"getindexinfo",
- "\nReturns the status of one or all available indices currently running in the node.\n",
- {
- {"index_name", RPCArg::Type::STR, RPCArg::Optional::OMITTED_NAMED_ARG, "Filter results for an index with a specific name."},
- },
- RPCResult{
- RPCResult::Type::OBJ_DYN, "", "", {
- {
- RPCResult::Type::OBJ, "name", "The name of the index",
- {
- {RPCResult::Type::BOOL, "synced", "Whether the index is synced or not"},
- {RPCResult::Type::NUM, "best_block_height", "The block height to which the index is synced"},
- }
- },
- },
- },
- RPCExamples{
- HelpExampleCli("getindexinfo", "")
- + HelpExampleRpc("getindexinfo", "")
- + HelpExampleCli("getindexinfo", "txindex")
- + HelpExampleRpc("getindexinfo", "txindex")
- },
- [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
-{
- UniValue result(UniValue::VOBJ);
- const std::string index_name = request.params[0].isNull() ? "" : request.params[0].get_str();
-
- if (g_txindex) {
- result.pushKVs(SummaryToJSON(g_txindex->GetSummary(), index_name));
- }
-
- if (g_coin_stats_index) {
- result.pushKVs(SummaryToJSON(g_coin_stats_index->GetSummary(), index_name));
- }
-
- ForEachBlockFilterIndex([&result, &index_name](const BlockFilterIndex& index) {
- result.pushKVs(SummaryToJSON(index.GetSummary(), index_name));
- });
-
- return result;
-},
- };
-}
-
-void RegisterMiscRPCCommands(CRPCTable &t)
-{
-// clang-format off
-static const CRPCCommand commands[] =
-{ // category actor (function)
- // --------------------- ------------------------
- { "control", &getmemoryinfo, },
- { "control", &logging, },
- { "util", &validateaddress, },
- { "util", &createmultisig, },
- { "util", &deriveaddresses, },
- { "util", &getdescriptorinfo, },
- { "util", &verifymessage, },
- { "util", &signmessagewithprivkey, },
- { "util", &getindexinfo, },
-
- /* Not shown in help */
- { "hidden", &setmocktime, },
- { "hidden", &mockscheduler, },
- { "hidden", &echo, },
- { "hidden", &echojson, },
- { "hidden", &echoipc, },
-#if defined(USE_SYSCALL_SANDBOX)
- { "hidden", &invokedisallowedsyscall, },
-#endif // USE_SYSCALL_SANDBOX
-};
-// clang-format on
- for (const auto& c : commands) {
- t.appendCommand(c.name, &c);
- }
-}
diff --git a/src/rpc/net.cpp b/src/rpc/net.cpp
index e9e9461b88..09dc8eb3eb 100644
--- a/src/rpc/net.cpp
+++ b/src/rpc/net.cpp
@@ -93,7 +93,7 @@ static RPCHelpMan getpeerinfo()
{
return RPCHelpMan{
"getpeerinfo",
- "\nReturns data about each connected network node as a json array of objects.\n",
+ "Returns data about each connected network peer as a json array of objects.",
{},
RPCResult{
RPCResult::Type::ARR, "", "",
@@ -105,7 +105,7 @@ static RPCHelpMan getpeerinfo()
{RPCResult::Type::STR, "addr", "(host:port) The IP address and port of the peer"},
{RPCResult::Type::STR, "addrbind", /*optional=*/true, "(ip:port) Bind address of the connection to the peer"},
{RPCResult::Type::STR, "addrlocal", /*optional=*/true, "(ip:port) Local address as reported by the peer"},
- {RPCResult::Type::STR, "network", "Network (" + Join(GetNetworkNames(/* append_unroutable */ true), ", ") + ")"},
+ {RPCResult::Type::STR, "network", "Network (" + Join(GetNetworkNames(/*append_unroutable=*/true), ", ") + ")"},
{RPCResult::Type::NUM, "mapped_as", /*optional=*/true, "The AS in the BGP route to the peer used for diversifying\n"
"peer selection (only available if the asmap config flag is set)"},
{RPCResult::Type::STR_HEX, "services", "The services offered"},
@@ -113,7 +113,7 @@ static RPCHelpMan getpeerinfo()
{
{RPCResult::Type::STR, "SERVICE_NAME", "the service name if it is recognised"}
}},
- {RPCResult::Type::BOOL, "relaytxes", "Whether peer has asked us to relay transactions to it"},
+ {RPCResult::Type::BOOL, "relaytxes", /*optional=*/true, "Whether peer has asked us to relay transactions to it"},
{RPCResult::Type::NUM_TIME, "lastsend", "The " + UNIX_EPOCH_TIME + " of the last send"},
{RPCResult::Type::NUM_TIME, "lastrecv", "The " + UNIX_EPOCH_TIME + " of the last receive"},
{RPCResult::Type::NUM_TIME, "last_transaction", "The " + UNIX_EPOCH_TIME + " of the last valid transaction received from this peer"},
@@ -144,7 +144,7 @@ static RPCHelpMan getpeerinfo()
{
{RPCResult::Type::STR, "permission_type", Join(NET_PERMISSIONS_DOC, ",\n") + ".\n"},
}},
- {RPCResult::Type::NUM, "minfeefilter", "The minimum fee rate for transactions this peer accepts"},
+ {RPCResult::Type::NUM, "minfeefilter", /*optional=*/true, "The minimum fee rate for transactions this peer accepts"},
{RPCResult::Type::OBJ_DYN, "bytessent_per_msg", "",
{
{RPCResult::Type::NUM, "msg", "The total bytes sent aggregated by message type\n"
@@ -197,7 +197,6 @@ static RPCHelpMan getpeerinfo()
}
obj.pushKV("services", strprintf("%016x", stats.nServices));
obj.pushKV("servicesnames", GetServicesNames(stats.nServices));
- obj.pushKV("relaytxes", stats.fRelayTxes);
obj.pushKV("lastsend", count_seconds(stats.m_last_send));
obj.pushKV("lastrecv", count_seconds(stats.m_last_recv));
obj.pushKV("last_transaction", count_seconds(stats.m_last_tx_time));
@@ -232,6 +231,8 @@ static RPCHelpMan getpeerinfo()
heights.push_back(height);
}
obj.pushKV("inflight", heights);
+ obj.pushKV("relaytxes", statestats.m_relay_txs);
+ obj.pushKV("minfeefilter", ValueFromAmount(statestats.m_fee_filter_received));
obj.pushKV("addr_relay_enabled", statestats.m_addr_relay_enabled);
obj.pushKV("addr_processed", statestats.m_addr_processed);
obj.pushKV("addr_rate_limited", statestats.m_addr_rate_limited);
@@ -241,7 +242,6 @@ static RPCHelpMan getpeerinfo()
permissions.push_back(permission);
}
obj.pushKV("permissions", permissions);
- obj.pushKV("minfeefilter", ValueFromAmount(stats.minFeeFilter));
UniValue sendPerMsgType(UniValue::VOBJ);
for (const auto& i : stats.mapSendBytesPerMsgType) {
@@ -556,7 +556,7 @@ static UniValue GetNetworksInfo()
for (int n = 0; n < NET_MAX; ++n) {
enum Network network = static_cast<enum Network>(n);
if (network == NET_UNROUTABLE || network == NET_INTERNAL) continue;
- proxyType proxy;
+ Proxy proxy;
UniValue obj(UniValue::VOBJ);
GetProxy(network, proxy);
obj.pushKV("name", GetNetworkName(network));
@@ -847,7 +847,9 @@ static RPCHelpMan setnetworkactive()
static RPCHelpMan getnodeaddresses()
{
return RPCHelpMan{"getnodeaddresses",
- "\nReturn known addresses, which can potentially be used to find new nodes in the network.\n",
+ "Return known addresses, after filtering for quality and recency.\n"
+ "These can potentially be used to find new peers in the network.\n"
+ "The total number of addresses known to the node may be higher.",
{
{"count", RPCArg::Type::NUM, RPCArg::Default{1}, "The maximum number of addresses to return. Specify 0 to return all known addresses."},
{"network", RPCArg::Type::STR, RPCArg::DefaultHint{"all networks"}, "Return only addresses of the specified network. Can be one of: " + Join(GetNetworkNames(), ", ") + "."},
@@ -886,7 +888,7 @@ static RPCHelpMan getnodeaddresses()
}
// returns a shuffled list of CAddress
- const std::vector<CAddress> vAddr{connman.GetAddresses(count, /* max_pct */ 0, network)};
+ const std::vector<CAddress> vAddr{connman.GetAddresses(count, /*max_pct=*/0, network)};
UniValue ret(UniValue::VARR);
for (const CAddress& addr : vAddr) {
@@ -957,30 +959,25 @@ static RPCHelpMan addpeeraddress()
};
}
-void RegisterNetRPCCommands(CRPCTable &t)
-{
-// clang-format off
-static const CRPCCommand commands[] =
-{ // category actor
- // --------------------- -----------------------
- { "network", &getconnectioncount, },
- { "network", &ping, },
- { "network", &getpeerinfo, },
- { "network", &addnode, },
- { "network", &disconnectnode, },
- { "network", &getaddednodeinfo, },
- { "network", &getnettotals, },
- { "network", &getnetworkinfo, },
- { "network", &setban, },
- { "network", &listbanned, },
- { "network", &clearbanned, },
- { "network", &setnetworkactive, },
- { "network", &getnodeaddresses, },
-
- { "hidden", &addconnection, },
- { "hidden", &addpeeraddress, },
-};
-// clang-format on
+void RegisterNetRPCCommands(CRPCTable& t)
+{
+ static const CRPCCommand commands[]{
+ {"network", &getconnectioncount},
+ {"network", &ping},
+ {"network", &getpeerinfo},
+ {"network", &addnode},
+ {"network", &disconnectnode},
+ {"network", &getaddednodeinfo},
+ {"network", &getnettotals},
+ {"network", &getnetworkinfo},
+ {"network", &setban},
+ {"network", &listbanned},
+ {"network", &clearbanned},
+ {"network", &setnetworkactive},
+ {"network", &getnodeaddresses},
+ {"hidden", &addconnection},
+ {"hidden", &addpeeraddress},
+ };
for (const auto& c : commands) {
t.appendCommand(c.name, &c);
}
diff --git a/src/rpc/node.cpp b/src/rpc/node.cpp
new file mode 100644
index 0000000000..24697a606e
--- /dev/null
+++ b/src/rpc/node.cpp
@@ -0,0 +1,440 @@
+// Copyright (c) 2010 Satoshi Nakamoto
+// Copyright (c) 2009-2021 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 <chainparams.h>
+#include <httpserver.h>
+#include <index/blockfilterindex.h>
+#include <index/coinstatsindex.h>
+#include <index/txindex.h>
+#include <interfaces/chain.h>
+#include <interfaces/echo.h>
+#include <interfaces/init.h>
+#include <interfaces/ipc.h>
+#include <node/context.h>
+#include <rpc/server.h>
+#include <rpc/server_util.h>
+#include <rpc/util.h>
+#include <scheduler.h>
+#include <univalue.h>
+#include <util/check.h>
+#include <util/syscall_sandbox.h>
+#include <util/system.h>
+
+#include <stdint.h>
+#ifdef HAVE_MALLOC_INFO
+#include <malloc.h>
+#endif
+
+using node::NodeContext;
+
+static RPCHelpMan setmocktime()
+{
+ return RPCHelpMan{"setmocktime",
+ "\nSet the local time to given timestamp (-regtest only)\n",
+ {
+ {"timestamp", RPCArg::Type::NUM, RPCArg::Optional::NO, UNIX_EPOCH_TIME + "\n"
+ "Pass 0 to go back to using the system time."},
+ },
+ RPCResult{RPCResult::Type::NONE, "", ""},
+ RPCExamples{""},
+ [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
+{
+ if (!Params().IsMockableChain()) {
+ throw std::runtime_error("setmocktime is for regression testing (-regtest mode) only");
+ }
+
+ // For now, don't change mocktime if we're in the middle of validation, as
+ // this could have an effect on mempool time-based eviction, as well as
+ // IsCurrentForFeeEstimation() and IsInitialBlockDownload().
+ // TODO: figure out the right way to synchronize around mocktime, and
+ // ensure all call sites of GetTime() are accessing this safely.
+ LOCK(cs_main);
+
+ RPCTypeCheck(request.params, {UniValue::VNUM});
+ const int64_t time{request.params[0].get_int64()};
+ if (time < 0) {
+ throw JSONRPCError(RPC_INVALID_PARAMETER, strprintf("Mocktime cannot be negative: %s.", time));
+ }
+ SetMockTime(time);
+ auto node_context = util::AnyPtr<NodeContext>(request.context);
+ if (node_context) {
+ for (const auto& chain_client : node_context->chain_clients) {
+ chain_client->setMockTime(time);
+ }
+ }
+
+ return NullUniValue;
+},
+ };
+}
+
+#if defined(USE_SYSCALL_SANDBOX)
+static RPCHelpMan invokedisallowedsyscall()
+{
+ return RPCHelpMan{
+ "invokedisallowedsyscall",
+ "\nInvoke a disallowed syscall to trigger a syscall sandbox violation. Used for testing purposes.\n",
+ {},
+ RPCResult{RPCResult::Type::NONE, "", ""},
+ RPCExamples{
+ HelpExampleCli("invokedisallowedsyscall", "") + HelpExampleRpc("invokedisallowedsyscall", "")},
+ [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue {
+ if (!Params().IsTestChain()) {
+ throw std::runtime_error("invokedisallowedsyscall is used for testing only.");
+ }
+ TestDisallowedSandboxCall();
+ return NullUniValue;
+ },
+ };
+}
+#endif // USE_SYSCALL_SANDBOX
+
+static RPCHelpMan mockscheduler()
+{
+ return RPCHelpMan{"mockscheduler",
+ "\nBump the scheduler into the future (-regtest only)\n",
+ {
+ {"delta_time", RPCArg::Type::NUM, RPCArg::Optional::NO, "Number of seconds to forward the scheduler into the future." },
+ },
+ RPCResult{RPCResult::Type::NONE, "", ""},
+ RPCExamples{""},
+ [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
+{
+ if (!Params().IsMockableChain()) {
+ throw std::runtime_error("mockscheduler is for regression testing (-regtest mode) only");
+ }
+
+ // check params are valid values
+ RPCTypeCheck(request.params, {UniValue::VNUM});
+ int64_t delta_seconds = request.params[0].get_int64();
+ if (delta_seconds <= 0 || delta_seconds > 3600) {
+ throw std::runtime_error("delta_time must be between 1 and 3600 seconds (1 hr)");
+ }
+
+ auto node_context = CHECK_NONFATAL(util::AnyPtr<NodeContext>(request.context));
+ // protect against null pointer dereference
+ CHECK_NONFATAL(node_context->scheduler);
+ node_context->scheduler->MockForward(std::chrono::seconds(delta_seconds));
+
+ return NullUniValue;
+},
+ };
+}
+
+static UniValue RPCLockedMemoryInfo()
+{
+ LockedPool::Stats stats = LockedPoolManager::Instance().stats();
+ UniValue obj(UniValue::VOBJ);
+ obj.pushKV("used", uint64_t(stats.used));
+ obj.pushKV("free", uint64_t(stats.free));
+ obj.pushKV("total", uint64_t(stats.total));
+ obj.pushKV("locked", uint64_t(stats.locked));
+ obj.pushKV("chunks_used", uint64_t(stats.chunks_used));
+ obj.pushKV("chunks_free", uint64_t(stats.chunks_free));
+ return obj;
+}
+
+#ifdef HAVE_MALLOC_INFO
+static std::string RPCMallocInfo()
+{
+ char *ptr = nullptr;
+ size_t size = 0;
+ FILE *f = open_memstream(&ptr, &size);
+ if (f) {
+ malloc_info(0, f);
+ fclose(f);
+ if (ptr) {
+ std::string rv(ptr, size);
+ free(ptr);
+ return rv;
+ }
+ }
+ return "";
+}
+#endif
+
+static RPCHelpMan getmemoryinfo()
+{
+ /* Please, avoid using the word "pool" here in the RPC interface or help,
+ * as users will undoubtedly confuse it with the other "memory pool"
+ */
+ return RPCHelpMan{"getmemoryinfo",
+ "Returns an object containing information about memory usage.\n",
+ {
+ {"mode", RPCArg::Type::STR, RPCArg::Default{"stats"}, "determines what kind of information is returned.\n"
+ " - \"stats\" returns general statistics about memory usage in the daemon.\n"
+ " - \"mallocinfo\" returns an XML string describing low-level heap state (only available if compiled with glibc 2.10+)."},
+ },
+ {
+ RPCResult{"mode \"stats\"",
+ RPCResult::Type::OBJ, "", "",
+ {
+ {RPCResult::Type::OBJ, "locked", "Information about locked memory manager",
+ {
+ {RPCResult::Type::NUM, "used", "Number of bytes used"},
+ {RPCResult::Type::NUM, "free", "Number of bytes available in current arenas"},
+ {RPCResult::Type::NUM, "total", "Total number of bytes managed"},
+ {RPCResult::Type::NUM, "locked", "Amount of bytes that succeeded locking. If this number is smaller than total, locking pages failed at some point and key data could be swapped to disk."},
+ {RPCResult::Type::NUM, "chunks_used", "Number allocated chunks"},
+ {RPCResult::Type::NUM, "chunks_free", "Number unused chunks"},
+ }},
+ }
+ },
+ RPCResult{"mode \"mallocinfo\"",
+ RPCResult::Type::STR, "", "\"<malloc version=\"1\">...\""
+ },
+ },
+ RPCExamples{
+ HelpExampleCli("getmemoryinfo", "")
+ + HelpExampleRpc("getmemoryinfo", "")
+ },
+ [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
+{
+ std::string mode = request.params[0].isNull() ? "stats" : request.params[0].get_str();
+ if (mode == "stats") {
+ UniValue obj(UniValue::VOBJ);
+ obj.pushKV("locked", RPCLockedMemoryInfo());
+ return obj;
+ } else if (mode == "mallocinfo") {
+#ifdef HAVE_MALLOC_INFO
+ return RPCMallocInfo();
+#else
+ throw JSONRPCError(RPC_INVALID_PARAMETER, "mallocinfo mode not available");
+#endif
+ } else {
+ throw JSONRPCError(RPC_INVALID_PARAMETER, "unknown mode " + mode);
+ }
+},
+ };
+}
+
+static void EnableOrDisableLogCategories(UniValue cats, bool enable) {
+ cats = cats.get_array();
+ for (unsigned int i = 0; i < cats.size(); ++i) {
+ std::string cat = cats[i].get_str();
+
+ bool success;
+ if (enable) {
+ success = LogInstance().EnableCategory(cat);
+ } else {
+ success = LogInstance().DisableCategory(cat);
+ }
+
+ if (!success) {
+ throw JSONRPCError(RPC_INVALID_PARAMETER, "unknown logging category " + cat);
+ }
+ }
+}
+
+static RPCHelpMan logging()
+{
+ return RPCHelpMan{"logging",
+ "Gets and sets the logging configuration.\n"
+ "When called without an argument, returns the list of categories with status that are currently being debug logged or not.\n"
+ "When called with arguments, adds or removes categories from debug logging and return the lists above.\n"
+ "The arguments are evaluated in order \"include\", \"exclude\".\n"
+ "If an item is both included and excluded, it will thus end up being excluded.\n"
+ "The valid logging categories are: " + LogInstance().LogCategoriesString() + "\n"
+ "In addition, the following are available as category names with special meanings:\n"
+ " - \"all\", \"1\" : represent all logging categories.\n"
+ " - \"none\", \"0\" : even if other logging categories are specified, ignore all of them.\n"
+ ,
+ {
+ {"include", RPCArg::Type::ARR, RPCArg::Optional::OMITTED_NAMED_ARG, "The categories to add to debug logging",
+ {
+ {"include_category", RPCArg::Type::STR, RPCArg::Optional::OMITTED, "the valid logging category"},
+ }},
+ {"exclude", RPCArg::Type::ARR, RPCArg::Optional::OMITTED_NAMED_ARG, "The categories to remove from debug logging",
+ {
+ {"exclude_category", RPCArg::Type::STR, RPCArg::Optional::OMITTED, "the valid logging category"},
+ }},
+ },
+ RPCResult{
+ RPCResult::Type::OBJ_DYN, "", "keys are the logging categories, and values indicates its status",
+ {
+ {RPCResult::Type::BOOL, "category", "if being debug logged or not. false:inactive, true:active"},
+ }
+ },
+ RPCExamples{
+ HelpExampleCli("logging", "\"[\\\"all\\\"]\" \"[\\\"http\\\"]\"")
+ + HelpExampleRpc("logging", "[\"all\"], [\"libevent\"]")
+ },
+ [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
+{
+ uint32_t original_log_categories = LogInstance().GetCategoryMask();
+ if (request.params[0].isArray()) {
+ EnableOrDisableLogCategories(request.params[0], true);
+ }
+ if (request.params[1].isArray()) {
+ EnableOrDisableLogCategories(request.params[1], false);
+ }
+ uint32_t updated_log_categories = LogInstance().GetCategoryMask();
+ uint32_t changed_log_categories = original_log_categories ^ updated_log_categories;
+
+ // Update libevent logging if BCLog::LIBEVENT has changed.
+ if (changed_log_categories & BCLog::LIBEVENT) {
+ UpdateHTTPServerLogging(LogInstance().WillLogCategory(BCLog::LIBEVENT));
+ }
+
+ UniValue result(UniValue::VOBJ);
+ for (const auto& logCatActive : LogInstance().LogCategoriesList()) {
+ result.pushKV(logCatActive.category, logCatActive.active);
+ }
+
+ return result;
+},
+ };
+}
+
+static RPCHelpMan echo(const std::string& name)
+{
+ return RPCHelpMan{name,
+ "\nSimply echo back the input arguments. This command is for testing.\n"
+ "\nIt will return an internal bug report when arg9='trigger_internal_bug' is passed.\n"
+ "\nThe difference between echo and echojson is that echojson has argument conversion enabled in the client-side table in "
+ "bitcoin-cli and the GUI. There is no server-side difference.",
+ {
+ {"arg0", RPCArg::Type::STR, RPCArg::Optional::OMITTED_NAMED_ARG, ""},
+ {"arg1", RPCArg::Type::STR, RPCArg::Optional::OMITTED_NAMED_ARG, ""},
+ {"arg2", RPCArg::Type::STR, RPCArg::Optional::OMITTED_NAMED_ARG, ""},
+ {"arg3", RPCArg::Type::STR, RPCArg::Optional::OMITTED_NAMED_ARG, ""},
+ {"arg4", RPCArg::Type::STR, RPCArg::Optional::OMITTED_NAMED_ARG, ""},
+ {"arg5", RPCArg::Type::STR, RPCArg::Optional::OMITTED_NAMED_ARG, ""},
+ {"arg6", RPCArg::Type::STR, RPCArg::Optional::OMITTED_NAMED_ARG, ""},
+ {"arg7", RPCArg::Type::STR, RPCArg::Optional::OMITTED_NAMED_ARG, ""},
+ {"arg8", RPCArg::Type::STR, RPCArg::Optional::OMITTED_NAMED_ARG, ""},
+ {"arg9", RPCArg::Type::STR, RPCArg::Optional::OMITTED_NAMED_ARG, ""},
+ },
+ RPCResult{RPCResult::Type::ANY, "", "Returns whatever was passed in"},
+ RPCExamples{""},
+ [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
+{
+ if (request.params[9].isStr()) {
+ CHECK_NONFATAL(request.params[9].get_str() != "trigger_internal_bug");
+ }
+
+ return request.params;
+},
+ };
+}
+
+static RPCHelpMan echo() { return echo("echo"); }
+static RPCHelpMan echojson() { return echo("echojson"); }
+
+static RPCHelpMan echoipc()
+{
+ return RPCHelpMan{
+ "echoipc",
+ "\nEcho back the input argument, passing it through a spawned process in a multiprocess build.\n"
+ "This command is for testing.\n",
+ {{"arg", RPCArg::Type::STR, RPCArg::Optional::NO, "The string to echo",}},
+ RPCResult{RPCResult::Type::STR, "echo", "The echoed string."},
+ RPCExamples{HelpExampleCli("echo", "\"Hello world\"") +
+ HelpExampleRpc("echo", "\"Hello world\"")},
+ [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue {
+ interfaces::Init& local_init = *EnsureAnyNodeContext(request.context).init;
+ std::unique_ptr<interfaces::Echo> echo;
+ if (interfaces::Ipc* ipc = local_init.ipc()) {
+ // Spawn a new bitcoin-node process and call makeEcho to get a
+ // client pointer to a interfaces::Echo instance running in
+ // that process. This is just for testing. A slightly more
+ // realistic test spawning a different executable instead of
+ // the same executable would add a new bitcoin-echo executable,
+ // and spawn bitcoin-echo below instead of bitcoin-node. But
+ // using bitcoin-node avoids the need to build and install a
+ // new executable just for this one test.
+ auto init = ipc->spawnProcess("bitcoin-node");
+ echo = init->makeEcho();
+ ipc->addCleanup(*echo, [init = init.release()] { delete init; });
+ } else {
+ // IPC support is not available because this is a bitcoind
+ // process not a bitcoind-node process, so just create a local
+ // interfaces::Echo object and return it so the `echoipc` RPC
+ // method will work, and the python test calling `echoipc`
+ // can expect the same result.
+ echo = local_init.makeEcho();
+ }
+ return echo->echo(request.params[0].get_str());
+ },
+ };
+}
+
+static UniValue SummaryToJSON(const IndexSummary&& summary, std::string index_name)
+{
+ UniValue ret_summary(UniValue::VOBJ);
+ if (!index_name.empty() && index_name != summary.name) return ret_summary;
+
+ UniValue entry(UniValue::VOBJ);
+ entry.pushKV("synced", summary.synced);
+ entry.pushKV("best_block_height", summary.best_block_height);
+ ret_summary.pushKV(summary.name, entry);
+ return ret_summary;
+}
+
+static RPCHelpMan getindexinfo()
+{
+ return RPCHelpMan{"getindexinfo",
+ "\nReturns the status of one or all available indices currently running in the node.\n",
+ {
+ {"index_name", RPCArg::Type::STR, RPCArg::Optional::OMITTED_NAMED_ARG, "Filter results for an index with a specific name."},
+ },
+ RPCResult{
+ RPCResult::Type::OBJ_DYN, "", "", {
+ {
+ RPCResult::Type::OBJ, "name", "The name of the index",
+ {
+ {RPCResult::Type::BOOL, "synced", "Whether the index is synced or not"},
+ {RPCResult::Type::NUM, "best_block_height", "The block height to which the index is synced"},
+ }
+ },
+ },
+ },
+ RPCExamples{
+ HelpExampleCli("getindexinfo", "")
+ + HelpExampleRpc("getindexinfo", "")
+ + HelpExampleCli("getindexinfo", "txindex")
+ + HelpExampleRpc("getindexinfo", "txindex")
+ },
+ [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
+{
+ UniValue result(UniValue::VOBJ);
+ const std::string index_name = request.params[0].isNull() ? "" : request.params[0].get_str();
+
+ if (g_txindex) {
+ result.pushKVs(SummaryToJSON(g_txindex->GetSummary(), index_name));
+ }
+
+ if (g_coin_stats_index) {
+ result.pushKVs(SummaryToJSON(g_coin_stats_index->GetSummary(), index_name));
+ }
+
+ ForEachBlockFilterIndex([&result, &index_name](const BlockFilterIndex& index) {
+ result.pushKVs(SummaryToJSON(index.GetSummary(), index_name));
+ });
+
+ return result;
+},
+ };
+}
+
+void RegisterNodeRPCCommands(CRPCTable& t)
+{
+ static const CRPCCommand commands[]{
+ {"control", &getmemoryinfo},
+ {"control", &logging},
+ {"util", &getindexinfo},
+ {"hidden", &setmocktime},
+ {"hidden", &mockscheduler},
+ {"hidden", &echo},
+ {"hidden", &echojson},
+ {"hidden", &echoipc},
+#if defined(USE_SYSCALL_SANDBOX)
+ {"hidden", &invokedisallowedsyscall},
+#endif // USE_SYSCALL_SANDBOX
+ };
+ for (const auto& c : commands) {
+ t.appendCommand(c.name, &c);
+ }
+}
diff --git a/src/rpc/output_script.cpp b/src/rpc/output_script.cpp
new file mode 100644
index 0000000000..1cf952d0c8
--- /dev/null
+++ b/src/rpc/output_script.cpp
@@ -0,0 +1,319 @@
+// 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 <key_io.h>
+#include <outputtype.h>
+#include <pubkey.h>
+#include <rpc/protocol.h>
+#include <rpc/request.h>
+#include <rpc/server.h>
+#include <rpc/util.h>
+#include <script/descriptor.h>
+#include <script/script.h>
+#include <script/signingprovider.h>
+#include <script/standard.h>
+#include <tinyformat.h>
+#include <univalue.h>
+#include <util/check.h>
+#include <util/strencodings.h>
+
+#include <cstdint>
+#include <memory>
+#include <optional>
+#include <string>
+#include <tuple>
+#include <vector>
+
+namespace node {
+struct NodeContext;
+}
+using node::NodeContext;
+
+static RPCHelpMan validateaddress()
+{
+ return RPCHelpMan{
+ "validateaddress",
+ "\nReturn information about the given bitcoin address.\n",
+ {
+ {"address", RPCArg::Type::STR, RPCArg::Optional::NO, "The bitcoin address to validate"},
+ },
+ RPCResult{
+ RPCResult::Type::OBJ, "", "",
+ {
+ {RPCResult::Type::BOOL, "isvalid", "If the address is valid or not"},
+ {RPCResult::Type::STR, "address", /*optional=*/true, "The bitcoin address validated"},
+ {RPCResult::Type::STR_HEX, "scriptPubKey", /*optional=*/true, "The hex-encoded scriptPubKey generated by the address"},
+ {RPCResult::Type::BOOL, "isscript", /*optional=*/true, "If the key is a script"},
+ {RPCResult::Type::BOOL, "iswitness", /*optional=*/true, "If the address is a witness address"},
+ {RPCResult::Type::NUM, "witness_version", /*optional=*/true, "The version number of the witness program"},
+ {RPCResult::Type::STR_HEX, "witness_program", /*optional=*/true, "The hex value of the witness program"},
+ {RPCResult::Type::STR, "error", /*optional=*/true, "Error message, if any"},
+ {RPCResult::Type::ARR, "error_locations", /*optional=*/true, "Indices of likely error locations in address, if known (e.g. Bech32 errors)",
+ {
+ {RPCResult::Type::NUM, "index", "index of a potential error"},
+ }},
+ }
+ },
+ RPCExamples{
+ HelpExampleCli("validateaddress", "\"" + EXAMPLE_ADDRESS[0] + "\"") +
+ HelpExampleRpc("validateaddress", "\"" + EXAMPLE_ADDRESS[0] + "\"")
+ },
+ [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
+ {
+ std::string error_msg;
+ std::vector<int> error_locations;
+ CTxDestination dest = DecodeDestination(request.params[0].get_str(), error_msg, &error_locations);
+ const bool isValid = IsValidDestination(dest);
+ CHECK_NONFATAL(isValid == error_msg.empty());
+
+ UniValue ret(UniValue::VOBJ);
+ ret.pushKV("isvalid", isValid);
+ if (isValid) {
+ std::string currentAddress = EncodeDestination(dest);
+ ret.pushKV("address", currentAddress);
+
+ CScript scriptPubKey = GetScriptForDestination(dest);
+ ret.pushKV("scriptPubKey", HexStr(scriptPubKey));
+
+ UniValue detail = DescribeAddress(dest);
+ ret.pushKVs(detail);
+ } else {
+ UniValue error_indices(UniValue::VARR);
+ for (int i : error_locations) error_indices.push_back(i);
+ ret.pushKV("error_locations", error_indices);
+ ret.pushKV("error", error_msg);
+ }
+
+ return ret;
+ },
+ };
+}
+
+static RPCHelpMan createmultisig()
+{
+ return RPCHelpMan{"createmultisig",
+ "\nCreates a multi-signature address with n signature of m keys required.\n"
+ "It returns a json object with the address and redeemScript.\n",
+ {
+ {"nrequired", RPCArg::Type::NUM, RPCArg::Optional::NO, "The number of required signatures out of the n keys."},
+ {"keys", RPCArg::Type::ARR, RPCArg::Optional::NO, "The hex-encoded public keys.",
+ {
+ {"key", RPCArg::Type::STR_HEX, RPCArg::Optional::OMITTED, "The hex-encoded public key"},
+ }},
+ {"address_type", RPCArg::Type::STR, RPCArg::Default{"legacy"}, "The address type to use. Options are \"legacy\", \"p2sh-segwit\", and \"bech32\"."},
+ },
+ RPCResult{
+ RPCResult::Type::OBJ, "", "",
+ {
+ {RPCResult::Type::STR, "address", "The value of the new multisig address."},
+ {RPCResult::Type::STR_HEX, "redeemScript", "The string value of the hex-encoded redemption script."},
+ {RPCResult::Type::STR, "descriptor", "The descriptor for this multisig"},
+ {RPCResult::Type::ARR, "warnings", /*optional=*/true, "Any warnings resulting from the creation of this multisig",
+ {
+ {RPCResult::Type::STR, "", ""},
+ }},
+ }
+ },
+ RPCExamples{
+ "\nCreate a multisig address from 2 public keys\n"
+ + HelpExampleCli("createmultisig", "2 \"[\\\"03789ed0bb717d88f7d321a368d905e7430207ebbd82bd342cf11ae157a7ace5fd\\\",\\\"03dbc6764b8884a92e871274b87583e6d5c2a58819473e17e107ef3f6aa5a61626\\\"]\"") +
+ "\nAs a JSON-RPC call\n"
+ + HelpExampleRpc("createmultisig", "2, [\"03789ed0bb717d88f7d321a368d905e7430207ebbd82bd342cf11ae157a7ace5fd\",\"03dbc6764b8884a92e871274b87583e6d5c2a58819473e17e107ef3f6aa5a61626\"]")
+ },
+ [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
+ {
+ int required = request.params[0].get_int();
+
+ // Get the public keys
+ const UniValue& keys = request.params[1].get_array();
+ std::vector<CPubKey> pubkeys;
+ for (unsigned int i = 0; i < keys.size(); ++i) {
+ if (IsHex(keys[i].get_str()) && (keys[i].get_str().length() == 66 || keys[i].get_str().length() == 130)) {
+ pubkeys.push_back(HexToPubKey(keys[i].get_str()));
+ } else {
+ throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, strprintf("Invalid public key: %s\n.", keys[i].get_str()));
+ }
+ }
+
+ // Get the output type
+ OutputType output_type = OutputType::LEGACY;
+ if (!request.params[2].isNull()) {
+ std::optional<OutputType> parsed = ParseOutputType(request.params[2].get_str());
+ if (!parsed) {
+ throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, strprintf("Unknown address type '%s'", request.params[2].get_str()));
+ } else if (parsed.value() == OutputType::BECH32M) {
+ throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "createmultisig cannot create bech32m multisig addresses");
+ }
+ output_type = parsed.value();
+ }
+
+ // Construct using pay-to-script-hash:
+ FillableSigningProvider keystore;
+ CScript inner;
+ const CTxDestination dest = AddAndGetMultisigDestination(required, pubkeys, output_type, keystore, inner);
+
+ // Make the descriptor
+ std::unique_ptr<Descriptor> descriptor = InferDescriptor(GetScriptForDestination(dest), keystore);
+
+ UniValue result(UniValue::VOBJ);
+ result.pushKV("address", EncodeDestination(dest));
+ result.pushKV("redeemScript", HexStr(inner));
+ result.pushKV("descriptor", descriptor->ToString());
+
+ UniValue warnings(UniValue::VARR);
+ if (!request.params[2].isNull() && OutputTypeFromDestination(dest) != output_type) {
+ // Only warns if the user has explicitly chosen an address type we cannot generate
+ warnings.push_back("Unable to make chosen address type, please ensure no uncompressed public keys are present.");
+ }
+ if (warnings.size()) result.pushKV("warnings", warnings);
+
+ return result;
+ },
+ };
+}
+
+static RPCHelpMan getdescriptorinfo()
+{
+ const std::string EXAMPLE_DESCRIPTOR = "wpkh([d34db33f/84h/0h/0h]0279be667ef9dcbbac55a06295Ce870b07029Bfcdb2dce28d959f2815b16f81798)";
+
+ return RPCHelpMan{"getdescriptorinfo",
+ {"\nAnalyses a descriptor.\n"},
+ {
+ {"descriptor", RPCArg::Type::STR, RPCArg::Optional::NO, "The descriptor."},
+ },
+ RPCResult{
+ RPCResult::Type::OBJ, "", "",
+ {
+ {RPCResult::Type::STR, "descriptor", "The descriptor in canonical form, without private keys"},
+ {RPCResult::Type::STR, "checksum", "The checksum for the input descriptor"},
+ {RPCResult::Type::BOOL, "isrange", "Whether the descriptor is ranged"},
+ {RPCResult::Type::BOOL, "issolvable", "Whether the descriptor is solvable"},
+ {RPCResult::Type::BOOL, "hasprivatekeys", "Whether the input descriptor contained at least one private key"},
+ }
+ },
+ RPCExamples{
+ "Analyse a descriptor\n" +
+ HelpExampleCli("getdescriptorinfo", "\"" + EXAMPLE_DESCRIPTOR + "\"") +
+ HelpExampleRpc("getdescriptorinfo", "\"" + EXAMPLE_DESCRIPTOR + "\"")
+ },
+ [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
+ {
+ RPCTypeCheck(request.params, {UniValue::VSTR});
+
+ FlatSigningProvider provider;
+ std::string error;
+ auto desc = Parse(request.params[0].get_str(), provider, error);
+ if (!desc) {
+ throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, error);
+ }
+
+ UniValue result(UniValue::VOBJ);
+ result.pushKV("descriptor", desc->ToString());
+ result.pushKV("checksum", GetDescriptorChecksum(request.params[0].get_str()));
+ result.pushKV("isrange", desc->IsRange());
+ result.pushKV("issolvable", desc->IsSolvable());
+ result.pushKV("hasprivatekeys", provider.keys.size() > 0);
+ return result;
+ },
+ };
+}
+
+static RPCHelpMan deriveaddresses()
+{
+ const std::string EXAMPLE_DESCRIPTOR = "wpkh([d34db33f/84h/0h/0h]xpub6DJ2dNUysrn5Vt36jH2KLBT2i1auw1tTSSomg8PhqNiUtx8QX2SvC9nrHu81fT41fvDUnhMjEzQgXnQjKEu3oaqMSzhSrHMxyyoEAmUHQbY/0/*)#cjjspncu";
+
+ return RPCHelpMan{"deriveaddresses",
+ {"\nDerives one or more addresses corresponding to an output descriptor.\n"
+ "Examples of output descriptors are:\n"
+ " pkh(<pubkey>) P2PKH outputs for the given pubkey\n"
+ " wpkh(<pubkey>) Native segwit P2PKH outputs for the given pubkey\n"
+ " sh(multi(<n>,<pubkey>,<pubkey>,...)) P2SH-multisig outputs for the given threshold and pubkeys\n"
+ " raw(<hex script>) Outputs whose scriptPubKey equals the specified hex scripts\n"
+ "\nIn the above, <pubkey> either refers to a fixed public key in hexadecimal notation, or to an xpub/xprv optionally followed by one\n"
+ "or more path elements separated by \"/\", where \"h\" represents a hardened child key.\n"
+ "For more information on output descriptors, see the documentation in the doc/descriptors.md file.\n"},
+ {
+ {"descriptor", RPCArg::Type::STR, RPCArg::Optional::NO, "The descriptor."},
+ {"range", RPCArg::Type::RANGE, RPCArg::Optional::OMITTED_NAMED_ARG, "If a ranged descriptor is used, this specifies the end or the range (in [begin,end] notation) to derive."},
+ },
+ RPCResult{
+ RPCResult::Type::ARR, "", "",
+ {
+ {RPCResult::Type::STR, "address", "the derived addresses"},
+ }
+ },
+ RPCExamples{
+ "First three native segwit receive addresses\n" +
+ HelpExampleCli("deriveaddresses", "\"" + EXAMPLE_DESCRIPTOR + "\" \"[0,2]\"") +
+ HelpExampleRpc("deriveaddresses", "\"" + EXAMPLE_DESCRIPTOR + "\", \"[0,2]\"")
+ },
+ [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
+ {
+ RPCTypeCheck(request.params, {UniValue::VSTR, UniValueType()}); // Range argument is checked later
+ const std::string desc_str = request.params[0].get_str();
+
+ int64_t range_begin = 0;
+ int64_t range_end = 0;
+
+ if (request.params.size() >= 2 && !request.params[1].isNull()) {
+ std::tie(range_begin, range_end) = ParseDescriptorRange(request.params[1]);
+ }
+
+ FlatSigningProvider key_provider;
+ std::string error;
+ auto desc = Parse(desc_str, key_provider, error, /* require_checksum = */ true);
+ if (!desc) {
+ throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, error);
+ }
+
+ if (!desc->IsRange() && request.params.size() > 1) {
+ throw JSONRPCError(RPC_INVALID_PARAMETER, "Range should not be specified for an un-ranged descriptor");
+ }
+
+ if (desc->IsRange() && request.params.size() == 1) {
+ throw JSONRPCError(RPC_INVALID_PARAMETER, "Range must be specified for a ranged descriptor");
+ }
+
+ UniValue addresses(UniValue::VARR);
+
+ for (int i = range_begin; i <= range_end; ++i) {
+ FlatSigningProvider provider;
+ std::vector<CScript> scripts;
+ if (!desc->Expand(i, key_provider, scripts, provider)) {
+ throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Cannot derive script without private keys");
+ }
+
+ for (const CScript& script : scripts) {
+ CTxDestination dest;
+ if (!ExtractDestination(script, dest)) {
+ throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Descriptor does not have a corresponding address");
+ }
+
+ addresses.push_back(EncodeDestination(dest));
+ }
+ }
+
+ // This should not be possible, but an assert seems overkill:
+ if (addresses.empty()) {
+ throw JSONRPCError(RPC_MISC_ERROR, "Unexpected empty result");
+ }
+
+ return addresses;
+ },
+ };
+}
+
+void RegisterOutputScriptRPCCommands(CRPCTable& t)
+{
+ static const CRPCCommand commands[]{
+ {"util", &validateaddress},
+ {"util", &createmultisig},
+ {"util", &deriveaddresses},
+ {"util", &getdescriptorinfo},
+ };
+ for (const auto& c : commands) {
+ t.appendCommand(c.name, &c);
+ }
+}
diff --git a/src/rpc/rawtransaction.cpp b/src/rpc/rawtransaction.cpp
index f227fde0f7..ce8f16540f 100644
--- a/src/rpc/rawtransaction.cpp
+++ b/src/rpc/rawtransaction.cpp
@@ -11,7 +11,6 @@
#include <core_io.h>
#include <index/txindex.h>
#include <key_io.h>
-#include <merkleblock.h>
#include <node/blockstorage.h>
#include <node/coin.h>
#include <node/context.h>
@@ -34,9 +33,10 @@
#include <script/standard.h>
#include <uint256.h>
#include <util/bip32.h>
-#include <util/moneystr.h>
+#include <util/check.h>
#include <util/strencodings.h>
#include <util/string.h>
+#include <util/vector.h>
#include <validation.h>
#include <validationinterface.h>
@@ -47,7 +47,6 @@
using node::AnalyzePSBT;
using node::BroadcastTransaction;
-using node::DEFAULT_MAX_RAW_TX_FEE_RATE;
using node::FindCoins;
using node::GetTransaction;
using node::NodeContext;
@@ -61,13 +60,13 @@ static void TxToJSON(const CTransaction& tx, const uint256 hashBlock, UniValue&
// Blockchain contextual information (confirmations and blocktime) is not
// available to code in bitcoin-common, so we query them here and push the
// data into the returned UniValue.
- TxToUniv(tx, uint256(), entry, true, RPCSerializationFlags());
+ TxToUniv(tx, /*block_hash=*/uint256(), entry, /*include_hex=*/true, RPCSerializationFlags());
if (!hashBlock.IsNull()) {
LOCK(cs_main);
entry.pushKV("blockhash", hashBlock.GetHex());
- CBlockIndex* pindex = active_chainstate.m_blockman.LookupBlockIndex(hashBlock);
+ const CBlockIndex* pindex = active_chainstate.m_blockman.LookupBlockIndex(hashBlock);
if (pindex) {
if (active_chainstate.m_chain.Contains(pindex)) {
entry.pushKV("confirmations", 1 + active_chainstate.m_chain.Height() - pindex->nHeight);
@@ -80,6 +79,54 @@ static void TxToJSON(const CTransaction& tx, const uint256 hashBlock, UniValue&
}
}
+static std::vector<RPCResult> DecodeTxDoc(const std::string& txid_field_doc)
+{
+ return {
+ {RPCResult::Type::STR_HEX, "txid", txid_field_doc},
+ {RPCResult::Type::STR_HEX, "hash", "The transaction hash (differs from txid for witness transactions)"},
+ {RPCResult::Type::NUM, "size", "The serialized transaction size"},
+ {RPCResult::Type::NUM, "vsize", "The virtual transaction size (differs from size for witness transactions)"},
+ {RPCResult::Type::NUM, "weight", "The transaction's weight (between vsize*4-3 and vsize*4)"},
+ {RPCResult::Type::NUM, "version", "The version"},
+ {RPCResult::Type::NUM_TIME, "locktime", "The lock time"},
+ {RPCResult::Type::ARR, "vin", "",
+ {
+ {RPCResult::Type::OBJ, "", "",
+ {
+ {RPCResult::Type::STR_HEX, "coinbase", /*optional=*/true, "The coinbase value (only if coinbase transaction)"},
+ {RPCResult::Type::STR_HEX, "txid", /*optional=*/true, "The transaction id (if not coinbase transaction)"},
+ {RPCResult::Type::NUM, "vout", /*optional=*/true, "The output number (if not coinbase transaction)"},
+ {RPCResult::Type::OBJ, "scriptSig", /*optional=*/true, "The script (if not coinbase transaction)",
+ {
+ {RPCResult::Type::STR, "asm", "asm"},
+ {RPCResult::Type::STR_HEX, "hex", "hex"},
+ }},
+ {RPCResult::Type::ARR, "txinwitness", /*optional=*/true, "",
+ {
+ {RPCResult::Type::STR_HEX, "hex", "hex-encoded witness data (if any)"},
+ }},
+ {RPCResult::Type::NUM, "sequence", "The script sequence number"},
+ }},
+ }},
+ {RPCResult::Type::ARR, "vout", "",
+ {
+ {RPCResult::Type::OBJ, "", "",
+ {
+ {RPCResult::Type::STR_AMOUNT, "value", "The value in " + CURRENCY_UNIT},
+ {RPCResult::Type::NUM, "n", "index"},
+ {RPCResult::Type::OBJ, "scriptPubKey", "",
+ {
+ {RPCResult::Type::STR, "asm", "the asm"},
+ {RPCResult::Type::STR, "desc", "Inferred descriptor for the output"},
+ {RPCResult::Type::STR_HEX, "hex", "the hex"},
+ {RPCResult::Type::STR, "type", "The type, eg 'pubkeyhash'"},
+ {RPCResult::Type::STR, "address", /*optional=*/true, "The Bitcoin address (only if a well-defined address exists)"},
+ }},
+ }},
+ }},
+ };
+}
+
static std::vector<RPCArg> CreateTxDoc()
{
return {
@@ -121,7 +168,7 @@ static RPCHelpMan getrawtransaction()
{
return RPCHelpMan{
"getrawtransaction",
- "\nReturn the raw transaction data.\n"
+ "Return the raw transaction data.\n"
"\nBy default, this call only returns a transaction if it is in the mempool. If -txindex is enabled\n"
"and no blockhash argument is passed, it will return the transaction if it is in the mempool or any block.\n"
@@ -130,7 +177,7 @@ static RPCHelpMan getrawtransaction()
"\nHint: Use gettransaction for wallet transactions.\n"
"\nIf verbose is 'true', returns an Object with information about 'txid'.\n"
- "If verbose is 'false' or omitted, returns a string that is serialized, hex-encoded data for 'txid'.\n",
+ "If verbose is 'false' or omitted, returns a string that is serialized, hex-encoded data for 'txid'.",
{
{"txid", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "The transaction id"},
{"verbose", RPCArg::Type::BOOL, RPCArg::Default{false}, "If false, return a string, otherwise return a json object"},
@@ -142,54 +189,16 @@ static RPCHelpMan getrawtransaction()
},
RPCResult{"if verbose is set to true",
RPCResult::Type::OBJ, "", "",
+ Cat<std::vector<RPCResult>>(
{
{RPCResult::Type::BOOL, "in_active_chain", /*optional=*/true, "Whether specified block is in the active chain or not (only present with explicit \"blockhash\" argument)"},
- {RPCResult::Type::STR_HEX, "hex", "The serialized, hex-encoded data for 'txid'"},
- {RPCResult::Type::STR_HEX, "txid", "The transaction id (same as provided)"},
- {RPCResult::Type::STR_HEX, "hash", "The transaction hash (differs from txid for witness transactions)"},
- {RPCResult::Type::NUM, "size", "The serialized transaction size"},
- {RPCResult::Type::NUM, "vsize", "The virtual transaction size (differs from size for witness transactions)"},
- {RPCResult::Type::NUM, "weight", "The transaction's weight (between vsize*4-3 and vsize*4)"},
- {RPCResult::Type::NUM, "version", "The version"},
- {RPCResult::Type::NUM_TIME, "locktime", "The lock time"},
- {RPCResult::Type::ARR, "vin", "",
- {
- {RPCResult::Type::OBJ, "", "",
- {
- {RPCResult::Type::STR_HEX, "txid", "The transaction id"},
- {RPCResult::Type::NUM, "vout", "The output number"},
- {RPCResult::Type::OBJ, "scriptSig", "The script",
- {
- {RPCResult::Type::STR, "asm", "asm"},
- {RPCResult::Type::STR_HEX, "hex", "hex"},
- }},
- {RPCResult::Type::NUM, "sequence", "The script sequence number"},
- {RPCResult::Type::ARR, "txinwitness", /*optional=*/true, "",
- {
- {RPCResult::Type::STR_HEX, "hex", "hex-encoded witness data (if any)"},
- }},
- }},
- }},
- {RPCResult::Type::ARR, "vout", "",
- {
- {RPCResult::Type::OBJ, "", "",
- {
- {RPCResult::Type::NUM, "value", "The value in " + CURRENCY_UNIT},
- {RPCResult::Type::NUM, "n", "index"},
- {RPCResult::Type::OBJ, "scriptPubKey", "",
- {
- {RPCResult::Type::STR, "asm", "the asm"},
- {RPCResult::Type::STR, "hex", "the hex"},
- {RPCResult::Type::STR, "type", "The type, eg 'pubkeyhash'"},
- {RPCResult::Type::STR, "address", /*optional=*/true, "The Bitcoin address (only if a well-defined address exists)"},
- }},
- }},
- }},
{RPCResult::Type::STR_HEX, "blockhash", /*optional=*/true, "the block hash"},
{RPCResult::Type::NUM, "confirmations", /*optional=*/true, "The confirmations"},
{RPCResult::Type::NUM_TIME, "blocktime", /*optional=*/true, "The block time expressed in " + UNIX_EPOCH_TIME},
{RPCResult::Type::NUM, "time", /*optional=*/true, "Same as \"blocktime\""},
- }
+ {RPCResult::Type::STR_HEX, "hex", "The serialized, hex-encoded data for 'txid'"},
+ },
+ DecodeTxDoc(/*txid_field_doc=*/"The transaction id (same as provided)")),
},
},
RPCExamples{
@@ -206,7 +215,7 @@ static RPCHelpMan getrawtransaction()
bool in_active_chain = true;
uint256 hash = ParseHashV(request.params[0], "parameter 1");
- CBlockIndex* blockindex = nullptr;
+ const CBlockIndex* blockindex = nullptr;
if (hash == Params().GenesisBlock().hashMerkleRoot) {
// Special exception for the genesis block coinbase transaction
@@ -240,7 +249,8 @@ static RPCHelpMan getrawtransaction()
if (!tx) {
std::string errmsg;
if (blockindex) {
- if (!(blockindex->nStatus & BLOCK_HAVE_DATA)) {
+ const bool block_has_data = WITH_LOCK(::cs_main, return blockindex->nStatus & BLOCK_HAVE_DATA);
+ if (!block_has_data) {
throw JSONRPCError(RPC_MISC_ERROR, "Block not available");
}
errmsg = "No such transaction found in the provided block";
@@ -266,155 +276,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());
- }
- }
-
- 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 can not 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()
{
return RPCHelpMan{"createrawtransaction",
@@ -457,7 +318,7 @@ static RPCHelpMan createrawtransaction()
static RPCHelpMan decoderawtransaction()
{
return RPCHelpMan{"decoderawtransaction",
- "\nReturn a JSON object representing the serialized, hex-encoded transaction.\n",
+ "Return a JSON object representing the serialized, hex-encoded transaction.",
{
{"hexstring", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "The transaction hex string"},
{"iswitness", RPCArg::Type::BOOL, RPCArg::DefaultHint{"depends on heuristic tests"}, "Whether the transaction hex is a serialized witness transaction.\n"
@@ -470,49 +331,7 @@ static RPCHelpMan decoderawtransaction()
},
RPCResult{
RPCResult::Type::OBJ, "", "",
- {
- {RPCResult::Type::STR_HEX, "txid", "The transaction id"},
- {RPCResult::Type::STR_HEX, "hash", "The transaction hash (differs from txid for witness transactions)"},
- {RPCResult::Type::NUM, "size", "The transaction size"},
- {RPCResult::Type::NUM, "vsize", "The virtual transaction size (differs from size for witness transactions)"},
- {RPCResult::Type::NUM, "weight", "The transaction's weight (between vsize*4 - 3 and vsize*4)"},
- {RPCResult::Type::NUM, "version", "The version"},
- {RPCResult::Type::NUM_TIME, "locktime", "The lock time"},
- {RPCResult::Type::ARR, "vin", "",
- {
- {RPCResult::Type::OBJ, "", "",
- {
- {RPCResult::Type::STR_HEX, "coinbase", /*optional=*/true, ""},
- {RPCResult::Type::STR_HEX, "txid", /*optional=*/true, "The transaction id"},
- {RPCResult::Type::NUM, "vout", /*optional=*/true, "The output number"},
- {RPCResult::Type::OBJ, "scriptSig", /*optional=*/true, "The script",
- {
- {RPCResult::Type::STR, "asm", "asm"},
- {RPCResult::Type::STR_HEX, "hex", "hex"},
- }},
- {RPCResult::Type::ARR, "txinwitness", /*optional=*/true, "",
- {
- {RPCResult::Type::STR_HEX, "hex", "hex-encoded witness data (if any)"},
- }},
- {RPCResult::Type::NUM, "sequence", "The script sequence number"},
- }},
- }},
- {RPCResult::Type::ARR, "vout", "",
- {
- {RPCResult::Type::OBJ, "", "",
- {
- {RPCResult::Type::NUM, "value", "The value in " + CURRENCY_UNIT},
- {RPCResult::Type::NUM, "n", "index"},
- {RPCResult::Type::OBJ, "scriptPubKey", "",
- {
- {RPCResult::Type::STR, "asm", "the asm"},
- {RPCResult::Type::STR_HEX, "hex", "the hex"},
- {RPCResult::Type::STR, "type", "The type, eg 'pubkeyhash'"},
- {RPCResult::Type::STR, "address", /*optional=*/true, "The Bitcoin address (only if a well-defined address exists)"},
- }},
- }},
- }},
- }
+ DecodeTxDoc(/*txid_field_doc=*/"The transaction id"),
},
RPCExamples{
HelpExampleCli("decoderawtransaction", "\"hexstring\"")
@@ -532,23 +351,13 @@ static RPCHelpMan decoderawtransaction()
}
UniValue result(UniValue::VOBJ);
- TxToUniv(CTransaction(std::move(mtx)), uint256(), result, false);
+ TxToUniv(CTransaction(std::move(mtx)), /*block_hash=*/uint256(), /*entry=*/result, /*include_hex=*/false);
return result;
},
};
}
-static std::string GetAllOutputTypes()
-{
- std::vector<std::string> ret;
- using U = std::underlying_type<TxoutType>::type;
- for (U i = (U)TxoutType::NONSTANDARD; i <= (U)TxoutType::WITNESS_UNKNOWN; ++i) {
- ret.emplace_back(GetTxnOutputType(static_cast<TxoutType>(i)));
- }
- return Join(ret, ", ");
-}
-
static RPCHelpMan decodescript()
{
return RPCHelpMan{
@@ -561,6 +370,7 @@ static RPCHelpMan decodescript()
RPCResult::Type::OBJ, "", "",
{
{RPCResult::Type::STR, "asm", "Script public key"},
+ {RPCResult::Type::STR, "desc", "Inferred descriptor for the script"},
{RPCResult::Type::STR, "type", "The output type (e.g. " + GetAllOutputTypes() + ")"},
{RPCResult::Type::STR, "address", /*optional=*/true, "The Bitcoin address (only if a well-defined address exists)"},
{RPCResult::Type::STR, "p2sh", /*optional=*/true,
@@ -572,6 +382,7 @@ static RPCHelpMan decodescript()
{RPCResult::Type::STR_HEX, "hex", "Hex string of the script public key"},
{RPCResult::Type::STR, "type", "The type of the script public key (e.g. witness_v0_keyhash or witness_v0_scripthash)"},
{RPCResult::Type::STR, "address", /*optional=*/true, "The Bitcoin address (only if a well-defined address exists)"},
+ {RPCResult::Type::STR, "desc", "Inferred descriptor for the script"},
{RPCResult::Type::STR, "p2sh-segwit", "address of the P2SH script wrapping this witness redeem script"},
}},
},
@@ -592,7 +403,7 @@ static RPCHelpMan decodescript()
} else {
// Empty scripts are valid
}
- ScriptPubKeyToUniv(script, r, /* include_hex */ false);
+ ScriptToUniv(script, /*out=*/r, /*include_hex=*/false, /*include_address=*/true);
std::vector<std::vector<unsigned char>> solutions_data;
const TxoutType which_type{Solver(script, solutions_data)};
@@ -656,7 +467,7 @@ static RPCHelpMan decodescript()
// Should not be wrapped
return false;
} // no default case, so the compiler can warn about missing cases
- CHECK_NONFATAL(false);
+ NONFATAL_UNREACHABLE();
}()};
if (can_wrap_P2WSH) {
UniValue sr(UniValue::VOBJ);
@@ -669,7 +480,7 @@ static RPCHelpMan decodescript()
// Scripts that are not fit for P2WPKH are encoded as P2WSH.
segwitScr = GetScriptForDestination(WitnessV0ScriptHash(script));
}
- ScriptPubKeyToUniv(segwitScr, sr, /* include_hex */ true);
+ ScriptToUniv(segwitScr, /*out=*/sr, /*include_hex=*/true, /*include_address=*/true);
sr.pushKV("p2sh-segwit", EncodeDestination(ScriptHash(segwitScr)));
r.pushKV("segwit", sr);
}
@@ -869,210 +680,6 @@ static RPCHelpMan signrawtransactionwithkey()
};
}
-static RPCHelpMan sendrawtransaction()
-{
- return RPCHelpMan{"sendrawtransaction",
- "\nSubmit a raw transaction (serialized, hex-encoded) to local node and network.\n"
- "\nThe transaction will be sent unconditionally to all peers, so using sendrawtransaction\n"
- "for manual rebroadcast may degrade privacy by leaking the transaction's origin, as\n"
- "nodes will normally not rebroadcast non-wallet transactions already in their mempool.\n"
- "\nA specific exception, RPC_TRANSACTION_ALREADY_IN_CHAIN, may throw if the transaction cannot be added to the mempool.\n"
- "\nRelated RPCs: createrawtransaction, signrawtransactionwithkey\n",
- {
- {"hexstring", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "The hex string of the raw transaction"},
- {"maxfeerate", RPCArg::Type::AMOUNT, RPCArg::Default{FormatMoney(DEFAULT_MAX_RAW_TX_FEE_RATE.GetFeePerK())},
- "Reject transactions whose fee rate is higher than the specified value, expressed in " + CURRENCY_UNIT +
- "/kvB.\nSet to 0 to accept any fee rate.\n"},
- },
- RPCResult{
- RPCResult::Type::STR_HEX, "", "The transaction hash in hex"
- },
- RPCExamples{
- "\nCreate a transaction\n"
- + HelpExampleCli("createrawtransaction", "\"[{\\\"txid\\\" : \\\"mytxid\\\",\\\"vout\\\":0}]\" \"{\\\"myaddress\\\":0.01}\"") +
- "Sign the transaction, and get back the hex\n"
- + HelpExampleCli("signrawtransactionwithwallet", "\"myhex\"") +
- "\nSend the transaction (signed hex)\n"
- + HelpExampleCli("sendrawtransaction", "\"signedhex\"") +
- "\nAs a JSON-RPC call\n"
- + HelpExampleRpc("sendrawtransaction", "\"signedhex\"")
- },
- [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
-{
- RPCTypeCheck(request.params, {
- UniValue::VSTR,
- UniValueType(), // VNUM or VSTR, checked inside AmountFromValue()
- });
-
- CMutableTransaction mtx;
- if (!DecodeHexTx(mtx, request.params[0].get_str())) {
- throw JSONRPCError(RPC_DESERIALIZATION_ERROR, "TX decode failed. Make sure the tx has at least one input.");
- }
- CTransactionRef tx(MakeTransactionRef(std::move(mtx)));
-
- const CFeeRate max_raw_tx_fee_rate = request.params[1].isNull() ?
- DEFAULT_MAX_RAW_TX_FEE_RATE :
- CFeeRate(AmountFromValue(request.params[1]));
-
- int64_t virtual_size = GetVirtualTransactionSize(*tx);
- CAmount max_raw_tx_fee = max_raw_tx_fee_rate.GetFee(virtual_size);
-
- std::string err_string;
- AssertLockNotHeld(cs_main);
- NodeContext& node = EnsureAnyNodeContext(request.context);
- const TransactionError err = BroadcastTransaction(node, tx, err_string, max_raw_tx_fee, /*relay*/ true, /*wait_callback*/ true);
- if (TransactionError::OK != err) {
- throw JSONRPCTransactionError(err, err_string);
- }
-
- return tx->GetHash().GetHex();
-},
- };
-}
-
-static RPCHelpMan testmempoolaccept()
-{
- return RPCHelpMan{"testmempoolaccept",
- "\nReturns result of mempool acceptance tests indicating if raw transaction(s) (serialized, hex-encoded) would be accepted by mempool.\n"
- "\nIf multiple transactions are passed in, parents must come before children and package policies apply: the transactions cannot conflict with any mempool transactions or each other.\n"
- "\nIf one transaction fails, other transactions may not be fully validated (the 'allowed' key will be blank).\n"
- "\nThe maximum number of transactions allowed is " + ToString(MAX_PACKAGE_COUNT) + ".\n"
- "\nThis checks if transactions violate the consensus or policy rules.\n"
- "\nSee sendrawtransaction call.\n",
- {
- {"rawtxs", RPCArg::Type::ARR, RPCArg::Optional::NO, "An array of hex strings of raw transactions.",
- {
- {"rawtx", RPCArg::Type::STR_HEX, RPCArg::Optional::OMITTED, ""},
- },
- },
- {"maxfeerate", RPCArg::Type::AMOUNT, RPCArg::Default{FormatMoney(DEFAULT_MAX_RAW_TX_FEE_RATE.GetFeePerK())},
- "Reject transactions whose fee rate is higher than the specified value, expressed in " + CURRENCY_UNIT + "/kvB\n"},
- },
- RPCResult{
- RPCResult::Type::ARR, "", "The result of the mempool acceptance test for each raw transaction in the input array.\n"
- "Returns results for each transaction in the same order they were passed in.\n"
- "Transactions that cannot be fully validated due to failures in other transactions will not contain an 'allowed' result.\n",
- {
- {RPCResult::Type::OBJ, "", "",
- {
- {RPCResult::Type::STR_HEX, "txid", "The transaction hash in hex"},
- {RPCResult::Type::STR_HEX, "wtxid", "The transaction witness hash in hex"},
- {RPCResult::Type::STR, "package-error", /*optional=*/true, "Package validation error, if any (only possible if rawtxs had more than 1 transaction)."},
- {RPCResult::Type::BOOL, "allowed", /*optional=*/true, "Whether this tx would be accepted to the mempool and pass client-specified maxfeerate. "
- "If not present, the tx was not fully validated due to a failure in another tx in the list."},
- {RPCResult::Type::NUM, "vsize", /*optional=*/true, "Virtual transaction size as defined in BIP 141. This is different from actual serialized size for witness transactions as witness data is discounted (only present when 'allowed' is true)"},
- {RPCResult::Type::OBJ, "fees", /*optional=*/true, "Transaction fees (only present if 'allowed' is true)",
- {
- {RPCResult::Type::STR_AMOUNT, "base", "transaction fee in " + CURRENCY_UNIT},
- }},
- {RPCResult::Type::STR, "reject-reason", /*optional=*/true, "Rejection string (only present when 'allowed' is false)"},
- }},
- }
- },
- RPCExamples{
- "\nCreate a transaction\n"
- + HelpExampleCli("createrawtransaction", "\"[{\\\"txid\\\" : \\\"mytxid\\\",\\\"vout\\\":0}]\" \"{\\\"myaddress\\\":0.01}\"") +
- "Sign the transaction, and get back the hex\n"
- + HelpExampleCli("signrawtransactionwithwallet", "\"myhex\"") +
- "\nTest acceptance of the transaction (signed hex)\n"
- + HelpExampleCli("testmempoolaccept", R"('["signedhex"]')") +
- "\nAs a JSON-RPC call\n"
- + HelpExampleRpc("testmempoolaccept", "[\"signedhex\"]")
- },
- [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
-{
- RPCTypeCheck(request.params, {
- UniValue::VARR,
- UniValueType(), // VNUM or VSTR, checked inside AmountFromValue()
- });
- const UniValue raw_transactions = request.params[0].get_array();
- if (raw_transactions.size() < 1 || raw_transactions.size() > MAX_PACKAGE_COUNT) {
- throw JSONRPCError(RPC_INVALID_PARAMETER,
- "Array must contain between 1 and " + ToString(MAX_PACKAGE_COUNT) + " transactions.");
- }
-
- const CFeeRate max_raw_tx_fee_rate = request.params[1].isNull() ?
- DEFAULT_MAX_RAW_TX_FEE_RATE :
- CFeeRate(AmountFromValue(request.params[1]));
-
- std::vector<CTransactionRef> txns;
- txns.reserve(raw_transactions.size());
- for (const auto& rawtx : raw_transactions.getValues()) {
- CMutableTransaction mtx;
- if (!DecodeHexTx(mtx, rawtx.get_str())) {
- throw JSONRPCError(RPC_DESERIALIZATION_ERROR,
- "TX decode failed: " + rawtx.get_str() + " Make sure the tx has at least one input.");
- }
- txns.emplace_back(MakeTransactionRef(std::move(mtx)));
- }
-
- NodeContext& node = EnsureAnyNodeContext(request.context);
- CTxMemPool& mempool = EnsureMemPool(node);
- ChainstateManager& chainman = EnsureChainman(node);
- CChainState& chainstate = chainman.ActiveChainstate();
- const PackageMempoolAcceptResult package_result = [&] {
- LOCK(::cs_main);
- if (txns.size() > 1) return ProcessNewPackage(chainstate, mempool, txns, /* test_accept */ true);
- return PackageMempoolAcceptResult(txns[0]->GetWitnessHash(),
- chainman.ProcessTransaction(txns[0], /*test_accept=*/ true));
- }();
-
- UniValue rpc_result(UniValue::VARR);
- // We will check transaction fees while we iterate through txns in order. If any transaction fee
- // exceeds maxfeerate, we will leave the rest of the validation results blank, because it
- // doesn't make sense to return a validation result for a transaction if its ancestor(s) would
- // not be submitted.
- bool exit_early{false};
- for (const auto& tx : txns) {
- UniValue result_inner(UniValue::VOBJ);
- result_inner.pushKV("txid", tx->GetHash().GetHex());
- result_inner.pushKV("wtxid", tx->GetWitnessHash().GetHex());
- if (package_result.m_state.GetResult() == PackageValidationResult::PCKG_POLICY) {
- result_inner.pushKV("package-error", package_result.m_state.GetRejectReason());
- }
- auto it = package_result.m_tx_results.find(tx->GetWitnessHash());
- if (exit_early || it == package_result.m_tx_results.end()) {
- // Validation unfinished. Just return the txid and wtxid.
- rpc_result.push_back(result_inner);
- continue;
- }
- const auto& tx_result = it->second;
- // Package testmempoolaccept doesn't allow transactions to already be in the mempool.
- CHECK_NONFATAL(tx_result.m_result_type != MempoolAcceptResult::ResultType::MEMPOOL_ENTRY);
- if (tx_result.m_result_type == MempoolAcceptResult::ResultType::VALID) {
- const CAmount fee = tx_result.m_base_fees.value();
- // Check that fee does not exceed maximum fee
- const int64_t virtual_size = tx_result.m_vsize.value();
- const CAmount max_raw_tx_fee = max_raw_tx_fee_rate.GetFee(virtual_size);
- if (max_raw_tx_fee && fee > max_raw_tx_fee) {
- result_inner.pushKV("allowed", false);
- result_inner.pushKV("reject-reason", "max-fee-exceeded");
- exit_early = true;
- } else {
- // Only return the fee and vsize if the transaction would pass ATMP.
- // These can be used to calculate the feerate.
- result_inner.pushKV("allowed", true);
- result_inner.pushKV("vsize", virtual_size);
- UniValue fees(UniValue::VOBJ);
- fees.pushKV("base", ValueFromAmount(fee));
- result_inner.pushKV("fees", fees);
- }
- } else {
- result_inner.pushKV("allowed", false);
- const TxValidationState state = tx_result.m_state;
- if (state.GetResult() == TxValidationResult::TX_MISSING_INPUTS) {
- result_inner.pushKV("reject-reason", "missing-inputs");
- } else {
- result_inner.pushKV("reject-reason", state.GetRejectReason());
- }
- }
- rpc_result.push_back(result_inner);
- }
- return rpc_result;
-},
- };
-}
-
static RPCHelpMan decodepsbt()
{
return RPCHelpMan{
@@ -1126,6 +733,7 @@ static RPCHelpMan decodepsbt()
{RPCResult::Type::OBJ, "scriptPubKey", "",
{
{RPCResult::Type::STR, "asm", "The asm"},
+ {RPCResult::Type::STR, "desc", "Inferred descriptor for the output"},
{RPCResult::Type::STR_HEX, "hex", "The hex"},
{RPCResult::Type::STR, "type", "The type, eg 'pubkeyhash'"},
{RPCResult::Type::STR, "address", /*optional=*/true, "The Bitcoin address (only if a well-defined address exists)"},
@@ -1260,7 +868,7 @@ static RPCHelpMan decodepsbt()
// Add the decoded tx
UniValue tx_univ(UniValue::VOBJ);
- TxToUniv(CTransaction(*psbtx.tx), uint256(), tx_univ, false);
+ TxToUniv(CTransaction(*psbtx.tx), /*block_hash=*/uint256(), /*entry=*/tx_univ, /*include_hex=*/false);
result.pushKV("tx", tx_univ);
// Add the global xpubs
@@ -1316,7 +924,7 @@ static RPCHelpMan decodepsbt()
txout = input.witness_utxo;
UniValue o(UniValue::VOBJ);
- ScriptPubKeyToUniv(txout.scriptPubKey, o, /* include_hex */ true);
+ ScriptToUniv(txout.scriptPubKey, /*out=*/o, /*include_hex=*/true, /*include_address=*/true);
UniValue out(UniValue::VOBJ);
out.pushKV("amount", ValueFromAmount(txout.nValue));
@@ -1330,7 +938,7 @@ static RPCHelpMan decodepsbt()
txout = input.non_witness_utxo->vout[psbtx.tx->vin[i].prevout.n];
UniValue non_wit(UniValue::VOBJ);
- TxToUniv(*input.non_witness_utxo, uint256(), non_wit, false);
+ TxToUniv(*input.non_witness_utxo, /*block_hash=*/uint256(), /*entry=*/non_wit, /*include_hex=*/false);
in.pushKV("non_witness_utxo", non_wit);
have_a_utxo = true;
@@ -1363,12 +971,12 @@ static RPCHelpMan decodepsbt()
// Redeem script and witness script
if (!input.redeem_script.empty()) {
UniValue r(UniValue::VOBJ);
- ScriptToUniv(input.redeem_script, r);
+ ScriptToUniv(input.redeem_script, /*out=*/r);
in.pushKV("redeem_script", r);
}
if (!input.witness_script.empty()) {
UniValue r(UniValue::VOBJ);
- ScriptToUniv(input.witness_script, r);
+ ScriptToUniv(input.witness_script, /*out=*/r);
in.pushKV("witness_script", r);
}
@@ -1473,12 +1081,12 @@ static RPCHelpMan decodepsbt()
// Redeem script and witness script
if (!output.redeem_script.empty()) {
UniValue r(UniValue::VOBJ);
- ScriptToUniv(output.redeem_script, r);
+ ScriptToUniv(output.redeem_script, /*out=*/r);
out.pushKV("redeem_script", r);
}
if (!output.witness_script.empty()) {
UniValue r(UniValue::VOBJ);
- ScriptToUniv(output.witness_script, r);
+ ScriptToUniv(output.witness_script, /*out=*/r);
out.pushKV("witness_script", r);
}
@@ -2072,33 +1680,24 @@ static RPCHelpMan analyzepsbt()
};
}
-void RegisterRawTransactionRPCCommands(CRPCTable &t)
+void RegisterRawTransactionRPCCommands(CRPCTable& t)
{
-// clang-format off
-static const CRPCCommand commands[] =
-{ // category actor (function)
- // --------------------- -----------------------
- { "rawtransactions", &getrawtransaction, },
- { "rawtransactions", &createrawtransaction, },
- { "rawtransactions", &decoderawtransaction, },
- { "rawtransactions", &decodescript, },
- { "rawtransactions", &sendrawtransaction, },
- { "rawtransactions", &combinerawtransaction, },
- { "rawtransactions", &signrawtransactionwithkey, },
- { "rawtransactions", &testmempoolaccept, },
- { "rawtransactions", &decodepsbt, },
- { "rawtransactions", &combinepsbt, },
- { "rawtransactions", &finalizepsbt, },
- { "rawtransactions", &createpsbt, },
- { "rawtransactions", &converttopsbt, },
- { "rawtransactions", &utxoupdatepsbt, },
- { "rawtransactions", &joinpsbts, },
- { "rawtransactions", &analyzepsbt, },
-
- { "blockchain", &gettxoutproof, },
- { "blockchain", &verifytxoutproof, },
-};
-// clang-format on
+ static const CRPCCommand commands[]{
+ {"rawtransactions", &getrawtransaction},
+ {"rawtransactions", &createrawtransaction},
+ {"rawtransactions", &decoderawtransaction},
+ {"rawtransactions", &decodescript},
+ {"rawtransactions", &combinerawtransaction},
+ {"rawtransactions", &signrawtransactionwithkey},
+ {"rawtransactions", &decodepsbt},
+ {"rawtransactions", &combinepsbt},
+ {"rawtransactions", &finalizepsbt},
+ {"rawtransactions", &createpsbt},
+ {"rawtransactions", &converttopsbt},
+ {"rawtransactions", &utxoupdatepsbt},
+ {"rawtransactions", &joinpsbts},
+ {"rawtransactions", &analyzepsbt},
+ };
for (const auto& c : commands) {
t.appendCommand(c.name, &c);
}
diff --git a/src/rpc/rawtransaction_util.cpp b/src/rpc/rawtransaction_util.cpp
index 3459897fe5..e23fe34480 100644
--- a/src/rpc/rawtransaction_util.cpp
+++ b/src/rpc/rawtransaction_util.cpp
@@ -63,7 +63,7 @@ CMutableTransaction ConstructTransaction(const UniValue& inputs_in, const UniVal
if (rbf) {
nSequence = MAX_BIP125_RBF_SEQUENCE; /* CTxIn::SEQUENCE_FINAL - 2 */
} else if (rawTx.nLockTime) {
- nSequence = CTxIn::SEQUENCE_FINAL - 1;
+ nSequence = CTxIn::MAX_SEQUENCE_NONFINAL; /* CTxIn::SEQUENCE_FINAL - 1 */
} else {
nSequence = CTxIn::SEQUENCE_FINAL;
}
diff --git a/src/rpc/register.h b/src/rpc/register.h
index c5055cc9d7..301f410da3 100644
--- a/src/rpc/register.h
+++ b/src/rpc/register.h
@@ -9,29 +9,33 @@
* headers for everything under src/rpc/ */
class CRPCTable;
-/** Register block chain RPC commands */
void RegisterBlockchainRPCCommands(CRPCTable &tableRPC);
-/** Register P2P networking RPC commands */
-void RegisterNetRPCCommands(CRPCTable &tableRPC);
-/** Register miscellaneous RPC commands */
-void RegisterMiscRPCCommands(CRPCTable &tableRPC);
-/** Register mining RPC commands */
+void RegisterFeeRPCCommands(CRPCTable&);
+void RegisterMempoolRPCCommands(CRPCTable&);
void RegisterMiningRPCCommands(CRPCTable &tableRPC);
-/** Register raw transaction RPC commands */
+void RegisterNodeRPCCommands(CRPCTable&);
+void RegisterNetRPCCommands(CRPCTable&);
+void RegisterOutputScriptRPCCommands(CRPCTable&);
void RegisterRawTransactionRPCCommands(CRPCTable &tableRPC);
-/** Register raw transaction RPC commands */
+void RegisterSignMessageRPCCommands(CRPCTable&);
void RegisterSignerRPCCommands(CRPCTable &tableRPC);
+void RegisterTxoutProofRPCCommands(CRPCTable&);
static inline void RegisterAllCoreRPCCommands(CRPCTable &t)
{
RegisterBlockchainRPCCommands(t);
- RegisterNetRPCCommands(t);
- RegisterMiscRPCCommands(t);
+ RegisterFeeRPCCommands(t);
+ RegisterMempoolRPCCommands(t);
RegisterMiningRPCCommands(t);
+ RegisterNodeRPCCommands(t);
+ RegisterNetRPCCommands(t);
+ RegisterOutputScriptRPCCommands(t);
RegisterRawTransactionRPCCommands(t);
+ RegisterSignMessageRPCCommands(t);
#ifdef ENABLE_EXTERNAL_SIGNER
RegisterSignerRPCCommands(t);
#endif // ENABLE_EXTERNAL_SIGNER
+ RegisterTxoutProofRPCCommands(t);
}
#endif // BITCOIN_RPC_REGISTER_H
diff --git a/src/rpc/request.cpp b/src/rpc/request.cpp
index fbb4e5ddd0..d0e068de19 100644
--- a/src/rpc/request.cpp
+++ b/src/rpc/request.cpp
@@ -12,6 +12,11 @@
#include <util/system.h>
#include <util/strencodings.h>
+#include <fstream>
+#include <stdexcept>
+#include <string>
+#include <vector>
+
/**
* JSON-RPC protocol. Bitcoin speaks version 1.0 for maximum compatibility,
* but uses JSON-RPC 1.1/2.0 standards for parts of the 1.0 standard that were
@@ -77,13 +82,13 @@ bool GenerateAuthCookie(std::string *cookie_out)
{
const size_t COOKIE_SIZE = 32;
unsigned char rand_pwd[COOKIE_SIZE];
- GetRandBytes(rand_pwd, COOKIE_SIZE);
+ GetRandBytes(rand_pwd);
std::string cookie = COOKIEAUTH_USER + ":" + HexStr(rand_pwd);
/** the umask determines what permissions are used to create this file -
* these are set to 077 in init.cpp unless overridden with -sysperms.
*/
- fsbridge::ofstream file;
+ std::ofstream file;
fs::path filepath_tmp = GetAuthCookieFile(true);
file.open(filepath_tmp);
if (!file.is_open()) {
@@ -107,7 +112,7 @@ bool GenerateAuthCookie(std::string *cookie_out)
bool GetAuthCookie(std::string *cookie_out)
{
- fsbridge::ifstream file;
+ std::ifstream file;
std::string cookie;
fs::path filepath = GetAuthCookieFile();
file.open(filepath);
diff --git a/src/rpc/server.cpp b/src/rpc/server.cpp
index c70236cc1c..3817a4a45c 100644
--- a/src/rpc/server.cpp
+++ b/src/rpc/server.cpp
@@ -9,10 +9,9 @@
#include <shutdown.h>
#include <sync.h>
#include <util/strencodings.h>
+#include <util/string.h>
#include <util/system.h>
-#include <boost/algorithm/string/classification.hpp>
-#include <boost/algorithm/string/split.hpp>
#include <boost/signals2/signal.hpp>
#include <cassert>
@@ -167,7 +166,7 @@ static RPCHelpMan stop()
// to the client (intended for testing)
"\nRequest a graceful shutdown of " PACKAGE_NAME ".",
{
- {"wait", RPCArg::Type::NUM, RPCArg::Optional::OMITTED_NAMED_ARG, "how long to wait in ms", "", {}, /* hidden */ true},
+ {"wait", RPCArg::Type::NUM, RPCArg::Optional::OMITTED_NAMED_ARG, "how long to wait in ms", "", {}, /*hidden=*/true},
},
RPCResult{RPCResult::Type::STR, "", "A string with the content '" + RESULT + "'"},
RPCExamples{""},
@@ -248,17 +247,13 @@ static RPCHelpMan getrpcinfo()
};
}
-// clang-format off
-static const CRPCCommand vRPCCommands[] =
-{ // category actor (function)
- // --------------------- -----------------------
+static const CRPCCommand vRPCCommands[]{
/* Overall control/query calls */
- { "control", &getrpcinfo, },
- { "control", &help, },
- { "control", &stop, },
- { "control", &uptime, },
+ {"control", &getrpcinfo},
+ {"control", &help},
+ {"control", &stop},
+ {"control", &uptime},
};
-// clang-format on
CRPCTable::CRPCTable()
{
@@ -407,8 +402,7 @@ static inline JSONRPCRequest transformNamedArguments(const JSONRPCRequest& in, c
// Process expected parameters.
int hole = 0;
for (const std::string &argNamePattern: argNames) {
- std::vector<std::string> vargNames;
- boost::algorithm::split(vargNames, argNamePattern, boost::algorithm::is_any_of("|"));
+ std::vector<std::string> vargNames = SplitString(argNamePattern, '|');
auto fr = argsIn.end();
for (const std::string & argName : vargNames) {
fr = argsIn.find(argName);
diff --git a/src/rpc/signmessage.cpp b/src/rpc/signmessage.cpp
new file mode 100644
index 0000000000..8c752ba1fd
--- /dev/null
+++ b/src/rpc/signmessage.cpp
@@ -0,0 +1,113 @@
+// 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 <key.h>
+#include <key_io.h>
+#include <rpc/protocol.h>
+#include <rpc/request.h>
+#include <rpc/server.h>
+#include <rpc/util.h>
+#include <univalue.h>
+#include <util/message.h>
+
+#include <string>
+
+static RPCHelpMan verifymessage()
+{
+ return RPCHelpMan{"verifymessage",
+ "Verify a signed message.",
+ {
+ {"address", RPCArg::Type::STR, RPCArg::Optional::NO, "The bitcoin address to use for the signature."},
+ {"signature", RPCArg::Type::STR, RPCArg::Optional::NO, "The signature provided by the signer in base 64 encoding (see signmessage)."},
+ {"message", RPCArg::Type::STR, RPCArg::Optional::NO, "The message that was signed."},
+ },
+ RPCResult{
+ RPCResult::Type::BOOL, "", "If the signature is verified or not."
+ },
+ RPCExamples{
+ "\nUnlock the wallet for 30 seconds\n"
+ + HelpExampleCli("walletpassphrase", "\"mypassphrase\" 30") +
+ "\nCreate the signature\n"
+ + HelpExampleCli("signmessage", "\"1D1ZrZNe3JUo7ZycKEYQQiQAWd9y54F4XX\" \"my message\"") +
+ "\nVerify the signature\n"
+ + HelpExampleCli("verifymessage", "\"1D1ZrZNe3JUo7ZycKEYQQiQAWd9y54F4XX\" \"signature\" \"my message\"") +
+ "\nAs a JSON-RPC call\n"
+ + HelpExampleRpc("verifymessage", "\"1D1ZrZNe3JUo7ZycKEYQQiQAWd9y54F4XX\", \"signature\", \"my message\"")
+ },
+ [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
+ {
+ std::string strAddress = request.params[0].get_str();
+ std::string strSign = request.params[1].get_str();
+ std::string strMessage = request.params[2].get_str();
+
+ switch (MessageVerify(strAddress, strSign, strMessage)) {
+ case MessageVerificationResult::ERR_INVALID_ADDRESS:
+ throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid address");
+ case MessageVerificationResult::ERR_ADDRESS_NO_KEY:
+ throw JSONRPCError(RPC_TYPE_ERROR, "Address does not refer to key");
+ case MessageVerificationResult::ERR_MALFORMED_SIGNATURE:
+ throw JSONRPCError(RPC_TYPE_ERROR, "Malformed base64 encoding");
+ case MessageVerificationResult::ERR_PUBKEY_NOT_RECOVERED:
+ case MessageVerificationResult::ERR_NOT_SIGNED:
+ return false;
+ case MessageVerificationResult::OK:
+ return true;
+ }
+
+ return false;
+ },
+ };
+}
+
+static RPCHelpMan signmessagewithprivkey()
+{
+ return RPCHelpMan{"signmessagewithprivkey",
+ "\nSign a message with the private key of an address\n",
+ {
+ {"privkey", RPCArg::Type::STR, RPCArg::Optional::NO, "The private key to sign the message with."},
+ {"message", RPCArg::Type::STR, RPCArg::Optional::NO, "The message to create a signature of."},
+ },
+ RPCResult{
+ RPCResult::Type::STR, "signature", "The signature of the message encoded in base 64"
+ },
+ RPCExamples{
+ "\nCreate the signature\n"
+ + HelpExampleCli("signmessagewithprivkey", "\"privkey\" \"my message\"") +
+ "\nVerify the signature\n"
+ + HelpExampleCli("verifymessage", "\"1D1ZrZNe3JUo7ZycKEYQQiQAWd9y54F4XX\" \"signature\" \"my message\"") +
+ "\nAs a JSON-RPC call\n"
+ + HelpExampleRpc("signmessagewithprivkey", "\"privkey\", \"my message\"")
+ },
+ [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
+ {
+ std::string strPrivkey = request.params[0].get_str();
+ std::string strMessage = request.params[1].get_str();
+
+ CKey key = DecodeSecret(strPrivkey);
+ if (!key.IsValid()) {
+ throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid private key");
+ }
+
+ std::string signature;
+
+ if (!MessageSign(key, strMessage, signature)) {
+ throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Sign failed");
+ }
+
+ return signature;
+ },
+ };
+}
+
+void RegisterSignMessageRPCCommands(CRPCTable& t)
+{
+ static const CRPCCommand commands[]{
+ {"util", &verifymessage},
+ {"util", &signmessagewithprivkey},
+ };
+ for (const auto& c : commands) {
+ t.appendCommand(c.name, &c);
+ }
+}
diff --git a/src/rpc/txoutproof.cpp b/src/rpc/txoutproof.cpp
new file mode 100644
index 0000000000..d16820baeb
--- /dev/null
+++ b/src/rpc/txoutproof.cpp
@@ -0,0 +1,181 @@
+// 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[]{
+ {"blockchain", &gettxoutproof},
+ {"blockchain", &verifytxoutproof},
+ };
+ for (const auto& c : commands) {
+ t.appendCommand(c.name, &c);
+ }
+}
diff --git a/src/rpc/util.cpp b/src/rpc/util.cpp
index 57e3da0351..ef3e58494e 100644
--- a/src/rpc/util.cpp
+++ b/src/rpc/util.cpp
@@ -9,18 +9,26 @@
#include <script/descriptor.h>
#include <script/signingprovider.h>
#include <tinyformat.h>
+#include <util/check.h>
#include <util/strencodings.h>
#include <util/string.h>
#include <util/translation.h>
#include <tuple>
-#include <boost/algorithm/string/classification.hpp>
-#include <boost/algorithm/string/split.hpp>
-
const std::string UNIX_EPOCH_TIME = "UNIX epoch time";
const std::string EXAMPLE_ADDRESS[2] = {"bc1q09vm5lfy0j5reeulh4x5752q25uqqvz34hufdl", "bc1q02ad21edsxd23d32dfgqqsz4vv4nmtfzuklhy3"};
+std::string GetAllOutputTypes()
+{
+ std::vector<std::string> ret;
+ using U = std::underlying_type<TxoutType>::type;
+ for (U i = (U)TxoutType::NONSTANDARD; i <= (U)TxoutType::WITNESS_UNKNOWN; ++i) {
+ ret.emplace_back(GetTxnOutputType(static_cast<TxoutType>(i)));
+ }
+ return Join(ret, ", ");
+}
+
void RPCTypeCheck(const UniValue& params,
const std::list<UniValueType>& typesExpected,
bool fAllowNull)
@@ -411,7 +419,7 @@ struct Sections {
if (arg.m_type_str.size() != 0 && push_name) {
left += "\"" + arg.GetName() + "\": " + arg.m_type_str.at(0);
} else {
- left += push_name ? arg.ToStringObj(/* oneline */ false) : arg.ToString(/* oneline */ false);
+ left += push_name ? arg.ToStringObj(/*oneline=*/false) : arg.ToString(/*oneline=*/false);
}
left += ",";
PushSection({left, arg.ToDescriptionString()});
@@ -503,8 +511,7 @@ RPCHelpMan::RPCHelpMan(std::string name, std::string description, std::vector<RP
{
std::set<std::string> named_args;
for (const auto& arg : m_args) {
- std::vector<std::string> names;
- boost::split(names, arg.m_names, boost::is_any_of("|"));
+ std::vector<std::string> names = SplitString(arg.m_names, '|');
// Should have unique named arguments
for (const std::string& name : names) {
CHECK_NONFATAL(named_args.insert(name).second);
@@ -532,7 +539,7 @@ RPCHelpMan::RPCHelpMan(std::string name, std::string description, std::vector<RP
// Null values are accepted in all arguments
break;
default:
- CHECK_NONFATAL(false);
+ NONFATAL_UNREACHABLE();
break;
}
}
@@ -617,7 +624,7 @@ std::string RPCHelpMan::ToString() const
if (was_optional) ret += ") ";
was_optional = false;
}
- ret += arg.ToString(/* oneline */ true);
+ ret += arg.ToString(/*oneline=*/true);
}
if (was_optional) ret += " )";
@@ -655,8 +662,7 @@ UniValue RPCHelpMan::GetArgMap() const
UniValue arr{UniValue::VARR};
for (int i{0}; i < int(m_args.size()); ++i) {
const auto& arg = m_args.at(i);
- std::vector<std::string> arg_names;
- boost::split(arg_names, arg.m_names, boost::is_any_of("|"));
+ std::vector<std::string> arg_names = SplitString(arg.m_names, '|');
for (const auto& arg_name : arg_names) {
UniValue map{UniValue::VARR};
map.push_back(m_name);
@@ -764,7 +770,7 @@ void RPCResult::ToSections(Sections& sections, const OuterType outer_type, const
// Elements in a JSON structure (dictionary or array) are separated by a comma
const std::string maybe_separator{outer_type != OuterType::NONE ? "," : ""};
- // The key name if recursed into an dictionary
+ // The key name if recursed into a dictionary
const std::string maybe_key{
outer_type == OuterType::OBJ ?
"\"" + this->m_key_name + "\" : " :
@@ -783,7 +789,7 @@ void RPCResult::ToSections(Sections& sections, const OuterType outer_type, const
return;
}
case Type::ANY: {
- CHECK_NONFATAL(false); // Only for testing
+ NONFATAL_UNREACHABLE(); // Only for testing
}
case Type::NONE: {
sections.PushSection({indent + "null" + maybe_separator, Description("json null")});
@@ -831,11 +837,14 @@ void RPCResult::ToSections(Sections& sections, const OuterType outer_type, const
}
case Type::OBJ_DYN:
case Type::OBJ: {
+ if (m_inner.empty()) {
+ sections.PushSection({indent + maybe_key + "{}", Description("empty JSON object")});
+ return;
+ }
sections.PushSection({indent + maybe_key + "{", Description("json object")});
for (const auto& i : m_inner) {
i.ToSections(sections, OuterType::OBJ, current_indent + 2);
}
- CHECK_NONFATAL(!m_inner.empty());
if (m_type == Type::OBJ_DYN && m_inner.back().m_type != Type::ELISION) {
// If the dictionary keys are dynamic, use three dots for continuation
sections.PushSection({indent_next + "...", ""});
@@ -847,15 +856,16 @@ void RPCResult::ToSections(Sections& sections, const OuterType outer_type, const
return;
}
} // no default case, so the compiler can warn about missing cases
- CHECK_NONFATAL(false);
+ NONFATAL_UNREACHABLE();
}
bool RPCResult::MatchesType(const UniValue& result) const
{
- switch (m_type) {
- case Type::ELISION: {
- return false;
+ if (m_skip_type_check) {
+ return true;
}
+ switch (m_type) {
+ case Type::ELISION:
case Type::ANY: {
return true;
}
@@ -876,14 +886,66 @@ bool RPCResult::MatchesType(const UniValue& result) const
}
case Type::ARR_FIXED:
case Type::ARR: {
- return UniValue::VARR == result.getType();
+ if (UniValue::VARR != result.getType()) return false;
+ for (size_t i{0}; i < result.get_array().size(); ++i) {
+ // If there are more results than documented, re-use the last doc_inner.
+ const RPCResult& doc_inner{m_inner.at(std::min(m_inner.size() - 1, i))};
+ if (!doc_inner.MatchesType(result.get_array()[i])) return false;
+ }
+ return true; // empty result array is valid
}
case Type::OBJ_DYN:
case Type::OBJ: {
- return UniValue::VOBJ == result.getType();
+ if (UniValue::VOBJ != result.getType()) return false;
+ if (!m_inner.empty() && m_inner.at(0).m_type == Type::ELISION) return true;
+ if (m_type == Type::OBJ_DYN) {
+ const RPCResult& doc_inner{m_inner.at(0)}; // Assume all types are the same, randomly pick the first
+ for (size_t i{0}; i < result.get_obj().size(); ++i) {
+ if (!doc_inner.MatchesType(result.get_obj()[i])) {
+ return false;
+ }
+ }
+ return true; // empty result obj is valid
+ }
+ std::set<std::string> doc_keys;
+ for (const auto& doc_entry : m_inner) {
+ doc_keys.insert(doc_entry.m_key_name);
+ }
+ std::map<std::string, UniValue> result_obj;
+ result.getObjMap(result_obj);
+ for (const auto& result_entry : result_obj) {
+ if (doc_keys.find(result_entry.first) == doc_keys.end()) {
+ return false; // missing documentation
+ }
+ }
+
+ for (const auto& doc_entry : m_inner) {
+ const auto result_it{result_obj.find(doc_entry.m_key_name)};
+ if (result_it == result_obj.end()) {
+ if (!doc_entry.m_optional) {
+ return false; // result is missing a required key
+ }
+ continue;
+ }
+ if (!doc_entry.MatchesType(result_it->second)) {
+ return false; // wrong type
+ }
+ }
+ return true;
}
} // no default case, so the compiler can warn about missing cases
- CHECK_NONFATAL(false);
+ NONFATAL_UNREACHABLE();
+}
+
+void RPCResult::CheckInnerDoc() const
+{
+ if (m_type == Type::OBJ) {
+ // May or may not be empty
+ return;
+ }
+ // Everything else must either be empty or not
+ const bool inner_needed{m_type == Type::ARR || m_type == Type::ARR_FIXED || m_type == Type::OBJ_DYN};
+ CHECK_NONFATAL(inner_needed != m_inner.empty());
}
std::string RPCArg::ToStringObj(const bool oneline) const
@@ -918,9 +980,9 @@ std::string RPCArg::ToStringObj(const bool oneline) const
case Type::OBJ:
case Type::OBJ_USER_KEYS:
// Currently unused, so avoid writing dead code
- CHECK_NONFATAL(false);
+ NONFATAL_UNREACHABLE();
} // no default case, so the compiler can warn about missing cases
- CHECK_NONFATAL(false);
+ NONFATAL_UNREACHABLE();
}
std::string RPCArg::ToString(const bool oneline) const
@@ -955,7 +1017,7 @@ std::string RPCArg::ToString(const bool oneline) const
return "[" + res + "...]";
}
} // no default case, so the compiler can warn about missing cases
- CHECK_NONFATAL(false);
+ NONFATAL_UNREACHABLE();
}
static std::pair<int64_t, int64_t> ParseRange(const UniValue& value)
diff --git a/src/rpc/util.h b/src/rpc/util.h
index d43ee33b0f..e16fed75bc 100644
--- a/src/rpc/util.h
+++ b/src/rpc/util.h
@@ -39,6 +39,13 @@ class CPubKey;
class CScript;
struct Sections;
+/**
+ * Gets all existing output types formatted for RPC help sections.
+ *
+ * @return Comma separated string representing output type names.
+ */
+std::string GetAllOutputTypes();
+
/** Wrapper for UniValue::VType, which includes typeAny:
* Used to denote don't care type. */
struct UniValueType {
@@ -249,6 +256,7 @@ struct RPCResult {
const std::string m_key_name; //!< Only used for dicts
const std::vector<RPCResult> m_inner; //!< Only used for arrays or dicts
const bool m_optional;
+ const bool m_skip_type_check;
const std::string m_description;
const std::string m_cond;
@@ -263,12 +271,12 @@ struct RPCResult {
m_key_name{std::move(m_key_name)},
m_inner{std::move(inner)},
m_optional{optional},
+ m_skip_type_check{false},
m_description{std::move(description)},
m_cond{std::move(cond)}
{
CHECK_NONFATAL(!m_cond.empty());
- const bool inner_needed{type == Type::ARR || type == Type::ARR_FIXED || type == Type::OBJ || type == Type::OBJ_DYN};
- CHECK_NONFATAL(inner_needed != inner.empty());
+ CheckInnerDoc();
}
RPCResult(
@@ -284,24 +292,26 @@ struct RPCResult {
const std::string m_key_name,
const bool optional,
const std::string description,
- const std::vector<RPCResult> inner = {})
+ const std::vector<RPCResult> inner = {},
+ bool skip_type_check = false)
: m_type{std::move(type)},
m_key_name{std::move(m_key_name)},
m_inner{std::move(inner)},
m_optional{optional},
+ m_skip_type_check{skip_type_check},
m_description{std::move(description)},
m_cond{}
{
- const bool inner_needed{type == Type::ARR || type == Type::ARR_FIXED || type == Type::OBJ || type == Type::OBJ_DYN};
- CHECK_NONFATAL(inner_needed != inner.empty());
+ CheckInnerDoc();
}
RPCResult(
const Type type,
const std::string m_key_name,
const std::string description,
- const std::vector<RPCResult> inner = {})
- : RPCResult{type, m_key_name, false, description, inner} {}
+ const std::vector<RPCResult> inner = {},
+ bool skip_type_check = false)
+ : RPCResult{type, m_key_name, false, description, inner, skip_type_check} {}
/** Append the sections of the result. */
void ToSections(Sections& sections, OuterType outer_type = OuterType::NONE, const int current_indent = 0) const;
@@ -311,6 +321,9 @@ struct RPCResult {
std::string ToDescriptionString() const;
/** Check whether the result JSON type matches. */
bool MatchesType(const UniValue& result) const;
+
+private:
+ void CheckInnerDoc() const;
};
struct RPCResults {