Skip to content
Merged
2 changes: 1 addition & 1 deletion src/rpc/blockchain.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2739,7 +2739,7 @@ static RPCHelpMan getdescriptoractivity()
{RPCResult::Type::OBJ, "output_spk", "", ScriptPubKeyDoc()},
}},
// TODO is the skip_type_check avoidable with a heterogeneous ARR?
}, /*skip_type_check=*/true},
}, {.skip_type_check=true}, },
},
},
RPCExamples{
Expand Down
14 changes: 6 additions & 8 deletions src/rpc/rawtransaction.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -248,7 +248,7 @@ static RPCHelpMan getrawtransaction()
{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)", /*wallet=*/false)),
TxDoc({.txid_field_doc="The transaction id (same as provided)"})),
},
RPCResult{"for verbosity = 2",
RPCResult::Type::OBJ, "", "",
Expand Down Expand Up @@ -422,7 +422,7 @@ static RPCHelpMan decoderawtransaction()
},
RPCResult{
RPCResult::Type::OBJ, "", "",
DecodeTxDoc(/*txid_field_doc=*/"The transaction id", /*wallet=*/false),
TxDoc(),
},
RPCExamples{
HelpExampleCli("decoderawtransaction", "\"hexstring\"")
Expand Down Expand Up @@ -781,9 +781,8 @@ const RPCResult decodepsbt_inputs{
{RPCResult::Type::OBJ, "", "",
{
{RPCResult::Type::OBJ, "non_witness_utxo", /*optional=*/true, "Decoded network transaction for non-witness UTXOs",
{
{RPCResult::Type::ELISION, "",""},
}},
TxDoc({.elision_description="The layout is the same as the output of decoderawtransaction."})
},
{RPCResult::Type::OBJ, "witness_utxo", /*optional=*/true, "Transaction output for witness UTXOs",
{
{RPCResult::Type::NUM, "amount", "The value in " + CURRENCY_UNIT},
Expand Down Expand Up @@ -1023,9 +1022,8 @@ static RPCHelpMan decodepsbt()
RPCResult::Type::OBJ, "", "",
{
{RPCResult::Type::OBJ, "tx", "The decoded network-serialized unsigned transaction.",
{
{RPCResult::Type::ELISION, "", "The layout is the same as the output of decoderawtransaction."},
}},
TxDoc({.elision_description="The layout is the same as the output of decoderawtransaction."})
},
{RPCResult::Type::ARR, "global_xpubs", "",
{
{RPCResult::Type::OBJ, "", "",
Expand Down
24 changes: 13 additions & 11 deletions src/rpc/rawtransaction_util.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -344,16 +344,18 @@ void SignTransactionResultToJSON(CMutableTransaction& mtx, bool complete, const
}
}

std::vector<RPCResult> DecodeTxDoc(const std::string& txid_field_doc, bool wallet)
std::vector<RPCResult> TxDoc(const TxDocOptions& opts)
{
std::optional<std::string> maybe_skip{};
if (opts.elision_description) maybe_skip.emplace();
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::STR_HEX, "txid", opts.txid_field_doc, {}, {.print_elision=opts.elision_description}},
{RPCResult::Type::STR_HEX, "hash", "The transaction hash (differs from txid for witness transactions)", {}, {.print_elision=maybe_skip}},
{RPCResult::Type::NUM, "size", "The serialized transaction size", {}, {.print_elision=maybe_skip}},
{RPCResult::Type::NUM, "vsize", "The virtual transaction size (differs from size for witness transactions)", {}, {.print_elision=maybe_skip}},
{RPCResult::Type::NUM, "weight", "The transaction's weight (between vsize*4-3 and vsize*4)", {}, {.print_elision=maybe_skip}},
{RPCResult::Type::NUM, "version", "The version", {}, {.print_elision=maybe_skip}},
{RPCResult::Type::NUM_TIME, "locktime", "The lock time", {}, {.print_elision=maybe_skip}},
{RPCResult::Type::ARR, "vin", "",
{
{RPCResult::Type::OBJ, "", "",
Expand All @@ -372,7 +374,7 @@ std::vector<RPCResult> DecodeTxDoc(const std::string& txid_field_doc, bool walle
}},
{RPCResult::Type::NUM, "sequence", "The script sequence number"},
}},
}},
}, {.print_elision=maybe_skip}},
{RPCResult::Type::ARR, "vout", "",
{
{RPCResult::Type::OBJ, "", "", Cat(
Expand All @@ -381,11 +383,11 @@ std::vector<RPCResult> DecodeTxDoc(const std::string& txid_field_doc, bool walle
{RPCResult::Type::NUM, "n", "index"},
{RPCResult::Type::OBJ, "scriptPubKey", "", ScriptPubKeyDoc()},
},
wallet ?
opts.wallet ?
std::vector<RPCResult>{{RPCResult::Type::BOOL, "ischange", /*optional=*/true, "Output script is change (only present if true)"}} :
std::vector<RPCResult>{}
)
},
}},
}, {.print_elision=maybe_skip}},
};
}
10 changes: 9 additions & 1 deletion src/rpc/rawtransaction_util.h
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,15 @@ void AddOutputs(CMutableTransaction& rawTx, const UniValue& outputs_in);
/** Create a transaction from univalue parameters */
CMutableTransaction ConstructTransaction(const UniValue& inputs_in, const UniValue& outputs_in, const UniValue& locktime, std::optional<bool> rbf, uint32_t version);

