1use std::{
2 collections::{HashMap, HashSet},
3 fmt::{Display, Formatter},
4 time::Duration,
5};
6
7use chrono::{Duration as ChronoDuration, Local, NaiveDateTime};
8use futures03::{future::join_all, stream::FuturesUnordered, FutureExt, StreamExt};
9use serde::{Deserialize, Serialize};
10use thiserror::Error;
11use tokio::{
12 sync::{
13 mpsc::{self, Receiver},
14 oneshot,
15 },
16 task::JoinHandle,
17 time::timeout,
18};
19use tracing::{debug, error, info, trace, warn};
20use tycho_common::{
21 display::opt,
22 models::{blockchain::BlockAggregatedChanges, ExtractorIdentity},
23 Bytes,
24};
25
26use crate::feed::{
27 block_history::{BlockHistory, BlockHistoryError, BlockPosition},
28 synchronizer::{StateSyncMessage, StateSynchronizer, SyncResult, SynchronizerError},
29};
30
31mod block_history;
32pub mod component_tracker;
33pub mod dto;
34pub mod synchronizer;
35
36const BLOCK_HISTORY_SIZE: usize = 15;
40
41pub trait HeaderLike {
46 fn block(self) -> Option<BlockHeader>;
47 fn block_number_or_timestamp(self) -> u64;
48}
49
50#[derive(Debug, Clone, PartialEq, Default, Serialize, Deserialize, Eq, Hash)]
51pub struct BlockHeader {
52 pub hash: Bytes,
53 pub number: u64,
54 pub parent_hash: Bytes,
55 pub revert: bool,
56 pub timestamp: u64,
57 #[serde(default, skip_serializing_if = "Option::is_none")]
59 pub partial_block_index: Option<u32>,
60}
61
62impl BlockHeader {
63 fn is_partial(&self) -> bool {
64 self.partial_block_index.is_some()
65 }
66}
67
68impl Display for BlockHeader {
69 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
70 let short_hash = if self.hash.len() >= 4 {
72 hex::encode(&self.hash[..4]) } else {
74 hex::encode(&self.hash)
75 };
76
77 match self.partial_block_index {
78 Some(idx) => write!(f, "Block #{} [0x{}..] (partial {})", self.number, short_hash, idx),
79 None => write!(f, "Block #{} [0x{}..]", self.number, short_hash),
80 }
81 }
82}
83
84impl From<&BlockAggregatedChanges> for BlockHeader {
85 fn from(block_changes: &BlockAggregatedChanges) -> Self {
86 let block = &block_changes.block;
87 Self {
88 hash: block.hash.clone(),
89 number: block.number,
90 parent_hash: block.parent_hash.clone(),
91 revert: block_changes.revert,
92 timestamp: block.ts.and_utc().timestamp() as u64,
93 partial_block_index: block_changes.partial_block_index,
94 }
95 }
96}
97
98impl HeaderLike for BlockHeader {
99 fn block(self) -> Option<BlockHeader> {
100 Some(self)
101 }
102
103 fn block_number_or_timestamp(self) -> u64 {
104 self.number
105 }
106}
107
108#[derive(Error, Debug)]
109pub enum BlockSynchronizerError {
110 #[error("Failed to initialize extractor '{extractor}': {source}")]
111 InitializationError {
112 extractor: ExtractorIdentity,
113 #[source]
114 source: SynchronizerError,
115 },
116
117 #[error("Failed to process new block: {0}")]
118 BlockHistoryError(#[from] BlockHistoryError),
119
120 #[error("Not a single synchronizer was ready: {0}")]
121 NoReadySynchronizers(String),
122
123 #[error("No synchronizers were set")]
124 NoSynchronizers,
125
126 #[error("Failed to convert duration: {0}")]
127 DurationConversionError(String),
128}
129
130type BlockSyncResult<T> = Result<T, BlockSynchronizerError>;
131
132pub struct BlockSynchronizer<S> {
180 synchronizers: Option<HashMap<ExtractorIdentity, S>>,
181 block_time: std::time::Duration,
183 latency_buffer: std::time::Duration,
185 startup_timeout: std::time::Duration,
187 max_messages: Option<usize>,
189 max_missed_blocks: u64,
191}
192
193#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
194#[serde(tag = "status", rename_all = "lowercase")]
195pub enum SynchronizerState {
196 Started,
198 Ready(BlockHeader),
200 Delayed(BlockHeader),
203 Stale(BlockHeader),
207 Advanced(BlockHeader),
212 Ended(String),
214}
215
216impl Display for SynchronizerState {
217 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
218 match self {
219 SynchronizerState::Started => write!(f, "Started"),
220 SynchronizerState::Ready(b) => write!(f, "Started({})", b.number),
221 SynchronizerState::Delayed(b) => write!(f, "Delayed({})", b.number),
222 SynchronizerState::Stale(b) => write!(f, "Stale({})", b.number),
223 SynchronizerState::Advanced(b) => write!(f, "Advanced({})", b.number),
224 SynchronizerState::Ended(reason) => write!(f, "Ended({})", reason),
225 }
226 }
227}
228
229pub struct SynchronizerStream {
230 extractor_id: ExtractorIdentity,
231 state: SynchronizerState,
232 error: Option<SynchronizerError>,
233 modify_ts: NaiveDateTime,
234 rx: Receiver<SyncResult<StateSyncMessage<BlockHeader>>>,
235}
236
237impl SynchronizerStream {
238 fn new(
239 extractor_id: &ExtractorIdentity,
240 rx: Receiver<SyncResult<StateSyncMessage<BlockHeader>>>,
241 ) -> Self {
242 Self {
243 extractor_id: extractor_id.clone(),
244 state: SynchronizerState::Started,
245 error: None,
246 modify_ts: Local::now().naive_utc(),
247 rx,
248 }
249 }
250
251 async fn try_advance(
261 &mut self,
262 block_history: &BlockHistory,
263 block_time: std::time::Duration,
264 latency_buffer: std::time::Duration,
265 stale_threshold: std::time::Duration,
266 skip_wait: bool,
267 ) -> BlockSyncResult<Option<StateSyncMessage<BlockHeader>>> {
268 let extractor_id = self.extractor_id.clone();
269 let latest_block = block_history.latest();
270
271 match &self.state {
272 SynchronizerState::Started | SynchronizerState::Ended(_) => {
273 warn!(state=?&self.state, "Advancing Synchronizer in this state not supported!");
274 Ok(None)
275 }
276 SynchronizerState::Advanced(b) => {
277 let future_block = b.clone();
278 self.transition(future_block, block_history, stale_threshold)?;
280 Ok(None)
281 }
282 SynchronizerState::Ready(previous_block) => {
283 self.try_recv_next_expected(
285 block_time + latency_buffer,
286 block_history,
287 previous_block.clone(),
288 stale_threshold,
289 )
290 .await
291 }
292 SynchronizerState::Delayed(old_block) => {
293 debug!(
295 %old_block,
296 latest_block=opt(&latest_block),
297 %extractor_id,
298 "Trying to catch up to latest block"
299 );
300 let timeout =
301 if skip_wait { std::time::Duration::ZERO } else { block_time + latency_buffer };
302 self.try_catch_up(block_history, timeout, stale_threshold)
303 .await
304 }
305 SynchronizerState::Stale(old_block) => {
306 debug!(
308 %old_block,
309 latest_block=opt(&latest_block),
310 %extractor_id,
311 "Trying to catch up stale synchronizer to latest block"
312 );
313 let timeout = if skip_wait { std::time::Duration::ZERO } else { block_time };
314 self.try_catch_up(block_history, timeout, stale_threshold)
315 .await
316 }
317 }
318 }
319
320 async fn try_recv_next_expected(
328 &mut self,
329 max_wait: std::time::Duration,
330 block_history: &BlockHistory,
331 previous_block: BlockHeader,
332 stale_threshold: std::time::Duration,
333 ) -> BlockSyncResult<Option<StateSyncMessage<BlockHeader>>> {
334 let extractor_id = self.extractor_id.clone();
335 match timeout(max_wait, self.rx.recv()).await {
336 Ok(Some(Ok(msg))) => {
337 self.transition(msg.header.clone(), block_history, stale_threshold)?;
338 Ok(Some(msg))
339 }
340 Ok(Some(Err(e))) => {
341 self.mark_errored(e);
343 Ok(None)
344 }
345 Ok(None) => {
346 warn!(
349 %extractor_id,
350 "Tried to poll from closed synchronizer.",
351 );
352 self.mark_closed();
353 Ok(None)
354 }
355 Err(_) => {
356 debug!(%extractor_id, %previous_block, "No block received within time limit.");
358 self.state = SynchronizerState::Delayed(previous_block.clone());
361 self.modify_ts = Local::now().naive_utc();
362 Ok(None)
363 }
364 }
365 }
366
367 async fn try_catch_up(
373 &mut self,
374 block_history: &BlockHistory,
375 max_wait: std::time::Duration,
376 stale_threshold: std::time::Duration,
377 ) -> BlockSyncResult<Option<StateSyncMessage<BlockHeader>>> {
378 let mut results = Vec::new();
379 let extractor_id = self.extractor_id.clone();
380
381 let deadline = std::time::Instant::now() + max_wait;
383
384 while std::time::Instant::now() < deadline {
385 match timeout(
386 deadline.saturating_duration_since(std::time::Instant::now()),
387 self.rx.recv(),
388 )
389 .await
390 {
391 Ok(Some(Ok(msg))) => {
392 debug!(%extractor_id, block=%msg.header, "Received new message during catch-up");
393 let block_pos = block_history.determine_block_position(&msg.header)?;
394 results.push(msg);
395 if matches!(block_pos, BlockPosition::NextExpected | BlockPosition::NextPartial)
396 {
397 break;
398 }
399 }
400 Ok(Some(Err(e))) => {
401 self.mark_errored(e);
403 return Ok(None);
404 }
405 Ok(None) => {
406 warn!(
409 %extractor_id,
410 "Tried to poll from closed synchronizer during catch up.",
411 );
412 self.mark_closed();
413 return Ok(None);
414 }
415 Err(_) => {
416 debug!(%extractor_id, "Timed out waiting for catch-up");
417 break;
418 }
419 }
420 }
421
422 let merged = results
423 .into_iter()
424 .reduce(|l, r| l.merge(r));
425
426 if let Some(msg) = merged {
427 debug!(%extractor_id, "Delayed extractor made progress!");
429 self.transition(msg.header.clone(), block_history, stale_threshold)?;
430 Ok(Some(msg))
431 } else {
432 self.check_and_transition_to_stale_if_needed(stale_threshold, None)?;
434 Ok(None)
435 }
436 }
437
438 fn check_and_transition_to_stale_if_needed(
440 &mut self,
441 stale_threshold: std::time::Duration,
442 fallback_header: Option<BlockHeader>,
443 ) -> Result<bool, BlockSynchronizerError> {
444 let now = Local::now().naive_utc();
445 let wait_duration = now.signed_duration_since(self.modify_ts);
446 let stale_threshold_chrono = ChronoDuration::from_std(stale_threshold)
447 .map_err(|e| BlockSynchronizerError::DurationConversionError(e.to_string()))?;
448
449 if wait_duration > stale_threshold_chrono {
450 let header_to_use = match (&self.state, fallback_header) {
451 (SynchronizerState::Ready(h), _) |
452 (SynchronizerState::Delayed(h), _) |
453 (SynchronizerState::Stale(h), _) => h.clone(),
454 (_, Some(h)) => h,
455 _ => BlockHeader::default(),
456 };
457
458 warn!(
459 extractor_id=%self.extractor_id,
460 last_message_at=?self.modify_ts,
461 "SynchronizerStream transition to stale due to timeout."
462 );
463 self.state = SynchronizerState::Stale(header_to_use);
464 self.modify_ts = now;
465 Ok(true)
466 } else {
467 Ok(false)
468 }
469 }
470
471 fn transition(
478 &mut self,
479 latest_retrieved: BlockHeader,
480 block_history: &BlockHistory,
481 stale_threshold: std::time::Duration,
482 ) -> Result<(), BlockSynchronizerError> {
483 let extractor_id = self.extractor_id.clone();
484 let last_message_at = self.modify_ts;
485 let block = &latest_retrieved;
486
487 match block_history.determine_block_position(&latest_retrieved)? {
488 BlockPosition::NextExpected | BlockPosition::NextPartial => {
489 self.state = SynchronizerState::Ready(latest_retrieved.clone());
490 trace!(
491 next = %latest_retrieved,
492 extractor = %extractor_id,
493 "SynchronizerStream transition to next expected"
494 )
495 }
496 BlockPosition::Latest | BlockPosition::Delayed => {
497 if !self.check_and_transition_to_stale_if_needed(
498 stale_threshold,
499 Some(latest_retrieved.clone()),
500 )? {
501 warn!(
502 %extractor_id,
503 ?last_message_at,
504 %block,
505 "SynchronizerStream transition transition to delayed."
506 );
507 self.state = SynchronizerState::Delayed(latest_retrieved.clone());
508 }
509 }
510 BlockPosition::Advanced => {
511 info!(
512 %extractor_id,
513 ?last_message_at,
514 latest = opt(&block_history.latest()),
515 %block,
516 "SynchronizerStream transition to advanced."
517 );
518 self.state = SynchronizerState::Advanced(latest_retrieved.clone());
519 }
520 }
521 self.modify_ts = Local::now().naive_utc();
522 Ok(())
523 }
524
525 fn mark_errored(&mut self, error: SynchronizerError) {
530 self.state = SynchronizerState::Ended(error.to_string());
531 self.modify_ts = Local::now().naive_utc();
532 self.error = Some(error);
533 }
534
535 fn mark_closed(&mut self) {
541 if !matches!(self.state, SynchronizerState::Ended(_)) {
542 self.state = SynchronizerState::Ended("Closed".to_string());
543 self.modify_ts = Local::now().naive_utc();
544 }
545 }
546
547 fn mark_stale(&mut self, header: &BlockHeader) {
549 self.state = SynchronizerState::Stale(header.clone());
550 self.modify_ts = Local::now().naive_utc();
551 }
552
553 fn mark_ready(&mut self, header: &BlockHeader) {
555 self.state = SynchronizerState::Ready(header.clone());
556 self.modify_ts = Local::now().naive_utc();
557 }
558
559 fn has_ended(&self) -> bool {
560 matches!(self.state, SynchronizerState::Ended(_))
561 }
562
563 fn is_stale(&self) -> bool {
564 matches!(self.state, SynchronizerState::Stale(_))
565 }
566
567 fn is_advanced(&self) -> bool {
568 matches!(self.state, SynchronizerState::Advanced(_))
569 }
570
571 fn get_current_header(&self) -> Option<&BlockHeader> {
575 match &self.state {
576 SynchronizerState::Ready(b) |
577 SynchronizerState::Delayed(b) |
578 SynchronizerState::Advanced(b) => Some(b),
579 _ => None,
580 }
581 }
582}
583
584#[derive(Debug, PartialEq, Clone)]
585pub struct FeedMessage<H = BlockHeader>
586where
587 H: HeaderLike,
588{
589 pub state_msgs: HashMap<String, StateSyncMessage<H>>,
590 pub sync_states: HashMap<String, SynchronizerState>,
591}
592
593impl<H> FeedMessage<H>
594where
595 H: HeaderLike,
596{
597 fn new(
598 state_msgs: HashMap<String, StateSyncMessage<H>>,
599 sync_states: HashMap<String, SynchronizerState>,
600 ) -> Self {
601 Self { state_msgs, sync_states }
602 }
603}
604
605impl<S> BlockSynchronizer<S>
606where
607 S: StateSynchronizer,
608{
609 pub fn new(
610 block_time: std::time::Duration,
611 latency_buffer: std::time::Duration,
612 max_missed_blocks: u64,
613 ) -> Self {
614 Self {
615 synchronizers: None,
616 max_messages: None,
617 block_time,
618 latency_buffer,
619 startup_timeout: block_time.mul_f64(max_missed_blocks as f64),
620 max_missed_blocks,
621 }
622 }
623
624 pub fn max_messages(&mut self, val: usize) {
630 self.max_messages = Some(val);
631 }
632
633 pub fn startup_timeout(mut self, val: Duration) {
637 self.startup_timeout = val;
638 }
639
640 pub fn register_synchronizer(mut self, id: ExtractorIdentity, synchronizer: S) -> Self {
641 let mut registered = self.synchronizers.unwrap_or_default();
642 registered.insert(id, synchronizer);
643 self.synchronizers = Some(registered);
644 self
645 }
646
647 #[cfg(test)]
648 pub fn with_short_timeouts() -> Self {
649 Self::new(Duration::from_millis(10), Duration::from_millis(10), 3)
650 }
651
652 async fn cleanup_synchronizers(
655 mut state_sync_tasks: FuturesUnordered<JoinHandle<()>>,
656 sync_close_senders: Vec<oneshot::Sender<()>>,
657 ) {
658 for close_sender in sync_close_senders {
660 let _ = close_sender.send(());
661 }
662
663 let mut completed_tasks = 0;
665 while let Ok(Some(_)) = timeout(Duration::from_secs(5), state_sync_tasks.next()).await {
666 completed_tasks += 1;
667 }
668
669 let remaining_tasks = state_sync_tasks.len();
671 if remaining_tasks > 0 {
672 warn!(
673 completed = completed_tasks,
674 timed_out = remaining_tasks,
675 "Some synchronizers timed out during cleanup and may not have shut down cleanly"
676 );
677 }
678 }
679
680 pub async fn run(
685 mut self,
686 ) -> BlockSyncResult<(JoinHandle<()>, Receiver<BlockSyncResult<FeedMessage<BlockHeader>>>)>
687 {
688 trace!("Starting BlockSynchronizer...");
689 let state_sync_tasks = FuturesUnordered::new();
690 let mut synchronizers = self
691 .synchronizers
692 .take()
693 .ok_or(BlockSynchronizerError::NoSynchronizers)?;
694 let init_results = join_all(synchronizers.iter_mut().map(|(id, s)| {
697 s.initialize()
698 .map(|res| (id.clone(), res))
699 }))
700 .await;
701 let mut to_skip = Vec::new();
702 for (extractor_id, result) in init_results {
703 match result {
704 Ok(()) => {}
705 Err(SynchronizerError::RPCError(crate::rpc::RPCError::UnknownExtractor(
706 reason,
707 ))) => {
708 warn!(%extractor_id, %reason, "Extractor not recognised by server, skipping");
709 to_skip.push(extractor_id);
710 }
711 Err(e) => {
712 return Err(BlockSynchronizerError::InitializationError {
713 extractor: extractor_id,
714 source: e,
715 })
716 }
717 }
718 }
719 for id in &to_skip {
720 synchronizers.remove(id);
721 }
722 if synchronizers.is_empty() {
723 return Err(BlockSynchronizerError::NoSynchronizers);
724 }
725
726 let mut sync_streams = Vec::with_capacity(synchronizers.len());
727 let mut sync_close_senders = Vec::new();
728 for (extractor_id, synchronizer) in synchronizers.drain() {
729 let (handle, rx) = synchronizer.start().await;
730 let (join_handle, close_sender) = handle.split();
731 state_sync_tasks.push(join_handle);
732 sync_close_senders.push(close_sender);
733
734 sync_streams.push(SynchronizerStream::new(&extractor_id, rx));
735 }
736
737 debug!("Waiting for initial synchronizer messages...");
740 let mut startup_futures = Vec::new();
741 for synchronizer in sync_streams.iter_mut() {
742 let fut = async {
743 let res = timeout(self.startup_timeout, synchronizer.rx.recv()).await;
744 (synchronizer, res)
745 };
746 startup_futures.push(fut);
747 }
748
749 let mut ready_sync_msgs = HashMap::new();
750 let initial_headers = join_all(startup_futures)
751 .await
752 .into_iter()
753 .filter_map(|(synchronizer, res)| {
754 let extractor_id = synchronizer.extractor_id.clone();
755 match res {
756 Ok(Some(Ok(msg))) => {
757 debug!(%extractor_id, height=?&msg.header.number, "Synchronizer started successfully!");
758 synchronizer.mark_ready(&msg.header);
760 let header = msg.header.clone();
761 ready_sync_msgs.insert(extractor_id.name.clone(), msg);
762 Some(header)
763 }
764 Ok(Some(Err(e))) => {
765 synchronizer.mark_errored(e);
766 None
767 }
768 Ok(None) => {
769 warn!(%extractor_id, "Synchronizer closed during startup");
774 synchronizer.mark_closed();
775 None
776 }
777 Err(_) => {
778 warn!(%extractor_id, "Timed out waiting for first message");
780 synchronizer.mark_stale(&BlockHeader::default());
781 None
782 }
783 }
784 })
785 .collect::<HashSet<_>>() .into_iter()
787 .collect::<Vec<_>>();
788
789 Self::require_active_stream(&sync_streams)?;
791 let mut block_history = BlockHistory::new(initial_headers, BLOCK_HISTORY_SIZE)?;
792 let start_header = block_history
796 .latest()
797 .ok_or(BlockHistoryError::EmptyHistory)?;
798 info!(
799 start_block=%start_header,
800 n_healthy=ready_sync_msgs.len(),
801 n_total=sync_streams.len(),
802 "Block synchronization started successfully!"
803 );
804
805 for stream in sync_streams.iter_mut() {
808 if let SynchronizerState::Ready(header) = stream.state.clone() {
809 if header.number < start_header.number {
810 debug!(
811 extractor_id=%stream.extractor_id,
812 synchronizer_block=header.number,
813 current_block=start_header.number,
814 "Marking synchronizer as delayed during initialization"
815 );
816 stream.state = SynchronizerState::Delayed(header);
817 }
818 }
819 }
820
821 let (sync_tx, sync_rx) = mpsc::channel(30);
822 let main_loop_jh = tokio::spawn(async move {
823 let mut n_iter = 1;
824 loop {
825 let msg = FeedMessage::new(
827 std::mem::take(&mut ready_sync_msgs),
828 sync_streams
829 .iter()
830 .map(|stream| (stream.extractor_id.name.to_string(), stream.state.clone()))
831 .collect(),
832 );
833 if sync_tx.send(Ok(msg)).await.is_err() {
834 info!("Receiver closed, block synchronizer terminating..");
835 return;
836 };
837
838 if let Some(max_messages) = self.max_messages {
840 if n_iter >= max_messages {
841 info!(max_messages, "StreamEnd");
842 return;
843 }
844 }
845 n_iter += 1;
846
847 let res = self
848 .handle_next_message(
849 &mut sync_streams,
850 &mut ready_sync_msgs,
851 &mut block_history,
852 )
853 .await;
854
855 if let Err(e) = res {
856 let _ = sync_tx.send(Err(e)).await;
858 return;
859 }
860 }
861 });
862
863 let nanny_jh = tokio::spawn(async move {
868 let _ = main_loop_jh.await.map_err(|e| {
870 if e.is_panic() {
871 error!("BlockSynchornizer main loop panicked: {e}")
872 }
873 });
874 debug!("Main loop exited. Closing synchronizers");
875 Self::cleanup_synchronizers(state_sync_tasks, sync_close_senders).await;
876 debug!("Shutdown complete");
877 });
878 Ok((nanny_jh, sync_rx))
879 }
880
881 async fn handle_next_message(
886 &self,
887 sync_streams: &mut [SynchronizerStream],
888 ready_sync_msgs: &mut HashMap<String, StateSyncMessage<BlockHeader>>,
889 block_history: &mut BlockHistory,
890 ) -> BlockSyncResult<()> {
891 let any_advanced = sync_streams
895 .iter()
896 .any(SynchronizerStream::is_advanced);
897 let mut recv_futures = Vec::new();
898 for stream in sync_streams.iter_mut() {
899 if stream.has_ended() {
902 continue;
903 }
904 recv_futures.push(async {
914 let res = stream
915 .try_advance(
916 block_history,
917 self.block_time,
918 self.latency_buffer,
919 self.block_time
920 .mul_f64(self.max_missed_blocks as f64),
921 any_advanced,
922 )
923 .await?;
924 Ok::<_, BlockSynchronizerError>(
925 res.map(|msg| (stream.extractor_id.name.clone(), msg)),
926 )
927 });
928 }
929 ready_sync_msgs.extend(
930 join_all(recv_futures)
931 .await
932 .into_iter()
933 .collect::<Result<Vec<_>, _>>()?
934 .into_iter()
935 .flatten(),
936 );
937
938 Self::check_streams(sync_streams)?;
941
942 if sync_streams
945 .iter()
946 .any(SynchronizerStream::is_advanced)
947 {
948 *block_history = Self::reinit_block_history(sync_streams, block_history)?;
949 } else if let Some(header) = sync_streams
950 .iter()
951 .filter_map(SynchronizerStream::get_current_header)
952 .max_by_key(|b| b.number)
953 {
954 block_history.push(header.clone())?;
955 }
956 Ok(())
959 }
960
961 fn reinit_block_history(
970 sync_streams: &mut [SynchronizerStream],
971 block_history: &mut BlockHistory,
972 ) -> Result<BlockHistory, BlockSynchronizerError> {
973 let previous = block_history
974 .latest()
975 .ok_or(BlockHistoryError::EmptyHistory)?
977 .clone();
978 let mut blocks: Vec<BlockHeader> = block_history
986 .blocks()
987 .cloned()
988 .collect();
989 blocks.extend(
990 sync_streams
991 .iter()
992 .filter_map(SynchronizerStream::get_current_header)
993 .cloned(),
994 );
995 let new_block_history = BlockHistory::new(blocks, BLOCK_HISTORY_SIZE)?;
996 let latest = new_block_history
997 .latest()
998 .ok_or(BlockHistoryError::EmptyHistory)?;
1000 info!(
1001 %previous,
1002 %latest,
1003 "Advanced synchronizer detected. Reinitialized block history."
1004 );
1005 sync_streams
1006 .iter_mut()
1007 .for_each(|stream| {
1008 if let Some(header) = stream.get_current_header() {
1011 if header.number < latest.number {
1012 stream.state = SynchronizerState::Delayed(header.clone());
1013 } else if header.number == latest.number {
1014 stream.state = SynchronizerState::Ready(header.clone());
1015 }
1016 }
1017 });
1018 Ok(new_block_history)
1019 }
1020
1021 fn require_active_stream(sync_streams: &[SynchronizerStream]) -> BlockSyncResult<()> {
1026 if sync_streams
1027 .iter()
1028 .any(|s| !s.has_ended() && !s.is_stale())
1029 {
1030 return Ok(());
1031 }
1032 let reason: Vec<String> = sync_streams
1033 .iter()
1034 .map(|s| format!("{} reported as {} at {}", s.extractor_id, s.state, s.modify_ts))
1035 .collect();
1036 Err(BlockSynchronizerError::NoReadySynchronizers(reason.join(", ")))
1037 }
1038
1039 fn check_streams(sync_streams: &[SynchronizerStream]) -> BlockSyncResult<()> {
1049 let mut has_any_ended = false;
1050 let mut latest_ended_stream: Option<&SynchronizerStream> = None;
1051
1052 for stream in sync_streams.iter() {
1053 if !stream.has_ended() && !stream.is_stale() {
1055 return Ok(());
1056 }
1057
1058 if stream.has_ended() {
1059 has_any_ended = true;
1060 if latest_ended_stream.is_none() ||
1061 stream.modify_ts >
1062 latest_ended_stream
1063 .as_ref()
1064 .unwrap()
1065 .modify_ts
1066 {
1067 latest_ended_stream = Some(stream);
1068 }
1069 }
1070 }
1071
1072 if !has_any_ended {
1075 return Ok(());
1076 }
1077
1078 let last_error_reason = if let Some(stream) = latest_ended_stream {
1080 if let Some(err) = &stream.error {
1081 format!("Synchronizer for {} errored with: {err}", stream.extractor_id)
1082 } else {
1083 format!("Synchronizer for {} became: {}", stream.extractor_id, stream.state)
1084 }
1085 } else {
1086 return Err(BlockSynchronizerError::NoSynchronizers);
1087 };
1088
1089 let mut reason = vec![last_error_reason];
1090
1091 sync_streams.iter().for_each(|stream| {
1092 reason.push(format!(
1093 "{} reported as {} at {}",
1094 stream.extractor_id, stream.state, stream.modify_ts
1095 ))
1096 });
1097
1098 Err(BlockSynchronizerError::NoReadySynchronizers(reason.join(", ")))
1099 }
1100}
1101
1102#[cfg(test)]
1103mod tests {
1104 use std::sync::Arc;
1105
1106 use async_trait::async_trait;
1107 use test_log::test;
1108 use tokio::sync::{oneshot, Mutex};
1109 use tycho_common::models::Chain;
1110
1111 use super::*;
1112 use crate::feed::synchronizer::{SyncResult, SynchronizerTaskHandle};
1113
1114 #[derive(Clone, Debug)]
1115 enum MockBehavior {
1116 Normal, IgnoreClose, ExitImmediately, }
1120
1121 type HeaderReceiver = Receiver<SyncResult<StateSyncMessage<BlockHeader>>>;
1122
1123 #[derive(Clone)]
1124 struct MockStateSync {
1125 header_tx: mpsc::Sender<SyncResult<StateSyncMessage<BlockHeader>>>,
1126 header_rx: Arc<Mutex<Option<HeaderReceiver>>>,
1127 close_received: Arc<Mutex<bool>>,
1128 behavior: MockBehavior,
1129 close_tx: Arc<Mutex<Option<oneshot::Sender<()>>>>,
1131 }
1132
1133 impl MockStateSync {
1134 fn new() -> Self {
1135 Self::with_behavior(MockBehavior::Normal)
1136 }
1137
1138 fn with_behavior(behavior: MockBehavior) -> Self {
1139 let (tx, rx) = mpsc::channel(1);
1140 Self {
1141 header_tx: tx,
1142 header_rx: Arc::new(Mutex::new(Some(rx))),
1143 close_received: Arc::new(Mutex::new(false)),
1144 behavior,
1145 close_tx: Arc::new(Mutex::new(None)),
1146 }
1147 }
1148
1149 async fn was_close_received(&self) -> bool {
1150 *self.close_received.lock().await
1151 }
1152
1153 async fn send_header(&self, header: StateSyncMessage<BlockHeader>) -> Result<(), String> {
1154 self.header_tx
1155 .send(Ok(header))
1156 .await
1157 .map_err(|e| format!("sending header failed: {e}"))
1158 }
1159
1160 async fn trigger_close(&self) {
1162 if let Some(close_tx) = self.close_tx.lock().await.take() {
1163 let _ = close_tx.send(());
1164 }
1165 }
1166 }
1167
1168 #[async_trait]
1169 impl StateSynchronizer for MockStateSync {
1170 async fn initialize(&mut self) -> SyncResult<()> {
1171 Ok(())
1172 }
1173
1174 async fn start(
1175 mut self,
1176 ) -> (SynchronizerTaskHandle, Receiver<SyncResult<StateSyncMessage<BlockHeader>>>) {
1177 let block_rx = {
1178 let mut guard = self.header_rx.lock().await;
1179 guard
1180 .take()
1181 .expect("Block receiver was not set!")
1182 };
1183
1184 let (close_tx_for_handle, close_rx) = oneshot::channel();
1187 let (close_tx_for_test, close_rx_for_test) = oneshot::channel();
1188
1189 {
1191 let mut guard = self.close_tx.lock().await;
1192 *guard = Some(close_tx_for_test);
1193 }
1194
1195 let behavior = self.behavior.clone();
1196 let close_received_clone = self.close_received.clone();
1197 let tx = self.header_tx.clone();
1198
1199 let jh = tokio::spawn(async move {
1200 match behavior {
1201 MockBehavior::IgnoreClose => {
1202 loop {
1205 tokio::time::sleep(tokio::time::Duration::from_secs(1)).await;
1206 }
1207 }
1208 MockBehavior::ExitImmediately => {
1209 tx.send(SyncResult::Err(SynchronizerError::ConnectionError(
1211 "Simulated immediate task failure".to_string(),
1212 )))
1213 .await
1214 .unwrap();
1215 }
1216 MockBehavior::Normal => {
1217 let _ = tokio::select! {
1220 result = close_rx => result,
1221 result = close_rx_for_test => result,
1222 };
1223 let mut guard = close_received_clone.lock().await;
1224 *guard = true;
1225 }
1226 }
1227 });
1228
1229 let handle = SynchronizerTaskHandle::new(jh, close_tx_for_handle);
1230 (handle, block_rx)
1231 }
1232 }
1233
1234 fn header_message(block: u8) -> StateSyncMessage<BlockHeader> {
1235 StateSyncMessage {
1236 header: BlockHeader {
1237 number: block as u64,
1238 hash: Bytes::from(vec![block]),
1239 parent_hash: Bytes::from(vec![block - 1]),
1240 revert: false,
1241 timestamp: 1000,
1242 partial_block_index: None,
1243 },
1244 ..Default::default()
1245 }
1246 }
1247
1248 fn partial_header_message(block: u8, partial_idx: u32) -> StateSyncMessage<BlockHeader> {
1251 let hash_bytes =
1253 [(block as u64).to_be_bytes().as_slice(), partial_idx.to_be_bytes().as_slice()]
1254 .concat();
1255 StateSyncMessage {
1256 header: BlockHeader {
1257 number: block as u64,
1258 hash: Bytes::from(hash_bytes),
1259 parent_hash: Bytes::from(vec![block - 1]),
1260 revert: false,
1261 timestamp: 1000,
1262 partial_block_index: Some(partial_idx),
1263 },
1264 ..Default::default()
1265 }
1266 }
1267
1268 fn revert_header_message(block: u8) -> StateSyncMessage<BlockHeader> {
1269 StateSyncMessage {
1270 header: BlockHeader {
1271 number: block as u64,
1272 hash: Bytes::from(vec![block]),
1273 parent_hash: Bytes::from(vec![block - 1]),
1274 revert: true,
1275 timestamp: 1000,
1276 partial_block_index: None,
1277 },
1278 ..Default::default()
1279 }
1280 }
1281
1282 fn full_header(number: u64, hash: u64, parent: u64) -> BlockHeader {
1285 BlockHeader {
1286 number,
1287 hash: Bytes::from(hash.to_be_bytes()),
1288 parent_hash: Bytes::from(parent.to_be_bytes()),
1289 revert: false,
1290 timestamp: 1000,
1291 partial_block_index: None,
1292 }
1293 }
1294
1295 fn stream_in_state(name: &str, state: SynchronizerState) -> SynchronizerStream {
1298 let (_tx, rx) = mpsc::channel(1);
1299 let id = ExtractorIdentity { chain: Chain::Ethereum, name: name.to_string() };
1300 let mut stream = SynchronizerStream::new(&id, rx);
1301 stream.state = state;
1302 stream
1303 }
1304
1305 async fn receive_message(rx: &mut Receiver<BlockSyncResult<FeedMessage>>) -> FeedMessage {
1306 timeout(Duration::from_millis(100), rx.recv())
1307 .await
1308 .expect("Responds in time")
1309 .expect("Should receive first message")
1310 .expect("No error")
1311 }
1312
1313 async fn setup_block_sync(
1314 ) -> (MockStateSync, MockStateSync, JoinHandle<()>, Receiver<BlockSyncResult<FeedMessage>>)
1315 {
1316 setup_block_sync_with_behaviour(MockBehavior::Normal, MockBehavior::Normal).await
1317 }
1318
1319 async fn setup_block_sync_with_behaviour(
1321 v2_behavior: MockBehavior,
1322 v3_behavior: MockBehavior,
1323 ) -> (MockStateSync, MockStateSync, JoinHandle<()>, Receiver<BlockSyncResult<FeedMessage>>)
1324 {
1325 let v2_sync = MockStateSync::with_behavior(v2_behavior);
1326 let v3_sync = MockStateSync::with_behavior(v3_behavior);
1327
1328 let mut block_sync = BlockSynchronizer::new(
1330 Duration::from_millis(20), Duration::from_millis(10), 3, );
1334 block_sync.max_messages(10); let block_sync = block_sync
1337 .register_synchronizer(
1338 ExtractorIdentity { chain: Chain::Ethereum, name: "uniswap-v2".to_string() },
1339 v2_sync.clone(),
1340 )
1341 .register_synchronizer(
1342 ExtractorIdentity { chain: Chain::Ethereum, name: "uniswap-v3".to_string() },
1343 v3_sync.clone(),
1344 );
1345
1346 let block1_msg = header_message(1);
1348 let _ = v2_sync
1349 .send_header(block1_msg.clone())
1350 .await;
1351 let _ = v3_sync
1352 .send_header(block1_msg.clone())
1353 .await;
1354
1355 let (nanny_handle, mut rx) = block_sync
1357 .run()
1358 .await
1359 .expect("BlockSynchronizer failed to start");
1360
1361 let first_feed_msg = receive_message(&mut rx).await;
1362 assert_eq!(first_feed_msg.state_msgs.len(), 2);
1363 assert!(matches!(
1364 first_feed_msg
1365 .sync_states
1366 .get("uniswap-v2")
1367 .unwrap(),
1368 SynchronizerState::Ready(_)
1369 ));
1370 assert!(matches!(
1371 first_feed_msg
1372 .sync_states
1373 .get("uniswap-v3")
1374 .unwrap(),
1375 SynchronizerState::Ready(_)
1376 ));
1377
1378 (v2_sync, v3_sync, nanny_handle, rx)
1379 }
1380
1381 async fn shutdown_block_synchronizer(
1382 nanny_handle: JoinHandle<()>,
1383 rx: Receiver<BlockSyncResult<FeedMessage>>,
1384 ) {
1385 drop(rx);
1389 timeout(Duration::from_secs(2), nanny_handle)
1390 .await
1391 .expect("Nanny failed to exit within time")
1392 .expect("Nanny panicked");
1393 }
1394
1395 async fn send_and_assert_ready(
1397 sync: &MockStateSync,
1398 sync_name: &str,
1399 rx: &mut Receiver<BlockSyncResult<FeedMessage>>,
1400 msg: StateSyncMessage<BlockHeader>,
1401 expected_block: u64,
1402 expected_partial: Option<u32>,
1403 ) {
1404 sync.send_header(msg)
1405 .await
1406 .expect("send failed");
1407 let feed_msg = receive_message(rx).await;
1408 let state = feed_msg
1409 .sync_states
1410 .get(sync_name)
1411 .unwrap();
1412 match state {
1413 SynchronizerState::Ready(h) => {
1414 assert_eq!(h.number, expected_block, "wrong block number");
1415 assert_eq!(h.partial_block_index, expected_partial, "wrong partial index");
1416 }
1417 other => panic!("expected Ready, got {:?}", other),
1418 }
1419 }
1420
1421 #[test(tokio::test)]
1422 async fn test_two_ready_synchronizers() {
1423 let (v2_sync, v3_sync, nanny_handle, mut rx) = setup_block_sync().await;
1424
1425 let second_msg = header_message(2);
1426 v2_sync
1427 .send_header(second_msg.clone())
1428 .await
1429 .expect("send_header failed");
1430 v3_sync
1431 .send_header(second_msg.clone())
1432 .await
1433 .expect("send_header failed");
1434 let second_feed_msg = receive_message(&mut rx).await;
1435
1436 let exp2 = FeedMessage {
1437 state_msgs: [
1438 ("uniswap-v2".to_string(), second_msg.clone()),
1439 ("uniswap-v3".to_string(), second_msg.clone()),
1440 ]
1441 .into_iter()
1442 .collect(),
1443 sync_states: [
1444 ("uniswap-v3".to_string(), SynchronizerState::Ready(second_msg.header.clone())),
1445 ("uniswap-v2".to_string(), SynchronizerState::Ready(second_msg.header.clone())),
1446 ]
1447 .into_iter()
1448 .collect(),
1449 };
1450 assert_eq!(second_feed_msg, exp2);
1451
1452 shutdown_block_synchronizer(nanny_handle, rx).await;
1453 }
1454
1455 #[test]
1464 fn test_reinit_preserves_history_for_revert_below_advanced_tip() {
1465 let old_blocks = vec![
1467 full_header(323, 323, 322),
1468 full_header(324, 324, 323),
1469 full_header(325, 325, 324),
1470 ];
1471 let mut old_history = BlockHistory::new(old_blocks, 15).unwrap();
1472
1473 let mut streams = vec![
1476 stream_in_state("aerodrome", SynchronizerState::Advanced(full_header(326, 326, 325))),
1477 stream_in_state("uniswap_v3", SynchronizerState::Delayed(full_header(325, 325, 324))),
1478 ];
1479
1480 let mut new_history = BlockSynchronizer::<MockStateSync>::reinit_block_history(
1481 &mut streams,
1482 &mut old_history,
1483 )
1484 .expect("reinit failed");
1485
1486 let revert = BlockHeader {
1488 number: 325,
1489 hash: Bytes::from(325u64.to_be_bytes()),
1490 parent_hash: Bytes::from(324u64.to_be_bytes()),
1491 revert: true,
1492 timestamp: 1000,
1493 partial_block_index: None,
1494 };
1495 new_history
1496 .push(revert)
1497 .expect("revert below advanced tip must not exceed history");
1498 }
1499
1500 #[test]
1505 fn test_reinit_discards_retained_history_on_detached_advanced_block() {
1506 let old_blocks = vec![
1507 full_header(323, 323, 322),
1508 full_header(324, 324, 323),
1509 full_header(325, 325, 324),
1510 ];
1511 let mut old_history = BlockHistory::new(old_blocks, BLOCK_HISTORY_SIZE).unwrap();
1512
1513 let mut streams = vec![stream_in_state(
1515 "aerodrome",
1516 SynchronizerState::Advanced(full_header(400, 400, 399)),
1517 )];
1518
1519 let new_history = BlockSynchronizer::<MockStateSync>::reinit_block_history(
1520 &mut streams,
1521 &mut old_history,
1522 )
1523 .expect("reinit failed");
1524
1525 let retained: Vec<u64> = new_history
1526 .blocks()
1527 .map(|b| b.number)
1528 .collect();
1529 assert_eq!(
1530 retained,
1531 vec![400],
1532 "detached advanced block must not be stitched to old history"
1533 );
1534 }
1535
1536 #[test(tokio::test)]
1537 async fn test_delayed_synchronizer_catches_up() {
1538 let (v2_sync, v3_sync, nanny_handle, mut rx) = setup_block_sync().await;
1539
1540 let block2_msg = header_message(2);
1542 v2_sync
1543 .send_header(block2_msg.clone())
1544 .await
1545 .expect("send_header failed");
1546
1547 let second_feed_msg = receive_message(&mut rx).await;
1549 debug!("Consumed second message for v2");
1550
1551 assert!(second_feed_msg
1552 .state_msgs
1553 .contains_key("uniswap-v2"));
1554 assert!(matches!(
1555 second_feed_msg.sync_states.get("uniswap-v2").unwrap(),
1556 SynchronizerState::Ready(header) if header.number == 2
1557 ));
1558 assert!(!second_feed_msg
1559 .state_msgs
1560 .contains_key("uniswap-v3"));
1561 assert!(matches!(
1562 second_feed_msg.sync_states.get("uniswap-v3").unwrap(),
1563 SynchronizerState::Delayed(header) if header.number == 1
1564 ));
1565
1566 v3_sync
1568 .send_header(block2_msg.clone())
1569 .await
1570 .expect("send_header failed");
1571
1572 let block3_msg = header_message(3);
1574 v2_sync
1575 .send_header(block3_msg.clone())
1576 .await
1577 .expect("send_header failed");
1578 v3_sync
1579 .send_header(block3_msg)
1580 .await
1581 .expect("send_header failed");
1582
1583 let mut third_feed_msg = receive_message(&mut rx).await;
1586
1587 if !third_feed_msg
1590 .state_msgs
1591 .contains_key("uniswap-v2")
1592 {
1593 third_feed_msg = rx
1594 .recv()
1595 .await
1596 .expect("header channel was closed")
1597 .expect("no error");
1598 }
1599 assert!(third_feed_msg
1600 .state_msgs
1601 .contains_key("uniswap-v2"));
1602 assert!(third_feed_msg
1603 .state_msgs
1604 .contains_key("uniswap-v3"));
1605 assert!(matches!(
1606 third_feed_msg.sync_states.get("uniswap-v2").unwrap(),
1607 SynchronizerState::Ready(header) if header.number == 3
1608 ));
1609 assert!(matches!(
1610 third_feed_msg.sync_states.get("uniswap-v3").unwrap(),
1611 SynchronizerState::Ready(header) if header.number == 3
1612 ));
1613
1614 shutdown_block_synchronizer(nanny_handle, rx).await;
1615 }
1616
1617 #[test(tokio::test)]
1618 async fn test_different_start_blocks() {
1619 let v2_sync = MockStateSync::new();
1620 let v3_sync = MockStateSync::new();
1621 let block_sync = BlockSynchronizer::with_short_timeouts()
1622 .register_synchronizer(
1623 ExtractorIdentity { chain: Chain::Ethereum, name: "uniswap-v2".to_string() },
1624 v2_sync.clone(),
1625 )
1626 .register_synchronizer(
1627 ExtractorIdentity { chain: Chain::Ethereum, name: "uniswap-v3".to_string() },
1628 v3_sync.clone(),
1629 );
1630
1631 let block1_msg = header_message(1);
1633 let block2_msg = header_message(2);
1634
1635 let _ = v2_sync
1636 .send_header(block1_msg.clone())
1637 .await;
1638 v3_sync
1639 .send_header(block2_msg.clone())
1640 .await
1641 .expect("send_header failed");
1642
1643 let (jh, mut rx) = block_sync
1645 .run()
1646 .await
1647 .expect("BlockSynchronizer failed to start.");
1648
1649 let first_feed_msg = receive_message(&mut rx).await;
1651 assert!(matches!(
1652 first_feed_msg.sync_states.get("uniswap-v2").unwrap(),
1653 SynchronizerState::Delayed(header) if header.number == 1
1654 ));
1655 assert!(matches!(
1656 first_feed_msg.sync_states.get("uniswap-v3").unwrap(),
1657 SynchronizerState::Ready(header) if header.number == 2
1658 ));
1659
1660 v2_sync
1662 .send_header(block2_msg.clone())
1663 .await
1664 .expect("send_header failed");
1665
1666 let block3_msg = header_message(3);
1668 let _ = v2_sync
1669 .send_header(block3_msg.clone())
1670 .await;
1671 v3_sync
1672 .send_header(block3_msg.clone())
1673 .await
1674 .expect("send_header failed");
1675
1676 let second_feed_msg = receive_message(&mut rx).await;
1678 assert_eq!(second_feed_msg.state_msgs.len(), 2);
1679 assert!(matches!(
1680 second_feed_msg.sync_states.get("uniswap-v2").unwrap(),
1681 SynchronizerState::Ready(header) if header.number == 3
1682 ));
1683 assert!(matches!(
1684 second_feed_msg.sync_states.get("uniswap-v3").unwrap(),
1685 SynchronizerState::Ready(header) if header.number == 3
1686 ));
1687
1688 shutdown_block_synchronizer(jh, rx).await;
1689 }
1690
1691 #[test(tokio::test)]
1692 async fn test_synchronizer_fails_other_goes_stale() {
1693 let (_v2_sync, v3_sync, nanny_handle, mut sync_rx) =
1694 setup_block_sync_with_behaviour(MockBehavior::ExitImmediately, MockBehavior::Normal)
1695 .await;
1696
1697 let mut error_reported = false;
1698 for _ in 0..3 {
1699 if let Some(msg) = sync_rx.recv().await {
1700 match msg {
1701 Err(_) => error_reported = true,
1702 Ok(msg) => {
1703 assert!(matches!(
1704 msg.sync_states
1705 .get("uniswap-v3")
1706 .unwrap(),
1707 SynchronizerState::Delayed(_)
1708 ));
1709 assert!(matches!(
1710 msg.sync_states
1711 .get("uniswap-v2")
1712 .unwrap(),
1713 SynchronizerState::Ended(_)
1714 ));
1715 }
1716 }
1717 }
1718 }
1719 assert!(error_reported, "BlockSynchronizer did not report final error");
1720
1721 let result = timeout(Duration::from_secs(2), nanny_handle).await;
1723 assert!(result.is_ok(), "Nanny should complete when synchronizer task exits");
1724
1725 assert!(
1727 v3_sync.was_close_received().await,
1728 "v3_sync should have received close signal during cleanup"
1729 );
1730 }
1731
1732 #[test(tokio::test)]
1733 async fn test_cleanup_timeout_warning() {
1734 let (_v2_sync, _v3_sync, nanny_handle, _rx) = setup_block_sync_with_behaviour(
1737 MockBehavior::ExitImmediately,
1738 MockBehavior::IgnoreClose,
1739 )
1740 .await;
1741
1742 let result = timeout(Duration::from_secs(10), nanny_handle).await;
1744 assert!(
1745 result.is_ok(),
1746 "Nanny should complete even when some synchronizers timeout during cleanup"
1747 );
1748
1749 }
1753
1754 #[test(tokio::test)]
1755 async fn test_one_synchronizer_goes_stale_while_other_works() {
1756 let (_v2_sync, v3_sync, nanny_handle, mut rx) = setup_block_sync().await;
1758
1759 let block2_msg = header_message(2);
1761 let _ = v3_sync
1762 .send_header(block2_msg.clone())
1763 .await;
1764 let second_feed_msg = receive_message(&mut rx).await;
1768 assert!(second_feed_msg
1769 .state_msgs
1770 .contains_key("uniswap-v3"));
1771 assert!(!second_feed_msg
1772 .state_msgs
1773 .contains_key("uniswap-v2"));
1774 assert!(matches!(
1775 second_feed_msg
1776 .sync_states
1777 .get("uniswap-v3")
1778 .unwrap(),
1779 SynchronizerState::Ready(_)
1780 ));
1781 if let Some(v2_state) = second_feed_msg
1783 .sync_states
1784 .get("uniswap-v2")
1785 {
1786 if matches!(v2_state, SynchronizerState::Delayed(_)) {
1787 assert!(
1789 !nanny_handle.is_finished(),
1790 "Nanny should still be running when synchronizer is delayed (not stale yet)"
1791 );
1792 }
1793 }
1794
1795 tokio::time::sleep(Duration::from_millis(15)).await;
1797
1798 let block3_msg = header_message(3);
1800 let _ = v3_sync
1801 .send_header(block3_msg.clone())
1802 .await;
1803
1804 tokio::time::sleep(Duration::from_millis(40)).await;
1805
1806 let mut stale_found = false;
1807 for _ in 0..2 {
1808 if let Some(Ok(msg)) = rx.recv().await {
1809 if let Some(SynchronizerState::Stale(_)) = msg.sync_states.get("uniswap-v2") {
1810 stale_found = true;
1811 }
1812 }
1813 }
1814 assert!(stale_found, "v2 synchronizer should be stale");
1815
1816 shutdown_block_synchronizer(nanny_handle, rx).await;
1817 }
1818
1819 #[test(tokio::test)]
1820 async fn test_all_synchronizers_stale_loop_continues() {
1821 let (v2_sync, v3_sync, nanny_handle, mut rx) = setup_block_sync().await;
1824
1825 let mut seen_delayed = false;
1827 let mut seen_stale = false;
1828 let start_time = tokio::time::Instant::now();
1829
1830 while let Ok(Some(Ok(msg))) =
1831 tokio::time::timeout(Duration::from_millis(50), rx.recv()).await
1832 {
1833 let v2_state = msg.sync_states.get("uniswap-v2");
1834 let v3_state = msg.sync_states.get("uniswap-v3");
1835
1836 if !seen_delayed &&
1837 (matches!(v2_state, Some(SynchronizerState::Delayed(_))) ||
1838 matches!(v3_state, Some(SynchronizerState::Delayed(_))))
1839 {
1840 seen_delayed = true;
1841 assert!(
1842 !nanny_handle.is_finished(),
1843 "Nanny must still run when synchronizers are Delayed"
1844 );
1845 }
1846
1847 if matches!(v2_state, Some(SynchronizerState::Stale(_))) &&
1848 matches!(v3_state, Some(SynchronizerState::Stale(_)))
1849 {
1850 seen_stale = true;
1851 assert!(
1852 !nanny_handle.is_finished(),
1853 "Main loop must not exit when all synchronizers are Stale (awaiting recovery)"
1854 );
1855 break;
1856 }
1857
1858 if start_time.elapsed() > Duration::from_millis(500) {
1859 break;
1860 }
1861 }
1862
1863 assert!(seen_delayed, "Synchronizers should transition through Delayed first");
1864 assert!(seen_stale, "Both synchronizers should reach Stale state");
1865
1866 v2_sync.trigger_close().await;
1871 v3_sync.trigger_close().await;
1872 drop(v2_sync);
1873 drop(v3_sync);
1874
1875 let mut error_reported = false;
1877 while let Some(msg) = rx.recv().await {
1878 if msg.is_err() {
1879 error_reported = true;
1880 }
1881 }
1882 assert!(error_reported, "Expected an error after all synchronizers ended");
1883
1884 let nanny_result = timeout(Duration::from_secs(2), nanny_handle).await;
1885 assert!(nanny_result.is_ok(), "Nanny should complete after all synchronizers ended");
1886 }
1887
1888 #[test(tokio::test)]
1889 async fn test_all_synchronizers_recover_after_going_stale() {
1890 let v2_sync = MockStateSync::new();
1893 let v3_sync = MockStateSync::new();
1894 let block_sync =
1895 BlockSynchronizer::new(Duration::from_millis(20), Duration::from_millis(10), 3)
1896 .register_synchronizer(
1897 ExtractorIdentity { chain: Chain::Ethereum, name: "uniswap-v2".to_string() },
1898 v2_sync.clone(),
1899 )
1900 .register_synchronizer(
1901 ExtractorIdentity { chain: Chain::Ethereum, name: "uniswap-v3".to_string() },
1902 v3_sync.clone(),
1903 );
1904
1905 v2_sync
1906 .send_header(header_message(1))
1907 .await
1908 .unwrap();
1909 v3_sync
1910 .send_header(header_message(1))
1911 .await
1912 .unwrap();
1913
1914 let (nanny_handle, mut rx) = block_sync
1915 .run()
1916 .await
1917 .expect("BlockSynchronizer start failed");
1918
1919 let first_msg = receive_message(&mut rx).await;
1920 assert!(matches!(
1921 first_msg.sync_states.get("uniswap-v2").unwrap(),
1922 SynchronizerState::Ready(h) if h.number == 1
1923 ));
1924 assert!(matches!(
1925 first_msg.sync_states.get("uniswap-v3").unwrap(),
1926 SynchronizerState::Ready(h) if h.number == 1
1927 ));
1928
1929 let mut seen_stale = false;
1931 let start_time = tokio::time::Instant::now();
1932 while let Ok(Some(Ok(msg))) =
1933 tokio::time::timeout(Duration::from_millis(50), rx.recv()).await
1934 {
1935 let v2 = msg
1936 .sync_states
1937 .get("uniswap-v2")
1938 .unwrap();
1939 let v3 = msg
1940 .sync_states
1941 .get("uniswap-v3")
1942 .unwrap();
1943 if matches!(v2, SynchronizerState::Stale(_)) &&
1944 matches!(v3, SynchronizerState::Stale(_))
1945 {
1946 seen_stale = true;
1947 assert!(
1948 !nanny_handle.is_finished(),
1949 "Main loop must not exit while synchronizers are Stale"
1950 );
1951 break;
1952 }
1953 if start_time.elapsed() > Duration::from_millis(500) {
1954 break;
1955 }
1956 }
1957 assert!(seen_stale, "Both synchronizers should go Stale");
1958
1959 v2_sync
1961 .send_header(header_message(5))
1962 .await
1963 .unwrap();
1964 v3_sync
1965 .send_header(header_message(5))
1966 .await
1967 .unwrap();
1968
1969 let mut recovered = false;
1972 for _ in 0..20 {
1973 let msg = receive_message(&mut rx).await;
1974 let v2 = msg
1975 .sync_states
1976 .get("uniswap-v2")
1977 .unwrap();
1978 let v3 = msg
1979 .sync_states
1980 .get("uniswap-v3")
1981 .unwrap();
1982 if matches!(v2, SynchronizerState::Ready(h) if h.number == 5) &&
1983 matches!(v3, SynchronizerState::Ready(h) if h.number == 5)
1984 {
1985 recovered = true;
1986 break;
1987 }
1988 }
1989 assert!(recovered, "Both synchronizers should recover to Ready at block 5");
1990
1991 drop(rx);
1993 timeout(Duration::from_secs(2), nanny_handle)
1994 .await
1995 .expect("Nanny timed out")
1996 .expect("Nanny panicked");
1997 }
1998
1999 #[test(tokio::test)]
2000 async fn test_stale_synchronizer_recovers() {
2001 let (v2_sync, v3_sync, nanny_handle, mut rx) = setup_block_sync().await;
2003
2004 tokio::time::sleep(Duration::from_millis(50)).await;
2006 let block2_msg = header_message(2);
2007 let _ = v2_sync
2008 .send_header(block2_msg.clone())
2009 .await;
2010
2011 for _ in 0..2 {
2013 if let Some(msg) = rx.recv().await {
2014 if let Ok(msg) = msg {
2015 if matches!(
2016 msg.sync_states
2017 .get("uniswap-v2")
2018 .unwrap(),
2019 SynchronizerState::Ready(_)
2020 ) {
2021 assert!(matches!(
2022 msg.sync_states
2023 .get("uniswap-v3")
2024 .unwrap(),
2025 SynchronizerState::Delayed(_)
2026 ));
2027 break;
2028 };
2029 }
2030 } else {
2031 panic!("Channel closed unexpectedly")
2032 }
2033 }
2034
2035 tokio::time::sleep(Duration::from_millis(15)).await;
2037 let block3_msg = header_message(3);
2038 let _ = v2_sync
2039 .send_header(block3_msg.clone())
2040 .await;
2041 let third_msg = receive_message(&mut rx).await;
2042 dbg!(&third_msg);
2043 assert!(matches!(
2044 third_msg
2045 .sync_states
2046 .get("uniswap-v2")
2047 .unwrap(),
2048 SynchronizerState::Ready(_)
2049 ));
2050 assert!(matches!(
2051 third_msg
2052 .sync_states
2053 .get("uniswap-v3")
2054 .unwrap(),
2055 SynchronizerState::Stale(_)
2056 ));
2057
2058 let block4_msg = header_message(4);
2059 let _ = v3_sync
2060 .send_header(block2_msg.clone())
2061 .await;
2062 let _ = v3_sync
2063 .send_header(block3_msg.clone())
2064 .await;
2065 let _ = v3_sync
2066 .send_header(block4_msg.clone())
2067 .await;
2068 let _ = v2_sync
2069 .send_header(block4_msg.clone())
2070 .await;
2071 let fourth_msg = receive_message(&mut rx).await;
2072 assert!(matches!(
2073 fourth_msg
2074 .sync_states
2075 .get("uniswap-v2")
2076 .unwrap(),
2077 SynchronizerState::Ready(_)
2078 ));
2079 assert!(matches!(
2080 fourth_msg
2081 .sync_states
2082 .get("uniswap-v3")
2083 .unwrap(),
2084 SynchronizerState::Ready(_)
2085 ));
2086
2087 shutdown_block_synchronizer(nanny_handle, rx).await;
2088
2089 assert!(
2091 v2_sync.was_close_received().await,
2092 "v2_sync should have received close signal during cleanup"
2093 );
2094 assert!(
2095 v3_sync.was_close_received().await,
2096 "v3_sync should have received close signal during cleanup"
2097 );
2098 }
2099
2100 #[test(tokio::test)]
2101 async fn test_all_synchronizer_advanced() {
2102 let (v2_sync, v3_sync, nanny_handle, mut rx) = setup_block_sync().await;
2106
2107 let block3 = header_message(3);
2108 v2_sync
2109 .send_header(block3.clone())
2110 .await
2111 .unwrap();
2112 v3_sync
2113 .send_header(block3)
2114 .await
2115 .unwrap();
2116
2117 let msg = receive_message(&mut rx).await;
2118 matches!(
2119 msg.sync_states
2120 .get("uniswap-v2")
2121 .unwrap(),
2122 SynchronizerState::Ready(_)
2123 );
2124 matches!(
2125 msg.sync_states
2126 .get("uniswap-v3")
2127 .unwrap(),
2128 SynchronizerState::Ready(_)
2129 );
2130
2131 shutdown_block_synchronizer(nanny_handle, rx).await;
2132 }
2133
2134 #[test(tokio::test)]
2135 async fn test_one_synchronizer_advanced() {
2136 let (v2_sync, v3_sync, nanny_handle, mut rx) = setup_block_sync().await;
2137
2138 let block2 = header_message(2);
2139 let block4 = header_message(4);
2140 v2_sync
2141 .send_header(block4.clone())
2142 .await
2143 .unwrap();
2144 v3_sync
2145 .send_header(block2.clone())
2146 .await
2147 .unwrap();
2148
2149 let msg = receive_message(&mut rx).await;
2150 matches!(
2151 msg.sync_states
2152 .get("uniswap-v2")
2153 .unwrap(),
2154 SynchronizerState::Ready(_)
2155 );
2156 matches!(
2157 msg.sync_states
2158 .get("uniswap-v3")
2159 .unwrap(),
2160 SynchronizerState::Delayed(_)
2161 );
2162
2163 shutdown_block_synchronizer(nanny_handle, rx).await;
2164 }
2165
2166 #[test(tokio::test)]
2167 async fn test_partial_blocks_normal_operation() {
2168 let (v2_sync, _v3_sync, nanny_handle, mut rx) = setup_block_sync().await;
2171
2172 send_and_assert_ready(
2174 &v2_sync,
2175 "uniswap-v2",
2176 &mut rx,
2177 partial_header_message(2, 0),
2178 2,
2179 Some(0),
2180 )
2181 .await;
2182 send_and_assert_ready(
2183 &v2_sync,
2184 "uniswap-v2",
2185 &mut rx,
2186 partial_header_message(2, 3),
2187 2,
2188 Some(3),
2189 )
2190 .await;
2191 send_and_assert_ready(
2192 &v2_sync,
2193 "uniswap-v2",
2194 &mut rx,
2195 partial_header_message(2, 7),
2196 2,
2197 Some(7),
2198 )
2199 .await;
2200
2201 send_and_assert_ready(
2203 &v2_sync,
2204 "uniswap-v2",
2205 &mut rx,
2206 partial_header_message(3, 0),
2207 3,
2208 Some(0),
2209 )
2210 .await;
2211 send_and_assert_ready(
2212 &v2_sync,
2213 "uniswap-v2",
2214 &mut rx,
2215 partial_header_message(3, 2),
2216 3,
2217 Some(2),
2218 )
2219 .await;
2220
2221 shutdown_block_synchronizer(nanny_handle, rx).await;
2222 }
2223
2224 #[test(tokio::test)]
2225 async fn test_partial_blocks_handles_reverts() {
2226 let (v2_sync, _v3_sync, nanny_handle, mut rx) = setup_block_sync().await;
2229
2230 send_and_assert_ready(&v2_sync, "uniswap-v2", &mut rx, header_message(2), 2, None).await;
2232
2233 send_and_assert_ready(
2235 &v2_sync,
2236 "uniswap-v2",
2237 &mut rx,
2238 partial_header_message(3, 0),
2239 3,
2240 Some(0),
2241 )
2242 .await;
2243 send_and_assert_ready(
2244 &v2_sync,
2245 "uniswap-v2",
2246 &mut rx,
2247 partial_header_message(3, 2),
2248 3,
2249 Some(2),
2250 )
2251 .await;
2252
2253 send_and_assert_ready(&v2_sync, "uniswap-v2", &mut rx, revert_header_message(2), 2, None)
2255 .await;
2256
2257 send_and_assert_ready(&v2_sync, "uniswap-v2", &mut rx, header_message(3), 3, None).await;
2259
2260 shutdown_block_synchronizer(nanny_handle, rx).await;
2261 }
2262
2263 #[test(tokio::test)]
2264 async fn test_partial_blocks_delayed_synchronizer_catches_up() {
2265 let (v2_sync, v3_sync, nanny_handle, mut rx) = setup_block_sync().await;
2268
2269 let partial_0 = partial_header_message(2, 0);
2271 v2_sync
2272 .send_header(partial_0.clone())
2273 .await
2274 .expect("send partial 0 failed");
2275
2276 let msg = receive_message(&mut rx).await;
2277 assert!(msg
2279 .state_msgs
2280 .contains_key("uniswap-v2"));
2281 assert!(!msg
2282 .state_msgs
2283 .contains_key("uniswap-v3"));
2284 assert!(matches!(
2285 msg.sync_states.get("uniswap-v2").unwrap(),
2286 SynchronizerState::Ready(h) if h.partial_block_index == Some(0)
2287 ));
2288 assert!(matches!(
2289 msg.sync_states
2290 .get("uniswap-v3")
2291 .unwrap(),
2292 SynchronizerState::Delayed(_)
2293 ));
2294
2295 let partial_2 = partial_header_message(2, 2);
2297 v2_sync
2298 .send_header(partial_2.clone())
2299 .await
2300 .expect("send partial 2 failed");
2301 v3_sync
2302 .send_header(partial_0.clone())
2303 .await
2304 .expect("v3 catch up partial 0 failed");
2305 v3_sync
2306 .send_header(partial_2.clone())
2307 .await
2308 .expect("v3 catch up partial 2 failed");
2309
2310 let mut v3_ready = false;
2312 for _ in 0..3 {
2313 let msg = receive_message(&mut rx).await;
2314 if matches!(
2315 msg.sync_states.get("uniswap-v3").unwrap(),
2316 SynchronizerState::Ready(h) if h.partial_block_index == Some(2)
2317 ) {
2318 v3_ready = true;
2319 break;
2320 }
2321 }
2322 assert!(v3_ready, "v3 caught up to partial 2");
2323
2324 shutdown_block_synchronizer(nanny_handle, rx).await;
2325 }
2326}