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
36pub trait HeaderLike {
41 fn block(self) -> Option<BlockHeader>;
42 fn block_number_or_timestamp(self) -> u64;
43}
44
45#[derive(Debug, Clone, PartialEq, Default, Serialize, Deserialize, Eq, Hash)]
46pub struct BlockHeader {
47 pub hash: Bytes,
48 pub number: u64,
49 pub parent_hash: Bytes,
50 pub revert: bool,
51 pub timestamp: u64,
52 #[serde(default, skip_serializing_if = "Option::is_none")]
54 pub partial_block_index: Option<u32>,
55}
56
57impl BlockHeader {
58 fn is_partial(&self) -> bool {
59 self.partial_block_index.is_some()
60 }
61}
62
63impl Display for BlockHeader {
64 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
65 let short_hash = if self.hash.len() >= 4 {
67 hex::encode(&self.hash[..4]) } else {
69 hex::encode(&self.hash)
70 };
71
72 match self.partial_block_index {
73 Some(idx) => write!(f, "Block #{} [0x{}..] (partial {})", self.number, short_hash, idx),
74 None => write!(f, "Block #{} [0x{}..]", self.number, short_hash),
75 }
76 }
77}
78
79impl From<&BlockAggregatedChanges> for BlockHeader {
80 fn from(block_changes: &BlockAggregatedChanges) -> Self {
81 let block = &block_changes.block;
82 Self {
83 hash: block.hash.clone(),
84 number: block.number,
85 parent_hash: block.parent_hash.clone(),
86 revert: block_changes.revert,
87 timestamp: block.ts.and_utc().timestamp() as u64,
88 partial_block_index: block_changes.partial_block_index,
89 }
90 }
91}
92
93impl HeaderLike for BlockHeader {
94 fn block(self) -> Option<BlockHeader> {
95 Some(self)
96 }
97
98 fn block_number_or_timestamp(self) -> u64 {
99 self.number
100 }
101}
102
103#[derive(Error, Debug)]
104pub enum BlockSynchronizerError {
105 #[error("Failed to initialize extractor '{extractor}': {source}")]
106 InitializationError {
107 extractor: ExtractorIdentity,
108 #[source]
109 source: SynchronizerError,
110 },
111
112 #[error("Failed to process new block: {0}")]
113 BlockHistoryError(#[from] BlockHistoryError),
114
115 #[error("Not a single synchronizer was ready: {0}")]
116 NoReadySynchronizers(String),
117
118 #[error("No synchronizers were set")]
119 NoSynchronizers,
120
121 #[error("Failed to convert duration: {0}")]
122 DurationConversionError(String),
123}
124
125type BlockSyncResult<T> = Result<T, BlockSynchronizerError>;
126
127pub struct BlockSynchronizer<S> {
175 synchronizers: Option<HashMap<ExtractorIdentity, S>>,
176 block_time: std::time::Duration,
178 latency_buffer: std::time::Duration,
180 startup_timeout: std::time::Duration,
182 max_messages: Option<usize>,
184 max_missed_blocks: u64,
186}
187
188#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
189#[serde(tag = "status", rename_all = "lowercase")]
190pub enum SynchronizerState {
191 Started,
193 Ready(BlockHeader),
195 Delayed(BlockHeader),
198 Stale(BlockHeader),
202 Advanced(BlockHeader),
207 Ended(String),
209}
210
211impl Display for SynchronizerState {
212 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
213 match self {
214 SynchronizerState::Started => write!(f, "Started"),
215 SynchronizerState::Ready(b) => write!(f, "Started({})", b.number),
216 SynchronizerState::Delayed(b) => write!(f, "Delayed({})", b.number),
217 SynchronizerState::Stale(b) => write!(f, "Stale({})", b.number),
218 SynchronizerState::Advanced(b) => write!(f, "Advanced({})", b.number),
219 SynchronizerState::Ended(reason) => write!(f, "Ended({})", reason),
220 }
221 }
222}
223
224pub struct SynchronizerStream {
225 extractor_id: ExtractorIdentity,
226 state: SynchronizerState,
227 error: Option<SynchronizerError>,
228 modify_ts: NaiveDateTime,
229 rx: Receiver<SyncResult<StateSyncMessage<BlockHeader>>>,
230}
231
232impl SynchronizerStream {
233 fn new(
234 extractor_id: &ExtractorIdentity,
235 rx: Receiver<SyncResult<StateSyncMessage<BlockHeader>>>,
236 ) -> Self {
237 Self {
238 extractor_id: extractor_id.clone(),
239 state: SynchronizerState::Started,
240 error: None,
241 modify_ts: Local::now().naive_utc(),
242 rx,
243 }
244 }
245
246 async fn try_advance(
256 &mut self,
257 block_history: &BlockHistory,
258 block_time: std::time::Duration,
259 latency_buffer: std::time::Duration,
260 stale_threshold: std::time::Duration,
261 skip_wait: bool,
262 ) -> BlockSyncResult<Option<StateSyncMessage<BlockHeader>>> {
263 let extractor_id = self.extractor_id.clone();
264 let latest_block = block_history.latest();
265
266 match &self.state {
267 SynchronizerState::Started | SynchronizerState::Ended(_) => {
268 warn!(state=?&self.state, "Advancing Synchronizer in this state not supported!");
269 Ok(None)
270 }
271 SynchronizerState::Advanced(b) => {
272 let future_block = b.clone();
273 self.transition(future_block, block_history, stale_threshold)?;
275 Ok(None)
276 }
277 SynchronizerState::Ready(previous_block) => {
278 self.try_recv_next_expected(
280 block_time + latency_buffer,
281 block_history,
282 previous_block.clone(),
283 stale_threshold,
284 )
285 .await
286 }
287 SynchronizerState::Delayed(old_block) => {
288 debug!(
290 %old_block,
291 latest_block=opt(&latest_block),
292 %extractor_id,
293 "Trying to catch up to latest block"
294 );
295 let timeout =
296 if skip_wait { std::time::Duration::ZERO } else { block_time + latency_buffer };
297 self.try_catch_up(block_history, timeout, stale_threshold)
298 .await
299 }
300 SynchronizerState::Stale(old_block) => {
301 debug!(
303 %old_block,
304 latest_block=opt(&latest_block),
305 %extractor_id,
306 "Trying to catch up stale synchronizer to latest block"
307 );
308 let timeout = if skip_wait { std::time::Duration::ZERO } else { block_time };
309 self.try_catch_up(block_history, timeout, stale_threshold)
310 .await
311 }
312 }
313 }
314
315 async fn try_recv_next_expected(
323 &mut self,
324 max_wait: std::time::Duration,
325 block_history: &BlockHistory,
326 previous_block: BlockHeader,
327 stale_threshold: std::time::Duration,
328 ) -> BlockSyncResult<Option<StateSyncMessage<BlockHeader>>> {
329 let extractor_id = self.extractor_id.clone();
330 match timeout(max_wait, self.rx.recv()).await {
331 Ok(Some(Ok(msg))) => {
332 self.transition(msg.header.clone(), block_history, stale_threshold)?;
333 Ok(Some(msg))
334 }
335 Ok(Some(Err(e))) => {
336 self.mark_errored(e);
338 Ok(None)
339 }
340 Ok(None) => {
341 warn!(
344 %extractor_id,
345 "Tried to poll from closed synchronizer.",
346 );
347 self.mark_closed();
348 Ok(None)
349 }
350 Err(_) => {
351 debug!(%extractor_id, %previous_block, "No block received within time limit.");
353 self.state = SynchronizerState::Delayed(previous_block.clone());
356 self.modify_ts = Local::now().naive_utc();
357 Ok(None)
358 }
359 }
360 }
361
362 async fn try_catch_up(
368 &mut self,
369 block_history: &BlockHistory,
370 max_wait: std::time::Duration,
371 stale_threshold: std::time::Duration,
372 ) -> BlockSyncResult<Option<StateSyncMessage<BlockHeader>>> {
373 let mut results = Vec::new();
374 let extractor_id = self.extractor_id.clone();
375
376 let deadline = std::time::Instant::now() + max_wait;
378
379 while std::time::Instant::now() < deadline {
380 match timeout(
381 deadline.saturating_duration_since(std::time::Instant::now()),
382 self.rx.recv(),
383 )
384 .await
385 {
386 Ok(Some(Ok(msg))) => {
387 debug!(%extractor_id, block=%msg.header, "Received new message during catch-up");
388 let block_pos = block_history.determine_block_position(&msg.header)?;
389 results.push(msg);
390 if matches!(block_pos, BlockPosition::NextExpected | BlockPosition::NextPartial)
391 {
392 break;
393 }
394 }
395 Ok(Some(Err(e))) => {
396 self.mark_errored(e);
398 return Ok(None);
399 }
400 Ok(None) => {
401 warn!(
404 %extractor_id,
405 "Tried to poll from closed synchronizer during catch up.",
406 );
407 self.mark_closed();
408 return Ok(None);
409 }
410 Err(_) => {
411 debug!(%extractor_id, "Timed out waiting for catch-up");
412 break;
413 }
414 }
415 }
416
417 let merged = results
418 .into_iter()
419 .reduce(|l, r| l.merge(r));
420
421 if let Some(msg) = merged {
422 debug!(%extractor_id, "Delayed extractor made progress!");
424 self.transition(msg.header.clone(), block_history, stale_threshold)?;
425 Ok(Some(msg))
426 } else {
427 self.check_and_transition_to_stale_if_needed(stale_threshold, None)?;
429 Ok(None)
430 }
431 }
432
433 fn check_and_transition_to_stale_if_needed(
435 &mut self,
436 stale_threshold: std::time::Duration,
437 fallback_header: Option<BlockHeader>,
438 ) -> Result<bool, BlockSynchronizerError> {
439 let now = Local::now().naive_utc();
440 let wait_duration = now.signed_duration_since(self.modify_ts);
441 let stale_threshold_chrono = ChronoDuration::from_std(stale_threshold)
442 .map_err(|e| BlockSynchronizerError::DurationConversionError(e.to_string()))?;
443
444 if wait_duration > stale_threshold_chrono {
445 let header_to_use = match (&self.state, fallback_header) {
446 (SynchronizerState::Ready(h), _) |
447 (SynchronizerState::Delayed(h), _) |
448 (SynchronizerState::Stale(h), _) => h.clone(),
449 (_, Some(h)) => h,
450 _ => BlockHeader::default(),
451 };
452
453 warn!(
454 extractor_id=%self.extractor_id,
455 last_message_at=?self.modify_ts,
456 "SynchronizerStream transition to stale due to timeout."
457 );
458 self.state = SynchronizerState::Stale(header_to_use);
459 self.modify_ts = now;
460 Ok(true)
461 } else {
462 Ok(false)
463 }
464 }
465
466 fn transition(
473 &mut self,
474 latest_retrieved: BlockHeader,
475 block_history: &BlockHistory,
476 stale_threshold: std::time::Duration,
477 ) -> Result<(), BlockSynchronizerError> {
478 let extractor_id = self.extractor_id.clone();
479 let last_message_at = self.modify_ts;
480 let block = &latest_retrieved;
481
482 match block_history.determine_block_position(&latest_retrieved)? {
483 BlockPosition::NextExpected | BlockPosition::NextPartial => {
484 self.state = SynchronizerState::Ready(latest_retrieved.clone());
485 trace!(
486 next = %latest_retrieved,
487 extractor = %extractor_id,
488 "SynchronizerStream transition to next expected"
489 )
490 }
491 BlockPosition::Latest | BlockPosition::Delayed => {
492 if !self.check_and_transition_to_stale_if_needed(
493 stale_threshold,
494 Some(latest_retrieved.clone()),
495 )? {
496 warn!(
497 %extractor_id,
498 ?last_message_at,
499 %block,
500 "SynchronizerStream transition transition to delayed."
501 );
502 self.state = SynchronizerState::Delayed(latest_retrieved.clone());
503 }
504 }
505 BlockPosition::Advanced => {
506 info!(
507 %extractor_id,
508 ?last_message_at,
509 latest = opt(&block_history.latest()),
510 %block,
511 "SynchronizerStream transition to advanced."
512 );
513 self.state = SynchronizerState::Advanced(latest_retrieved.clone());
514 }
515 }
516 self.modify_ts = Local::now().naive_utc();
517 Ok(())
518 }
519
520 fn mark_errored(&mut self, error: SynchronizerError) {
525 self.state = SynchronizerState::Ended(error.to_string());
526 self.modify_ts = Local::now().naive_utc();
527 self.error = Some(error);
528 }
529
530 fn mark_closed(&mut self) {
536 if !matches!(self.state, SynchronizerState::Ended(_)) {
537 self.state = SynchronizerState::Ended("Closed".to_string());
538 self.modify_ts = Local::now().naive_utc();
539 }
540 }
541
542 fn mark_stale(&mut self, header: &BlockHeader) {
544 self.state = SynchronizerState::Stale(header.clone());
545 self.modify_ts = Local::now().naive_utc();
546 }
547
548 fn mark_ready(&mut self, header: &BlockHeader) {
550 self.state = SynchronizerState::Ready(header.clone());
551 self.modify_ts = Local::now().naive_utc();
552 }
553
554 fn has_ended(&self) -> bool {
555 matches!(self.state, SynchronizerState::Ended(_))
556 }
557
558 fn is_stale(&self) -> bool {
559 matches!(self.state, SynchronizerState::Stale(_))
560 }
561
562 fn is_advanced(&self) -> bool {
563 matches!(self.state, SynchronizerState::Advanced(_))
564 }
565
566 fn get_current_header(&self) -> Option<&BlockHeader> {
570 match &self.state {
571 SynchronizerState::Ready(b) |
572 SynchronizerState::Delayed(b) |
573 SynchronizerState::Advanced(b) => Some(b),
574 _ => None,
575 }
576 }
577}
578
579#[derive(Debug, PartialEq, Clone)]
580pub struct FeedMessage<H = BlockHeader>
581where
582 H: HeaderLike,
583{
584 pub state_msgs: HashMap<String, StateSyncMessage<H>>,
585 pub sync_states: HashMap<String, SynchronizerState>,
586}
587
588impl<H> FeedMessage<H>
589where
590 H: HeaderLike,
591{
592 fn new(
593 state_msgs: HashMap<String, StateSyncMessage<H>>,
594 sync_states: HashMap<String, SynchronizerState>,
595 ) -> Self {
596 Self { state_msgs, sync_states }
597 }
598}
599
600impl<S> BlockSynchronizer<S>
601where
602 S: StateSynchronizer,
603{
604 pub fn new(
605 block_time: std::time::Duration,
606 latency_buffer: std::time::Duration,
607 max_missed_blocks: u64,
608 ) -> Self {
609 Self {
610 synchronizers: None,
611 max_messages: None,
612 block_time,
613 latency_buffer,
614 startup_timeout: block_time.mul_f64(max_missed_blocks as f64),
615 max_missed_blocks,
616 }
617 }
618
619 pub fn max_messages(&mut self, val: usize) {
625 self.max_messages = Some(val);
626 }
627
628 pub fn startup_timeout(mut self, val: Duration) {
632 self.startup_timeout = val;
633 }
634
635 pub fn register_synchronizer(mut self, id: ExtractorIdentity, synchronizer: S) -> Self {
636 let mut registered = self.synchronizers.unwrap_or_default();
637 registered.insert(id, synchronizer);
638 self.synchronizers = Some(registered);
639 self
640 }
641
642 #[cfg(test)]
643 pub fn with_short_timeouts() -> Self {
644 Self::new(Duration::from_millis(10), Duration::from_millis(10), 3)
645 }
646
647 async fn cleanup_synchronizers(
650 mut state_sync_tasks: FuturesUnordered<JoinHandle<()>>,
651 sync_close_senders: Vec<oneshot::Sender<()>>,
652 ) {
653 for close_sender in sync_close_senders {
655 let _ = close_sender.send(());
656 }
657
658 let mut completed_tasks = 0;
660 while let Ok(Some(_)) = timeout(Duration::from_secs(5), state_sync_tasks.next()).await {
661 completed_tasks += 1;
662 }
663
664 let remaining_tasks = state_sync_tasks.len();
666 if remaining_tasks > 0 {
667 warn!(
668 completed = completed_tasks,
669 timed_out = remaining_tasks,
670 "Some synchronizers timed out during cleanup and may not have shut down cleanly"
671 );
672 }
673 }
674
675 pub async fn run(
680 mut self,
681 ) -> BlockSyncResult<(JoinHandle<()>, Receiver<BlockSyncResult<FeedMessage<BlockHeader>>>)>
682 {
683 trace!("Starting BlockSynchronizer...");
684 let state_sync_tasks = FuturesUnordered::new();
685 let mut synchronizers = self
686 .synchronizers
687 .take()
688 .ok_or(BlockSynchronizerError::NoSynchronizers)?;
689 let init_results = join_all(synchronizers.iter_mut().map(|(id, s)| {
692 s.initialize()
693 .map(|res| (id.clone(), res))
694 }))
695 .await;
696 let mut to_skip = Vec::new();
697 for (extractor_id, result) in init_results {
698 match result {
699 Ok(()) => {}
700 Err(SynchronizerError::RPCError(crate::rpc::RPCError::UnknownExtractor(
701 reason,
702 ))) => {
703 warn!(%extractor_id, %reason, "Extractor not recognised by server, skipping");
704 to_skip.push(extractor_id);
705 }
706 Err(e) => {
707 return Err(BlockSynchronizerError::InitializationError {
708 extractor: extractor_id,
709 source: e,
710 })
711 }
712 }
713 }
714 for id in &to_skip {
715 synchronizers.remove(id);
716 }
717 if synchronizers.is_empty() {
718 return Err(BlockSynchronizerError::NoSynchronizers);
719 }
720
721 let mut sync_streams = Vec::with_capacity(synchronizers.len());
722 let mut sync_close_senders = Vec::new();
723 for (extractor_id, synchronizer) in synchronizers.drain() {
724 let (handle, rx) = synchronizer.start().await;
725 let (join_handle, close_sender) = handle.split();
726 state_sync_tasks.push(join_handle);
727 sync_close_senders.push(close_sender);
728
729 sync_streams.push(SynchronizerStream::new(&extractor_id, rx));
730 }
731
732 debug!("Waiting for initial synchronizer messages...");
735 let mut startup_futures = Vec::new();
736 for synchronizer in sync_streams.iter_mut() {
737 let fut = async {
738 let res = timeout(self.startup_timeout, synchronizer.rx.recv()).await;
739 (synchronizer, res)
740 };
741 startup_futures.push(fut);
742 }
743
744 let mut ready_sync_msgs = HashMap::new();
745 let initial_headers = join_all(startup_futures)
746 .await
747 .into_iter()
748 .filter_map(|(synchronizer, res)| {
749 let extractor_id = synchronizer.extractor_id.clone();
750 match res {
751 Ok(Some(Ok(msg))) => {
752 debug!(%extractor_id, height=?&msg.header.number, "Synchronizer started successfully!");
753 synchronizer.mark_ready(&msg.header);
755 let header = msg.header.clone();
756 ready_sync_msgs.insert(extractor_id.name.clone(), msg);
757 Some(header)
758 }
759 Ok(Some(Err(e))) => {
760 synchronizer.mark_errored(e);
761 None
762 }
763 Ok(None) => {
764 warn!(%extractor_id, "Synchronizer closed during startup");
769 synchronizer.mark_closed();
770 None
771 }
772 Err(_) => {
773 warn!(%extractor_id, "Timed out waiting for first message");
775 synchronizer.mark_stale(&BlockHeader::default());
776 None
777 }
778 }
779 })
780 .collect::<HashSet<_>>() .into_iter()
782 .collect::<Vec<_>>();
783
784 Self::require_active_stream(&sync_streams)?;
786 let mut block_history = BlockHistory::new(initial_headers, 15)?;
787 let start_header = block_history
791 .latest()
792 .ok_or(BlockHistoryError::EmptyHistory)?;
793 info!(
794 start_block=%start_header,
795 n_healthy=ready_sync_msgs.len(),
796 n_total=sync_streams.len(),
797 "Block synchronization started successfully!"
798 );
799
800 for stream in sync_streams.iter_mut() {
803 if let SynchronizerState::Ready(header) = stream.state.clone() {
804 if header.number < start_header.number {
805 debug!(
806 extractor_id=%stream.extractor_id,
807 synchronizer_block=header.number,
808 current_block=start_header.number,
809 "Marking synchronizer as delayed during initialization"
810 );
811 stream.state = SynchronizerState::Delayed(header);
812 }
813 }
814 }
815
816 let (sync_tx, sync_rx) = mpsc::channel(30);
817 let main_loop_jh = tokio::spawn(async move {
818 let mut n_iter = 1;
819 loop {
820 let msg = FeedMessage::new(
822 std::mem::take(&mut ready_sync_msgs),
823 sync_streams
824 .iter()
825 .map(|stream| (stream.extractor_id.name.to_string(), stream.state.clone()))
826 .collect(),
827 );
828 if sync_tx.send(Ok(msg)).await.is_err() {
829 info!("Receiver closed, block synchronizer terminating..");
830 return;
831 };
832
833 if let Some(max_messages) = self.max_messages {
835 if n_iter >= max_messages {
836 info!(max_messages, "StreamEnd");
837 return;
838 }
839 }
840 n_iter += 1;
841
842 let res = self
843 .handle_next_message(
844 &mut sync_streams,
845 &mut ready_sync_msgs,
846 &mut block_history,
847 )
848 .await;
849
850 if let Err(e) = res {
851 let _ = sync_tx.send(Err(e)).await;
853 return;
854 }
855 }
856 });
857
858 let nanny_jh = tokio::spawn(async move {
863 let _ = main_loop_jh.await.map_err(|e| {
865 if e.is_panic() {
866 error!("BlockSynchornizer main loop panicked: {e}")
867 }
868 });
869 debug!("Main loop exited. Closing synchronizers");
870 Self::cleanup_synchronizers(state_sync_tasks, sync_close_senders).await;
871 debug!("Shutdown complete");
872 });
873 Ok((nanny_jh, sync_rx))
874 }
875
876 async fn handle_next_message(
881 &self,
882 sync_streams: &mut [SynchronizerStream],
883 ready_sync_msgs: &mut HashMap<String, StateSyncMessage<BlockHeader>>,
884 block_history: &mut BlockHistory,
885 ) -> BlockSyncResult<()> {
886 let any_advanced = sync_streams
890 .iter()
891 .any(SynchronizerStream::is_advanced);
892 let mut recv_futures = Vec::new();
893 for stream in sync_streams.iter_mut() {
894 if stream.has_ended() {
897 continue;
898 }
899 recv_futures.push(async {
909 let res = stream
910 .try_advance(
911 block_history,
912 self.block_time,
913 self.latency_buffer,
914 self.block_time
915 .mul_f64(self.max_missed_blocks as f64),
916 any_advanced,
917 )
918 .await?;
919 Ok::<_, BlockSynchronizerError>(
920 res.map(|msg| (stream.extractor_id.name.clone(), msg)),
921 )
922 });
923 }
924 ready_sync_msgs.extend(
925 join_all(recv_futures)
926 .await
927 .into_iter()
928 .collect::<Result<Vec<_>, _>>()?
929 .into_iter()
930 .flatten(),
931 );
932
933 Self::check_streams(sync_streams)?;
936
937 if sync_streams
940 .iter()
941 .any(SynchronizerStream::is_advanced)
942 {
943 *block_history = Self::reinit_block_history(sync_streams, block_history)?;
944 } else if let Some(header) = sync_streams
945 .iter()
946 .filter_map(SynchronizerStream::get_current_header)
947 .max_by_key(|b| b.number)
948 {
949 block_history.push(header.clone())?;
950 }
951 Ok(())
954 }
955
956 fn reinit_block_history(
965 sync_streams: &mut [SynchronizerStream],
966 block_history: &mut BlockHistory,
967 ) -> Result<BlockHistory, BlockSynchronizerError> {
968 let previous = block_history
969 .latest()
970 .ok_or(BlockHistoryError::EmptyHistory)?;
972 let blocks = sync_streams
973 .iter()
974 .filter_map(SynchronizerStream::get_current_header)
975 .cloned()
976 .collect();
977 let new_block_history = BlockHistory::new(blocks, 10)?;
978 let latest = new_block_history
979 .latest()
980 .ok_or(BlockHistoryError::EmptyHistory)?;
982 info!(
983 %previous,
984 %latest,
985 "Advanced synchronizer detected. Reinitialized block history."
986 );
987 sync_streams
988 .iter_mut()
989 .for_each(|stream| {
990 if let Some(header) = stream.get_current_header() {
993 if header.number < latest.number {
994 stream.state = SynchronizerState::Delayed(header.clone());
995 } else if header.number == latest.number {
996 stream.state = SynchronizerState::Ready(header.clone());
997 }
998 }
999 });
1000 Ok(new_block_history)
1001 }
1002
1003 fn require_active_stream(sync_streams: &[SynchronizerStream]) -> BlockSyncResult<()> {
1008 if sync_streams
1009 .iter()
1010 .any(|s| !s.has_ended() && !s.is_stale())
1011 {
1012 return Ok(());
1013 }
1014 let reason: Vec<String> = sync_streams
1015 .iter()
1016 .map(|s| format!("{} reported as {} at {}", s.extractor_id, s.state, s.modify_ts))
1017 .collect();
1018 Err(BlockSynchronizerError::NoReadySynchronizers(reason.join(", ")))
1019 }
1020
1021 fn check_streams(sync_streams: &[SynchronizerStream]) -> BlockSyncResult<()> {
1031 let mut has_any_ended = false;
1032 let mut latest_ended_stream: Option<&SynchronizerStream> = None;
1033
1034 for stream in sync_streams.iter() {
1035 if !stream.has_ended() && !stream.is_stale() {
1037 return Ok(());
1038 }
1039
1040 if stream.has_ended() {
1041 has_any_ended = true;
1042 if latest_ended_stream.is_none() ||
1043 stream.modify_ts >
1044 latest_ended_stream
1045 .as_ref()
1046 .unwrap()
1047 .modify_ts
1048 {
1049 latest_ended_stream = Some(stream);
1050 }
1051 }
1052 }
1053
1054 if !has_any_ended {
1057 return Ok(());
1058 }
1059
1060 let last_error_reason = if let Some(stream) = latest_ended_stream {
1062 if let Some(err) = &stream.error {
1063 format!("Synchronizer for {} errored with: {err}", stream.extractor_id)
1064 } else {
1065 format!("Synchronizer for {} became: {}", stream.extractor_id, stream.state)
1066 }
1067 } else {
1068 return Err(BlockSynchronizerError::NoSynchronizers);
1069 };
1070
1071 let mut reason = vec![last_error_reason];
1072
1073 sync_streams.iter().for_each(|stream| {
1074 reason.push(format!(
1075 "{} reported as {} at {}",
1076 stream.extractor_id, stream.state, stream.modify_ts
1077 ))
1078 });
1079
1080 Err(BlockSynchronizerError::NoReadySynchronizers(reason.join(", ")))
1081 }
1082}
1083
1084#[cfg(test)]
1085mod tests {
1086 use std::sync::Arc;
1087
1088 use async_trait::async_trait;
1089 use test_log::test;
1090 use tokio::sync::{oneshot, Mutex};
1091 use tycho_common::models::Chain;
1092
1093 use super::*;
1094 use crate::feed::synchronizer::{SyncResult, SynchronizerTaskHandle};
1095
1096 #[derive(Clone, Debug)]
1097 enum MockBehavior {
1098 Normal, IgnoreClose, ExitImmediately, }
1102
1103 type HeaderReceiver = Receiver<SyncResult<StateSyncMessage<BlockHeader>>>;
1104
1105 #[derive(Clone)]
1106 struct MockStateSync {
1107 header_tx: mpsc::Sender<SyncResult<StateSyncMessage<BlockHeader>>>,
1108 header_rx: Arc<Mutex<Option<HeaderReceiver>>>,
1109 close_received: Arc<Mutex<bool>>,
1110 behavior: MockBehavior,
1111 close_tx: Arc<Mutex<Option<oneshot::Sender<()>>>>,
1113 }
1114
1115 impl MockStateSync {
1116 fn new() -> Self {
1117 Self::with_behavior(MockBehavior::Normal)
1118 }
1119
1120 fn with_behavior(behavior: MockBehavior) -> Self {
1121 let (tx, rx) = mpsc::channel(1);
1122 Self {
1123 header_tx: tx,
1124 header_rx: Arc::new(Mutex::new(Some(rx))),
1125 close_received: Arc::new(Mutex::new(false)),
1126 behavior,
1127 close_tx: Arc::new(Mutex::new(None)),
1128 }
1129 }
1130
1131 async fn was_close_received(&self) -> bool {
1132 *self.close_received.lock().await
1133 }
1134
1135 async fn send_header(&self, header: StateSyncMessage<BlockHeader>) -> Result<(), String> {
1136 self.header_tx
1137 .send(Ok(header))
1138 .await
1139 .map_err(|e| format!("sending header failed: {e}"))
1140 }
1141
1142 async fn trigger_close(&self) {
1144 if let Some(close_tx) = self.close_tx.lock().await.take() {
1145 let _ = close_tx.send(());
1146 }
1147 }
1148 }
1149
1150 #[async_trait]
1151 impl StateSynchronizer for MockStateSync {
1152 async fn initialize(&mut self) -> SyncResult<()> {
1153 Ok(())
1154 }
1155
1156 async fn start(
1157 mut self,
1158 ) -> (SynchronizerTaskHandle, Receiver<SyncResult<StateSyncMessage<BlockHeader>>>) {
1159 let block_rx = {
1160 let mut guard = self.header_rx.lock().await;
1161 guard
1162 .take()
1163 .expect("Block receiver was not set!")
1164 };
1165
1166 let (close_tx_for_handle, close_rx) = oneshot::channel();
1169 let (close_tx_for_test, close_rx_for_test) = oneshot::channel();
1170
1171 {
1173 let mut guard = self.close_tx.lock().await;
1174 *guard = Some(close_tx_for_test);
1175 }
1176
1177 let behavior = self.behavior.clone();
1178 let close_received_clone = self.close_received.clone();
1179 let tx = self.header_tx.clone();
1180
1181 let jh = tokio::spawn(async move {
1182 match behavior {
1183 MockBehavior::IgnoreClose => {
1184 loop {
1187 tokio::time::sleep(tokio::time::Duration::from_secs(1)).await;
1188 }
1189 }
1190 MockBehavior::ExitImmediately => {
1191 tx.send(SyncResult::Err(SynchronizerError::ConnectionError(
1193 "Simulated immediate task failure".to_string(),
1194 )))
1195 .await
1196 .unwrap();
1197 }
1198 MockBehavior::Normal => {
1199 let _ = tokio::select! {
1202 result = close_rx => result,
1203 result = close_rx_for_test => result,
1204 };
1205 let mut guard = close_received_clone.lock().await;
1206 *guard = true;
1207 }
1208 }
1209 });
1210
1211 let handle = SynchronizerTaskHandle::new(jh, close_tx_for_handle);
1212 (handle, block_rx)
1213 }
1214 }
1215
1216 fn header_message(block: u8) -> StateSyncMessage<BlockHeader> {
1217 StateSyncMessage {
1218 header: BlockHeader {
1219 number: block as u64,
1220 hash: Bytes::from(vec![block]),
1221 parent_hash: Bytes::from(vec![block - 1]),
1222 revert: false,
1223 timestamp: 1000,
1224 partial_block_index: None,
1225 },
1226 ..Default::default()
1227 }
1228 }
1229
1230 fn partial_header_message(block: u8, partial_idx: u32) -> StateSyncMessage<BlockHeader> {
1233 let hash_bytes =
1235 [(block as u64).to_be_bytes().as_slice(), partial_idx.to_be_bytes().as_slice()]
1236 .concat();
1237 StateSyncMessage {
1238 header: BlockHeader {
1239 number: block as u64,
1240 hash: Bytes::from(hash_bytes),
1241 parent_hash: Bytes::from(vec![block - 1]),
1242 revert: false,
1243 timestamp: 1000,
1244 partial_block_index: Some(partial_idx),
1245 },
1246 ..Default::default()
1247 }
1248 }
1249
1250 fn revert_header_message(block: u8) -> StateSyncMessage<BlockHeader> {
1251 StateSyncMessage {
1252 header: BlockHeader {
1253 number: block as u64,
1254 hash: Bytes::from(vec![block]),
1255 parent_hash: Bytes::from(vec![block - 1]),
1256 revert: true,
1257 timestamp: 1000,
1258 partial_block_index: None,
1259 },
1260 ..Default::default()
1261 }
1262 }
1263
1264 async fn receive_message(rx: &mut Receiver<BlockSyncResult<FeedMessage>>) -> FeedMessage {
1265 timeout(Duration::from_millis(100), rx.recv())
1266 .await
1267 .expect("Responds in time")
1268 .expect("Should receive first message")
1269 .expect("No error")
1270 }
1271
1272 async fn setup_block_sync(
1273 ) -> (MockStateSync, MockStateSync, JoinHandle<()>, Receiver<BlockSyncResult<FeedMessage>>)
1274 {
1275 setup_block_sync_with_behaviour(MockBehavior::Normal, MockBehavior::Normal).await
1276 }
1277
1278 async fn setup_block_sync_with_behaviour(
1280 v2_behavior: MockBehavior,
1281 v3_behavior: MockBehavior,
1282 ) -> (MockStateSync, MockStateSync, JoinHandle<()>, Receiver<BlockSyncResult<FeedMessage>>)
1283 {
1284 let v2_sync = MockStateSync::with_behavior(v2_behavior);
1285 let v3_sync = MockStateSync::with_behavior(v3_behavior);
1286
1287 let mut block_sync = BlockSynchronizer::new(
1289 Duration::from_millis(20), Duration::from_millis(10), 3, );
1293 block_sync.max_messages(10); let block_sync = block_sync
1296 .register_synchronizer(
1297 ExtractorIdentity { chain: Chain::Ethereum, name: "uniswap-v2".to_string() },
1298 v2_sync.clone(),
1299 )
1300 .register_synchronizer(
1301 ExtractorIdentity { chain: Chain::Ethereum, name: "uniswap-v3".to_string() },
1302 v3_sync.clone(),
1303 );
1304
1305 let block1_msg = header_message(1);
1307 let _ = v2_sync
1308 .send_header(block1_msg.clone())
1309 .await;
1310 let _ = v3_sync
1311 .send_header(block1_msg.clone())
1312 .await;
1313
1314 let (nanny_handle, mut rx) = block_sync
1316 .run()
1317 .await
1318 .expect("BlockSynchronizer failed to start");
1319
1320 let first_feed_msg = receive_message(&mut rx).await;
1321 assert_eq!(first_feed_msg.state_msgs.len(), 2);
1322 assert!(matches!(
1323 first_feed_msg
1324 .sync_states
1325 .get("uniswap-v2")
1326 .unwrap(),
1327 SynchronizerState::Ready(_)
1328 ));
1329 assert!(matches!(
1330 first_feed_msg
1331 .sync_states
1332 .get("uniswap-v3")
1333 .unwrap(),
1334 SynchronizerState::Ready(_)
1335 ));
1336
1337 (v2_sync, v3_sync, nanny_handle, rx)
1338 }
1339
1340 async fn shutdown_block_synchronizer(
1341 nanny_handle: JoinHandle<()>,
1342 rx: Receiver<BlockSyncResult<FeedMessage>>,
1343 ) {
1344 drop(rx);
1348 timeout(Duration::from_secs(2), nanny_handle)
1349 .await
1350 .expect("Nanny failed to exit within time")
1351 .expect("Nanny panicked");
1352 }
1353
1354 async fn send_and_assert_ready(
1356 sync: &MockStateSync,
1357 sync_name: &str,
1358 rx: &mut Receiver<BlockSyncResult<FeedMessage>>,
1359 msg: StateSyncMessage<BlockHeader>,
1360 expected_block: u64,
1361 expected_partial: Option<u32>,
1362 ) {
1363 sync.send_header(msg)
1364 .await
1365 .expect("send failed");
1366 let feed_msg = receive_message(rx).await;
1367 let state = feed_msg
1368 .sync_states
1369 .get(sync_name)
1370 .unwrap();
1371 match state {
1372 SynchronizerState::Ready(h) => {
1373 assert_eq!(h.number, expected_block, "wrong block number");
1374 assert_eq!(h.partial_block_index, expected_partial, "wrong partial index");
1375 }
1376 other => panic!("expected Ready, got {:?}", other),
1377 }
1378 }
1379
1380 #[test(tokio::test)]
1381 async fn test_two_ready_synchronizers() {
1382 let (v2_sync, v3_sync, nanny_handle, mut rx) = setup_block_sync().await;
1383
1384 let second_msg = header_message(2);
1385 v2_sync
1386 .send_header(second_msg.clone())
1387 .await
1388 .expect("send_header failed");
1389 v3_sync
1390 .send_header(second_msg.clone())
1391 .await
1392 .expect("send_header failed");
1393 let second_feed_msg = receive_message(&mut rx).await;
1394
1395 let exp2 = FeedMessage {
1396 state_msgs: [
1397 ("uniswap-v2".to_string(), second_msg.clone()),
1398 ("uniswap-v3".to_string(), second_msg.clone()),
1399 ]
1400 .into_iter()
1401 .collect(),
1402 sync_states: [
1403 ("uniswap-v3".to_string(), SynchronizerState::Ready(second_msg.header.clone())),
1404 ("uniswap-v2".to_string(), SynchronizerState::Ready(second_msg.header.clone())),
1405 ]
1406 .into_iter()
1407 .collect(),
1408 };
1409 assert_eq!(second_feed_msg, exp2);
1410
1411 shutdown_block_synchronizer(nanny_handle, rx).await;
1412 }
1413
1414 #[test(tokio::test)]
1415 async fn test_delayed_synchronizer_catches_up() {
1416 let (v2_sync, v3_sync, nanny_handle, mut rx) = setup_block_sync().await;
1417
1418 let block2_msg = header_message(2);
1420 v2_sync
1421 .send_header(block2_msg.clone())
1422 .await
1423 .expect("send_header failed");
1424
1425 let second_feed_msg = receive_message(&mut rx).await;
1427 debug!("Consumed second message for v2");
1428
1429 assert!(second_feed_msg
1430 .state_msgs
1431 .contains_key("uniswap-v2"));
1432 assert!(matches!(
1433 second_feed_msg.sync_states.get("uniswap-v2").unwrap(),
1434 SynchronizerState::Ready(header) if header.number == 2
1435 ));
1436 assert!(!second_feed_msg
1437 .state_msgs
1438 .contains_key("uniswap-v3"));
1439 assert!(matches!(
1440 second_feed_msg.sync_states.get("uniswap-v3").unwrap(),
1441 SynchronizerState::Delayed(header) if header.number == 1
1442 ));
1443
1444 v3_sync
1446 .send_header(block2_msg.clone())
1447 .await
1448 .expect("send_header failed");
1449
1450 let block3_msg = header_message(3);
1452 v2_sync
1453 .send_header(block3_msg.clone())
1454 .await
1455 .expect("send_header failed");
1456 v3_sync
1457 .send_header(block3_msg)
1458 .await
1459 .expect("send_header failed");
1460
1461 let mut third_feed_msg = receive_message(&mut rx).await;
1464
1465 if !third_feed_msg
1468 .state_msgs
1469 .contains_key("uniswap-v2")
1470 {
1471 third_feed_msg = rx
1472 .recv()
1473 .await
1474 .expect("header channel was closed")
1475 .expect("no error");
1476 }
1477 assert!(third_feed_msg
1478 .state_msgs
1479 .contains_key("uniswap-v2"));
1480 assert!(third_feed_msg
1481 .state_msgs
1482 .contains_key("uniswap-v3"));
1483 assert!(matches!(
1484 third_feed_msg.sync_states.get("uniswap-v2").unwrap(),
1485 SynchronizerState::Ready(header) if header.number == 3
1486 ));
1487 assert!(matches!(
1488 third_feed_msg.sync_states.get("uniswap-v3").unwrap(),
1489 SynchronizerState::Ready(header) if header.number == 3
1490 ));
1491
1492 shutdown_block_synchronizer(nanny_handle, rx).await;
1493 }
1494
1495 #[test(tokio::test)]
1496 async fn test_different_start_blocks() {
1497 let v2_sync = MockStateSync::new();
1498 let v3_sync = MockStateSync::new();
1499 let block_sync = BlockSynchronizer::with_short_timeouts()
1500 .register_synchronizer(
1501 ExtractorIdentity { chain: Chain::Ethereum, name: "uniswap-v2".to_string() },
1502 v2_sync.clone(),
1503 )
1504 .register_synchronizer(
1505 ExtractorIdentity { chain: Chain::Ethereum, name: "uniswap-v3".to_string() },
1506 v3_sync.clone(),
1507 );
1508
1509 let block1_msg = header_message(1);
1511 let block2_msg = header_message(2);
1512
1513 let _ = v2_sync
1514 .send_header(block1_msg.clone())
1515 .await;
1516 v3_sync
1517 .send_header(block2_msg.clone())
1518 .await
1519 .expect("send_header failed");
1520
1521 let (jh, mut rx) = block_sync
1523 .run()
1524 .await
1525 .expect("BlockSynchronizer failed to start.");
1526
1527 let first_feed_msg = receive_message(&mut rx).await;
1529 assert!(matches!(
1530 first_feed_msg.sync_states.get("uniswap-v2").unwrap(),
1531 SynchronizerState::Delayed(header) if header.number == 1
1532 ));
1533 assert!(matches!(
1534 first_feed_msg.sync_states.get("uniswap-v3").unwrap(),
1535 SynchronizerState::Ready(header) if header.number == 2
1536 ));
1537
1538 v2_sync
1540 .send_header(block2_msg.clone())
1541 .await
1542 .expect("send_header failed");
1543
1544 let block3_msg = header_message(3);
1546 let _ = v2_sync
1547 .send_header(block3_msg.clone())
1548 .await;
1549 v3_sync
1550 .send_header(block3_msg.clone())
1551 .await
1552 .expect("send_header failed");
1553
1554 let second_feed_msg = receive_message(&mut rx).await;
1556 assert_eq!(second_feed_msg.state_msgs.len(), 2);
1557 assert!(matches!(
1558 second_feed_msg.sync_states.get("uniswap-v2").unwrap(),
1559 SynchronizerState::Ready(header) if header.number == 3
1560 ));
1561 assert!(matches!(
1562 second_feed_msg.sync_states.get("uniswap-v3").unwrap(),
1563 SynchronizerState::Ready(header) if header.number == 3
1564 ));
1565
1566 shutdown_block_synchronizer(jh, rx).await;
1567 }
1568
1569 #[test(tokio::test)]
1570 async fn test_synchronizer_fails_other_goes_stale() {
1571 let (_v2_sync, v3_sync, nanny_handle, mut sync_rx) =
1572 setup_block_sync_with_behaviour(MockBehavior::ExitImmediately, MockBehavior::Normal)
1573 .await;
1574
1575 let mut error_reported = false;
1576 for _ in 0..3 {
1577 if let Some(msg) = sync_rx.recv().await {
1578 match msg {
1579 Err(_) => error_reported = true,
1580 Ok(msg) => {
1581 assert!(matches!(
1582 msg.sync_states
1583 .get("uniswap-v3")
1584 .unwrap(),
1585 SynchronizerState::Delayed(_)
1586 ));
1587 assert!(matches!(
1588 msg.sync_states
1589 .get("uniswap-v2")
1590 .unwrap(),
1591 SynchronizerState::Ended(_)
1592 ));
1593 }
1594 }
1595 }
1596 }
1597 assert!(error_reported, "BlockSynchronizer did not report final error");
1598
1599 let result = timeout(Duration::from_secs(2), nanny_handle).await;
1601 assert!(result.is_ok(), "Nanny should complete when synchronizer task exits");
1602
1603 assert!(
1605 v3_sync.was_close_received().await,
1606 "v3_sync should have received close signal during cleanup"
1607 );
1608 }
1609
1610 #[test(tokio::test)]
1611 async fn test_cleanup_timeout_warning() {
1612 let (_v2_sync, _v3_sync, nanny_handle, _rx) = setup_block_sync_with_behaviour(
1615 MockBehavior::ExitImmediately,
1616 MockBehavior::IgnoreClose,
1617 )
1618 .await;
1619
1620 let result = timeout(Duration::from_secs(10), nanny_handle).await;
1622 assert!(
1623 result.is_ok(),
1624 "Nanny should complete even when some synchronizers timeout during cleanup"
1625 );
1626
1627 }
1631
1632 #[test(tokio::test)]
1633 async fn test_one_synchronizer_goes_stale_while_other_works() {
1634 let (_v2_sync, v3_sync, nanny_handle, mut rx) = setup_block_sync().await;
1636
1637 let block2_msg = header_message(2);
1639 let _ = v3_sync
1640 .send_header(block2_msg.clone())
1641 .await;
1642 let second_feed_msg = receive_message(&mut rx).await;
1646 assert!(second_feed_msg
1647 .state_msgs
1648 .contains_key("uniswap-v3"));
1649 assert!(!second_feed_msg
1650 .state_msgs
1651 .contains_key("uniswap-v2"));
1652 assert!(matches!(
1653 second_feed_msg
1654 .sync_states
1655 .get("uniswap-v3")
1656 .unwrap(),
1657 SynchronizerState::Ready(_)
1658 ));
1659 if let Some(v2_state) = second_feed_msg
1661 .sync_states
1662 .get("uniswap-v2")
1663 {
1664 if matches!(v2_state, SynchronizerState::Delayed(_)) {
1665 assert!(
1667 !nanny_handle.is_finished(),
1668 "Nanny should still be running when synchronizer is delayed (not stale yet)"
1669 );
1670 }
1671 }
1672
1673 tokio::time::sleep(Duration::from_millis(15)).await;
1675
1676 let block3_msg = header_message(3);
1678 let _ = v3_sync
1679 .send_header(block3_msg.clone())
1680 .await;
1681
1682 tokio::time::sleep(Duration::from_millis(40)).await;
1683
1684 let mut stale_found = false;
1685 for _ in 0..2 {
1686 if let Some(Ok(msg)) = rx.recv().await {
1687 if let Some(SynchronizerState::Stale(_)) = msg.sync_states.get("uniswap-v2") {
1688 stale_found = true;
1689 }
1690 }
1691 }
1692 assert!(stale_found, "v2 synchronizer should be stale");
1693
1694 shutdown_block_synchronizer(nanny_handle, rx).await;
1695 }
1696
1697 #[test(tokio::test)]
1698 async fn test_all_synchronizers_stale_loop_continues() {
1699 let (v2_sync, v3_sync, nanny_handle, mut rx) = setup_block_sync().await;
1702
1703 let mut seen_delayed = false;
1705 let mut seen_stale = false;
1706 let start_time = tokio::time::Instant::now();
1707
1708 while let Ok(Some(Ok(msg))) =
1709 tokio::time::timeout(Duration::from_millis(50), rx.recv()).await
1710 {
1711 let v2_state = msg.sync_states.get("uniswap-v2");
1712 let v3_state = msg.sync_states.get("uniswap-v3");
1713
1714 if !seen_delayed &&
1715 (matches!(v2_state, Some(SynchronizerState::Delayed(_))) ||
1716 matches!(v3_state, Some(SynchronizerState::Delayed(_))))
1717 {
1718 seen_delayed = true;
1719 assert!(
1720 !nanny_handle.is_finished(),
1721 "Nanny must still run when synchronizers are Delayed"
1722 );
1723 }
1724
1725 if matches!(v2_state, Some(SynchronizerState::Stale(_))) &&
1726 matches!(v3_state, Some(SynchronizerState::Stale(_)))
1727 {
1728 seen_stale = true;
1729 assert!(
1730 !nanny_handle.is_finished(),
1731 "Main loop must not exit when all synchronizers are Stale (awaiting recovery)"
1732 );
1733 break;
1734 }
1735
1736 if start_time.elapsed() > Duration::from_millis(500) {
1737 break;
1738 }
1739 }
1740
1741 assert!(seen_delayed, "Synchronizers should transition through Delayed first");
1742 assert!(seen_stale, "Both synchronizers should reach Stale state");
1743
1744 v2_sync.trigger_close().await;
1749 v3_sync.trigger_close().await;
1750 drop(v2_sync);
1751 drop(v3_sync);
1752
1753 let mut error_reported = false;
1755 while let Some(msg) = rx.recv().await {
1756 if msg.is_err() {
1757 error_reported = true;
1758 }
1759 }
1760 assert!(error_reported, "Expected an error after all synchronizers ended");
1761
1762 let nanny_result = timeout(Duration::from_secs(2), nanny_handle).await;
1763 assert!(nanny_result.is_ok(), "Nanny should complete after all synchronizers ended");
1764 }
1765
1766 #[test(tokio::test)]
1767 async fn test_all_synchronizers_recover_after_going_stale() {
1768 let v2_sync = MockStateSync::new();
1771 let v3_sync = MockStateSync::new();
1772 let block_sync =
1773 BlockSynchronizer::new(Duration::from_millis(20), Duration::from_millis(10), 3)
1774 .register_synchronizer(
1775 ExtractorIdentity { chain: Chain::Ethereum, name: "uniswap-v2".to_string() },
1776 v2_sync.clone(),
1777 )
1778 .register_synchronizer(
1779 ExtractorIdentity { chain: Chain::Ethereum, name: "uniswap-v3".to_string() },
1780 v3_sync.clone(),
1781 );
1782
1783 v2_sync
1784 .send_header(header_message(1))
1785 .await
1786 .unwrap();
1787 v3_sync
1788 .send_header(header_message(1))
1789 .await
1790 .unwrap();
1791
1792 let (nanny_handle, mut rx) = block_sync
1793 .run()
1794 .await
1795 .expect("BlockSynchronizer start failed");
1796
1797 let first_msg = receive_message(&mut rx).await;
1798 assert!(matches!(
1799 first_msg.sync_states.get("uniswap-v2").unwrap(),
1800 SynchronizerState::Ready(h) if h.number == 1
1801 ));
1802 assert!(matches!(
1803 first_msg.sync_states.get("uniswap-v3").unwrap(),
1804 SynchronizerState::Ready(h) if h.number == 1
1805 ));
1806
1807 let mut seen_stale = false;
1809 let start_time = tokio::time::Instant::now();
1810 while let Ok(Some(Ok(msg))) =
1811 tokio::time::timeout(Duration::from_millis(50), rx.recv()).await
1812 {
1813 let v2 = msg
1814 .sync_states
1815 .get("uniswap-v2")
1816 .unwrap();
1817 let v3 = msg
1818 .sync_states
1819 .get("uniswap-v3")
1820 .unwrap();
1821 if matches!(v2, SynchronizerState::Stale(_)) &&
1822 matches!(v3, SynchronizerState::Stale(_))
1823 {
1824 seen_stale = true;
1825 assert!(
1826 !nanny_handle.is_finished(),
1827 "Main loop must not exit while synchronizers are Stale"
1828 );
1829 break;
1830 }
1831 if start_time.elapsed() > Duration::from_millis(500) {
1832 break;
1833 }
1834 }
1835 assert!(seen_stale, "Both synchronizers should go Stale");
1836
1837 v2_sync
1839 .send_header(header_message(5))
1840 .await
1841 .unwrap();
1842 v3_sync
1843 .send_header(header_message(5))
1844 .await
1845 .unwrap();
1846
1847 let mut recovered = false;
1850 for _ in 0..20 {
1851 let msg = receive_message(&mut rx).await;
1852 let v2 = msg
1853 .sync_states
1854 .get("uniswap-v2")
1855 .unwrap();
1856 let v3 = msg
1857 .sync_states
1858 .get("uniswap-v3")
1859 .unwrap();
1860 if matches!(v2, SynchronizerState::Ready(h) if h.number == 5) &&
1861 matches!(v3, SynchronizerState::Ready(h) if h.number == 5)
1862 {
1863 recovered = true;
1864 break;
1865 }
1866 }
1867 assert!(recovered, "Both synchronizers should recover to Ready at block 5");
1868
1869 drop(rx);
1871 timeout(Duration::from_secs(2), nanny_handle)
1872 .await
1873 .expect("Nanny timed out")
1874 .expect("Nanny panicked");
1875 }
1876
1877 #[test(tokio::test)]
1878 async fn test_stale_synchronizer_recovers() {
1879 let (v2_sync, v3_sync, nanny_handle, mut rx) = setup_block_sync().await;
1881
1882 tokio::time::sleep(Duration::from_millis(50)).await;
1884 let block2_msg = header_message(2);
1885 let _ = v2_sync
1886 .send_header(block2_msg.clone())
1887 .await;
1888
1889 for _ in 0..2 {
1891 if let Some(msg) = rx.recv().await {
1892 if let Ok(msg) = msg {
1893 if matches!(
1894 msg.sync_states
1895 .get("uniswap-v2")
1896 .unwrap(),
1897 SynchronizerState::Ready(_)
1898 ) {
1899 assert!(matches!(
1900 msg.sync_states
1901 .get("uniswap-v3")
1902 .unwrap(),
1903 SynchronizerState::Delayed(_)
1904 ));
1905 break;
1906 };
1907 }
1908 } else {
1909 panic!("Channel closed unexpectedly")
1910 }
1911 }
1912
1913 tokio::time::sleep(Duration::from_millis(15)).await;
1915 let block3_msg = header_message(3);
1916 let _ = v2_sync
1917 .send_header(block3_msg.clone())
1918 .await;
1919 let third_msg = receive_message(&mut rx).await;
1920 dbg!(&third_msg);
1921 assert!(matches!(
1922 third_msg
1923 .sync_states
1924 .get("uniswap-v2")
1925 .unwrap(),
1926 SynchronizerState::Ready(_)
1927 ));
1928 assert!(matches!(
1929 third_msg
1930 .sync_states
1931 .get("uniswap-v3")
1932 .unwrap(),
1933 SynchronizerState::Stale(_)
1934 ));
1935
1936 let block4_msg = header_message(4);
1937 let _ = v3_sync
1938 .send_header(block2_msg.clone())
1939 .await;
1940 let _ = v3_sync
1941 .send_header(block3_msg.clone())
1942 .await;
1943 let _ = v3_sync
1944 .send_header(block4_msg.clone())
1945 .await;
1946 let _ = v2_sync
1947 .send_header(block4_msg.clone())
1948 .await;
1949 let fourth_msg = receive_message(&mut rx).await;
1950 assert!(matches!(
1951 fourth_msg
1952 .sync_states
1953 .get("uniswap-v2")
1954 .unwrap(),
1955 SynchronizerState::Ready(_)
1956 ));
1957 assert!(matches!(
1958 fourth_msg
1959 .sync_states
1960 .get("uniswap-v3")
1961 .unwrap(),
1962 SynchronizerState::Ready(_)
1963 ));
1964
1965 shutdown_block_synchronizer(nanny_handle, rx).await;
1966
1967 assert!(
1969 v2_sync.was_close_received().await,
1970 "v2_sync should have received close signal during cleanup"
1971 );
1972 assert!(
1973 v3_sync.was_close_received().await,
1974 "v3_sync should have received close signal during cleanup"
1975 );
1976 }
1977
1978 #[test(tokio::test)]
1979 async fn test_all_synchronizer_advanced() {
1980 let (v2_sync, v3_sync, nanny_handle, mut rx) = setup_block_sync().await;
1984
1985 let block3 = header_message(3);
1986 v2_sync
1987 .send_header(block3.clone())
1988 .await
1989 .unwrap();
1990 v3_sync
1991 .send_header(block3)
1992 .await
1993 .unwrap();
1994
1995 let msg = receive_message(&mut rx).await;
1996 matches!(
1997 msg.sync_states
1998 .get("uniswap-v2")
1999 .unwrap(),
2000 SynchronizerState::Ready(_)
2001 );
2002 matches!(
2003 msg.sync_states
2004 .get("uniswap-v3")
2005 .unwrap(),
2006 SynchronizerState::Ready(_)
2007 );
2008
2009 shutdown_block_synchronizer(nanny_handle, rx).await;
2010 }
2011
2012 #[test(tokio::test)]
2013 async fn test_one_synchronizer_advanced() {
2014 let (v2_sync, v3_sync, nanny_handle, mut rx) = setup_block_sync().await;
2015
2016 let block2 = header_message(2);
2017 let block4 = header_message(4);
2018 v2_sync
2019 .send_header(block4.clone())
2020 .await
2021 .unwrap();
2022 v3_sync
2023 .send_header(block2.clone())
2024 .await
2025 .unwrap();
2026
2027 let msg = receive_message(&mut rx).await;
2028 matches!(
2029 msg.sync_states
2030 .get("uniswap-v2")
2031 .unwrap(),
2032 SynchronizerState::Ready(_)
2033 );
2034 matches!(
2035 msg.sync_states
2036 .get("uniswap-v3")
2037 .unwrap(),
2038 SynchronizerState::Delayed(_)
2039 );
2040
2041 shutdown_block_synchronizer(nanny_handle, rx).await;
2042 }
2043
2044 #[test(tokio::test)]
2045 async fn test_partial_blocks_normal_operation() {
2046 let (v2_sync, _v3_sync, nanny_handle, mut rx) = setup_block_sync().await;
2049
2050 send_and_assert_ready(
2052 &v2_sync,
2053 "uniswap-v2",
2054 &mut rx,
2055 partial_header_message(2, 0),
2056 2,
2057 Some(0),
2058 )
2059 .await;
2060 send_and_assert_ready(
2061 &v2_sync,
2062 "uniswap-v2",
2063 &mut rx,
2064 partial_header_message(2, 3),
2065 2,
2066 Some(3),
2067 )
2068 .await;
2069 send_and_assert_ready(
2070 &v2_sync,
2071 "uniswap-v2",
2072 &mut rx,
2073 partial_header_message(2, 7),
2074 2,
2075 Some(7),
2076 )
2077 .await;
2078
2079 send_and_assert_ready(
2081 &v2_sync,
2082 "uniswap-v2",
2083 &mut rx,
2084 partial_header_message(3, 0),
2085 3,
2086 Some(0),
2087 )
2088 .await;
2089 send_and_assert_ready(
2090 &v2_sync,
2091 "uniswap-v2",
2092 &mut rx,
2093 partial_header_message(3, 2),
2094 3,
2095 Some(2),
2096 )
2097 .await;
2098
2099 shutdown_block_synchronizer(nanny_handle, rx).await;
2100 }
2101
2102 #[test(tokio::test)]
2103 async fn test_partial_blocks_handles_reverts() {
2104 let (v2_sync, _v3_sync, nanny_handle, mut rx) = setup_block_sync().await;
2107
2108 send_and_assert_ready(&v2_sync, "uniswap-v2", &mut rx, header_message(2), 2, None).await;
2110
2111 send_and_assert_ready(
2113 &v2_sync,
2114 "uniswap-v2",
2115 &mut rx,
2116 partial_header_message(3, 0),
2117 3,
2118 Some(0),
2119 )
2120 .await;
2121 send_and_assert_ready(
2122 &v2_sync,
2123 "uniswap-v2",
2124 &mut rx,
2125 partial_header_message(3, 2),
2126 3,
2127 Some(2),
2128 )
2129 .await;
2130
2131 send_and_assert_ready(&v2_sync, "uniswap-v2", &mut rx, revert_header_message(2), 2, None)
2133 .await;
2134
2135 send_and_assert_ready(&v2_sync, "uniswap-v2", &mut rx, header_message(3), 3, None).await;
2137
2138 shutdown_block_synchronizer(nanny_handle, rx).await;
2139 }
2140
2141 #[test(tokio::test)]
2142 async fn test_partial_blocks_delayed_synchronizer_catches_up() {
2143 let (v2_sync, v3_sync, nanny_handle, mut rx) = setup_block_sync().await;
2146
2147 let partial_0 = partial_header_message(2, 0);
2149 v2_sync
2150 .send_header(partial_0.clone())
2151 .await
2152 .expect("send partial 0 failed");
2153
2154 let msg = receive_message(&mut rx).await;
2155 assert!(msg
2157 .state_msgs
2158 .contains_key("uniswap-v2"));
2159 assert!(!msg
2160 .state_msgs
2161 .contains_key("uniswap-v3"));
2162 assert!(matches!(
2163 msg.sync_states.get("uniswap-v2").unwrap(),
2164 SynchronizerState::Ready(h) if h.partial_block_index == Some(0)
2165 ));
2166 assert!(matches!(
2167 msg.sync_states
2168 .get("uniswap-v3")
2169 .unwrap(),
2170 SynchronizerState::Delayed(_)
2171 ));
2172
2173 let partial_2 = partial_header_message(2, 2);
2175 v2_sync
2176 .send_header(partial_2.clone())
2177 .await
2178 .expect("send partial 2 failed");
2179 v3_sync
2180 .send_header(partial_0.clone())
2181 .await
2182 .expect("v3 catch up partial 0 failed");
2183 v3_sync
2184 .send_header(partial_2.clone())
2185 .await
2186 .expect("v3 catch up partial 2 failed");
2187
2188 let mut v3_ready = false;
2190 for _ in 0..3 {
2191 let msg = receive_message(&mut rx).await;
2192 if matches!(
2193 msg.sync_states.get("uniswap-v3").unwrap(),
2194 SynchronizerState::Ready(h) if h.partial_block_index == Some(2)
2195 ) {
2196 v3_ready = true;
2197 break;
2198 }
2199 }
2200 assert!(v3_ready, "v3 caught up to partial 2");
2201
2202 shutdown_block_synchronizer(nanny_handle, rx).await;
2203 }
2204}