aboutsummaryrefslogtreecommitdiff
path: root/src/rpc
diff options
context:
space:
mode:
Diffstat (limited to 'src/rpc')
-rw-r--r--src/rpc/blockchain.cpp431
-rw-r--r--src/rpc/blockchain.h2
-rw-r--r--src/rpc/client.cpp2
-rw-r--r--src/rpc/external_signer.cpp12
-rw-r--r--src/rpc/fees.cpp233
-rw-r--r--src/rpc/mempool.cpp289
-rw-r--r--src/rpc/mining.cpp293
-rw-r--r--src/rpc/misc.cpp824
-rw-r--r--src/rpc/net.cpp111
-rw-r--r--src/rpc/node.cpp440
-rw-r--r--src/rpc/output_script.cpp314
-rw-r--r--src/rpc/rawtransaction.cpp496
-rw-r--r--src/rpc/rawtransaction_util.cpp15
-rw-r--r--src/rpc/rawtransaction_util.h3
-rw-r--r--src/rpc/register.h18
-rw-r--r--src/rpc/request.cpp4
-rw-r--r--src/rpc/server.cpp38
-rw-r--r--src/rpc/signmessage.cpp113
-rw-r--r--src/rpc/txoutproof.cpp6
-rw-r--r--src/rpc/util.cpp39
-rw-r--r--src/rpc/util.h9
21 files changed, 2084 insertions, 1608 deletions
diff --git a/src/rpc/blockchain.cpp b/src/rpc/blockchain.cpp
index f46e5e9fef..8f116a05ef 100644
--- a/src/rpc/blockchain.cpp
+++ b/src/rpc/blockchain.cpp
@@ -19,11 +19,11 @@
#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 <primitives/transaction.h>
@@ -37,7 +37,9 @@
#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>
@@ -50,10 +52,10 @@
#include <memory>
#include <mutex>
+using kernel::CCoinsStats;
+using kernel::CoinStatsHashType;
+
using node::BlockManager;
-using node::CCoinsStats;
-using node::CoinStatsHashType;
-using node::GetUTXOStats;
using node::NodeContext;
using node::ReadBlockFromDisk;
using node::SnapshotMetadata;
@@ -65,7 +67,7 @@ struct CUpdatedBlock
int height;
};
-static Mutex cs_blockchange;
+static GlobalMutex cs_blockchange;
static std::condition_variable cond_blockchange;
static CUpdatedBlock latestblock GUARDED_BY(cs_blockchange);
@@ -109,7 +111,7 @@ static const CBlockIndex* ParseHashOrHeight(const UniValue& param, ChainstateMan
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));
}
@@ -270,7 +272,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;
{
@@ -316,7 +318,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;
{
@@ -360,10 +362,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;
{
@@ -395,7 +397,7 @@ static RPCHelpMan syncwithvalidationinterfacequeue()
[&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
{
SyncWithValidationInterfaceQueue();
- return NullUniValue;
+ return UniValue::VNULL;
},
};
}
@@ -439,12 +441,17 @@ static RPCHelpMan getblockfrompeer()
},
[&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
{
+ RPCTypeCheck(request.params, {
+ UniValue::VSTR, // blockhash
+ UniValue::VNUM, // peer_id
+ });
+
const NodeContext& node = EnsureAnyNodeContext(request.context);
ChainstateManager& chainman = EnsureChainman(node);
PeerManager& peerman = EnsurePeerman(node);
const uint256& block_hash{ParseHashV(request.params[0], "blockhash")};
- const NodeId peer_id{request.params[1].get_int64()};
+ const NodeId peer_id{request.params[1].getInt<int64_t>()};
const CBlockIndex* const index = WITH_LOCK(cs_main, return chainman.m_blockman.LookupBlockIndex(block_hash););
@@ -484,7 +491,7 @@ 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");
@@ -597,6 +604,30 @@ static CBlockUndo GetUndoChecked(BlockManager& blockman, const CBlockIndex* pblo
return blockUndo;
}
+const RPCResult getblock_vin{
+ 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::STR_AMOUNT, "value", "The value in " + CURRENCY_UNIT},
+ {RPCResult::Type::OBJ, "scriptPubKey", "",
+ {
+ {RPCResult::Type::STR, "asm", "Disassembly of the public key script"},
+ {RPCResult::Type::STR, "desc", "Inferred descriptor for the output"},
+ {RPCResult::Type::STR_HEX, "hex", "The raw public key script bytes, hex-encoded"},
+ {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() + ")"},
+ }},
+ }},
+ }},
+ }
+};
+
static RPCHelpMan getblock()
{
return RPCHelpMan{"getblock",
@@ -656,26 +687,7 @@ static RPCHelpMan getblock()
{
{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() + ")"},
- }},
- }},
- }},
- }},
+ getblock_vin,
}},
}},
}},
@@ -693,7 +705,7 @@ 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>();
}
}
@@ -758,9 +770,10 @@ 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).
@@ -775,22 +788,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);
},
};
}
@@ -808,6 +819,36 @@ CoinStatsHashType ParseHashType(const std::string& 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",
@@ -815,7 +856,7 @@ static RPCHelpMan gettxoutsetinfo()
"Note this call may take some time if you are not using coinstatsindex.\n",
{
{"hash_type", RPCArg::Type::STR, RPCArg::Default{"hash_serialized_2"}, "Which UTXO set hash should be calculated. Options: 'hash_serialized_2' (the legacy algorithm), 'muhash', 'none'."},
- {"hash_or_height", RPCArg::Type::NUM, RPCArg::Optional::OMITTED_NAMED_ARG, "The block hash or height of the target height (only available with coinstatsindex).", "", {"", "string or numeric"}},
+ {"hash_or_height", RPCArg::Type::NUM, RPCArg::DefaultHint{"the current best block"}, "The block hash or height of the target height (only available with coinstatsindex).", "", {"", "string or numeric"}},
{"use_index", RPCArg::Type::BOOL, RPCArg::Default{true}, "Use coinstatsindex, if available."},
},
RPCResult{
@@ -851,6 +892,7 @@ static RPCHelpMan gettxoutsetinfo()
HelpExampleCli("gettxoutsetinfo", R"("none")") +
HelpExampleCli("gettxoutsetinfo", R"("none" 1000)") +
HelpExampleCli("gettxoutsetinfo", R"("none" '"00000000c937983704a73af28acdec37b049d214adbda81d7e2a3dd146f6ed09"')") +
+ HelpExampleCli("-named gettxoutsetinfo", R"(hash_type='muhash' use_index='false')") +
HelpExampleRpc("gettxoutsetinfo", "") +
HelpExampleRpc("gettxoutsetinfo", R"("none")") +
HelpExampleRpc("gettxoutsetinfo", R"("none", 1000)") +
@@ -862,8 +904,7 @@ static RPCHelpMan gettxoutsetinfo()
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);
@@ -884,14 +925,17 @@ 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");
}
+ if (!index_requested) {
+ throw JSONRPCError(RPC_INVALID_PARAMETER, "Cannot set use_index to false when querying for a specific block");
+ }
pindex = ParseHashOrHeight(request.params[1], chainman);
}
- if (stats.index_requested && g_coin_stats_index) {
+ if (index_requested && g_coin_stats_index) {
if (!g_coin_stats_index->BlockUntilSyncedToCurrentChain()) {
const IndexSummary summary{g_coin_stats_index->GetSummary()};
@@ -903,7 +947,9 @@ static RPCHelpMan gettxoutsetinfo()
}
}
- if (GetUTXOStats(coins_view, *blockman, stats, node.rpc_interruption_point, pindex)) {
+ 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);
@@ -922,10 +968,13 @@ static RPCHelpMan gettxoutsetinfo()
} else {
ret.pushKV("total_unspendable_amount", ValueFromAmount(stats.total_unspendable_amount));
- CCoinsStats prev_stats{hash_type};
-
+ 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);
@@ -967,9 +1016,9 @@ static RPCHelpMan gettxout()
{RPCResult::Type::NUM, "confirmations", "The number of confirmations"},
{RPCResult::Type::STR_AMOUNT, "value", "The transaction value in " + CURRENCY_UNIT},
{RPCResult::Type::OBJ, "scriptPubKey", "", {
- {RPCResult::Type::STR, "asm", ""},
+ {RPCResult::Type::STR, "asm", "Disassembly of the public key script"},
{RPCResult::Type::STR, "desc", "Inferred descriptor for the output"},
- {RPCResult::Type::STR_HEX, "hex", ""},
+ {RPCResult::Type::STR_HEX, "hex", "The raw public key script bytes, hex-encoded"},
{RPCResult::Type::STR, "type", "The type, eg pubkeyhash"},
{RPCResult::Type::STR, "address", /*optional=*/true, "The Bitcoin address (only if a well-defined address exists)"},
}},
@@ -993,8 +1042,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();
@@ -1008,11 +1056,11 @@ static RPCHelpMan gettxout()
LOCK(mempool.cs);
CCoinsViewMemPool view(coins_view, mempool);
if (!view.GetCoin(out, coin) || mempool.isSpent(out)) {
- return NullUniValue;
+ return UniValue::VNULL;
}
} else {
if (!coins_view->GetCoin(out, coin)) {
- return NullUniValue;
+ return UniValue::VNULL;
}
}
@@ -1051,39 +1099,39 @@ 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().GetConsensus(), active_chainstate.CoinsTip(), check_level, check_depth);
+ active_chainstate, chainman.GetParams().GetConsensus(), active_chainstate.CoinsTip(), check_level, check_depth);
},
};
}
-static void SoftForkDescPushBack(const CBlockIndex* blockindex, 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");
// getdeploymentinfo reports the softfork as active from when the chain height is
// one below the activation height
- rv.pushKV("active", DeploymentActiveAfter(blockindex, 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* blockindex, 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 {
@@ -1099,29 +1147,29 @@ static void SoftForkDescPushBack(const CBlockIndex* blockindex, UniValue& softfo
UniValue bip9(UniValue::VOBJ);
- const ThresholdState next_state = g_versionbitscache.State(blockindex, consensusParams, id);
- const ThresholdState current_state = g_versionbitscache.State(blockindex->pprev, consensusParams, id);
+ 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", consensusParams.vDeployments[id].bit);
+ bip9.pushKV("bit", chainman.GetConsensus().vDeployments[id].bit);
}
- bip9.pushKV("start_time", consensusParams.vDeployments[id].nStartTime);
- bip9.pushKV("timeout", consensusParams.vDeployments[id].nTimeout);
- bip9.pushKV("min_activation_height", consensusParams.vDeployments[id].min_activation_height);
+ 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", g_versionbitscache.StateSinceHeight(blockindex->pprev, consensusParams, id));
+ 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);
std::vector<bool> signals;
- BIP9Stats statsStruct = g_versionbitscache.Statistics(blockindex, consensusParams, id, &signals);
+ BIP9Stats statsStruct = chainman.m_versionbitscache.Statistics(blockindex, chainman.GetConsensus(), id, &signals);
statsUV.pushKV("period", statsStruct.period);
statsUV.pushKV("elapsed", statsStruct.elapsed);
statsUV.pushKV("count", statsStruct.count);
@@ -1142,7 +1190,7 @@ static void SoftForkDescPushBack(const CBlockIndex* blockindex, UniValue& softfo
UniValue rv(UniValue::VOBJ);
rv.pushKV("type", "bip9");
if (ThresholdState::ACTIVE == next_state) {
- rv.pushKV("height", g_versionbitscache.StateSinceHeight(blockindex, consensusParams, id));
+ rv.pushKV("height", chainman.m_versionbitscache.StateSinceHeight(blockindex, chainman.GetConsensus(), id));
}
rv.pushKV("active", ThresholdState::ACTIVE == next_state);
rv.pushKV("bip9", bip9);
@@ -1150,16 +1198,9 @@ static void SoftForkDescPushBack(const CBlockIndex* blockindex, UniValue& softfo
softforks.pushKV(DeploymentName(id), rv);
}
-namespace {
-/* TODO: when -deprecatedrpc=softforks is removed, drop these */
-UniValue DeploymentInfo(const CBlockIndex* tip, const Consensus::Params& consensusParams);
-extern const std::vector<RPCResult> RPCHelpForDeployment;
-}
-
// used by rest.cpp:rest_chaininfo, so cannot be static
RPCHelpMan getblockchaininfo()
{
- /* TODO: from v24, remove -deprecatedrpc=softforks */
return RPCHelpMan{"getblockchaininfo",
"Returns an object containing various state info regarding blockchain processing.\n",
{},
@@ -1178,15 +1219,9 @@ RPCHelpMan getblockchaininfo()
{RPCResult::Type::STR_HEX, "chainwork", "total amount of work in active chain, in hexadecimal"},
{RPCResult::Type::NUM, "size_on_disk", "the estimated size of the block and undo files on disk"},
{RPCResult::Type::BOOL, "pruned", "if the blocks are subject to pruning"},
- {RPCResult::Type::NUM, "pruneheight", /*optional=*/true, "lowest-height complete block stored (only present if pruning is enabled)"},
+ {RPCResult::Type::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::OBJ_DYN, "softforks", /*optional=*/true, "(DEPRECATED, returned only if config option -deprecatedrpc=softforks is passed) status of softforks",
- {
- {RPCResult::Type::OBJ, "xxxx", "name of the softfork",
- RPCHelpForDeployment
- },
- }},
{RPCResult::Type::STR, "warnings", "any network and blockchain warnings"},
}},
RPCExamples{
@@ -1200,30 +1235,23 @@ RPCHelpMan getblockchaininfo()
LOCK(cs_main);
CChainState& active_chainstate = chainman.ActiveChainstate();
- const CBlockIndex* tip = active_chainstate.m_chain.Tip();
- CHECK_NONFATAL(tip);
- const int height = tip->nHeight;
+ const CBlockIndex& tip{*CHECK_NONFATAL(active_chainstate.m_chain.Tip())};
+ const int height{tip.nHeight};
UniValue obj(UniValue::VOBJ);
- obj.pushKV("chain", Params().NetworkIDString());
+ obj.pushKV("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", (double)GetDifficulty(tip));
- obj.pushKV("time", (int64_t)tip->nTime);
- obj.pushKV("mediantime", (int64_t)tip->GetMedianTimePast());
- obj.pushKV("verificationprogress", GuessVerificationProgress(Params().TxData(), tip));
+ obj.pushKV("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("chainwork", tip.nChainWork.GetHex());
obj.pushKV("size_on_disk", chainman.m_blockman.CalculateCurrentUsage());
obj.pushKV("pruned", node::fPruneMode);
if (node::fPruneMode) {
- const CBlockIndex* block = tip;
- CHECK_NONFATAL(block);
- while (block->pprev && (block->pprev->nStatus & BLOCK_HAVE_DATA)) {
- block = block->pprev;
- }
-
- obj.pushKV("pruneheight", block->nHeight);
+ obj.pushKV("pruneheight", chainman.m_blockman.GetFirstStoredBlock(tip)->nHeight);
// if 0, execution bypasses the whole if block.
bool automatic_pruning{args.GetIntArg("-prune", 0) != 1};
@@ -1233,11 +1261,6 @@ RPCHelpMan getblockchaininfo()
}
}
- if (IsDeprecatedRPCEnabled("softforks")) {
- const Consensus::Params& consensusParams = Params().GetConsensus();
- obj.pushKV("softforks", DeploymentInfo(tip, consensusParams));
- }
-
obj.pushKV("warnings", GetWarnings(false).original);
return obj;
},
@@ -1270,16 +1293,16 @@ const std::vector<RPCResult> RPCHelpForDeployment{
}},
};
-UniValue DeploymentInfo(const CBlockIndex* blockindex, const Consensus::Params& consensusParams)
+UniValue DeploymentInfo(const CBlockIndex* blockindex, const ChainstateManager& chainman)
{
UniValue softforks(UniValue::VOBJ);
- SoftForkDescPushBack(blockindex, softforks, consensusParams, Consensus::DEPLOYMENT_HEIGHTINCB);
- SoftForkDescPushBack(blockindex, softforks, consensusParams, Consensus::DEPLOYMENT_DERSIG);
- SoftForkDescPushBack(blockindex, softforks, consensusParams, Consensus::DEPLOYMENT_CLTV);
- SoftForkDescPushBack(blockindex, softforks, consensusParams, Consensus::DEPLOYMENT_CSV);
- SoftForkDescPushBack(blockindex, softforks, consensusParams, Consensus::DEPLOYMENT_SEGWIT);
- SoftForkDescPushBack(blockindex, softforks, consensusParams, Consensus::DEPLOYMENT_TESTDUMMY);
- SoftForkDescPushBack(blockindex, softforks, consensusParams, Consensus::DEPLOYMENT_TAPROOT);
+ 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
@@ -1309,8 +1332,7 @@ static RPCHelpMan getdeploymentinfo()
const CBlockIndex* blockindex;
if (request.params[0].isNull()) {
- blockindex = active_chainstate.m_chain.Tip();
- CHECK_NONFATAL(blockindex);
+ blockindex = CHECK_NONFATAL(active_chainstate.m_chain.Tip());
} else {
const uint256 hash(ParseHashV(request.params[0], "blockhash"));
blockindex = chainman.m_blockman.LookupBlockIndex(hash);
@@ -1319,12 +1341,10 @@ static RPCHelpMan getdeploymentinfo()
}
}
- const Consensus::Params& consensusParams = Params().GetConsensus();
-
UniValue deploymentinfo(UniValue::VOBJ);
deploymentinfo.pushKV("hash", blockindex->GetBlockHash().ToString());
deploymentinfo.pushKV("height", blockindex->nHeight);
- deploymentinfo.pushKV("deployments", DeploymentInfo(blockindex, consensusParams));
+ deploymentinfo.pushKV("deployments", DeploymentInfo(blockindex, chainman));
return deploymentinfo;
},
};
@@ -1478,7 +1498,7 @@ static RPCHelpMan preciousblock()
throw JSONRPCError(RPC_DATABASE_ERROR, state.ToString());
}
- return NullUniValue;
+ return UniValue::VNULL;
},
};
}
@@ -1519,7 +1539,7 @@ static RPCHelpMan invalidateblock()
throw JSONRPCError(RPC_DATABASE_ERROR, state.ToString());
}
- return NullUniValue;
+ return UniValue::VNULL;
},
};
}
@@ -1559,7 +1579,7 @@ static RPCHelpMan reconsiderblock()
throw JSONRPCError(RPC_DATABASE_ERROR, state.ToString());
}
- return NullUniValue;
+ return UniValue::VNULL;
},
};
}
@@ -1592,7 +1612,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);
@@ -1614,16 +1634,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);
@@ -1764,8 +1784,7 @@ static RPCHelpMan getblockstats()
{
ChainstateManager& chainman = EnsureAnyChainman(request.context);
LOCK(cs_main);
- const 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()) {
@@ -1776,8 +1795,8 @@ static RPCHelpMan getblockstats()
}
}
- const CBlock block = GetBlockChecked(chainman.m_blockman, pindex);
- const CBlockUndo blockUndo = GetUndoChecked(chainman.m_blockman, 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;
@@ -1895,25 +1914,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);
@@ -1978,9 +1997,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);
@@ -2036,13 +2055,7 @@ static RPCHelpMan scantxoutset()
"[scanobjects,...]"},
},
{
- RPCResult{"When action=='abort'", RPCResult::Type::BOOL, "", ""},
- RPCResult{"When action=='status' and no scan is in progress", RPCResult::Type::NONE, "", ""},
- RPCResult{"When action=='status' and scan is in progress", RPCResult::Type::OBJ, "", "",
- {
- {RPCResult::Type::NUM, "progress", "The scan progress"},
- }},
- RPCResult{"When action=='start'", RPCResult::Type::OBJ, "", "", {
+ RPCResult{"when action=='start'; only returns after scan completes", RPCResult::Type::OBJ, "", "", {
{RPCResult::Type::BOOL, "success", "Whether the scan was completed"},
{RPCResult::Type::NUM, "txouts", "The number of unspent transaction outputs scanned"},
{RPCResult::Type::NUM, "height", "The current block height (index)"},
@@ -2061,6 +2074,12 @@ static RPCHelpMan scantxoutset()
}},
{RPCResult::Type::STR_AMOUNT, "total_amount", "The total amount of all found unspent outputs in " + CURRENCY_UNIT},
}},
+ RPCResult{"when action=='abort'", RPCResult::Type::BOOL, "success", "True if scan will be aborted (not necessarily before this RPC returns), or false if there is no scan to abort"},
+ RPCResult{"when action=='status' and a scan is currently in progress", RPCResult::Type::OBJ, "", "",
+ {
+ {RPCResult::Type::NUM, "progress", "Approximate percent complete"},
+ }},
+ RPCResult{"when action=='status' and no scan is in progress - possibly already completed", RPCResult::Type::NONE, "", ""},
},
RPCExamples{
HelpExampleCli("scantxoutset", "start \'[\"" + EXAMPLE_DESCRIPTOR_RAW + "\"]\'") +
@@ -2079,9 +2098,9 @@ static RPCHelpMan scantxoutset()
CoinsViewScanReserver reserver;
if (reserver.reserve()) {
// no scan in progress
- return NullUniValue;
+ return UniValue::VNULL;
}
- 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;
@@ -2131,10 +2150,8 @@ static RPCHelpMan scantxoutset()
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);
@@ -2175,7 +2192,7 @@ static RPCHelpMan getblockfilter()
"\nRetrieve a BIP 157 content filter for a particular block.\n",
{
{"blockhash", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "The hash of the block"},
- {"filtertype", RPCArg::Type::STR, RPCArg::Default{"basic"}, "The type name of the filter"},
+ {"filtertype", RPCArg::Type::STR, RPCArg::Default{BlockFilterTypeName(BlockFilterType::BASIC)}, "The type name of the filter"},
},
RPCResult{
RPCResult::Type::OBJ, "", "",
@@ -2190,7 +2207,7 @@ static RPCHelpMan getblockfilter()
[&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
{
uint256 block_hash = ParseHashV(request.params[0], "blockhash");
- std::string filtertype_name = "basic";
+ std::string filtertype_name = BlockFilterTypeName(BlockFilterType::BASIC);
if (!request.params[1].isNull()) {
filtertype_name = request.params[1].get_str();
}
@@ -2291,7 +2308,13 @@ static RPCHelpMan dumptxoutset()
}
FILE* file{fsbridge::fopen(temppath, "wb")};
- CAutoFile afile{file, SER_DISK, CLIENT_VERSION};
+ AutoFile afile{file};
+ 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, path, temppath);
@@ -2306,12 +2329,12 @@ static RPCHelpMan dumptxoutset()
UniValue CreateUTXOSnapshot(
NodeContext& node,
CChainState& chainstate,
- CAutoFile& afile,
+ AutoFile& afile,
const fs::path& path,
const fs::path& temppath)
{
std::unique_ptr<CCoinsViewCursor> pcursor;
- CCoinsStats stats{CoinStatsHashType::HASH_SERIALIZED};
+ std::optional<CCoinsStats> maybe_stats;
const CBlockIndex* tip;
{
@@ -2331,20 +2354,20 @@ UniValue CreateUTXOSnapshot(
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));
}
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(), stats.coins_count, tip->nChainTx};
+ SnapshotMetadata metadata{tip->GetBlockHash(), maybe_stats->coins_count, tip->nChainTx};
afile << metadata;
@@ -2366,55 +2389,47 @@ UniValue CreateUTXOSnapshot(
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", stats.hashSerialized.ToString());
+ 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", &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, },
-
- /* 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 5fbd9d5fd3..a332fd4892 100644
--- a/src/rpc/blockchain.h
+++ b/src/rpc/blockchain.h
@@ -55,7 +55,7 @@ void CalculatePercentilesByWeight(CAmount result[NUM_GETBLOCKSTATS_PERCENTILES],
UniValue CreateUTXOSnapshot(
node::NodeContext& node,
CChainState& chainstate,
- CAutoFile& afile,
+ AutoFile& afile,
const fs::path& path,
const fs::path& tmppath);
diff --git a/src/rpc/client.cpp b/src/rpc/client.cpp
index 23e9d4074c..9be3ab7df0 100644
--- a/src/rpc/client.cpp
+++ b/src/rpc/client.cpp
@@ -110,6 +110,7 @@ static const CRPCConvertParam vRPCConvertParams[] =
{ "sendrawtransaction", 1, "maxfeerate" },
{ "testmempoolaccept", 0, "rawtxs" },
{ "testmempoolaccept", 1, "maxfeerate" },
+ { "submitpackage", 0, "package" },
{ "combinerawtransaction", 0, "txs" },
{ "fundrawtransaction", 1, "options" },
{ "fundrawtransaction", 2, "iswitness" },
@@ -173,6 +174,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" },
diff --git a/src/rpc/external_signer.cpp b/src/rpc/external_signer.cpp
index 82aa6f9516..4de7fc4205 100644
--- a/src/rpc/external_signer.cpp
+++ b/src/rpc/external_signer.cpp
@@ -62,15 +62,11 @@ static RPCHelpMan enumeratesigners()
};
}
-void RegisterSignerRPCCommands(CRPCTable &t)
+void RegisterSignerRPCCommands(CRPCTable& t)
{
-// clang-format off
-static const CRPCCommand commands[] =
-{ // category actor (function)
- // --------------------- ------------------------
- { "signer", &enumeratesigners, },
-};
-// clang-format on
+ static const CRPCCommand commands[]{
+ {"signer", &enumeratesigners},
+ };
for (const auto& c : commands) {
t.appendCommand(c.name, &c);
}
diff --git a/src/rpc/fees.cpp b/src/rpc/fees.cpp
new file mode 100644
index 0000000000..aa047bdea8
--- /dev/null
+++ b/src/rpc/fees.cpp
@@ -0,0 +1,233 @@
+// 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 <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 <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()};
+ CFeeRate min_relay_feerate{mempool.m_min_relay_feerate};
+ 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
index 1caf4ad96c..02b51ce0a0 100644
--- a/src/rpc/mempool.cpp
+++ b/src/rpc/mempool.cpp
@@ -5,9 +5,14 @@
#include <rpc/blockchain.h>
+#include <kernel/mempool_persist.h>
+
+#include <chainparams.h>
#include <core_io.h>
#include <fs.h>
+#include <node/mempool_persist_args.h>
#include <policy/rbf.h>
+#include <policy/settings.h>
#include <primitives/transaction.h>
#include <rpc/server.h>
#include <rpc/server_util.h>
@@ -15,9 +20,12 @@
#include <txmempool.h>
#include <univalue.h>
#include <util/moneystr.h>
-#include <validation.h>
+#include <util/time.h>
+
+using kernel::DumpMempool;
using node::DEFAULT_MAX_RAW_TX_FEE_RATE;
+using node::MempoolPath;
using node::NodeContext;
static RPCHelpMan sendrawtransaction()
@@ -229,23 +237,12 @@ static std::vector<RPCResult> MempoolEntryDescription()
return {
RPCResult{RPCResult::Type::NUM, "vsize", "virtual transaction size as defined in BIP 141. This is different from actual serialized size for witness transactions as witness data is discounted."},
RPCResult{RPCResult::Type::NUM, "weight", "transaction weight as defined in BIP 141."},
- RPCResult{RPCResult::Type::STR_AMOUNT, "fee", /*optional=*/true,
- "transaction fee, denominated in " + CURRENCY_UNIT + " (DEPRECATED, returned only if config option -deprecatedrpc=fees is passed)"},
- RPCResult{RPCResult::Type::STR_AMOUNT, "modifiedfee", /*optional=*/true,
- "transaction fee with fee deltas used for mining priority, denominated in " + CURRENCY_UNIT +
- " (DEPRECATED, returned only if config option -deprecatedrpc=fees is passed)"},
RPCResult{RPCResult::Type::NUM_TIME, "time", "local time transaction entered pool in seconds since 1 Jan 1970 GMT"},
RPCResult{RPCResult::Type::NUM, "height", "block height when transaction entered pool"},
RPCResult{RPCResult::Type::NUM, "descendantcount", "number of in-mempool descendant transactions (including this one)"},
RPCResult{RPCResult::Type::NUM, "descendantsize", "virtual transaction size of in-mempool descendants (including this one)"},
- RPCResult{RPCResult::Type::STR_AMOUNT, "descendantfees", /*optional=*/true,
- "transaction fees of in-mempool descendants (including this one) with fee deltas used for mining priority, denominated in " +
- CURRENCY_ATOM + "s (DEPRECATED, returned only if config option -deprecatedrpc=fees is passed)"},
RPCResult{RPCResult::Type::NUM, "ancestorcount", "number of in-mempool ancestor transactions (including this one)"},
RPCResult{RPCResult::Type::NUM, "ancestorsize", "virtual transaction size of in-mempool ancestors (including this one)"},
- RPCResult{RPCResult::Type::STR_AMOUNT, "ancestorfees", /*optional=*/true,
- "transaction fees of in-mempool ancestors (including this one) with fee deltas used for mining priority, denominated in " +
- CURRENCY_ATOM + "s (DEPRECATED, returned only if config option -deprecatedrpc=fees is passed)"},
RPCResult{RPCResult::Type::STR_HEX, "wtxid", "hash of serialized transaction, including witness data"},
RPCResult{RPCResult::Type::OBJ, "fees", "",
{
@@ -269,24 +266,12 @@ static void entryToJSON(const CTxMemPool& pool, UniValue& info, const CTxMemPool
info.pushKV("vsize", (int)e.GetTxSize());
info.pushKV("weight", (int)e.GetTxWeight());
- // TODO: top-level fee fields are deprecated. deprecated_fee_fields_enabled blocks should be removed in v24
- const bool deprecated_fee_fields_enabled{IsDeprecatedRPCEnabled("fees")};
- if (deprecated_fee_fields_enabled) {
- info.pushKV("fee", ValueFromAmount(e.GetFee()));
- info.pushKV("modifiedfee", ValueFromAmount(e.GetModifiedFee()));
- }
info.pushKV("time", count_seconds(e.GetTime()));
info.pushKV("height", (int)e.GetHeight());
info.pushKV("descendantcount", e.GetCountWithDescendants());
info.pushKV("descendantsize", e.GetSizeWithDescendants());
- if (deprecated_fee_fields_enabled) {
- info.pushKV("descendantfees", e.GetModFeesWithDescendants());
- }
info.pushKV("ancestorcount", e.GetCountWithAncestors());
info.pushKV("ancestorsize", e.GetSizeWithAncestors());
- if (deprecated_fee_fields_enabled) {
- info.pushKV("ancestorfees", e.GetModFeesWithAncestors());
- }
info.pushKV("wtxid", pool.vTxHashes[e.vTxHashesIdx].first.ToString());
UniValue fees(UniValue::VOBJ);
@@ -587,28 +572,112 @@ static RPCHelpMan getmempoolentry()
};
}
+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("loaded", pool.GetLoadTried());
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("maxmempool", pool.m_max_size_bytes);
+ ret.pushKV("mempoolminfee", ValueFromAmount(std::max(pool.GetMinFee(), pool.m_min_relay_feerate).GetFeePerK()));
+ ret.pushKV("minrelaytxfee", ValueFromAmount(pool.m_min_relay_feerate.GetFeePerK()));
+ ret.pushKV("incrementalrelayfee", ValueFromAmount(pool.m_incremental_relay_feerate.GetFeePerK()));
ret.pushKV("unbroadcastcount", uint64_t{pool.GetUnbroadcastTxs().size()});
+ ret.pushKV("fullrbf", pool.m_full_rbf);
return ret;
}
static RPCHelpMan getmempoolinfo()
{
return RPCHelpMan{"getmempoolinfo",
- "\nReturns details on the active state of the TX memory pool.\n",
+ "Returns details on the active state of the TX memory pool.",
{},
RPCResult{
RPCResult::Type::OBJ, "", "",
@@ -621,7 +690,9 @@ static RPCHelpMan getmempoolinfo()
{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"}
+ {RPCResult::Type::NUM, "incrementalrelayfee", "minimum fee rate increment for mempool limiting or BIP 125 replacement in " + CURRENCY_UNIT + "/kvB"},
+ {RPCResult::Type::NUM, "unbroadcastcount", "Current number of transactions that haven't passed initial broadcast yet"},
+ {RPCResult::Type::BOOL, "fullrbf", "True if the mempool accepts RBF without replaceability signaling inspection"},
}},
RPCExamples{
HelpExampleCli("getmempoolinfo", "")
@@ -653,35 +724,181 @@ static RPCHelpMan savemempool()
const ArgsManager& args{EnsureAnyArgsman(request.context)};
const CTxMemPool& mempool = EnsureAnyMemPool(request.context);
- if (!mempool.IsLoaded()) {
+ if (!mempool.GetLoadTried()) {
throw JSONRPCError(RPC_MISC_ERROR, "The mempool was not loaded yet");
}
- if (!DumpMempool(mempool)) {
+ const fs::path& dump_path = MempoolPath(args);
+
+ if (!DumpMempool(mempool, dump_path)) {
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());
+ ret.pushKV("filename", dump_path.u8string());
return ret;
},
};
}
+static RPCHelpMan submitpackage()
+{
+ return RPCHelpMan{"submitpackage",
+ "Submit a package of raw transactions (serialized, hex-encoded) to local node (-regtest only).\n"
+ "The package will be validated according to consensus and mempool policy rules. If all transactions pass, they will be accepted to mempool.\n"
+ "This RPC is experimental and the interface may be unstable. Refer to doc/policy/packages.md for documentation on package policies.\n"
+ "Warning: until package relay is in use, successful submission does not mean the transaction will propagate to other nodes on the network.\n"
+ "Currently, each transaction is broadcasted individually after submission, which means they must meet other nodes' feerate requirements alone.\n"
+ ,
+ {
+ {"package", RPCArg::Type::ARR, RPCArg::Optional::NO, "An array of raw transactions.",
+ {
+ {"rawtx", RPCArg::Type::STR_HEX, RPCArg::Optional::OMITTED, ""},
+ },
+ },
+ },
+ RPCResult{
+ RPCResult::Type::OBJ, "", "",
+ {
+ {RPCResult::Type::OBJ_DYN, "tx-results", "transaction results keyed by wtxid",
+ {
+ {RPCResult::Type::OBJ, "wtxid", "transaction wtxid", {
+ {RPCResult::Type::STR_HEX, "txid", "The transaction hash in hex"},
+ {RPCResult::Type::STR_HEX, "other-wtxid", /*optional=*/true, "The wtxid of a different transaction with the same txid but different witness found in the mempool. This means the submitted transaction was ignored."},
+ {RPCResult::Type::NUM, "vsize", "Virtual transaction size as defined in BIP 141."},
+ {RPCResult::Type::OBJ, "fees", "Transaction fees", {
+ {RPCResult::Type::STR_AMOUNT, "base", "transaction fee in " + CURRENCY_UNIT},
+ }},
+ }}
+ }},
+ {RPCResult::Type::STR_AMOUNT, "package-feerate", /*optional=*/true, "package feerate used for feerate checks in " + CURRENCY_UNIT + " per KvB. Excludes transactions which were deduplicated or accepted individually."},
+ {RPCResult::Type::ARR, "replaced-transactions", /*optional=*/true, "List of txids of replaced transactions",
+ {
+ {RPCResult::Type::STR_HEX, "", "The transaction id"},
+ }},
+ },
+ },
+ RPCExamples{
+ HelpExampleCli("testmempoolaccept", "[rawtx1, rawtx2]") +
+ HelpExampleCli("submitpackage", "[rawtx1, rawtx2]")
+ },
+ [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
+ {
+ if (!Params().IsMockableChain()) {
+ throw std::runtime_error("submitpackage is for regression testing (-regtest mode) only");
+ }
+ RPCTypeCheck(request.params, {
+ UniValue::VARR,
+ });
+ 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.");
+ }
+
+ 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 auto package_result = WITH_LOCK(::cs_main, return ProcessNewPackage(chainstate, mempool, txns, /*test_accept=*/ false));
+
+ // First catch any errors.
+ switch(package_result.m_state.GetResult()) {
+ case PackageValidationResult::PCKG_RESULT_UNSET: break;
+ case PackageValidationResult::PCKG_POLICY:
+ {
+ throw JSONRPCTransactionError(TransactionError::INVALID_PACKAGE,
+ package_result.m_state.GetRejectReason());
+ }
+ case PackageValidationResult::PCKG_MEMPOOL_ERROR:
+ {
+ throw JSONRPCTransactionError(TransactionError::MEMPOOL_ERROR,
+ package_result.m_state.GetRejectReason());
+ }
+ case PackageValidationResult::PCKG_TX:
+ {
+ for (const auto& tx : txns) {
+ auto it = package_result.m_tx_results.find(tx->GetWitnessHash());
+ if (it != package_result.m_tx_results.end() && it->second.m_state.IsInvalid()) {
+ throw JSONRPCTransactionError(TransactionError::MEMPOOL_REJECTED,
+ strprintf("%s failed: %s", tx->GetHash().ToString(), it->second.m_state.GetRejectReason()));
+ }
+ }
+ // If a PCKG_TX error was returned, there must have been an invalid transaction.
+ NONFATAL_UNREACHABLE();
+ }
+ }
+ for (const auto& tx : txns) {
+ size_t num_submitted{0};
+ std::string err_string;
+ const auto err = BroadcastTransaction(node, tx, err_string, 0, true, true);
+ if (err != TransactionError::OK) {
+ throw JSONRPCTransactionError(err,
+ strprintf("transaction broadcast failed: %s (all transactions were submitted, %d transactions were broadcast successfully)",
+ err_string, num_submitted));
+ }
+ }
+ UniValue rpc_result{UniValue::VOBJ};
+ UniValue tx_result_map{UniValue::VOBJ};
+ std::set<uint256> replaced_txids;
+ for (const auto& tx : txns) {
+ auto it = package_result.m_tx_results.find(tx->GetWitnessHash());
+ CHECK_NONFATAL(it != package_result.m_tx_results.end());
+ UniValue result_inner{UniValue::VOBJ};
+ result_inner.pushKV("txid", tx->GetHash().GetHex());
+ if (it->second.m_result_type == MempoolAcceptResult::ResultType::DIFFERENT_WITNESS) {
+ result_inner.pushKV("other-wtxid", it->second.m_other_wtxid.value().GetHex());
+ }
+ if (it->second.m_result_type == MempoolAcceptResult::ResultType::VALID ||
+ it->second.m_result_type == MempoolAcceptResult::ResultType::MEMPOOL_ENTRY) {
+ result_inner.pushKV("vsize", int64_t{it->second.m_vsize.value()});
+ UniValue fees(UniValue::VOBJ);
+ fees.pushKV("base", ValueFromAmount(it->second.m_base_fees.value()));
+ result_inner.pushKV("fees", fees);
+ if (it->second.m_replaced_transactions.has_value()) {
+ for (const auto& ptx : it->second.m_replaced_transactions.value()) {
+ replaced_txids.insert(ptx->GetHash());
+ }
+ }
+ }
+ tx_result_map.pushKV(tx->GetWitnessHash().GetHex(), result_inner);
+ }
+ rpc_result.pushKV("tx-results", tx_result_map);
+ if (package_result.m_package_feerate.has_value()) {
+ rpc_result.pushKV("package-feerate", ValueFromAmount(package_result.m_package_feerate.value().GetFeePerK()));
+ }
+ UniValue replaced_list(UniValue::VARR);
+ for (const uint256& hash : replaced_txids) replaced_list.push_back(hash.ToString());
+ rpc_result.pushKV("replaced-transactions", replaced_list);
+ return rpc_result;
+ },
+ };
+}
+
void RegisterMempoolRPCCommands(CRPCTable& t)
{
static const CRPCCommand commands[]{
- // category actor (function)
- // -------- ----------------
{"rawtransactions", &sendrawtransaction},
{"rawtransactions", &testmempoolaccept},
{"blockchain", &getmempoolancestors},
{"blockchain", &getmempooldescendants},
{"blockchain", &getmempoolentry},
+ {"blockchain", &gettxspendingprevout},
{"blockchain", &getmempoolinfo},
{"blockchain", &getrawmempool},
{"blockchain", &savemempool},
+ {"hidden", &submitpackage},
};
for (const auto& c : commands) {
t.appendCommand(c.name, &c);
diff --git a/src/rpc/mining.cpp b/src/rpc/mining.cpp
index 211026c8d9..2902b35865 100644
--- a/src/rpc/mining.cpp
+++ b/src/rpc/mining.cpp
@@ -17,7 +17,6 @@
#include <net.h>
#include <node/context.h>
#include <node/miner.h>
-#include <policy/fees.h>
#include <pow.h>
#include <rpc/blockchain.h>
#include <rpc/mining.h>
@@ -28,9 +27,9 @@
#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>
@@ -111,7 +110,7 @@ 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());
},
};
}
@@ -121,9 +120,7 @@ static bool GenerateBlock(ChainstateManager& chainman, CBlock& block, uint64_t&
block_hash.SetNull();
block.hashMerkleRoot = BlockMerkleRoot(block);
- 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;
}
@@ -135,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");
}
@@ -147,7 +144,7 @@ static UniValue generateBlocks(ChainstateManager& chainman, const CTxMemPool& me
{
UniValue blockHashes(UniValue::VARR);
while (nGenerate > 0 && !ShutdownRequested()) {
- std::unique_ptr<CBlockTemplate> pblocktemplate(BlockAssembler(chainman.ActiveChainstate(), mempool, Params()).CreateNewBlock(coinbase_script));
+ 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;
@@ -219,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;
@@ -266,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)) {
@@ -351,15 +348,13 @@ static RPCHelpMan generateblock()
}
}
- CChainParams chainparams(Params());
CBlock block;
ChainstateManager& chainman = EnsureChainman(node);
{
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(), nullptr}.CreateNewBlock(coinbase_script));
if (!blocktemplate) {
throw JSONRPCError(RPC_INTERNAL_ERROR, "Couldn't create new block");
}
@@ -376,7 +371,7 @@ 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()));
}
}
@@ -431,7 +426,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;
},
@@ -464,7 +459,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.");
@@ -481,7 +476,7 @@ static RPCHelpMan prioritisetransaction()
static UniValue BIP22ValidationResult(const BlockValidationState& state)
{
if (state.IsValid())
- return NullUniValue;
+ return UniValue::VNULL;
if (state.IsError())
throw JSONRPCError(RPC_VERIFY_ERROR, state.ToString());
@@ -645,7 +640,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);
}
@@ -659,7 +654,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>();
}
}
}
@@ -667,7 +662,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!");
@@ -728,7 +723,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) {
@@ -757,7 +752,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");
@@ -772,7 +767,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");
@@ -838,7 +833,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:
@@ -846,7 +841,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:
{
@@ -855,7 +850,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;
@@ -932,10 +927,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 {
@@ -995,14 +990,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, /*force_processing=*/true, /*new_block=*/&new_block);
+ bool accepted = chainman.ProcessNewBlock(blockptr, /*force_processing=*/true, /*new_block=*/&new_block);
UnregisterSharedValidationInterface(sc);
if (!new_block && accepted) {
return "duplicate";
@@ -1044,8 +1039,8 @@ static RPCHelpMan submitheader()
}
BlockValidationState state;
- chainman.ProcessNewBlockHeaders({h}, state, Params());
- if (state.IsValid()) return NullUniValue;
+ chainman.ProcessNewBlockHeaders({h}, state);
+ if (state.IsValid()) return UniValue::VNULL;
if (state.IsError()) {
throw JSONRPCError(RPC_VERIFY_ERROR, state.ToString());
}
@@ -1054,225 +1049,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") +
- 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
+void RegisterMiningRPCCommands(CRPCTable& t)
{
- 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, },
-
-
- { "hidden", &generatetoaddress, },
- { "hidden", &generatetodescriptor, },
- { "hidden", &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 99671ee6ac..0000000000
--- a/src/rpc/misc.cpp
+++ /dev/null
@@ -1,824 +0,0 @@
-// Copyright (c) 2010 Satoshi Nakamoto
-// Copyright (c) 2009-2021 The Bitcoin Core developers
-// Distributed under the MIT software license, see the accompanying
-// file COPYING or http://www.opensource.org/licenses/mit-license.php.
-
-#include <httpserver.h>
-#include <index/blockfilterindex.h>
-#include <index/coinstatsindex.h>
-#include <index/txindex.h>
-#include <interfaces/chain.h>
-#include <interfaces/echo.h>
-#include <interfaces/init.h>
-#include <interfaces/ipc.h>
-#include <key_io.h>
-#include <node/context.h>
-#include <outputtype.h>
-#include <rpc/blockchain.h>
-#include <rpc/server.h>
-#include <rpc/server_util.h>
-#include <rpc/util.h>
-#include <scheduler.h>
-#include <script/descriptor.h>
-#include <util/check.h>
-#include <util/message.h> // For MessageSign(), MessageVerify()
-#include <util/strencodings.h>
-#include <util/syscall_sandbox.h>
-#include <util/system.h>
-
-#include <optional>
-#include <stdint.h>
-#include <tuple>
-#ifdef HAVE_MALLOC_INFO
-#include <malloc.h>
-#endif
-
-#include <univalue.h>
-
-using node::NodeContext;
-
-static RPCHelpMan validateaddress()
-{
- return RPCHelpMan{
- "validateaddress",
- "\nReturn information about the given bitcoin address.\n",
- {
- {"address", RPCArg::Type::STR, RPCArg::Optional::NO, "The bitcoin address to validate"},
- },
- RPCResult{
- RPCResult::Type::OBJ, "", "",
- {
- {RPCResult::Type::BOOL, "isvalid", "If the address is valid or not"},
- {RPCResult::Type::STR, "address", /*optional=*/true, "The bitcoin address validated"},
- {RPCResult::Type::STR_HEX, "scriptPubKey", /*optional=*/true, "The hex-encoded scriptPubKey generated by the address"},
- {RPCResult::Type::BOOL, "isscript", /*optional=*/true, "If the key is a script"},
- {RPCResult::Type::BOOL, "iswitness", /*optional=*/true, "If the address is a witness address"},
- {RPCResult::Type::NUM, "witness_version", /*optional=*/true, "The version number of the witness program"},
- {RPCResult::Type::STR_HEX, "witness_program", /*optional=*/true, "The hex value of the witness program"},
- {RPCResult::Type::STR, "error", /*optional=*/true, "Error message, if any"},
- {RPCResult::Type::ARR, "error_locations", /*optional=*/true, "Indices of likely error locations in address, if known (e.g. Bech32 errors)",
- {
- {RPCResult::Type::NUM, "index", "index of a potential error"},
- }},
- }
- },
- RPCExamples{
- HelpExampleCli("validateaddress", "\"" + EXAMPLE_ADDRESS[0] + "\"") +
- HelpExampleRpc("validateaddress", "\"" + EXAMPLE_ADDRESS[0] + "\"")
- },
- [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
-{
- std::string error_msg;
- std::vector<int> error_locations;
- CTxDestination dest = DecodeDestination(request.params[0].get_str(), error_msg, &error_locations);
- const bool isValid = IsValidDestination(dest);
- CHECK_NONFATAL(isValid == error_msg.empty());
-
- UniValue ret(UniValue::VOBJ);
- ret.pushKV("isvalid", isValid);
- if (isValid) {
- std::string currentAddress = EncodeDestination(dest);
- ret.pushKV("address", currentAddress);
-
- CScript scriptPubKey = GetScriptForDestination(dest);
- ret.pushKV("scriptPubKey", HexStr(scriptPubKey));
-
- UniValue detail = DescribeAddress(dest);
- ret.pushKVs(detail);
- } else {
- UniValue error_indices(UniValue::VARR);
- for (int i : error_locations) error_indices.push_back(i);
- ret.pushKV("error_locations", error_indices);
- ret.pushKV("error", error_msg);
- }
-
- return ret;
-},
- };
-}
-
-static RPCHelpMan createmultisig()
-{
- return RPCHelpMan{"createmultisig",
- "\nCreates a multi-signature address with n signature of m keys required.\n"
- "It returns a json object with the address and redeemScript.\n",
- {
- {"nrequired", RPCArg::Type::NUM, RPCArg::Optional::NO, "The number of required signatures out of the n keys."},
- {"keys", RPCArg::Type::ARR, RPCArg::Optional::NO, "The hex-encoded public keys.",
- {
- {"key", RPCArg::Type::STR_HEX, RPCArg::Optional::OMITTED, "The hex-encoded public key"},
- }},
- {"address_type", RPCArg::Type::STR, RPCArg::Default{"legacy"}, "The address type to use. Options are \"legacy\", \"p2sh-segwit\", and \"bech32\"."},
- },
- RPCResult{
- RPCResult::Type::OBJ, "", "",
- {
- {RPCResult::Type::STR, "address", "The value of the new multisig address."},
- {RPCResult::Type::STR_HEX, "redeemScript", "The string value of the hex-encoded redemption script."},
- {RPCResult::Type::STR, "descriptor", "The descriptor for this multisig"},
- {RPCResult::Type::ARR, "warnings", /*optional=*/true, "Any warnings resulting from the creation of this multisig",
- {
- {RPCResult::Type::STR, "", ""},
- }},
- }
- },
- RPCExamples{
- "\nCreate a multisig address from 2 public keys\n"
- + HelpExampleCli("createmultisig", "2 \"[\\\"03789ed0bb717d88f7d321a368d905e7430207ebbd82bd342cf11ae157a7ace5fd\\\",\\\"03dbc6764b8884a92e871274b87583e6d5c2a58819473e17e107ef3f6aa5a61626\\\"]\"") +
- "\nAs a JSON-RPC call\n"
- + HelpExampleRpc("createmultisig", "2, [\"03789ed0bb717d88f7d321a368d905e7430207ebbd82bd342cf11ae157a7ace5fd\",\"03dbc6764b8884a92e871274b87583e6d5c2a58819473e17e107ef3f6aa5a61626\"]")
- },
- [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
-{
- int required = request.params[0].get_int();
-
- // Get the public keys
- const UniValue& keys = request.params[1].get_array();
- std::vector<CPubKey> pubkeys;
- for (unsigned int i = 0; i < keys.size(); ++i) {
- if (IsHex(keys[i].get_str()) && (keys[i].get_str().length() == 66 || keys[i].get_str().length() == 130)) {
- pubkeys.push_back(HexToPubKey(keys[i].get_str()));
- } else {
- throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, strprintf("Invalid public key: %s\n.", keys[i].get_str()));
- }
- }
-
- // Get the output type
- OutputType output_type = OutputType::LEGACY;
- if (!request.params[2].isNull()) {
- std::optional<OutputType> parsed = ParseOutputType(request.params[2].get_str());
- if (!parsed) {
- throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, strprintf("Unknown address type '%s'", request.params[2].get_str()));
- } else if (parsed.value() == OutputType::BECH32M) {
- throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "createmultisig cannot create bech32m multisig addresses");
- }
- output_type = parsed.value();
- }
-
- // Construct using pay-to-script-hash:
- FillableSigningProvider keystore;
- CScript inner;
- const CTxDestination dest = AddAndGetMultisigDestination(required, pubkeys, output_type, keystore, inner);
-
- // Make the descriptor
- std::unique_ptr<Descriptor> descriptor = InferDescriptor(GetScriptForDestination(dest), keystore);
-
- UniValue result(UniValue::VOBJ);
- result.pushKV("address", EncodeDestination(dest));
- result.pushKV("redeemScript", HexStr(inner));
- result.pushKV("descriptor", descriptor->ToString());
-
- UniValue warnings(UniValue::VARR);
- if (!request.params[2].isNull() && OutputTypeFromDestination(dest) != output_type) {
- // Only warns if the user has explicitly chosen an address type we cannot generate
- warnings.push_back("Unable to make chosen address type, please ensure no uncompressed public keys are present.");
- }
- if (warnings.size()) result.pushKV("warnings", warnings);
-
- return result;
-},
- };
-}
-
-static RPCHelpMan getdescriptorinfo()
-{
- const std::string EXAMPLE_DESCRIPTOR = "wpkh([d34db33f/84h/0h/0h]0279be667ef9dcbbac55a06295Ce870b07029Bfcdb2dce28d959f2815b16f81798)";
-
- return RPCHelpMan{"getdescriptorinfo",
- {"\nAnalyses a descriptor.\n"},
- {
- {"descriptor", RPCArg::Type::STR, RPCArg::Optional::NO, "The descriptor."},
- },
- RPCResult{
- RPCResult::Type::OBJ, "", "",
- {
- {RPCResult::Type::STR, "descriptor", "The descriptor in canonical form, without private keys"},
- {RPCResult::Type::STR, "checksum", "The checksum for the input descriptor"},
- {RPCResult::Type::BOOL, "isrange", "Whether the descriptor is ranged"},
- {RPCResult::Type::BOOL, "issolvable", "Whether the descriptor is solvable"},
- {RPCResult::Type::BOOL, "hasprivatekeys", "Whether the input descriptor contained at least one private key"},
- }
- },
- RPCExamples{
- "Analyse a descriptor\n" +
- HelpExampleCli("getdescriptorinfo", "\"" + EXAMPLE_DESCRIPTOR + "\"") +
- HelpExampleRpc("getdescriptorinfo", "\"" + EXAMPLE_DESCRIPTOR + "\"")
- },
- [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
-{
- RPCTypeCheck(request.params, {UniValue::VSTR});
-
- FlatSigningProvider provider;
- std::string error;
- auto desc = Parse(request.params[0].get_str(), provider, error);
- if (!desc) {
- throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, error);
- }
-
- UniValue result(UniValue::VOBJ);
- result.pushKV("descriptor", desc->ToString());
- result.pushKV("checksum", GetDescriptorChecksum(request.params[0].get_str()));
- result.pushKV("isrange", desc->IsRange());
- result.pushKV("issolvable", desc->IsSolvable());
- result.pushKV("hasprivatekeys", provider.keys.size() > 0);
- return result;
-},
- };
-}
-
-static RPCHelpMan deriveaddresses()
-{
- const std::string EXAMPLE_DESCRIPTOR = "wpkh([d34db33f/84h/0h/0h]xpub6DJ2dNUysrn5Vt36jH2KLBT2i1auw1tTSSomg8PhqNiUtx8QX2SvC9nrHu81fT41fvDUnhMjEzQgXnQjKEu3oaqMSzhSrHMxyyoEAmUHQbY/0/*)#cjjspncu";
-
- return RPCHelpMan{"deriveaddresses",
- {"\nDerives one or more addresses corresponding to an output descriptor.\n"
- "Examples of output descriptors are:\n"
- " pkh(<pubkey>) P2PKH outputs for the given pubkey\n"
- " wpkh(<pubkey>) Native segwit P2PKH outputs for the given pubkey\n"
- " sh(multi(<n>,<pubkey>,<pubkey>,...)) P2SH-multisig outputs for the given threshold and pubkeys\n"
- " raw(<hex script>) Outputs whose scriptPubKey equals the specified hex scripts\n"
- "\nIn the above, <pubkey> either refers to a fixed public key in hexadecimal notation, or to an xpub/xprv optionally followed by one\n"
- "or more path elements separated by \"/\", where \"h\" represents a hardened child key.\n"
- "For more information on output descriptors, see the documentation in the doc/descriptors.md file.\n"},
- {
- {"descriptor", RPCArg::Type::STR, RPCArg::Optional::NO, "The descriptor."},
- {"range", RPCArg::Type::RANGE, RPCArg::Optional::OMITTED_NAMED_ARG, "If a ranged descriptor is used, this specifies the end or the range (in [begin,end] notation) to derive."},
- },
- RPCResult{
- RPCResult::Type::ARR, "", "",
- {
- {RPCResult::Type::STR, "address", "the derived addresses"},
- }
- },
- RPCExamples{
- "First three native segwit receive addresses\n" +
- HelpExampleCli("deriveaddresses", "\"" + EXAMPLE_DESCRIPTOR + "\" \"[0,2]\"") +
- HelpExampleRpc("deriveaddresses", "\"" + EXAMPLE_DESCRIPTOR + "\", \"[0,2]\"")
- },
- [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
-{
- RPCTypeCheck(request.params, {UniValue::VSTR, UniValueType()}); // Range argument is checked later
- const std::string desc_str = request.params[0].get_str();
-
- int64_t range_begin = 0;
- int64_t range_end = 0;
-
- if (request.params.size() >= 2 && !request.params[1].isNull()) {
- std::tie(range_begin, range_end) = ParseDescriptorRange(request.params[1]);
- }
-
- FlatSigningProvider key_provider;
- std::string error;
- auto desc = Parse(desc_str, key_provider, error, /* require_checksum = */ true);
- if (!desc) {
- throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, error);
- }
-
- if (!desc->IsRange() && request.params.size() > 1) {
- throw JSONRPCError(RPC_INVALID_PARAMETER, "Range should not be specified for an un-ranged descriptor");
- }
-
- if (desc->IsRange() && request.params.size() == 1) {
- throw JSONRPCError(RPC_INVALID_PARAMETER, "Range must be specified for a ranged descriptor");
- }
-
- UniValue addresses(UniValue::VARR);
-
- for (int i = range_begin; i <= range_end; ++i) {
- FlatSigningProvider provider;
- std::vector<CScript> scripts;
- if (!desc->Expand(i, key_provider, scripts, provider)) {
- throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Cannot derive script without private keys");
- }
-
- for (const CScript &script : scripts) {
- CTxDestination dest;
- if (!ExtractDestination(script, dest)) {
- throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Descriptor does not have a corresponding address");
- }
-
- addresses.push_back(EncodeDestination(dest));
- }
- }
-
- // This should not be possible, but an assert seems overkill:
- if (addresses.empty()) {
- throw JSONRPCError(RPC_MISC_ERROR, "Unexpected empty result");
- }
-
- return addresses;
-},
- };
-}
-
-static RPCHelpMan verifymessage()
-{
- return RPCHelpMan{"verifymessage",
- "Verify a signed message.",
- {
- {"address", RPCArg::Type::STR, RPCArg::Optional::NO, "The bitcoin address to use for the signature."},
- {"signature", RPCArg::Type::STR, RPCArg::Optional::NO, "The signature provided by the signer in base 64 encoding (see signmessage)."},
- {"message", RPCArg::Type::STR, RPCArg::Optional::NO, "The message that was signed."},
- },
- RPCResult{
- RPCResult::Type::BOOL, "", "If the signature is verified or not."
- },
- RPCExamples{
- "\nUnlock the wallet for 30 seconds\n"
- + HelpExampleCli("walletpassphrase", "\"mypassphrase\" 30") +
- "\nCreate the signature\n"
- + HelpExampleCli("signmessage", "\"1D1ZrZNe3JUo7ZycKEYQQiQAWd9y54F4XX\" \"my message\"") +
- "\nVerify the signature\n"
- + HelpExampleCli("verifymessage", "\"1D1ZrZNe3JUo7ZycKEYQQiQAWd9y54F4XX\" \"signature\" \"my message\"") +
- "\nAs a JSON-RPC call\n"
- + HelpExampleRpc("verifymessage", "\"1D1ZrZNe3JUo7ZycKEYQQiQAWd9y54F4XX\", \"signature\", \"my message\"")
- },
- [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
-{
- LOCK(cs_main);
-
- std::string strAddress = request.params[0].get_str();
- std::string strSign = request.params[1].get_str();
- std::string strMessage = request.params[2].get_str();
-
- switch (MessageVerify(strAddress, strSign, strMessage)) {
- case MessageVerificationResult::ERR_INVALID_ADDRESS:
- throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid address");
- case MessageVerificationResult::ERR_ADDRESS_NO_KEY:
- throw JSONRPCError(RPC_TYPE_ERROR, "Address does not refer to key");
- case MessageVerificationResult::ERR_MALFORMED_SIGNATURE:
- throw JSONRPCError(RPC_TYPE_ERROR, "Malformed base64 encoding");
- case MessageVerificationResult::ERR_PUBKEY_NOT_RECOVERED:
- case MessageVerificationResult::ERR_NOT_SIGNED:
- return false;
- case MessageVerificationResult::OK:
- return true;
- }
-
- return false;
-},
- };
-}
-
-static RPCHelpMan signmessagewithprivkey()
-{
- return RPCHelpMan{"signmessagewithprivkey",
- "\nSign a message with the private key of an address\n",
- {
- {"privkey", RPCArg::Type::STR, RPCArg::Optional::NO, "The private key to sign the message with."},
- {"message", RPCArg::Type::STR, RPCArg::Optional::NO, "The message to create a signature of."},
- },
- RPCResult{
- RPCResult::Type::STR, "signature", "The signature of the message encoded in base 64"
- },
- RPCExamples{
- "\nCreate the signature\n"
- + HelpExampleCli("signmessagewithprivkey", "\"privkey\" \"my message\"") +
- "\nVerify the signature\n"
- + HelpExampleCli("verifymessage", "\"1D1ZrZNe3JUo7ZycKEYQQiQAWd9y54F4XX\" \"signature\" \"my message\"") +
- "\nAs a JSON-RPC call\n"
- + HelpExampleRpc("signmessagewithprivkey", "\"privkey\", \"my message\"")
- },
- [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
-{
- std::string strPrivkey = request.params[0].get_str();
- std::string strMessage = request.params[1].get_str();
-
- CKey key = DecodeSecret(strPrivkey);
- if (!key.IsValid()) {
- throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid private key");
- }
-
- std::string signature;
-
- if (!MessageSign(key, strMessage, signature)) {
- throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Sign failed");
- }
-
- return signature;
-},
- };
-}
-
-static RPCHelpMan setmocktime()
-{
- return RPCHelpMan{"setmocktime",
- "\nSet the local time to given timestamp (-regtest only)\n",
- {
- {"timestamp", RPCArg::Type::NUM, RPCArg::Optional::NO, UNIX_EPOCH_TIME + "\n"
- "Pass 0 to go back to using the system time."},
- },
- RPCResult{RPCResult::Type::NONE, "", ""},
- RPCExamples{""},
- [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
-{
- if (!Params().IsMockableChain()) {
- throw std::runtime_error("setmocktime is for regression testing (-regtest mode) only");
- }
-
- // For now, don't change mocktime if we're in the middle of validation, as
- // this could have an effect on mempool time-based eviction, as well as
- // IsCurrentForFeeEstimation() and IsInitialBlockDownload().
- // TODO: figure out the right way to synchronize around mocktime, and
- // ensure all call sites of GetTime() are accessing this safely.
- LOCK(cs_main);
-
- RPCTypeCheck(request.params, {UniValue::VNUM});
- const int64_t time{request.params[0].get_int64()};
- if (time < 0) {
- throw JSONRPCError(RPC_INVALID_PARAMETER, strprintf("Mocktime cannot be negative: %s.", time));
- }
- SetMockTime(time);
- auto node_context = util::AnyPtr<NodeContext>(request.context);
- if (node_context) {
- for (const auto& chain_client : node_context->chain_clients) {
- chain_client->setMockTime(time);
- }
- }
-
- return NullUniValue;
-},
- };
-}
-
-#if defined(USE_SYSCALL_SANDBOX)
-static RPCHelpMan invokedisallowedsyscall()
-{
- return RPCHelpMan{
- "invokedisallowedsyscall",
- "\nInvoke a disallowed syscall to trigger a syscall sandbox violation. Used for testing purposes.\n",
- {},
- RPCResult{RPCResult::Type::NONE, "", ""},
- RPCExamples{
- HelpExampleCli("invokedisallowedsyscall", "") + HelpExampleRpc("invokedisallowedsyscall", "")},
- [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue {
- if (!Params().IsTestChain()) {
- throw std::runtime_error("invokedisallowedsyscall is used for testing only.");
- }
- TestDisallowedSandboxCall();
- return NullUniValue;
- },
- };
-}
-#endif // USE_SYSCALL_SANDBOX
-
-static RPCHelpMan mockscheduler()
-{
- return RPCHelpMan{"mockscheduler",
- "\nBump the scheduler into the future (-regtest only)\n",
- {
- {"delta_time", RPCArg::Type::NUM, RPCArg::Optional::NO, "Number of seconds to forward the scheduler into the future." },
- },
- RPCResult{RPCResult::Type::NONE, "", ""},
- RPCExamples{""},
- [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
-{
- if (!Params().IsMockableChain()) {
- throw std::runtime_error("mockscheduler is for regression testing (-regtest mode) only");
- }
-
- // check params are valid values
- RPCTypeCheck(request.params, {UniValue::VNUM});
- int64_t delta_seconds = request.params[0].get_int64();
- if (delta_seconds <= 0 || delta_seconds > 3600) {
- throw std::runtime_error("delta_time must be between 1 and 3600 seconds (1 hr)");
- }
-
- auto node_context = util::AnyPtr<NodeContext>(request.context);
- // protect against null pointer dereference
- CHECK_NONFATAL(node_context);
- CHECK_NONFATAL(node_context->scheduler);
- node_context->scheduler->MockForward(std::chrono::seconds(delta_seconds));
-
- return NullUniValue;
-},
- };
-}
-
-static UniValue RPCLockedMemoryInfo()
-{
- LockedPool::Stats stats = LockedPoolManager::Instance().stats();
- UniValue obj(UniValue::VOBJ);
- obj.pushKV("used", uint64_t(stats.used));
- obj.pushKV("free", uint64_t(stats.free));
- obj.pushKV("total", uint64_t(stats.total));
- obj.pushKV("locked", uint64_t(stats.locked));
- obj.pushKV("chunks_used", uint64_t(stats.chunks_used));
- obj.pushKV("chunks_free", uint64_t(stats.chunks_free));
- return obj;
-}
-
-#ifdef HAVE_MALLOC_INFO
-static std::string RPCMallocInfo()
-{
- char *ptr = nullptr;
- size_t size = 0;
- FILE *f = open_memstream(&ptr, &size);
- if (f) {
- malloc_info(0, f);
- fclose(f);
- if (ptr) {
- std::string rv(ptr, size);
- free(ptr);
- return rv;
- }
- }
- return "";
-}
-#endif
-
-static RPCHelpMan getmemoryinfo()
-{
- /* Please, avoid using the word "pool" here in the RPC interface or help,
- * as users will undoubtedly confuse it with the other "memory pool"
- */
- return RPCHelpMan{"getmemoryinfo",
- "Returns an object containing information about memory usage.\n",
- {
- {"mode", RPCArg::Type::STR, RPCArg::Default{"stats"}, "determines what kind of information is returned.\n"
- " - \"stats\" returns general statistics about memory usage in the daemon.\n"
- " - \"mallocinfo\" returns an XML string describing low-level heap state (only available if compiled with glibc 2.10+)."},
- },
- {
- RPCResult{"mode \"stats\"",
- RPCResult::Type::OBJ, "", "",
- {
- {RPCResult::Type::OBJ, "locked", "Information about locked memory manager",
- {
- {RPCResult::Type::NUM, "used", "Number of bytes used"},
- {RPCResult::Type::NUM, "free", "Number of bytes available in current arenas"},
- {RPCResult::Type::NUM, "total", "Total number of bytes managed"},
- {RPCResult::Type::NUM, "locked", "Amount of bytes that succeeded locking. If this number is smaller than total, locking pages failed at some point and key data could be swapped to disk."},
- {RPCResult::Type::NUM, "chunks_used", "Number allocated chunks"},
- {RPCResult::Type::NUM, "chunks_free", "Number unused chunks"},
- }},
- }
- },
- RPCResult{"mode \"mallocinfo\"",
- RPCResult::Type::STR, "", "\"<malloc version=\"1\">...\""
- },
- },
- RPCExamples{
- HelpExampleCli("getmemoryinfo", "")
- + HelpExampleRpc("getmemoryinfo", "")
- },
- [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
-{
- std::string mode = request.params[0].isNull() ? "stats" : request.params[0].get_str();
- if (mode == "stats") {
- UniValue obj(UniValue::VOBJ);
- obj.pushKV("locked", RPCLockedMemoryInfo());
- return obj;
- } else if (mode == "mallocinfo") {
-#ifdef HAVE_MALLOC_INFO
- return RPCMallocInfo();
-#else
- throw JSONRPCError(RPC_INVALID_PARAMETER, "mallocinfo mode not available");
-#endif
- } else {
- throw JSONRPCError(RPC_INVALID_PARAMETER, "unknown mode " + mode);
- }
-},
- };
-}
-
-static void EnableOrDisableLogCategories(UniValue cats, bool enable) {
- cats = cats.get_array();
- for (unsigned int i = 0; i < cats.size(); ++i) {
- std::string cat = cats[i].get_str();
-
- bool success;
- if (enable) {
- success = LogInstance().EnableCategory(cat);
- } else {
- success = LogInstance().DisableCategory(cat);
- }
-
- if (!success) {
- throw JSONRPCError(RPC_INVALID_PARAMETER, "unknown logging category " + cat);
- }
- }
-}
-
-static RPCHelpMan logging()
-{
- return RPCHelpMan{"logging",
- "Gets and sets the logging configuration.\n"
- "When called without an argument, returns the list of categories with status that are currently being debug logged or not.\n"
- "When called with arguments, adds or removes categories from debug logging and return the lists above.\n"
- "The arguments are evaluated in order \"include\", \"exclude\".\n"
- "If an item is both included and excluded, it will thus end up being excluded.\n"
- "The valid logging categories are: " + LogInstance().LogCategoriesString() + "\n"
- "In addition, the following are available as category names with special meanings:\n"
- " - \"all\", \"1\" : represent all logging categories.\n"
- " - \"none\", \"0\" : even if other logging categories are specified, ignore all of them.\n"
- ,
- {
- {"include", RPCArg::Type::ARR, RPCArg::Optional::OMITTED_NAMED_ARG, "The categories to add to debug logging",
- {
- {"include_category", RPCArg::Type::STR, RPCArg::Optional::OMITTED, "the valid logging category"},
- }},
- {"exclude", RPCArg::Type::ARR, RPCArg::Optional::OMITTED_NAMED_ARG, "The categories to remove from debug logging",
- {
- {"exclude_category", RPCArg::Type::STR, RPCArg::Optional::OMITTED, "the valid logging category"},
- }},
- },
- RPCResult{
- RPCResult::Type::OBJ_DYN, "", "keys are the logging categories, and values indicates its status",
- {
- {RPCResult::Type::BOOL, "category", "if being debug logged or not. false:inactive, true:active"},
- }
- },
- RPCExamples{
- HelpExampleCli("logging", "\"[\\\"all\\\"]\" \"[\\\"http\\\"]\"")
- + HelpExampleRpc("logging", "[\"all\"], [\"libevent\"]")
- },
- [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
-{
- uint32_t original_log_categories = LogInstance().GetCategoryMask();
- if (request.params[0].isArray()) {
- EnableOrDisableLogCategories(request.params[0], true);
- }
- if (request.params[1].isArray()) {
- EnableOrDisableLogCategories(request.params[1], false);
- }
- uint32_t updated_log_categories = LogInstance().GetCategoryMask();
- uint32_t changed_log_categories = original_log_categories ^ updated_log_categories;
-
- // Update libevent logging if BCLog::LIBEVENT has changed.
- if (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 RegisterMiscRPCCommands(CRPCTable &t)
-{
-// clang-format off
-static const CRPCCommand commands[] =
-{ // category actor (function)
- // --------------------- ------------------------
- { "control", &getmemoryinfo, },
- { "control", &logging, },
- { "util", &validateaddress, },
- { "util", &createmultisig, },
- { "util", &deriveaddresses, },
- { "util", &getdescriptorinfo, },
- { "util", &verifymessage, },
- { "util", &signmessagewithprivkey, },
- { "util", &getindexinfo, },
-
- /* Not shown in help */
- { "hidden", &setmocktime, },
- { "hidden", &mockscheduler, },
- { "hidden", &echo, },
- { "hidden", &echojson, },
- { "hidden", &echoipc, },
-#if defined(USE_SYSCALL_SANDBOX)
- { "hidden", &invokedisallowedsyscall, },
-#endif // USE_SYSCALL_SANDBOX
-};
-// clang-format on
- for (const auto& c : commands) {
- t.appendCommand(c.name, &c);
- }
-}
diff --git a/src/rpc/net.cpp b/src/rpc/net.cpp
index 225feabf14..0ee905a77a 100644
--- a/src/rpc/net.cpp
+++ b/src/rpc/net.cpp
@@ -60,7 +60,7 @@ static RPCHelpMan getconnectioncount()
NodeContext& node = EnsureAnyNodeContext(request.context);
const CConnman& connman = EnsureConnman(node);
- return (int)connman.GetNodeCount(ConnectionDirection::Both);
+ return connman.GetNodeCount(ConnectionDirection::Both);
},
};
}
@@ -84,7 +84,7 @@ static RPCHelpMan ping()
// Request that each node send a ping during next message processing pass
peerman.SendPings();
- return NullUniValue;
+ return UniValue::VNULL;
},
};
}
@@ -156,7 +156,7 @@ static RPCHelpMan getpeerinfo()
{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+"'."}
+ "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"
@@ -195,8 +195,9 @@ static RPCHelpMan getpeerinfo()
if (stats.m_mapped_as != 0) {
obj.pushKV("mapped_as", uint64_t(stats.m_mapped_as));
}
- obj.pushKV("services", strprintf("%016x", stats.nServices));
- obj.pushKV("servicesnames", GetServicesNames(stats.nServices));
+ ServiceFlags services{fStateStats ? statestats.their_services : ServiceFlags::NODE_NONE};
+ obj.pushKV("services", strprintf("%016x", services));
+ obj.pushKV("servicesnames", GetServicesNames(services));
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));
@@ -206,13 +207,13 @@ static RPCHelpMan getpeerinfo()
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));
+ obj.pushKV("pingtime", Ticks<SecondsDouble>(stats.m_last_ping_time));
}
if (stats.m_min_ping_time < std::chrono::microseconds::max()) {
- obj.pushKV("minping", CountSecondsDouble(stats.m_min_ping_time));
+ obj.pushKV("minping", Ticks<SecondsDouble>(stats.m_min_ping_time));
}
if (fStateStats && statestats.m_ping_wait > 0s) {
- obj.pushKV("pingwait", CountSecondsDouble(statestats.m_ping_wait));
+ obj.pushKV("pingwait", Ticks<SecondsDouble>(statestats.m_ping_wait));
}
obj.pushKV("version", stats.nVersion);
// Use the sanitized form of subver here, to avoid tricksy remote peers from
@@ -243,19 +244,19 @@ static RPCHelpMan getpeerinfo()
}
obj.pushKV("permissions", permissions);
- 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);
@@ -303,7 +304,7 @@ static RPCHelpMan addnode()
{
CAddress addr;
connman.OpenNetworkConnection(addr, false, nullptr, strNode.c_str(), ConnectionType::MANUAL);
- return NullUniValue;
+ return UniValue::VNULL;
}
if (strCommand == "add")
@@ -319,7 +320,7 @@ static RPCHelpMan addnode()
}
}
- return NullUniValue;
+ return UniValue::VNULL;
},
};
}
@@ -412,7 +413,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.");
@@ -422,7 +423,7 @@ static RPCHelpMan disconnectnode()
throw JSONRPCError(RPC_CLIENT_NODE_NOT_CONNECTED, "Node not found in connected nodes");
}
- return NullUniValue;
+ return UniValue::VNULL;
},
};
}
@@ -639,13 +640,16 @@ static RPCHelpMan getnetworkinfo()
obj.pushKV("timeoffset", GetTimeOffset());
if (node.connman) {
obj.pushKV("networkactive", node.connman->GetNetworkActive());
- obj.pushKV("connections", (int)node.connman->GetNodeCount(ConnectionDirection::Both));
- obj.pushKV("connections_in", (int)node.connman->GetNodeCount(ConnectionDirection::In));
- obj.pushKV("connections_out", (int)node.connman->GetNodeCount(ConnectionDirection::Out));
+ obj.pushKV("connections", node.connman->GetNodeCount(ConnectionDirection::Both));
+ obj.pushKV("connections_in", node.connman->GetNodeCount(ConnectionDirection::In));
+ obj.pushKV("connections_out", node.connman->GetNodeCount(ConnectionDirection::Out));
}
obj.pushKV("networks", GetNetworksInfo());
- obj.pushKV("relayfee", ValueFromAmount(::minRelayTxFee.GetFeePerK()));
- obj.pushKV("incrementalfee", ValueFromAmount(::incrementalRelayFee.GetFeePerK()));
+ if (node.mempool) {
+ // Those fields can be deprecated, to be replaced by the getmempoolinfo fields
+ obj.pushKV("relayfee", ValueFromAmount(node.mempool->m_min_relay_feerate.GetFeePerK()));
+ obj.pushKV("incrementalfee", ValueFromAmount(node.mempool->m_incremental_relay_feerate.GetFeePerK()));
+ }
UniValue localAddresses(UniValue::VARR);
{
LOCK(g_maplocalhost_mutex);
@@ -720,7 +724,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())
@@ -744,7 +748,7 @@ static RPCHelpMan setban()
throw JSONRPCError(RPC_CLIENT_INVALID_IP_OR_SUBNET, "Error: Unban failed. Requested address/subnet was not previously manually banned.");
}
}
- return NullUniValue;
+ return UniValue::VNULL;
},
};
}
@@ -818,7 +822,7 @@ static RPCHelpMan clearbanned()
node.banman->ClearBanned();
- return NullUniValue;
+ return UniValue::VNULL;
},
};
}
@@ -879,7 +883,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 +897,7 @@ static RPCHelpMan getnodeaddresses()
for (const CAddress& addr : vAddr) {
UniValue obj(UniValue::VOBJ);
- obj.pushKV("time", (int)addr.nTime);
+ obj.pushKV("time", int64_t{TicksSinceEpoch<std::chrono::seconds>(addr.nTime)});
obj.pushKV("services", (uint64_t)addr.nServices);
obj.pushKV("address", addr.ToStringIP());
obj.pushKV("port", addr.GetPort());
@@ -932,7 +936,7 @@ 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);
@@ -941,7 +945,7 @@ static RPCHelpMan addpeeraddress()
if (LookupHost(addr_string, net_addr, false)) {
CAddress address{{net_addr, port}, ServiceFlags{NODE_NETWORK | NODE_WITNESS}};
- address.nTime = GetAdjustedTime();
+ address.nTime = AdjustedTime();
// The source address is set equal to the address. This is equivalent to the peer
// announcing itself.
if (node.addrman->Add({address}, address)) {
@@ -959,30 +963,25 @@ static RPCHelpMan addpeeraddress()
};
}
-void RegisterNetRPCCommands(CRPCTable &t)
-{
-// clang-format off
-static const CRPCCommand commands[] =
-{ // category actor
- // --------------------- -----------------------
- { "network", &getconnectioncount, },
- { "network", &ping, },
- { "network", &getpeerinfo, },
- { "network", &addnode, },
- { "network", &disconnectnode, },
- { "network", &getaddednodeinfo, },
- { "network", &getnettotals, },
- { "network", &getnetworkinfo, },
- { "network", &setban, },
- { "network", &listbanned, },
- { "network", &clearbanned, },
- { "network", &setnetworkactive, },
- { "network", &getnodeaddresses, },
-
- { "hidden", &addconnection, },
- { "hidden", &addpeeraddress, },
-};
-// clang-format on
+void RegisterNetRPCCommands(CRPCTable& t)
+{
+ static const CRPCCommand commands[]{
+ {"network", &getconnectioncount},
+ {"network", &ping},
+ {"network", &getpeerinfo},
+ {"network", &addnode},
+ {"network", &disconnectnode},
+ {"network", &getaddednodeinfo},
+ {"network", &getnettotals},
+ {"network", &getnetworkinfo},
+ {"network", &setban},
+ {"network", &listbanned},
+ {"network", &clearbanned},
+ {"network", &setnetworkactive},
+ {"network", &getnodeaddresses},
+ {"hidden", &addconnection},
+ {"hidden", &addpeeraddress},
+ };
for (const auto& c : commands) {
t.appendCommand(c.name, &c);
}
diff --git a/src/rpc/node.cpp b/src/rpc/node.cpp
new file mode 100644
index 0000000000..605ebc15a7
--- /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 UniValue::VNULL;
+},
+ };
+}
+
+#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 UniValue::VNULL;
+ },
+ };
+}
+#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 UniValue::VNULL;
+},
+ };
+}
+
+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..744f809814
--- /dev/null
+++ b/src/rpc/output_script.cpp
@@ -0,0 +1,314 @@
+// 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>
+
+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/rawtransaction.cpp b/src/rpc/rawtransaction.cpp
index 8e4b396da9..7ffb499330 100644
--- a/src/rpc/rawtransaction.cpp
+++ b/src/rpc/rawtransaction.cpp
@@ -33,6 +33,7 @@
#include <script/standard.h>
#include <uint256.h>
#include <util/bip32.h>
+#include <util/check.h>
#include <util/strencodings.h>
#include <util/string.h>
#include <util/vector.h>
@@ -45,12 +46,10 @@
#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)
{
@@ -97,8 +96,8 @@ static std::vector<RPCResult> DecodeTxDoc(const std::string& txid_field_doc)
{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::STR, "asm", "Disassembly of the signature script"},
+ {RPCResult::Type::STR_HEX, "hex", "The raw signature script bytes, hex-encoded"},
}},
{RPCResult::Type::ARR, "txinwitness", /*optional=*/true, "",
{
@@ -115,9 +114,9 @@ static std::vector<RPCResult> DecodeTxDoc(const std::string& txid_field_doc)
{RPCResult::Type::NUM, "n", "index"},
{RPCResult::Type::OBJ, "scriptPubKey", "",
{
- {RPCResult::Type::STR, "asm", "the asm"},
+ {RPCResult::Type::STR, "asm", "Disassembly of the public key script"},
{RPCResult::Type::STR, "desc", "Inferred descriptor for the output"},
- {RPCResult::Type::STR_HEX, "hex", "the hex"},
+ {RPCResult::Type::STR_HEX, "hex", "The raw public key script bytes, hex-encoded"},
{RPCResult::Type::STR, "type", "The type, eg 'pubkeyhash'"},
{RPCResult::Type::STR, "address", /*optional=*/true, "The Bitcoin address (only if a well-defined address exists)"},
}},
@@ -158,7 +157,7 @@ static std::vector<RPCArg> CreateTxDoc()
},
},
{"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"
+ {"replaceable", RPCArg::Type::BOOL, RPCArg::Default{true}, "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."},
};
}
@@ -216,7 +215,7 @@ static RPCHelpMan getrawtransaction()
uint256 hash = ParseHashV(request.params[0], "parameter 1");
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");
}
@@ -224,7 +223,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()) {
@@ -244,7 +243,7 @@ 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) {
@@ -303,7 +302,7 @@ static RPCHelpMan createrawtransaction()
}, true
);
- bool rbf = false;
+ std::optional<bool> rbf;
if (!request.params[3].isNull()) {
rbf = request.params[3].isTrue();
}
@@ -466,7 +465,7 @@ static RPCHelpMan decodescript()
// Should not be wrapped
return false;
} // no default case, so the compiler can warn about missing cases
- CHECK_NONFATAL(false);
+ NONFATAL_UNREACHABLE();
}()};
if (can_wrap_P2WSH) {
UniValue sr(UniValue::VOBJ);
@@ -566,7 +565,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);
}
@@ -679,6 +678,200 @@ static RPCHelpMan signrawtransactionwithkey()
};
}
+const RPCResult decodepsbt_inputs{
+ RPCResult::Type::ARR, "inputs", "",
+ {
+ {RPCResult::Type::OBJ, "", "",
+ {
+ {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::NUM, "amount", "The value in " + CURRENCY_UNIT},
+ {RPCResult::Type::OBJ, "scriptPubKey", "",
+ {
+ {RPCResult::Type::STR, "asm", "Disassembly of the public key script"},
+ {RPCResult::Type::STR, "desc", "Inferred descriptor for the output"},
+ {RPCResult::Type::STR_HEX, "hex", "The raw public key script bytes, hex-encoded"},
+ {RPCResult::Type::STR, "type", "The type, eg 'pubkeyhash'"},
+ {RPCResult::Type::STR, "address", /*optional=*/true, "The Bitcoin address (only if a well-defined address exists)"},
+ }},
+ }},
+ {RPCResult::Type::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, "asm", "Disassembly of the redeem script"},
+ {RPCResult::Type::STR_HEX, "hex", "The raw redeem script bytes, hex-encoded"},
+ {RPCResult::Type::STR, "type", "The type, eg 'pubkeyhash'"},
+ }},
+ {RPCResult::Type::OBJ, "witness_script", /*optional=*/true, "",
+ {
+ {RPCResult::Type::STR, "asm", "Disassembly of the witness script"},
+ {RPCResult::Type::STR_HEX, "hex", "The raw witness script bytes, hex-encoded"},
+ {RPCResult::Type::STR, "type", "The type, eg 'pubkeyhash'"},
+ }},
+ {RPCResult::Type::ARR, "bip32_derivs", /*optional=*/true, "",
+ {
+ {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::STR, "asm", "Disassembly of the final signature script"},
+ {RPCResult::Type::STR_HEX, "hex", "The raw final signature script bytes, hex-encoded"},
+ }},
+ {RPCResult::Type::ARR, "final_scriptwitness", /*optional=*/true, "",
+ {
+ {RPCResult::Type::STR_HEX, "", "hex-encoded witness data (if any)"},
+ }},
+ {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::STR_HEX, "taproot_key_path_sig", /*optional=*/ true, "hex-encoded signature for the Taproot key path spend"},
+ {RPCResult::Type::ARR, "taproot_script_path_sigs", /*optional=*/ true, "",
+ {
+ {RPCResult::Type::OBJ, "signature", /*optional=*/ true, "The signature for the pubkey and leaf hash combination",
+ {
+ {RPCResult::Type::STR, "pubkey", "The x-only pubkey for this signature"},
+ {RPCResult::Type::STR, "leaf_hash", "The leaf hash for this signature"},
+ {RPCResult::Type::STR, "sig", "The signature itself"},
+ }},
+ }},
+ {RPCResult::Type::ARR, "taproot_scripts", /*optional=*/ true, "",
+ {
+ {RPCResult::Type::OBJ, "", "",
+ {
+ {RPCResult::Type::STR_HEX, "script", "A leaf script"},
+ {RPCResult::Type::NUM, "leaf_ver", "The version number for the leaf script"},
+ {RPCResult::Type::ARR, "control_blocks", "The control blocks for this script",
+ {
+ {RPCResult::Type::STR_HEX, "control_block", "A hex-encoded control block for this script"},
+ }},
+ }},
+ }},
+ {RPCResult::Type::ARR, "taproot_bip32_derivs", /*optional=*/ true, "",
+ {
+ {RPCResult::Type::OBJ, "", "",
+ {
+ {RPCResult::Type::STR, "pubkey", "The x-only public key this path corresponds to"},
+ {RPCResult::Type::STR, "master_fingerprint", "The fingerprint of the master key"},
+ {RPCResult::Type::STR, "path", "The path"},
+ {RPCResult::Type::ARR, "leaf_hashes", "The hashes of the leaves this pubkey appears in",
+ {
+ {RPCResult::Type::STR_HEX, "hash", "The hash of a leaf this pubkey appears in"},
+ }},
+ }},
+ }},
+ {RPCResult::Type::STR_HEX, "taproot_internal_key", /*optional=*/ true, "The hex-encoded Taproot x-only internal key"},
+ {RPCResult::Type::STR_HEX, "taproot_merkle_root", /*optional=*/ true, "The hex-encoded Taproot merkle root"},
+ {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"},
+ }},
+ }},
+ }},
+ }
+};
+
+const RPCResult decodepsbt_outputs{
+ RPCResult::Type::ARR, "outputs", "",
+ {
+ {RPCResult::Type::OBJ, "", "",
+ {
+ {RPCResult::Type::OBJ, "redeem_script", /*optional=*/true, "",
+ {
+ {RPCResult::Type::STR, "asm", "Disassembly of the redeem script"},
+ {RPCResult::Type::STR_HEX, "hex", "The raw redeem script bytes, hex-encoded"},
+ {RPCResult::Type::STR, "type", "The type, eg 'pubkeyhash'"},
+ }},
+ {RPCResult::Type::OBJ, "witness_script", /*optional=*/true, "",
+ {
+ {RPCResult::Type::STR, "asm", "Disassembly of the witness script"},
+ {RPCResult::Type::STR_HEX, "hex", "The raw witness script bytes, hex-encoded"},
+ {RPCResult::Type::STR, "type", "The type, eg 'pubkeyhash'"},
+ }},
+ {RPCResult::Type::ARR, "bip32_derivs", /*optional=*/true, "",
+ {
+ {RPCResult::Type::OBJ, "", "",
+ {
+ {RPCResult::Type::STR, "pubkey", "The public key this path corresponds to"},
+ {RPCResult::Type::STR, "master_fingerprint", "The fingerprint of the master key"},
+ {RPCResult::Type::STR, "path", "The path"},
+ }},
+ }},
+ {RPCResult::Type::STR_HEX, "taproot_internal_key", /*optional=*/ true, "The hex-encoded Taproot x-only internal key"},
+ {RPCResult::Type::ARR, "taproot_tree", /*optional=*/ true, "The tuples that make up the Taproot tree, in depth first search order",
+ {
+ {RPCResult::Type::OBJ, "tuple", /*optional=*/ true, "A single leaf script in the taproot tree",
+ {
+ {RPCResult::Type::NUM, "depth", "The depth of this element in the tree"},
+ {RPCResult::Type::NUM, "leaf_ver", "The version of this leaf"},
+ {RPCResult::Type::STR, "script", "The hex-encoded script itself"},
+ }},
+ }},
+ {RPCResult::Type::ARR, "taproot_bip32_derivs", /*optional=*/ true, "",
+ {
+ {RPCResult::Type::OBJ, "", "",
+ {
+ {RPCResult::Type::STR, "pubkey", "The x-only public key this path corresponds to"},
+ {RPCResult::Type::STR, "master_fingerprint", "The fingerprint of the master key"},
+ {RPCResult::Type::STR, "path", "The path"},
+ {RPCResult::Type::ARR, "leaf_hashes", "The hashes of the leaves this pubkey appears in",
+ {
+ {RPCResult::Type::STR_HEX, "hash", "The hash of a leaf this pubkey appears in"},
+ }},
+ }},
+ }},
+ {RPCResult::Type::OBJ_DYN, "unknown", /*optional=*/true, "The unknown output 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"},
+ }},
+ }},
+ }},
+ }
+};
+
static RPCHelpMan decodepsbt()
{
return RPCHelpMan{
@@ -718,134 +911,8 @@ static RPCHelpMan decodepsbt()
{
{RPCResult::Type::STR_HEX, "key", "(key-value pair) An unknown key-value pair"},
}},
- {RPCResult::Type::ARR, "inputs", "",
- {
- {RPCResult::Type::OBJ, "", "",
- {
- {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::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", /*optional=*/true, "The Bitcoin address (only if a well-defined address exists)"},
- }},
- }},
- {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, "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::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::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::STR, "asm", "The asm"},
- {RPCResult::Type::STR, "hex", "The hex"},
- }},
- {RPCResult::Type::ARR, "final_scriptwitness", /*optional=*/true, "",
- {
- {RPCResult::Type::STR_HEX, "", "hex-encoded witness data (if any)"},
- }},
- {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::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::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::OBJ, "", "",
- {
- {RPCResult::Type::STR, "pubkey", "The public key this path corresponds to"},
- {RPCResult::Type::STR, "master_fingerprint", "The fingerprint of the master key"},
- {RPCResult::Type::STR, "path", "The path"},
- }},
- }},
- {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"},
- }},
- }},
- }},
- }},
+ decodepsbt_inputs,
+ decodepsbt_outputs,
{RPCResult::Type::STR_AMOUNT, "fee", /*optional=*/true, "The transaction fee paid if all UTXOs slots in the PSBT have been filled."},
}
},
@@ -1044,6 +1111,72 @@ static RPCHelpMan decodepsbt()
in.pushKV("hash256_preimages", hash256_preimages);
}
+ // Taproot key path signature
+ if (!input.m_tap_key_sig.empty()) {
+ in.pushKV("taproot_key_path_sig", HexStr(input.m_tap_key_sig));
+ }
+
+ // Taproot script path signatures
+ if (!input.m_tap_script_sigs.empty()) {
+ UniValue script_sigs(UniValue::VARR);
+ for (const auto& [pubkey_leaf, sig] : input.m_tap_script_sigs) {
+ const auto& [xonly, leaf_hash] = pubkey_leaf;
+ UniValue sigobj(UniValue::VOBJ);
+ sigobj.pushKV("pubkey", HexStr(xonly));
+ sigobj.pushKV("leaf_hash", HexStr(leaf_hash));
+ sigobj.pushKV("sig", HexStr(sig));
+ script_sigs.push_back(sigobj);
+ }
+ in.pushKV("taproot_script_path_sigs", script_sigs);
+ }
+
+ // Taproot leaf scripts
+ if (!input.m_tap_scripts.empty()) {
+ UniValue tap_scripts(UniValue::VARR);
+ for (const auto& [leaf, control_blocks] : input.m_tap_scripts) {
+ const auto& [script, leaf_ver] = leaf;
+ UniValue script_info(UniValue::VOBJ);
+ script_info.pushKV("script", HexStr(script));
+ script_info.pushKV("leaf_ver", leaf_ver);
+ UniValue control_blocks_univ(UniValue::VARR);
+ for (const auto& control_block : control_blocks) {
+ control_blocks_univ.push_back(HexStr(control_block));
+ }
+ script_info.pushKV("control_blocks", control_blocks_univ);
+ tap_scripts.push_back(script_info);
+ }
+ in.pushKV("taproot_scripts", tap_scripts);
+ }
+
+ // Taproot bip32 keypaths
+ if (!input.m_tap_bip32_paths.empty()) {
+ UniValue keypaths(UniValue::VARR);
+ for (const auto& [xonly, leaf_origin] : input.m_tap_bip32_paths) {
+ const auto& [leaf_hashes, origin] = leaf_origin;
+ UniValue path_obj(UniValue::VOBJ);
+ path_obj.pushKV("pubkey", HexStr(xonly));
+ path_obj.pushKV("master_fingerprint", strprintf("%08x", ReadBE32(origin.fingerprint)));
+ path_obj.pushKV("path", WriteHDKeypath(origin.path));
+ UniValue leaf_hashes_arr(UniValue::VARR);
+ for (const auto& leaf_hash : leaf_hashes) {
+ leaf_hashes_arr.push_back(HexStr(leaf_hash));
+ }
+ path_obj.pushKV("leaf_hashes", leaf_hashes_arr);
+ keypaths.push_back(path_obj);
+ }
+ in.pushKV("taproot_bip32_derivs", keypaths);
+ }
+
+ // Taproot internal key
+ if (!input.m_tap_internal_key.IsNull()) {
+ in.pushKV("taproot_internal_key", HexStr(input.m_tap_internal_key));
+ }
+
+ // Write taproot merkle root
+ if (!input.m_tap_merkle_root.IsNull()) {
+ in.pushKV("taproot_merkle_root", HexStr(input.m_tap_merkle_root));
+ }
+
// Proprietary
if (!input.m_proprietary.empty()) {
UniValue proprietary(UniValue::VARR);
@@ -1102,6 +1235,47 @@ static RPCHelpMan decodepsbt()
out.pushKV("bip32_derivs", keypaths);
}
+ // Taproot internal key
+ if (!output.m_tap_internal_key.IsNull()) {
+ out.pushKV("taproot_internal_key", HexStr(output.m_tap_internal_key));
+ }
+
+ // Taproot tree
+ if (output.m_tap_tree.has_value()) {
+ UniValue tree(UniValue::VARR);
+ const auto& tuples = output.m_tap_tree->GetTreeTuples();
+ for (const auto& tuple : tuples) {
+ uint8_t depth = std::get<0>(tuple);
+ uint8_t leaf_ver = std::get<1>(tuple);
+ CScript script = std::get<2>(tuple);
+ UniValue elem(UniValue::VOBJ);
+ elem.pushKV("depth", (int)depth);
+ elem.pushKV("leaf_ver", (int)leaf_ver);
+ elem.pushKV("script", HexStr(script));
+ tree.push_back(elem);
+ }
+ out.pushKV("taproot_tree", tree);
+ }
+
+ // Taproot bip32 keypaths
+ if (!output.m_tap_bip32_paths.empty()) {
+ UniValue keypaths(UniValue::VARR);
+ for (const auto& [xonly, leaf_origin] : output.m_tap_bip32_paths) {
+ const auto& [leaf_hashes, origin] = leaf_origin;
+ UniValue path_obj(UniValue::VOBJ);
+ path_obj.pushKV("pubkey", HexStr(xonly));
+ path_obj.pushKV("master_fingerprint", strprintf("%08x", ReadBE32(origin.fingerprint)));
+ path_obj.pushKV("path", WriteHDKeypath(origin.path));
+ UniValue leaf_hashes_arr(UniValue::VARR);
+ for (const auto& leaf_hash : leaf_hashes) {
+ leaf_hashes_arr.push_back(HexStr(leaf_hash));
+ }
+ path_obj.pushKV("leaf_hashes", leaf_hashes_arr);
+ keypaths.push_back(path_obj);
+ }
+ out.pushKV("taproot_bip32_derivs", keypaths);
+ }
+
// Proprietary
if (!output.m_proprietary.empty()) {
UniValue proprietary(UniValue::VARR);
@@ -1277,7 +1451,7 @@ static RPCHelpMan createpsbt()
}, true
);
- bool rbf = false;
+ std::optional<bool> rbf;
if (!request.params[3].isNull()) {
rbf = request.params[3].isTrue();
}
@@ -1679,28 +1853,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", &combinerawtransaction, },
- { "rawtransactions", &signrawtransactionwithkey, },
- { "rawtransactions", &decodepsbt, },
- { "rawtransactions", &combinepsbt, },
- { "rawtransactions", &finalizepsbt, },
- { "rawtransactions", &createpsbt, },
- { "rawtransactions", &converttopsbt, },
- { "rawtransactions", &utxoupdatepsbt, },
- { "rawtransactions", &joinpsbts, },
- { "rawtransactions", &analyzepsbt, },
-};
-// 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 e23fe34480..b06e9f6e4b 100644
--- a/src/rpc/rawtransaction_util.cpp
+++ b/src/rpc/rawtransaction_util.cpp
@@ -21,7 +21,7 @@
#include <util/strencodings.h>
#include <util/translation.h>
-CMutableTransaction ConstructTransaction(const UniValue& inputs_in, const UniValue& outputs_in, const UniValue& locktime, bool rbf)
+CMutableTransaction ConstructTransaction(const UniValue& inputs_in, const UniValue& outputs_in, const UniValue& locktime, std::optional<bool> rbf)
{
if (outputs_in.isNull()) {
throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid parameter, output argument must be non-null");
@@ -40,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;
@@ -55,12 +55,13 @@ 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");
uint32_t nSequence;
- if (rbf) {
+
+ if (rbf.value_or(true)) {
nSequence = MAX_BIP125_RBF_SEQUENCE; /* CTxIn::SEQUENCE_FINAL - 2 */
} else if (rawTx.nLockTime) {
nSequence = CTxIn::MAX_SEQUENCE_NONFINAL; /* CTxIn::SEQUENCE_FINAL - 1 */
@@ -71,7 +72,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 {
@@ -132,7 +133,7 @@ CMutableTransaction ConstructTransaction(const UniValue& inputs_in, const UniVal
}
}
- if (rbf && rawTx.vin.size() > 0 && !SignalsOptInRBF(CTransaction(rawTx))) {
+ if (rbf.has_value() && rbf.value() && rawTx.vin.size() > 0 && !SignalsOptInRBF(CTransaction(rawTx))) {
throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid parameter combination: Sequence number(s) contradict replaceable option");
}
@@ -177,7 +178,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");
}
diff --git a/src/rpc/rawtransaction_util.h b/src/rpc/rawtransaction_util.h
index c3eb1417f8..9b5c9f08d4 100644
--- a/src/rpc/rawtransaction_util.h
+++ b/src/rpc/rawtransaction_util.h
@@ -7,6 +7,7 @@
#include <map>
#include <string>
+#include <optional>
struct bilingual_str;
class FillableSigningProvider;
@@ -38,6 +39,6 @@ void SignTransactionResultToJSON(CMutableTransaction& mtx, bool complete, const
void ParsePrevouts(const UniValue& prevTxsUnival, FillableSigningProvider* keystore, std::map<COutPoint, Coin>& coins);
/** Create a transaction from univalue parameters */
-CMutableTransaction ConstructTransaction(const UniValue& inputs_in, const UniValue& outputs_in, const UniValue& locktime, bool rbf);
+CMutableTransaction ConstructTransaction(const UniValue& inputs_in, const UniValue& outputs_in, const UniValue& locktime, std::optional<bool> rbf);
#endif // BITCOIN_RPC_RAWTRANSACTION_UTIL_H
diff --git a/src/rpc/register.h b/src/rpc/register.h
index 5a604ad428..301f410da3 100644
--- a/src/rpc/register.h
+++ b/src/rpc/register.h
@@ -10,26 +10,32 @@
class CRPCTable;
void RegisterBlockchainRPCCommands(CRPCTable &tableRPC);
+void RegisterFeeRPCCommands(CRPCTable&);
void RegisterMempoolRPCCommands(CRPCTable&);
-void RegisterTxoutProofRPCCommands(CRPCTable&);
-void RegisterNetRPCCommands(CRPCTable &tableRPC);
-void RegisterMiscRPCCommands(CRPCTable &tableRPC);
void RegisterMiningRPCCommands(CRPCTable &tableRPC);
+void RegisterNodeRPCCommands(CRPCTable&);
+void RegisterNetRPCCommands(CRPCTable&);
+void RegisterOutputScriptRPCCommands(CRPCTable&);
void RegisterRawTransactionRPCCommands(CRPCTable &tableRPC);
+void RegisterSignMessageRPCCommands(CRPCTable&);
void RegisterSignerRPCCommands(CRPCTable &tableRPC);
+void RegisterTxoutProofRPCCommands(CRPCTable&);
static inline void RegisterAllCoreRPCCommands(CRPCTable &t)
{
RegisterBlockchainRPCCommands(t);
+ RegisterFeeRPCCommands(t);
RegisterMempoolRPCCommands(t);
- RegisterTxoutProofRPCCommands(t);
- RegisterNetRPCCommands(t);
- RegisterMiscRPCCommands(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 c2f1b15d9c..8595fa78bb 100644
--- a/src/rpc/request.cpp
+++ b/src/rpc/request.cpp
@@ -82,7 +82,7 @@ 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 -
@@ -146,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/server.cpp b/src/rpc/server.cpp
index 333ed6f5da..e9987d73be 100644
--- a/src/rpc/server.cpp
+++ b/src/rpc/server.cpp
@@ -9,32 +9,35 @@
#include <shutdown.h>
#include <sync.h>
#include <util/strencodings.h>
+#include <util/string.h>
#include <util/system.h>
+#include <util/time.h>
-#include <boost/algorithm/string/classification.hpp>
-#include <boost/algorithm/string/split.hpp>
#include <boost/signals2/signal.hpp>
#include <cassert>
-#include <memory> // for unique_ptr
+#include <chrono>
+#include <memory>
#include <mutex>
#include <unordered_map>
-static Mutex g_rpc_warmup_mutex;
+using SteadyClock = std::chrono::steady_clock;
+
+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);
struct RPCCommandExecutionInfo
{
std::string method;
- int64_t start;
+ SteadyClock::time_point start;
};
struct RPCServerInfo
@@ -51,7 +54,7 @@ struct RPCCommandExecution
explicit RPCCommandExecution(const std::string& method)
{
LOCK(g_rpc_server_info.mutex);
- it = g_rpc_server_info.active_commands.insert(g_rpc_server_info.active_commands.end(), {method, GetTimeMicros()});
+ it = g_rpc_server_info.active_commands.insert(g_rpc_server_info.active_commands.end(), {method, SteadyClock::now()});
}
~RPCCommandExecution()
{
@@ -177,7 +180,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;
},
@@ -232,7 +235,7 @@ static RPCHelpMan getrpcinfo()
for (const RPCCommandExecutionInfo& info : g_rpc_server_info.active_commands) {
UniValue entry(UniValue::VOBJ);
entry.pushKV("method", info.method);
- entry.pushKV("duration", GetTimeMicros() - info.start);
+ entry.pushKV("duration", int64_t{Ticks<std::chrono::microseconds>(SteadyClock::now() - info.start)});
active_commands.push_back(entry);
}
@@ -248,17 +251,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 +406,7 @@ static inline JSONRPCRequest transformNamedArguments(const JSONRPCRequest& in, c
// Process expected parameters.
int hole = 0;
for (const std::string &argNamePattern: argNames) {
- std::vector<std::string> vargNames;
- boost::algorithm::split(vargNames, argNamePattern, boost::algorithm::is_any_of("|"));
+ std::vector<std::string> vargNames = SplitString(argNamePattern, '|');
auto fr = argsIn.end();
for (const std::string & argName : vargNames) {
fr = argsIn.find(argName);
diff --git a/src/rpc/signmessage.cpp b/src/rpc/signmessage.cpp
new file mode 100644
index 0000000000..8c752ba1fd
--- /dev/null
+++ b/src/rpc/signmessage.cpp
@@ -0,0 +1,113 @@
+// Copyright (c) 2010 Satoshi Nakamoto
+// Copyright (c) 2009-2022 The Bitcoin Core developers
+// Distributed under the MIT software license, see the accompanying
+// file COPYING or http://www.opensource.org/licenses/mit-license.php.
+
+#include <key.h>
+#include <key_io.h>
+#include <rpc/protocol.h>
+#include <rpc/request.h>
+#include <rpc/server.h>
+#include <rpc/util.h>
+#include <univalue.h>
+#include <util/message.h>
+
+#include <string>
+
+static RPCHelpMan verifymessage()
+{
+ return RPCHelpMan{"verifymessage",
+ "Verify a signed message.",
+ {
+ {"address", RPCArg::Type::STR, RPCArg::Optional::NO, "The bitcoin address to use for the signature."},
+ {"signature", RPCArg::Type::STR, RPCArg::Optional::NO, "The signature provided by the signer in base 64 encoding (see signmessage)."},
+ {"message", RPCArg::Type::STR, RPCArg::Optional::NO, "The message that was signed."},
+ },
+ RPCResult{
+ RPCResult::Type::BOOL, "", "If the signature is verified or not."
+ },
+ RPCExamples{
+ "\nUnlock the wallet for 30 seconds\n"
+ + HelpExampleCli("walletpassphrase", "\"mypassphrase\" 30") +
+ "\nCreate the signature\n"
+ + HelpExampleCli("signmessage", "\"1D1ZrZNe3JUo7ZycKEYQQiQAWd9y54F4XX\" \"my message\"") +
+ "\nVerify the signature\n"
+ + HelpExampleCli("verifymessage", "\"1D1ZrZNe3JUo7ZycKEYQQiQAWd9y54F4XX\" \"signature\" \"my message\"") +
+ "\nAs a JSON-RPC call\n"
+ + HelpExampleRpc("verifymessage", "\"1D1ZrZNe3JUo7ZycKEYQQiQAWd9y54F4XX\", \"signature\", \"my message\"")
+ },
+ [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
+ {
+ std::string strAddress = request.params[0].get_str();
+ std::string strSign = request.params[1].get_str();
+ std::string strMessage = request.params[2].get_str();
+
+ switch (MessageVerify(strAddress, strSign, strMessage)) {
+ case MessageVerificationResult::ERR_INVALID_ADDRESS:
+ throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid address");
+ case MessageVerificationResult::ERR_ADDRESS_NO_KEY:
+ throw JSONRPCError(RPC_TYPE_ERROR, "Address does not refer to key");
+ case MessageVerificationResult::ERR_MALFORMED_SIGNATURE:
+ throw JSONRPCError(RPC_TYPE_ERROR, "Malformed base64 encoding");
+ case MessageVerificationResult::ERR_PUBKEY_NOT_RECOVERED:
+ case MessageVerificationResult::ERR_NOT_SIGNED:
+ return false;
+ case MessageVerificationResult::OK:
+ return true;
+ }
+
+ return false;
+ },
+ };
+}
+
+static RPCHelpMan signmessagewithprivkey()
+{
+ return RPCHelpMan{"signmessagewithprivkey",
+ "\nSign a message with the private key of an address\n",
+ {
+ {"privkey", RPCArg::Type::STR, RPCArg::Optional::NO, "The private key to sign the message with."},
+ {"message", RPCArg::Type::STR, RPCArg::Optional::NO, "The message to create a signature of."},
+ },
+ RPCResult{
+ RPCResult::Type::STR, "signature", "The signature of the message encoded in base 64"
+ },
+ RPCExamples{
+ "\nCreate the signature\n"
+ + HelpExampleCli("signmessagewithprivkey", "\"privkey\" \"my message\"") +
+ "\nVerify the signature\n"
+ + HelpExampleCli("verifymessage", "\"1D1ZrZNe3JUo7ZycKEYQQiQAWd9y54F4XX\" \"signature\" \"my message\"") +
+ "\nAs a JSON-RPC call\n"
+ + HelpExampleRpc("signmessagewithprivkey", "\"privkey\", \"my message\"")
+ },
+ [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
+ {
+ std::string strPrivkey = request.params[0].get_str();
+ std::string strMessage = request.params[1].get_str();
+
+ CKey key = DecodeSecret(strPrivkey);
+ if (!key.IsValid()) {
+ throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid private key");
+ }
+
+ std::string signature;
+
+ if (!MessageSign(key, strMessage, signature)) {
+ throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Sign failed");
+ }
+
+ return signature;
+ },
+ };
+}
+
+void RegisterSignMessageRPCCommands(CRPCTable& t)
+{
+ static const CRPCCommand commands[]{
+ {"util", &verifymessage},
+ {"util", &signmessagewithprivkey},
+ };
+ for (const auto& c : commands) {
+ t.appendCommand(c.name, &c);
+ }
+}
diff --git a/src/rpc/txoutproof.cpp b/src/rpc/txoutproof.cpp
index a5443b0329..dcf6c6bee1 100644
--- a/src/rpc/txoutproof.cpp
+++ b/src/rpc/txoutproof.cpp
@@ -87,7 +87,7 @@ static RPCHelpMan gettxoutproof()
LOCK(cs_main);
if (pblockindex == nullptr) {
- const CTransactionRef tx = GetTransaction(/*block_index=*/nullptr, /*mempool=*/nullptr, *setTxids.begin(), Params().GetConsensus(), hashBlock);
+ 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");
}
@@ -98,7 +98,7 @@ static RPCHelpMan gettxoutproof()
}
CBlock block;
- if (!ReadBlockFromDisk(block, pblockindex, Params().GetConsensus())) {
+ if (!ReadBlockFromDisk(block, pblockindex, chainman.GetConsensus())) {
throw JSONRPCError(RPC_INTERNAL_ERROR, "Can't read block from disk");
}
@@ -172,8 +172,6 @@ static RPCHelpMan verifytxoutproof()
void RegisterTxoutProofRPCCommands(CRPCTable& t)
{
static const CRPCCommand commands[]{
- // category actor (function)
- // -------- ----------------
{"blockchain", &gettxoutproof},
{"blockchain", &verifytxoutproof},
};
diff --git a/src/rpc/util.cpp b/src/rpc/util.cpp
index 01fae140cc..7517f64ea1 100644
--- a/src/rpc/util.cpp
+++ b/src/rpc/util.cpp
@@ -9,15 +9,14 @@
#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"};
@@ -269,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
{
@@ -339,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));
@@ -513,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);
@@ -542,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;
}
}
@@ -584,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;
}
@@ -665,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);
@@ -793,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")});
@@ -860,7 +859,7 @@ 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
@@ -938,7 +937,7 @@ bool RPCResult::MatchesType(const UniValue& result) const
return true;
}
} // no default case, so the compiler can warn about missing cases
- CHECK_NONFATAL(false);
+ NONFATAL_UNREACHABLE();
}
void RPCResult::CheckInnerDoc() const
@@ -984,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
@@ -1021,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 e16fed75bc..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.