mirror of
https://github.com/bitcoin/bitcoin.git
synced 2026-09-12 21:52:53 +02:00
rpc: extend TxDoc() for getblock verbosity 2/3
Add fee, hex, fee_doc, prevout_doc, and vin_item_doc options to TxDocOptions, and use them to describe getblock verbosity=2/3 with structured metadata instead of inline ELISION-based reuse. Add GetBlockFields() to share the block-level result layout across verbosity levels.
This commit is contained in:
committed by
satsfy (Renato Britto)
parent
0380a1c46b
commit
372ac283ac
@@ -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\"")
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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{};
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user