@@ -4,7 +4,7 @@ use anyhow::Context;
44use futures:: { future:: BoxFuture , stream:: BoxStream , FutureExt , Stream , StreamExt } ;
55use sqd_data_client:: { BlockStreamRequest , BlockStreamResponse , DataClient } ;
66use sqd_primitives:: { Block , BlockNumber , BlockRef } ;
7- use tokio:: time:: Sleep ;
7+ use tokio:: time:: { Instant , Sleep } ;
88use tracing:: { info, warn} ;
99
1010use crate :: types:: { DataEvent , DataSource } ;
@@ -44,7 +44,8 @@ struct DataSourceState<F> {
4444 position : BlockStreamRequest ,
4545 position_is_canonical : bool ,
4646 max_seen_finalized_block : BlockNumber ,
47- fork_consensus_timeout : Option < Pin < Box < Sleep > > >
47+ fork_consensus_timeout : Option < Pin < Box < Sleep > > > ,
48+ fork_consensus_started_at : Option < Instant >
4849}
4950
5051impl < F > DataSourceState < F > {
@@ -78,6 +79,7 @@ impl<F> DataSourceState<F> {
7879 }
7980 Poll :: Ready ( Ok ( BlockStreamResponse :: Fork ( prev_blocks) ) ) => {
8081 let req = req. clone ( ) ;
82+ self . fork_consensus_started_at . get_or_insert_with ( Instant :: now) ;
8183 ep. on_fork_signal ( req. first_block , & prev_blocks) ;
8284 ep. error_counter = 0 ;
8385 ep. state = EndpointState :: Fork { req, prev_blocks } ;
@@ -143,7 +145,7 @@ impl<F> DataSourceState<F> {
143145 }
144146 self . position . first_block = block. number ( ) + 1 ;
145147 self . position_is_canonical = true ;
146- self . fork_consensus_timeout = None ;
148+ self . reset_fork_consensus ( ) ;
147149
148150 if is_final {
149151 set_head ( & mut self . finalized_head , block. number ( ) , block. hash ( ) ) ;
@@ -152,6 +154,11 @@ impl<F> DataSourceState<F> {
152154 true
153155 }
154156
157+ fn reset_fork_consensus ( & mut self ) {
158+ self . fork_consensus_timeout = None ;
159+ self . fork_consensus_started_at = None ;
160+ }
161+
155162 fn on_new_finalized_head ( & mut self , new_head : Option < & BlockRef > ) -> bool {
156163 let Some ( new_head) = new_head else { return false } ;
157164
@@ -285,7 +292,8 @@ where
285292 } ,
286293 position_is_canonical : false ,
287294 max_seen_finalized_block : 0 ,
288- fork_consensus_timeout : None
295+ fork_consensus_timeout : None ,
296+ fork_consensus_started_at : None
289297 } ;
290298
291299 Self { endpoints, state }
@@ -302,21 +310,38 @@ where
302310 let forks = self . endpoints . iter ( ) . filter ( |ep| ep. is_on_fork ( ) ) . count ( ) ;
303311 if forks > 0 {
304312 let active = self . endpoints . iter ( ) . filter ( |ep| ep. is_active ( ) ) . count ( ) ;
305- if forks > self . endpoints . len ( ) / 2 || forks == active || self . fork_consensus_timeout ( cx) {
306- let chain = self . extract_fork ( ) ;
307- info ! (
308- forked_endpoints = forks,
309- active_endpoints = active,
310- total_endpoints = self . endpoints. len( ) ,
311- hint_count = chain. len( ) ,
312- oldest_hint =? chain. first( ) . map( |b| b. number) ,
313- newest_hint =? chain. last( ) . map( |b| b. number) ,
314- "fork consensus reached"
315- ) ;
316- return Poll :: Ready ( DataEvent :: Fork ( chain) ) ;
317- }
313+ let decision = if forks > self . endpoints . len ( ) / 2 {
314+ "majority"
315+ } else if forks == active {
316+ "all_active"
317+ } else if self . fork_consensus_timeout ( cx) {
318+ "timeout"
319+ } else {
320+ return Poll :: Pending ;
321+ } ;
322+
323+ let consensus_duration = self
324+ . state
325+ . fork_consensus_started_at
326+ . expect ( "fork consensus must start with the first fork signal" )
327+ . elapsed ( ) ;
328+ crate :: metrics:: record_ingest_fork_consensus_duration ( decision, consensus_duration) ;
329+
330+ let chain = self . extract_fork ( ) ;
331+ info ! (
332+ decision = decision,
333+ consensus_duration_seconds = consensus_duration. as_secs_f64( ) ,
334+ forked_endpoints = forks,
335+ active_endpoints = active,
336+ total_endpoints = self . endpoints. len( ) ,
337+ hint_count = chain. len( ) ,
338+ oldest_hint =? chain. first( ) . map( |b| b. number) ,
339+ newest_hint =? chain. last( ) . map( |b| b. number) ,
340+ "fork consensus reached"
341+ ) ;
342+ return Poll :: Ready ( DataEvent :: Fork ( chain) ) ;
318343 } else {
319- self . state . fork_consensus_timeout = None
344+ self . state . reset_fork_consensus ( )
320345 }
321346
322347 Poll :: Pending
@@ -338,7 +363,7 @@ where
338363 }
339364
340365 fn extract_fork ( & mut self ) -> Vec < BlockRef > {
341- self . state . fork_consensus_timeout = None ;
366+ self . state . reset_fork_consensus ( ) ;
342367 let mut chain = Vec :: new ( ) ;
343368 for ep in self . endpoints . iter_mut ( ) {
344369 match std:: mem:: replace ( & mut ep. state , EndpointState :: Ready ) {
@@ -381,6 +406,7 @@ where
381406 self . state . position . set_parent_block_hash ( parent_block_hash) ;
382407 self . state . position_is_canonical = false ;
383408 self . state . finalized_head = None ;
409+ self . state . reset_fork_consensus ( ) ;
384410 for ep in self . endpoints . iter_mut ( ) {
385411 ep. state = EndpointState :: Ready ;
386412 ep. last_committed_block = None ;
0 commit comments