rpc: add coinbase_tx field to getblock
What changed, and why it matters
This change adds a new convenience field called coinbase_tx to the getblock RPC response in Bitcoin Core. It exposes only small, already-public metadata about the first transaction in a block (the coinbase transaction), such as version, locktime, sequence, coinbase script, and witness data. It does not expose transaction outputs or any new sensitive data. This is a feature addition, not a security fix or vulnerability.
No security action required. Treat as a normal feature addition. Reviewers may optionally consider whether the new RPC field has any privacy or DoS implications for RPC users, but the exposed fields are small and already public on-chain data.
Security signals we found
No security-relevant signals detected
Feature addition exposing already-public blockchain data via RPC
Uses CHECK_NONFATAL for invariants rather than runtime error handling
Evidence from the diff
The commit introduces coinbaseTxToJSON() in src/rpc/blockchain.cpp and wires it into blockToJSON() so that getblock returns a coinbase_tx object at verbosity levels 1, 2, and 3. The object contains version, nLockTime, the first input’s nSequence, the coinbase scriptSig as hex, and the first witness stack element as hex if present. The implementation uses CHECK_NONFATAL for assumptions that block.vtx and coinbase_tx.vin are non-empty and that the witness stack has size 1. The release notes and RPC help are updated, and functional tests validate the new field against verbosity-2 data.
Changed components
src/rpc/blockchain.cppRPC getblockdoc/release-notes-34512.mdtest/functional/rpc_blockchain.pyInspect captured patch +61 / −0
diff --git a/doc/release-notes-34512.md b/doc/release-notes-34512.md
new file mode 100644
index 00000000..b8634488
--- /dev/null
+++ b/doc/release-notes-34512.md
@@ -0,0 +1,8 @@
+Updated RPCs
+------------
+
+- The `getblock` RPC now returns a `coinbase_tx` object at verbosity levels 1, 2,
+ and 3. It contains `version`, `locktime`, `sequence`, `coinbase` and
+ `witness`. This allows for efficiently querying coinbase
+ transaction properties without fetching the full transaction data at
+ verbosity 2+. (#34512)
diff --git a/src/rpc/blockchain.cpp b/src/rpc/blockchain.cpp
index a883570f..ca343a4c 100644
--- a/src/rpc/blockchain.cpp
+++ b/src/rpc/blockchain.cpp
@@ -181,6 +181,24 @@ UniValue blockheaderToJSON(const CBlockIndex& tip, const CBlockIndex& blockindex
return result;
}
+/** Serialize coinbase transaction metadata */
+UniValue coinbaseTxToJSON(const CTransaction& coinbase_tx)
+{
+ CHECK_NONFATAL(!coinbase_tx.vin.empty());
+ const CTxIn& vin_0{coinbase_tx.vin[0]};
+ UniValue coinbase_tx_obj(UniValue::VOBJ);
+ coinbase_tx_obj.pushKV("version", coinbase_tx.version);
+ coinbase_tx_obj.pushKV("locktime", coinbase_tx.nLockTime);
+ coinbase_tx_obj.pushKV("sequence", vin_0.nSequence);
+ coinbase_tx_obj.pushKV("coinbase", HexStr(vin_0.scriptSig));
+ const auto& witness_stack{vin_0.scriptWitness.stack};
+ if (!witness_stack.empty()) {
+ CHECK_NONFATAL(witness_stack.size() == 1);
+ coinbase_tx_obj.pushKV("witness", HexStr(witness_stack[0]));
+ }
+ return coinbase_tx_obj;
+}
+
UniValue blockToJSON(BlockManager& blockman, const CBlock& block, const CBlockIndex& tip, const CBlockIndex& blockindex, TxVerbosity verbosity, const uint256 pow_limit)
{
UniValue result = blockheaderToJSON(tip, blockindex, pow_limit);
@@ -188,6 +206,10 @@ UniValue blockToJSON(BlockManager& blockman, const CBlock& block, const CBlockIn
result.pushKV("strippedsize", ::GetSerializeSize(TX_NO_WITNESS(block)));
result.pushKV("size", ::GetSerializeSize(TX_WITH_WITNESS(block)));
result.pushKV("weight", ::GetBlockWeight(block));
+
+ CHECK_NONFATAL(!block.vtx.empty());
+ result.pushKV("coinbase_tx", coinbaseTxToJSON(*block.vtx[0]));
+
UniValue txs(UniValue::VARR);
txs.reserve(block.vtx.size());
@@ -760,6 +782,14 @@ static RPCHelpMan getblock()
{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"},
diff --git a/test/functional/rpc_blockchain.py b/test/functional/rpc_blockchain.py
index 5c1f2ee4..cecd0c03 100755
--- a/test/functional/rpc_blockchain.py
+++ b/test/functional/rpc_blockchain.py
@@ -646,6 +646,26 @@ class BlockchainTest(BitcoinTestFramework):
self.wallet.send_self_transfer(fee_rate=fee_per_kb, from_node=node)
blockhash = self.generate(node, 1)[0]
+ def assert_coinbase_metadata(hash, verbosity):
+ block = node.getblock(hash, verbosity)
+ coinbase_tx = node.getblock(hash, 2)["tx"][0]
+
+ expected_keys = {"version", "locktime", "sequence", "coinbase"}
+ if "txinwitness" in coinbase_tx["vin"][0]:
+ expected_keys.add("witness")
+ assert_equal(set(block["coinbase_tx"].keys()), expected_keys)
+
+ assert_equal(block["coinbase_tx"]["version"], coinbase_tx["version"])
+ assert_equal(block["coinbase_tx"]["locktime"], coinbase_tx["locktime"])
+ assert_equal(block["coinbase_tx"]["sequence"], coinbase_tx["vin"][0]["sequence"])
+ assert_equal(block["coinbase_tx"]["coinbase"], coinbase_tx["vin"][0]["coinbase"])
+
+ witness_stack = coinbase_tx["vin"][0].get("txinwitness")
+ if witness_stack is None:
+ assert "witness" not in block["coinbase_tx"]
+ else:
+ assert_equal(block["coinbase_tx"]["witness"], witness_stack[0])
+
def assert_hexblock_hashes(verbosity):
block = node.getblock(blockhash, verbosity)
assert_equal(blockhash, hash256(bytes.fromhex(block[:160]))[::-1].hex())
@@ -692,6 +712,9 @@ class BlockchainTest(BitcoinTestFramework):
assert_fee_not_in_block(blockhash, 1)
assert_fee_not_in_block(blockhash, True)
+ self.log.info("Test getblock coinbase metadata fields")
+ assert_coinbase_metadata(blockhash, 1)
+
self.log.info('Test that getblock with verbosity 2 and 3 includes expected fee')
assert_fee_in_block(blockhash, 2)
assert_fee_in_block(blockhash, 3)
Why this scored 18/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.