rpc: Run type check on decodepsbt result#34799
rpc: Run type check on decodepsbt result#34799maflcko wants to merge 3 commits intobitcoin:masterfrom
Conversation
Initially only move skip_type_check there. In the future, more options can be added, without having to touch the constructors.
|
The following sections might be updated with supplementary metadata relevant to reviewers and maintainers. ReviewsSee the guideline for information on the review process.
If your review is incorrectly listed, please copy-paste ConflictsReviewers, this pull request conflicts with the following ones:
If you consider this pull request important, please also help to review the conflicting pull requests. Ideally, start with the one that should be merged first. LLM Linter (✨ experimental)Possible places where named args for integral literals may be used (e.g.
2026-03-12 10:11:24 |
This prepares the function to be more flexible, when more options are passed in the future.
fa23ecd to
fab38de
Compare
satsfy
left a comment
There was a problem hiding this comment.
Concept ACK
Thanks for this, it enables much finer control of the TxDoc() in bitcoin-cli help. I'm rebasing it in #34764.
Separately, I'm planning to handle the more general elision case in my branch, taking inspiration from your print_elision mechanism. For example, getrawtransaction verbosity = 2 previously showed:
Result (for verbosity = 2):
{ (json object)
..., Same output as verbosity = 1
"fee" : n, (numeric, optional) transaction fee in BTC, omitted if block undo data is not available,
...only shows what is different from verbosity = 1
}But now expands everything. The solution you introduced here could potentially solve that too.
fab38de to
fa1d760
Compare
|
Concept ACK. This seems like the correct fix to the conflation of type-check skipping with (display) elision, and therefore fixes it in the right layer. I think this is the pattern we'd want to follow to convert the remaining elision sites to use |
Bortlesboat
left a comment
There was a problem hiding this comment.
Concept ACK fa1d760.
I traced the relevant path through RPCHelpMan::HandleRequest() -> RPCResult::MatchesType() and RPCResult::ToSections(), and the separation here looks right to me: print_elision is now a presentation concern, while type-check suppression stays under skip_type_check.
That seems to fix the decodepsbt issue in the correct layer. Before this change, using ELISION for human-readable help also caused the result metadata to skip validation entirely. After this change, decodepsbt can keep the concise help text via TxDoc({.elision_description=...}) while still participating in runtime doc checks.
I wasn't able to rebuild this revision from the current terminal because cmake is not on PATH here, so this is a source-level review rather than a tested ACK.
| (this->m_description.empty() ? "" : " " + this->m_description); | ||
| }; | ||
|
|
||
| if (m_opts.print_elision) { |
There was a problem hiding this comment.
If print_elision is set to an empty string, all children silently return and the parent's OBJ handler corrupts the output. Do you think it's worth guarding against?
There was a problem hiding this comment.
Yeah, I guess it can't hurt to fail than to silently produce a corrupt help test. Done, thx!
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
```
fa1d760 to
fafa5b6
Compare
It should work, see a diffdiff --git a/src/wallet/rpc/addresses.cpp b/src/wallet/rpc/addresses.cpp
index a04e0903b9..5c0866e3dc 100644
--- a/src/wallet/rpc/addresses.cpp
+++ b/src/wallet/rpc/addresses.cpp
@@ -365,6 +365,50 @@ static UniValue DescribeWalletAddress(const CWallet& wallet, const CTxDestinatio
return ret;
}
+static std::vector<RPCResult> AddrInfoDoc(const std::optional<std::string>& elision_description = {})
+{
+ std::optional<std::string> maybe_skip{};
+ if (elision_description) maybe_skip.emplace();
+ return {
+ {RPCResult::Type::STR, "address", "The bitcoin address validated.", {}, {.print_elision = elision_description}},
+ {RPCResult::Type::STR_HEX, "scriptPubKey", "The hex-encoded output script generated by the address.", {}, {.print_elision = maybe_skip}},
+ {RPCResult::Type::BOOL, "isscript", /*optional=*/true, "If the key is a script.", {}, {.print_elision = maybe_skip}},
+ {RPCResult::Type::BOOL, "iswitness", "If the address is a witness address.", {}, {.print_elision = maybe_skip}},
+ {RPCResult::Type::NUM, "witness_version", /*optional=*/true, "The version number of the witness program.", {}, {.print_elision = maybe_skip}},
+ {RPCResult::Type::STR_HEX, "witness_program", /*optional=*/true, "The hex value of the witness program.", {}, {.print_elision = maybe_skip}},
+ {RPCResult::Type::STR_HEX, "pubkey", /*optional=*/true, "The hex value of the raw public key for single-key addresses (possibly embedded in P2SH or P2WSH).", {}, {.print_elision = maybe_skip}},
+ {RPCResult::Type::STR, "script", /*optional=*/true, "The output script type. Only if isscript is true and the redeemscript is known. Possible\n"
+ "types: nonstandard, pubkey, pubkeyhash, scripthash, multisig, nulldata, witness_v0_keyhash,\n"
+ "witness_v0_scripthash, witness_unknown.",
+ {},
+ {.print_elision = maybe_skip}},
+ {RPCResult::Type::STR_HEX, "hex", /*optional=*/true, "The redeemscript for the p2sh address.", {}, {.print_elision = maybe_skip}},
+ {RPCResult::Type::BOOL, "iscompressed", /*optional=*/true, "If the pubkey is compressed.", {}, {.print_elision = maybe_skip}},
+ {
+ RPCResult::Type::OBJ,
+ "embedded",
+ /*optional=*/true,
+ "Information about the address embedded in P2SH or P2WSH, if relevant and known.",
+ {
+ {RPCResult::Type::STR, "address", "The bitcoin address validated.", {}, {.print_elision = elision_description}},
+ {RPCResult::Type::STR_HEX, "scriptPubKey", "The hex-encoded output script generated by the address.", {}, {.print_elision = maybe_skip}},
+ {RPCResult::Type::BOOL, "isscript", /*optional=*/true, "If the key is a script.", {}, {.print_elision = maybe_skip}},
+ {RPCResult::Type::STR_HEX, "pubkey", /*optional=*/true, "The hex value of the raw public key for single-key addresses (possibly embedded in P2SH or P2WSH).", {}, {.print_elision = maybe_skip}},
+ {RPCResult::Type::STR, "script", /*optional=*/true, "The output script type. Only if isscript is true and the redeemscript is known. Possible\n"
+ "types: nonstandard, pubkey, pubkeyhash, scripthash, multisig, nulldata, witness_v0_keyhash,\n"
+ "witness_v0_scripthash, witness_unknown.",
+ {},
+ {.print_elision = maybe_skip}},
+ {RPCResult::Type::STR_HEX, "hex", /*optional=*/true, "The redeemscript for the p2sh address.", {}, {.print_elision = maybe_skip}},
+ {RPCResult::Type::BOOL, "iswitness", "If the address is a witness address.", {}, {.print_elision = maybe_skip}},
+ {RPCResult::Type::BOOL, "iscompressed", /*optional=*/true, "If the pubkey is compressed.", {}, {.print_elision = maybe_skip}},
+ },
+ {.print_elision = maybe_skip},
+ },
+ };
+}
+
+
RPCHelpMan getaddressinfo()
{
return RPCHelpMan{
@@ -401,8 +445,8 @@ RPCHelpMan getaddressinfo()
{RPCResult::Type::STR_HEX, "pubkey", /*optional=*/true, "The hex value of the raw public key for single-key addresses (possibly embedded in P2SH or P2WSH)."},
{RPCResult::Type::OBJ, "embedded", /*optional=*/true, "Information about the address embedded in P2SH or P2WSH, if relevant and known.",
{
- {RPCResult::Type::ELISION, "", "Includes all getaddressinfo output fields for the embedded address, excluding metadata (timestamp, hdkeypath, hdseedid)\n"
- "and relation to the wallet (ismine)."},
+ AddrInfoDoc("Includes all getaddressinfo output fields for the embedded address, excluding metadata (timestamp, hdkeypath, hdseedid)\n"
+ "and relation to the wallet (ismine)."),
}},
{RPCResult::Type::BOOL, "iscompressed", /*optional=*/true, "If the pubkey is compressed."},
{RPCResult::Type::NUM_TIME, "timestamp", /*optional=*/true, "The creation time of the key, if available, expressed in " + UNIX_EPOCH_TIME + "."},However, I think the docs are wrong, and it could make sense to fix them. However, this pull is mostly meant as a refactor and fixing the docs can be done in a follow-up. |
Nice! I had tried this before but ran into one ambiguity: the runtime code in
I'm planning to try and do this in #34764, or do you think it should be follow-up? |
Why not? It is not possible to, let's say, nest witness twice. Also, if the docs didn't match, it would be caught, which is the goal of this pull. However, I am thinking if in-lining is better here, so I think it should be a separate pull request or a follow-up pull request. The goal of this pull is to not fix any bugs or change the docs. It is mostly meant as a refactor. |
Bortlesboat
left a comment
There was a problem hiding this comment.
Re-ACK fafa5b6 (source-level).
I reviewed the changes since my prior ACK on fa1d760, including the RPCResultOptions migration and the new print_elision path in RPCResult::ToSections(). The separation between display elision and type-check behavior now looks consistent, and decodepsbt now references TxDoc({.elision_description=...}) in a way that keeps MatchesType() active.
No blocking issues found in this delta. I still haven’t rebuilt this revision locally in this environment, so this is untested source review.
| /// 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. | ||
| std::optional<std::string> print_elision{std::nullopt}; |
There was a problem hiding this comment.
Would it be worth adding even more comment here to explain the three states:
/// If nullopt: normal display. If empty: suppress from help. If non-empty: show "..." with this description.
|
Concept ACK |
For RPCResults, the type may be ELISION, which is confusing and brittle:
Fix this issue by introducing a new print_elision option and using it in
decodepsbt.This change will ensure that
RPCResult::MatchesTypeis properly run.Can be tested by introducing a bug:
And then running (in a debug build)
decodepsbt cHNidP8BAAoCAAAAAAAAAAAAAA==Before, on master: passes
Now, on this pull: Properly detects the bug