rpc: introduce HelpElision variant and ElideGroup helper
What changed, and why it matters
This commit is a clean-up change to how Bitcoin Core formats its RPC help text. It replaces a slightly awkward way of hiding or summarising groups of help fields with a clearer helper function. There is no change to transaction handling, networking, wallet logic, or any code that processes untrusted data. It only affects the text users see when they call help commands.
No security action required. This is a refactoring commit affecting RPC help-text rendering only.
Security signals we found
No strong security signals were identified.
Evidence from the diff
The patch refactors RPCResultOptions::print_elision from std::optional
Changed components
src/rpc/util.hsrc/rpc/util.cppsrc/rpc/rawtransaction_util.cppInspect captured patch +71 / −29
diff --git a/src/rpc/rawtransaction_util.cpp b/src/rpc/rawtransaction_util.cpp
index f8b6e208..4f9f7b5c 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<RPCResult> TxDoc(const TxDocOptions& opts)
{
- std::optional<std::string> 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>{
+ {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<RPCResult> 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<RPCResult> TxDoc(const TxDocOptions& opts)
{RPCResult::Type::NUM, "n", "index"},
{RPCResult::Type::OBJ, "scriptPubKey", "", ScriptPubKeyDoc()},
},
- opts.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}},
+ )},
+ }},
};
+
+ 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 80b08d01..dd207278 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<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(); });
+ const auto is_elided = [](const RPCResult& res) {
+ return !std::holds_alternative<HelpElisionNone>(res.m_opts.print_elision);
+ };
+ const auto has_summary_text = [](const RPCResult& res) {
+ const auto* text = std::get_if<std::string>(&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<std::string>(&m_opts.print_elision)) {
+ if (!text->empty()) {
+ sections.PushSection({indent + "..." + maybe_separator, *text});
}
return;
}
+ if (std::holds_alternative<HelpElisionSkip>(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<RPCResult> ElideGroup(std::vector<RPCResult> fields, std::string summary)
+{
+ if (fields.empty()) return fields;
+ std::vector<RPCResult> 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 77199dfe..ce3d507e 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<HelpElisionNone, HelpElisionSkip, std::string>;
+
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};
+ 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<RPCResult> ElideGroup(std::vector<RPCResult> fields, std::string summary = "");
+
struct RPCResults {
const std::vector<RPCResult> m_results;
Why this scored 15/100
Community notes
Notes can correct, qualify, or add evidence to the AI analysis. Every note shown here has been validated by a human moderator.
The AI analysis stands alone for now. Submit a note if you can add evidence or important context.