aboutsummaryrefslogtreecommitdiff
path: root/src/rpc
diff options
context:
space:
mode:
Diffstat (limited to 'src/rpc')
-rw-r--r--src/rpc/blockchain.cpp1368
-rw-r--r--src/rpc/blockchain.h41
-rw-r--r--src/rpc/client.cpp17
-rw-r--r--src/rpc/external_signer.cpp21
-rw-r--r--src/rpc/fees.cpp236
-rw-r--r--src/rpc/mempool.cpp748
-rw-r--r--src/rpc/mempool.h17
-rw-r--r--src/rpc/mining.cpp371
-rw-r--r--src/rpc/misc.cpp776
-rw-r--r--src/rpc/net.cpp282
-rw-r--r--src/rpc/net.h15
-rw-r--r--src/rpc/node.cpp440
-rw-r--r--src/rpc/output_script.cpp319
-rw-r--r--src/rpc/protocol.h3
-rw-r--r--src/rpc/rawtransaction.cpp1111
-rw-r--r--src/rpc/rawtransaction_util.cpp22
-rw-r--r--src/rpc/rawtransaction_util.h5
-rw-r--r--src/rpc/register.h26
-rw-r--r--src/rpc/request.cpp23
-rw-r--r--src/rpc/request.h2
-rw-r--r--src/rpc/server.cpp34
-rw-r--r--src/rpc/server.h3
-rw-r--r--src/rpc/server_util.cpp95
-rw-r--r--src/rpc/server_util.h32
-rw-r--r--src/rpc/signmessage.cpp113
-rw-r--r--src/rpc/txoutproof.cpp181
-rw-r--r--src/rpc/util.cpp131
-rw-r--r--src/rpc/util.h36
28 files changed, 3635 insertions, 2833 deletions
diff --git a/src/rpc/blockchain.cpp b/src/rpc/blockchain.cpp
index 0556451e1a..9766a237c7 100644
--- a/src/rpc/blockchain.cpp
+++ b/src/rpc/blockchain.cpp
@@ -1,33 +1,34 @@
// Copyright (c) 2010 Satoshi Nakamoto
-// Copyright (c) 2009-2020 The Bitcoin Core developers
+// 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 <amount.h>
#include <blockfilter.h>
#include <chain.h>
#include <chainparams.h>
#include <coins.h>
+#include <consensus/amount.h>
#include <consensus/params.h>
#include <consensus/validation.h>
#include <core_io.h>
#include <deploymentinfo.h>
#include <deploymentstatus.h>
+#include <fs.h>
#include <hash.h>
#include <index/blockfilterindex.h>
#include <index/coinstatsindex.h>
+#include <kernel/coinstats.h>
+#include <logging/timer.h>
+#include <net.h>
+#include <net_processing.h>
#include <node/blockstorage.h>
-#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>
#include <rpc/util.h>
#include <script/descriptor.h>
#include <streams.h>
@@ -35,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/system.h>
#include <util/translation.h>
#include <validation.h>
#include <validationinterface.h>
@@ -45,70 +47,29 @@
#include <stdint.h>
-#include <univalue.h>
-
#include <condition_variable>
#include <memory>
#include <mutex>
+using kernel::CCoinsStats;
+using kernel::CoinStatsHashType;
+
+using node::BlockManager;
+using node::NodeContext;
+using node::ReadBlockFromDisk;
+using node::SnapshotMetadata;
+using node::UndoReadFromDisk;
+
struct CUpdatedBlock
{
uint256 hash;
int height;
};
-static Mutex cs_blockchange;
+static GlobalMutex cs_blockchange;
static std::condition_variable cond_blockchange;
static CUpdatedBlock latestblock GUARDED_BY(cs_blockchange);
-NodeContext& EnsureAnyNodeContext(const std::any& context)
-{
- auto node_context = util::AnyPtr<NodeContext>(context);
- if (!node_context) {
- throw JSONRPCError(RPC_INTERNAL_ERROR, "Node context not found");
- }
- return *node_context;
-}
-
-CTxMemPool& EnsureMemPool(const NodeContext& node)
-{
- if (!node.mempool) {
- throw JSONRPCError(RPC_CLIENT_MEMPOOL_DISABLED, "Mempool disabled or instance not found");
- }
- return *node.mempool;
-}
-
-CTxMemPool& EnsureAnyMemPool(const std::any& context)
-{
- return EnsureMemPool(EnsureAnyNodeContext(context));
-}
-
-ChainstateManager& EnsureChainman(const NodeContext& node)
-{
- if (!node.chainman) {
- throw JSONRPCError(RPC_INTERNAL_ERROR, "Node chainman not found");
- }
- return *node.chainman;
-}
-
-ChainstateManager& EnsureAnyChainman(const std::any& context)
-{
- return EnsureChainman(EnsureAnyNodeContext(context));
-}
-
-CBlockPolicyEstimator& EnsureFeeEstimator(const NodeContext& node)
-{
- if (!node.fee_estimator) {
- throw JSONRPCError(RPC_INTERNAL_ERROR, "Fee estimation disabled");
- }
- return *node.fee_estimator;
-}
-
-CBlockPolicyEstimator& EnsureAnyFeeEstimator(const std::any& context)
-{
- return EnsureFeeEstimator(EnsureAnyNodeContext(context));
-}
-
/* Calculate the difficulty for a given block index.
*/
double GetDifficulty(const CBlockIndex* blockindex)
@@ -143,12 +104,13 @@ 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();
if (param.isNum()) {
- const int height{param.get_int()};
+ const int height{param.getInt<int>()};
if (height < 0) {
throw JSONRPCError(RPC_INVALID_PARAMETER, strprintf("Target block height %d is negative", height));
}
@@ -160,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");
@@ -199,7 +161,7 @@ UniValue blockheaderToJSON(const CBlockIndex* tip, const CBlockIndex* blockindex
return result;
}
-UniValue blockToJSON(const CBlock& block, const CBlockIndex* tip, const CBlockIndex* blockindex, bool txDetails)
+UniValue blockToJSON(BlockManager& blockman, const CBlock& block, const CBlockIndex* tip, const CBlockIndex* blockindex, TxVerbosity verbosity)
{
UniValue result = blockheaderToJSON(tip, blockindex);
@@ -207,22 +169,30 @@ UniValue blockToJSON(const CBlock& block, const CBlockIndex* tip, const CBlockIn
result.pushKV("size", (int)::GetSerializeSize(block, PROTOCOL_VERSION));
result.pushKV("weight", (int)::GetBlockWeight(block));
UniValue txs(UniValue::VARR);
- if (txDetails) {
- CBlockUndo blockUndo;
- const bool have_undo = !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 == 0) doesn't have undo data
- const CTxUndo* txundo = (have_undo && i) ? &blockUndo.vtxundo.at(i - 1) : nullptr;
- UniValue objTx(UniValue::VOBJ);
- TxToUniv(*tx, uint256(), objTx, true, RPCSerializationFlags(), txundo);
- txs.push_back(objTx);
- }
- } else {
- for (const CTransactionRef& tx : block.vtx) {
- txs.push_back(tx->GetHash().GetHex());
- }
+
+ switch (verbosity) {
+ case TxVerbosity::SHOW_TXID:
+ for (const CTransactionRef& tx : block.vtx) {
+ txs.push_back(tx->GetHash().GetHex());
+ }
+ break;
+
+ case TxVerbosity::SHOW_DETAILS:
+ case TxVerbosity::SHOW_DETAILS_AND_PREVOUT:
+ CBlockUndo blockUndo;
+ 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, /*block_hash=*/uint256(), /*entry=*/objTx, /*include_hex=*/true, RPCSerializationFlags(), txundo, verbosity);
+ txs.push_back(objTx);
+ }
+ break;
}
+
result.pushKV("tx", txs);
return result;
@@ -301,7 +271,7 @@ static RPCHelpMan waitfornewblock()
{
int timeout = 0;
if (!request.params[0].isNull())
- timeout = request.params[0].get_int();
+ timeout = request.params[0].getInt<int>();
CUpdatedBlock block;
{
@@ -347,7 +317,7 @@ static RPCHelpMan waitforblock()
uint256 hash(ParseHashV(request.params[0], "blockhash"));
if (!request.params[1].isNull())
- timeout = request.params[1].get_int();
+ timeout = request.params[1].getInt<int>();
CUpdatedBlock block;
{
@@ -391,10 +361,10 @@ static RPCHelpMan waitforblockheight()
{
int timeout = 0;
- int height = request.params[0].get_int();
+ int height = request.params[0].getInt<int>();
if (!request.params[1].isNull())
- timeout = request.params[1].get_int();
+ timeout = request.params[1].getInt<int>();
CUpdatedBlock block;
{
@@ -451,346 +421,47 @@ 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", "transaction fee in " + CURRENCY_UNIT + " (DEPRECATED)"},
- RPCResult{RPCResult::Type::STR_AMOUNT, "modifiedfee", "transaction fee with fee deltas used for mining priority (DEPRECATED)"},
- 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", "modified fees (see above) of in-mempool descendants (including this one) (DEPRECATED)"},
- 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", "modified fees (see above) of in-mempool ancestors (including this one) (DEPRECATED)"},
- 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 in " + CURRENCY_UNIT},
- RPCResult{RPCResult::Type::STR_AMOUNT, "modified", "transaction fee with fee deltas used for mining priority in " + CURRENCY_UNIT},
- RPCResult{RPCResult::Type::STR_AMOUNT, "ancestor", "modified fees (see above) of in-mempool ancestors (including this one) in " + CURRENCY_UNIT},
- RPCResult{RPCResult::Type::STR_AMOUNT, "descendant", "modified fees (see above) of in-mempool descendants (including this one) 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);
-
- 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);
-
- info.pushKV("vsize", (int)e.GetTxSize());
- info.pushKV("weight", (int)e.GetTxWeight());
- 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());
- info.pushKV("descendantfees", e.GetModFeesWithDescendants());
- info.pushKV("ancestorcount", e.GetCountWithAncestors());
- info.pushKV("ancestorsize", e.GetSizeWithAncestors());
- info.pushKV("ancestorfees", e.GetModFeesWithAncestors());
- info.pushKV("wtxid", pool.vTxHashes[e.vTxHashesIdx].first.ToString());
- const CTransaction& tx = e.GetTx();
- std::set<std::string> setDepends;
- for (const CTxIn& txin : tx.vin)
- {
- if (pool.exists(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)
+static RPCHelpMan getblockfrompeer()
{
- 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;
+ return RPCHelpMan{
+ "getblockfrompeer",
+ "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.",
{
- 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\"")
- },
+ {"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, "", /*optional=*/false, "", {}},
+ RPCExamples{
+ HelpExampleCli("getblockfrompeer", "\"00000000c937983704a73af28acdec37b049d214adbda81d7e2a3dd146f6ed09\" 0")
+ + HelpExampleRpc("getblockfrompeer", "\"00000000c937983704a73af28acdec37b049d214adbda81d7e2a3dd146f6ed09\" 0")
+ },
[&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
{
- bool fVerbose = false;
- if (!request.params[1].isNull())
- fVerbose = request.params[1].get_bool();
+ const NodeContext& node = EnsureAnyNodeContext(request.context);
+ ChainstateManager& chainman = EnsureChainman(node);
+ PeerManager& peerman = EnsurePeerman(node);
- uint256 hash = ParseHashV(request.params[0], "parameter 1");
+ const uint256& block_hash{ParseHashV(request.params[0], "blockhash")};
+ const NodeId peer_id{request.params[1].getInt<int64_t>()};
- const CTxMemPool& mempool = EnsureAnyMemPool(request.context);
- LOCK(mempool.cs);
+ const CBlockIndex* const index = WITH_LOCK(cs_main, return chainman.m_blockman.LookupBlockIndex(block_hash););
- CTxMemPool::txiter it = mempool.mapTx.find(hash);
- if (it == mempool.mapTx.end()) {
- throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Transaction not in mempool");
+ if (!index) {
+ throw JSONRPCError(RPC_MISC_ERROR, "Block header missing");
}
- 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;
+ 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");
}
-},
- };
-}
-
-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");
+ if (const auto err{peerman.FetchBlock(peer_id, *index)}) {
+ throw JSONRPCError(RPC_MISC_ERROR, err.value());
}
-
- const CTxMemPoolEntry &e = *it;
- UniValue info(UniValue::VOBJ);
- entryToJSON(mempool, info, e);
- return info;
+ return UniValue::VOBJ;
},
};
}
@@ -814,11 +485,11 @@ static RPCHelpMan getblockhash()
LOCK(cs_main);
const CChain& active_chain = chainman.ActiveChain();
- int nHeight = request.params[0].get_int();
+ int nHeight = request.params[0].getInt<int>();
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();
},
};
@@ -850,8 +521,8 @@ static RPCHelpMan getblockheader()
{RPCResult::Type::NUM, "difficulty", "The difficulty"},
{RPCResult::Type::STR_HEX, "chainwork", "Expected number of hashes required to produce the current chain"},
{RPCResult::Type::NUM, "nTx", "The number of transactions in the block"},
- {RPCResult::Type::STR_HEX, "previousblockhash", /* optional */ true, "The hash of the previous block (if available)"},
- {RPCResult::Type::STR_HEX, "nextblockhash", /* optional */ true, "The hash of the next block (if available)"},
+ {RPCResult::Type::STR_HEX, "previousblockhash", /*optional=*/true, "The hash of the previous block (if available)"},
+ {RPCResult::Type::STR_HEX, "nextblockhash", /*optional=*/true, "The hash of the next block (if available)"},
}},
RPCResult{"for verbose=false",
RPCResult::Type::STR_HEX, "", "A string that is serialized, hex-encoded data for block 'hash'"},
@@ -894,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)");
}
@@ -911,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)");
}
@@ -930,10 +603,11 @@ static RPCHelpMan getblock()
return RPCHelpMan{"getblock",
"\nIf verbosity is 0, returns a string that is serialized, hex-encoded data for block 'hash'.\n"
"If verbosity is 1, returns an Object with information about block <hash>.\n"
- "If verbosity is 2, returns an Object with information about block <hash> and information about each transaction. \n",
+ "If verbosity is 2, returns an Object with information about block <hash> and information about each transaction.\n"
+ "If verbosity is 3, returns an Object with information about block <hash> and information about each transaction, including prevout information for inputs (only for unpruned blocks in the current best chain).\n",
{
{"blockhash", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "The block hash"},
- {"verbosity|verbose", RPCArg::Type::NUM, RPCArg::Default{1}, "0 for hex-encoded data, 1 for a json object, and 2 for json object with transaction data"},
+ {"verbosity|verbose", RPCArg::Type::NUM, RPCArg::Default{1}, "0 for hex-encoded data, 1 for a JSON object, 2 for JSON object with transaction data, and 3 for JSON object with transaction data including prevout information for inputs"},
},
{
RPCResult{"for verbosity = 0",
@@ -959,8 +633,8 @@ static RPCHelpMan getblock()
{RPCResult::Type::NUM, "difficulty", "The difficulty"},
{RPCResult::Type::STR_HEX, "chainwork", "Expected number of hashes required to produce the chain up to this block (in hex)"},
{RPCResult::Type::NUM, "nTx", "The number of transactions in the block"},
- {RPCResult::Type::STR_HEX, "previousblockhash", /* optional */ true, "The hash of the previous block (if available)"},
- {RPCResult::Type::STR_HEX, "nextblockhash", /* optional */ true, "The hash of the next block (if available)"},
+ {RPCResult::Type::STR_HEX, "previousblockhash", /*optional=*/true, "The hash of the previous block (if available)"},
+ {RPCResult::Type::STR_HEX, "nextblockhash", /*optional=*/true, "The hash of the next block (if available)"},
}},
RPCResult{"for verbosity = 2",
RPCResult::Type::OBJ, "", "",
@@ -975,6 +649,37 @@ static RPCHelpMan getblock()
}},
}},
}},
+ RPCResult{"for verbosity = 3",
+ RPCResult::Type::OBJ, "", "",
+ {
+ {RPCResult::Type::ELISION, "", "Same output as verbosity = 2"},
+ {RPCResult::Type::ARR, "tx", "",
+ {
+ {RPCResult::Type::OBJ, "", "",
+ {
+ {RPCResult::Type::ARR, "vin", "",
+ {
+ {RPCResult::Type::OBJ, "", "",
+ {
+ {RPCResult::Type::ELISION, "", "The same output as verbosity = 2"},
+ {RPCResult::Type::OBJ, "prevout", "(Only if undo information is available)",
+ {
+ {RPCResult::Type::BOOL, "generated", "Coinbase or not"},
+ {RPCResult::Type::NUM, "height", "The height of the prevout"},
+ {RPCResult::Type::NUM, "value", "The value in " + CURRENCY_UNIT},
+ {RPCResult::Type::OBJ, "scriptPubKey", "",
+ {
+ {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 (one of: " + GetAllOutputTypes() + ")"},
+ }},
+ }},
+ }},
+ }},
+ }},
+ }},
+ }},
},
RPCExamples{
HelpExampleCli("getblock", "\"00000000c937983704a73af28acdec37b049d214adbda81d7e2a3dd146f6ed09\"")
@@ -989,15 +694,15 @@ static RPCHelpMan getblock()
if (request.params[1].isBool()) {
verbosity = request.params[1].get_bool() ? 1 : 0;
} else {
- verbosity = request.params[1].get_int();
+ verbosity = request.params[1].getInt<int>();
}
}
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();
@@ -1006,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)
@@ -1017,7 +722,16 @@ static RPCHelpMan getblock()
return strHex;
}
- return blockToJSON(block, tip, pblockindex, verbosity >= 2);
+ TxVerbosity tx_verbosity;
+ if (verbosity == 1) {
+ tx_verbosity = TxVerbosity::SHOW_TXID;
+ } else if (verbosity == 2) {
+ tx_verbosity = TxVerbosity::SHOW_DETAILS;
+ } else {
+ tx_verbosity = TxVerbosity::SHOW_DETAILS_AND_PREVOUT;
+ }
+
+ return blockToJSON(chainman.m_blockman, block, tip, pblockindex, tx_verbosity);
},
};
}
@@ -1037,7 +751,7 @@ static RPCHelpMan pruneblockchain()
},
[&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
{
- if (!fPruneMode)
+ if (!node::fPruneMode)
throw JSONRPCError(RPC_MISC_ERROR, "Cannot prune blocks because node is not in prune mode.");
ChainstateManager& chainman = EnsureAnyChainman(request.context);
@@ -1045,15 +759,16 @@ static RPCHelpMan pruneblockchain()
CChainState& active_chainstate = chainman.ActiveChainstate();
CChain& active_chain = active_chainstate.m_chain;
- int heightParam = request.params[0].get_int();
- if (heightParam < 0)
+ int heightParam = request.params[0].getInt<int>();
+ 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.");
}
@@ -1062,22 +777,20 @@ static RPCHelpMan pruneblockchain()
unsigned int height = (unsigned int) heightParam;
unsigned int chainHeight = (unsigned int) active_chain.Height();
- if (chainHeight < Params().PruneAfterHeight())
+ if (chainHeight < chainman.GetParams().PruneAfterHeight()) {
throw JSONRPCError(RPC_MISC_ERROR, "Blockchain is too short for pruning.");
- else if (height > chainHeight)
+ } else if (height > chainHeight) {
throw JSONRPCError(RPC_INVALID_PARAMETER, "Blockchain is shorter than the attempted prune height.");
- else if (height > chainHeight - MIN_BLOCKS_TO_KEEP) {
+ } else if (height > chainHeight - MIN_BLOCKS_TO_KEEP) {
LogPrint(BCLog::RPC, "Attempt to prune blocks close to the tip. Retaining the minimum number of blocks.\n");
height = chainHeight - MIN_BLOCKS_TO_KEEP;
}
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<int64_t>(last_block->nHeight - 1);
},
};
}
@@ -1091,10 +804,40 @@ CoinStatsHashType ParseHashType(const std::string& hash_type_input)
} else if (hash_type_input == "none") {
return CoinStatsHashType::NONE;
} else {
- throw JSONRPCError(RPC_INVALID_PARAMETER, strprintf("%s is not a valid hash_type", hash_type_input));
+ throw JSONRPCError(RPC_INVALID_PARAMETER, strprintf("'%s' is not a valid hash_type", hash_type_input));
}
}
+/**
+ * Calculate statistics about the unspent transaction output set
+ *
+ * @param[in] index_requested Signals if the coinstatsindex should be used (when available).
+ */
+static std::optional<kernel::CCoinsStats> GetUTXOStats(CCoinsView* view, node::BlockManager& blockman,
+ kernel::CoinStatsHashType hash_type,
+ const std::function<void()>& interruption_point = {},
+ const CBlockIndex* pindex = nullptr,
+ bool index_requested = true)
+{
+ // Use CoinStatsIndex if it is requested and available and a hash_type of Muhash or None was requested
+ if ((hash_type == kernel::CoinStatsHashType::MUHASH || hash_type == kernel::CoinStatsHashType::NONE) && g_coin_stats_index && index_requested) {
+ if (pindex) {
+ return g_coin_stats_index->LookUpStats(pindex);
+ } else {
+ CBlockIndex* block_index = WITH_LOCK(::cs_main, return blockman.LookupBlockIndex(view->GetBestBlock()));
+ return g_coin_stats_index->LookUpStats(block_index);
+ }
+ }
+
+ // If the coinstats index isn't requested or is otherwise not usable, the
+ // pindex should either be null or equal to the view's best block. This is
+ // because without the coinstats index we can only get coinstats about the
+ // best block.
+ CHECK_NONFATAL(!pindex || pindex->GetBlockHash() == view->GetBestBlock());
+
+ return kernel::ComputeUTXOStats(hash_type, view, blockman, interruption_point);
+}
+
static RPCHelpMan gettxoutsetinfo()
{
return RPCHelpMan{"gettxoutsetinfo",
@@ -1112,21 +855,21 @@ static RPCHelpMan gettxoutsetinfo()
{RPCResult::Type::STR_HEX, "bestblock", "The hash of the block at which these statistics are calculated"},
{RPCResult::Type::NUM, "txouts", "The number of unspent transaction outputs"},
{RPCResult::Type::NUM, "bogosize", "Database-independent, meaningless metric indicating the UTXO set size"},
- {RPCResult::Type::STR_HEX, "hash_serialized_2", /* optional */ true, "The serialized hash (only present if 'hash_serialized_2' hash_type is chosen)"},
- {RPCResult::Type::STR_HEX, "muhash", /* optional */ true, "The serialized hash (only present if 'muhash' hash_type is chosen)"},
- {RPCResult::Type::NUM, "transactions", "The number of transactions with unspent outputs (not available when coinstatsindex is used)"},
- {RPCResult::Type::NUM, "disk_size", "The estimated size of the chainstate on disk (not available when coinstatsindex is used)"},
+ {RPCResult::Type::STR_HEX, "hash_serialized_2", /*optional=*/true, "The serialized hash (only present if 'hash_serialized_2' hash_type is chosen)"},
+ {RPCResult::Type::STR_HEX, "muhash", /*optional=*/true, "The serialized hash (only present if 'muhash' hash_type is chosen)"},
+ {RPCResult::Type::NUM, "transactions", /*optional=*/true, "The number of transactions with unspent outputs (not available when coinstatsindex is used)"},
+ {RPCResult::Type::NUM, "disk_size", /*optional=*/true, "The estimated size of the chainstate on disk (not available when coinstatsindex is used)"},
{RPCResult::Type::STR_AMOUNT, "total_amount", "The total amount of coins in the UTXO set"},
- {RPCResult::Type::STR_AMOUNT, "total_unspendable_amount", "The total amount of coins permanently excluded from the UTXO set (only available if coinstatsindex is used)"},
- {RPCResult::Type::OBJ, "block_info", "Info on amounts in the block at this block height (only available if coinstatsindex is used)",
+ {RPCResult::Type::STR_AMOUNT, "total_unspendable_amount", /*optional=*/true, "The total amount of coins permanently excluded from the UTXO set (only available if coinstatsindex is used)"},
+ {RPCResult::Type::OBJ, "block_info", /*optional=*/true, "Info on amounts in the block at this block height (only available if coinstatsindex is used)",
{
- {RPCResult::Type::STR_AMOUNT, "prevout_spent", ""},
- {RPCResult::Type::STR_AMOUNT, "coinbase", ""},
- {RPCResult::Type::STR_AMOUNT, "new_outputs_ex_coinbase", ""},
- {RPCResult::Type::STR_AMOUNT, "unspendable", ""},
+ {RPCResult::Type::STR_AMOUNT, "prevout_spent", "Total amount of all prevouts spent in this block"},
+ {RPCResult::Type::STR_AMOUNT, "coinbase", "Coinbase subsidy amount of this block"},
+ {RPCResult::Type::STR_AMOUNT, "new_outputs_ex_coinbase", "Total amount of new outputs created by this block"},
+ {RPCResult::Type::STR_AMOUNT, "unspendable", "Total amount of unspendable outputs created in this block"},
{RPCResult::Type::OBJ, "unspendables", "Detailed view of the unspendable categories",
{
- {RPCResult::Type::STR_AMOUNT, "genesis_block", ""},
+ {RPCResult::Type::STR_AMOUNT, "genesis_block", "The unspendable amount of the Genesis block subsidy"},
{RPCResult::Type::STR_AMOUNT, "bip30", "Transactions overridden by duplicates (no longer possible with BIP30)"},
{RPCResult::Type::STR_AMOUNT, "scripts", "Amounts sent to scripts that are unspendable (for example OP_RETURN outputs)"},
{RPCResult::Type::STR_AMOUNT, "unclaimed_rewards", "Fee rewards that miners did not claim in their coinbase transaction"},
@@ -1147,10 +890,9 @@ 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();
+ bool index_requested = request.params[2].isNull() || request.params[2].get_bool();
NodeContext& node = EnsureAnyNodeContext(request.context);
ChainstateManager& chainman = EnsureChainman(node);
@@ -1171,14 +913,28 @@ static RPCHelpMan gettxoutsetinfo()
throw JSONRPCError(RPC_INVALID_PARAMETER, "Querying specific block heights requires coinstatsindex");
}
- if (stats.m_hash_type == CoinStatsHashType::HASH_SERIALIZED) {
+ if (hash_type == CoinStatsHashType::HASH_SERIALIZED) {
throw JSONRPCError(RPC_INVALID_PARAMETER, "hash_serialized_2 hash type cannot be queried for a specific block");
}
pindex = ParseHashOrHeight(request.params[1], chainman);
}
- if (GetUTXOStats(coins_view, *blockman, stats, node.rpc_interruption_point, pindex)) {
+ if (index_requested && g_coin_stats_index) {
+ if (!g_coin_stats_index->BlockUntilSyncedToCurrentChain()) {
+ const IndexSummary summary{g_coin_stats_index->GetSummary()};
+
+ // If a specific block was requested and the index has already synced past that height, we can return the
+ // data already even though the index is not fully synced yet.
+ if (pindex->nHeight > summary.best_block_height) {
+ throw JSONRPCError(RPC_INTERNAL_ERROR, strprintf("Unable to get data because coinstatsindex is still syncing. Current height: %d", summary.best_block_height));
+ }
+ }
+ }
+
+ const std::optional<CCoinsStats> maybe_stats = GetUTXOStats(coins_view, *blockman, hash_type, node.rpc_interruption_point, pindex, index_requested);
+ if (maybe_stats.has_value()) {
+ const CCoinsStats& stats = maybe_stats.value();
ret.pushKV("height", (int64_t)stats.nHeight);
ret.pushKV("bestblock", stats.hashBlock.GetHex());
ret.pushKV("txouts", (int64_t)stats.nTransactionOutputs);
@@ -1187,44 +943,41 @@ static RPCHelpMan gettxoutsetinfo()
ret.pushKV("hash_serialized_2", stats.hashSerialized.GetHex());
}
if (hash_type == CoinStatsHashType::MUHASH) {
- ret.pushKV("muhash", stats.hashSerialized.GetHex());
+ ret.pushKV("muhash", stats.hashSerialized.GetHex());
}
- ret.pushKV("total_amount", ValueFromAmount(stats.nTotalAmount));
+ CHECK_NONFATAL(stats.total_amount.has_value());
+ ret.pushKV("total_amount", ValueFromAmount(stats.total_amount.value()));
if (!stats.index_used) {
ret.pushKV("transactions", static_cast<int64_t>(stats.nTransactions));
ret.pushKV("disk_size", stats.nDiskSize);
} else {
- ret.pushKV("total_unspendable_amount", ValueFromAmount(stats.block_unspendable_amount));
-
- CCoinsStats prev_stats{hash_type};
+ ret.pushKV("total_unspendable_amount", ValueFromAmount(stats.total_unspendable_amount));
+ CCoinsStats prev_stats{};
if (pindex->nHeight > 0) {
- GetUTXOStats(coins_view, *blockman, prev_stats, node.rpc_interruption_point, pindex->pprev);
+ const std::optional<CCoinsStats> maybe_prev_stats = GetUTXOStats(coins_view, *blockman, hash_type, node.rpc_interruption_point, pindex->pprev, index_requested);
+ if (!maybe_prev_stats) {
+ throw JSONRPCError(RPC_INTERNAL_ERROR, "Unable to read UTXO set");
+ }
+ prev_stats = maybe_prev_stats.value();
}
UniValue block_info(UniValue::VOBJ);
- block_info.pushKV("prevout_spent", ValueFromAmount(stats.block_prevout_spent_amount - prev_stats.block_prevout_spent_amount));
- block_info.pushKV("coinbase", ValueFromAmount(stats.block_coinbase_amount - prev_stats.block_coinbase_amount));
- block_info.pushKV("new_outputs_ex_coinbase", ValueFromAmount(stats.block_new_outputs_ex_coinbase_amount - prev_stats.block_new_outputs_ex_coinbase_amount));
- block_info.pushKV("unspendable", ValueFromAmount(stats.block_unspendable_amount - prev_stats.block_unspendable_amount));
+ block_info.pushKV("prevout_spent", ValueFromAmount(stats.total_prevout_spent_amount - prev_stats.total_prevout_spent_amount));
+ block_info.pushKV("coinbase", ValueFromAmount(stats.total_coinbase_amount - prev_stats.total_coinbase_amount));
+ block_info.pushKV("new_outputs_ex_coinbase", ValueFromAmount(stats.total_new_outputs_ex_coinbase_amount - prev_stats.total_new_outputs_ex_coinbase_amount));
+ block_info.pushKV("unspendable", ValueFromAmount(stats.total_unspendable_amount - prev_stats.total_unspendable_amount));
UniValue unspendables(UniValue::VOBJ);
- unspendables.pushKV("genesis_block", ValueFromAmount(stats.unspendables_genesis_block - prev_stats.unspendables_genesis_block));
- unspendables.pushKV("bip30", ValueFromAmount(stats.unspendables_bip30 - prev_stats.unspendables_bip30));
- unspendables.pushKV("scripts", ValueFromAmount(stats.unspendables_scripts - prev_stats.unspendables_scripts));
- unspendables.pushKV("unclaimed_rewards", ValueFromAmount(stats.unspendables_unclaimed_rewards - prev_stats.unspendables_unclaimed_rewards));
+ unspendables.pushKV("genesis_block", ValueFromAmount(stats.total_unspendables_genesis_block - prev_stats.total_unspendables_genesis_block));
+ unspendables.pushKV("bip30", ValueFromAmount(stats.total_unspendables_bip30 - prev_stats.total_unspendables_bip30));
+ unspendables.pushKV("scripts", ValueFromAmount(stats.total_unspendables_scripts - prev_stats.total_unspendables_scripts));
+ unspendables.pushKV("unclaimed_rewards", ValueFromAmount(stats.total_unspendables_unclaimed_rewards - prev_stats.total_unspendables_unclaimed_rewards));
block_info.pushKV("unspendables", unspendables);
ret.pushKV("block_info", block_info);
}
} else {
- if (g_coin_stats_index) {
- const IndexSummary summary{g_coin_stats_index->GetSummary()};
-
- if (!summary.synced) {
- throw JSONRPCError(RPC_INTERNAL_ERROR, strprintf("Unable to read UTXO set because coinstatsindex is still syncing. Current height: %d", summary.best_block_height));
- }
- }
throw JSONRPCError(RPC_INTERNAL_ERROR, "Unable to read UTXO set");
}
return ret;
@@ -1249,12 +1002,10 @@ 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::NUM, "reqSigs", /* optional */ true, "(DEPRECATED, returned only if config option -deprecatedrpc=addresses is passed) Number of required signatures"},
{RPCResult::Type::STR, "type", "The type, eg pubkeyhash"},
- {RPCResult::Type::STR, "address", /* optional */ true, "bitcoin address (only if a well-defined address exists)"},
- {RPCResult::Type::ARR, "addresses", /* optional */ true, "(DEPRECATED, returned only if config option -deprecatedrpc=addresses is passed) Array of bitcoin addresses",
- {{RPCResult::Type::STR, "address", "bitcoin address"}}},
+ {RPCResult::Type::STR, "address", /*optional=*/true, "The Bitcoin address (only if a well-defined address exists)"},
}},
{RPCResult::Type::BOOL, "coinbase", "Coinbase or not"},
}},
@@ -1276,8 +1027,7 @@ static RPCHelpMan gettxout()
UniValue ret(UniValue::VOBJ);
uint256 hash(ParseHashV(request.params[0], "txid"));
- int n = request.params[1].get_int();
- COutPoint out(hash, n);
+ COutPoint out{hash, request.params[1].getInt<uint32_t>()};
bool fMempool = true;
if (!request.params[2].isNull())
fMempool = request.params[2].get_bool();
@@ -1308,7 +1058,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);
@@ -1323,7 +1073,7 @@ static RPCHelpMan verifychain()
"\nVerifies blockchain database.\n",
{
{"checklevel", RPCArg::Type::NUM, RPCArg::DefaultHint{strprintf("%d, range=0-4", DEFAULT_CHECKLEVEL)},
- strprintf("How thorough the block verification is:\n - %s", Join(CHECKLEVEL_DOC, "\n- "))},
+ strprintf("How thorough the block verification is:\n%s", MakeUnorderedList(CHECKLEVEL_DOC))},
{"nblocks", RPCArg::Type::NUM, RPCArg::DefaultHint{strprintf("%d, 0=all", DEFAULT_CHECKBLOCKS)}, "The number of blocks to check."},
},
RPCResult{
@@ -1334,190 +1084,257 @@ 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_depth{request.params[1].isNull() ? DEFAULT_CHECKBLOCKS : request.params[1].get_int()};
+ const int check_level{request.params[0].isNull() ? DEFAULT_CHECKLEVEL : request.params[0].getInt<int>()};
+ const int check_depth{request.params[1].isNull() ? DEFAULT_CHECKBLOCKS : request.params[1].getInt<int>()};
ChainstateManager& chainman = EnsureAnyChainman(request.context);
LOCK(cs_main);
CChainState& active_chainstate = chainman.ActiveChainstate();
return CVerifyDB().VerifyDB(
- active_chainstate, Params(), active_chainstate.CoinsTip(), check_level, check_depth);
+ active_chainstate, chainman.GetParams().GetConsensus(), active_chainstate.CoinsTip(), check_level, check_depth);
},
};
}
-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 ChainstateManager& chainman, Consensus::BuriedDeployment dep)
{
// For buried deployments.
- if (!DeploymentEnabled(params, dep)) return;
+ if (!DeploymentEnabled(chainman, dep)) return;
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("height", params.DeploymentHeight(dep));
+ rv.pushKV("active", DeploymentActiveAfter(blockindex, chainman, dep));
+ rv.pushKV("height", chainman.GetConsensus().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 ChainstateManager& chainman, Consensus::DeploymentPos id)
{
// For BIP9 deployments.
- if (!DeploymentEnabled(consensusParams, id)) return;
+ if (!DeploymentEnabled(chainman, 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;
- }
- if (ThresholdState::STARTED == thresholdState)
- {
- bip9.pushKV("bit", consensusParams.vDeployments[id].bit);
+
+ const ThresholdState next_state = chainman.m_versionbitscache.State(blockindex, chainman.GetConsensus(), id);
+ const ThresholdState current_state = chainman.m_versionbitscache.State(blockindex->pprev, chainman.GetConsensus(), id);
+
+ const bool has_signal = (ThresholdState::STARTED == current_state || ThresholdState::LOCKED_IN == current_state);
+
+ // BIP9 parameters
+ if (has_signal) {
+ bip9.pushKV("bit", chainman.GetConsensus().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);
- if (ThresholdState::STARTED == thresholdState)
- {
+ bip9.pushKV("start_time", chainman.GetConsensus().vDeployments[id].nStartTime);
+ bip9.pushKV("timeout", chainman.GetConsensus().vDeployments[id].nTimeout);
+ bip9.pushKV("min_activation_height", chainman.GetConsensus().vDeployments[id].min_activation_height);
+
+ // BIP9 status
+ bip9.pushKV("status", get_state_name(current_state));
+ bip9.pushKV("since", chainman.m_versionbitscache.StateSinceHeight(blockindex->pprev, chainman.GetConsensus(), 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 = chainman.m_versionbitscache.Statistics(blockindex, chainman.GetConsensus(), id, &signals);
statsUV.pushKV("period", statsStruct.period);
- statsUV.pushKV("threshold", statsStruct.threshold);
statsUV.pushKV("elapsed", statsStruct.elapsed);
statsUV.pushKV("count", statsStruct.count);
- statsUV.pushKV("possible", statsStruct.possible);
+ 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", chainman.m_versionbitscache.StateSinceHeight(blockindex, chainman.GetConsensus(), id));
}
- rv.pushKV("active", ThresholdState::ACTIVE == thresholdState);
+ rv.pushKV("active", ThresholdState::ACTIVE == next_state);
+ rv.pushKV("bip9", bip9);
softforks.pushKV(DeploymentName(id), rv);
}
+// used by rest.cpp:rest_chaininfo, so cannot be static
RPCHelpMan getblockchaininfo()
{
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, "mediantime", "median time for the current best block"},
- {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", "lowest-height complete block stored (only present if pruning is enabled)"},
- {RPCResult::Type::BOOL, "automatic_pruning", "whether automatic pruning is enabled (only present if pruning is enabled)"},
- {RPCResult::Type::NUM, "prune_target_size", "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", "status of bip9 softforks (only for \"bip9\" type)",
- {
- {RPCResult::Type::STR, "status", "one of \"defined\", \"started\", \"locked_in\", \"active\", \"failed\""},
- {RPCResult::Type::NUM, "bit", "the bit (0-28) in the block version field used to signal this softfork (only for \"started\" 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", "numeric statistics about BIP9 signalling for a softfork (only for \"started\" status)",
- {
- {RPCResult::Type::NUM, "period", "the length in blocks of the BIP9 signalling period"},
- {RPCResult::Type::NUM, "threshold", "the number of blocks with the version bit set required to activate the feature"},
- {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", "returns false if there are not enough blocks left in this period to pass activation threshold"},
- }},
- }},
- {RPCResult::Type::NUM, "height", "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, "height of the last block pruned, plus one (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::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)};
ChainstateManager& chainman = EnsureAnyChainman(request.context);
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("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("size_on_disk", CalculateCurrentUsage());
- obj.pushKV("pruned", fPruneMode);
- if (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("chain", chainman.GetParams().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(chainman.GetParams().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);
+ if (node::fPruneMode) {
+ obj.pushKV("pruneheight", chainman.m_blockman.GetFirstStoredBlock(tip)->nHeight);
// if 0, execution bypasses the whole if block.
- bool automatic_pruning = (gArgs.GetArg("-prune", 0) != 1);
+ bool automatic_pruning{args.GetIntArg("-prune", 0) != 1};
obj.pushKV("automatic_pruning", automatic_pruning);
if (automatic_pruning) {
- obj.pushKV("prune_target_size", nPruneTarget);
+ obj.pushKV("prune_target_size", node::nPruneTarget);
}
}
- 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);
-
obj.pushKV("warnings", GetWarnings(false).original);
return obj;
},
};
}
+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 ChainstateManager& chainman)
+{
+ UniValue softforks(UniValue::VOBJ);
+ SoftForkDescPushBack(blockindex, softforks, chainman, Consensus::DEPLOYMENT_HEIGHTINCB);
+ SoftForkDescPushBack(blockindex, softforks, chainman, Consensus::DEPLOYMENT_DERSIG);
+ SoftForkDescPushBack(blockindex, softforks, chainman, Consensus::DEPLOYMENT_CLTV);
+ SoftForkDescPushBack(blockindex, softforks, chainman, Consensus::DEPLOYMENT_CSV);
+ SoftForkDescPushBack(blockindex, softforks, chainman, Consensus::DEPLOYMENT_SEGWIT);
+ SoftForkDescPushBack(blockindex, softforks, chainman, Consensus::DEPLOYMENT_TESTDUMMY);
+ SoftForkDescPushBack(blockindex, softforks, chainman, 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");
+ }
+ }
+
+ UniValue deploymentinfo(UniValue::VOBJ);
+ deploymentinfo.pushKV("hash", blockindex->GetBlockHash().ToString());
+ deploymentinfo.pushKV("height", blockindex->nHeight);
+ deploymentinfo.pushKV("deployments", DeploymentInfo(blockindex, chainman));
+ return deploymentinfo;
+ },
+ };
+}
+
/** Comparison function for sorting the getchaintips heads. */
struct CompareBlocksByHeight
{
@@ -1575,10 +1392,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);
}
}
@@ -1631,53 +1448,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.GetArg("-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 prioritizetransaction"},
- {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",
@@ -1815,9 +1585,9 @@ static RPCHelpMan getchaintxstats()
{RPCResult::Type::STR_HEX, "window_final_block_hash", "The hash of the final block in the window"},
{RPCResult::Type::NUM, "window_final_block_height", "The height of the final block in the window."},
{RPCResult::Type::NUM, "window_block_count", "Size of the window in number of blocks"},
- {RPCResult::Type::NUM, "window_tx_count", /* optional */ true, "The number of transactions in the window. Only returned if \"window_block_count\" is > 0"},
- {RPCResult::Type::NUM, "window_interval", /* optional */ true, "The elapsed time in the window in seconds. Only returned if \"window_block_count\" is > 0"},
- {RPCResult::Type::NUM, "txrate", /* optional */ true, "The average rate of transactions per second in the window. Only returned if \"window_interval\" is > 0"},
+ {RPCResult::Type::NUM, "window_tx_count", /*optional=*/true, "The number of transactions in the window. Only returned if \"window_block_count\" is > 0"},
+ {RPCResult::Type::NUM, "window_interval", /*optional=*/true, "The elapsed time in the window in seconds. Only returned if \"window_block_count\" is > 0"},
+ {RPCResult::Type::NUM, "txrate", /*optional=*/true, "The average rate of transactions per second in the window. Only returned if \"window_interval\" is > 0"},
}},
RPCExamples{
HelpExampleCli("getchaintxstats", "")
@@ -1827,7 +1597,7 @@ static RPCHelpMan getchaintxstats()
{
ChainstateManager& chainman = EnsureAnyChainman(request.context);
const CBlockIndex* pindex;
- int blockcount = 30 * 24 * 60 * 60 / Params().GetConsensus().nPowTargetSpacing; // By default: 1 month
+ int blockcount = 30 * 24 * 60 * 60 / chainman.GetParams().GetConsensus().nPowTargetSpacing; // By default: 1 month
if (request.params[1].isNull()) {
LOCK(cs_main);
@@ -1849,16 +1619,16 @@ static RPCHelpMan getchaintxstats()
if (request.params[0].isNull()) {
blockcount = std::max(0, std::min(blockcount, pindex->nHeight - 1));
} else {
- blockcount = request.params[0].get_int();
+ blockcount = request.params[0].getInt<int>();
if (blockcount < 0 || (blockcount > 0 && blockcount >= pindex->nHeight)) {
throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid block count: should be between 0 and the block's height - 1");
}
}
- const CBlockIndex* pindexPast = pindex->GetAncestor(pindex->nHeight - blockcount);
- int nTimeDiff = pindex->GetMedianTimePast() - pindexPast->GetMedianTimePast();
- int nTxDiff = pindex->nChainTx - pindexPast->nChainTx;
+ const CBlockIndex& past_block{*CHECK_NONFATAL(pindex->GetAncestor(pindex->nHeight - blockcount))};
+ const int64_t nTimeDiff{pindex->GetMedianTimePast() - past_block.GetMedianTimePast()};
+ const int nTxDiff = pindex->nChainTx - past_block.nChainTx;
UniValue ret(UniValue::VOBJ);
ret.pushKV("time", (int64_t)pindex->nTime);
@@ -1924,16 +1694,6 @@ void CalculatePercentilesByWeight(CAmount result[NUM_GETBLOCKSTATS_PERCENTILES],
}
}
-void ScriptPubKeyToUniv(const CScript& scriptPubKey, UniValue& out, bool fIncludeHex)
-{
- ScriptPubKeyToUniv(scriptPubKey, out, fIncludeHex, IsDeprecatedRPCEnabled("addresses"));
-}
-
-void TxToUniv(const CTransaction& tx, const uint256& hashBlock, UniValue& entry, bool include_hex, int serialize_flags, const CTxUndo* txundo)
-{
- TxToUniv(tx, hashBlock, IsDeprecatedRPCEnabled("addresses"), entry, include_hex, serialize_flags, txundo);
-}
-
template<typename T>
static inline bool SetHasKeys(const std::set<T>& set) {return false;}
template<typename T, typename Tk, typename... Args>
@@ -1962,11 +1722,11 @@ static RPCHelpMan getblockstats()
RPCResult{
RPCResult::Type::OBJ, "", "",
{
- {RPCResult::Type::NUM, "avgfee", "Average fee in the block"},
- {RPCResult::Type::NUM, "avgfeerate", "Average feerate (in satoshis per virtual byte)"},
- {RPCResult::Type::NUM, "avgtxsize", "Average transaction size"},
- {RPCResult::Type::STR_HEX, "blockhash", "The block hash (to check for potential reorgs)"},
- {RPCResult::Type::ARR_FIXED, "feerate_percentiles", "Feerates at the 10th, 25th, 50th, 75th, and 90th percentile weight unit (in satoshis per virtual byte)",
+ {RPCResult::Type::NUM, "avgfee", /*optional=*/true, "Average fee in the block"},
+ {RPCResult::Type::NUM, "avgfeerate", /*optional=*/true, "Average feerate (in satoshis per virtual byte)"},
+ {RPCResult::Type::NUM, "avgtxsize", /*optional=*/true, "Average transaction size"},
+ {RPCResult::Type::STR_HEX, "blockhash", /*optional=*/true, "The block hash (to check for potential reorgs)"},
+ {RPCResult::Type::ARR_FIXED, "feerate_percentiles", /*optional=*/true, "Feerates at the 10th, 25th, 50th, 75th, and 90th percentile weight unit (in satoshis per virtual byte)",
{
{RPCResult::Type::NUM, "10th_percentile_feerate", "The 10th percentile feerate"},
{RPCResult::Type::NUM, "25th_percentile_feerate", "The 25th percentile feerate"},
@@ -1974,30 +1734,30 @@ static RPCHelpMan getblockstats()
{RPCResult::Type::NUM, "75th_percentile_feerate", "The 75th percentile feerate"},
{RPCResult::Type::NUM, "90th_percentile_feerate", "The 90th percentile feerate"},
}},
- {RPCResult::Type::NUM, "height", "The height of the block"},
- {RPCResult::Type::NUM, "ins", "The number of inputs (excluding coinbase)"},
- {RPCResult::Type::NUM, "maxfee", "Maximum fee in the block"},
- {RPCResult::Type::NUM, "maxfeerate", "Maximum feerate (in satoshis per virtual byte)"},
- {RPCResult::Type::NUM, "maxtxsize", "Maximum transaction size"},
- {RPCResult::Type::NUM, "medianfee", "Truncated median fee in the block"},
- {RPCResult::Type::NUM, "mediantime", "The block median time past"},
- {RPCResult::Type::NUM, "mediantxsize", "Truncated median transaction size"},
- {RPCResult::Type::NUM, "minfee", "Minimum fee in the block"},
- {RPCResult::Type::NUM, "minfeerate", "Minimum feerate (in satoshis per virtual byte)"},
- {RPCResult::Type::NUM, "mintxsize", "Minimum transaction size"},
- {RPCResult::Type::NUM, "outs", "The number of outputs"},
- {RPCResult::Type::NUM, "subsidy", "The block subsidy"},
- {RPCResult::Type::NUM, "swtotal_size", "Total size of all segwit transactions"},
- {RPCResult::Type::NUM, "swtotal_weight", "Total weight of all segwit transactions"},
- {RPCResult::Type::NUM, "swtxs", "The number of segwit transactions"},
- {RPCResult::Type::NUM, "time", "The block time"},
- {RPCResult::Type::NUM, "total_out", "Total amount in all outputs (excluding coinbase and thus reward [ie subsidy + totalfee])"},
- {RPCResult::Type::NUM, "total_size", "Total size of all non-coinbase transactions"},
- {RPCResult::Type::NUM, "total_weight", "Total weight of all non-coinbase transactions"},
- {RPCResult::Type::NUM, "totalfee", "The fee total"},
- {RPCResult::Type::NUM, "txs", "The number of transactions (including coinbase)"},
- {RPCResult::Type::NUM, "utxo_increase", "The increase/decrease in the number of unspent outputs"},
- {RPCResult::Type::NUM, "utxo_size_inc", "The increase/decrease in size for the utxo index (not discounting op_return and similar)"},
+ {RPCResult::Type::NUM, "height", /*optional=*/true, "The height of the block"},
+ {RPCResult::Type::NUM, "ins", /*optional=*/true, "The number of inputs (excluding coinbase)"},
+ {RPCResult::Type::NUM, "maxfee", /*optional=*/true, "Maximum fee in the block"},
+ {RPCResult::Type::NUM, "maxfeerate", /*optional=*/true, "Maximum feerate (in satoshis per virtual byte)"},
+ {RPCResult::Type::NUM, "maxtxsize", /*optional=*/true, "Maximum transaction size"},
+ {RPCResult::Type::NUM, "medianfee", /*optional=*/true, "Truncated median fee in the block"},
+ {RPCResult::Type::NUM, "mediantime", /*optional=*/true, "The block median time past"},
+ {RPCResult::Type::NUM, "mediantxsize", /*optional=*/true, "Truncated median transaction size"},
+ {RPCResult::Type::NUM, "minfee", /*optional=*/true, "Minimum fee in the block"},
+ {RPCResult::Type::NUM, "minfeerate", /*optional=*/true, "Minimum feerate (in satoshis per virtual byte)"},
+ {RPCResult::Type::NUM, "mintxsize", /*optional=*/true, "Minimum transaction size"},
+ {RPCResult::Type::NUM, "outs", /*optional=*/true, "The number of outputs"},
+ {RPCResult::Type::NUM, "subsidy", /*optional=*/true, "The block subsidy"},
+ {RPCResult::Type::NUM, "swtotal_size", /*optional=*/true, "Total size of all segwit transactions"},
+ {RPCResult::Type::NUM, "swtotal_weight", /*optional=*/true, "Total weight of all segwit transactions"},
+ {RPCResult::Type::NUM, "swtxs", /*optional=*/true, "The number of segwit transactions"},
+ {RPCResult::Type::NUM, "time", /*optional=*/true, "The block time"},
+ {RPCResult::Type::NUM, "total_out", /*optional=*/true, "Total amount in all outputs (excluding coinbase and thus reward [ie subsidy + totalfee])"},
+ {RPCResult::Type::NUM, "total_size", /*optional=*/true, "Total size of all non-coinbase transactions"},
+ {RPCResult::Type::NUM, "total_weight", /*optional=*/true, "Total weight of all non-coinbase transactions"},
+ {RPCResult::Type::NUM, "totalfee", /*optional=*/true, "The fee total"},
+ {RPCResult::Type::NUM, "txs", /*optional=*/true, "The number of transactions (including coinbase)"},
+ {RPCResult::Type::NUM, "utxo_increase", /*optional=*/true, "The increase/decrease in the number of unspent outputs"},
+ {RPCResult::Type::NUM, "utxo_size_inc", /*optional=*/true, "The increase/decrease in size for the utxo index (not discounting op_return and similar)"},
}},
RPCExamples{
HelpExampleCli("getblockstats", R"('"00000000c937983704a73af28acdec37b049d214adbda81d7e2a3dd146f6ed09"' '["minfeerate","avgfeerate"]')") +
@@ -2009,8 +1769,7 @@ static RPCHelpMan getblockstats()
{
ChainstateManager& chainman = EnsureAnyChainman(request.context);
LOCK(cs_main);
- CBlockIndex* pindex{ParseHashOrHeight(request.params[0], chainman)};
- CHECK_NONFATAL(pindex != nullptr);
+ const CBlockIndex& pindex{*CHECK_NONFATAL(ParseHashOrHeight(request.params[0], chainman))};
std::set<std::string> stats;
if (!request.params[1].isNull()) {
@@ -2021,8 +1780,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;
@@ -2140,25 +1899,25 @@ static RPCHelpMan getblockstats()
ret_all.pushKV("avgfee", (block.vtx.size() > 1) ? totalfee / (block.vtx.size() - 1) : 0);
ret_all.pushKV("avgfeerate", total_weight ? (totalfee * WITNESS_SCALE_FACTOR) / total_weight : 0); // Unit: sat/vbyte
ret_all.pushKV("avgtxsize", (block.vtx.size() > 1) ? total_size / (block.vtx.size() - 1) : 0);
- ret_all.pushKV("blockhash", pindex->GetBlockHash().GetHex());
+ ret_all.pushKV("blockhash", pindex.GetBlockHash().GetHex());
ret_all.pushKV("feerate_percentiles", feerates_res);
- ret_all.pushKV("height", (int64_t)pindex->nHeight);
+ ret_all.pushKV("height", (int64_t)pindex.nHeight);
ret_all.pushKV("ins", inputs);
ret_all.pushKV("maxfee", maxfee);
ret_all.pushKV("maxfeerate", maxfeerate);
ret_all.pushKV("maxtxsize", maxtxsize);
ret_all.pushKV("medianfee", CalculateTruncatedMedian(fee_array));
- ret_all.pushKV("mediantime", pindex->GetMedianTimePast());
+ ret_all.pushKV("mediantime", pindex.GetMedianTimePast());
ret_all.pushKV("mediantxsize", CalculateTruncatedMedian(txsize_array));
ret_all.pushKV("minfee", (minfee == MAX_MONEY) ? 0 : minfee);
ret_all.pushKV("minfeerate", (minfeerate == MAX_MONEY) ? 0 : minfeerate);
ret_all.pushKV("mintxsize", mintxsize == MAX_BLOCK_SERIALIZED_SIZE ? 0 : mintxsize);
ret_all.pushKV("outs", outputs);
- ret_all.pushKV("subsidy", GetBlockSubsidy(pindex->nHeight, Params().GetConsensus()));
+ ret_all.pushKV("subsidy", GetBlockSubsidy(pindex.nHeight, chainman.GetParams().GetConsensus()));
ret_all.pushKV("swtotal_size", swtotal_size);
ret_all.pushKV("swtotal_weight", swtotal_weight);
ret_all.pushKV("swtxs", swtxs);
- ret_all.pushKV("time", pindex->GetBlockTime());
+ ret_all.pushKV("time", pindex.GetBlockTime());
ret_all.pushKV("total_out", total_out);
ret_all.pushKV("total_size", total_size);
ret_all.pushKV("total_weight", total_weight);
@@ -2175,7 +1934,7 @@ static RPCHelpMan getblockstats()
for (const std::string& stat : stats) {
const UniValue& value = ret_all[stat];
if (value.isNull()) {
- throw JSONRPCError(RPC_INVALID_PARAMETER, strprintf("Invalid selected statistic %s", stat));
+ throw JSONRPCError(RPC_INVALID_PARAMETER, strprintf("Invalid selected statistic '%s'", stat));
}
ret.pushKV(stat, value);
}
@@ -2184,33 +1943,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::NONE, "", ""},
- RPCExamples{
- HelpExampleCli("savemempool", "")
- + HelpExampleRpc("savemempool", "")
- },
- [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
-{
- 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");
- }
-
- return NullUniValue;
-},
- };
-}
-
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)
@@ -2250,9 +1982,9 @@ static std::atomic<bool> g_should_abort_scan;
class CoinsViewScanReserver
{
private:
- bool m_could_reserve;
+ bool m_could_reserve{false};
public:
- explicit CoinsViewScanReserver() : m_could_reserve(false) {}
+ explicit CoinsViewScanReserver() = default;
bool reserve() {
CHECK_NONFATAL(!m_could_reserve);
@@ -2274,6 +2006,9 @@ public:
static RPCHelpMan scantxoutset()
{
+ // scriptPubKey corresponding to mainnet address 12cbQLTFMXRnSzktFkuoG3eHoMeFtpTu3S
+ const std::string EXAMPLE_DESCRIPTOR_RAW = "raw(76a91411b366edfc0a8b66feebae5c2e25a7b6a5d1cf3188ac)#fm24fxxy";
+
return RPCHelpMan{"scantxoutset",
"\nScans the unspent transaction output set for entries that match certain output descriptors.\n"
"Examples of output descriptors are:\n"
@@ -2331,7 +2066,14 @@ static RPCHelpMan scantxoutset()
}},
RPCResult{"when action=='status' and no scan is in progress - possibly already completed", RPCResult::Type::NONE, "", ""},
},
- RPCExamples{""},
+ RPCExamples{
+ HelpExampleCli("scantxoutset", "start \'[\"" + EXAMPLE_DESCRIPTOR_RAW + "\"]\'") +
+ HelpExampleCli("scantxoutset", "status") +
+ HelpExampleCli("scantxoutset", "abort") +
+ HelpExampleRpc("scantxoutset", "\"start\", [\"" + EXAMPLE_DESCRIPTOR_RAW + "\"]") +
+ HelpExampleRpc("scantxoutset", "\"status\"") +
+ HelpExampleRpc("scantxoutset", "\"abort\"")
+ },
[&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
{
RPCTypeCheck(request.params, {UniValue::VSTR, UniValue::VARR});
@@ -2343,7 +2085,7 @@ static RPCHelpMan scantxoutset()
// no scan in progress
return NullUniValue;
}
- result.pushKV("progress", g_scan_progress);
+ result.pushKV("progress", g_scan_progress.load());
return result;
} else if (request.params[0].get_str() == "abort") {
CoinsViewScanReserver reserver;
@@ -2386,17 +2128,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);
@@ -2519,13 +2259,9 @@ static RPCHelpMan dumptxoutset()
{
return RPCHelpMan{
"dumptxoutset",
- "\nWrite the serialized UTXO set to disk.\n",
+ "Write the serialized UTXO set to disk.",
{
- {"path",
- RPCArg::Type::STR,
- RPCArg::Optional::NO,
- /* default_val */ "",
- "path to the output file. If relative, will be prefixed by datadir."},
+ {"path", RPCArg::Type::STR, RPCArg::Optional::NO, "Path to the output file. If relative, will be prefixed by datadir."},
},
RPCResult{
RPCResult::Type::OBJ, "", "",
@@ -2534,6 +2270,8 @@ static RPCHelpMan dumptxoutset()
{RPCResult::Type::STR_HEX, "base_hash", "the hash of the base of the snapshot"},
{RPCResult::Type::NUM, "base_height", "the height of the base of the snapshot"},
{RPCResult::Type::STR, "path", "the absolute path that the snapshot was written to"},
+ {RPCResult::Type::STR_HEX, "txoutset_hash", "the hash of the UTXO set contents"},
+ {RPCResult::Type::NUM, "nchaintx", "the number of transactions in the chain up to and including the base block"},
}
},
RPCExamples{
@@ -2541,35 +2279,48 @@ static RPCHelpMan dumptxoutset()
},
[&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
{
- const fs::path path = fsbridge::AbsPathJoin(gArgs.GetDataDirNet(), request.params[0].get_str());
+ const ArgsManager& args{EnsureAnyArgsman(request.context)};
+ const fs::path path = fsbridge::AbsPathJoin(args.GetDataDirNet(), fs::u8path(request.params[0].get_str()));
// Write to a temporary path and then move into `path` on completion
// to avoid confusion due to an interruption.
- const fs::path temppath = fsbridge::AbsPathJoin(gArgs.GetDataDirNet(), request.params[0].get_str() + ".incomplete");
+ const fs::path temppath = fsbridge::AbsPathJoin(args.GetDataDirNet(), fs::u8path(request.params[0].get_str() + ".incomplete"));
if (fs::exists(path)) {
throw JSONRPCError(
RPC_INVALID_PARAMETER,
- path.string() + " already exists. If you are sure this is what you want, "
+ path.u8string() + " already exists. If you are sure this is what you want, "
"move it out of the way first");
}
FILE* file{fsbridge::fopen(temppath, "wb")};
CAutoFile afile{file, SER_DISK, CLIENT_VERSION};
+ if (afile.IsNull()) {
+ throw JSONRPCError(
+ RPC_INVALID_PARAMETER,
+ "Couldn't open file " + temppath.u8string() + " for writing.");
+ }
+
NodeContext& node = EnsureAnyNodeContext(request.context);
- UniValue result = CreateUTXOSnapshot(node, node.chainman->ActiveChainstate(), afile);
+ UniValue result = CreateUTXOSnapshot(
+ node, node.chainman->ActiveChainstate(), afile, path, temppath);
fs::rename(temppath, path);
- result.pushKV("path", path.string());
+ result.pushKV("path", path.u8string());
return result;
},
};
}
-UniValue CreateUTXOSnapshot(NodeContext& node, CChainState& chainstate, CAutoFile& afile)
+UniValue CreateUTXOSnapshot(
+ NodeContext& node,
+ CChainState& chainstate,
+ CAutoFile& afile,
+ const fs::path& path,
+ const fs::path& temppath)
{
std::unique_ptr<CCoinsViewCursor> pcursor;
- CCoinsStats stats{CoinStatsHashType::NONE};
- CBlockIndex* tip;
+ std::optional<CCoinsStats> maybe_stats;
+ const CBlockIndex* tip;
{
// We need to lock cs_main to ensure that the coinsdb isn't written to
@@ -2588,16 +2339,20 @@ UniValue CreateUTXOSnapshot(NodeContext& node, CChainState& chainstate, CAutoFil
chainstate.ForceFlushStateToDisk();
- if (!GetUTXOStats(&chainstate.CoinsDB(), chainstate.m_blockman, stats, node.rpc_interruption_point)) {
+ maybe_stats = GetUTXOStats(&chainstate.CoinsDB(), chainstate.m_blockman, CoinStatsHashType::HASH_SERIALIZED, node.rpc_interruption_point);
+ if (!maybe_stats) {
throw JSONRPCError(RPC_INTERNAL_ERROR, "Unable to read UTXO set");
}
pcursor = chainstate.CoinsDB().Cursor();
- tip = chainstate.m_blockman.LookupBlockIndex(stats.hashBlock);
- CHECK_NONFATAL(tip);
+ tip = CHECK_NONFATAL(chainstate.m_blockman.LookupBlockIndex(maybe_stats->hashBlock));
}
- SnapshotMetadata metadata{tip->GetBlockHash(), stats.coins_count, tip->nChainTx};
+ LOG_TIME_SECONDS(strprintf("writing UTXO snapshot at height %s (%s) to file %s (via %s)",
+ tip->nHeight, tip->GetBlockHash().ToString(),
+ fs::PathToString(path), fs::PathToString(temppath)));
+
+ SnapshotMetadata metadata{tip->GetBlockHash(), maybe_stats->coins_count, tip->nChainTx};
afile << metadata;
@@ -2619,54 +2374,47 @@ UniValue CreateUTXOSnapshot(NodeContext& node, CChainState& chainstate, CAutoFil
afile.fclose();
UniValue result(UniValue::VOBJ);
- result.pushKV("coins_written", stats.coins_count);
+ result.pushKV("coins_written", maybe_stats->coins_count);
result.pushKV("base_hash", tip->GetBlockHash().ToString());
result.pushKV("base_height", tip->nHeight);
-
+ result.pushKV("path", path.u8string());
+ result.pushKV("txoutset_hash", maybe_stats->hashSerialized.ToString());
+ // Cast required because univalue doesn't have serialization specified for
+ // `unsigned int`, nChainTx's type.
+ result.pushKV("nchaintx", uint64_t{tip->nChainTx});
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", &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 ffb6f03b47..5fbd9d5fd3 100644
--- a/src/rpc/blockchain.h
+++ b/src/rpc/blockchain.h
@@ -1,14 +1,16 @@
-// Copyright (c) 2017-2020 The Bitcoin Core developers
+// Copyright (c) 2017-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.
#ifndef BITCOIN_RPC_BLOCKCHAIN_H
#define BITCOIN_RPC_BLOCKCHAIN_H
-#include <amount.h>
+#include <consensus/amount.h>
#include <core_io.h>
+#include <fs.h>
#include <streams.h>
#include <sync.h>
+#include <validation.h>
#include <any>
#include <stdint.h>
@@ -18,12 +20,11 @@ extern RecursiveMutex cs_main;
class CBlock;
class CBlockIndex;
-class CBlockPolicyEstimator;
class CChainState;
-class CTxMemPool;
-class ChainstateManager;
class UniValue;
+namespace node {
struct NodeContext;
+} // namespace node
static constexpr int NUM_GETBLOCKSTATS_PERCENTILES = 5;
@@ -39,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, bool txDetails = false) 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);
@@ -53,21 +48,15 @@ UniValue blockheaderToJSON(const CBlockIndex* tip, const CBlockIndex* blockindex
/** Used by getblockstats to get feerates at different percentiles by weight */
void CalculatePercentilesByWeight(CAmount result[NUM_GETBLOCKSTATS_PERCENTILES], std::vector<std::pair<CAmount, int64_t>>& scores, int64_t total_weight);
-void ScriptPubKeyToUniv(const CScript& scriptPubKey, UniValue& out, bool fIncludeHex);
-void TxToUniv(const CTransaction& tx, const uint256& hashBlock, UniValue& entry, bool include_hex = true, int serialize_flags = 0, const CTxUndo* txundo = nullptr);
-
-NodeContext& EnsureAnyNodeContext(const std::any& context);
-CTxMemPool& EnsureMemPool(const NodeContext& node);
-CTxMemPool& EnsureAnyMemPool(const std::any& context);
-ChainstateManager& EnsureChainman(const NodeContext& node);
-ChainstateManager& EnsureAnyChainman(const std::any& context);
-CBlockPolicyEstimator& EnsureFeeEstimator(const NodeContext& node);
-CBlockPolicyEstimator& EnsureAnyFeeEstimator(const std::any& context);
-
/**
* Helper to create UTXO snapshots given a chainstate and a file handle.
* @return a UniValue map containing metadata about the snapshot.
*/
-UniValue CreateUTXOSnapshot(NodeContext& node, CChainState& chainstate, CAutoFile& afile);
-
-#endif
+UniValue CreateUTXOSnapshot(
+ node::NodeContext& node,
+ CChainState& chainstate,
+ CAutoFile& afile,
+ const fs::path& path,
+ const fs::path& tmppath);
+
+#endif // BITCOIN_RPC_BLOCKCHAIN_H
diff --git a/src/rpc/client.cpp b/src/rpc/client.cpp
index 9c8582c7a3..ae0d0112ba 100644
--- a/src/rpc/client.cpp
+++ b/src/rpc/client.cpp
@@ -1,5 +1,5 @@
// Copyright (c) 2010 Satoshi Nakamoto
-// Copyright (c) 2009-2020 The Bitcoin Core developers
+// 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.
@@ -46,16 +46,21 @@ static const CRPCConvertParam vRPCConvertParams[] =
{ "settxfee", 0, "amount" },
{ "sethdseed", 0, "newkeypool" },
{ "getreceivedbyaddress", 1, "minconf" },
+ { "getreceivedbyaddress", 2, "include_immature_coinbase" },
{ "getreceivedbylabel", 1, "minconf" },
+ { "getreceivedbylabel", 2, "include_immature_coinbase" },
{ "listreceivedbyaddress", 0, "minconf" },
{ "listreceivedbyaddress", 1, "include_empty" },
{ "listreceivedbyaddress", 2, "include_watchonly" },
+ { "listreceivedbyaddress", 4, "include_immature_coinbase" },
{ "listreceivedbylabel", 0, "minconf" },
{ "listreceivedbylabel", 1, "include_empty" },
{ "listreceivedbylabel", 2, "include_watchonly" },
+ { "listreceivedbylabel", 3, "include_immature_coinbase" },
{ "getbalance", 1, "minconf" },
{ "getbalance", 2, "include_watchonly" },
{ "getbalance", 3, "avoid_reuse" },
+ { "getblockfrompeer", 1, "peer_id" },
{ "getblockhash", 0, "height" },
{ "waitforblockheight", 0, "height" },
{ "waitforblockheight", 1, "timeout" },
@@ -115,6 +120,7 @@ static const CRPCConvertParam vRPCConvertParams[] =
{ "walletcreatefundedpsbt", 4, "bip32derivs" },
{ "walletprocesspsbt", 1, "sign" },
{ "walletprocesspsbt", 3, "bip32derivs" },
+ { "walletprocesspsbt", 4, "finalize" },
{ "createpsbt", 0, "inputs" },
{ "createpsbt", 1, "outputs" },
{ "createpsbt", 2, "locktime" },
@@ -131,10 +137,15 @@ static const CRPCConvertParam vRPCConvertParams[] =
{ "gettxoutsetinfo", 2, "use_index"},
{ "lockunspent", 0, "unlock" },
{ "lockunspent", 1, "transactions" },
+ { "lockunspent", 2, "persistent" },
{ "send", 0, "outputs" },
{ "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" },
@@ -142,6 +153,7 @@ static const CRPCConvertParam vRPCConvertParams[] =
{ "importmulti", 0, "requests" },
{ "importmulti", 1, "options" },
{ "importdescriptors", 0, "requests" },
+ { "listdescriptors", 0, "private" },
{ "verifychain", 0, "checklevel" },
{ "verifychain", 1, "nblocks" },
{ "getblockstats", 0, "hash_or_height" },
@@ -161,6 +173,7 @@ static const CRPCConvertParam vRPCConvertParams[] =
{ "setwalletflag", 1, "value" },
{ "getmempoolancestors", 1, "verbose" },
{ "getmempooldescendants", 1, "verbose" },
+ { "gettxspendingprevout", 0, "outputs" },
{ "bumpfee", 1, "options" },
{ "psbtbumpfee", 1, "options" },
{ "logging", 0, "include" },
@@ -186,10 +199,12 @@ static const CRPCConvertParam vRPCConvertParams[] =
{ "createwallet", 5, "descriptors"},
{ "createwallet", 6, "load_on_startup"},
{ "createwallet", 7, "external_signer"},
+ { "restorewallet", 2, "load_on_startup"},
{ "loadwallet", 1, "load_on_startup"},
{ "unloadwallet", 1, "load_on_startup"},
{ "getnodeaddresses", 0, "count"},
{ "addpeeraddress", 1, "port"},
+ { "addpeeraddress", 2, "tried"},
{ "stop", 0, "wait" },
};
// clang-format on
diff --git a/src/rpc/external_signer.cpp b/src/rpc/external_signer.cpp
index 6ec2b1a07f..4de7fc4205 100644
--- a/src/rpc/external_signer.cpp
+++ b/src/rpc/external_signer.cpp
@@ -22,10 +22,13 @@ static RPCHelpMan enumeratesigners()
RPCResult{
RPCResult::Type::OBJ, "", "",
{
- {RPCResult::Type::ARR, "signers", /* optional */ false, "",
+ {RPCResult::Type::ARR, "signers", /*optional=*/false, "",
{
- {RPCResult::Type::STR_HEX, "masterkeyfingerprint", "Master key fingerprint"},
- {RPCResult::Type::STR, "name", "Device name"},
+ {RPCResult::Type::OBJ, "", "",
+ {
+ {RPCResult::Type::STR_HEX, "fingerprint", "Master key fingerprint"},
+ {RPCResult::Type::STR, "name", "Device name"},
+ }},
},
}
}
@@ -59,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..1873bc1587
--- /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 <policy/settings.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 <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..97ec95a166
--- /dev/null
+++ b/src/rpc/mempool.cpp
@@ -0,0 +1,748 @@
+// 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 <policy/settings.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>
+
+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::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::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_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());
+ info.pushKV("time", count_seconds(e.GetTime()));
+ info.pushKV("height", (int)e.GetHeight());
+ info.pushKV("descendantcount", e.GetCountWithDescendants());
+ info.pushKV("descendantsize", e.GetSizeWithDescendants());
+ info.pushKV("ancestorcount", e.GetCountWithAncestors());
+ info.pushKV("ancestorsize", e.GetSizeWithAncestors());
+ 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 gettxspendingprevout()
+{
+ return RPCHelpMan{"gettxspendingprevout",
+ "Scans the mempool to find transactions spending any of the given outputs",
+ {
+ {"outputs", RPCArg::Type::ARR, RPCArg::Optional::NO, "The transaction outputs that we want to check, and within each, the txid (string) vout (numeric).",
+ {
+ {"", RPCArg::Type::OBJ, RPCArg::Optional::OMITTED, "",
+ {
+ {"txid", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "The transaction id"},
+ {"vout", RPCArg::Type::NUM, RPCArg::Optional::NO, "The output number"},
+ },
+ },
+ },
+ },
+ },
+ RPCResult{
+ RPCResult::Type::ARR, "", "",
+ {
+ {RPCResult::Type::OBJ, "", "",
+ {
+ {RPCResult::Type::STR_HEX, "txid", "the transaction id of the checked output"},
+ {RPCResult::Type::NUM, "vout", "the vout value of the checked output"},
+ {RPCResult::Type::STR_HEX, "spendingtxid", /*optional=*/true, "the transaction id of the mempool transaction spending this output (omitted if unspent)"},
+ }},
+ }
+ },
+ RPCExamples{
+ HelpExampleCli("gettxspendingprevout", "\"[{\\\"txid\\\":\\\"a08e6907dbbd3d809776dbfc5d82e371b764ed838b5655e72f463568df1aadf0\\\",\\\"vout\\\":3}]\"")
+ + HelpExampleRpc("gettxspendingprevout", "\"[{\\\"txid\\\":\\\"a08e6907dbbd3d809776dbfc5d82e371b764ed838b5655e72f463568df1aadf0\\\",\\\"vout\\\":3}]\"")
+ },
+ [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
+ {
+ RPCTypeCheckArgument(request.params[0], UniValue::VARR);
+ const UniValue& output_params = request.params[0];
+ if (output_params.empty()) {
+ throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid parameter, outputs are missing");
+ }
+
+ std::vector<COutPoint> prevouts;
+ prevouts.reserve(output_params.size());
+
+ for (unsigned int idx = 0; idx < output_params.size(); idx++) {
+ const UniValue& o = output_params[idx].get_obj();
+
+ RPCTypeCheckObj(o,
+ {
+ {"txid", UniValueType(UniValue::VSTR)},
+ {"vout", UniValueType(UniValue::VNUM)},
+ }, /*fAllowNull=*/false, /*fStrict=*/true);
+
+ const uint256 txid(ParseHashO(o, "txid"));
+ const int nOutput{find_value(o, "vout").getInt<int>()};
+ if (nOutput < 0) {
+ throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid parameter, vout cannot be negative");
+ }
+
+ prevouts.emplace_back(txid, nOutput);
+ }
+
+ const CTxMemPool& mempool = EnsureAnyMemPool(request.context);
+ LOCK(mempool.cs);
+
+ UniValue result{UniValue::VARR};
+
+ for (const COutPoint& prevout : prevouts) {
+ UniValue o(UniValue::VOBJ);
+ o.pushKV("txid", prevout.hash.ToString());
+ o.pushKV("vout", (uint64_t)prevout.n);
+
+ const CTransaction* spendingTx = mempool.GetConflictTx(prevout);
+ if (spendingTx != nullptr) {
+ o.pushKV("spendingtxid", spendingTx->GetHash().ToString());
+ }
+
+ result.push_back(o);
+ }
+
+ return result;
+ },
+ };
+}
+
+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", &gettxspendingprevout},
+ {"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 692096367c..8fb6daf0cb 100644
--- a/src/rpc/mining.cpp
+++ b/src/rpc/mining.cpp
@@ -1,35 +1,35 @@
// Copyright (c) 2010 Satoshi Nakamoto
-// Copyright (c) 2009-2020 The Bitcoin Core developers
+// 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 <amount.h>
#include <chain.h>
#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>
#include <deploymentinfo.h>
#include <deploymentstatus.h>
#include <key_io.h>
-#include <miner.h>
#include <net.h>
#include <node/context.h>
-#include <policy/fees.h>
+#include <node/miner.h>
#include <pow.h>
#include <rpc/blockchain.h>
#include <rpc/mining.h>
-#include <rpc/net.h>
#include <rpc/server.h>
+#include <rpc/server_util.h>
#include <rpc/util.h>
#include <script/descriptor.h>
#include <script/script.h>
#include <script/signingprovider.h>
#include <shutdown.h>
+#include <timedata.h>
#include <txmempool.h>
#include <univalue.h>
-#include <util/fees.h>
#include <util/strencodings.h>
#include <util/string.h>
#include <util/system.h>
@@ -41,6 +41,12 @@
#include <memory>
#include <stdint.h>
+using node::BlockAssembler;
+using node::CBlockTemplate;
+using node::NodeContext;
+using node::RegenerateCommitments;
+using node::UpdateTime;
+
/**
* Return average network hashes per second based on the last 'lookup' blocks,
* or from the last difficulty change if 'lookup' is nonpositive.
@@ -104,23 +110,17 @@ static RPCHelpMan getnetworkhashps()
{
ChainstateManager& chainman = EnsureAnyChainman(request.context);
LOCK(cs_main);
- return GetNetworkHashPS(!request.params[0].isNull() ? request.params[0].get_int() : 120, !request.params[1].isNull() ? request.params[1].get_int() : -1, chainman.ActiveChain());
+ return GetNetworkHashPS(!request.params[0].isNull() ? request.params[0].getInt<int>() : 120, !request.params[1].isNull() ? request.params[1].getInt<int>() : -1, chainman.ActiveChain());
},
};
}
-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();
+ block.hashMerkleRoot = BlockMerkleRoot(block);
- {
- LOCK(cs_main);
- IncrementExtraNonce(&block, chainman.ActiveChain().Tip(), extra_nonce);
- }
-
- CChainParams chainparams(Params());
-
- while (max_tries > 0 && block.nNonce < std::numeric_limits<uint32_t>::max() && !CheckProofOfWork(block.GetHash(), block.nBits, chainparams.GetConsensus()) && !ShutdownRequested()) {
+ while (max_tries > 0 && block.nNonce < std::numeric_limits<uint32_t>::max() && !CheckProofOfWork(block.GetHash(), block.nBits, chainman.GetConsensus()) && !ShutdownRequested()) {
++block.nNonce;
--max_tries;
}
@@ -132,7 +132,7 @@ static bool GenerateBlock(ChainstateManager& chainman, CBlock& block, uint64_t&
}
std::shared_ptr<const CBlock> shared_pblock = std::make_shared<const CBlock>(block);
- if (!chainman.ProcessNewBlock(chainparams, shared_pblock, true, nullptr)) {
+ if (!chainman.ProcessNewBlock(shared_pblock, true, nullptr)) {
throw JSONRPCError(RPC_INTERNAL_ERROR, "ProcessNewBlock, block not accepted");
}
@@ -142,30 +142,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())
- {
- std::unique_ptr<CBlockTemplate> pblocktemplate(BlockAssembler(chainman.ActiveChainstate(), mempool, Params()).CreateNewBlock(coinbase_script));
+ while (nGenerate > 0 && !ShutdownRequested()) {
+ std::unique_ptr<CBlockTemplate> pblocktemplate(BlockAssembler{chainman.ActiveChainstate(), mempool}.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());
}
}
@@ -184,7 +174,7 @@ static bool getScriptFromDescriptor(const std::string& descriptor, CScript& scri
FlatSigningProvider provider;
std::vector<CScript> scripts;
if (!desc->Expand(0, key_provider, scripts, provider)) {
- throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, strprintf("Cannot derive script without private keys"));
+ throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Cannot derive script without private keys");
}
// Combo descriptors can have 2 or 4 scripts, so we can't just check scripts.size() == 1
@@ -210,9 +200,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."},
},
@@ -226,8 +216,8 @@ static RPCHelpMan generatetodescriptor()
"\nGenerate 11 blocks to mydesc\n" + HelpExampleCli("generatetodescriptor", "11 \"mydesc\"")},
[&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
{
- const int num_blocks{request.params[0].get_int()};
- const uint64_t max_tries{request.params[2].isNull() ? DEFAULT_MAX_TRIES : request.params[2].get_int()};
+ const int num_blocks{request.params[0].getInt<int>()};
+ const uint64_t max_tries{request.params[2].isNull() ? DEFAULT_MAX_TRIES : request.params[2].getInt<int>()};
CScript coinbase_script;
std::string error;
@@ -254,18 +244,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"
@@ -273,8 +263,8 @@ static RPCHelpMan generatetoaddress()
},
[&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
{
- const int num_blocks{request.params[0].get_int()};
- const uint64_t max_tries{request.params[2].isNull() ? DEFAULT_MAX_TRIES : request.params[2].get_int()};
+ const int num_blocks{request.params[0].getInt<int>()};
+ const uint64_t max_tries{request.params[2].isNull() ? DEFAULT_MAX_TRIES : request.params[2].getInt<int>()};
CTxDestination destination = DecodeDestination(request.params[1].get_str());
if (!IsValidDestination(destination)) {
@@ -295,7 +285,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"
@@ -358,7 +348,6 @@ static RPCHelpMan generateblock()
}
}
- CChainParams chainparams(Params());
CBlock block;
ChainstateManager& chainman = EnsureChainman(node);
@@ -366,7 +355,7 @@ static RPCHelpMan generateblock()
LOCK(cs_main);
CTxMemPool empty_mempool;
- std::unique_ptr<CBlockTemplate> blocktemplate(BlockAssembler(chainman.ActiveChainstate(), empty_mempool, chainparams).CreateNewBlock(coinbase_script));
+ std::unique_ptr<CBlockTemplate> blocktemplate(BlockAssembler{chainman.ActiveChainstate(), empty_mempool}.CreateNewBlock(coinbase_script));
if (!blocktemplate) {
throw JSONRPCError(RPC_INTERNAL_ERROR, "Couldn't create new block");
}
@@ -383,16 +372,15 @@ static RPCHelpMan generateblock()
LOCK(cs_main);
BlockValidationState state;
- if (!TestBlockValidity(state, chainparams, chainman.ActiveChainstate(), block, chainman.m_blockman.LookupBlockIndex(block.hashPrevBlock), false, false)) {
+ if (!TestBlockValidity(state, chainman.GetParams(), chainman.ActiveChainstate(), block, chainman.m_blockman.LookupBlockIndex(block.hashPrevBlock), GetAdjustedTime, false, false)) {
throw JSONRPCError(RPC_VERIFY_ERROR, strprintf("TestBlockValidity failed: %s", state.ToString()));
}
}
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.");
}
@@ -412,8 +400,8 @@ static RPCHelpMan getmininginfo()
RPCResult::Type::OBJ, "", "",
{
{RPCResult::Type::NUM, "blocks", "The current block"},
- {RPCResult::Type::NUM, "currentblockweight", /* optional */ true, "The block weight of the last assembled block (only present if a block was ever assembled)"},
- {RPCResult::Type::NUM, "currentblocktx", /* optional */ true, "The number of block transactions of the last assembled block (only present if a block was ever assembled)"},
+ {RPCResult::Type::NUM, "currentblockweight", /*optional=*/true, "The block weight of the last assembled block (only present if a block was ever assembled)"},
+ {RPCResult::Type::NUM, "currentblocktx", /*optional=*/true, "The number of block transactions of the last assembled block (only present if a block was ever assembled)"},
{RPCResult::Type::NUM, "difficulty", "The current difficulty"},
{RPCResult::Type::NUM, "networkhashps", "The network hashes per second"},
{RPCResult::Type::NUM, "pooledtx", "The size of the mempool"},
@@ -439,7 +427,7 @@ static RPCHelpMan getmininginfo()
obj.pushKV("difficulty", (double)GetDifficulty(active_chain.Tip()));
obj.pushKV("networkhashps", getnetworkhashps().HandleRequest(request));
obj.pushKV("pooledtx", (uint64_t)mempool.size());
- obj.pushKV("chain", Params().NetworkIDString());
+ obj.pushKV("chain", chainman.GetParams().NetworkIDString());
obj.pushKV("warnings", GetWarnings(false).original);
return obj;
},
@@ -472,7 +460,7 @@ static RPCHelpMan prioritisetransaction()
LOCK(cs_main);
uint256 hash(ParseHashV(request.params[0], "txid"));
- CAmount nAmount = request.params[2].get_int64();
+ CAmount nAmount = request.params[2].getInt<int64_t>();
if (!(request.params[1].isNull() || request.params[1].get_real() == 0)) {
throw JSONRPCError(RPC_INVALID_PARAMETER, "Priority is no longer supported, dummy argument to prioritisetransaction must be 0.");
@@ -553,6 +541,10 @@ static RPCHelpMan getblocktemplate()
{
{RPCResult::Type::NUM, "rulename", "identifies the bit number as indicating acceptance and readiness for the named softfork rule"},
}},
+ {RPCResult::Type::ARR, "capabilities", "",
+ {
+ {RPCResult::Type::STR, "value", "A supported feature, for example 'proposal'"},
+ }},
{RPCResult::Type::NUM, "vbrequired", "bit mask of versionbits the server requires set in submissions"},
{RPCResult::Type::STR, "previousblockhash", "The hash of current highest block"},
{RPCResult::Type::ARR, "transactions", "contents of non-coinbase transactions that should be included in the next block",
@@ -586,11 +578,12 @@ static RPCHelpMan getblocktemplate()
{RPCResult::Type::STR_HEX, "noncerange", "A range of valid nonces"},
{RPCResult::Type::NUM, "sigoplimit", "limit of sigops in blocks"},
{RPCResult::Type::NUM, "sizelimit", "limit of block size"},
- {RPCResult::Type::NUM, "weightlimit", "limit of block weight"},
+ {RPCResult::Type::NUM, "weightlimit", /*optional=*/true, "limit of block weight"},
{RPCResult::Type::NUM_TIME, "curtime", "current timestamp in " + UNIX_EPOCH_TIME},
{RPCResult::Type::STR, "bits", "compressed target of next block"},
{RPCResult::Type::NUM, "height", "The height of the next block"},
- {RPCResult::Type::STR, "default_witness_commitment", /* optional */ true, "a valid witness commitment for the unmodified block template"},
+ {RPCResult::Type::STR_HEX, "signet_challenge", /*optional=*/true, "Only on signet"},
+ {RPCResult::Type::STR_HEX, "default_witness_commitment", /*optional=*/true, "a valid witness commitment for the unmodified block template"},
}},
},
RPCExamples{
@@ -648,7 +641,7 @@ static RPCHelpMan getblocktemplate()
if (block.hashPrevBlock != pindexPrev->GetBlockHash())
return "inconclusive-not-best-prevblk";
BlockValidationState state;
- TestBlockValidity(state, Params(), active_chainstate, block, pindexPrev, false, true);
+ TestBlockValidity(state, chainman.GetParams(), active_chainstate, block, pindexPrev, GetAdjustedTime, false, true);
return BIP22ValidationResult(state);
}
@@ -662,7 +655,7 @@ static RPCHelpMan getblocktemplate()
// NOTE: It is important that this NOT be read if versionbits is supported
const UniValue& uvMaxVersion = find_value(oparam, "maxversion");
if (uvMaxVersion.isNum()) {
- nMaxVersionPreVB = uvMaxVersion.get_int64();
+ nMaxVersionPreVB = uvMaxVersion.getInt<int64_t>();
}
}
}
@@ -670,7 +663,7 @@ static RPCHelpMan getblocktemplate()
if (strMode != "template")
throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid mode");
- if (!Params().IsTestChain()) {
+ if (!chainman.GetParams().IsTestChain()) {
const CConnman& connman = EnsureConnman(node);
if (connman.GetNodeCount(ConnectionDirection::Both) == 0) {
throw JSONRPCError(RPC_CLIENT_NOT_CONNECTED, PACKAGE_NAME " is not connected!");
@@ -697,7 +690,7 @@ static RPCHelpMan getblocktemplate()
std::string lpstr = lpval.get_str();
hashWatchedChain = ParseHashV(lpstr.substr(0, 64), "longpollid");
- nTransactionsUpdatedLastLP = atoi64(lpstr.substr(64));
+ nTransactionsUpdatedLastLP = LocaleIndependentAtoi<int64_t>(lpstr.substr(64));
}
else
{
@@ -731,7 +724,7 @@ static RPCHelpMan getblocktemplate()
// TODO: Maybe recheck connections/IBD and (if something wrong) send an expires-immediately template to stop miners?
}
- const Consensus::Params& consensusParams = Params().GetConsensus();
+ const Consensus::Params& consensusParams = chainman.GetParams().GetConsensus();
// GBT must be called with 'signet' set in the rules for signet chains
if (consensusParams.signet_blocks && setClientRules.count("signet") != 1) {
@@ -760,7 +753,7 @@ static RPCHelpMan getblocktemplate()
// Create new block
CScript scriptDummy = CScript() << OP_TRUE;
- pblocktemplate = BlockAssembler(active_chainstate, mempool, Params()).CreateNewBlock(scriptDummy);
+ pblocktemplate = BlockAssembler{active_chainstate, mempool}.CreateNewBlock(scriptDummy);
if (!pblocktemplate)
throw JSONRPCError(RPC_OUT_OF_MEMORY, "Out of memory");
@@ -775,7 +768,7 @@ static RPCHelpMan getblocktemplate()
pblock->nNonce = 0;
// NOTE: If at some point we support pre-segwit miners post-segwit-activation, this needs to take segwit support into consideration
- const bool fPreSegWit = !DeploymentActiveAfter(pindexPrev, consensusParams, Consensus::DEPLOYMENT_SEGWIT);
+ const bool fPreSegWit = !DeploymentActiveAfter(pindexPrev, chainman, Consensus::DEPLOYMENT_SEGWIT);
UniValue aCaps(UniValue::VARR); aCaps.push_back("proposal");
@@ -841,7 +834,7 @@ static RPCHelpMan getblocktemplate()
UniValue vbavailable(UniValue::VOBJ);
for (int j = 0; j < (int)Consensus::MAX_VERSION_BITS_DEPLOYMENTS; ++j) {
Consensus::DeploymentPos pos = Consensus::DeploymentPos(j);
- ThresholdState state = g_versionbitscache.State(pindexPrev, consensusParams, pos);
+ ThresholdState state = chainman.m_versionbitscache.State(pindexPrev, consensusParams, pos);
switch (state) {
case ThresholdState::DEFINED:
case ThresholdState::FAILED:
@@ -849,7 +842,7 @@ static RPCHelpMan getblocktemplate()
break;
case ThresholdState::LOCKED_IN:
// Ensure bit is set in block version
- pblock->nVersion |= g_versionbitscache.Mask(consensusParams, pos);
+ pblock->nVersion |= chainman.m_versionbitscache.Mask(consensusParams, pos);
[[fallthrough]];
case ThresholdState::STARTED:
{
@@ -858,7 +851,7 @@ static RPCHelpMan getblocktemplate()
if (setClientRules.find(vbinfo.name) == setClientRules.end()) {
if (!vbinfo.gbt_force) {
// If the client doesn't support this, don't indicate it in the [default] version
- pblock->nVersion &= ~g_versionbitscache.Mask(consensusParams, pos);
+ pblock->nVersion &= ~chainman.m_versionbitscache.Mask(consensusParams, pos);
}
}
break;
@@ -935,10 +928,10 @@ class submitblock_StateCatcher final : public CValidationInterface
{
public:
uint256 hash;
- bool found;
+ bool found{false};
BlockValidationState state;
- explicit submitblock_StateCatcher(const uint256 &hashIn) : hash(hashIn), found(false), state() {}
+ explicit submitblock_StateCatcher(const uint256 &hashIn) : hash(hashIn), state() {}
protected:
void BlockChecked(const CBlock& block, const BlockValidationState& stateIn) override {
@@ -998,14 +991,14 @@ static RPCHelpMan submitblock()
LOCK(cs_main);
const CBlockIndex* pindex = chainman.m_blockman.LookupBlockIndex(block.hashPrevBlock);
if (pindex) {
- UpdateUncommittedBlockStructures(block, pindex, Params().GetConsensus());
+ chainman.UpdateUncommittedBlockStructures(block, pindex);
}
}
bool new_block;
auto sc = std::make_shared<submitblock_StateCatcher>(block.GetHash());
RegisterSharedValidationInterface(sc);
- bool accepted = chainman.ProcessNewBlock(Params(), blockptr, /* fForceProcessing */ true, /* fNewBlock */ &new_block);
+ bool accepted = chainman.ProcessNewBlock(blockptr, /*force_processing=*/true, /*new_block=*/&new_block);
UnregisterSharedValidationInterface(sc);
if (!new_block && accepted) {
return "duplicate";
@@ -1047,7 +1040,7 @@ static RPCHelpMan submitheader()
}
BlockValidationState state;
- chainman.ProcessNewBlockHeaders({h}, state, Params());
+ chainman.ProcessNewBlockHeaders({h}, state);
if (state.IsValid()) return NullUniValue;
if (state.IsError()) {
throw JSONRPCError(RPC_VERIFY_ERROR, state.ToString());
@@ -1057,219 +1050,21 @@ static RPCHelpMan submitheader()
};
}
-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")
- },
- [&](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);
-
- 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)) {
- 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()
+void RegisterMiningRPCCommands(CRPCTable& t)
{
- 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;
-},
+ static const CRPCCommand commands[]{
+ {"mining", &getnetworkhashps},
+ {"mining", &getmininginfo},
+ {"mining", &prioritisetransaction},
+ {"mining", &getblocktemplate},
+ {"mining", &submitblock},
+ {"mining", &submitheader},
+
+ {"hidden", &generatetoaddress},
+ {"hidden", &generatetodescriptor},
+ {"hidden", &generateblock},
+ {"hidden", &generate},
};
-}
-
-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 5178ce60e8..0000000000
--- a/src/rpc/misc.cpp
+++ /dev/null
@@ -1,776 +0,0 @@
-// Copyright (c) 2010 Satoshi Nakamoto
-// Copyright (c) 2009-2020 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/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/system.h>
-
-#include <stdint.h>
-#include <tuple>
-#ifdef HAVE_MALLOC_INFO
-#include <malloc.h>
-#endif
-
-#include <univalue.h>
-
-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", "The bitcoin address validated"},
- {RPCResult::Type::STR_HEX, "scriptPubKey", "The hex-encoded scriptPubKey generated by the address"},
- {RPCResult::Type::BOOL, "isscript", "If the key is a script"},
- {RPCResult::Type::BOOL, "iswitness", "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"},
- }
- },
- RPCExamples{
- HelpExampleCli("validateaddress", "\"" + EXAMPLE_ADDRESS[0] + "\"") +
- HelpExampleRpc("validateaddress", "\"" + EXAMPLE_ADDRESS[0] + "\"")
- },
- [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
-{
- std::string error_msg;
- CTxDestination dest = DecodeDestination(request.params[0].get_str(), error_msg);
- 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 {
- 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"},
- }
- },
- 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()) {
- if (!ParseOutputType(request.params[2].get_str(), output_type)) {
- throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, strprintf("Unknown address type '%s'", request.params[2].get_str()));
- }
- if (output_type == OutputType::BECH32M) {
- throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "createmultisig cannot create bech32m multisig addresses");
- }
- }
-
- // 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());
-
- return result;
-},
- };
-}
-
-static RPCHelpMan getdescriptorinfo()
-{
- 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", "\"wpkh([d34db33f/84h/0h/0h]0279be667ef9dcbbac55a06295Ce870b07029Bfcdb2dce28d959f2815b16f81798)\"")
- },
- [&](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()
-{
- 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", "\"wpkh([d34db33f/84h/0h/0h]xpub6DJ2dNUysrn5Vt36jH2KLBT2i1auw1tTSSomg8PhqNiUtx8QX2SvC9nrHu81fT41fvDUnhMjEzQgXnQjKEu3oaqMSzhSrHMxyyoEAmUHQbY/0/*)#cjjspncu\" \"[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, strprintf("Cannot derive script without private keys"));
- }
-
- for (const CScript &script : scripts) {
- CTxDestination dest;
- if (!ExtractDestination(script, dest)) {
- throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, strprintf("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",
- "\nVerify a signed message\n",
- {
- {"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;
-},
- };
-}
-
-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 is only available when compiled with glibc 2.10+");
-#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 {
- std::unique_ptr<interfaces::Echo> echo;
- if (interfaces::Ipc* ipc = Assert(EnsureAnyNodeContext(request.context).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 = interfaces::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, "", "", {
- {
- 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, },
-};
-// 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 dba0f971b2..0a061f2451 100644
--- a/src/rpc/net.cpp
+++ b/src/rpc/net.cpp
@@ -1,14 +1,14 @@
-// Copyright (c) 2009-2020 The Bitcoin Core developers
+// 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 <rpc/server.h>
+#include <addrman.h>
#include <banman.h>
#include <chainparams.h>
#include <clientversion.h>
#include <core_io.h>
-#include <net.h>
#include <net_permissions.h>
#include <net_processing.h>
#include <net_types.h> // For banmap_t
@@ -17,12 +17,12 @@
#include <policy/settings.h>
#include <rpc/blockchain.h>
#include <rpc/protocol.h>
+#include <rpc/server_util.h>
#include <rpc/util.h>
#include <sync.h>
#include <timedata.h>
#include <util/strencodings.h>
#include <util/string.h>
-#include <util/system.h>
#include <util/translation.h>
#include <validation.h>
#include <version.h>
@@ -32,6 +32,8 @@
#include <univalue.h>
+using node::NodeContext;
+
const std::vector<std::string> CONNECTION_TYPE_DOC{
"outbound-full-relay (default automatic connections)",
"block-relay-only (does not relay transactions or addresses)",
@@ -41,22 +43,6 @@ const std::vector<std::string> CONNECTION_TYPE_DOC{
"feeler (short-lived automatic connection for testing addresses)"
};
-CConnman& EnsureConnman(const NodeContext& node)
-{
- if (!node.connman) {
- throw JSONRPCError(RPC_CLIENT_P2P_DISABLED, "Error: Peer-to-peer functionality missing or disabled");
- }
- return *node.connman;
-}
-
-PeerManager& EnsurePeerman(const NodeContext& node)
-{
- if (!node.peerman) {
- throw JSONRPCError(RPC_CLIENT_P2P_DISABLED, "Error: Peer-to-peer functionality missing or disabled");
- }
- return *node.peerman;
-}
-
static RPCHelpMan getconnectioncount()
{
return RPCHelpMan{"getconnectioncount",
@@ -105,79 +91,83 @@ static RPCHelpMan ping()
static RPCHelpMan getpeerinfo()
{
- return RPCHelpMan{"getpeerinfo",
- "\nReturns data about each connected network node as a json array of objects.\n",
- {},
- RPCResult{
- RPCResult::Type::ARR, "", "",
+ return RPCHelpMan{
+ "getpeerinfo",
+ "Returns data about each connected network peer as a json array of objects.",
+ {},
+ RPCResult{
+ RPCResult::Type::ARR, "", "",
+ {
+ {RPCResult::Type::OBJ, "", "",
+ {
{
- {RPCResult::Type::OBJ, "", "",
- {
- {
- {RPCResult::Type::NUM, "id", "Peer index"},
- {RPCResult::Type::STR, "addr", "(host:port) The IP address and port of the peer"},
- {RPCResult::Type::STR, "addrbind", "(ip:port) Bind address of the connection to the peer"},
- {RPCResult::Type::STR, "addrlocal", "(ip:port) Local address as reported by the peer"},
- {RPCResult::Type::STR, "network", "Network (" + Join(GetNetworkNames(/* append_unroutable */ true), ", ") + ")"},
- {RPCResult::Type::NUM, "mapped_as", "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"},
- {RPCResult::Type::ARR, "servicesnames", "the services offered, in human-readable form",
- {
- {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::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"},
- {RPCResult::Type::NUM_TIME, "last_block", "The " + UNIX_EPOCH_TIME + " of the last block received from this peer"},
- {RPCResult::Type::NUM, "bytessent", "The total bytes sent"},
- {RPCResult::Type::NUM, "bytesrecv", "The total bytes received"},
- {RPCResult::Type::NUM_TIME, "conntime", "The " + UNIX_EPOCH_TIME + " of the connection"},
- {RPCResult::Type::NUM, "timeoffset", "The time offset in seconds"},
- {RPCResult::Type::NUM, "pingtime", "ping time (if available)"},
- {RPCResult::Type::NUM, "minping", "minimum observed ping time (if any at all)"},
- {RPCResult::Type::NUM, "pingwait", "ping wait (if non-zero)"},
- {RPCResult::Type::NUM, "version", "The peer version, such as 70001"},
- {RPCResult::Type::STR, "subver", "The string version"},
- {RPCResult::Type::BOOL, "inbound", "Inbound (true) or Outbound (false)"},
- {RPCResult::Type::BOOL, "bip152_hb_to", "Whether we selected peer as (compact blocks) high-bandwidth peer"},
- {RPCResult::Type::BOOL, "bip152_hb_from", "Whether peer selected us as (compact blocks) high-bandwidth peer"},
- {RPCResult::Type::NUM, "startingheight", "The starting height (block) of the peer"},
- {RPCResult::Type::NUM, "synced_headers", "The last header we have in common with this peer"},
- {RPCResult::Type::NUM, "synced_blocks", "The last block we have in common with this peer"},
- {RPCResult::Type::ARR, "inflight", "",
- {
- {RPCResult::Type::NUM, "n", "The heights of blocks we're currently asking from this peer"},
- }},
- {RPCResult::Type::ARR, "permissions", "Any special permissions that have been granted to this peer",
- {
- {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::OBJ_DYN, "bytessent_per_msg", "",
- {
- {RPCResult::Type::NUM, "msg", "The total bytes sent aggregated by message type\n"
- "When a message type is not listed in this json object, the bytes sent are 0.\n"
- "Only known message types can appear as keys in the object."}
- }},
- {RPCResult::Type::OBJ_DYN, "bytesrecv_per_msg", "",
- {
- {RPCResult::Type::NUM, "msg", "The total bytes received aggregated by message type\n"
- "When a message type is not listed in this json object, the bytes received are 0.\n"
- "Only known message types can appear as keys in the object and all bytes received\n"
- "of unknown message types are listed under '"+NET_MESSAGE_COMMAND_OTHER+"'."}
- }},
- {RPCResult::Type::STR, "connection_type", "Type of connection: \n" + Join(CONNECTION_TYPE_DOC, ",\n") + ".\n"
- "Please note this output is unlikely to be stable in upcoming releases as we iterate to\n"
- "best capture connection behaviors."},
- }},
+ {RPCResult::Type::NUM, "id", "Peer index"},
+ {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::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"},
+ {RPCResult::Type::ARR, "servicesnames", "the services offered, in human-readable form",
+ {
+ {RPCResult::Type::STR, "SERVICE_NAME", "the service name if it is recognised"}
}},
- },
- RPCExamples{
- HelpExampleCli("getpeerinfo", "")
+ {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"},
+ {RPCResult::Type::NUM_TIME, "last_block", "The " + UNIX_EPOCH_TIME + " of the last block received from this peer"},
+ {RPCResult::Type::NUM, "bytessent", "The total bytes sent"},
+ {RPCResult::Type::NUM, "bytesrecv", "The total bytes received"},
+ {RPCResult::Type::NUM_TIME, "conntime", "The " + UNIX_EPOCH_TIME + " of the connection"},
+ {RPCResult::Type::NUM, "timeoffset", "The time offset in seconds"},
+ {RPCResult::Type::NUM, "pingtime", /*optional=*/true, "ping time (if available)"},
+ {RPCResult::Type::NUM, "minping", /*optional=*/true, "minimum observed ping time (if any at all)"},
+ {RPCResult::Type::NUM, "pingwait", /*optional=*/true, "ping wait (if non-zero)"},
+ {RPCResult::Type::NUM, "version", "The peer version, such as 70001"},
+ {RPCResult::Type::STR, "subver", "The string version"},
+ {RPCResult::Type::BOOL, "inbound", "Inbound (true) or Outbound (false)"},
+ {RPCResult::Type::BOOL, "bip152_hb_to", "Whether we selected peer as (compact blocks) high-bandwidth peer"},
+ {RPCResult::Type::BOOL, "bip152_hb_from", "Whether peer selected us as (compact blocks) high-bandwidth peer"},
+ {RPCResult::Type::NUM, "startingheight", /*optional=*/true, "The starting height (block) of the peer"},
+ {RPCResult::Type::NUM, "synced_headers", /*optional=*/true, "The last header we have in common with this peer"},
+ {RPCResult::Type::NUM, "synced_blocks", /*optional=*/true, "The last block we have in common with this peer"},
+ {RPCResult::Type::ARR, "inflight", /*optional=*/true, "",
+ {
+ {RPCResult::Type::NUM, "n", "The heights of blocks we're currently asking from this peer"},
+ }},
+ {RPCResult::Type::BOOL, "addr_relay_enabled", /*optional=*/true, "Whether we participate in address relay with this peer"},
+ {RPCResult::Type::NUM, "addr_processed", /*optional=*/true, "The total number of addresses processed, excluding those dropped due to rate limiting"},
+ {RPCResult::Type::NUM, "addr_rate_limited", /*optional=*/true, "The total number of addresses dropped due to rate limiting"},
+ {RPCResult::Type::ARR, "permissions", "Any special permissions that have been granted to this peer",
+ {
+ {RPCResult::Type::STR, "permission_type", Join(NET_PERMISSIONS_DOC, ",\n") + ".\n"},
+ }},
+ {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"
+ "When a message type is not listed in this json object, the bytes sent are 0.\n"
+ "Only known message types can appear as keys in the object."}
+ }},
+ {RPCResult::Type::OBJ_DYN, "bytesrecv_per_msg", "",
+ {
+ {RPCResult::Type::NUM, "msg", "The total bytes received aggregated by message type\n"
+ "When a message type is not listed in this json object, the bytes received are 0.\n"
+ "Only known message types can appear as keys in the object and all bytes received\n"
+ "of unknown message types are listed under '"+NET_MESSAGE_TYPE_OTHER+"'."}
+ }},
+ {RPCResult::Type::STR, "connection_type", "Type of connection: \n" + Join(CONNECTION_TYPE_DOC, ",\n") + ".\n"
+ "Please note this output is unlikely to be stable in upcoming releases as we iterate to\n"
+ "best capture connection behaviors."},
+ }},
+ }},
+ },
+ RPCExamples{
+ HelpExampleCli("getpeerinfo", "")
+ HelpExampleRpc("getpeerinfo", "")
- },
+ },
[&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
{
NodeContext& node = EnsureAnyNodeContext(request.context);
@@ -194,7 +184,7 @@ static RPCHelpMan getpeerinfo()
CNodeStateStats statestats;
bool fStateStats = peerman.GetNodeStateStats(stats.nodeid, statestats);
obj.pushKV("id", stats.nodeid);
- obj.pushKV("addr", stats.addrName);
+ obj.pushKV("addr", stats.m_addr_name);
if (stats.addrBind.IsValid()) {
obj.pushKV("addrbind", stats.addrBind.ToString());
}
@@ -207,14 +197,13 @@ static RPCHelpMan getpeerinfo()
}
obj.pushKV("services", strprintf("%016x", stats.nServices));
obj.pushKV("servicesnames", GetServicesNames(stats.nServices));
- obj.pushKV("relaytxes", stats.fRelayTxes);
- obj.pushKV("lastsend", stats.nLastSend);
- obj.pushKV("lastrecv", stats.nLastRecv);
- obj.pushKV("last_transaction", stats.nLastTXTime);
- obj.pushKV("last_block", stats.nLastBlockTime);
+ 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));
+ obj.pushKV("last_block", count_seconds(stats.m_last_block_time));
obj.pushKV("bytessent", stats.nSendBytes);
obj.pushKV("bytesrecv", stats.nRecvBytes);
- obj.pushKV("conntime", stats.nTimeConnected);
+ obj.pushKV("conntime", count_seconds(stats.m_connected));
obj.pushKV("timeoffset", stats.nTimeOffset);
if (stats.m_last_ping_time > 0us) {
obj.pushKV("pingtime", CountSecondsDouble(stats.m_last_ping_time));
@@ -242,6 +231,9 @@ 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);
}
@@ -250,21 +242,20 @@ static RPCHelpMan getpeerinfo()
permissions.push_back(permission);
}
obj.pushKV("permissions", permissions);
- obj.pushKV("minfeefilter", ValueFromAmount(stats.minFeeFilter));
- UniValue sendPerMsgCmd(UniValue::VOBJ);
- for (const auto& i : stats.mapSendBytesPerMsgCmd) {
+ UniValue sendPerMsgType(UniValue::VOBJ);
+ for (const auto& i : stats.mapSendBytesPerMsgType) {
if (i.second > 0)
- sendPerMsgCmd.pushKV(i.first, i.second);
+ sendPerMsgType.pushKV(i.first, i.second);
}
- obj.pushKV("bytessent_per_msg", sendPerMsgCmd);
+ obj.pushKV("bytessent_per_msg", sendPerMsgType);
- UniValue recvPerMsgCmd(UniValue::VOBJ);
- for (const auto& i : stats.mapRecvBytesPerMsgCmd) {
+ UniValue recvPerMsgType(UniValue::VOBJ);
+ for (const auto& i : stats.mapRecvBytesPerMsgType) {
if (i.second > 0)
- recvPerMsgCmd.pushKV(i.first, i.second);
+ recvPerMsgType.pushKV(i.first, i.second);
}
- obj.pushKV("bytesrecv_per_msg", recvPerMsgCmd);
+ obj.pushKV("bytesrecv_per_msg", recvPerMsgType);
obj.pushKV("connection_type", ConnectionTypeAsString(stats.m_conn_type));
ret.push_back(obj);
@@ -339,7 +330,7 @@ static RPCHelpMan addconnection()
"\nOpen an outbound connection to a specified node. This RPC is for testing only.\n",
{
{"address", RPCArg::Type::STR, RPCArg::Optional::NO, "The IP address and port to attempt connecting to."},
- {"connection_type", RPCArg::Type::STR, RPCArg::Optional::NO, "Type of connection to open (\"outbound-full-relay\", \"block-relay-only\" or \"addr-fetch\")."},
+ {"connection_type", RPCArg::Type::STR, RPCArg::Optional::NO, "Type of connection to open (\"outbound-full-relay\", \"block-relay-only\", \"addr-fetch\" or \"feeler\")."},
},
RPCResult{
RPCResult::Type::OBJ, "", "",
@@ -367,6 +358,8 @@ static RPCHelpMan addconnection()
conn_type = ConnectionType::BLOCK_RELAY;
} else if (conn_type_in == "addr-fetch") {
conn_type = ConnectionType::ADDR_FETCH;
+ } else if (conn_type_in == "feeler") {
+ conn_type = ConnectionType::FEELER;
} else {
throw JSONRPCError(RPC_INVALID_PARAMETER, self.ToString());
}
@@ -419,7 +412,7 @@ static RPCHelpMan disconnectnode()
success = connman.DisconnectNode(address_arg.get_str());
} else if (!id_arg.isNull() && (address_arg.isNull() || (address_arg.isStr() && address_arg.get_str().empty()))) {
/* handle disconnect-by-id */
- NodeId nodeid = (NodeId) id_arg.get_int64();
+ NodeId nodeid = (NodeId) id_arg.getInt<int64_t>();
success = connman.DisconnectNode(nodeid);
} else {
throw JSONRPCError(RPC_INVALID_PARAMS, "Only one of address and nodeid should be provided.");
@@ -562,8 +555,8 @@ static UniValue GetNetworksInfo()
UniValue networks(UniValue::VARR);
for (int n = 0; n < NET_MAX; ++n) {
enum Network network = static_cast<enum Network>(n);
- if (network == NET_UNROUTABLE || network == NET_CJDNS || network == NET_INTERNAL) continue;
- proxyType proxy;
+ if (network == NET_UNROUTABLE || network == NET_INTERNAL) continue;
+ Proxy proxy;
UniValue obj(UniValue::VOBJ);
GetProxy(network, proxy);
obj.pushKV("name", GetNetworkName(network));
@@ -655,7 +648,7 @@ static RPCHelpMan getnetworkinfo()
obj.pushKV("incrementalfee", ValueFromAmount(::incrementalRelayFee.GetFeePerK()));
UniValue localAddresses(UniValue::VARR);
{
- LOCK(cs_mapLocalHost);
+ LOCK(g_maplocalhost_mutex);
for (const std::pair<const CNetAddr, LocalServiceInfo> &item : mapLocalHost)
{
UniValue rec(UniValue::VOBJ);
@@ -727,7 +720,7 @@ static RPCHelpMan setban()
int64_t banTime = 0; //use standard bantime if not specified
if (!request.params[2].isNull())
- banTime = request.params[2].get_int64();
+ banTime = request.params[2].getInt<int64_t>();
bool absolute = false;
if (request.params[3].isTrue())
@@ -854,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(), ", ") + "."},
@@ -884,7 +879,7 @@ static RPCHelpMan getnodeaddresses()
NodeContext& node = EnsureAnyNodeContext(request.context);
const CConnman& connman = EnsureConnman(node);
- const int count{request.params[0].isNull() ? 1 : request.params[0].get_int()};
+ const int count{request.params[0].isNull() ? 1 : request.params[0].getInt<int>()};
if (count < 0) throw JSONRPCError(RPC_INVALID_PARAMETER, "Address count out of range");
const std::optional<Network> network{request.params[1].isNull() ? std::nullopt : std::optional<Network>{ParseNetwork(request.params[1].get_str())}};
@@ -893,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) {
@@ -917,6 +912,7 @@ static RPCHelpMan addpeeraddress()
{
{"address", RPCArg::Type::STR, RPCArg::Optional::NO, "The IP address of the peer"},
{"port", RPCArg::Type::NUM, RPCArg::Optional::NO, "The port of the peer"},
+ {"tried", RPCArg::Type::BOOL, RPCArg::Default{false}, "If true, attempt to add the peer to the tried addresses table"},
},
RPCResult{
RPCResult::Type::OBJ, "", "",
@@ -925,8 +921,8 @@ static RPCHelpMan addpeeraddress()
},
},
RPCExamples{
- HelpExampleCli("addpeeraddress", "\"1.2.3.4\" 8333")
- + HelpExampleRpc("addpeeraddress", "\"1.2.3.4\", 8333")
+ HelpExampleCli("addpeeraddress", "\"1.2.3.4\" 8333 true")
+ + HelpExampleRpc("addpeeraddress", "\"1.2.3.4\", 8333, true")
},
[&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
{
@@ -936,7 +932,8 @@ static RPCHelpMan addpeeraddress()
}
const std::string& addr_string{request.params[0].get_str()};
- const uint16_t port{static_cast<uint16_t>(request.params[1].get_int())};
+ const auto port{request.params[1].getInt<uint16_t>()};
+ const bool tried{request.params[2].isTrue()};
UniValue obj(UniValue::VOBJ);
CNetAddr net_addr;
@@ -947,7 +944,13 @@ static RPCHelpMan addpeeraddress()
address.nTime = GetAdjustedTime();
// The source address is set equal to the address. This is equivalent to the peer
// announcing itself.
- if (node.addrman->Add(address, address)) success = true;
+ if (node.addrman->Add({address}, address)) {
+ success = true;
+ if (tried) {
+ // Attempt to move the address to the tried addresses table.
+ node.addrman->Good(address);
+ }
+ }
}
obj.pushKV("success", success);
@@ -956,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/net.h b/src/rpc/net.h
deleted file mode 100644
index 7a4d8440ba..0000000000
--- a/src/rpc/net.h
+++ /dev/null
@@ -1,15 +0,0 @@
-// Copyright (c) 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.
-
-#ifndef BITCOIN_RPC_NET_H
-#define BITCOIN_RPC_NET_H
-
-class CConnman;
-class PeerManager;
-struct NodeContext;
-
-CConnman& EnsureConnman(const NodeContext& node);
-PeerManager& EnsurePeerman(const NodeContext& node);
-
-#endif // BITCOIN_RPC_NET_H
diff --git a/src/rpc/node.cpp b/src/rpc/node.cpp
new file mode 100644
index 0000000000..5475662b82
--- /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].getInt<int64_t>()};
+ 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].getInt<int64_t>();
+ 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..f4bb76f50f
--- /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].getInt<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 (descriptor->GetOutputType() != 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.empty()) 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/protocol.h b/src/rpc/protocol.h
index fc00a1efad..75e42e4c88 100644
--- a/src/rpc/protocol.h
+++ b/src/rpc/protocol.h
@@ -1,5 +1,5 @@
// Copyright (c) 2010 Satoshi Nakamoto
-// Copyright (c) 2009-2019 The Bitcoin Core developers
+// 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.
@@ -80,6 +80,7 @@ enum RPCErrorCode
RPC_WALLET_NOT_FOUND = -18, //!< Invalid wallet specified
RPC_WALLET_NOT_SPECIFIED = -19, //!< No wallet specified (error when there are multiple wallets loaded)
RPC_WALLET_ALREADY_LOADED = -35, //!< This same wallet is already loaded
+ RPC_WALLET_ALREADY_EXISTS = -36, //!< There is already a wallet with the same name
//! Backwards compatible aliases
RPC_WALLET_INVALID_ACCOUNT_NAME = RPC_WALLET_INVALID_LABEL_NAME,
diff --git a/src/rpc/rawtransaction.cpp b/src/rpc/rawtransaction.cpp
index 617dfec98f..b9b8c36bb3 100644
--- a/src/rpc/rawtransaction.cpp
+++ b/src/rpc/rawtransaction.cpp
@@ -1,15 +1,16 @@
// Copyright (c) 2010 Satoshi Nakamoto
-// Copyright (c) 2009-2020 The Bitcoin Core developers
+// 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 <base58.h>
#include <chain.h>
#include <coins.h>
+#include <consensus/amount.h>
#include <consensus/validation.h>
#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>
@@ -24,6 +25,7 @@
#include <rpc/blockchain.h>
#include <rpc/rawtransaction_util.h>
#include <rpc/server.h>
+#include <rpc/server_util.h>
#include <rpc/util.h>
#include <script/script.h>
#include <script/sign.h>
@@ -31,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>
@@ -42,6 +45,14 @@
#include <univalue.h>
+using node::AnalyzePSBT;
+using node::BroadcastTransaction;
+using node::FindCoins;
+using node::GetTransaction;
+using node::NodeContext;
+using node::PSBTAnalysis;
+using node::ReadBlockFromDisk;
+
static void TxToJSON(const CTransaction& tx, const uint256 hashBlock, UniValue& entry, CChainState& active_chainstate)
{
// Call into TxToUniv() in bitcoin-common to decode the transaction hex.
@@ -49,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);
@@ -68,22 +79,105 @@ 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 {
+ {"inputs", RPCArg::Type::ARR, RPCArg::Optional::NO, "The inputs",
+ {
+ {"", RPCArg::Type::OBJ, RPCArg::Optional::OMITTED, "",
+ {
+ {"txid", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "The transaction id"},
+ {"vout", RPCArg::Type::NUM, RPCArg::Optional::NO, "The output number"},
+ {"sequence", RPCArg::Type::NUM, RPCArg::DefaultHint{"depends on the value of the 'replaceable' and 'locktime' arguments"}, "The sequence number"},
+ },
+ },
+ },
+ },
+ {"outputs", RPCArg::Type::ARR, RPCArg::Optional::NO, "The outputs (key-value pairs), where none of the keys are duplicated.\n"
+ "That is, each address can only appear once and there can only be one 'data' object.\n"
+ "For compatibility reasons, a dictionary, which holds the key-value pairs directly, is also\n"
+ " accepted as second parameter.",
+ {
+ {"", RPCArg::Type::OBJ_USER_KEYS, RPCArg::Optional::OMITTED, "",
+ {
+ {"address", RPCArg::Type::AMOUNT, RPCArg::Optional::NO, "A key-value pair. The key (string) is the bitcoin address, the value (float or string) is the amount in " + CURRENCY_UNIT},
+ },
+ },
+ {"", RPCArg::Type::OBJ, RPCArg::Optional::OMITTED, "",
+ {
+ {"data", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "A key-value pair. The key must be \"data\", the value is hex-encoded data"},
+ },
+ },
+ },
+ },
+ {"locktime", RPCArg::Type::NUM, RPCArg::Default{0}, "Raw locktime. Non-0 value also locktime-activates inputs"},
+ {"replaceable", RPCArg::Type::BOOL, RPCArg::Default{false}, "Marks this transaction as BIP125-replaceable.\n"
+ "Allows this transaction to be replaced by a transaction with higher fees. If provided, it is an error if explicit sequence numbers are incompatible."},
+ };
+}
+
static RPCHelpMan getrawtransaction()
{
return RPCHelpMan{
"getrawtransaction",
- "\nReturn the raw transaction data.\n"
-
- "\nBy default this function only works for mempool transactions. When called with a blockhash\n"
- "argument, getrawtransaction will return the transaction if the specified block is available and\n"
- "the transaction is found in that block. When called without a blockhash argument, getrawtransaction\n"
- "will return the transaction if it is in the mempool, or if -txindex is enabled and the transaction\n"
- "is in a block in the blockchain.\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"
+ "If a blockhash argument is passed, it will return the transaction if\n"
+ "the specified block is available and the transaction is in that block.\n"
"\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"},
@@ -95,59 +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", "Whether specified block is in the active chain or not (only present with explicit \"blockhash\" argument)"},
+ {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, "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'"},
- {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", "",
- {
- {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::NUM, "reqSigs", /* optional */ true, "(DEPRECATED, returned only if config option -deprecatedrpc=addresses is passed) Number of required signatures"},
- {RPCResult::Type::STR, "type", "The type, eg 'pubkeyhash'"},
- {RPCResult::Type::STR, "address", /* optional */ true, "bitcoin address (only if a well-defined address exists)"},
- {RPCResult::Type::ARR, "addresses", /* optional */ true, "(DEPRECATED, returned only if config option -deprecatedrpc=addresses is passed) Array of bitcoin addresses",
- {
- {RPCResult::Type::STR, "address", "bitcoin address"},
- }},
- }},
- }},
- }},
- {RPCResult::Type::STR_HEX, "blockhash", "the block hash"},
- {RPCResult::Type::NUM, "confirmations", "The confirmations"},
- {RPCResult::Type::NUM_TIME, "blocktime", "The block time expressed in " + UNIX_EPOCH_TIME},
- {RPCResult::Type::NUM, "time", "Same as \"blocktime\""},
- }
+ },
+ DecodeTxDoc(/*txid_field_doc=*/"The transaction id (same as provided)")),
},
},
RPCExamples{
@@ -164,9 +215,9 @@ 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) {
+ if (hash == chainman.GetParams().GenesisBlock().hashMerkleRoot) {
// Special exception for the genesis block coinbase transaction
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "The genesis block coinbase is not considered an ordinary transaction and cannot be retrieved");
}
@@ -174,7 +225,7 @@ static RPCHelpMan getrawtransaction()
// Accept either a bool (true) or a num (>=1) to indicate verbose output.
bool fVerbose = false;
if (!request.params[1].isNull()) {
- fVerbose = request.params[1].isNum() ? (request.params[1].get_int() != 0) : request.params[1].get_bool();
+ fVerbose = request.params[1].isNum() ? (request.params[1].getInt<int>() != 0) : request.params[1].get_bool();
}
if (!request.params[2].isNull()) {
@@ -194,11 +245,12 @@ static RPCHelpMan getrawtransaction()
}
uint256 hash_block;
- const CTransactionRef tx = GetTransaction(blockindex, node.mempool.get(), hash, Params().GetConsensus(), hash_block);
+ const CTransactionRef tx = GetTransaction(blockindex, node.mempool.get(), hash, chainman.GetConsensus(), hash_block);
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";
@@ -224,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",
@@ -381,39 +284,7 @@ static RPCHelpMan createrawtransaction()
"Returns hex-encoded raw transaction.\n"
"Note that the transaction's inputs are not signed, and\n"
"it is not stored in the wallet or transmitted to the network.\n",
- {
- {"inputs", RPCArg::Type::ARR, RPCArg::Optional::NO, "The inputs",
- {
- {"", RPCArg::Type::OBJ, RPCArg::Optional::OMITTED, "",
- {
- {"txid", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "The transaction id"},
- {"vout", RPCArg::Type::NUM, RPCArg::Optional::NO, "The output number"},
- {"sequence", RPCArg::Type::NUM, RPCArg::DefaultHint{"depends on the value of the 'replaceable' and 'locktime' arguments"}, "The sequence number"},
- },
- },
- },
- },
- {"outputs", RPCArg::Type::ARR, RPCArg::Optional::NO, "The outputs (key-value pairs), where none of the keys are duplicated.\n"
- "That is, each address can only appear once and there can only be one 'data' object.\n"
- "For compatibility reasons, a dictionary, which holds the key-value pairs directly, is also\n"
- " accepted as second parameter.",
- {
- {"", RPCArg::Type::OBJ_USER_KEYS, RPCArg::Optional::OMITTED, "",
- {
- {"address", RPCArg::Type::AMOUNT, RPCArg::Optional::NO, "A key-value pair. The key (string) is the bitcoin address, the value (float or string) is the amount in " + CURRENCY_UNIT},
- },
- },
- {"", RPCArg::Type::OBJ, RPCArg::Optional::OMITTED, "",
- {
- {"data", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "A key-value pair. The key must be \"data\", the value is hex-encoded data"},
- },
- },
- },
- },
- {"locktime", RPCArg::Type::NUM, RPCArg::Default{0}, "Raw locktime. Non-0 value also locktime-activates inputs"},
- {"replaceable", RPCArg::Type::BOOL, RPCArg::Default{false}, "Marks this transaction as BIP125-replaceable.\n"
- " Allows this transaction to be replaced by a transaction with higher fees. If provided, it is an error if explicit sequence numbers are incompatible."},
- },
+ CreateTxDoc(),
RPCResult{
RPCResult::Type::STR_HEX, "transaction", "hex string of the transaction"
},
@@ -447,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"
@@ -460,53 +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, "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::ARR, "txinwitness", "",
- {
- {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::NUM, "reqSigs", /* optional */ true, "(DEPRECATED, returned only if config option -deprecatedrpc=addresses is passed) Number of required signatures"},
- {RPCResult::Type::STR, "type", "The type, eg 'pubkeyhash'"},
- {RPCResult::Type::STR, "address", /* optional */ true, "bitcoin address (only if a well-defined address exists)"},
- {RPCResult::Type::ARR, "addresses", /* optional */ true, "(DEPRECATED, returned only if config option -deprecatedrpc=addresses is passed) Array of bitcoin addresses",
- {
- {RPCResult::Type::STR, "address", "bitcoin address"},
- }},
- }},
- }},
- }},
- }
+ DecodeTxDoc(/*txid_field_doc=*/"The transaction id"),
},
RPCExamples{
HelpExampleCli("decoderawtransaction", "\"hexstring\"")
@@ -526,61 +351,46 @@ 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{"decodescript",
- "\nDecode a hex-encoded script.\n",
- {
- {"hexstring", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "the hex-encoded script"},
- },
- RPCResult{
- RPCResult::Type::OBJ, "", "",
- {
- {RPCResult::Type::STR, "asm", "Script public key"},
- {RPCResult::Type::STR, "type", "The output type (e.g. "+GetAllOutputTypes()+")"},
- {RPCResult::Type::STR, "address", /* optional */ true, "bitcoin address (only if a well-defined address exists)"},
- {RPCResult::Type::NUM, "reqSigs", /* optional */ true, "(DEPRECATED, returned only if config option -deprecatedrpc=addresses is passed) Number of required signatures"},
- {RPCResult::Type::ARR, "addresses", /* optional */ true, "(DEPRECATED, returned only if config option -deprecatedrpc=addresses is passed) Array of bitcoin addresses",
- {
- {RPCResult::Type::STR, "address", "bitcoin address"},
- }},
- {RPCResult::Type::STR, "p2sh", "address of P2SH script wrapping this redeem script (not returned if the script is already a P2SH)"},
- {RPCResult::Type::OBJ, "segwit", "Result of a witness script public key wrapping this redeem script (not returned if the script is a P2SH or witness)",
- {
- {RPCResult::Type::STR, "asm", "String representation of the script public key"},
- {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, "bitcoin address (only if a well-defined address exists)"},
- {RPCResult::Type::NUM, "reqSigs", /* optional */ true, "(DEPRECATED, returned only if config option -deprecatedrpc=addresses is passed) Number of required signatures"},
- {RPCResult::Type::ARR, "addresses", /* optional */ true, "(DEPRECATED, returned only if config option -deprecatedrpc=addresses is passed) Array of bitcoin addresses",
- {
- {RPCResult::Type::STR, "address", "segwit address"},
- }},
- {RPCResult::Type::STR, "p2sh-segwit", "address of the P2SH script wrapping this witness redeem script"},
- }},
- }
- },
- RPCExamples{
- HelpExampleCli("decodescript", "\"hexstring\"")
- + HelpExampleRpc("decodescript", "\"hexstring\"")
- },
+ return RPCHelpMan{
+ "decodescript",
+ "\nDecode a hex-encoded script.\n",
+ {
+ {"hexstring", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "the hex-encoded script"},
+ },
+ RPCResult{
+ 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,
+ "address of P2SH script wrapping this redeem script (not returned for types that should not be wrapped)"},
+ {RPCResult::Type::OBJ, "segwit", /*optional=*/true,
+ "Result of a witness script public key wrapping this redeem script (not returned for types that should not be wrapped)",
+ {
+ {RPCResult::Type::STR, "asm", "String representation of the script public key"},
+ {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"},
+ }},
+ },
+ },
+ RPCExamples{
+ HelpExampleCli("decodescript", "\"hexstring\"")
+ + HelpExampleRpc("decodescript", "\"hexstring\"")
+ },
[&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
{
RPCTypeCheck(request.params, {UniValue::VSTR});
@@ -593,29 +403,73 @@ static RPCHelpMan decodescript()
} else {
// Empty scripts are valid
}
- ScriptPubKeyToUniv(script, r, /* fIncludeHex */ false);
-
- UniValue type;
- type = find_value(r, "type");
+ 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)};
+
+ const bool can_wrap{[&] {
+ switch (which_type) {
+ case TxoutType::MULTISIG:
+ case TxoutType::NONSTANDARD:
+ case TxoutType::PUBKEY:
+ case TxoutType::PUBKEYHASH:
+ case TxoutType::WITNESS_V0_KEYHASH:
+ case TxoutType::WITNESS_V0_SCRIPTHASH:
+ // Can be wrapped if the checks below pass
+ break;
+ case TxoutType::NULL_DATA:
+ case TxoutType::SCRIPTHASH:
+ case TxoutType::WITNESS_UNKNOWN:
+ case TxoutType::WITNESS_V1_TAPROOT:
+ // Should not be wrapped
+ return false;
+ } // no default case, so the compiler can warn about missing cases
+ if (!script.HasValidOps() || script.IsUnspendable()) {
+ return false;
+ }
+ for (CScript::const_iterator it{script.begin()}; it != script.end();) {
+ opcodetype op;
+ CHECK_NONFATAL(script.GetOp(it, op));
+ if (op == OP_CHECKSIGADD || IsOpSuccess(op)) {
+ return false;
+ }
+ }
+ return true;
+ }()};
- if (type.isStr() && type.get_str() != "scripthash") {
- // P2SH cannot be wrapped in a P2SH. If this script is already a P2SH,
- // don't return the address for a P2SH of the P2SH.
+ if (can_wrap) {
r.pushKV("p2sh", EncodeDestination(ScriptHash(script)));
// P2SH and witness programs cannot be wrapped in P2WSH, if this script
// is a witness program, don't return addresses for a segwit programs.
- if (type.get_str() == "pubkey" || type.get_str() == "pubkeyhash" || type.get_str() == "multisig" || type.get_str() == "nonstandard") {
- std::vector<std::vector<unsigned char>> solutions_data;
- TxoutType which_type = Solver(script, solutions_data);
+ const bool can_wrap_P2WSH{[&] {
+ switch (which_type) {
+ case TxoutType::MULTISIG:
+ case TxoutType::PUBKEY:
// Uncompressed pubkeys cannot be used with segwit checksigs.
// If the script contains an uncompressed pubkey, skip encoding of a segwit program.
- if ((which_type == TxoutType::PUBKEY) || (which_type == TxoutType::MULTISIG)) {
for (const auto& solution : solutions_data) {
if ((solution.size() != 1) && !CPubKey(solution).IsCompressed()) {
- return r;
+ return false;
}
}
- }
+ return true;
+ case TxoutType::NONSTANDARD:
+ case TxoutType::PUBKEYHASH:
+ // Can be P2WSH wrapped
+ return true;
+ case TxoutType::NULL_DATA:
+ case TxoutType::SCRIPTHASH:
+ case TxoutType::WITNESS_UNKNOWN:
+ case TxoutType::WITNESS_V0_KEYHASH:
+ case TxoutType::WITNESS_V0_SCRIPTHASH:
+ case TxoutType::WITNESS_V1_TAPROOT:
+ // Should not be wrapped
+ return false;
+ } // no default case, so the compiler can warn about missing cases
+ NONFATAL_UNREACHABLE();
+ }()};
+ if (can_wrap_P2WSH) {
UniValue sr(UniValue::VOBJ);
CScript segwitScr;
if (which_type == TxoutType::PUBKEY) {
@@ -624,10 +478,9 @@ static RPCHelpMan decodescript()
segwitScr = GetScriptForDestination(WitnessV0KeyHash(uint160{solutions_data[0]}));
} else {
// Scripts that are not fit for P2WPKH are encoded as P2WSH.
- // Newer segwit program versions should be considered when then become available.
segwitScr = GetScriptForDestination(WitnessV0ScriptHash(script));
}
- ScriptPubKeyToUniv(segwitScr, sr, /* fIncludeHex */ true);
+ ScriptToUniv(segwitScr, /*out=*/sr, /*include_hex=*/true, /*include_address=*/true);
sr.pushKV("p2sh-segwit", EncodeDestination(ScriptHash(segwitScr)));
r.pushKV("segwit", sr);
}
@@ -714,7 +567,7 @@ static RPCHelpMan combinerawtransaction()
sigdata.MergeSignatureData(DataFromTransaction(txv, i, coin.out));
}
}
- ProduceSignature(DUMMY_SIGNING_PROVIDER, MutableTransactionSignatureCreator(&mergedTx, i, coin.out.nValue, 1), coin.out.scriptPubKey, sigdata);
+ ProduceSignature(DUMMY_SIGNING_PROVIDER, MutableTransactionSignatureCreator(mergedTx, i, coin.out.nValue, 1), coin.out.scriptPubKey, sigdata);
UpdateInput(txin, sigdata);
}
@@ -753,7 +606,7 @@ static RPCHelpMan signrawtransactionwithkey()
},
},
},
- {"sighashtype", RPCArg::Type::STR, RPCArg::Default{"DEFAULT"}, "The signature hash type. Must be one of:\n"
+ {"sighashtype", RPCArg::Type::STR, RPCArg::Default{"DEFAULT for Taproot, ALL otherwise"}, "The signature hash type. Must be one of:\n"
" \"DEFAULT\"\n"
" \"ALL\"\n"
" \"NONE\"\n"
@@ -768,12 +621,16 @@ static RPCHelpMan signrawtransactionwithkey()
{
{RPCResult::Type::STR_HEX, "hex", "The hex-encoded raw transaction with signature(s)"},
{RPCResult::Type::BOOL, "complete", "If the transaction has a complete set of signatures"},
- {RPCResult::Type::ARR, "errors", /* optional */ true, "Script verification errors (if there are any)",
+ {RPCResult::Type::ARR, "errors", /*optional=*/true, "Script verification errors (if there are any)",
{
{RPCResult::Type::OBJ, "", "",
{
{RPCResult::Type::STR_HEX, "txid", "The hash of the referenced, previous transaction"},
{RPCResult::Type::NUM, "vout", "The index of the output to spent and used as input"},
+ {RPCResult::Type::ARR, "witness", "",
+ {
+ {RPCResult::Type::STR_HEX, "witness", ""},
+ }},
{RPCResult::Type::STR_HEX, "scriptSig", "The hex-encoded signature script"},
{RPCResult::Type::NUM, "sequence", "Script sequence number"},
{RPCResult::Type::STR, "error", "Verification or signing error related to the input"},
@@ -823,211 +680,11 @@ 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"
- "It is possible for transactions to not be fully validated ('allowed' unset) if another transaction failed.\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", "Package validation error, if any (only possible if rawtxs had more than 1 transaction)."},
- {RPCResult::Type::BOOL, "allowed", "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", "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", "Transaction fees (only present if 'allowed' is true)",
- {
- {RPCResult::Type::STR_AMOUNT, "base", "transaction fee in " + CURRENCY_UNIT},
- }},
- {RPCResult::Type::STR, "reject-reason", "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);
- CChainState& chainstate = EnsureChainman(node).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(),
- AcceptToMemoryPool(chainstate, mempool, txns[0], /* bypass_limits */ false, /* 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;
- 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 = GetVirtualTransactionSize(*tx);
- 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{"decodepsbt",
- "\nReturn a JSON object representing the serialized, base64-encoded partially signed Bitcoin transaction.\n",
+ return RPCHelpMan{
+ "decodepsbt",
+ "Return a JSON object representing the serialized, base64-encoded partially signed Bitcoin transaction.",
{
{"psbt", RPCArg::Type::STR, RPCArg::Optional::NO, "The PSBT base64 string"},
},
@@ -1038,6 +695,26 @@ static RPCHelpMan decodepsbt()
{
{RPCResult::Type::ELISION, "", "The layout is the same as the output of decoderawtransaction."},
}},
+ {RPCResult::Type::ARR, "global_xpubs", "",
+ {
+ {RPCResult::Type::OBJ, "", "",
+ {
+ {RPCResult::Type::STR, "xpub", "The extended public key this path corresponds to"},
+ {RPCResult::Type::STR_HEX, "master_fingerprint", "The fingerprint of the master key"},
+ {RPCResult::Type::STR, "path", "The path"},
+ }},
+ }},
+ {RPCResult::Type::NUM, "psbt_version", "The PSBT version number. Not to be confused with the unsigned transaction version"},
+ {RPCResult::Type::ARR, "proprietary", "The global proprietary map",
+ {
+ {RPCResult::Type::OBJ, "", "",
+ {
+ {RPCResult::Type::STR_HEX, "identifier", "The hex string for the proprietary identifier"},
+ {RPCResult::Type::NUM, "subtype", "The number for the subtype"},
+ {RPCResult::Type::STR_HEX, "key", "The hex for the key"},
+ {RPCResult::Type::STR_HEX, "value", "The hex for the value"},
+ }},
+ }},
{RPCResult::Type::OBJ_DYN, "unknown", "The unknown global fields",
{
{RPCResult::Type::STR_HEX, "key", "(key-value pair) An unknown key-value pair"},
@@ -1046,78 +723,106 @@ static RPCHelpMan decodepsbt()
{
{RPCResult::Type::OBJ, "", "",
{
- {RPCResult::Type::OBJ, "non_witness_utxo", /* optional */ true, "Decoded network transaction for non-witness UTXOs",
+ {RPCResult::Type::OBJ, "non_witness_utxo", /*optional=*/true, "Decoded network transaction for non-witness UTXOs",
{
{RPCResult::Type::ELISION, "",""},
}},
- {RPCResult::Type::OBJ, "witness_utxo", /* optional */ true, "Transaction output for witness UTXOs",
+ {RPCResult::Type::OBJ, "witness_utxo", /*optional=*/true, "Transaction output for witness UTXOs",
{
{RPCResult::Type::NUM, "amount", "The value in " + CURRENCY_UNIT},
{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"," Bitcoin address if there is one"},
+ {RPCResult::Type::STR, "address", /*optional=*/true, "The Bitcoin address (only if a well-defined address exists)"},
}},
}},
- {RPCResult::Type::OBJ_DYN, "partial_signatures", /* optional */ true, "",
+ {RPCResult::Type::OBJ_DYN, "partial_signatures", /*optional=*/true, "",
{
{RPCResult::Type::STR, "pubkey", "The public key and signature that corresponds to it."},
}},
- {RPCResult::Type::STR, "sighash", /* optional */ true, "The sighash type to be used"},
- {RPCResult::Type::OBJ, "redeem_script", /* optional */ true, "",
+ {RPCResult::Type::STR, "sighash", /*optional=*/true, "The sighash type to be used"},
+ {RPCResult::Type::OBJ, "redeem_script", /*optional=*/true, "",
{
{RPCResult::Type::STR, "asm", "The asm"},
{RPCResult::Type::STR_HEX, "hex", "The hex"},
{RPCResult::Type::STR, "type", "The type, eg 'pubkeyhash'"},
}},
- {RPCResult::Type::OBJ, "witness_script", /* optional */ true, "",
+ {RPCResult::Type::OBJ, "witness_script", /*optional=*/true, "",
{
{RPCResult::Type::STR, "asm", "The asm"},
{RPCResult::Type::STR_HEX, "hex", "The hex"},
{RPCResult::Type::STR, "type", "The type, eg 'pubkeyhash'"},
}},
- {RPCResult::Type::ARR, "bip32_derivs", /* optional */ true, "",
+ {RPCResult::Type::ARR, "bip32_derivs", /*optional=*/true, "",
{
- {RPCResult::Type::OBJ, "pubkey", /* optional */ true, "The public key with the derivation path as the value.",
+ {RPCResult::Type::OBJ, "", "",
{
+ {RPCResult::Type::STR, "pubkey", "The public key with the derivation path as the value."},
{RPCResult::Type::STR, "master_fingerprint", "The fingerprint of the master key"},
{RPCResult::Type::STR, "path", "The path"},
}},
}},
- {RPCResult::Type::OBJ, "final_scriptsig", /* optional */ true, "",
+ {RPCResult::Type::OBJ, "final_scriptSig", /*optional=*/true, "",
{
{RPCResult::Type::STR, "asm", "The asm"},
{RPCResult::Type::STR, "hex", "The hex"},
}},
- {RPCResult::Type::ARR, "final_scriptwitness", "",
+ {RPCResult::Type::ARR, "final_scriptwitness", /*optional=*/true, "",
{
{RPCResult::Type::STR_HEX, "", "hex-encoded witness data (if any)"},
}},
- {RPCResult::Type::OBJ_DYN, "unknown", "The unknown global fields",
+ {RPCResult::Type::OBJ_DYN, "ripemd160_preimages", /*optional=*/ true, "",
+ {
+ {RPCResult::Type::STR, "hash", "The hash and preimage that corresponds to it."},
+ }},
+ {RPCResult::Type::OBJ_DYN, "sha256_preimages", /*optional=*/ true, "",
+ {
+ {RPCResult::Type::STR, "hash", "The hash and preimage that corresponds to it."},
+ }},
+ {RPCResult::Type::OBJ_DYN, "hash160_preimages", /*optional=*/ true, "",
+ {
+ {RPCResult::Type::STR, "hash", "The hash and preimage that corresponds to it."},
+ }},
+ {RPCResult::Type::OBJ_DYN, "hash256_preimages", /*optional=*/ true, "",
+ {
+ {RPCResult::Type::STR, "hash", "The hash and preimage that corresponds to it."},
+ }},
+ {RPCResult::Type::OBJ_DYN, "unknown", /*optional=*/ true, "The unknown input fields",
{
{RPCResult::Type::STR_HEX, "key", "(key-value pair) An unknown key-value pair"},
}},
+ {RPCResult::Type::ARR, "proprietary", /*optional=*/true, "The input proprietary map",
+ {
+ {RPCResult::Type::OBJ, "", "",
+ {
+ {RPCResult::Type::STR_HEX, "identifier", "The hex string for the proprietary identifier"},
+ {RPCResult::Type::NUM, "subtype", "The number for the subtype"},
+ {RPCResult::Type::STR_HEX, "key", "The hex for the key"},
+ {RPCResult::Type::STR_HEX, "value", "The hex for the value"},
+ }},
+ }},
}},
}},
{RPCResult::Type::ARR, "outputs", "",
{
{RPCResult::Type::OBJ, "", "",
{
- {RPCResult::Type::OBJ, "redeem_script", /* optional */ true, "",
+ {RPCResult::Type::OBJ, "redeem_script", /*optional=*/true, "",
{
{RPCResult::Type::STR, "asm", "The asm"},
{RPCResult::Type::STR_HEX, "hex", "The hex"},
{RPCResult::Type::STR, "type", "The type, eg 'pubkeyhash'"},
}},
- {RPCResult::Type::OBJ, "witness_script", /* optional */ true, "",
+ {RPCResult::Type::OBJ, "witness_script", /*optional=*/true, "",
{
{RPCResult::Type::STR, "asm", "The asm"},
{RPCResult::Type::STR_HEX, "hex", "The hex"},
{RPCResult::Type::STR, "type", "The type, eg 'pubkeyhash'"},
}},
- {RPCResult::Type::ARR, "bip32_derivs", /* optional */ true, "",
+ {RPCResult::Type::ARR, "bip32_derivs", /*optional=*/true, "",
{
{RPCResult::Type::OBJ, "", "",
{
@@ -1126,13 +831,23 @@ static RPCHelpMan decodepsbt()
{RPCResult::Type::STR, "path", "The path"},
}},
}},
- {RPCResult::Type::OBJ_DYN, "unknown", "The unknown global fields",
+ {RPCResult::Type::OBJ_DYN, "unknown", /*optional=*/true, "The unknown global fields",
{
{RPCResult::Type::STR_HEX, "key", "(key-value pair) An unknown key-value pair"},
}},
+ {RPCResult::Type::ARR, "proprietary", /*optional=*/true, "The output proprietary map",
+ {
+ {RPCResult::Type::OBJ, "", "",
+ {
+ {RPCResult::Type::STR_HEX, "identifier", "The hex string for the proprietary identifier"},
+ {RPCResult::Type::NUM, "subtype", "The number for the subtype"},
+ {RPCResult::Type::STR_HEX, "key", "The hex for the key"},
+ {RPCResult::Type::STR_HEX, "value", "The hex for the value"},
+ }},
+ }},
}},
}},
- {RPCResult::Type::STR_AMOUNT, "fee", /* optional */ true, "The transaction fee paid if all UTXOs slots in the PSBT have been filled."},
+ {RPCResult::Type::STR_AMOUNT, "fee", /*optional=*/true, "The transaction fee paid if all UTXOs slots in the PSBT have been filled."},
}
},
RPCExamples{
@@ -1153,9 +868,41 @@ 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
+ UniValue global_xpubs(UniValue::VARR);
+ for (std::pair<KeyOriginInfo, std::set<CExtPubKey>> xpub_pair : psbtx.m_xpubs) {
+ for (auto& xpub : xpub_pair.second) {
+ std::vector<unsigned char> ser_xpub;
+ ser_xpub.assign(BIP32_EXTKEY_WITH_VERSION_SIZE, 0);
+ xpub.EncodeWithVersion(ser_xpub.data());
+
+ UniValue keypath(UniValue::VOBJ);
+ keypath.pushKV("xpub", EncodeBase58Check(ser_xpub));
+ keypath.pushKV("master_fingerprint", HexStr(Span<unsigned char>(xpub_pair.first.fingerprint, xpub_pair.first.fingerprint + 4)));
+ keypath.pushKV("path", WriteHDKeypath(xpub_pair.first.path));
+ global_xpubs.push_back(keypath);
+ }
+ }
+ result.pushKV("global_xpubs", global_xpubs);
+
+ // PSBT version
+ result.pushKV("psbt_version", static_cast<uint64_t>(psbtx.GetVersion()));
+
+ // Proprietary
+ UniValue proprietary(UniValue::VARR);
+ for (const auto& entry : psbtx.m_proprietary) {
+ UniValue this_prop(UniValue::VOBJ);
+ this_prop.pushKV("identifier", HexStr(entry.identifier));
+ this_prop.pushKV("subtype", entry.subtype);
+ this_prop.pushKV("key", HexStr(entry.key));
+ this_prop.pushKV("value", HexStr(entry.value));
+ proprietary.push_back(this_prop);
+ }
+ result.pushKV("proprietary", proprietary);
+
// Unknown data
UniValue unknowns(UniValue::VOBJ);
for (auto entry : psbtx.unknown) {
@@ -1177,7 +924,7 @@ static RPCHelpMan decodepsbt()
txout = input.witness_utxo;
UniValue o(UniValue::VOBJ);
- ScriptToUniv(txout.scriptPubKey, o, true);
+ ScriptToUniv(txout.scriptPubKey, /*out=*/o, /*include_hex=*/true, /*include_address=*/true);
UniValue out(UniValue::VOBJ);
out.pushKV("amount", ValueFromAmount(txout.nValue));
@@ -1191,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;
@@ -1217,19 +964,19 @@ static RPCHelpMan decodepsbt()
}
// Sighash
- if (input.sighash_type > 0) {
- in.pushKV("sighash", SighashToStr((unsigned char)input.sighash_type));
+ if (input.sighash_type != std::nullopt) {
+ in.pushKV("sighash", SighashToStr((unsigned char)*input.sighash_type));
}
// Redeem script and witness script
if (!input.redeem_script.empty()) {
UniValue r(UniValue::VOBJ);
- ScriptToUniv(input.redeem_script, r, false);
+ 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, false);
+ ScriptToUniv(input.witness_script, /*out=*/r);
in.pushKV("witness_script", r);
}
@@ -1262,6 +1009,56 @@ static RPCHelpMan decodepsbt()
in.pushKV("final_scriptwitness", txinwitness);
}
+ // Ripemd160 hash preimages
+ if (!input.ripemd160_preimages.empty()) {
+ UniValue ripemd160_preimages(UniValue::VOBJ);
+ for (const auto& [hash, preimage] : input.ripemd160_preimages) {
+ ripemd160_preimages.pushKV(HexStr(hash), HexStr(preimage));
+ }
+ in.pushKV("ripemd160_preimages", ripemd160_preimages);
+ }
+
+ // Sha256 hash preimages
+ if (!input.sha256_preimages.empty()) {
+ UniValue sha256_preimages(UniValue::VOBJ);
+ for (const auto& [hash, preimage] : input.sha256_preimages) {
+ sha256_preimages.pushKV(HexStr(hash), HexStr(preimage));
+ }
+ in.pushKV("sha256_preimages", sha256_preimages);
+ }
+
+ // Hash160 hash preimages
+ if (!input.hash160_preimages.empty()) {
+ UniValue hash160_preimages(UniValue::VOBJ);
+ for (const auto& [hash, preimage] : input.hash160_preimages) {
+ hash160_preimages.pushKV(HexStr(hash), HexStr(preimage));
+ }
+ in.pushKV("hash160_preimages", hash160_preimages);
+ }
+
+ // Hash256 hash preimages
+ if (!input.hash256_preimages.empty()) {
+ UniValue hash256_preimages(UniValue::VOBJ);
+ for (const auto& [hash, preimage] : input.hash256_preimages) {
+ hash256_preimages.pushKV(HexStr(hash), HexStr(preimage));
+ }
+ in.pushKV("hash256_preimages", hash256_preimages);
+ }
+
+ // Proprietary
+ if (!input.m_proprietary.empty()) {
+ UniValue proprietary(UniValue::VARR);
+ for (const auto& entry : input.m_proprietary) {
+ UniValue this_prop(UniValue::VOBJ);
+ this_prop.pushKV("identifier", HexStr(entry.identifier));
+ this_prop.pushKV("subtype", entry.subtype);
+ this_prop.pushKV("key", HexStr(entry.key));
+ this_prop.pushKV("value", HexStr(entry.value));
+ proprietary.push_back(this_prop);
+ }
+ in.pushKV("proprietary", proprietary);
+ }
+
// Unknown data
if (input.unknown.size() > 0) {
UniValue unknowns(UniValue::VOBJ);
@@ -1284,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, false);
+ 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, false);
+ ScriptToUniv(output.witness_script, /*out=*/r);
out.pushKV("witness_script", r);
}
@@ -1306,6 +1103,20 @@ static RPCHelpMan decodepsbt()
out.pushKV("bip32_derivs", keypaths);
}
+ // Proprietary
+ if (!output.m_proprietary.empty()) {
+ UniValue proprietary(UniValue::VARR);
+ for (const auto& entry : output.m_proprietary) {
+ UniValue this_prop(UniValue::VOBJ);
+ this_prop.pushKV("identifier", HexStr(entry.identifier));
+ this_prop.pushKV("subtype", entry.subtype);
+ this_prop.pushKV("key", HexStr(entry.key));
+ this_prop.pushKV("value", HexStr(entry.value));
+ proprietary.push_back(this_prop);
+ }
+ out.pushKV("proprietary", proprietary);
+ }
+
// Unknown data
if (output.unknown.size() > 0) {
UniValue unknowns(UniValue::VOBJ);
@@ -1400,8 +1211,8 @@ static RPCHelpMan finalizepsbt()
RPCResult{
RPCResult::Type::OBJ, "", "",
{
- {RPCResult::Type::STR, "psbt", "The base64-encoded partially signed transaction if not extracted"},
- {RPCResult::Type::STR_HEX, "hex", "The hex-encoded network transaction if extracted"},
+ {RPCResult::Type::STR, "psbt", /*optional=*/true, "The base64-encoded partially signed transaction if not extracted"},
+ {RPCResult::Type::STR_HEX, "hex", /*optional=*/true, "The hex-encoded network transaction if extracted"},
{RPCResult::Type::BOOL, "complete", "If the transaction has a complete set of signatures"},
}
},
@@ -1449,39 +1260,7 @@ static RPCHelpMan createpsbt()
return RPCHelpMan{"createpsbt",
"\nCreates a transaction in the Partially Signed Transaction format.\n"
"Implements the Creator role.\n",
- {
- {"inputs", RPCArg::Type::ARR, RPCArg::Optional::NO, "The json objects",
- {
- {"", RPCArg::Type::OBJ, RPCArg::Optional::OMITTED, "",
- {
- {"txid", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "The transaction id"},
- {"vout", RPCArg::Type::NUM, RPCArg::Optional::NO, "The output number"},
- {"sequence", RPCArg::Type::NUM, RPCArg::DefaultHint{"depends on the value of the 'replaceable' and 'locktime' arguments"}, "The sequence number"},
- },
- },
- },
- },
- {"outputs", RPCArg::Type::ARR, RPCArg::Optional::NO, "The outputs (key-value pairs), where none of the keys are duplicated.\n"
- "That is, each address can only appear once and there can only be one 'data' object.\n"
- "For compatibility reasons, a dictionary, which holds the key-value pairs directly, is also\n"
- " accepted as second parameter.",
- {
- {"", RPCArg::Type::OBJ_USER_KEYS, RPCArg::Optional::OMITTED, "",
- {
- {"address", RPCArg::Type::AMOUNT, RPCArg::Optional::NO, "A key-value pair. The key (string) is the bitcoin address, the value (float or string) is the amount in " + CURRENCY_UNIT},
- },
- },
- {"", RPCArg::Type::OBJ, RPCArg::Optional::OMITTED, "",
- {
- {"data", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "A key-value pair. The key must be \"data\", the value is hex-encoded data"},
- },
- },
- },
- },
- {"locktime", RPCArg::Type::NUM, RPCArg::Default{0}, "Raw locktime. Non-0 value also locktime-activates inputs"},
- {"replaceable", RPCArg::Type::BOOL, RPCArg::Default{false}, "Marks this transaction as BIP125 replaceable.\n"
- " Allows this transaction to be replaced by a transaction with higher fees. If provided, it is an error if explicit sequence numbers are incompatible."},
- },
+ CreateTxDoc(),
RPCResult{
RPCResult::Type::STR, "", "The resulting raw transaction (base64-encoded string)"
},
@@ -1633,7 +1412,7 @@ static RPCHelpMan utxoupdatepsbt()
}
}
// We don't actually need private keys further on; hide them as a precaution.
- HidingSigningProvider public_provider(&provider, /* nosign */ true, /* nobip32derivs */ false);
+ HidingSigningProvider public_provider(&provider, /*hide_secret=*/true, /*hide_origin=*/false);
// Fetch previous transactions (inputs):
CCoinsView viewDummy;
@@ -1672,7 +1451,7 @@ static RPCHelpMan utxoupdatepsbt()
// Update script/keypath information using descriptor data.
// Note that SignPSBTInput does a lot more than just constructing ECDSA signatures
// we don't actually care about those here, in fact.
- SignPSBTInput(public_provider, psbtx, i, &txdata, /* sighash_type */ 1);
+ SignPSBTInput(public_provider, psbtx, i, &txdata, /*sighash=*/1);
}
// Update script/keypath information using descriptor data.
@@ -1751,6 +1530,13 @@ static RPCHelpMan joinpsbts()
for (unsigned int i = 0; i < psbt.tx->vout.size(); ++i) {
merged_psbt.AddOutput(psbt.tx->vout[i], psbt.outputs[i]);
}
+ for (auto& xpub_pair : psbt.m_xpubs) {
+ if (merged_psbt.m_xpubs.count(xpub_pair.first) == 0) {
+ merged_psbt.m_xpubs[xpub_pair.first] = xpub_pair.second;
+ } else {
+ merged_psbt.m_xpubs[xpub_pair.first].insert(xpub_pair.second.begin(), xpub_pair.second.end());
+ }
+ }
merged_psbt.unknown.insert(psbt.unknown.begin(), psbt.unknown.end());
}
@@ -1793,33 +1579,33 @@ static RPCHelpMan analyzepsbt()
RPCResult {
RPCResult::Type::OBJ, "", "",
{
- {RPCResult::Type::ARR, "inputs", "",
+ {RPCResult::Type::ARR, "inputs", /*optional=*/true, "",
{
{RPCResult::Type::OBJ, "", "",
{
{RPCResult::Type::BOOL, "has_utxo", "Whether a UTXO is provided"},
{RPCResult::Type::BOOL, "is_final", "Whether the input is finalized"},
- {RPCResult::Type::OBJ, "missing", /* optional */ true, "Things that are missing that are required to complete this input",
+ {RPCResult::Type::OBJ, "missing", /*optional=*/true, "Things that are missing that are required to complete this input",
{
- {RPCResult::Type::ARR, "pubkeys", /* optional */ true, "",
+ {RPCResult::Type::ARR, "pubkeys", /*optional=*/true, "",
{
{RPCResult::Type::STR_HEX, "keyid", "Public key ID, hash160 of the public key, of a public key whose BIP 32 derivation path is missing"},
}},
- {RPCResult::Type::ARR, "signatures", /* optional */ true, "",
+ {RPCResult::Type::ARR, "signatures", /*optional=*/true, "",
{
{RPCResult::Type::STR_HEX, "keyid", "Public key ID, hash160 of the public key, of a public key whose signature is missing"},
}},
- {RPCResult::Type::STR_HEX, "redeemscript", /* optional */ true, "Hash160 of the redeemScript that is missing"},
- {RPCResult::Type::STR_HEX, "witnessscript", /* optional */ true, "SHA256 of the witnessScript that is missing"},
+ {RPCResult::Type::STR_HEX, "redeemscript", /*optional=*/true, "Hash160 of the redeemScript that is missing"},
+ {RPCResult::Type::STR_HEX, "witnessscript", /*optional=*/true, "SHA256 of the witnessScript that is missing"},
}},
- {RPCResult::Type::STR, "next", /* optional */ true, "Role of the next person that this input needs to go to"},
+ {RPCResult::Type::STR, "next", /*optional=*/true, "Role of the next person that this input needs to go to"},
}},
}},
- {RPCResult::Type::NUM, "estimated_vsize", /* optional */ true, "Estimated vsize of the final signed transaction"},
- {RPCResult::Type::STR_AMOUNT, "estimated_feerate", /* optional */ true, "Estimated feerate of the final signed transaction in " + CURRENCY_UNIT + "/kvB. Shown only if all UTXO slots in the PSBT have been filled"},
- {RPCResult::Type::STR_AMOUNT, "fee", /* optional */ true, "The transaction fee paid. Shown only if all UTXO slots in the PSBT have been filled"},
+ {RPCResult::Type::NUM, "estimated_vsize", /*optional=*/true, "Estimated vsize of the final signed transaction"},
+ {RPCResult::Type::STR_AMOUNT, "estimated_feerate", /*optional=*/true, "Estimated feerate of the final signed transaction in " + CURRENCY_UNIT + "/kvB. Shown only if all UTXO slots in the PSBT have been filled"},
+ {RPCResult::Type::STR_AMOUNT, "fee", /*optional=*/true, "The transaction fee paid. Shown only if all UTXO slots in the PSBT have been filled"},
{RPCResult::Type::STR, "next", "Role of the next person that this psbt needs to go to"},
- {RPCResult::Type::STR, "error", /* optional */ true, "Error message (if there is one)"},
+ {RPCResult::Type::STR, "error", /*optional=*/true, "Error message (if there is one)"},
}
},
RPCExamples {
@@ -1894,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 122a92f084..86b5b7e960 100644
--- a/src/rpc/rawtransaction_util.cpp
+++ b/src/rpc/rawtransaction_util.cpp
@@ -1,11 +1,12 @@
// Copyright (c) 2010 Satoshi Nakamoto
-// Copyright (c) 2009-2020 The Bitcoin Core developers
+// 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 <rpc/rawtransaction_util.h>
#include <coins.h>
+#include <consensus/amount.h>
#include <core_io.h>
#include <key_io.h>
#include <policy/policy.h>
@@ -18,6 +19,7 @@
#include <univalue.h>
#include <util/rbf.h>
#include <util/strencodings.h>
+#include <util/translation.h>
CMutableTransaction ConstructTransaction(const UniValue& inputs_in, const UniValue& outputs_in, const UniValue& locktime, bool rbf)
{
@@ -38,7 +40,7 @@ CMutableTransaction ConstructTransaction(const UniValue& inputs_in, const UniVal
CMutableTransaction rawTx;
if (!locktime.isNull()) {
- int64_t nLockTime = locktime.get_int64();
+ int64_t nLockTime = locktime.getInt<int64_t>();
if (nLockTime < 0 || nLockTime > LOCKTIME_MAX)
throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid parameter, locktime out of range");
rawTx.nLockTime = nLockTime;
@@ -53,7 +55,7 @@ CMutableTransaction ConstructTransaction(const UniValue& inputs_in, const UniVal
const UniValue& vout_v = find_value(o, "vout");
if (!vout_v.isNum())
throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid parameter, missing vout key");
- int nOutput = vout_v.get_int();
+ int nOutput = vout_v.getInt<int>();
if (nOutput < 0)
throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid parameter, vout cannot be negative");
@@ -61,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;
}
@@ -69,7 +71,7 @@ CMutableTransaction ConstructTransaction(const UniValue& inputs_in, const UniVal
// set the sequence number if passed in the parameters object
const UniValue& sequenceObj = find_value(o, "sequence");
if (sequenceObj.isNum()) {
- int64_t seqNr64 = sequenceObj.get_int64();
+ int64_t seqNr64 = sequenceObj.getInt<int64_t>();
if (seqNr64 < 0 || seqNr64 > CTxIn::SEQUENCE_FINAL) {
throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid parameter, sequence number is out of range");
} else {
@@ -175,7 +177,7 @@ void ParsePrevouts(const UniValue& prevTxsUnival, FillableSigningProvider* keyst
uint256 txid = ParseHashO(prevOut, "txid");
- int nOut = find_value(prevOut, "vout").get_int();
+ int nOut = find_value(prevOut, "vout").getInt<int>();
if (nOut < 0) {
throw JSONRPCError(RPC_DESERIALIZATION_ERROR, "vout cannot be negative");
}
@@ -280,22 +282,22 @@ void SignTransaction(CMutableTransaction& mtx, const SigningProvider* keystore,
int nHashType = ParseSighashString(hashType);
// Script verification errors
- std::map<int, std::string> input_errors;
+ std::map<int, bilingual_str> input_errors;
bool complete = SignTransaction(mtx, keystore, coins, nHashType, input_errors);
SignTransactionResultToJSON(mtx, complete, coins, input_errors, result);
}
-void SignTransactionResultToJSON(CMutableTransaction& mtx, bool complete, const std::map<COutPoint, Coin>& coins, const std::map<int, std::string>& input_errors, UniValue& result)
+void SignTransactionResultToJSON(CMutableTransaction& mtx, bool complete, const std::map<COutPoint, Coin>& coins, const std::map<int, bilingual_str>& input_errors, UniValue& result)
{
// Make errors UniValue
UniValue vErrors(UniValue::VARR);
for (const auto& err_pair : input_errors) {
- if (err_pair.second == "Missing amount") {
+ if (err_pair.second.original == "Missing amount") {
// This particular error needs to be an exception for some reason
throw JSONRPCError(RPC_TYPE_ERROR, strprintf("Missing amount for %s", coins.at(mtx.vin.at(err_pair.first).prevout).out.ToString()));
}
- TxInErrorToJSON(mtx.vin.at(err_pair.first), vErrors, err_pair.second);
+ TxInErrorToJSON(mtx.vin.at(err_pair.first), vErrors, err_pair.second.original);
}
result.pushKV("hex", EncodeHexTx(CTransaction(mtx)));
diff --git a/src/rpc/rawtransaction_util.h b/src/rpc/rawtransaction_util.h
index ce7d5834fa..c3eb1417f8 100644
--- a/src/rpc/rawtransaction_util.h
+++ b/src/rpc/rawtransaction_util.h
@@ -1,4 +1,4 @@
-// Copyright (c) 2017-2020 The Bitcoin Core developers
+// Copyright (c) 2017-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.
@@ -8,6 +8,7 @@
#include <map>
#include <string>
+struct bilingual_str;
class FillableSigningProvider;
class UniValue;
struct CMutableTransaction;
@@ -25,7 +26,7 @@ class SigningProvider;
* @param result JSON object where signed transaction results accumulate
*/
void SignTransaction(CMutableTransaction& mtx, const SigningProvider* keystore, const std::map<COutPoint, Coin>& coins, const UniValue& hashType, UniValue& result);
-void SignTransactionResultToJSON(CMutableTransaction& mtx, bool complete, const std::map<COutPoint, Coin>& coins, const std::map<int, std::string>& input_errors, UniValue& result);
+void SignTransactionResultToJSON(CMutableTransaction& mtx, bool complete, const std::map<COutPoint, Coin>& coins, const std::map<int, bilingual_str>& input_errors, UniValue& result);
/**
* Parse a prevtxs UniValue array and get the map of coins from it
diff --git a/src/rpc/register.h b/src/rpc/register.h
index 6724203ffe..301f410da3 100644
--- a/src/rpc/register.h
+++ b/src/rpc/register.h
@@ -1,4 +1,4 @@
-// Copyright (c) 2009-2018 The Bitcoin Core developers
+// 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.
@@ -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 a7866474e1..304c265b31 100644
--- a/src/rpc/request.cpp
+++ b/src/rpc/request.cpp
@@ -1,5 +1,5 @@
// Copyright (c) 2010 Satoshi Nakamoto
-// Copyright (c) 2009-2020 The Bitcoin Core developers
+// 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.
@@ -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
@@ -70,24 +75,24 @@ static fs::path GetAuthCookieFile(bool temp=false)
if (temp) {
arg += ".tmp";
}
- return AbsPathForConfigVal(fs::path(arg));
+ return AbsPathForConfigVal(fs::PathFromString(arg));
}
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()) {
- LogPrintf("Unable to open cookie authentication file %s for writing\n", filepath_tmp.string());
+ LogPrintf("Unable to open cookie authentication file %s for writing\n", fs::PathToString(filepath_tmp));
return false;
}
file << cookie;
@@ -95,10 +100,10 @@ bool GenerateAuthCookie(std::string *cookie_out)
fs::path filepath = GetAuthCookieFile(false);
if (!RenameOver(filepath_tmp, filepath)) {
- LogPrintf("Unable to rename cookie authentication file %s to %s\n", filepath_tmp.string(), filepath.string());
+ LogPrintf("Unable to rename cookie authentication file %s to %s\n", fs::PathToString(filepath_tmp), fs::PathToString(filepath));
return false;
}
- LogPrintf("Generated RPC authentication cookie %s\n", filepath.string());
+ LogPrintf("Generated RPC authentication cookie %s\n", fs::PathToString(filepath));
if (cookie_out)
*cookie_out = cookie;
@@ -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);
@@ -141,7 +146,7 @@ std::vector<UniValue> JSONRPCProcessBatchReply(const UniValue& in)
if (!rec.isObject()) {
throw std::runtime_error("Batch member must be an object");
}
- size_t id = rec["id"].get_int();
+ size_t id = rec["id"].getInt<int>();
if (id >= num) {
throw std::runtime_error("Batch member id is larger than batch size");
}
diff --git a/src/rpc/request.h b/src/rpc/request.h
index bb091efea8..a682c58d96 100644
--- a/src/rpc/request.h
+++ b/src/rpc/request.h
@@ -1,5 +1,5 @@
// Copyright (c) 2010 Satoshi Nakamoto
-// Copyright (c) 2009-2020 The Bitcoin Core developers
+// 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.
diff --git a/src/rpc/server.cpp b/src/rpc/server.cpp
index cf80b08b96..66ed18045e 100644
--- a/src/rpc/server.cpp
+++ b/src/rpc/server.cpp
@@ -1,5 +1,5 @@
// Copyright (c) 2010 Satoshi Nakamoto
-// Copyright (c) 2009-2020 The Bitcoin Core developers
+// 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.
@@ -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>
@@ -20,14 +19,14 @@
#include <mutex>
#include <unordered_map>
-static Mutex g_rpc_warmup_mutex;
+static GlobalMutex g_rpc_warmup_mutex;
static std::atomic<bool> g_rpc_running{false};
static bool fRPCInWarmup GUARDED_BY(g_rpc_warmup_mutex) = true;
static std::string rpcWarmupStatus GUARDED_BY(g_rpc_warmup_mutex) = "RPC server started";
/* Timer-creating functions */
static RPCTimerInterface* timerInterface = nullptr;
/* Map of name to timer. */
-static Mutex g_deadline_timers_mutex;
+static GlobalMutex g_deadline_timers_mutex;
static std::map<std::string, std::unique_ptr<RPCTimerBase> > deadlineTimers GUARDED_BY(g_deadline_timers_mutex);
static bool ExecuteCommand(const CRPCCommand& command, const JSONRPCRequest& request, UniValue& result, bool last_handler);
@@ -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{""},
@@ -177,7 +176,7 @@ static RPCHelpMan stop()
// this reply will get back to the client.
StartShutdown();
if (jsonRequest.params[0].isNum()) {
- UninterruptibleSleep(std::chrono::milliseconds{jsonRequest.params[0].get_int()});
+ UninterruptibleSleep(std::chrono::milliseconds{jsonRequest.params[0].getInt<int>()});
}
return RESULT;
},
@@ -239,7 +238,7 @@ static RPCHelpMan getrpcinfo()
UniValue result(UniValue::VOBJ);
result.pushKV("active_commands", active_commands);
- const std::string path = LogInstance().m_file_path.string();
+ const std::string path = LogInstance().m_file_path.u8string();
UniValue log_path(UniValue::VSTR, path);
result.pushKV("logpath", log_path);
@@ -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);
@@ -540,7 +534,7 @@ void RPCRunLater(const std::string& name, std::function<void()> func, int64_t nS
int RPCSerializationFlags()
{
int flag = 0;
- if (gArgs.GetArg("-rpcserialversion", DEFAULT_RPC_SERIALIZE_VERSION) == 0)
+ if (gArgs.GetIntArg("-rpcserialversion", DEFAULT_RPC_SERIALIZE_VERSION) == 0)
flag |= SERIALIZE_TRANSACTION_NO_WITNESS;
return flag;
}
diff --git a/src/rpc/server.h b/src/rpc/server.h
index 03967020c2..01e8556050 100644
--- a/src/rpc/server.h
+++ b/src/rpc/server.h
@@ -1,12 +1,11 @@
// Copyright (c) 2010 Satoshi Nakamoto
-// Copyright (c) 2009-2020 The Bitcoin Core developers
+// 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.
#ifndef BITCOIN_RPC_SERVER_H
#define BITCOIN_RPC_SERVER_H
-#include <amount.h>
#include <rpc/request.h>
#include <rpc/util.h>
diff --git a/src/rpc/server_util.cpp b/src/rpc/server_util.cpp
new file mode 100644
index 0000000000..6a1b41f066
--- /dev/null
+++ b/src/rpc/server_util.cpp
@@ -0,0 +1,95 @@
+// Copyright (c) 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 <rpc/server_util.h>
+
+#include <net_processing.h>
+#include <node/context.h>
+#include <policy/fees.h>
+#include <rpc/protocol.h>
+#include <rpc/request.h>
+#include <txmempool.h>
+#include <util/system.h>
+#include <validation.h>
+
+#include <any>
+
+using node::NodeContext;
+
+NodeContext& EnsureAnyNodeContext(const std::any& context)
+{
+ auto node_context = util::AnyPtr<NodeContext>(context);
+ if (!node_context) {
+ throw JSONRPCError(RPC_INTERNAL_ERROR, "Node context not found");
+ }
+ return *node_context;
+}
+
+CTxMemPool& EnsureMemPool(const NodeContext& node)
+{
+ if (!node.mempool) {
+ throw JSONRPCError(RPC_CLIENT_MEMPOOL_DISABLED, "Mempool disabled or instance not found");
+ }
+ return *node.mempool;
+}
+
+CTxMemPool& EnsureAnyMemPool(const std::any& context)
+{
+ return EnsureMemPool(EnsureAnyNodeContext(context));
+}
+
+ArgsManager& EnsureArgsman(const NodeContext& node)
+{
+ if (!node.args) {
+ throw JSONRPCError(RPC_INTERNAL_ERROR, "Node args not found");
+ }
+ return *node.args;
+}
+
+ArgsManager& EnsureAnyArgsman(const std::any& context)
+{
+ return EnsureArgsman(EnsureAnyNodeContext(context));
+}
+
+ChainstateManager& EnsureChainman(const NodeContext& node)
+{
+ if (!node.chainman) {
+ throw JSONRPCError(RPC_INTERNAL_ERROR, "Node chainman not found");
+ }
+ return *node.chainman;
+}
+
+ChainstateManager& EnsureAnyChainman(const std::any& context)
+{
+ return EnsureChainman(EnsureAnyNodeContext(context));
+}
+
+CBlockPolicyEstimator& EnsureFeeEstimator(const NodeContext& node)
+{
+ if (!node.fee_estimator) {
+ throw JSONRPCError(RPC_INTERNAL_ERROR, "Fee estimation disabled");
+ }
+ return *node.fee_estimator;
+}
+
+CBlockPolicyEstimator& EnsureAnyFeeEstimator(const std::any& context)
+{
+ return EnsureFeeEstimator(EnsureAnyNodeContext(context));
+}
+
+CConnman& EnsureConnman(const NodeContext& node)
+{
+ if (!node.connman) {
+ throw JSONRPCError(RPC_CLIENT_P2P_DISABLED, "Error: Peer-to-peer functionality missing or disabled");
+ }
+ return *node.connman;
+}
+
+PeerManager& EnsurePeerman(const NodeContext& node)
+{
+ if (!node.peerman) {
+ throw JSONRPCError(RPC_CLIENT_P2P_DISABLED, "Error: Peer-to-peer functionality missing or disabled");
+ }
+ return *node.peerman;
+}
diff --git a/src/rpc/server_util.h b/src/rpc/server_util.h
new file mode 100644
index 0000000000..2cc710a803
--- /dev/null
+++ b/src/rpc/server_util.h
@@ -0,0 +1,32 @@
+// Copyright (c) 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.
+
+#ifndef BITCOIN_RPC_SERVER_UTIL_H
+#define BITCOIN_RPC_SERVER_UTIL_H
+
+#include <any>
+
+class ArgsManager;
+class CBlockPolicyEstimator;
+class CConnman;
+class CTxMemPool;
+class ChainstateManager;
+class PeerManager;
+namespace node {
+struct NodeContext;
+} // namespace node
+
+node::NodeContext& EnsureAnyNodeContext(const std::any& context);
+CTxMemPool& EnsureMemPool(const node::NodeContext& node);
+CTxMemPool& EnsureAnyMemPool(const std::any& context);
+ArgsManager& EnsureArgsman(const node::NodeContext& node);
+ArgsManager& EnsureAnyArgsman(const std::any& context);
+ChainstateManager& EnsureChainman(const node::NodeContext& node);
+ChainstateManager& EnsureAnyChainman(const std::any& context);
+CBlockPolicyEstimator& EnsureFeeEstimator(const node::NodeContext& node);
+CBlockPolicyEstimator& EnsureAnyFeeEstimator(const std::any& context);
+CConnman& EnsureConnman(const node::NodeContext& node);
+PeerManager& EnsurePeerman(const node::NodeContext& node);
+
+#endif // BITCOIN_RPC_SERVER_UTIL_H
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..dcf6c6bee1
--- /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(), chainman.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, chainman.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 2059628b54..7517f64ea1 100644
--- a/src/rpc/util.cpp
+++ b/src/rpc/util.cpp
@@ -2,24 +2,34 @@
// Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
+#include <consensus/amount.h>
#include <key_io.h>
#include <outputtype.h>
#include <rpc/util.h>
#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/system.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)
@@ -209,7 +219,7 @@ CPubKey AddrToPubKey(const FillableSigningProvider& keystore, const std::string&
}
CKeyID key = GetKeyForDestination(keystore, dest);
if (key.IsNull()) {
- throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, strprintf("%s does not refer to a key", addr_in));
+ throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, strprintf("'%s' does not refer to a key", addr_in));
}
CPubKey vchPubKey;
if (!keystore.GetPubKey(key, vchPubKey)) {
@@ -258,7 +268,7 @@ CTxDestination AddAndGetMultisigDestination(const int required, const std::vecto
class DescribeAddressVisitor
{
public:
- explicit DescribeAddressVisitor() {}
+ explicit DescribeAddressVisitor() = default;
UniValue operator()(const CNoDestination& dest) const
{
@@ -316,7 +326,7 @@ public:
UniValue obj(UniValue::VOBJ);
obj.pushKV("iswitness", true);
obj.pushKV("witness_version", (int)id.version);
- obj.pushKV("witness_program", HexStr(Span<const unsigned char>(id.program, id.length)));
+ obj.pushKV("witness_program", HexStr({id.program, id.length}));
return obj;
}
};
@@ -328,7 +338,7 @@ UniValue DescribeAddress(const CTxDestination& dest)
unsigned int ParseConfirmTarget(const UniValue& value, unsigned int max_target)
{
- const int target{value.get_int()};
+ const int target{value.getInt<int>()};
const unsigned int unsigned_target{static_cast<unsigned int>(target)};
if (target < 1 || unsigned_target > max_target) {
throw JSONRPCError(RPC_INVALID_PARAMETER, strprintf("Invalid conf_target, must be between %u and %u", 1, max_target));
@@ -410,7 +420,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()});
@@ -502,8 +512,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);
@@ -531,7 +540,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;
}
}
@@ -573,7 +582,9 @@ UniValue RPCHelpMan::HandleRequest(const JSONRPCRequest& request) const
throw std::runtime_error(ToString());
}
const UniValue ret = m_fun(*this, request);
- CHECK_NONFATAL(std::any_of(m_results.m_results.begin(), m_results.m_results.end(), [ret](const RPCResult& res) { return res.MatchesType(ret); }));
+ if (gArgs.GetBoolArg("-rpcdoccheck", DEFAULT_RPC_DOC_CHECK)) {
+ CHECK_NONFATAL(std::any_of(m_results.m_results.begin(), m_results.m_results.end(), [&ret](const RPCResult& res) { return res.MatchesType(ret); }));
+ }
return ret;
}
@@ -616,13 +627,12 @@ 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 += " )";
- ret += "\n";
// Description
- ret += m_description;
+ ret += "\n\n" + TrimString(m_description) + "\n";
// Arguments
Sections sections;
@@ -655,8 +665,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 +773,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 +792,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 +840,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 +859,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 +889,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 +983,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,17 +1020,17 @@ 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)
{
if (value.isNum()) {
- return {0, value.get_int64()};
+ return {0, value.getInt<int64_t>()};
}
if (value.isArray() && value.size() == 2 && value[0].isNum() && value[1].isNum()) {
- int64_t low = value[0].get_int64();
- int64_t high = value[1].get_int64();
+ int64_t low = value[0].getInt<int64_t>();
+ int64_t high = value[1].getInt<int64_t>();
if (low > high) throw JSONRPCError(RPC_INVALID_PARAMETER, "Range specified as [begin,end] must not have begin after end");
return {low, high};
}
diff --git a/src/rpc/util.h b/src/rpc/util.h
index d43ee33b0f..e883dc008e 100644
--- a/src/rpc/util.h
+++ b/src/rpc/util.h
@@ -5,7 +5,6 @@
#ifndef BITCOIN_RPC_UTIL_H
#define BITCOIN_RPC_UTIL_H
-#include <node/coinstats.h>
#include <node/transaction.h>
#include <outputtype.h>
#include <protocol.h>
@@ -22,6 +21,14 @@
#include <variant>
#include <vector>
+static constexpr bool DEFAULT_RPC_DOC_CHECK{
+#ifdef RPC_DOC_CHECK
+ true
+#else
+ false
+#endif
+};
+
/**
* String used to describe UNIX epoch time in documentation, factored out to a
* constant for consistency.
@@ -39,6 +46,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 +263,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 +278,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 +299,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 +328,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 {