mirror of
https://github.com/bitcoin/bitcoin.git
synced 2026-04-13 09:07:46 +02:00
rpc: Run type check on decodepsbt result
For RPCResults, the type may be ELISION, which is confusing and brittle:
* The elision should only affect the help output, not the type.
* The type should be the real type, so that type checks can be run on
it.
Fix this issue by introducing a new print_elision option and using it
in decodepsbt.
This change will ensure that RPCResult::MatchesType is properly run.
Also, this clarifies the RPC output minimally:
```diff
--- a/decodepsbt
+++ b/decodepsbt
@@ -35,7 +35,7 @@ Result:
"inputs" : [ (json array)
{ (json object)
"non_witness_utxo" : { (json object, optional) Decoded network transaction for non-witness UTXOs
- ...
+ ... The layout is the same as the output of decoderawtransaction.
},
"witness_utxo" : { (json object, optional) Transaction output for witness UTXOs
"amount" : n, (numeric) The value in BTC
```
This commit is contained in:
@@ -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},
|
||||
@@ -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, "", "",
|
||||
|
||||
@@ -346,14 +346,16 @@ 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},
|
||||
{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, "", "",
|
||||
@@ -372,7 +374,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(
|
||||
@@ -386,6 +388,6 @@ std::vector<RPCResult> TxDoc(const TxDocOptions& opts)
|
||||
std::vector<RPCResult>{}
|
||||
)
|
||||
},
|
||||
}},
|
||||
}, {.print_elision=maybe_skip}},
|
||||
};
|
||||
}
|
||||
|
||||
@@ -61,6 +61,8 @@ struct TxDocOptions {
|
||||
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> TxDoc(const TxDocOptions& opts = {});
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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 {
|
||||
@@ -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);
|
||||
|
||||
@@ -294,6 +294,15 @@ struct RPCArg {
|
||||
|
||||
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 {
|
||||
|
||||
Reference in New Issue
Block a user