struct TxDocOptions {
/// The description of the txid field
std::string txid_field_doc{"The transaction id"};
/// Include wallet-related fields (e.g. ischange on outputs)
bool wallet{false};
/// Treat this as an elided Result in the help
std::optional<std::string> elision_description{};
};
/** Explain the UniValue "decoded" transaction object, may include extra fields if processed by wallet **/
std::vector<RPCResult> DecodeTxDoc(const std::string& txid_field_doc, bool wallet);
std::vector<RPCResult> TxDoc(const TxDocOptions& opts = {});

#endif // BITCOIN_RPC_RAWTRANSACTION_UTIL_H
19 changes: 17 additions & 2 deletions src/rpc/util.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1015,9 +1015,22 @@ void RPCResult::ToSections(Sections& sections, const OuterType outer_type, const
(this->m_description.empty() ? "" : " " + this->m_description);
};

// Ensure at least one elision description exists, if there is any elision
const auto elision_has_description{[](const std::vector<RPCResult>& inner) {
return std::ranges::none_of(inner, [](const auto& res) { return res.m_opts.print_elision.has_value(); }) ||
std::ranges::any_of(inner, [](const auto& res) { return res.m_opts.print_elision.has_value() && !res.m_opts.print_elision->empty(); });
}};

if (m_opts.print_elision) {
if (!m_opts.print_elision->empty()) {
sections.PushSection({indent + "..." + maybe_separator, *m_opts.print_elision});
}
return;
}

