Skip to content

Commit 173ab0c

Browse files
committed
Merge bitcoin/bitcoin#29720: rpc: Avoid getchaintxstats invalid results
2342b46 test: Add coverage for getchaintxstats in assumeutxo context (Fabian Jahr) faf2a67 rpc: Reorder getchaintxstats output (MarcoFalke) fa2dada rpc: Avoid getchaintxstats invalid results (MarcoFalke) Pull request description: The `getchaintxstats` RPC reply during AU background download may return non-zero, but invalid, values for `window_tx_count` and `txrate`. For example, `txcount` may be zero for a to-be-downloaded block, but may be non-zero for an ancestor block which is already downloaded. Thus, the values returned may be negative (and cause intermediate integer sanitizer violations). Also, `txcount` may be accurate for the snapshot base block, or a descendant of it. However it may be zero for an ancestor block that still needs to be downloaded. Thus, the values returned may be positive, but wrong. Fix all issues by skipping the returned value if either `txcount` is unset (equal to zero). Also, skip `txcount` in the returned value, if it is unset (equal to zero). Fixes bitcoin/bitcoin#29328 ACKs for top commit: fjahr: re-ACK 2342b46 achow101: ACK 2342b46 mzumsande: ACK 2342b46 Tree-SHA512: 931cecc40ee5dc0f96be728db7eb297155f8343076cd29c8b8c050c99fd1d568b80f54c9459a34ca7a9489c2474c729796d00eeb1934d6a9f7b4d6a53e3ec430
2 parents 3325a0a + 2342b46 commit 173ab0c

File tree

3 files changed

+38
-13
lines changed

3 files changed

+38
-13
lines changed

src/rpc/blockchain.cpp

