From 44fc3a290d60076141f86717df56c4cf4cf34585 Mon Sep 17 00:00:00 2001 From: Renato Britto Date: Mon, 16 Mar 2026 19:43:21 -0300 Subject: [PATCH] rpc: introduce HelpElision variant and ElideGroup helper Switch RPCResultOptions::print_elision to a variant and add a constructor that copies a result with replacement options. Use the new ElideGroup() in TxDoc() to apply elision to field groups instead of setting print_elision per field. Type::ELISION stays as a deprecated alias. --- src/rpc/rawtransaction_util.cpp | 33 ++++++++++++++++-------------- src/rpc/util.cpp | 36 ++++++++++++++++++++++++++++----- src/rpc/util.h | 31 +++++++++++++++++++--------- 3 files changed, 71 insertions(+), 29 deletions(-) diff --git a/src/rpc/rawtransaction_util.cpp b/src/rpc/rawtransaction_util.cpp index f8b6e20875c..4f9f7b5c518 100644 --- a/src/rpc/rawtransaction_util.cpp +++ b/src/rpc/rawtransaction_util.cpp @@ -346,16 +346,14 @@ void SignTransactionResultToJSON(CMutableTransaction& mtx, bool complete, const std::vector TxDoc(const TxDocOptions& opts) { - std::optional maybe_skip{}; - if (opts.elision_description) maybe_skip.emplace(); - return { - {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}}, + auto fields = std::vector{ + {RPCResult::Type::STR_HEX, "txid", opts.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, "", "", @@ -374,7 +372,7 @@ std::vector TxDoc(const TxDocOptions& opts) }}, {RPCResult::Type::NUM, "sequence", "The script sequence number"}, }}, - }, {.print_elision=maybe_skip}}, + }}, {RPCResult::Type::ARR, "vout", "", { {RPCResult::Type::OBJ, "", "", Cat( @@ -383,11 +381,16 @@ std::vector TxDoc(const TxDocOptions& opts) {RPCResult::Type::NUM, "n", "index"}, {RPCResult::Type::OBJ, "scriptPubKey", "", ScriptPubKeyDoc()}, }, - opts.wallet ? + opts.wallet ? std::vector{{RPCResult::Type::BOOL, "ischange", /*optional=*/true, "Output script is change (only present if true)"}} : std::vector{} - ) - }, - }, {.print_elision=maybe_skip}}, + )}, + }}, }; + + if (opts.elision_description) { + fields = ElideGroup(std::move(fields), *opts.elision_description); + } + + return fields; } diff --git a/src/rpc/util.cpp b/src/rpc/util.cpp index 80b08d0140c..dd207278180 100644 --- a/src/rpc/util.cpp +++ b/src/rpc/util.cpp @@ -1017,16 +1017,25 @@ void RPCResult::ToSections(Sections& sections, const OuterType outer_type, const // Ensure at least one elision description exists, if there is any elision const auto elision_has_description{[](const std::vector& 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(); }); + const auto is_elided = [](const RPCResult& res) { + return !std::holds_alternative(res.m_opts.print_elision); + }; + const auto has_summary_text = [](const RPCResult& res) { + const auto* text = std::get_if(&res.m_opts.print_elision); + return text && !text->empty(); + }; + return std::ranges::none_of(inner, is_elided) || std::ranges::any_of(inner, has_summary_text); }}; - if (m_opts.print_elision) { - if (!m_opts.print_elision->empty()) { - sections.PushSection({indent + "..." + maybe_separator, *m_opts.print_elision}); + if (const auto* text = std::get_if(&m_opts.print_elision)) { + if (!text->empty()) { + sections.PushSection({indent + "..." + maybe_separator, *text}); } return; } + if (std::holds_alternative(m_opts.print_elision)) { + return; + } switch (m_type) { case Type::ELISION: { @@ -1418,3 +1427,20 @@ uint256 GetTarget(const CBlockIndex& blockindex, const uint256 pow_limit) arith_uint256 target{*CHECK_NONFATAL(DeriveTarget(blockindex.nBits, pow_limit))}; return ArithToUint256(target); } + +std::vector ElideGroup(std::vector fields, std::string summary) +{ + if (fields.empty()) return fields; + std::vector result; + result.reserve(fields.size()); + for (size_t i = 0; i < fields.size(); ++i) { + RPCResultOptions opts = fields[i].m_opts; + if (i == 0) { + opts.print_elision = summary; + } else { + opts.print_elision = HelpElisionSkip{}; + } + result.emplace_back(fields[i], std::move(opts)); + } + return result; +} diff --git a/src/rpc/util.h b/src/rpc/util.h index 77199dfe3fd..ce3d507e3b0 100644 --- a/src/rpc/util.h +++ b/src/rpc/util.h @@ -292,18 +292,17 @@ struct RPCArg { std::string ToDescriptionString(bool is_named_arg) const; }; +/// Controls how an RPCResult is rendered in human-readable help text. +/// The std::string alternative carries the summary text rendered as "...". +struct HelpElisionNone {}; //!< field printed normally +struct HelpElisionSkip {}; //!< field hidden from help +using HelpElision = std::variant; + 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 print_elision{std::nullopt}; + HelpElision print_elision{HelpElisionNone{}}; }; + // NOLINTNEXTLINE(misc-no-recursion) struct RPCResult { enum class Type { @@ -385,6 +384,16 @@ struct RPCResult { RPCResultOptions opts = {}) : RPCResult{type, std::move(m_key_name), /*optional=*/false, std::move(description), std::move(inner), std::move(opts)} {} + /// Copy with replacement options, for stamping new opts onto an existing result. + RPCResult(const RPCResult& other, RPCResultOptions opts) + : m_type{other.m_type}, + m_key_name{other.m_key_name}, + m_inner{other.m_inner}, + m_optional{other.m_optional}, + m_opts{std::move(opts)}, + m_description{other.m_description}, + m_cond{other.m_cond} {} + /** Append the sections of the result. */ void ToSections(Sections& sections, OuterType outer_type = OuterType::NONE, int current_indent = 0) const; /** Return the type string of the result when it is in an object (dict). */ @@ -400,6 +409,10 @@ private: void CheckInnerDoc() const; }; +/// Stamp elision onto an entire vector of RPCResult fields at once. +/// Merges into existing m_opts so that flags like skip_type_check are preserved. +std::vector ElideGroup(std::vector fields, std::string summary = ""); + struct RPCResults { const std::vector m_results;