Skip to content

Commit 92ec81e

Browse files
will-2012claude
authored andcommitted
fix: make the received block metrics correct (#357)
* fix: make receive block metrics correct * ci: pin udeps job to nightly-2026-05-11 cargo-udeps 0.1.61 segfaults on nightly >= 2026-05-19 due to an incompatibility with rustc internals. Pin to the last known-good nightly to unblock CI. A follow-up should migrate to cargo-machete to remove the nightly fragility entirely. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com> (cherry picked from commit 32a07ee)
1 parent f4226f6 commit 92ec81e

3 files changed

Lines changed: 52 additions & 17 deletions

File tree

.github/workflows/ci.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -49,13 +49,13 @@ jobs:
4949
run: sudo apt-get update && sudo apt-get install -y liburing-dev pkg-config libclang-dev
5050
- uses: actions-rs/toolchain@v1
5151
with:
52-
toolchain: nightly
52+
toolchain: nightly-2026-05-11
5353
components: rustfmt
5454
override: true
5555
- uses: Swatinem/rust-cache@v2
5656
- uses: taiki-e/install-action@cargo-udeps
5757
- name: Run cargo udeps
58-
run: cargo +nightly udeps --workspace --lib --examples --tests --benches --all-features --locked
58+
run: cargo +nightly-2026-05-11 udeps --workspace --lib --examples --tests --benches --all-features --locked
5959

6060
clippy:
6161
name: clippy

src/consensus/parlia/block_stats.rs

Lines changed: 41 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,13 @@
33
//! Mirrors geth's `BlockStats` / `reportRecentBlocksLoop` functionality by tracking
44
//! block timestamps and event timestamps to compute chain delay metrics.
55
6+
use alloy_consensus::Header;
67
use alloy_primitives::B256;
78
use lru::LruCache;
89
use once_cell::sync::Lazy;
910
use std::{num::NonZero, sync::RwLock};
1011

12+
use crate::consensus::parlia::util::calculate_millisecond_timestamp;
1113
use crate::metrics::BscChainDelayMetrics;
1214

1315
/// Size of the block stats LRU cache.
@@ -18,7 +20,9 @@ const DEFAULT_MAJORITY_THRESHOLD: usize = 14;
1820

1921
/// Per-block tracking data for chain delay metrics.
2022
struct BlockStat {
21-
/// Block timestamp in milliseconds (header.timestamp * 1000).
23+
/// Block timestamp in milliseconds, from `calculate_millisecond_timestamp(header)`
24+
/// (combines `header.timestamp` seconds with the Lorentz-era ms portion stored in
25+
/// `header.mix_hash`).
2226
block_timestamp_ms: i64,
2327
/// Whether the first vote delay has been reported.
2428
first_vote_reported: bool,
@@ -38,23 +42,49 @@ fn now_ms() -> i64 {
3842
.as_millis() as i64
3943
}
4044

41-
/// Register a block's timestamp when it is first received from the network.
42-
/// Also records the `chain.delay.block_recv` metric (delay from block creation to reception).
43-
pub fn on_block_received(block_hash: B256, block_timestamp_secs: u64) {
44-
let block_ts_ms = block_timestamp_secs as i64 * 1000;
45+
/// Cache a block's millisecond-precision timestamp so subsequent `on_vote_received` calls can
46+
/// compute vote-delay metrics. Does **not** touch the `chain.delay.block_recv` histogram.
47+
///
48+
/// On Lorentz and later forks, the block timestamp has millisecond precision (split between
49+
/// `header.timestamp` (seconds) and `header.mix_hash` (ms part)); we use
50+
/// `calculate_millisecond_timestamp` so delays are not biased by the 0-999 ms portion.
51+
fn cache_block_timestamp(block_hash: B256, header: &Header) -> i64 {
52+
let block_ts_ms = calculate_millisecond_timestamp(header) as i64;
53+
let mut cache = BLOCK_STATS.write().expect("block stats poisoned");
54+
cache.get_or_insert(block_hash, || BlockStat {
55+
block_timestamp_ms: block_ts_ms,
56+
first_vote_reported: false,
57+
majority_vote_reported: false,
58+
});
59+
block_ts_ms
60+
}
61+
62+
/// Register a block's timestamp when it is first received from the network, and record the
63+
/// `chain.delay.block_recv` metric (delay from block creation to first network reception).
64+
///
65+
/// This is the network-receive path. For locally mined blocks call [`register_self_mined_block`]
66+
/// instead — they would otherwise pollute `block_recv` with samples that actually measure local
67+
/// mining/finalize latency rather than true network propagation delay (mirrors geth-bsc, where
68+
/// `RecvNewBlockTime` is only set in `handleBlockBroadcast`).
69+
pub fn on_block_received(block_hash: B256, header: &Header) {
70+
let block_ts_ms = cache_block_timestamp(block_hash, header);
4571
let recv_time = now_ms();
4672

4773
let delay_ms = recv_time - block_ts_ms;
4874
if delay_ms >= 0 {
4975
CHAIN_DELAY_METRICS.block_recv.record(delay_ms as f64);
5076
}
77+
}
5178

52-
let mut cache = BLOCK_STATS.write().expect("block stats poisoned");
53-
cache.get_or_insert(block_hash, || BlockStat {
54-
block_timestamp_ms: block_ts_ms,
55-
first_vote_reported: false,
56-
majority_vote_reported: false,
57-
});
79+
/// Register a self-mined block's timestamp so subsequent `on_vote_received` calls work, **without**
80+
/// recording `chain.delay.block_recv`.
81+
///
82+
/// Mirrors geth-bsc's split between `SendBlockTime` (miner path) and `RecvNewBlockTime`
83+
/// (network path): we want votes for our own block to still count toward `vote_first` /
84+
/// `vote_majority`, but the block-recv histogram must stay clean of self-mined samples so it
85+
/// can be used to diagnose cross-region network propagation delays.
86+
pub fn register_self_mined_block(block_hash: B256, header: &Header) {
87+
cache_block_timestamp(block_hash, header);
5888
}
5989

6090
/// Called when a vote is added for a block. Records first-vote and majority-vote delay

src/node/network/block_import/service.rs

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -369,10 +369,15 @@ where
369369
// Clone header for FCU update
370370
let header_for_fcu = block.header.clone();
371371

372-
// Register block stats for chain delay vote metrics (mined blocks also receive votes)
373-
crate::consensus::parlia::block_stats::on_block_received(
372+
// Register block stats so vote-delay metrics can still be computed when votes arrive for
373+
// this self-mined block. We deliberately do NOT call `on_block_received` here — that
374+
// records `chain.delay.block_recv`, which is meant to measure pure network propagation
375+
// delay; for a block we just produced locally the sample would actually reflect local
376+
// mining/finalize latency and would pollute cross-region diagnosis. Mirrors geth-bsc,
377+
// which only sets `RecvNewBlockTime` inside `handleBlockBroadcast` (the network path).
378+
crate::consensus::parlia::block_stats::register_self_mined_block(
374379
block_hash,
375-
block.header.timestamp,
380+
&block.header,
376381
);
377382

378383
// send to EVN peers first
@@ -541,7 +546,7 @@ where
541546
// Record chain delay metrics: time from block creation to first network reception
542547
crate::consensus::parlia::block_stats::on_block_received(
543548
block.hash,
544-
block.block.0.block.header.timestamp,
549+
&block.block.0.block.header,
545550
);
546551

547552
// send to EVN peers first

0 commit comments

Comments
 (0)