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 partial_header(number: u64, partial_idx: u32, parent: u64) -> BlockHeader {
1299 BlockHeader {
1300 number,
1301 hash: Bytes::from(
1302 [number.to_be_bytes().as_slice(), partial_idx.to_be_bytes().as_slice()].concat(),
1303 ),
1304 parent_hash: Bytes::from(parent.to_be_bytes()),
1305 revert: false,
1306 timestamp: 1000,
1307 partial_block_index: Some(partial_idx),
1308 }
1309 }
1310
1311 fn sealed_partial_header(number: u64, partial_idx: u32, parent: u64) -> BlockHeader {
1314 BlockHeader {
1315 number,
1316 hash: Bytes::from(number.to_be_bytes()),
1317 parent_hash: Bytes::from(parent.to_be_bytes()),
1318 revert: false,
1319 timestamp: 1000,
1320 partial_block_index: Some(partial_idx),
1321 }
1322 }
1323
1324 fn stream_in_state(name: &str, state: SynchronizerState) -> SynchronizerStream {
1327 let (_tx, rx) = mpsc::channel(1);
1328 let id = ExtractorIdentity { chain: Chain::Ethereum, name: name.to_string() };
1329 let mut stream = SynchronizerStream::new(&id, rx);
1330 stream.state = state;
1331 stream
1332 }
1333
1334 async fn receive_message(rx: &mut Receiver<BlockSyncResult<FeedMessage>>) -> FeedMessage {
1335 timeout(Duration::from_millis(100), rx.recv())
1336 .await
1337 .expect("Responds in time")
1338 .expect("Should receive first message")
1339 .expect("No error")
1340 }
1341
1342 async fn setup_block_sync(
1343 ) -> (MockStateSync, MockStateSync, JoinHandle<()>, Receiver<BlockSyncResult<FeedMessage>>)
1344 {
1345 setup_block_sync_with_behaviour(MockBehavior::Normal, MockBehavior::Normal).await
1346 }
1347
1348 async fn setup_block_sync_with_behaviour(
1350 v2_behavior: MockBehavior,
1351 v3_behavior: MockBehavior,
1352 ) -> (MockStateSync, MockStateSync, JoinHandle<()>, Receiver<BlockSyncResult<FeedMessage>>)
1353 {
1354 let v2_sync = MockStateSync::with_behavior(v2_behavior);
1355 let v3_sync = MockStateSync::with_behavior(v3_behavior);
1356
1357 let mut block_sync = BlockSynchronizer::new(
1359 Duration::from_millis(20), Duration::from_millis(10), 3, );
1363 block_sync.max_messages(10); let block_sync = block_sync
1366 .register_synchronizer(
1367 ExtractorIdentity { chain: Chain::Ethereum, name: "uniswap-v2".to_string() },
1368 v2_sync.clone(),
1369 )
1370 .register_synchronizer(
1371 ExtractorIdentity { chain: Chain::Ethereum, name: "uniswap-v3".to_string() },
1372 v3_sync.clone(),
1373 );
1374
1375 let block1_msg = header_message(1);
1377 let _ = v2_sync
1378 .send_header(block1_msg.clone())
1379 .await;
1380 let _ = v3_sync
1381 .send_header(block1_msg.clone())
1382 .await;
1383
1384 let (nanny_handle, mut rx) = block_sync
1386 .run()
1387 .await
1388 .expect("BlockSynchronizer failed to start");
1389
1390 let first_feed_msg = receive_message(&mut rx).await;
1391 assert_eq!(first_feed_msg.state_msgs.len(), 2);
1392 assert!(matches!(
1393 first_feed_msg
1394 .sync_states
1395 .get("uniswap-v2")
1396 .unwrap(),
1397 SynchronizerState::Ready(_)
1398 ));
1399 assert!(matches!(
1400 first_feed_msg
1401 .sync_states
1402 .get("uniswap-v3")
1403 .unwrap(),
1404 SynchronizerState::Ready(_)
1405 ));
1406
1407 (v2_sync, v3_sync, nanny_handle, rx)
1408 }
1409
1410 async fn shutdown_block_synchronizer(
1411 nanny_handle: JoinHandle<()>,
1412 rx: Receiver<BlockSyncResult<FeedMessage>>,
1413 ) {
1414 drop(rx);
1418 timeout(Duration::from_secs(2), nanny_handle)
1419 .await
1420 .expect("Nanny failed to exit within time")
1421 .expect("Nanny panicked");
1422 }
1423
1424 async fn send_and_assert_ready(
1426 sync: &MockStateSync,
1427 sync_name: &str,
1428 rx: &mut Receiver<BlockSyncResult<FeedMessage>>,
1429 msg: StateSyncMessage<BlockHeader>,
1430 expected_block: u64,
1431 expected_partial: Option<u32>,
1432 ) {
1433 sync.send_header(msg)
1434 .await
1435 .expect("send failed");
1436 let feed_msg = receive_message(rx).await;
1437 let state = feed_msg
1438 .sync_states
1439 .get(sync_name)
1440 .unwrap();
1441 match state {
1442 SynchronizerState::Ready(h) => {
1443 assert_eq!(h.number, expected_block, "wrong block number");
1444 assert_eq!(h.partial_block_index, expected_partial, "wrong partial index");
1445 }
1446 other => panic!("expected Ready, got {:?}", other),
1447 }
1448 }
1449
1450 #[test(tokio::test)]
1451 async fn test_two_ready_synchronizers() {
1452 let (v2_sync, v3_sync, nanny_handle, mut rx) = setup_block_sync().await;
1453
1454 let second_msg = header_message(2);
1455 v2_sync
1456 .send_header(second_msg.clone())
1457 .await
1458 .expect("send_header failed");
1459 v3_sync
1460 .send_header(second_msg.clone())
1461 .await
1462 .expect("send_header failed");
1463 let second_feed_msg = receive_message(&mut rx).await;
1464
1465 let exp2 = FeedMessage {
1466 state_msgs: [
1467 ("uniswap-v2".to_string(), second_msg.clone()),
1468 ("uniswap-v3".to_string(), second_msg.clone()),
1469 ]
1470 .into_iter()
1471 .collect(),
1472 sync_states: [
1473 ("uniswap-v3".to_string(), SynchronizerState::Ready(second_msg.header.clone())),
1474 ("uniswap-v2".to_string(), SynchronizerState::Ready(second_msg.header.clone())),
1475 ]
1476 .into_iter()
1477 .collect(),
1478 };
1479 assert_eq!(second_feed_msg, exp2);
1480
1481 shutdown_block_synchronizer(nanny_handle, rx).await;
1482 }
1483
1484 #[test]
1493 fn test_reinit_preserves_history_for_revert_below_advanced_tip() {
1494 let old_blocks = vec![
1496 full_header(323, 323, 322),
1497 full_header(324, 324, 323),
1498 full_header(325, 325, 324),
1499 ];
1500 let mut old_history = BlockHistory::new(old_blocks, 15).unwrap();
1501
1502 let mut streams = vec![
1505 stream_in_state("aerodrome", SynchronizerState::Advanced(full_header(326, 326, 325))),
1506 stream_in_state("uniswap_v3", SynchronizerState::Delayed(full_header(325, 325, 324))),
1507 ];
1508
1509 let mut new_history = BlockSynchronizer::<MockStateSync>::reinit_block_history(
1510 &mut streams,
1511 &mut old_history,
1512 )
1513 .expect("reinit failed");
1514
1515 let revert = BlockHeader {
1517 number: 325,
1518 hash: Bytes::from(325u64.to_be_bytes()),
1519 parent_hash: Bytes::from(324u64.to_be_bytes()),
1520 revert: true,
1521 timestamp: 1000,
1522 partial_block_index: None,
1523 };
1524 new_history
1525 .push(revert)
1526 .expect("revert below advanced tip must not exceed history");
1527 }
1528
1529 #[test]
1534 fn test_reinit_discards_retained_history_on_detached_advanced_block() {
1535 let old_blocks = vec![
1536 full_header(323, 323, 322),
1537 full_header(324, 324, 323),
1538 full_header(325, 325, 324),
1539 ];
1540 let mut old_history = BlockHistory::new(old_blocks, BLOCK_HISTORY_SIZE).unwrap();
1541
1542 let mut streams = vec![stream_in_state(
1544 "aerodrome",
1545 SynchronizerState::Advanced(full_header(400, 400, 399)),
1546 )];
1547
1548 let new_history = BlockSynchronizer::<MockStateSync>::reinit_block_history(
1549 &mut streams,
1550 &mut old_history,
1551 )
1552 .expect("reinit failed");
1553
1554 let retained: Vec<u64> = new_history
1555 .blocks()
1556 .map(|b| b.number)
1557 .collect();
1558 assert_eq!(
1559 retained,
1560 vec![400],
1561 "detached advanced block must not be stitched to old history"
1562 );
1563 }
1564
1565 #[test]
1574 fn test_reinit_preserves_history_for_partial_advanced_block() {
1575 let old_blocks = vec![
1577 full_header(323, 323, 322),
1578 full_header(324, 324, 323),
1579 partial_header(325, 2, 324),
1580 ];
1581 let mut old_history = BlockHistory::new(old_blocks, BLOCK_HISTORY_SIZE).unwrap();
1582
1583 let advanced = partial_header(326, 0, 325);
1586 assert_eq!(
1587 old_history
1588 .determine_block_position(&advanced)
1589 .unwrap(),
1590 BlockPosition::Advanced
1591 );
1592
1593 let mut streams = vec![
1594 stream_in_state("aerodrome", SynchronizerState::Advanced(advanced)),
1595 stream_in_state("uniswap_v3", SynchronizerState::Delayed(partial_header(325, 2, 324))),
1596 ];
1597
1598 let new_history = BlockSynchronizer::<MockStateSync>::reinit_block_history(
1599 &mut streams,
1600 &mut old_history,
1601 )
1602 .expect("reinit failed");
1603
1604 let retained: Vec<u64> = new_history
1605 .blocks()
1606 .map(|b| b.number)
1607 .collect();
1608 assert_eq!(retained, vec![323, 324, 325, 326]);
1609 }
1610
1611 #[test]
1616 fn test_revert_below_tip_resolves_after_partial_advanced_reinit() {
1617 let old_blocks = vec![
1618 full_header(323, 323, 322),
1619 full_header(324, 324, 323),
1620 partial_header(325, 2, 324),
1621 ];
1622 let mut old_history = BlockHistory::new(old_blocks, BLOCK_HISTORY_SIZE).unwrap();
1623
1624 let mut streams = vec![
1625 stream_in_state("aerodrome", SynchronizerState::Advanced(partial_header(326, 0, 325))),
1626 stream_in_state("uniswap_v3", SynchronizerState::Delayed(partial_header(325, 2, 324))),
1627 ];
1628
1629 let mut new_history = BlockSynchronizer::<MockStateSync>::reinit_block_history(
1630 &mut streams,
1631 &mut old_history,
1632 )
1633 .expect("reinit failed");
1634
1635 new_history
1638 .push(sealed_partial_header(326, 5, 325))
1639 .expect("sealed partial push failed");
1640 new_history
1641 .push(partial_header(327, 0, 326))
1642 .expect("next partial push failed");
1643
1644 let revert = BlockHeader {
1647 number: 326,
1648 hash: Bytes::from(326u64.to_be_bytes()),
1649 parent_hash: Bytes::from(325u64.to_be_bytes()),
1650 revert: true,
1651 timestamp: 1000,
1652 partial_block_index: None,
1653 };
1654 new_history
1655 .push(revert)
1656 .expect("1-block revert after partial-advanced reinit must resolve");
1657
1658 let retained: Vec<u64> = new_history
1661 .blocks()
1662 .map(|b| b.number)
1663 .collect();
1664 assert_eq!(retained, vec![323, 324, 325, 326]);
1665 let latest = new_history.latest().unwrap();
1666 assert_eq!(latest.number, 326);
1667 assert!(latest.revert);
1668 assert!(!latest.is_partial());
1669 }
1670
1671 #[test(tokio::test)]
1672 async fn test_delayed_synchronizer_catches_up() {
1673 let (v2_sync, v3_sync, nanny_handle, mut rx) = setup_block_sync().await;
1674
1675 let block2_msg = header_message(2);
1677 v2_sync
1678 .send_header(block2_msg.clone())
1679 .await
1680 .expect("send_header failed");
1681
1682 let second_feed_msg = receive_message(&mut rx).await;
1684 debug!("Consumed second message for v2");
1685
1686 assert!(second_feed_msg
1687 .state_msgs
1688 .contains_key("uniswap-v2"));
1689 assert!(matches!(
1690 second_feed_msg.sync_states.get("uniswap-v2").unwrap(),
1691 SynchronizerState::Ready(header) if header.number == 2
1692 ));
1693 assert!(!second_feed_msg
1694 .state_msgs
1695 .contains_key("uniswap-v3"));
1696 assert!(matches!(
1697 second_feed_msg.sync_states.get("uniswap-v3").unwrap(),
1698 SynchronizerState::Delayed(header) if header.number == 1
1699 ));
1700
1701 v3_sync
1703 .send_header(block2_msg.clone())
1704 .await
1705 .expect("send_header failed");
1706
1707 let block3_msg = header_message(3);
1709 v2_sync
1710 .send_header(block3_msg.clone())
1711 .await
1712 .expect("send_header failed");
1713 v3_sync
1714 .send_header(block3_msg)
1715 .await
1716 .expect("send_header failed");
1717
1718 let mut third_feed_msg = receive_message(&mut rx).await;
1721
1722 if !third_feed_msg
1725 .state_msgs
1726 .contains_key("uniswap-v2")
1727 {
1728 third_feed_msg = rx
1729 .recv()
1730 .await
1731 .expect("header channel was closed")
1732 .expect("no error");
1733 }
1734 assert!(third_feed_msg
1735 .state_msgs
1736 .contains_key("uniswap-v2"));
1737 assert!(third_feed_msg
1738 .state_msgs
1739 .contains_key("uniswap-v3"));
1740 assert!(matches!(
1741 third_feed_msg.sync_states.get("uniswap-v2").unwrap(),
1742 SynchronizerState::Ready(header) if header.number == 3
1743 ));
1744 assert!(matches!(
1745 third_feed_msg.sync_states.get("uniswap-v3").unwrap(),
1746 SynchronizerState::Ready(header) if header.number == 3
1747 ));
1748
1749 shutdown_block_synchronizer(nanny_handle, rx).await;
1750 }
1751
1752 #[test(tokio::test)]
1753 async fn test_different_start_blocks() {
1754 let v2_sync = MockStateSync::new();
1755 let v3_sync = MockStateSync::new();
1756 let block_sync = BlockSynchronizer::with_short_timeouts()
1757 .register_synchronizer(
1758 ExtractorIdentity { chain: Chain::Ethereum, name: "uniswap-v2".to_string() },
1759 v2_sync.clone(),
1760 )
1761 .register_synchronizer(
1762 ExtractorIdentity { chain: Chain::Ethereum, name: "uniswap-v3".to_string() },
1763 v3_sync.clone(),
1764 );
1765
1766 let block1_msg = header_message(1);
1768 let block2_msg = header_message(2);
1769
1770 let _ = v2_sync
1771 .send_header(block1_msg.clone())
1772 .await;
1773 v3_sync
1774 .send_header(block2_msg.clone())
1775 .await
1776 .expect("send_header failed");
1777
1778 let (jh, mut rx) = block_sync
1780 .run()
1781 .await
1782 .expect("BlockSynchronizer failed to start.");
1783
1784 let first_feed_msg = receive_message(&mut rx).await;
1786 assert!(matches!(
1787 first_feed_msg.sync_states.get("uniswap-v2").unwrap(),
1788 SynchronizerState::Delayed(header) if header.number == 1
1789 ));
1790 assert!(matches!(
1791 first_feed_msg.sync_states.get("uniswap-v3").unwrap(),
1792 SynchronizerState::Ready(header) if header.number == 2
1793 ));
1794
1795 v2_sync
1797 .send_header(block2_msg.clone())
1798 .await
1799 .expect("send_header failed");
1800
1801 let block3_msg = header_message(3);
1803 let _ = v2_sync
1804 .send_header(block3_msg.clone())
1805 .await;
1806 v3_sync
1807 .send_header(block3_msg.clone())
1808 .await
1809 .expect("send_header failed");
1810
1811 let second_feed_msg = receive_message(&mut rx).await;
1813 assert_eq!(second_feed_msg.state_msgs.len(), 2);
1814 assert!(matches!(
1815 second_feed_msg.sync_states.get("uniswap-v2").unwrap(),
1816 SynchronizerState::Ready(header) if header.number == 3
1817 ));
1818 assert!(matches!(
1819 second_feed_msg.sync_states.get("uniswap-v3").unwrap(),
1820 SynchronizerState::Ready(header) if header.number == 3
1821 ));
1822
1823 shutdown_block_synchronizer(jh, rx).await;
1824 }
1825
1826 #[test(tokio::test)]
1827 async fn test_synchronizer_fails_other_goes_stale() {
1828 let (_v2_sync, v3_sync, nanny_handle, mut sync_rx) =
1829 setup_block_sync_with_behaviour(MockBehavior::ExitImmediately, MockBehavior::Normal)
1830 .await;
1831
1832 let mut error_reported = false;
1833 for _ in 0..3 {
1834 if let Some(msg) = sync_rx.recv().await {
1835 match msg {
1836 Err(_) => error_reported = true,
1837 Ok(msg) => {
1838 assert!(matches!(
1839 msg.sync_states
1840 .get("uniswap-v3")
1841 .unwrap(),
1842 SynchronizerState::Delayed(_)
1843 ));
1844 assert!(matches!(
1845 msg.sync_states
1846 .get("uniswap-v2")
1847 .unwrap(),
1848 SynchronizerState::Ended(_)
1849 ));
1850 }
1851 }
1852 }
1853 }
1854 assert!(error_reported, "BlockSynchronizer did not report final error");
1855
1856 let result = timeout(Duration::from_secs(2), nanny_handle).await;
1858 assert!(result.is_ok(), "Nanny should complete when synchronizer task exits");
1859
1860 assert!(
1862 v3_sync.was_close_received().await,
1863 "v3_sync should have received close signal during cleanup"
1864 );
1865 }
1866
1867 #[test(tokio::test)]
1868 async fn test_cleanup_timeout_warning() {
1869 let (_v2_sync, _v3_sync, nanny_handle, _rx) = setup_block_sync_with_behaviour(
1872 MockBehavior::ExitImmediately,
1873 MockBehavior::IgnoreClose,
1874 )
1875 .await;
1876
1877 let result = timeout(Duration::from_secs(10), nanny_handle).await;
1879 assert!(
1880 result.is_ok(),
1881 "Nanny should complete even when some synchronizers timeout during cleanup"
1882 );
1883
1884 }
1888
1889 #[test(tokio::test)]
1890 async fn test_one_synchronizer_goes_stale_while_other_works() {
1891 let (_v2_sync, v3_sync, nanny_handle, mut rx) = setup_block_sync().await;
1893
1894 let block2_msg = header_message(2);
1896 let _ = v3_sync
1897 .send_header(block2_msg.clone())
1898 .await;
1899 let second_feed_msg = receive_message(&mut rx).await;
1903 assert!(second_feed_msg
1904 .state_msgs
1905 .contains_key("uniswap-v3"));
1906 assert!(!second_feed_msg
1907 .state_msgs
1908 .contains_key("uniswap-v2"));
1909 assert!(matches!(
1910 second_feed_msg
1911 .sync_states
1912 .get("uniswap-v3")
1913 .unwrap(),
1914 SynchronizerState::Ready(_)
1915 ));
1916 if let Some(v2_state) = second_feed_msg
1918 .sync_states
1919 .get("uniswap-v2")
1920 {
1921 if matches!(v2_state, SynchronizerState::Delayed(_)) {
1922 assert!(
1924 !nanny_handle.is_finished(),
1925 "Nanny should still be running when synchronizer is delayed (not stale yet)"
1926 );
1927 }
1928 }
1929
1930 tokio::time::sleep(Duration::from_millis(15)).await;
1932
1933 let block3_msg = header_message(3);
1935 let _ = v3_sync
1936 .send_header(block3_msg.clone())
1937 .await;
1938
1939 tokio::time::sleep(Duration::from_millis(40)).await;
1940
1941 let mut stale_found = false;
1942 for _ in 0..2 {
1943 if let Some(Ok(msg)) = rx.recv().await {
1944 if let Some(SynchronizerState::Stale(_)) = msg.sync_states.get("uniswap-v2") {
1945 stale_found = true;
1946 }
1947 }
1948 }
1949 assert!(stale_found, "v2 synchronizer should be stale");
1950
1951 shutdown_block_synchronizer(nanny_handle, rx).await;
1952 }
1953
1954 #[test(tokio::test)]
1955 async fn test_all_synchronizers_stale_loop_continues() {
1956 let (v2_sync, v3_sync, nanny_handle, mut rx) = setup_block_sync().await;
1959
1960 let mut seen_delayed = false;
1962 let mut seen_stale = false;
1963 let start_time = tokio::time::Instant::now();
1964
1965 while let Ok(Some(Ok(msg))) =
1966 tokio::time::timeout(Duration::from_millis(50), rx.recv()).await
1967 {
1968 let v2_state = msg.sync_states.get("uniswap-v2");
1969 let v3_state = msg.sync_states.get("uniswap-v3");
1970
1971 if !seen_delayed &&
1972 (matches!(v2_state, Some(SynchronizerState::Delayed(_))) ||
1973 matches!(v3_state, Some(SynchronizerState::Delayed(_))))
1974 {
1975 seen_delayed = true;
1976 assert!(
1977 !nanny_handle.is_finished(),
1978 "Nanny must still run when synchronizers are Delayed"
1979 );
1980 }
1981
1982 if matches!(v2_state, Some(SynchronizerState::Stale(_))) &&
1983 matches!(v3_state, Some(SynchronizerState::Stale(_)))
1984 {
1985 seen_stale = true;
1986 assert!(
1987 !nanny_handle.is_finished(),
1988 "Main loop must not exit when all synchronizers are Stale (awaiting recovery)"
1989 );
1990 break;
1991 }
1992
1993 if start_time.elapsed() > Duration::from_millis(500) {
1994 break;
1995 }
1996 }
1997
1998 assert!(seen_delayed, "Synchronizers should transition through Delayed first");
1999 assert!(seen_stale, "Both synchronizers should reach Stale state");
2000
2001 v2_sync.trigger_close().await;
2006 v3_sync.trigger_close().await;
2007 drop(v2_sync);
2008 drop(v3_sync);
2009
2010 let mut error_reported = false;
2012 while let Some(msg) = rx.recv().await {
2013 if msg.is_err() {
2014 error_reported = true;
2015 }
2016 }
2017 assert!(error_reported, "Expected an error after all synchronizers ended");
2018
2019 let nanny_result = timeout(Duration::from_secs(2), nanny_handle).await;
2020 assert!(nanny_result.is_ok(), "Nanny should complete after all synchronizers ended");
2021 }
2022
2023 #[test(tokio::test)]
2024 async fn test_all_synchronizers_recover_after_going_stale() {
2025 let v2_sync = MockStateSync::new();
2028 let v3_sync = MockStateSync::new();
2029 let block_sync =
2030 BlockSynchronizer::new(Duration::from_millis(20), Duration::from_millis(10), 3)
2031 .register_synchronizer(
2032 ExtractorIdentity { chain: Chain::Ethereum, name: "uniswap-v2".to_string() },
2033 v2_sync.clone(),
2034 )
2035 .register_synchronizer(
2036 ExtractorIdentity { chain: Chain::Ethereum, name: "uniswap-v3".to_string() },
2037 v3_sync.clone(),
2038 );
2039
2040 v2_sync
2041 .send_header(header_message(1))
2042 .await
2043 .unwrap();
2044 v3_sync
2045 .send_header(header_message(1))
2046 .await
2047 .unwrap();
2048
2049 let (nanny_handle, mut rx) = block_sync
2050 .run()
2051 .await
2052 .expect("BlockSynchronizer start failed");
2053
2054 let first_msg = receive_message(&mut rx).await;
2055 assert!(matches!(
2056 first_msg.sync_states.get("uniswap-v2").unwrap(),
2057 SynchronizerState::Ready(h) if h.number == 1
2058 ));
2059 assert!(matches!(
2060 first_msg.sync_states.get("uniswap-v3").unwrap(),
2061 SynchronizerState::Ready(h) if h.number == 1
2062 ));
2063
2064 let mut seen_stale = false;
2066 let start_time = tokio::time::Instant::now();
2067 while let Ok(Some(Ok(msg))) =
2068 tokio::time::timeout(Duration::from_millis(50), rx.recv()).await
2069 {
2070 let v2 = msg
2071 .sync_states
2072 .get("uniswap-v2")
2073 .unwrap();
2074 let v3 = msg
2075 .sync_states
2076 .get("uniswap-v3")
2077 .unwrap();
2078 if matches!(v2, SynchronizerState::Stale(_)) &&
2079 matches!(v3, SynchronizerState::Stale(_))
2080 {
2081 seen_stale = true;
2082 assert!(
2083 !nanny_handle.is_finished(),
2084 "Main loop must not exit while synchronizers are Stale"
2085 );
2086 break;
2087 }
2088 if start_time.elapsed() > Duration::from_millis(500) {
2089 break;
2090 }
2091 }
2092 assert!(seen_stale, "Both synchronizers should go Stale");
2093
2094 v2_sync
2096 .send_header(header_message(5))
2097 .await
2098 .unwrap();
2099 v3_sync
2100 .send_header(header_message(5))
2101 .await
2102 .unwrap();
2103
2104 let mut recovered = false;
2107 for _ in 0..20 {
2108 let msg = receive_message(&mut rx).await;
2109 let v2 = msg
2110 .sync_states
2111 .get("uniswap-v2")
2112 .unwrap();
2113 let v3 = msg
2114 .sync_states
2115 .get("uniswap-v3")
2116 .unwrap();
2117 if matches!(v2, SynchronizerState::Ready(h) if h.number == 5) &&
2118 matches!(v3, SynchronizerState::Ready(h) if h.number == 5)
2119 {
2120 recovered = true;
2121 break;
2122 }
2123 }
2124 assert!(recovered, "Both synchronizers should recover to Ready at block 5");
2125
2126 drop(rx);
2128 timeout(Duration::from_secs(2), nanny_handle)
2129 .await
2130 .expect("Nanny timed out")
2131 .expect("Nanny panicked");
2132 }
2133
2134 #[test(tokio::test)]
2135 async fn test_stale_synchronizer_recovers() {
2136 let (v2_sync, v3_sync, nanny_handle, mut rx) = setup_block_sync().await;
2138
2139 tokio::time::sleep(Duration::from_millis(50)).await;
2141 let block2_msg = header_message(2);
2142 let _ = v2_sync
2143 .send_header(block2_msg.clone())
2144 .await;
2145
2146 for _ in 0..2 {
2148 if let Some(msg) = rx.recv().await {
2149 if let Ok(msg) = msg {
2150 if matches!(
2151 msg.sync_states
2152 .get("uniswap-v2")
2153 .unwrap(),
2154 SynchronizerState::Ready(_)
2155 ) {
2156 assert!(matches!(
2157 msg.sync_states
2158 .get("uniswap-v3")
2159 .unwrap(),
2160 SynchronizerState::Delayed(_)
2161 ));
2162 break;
2163 };
2164 }
2165 } else {
2166 panic!("Channel closed unexpectedly")
2167 }
2168 }
2169
2170 tokio::time::sleep(Duration::from_millis(15)).await;
2172 let block3_msg = header_message(3);
2173 let _ = v2_sync
2174 .send_header(block3_msg.clone())
2175 .await;
2176 let third_msg = receive_message(&mut rx).await;
2177 dbg!(&third_msg);
2178 assert!(matches!(
2179 third_msg
2180 .sync_states
2181 .get("uniswap-v2")
2182 .unwrap(),
2183 SynchronizerState::Ready(_)
2184 ));
2185 assert!(matches!(
2186 third_msg
2187 .sync_states
2188 .get("uniswap-v3")
2189 .unwrap(),
2190 SynchronizerState::Stale(_)
2191 ));
2192
2193 let block4_msg = header_message(4);
2194 let _ = v3_sync
2195 .send_header(block2_msg.clone())
2196 .await;
2197 let _ = v3_sync
2198 .send_header(block3_msg.clone())
2199 .await;
2200 let _ = v3_sync
2201 .send_header(block4_msg.clone())
2202 .await;
2203 let _ = v2_sync
2204 .send_header(block4_msg.clone())
2205 .await;
2206 let fourth_msg = receive_message(&mut rx).await;
2207 assert!(matches!(
2208 fourth_msg
2209 .sync_states
2210 .get("uniswap-v2")
2211 .unwrap(),
2212 SynchronizerState::Ready(_)
2213 ));
2214 assert!(matches!(
2215 fourth_msg
2216 .sync_states
2217 .get("uniswap-v3")
2218 .unwrap(),
2219 SynchronizerState::Ready(_)
2220 ));
2221
2222 shutdown_block_synchronizer(nanny_handle, rx).await;
2223
2224 assert!(
2226 v2_sync.was_close_received().await,
2227 "v2_sync should have received close signal during cleanup"
2228 );
2229 assert!(
2230 v3_sync.was_close_received().await,
2231 "v3_sync should have received close signal during cleanup"
2232 );
2233 }
2234
2235 #[test(tokio::test)]
2236 async fn test_all_synchronizer_advanced() {
2237 let (v2_sync, v3_sync, nanny_handle, mut rx) = setup_block_sync().await;
2241
2242 let block3 = header_message(3);
2243 v2_sync
2244 .send_header(block3.clone())
2245 .await
2246 .unwrap();
2247 v3_sync
2248 .send_header(block3)
2249 .await
2250 .unwrap();
2251
2252 let msg = receive_message(&mut rx).await;
2253 matches!(
2254 msg.sync_states
2255 .get("uniswap-v2")
2256 .unwrap(),
2257 SynchronizerState::Ready(_)
2258 );
2259 matches!(
2260 msg.sync_states
2261 .get("uniswap-v3")
2262 .unwrap(),
2263 SynchronizerState::Ready(_)
2264 );
2265
2266 shutdown_block_synchronizer(nanny_handle, rx).await;
2267 }
2268
2269 #[test(tokio::test)]
2270 async fn test_one_synchronizer_advanced() {
2271 let (v2_sync, v3_sync, nanny_handle, mut rx) = setup_block_sync().await;
2272
2273 let block2 = header_message(2);
2274 let block4 = header_message(4);
2275 v2_sync
2276 .send_header(block4.clone())
2277 .await
2278 .unwrap();
2279 v3_sync
2280 .send_header(block2.clone())
2281 .await
2282 .unwrap();
2283
2284 let msg = receive_message(&mut rx).await;
2285 matches!(
2286 msg.sync_states
2287 .get("uniswap-v2")
2288 .unwrap(),
2289 SynchronizerState::Ready(_)
2290 );
2291 matches!(
2292 msg.sync_states
2293 .get("uniswap-v3")
2294 .unwrap(),
2295 SynchronizerState::Delayed(_)
2296 );
2297
2298 shutdown_block_synchronizer(nanny_handle, rx).await;
2299 }
2300
2301 #[test(tokio::test)]
2302 async fn test_partial_blocks_normal_operation() {
2303 let (v2_sync, _v3_sync, nanny_handle, mut rx) = setup_block_sync().await;
2306
2307 send_and_assert_ready(
2309 &v2_sync,
2310 "uniswap-v2",
2311 &mut rx,
2312 partial_header_message(2, 0),
2313 2,
2314 Some(0),
2315 )
2316 .await;
2317 send_and_assert_ready(
2318 &v2_sync,
2319 "uniswap-v2",
2320 &mut rx,
2321 partial_header_message(2, 3),
2322 2,
2323 Some(3),
2324 )
2325 .await;
2326 send_and_assert_ready(
2327 &v2_sync,
2328 "uniswap-v2",
2329 &mut rx,
2330 partial_header_message(2, 7),
2331 2,
2332 Some(7),
2333 )
2334 .await;
2335
2336 send_and_assert_ready(
2338 &v2_sync,
2339 "uniswap-v2",
2340 &mut rx,
2341 partial_header_message(3, 0),
2342 3,
2343 Some(0),
2344 )
2345 .await;
2346 send_and_assert_ready(
2347 &v2_sync,
2348 "uniswap-v2",
2349 &mut rx,
2350 partial_header_message(3, 2),
2351 3,
2352 Some(2),
2353 )
2354 .await;
2355
2356 shutdown_block_synchronizer(nanny_handle, rx).await;
2357 }
2358
2359 #[test(tokio::test)]
2360 async fn test_partial_blocks_handles_reverts() {
2361 let (v2_sync, _v3_sync, nanny_handle, mut rx) = setup_block_sync().await;
2364
2365 send_and_assert_ready(&v2_sync, "uniswap-v2", &mut rx, header_message(2), 2, None).await;
2367
2368 send_and_assert_ready(
2370 &v2_sync,
2371 "uniswap-v2",
2372 &mut rx,
2373 partial_header_message(3, 0),
2374 3,
2375 Some(0),
2376 )
2377 .await;
2378 send_and_assert_ready(
2379 &v2_sync,
2380 "uniswap-v2",
2381 &mut rx,
2382 partial_header_message(3, 2),
2383 3,
2384 Some(2),
2385 )
2386 .await;
2387
2388 send_and_assert_ready(&v2_sync, "uniswap-v2", &mut rx, revert_header_message(2), 2, None)
2390 .await;
2391
2392 send_and_assert_ready(&v2_sync, "uniswap-v2", &mut rx, header_message(3), 3, None).await;
2394
2395 shutdown_block_synchronizer(nanny_handle, rx).await;
2396 }
2397
2398 #[test(tokio::test)]
2399 async fn test_partial_blocks_delayed_synchronizer_catches_up() {
2400 let (v2_sync, v3_sync, nanny_handle, mut rx) = setup_block_sync().await;
2403
2404 let partial_0 = partial_header_message(2, 0);
2406 v2_sync
2407 .send_header(partial_0.clone())
2408 .await
2409 .expect("send partial 0 failed");
2410
2411 let msg = receive_message(&mut rx).await;
2412 assert!(msg
2414 .state_msgs
2415 .contains_key("uniswap-v2"));
2416 assert!(!msg
2417 .state_msgs
2418 .contains_key("uniswap-v3"));
2419 assert!(matches!(
2420 msg.sync_states.get("uniswap-v2").unwrap(),
2421 SynchronizerState::Ready(h) if h.partial_block_index == Some(0)
2422 ));
2423 assert!(matches!(
2424 msg.sync_states
2425 .get("uniswap-v3")
2426 .unwrap(),
2427 SynchronizerState::Delayed(_)
2428 ));
2429
2430 let partial_2 = partial_header_message(2, 2);
2432 v2_sync
2433 .send_header(partial_2.clone())
2434 .await
2435 .expect("send partial 2 failed");
2436 v3_sync
2437 .send_header(partial_0.clone())
2438 .await
2439 .expect("v3 catch up partial 0 failed");
2440 v3_sync
2441 .send_header(partial_2.clone())
2442 .await
2443 .expect("v3 catch up partial 2 failed");
2444
2445 let mut v3_ready = false;
2447 for _ in 0..3 {
2448 let msg = receive_message(&mut rx).await;
2449 if matches!(
2450 msg.sync_states.get("uniswap-v3").unwrap(),
2451 SynchronizerState::Ready(h) if h.partial_block_index == Some(2)
2452 ) {
2453 v3_ready = true;
2454 break;
2455 }
2456 }
2457 assert!(v3_ready, "v3 caught up to partial 2");
2458
2459 shutdown_block_synchronizer(nanny_handle, rx).await;
2460 }
2461}