Skip to content

Commit cc7cbd7

Browse files
committed
Merge #13451: rpc: expose CBlockIndex::nTx in getblock(header)
86edf4a expose CBlockIndex::nTx in getblock(header) (Gregory Sanders) Pull request description: Recent publication of a weakness in Bitcoin's merkle tree construction demonstrates many SPV applications vulnerable to an expensive to pull off yet still plausible attack: https://bitslog.wordpress.com/2018/06/09/leaf-node-weakness-in-bitcoin-merkle-tree-design/ Including the coinbase in the txoutproof seems the most effective fix, however results in a significant efficiency downgrade. Transactors will not even know a priori what the size of their proof will be within a couple orders of magnitude, unless they use the mid-state of SHA2 as detailed in the blog post. Some applications, like Elements blockchain platform that take SPV-style proofs have optional access to a bitcoind to verify these proofs of inclusion and check depth in the chain. Returning `CBlockIndex::nTx` would allow an extremely easy and compact way of checking the depth of the tree, with no additional overhead to the codebase, and works with pruned nodes. `getblockheader` is arguably not the place for it, but as mentioned before, is a natural workflow for us checking depth of a block in a possibly pruned node. We should also ensure that `verifytxoutproof` ends up validating this depth fact as well, but left this for another PR. Tree-SHA512: af4cf48e704c6088f8da06a477fda1aaa6f8770cee9b876c4465d1075966d6a95831a88817673fe5a0d6bbcdc1ffcbc1892e2be0d838c60fc6958d33eacdcc14
2 parents 4a7e64f + 86edf4a commit cc7cbd7

File tree

3 files changed

+12
-0
lines changed

3 files changed

+12
-0
lines changed

src/rpc/blockchain.cpp

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,7 @@ UniValue blockheaderToJSON(const CBlockIndex* blockindex)
9595
result.pushKV("bits", strprintf("%08x", blockindex->nBits));
9696
result.pushKV("difficulty", GetDifficulty(blockindex));
9797
result.pushKV("chainwork", blockindex->nChainWork.GetHex());
98+
result.pushKV("nTx", (uint64_t)blockindex->nTx);
9899

99100
if (blockindex->pprev)
100101
result.pushKV("previousblockhash", blockindex->pprev->GetBlockHash().GetHex());
@@ -140,6 +141,7 @@ UniValue blockToJSON(const CBlock& block, const CBlockIndex* blockindex, bool tx
140141
result.pushKV("bits", strprintf("%08x", block.nBits));
141142
result.pushKV("difficulty", GetDifficulty(blockindex));
142143
result.pushKV("chainwork", blockindex->nChainWork.GetHex());
144+
result.pushKV("nTx", (uint64_t)blockindex->nTx);
143145

144146
if (blockindex->pprev)
145147
result.pushKV("previousblockhash", blockindex->pprev->GetBlockHash().GetHex());
@@ -694,6 +696,7 @@ static UniValue getblockheader(const JSONRPCRequest& request)
694696
" \"bits\" : \"1d00ffff\", (string) The bits\n"
695697
" \"difficulty\" : x.xxx, (numeric) The difficulty\n"
696698
" \"chainwork\" : \"0000...1f3\" (string) Expected number of hashes required to produce the current chain (in hex)\n"
699+
" \"nTx\" : n, (numeric) The number of transactions in the block.\n"
697700
" \"previousblockhash\" : \"hash\", (string) The hash of the previous block\n"
698701
" \"nextblockhash\" : \"hash\", (string) The hash of the next block\n"
699702
"}\n"
@@ -782,6 +785,7 @@ static UniValue getblock(const JSONRPCRequest& request)
782785
" \"bits\" : \"1d00ffff\", (string) The bits\n"
783786
" \"difficulty\" : x.xxx, (numeric) The difficulty\n"
784787
" \"chainwork\" : \"xxxx\", (string) Expected number of hashes required to produce the chain up to this block (in hex)\n"
788+
" \"nTx\" : n, (numeric) The number of transactions in the block.\n"
785789
" \"previousblockhash\" : \"hash\", (string) The hash of the previous block\n"
786790
" \"nextblockhash\" : \"hash\" (string) The hash of the next block\n"
787791
"}\n"

test/functional/feature_pruning.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -260,10 +260,17 @@ def has_block(index):
260260
# should not prune because chain tip of node 3 (995) < PruneAfterHeight (1000)
261261
assert_raises_rpc_error(-1, "Blockchain is too short for pruning", node.pruneblockchain, height(500))
262262

263+
# Save block transaction count before pruning, assert value
264+
block1_details = node.getblock(node.getblockhash(1))
265+
assert_equal(block1_details["nTx"], len(block1_details["tx"]))
266+
263267
# mine 6 blocks so we are at height 1001 (i.e., above PruneAfterHeight)
264268
node.generate(6)
265269
assert_equal(node.getblockchaininfo()["blocks"], 1001)
266270

271+
# Pruned block should still know the number of transactions
272+
assert_equal(node.getblockheader(node.getblockhash(1))["nTx"], block1_details["nTx"])
273+
267274
# negative heights should raise an exception
268275
assert_raises_rpc_error(-8, "Negative", node.pruneblockchain, -10)
269276

test/functional/rpc_blockchain.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -217,6 +217,7 @@ def _test_getblockheader(self):
217217
assert_equal(header['confirmations'], 1)
218218
assert_equal(header['previousblockhash'], secondbesthash)
219219
assert_is_hex_string(header['chainwork'])
220+
assert_equal(header['nTx'], 1)
220221
assert_is_hash_string(header['hash'])
221222
assert_is_hash_string(header['previousblockhash'])
222223
assert_is_hash_string(header['merkleroot'])

0 commit comments

Comments
 (0)