Skip to content

Commit 986003a

Browse files
committed
Merge bitcoin/bitcoin#22918: rpc: Add level 3 verbosity to getblock RPC call (#21245 modified)
5c34507 core_write: Rename calculate_fee to have_undo for clarity (fyquah) 8edf620 release-notes: Add release note about getblock verbosity level 3. (fyquah) 459104b rest: Add test for prevout fields in getblock (fyquah) 4330af6 rpc: Add test for level 3 verbosity getblock rpc call. (fyquah) 51dbc16 rpc: Add level 3 verbosity to getblock RPC call. (fyquah) 3cc9534 rpc: Replace boolean argument for tx details with enum class. (fyquah) Pull request description: Author of #21245 expressed [time issues](bitcoin/bitcoin#21245 (comment)) in the original PR. Given that #21245 has received a lot of review*, I have decided to open this new pull request with [modifications required to get ACK from luke-jr ](bitcoin/bitcoin#21245 (comment)) and a few nits of mine. ### Original PR description > Display the prevout in transaction inputs when calling getblock level 3 verbosity. This PR affects the existing `/rest/block` API by adding a `prevout` fields to tx inputs. This is mentioned in the change to the release notes. > > I added some functional tests that > > * checks that the RPC call still works when TxUndo can't be found > > * Doesn't display the "value" or "scriptPubKey" of the previous output when at a lower verbosity level > > > This "completes" the issue #18771 ### Possible improvements * kiminuo/bitcoin@b0bf4f2 - I can include even this commit to this PR if deemed useful or I can leave it for a follow-up PR. See bitcoin/bitcoin#21245 (comment) for more context. ### Examples Examples of the `getblock` output with various verbose levels. Note that `000000000000001f682b188971cc1a121546be4e9d5baf22934fdc7f538288d5` contains only 2 transactions. #### Verbose level 0 ```bash ./bitcoin-cli -testnet getblock 000000000000001f682b188971cc1a121546be4e9d5baf22934fdc7f538288d5 0 ``` ##### Verbose level 1 ```bash ./bitcoin-cli -testnet getblock 000000000000001f682b188971cc1a121546be4e9d5baf22934fdc7f538288d5 1 ``` ##### Verbose level 2 ```bash ./bitcoin-cli -testnet getblock 000000000000001f682b188971cc1a121546be4e9d5baf22934fdc7f538288d5 2 ``` ##### Verbose level 3 ```bash ./bitcoin-cli -testnet getblock 000000000000001f682b188971cc1a121546be4e9d5baf22934fdc7f538288d5 3 ``` #### REST ```bash curl -H "content-type:text/plain;" http://127.0.0.1:18332/rest/block/000000000000001f682b188971cc1a121546be4e9d5baf22934fdc7f538288d5.json ``` <sub>* ... and my everyday obsessive checking of my email inbox whether the PR moves forward.</sub> Edit laanwj: Removed at symbol from message, and large example output to prevent it from all ending up in the commit message. ACKs for top commit: 0xB10C: ACK 5c34507 meshcollider: utACK 5c34507 theStack: ACK 5c34507 👘 promag: Concept ACK 5c34507 Tree-SHA512: bbff120d8fd76e617b723b102b0c606e0d8eb27f21c631d5f4cdab0892137c4bc7c65b1df144993405f942c91be47a26e80480102af55bff22621c19f518aea3
2 parents 2d25161 + 5c34507 commit 986003a

File tree

9 files changed

+148
-45
lines changed

9 files changed

+148
-45
lines changed