Lines changed: 20 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1643,13 +1643,19 @@ static RPCHelpMan getchaintxstats()
16431643
RPCResult::Type::OBJ, "", "",
16441644
{
16451645
{RPCResult::Type::NUM_TIME, "time", "The timestamp for the final block in the window, expressed in " + UNIX_EPOCH_TIME},
1646-
{RPCResult::Type::NUM, "txcount", "The total number of transactions in the chain up to that point"},
1646+
{RPCResult::Type::NUM, "txcount", /*optional=*/true,
1647+
"The total number of transactions in the chain up to that point, if known. "
1648+
"It may be unknown when using assumeutxo."},
16471649
{RPCResult::Type::STR_HEX, "window_final_block_hash", "The hash of the final block in the window"},
16481650
{RPCResult::Type::NUM, "window_final_block_height", "The height of the final block in the window."},
16491651
{RPCResult::Type::NUM, "window_block_count", "Size of the window in number of blocks"},
1650-
{RPCResult::Type::NUM, "window_tx_count", /*optional=*/true, "The number of transactions in the window. Only returned if \"window_block_count\" is > 0"},
16511652
{RPCResult::Type::NUM, "window_interval", /*optional=*/true, "The elapsed time in the window in seconds. Only returned if \"window_block_count\" is > 0"},
1652-
{RPCResult::Type::NUM, "txrate", /*optional=*/true, "The average rate of transactions per second in the window. Only returned if \"window_interval\" is > 0"},
1653+
{RPCResult::Type::NUM, "window_tx_count", /*optional=*/true,
1654+
"The number of transactions in the window. "
1655+
"Only returned if \"window_block_count\" is > 0 and if txcount exists for the start and end of the window."},
1656+
{RPCResult::Type::NUM, "txrate", /*optional=*/true,
1657+
"The average rate of transactions per second in the window. "
1658+
"Only returned if \"window_interval\" is > 0 and if window_tx_count exists."},
16531659
}},
16541660
RPCExamples{
16551661
HelpExampleCli("getchaintxstats", "")
@@ -1690,19 +1696,25 @@ static RPCHelpMan getchaintxstats()
16901696

16911697
const CBlockIndex& past_block{*CHECK_NONFATAL(pindex->GetAncestor(pindex->nHeight - blockcount))};
16921698
const int64_t nTimeDiff{pindex->GetMedianTimePast() - past_block.GetMedianTimePast()};
1693-
const int nTxDiff = pindex->nChainTx - past_block.nChainTx;
1699+
const auto window_tx_count{
1700+
(pindex->nChainTx != 0 && past_block.nChainTx != 0) ? std::optional{pindex->nChainTx - past_block.nChainTx} : std::nullopt,
1701+
};
16941702

16951703
UniValue ret(UniValue::VOBJ);
16961704
ret.pushKV("time", (int64_t)pindex->nTime);
1697-
ret.pushKV("txcount", (int64_t)pindex->nChainTx);
1705+
if (pindex->nChainTx) {
1706+
ret.pushKV("txcount", pindex->nChainTx);
1707+
}
16981708
ret.pushKV("window_final_block_hash", pindex->GetBlockHash().GetHex());
16991709
ret.pushKV("window_final_block_height", pindex->nHeight);
17001710
ret.pushKV("window_block_count", blockcount);
17011711
if (blockcount > 0) {
1702-
ret.pushKV("window_tx_count", nTxDiff);
17031712
ret.pushKV("window_interval", nTimeDiff);
1704-
if (nTimeDiff > 0) {
1705-
ret.pushKV("txrate", ((double)nTxDiff) / nTimeDiff);
1713+
if (window_tx_count) {
1714+
ret.pushKV("window_tx_count", *window_tx_count);
1715+
if (nTimeDiff > 0) {
1716+
ret.pushKV("txrate", double(*window_tx_count) / nTimeDiff);
1717+
}
17061718
}
17071719
}
17081720

test/functional/feature_assumeutxo.py

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@
3333
from test_framework.messages import tx_from_hex
3434
from test_framework.test_framework import BitcoinTestFramework
3535
from test_framework.util import (
36+
assert_approx,
3637
assert_equal,
3738
assert_raises_rpc_error,
3839
)
@@ -315,21 +316,35 @@ def check_tx_counts(final: bool) -> None:
315316
the snapshot, and final values after the snapshot is validated."""
316317
for height, block in blocks.items():
317318
tx = n1.getblockheader(block.hash)["nTx"]
318-
chain_tx = n1.getchaintxstats(nblocks=1, blockhash=block.hash)["txcount"]
319+
stats = n1.getchaintxstats(nblocks=1, blockhash=block.hash)
320+
chain_tx = stats.get("txcount", None)
321+
window_tx_count = stats.get("window_tx_count", None)
322+
tx_rate = stats.get("txrate", None)
323+
window_interval = stats.get("window_interval")
319324

320325
# Intermediate nTx of the starting block should be set, but nTx of
321326
# later blocks should be 0 before they are downloaded.
327+
# The window_tx_count of one block is equal to the blocks tx count.
328+
# If the window tx count is unknown, the value is missing.
329+
# The tx_rate is calculated from window_tx_count and window_interval
330+
# when possible.
322331
if final or height == START_HEIGHT:
323332
assert_equal(tx, block.tx)
333+
assert_equal(window_tx_count, tx)
334+
if window_interval > 0:
335+
assert_approx(tx_rate, window_tx_count / window_interval, vspan=0.1)
336+
else:
337+
assert_equal(tx_rate, None)
324338
else:
325339
assert_equal(tx, 0)
340+
assert_equal(window_tx_count, None)
326341

327342
# Intermediate nChainTx of the starting block and snapshot block
328-
# should be set, but others should be 0 until they are downloaded.
343+
# should be set, but others should be None until they are downloaded.
329344
if final or height in (START_HEIGHT, SNAPSHOT_BASE_HEIGHT):
330345
assert_equal(chain_tx, block.chain_tx)
331346
else:
332-
assert_equal(chain_tx, 0)
347+
assert_equal(chain_tx, None)
333348

334349
check_tx_counts(final=False)
335350

test/sanitizer_suppressions/ubsan

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,6 @@ unsigned-integer-overflow:CCoinsViewCache::Uncache
5151
unsigned-integer-overflow:CompressAmount
5252
unsigned-integer-overflow:DecompressAmount
5353
unsigned-integer-overflow:crypto/
54-
unsigned-integer-overflow:getchaintxstats*
5554
unsigned-integer-overflow:MurmurHash3
5655
unsigned-integer-overflow:CBlockPolicyEstimator::processBlockTx
5756
unsigned-integer-overflow:TxConfirmStats::EstimateMedianVal
@@ -63,7 +62,6 @@ implicit-integer-sign-change:CBlockPolicyEstimator::processBlockTx
6362
implicit-integer-sign-change:SetStdinEcho
6463
implicit-integer-sign-change:compressor.h
6564
implicit-integer-sign-change:crypto/
66-
implicit-integer-sign-change:getchaintxstats*
6765
implicit-integer-sign-change:TxConfirmStats::removeTx
6866
implicit-integer-sign-change:prevector.h
6967
implicit-integer-sign-change:verify_flags

0 commit comments

Comments
 (0)