switch (m_type) {
case Type::ELISION: {
// If the inner result is empty, use three dots for elision
// Deprecated alias of m_opts.print_elision
sections.PushSection({indent + "..." + maybe_separator, m_description});
return;
}
Expand Down Expand Up @@ -1059,6 +1072,7 @@ void RPCResult::ToSections(Sections& sections, const OuterType outer_type, const
i.ToSections(sections, OuterType::ARR, current_indent + 2);
}
CHECK_NONFATAL(!m_inner.empty());
CHECK_NONFATAL(elision_has_description(m_inner));
if (m_type == Type::ARR && m_inner.back().m_type != Type::ELISION) {
sections.PushSection({indent_next + "...", ""});
} else {
Expand All @@ -1074,6 +1088,7 @@ void RPCResult::ToSections(Sections& sections, const OuterType outer_type, const
sections.PushSection({indent + maybe_key + "{}", Description("empty JSON object")});
return;
}
CHECK_NONFATAL(elision_has_description(m_inner));
sections.PushSection({indent + maybe_key + "{", Description("json object")});
for (const auto& i : m_inner) {
i.ToSections(sections, OuterType::OBJ, current_indent + 2);
Expand Down Expand Up @@ -1130,7 +1145,7 @@ static std::optional<UniValue::VType> ExpectedType(RPCResult::Type type)
// NOLINTNEXTLINE(misc-no-recursion)
UniValue RPCResult::MatchesType(const UniValue& result) const
{
if (m_skip_type_check) {
if (m_opts.skip_type_check) {
return true;
}

Expand Down
32 changes: 23 additions & 9 deletions src/rpc/util.h
Original file line number Diff line number Diff line change
Expand Up @@ -292,6 +292,18 @@ struct RPCArg {
std::string ToDescriptionString(bool is_named_arg) const;
};

struct RPCResultOptions {
bool skip_type_check{false};
/// Whether to treat this as elided in the human-readable description, and
/// possibly supply a description for the elision. Normally, there will be
/// one string on any of the elided results, for example `Same output as
/// verbosity = 1`, and all other elided strings will be empty.
///
/// - If nullopt: normal display.
/// - If empty string: suppress from help.
/// - If non-empty: show "..." with this description.
std::optional<std::string> print_elision{std::nullopt};
};
// NOLINTNEXTLINE(misc-no-recursion)
struct RPCResult {
enum class Type {
Expand All @@ -314,7 +326,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 RPCResultOptions m_opts;
const std::string m_description;
const std::string m_cond;

Expand All @@ -324,12 +336,13 @@ struct RPCResult {
std::string m_key_name,
bool optional,
std::string description,
std::vector<RPCResult> inner = {})
std::vector<RPCResult> inner = {},
RPCResultOptions opts = {})
: 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{false},
m_opts{std::move(opts)},
m_description{std::move(description)},
m_cond{std::move(cond)}
{
Expand All @@ -342,21 +355,22 @@ struct RPCResult {
Type type,
std::string m_key_name,
std::string description,
std::vector<RPCResult> inner = {})
: RPCResult{std::move(cond), type, std::move(m_key_name), /*optional=*/false, std::move(description), std::move(inner)} {}
std::vector<RPCResult> inner = {},
RPCResultOptions opts = {})
: RPCResult{std::move(cond), type, std::move(m_key_name), /*optional=*/false, std::move(description), std::move(inner), std::move(opts)} {}

RPCResult(
Type type,
std::string m_key_name,
bool optional,
std::string description,
std::vector<RPCResult> inner = {},
bool skip_type_check = false)
RPCResultOptions opts = {})
: 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_opts{std::move(opts)},
m_description{std::move(description)},
m_cond{}
{
Expand All @@ -368,8 +382,8 @@ struct RPCResult {
std::string m_key_name,
std::string description,
std::vector<RPCResult> inner = {},
bool skip_type_check = false)
: RPCResult{type, std::move(m_key_name), /*optional=*/false, std::move(description), std::move(inner), skip_type_check} {}
RPCResultOptions opts = {})
: RPCResult{type, std::move(m_key_name), /*optional=*/false, std::move(description), std::move(inner), std::move(opts)} {}

/** Append the sections of the result. */
void ToSections(Sections& sections, OuterType outer_type = OuterType::NONE, int current_indent = 0) const;
Expand Down
5 changes: 3 additions & 2 deletions src/test/fuzz/addrman.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
#include <test/fuzz/util.h>
#include <test/fuzz/util/net.h>
#include <test/util/setup_common.h>
#include <test/util/time.h>
#include <util/asmap.h>
#include <util/chaintype.h>

Expand Down Expand Up @@ -116,7 +117,7 @@ FUZZ_TARGET(addrman, .init = initialize_addrman)
{
SeedRandomStateForTest(SeedRand::ZEROS);
FuzzedDataProvider fuzzed_data_provider(buffer.data(), buffer.size());
SetMockTime(ConsumeTime(fuzzed_data_provider));
NodeClockContext clock_ctx{ConsumeTime(fuzzed_data_provider)};
NetGroupManager netgroupman{ConsumeNetGroupManager(fuzzed_data_provider)};
auto addr_man_ptr = std::make_unique<AddrManDeterministic>(netgroupman, fuzzed_data_provider, GetCheckRatio());
if (fuzzed_data_provider.ConsumeBool()) {
Expand Down Expand Up @@ -201,7 +202,7 @@ FUZZ_TARGET(addrman_serdeser, .init = initialize_addrman)
{
SeedRandomStateForTest(SeedRand::ZEROS);
FuzzedDataProvider fuzzed_data_provider(buffer.data(), buffer.size());
SetMockTime(ConsumeTime(fuzzed_data_provider));
NodeClockContext clock_ctx{ConsumeTime(fuzzed_data_provider)};

NetGroupManager netgroupman{ConsumeNetGroupManager(fuzzed_data_provider)};
AddrManDeterministic addr_man1{netgroupman, fuzzed_data_provider, GetCheckRatio()};
Expand Down
5 changes: 3 additions & 2 deletions src/test/fuzz/banman.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
#include <test/fuzz/util.h>
#include <test/fuzz/util/net.h>
#include <test/util/setup_common.h>
#include <test/util/time.h>
#include <util/fs.h>
#include <util/readwritefile.h>

Expand Down Expand Up @@ -44,7 +45,7 @@ FUZZ_TARGET(banman, .init = initialize_banman)
{
SeedRandomStateForTest(SeedRand::ZEROS);
FuzzedDataProvider fuzzed_data_provider{buffer.data(), buffer.size()};
SetMockTime(ConsumeTime(fuzzed_data_provider));
NodeClockContext clock_ctx{ConsumeTime(fuzzed_data_provider)};
fs::path banlist_file = gArgs.GetDataDirNet() / "fuzzed_banlist";

const bool start_with_corrupted_banlist{fuzzed_data_provider.ConsumeBool()};
Expand Down Expand Up @@ -124,7 +125,7 @@ FUZZ_TARGET(banman, .init = initialize_banman)
}
if (!force_read_and_write_to_err) {
ban_man.DumpBanlist();
SetMockTime(ConsumeTime(fuzzed_data_provider));
clock_ctx.set(ConsumeTime(fuzzed_data_provider));
banmap_t banmap;
ban_man.GetBanned(banmap);
BanMan ban_man_read{banlist_file, /*client_interface=*/nullptr, /*default_ban_time=*/0};
Expand Down
3 changes: 2 additions & 1 deletion src/test/fuzz/block_index_tree.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
#include <test/fuzz/fuzz.h>
#include <test/fuzz/util.h>
#include <test/util/setup_common.h>
#include <test/util/time.h>
#include <test/util/validation.h>
#include <validation.h>

Expand Down Expand Up @@ -41,7 +42,7 @@ void initialize_block_index_tree()
FUZZ_TARGET(block_index_tree, .init = initialize_block_index_tree)
{
FuzzedDataProvider fuzzed_data_provider(buffer.data(), buffer.size());
SetMockTime(ConsumeTime(fuzzed_data_provider));
NodeClockContext clock_ctx{ConsumeTime(fuzzed_data_provider)};
auto& chainman = static_cast<TestChainstateManager&>(*g_setup->m_node.chainman);
auto& blockman = static_cast<TestBlockManager&>(chainman.m_blockman);
CBlockIndex* genesis = chainman.ActiveChainstate().m_chain[0];
Expand Down
3 changes: 2 additions & 1 deletion src/test/fuzz/connman.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
#include <test/fuzz/util/net.h>
#include <test/fuzz/util/threadinterrupt.h>
#include <test/util/setup_common.h>
#include <test/util/time.h>
#include <util/translation.h>

#include <cstdint>
Expand All @@ -40,7 +41,7 @@ FUZZ_TARGET(connman, .init = initialize_connman)
{
SeedRandomStateForTest(SeedRand::ZEROS);
FuzzedDataProvider fuzzed_data_provider{buffer.data(), buffer.size()};
SetMockTime(ConsumeTime(fuzzed_data_provider));
NodeClockContext clock_ctx{ConsumeTime(fuzzed_data_provider)};
auto netgroupman{ConsumeNetGroupManager(fuzzed_data_provider)};
auto addr_man_ptr{std::make_unique<AddrManDeterministic>(netgroupman, fuzzed_data_provider, GetCheckRatio())};
if (fuzzed_data_provider.ConsumeBool()) {
Expand Down
3 changes: 2 additions & 1 deletion src/test/fuzz/headerssync.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
#include <test/fuzz/fuzz.h>
#include <test/fuzz/util.h>
#include <test/util/setup_common.h>
#include <test/util/time.h>
#include <uint256.h>
#include <util/chaintype.h>
#include <util/time.h>
Expand Down Expand Up @@ -59,7 +60,7 @@ FUZZ_TARGET(headers_sync_state, .init = initialize_headers_sync_state_fuzz)
CBlockHeader genesis_header{Params().GenesisBlock()};
CBlockIndex start_index(genesis_header);

SetMockTime(ConsumeTime(fuzzed_data_provider, /*min=*/start_index.GetMedianTimePast()));
NodeClockContext clock_ctx{ConsumeTime(fuzzed_data_provider, /*min=*/start_index.GetMedianTimePast())};

const uint256 genesis_hash = genesis_header.GetHash();
start_index.phashBlock = &genesis_hash;
Expand Down
3 changes: 2 additions & 1 deletion src/test/fuzz/i2p.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
#include <test/fuzz/util/net.h>
#include <test/fuzz/util/threadinterrupt.h>
#include <test/util/setup_common.h>
#include <test/util/time.h>
#include <util/fs_helpers.h>
#include <util/threadinterrupt.h>

Expand All @@ -26,7 +27,7 @@ FUZZ_TARGET(i2p, .init = initialize_i2p)
SeedRandomStateForTest(SeedRand::ZEROS);
FuzzedDataProvider fuzzed_data_provider{buffer.data(), buffer.size()};

SetMockTime(ConsumeTime(fuzzed_data_provider));
NodeClockContext clock_ctx{ConsumeTime(fuzzed_data_provider)};

// Mock CreateSock() to create FuzzedSock.
auto CreateSockOrig = CreateSock;
Expand Down
3 changes: 2 additions & 1 deletion src/test/fuzz/load_external_block_file.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
#include <test/fuzz/fuzz.h>
#include <test/fuzz/util.h>
#include <test/util/setup_common.h>
#include <test/util/time.h>
#include <util/time.h>
#include <validation.h>

Expand All @@ -28,7 +29,7 @@ void initialize_load_external_block_file()
FUZZ_TARGET(load_external_block_file, .init = initialize_load_external_block_file)
{
FuzzedDataProvider fuzzed_data_provider{buffer.data(), buffer.size()};
SetMockTime(ConsumeTime(fuzzed_data_provider));
NodeClockContext clock_ctx{ConsumeTime(fuzzed_data_provider)};
FuzzedFileProvider fuzzed_file_provider{fuzzed_data_provider};
AutoFile fuzzed_block_file{fuzzed_file_provider.open()};
if (fuzzed_block_file.IsNull()) {
Expand Down
Loading
Loading