doc/release-notes.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,14 @@ Updated RPCs
8282
`gettransaction verbose=true` and REST endpoints `/rest/tx`, `/rest/getutxos`,
8383
`/rest/block` no longer return the `addresses` and `reqSigs` fields, which
8484
were previously deprecated in 22.0. (#22650)
85+
- The `getblock` RPC command now supports verbose level 3 containing transaction inputs
86+
`prevout` information. The existing `/rest/block/` REST endpoint is modified to contain
87+
this information too. Every `vin` field will contain an additional `prevout` subfield
88+
describing the spent output. `prevout` contains the following keys:
89+
- `generated` - true if the spent coins was a coinbase.
90+
- `height`
91+
- `value`
92+
- `scriptPubKey`
8593

8694
- `listunspent` now includes `ancestorcount`, `ancestorsize`, and
8795
`ancestorfees` for each transaction output that is still in the mempool.

src/bench/rpc_blockchain.cpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ static void BlockToJsonVerbose(benchmark::Bench& bench)
4040
{
4141
TestBlockAndIndex data;
4242
bench.run([&] {
43-
auto univalue = blockToJSON(data.block, &data.blockindex, &data.blockindex, /*verbose*/ true);
43+
auto univalue = blockToJSON(data.block, &data.blockindex, &data.blockindex, TxVerbosity::SHOW_DETAILS_AND_PREVOUT);
4444
ankerl::nanobench::doNotOptimizeAway(univalue);
4545
});
4646
}
@@ -50,7 +50,7 @@ BENCHMARK(BlockToJsonVerbose);
5050
static void BlockToJsonVerboseWrite(benchmark::Bench& bench)
5151
{
5252
TestBlockAndIndex data;
53-
auto univalue = blockToJSON(data.block, &data.blockindex, &data.blockindex, /*verbose*/ true);
53+
auto univalue = blockToJSON(data.block, &data.blockindex, &data.blockindex, TxVerbosity::SHOW_DETAILS_AND_PREVOUT);
5454
bench.run([&] {
5555
auto str = univalue.write();
5656
ankerl::nanobench::doNotOptimizeAway(str);

src/core_io.h

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,15 @@ class uint256;
2020
class UniValue;
2121
class CTxUndo;
2222

23+
/**
24+
* Verbose level for block's transaction
25+
*/
26+
enum class TxVerbosity {
27+
SHOW_TXID, //!< Only TXID for each block's transaction
28+
SHOW_DETAILS, //!< Include TXID, inputs, outputs, and other common block's transaction information
29+
SHOW_DETAILS_AND_PREVOUT //!< The same as previous option with information about prevouts if available
30+
};
31+
2332
// core_read.cpp
2433
CScript ParseScript(const std::string& s);
2534
std::string ScriptToAsmStr(const CScript& script, const bool fAttemptSighashDecode = false);
@@ -46,6 +55,6 @@ std::string EncodeHexTx(const CTransaction& tx, const int serializeFlags = 0);
4655
std::string SighashToStr(unsigned char sighash_type);
4756
void ScriptPubKeyToUniv(const CScript& scriptPubKey, UniValue& out, bool include_hex, bool include_address = true);
4857
void ScriptToUniv(const CScript& script, UniValue& out);
49-
void TxToUniv(const CTransaction& tx, const uint256& hashBlock, UniValue& entry, bool include_hex = true, int serialize_flags = 0, const CTxUndo* txundo = nullptr);
58+
void TxToUniv(const CTransaction& tx, const uint256& hashBlock, UniValue& entry, bool include_hex = true, int serialize_flags = 0, const CTxUndo* txundo = nullptr, TxVerbosity verbosity = TxVerbosity::SHOW_DETAILS);
5059

5160
#endif // BITCOIN_CORE_IO_H

src/core_write.cpp

Lines changed: 25 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -163,7 +163,7 @@ void ScriptPubKeyToUniv(const CScript& scriptPubKey, UniValue& out, bool include
163163
out.pushKV("type", GetTxnOutputType(type));
164164
}
165165

166-
void TxToUniv(const CTransaction& tx, const uint256& hashBlock, UniValue& entry, bool include_hex, int serialize_flags, const CTxUndo* txundo)
166+
void TxToUniv(const CTransaction& tx, const uint256& hashBlock, UniValue& entry, bool include_hex, int serialize_flags, const CTxUndo* txundo, TxVerbosity verbosity)
167167
{
168168
entry.pushKV("txid", tx.GetHash().GetHex());
169169
entry.pushKV("hash", tx.GetWitnessHash().GetHex());
@@ -179,7 +179,7 @@ void TxToUniv(const CTransaction& tx, const uint256& hashBlock, UniValue& entry,
179179

180180
// If available, use Undo data to calculate the fee. Note that txundo == nullptr
181181
// for coinbase transactions and for transactions where undo data is unavailable.
182-
const bool calculate_fee = txundo != nullptr;
182+
const bool have_undo = txundo != nullptr;
183183
CAmount amt_total_in = 0;
184184
CAmount amt_total_out = 0;
185185

@@ -203,9 +203,28 @@ void TxToUniv(const CTransaction& tx, const uint256& hashBlock, UniValue& entry,
203203
}
204204
in.pushKV("txinwitness", txinwitness);
205205
}
206-
if (calculate_fee) {
207-
const CTxOut& prev_txout = txundo->vprevout[i].out;
206+
if (have_undo) {
207+
const Coin& prev_coin = txundo->vprevout[i];
208+
const CTxOut& prev_txout = prev_coin.out;
209+
208210
amt_total_in += prev_txout.nValue;
211+
switch (verbosity) {
212+
case TxVerbosity::SHOW_TXID:
213+
case TxVerbosity::SHOW_DETAILS:
214+
break;
215+
216+
case TxVerbosity::SHOW_DETAILS_AND_PREVOUT:
217+
UniValue o_script_pub_key(UniValue::VOBJ);
218+
ScriptPubKeyToUniv(prev_txout.scriptPubKey, o_script_pub_key, /* includeHex */ true);
219+
220+
UniValue p(UniValue::VOBJ);
221+
p.pushKV("generated", bool(prev_coin.fCoinBase));
222+
p.pushKV("height", uint64_t(prev_coin.nHeight));
223+
p.pushKV("value", ValueFromAmount(prev_txout.nValue));
224+
p.pushKV("scriptPubKey", o_script_pub_key);
225+
in.pushKV("prevout", p);
226+
break;
227+
}
209228
}
210229
in.pushKV("sequence", (int64_t)txin.nSequence);
211230
vin.push_back(in);
@@ -226,13 +245,13 @@ void TxToUniv(const CTransaction& tx, const uint256& hashBlock, UniValue& entry,
226245
out.pushKV("scriptPubKey", o);
227246
vout.push_back(out);
228247

229-
if (calculate_fee) {
248+
if (have_undo) {
230249
amt_total_out += txout.nValue;
231250
}
232251
}
233252
entry.pushKV("vout", vout);
234253

235-
if (calculate_fee) {
254+
if (have_undo) {
236255
const CAmount fee = amt_total_in - amt_total_out;
237256
CHECK_NONFATAL(MoneyRange(fee));
238257
entry.pushKV("fee", ValueFromAmount(fee));

src/rest.cpp

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -262,7 +262,7 @@ static bool rest_headers(const std::any& context,
262262
static bool rest_block(const std::any& context,
263263
HTTPRequest* req,
264264
const std::string& strURIPart,
265-
bool showTxDetails)
265+
TxVerbosity tx_verbosity)
266266
{
267267
if (!CheckWarmup(req))
268268
return false;
@@ -314,7 +314,7 @@ static bool rest_block(const std::any& context,
314314
}
315315

316316
case RetFormat::JSON: {
317-
UniValue objBlock = blockToJSON(block, tip, pblockindex, showTxDetails);
317+
UniValue objBlock = blockToJSON(block, tip, pblockindex, tx_verbosity);
318318
std::string strJSON = objBlock.write() + "\n";
319319
req->WriteHeader("Content-Type", "application/json");
320320
req->WriteReply(HTTP_OK, strJSON);
@@ -329,12 +329,12 @@ static bool rest_block(const std::any& context,
329329

330330
static bool rest_block_extended(const std::any& context, HTTPRequest* req, const std::string& strURIPart)
331331
{
332-
return rest_block(context, req, strURIPart, true);
332+
return rest_block(context, req, strURIPart, TxVerbosity::SHOW_DETAILS_AND_PREVOUT);
333333
}
334334

335335
static bool rest_block_notxdetails(const std::any& context, HTTPRequest* req, const std::string& strURIPart)
336336
{
337-
return rest_block(context, req, strURIPart, false);
337+
return rest_block(context, req, strURIPart, TxVerbosity::SHOW_TXID);
338338
}
339339

340340
// A bit of a hack - dependency on a function defined in rpc/blockchain.cpp

src/rpc/blockchain.cpp

Lines changed: 35 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -200,30 +200,37 @@ UniValue blockheaderToJSON(const CBlockIndex* tip, const CBlockIndex* blockindex
200200
return result;
201201
}
202202

203-
UniValue blockToJSON(const CBlock& block, const CBlockIndex* tip, const CBlockIndex* blockindex, bool txDetails)
203+
UniValue blockToJSON(const CBlock& block, const CBlockIndex* tip, const CBlockIndex* blockindex, TxVerbosity verbosity)
204204
{
205205
UniValue result = blockheaderToJSON(tip, blockindex);
206206

207207
result.pushKV("strippedsize", (int)::GetSerializeSize(block, PROTOCOL_VERSION | SERIALIZE_TRANSACTION_NO_WITNESS));
208208
result.pushKV("size", (int)::GetSerializeSize(block, PROTOCOL_VERSION));
209209
result.pushKV("weight", (int)::GetBlockWeight(block));
210210
UniValue txs(UniValue::VARR);
211-
if (txDetails) {
212-
CBlockUndo blockUndo;
213-
const bool have_undo = !IsBlockPruned(blockindex) && UndoReadFromDisk(blockUndo, blockindex);
214-
for (size_t i = 0; i < block.vtx.size(); ++i) {
215-
const CTransactionRef& tx = block.vtx.at(i);
216-
// coinbase transaction (i == 0) doesn't have undo data
217-
const CTxUndo* txundo = (have_undo && i) ? &blockUndo.vtxundo.at(i - 1) : nullptr;
218-
UniValue objTx(UniValue::VOBJ);
219-
TxToUniv(*tx, uint256(), objTx, true, RPCSerializationFlags(), txundo);
220-
txs.push_back(objTx);
221-
}
222-
} else {
223-
for (const CTransactionRef& tx : block.vtx) {
224-
txs.push_back(tx->GetHash().GetHex());
225-
}
211+
212+
switch (verbosity) {
213+
case TxVerbosity::SHOW_TXID:
214+
for (const CTransactionRef& tx : block.vtx) {
215+
txs.push_back(tx->GetHash().GetHex());
216+
}
217+
break;
218+
219+
case TxVerbosity::SHOW_DETAILS:
220+
case TxVerbosity::SHOW_DETAILS_AND_PREVOUT:
221+
CBlockUndo blockUndo;
222+
const bool have_undo = !IsBlockPruned(blockindex) && UndoReadFromDisk(blockUndo, blockindex);
223+
224+
for (size_t i = 0; i < block.vtx.size(); ++i) {
225+
const CTransactionRef& tx = block.vtx.at(i);
226+
// coinbase transaction (i.e. i == 0) doesn't have undo data
227+
const CTxUndo* txundo = (have_undo && i > 0) ? &blockUndo.vtxundo.at(i - 1) : nullptr;
228+
UniValue objTx(UniValue::VOBJ);
229+
TxToUniv(*tx, uint256(), objTx, true, RPCSerializationFlags(), txundo, verbosity);
230+
txs.push_back(objTx);
231+
}
226232
}
233+
227234
result.pushKV("tx", txs);
228235

229236
return result;
@@ -931,7 +938,8 @@ static RPCHelpMan getblock()
931938
return RPCHelpMan{"getblock",
932939
"\nIf verbosity is 0, returns a string that is serialized, hex-encoded data for block 'hash'.\n"
933940
"If verbosity is 1, returns an Object with information about block <hash>.\n"
934-
"If verbosity is 2, returns an Object with information about block <hash> and information about each transaction. \n",
941+
"If verbosity is 2, returns an Object with information about block <hash> and information about each transaction.\n"
942+
"If verbosity is 3, returns an Object with information about block <hash> and information about each transaction, including prevout information for inputs (only for unpruned blocks in the current best chain).\n",
935943
{
936944
{"blockhash", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "The block hash"},
937945
{"verbosity|verbose", RPCArg::Type::NUM, RPCArg::Default{1}, "0 for hex-encoded data, 1 for a json object, and 2 for json object with transaction data"},
@@ -1018,7 +1026,16 @@ static RPCHelpMan getblock()
10181026
return strHex;
10191027
}
10201028

1021-
return blockToJSON(block, tip, pblockindex, verbosity >= 2);
1029+
TxVerbosity tx_verbosity;
1030+
if (verbosity == 1) {
1031+
tx_verbosity = TxVerbosity::SHOW_TXID;
1032+
} else if (verbosity == 2) {
1033+
tx_verbosity = TxVerbosity::SHOW_DETAILS;
1034+
} else {
1035+
tx_verbosity = TxVerbosity::SHOW_DETAILS_AND_PREVOUT;
1036+
}
1037+
1038+
return blockToJSON(block, tip, pblockindex, tx_verbosity);
10221039
},
10231040
};
10241041
}

src/rpc/blockchain.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
#define BITCOIN_RPC_BLOCKCHAIN_H
77

88
#include <consensus/amount.h>
9+
#include <core_io.h>
910
#include <streams.h>
1011
#include <sync.h>
1112

@@ -38,7 +39,7 @@ double GetDifficulty(const CBlockIndex* blockindex);
3839
void RPCNotifyBlockChange(const CBlockIndex*);
3940

4041
/** Block description to JSON */
41-
UniValue blockToJSON(const CBlock& block, const CBlockIndex* tip, const CBlockIndex* blockindex, bool txDetails = false) LOCKS_EXCLUDED(cs_main);
42+
UniValue blockToJSON(const CBlock& block, const CBlockIndex* tip, const CBlockIndex* blockindex, TxVerbosity verbosity) LOCKS_EXCLUDED(cs_main);
4243

4344
/** Mempool information to JSON */
4445
UniValue MempoolInfoToJSON(const CTxMemPool& pool);

test/functional/interface_rest.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -318,6 +318,15 @@ def run_test(self):
318318
if 'coinbase' not in tx['vin'][0]}
319319
assert_equal(non_coinbase_txs, set(txs))
320320

321+
# Verify that the non-coinbase tx has "prevout" key set
322+
for tx_obj in json_obj["tx"]:
323+
for vin in tx_obj["vin"]:
324+
if "coinbase" not in vin:
325+
assert "prevout" in vin
326+
assert_equal(vin["prevout"]["generated"], False)
327+
else:
328+
assert "prevout" not in vin
329+
321330
# Check the same but without tx details
322331
json_obj = self.test_rest_request(f"/block/notxdetails/{newblockhash[0]}")
323332
for tx in txs:

test/functional/rpc_blockchain.py

Lines changed: 53 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -434,17 +434,55 @@ def _test_getblock(self):
434434
miniwallet.send_self_transfer(fee_rate=fee_per_kb, from_node=node)
435435
blockhash = self.generate(node, 1)[0]
436436

437-
self.log.info("Test getblock with verbosity 1 doesn't include fee")
438-
block = node.getblock(blockhash, 1)
439-
assert 'fee' not in block['tx'][1]
440-
441-
self.log.info('Test getblock with verbosity 2 includes expected fee')
442-
block = node.getblock(blockhash, 2)
443-
tx = block['tx'][1]
444-
assert 'fee' in tx
445-
assert_equal(tx['fee'], tx['vsize'] * fee_per_byte)
446-
447-
self.log.info("Test getblock with verbosity 2 still works with pruned Undo data")
437+
def assert_fee_not_in_block(verbosity):
438+
block = node.getblock(blockhash, verbosity)
439+
assert 'fee' not in block['tx'][1]
440+
441+
def assert_fee_in_block(verbosity):
442+
block = node.getblock(blockhash, verbosity)
443+
tx = block['tx'][1]
444+
assert 'fee' in tx
445+
assert_equal(tx['fee'], tx['vsize'] * fee_per_byte)
446+
447+
def assert_vin_contains_prevout(verbosity):
448+
block = node.getblock(blockhash, verbosity)
449+
tx = block["tx"][1]
450+
total_vin = Decimal("0.00000000")
451+
total_vout = Decimal("0.00000000")
452+
for vin in tx["vin"]:
453+
assert "prevout" in vin
454+
assert_equal(set(vin["prevout"].keys()), set(("value", "height", "generated", "scriptPubKey")))
455+
assert_equal(vin["prevout"]["generated"], True)
456+
total_vin += vin["prevout"]["value"]
457+
for vout in tx["vout"]:
458+
total_vout += vout["value"]
459+
assert_equal(total_vin, total_vout + tx["fee"])
460+
461+
def assert_vin_does_not_contain_prevout(verbosity):
462+
block = node.getblock(blockhash, verbosity)
463+
tx = block["tx"][1]
464+
if isinstance(tx, str):
465+
# In verbosity level 1, only the transaction hashes are written
466+
pass
467+
else:
468+
for vin in tx["vin"]:
469+
assert "prevout" not in vin
470+
471+
self.log.info("Test that getblock with verbosity 1 doesn't include fee")
472+
assert_fee_not_in_block(1)
473+
474+
self.log.info('Test that getblock with verbosity 2 and 3 includes expected fee')
475+
assert_fee_in_block(2)
476+
assert_fee_in_block(3)
477+
478+
self.log.info("Test that getblock with verbosity 1 and 2 does not include prevout")
479+
assert_vin_does_not_contain_prevout(1)
480+
assert_vin_does_not_contain_prevout(2)
481+
482+
self.log.info("Test that getblock with verbosity 3 includes prevout")
483+
assert_vin_contains_prevout(3)
484+
485+
self.log.info("Test that getblock with verbosity 2 and 3 still works with pruned Undo data")
448486
datadir = get_datadir_path(self.options.tmpdir, 0)
449487

450488
self.log.info("Test getblock with invalid verbosity type returns proper error message")
@@ -458,8 +496,10 @@ def move_block_file(old, new):
458496
# Move instead of deleting so we can restore chain state afterwards
459497
move_block_file('rev00000.dat', 'rev_wrong')
460498

461-
block = node.getblock(blockhash, 2)
462-
assert 'fee' not in block['tx'][1]
499+
assert_fee_not_in_block(2)
500+
assert_fee_not_in_block(3)
501+
assert_vin_does_not_contain_prevout(2)
502+
assert_vin_does_not_contain_prevout(3)
463503

464504
# Restore chain state
465505
move_block_file('rev_wrong', 'rev00000.dat')

0 commit comments

Comments
 (0)