rpc: extend TxDoc() for getblock verbosity 2/3
What changed, and why it matters
This commit is a code cleanup in Bitcoin Core's RPC help documentation. It restructures how the help text for the getblock command is generated, replacing duplicated inline descriptions with shared helper functions. It does not change what data the software returns, how it processes network data, or any security-sensitive logic.
No security action required. This is a documentation/refactoring change.
Security signals we found
No strong security signals were identified.
Evidence from the diff
The patch refactors RPC result documentation for getblock verbosity levels 0-3. It introduces GetBlockFields() to share the block-level result layout and extends TxDoc() with new options (fee, hex, fee_doc, prevout_doc, vin_item_doc) to replace inline ELISION-based reuse. The changes are confined to help-text generation in src/rpc/blockchain.cpp and src/rpc/rawtransaction_util.cpp/.h. No consensus, networking, wallet, or transaction validation code is modified.
Changed components
src/rpc/blockchain.cppsrc/rpc/rawtransaction_util.cppsrc/rpc/rawtransaction_util.hInspect captured patch +118 / −91
diff --git a/src/rpc/blockchain.cpp b/src/rpc/blockchain.cpp
index 14412c90..d78b82bc 100644
--- a/src/rpc/blockchain.cpp
+++ b/src/rpc/blockchain.cpp
@@ -33,6 +33,7 @@
#include <node/utxo_snapshot.h>
#include <node/warnings.h>
#include <primitives/transaction.h>
+#include <rpc/rawtransaction_util.h>
#include <rpc/server.h>
#include <rpc/server_util.h>
#include <rpc/util.h>
@@ -743,32 +744,62 @@ static CBlockUndo GetUndoChecked(BlockManager& blockman, const CBlockIndex& bloc
return blockUndo;
}
-const RPCResult& GetBlockVin()
+static std::vector<RPCResult> GetBlockFields(RPCResult tx_result, std::optional<std::string> elision_msg = std::nullopt)
{
- static const RPCResult getblock_vin{
- RPCResult::Type::ARR, "vin", "",
+ auto fields = std::vector<RPCResult>{
+ {RPCResult::Type::STR_HEX, "hash", "the block hash (same as provided)"},
+ {RPCResult::Type::NUM, "confirmations", "The number of confirmations, or -1 if the block is not on the main chain"},
+ {RPCResult::Type::NUM, "size", "The block size"},
+ {RPCResult::Type::NUM, "strippedsize", "The block size excluding witness data"},
+ {RPCResult::Type::NUM, "weight", "The block weight as defined in BIP 141"},
+ {RPCResult::Type::OBJ, "coinbase_tx", "Coinbase transaction metadata",
{
- {RPCResult::Type::OBJ, "", "",
- {
- {RPCResult::Type::ELISION, "", "The same output as verbosity = 2"},
- {RPCResult::Type::OBJ, "prevout", "(Only if undo information is available)",
- {
- {RPCResult::Type::BOOL, "generated", "Coinbase or not"},
- {RPCResult::Type::NUM, "height", "The height of the prevout"},
- {RPCResult::Type::STR_AMOUNT, "value", "The value in " + CURRENCY_UNIT},
- {RPCResult::Type::OBJ, "scriptPubKey", "",
- {
- {RPCResult::Type::STR, "asm", "Disassembly of the output script"},
- {RPCResult::Type::STR, "desc", "Inferred descriptor for the output"},
- {RPCResult::Type::STR_HEX, "hex", "The raw output script bytes, hex-encoded"},
- {RPCResult::Type::STR, "address", /*optional=*/true, "The Bitcoin address (only if a well-defined address exists)"},
- {RPCResult::Type::STR, "type", "The type (one of: " + GetAllOutputTypes() + ")"},
- }},
- }},
- }},
- }
+ {RPCResult::Type::NUM, "version", "The coinbase transaction version"},
+ {RPCResult::Type::NUM, "locktime", "The coinbase transaction's locktime (nLockTime)"},
+ {RPCResult::Type::NUM, "sequence", "The coinbase input's sequence number (nSequence)"},
+ {RPCResult::Type::STR_HEX, "coinbase", "The coinbase input's script"},
+ {RPCResult::Type::STR_HEX, "witness", /*optional=*/true, "The coinbase input's first (and only) witness stack element, if present"},
+ }},
+ {RPCResult::Type::NUM, "height", "The block height or index"},
+ {RPCResult::Type::NUM, "version", "The block version"},
+ {RPCResult::Type::STR_HEX, "versionHex", "The block version formatted in hexadecimal"},
+ {RPCResult::Type::STR_HEX, "merkleroot", "The merkle root"},
};
- return getblock_vin;
+ fields.push_back(std::move(tx_result));
+ fields.emplace_back(RPCResult::Type::NUM_TIME, "time", "The block time expressed in " + UNIX_EPOCH_TIME);
+ fields.emplace_back(RPCResult::Type::NUM_TIME, "mediantime", "The median block time expressed in " + UNIX_EPOCH_TIME);
+ fields.emplace_back(RPCResult::Type::NUM, "nonce", "The nonce");
+ fields.emplace_back(RPCResult::Type::STR_HEX, "bits", "nBits: compact representation of the block difficulty target");
+ fields.emplace_back(RPCResult::Type::STR_HEX, "target", "The difficulty target");
+ fields.emplace_back(RPCResult::Type::NUM, "difficulty", "The difficulty");
+ fields.emplace_back(RPCResult::Type::STR_HEX, "chainwork", "Expected number of hashes required to produce the chain up to this block (in hex)");
+ fields.emplace_back(RPCResult::Type::NUM, "nTx", "The number of transactions in the block");
+ fields.emplace_back(RPCResult::Type::STR_HEX, "previousblockhash", /*optional=*/true, "The hash of the previous block (if available)");
+ fields.emplace_back(RPCResult::Type::STR_HEX, "nextblockhash", /*optional=*/true, "The hash of the next block (if available)");
+ if (elision_msg) {
+ // Elide all block-level fields except the tx array (which differs per verbosity)
+ std::vector<RPCResult> new_fields;
+ new_fields.reserve(fields.size());
+ bool first = true;
+ for (const auto& f : fields) {
+ if (f.m_key_name == "tx") {
+ new_fields.push_back(f);
+ continue;
+ }
+ if (first) {
+ RPCResultOptions eopts = f.m_opts;
+ eopts.print_elision = *elision_msg;
+ new_fields.emplace_back(f, std::move(eopts));
+ first = false;
+ } else {
+ RPCResultOptions eopts = f.m_opts;
+ eopts.print_elision = HelpElisionSkip{};
+ new_fields.emplace_back(f, std::move(eopts));
+ }
+ }
+ fields = std::move(new_fields);
+ }
+ return fields;
}
static RPCMethod getblock()
@@ -785,67 +816,33 @@ static RPCMethod getblock()
RPCArgOptions{.skip_type_check = true}},
},
{
- RPCResult{"for verbosity = 0",
- RPCResult::Type::STR_HEX, "", "A string that is serialized, hex-encoded data for block 'hash'"},
- RPCResult{"for verbosity = 1",
- RPCResult::Type::OBJ, "", "",
- {
- {RPCResult::Type::STR_HEX, "hash", "the block hash (same as provided)"},
- {RPCResult::Type::NUM, "confirmations", "The number of confirmations, or -1 if the block is not on the main chain"},
- {RPCResult::Type::NUM, "size", "The block size"},
- {RPCResult::Type::NUM, "strippedsize", "The block size excluding witness data"},
- {RPCResult::Type::NUM, "weight", "The block weight as defined in BIP 141"},
- {RPCResult::Type::OBJ, "coinbase_tx", "Coinbase transaction metadata",
- {
- {RPCResult::Type::NUM, "version", "The coinbase transaction version"},
- {RPCResult::Type::NUM, "locktime", "The coinbase transaction's locktime (nLockTime)"},
- {RPCResult::Type::NUM, "sequence", "The coinbase input's sequence number (nSequence)"},
- {RPCResult::Type::STR_HEX, "coinbase", "The coinbase input's script"},
- {RPCResult::Type::STR_HEX, "witness", /*optional=*/true, "The coinbase input's first (and only) witness stack element, if present"},
- }},
- {RPCResult::Type::NUM, "height", "The block height or index"},
- {RPCResult::Type::NUM, "version", "The block version"},
- {RPCResult::Type::STR_HEX, "versionHex", "The block version formatted in hexadecimal"},
- {RPCResult::Type::STR_HEX, "merkleroot", "The merkle root"},
- {RPCResult::Type::ARR, "tx", "The transaction ids",
- {{RPCResult::Type::STR_HEX, "", "The transaction id"}}},
- {RPCResult::Type::NUM_TIME, "time", "The block time expressed in " + UNIX_EPOCH_TIME},
- {RPCResult::Type::NUM_TIME, "mediantime", "The median block time expressed in " + UNIX_EPOCH_TIME},
- {RPCResult::Type::NUM, "nonce", "The nonce"},
- {RPCResult::Type::STR_HEX, "bits", "nBits: compact representation of the block difficulty target"},
- {RPCResult::Type::STR_HEX, "target", "The difficulty target"},
- {RPCResult::Type::NUM, "difficulty", "The difficulty"},
- {RPCResult::Type::STR_HEX, "chainwork", "Expected number of hashes required to produce the chain up to this block (in hex)"},
- {RPCResult::Type::NUM, "nTx", "The number of transactions in the block"},
- {RPCResult::Type::STR_HEX, "previousblockhash", /*optional=*/true, "The hash of the previous block (if available)"},
- {RPCResult::Type::STR_HEX, "nextblockhash", /*optional=*/true, "The hash of the next block (if available)"},
- }},
- RPCResult{"for verbosity = 2",
- RPCResult::Type::OBJ, "", "",
- {
- {RPCResult::Type::ELISION, "", "Same output as verbosity = 1"},
- {RPCResult::Type::ARR, "tx", "",
- {
- {RPCResult::Type::OBJ, "", "",
+ RPCResult{"for verbosity = 0", RPCResult::Type::STR_HEX, "", "A string that is serialized, hex-encoded data for block 'hash'"},
+ RPCResult{"for verbosity = 1", RPCResult::Type::OBJ, "", "",
+ GetBlockFields({RPCResult::Type::ARR, "tx", "The transaction ids",
+ {{RPCResult::Type::STR_HEX, "", "The transaction id"}}})},
+ RPCResult{"for verbosity = 2", RPCResult::Type::OBJ, "", "",
+ GetBlockFields({RPCResult::Type::ARR, "tx", "",
{
- {RPCResult::Type::ELISION, "", "The transactions in the format of the getrawtransaction RPC. Different from verbosity = 1 \"tx\" result"},
- {RPCResult::Type::NUM, "fee", /*optional=*/true, "The transaction fee in " + CURRENCY_UNIT + ", omitted if block undo data is not available"},
- }},
- }},
- }},
- RPCResult{"for verbosity = 3",
- RPCResult::Type::OBJ, "", "",
- {
- {RPCResult::Type::ELISION, "", "Same output as verbosity = 2"},
- {RPCResult::Type::ARR, "tx", "",
- {
- {RPCResult::Type::OBJ, "", "",
+ {RPCResult::Type::OBJ, "", "",
+ TxDoc({.elision_mode = ElisionMode::WithSummary,
+ .elision_summary = "The transactions in the format of the getrawtransaction RPC. Different from verbosity = 1 \"tx\" result",
+ .fee = true, .hex = true,
+ .fee_doc = "The transaction fee in " + CURRENCY_UNIT + ", omitted if block undo data is not available"})},
+ }}, /*elision_msg=*/"Same output as verbosity = 1")},
+ RPCResult{"for verbosity = 3", RPCResult::Type::OBJ, "", "",
+ GetBlockFields({RPCResult::Type::ARR, "tx", "",
{
- GetBlockVin(),
- }},
- }},
- }},
- },
+ {RPCResult::Type::OBJ, "", "",
+ TxDoc({.elision_mode = ElisionMode::Silent,
+ .prevout = true,
+ .prevout_optional = true,
+ .fee = true,
+ .hex = true,
+ .vin_item_doc = "",
+ .prevout_doc = "(Only if undo information is available)",
+ .vin_inner_elision = "The same output as verbosity = 2"})},
+ }}, /*elision_msg=*/"Same output as verbosity = 2")},
+ },
RPCExamples{
HelpExampleCli("getblock", "\"00000000c937983704a73af28acdec37b049d214adbda81d7e2a3dd146f6ed09\"")
+ HelpExampleRpc("getblock", "\"00000000c937983704a73af28acdec37b049d214adbda81d7e2a3dd146f6ed09\"")
diff --git a/src/rpc/rawtransaction_util.cpp b/src/rpc/rawtransaction_util.cpp
index a1973342..c90ead5a 100644
--- a/src/rpc/rawtransaction_util.cpp
+++ b/src/rpc/rawtransaction_util.cpp
@@ -17,6 +17,7 @@
#include <script/signingprovider.h>
#include <tinyformat.h>
#include <univalue.h>
+#include <util/check.h>
#include <util/rbf.h>
#include <util/string.h>
#include <util/strencodings.h>
@@ -346,6 +347,17 @@ void SignTransactionResultToJSON(CMutableTransaction& mtx, bool complete, const
std::vector<RPCResult> TxDoc(const TxDocOptions& opts)
{
+ CHECK_NONFATAL(!opts.fee_doc || opts.fee);
+ CHECK_NONFATAL(!opts.prevout_doc || opts.prevout);
+ CHECK_NONFATAL(!opts.vin_item_doc || opts.vin_inner_elision);
+ CHECK_NONFATAL(opts.elision_mode != ElisionMode::WithSummary || opts.elision_summary.has_value());
+
+ const std::string fee_doc{opts.fee_doc.value_or(
+ "transaction fee in " + CURRENCY_UNIT + ", omitted if block undo data is not available")};
+ const std::string prevout_doc{opts.prevout_doc.value_or(
+ "The previous output, omitted if block undo data is not available")};
+ const std::string vin_item_doc{opts.vin_item_doc.value_or("utxo being spent")};
+
auto vin_inner = std::vector<RPCResult>{
{RPCResult::Type::STR_HEX, "coinbase", /*optional=*/true, "The coinbase value (only if coinbase transaction)"},
{RPCResult::Type::STR_HEX, "txid", /*optional=*/true, "The transaction id (if not coinbase transaction)"},
@@ -361,14 +373,15 @@ std::vector<RPCResult> TxDoc(const TxDocOptions& opts)
}},
};
if (opts.prevout) {
- vin_inner.emplace_back(RPCResult::Type::OBJ, "prevout", opts.prevout_optional,
- "The previous output, omitted if block undo data is not available",
- std::vector<RPCResult>{
- {RPCResult::Type::BOOL, "generated", "Coinbase or not"},
- {RPCResult::Type::NUM, "height", "The height of the prevout"},
- {RPCResult::Type::STR_AMOUNT, "value", "The value in " + CURRENCY_UNIT},
- {RPCResult::Type::OBJ, "scriptPubKey", "", ScriptPubKeyDoc()},
- });
+ vin_inner.emplace_back(
+ RPCResult::Type::OBJ, "prevout", opts.prevout_optional, prevout_doc,
+ std::vector<RPCResult>{
+ {RPCResult::Type::BOOL, "generated", "Coinbase or not"},
+ {RPCResult::Type::NUM, "height", "The height of the prevout"},
+ {RPCResult::Type::STR_AMOUNT, "value", "The value in " + CURRENCY_UNIT},
+ {RPCResult::Type::OBJ, "scriptPubKey", "", ScriptPubKeyDoc()},
+ }
+ );
}
vin_inner.emplace_back(RPCResult::Type::NUM, "sequence", "The script sequence number");
@@ -401,7 +414,7 @@ std::vector<RPCResult> TxDoc(const TxDocOptions& opts)
{RPCResult::Type::NUM_TIME, "locktime", "The lock time"},
{RPCResult::Type::ARR, "vin", "",
{
- {RPCResult::Type::OBJ, "", "", std::move(vin_inner)},
+ {RPCResult::Type::OBJ, "", opts.vin_inner_elision ? vin_item_doc : "", std::move(vin_inner)},
}},
{RPCResult::Type::ARR, "vout", "",
{
@@ -418,12 +431,19 @@ std::vector<RPCResult> TxDoc(const TxDocOptions& opts)
}},
};
+ if (opts.fee) fields.emplace_back(RPCResult::Type::NUM, "fee", /*optional=*/true, fee_doc);
+ if (opts.hex) fields.emplace_back(RPCResult::Type::STR_HEX, "hex", "The hex-encoded transaction data");
+
if (opts.elision_mode != ElisionMode::None) {
const bool silent = opts.elision_mode == ElisionMode::Silent;
std::vector<RPCResult> new_fields;
new_fields.reserve(fields.size());
bool first = true;
for (const auto& f : fields) {
+ if (!silent && f.m_key_name == "fee") {
+ new_fields.push_back(f);
+ continue;
+ }
if (f.m_key_name == "vin" && opts.vin_inner_elision) {
new_fields.push_back(f);
continue;
diff --git a/src/rpc/rawtransaction_util.h b/src/rpc/rawtransaction_util.h
index ee2f3040..52d5e394 100644
--- a/src/rpc/rawtransaction_util.h
+++ b/src/rpc/rawtransaction_util.h
@@ -75,6 +75,16 @@ struct TxDocOptions {
bool prevout{false};
/// Mark prevout field as optional (omitted when undo data unavailable)
bool prevout_optional{false};
+ /// Include fee field
+ bool fee{false};
+ /// Include hex field
+ bool hex{false};
+ /// Customize the vin item object's description (only meaningful when vin_inner_elision is set)
+ std::optional<std::string> vin_item_doc{};
+ /// Customize the prevout field's description (only meaningful when prevout is true)
+ std::optional<std::string> prevout_doc{};
+ /// Customize the fee field's description (only meaningful when fee is true)
+ std::optional<std::string> fee_doc{};
/// Elide vin inner fields but keep vin array with prevout expanded.
std::optional<std::string> vin_inner_elision{};
};
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.