@@ -2,7 +2,7 @@ use std::collections::{HashMap, HashSet, VecDeque};
22use std:: time:: { Duration , Instant , SystemTime } ;
33
44use ethlambda_crypto:: signature:: { ValidatorPublicKey , ValidatorSignature } ;
5- use ethlambda_network_api:: { BlockChainToP2PRef , InitP2P } ;
5+ use ethlambda_network_api:: { BlockChainToP2PRef , BlockSource , InitP2P } ;
66use ethlambda_state_transition:: is_proposer;
77use ethlambda_storage:: { ALL_TABLES , Store } ;
88use ethlambda_types:: {
@@ -109,6 +109,20 @@ impl SlotInterval {
109109 _ => unreachable ! ( "slots only have 5 intervals" ) ,
110110 }
111111 }
112+
113+ /// Milliseconds from genesis to the start of this interval in `slot`.
114+ ///
115+ /// Inverse of [`Self::from_ms_since_genesis`].
116+ pub ( crate ) fn to_ms_since_genesis ( self , slot : u64 ) -> u64 {
117+ let interval = match self {
118+ Self :: BlockPublication => 0 ,
119+ Self :: AttestationProduction => 1 ,
120+ Self :: Aggregation => 2 ,
121+ Self :: SafeTargetUpdate => 3 ,
122+ Self :: EndOfSlot => 4 ,
123+ } ;
124+ slot * MILLISECONDS_PER_SLOT + interval * MILLISECONDS_PER_INTERVAL
125+ }
112126}
113127
114128/// Milliseconds until the next interval boundary, measured relative to genesis.
@@ -1367,16 +1381,37 @@ impl Handler<InitP2P> for BlockChainServer {
13671381
13681382impl Handler < NewBlock > for BlockChainServer {
13691383 async fn handle ( & mut self , msg : NewBlock , _ctx : & Context < Self > ) {
1370- self . events . emit ( ChainEvent :: BlockGossip {
1371- slot : msg. block . message . slot ,
1372- block : msg. block . message . hash_tree_root ( ) ,
1373- } ) ;
1384+ let arrival_ms = unix_now_ms ( ) ;
1385+ // Gate both the event and the arrival metric on BlockSource::Gossip for
1386+ // two reasons: `ChainEvent::BlockGossip` is documented (events.rs) as "a
1387+ // block seen on gossip, before import", yet without this gate it also
1388+ // fired for req/resp sync blocks; and sync backfill delivers blocks many
1389+ // slots after they were due, which would swamp the arrival histogram
1390+ // with stale deltas that reflect catch-up speed, not gossip timeliness.
1391+ // `self.on_block(msg.block)` still runs for every source below: it is
1392+ // the import path and must not be gated.
1393+ if msg. source == BlockSource :: Gossip {
1394+ let slot = msg. block . message . slot ;
1395+ self . events . emit ( ChainEvent :: BlockGossip {
1396+ slot,
1397+ block : msg. block . message . hash_tree_root ( ) ,
1398+ } ) ;
1399+ let genesis_ms = self . store . config ( ) . expect ( "config exists" ) . genesis_time * 1000 ;
1400+ metrics:: observe_gossip_block_arrival ( arrival_ms, genesis_ms, slot) ;
1401+ }
13741402 self . on_block ( msg. block ) ;
13751403 }
13761404}
13771405
13781406impl Handler < NewAttestation > for BlockChainServer {
13791407 async fn handle ( & mut self , msg : NewAttestation , ctx : & Context < Self > ) {
1408+ let arrival_ms = unix_now_ms ( ) ;
1409+ let genesis_ms = self . store . config ( ) . expect ( "config exists" ) . genesis_time * 1000 ;
1410+ metrics:: observe_gossip_attestation_arrival (
1411+ arrival_ms,
1412+ genesis_ms,
1413+ msg. attestation . data . slot ,
1414+ ) ;
13801415 self . on_gossip_attestation ( & msg. attestation ) ;
13811416 // Early aggregation only advances the current slot's group counts, so a
13821417 // late- or future-slot attestation can never cross the threshold; skip
@@ -1390,6 +1425,9 @@ impl Handler<NewAttestation> for BlockChainServer {
13901425
13911426impl Handler < NewAggregatedAttestation > for BlockChainServer {
13921427 async fn handle ( & mut self , msg : NewAggregatedAttestation , _ctx : & Context < Self > ) {
1428+ let arrival_ms = unix_now_ms ( ) ;
1429+ let genesis_ms = self . store . config ( ) . expect ( "config exists" ) . genesis_time * 1000 ;
1430+ metrics:: observe_gossip_aggregation_arrival ( arrival_ms, genesis_ms) ;
13931431 self . on_gossip_aggregated_attestation ( msg. attestation ) ;
13941432 }
13951433}
@@ -1400,6 +1438,8 @@ impl Handler<NewAggregatedAttestation> for BlockChainServer {
14001438
14011439impl Handler < AggregateProduced > for BlockChainServer {
14021440 async fn handle ( & mut self , msg : AggregateProduced , _ctx : & Context < Self > ) {
1441+ let arrival_ms = unix_now_ms ( ) ;
1442+
14031443 // Drop results from a prior session (or from an unexpected late worker).
14041444 // Current session may be None if the actor already cleaned it up; accept
14051445 // the message only when ids match.
@@ -1413,6 +1453,17 @@ impl Handler<AggregateProduced> for BlockChainServer {
14131453 return ;
14141454 }
14151455
1456+ // Count our own aggregate in the same series as gossip-received ones,
1457+ // so an aggregator does not report an empty aggregate arrival profile.
1458+ // Delivery of this message is held to the interval-2 boundary upstream,
1459+ // so a local aggregate lands near zero unless proving overran the
1460+ // interval. Sharing one series with received aggregates is deliberate
1461+ // and costs little in practice: a late aggregate is late for every node
1462+ // at once, so both populations are dominated by production time rather
1463+ // than propagation and their distributions look alike.
1464+ let genesis_ms = self . store . config ( ) . expect ( "config exists" ) . genesis_time * 1000 ;
1465+ metrics:: observe_gossip_aggregation_arrival ( arrival_ms, genesis_ms) ;
1466+
14161467 // Publish alignment is enforced upstream: the worker delays delivery of
14171468 // this message until the interval-2 boundary, so by the time it lands
14181469 // the aggregate is safe to apply and gossip immediately.
0 commit comments