aboutsummaryrefslogtreecommitdiff
path: root/src/rpc
diff options
context:
space:
mode:
Diffstat (limited to 'src/rpc')
-rw-r--r--src/rpc/blockchain.cpp55
-rw-r--r--src/rpc/blockchain.h3
-rw-r--r--src/rpc/client.cpp4
-rw-r--r--src/rpc/external_signer.cpp2
-rw-r--r--src/rpc/mempool.cpp229
-rw-r--r--src/rpc/mining.cpp29
-rw-r--r--src/rpc/misc.cpp13
-rw-r--r--src/rpc/net.cpp14
-rw-r--r--src/rpc/rawtransaction.cpp378
-rw-r--r--src/rpc/server.cpp2
-rw-r--r--src/rpc/txoutproof.cpp2
-rw-r--r--src/rpc/util.cpp58
-rw-r--r--src/rpc/util.h11
13 files changed, 400 insertions, 400 deletions
diff --git a/src/rpc/blockchain.cpp b/src/rpc/blockchain.cpp
index 6eb82bed43..f46e5e9fef 100644
--- a/src/rpc/blockchain.cpp
+++ b/src/rpc/blockchain.cpp
@@ -54,7 +54,6 @@ using node::BlockManager;
using node::CCoinsStats;
using node::CoinStatsHashType;
using node::GetUTXOStats;
-using node::IsBlockPruned;
using node::NodeContext;
using node::ReadBlockFromDisk;
using node::SnapshotMetadata;
@@ -161,7 +160,7 @@ UniValue blockheaderToJSON(const CBlockIndex* tip, const CBlockIndex* blockindex
return result;
}
-UniValue blockToJSON(const CBlock& block, const CBlockIndex* tip, const CBlockIndex* blockindex, TxVerbosity verbosity)
+UniValue blockToJSON(BlockManager& blockman, const CBlock& block, const CBlockIndex* tip, const CBlockIndex* blockindex, TxVerbosity verbosity)
{
UniValue result = blockheaderToJSON(tip, blockindex);
@@ -180,14 +179,14 @@ UniValue blockToJSON(const CBlock& block, const CBlockIndex* tip, const CBlockIn
case TxVerbosity::SHOW_DETAILS:
case TxVerbosity::SHOW_DETAILS_AND_PREVOUT:
CBlockUndo blockUndo;
- const bool have_undo{WITH_LOCK(::cs_main, return !IsBlockPruned(blockindex) && UndoReadFromDisk(blockUndo, blockindex))};
+ const bool have_undo{WITH_LOCK(::cs_main, return !blockman.IsBlockPruned(blockindex) && UndoReadFromDisk(blockUndo, blockindex))};
for (size_t i = 0; i < block.vtx.size(); ++i) {
const CTransactionRef& tx = block.vtx.at(i);
// coinbase transaction (i.e. i == 0) doesn't have undo data
const CTxUndo* txundo = (have_undo && i > 0) ? &blockUndo.vtxundo.at(i - 1) : nullptr;
UniValue objTx(UniValue::VOBJ);
- TxToUniv(*tx, uint256(), objTx, true, RPCSerializationFlags(), txundo, verbosity);
+ TxToUniv(*tx, /*block_hash=*/uint256(), /*entry=*/objTx, /*include_hex=*/true, RPCSerializationFlags(), txundo, verbosity);
txs.push_back(objTx);
}
break;
@@ -430,7 +429,7 @@ static RPCHelpMan getblockfrompeer()
"Subsequent calls for the same block and a new peer will cause the response from the previous peer to be ignored.\n\n"
"Returns an empty JSON object if the request was successfully scheduled.",
{
- {"block_hash", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "The block hash to try to fetch"},
+ {"blockhash", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "The block hash to try to fetch"},
{"peer_id", RPCArg::Type::NUM, RPCArg::Optional::NO, "The peer to fetch it from (see getpeerinfo for peer IDs)"},
},
RPCResult{RPCResult::Type::OBJ, "", /*optional=*/false, "", {}},
@@ -444,7 +443,7 @@ static RPCHelpMan getblockfrompeer()
ChainstateManager& chainman = EnsureChainman(node);
PeerManager& peerman = EnsurePeerman(node);
- const uint256& block_hash{ParseHashV(request.params[0], "block_hash")};
+ const uint256& block_hash{ParseHashV(request.params[0], "blockhash")};
const NodeId peer_id{request.params[1].get_int64()};
const CBlockIndex* const index = WITH_LOCK(cs_main, return chainman.m_blockman.LookupBlockIndex(block_hash););
@@ -565,11 +564,11 @@ static RPCHelpMan getblockheader()
};
}
-static CBlock GetBlockChecked(const CBlockIndex* pblockindex) EXCLUSIVE_LOCKS_REQUIRED(::cs_main)
+static CBlock GetBlockChecked(BlockManager& blockman, const CBlockIndex* pblockindex) EXCLUSIVE_LOCKS_REQUIRED(::cs_main)
{
AssertLockHeld(::cs_main);
CBlock block;
- if (IsBlockPruned(pblockindex)) {
+ if (blockman.IsBlockPruned(pblockindex)) {
throw JSONRPCError(RPC_MISC_ERROR, "Block not available (pruned data)");
}
@@ -583,11 +582,11 @@ static CBlock GetBlockChecked(const CBlockIndex* pblockindex) EXCLUSIVE_LOCKS_RE
return block;
}
-static CBlockUndo GetUndoChecked(const CBlockIndex* pblockindex) EXCLUSIVE_LOCKS_REQUIRED(cs_main)
+static CBlockUndo GetUndoChecked(BlockManager& blockman, const CBlockIndex* pblockindex) EXCLUSIVE_LOCKS_REQUIRED(cs_main)
{
AssertLockHeld(::cs_main);
CBlockUndo blockUndo;
- if (IsBlockPruned(pblockindex)) {
+ if (blockman.IsBlockPruned(pblockindex)) {
throw JSONRPCError(RPC_MISC_ERROR, "Undo data not available (pruned data)");
}
@@ -671,7 +670,7 @@ static RPCHelpMan getblock()
{
{RPCResult::Type::STR, "asm", "The asm"},
{RPCResult::Type::STR, "hex", "The hex"},
- {RPCResult::Type::STR, "address", /* optional */ true, "The Bitcoin address (only if a well-defined address exists)"},
+ {RPCResult::Type::STR, "address", /*optional=*/true, "The Bitcoin address (only if a well-defined address exists)"},
{RPCResult::Type::STR, "type", "The type (one of: " + GetAllOutputTypes() + ")"},
}},
}},
@@ -701,8 +700,8 @@ static RPCHelpMan getblock()
CBlock block;
const CBlockIndex* pblockindex;
const CBlockIndex* tip;
+ ChainstateManager& chainman = EnsureAnyChainman(request.context);
{
- ChainstateManager& chainman = EnsureAnyChainman(request.context);
LOCK(cs_main);
pblockindex = chainman.m_blockman.LookupBlockIndex(hash);
tip = chainman.ActiveChain().Tip();
@@ -711,7 +710,7 @@ static RPCHelpMan getblock()
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Block not found");
}
- block = GetBlockChecked(pblockindex);
+ block = GetBlockChecked(chainman.m_blockman, pblockindex);
}
if (verbosity <= 0)
@@ -731,7 +730,7 @@ static RPCHelpMan getblock()
tx_verbosity = TxVerbosity::SHOW_DETAILS_AND_PREVOUT;
}
- return blockToJSON(block, tip, pblockindex, tx_verbosity);
+ return blockToJSON(chainman.m_blockman, block, tip, pblockindex, tx_verbosity);
},
};
}
@@ -1026,7 +1025,7 @@ static RPCHelpMan gettxout()
}
ret.pushKV("value", ValueFromAmount(coin.out.nValue));
UniValue o(UniValue::VOBJ);
- ScriptPubKeyToUniv(coin.out.scriptPubKey, o, true);
+ ScriptToUniv(coin.out.scriptPubKey, /*out=*/o, /*include_hex=*/true, /*include_address=*/true);
ret.pushKV("scriptPubKey", o);
ret.pushKV("coinbase", (bool)coin.fCoinBase);
@@ -1205,18 +1204,18 @@ RPCHelpMan getblockchaininfo()
CHECK_NONFATAL(tip);
const int height = tip->nHeight;
UniValue obj(UniValue::VOBJ);
- obj.pushKV("chain", Params().NetworkIDString());
- obj.pushKV("blocks", height);
- obj.pushKV("headers", pindexBestHeader ? pindexBestHeader->nHeight : -1);
- obj.pushKV("bestblockhash", tip->GetBlockHash().GetHex());
- obj.pushKV("difficulty", (double)GetDifficulty(tip));
- obj.pushKV("time", (int64_t)tip->nTime);
- obj.pushKV("mediantime", (int64_t)tip->GetMedianTimePast());
- obj.pushKV("verificationprogress", GuessVerificationProgress(Params().TxData(), tip));
- obj.pushKV("initialblockdownload", active_chainstate.IsInitialBlockDownload());
- obj.pushKV("chainwork", tip->nChainWork.GetHex());
+ obj.pushKV("chain", Params().NetworkIDString());
+ obj.pushKV("blocks", height);
+ obj.pushKV("headers", chainman.m_best_header ? chainman.m_best_header->nHeight : -1);
+ obj.pushKV("bestblockhash", tip->GetBlockHash().GetHex());
+ obj.pushKV("difficulty", (double)GetDifficulty(tip));
+ obj.pushKV("time", (int64_t)tip->nTime);
+ obj.pushKV("mediantime", (int64_t)tip->GetMedianTimePast());
+ obj.pushKV("verificationprogress", GuessVerificationProgress(Params().TxData(), tip));
+ obj.pushKV("initialblockdownload", active_chainstate.IsInitialBlockDownload());
+ obj.pushKV("chainwork", tip->nChainWork.GetHex());
obj.pushKV("size_on_disk", chainman.m_blockman.CalculateCurrentUsage());
- obj.pushKV("pruned", node::fPruneMode);
+ obj.pushKV("pruned", node::fPruneMode);
if (node::fPruneMode) {
const CBlockIndex* block = tip;
CHECK_NONFATAL(block);
@@ -1777,8 +1776,8 @@ static RPCHelpMan getblockstats()
}
}
- const CBlock block = GetBlockChecked(pindex);
- const CBlockUndo blockUndo = GetUndoChecked(pindex);
+ const CBlock block = GetBlockChecked(chainman.m_blockman, pindex);
+ const CBlockUndo blockUndo = GetUndoChecked(chainman.m_blockman, pindex);
const bool do_all = stats.size() == 0; // Calculate everything if nothing selected (default)
const bool do_mediantxsize = do_all || stats.count("mediantxsize") != 0;
diff --git a/src/rpc/blockchain.h b/src/rpc/blockchain.h
index a8c6d171cc..5fbd9d5fd3 100644
--- a/src/rpc/blockchain.h
+++ b/src/rpc/blockchain.h
@@ -10,6 +10,7 @@
#include <fs.h>
#include <streams.h>
#include <sync.h>
+#include <validation.h>
#include <any>
#include <stdint.h>
@@ -39,7 +40,7 @@ double GetDifficulty(const CBlockIndex* blockindex);
void RPCNotifyBlockChange(const CBlockIndex*);
/** Block description to JSON */
-UniValue blockToJSON(const CBlock& block, const CBlockIndex* tip, const CBlockIndex* blockindex, TxVerbosity verbosity) LOCKS_EXCLUDED(cs_main);
+UniValue blockToJSON(node::BlockManager& blockman, const CBlock& block, const CBlockIndex* tip, const CBlockIndex* blockindex, TxVerbosity verbosity) LOCKS_EXCLUDED(cs_main);
/** Block header to JSON */
UniValue blockheaderToJSON(const CBlockIndex* tip, const CBlockIndex* blockindex) LOCKS_EXCLUDED(cs_main);
diff --git a/src/rpc/client.cpp b/src/rpc/client.cpp
index c480a093a4..23e9d4074c 100644
--- a/src/rpc/client.cpp
+++ b/src/rpc/client.cpp
@@ -142,6 +142,10 @@ static const CRPCConvertParam vRPCConvertParams[] =
{ "send", 1, "conf_target" },
{ "send", 3, "fee_rate"},
{ "send", 4, "options" },
+ { "sendall", 0, "recipients" },
+ { "sendall", 1, "conf_target" },
+ { "sendall", 3, "fee_rate"},
+ { "sendall", 4, "options" },
{ "importprivkey", 2, "rescan" },
{ "importaddress", 2, "rescan" },
{ "importaddress", 3, "p2sh" },
diff --git a/src/rpc/external_signer.cpp b/src/rpc/external_signer.cpp
index 60ec15e904..82aa6f9516 100644
--- a/src/rpc/external_signer.cpp
+++ b/src/rpc/external_signer.cpp
@@ -22,7 +22,7 @@ static RPCHelpMan enumeratesigners()
RPCResult{
RPCResult::Type::OBJ, "", "",
{
- {RPCResult::Type::ARR, "signers", /* optional */ false, "",
+ {RPCResult::Type::ARR, "signers", /*optional=*/false, "",
{
{RPCResult::Type::OBJ, "", "",
{
diff --git a/src/rpc/mempool.cpp b/src/rpc/mempool.cpp
index bc7ef0c08e..1caf4ad96c 100644
--- a/src/rpc/mempool.cpp
+++ b/src/rpc/mempool.cpp
@@ -14,9 +14,219 @@
#include <rpc/util.h>
#include <txmempool.h>
#include <univalue.h>
+#include <util/moneystr.h>
#include <validation.h>
-static std::vector<RPCResult> MempoolEntryDescription() { return {
+using node::DEFAULT_MAX_RAW_TX_FEE_RATE;
+using node::NodeContext;
+
+static RPCHelpMan sendrawtransaction()
+{
+ return RPCHelpMan{"sendrawtransaction",
+ "\nSubmit a raw transaction (serialized, hex-encoded) to local node and network.\n"
+ "\nThe transaction will be sent unconditionally to all peers, so using sendrawtransaction\n"
+ "for manual rebroadcast may degrade privacy by leaking the transaction's origin, as\n"
+ "nodes will normally not rebroadcast non-wallet transactions already in their mempool.\n"
+ "\nA specific exception, RPC_TRANSACTION_ALREADY_IN_CHAIN, may throw if the transaction cannot be added to the mempool.\n"
+ "\nRelated RPCs: createrawtransaction, signrawtransactionwithkey\n",
+ {
+ {"hexstring", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "The hex string of the raw transaction"},
+ {"maxfeerate", RPCArg::Type::AMOUNT, RPCArg::Default{FormatMoney(DEFAULT_MAX_RAW_TX_FEE_RATE.GetFeePerK())},
+ "Reject transactions whose fee rate is higher than the specified value, expressed in " + CURRENCY_UNIT +
+ "/kvB.\nSet to 0 to accept any fee rate.\n"},
+ },
+ RPCResult{
+ RPCResult::Type::STR_HEX, "", "The transaction hash in hex"
+ },
+ RPCExamples{
+ "\nCreate a transaction\n"
+ + HelpExampleCli("createrawtransaction", "\"[{\\\"txid\\\" : \\\"mytxid\\\",\\\"vout\\\":0}]\" \"{\\\"myaddress\\\":0.01}\"") +
+ "Sign the transaction, and get back the hex\n"
+ + HelpExampleCli("signrawtransactionwithwallet", "\"myhex\"") +
+ "\nSend the transaction (signed hex)\n"
+ + HelpExampleCli("sendrawtransaction", "\"signedhex\"") +
+ "\nAs a JSON-RPC call\n"
+ + HelpExampleRpc("sendrawtransaction", "\"signedhex\"")
+ },
+ [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
+ {
+ RPCTypeCheck(request.params, {
+ UniValue::VSTR,
+ UniValueType(), // VNUM or VSTR, checked inside AmountFromValue()
+ });
+
+ CMutableTransaction mtx;
+ if (!DecodeHexTx(mtx, request.params[0].get_str())) {
+ throw JSONRPCError(RPC_DESERIALIZATION_ERROR, "TX decode failed. Make sure the tx has at least one input.");
+ }
+ CTransactionRef tx(MakeTransactionRef(std::move(mtx)));
+
+ const CFeeRate max_raw_tx_fee_rate = request.params[1].isNull() ?
+ DEFAULT_MAX_RAW_TX_FEE_RATE :
+ CFeeRate(AmountFromValue(request.params[1]));
+
+ int64_t virtual_size = GetVirtualTransactionSize(*tx);
+ CAmount max_raw_tx_fee = max_raw_tx_fee_rate.GetFee(virtual_size);
+
+ std::string err_string;
+ AssertLockNotHeld(cs_main);
+ NodeContext& node = EnsureAnyNodeContext(request.context);
+ const TransactionError err = BroadcastTransaction(node, tx, err_string, max_raw_tx_fee, /*relay=*/true, /*wait_callback=*/true);
+ if (TransactionError::OK != err) {
+ throw JSONRPCTransactionError(err, err_string);
+ }
+
+ return tx->GetHash().GetHex();
+ },
+ };
+}
+
+static RPCHelpMan testmempoolaccept()
+{
+ return RPCHelpMan{"testmempoolaccept",
+ "\nReturns result of mempool acceptance tests indicating if raw transaction(s) (serialized, hex-encoded) would be accepted by mempool.\n"
+ "\nIf multiple transactions are passed in, parents must come before children and package policies apply: the transactions cannot conflict with any mempool transactions or each other.\n"
+ "\nIf one transaction fails, other transactions may not be fully validated (the 'allowed' key will be blank).\n"
+ "\nThe maximum number of transactions allowed is " + ToString(MAX_PACKAGE_COUNT) + ".\n"
+ "\nThis checks if transactions violate the consensus or policy rules.\n"
+ "\nSee sendrawtransaction call.\n",
+ {
+ {"rawtxs", RPCArg::Type::ARR, RPCArg::Optional::NO, "An array of hex strings of raw transactions.",
+ {
+ {"rawtx", RPCArg::Type::STR_HEX, RPCArg::Optional::OMITTED, ""},
+ },
+ },
+ {"maxfeerate", RPCArg::Type::AMOUNT, RPCArg::Default{FormatMoney(DEFAULT_MAX_RAW_TX_FEE_RATE.GetFeePerK())},
+ "Reject transactions whose fee rate is higher than the specified value, expressed in " + CURRENCY_UNIT + "/kvB\n"},
+ },
+ RPCResult{
+ RPCResult::Type::ARR, "", "The result of the mempool acceptance test for each raw transaction in the input array.\n"
+ "Returns results for each transaction in the same order they were passed in.\n"
+ "Transactions that cannot be fully validated due to failures in other transactions will not contain an 'allowed' result.\n",
+ {
+ {RPCResult::Type::OBJ, "", "",
+ {
+ {RPCResult::Type::STR_HEX, "txid", "The transaction hash in hex"},
+ {RPCResult::Type::STR_HEX, "wtxid", "The transaction witness hash in hex"},
+ {RPCResult::Type::STR, "package-error", /*optional=*/true, "Package validation error, if any (only possible if rawtxs had more than 1 transaction)."},
+ {RPCResult::Type::BOOL, "allowed", /*optional=*/true, "Whether this tx would be accepted to the mempool and pass client-specified maxfeerate. "
+ "If not present, the tx was not fully validated due to a failure in another tx in the list."},
+ {RPCResult::Type::NUM, "vsize", /*optional=*/true, "Virtual transaction size as defined in BIP 141. This is different from actual serialized size for witness transactions as witness data is discounted (only present when 'allowed' is true)"},
+ {RPCResult::Type::OBJ, "fees", /*optional=*/true, "Transaction fees (only present if 'allowed' is true)",
+ {
+ {RPCResult::Type::STR_AMOUNT, "base", "transaction fee in " + CURRENCY_UNIT},
+ }},
+ {RPCResult::Type::STR, "reject-reason", /*optional=*/true, "Rejection string (only present when 'allowed' is false)"},
+ }},
+ }
+ },
+ RPCExamples{
+ "\nCreate a transaction\n"
+ + HelpExampleCli("createrawtransaction", "\"[{\\\"txid\\\" : \\\"mytxid\\\",\\\"vout\\\":0}]\" \"{\\\"myaddress\\\":0.01}\"") +
+ "Sign the transaction, and get back the hex\n"
+ + HelpExampleCli("signrawtransactionwithwallet", "\"myhex\"") +
+ "\nTest acceptance of the transaction (signed hex)\n"
+ + HelpExampleCli("testmempoolaccept", R"('["signedhex"]')") +
+ "\nAs a JSON-RPC call\n"
+ + HelpExampleRpc("testmempoolaccept", "[\"signedhex\"]")
+ },
+ [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
+ {
+ RPCTypeCheck(request.params, {
+ UniValue::VARR,
+ UniValueType(), // VNUM or VSTR, checked inside AmountFromValue()
+ });
+ const UniValue raw_transactions = request.params[0].get_array();
+ if (raw_transactions.size() < 1 || raw_transactions.size() > MAX_PACKAGE_COUNT) {
+ throw JSONRPCError(RPC_INVALID_PARAMETER,
+ "Array must contain between 1 and " + ToString(MAX_PACKAGE_COUNT) + " transactions.");
+ }
+
+ const CFeeRate max_raw_tx_fee_rate = request.params[1].isNull() ?
+ DEFAULT_MAX_RAW_TX_FEE_RATE :
+ CFeeRate(AmountFromValue(request.params[1]));
+
+ std::vector<CTransactionRef> txns;
+ txns.reserve(raw_transactions.size());
+ for (const auto& rawtx : raw_transactions.getValues()) {
+ CMutableTransaction mtx;
+ if (!DecodeHexTx(mtx, rawtx.get_str())) {
+ throw JSONRPCError(RPC_DESERIALIZATION_ERROR,
+ "TX decode failed: " + rawtx.get_str() + " Make sure the tx has at least one input.");
+ }
+ txns.emplace_back(MakeTransactionRef(std::move(mtx)));
+ }
+
+ NodeContext& node = EnsureAnyNodeContext(request.context);
+ CTxMemPool& mempool = EnsureMemPool(node);
+ ChainstateManager& chainman = EnsureChainman(node);
+ CChainState& chainstate = chainman.ActiveChainstate();
+ const PackageMempoolAcceptResult package_result = [&] {
+ LOCK(::cs_main);
+ if (txns.size() > 1) return ProcessNewPackage(chainstate, mempool, txns, /*test_accept=*/true);
+ return PackageMempoolAcceptResult(txns[0]->GetWitnessHash(),
+ chainman.ProcessTransaction(txns[0], /*test_accept=*/true));
+ }();
+
+ UniValue rpc_result(UniValue::VARR);
+ // We will check transaction fees while we iterate through txns in order. If any transaction fee
+ // exceeds maxfeerate, we will leave the rest of the validation results blank, because it
+ // doesn't make sense to return a validation result for a transaction if its ancestor(s) would
+ // not be submitted.
+ bool exit_early{false};
+ for (const auto& tx : txns) {
+ UniValue result_inner(UniValue::VOBJ);
+ result_inner.pushKV("txid", tx->GetHash().GetHex());
+ result_inner.pushKV("wtxid", tx->GetWitnessHash().GetHex());
+ if (package_result.m_state.GetResult() == PackageValidationResult::PCKG_POLICY) {
+ result_inner.pushKV("package-error", package_result.m_state.GetRejectReason());
+ }
+ auto it = package_result.m_tx_results.find(tx->GetWitnessHash());
+ if (exit_early || it == package_result.m_tx_results.end()) {
+ // Validation unfinished. Just return the txid and wtxid.
+ rpc_result.push_back(result_inner);
+ continue;
+ }
+ const auto& tx_result = it->second;
+ // Package testmempoolaccept doesn't allow transactions to already be in the mempool.
+ CHECK_NONFATAL(tx_result.m_result_type != MempoolAcceptResult::ResultType::MEMPOOL_ENTRY);
+ if (tx_result.m_result_type == MempoolAcceptResult::ResultType::VALID) {
+ const CAmount fee = tx_result.m_base_fees.value();
+ // Check that fee does not exceed maximum fee
+ const int64_t virtual_size = tx_result.m_vsize.value();
+ const CAmount max_raw_tx_fee = max_raw_tx_fee_rate.GetFee(virtual_size);
+ if (max_raw_tx_fee && fee > max_raw_tx_fee) {
+ result_inner.pushKV("allowed", false);
+ result_inner.pushKV("reject-reason", "max-fee-exceeded");
+ exit_early = true;
+ } else {
+ // Only return the fee and vsize if the transaction would pass ATMP.
+ // These can be used to calculate the feerate.
+ result_inner.pushKV("allowed", true);
+ result_inner.pushKV("vsize", virtual_size);
+ UniValue fees(UniValue::VOBJ);
+ fees.pushKV("base", ValueFromAmount(fee));
+ result_inner.pushKV("fees", fees);
+ }
+ } else {
+ result_inner.pushKV("allowed", false);
+ const TxValidationState state = tx_result.m_state;
+ if (state.GetResult() == TxValidationResult::TX_MISSING_INPUTS) {
+ result_inner.pushKV("reject-reason", "missing-inputs");
+ } else {
+ result_inner.pushKV("reject-reason", state.GetRejectReason());
+ }
+ }
+ rpc_result.push_back(result_inner);
+ }
+ return rpc_result;
+ },
+ };
+}
+
+static std::vector<RPCResult> MempoolEntryDescription()
+{
+ return {
RPCResult{RPCResult::Type::NUM, "vsize", "virtual transaction size as defined in BIP 141. This is different from actual serialized size for witness transactions as witness data is discounted."},
RPCResult{RPCResult::Type::NUM, "weight", "transaction weight as defined in BIP 141."},
RPCResult{RPCResult::Type::STR_AMOUNT, "fee", /*optional=*/true,
@@ -50,7 +260,8 @@ static std::vector<RPCResult> MempoolEntryDescription() { return {
{RPCResult{RPCResult::Type::STR_HEX, "transactionid", "child transaction id"}}},
RPCResult{RPCResult::Type::BOOL, "bip125-replaceable", "Whether this transaction could be replaced due to BIP125 (replace-by-fee)"},
RPCResult{RPCResult::Type::BOOL, "unbroadcast", "Whether this transaction is currently unbroadcast (initial broadcast not yet acknowledged by any peers)"},
-};}
+ };
+}
static void entryToJSON(const CTxMemPool& pool, UniValue& info, const CTxMemPoolEntry& e) EXCLUSIVE_LOCKS_REQUIRED(pool.cs)
{
@@ -164,7 +375,7 @@ UniValue MempoolToJSON(const CTxMemPool& pool, bool verbose, bool include_mempoo
}
}
-RPCHelpMan getrawmempool()
+static RPCHelpMan getrawmempool()
{
return RPCHelpMan{"getrawmempool",
"\nReturns all transaction ids in memory pool as a json array of string transaction ids.\n"
@@ -214,7 +425,7 @@ RPCHelpMan getrawmempool()
};
}
-RPCHelpMan getmempoolancestors()
+static RPCHelpMan getmempoolancestors()
{
return RPCHelpMan{"getmempoolancestors",
"\nIf txid is in the mempool, returns all in-mempool ancestors.\n",
@@ -278,7 +489,7 @@ RPCHelpMan getmempoolancestors()
};
}
-RPCHelpMan getmempooldescendants()
+static RPCHelpMan getmempooldescendants()
{
return RPCHelpMan{"getmempooldescendants",
"\nIf txid is in the mempool, returns all in-mempool descendants.\n",
@@ -343,7 +554,7 @@ RPCHelpMan getmempooldescendants()
};
}
-RPCHelpMan getmempoolentry()
+static RPCHelpMan getmempoolentry()
{
return RPCHelpMan{"getmempoolentry",
"\nReturns mempool data for given transaction\n",
@@ -394,7 +605,7 @@ UniValue MempoolInfoToJSON(const CTxMemPool& pool)
return ret;
}
-RPCHelpMan getmempoolinfo()
+static RPCHelpMan getmempoolinfo()
{
return RPCHelpMan{"getmempoolinfo",
"\nReturns details on the active state of the TX memory pool.\n",
@@ -423,7 +634,7 @@ RPCHelpMan getmempoolinfo()
};
}
-RPCHelpMan savemempool()
+static RPCHelpMan savemempool()
{
return RPCHelpMan{"savemempool",
"\nDumps the mempool to disk. It will fail until the previous dump is fully loaded.\n",
@@ -463,6 +674,8 @@ void RegisterMempoolRPCCommands(CRPCTable& t)
static const CRPCCommand commands[]{
// category actor (function)
// -------- ----------------
+ {"rawtransactions", &sendrawtransaction},
+ {"rawtransactions", &testmempoolaccept},
{"blockchain", &getmempoolancestors},
{"blockchain", &getmempooldescendants},
{"blockchain", &getmempoolentry},
diff --git a/src/rpc/mining.cpp b/src/rpc/mining.cpp
index 1d1ae92c58..211026c8d9 100644
--- a/src/rpc/mining.cpp
+++ b/src/rpc/mining.cpp
@@ -7,6 +7,7 @@
#include <chainparams.h>
#include <consensus/amount.h>
#include <consensus/consensus.h>
+#include <consensus/merkle.h>
#include <consensus/params.h>
#include <consensus/validation.h>
#include <core_io.h>
@@ -43,7 +44,6 @@
using node::BlockAssembler;
using node::CBlockTemplate;
-using node::IncrementExtraNonce;
using node::NodeContext;
using node::RegenerateCommitments;
using node::UpdateTime;
@@ -116,14 +116,10 @@ static RPCHelpMan getnetworkhashps()
};
}
-static bool GenerateBlock(ChainstateManager& chainman, CBlock& block, uint64_t& max_tries, unsigned int& extra_nonce, uint256& block_hash)
+static bool GenerateBlock(ChainstateManager& chainman, CBlock& block, uint64_t& max_tries, uint256& block_hash)
{
block_hash.SetNull();
-
- {
- LOCK(cs_main);
- IncrementExtraNonce(&block, chainman.ActiveChain().Tip(), extra_nonce);
- }
+ block.hashMerkleRoot = BlockMerkleRoot(block);
CChainParams chainparams(Params());
@@ -149,30 +145,20 @@ static bool GenerateBlock(ChainstateManager& chainman, CBlock& block, uint64_t&
static UniValue generateBlocks(ChainstateManager& chainman, const CTxMemPool& mempool, const CScript& coinbase_script, int nGenerate, uint64_t nMaxTries)
{
- int nHeightEnd = 0;
- int nHeight = 0;
-
- { // Don't keep cs_main locked
- LOCK(cs_main);
- nHeight = chainman.ActiveChain().Height();
- nHeightEnd = nHeight+nGenerate;
- }
- unsigned int nExtraNonce = 0;
UniValue blockHashes(UniValue::VARR);
- while (nHeight < nHeightEnd && !ShutdownRequested())
- {
+ while (nGenerate > 0 && !ShutdownRequested()) {
std::unique_ptr<CBlockTemplate> pblocktemplate(BlockAssembler(chainman.ActiveChainstate(), mempool, Params()).CreateNewBlock(coinbase_script));
if (!pblocktemplate.get())
throw JSONRPCError(RPC_INTERNAL_ERROR, "Couldn't create new block");
CBlock *pblock = &pblocktemplate->block;
uint256 block_hash;
- if (!GenerateBlock(chainman, *pblock, nMaxTries, nExtraNonce, block_hash)) {
+ if (!GenerateBlock(chainman, *pblock, nMaxTries, block_hash)) {
break;
}
if (!block_hash.IsNull()) {
- ++nHeight;
+ --nGenerate;
blockHashes.push_back(block_hash.GetHex());
}
}
@@ -397,9 +383,8 @@ static RPCHelpMan generateblock()
uint256 block_hash;
uint64_t max_tries{DEFAULT_MAX_TRIES};
- unsigned int extra_nonce{0};
- if (!GenerateBlock(chainman, block, max_tries, extra_nonce, block_hash) || block_hash.IsNull()) {
+ if (!GenerateBlock(chainman, block, max_tries, block_hash) || block_hash.IsNull()) {
throw JSONRPCError(RPC_MISC_ERROR, "Failed to make block.");
}
diff --git a/src/rpc/misc.cpp b/src/rpc/misc.cpp
index 8d7b48d697..99671ee6ac 100644
--- a/src/rpc/misc.cpp
+++ b/src/rpc/misc.cpp
@@ -116,7 +116,7 @@ static RPCHelpMan createmultisig()
{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::ARR, "warnings", /*optional=*/true, "Any warnings resulting from the creation of this multisig",
{
{RPCResult::Type::STR, "", ""},
}},
@@ -646,17 +646,8 @@ static RPCHelpMan logging()
uint32_t changed_log_categories = original_log_categories ^ updated_log_categories;
// Update libevent logging if BCLog::LIBEVENT has changed.
- // If the library version doesn't allow it, UpdateHTTPServerLogging() returns false,
- // in which case we should clear the BCLog::LIBEVENT flag.
- // Throw an error if the user has explicitly asked to change only the libevent
- // flag and it failed.
if (changed_log_categories & BCLog::LIBEVENT) {
- if (!UpdateHTTPServerLogging(LogInstance().WillLogCategory(BCLog::LIBEVENT))) {
- LogInstance().DisableCategory(BCLog::LIBEVENT);
- if (changed_log_categories == BCLog::LIBEVENT) {
- throw JSONRPCError(RPC_INVALID_PARAMETER, "libevent logging cannot be updated when using libevent before v2.1.1.");
- }
- }
+ UpdateHTTPServerLogging(LogInstance().WillLogCategory(BCLog::LIBEVENT));
}
UniValue result(UniValue::VOBJ);
diff --git a/src/rpc/net.cpp b/src/rpc/net.cpp
index 1bde4fccbb..225feabf14 100644
--- a/src/rpc/net.cpp
+++ b/src/rpc/net.cpp
@@ -93,7 +93,7 @@ static RPCHelpMan getpeerinfo()
{
return RPCHelpMan{
"getpeerinfo",
- "\nReturns data about each connected network node as a json array of objects.\n",
+ "Returns data about each connected network peer as a json array of objects.",
{},
RPCResult{
RPCResult::Type::ARR, "", "",
@@ -105,7 +105,7 @@ static RPCHelpMan getpeerinfo()
{RPCResult::Type::STR, "addr", "(host:port) The IP address and port of the peer"},
{RPCResult::Type::STR, "addrbind", /*optional=*/true, "(ip:port) Bind address of the connection to the peer"},
{RPCResult::Type::STR, "addrlocal", /*optional=*/true, "(ip:port) Local address as reported by the peer"},
- {RPCResult::Type::STR, "network", "Network (" + Join(GetNetworkNames(/* append_unroutable */ true), ", ") + ")"},
+ {RPCResult::Type::STR, "network", "Network (" + Join(GetNetworkNames(/*append_unroutable=*/true), ", ") + ")"},
{RPCResult::Type::NUM, "mapped_as", /*optional=*/true, "The AS in the BGP route to the peer used for diversifying\n"
"peer selection (only available if the asmap config flag is set)"},
{RPCResult::Type::STR_HEX, "services", "The services offered"},
@@ -113,7 +113,7 @@ static RPCHelpMan getpeerinfo()
{
{RPCResult::Type::STR, "SERVICE_NAME", "the service name if it is recognised"}
}},
- {RPCResult::Type::BOOL, "relaytxes", "Whether peer has asked us to relay transactions to it"},
+ {RPCResult::Type::BOOL, "relaytxes", /*optional=*/true, "Whether peer has asked us to relay transactions to it"},
{RPCResult::Type::NUM_TIME, "lastsend", "The " + UNIX_EPOCH_TIME + " of the last send"},
{RPCResult::Type::NUM_TIME, "lastrecv", "The " + UNIX_EPOCH_TIME + " of the last receive"},
{RPCResult::Type::NUM_TIME, "last_transaction", "The " + UNIX_EPOCH_TIME + " of the last valid transaction received from this peer"},
@@ -144,7 +144,7 @@ static RPCHelpMan getpeerinfo()
{
{RPCResult::Type::STR, "permission_type", Join(NET_PERMISSIONS_DOC, ",\n") + ".\n"},
}},
- {RPCResult::Type::NUM, "minfeefilter", "The minimum fee rate for transactions this peer accepts"},
+ {RPCResult::Type::NUM, "minfeefilter", /*optional=*/true, "The minimum fee rate for transactions this peer accepts"},
{RPCResult::Type::OBJ_DYN, "bytessent_per_msg", "",
{
{RPCResult::Type::NUM, "msg", "The total bytes sent aggregated by message type\n"
@@ -197,7 +197,6 @@ static RPCHelpMan getpeerinfo()
}
obj.pushKV("services", strprintf("%016x", stats.nServices));
obj.pushKV("servicesnames", GetServicesNames(stats.nServices));
- obj.pushKV("relaytxes", stats.fRelayTxes);
obj.pushKV("lastsend", count_seconds(stats.m_last_send));
obj.pushKV("lastrecv", count_seconds(stats.m_last_recv));
obj.pushKV("last_transaction", count_seconds(stats.m_last_tx_time));
@@ -232,6 +231,8 @@ static RPCHelpMan getpeerinfo()
heights.push_back(height);
}
obj.pushKV("inflight", heights);
+ obj.pushKV("relaytxes", statestats.m_relay_txs);
+ obj.pushKV("minfeefilter", ValueFromAmount(statestats.m_fee_filter_received));
obj.pushKV("addr_relay_enabled", statestats.m_addr_relay_enabled);
obj.pushKV("addr_processed", statestats.m_addr_processed);
obj.pushKV("addr_rate_limited", statestats.m_addr_rate_limited);
@@ -241,7 +242,6 @@ static RPCHelpMan getpeerinfo()
permissions.push_back(permission);
}
obj.pushKV("permissions", permissions);
- obj.pushKV("minfeefilter", ValueFromAmount(stats.minFeeFilter));
UniValue sendPerMsgCmd(UniValue::VOBJ);
for (const auto& i : stats.mapSendBytesPerMsgCmd) {
@@ -888,7 +888,7 @@ static RPCHelpMan getnodeaddresses()
}
// returns a shuffled list of CAddress
- const std::vector<CAddress> vAddr{connman.GetAddresses(count, /* max_pct */ 0, network)};
+ const std::vector<CAddress> vAddr{connman.GetAddresses(count, /*max_pct=*/0, network)};
UniValue ret(UniValue::VARR);
for (const CAddress& addr : vAddr) {
diff --git a/src/rpc/rawtransaction.cpp b/src/rpc/rawtransaction.cpp
index 148734f31a..8e4b396da9 100644
--- a/src/rpc/rawtransaction.cpp
+++ b/src/rpc/rawtransaction.cpp
@@ -33,9 +33,9 @@
#include <script/standard.h>
#include <uint256.h>
#include <util/bip32.h>
-#include <util/moneystr.h>
#include <util/strencodings.h>
#include <util/string.h>
+#include <util/vector.h>
#include <validation.h>
#include <validationinterface.h>
@@ -46,7 +46,6 @@
using node::AnalyzePSBT;
using node::BroadcastTransaction;
-using node::DEFAULT_MAX_RAW_TX_FEE_RATE;
using node::FindCoins;
using node::GetTransaction;
using node::NodeContext;
@@ -60,7 +59,7 @@ static void TxToJSON(const CTransaction& tx, const uint256 hashBlock, UniValue&
// Blockchain contextual information (confirmations and blocktime) is not
// available to code in bitcoin-common, so we query them here and push the
// data into the returned UniValue.
- TxToUniv(tx, uint256(), entry, true, RPCSerializationFlags());
+ TxToUniv(tx, /*block_hash=*/uint256(), entry, /*include_hex=*/true, RPCSerializationFlags());
if (!hashBlock.IsNull()) {
LOCK(cs_main);
@@ -79,6 +78,54 @@ static void TxToJSON(const CTransaction& tx, const uint256 hashBlock, UniValue&
}
}
+static std::vector<RPCResult> DecodeTxDoc(const std::string& txid_field_doc)
+{
+ return {
+ {RPCResult::Type::STR_HEX, "txid", txid_field_doc},
+ {RPCResult::Type::STR_HEX, "hash", "The transaction hash (differs from txid for witness transactions)"},
+ {RPCResult::Type::NUM, "size", "The serialized transaction size"},
+ {RPCResult::Type::NUM, "vsize", "The virtual transaction size (differs from size for witness transactions)"},
+ {RPCResult::Type::NUM, "weight", "The transaction's weight (between vsize*4-3 and vsize*4)"},
+ {RPCResult::Type::NUM, "version", "The version"},
+ {RPCResult::Type::NUM_TIME, "locktime", "The lock time"},
+ {RPCResult::Type::ARR, "vin", "",
+ {
+ {RPCResult::Type::OBJ, "", "",
+ {
+ {RPCResult::Type::STR_HEX, "coinbase", /*optional=*/true, "The coinbase value (only if coinbase transaction)"},
+ {RPCResult::Type::STR_HEX, "txid", /*optional=*/true, "The transaction id (if not coinbase transaction)"},
+ {RPCResult::Type::NUM, "vout", /*optional=*/true, "The output number (if not coinbase transaction)"},
+ {RPCResult::Type::OBJ, "scriptSig", /*optional=*/true, "The script (if not coinbase transaction)",
+ {
+ {RPCResult::Type::STR, "asm", "asm"},
+ {RPCResult::Type::STR_HEX, "hex", "hex"},
+ }},
+ {RPCResult::Type::ARR, "txinwitness", /*optional=*/true, "",
+ {
+ {RPCResult::Type::STR_HEX, "hex", "hex-encoded witness data (if any)"},
+ }},
+ {RPCResult::Type::NUM, "sequence", "The script sequence number"},
+ }},
+ }},
+ {RPCResult::Type::ARR, "vout", "",
+ {
+ {RPCResult::Type::OBJ, "", "",
+ {
+ {RPCResult::Type::STR_AMOUNT, "value", "The value in " + CURRENCY_UNIT},
+ {RPCResult::Type::NUM, "n", "index"},
+ {RPCResult::Type::OBJ, "scriptPubKey", "",
+ {
+ {RPCResult::Type::STR, "asm", "the asm"},
+ {RPCResult::Type::STR, "desc", "Inferred descriptor for the output"},
+ {RPCResult::Type::STR_HEX, "hex", "the hex"},
+ {RPCResult::Type::STR, "type", "The type, eg 'pubkeyhash'"},
+ {RPCResult::Type::STR, "address", /*optional=*/true, "The Bitcoin address (only if a well-defined address exists)"},
+ }},
+ }},
+ }},
+ };
+}
+
static std::vector<RPCArg> CreateTxDoc()
{
return {
@@ -120,7 +167,7 @@ static RPCHelpMan getrawtransaction()
{
return RPCHelpMan{
"getrawtransaction",
- "\nReturn the raw transaction data.\n"
+ "Return the raw transaction data.\n"
"\nBy default, this call only returns a transaction if it is in the mempool. If -txindex is enabled\n"
"and no blockhash argument is passed, it will return the transaction if it is in the mempool or any block.\n"
@@ -129,7 +176,7 @@ static RPCHelpMan getrawtransaction()
"\nHint: Use gettransaction for wallet transactions.\n"
"\nIf verbose is 'true', returns an Object with information about 'txid'.\n"
- "If verbose is 'false' or omitted, returns a string that is serialized, hex-encoded data for 'txid'.\n",
+ "If verbose is 'false' or omitted, returns a string that is serialized, hex-encoded data for 'txid'.",
{
{"txid", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "The transaction id"},
{"verbose", RPCArg::Type::BOOL, RPCArg::Default{false}, "If false, return a string, otherwise return a json object"},
@@ -141,55 +188,16 @@ static RPCHelpMan getrawtransaction()
},
RPCResult{"if verbose is set to true",
RPCResult::Type::OBJ, "", "",
+ Cat<std::vector<RPCResult>>(
{
{RPCResult::Type::BOOL, "in_active_chain", /*optional=*/true, "Whether specified block is in the active chain or not (only present with explicit \"blockhash\" argument)"},
- {RPCResult::Type::STR_HEX, "hex", "The serialized, hex-encoded data for 'txid'"},
- {RPCResult::Type::STR_HEX, "txid", "The transaction id (same as provided)"},
- {RPCResult::Type::STR_HEX, "hash", "The transaction hash (differs from txid for witness transactions)"},
- {RPCResult::Type::NUM, "size", "The serialized transaction size"},
- {RPCResult::Type::NUM, "vsize", "The virtual transaction size (differs from size for witness transactions)"},
- {RPCResult::Type::NUM, "weight", "The transaction's weight (between vsize*4-3 and vsize*4)"},
- {RPCResult::Type::NUM, "version", "The version"},
- {RPCResult::Type::NUM_TIME, "locktime", "The lock time"},
- {RPCResult::Type::ARR, "vin", "",
- {
- {RPCResult::Type::OBJ, "", "",
- {
- {RPCResult::Type::STR_HEX, "txid", "The transaction id"},
- {RPCResult::Type::NUM, "vout", "The output number"},
- {RPCResult::Type::OBJ, "scriptSig", "The script",
- {
- {RPCResult::Type::STR, "asm", "asm"},
- {RPCResult::Type::STR_HEX, "hex", "hex"},
- }},
- {RPCResult::Type::NUM, "sequence", "The script sequence number"},
- {RPCResult::Type::ARR, "txinwitness", /*optional=*/true, "",
- {
- {RPCResult::Type::STR_HEX, "hex", "hex-encoded witness data (if any)"},
- }},
- }},
- }},
- {RPCResult::Type::ARR, "vout", "",
- {
- {RPCResult::Type::OBJ, "", "",
- {
- {RPCResult::Type::NUM, "value", "The value in " + CURRENCY_UNIT},
- {RPCResult::Type::NUM, "n", "index"},
- {RPCResult::Type::OBJ, "scriptPubKey", "",
- {
- {RPCResult::Type::STR, "asm", "the asm"},
- {RPCResult::Type::STR, "desc", "Inferred descriptor for the output"},
- {RPCResult::Type::STR, "hex", "the hex"},
- {RPCResult::Type::STR, "type", "The type, eg 'pubkeyhash'"},
- {RPCResult::Type::STR, "address", /*optional=*/true, "The Bitcoin address (only if a well-defined address exists)"},
- }},
- }},
- }},
{RPCResult::Type::STR_HEX, "blockhash", /*optional=*/true, "the block hash"},
{RPCResult::Type::NUM, "confirmations", /*optional=*/true, "The confirmations"},
{RPCResult::Type::NUM_TIME, "blocktime", /*optional=*/true, "The block time expressed in " + UNIX_EPOCH_TIME},
{RPCResult::Type::NUM, "time", /*optional=*/true, "Same as \"blocktime\""},
- }
+ {RPCResult::Type::STR_HEX, "hex", "The serialized, hex-encoded data for 'txid'"},
+ },
+ DecodeTxDoc(/*txid_field_doc=*/"The transaction id (same as provided)")),
},
},
RPCExamples{
@@ -309,7 +317,7 @@ static RPCHelpMan createrawtransaction()
static RPCHelpMan decoderawtransaction()
{
return RPCHelpMan{"decoderawtransaction",
- "\nReturn a JSON object representing the serialized, hex-encoded transaction.\n",
+ "Return a JSON object representing the serialized, hex-encoded transaction.",
{
{"hexstring", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "The transaction hex string"},
{"iswitness", RPCArg::Type::BOOL, RPCArg::DefaultHint{"depends on heuristic tests"}, "Whether the transaction hex is a serialized witness transaction.\n"
@@ -322,50 +330,7 @@ static RPCHelpMan decoderawtransaction()
},
RPCResult{
RPCResult::Type::OBJ, "", "",
- {
- {RPCResult::Type::STR_HEX, "txid", "The transaction id"},
- {RPCResult::Type::STR_HEX, "hash", "The transaction hash (differs from txid for witness transactions)"},
- {RPCResult::Type::NUM, "size", "The transaction size"},
- {RPCResult::Type::NUM, "vsize", "The virtual transaction size (differs from size for witness transactions)"},
- {RPCResult::Type::NUM, "weight", "The transaction's weight (between vsize*4 - 3 and vsize*4)"},
- {RPCResult::Type::NUM, "version", "The version"},
- {RPCResult::Type::NUM_TIME, "locktime", "The lock time"},
- {RPCResult::Type::ARR, "vin", "",
- {
- {RPCResult::Type::OBJ, "", "",
- {
- {RPCResult::Type::STR_HEX, "coinbase", /*optional=*/true, ""},
- {RPCResult::Type::STR_HEX, "txid", /*optional=*/true, "The transaction id"},
- {RPCResult::Type::NUM, "vout", /*optional=*/true, "The output number"},
- {RPCResult::Type::OBJ, "scriptSig", /*optional=*/true, "The script",
- {
- {RPCResult::Type::STR, "asm", "asm"},
- {RPCResult::Type::STR_HEX, "hex", "hex"},
- }},
- {RPCResult::Type::ARR, "txinwitness", /*optional=*/true, "",
- {
- {RPCResult::Type::STR_HEX, "hex", "hex-encoded witness data (if any)"},
- }},
- {RPCResult::Type::NUM, "sequence", "The script sequence number"},
- }},
- }},
- {RPCResult::Type::ARR, "vout", "",
- {
- {RPCResult::Type::OBJ, "", "",
- {
- {RPCResult::Type::NUM, "value", "The value in " + CURRENCY_UNIT},
- {RPCResult::Type::NUM, "n", "index"},
- {RPCResult::Type::OBJ, "scriptPubKey", "",
- {
- {RPCResult::Type::STR, "asm", "the asm"},
- {RPCResult::Type::STR, "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)"},
- }},
- }},
- }},
- }
+ DecodeTxDoc(/*txid_field_doc=*/"The transaction id"),
},
RPCExamples{
HelpExampleCli("decoderawtransaction", "\"hexstring\"")
@@ -385,7 +350,7 @@ static RPCHelpMan decoderawtransaction()
}
UniValue result(UniValue::VOBJ);
- TxToUniv(CTransaction(std::move(mtx)), uint256(), result, false);
+ TxToUniv(CTransaction(std::move(mtx)), /*block_hash=*/uint256(), /*entry=*/result, /*include_hex=*/false);
return result;
},
@@ -437,7 +402,7 @@ static RPCHelpMan decodescript()
} else {
// Empty scripts are valid
}
- ScriptPubKeyToUniv(script, r, /* include_hex */ false);
+ ScriptToUniv(script, /*out=*/r, /*include_hex=*/false, /*include_address=*/true);
std::vector<std::vector<unsigned char>> solutions_data;
const TxoutType which_type{Solver(script, solutions_data)};
@@ -514,7 +479,7 @@ static RPCHelpMan decodescript()
// Scripts that are not fit for P2WPKH are encoded as P2WSH.
segwitScr = GetScriptForDestination(WitnessV0ScriptHash(script));
}
- ScriptPubKeyToUniv(segwitScr, sr, /* include_hex */ true);
+ ScriptToUniv(segwitScr, /*out=*/sr, /*include_hex=*/true, /*include_address=*/true);
sr.pushKV("p2sh-segwit", EncodeDestination(ScriptHash(segwitScr)));
r.pushKV("segwit", sr);
}
@@ -714,210 +679,6 @@ static RPCHelpMan signrawtransactionwithkey()
};
}
-static RPCHelpMan sendrawtransaction()
-{
- return RPCHelpMan{"sendrawtransaction",
- "\nSubmit a raw transaction (serialized, hex-encoded) to local node and network.\n"
- "\nThe transaction will be sent unconditionally to all peers, so using sendrawtransaction\n"
- "for manual rebroadcast may degrade privacy by leaking the transaction's origin, as\n"
- "nodes will normally not rebroadcast non-wallet transactions already in their mempool.\n"
- "\nA specific exception, RPC_TRANSACTION_ALREADY_IN_CHAIN, may throw if the transaction cannot be added to the mempool.\n"
- "\nRelated RPCs: createrawtransaction, signrawtransactionwithkey\n",
- {
- {"hexstring", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "The hex string of the raw transaction"},
- {"maxfeerate", RPCArg::Type::AMOUNT, RPCArg::Default{FormatMoney(DEFAULT_MAX_RAW_TX_FEE_RATE.GetFeePerK())},
- "Reject transactions whose fee rate is higher than the specified value, expressed in " + CURRENCY_UNIT +
- "/kvB.\nSet to 0 to accept any fee rate.\n"},
- },
- RPCResult{
- RPCResult::Type::STR_HEX, "", "The transaction hash in hex"
- },
- RPCExamples{
- "\nCreate a transaction\n"
- + HelpExampleCli("createrawtransaction", "\"[{\\\"txid\\\" : \\\"mytxid\\\",\\\"vout\\\":0}]\" \"{\\\"myaddress\\\":0.01}\"") +
- "Sign the transaction, and get back the hex\n"
- + HelpExampleCli("signrawtransactionwithwallet", "\"myhex\"") +
- "\nSend the transaction (signed hex)\n"
- + HelpExampleCli("sendrawtransaction", "\"signedhex\"") +
- "\nAs a JSON-RPC call\n"
- + HelpExampleRpc("sendrawtransaction", "\"signedhex\"")
- },
- [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
-{
- RPCTypeCheck(request.params, {
- UniValue::VSTR,
- UniValueType(), // VNUM or VSTR, checked inside AmountFromValue()
- });
-
- CMutableTransaction mtx;
- if (!DecodeHexTx(mtx, request.params[0].get_str())) {
- throw JSONRPCError(RPC_DESERIALIZATION_ERROR, "TX decode failed. Make sure the tx has at least one input.");
- }
- CTransactionRef tx(MakeTransactionRef(std::move(mtx)));
-
- const CFeeRate max_raw_tx_fee_rate = request.params[1].isNull() ?
- DEFAULT_MAX_RAW_TX_FEE_RATE :
- CFeeRate(AmountFromValue(request.params[1]));
-
- int64_t virtual_size = GetVirtualTransactionSize(*tx);
- CAmount max_raw_tx_fee = max_raw_tx_fee_rate.GetFee(virtual_size);
-
- std::string err_string;
- AssertLockNotHeld(cs_main);
- NodeContext& node = EnsureAnyNodeContext(request.context);
- const TransactionError err = BroadcastTransaction(node, tx, err_string, max_raw_tx_fee, /*relay*/ true, /*wait_callback*/ true);
- if (TransactionError::OK != err) {
- throw JSONRPCTransactionError(err, err_string);
- }
-
- return tx->GetHash().GetHex();
-},
- };
-}
-
-static RPCHelpMan testmempoolaccept()
-{
- return RPCHelpMan{"testmempoolaccept",
- "\nReturns result of mempool acceptance tests indicating if raw transaction(s) (serialized, hex-encoded) would be accepted by mempool.\n"
- "\nIf multiple transactions are passed in, parents must come before children and package policies apply: the transactions cannot conflict with any mempool transactions or each other.\n"
- "\nIf one transaction fails, other transactions may not be fully validated (the 'allowed' key will be blank).\n"
- "\nThe maximum number of transactions allowed is " + ToString(MAX_PACKAGE_COUNT) + ".\n"
- "\nThis checks if transactions violate the consensus or policy rules.\n"
- "\nSee sendrawtransaction call.\n",
- {
- {"rawtxs", RPCArg::Type::ARR, RPCArg::Optional::NO, "An array of hex strings of raw transactions.",
- {
- {"rawtx", RPCArg::Type::STR_HEX, RPCArg::Optional::OMITTED, ""},
- },
- },
- {"maxfeerate", RPCArg::Type::AMOUNT, RPCArg::Default{FormatMoney(DEFAULT_MAX_RAW_TX_FEE_RATE.GetFeePerK())},
- "Reject transactions whose fee rate is higher than the specified value, expressed in " + CURRENCY_UNIT + "/kvB\n"},
- },
- RPCResult{
- RPCResult::Type::ARR, "", "The result of the mempool acceptance test for each raw transaction in the input array.\n"
- "Returns results for each transaction in the same order they were passed in.\n"
- "Transactions that cannot be fully validated due to failures in other transactions will not contain an 'allowed' result.\n",
- {
- {RPCResult::Type::OBJ, "", "",
- {
- {RPCResult::Type::STR_HEX, "txid", "The transaction hash in hex"},
- {RPCResult::Type::STR_HEX, "wtxid", "The transaction witness hash in hex"},
- {RPCResult::Type::STR, "package-error", /*optional=*/true, "Package validation error, if any (only possible if rawtxs had more than 1 transaction)."},
- {RPCResult::Type::BOOL, "allowed", /*optional=*/true, "Whether this tx would be accepted to the mempool and pass client-specified maxfeerate. "
- "If not present, the tx was not fully validated due to a failure in another tx in the list."},
- {RPCResult::Type::NUM, "vsize", /*optional=*/true, "Virtual transaction size as defined in BIP 141. This is different from actual serialized size for witness transactions as witness data is discounted (only present when 'allowed' is true)"},
- {RPCResult::Type::OBJ, "fees", /*optional=*/true, "Transaction fees (only present if 'allowed' is true)",
- {
- {RPCResult::Type::STR_AMOUNT, "base", "transaction fee in " + CURRENCY_UNIT},
- }},
- {RPCResult::Type::STR, "reject-reason", /*optional=*/true, "Rejection string (only present when 'allowed' is false)"},
- }},
- }
- },
- RPCExamples{
- "\nCreate a transaction\n"
- + HelpExampleCli("createrawtransaction", "\"[{\\\"txid\\\" : \\\"mytxid\\\",\\\"vout\\\":0}]\" \"{\\\"myaddress\\\":0.01}\"") +
- "Sign the transaction, and get back the hex\n"
- + HelpExampleCli("signrawtransactionwithwallet", "\"myhex\"") +
- "\nTest acceptance of the transaction (signed hex)\n"
- + HelpExampleCli("testmempoolaccept", R"('["signedhex"]')") +
- "\nAs a JSON-RPC call\n"
- + HelpExampleRpc("testmempoolaccept", "[\"signedhex\"]")
- },
- [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
-{
- RPCTypeCheck(request.params, {
- UniValue::VARR,
- UniValueType(), // VNUM or VSTR, checked inside AmountFromValue()
- });
- const UniValue raw_transactions = request.params[0].get_array();
- if (raw_transactions.size() < 1 || raw_transactions.size() > MAX_PACKAGE_COUNT) {
- throw JSONRPCError(RPC_INVALID_PARAMETER,
- "Array must contain between 1 and " + ToString(MAX_PACKAGE_COUNT) + " transactions.");
- }
-
- const CFeeRate max_raw_tx_fee_rate = request.params[1].isNull() ?
- DEFAULT_MAX_RAW_TX_FEE_RATE :
- CFeeRate(AmountFromValue(request.params[1]));
-
- std::vector<CTransactionRef> txns;
- txns.reserve(raw_transactions.size());
- for (const auto& rawtx : raw_transactions.getValues()) {
- CMutableTransaction mtx;
- if (!DecodeHexTx(mtx, rawtx.get_str())) {
- throw JSONRPCError(RPC_DESERIALIZATION_ERROR,
- "TX decode failed: " + rawtx.get_str() + " Make sure the tx has at least one input.");
- }
- txns.emplace_back(MakeTransactionRef(std::move(mtx)));
- }
-
- NodeContext& node = EnsureAnyNodeContext(request.context);
- CTxMemPool& mempool = EnsureMemPool(node);
- ChainstateManager& chainman = EnsureChainman(node);
- CChainState& chainstate = chainman.ActiveChainstate();
- const PackageMempoolAcceptResult package_result = [&] {
- LOCK(::cs_main);
- if (txns.size() > 1) return ProcessNewPackage(chainstate, mempool, txns, /* test_accept */ true);
- return PackageMempoolAcceptResult(txns[0]->GetWitnessHash(),
- chainman.ProcessTransaction(txns[0], /*test_accept=*/ true));
- }();
-
- UniValue rpc_result(UniValue::VARR);
- // We will check transaction fees while we iterate through txns in order. If any transaction fee
- // exceeds maxfeerate, we will leave the rest of the validation results blank, because it
- // doesn't make sense to return a validation result for a transaction if its ancestor(s) would
- // not be submitted.
- bool exit_early{false};
- for (const auto& tx : txns) {
- UniValue result_inner(UniValue::VOBJ);
- result_inner.pushKV("txid", tx->GetHash().GetHex());
- result_inner.pushKV("wtxid", tx->GetWitnessHash().GetHex());
- if (package_result.m_state.GetResult() == PackageValidationResult::PCKG_POLICY) {
- result_inner.pushKV("package-error", package_result.m_state.GetRejectReason());
- }
- auto it = package_result.m_tx_results.find(tx->GetWitnessHash());
- if (exit_early || it == package_result.m_tx_results.end()) {
- // Validation unfinished. Just return the txid and wtxid.
- rpc_result.push_back(result_inner);
- continue;
- }
- const auto& tx_result = it->second;
- // Package testmempoolaccept doesn't allow transactions to already be in the mempool.
- CHECK_NONFATAL(tx_result.m_result_type != MempoolAcceptResult::ResultType::MEMPOOL_ENTRY);
- if (tx_result.m_result_type == MempoolAcceptResult::ResultType::VALID) {
- const CAmount fee = tx_result.m_base_fees.value();
- // Check that fee does not exceed maximum fee
- const int64_t virtual_size = tx_result.m_vsize.value();
- const CAmount max_raw_tx_fee = max_raw_tx_fee_rate.GetFee(virtual_size);
- if (max_raw_tx_fee && fee > max_raw_tx_fee) {
- result_inner.pushKV("allowed", false);
- result_inner.pushKV("reject-reason", "max-fee-exceeded");
- exit_early = true;
- } else {
- // Only return the fee and vsize if the transaction would pass ATMP.
- // These can be used to calculate the feerate.
- result_inner.pushKV("allowed", true);
- result_inner.pushKV("vsize", virtual_size);
- UniValue fees(UniValue::VOBJ);
- fees.pushKV("base", ValueFromAmount(fee));
- result_inner.pushKV("fees", fees);
- }
- } else {
- result_inner.pushKV("allowed", false);
- const TxValidationState state = tx_result.m_state;
- if (state.GetResult() == TxValidationResult::TX_MISSING_INPUTS) {
- result_inner.pushKV("reject-reason", "missing-inputs");
- } else {
- result_inner.pushKV("reject-reason", state.GetRejectReason());
- }
- }
- rpc_result.push_back(result_inner);
- }
- return rpc_result;
-},
- };
-}
-
static RPCHelpMan decodepsbt()
{
return RPCHelpMan{
@@ -971,6 +732,7 @@ static RPCHelpMan decodepsbt()
{RPCResult::Type::OBJ, "scriptPubKey", "",
{
{RPCResult::Type::STR, "asm", "The asm"},
+ {RPCResult::Type::STR, "desc", "Inferred descriptor for the output"},
{RPCResult::Type::STR_HEX, "hex", "The hex"},
{RPCResult::Type::STR, "type", "The type, eg 'pubkeyhash'"},
{RPCResult::Type::STR, "address", /*optional=*/true, "The Bitcoin address (only if a well-defined address exists)"},
@@ -1105,7 +867,7 @@ static RPCHelpMan decodepsbt()
// Add the decoded tx
UniValue tx_univ(UniValue::VOBJ);
- TxToUniv(CTransaction(*psbtx.tx), uint256(), tx_univ, false);
+ TxToUniv(CTransaction(*psbtx.tx), /*block_hash=*/uint256(), /*entry=*/tx_univ, /*include_hex=*/false);
result.pushKV("tx", tx_univ);
// Add the global xpubs
@@ -1161,7 +923,7 @@ static RPCHelpMan decodepsbt()
txout = input.witness_utxo;
UniValue o(UniValue::VOBJ);
- ScriptPubKeyToUniv(txout.scriptPubKey, o, /* include_hex */ true);
+ ScriptToUniv(txout.scriptPubKey, /*out=*/o, /*include_hex=*/true, /*include_address=*/true);
UniValue out(UniValue::VOBJ);
out.pushKV("amount", ValueFromAmount(txout.nValue));
@@ -1175,7 +937,7 @@ static RPCHelpMan decodepsbt()
txout = input.non_witness_utxo->vout[psbtx.tx->vin[i].prevout.n];
UniValue non_wit(UniValue::VOBJ);
- TxToUniv(*input.non_witness_utxo, uint256(), non_wit, false);
+ TxToUniv(*input.non_witness_utxo, /*block_hash=*/uint256(), /*entry=*/non_wit, /*include_hex=*/false);
in.pushKV("non_witness_utxo", non_wit);
have_a_utxo = true;
@@ -1208,12 +970,12 @@ static RPCHelpMan decodepsbt()
// Redeem script and witness script
if (!input.redeem_script.empty()) {
UniValue r(UniValue::VOBJ);
- ScriptToUniv(input.redeem_script, r);
+ ScriptToUniv(input.redeem_script, /*out=*/r);
in.pushKV("redeem_script", r);
}
if (!input.witness_script.empty()) {
UniValue r(UniValue::VOBJ);
- ScriptToUniv(input.witness_script, r);
+ ScriptToUniv(input.witness_script, /*out=*/r);
in.pushKV("witness_script", r);
}
@@ -1318,12 +1080,12 @@ static RPCHelpMan decodepsbt()
// Redeem script and witness script
if (!output.redeem_script.empty()) {
UniValue r(UniValue::VOBJ);
- ScriptToUniv(output.redeem_script, r);
+ ScriptToUniv(output.redeem_script, /*out=*/r);
out.pushKV("redeem_script", r);
}
if (!output.witness_script.empty()) {
UniValue r(UniValue::VOBJ);
- ScriptToUniv(output.witness_script, r);
+ ScriptToUniv(output.witness_script, /*out=*/r);
out.pushKV("witness_script", r);
}
@@ -1927,10 +1689,8 @@ static const CRPCCommand commands[] =
{ "rawtransactions", &createrawtransaction, },
{ "rawtransactions", &decoderawtransaction, },
{ "rawtransactions", &decodescript, },
- { "rawtransactions", &sendrawtransaction, },
{ "rawtransactions", &combinerawtransaction, },
{ "rawtransactions", &signrawtransactionwithkey, },
- { "rawtransactions", &testmempoolaccept, },
{ "rawtransactions", &decodepsbt, },
{ "rawtransactions", &combinepsbt, },
{ "rawtransactions", &finalizepsbt, },
diff --git a/src/rpc/server.cpp b/src/rpc/server.cpp
index c70236cc1c..333ed6f5da 100644
--- a/src/rpc/server.cpp
+++ b/src/rpc/server.cpp
@@ -167,7 +167,7 @@ static RPCHelpMan stop()
// to the client (intended for testing)
"\nRequest a graceful shutdown of " PACKAGE_NAME ".",
{
- {"wait", RPCArg::Type::NUM, RPCArg::Optional::OMITTED_NAMED_ARG, "how long to wait in ms", "", {}, /* hidden */ true},
+ {"wait", RPCArg::Type::NUM, RPCArg::Optional::OMITTED_NAMED_ARG, "how long to wait in ms", "", {}, /*hidden=*/true},
},
RPCResult{RPCResult::Type::STR, "", "A string with the content '" + RESULT + "'"},
RPCExamples{""},
diff --git a/src/rpc/txoutproof.cpp b/src/rpc/txoutproof.cpp
index 2700fb400c..a5443b0329 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(), Params().GetConsensus(), hashBlock);
if (!tx || hashBlock.IsNull()) {
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Transaction not yet in block");
}
diff --git a/src/rpc/util.cpp b/src/rpc/util.cpp
index 7c859268be..01fae140cc 100644
--- a/src/rpc/util.cpp
+++ b/src/rpc/util.cpp
@@ -421,7 +421,7 @@ struct Sections {
if (arg.m_type_str.size() != 0 && push_name) {
left += "\"" + arg.GetName() + "\": " + arg.m_type_str.at(0);
} else {
- left += push_name ? arg.ToStringObj(/* oneline */ false) : arg.ToString(/* oneline */ false);
+ left += push_name ? arg.ToStringObj(/*oneline=*/false) : arg.ToString(/*oneline=*/false);
}
left += ",";
PushSection({left, arg.ToDescriptionString()});
@@ -627,7 +627,7 @@ std::string RPCHelpMan::ToString() const
if (was_optional) ret += ") ";
was_optional = false;
}
- ret += arg.ToString(/* oneline */ true);
+ ret += arg.ToString(/*oneline=*/true);
}
if (was_optional) ret += " )";
@@ -774,7 +774,7 @@ void RPCResult::ToSections(Sections& sections, const OuterType outer_type, const
// Elements in a JSON structure (dictionary or array) are separated by a comma
const std::string maybe_separator{outer_type != OuterType::NONE ? "," : ""};
- // The key name if recursed into an dictionary
+ // The key name if recursed into a dictionary
const std::string maybe_key{
outer_type == OuterType::OBJ ?
"\"" + this->m_key_name + "\" : " :
@@ -865,10 +865,11 @@ void RPCResult::ToSections(Sections& sections, const OuterType outer_type, const
bool RPCResult::MatchesType(const UniValue& result) const
{
- switch (m_type) {
- case Type::ELISION: {
- return false;
+ if (m_skip_type_check) {
+ return true;
}
+ switch (m_type) {
+ case Type::ELISION:
case Type::ANY: {
return true;
}
@@ -889,11 +890,52 @@ bool RPCResult::MatchesType(const UniValue& result) const
}
case Type::ARR_FIXED:
case Type::ARR: {
- return UniValue::VARR == result.getType();
+ if (UniValue::VARR != result.getType()) return false;
+ for (size_t i{0}; i < result.get_array().size(); ++i) {
+ // If there are more results than documented, re-use the last doc_inner.
+ const RPCResult& doc_inner{m_inner.at(std::min(m_inner.size() - 1, i))};
+ if (!doc_inner.MatchesType(result.get_array()[i])) return false;
+ }
+ return true; // empty result array is valid
}
case Type::OBJ_DYN:
case Type::OBJ: {
- return UniValue::VOBJ == result.getType();
+ if (UniValue::VOBJ != result.getType()) return false;
+ if (!m_inner.empty() && m_inner.at(0).m_type == Type::ELISION) return true;
+ if (m_type == Type::OBJ_DYN) {
+ const RPCResult& doc_inner{m_inner.at(0)}; // Assume all types are the same, randomly pick the first
+ for (size_t i{0}; i < result.get_obj().size(); ++i) {
+ if (!doc_inner.MatchesType(result.get_obj()[i])) {
+ return false;
+ }
+ }
+ return true; // empty result obj is valid
+ }
+ std::set<std::string> doc_keys;
+ for (const auto& doc_entry : m_inner) {
+ doc_keys.insert(doc_entry.m_key_name);
+ }
+ std::map<std::string, UniValue> result_obj;
+ result.getObjMap(result_obj);
+ for (const auto& result_entry : result_obj) {
+ if (doc_keys.find(result_entry.first) == doc_keys.end()) {
+ return false; // missing documentation
+ }
+ }
+
+ for (const auto& doc_entry : m_inner) {
+ const auto result_it{result_obj.find(doc_entry.m_key_name)};
+ if (result_it == result_obj.end()) {
+ if (!doc_entry.m_optional) {
+ return false; // result is missing a required key
+ }
+ continue;
+ }
+ if (!doc_entry.MatchesType(result_it->second)) {
+ return false; // wrong type
+ }
+ }
+ return true;
}
} // no default case, so the compiler can warn about missing cases
CHECK_NONFATAL(false);
diff --git a/src/rpc/util.h b/src/rpc/util.h
index 89d32d4193..e16fed75bc 100644
--- a/src/rpc/util.h
+++ b/src/rpc/util.h
@@ -256,6 +256,7 @@ struct RPCResult {
const std::string m_key_name; //!< Only used for dicts
const std::vector<RPCResult> m_inner; //!< Only used for arrays or dicts
const bool m_optional;
+ const bool m_skip_type_check;
const std::string m_description;
const std::string m_cond;
@@ -270,6 +271,7 @@ struct RPCResult {
m_key_name{std::move(m_key_name)},
m_inner{std::move(inner)},
m_optional{optional},
+ m_skip_type_check{false},
m_description{std::move(description)},
m_cond{std::move(cond)}
{
@@ -290,11 +292,13 @@ struct RPCResult {
const std::string m_key_name,
const bool optional,
const std::string description,
- const std::vector<RPCResult> inner = {})
+ const std::vector<RPCResult> inner = {},
+ bool skip_type_check = false)
: m_type{std::move(type)},
m_key_name{std::move(m_key_name)},
m_inner{std::move(inner)},
m_optional{optional},
+ m_skip_type_check{skip_type_check},
m_description{std::move(description)},
m_cond{}
{
@@ -305,8 +309,9 @@ struct RPCResult {
const Type type,
const std::string m_key_name,
const std::string description,
- const std::vector<RPCResult> inner = {})
- : RPCResult{type, m_key_name, false, description, inner} {}
+ const std::vector<RPCResult> inner = {},
+ bool skip_type_check = false)
+ : RPCResult{type, m_key_name, false, description, inner, skip_type_check} {}
/** Append the sections of the result. */
void ToSections(Sections& sections, OuterType outer_type = OuterType::NONE, const int current_indent = 0) const;