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::{
23 blockchain::{Block, BlockAggregatedChanges},
24 ExtractorIdentity,
25 },
26 Bytes,
27};
28
29use crate::feed::{
30 block_history::{BlockHistory, BlockHistoryError, BlockPosition},
31 synchronizer::{StateSyncMessage, StateSynchronizer, SyncResult, SynchronizerError},
32};
33
34mod block_history;
35pub mod component_tracker;
36pub mod dto;
37pub mod synchronizer;
38
39const BLOCK_HISTORY_SIZE: usize = 15;
43
44pub trait HeaderLike {
49 fn block(self) -> Option<BlockHeader>;
50 fn block_number_or_timestamp(self) -> u64;
51}
52
53#[derive(Debug, Clone, PartialEq, Default, Serialize, Deserialize, Eq, Hash)]
54pub struct BlockHeader {
55 pub hash: Bytes,
56 pub number: u64,
57 pub parent_hash: Bytes,
58 pub revert: bool,
59 pub timestamp: u64,
60 #[serde(default, skip_serializing_if = "Option::is_none")]
62 pub partial_block_index: Option<u32>,
63}
64
65impl BlockHeader {
66 fn is_partial(&self) -> bool {
67 self.partial_block_index.is_some()
68 }
69}
70
71impl Display for BlockHeader {
72 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
73 let short_hash = if self.hash.len() >= 4 {
75 hex::encode(&self.hash[..4]) } else {
77 hex::encode(&self.hash)
78 };
79
80 match self.partial_block_index {
81 Some(idx) => write!(f, "Block #{} [0x{}..] (partial {})", self.number, short_hash, idx),
82 None => write!(f, "Block #{} [0x{}..]", self.number, short_hash),
83 }
84 }
85}
86
87impl From<&Block> for BlockHeader {
88 fn from(block: &Block) -> Self {
89 Self {
90 hash: block.hash.clone(),
91 number: block.number,
92 parent_hash: block.parent_hash.clone(),
93 timestamp: block.ts.and_utc().timestamp() as u64,
94 ..Default::default()
95 }
96 }
97}
98
99impl From<&BlockAggregatedChanges> for BlockHeader {
100 fn from(block_changes: &BlockAggregatedChanges) -> Self {
101 Self {
102 revert: block_changes.revert,
103 partial_block_index: block_changes.partial_block_index,
104 ..Self::from(&block_changes.block)
105 }
106 }
107}
108
109impl HeaderLike for BlockHeader {
110 fn block(self) -> Option<BlockHeader> {
111 Some(self)
112 }
113
114 fn block_number_or_timestamp(self) -> u64 {
115 self.number
116 }
117}
118
119#[derive(Error, Debug)]
120pub enum BlockSynchronizerError {
121 #[error("Failed to initialize extractor '{extractor}': {source}")]
122 InitializationError {
123 extractor: ExtractorIdentity,
124 #[source]
125 source: SynchronizerError,
126 },
127
128 #[error("Failed to process new block: {0}")]
129 BlockHistoryError(#[from] BlockHistoryError),
130
131 #[error("Not a single synchronizer was ready: {0}")]
132 NoReadySynchronizers(String),
133
134 #[error("No synchronizers were set")]
135 NoSynchronizers,
136
137 #[error("Failed to convert duration: {0}")]
138 DurationConversionError(String),
139}
140
141type BlockSyncResult<T> = Result<T, BlockSynchronizerError>;
142
143pub struct BlockSynchronizer<S> {
191 synchronizers: Option<HashMap<ExtractorIdentity, S>>,
192 block_time: std::time::Duration,
194 latency_buffer: std::time::Duration,
196 startup_timeout: std::time::Duration,
198 max_messages: Option<usize>,
200 max_missed_blocks: u64,
202}
203
204#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
205#[serde(tag = "status", rename_all = "lowercase")]
206pub enum SynchronizerState {
207 Started,
209 Ready(BlockHeader),
211 Delayed(BlockHeader),
214 Stale(BlockHeader),
218 Advanced(BlockHeader),
223 Ended(String),
225}
226
227impl Display for SynchronizerState {
228 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
229 match self {
230 SynchronizerState::Started => write!(f, "Started"),
231 SynchronizerState::Ready(b) => write!(f, "Started({})", b.number),
232 SynchronizerState::Delayed(b) => write!(f, "Delayed({})", b.number),
233 SynchronizerState::Stale(b) => write!(f, "Stale({})", b.number),
234 SynchronizerState::Advanced(b) => write!(f, "Advanced({})", b.number),
235 SynchronizerState::Ended(reason) => write!(f, "Ended({})", reason),
236 }
237 }
238}
239
240pub struct SynchronizerStream {
241 extractor_id: ExtractorIdentity,
242 state: SynchronizerState,
243 error: Option<SynchronizerError>,
244 modify_ts: NaiveDateTime,
245 rx: Receiver<SyncResult<StateSyncMessage<BlockHeader>>>,
246}
247
248impl SynchronizerStream {
249 fn new(
250 extractor_id: &ExtractorIdentity,
251 rx: Receiver<SyncResult<StateSyncMessage<BlockHeader>>>,
252 ) -> Self {
253 Self {
254 extractor_id: extractor_id.clone(),
255 state: SynchronizerState::Started,
256 error: None,
257 modify_ts: Local::now().naive_utc(),
258 rx,
259 }
260 }
261
262 async fn try_advance(
272 &mut self,
273 block_history: &BlockHistory,
274 block_time: std::time::Duration,
275 latency_buffer: std::time::Duration,
276 stale_threshold: std::time::Duration,
277 skip_wait: bool,
278 ) -> BlockSyncResult<Option<StateSyncMessage<BlockHeader>>> {
279 let extractor_id = self.extractor_id.clone();
280 let latest_block = block_history.latest();
281
282 match &self.state {
283 SynchronizerState::Started | SynchronizerState::Ended(_) => {
284 warn!(state=?&self.state, "Advancing Synchronizer in this state not supported!");
285 Ok(None)
286 }
287 SynchronizerState::Advanced(b) => {
288 let future_block = b.clone();
289 self.transition(future_block, block_history, stale_threshold)?;
291 Ok(None)
292 }
293 SynchronizerState::Ready(previous_block) => {
294 self.try_recv_next_expected(
296 block_time + latency_buffer,
297 block_history,
298 previous_block.clone(),
299 stale_threshold,
300 )
301 .await
302 }
303 SynchronizerState::Delayed(old_block) => {
304 debug!(
306 %old_block,
307 latest_block=opt(&latest_block),
308 %extractor_id,
309 "Trying to catch up to latest block"
310 );
311 let timeout =
312 if skip_wait { std::time::Duration::ZERO } else { block_time + latency_buffer };
313 self.try_catch_up(block_history, timeout, stale_threshold)
314 .await
315 }
316 SynchronizerState::Stale(old_block) => {
317 debug!(
319 %old_block,
320 latest_block=opt(&latest_block),
321 %extractor_id,
322 "Trying to catch up stale synchronizer to latest block"
323 );
324 let timeout = if skip_wait { std::time::Duration::ZERO } else { block_time };
325 self.try_catch_up(block_history, timeout, stale_threshold)
326 .await
327 }
328 }
329 }
330
331 async fn try_recv_next_expected(
339 &mut self,
340 max_wait: std::time::Duration,
341 block_history: &BlockHistory,
342 previous_block: BlockHeader,
343 stale_threshold: std::time::Duration,
344 ) -> BlockSyncResult<Option<StateSyncMessage<BlockHeader>>> {
345 let extractor_id = self.extractor_id.clone();
346 match timeout(max_wait, self.rx.recv()).await {
347 Ok(Some(Ok(msg))) => {
348 self.transition(msg.header.clone(), block_history, stale_threshold)?;
349 Ok(Some(msg))
350 }
351 Ok(Some(Err(e))) => {
352 self.mark_errored(e);
354 Ok(None)
355 }
356 Ok(None) => {
357 warn!(
360 %extractor_id,
361 "Tried to poll from closed synchronizer.",
362 );
363 self.mark_closed();
364 Ok(None)
365 }
366 Err(_) => {
367 debug!(%extractor_id, %previous_block, "No block received within time limit.");
369 self.state = SynchronizerState::Delayed(previous_block.clone());
372 self.modify_ts = Local::now().naive_utc();
373 Ok(None)
374 }
375 }
376 }
377
378 async fn try_catch_up(
384 &mut self,
385 block_history: &BlockHistory,
386 max_wait: std::time::Duration,
387 stale_threshold: std::time::Duration,
388 ) -> BlockSyncResult<Option<StateSyncMessage<BlockHeader>>> {
389 let mut results = Vec::new();
390 let extractor_id = self.extractor_id.clone();
391
392 let deadline = std::time::Instant::now() + max_wait;
394
395 while std::time::Instant::now() < deadline {
396 match timeout(
397 deadline.saturating_duration_since(std::time::Instant::now()),
398 self.rx.recv(),
399 )
400 .await
401 {
402 Ok(Some(Ok(msg))) => {
403 debug!(%extractor_id, block=%msg.header, "Received new message during catch-up");
404 let block_pos = block_history.determine_block_position(&msg.header)?;
405 results.push(msg);
406 if matches!(block_pos, BlockPosition::NextExpected | BlockPosition::NextPartial)
407 {
408 break;
409 }
410 }
411 Ok(Some(Err(e))) => {
412 self.mark_errored(e);
414 return Ok(None);
415 }
416 Ok(None) => {
417 warn!(
420 %extractor_id,
421 "Tried to poll from closed synchronizer during catch up.",
422 );
423 self.mark_closed();
424 return Ok(None);
425 }
426 Err(_) => {
427 debug!(%extractor_id, "Timed out waiting for catch-up");
428 break;
429 }
430 }
431 }
432
433 let merged = results
434 .into_iter()
435 .reduce(|l, r| l.merge(r));
436
437 if let Some(msg) = merged {
438 debug!(%extractor_id, "Delayed extractor made progress!");
440 self.transition(msg.header.clone(), block_history, stale_threshold)?;
441 Ok(Some(msg))
442 } else {
443 self.check_and_transition_to_stale_if_needed(stale_threshold, None)?;
445 Ok(None)
446 }
447 }
448
449 fn check_and_transition_to_stale_if_needed(
451 &mut self,
452 stale_threshold: std::time::Duration,
453 fallback_header: Option<BlockHeader>,
454 ) -> Result<bool, BlockSynchronizerError> {
455 let now = Local::now().naive_utc();
456 let wait_duration = now.signed_duration_since(self.modify_ts);
457 let stale_threshold_chrono = ChronoDuration::from_std(stale_threshold)
458 .map_err(|e| BlockSynchronizerError::DurationConversionError(e.to_string()))?;
459
460 if wait_duration > stale_threshold_chrono {
461 let header_to_use = match (&self.state, fallback_header) {
462 (SynchronizerState::Ready(h), _) |
463 (SynchronizerState::Delayed(h), _) |
464 (SynchronizerState::Stale(h), _) => h.clone(),
465 (_, Some(h)) => h,
466 _ => BlockHeader::default(),
467 };
468
469 warn!(
470 extractor_id=%self.extractor_id,
471 last_message_at=?self.modify_ts,
472 "SynchronizerStream transition to stale due to timeout."
473 );
474 self.state = SynchronizerState::Stale(header_to_use);
475 self.modify_ts = now;
476 Ok(true)
477 } else {
478 Ok(false)
479 }
480 }
481
482 fn transition(
489 &mut self,
490 latest_retrieved: BlockHeader,
491 block_history: &BlockHistory,
492 stale_threshold: std::time::Duration,
493 ) -> Result<(), BlockSynchronizerError> {
494 let extractor_id = self.extractor_id.clone();
495 let last_message_at = self.modify_ts;
496 let block = &latest_retrieved;
497
498 match block_history.determine_block_position(&latest_retrieved)? {
499 BlockPosition::NextExpected | BlockPosition::NextPartial => {
500 self.state = SynchronizerState::Ready(latest_retrieved.clone());
501 trace!(
502 next = %latest_retrieved,
503 extractor = %extractor_id,
504 "SynchronizerStream transition to next expected"
505 )
506 }
507 BlockPosition::Latest | BlockPosition::Delayed => {
508 if !self.check_and_transition_to_stale_if_needed(
509 stale_threshold,
510 Some(latest_retrieved.clone()),
511 )? {
512 warn!(
513 %extractor_id,
514 ?last_message_at,
515 %block,
516 "SynchronizerStream transition transition to delayed."
517 );
518 self.state = SynchronizerState::Delayed(latest_retrieved.clone());
519 }
520 }
521 BlockPosition::Advanced => {
522 info!(
523 %extractor_id,
524 ?last_message_at,
525 latest = opt(&block_history.latest()),
526 %block,
527 "SynchronizerStream transition to advanced."
528 );
529 self.state = SynchronizerState::Advanced(latest_retrieved.clone());
530 }
531 }
532 self.modify_ts = Local::now().naive_utc();
533 Ok(())
534 }
535
536 fn mark_errored(&mut self, error: SynchronizerError) {
541 self.state = SynchronizerState::Ended(error.to_string());
542 self.modify_ts = Local::now().naive_utc();
543 self.error = Some(error);
544 }
545
546 fn mark_closed(&mut self) {
552 if !matches!(self.state, SynchronizerState::Ended(_)) {
553 self.state = SynchronizerState::Ended("Closed".to_string());
554 self.modify_ts = Local::now().naive_utc();
555 }
556 }
557
558 fn mark_stale(&mut self, header: &BlockHeader) {
560 self.state = SynchronizerState::Stale(header.clone());
561 self.modify_ts = Local::now().naive_utc();
562 }
563
564 fn mark_ready(&mut self, header: &BlockHeader) {
566 self.state = SynchronizerState::Ready(header.clone());
567 self.modify_ts = Local::now().naive_utc();
568 }
569
570 fn has_ended(&self) -> bool {
571 matches!(self.state, SynchronizerState::Ended(_))
572 }
573
574 fn is_stale(&self) -> bool {
575 matches!(self.state, SynchronizerState::Stale(_))
576 }
577
578 fn is_advanced(&self) -> bool {
579 matches!(self.state, SynchronizerState::Advanced(_))
580 }
581
582 fn get_current_header(&self) -> Option<&BlockHeader> {
586 match &self.state {
587 SynchronizerState::Ready(b) |
588 SynchronizerState::Delayed(b) |
589 SynchronizerState::Advanced(b) => Some(b),
590 _ => None,
591 }
592 }
593}
594
595#[derive(Debug, PartialEq, Clone)]
596pub struct FeedMessage<H = BlockHeader>
597where
598 H: HeaderLike,
599{
600 pub state_msgs: HashMap<String, StateSyncMessage<H>>,
601 pub sync_states: HashMap<String, SynchronizerState>,
602}
603
604impl<H> FeedMessage<H>
605where
606 H: HeaderLike,
607{
608 fn new(
609 state_msgs: HashMap<String, StateSyncMessage<H>>,
610 sync_states: HashMap<String, SynchronizerState>,
611 ) -> Self {
612 Self { state_msgs, sync_states }
613 }
614}
615
616impl<S> BlockSynchronizer<S>
617where
618 S: StateSynchronizer,
619{
620 pub fn new(
621 block_time: std::time::Duration,
622 latency_buffer: std::time::Duration,
623 max_missed_blocks: u64,
624 ) -> Self {
625 Self {
626 synchronizers: None,
627 max_messages: None,
628 block_time,
629 latency_buffer,
630 startup_timeout: block_time.mul_f64(max_missed_blocks as f64),
631 max_missed_blocks,
632 }
633 }
634
635 pub fn max_messages(&mut self, val: usize) {
641 self.max_messages = Some(val);
642 }
643
644 pub fn startup_timeout(mut self, val: Duration) {
648 self.startup_timeout = val;
649 }
650
651 pub fn register_synchronizer(mut self, id: ExtractorIdentity, synchronizer: S) -> Self {
652 let mut registered = self.synchronizers.unwrap_or_default();
653 registered.insert(id, synchronizer);
654 self.synchronizers = Some(registered);
655 self
656 }
657
658 #[cfg(test)]
659 pub fn with_short_timeouts() -> Self {
660 Self::new(Duration::from_millis(10), Duration::from_millis(10), 3)
661 }
662
663 async fn cleanup_synchronizers(
666 mut state_sync_tasks: FuturesUnordered<JoinHandle<()>>,
667 sync_close_senders: Vec<oneshot::Sender<()>>,
668 ) {
669 for close_sender in sync_close_senders {
671 let _ = close_sender.send(());
672 }
673
674 let mut completed_tasks = 0;
676 while let Ok(Some(_)) = timeout(Duration::from_secs(5), state_sync_tasks.next()).await {
677 completed_tasks += 1;
678 }
679
680 let remaining_tasks = state_sync_tasks.len();
682 if remaining_tasks > 0 {
683 warn!(
684 completed = completed_tasks,
685 timed_out = remaining_tasks,
686 "Some synchronizers timed out during cleanup and may not have shut down cleanly"
687 );
688 }
689 }
690
691 pub async fn run(
696 mut self,
697 ) -> BlockSyncResult<(JoinHandle<()>, Receiver<BlockSyncResult<FeedMessage<BlockHeader>>>)>
698 {
699 trace!("Starting BlockSynchronizer...");
700 let state_sync_tasks = FuturesUnordered::new();
701 let mut synchronizers = self
702 .synchronizers
703 .take()
704 .ok_or(BlockSynchronizerError::NoSynchronizers)?;
705 let init_results = join_all(synchronizers.iter_mut().map(|(id, s)| {
708 s.initialize()
709 .map(|res| (id.clone(), res))
710 }))
711 .await;
712 let mut to_skip = Vec::new();
713 for (extractor_id, result) in init_results {
714 match result {
715 Ok(()) => {}
716 Err(SynchronizerError::RPCError(crate::rpc::RPCError::UnknownExtractor(
717 reason,
718 ))) => {
719 warn!(%extractor_id, %reason, "Extractor not recognised by server, skipping");
720 to_skip.push(extractor_id);
721 }
722 Err(e) => {
723 return Err(BlockSynchronizerError::InitializationError {
724 extractor: extractor_id,
725 source: e,
726 })
727 }
728 }
729 }
730 for id in &to_skip {
731 synchronizers.remove(id);
732 }
733 if synchronizers.is_empty() {
734 return Err(BlockSynchronizerError::NoSynchronizers);
735 }
736
737 let mut sync_streams = Vec::with_capacity(synchronizers.len());
738 let mut sync_close_senders = Vec::new();
739 for (extractor_id, synchronizer) in synchronizers.drain() {
740 let (handle, rx) = synchronizer.start().await;
741 let (join_handle, close_sender) = handle.split();
742 state_sync_tasks.push(join_handle);
743 sync_close_senders.push(close_sender);
744
745 sync_streams.push(SynchronizerStream::new(&extractor_id, rx));
746 }
747
748 debug!("Waiting for initial synchronizer messages...");
751 let mut startup_futures = Vec::new();
752 for synchronizer in sync_streams.iter_mut() {
753 let fut = async {
754 let res = timeout(self.startup_timeout, synchronizer.rx.recv()).await;
755 (synchronizer, res)
756 };
757 startup_futures.push(fut);
758 }
759
760 let mut ready_sync_msgs = HashMap::new();
761 let initial_headers = join_all(startup_futures)
762 .await
763 .into_iter()
764 .filter_map(|(synchronizer, res)| {
765 let extractor_id = synchronizer.extractor_id.clone();
766 match res {
767 Ok(Some(Ok(msg))) => {
768 debug!(%extractor_id, height=?&msg.header.number, "Synchronizer started successfully!");
769 synchronizer.mark_ready(&msg.header);
771 let header = msg.header.clone();
772 ready_sync_msgs.insert(extractor_id.name.clone(), msg);
773 Some(header)
774 }
775 Ok(Some(Err(e))) => {
776 synchronizer.mark_errored(e);
777 None
778 }
779 Ok(None) => {
780 warn!(%extractor_id, "Synchronizer closed during startup");
785 synchronizer.mark_closed();
786 None
787 }
788 Err(_) => {
789 warn!(%extractor_id, "Timed out waiting for first message");
791 synchronizer.mark_stale(&BlockHeader::default());
792 None
793 }
794 }
795 })
796 .collect::<HashSet<_>>() .into_iter()
798 .collect::<Vec<_>>();
799
800 Self::require_active_stream(&sync_streams)?;
802 let mut block_history = BlockHistory::new(initial_headers, BLOCK_HISTORY_SIZE)?;
803 let start_header = block_history
807 .latest()
808 .ok_or(BlockHistoryError::EmptyHistory)?;
809 info!(
810 start_block=%start_header,
811 n_healthy=ready_sync_msgs.len(),
812 n_total=sync_streams.len(),
813 "Block synchronization started successfully!"
814 );
815
816 for stream in sync_streams.iter_mut() {
819 if let SynchronizerState::Ready(header) = stream.state.clone() {
820 if header.number < start_header.number {
821 debug!(
822 extractor_id=%stream.extractor_id,
823 synchronizer_block=header.number,
824 current_block=start_header.number,
825 "Marking synchronizer as delayed during initialization"
826 );
827 stream.state = SynchronizerState::Delayed(header);
828 }
829 }
830 }
831
832 let (sync_tx, sync_rx) = mpsc::channel(30);
833 let main_loop_jh = tokio::spawn(async move {
834 let mut n_iter = 1;
835 loop {
836 let msg = FeedMessage::new(
838 std::mem::take(&mut ready_sync_msgs),
839 sync_streams
840 .iter()
841 .map(|stream| (stream.extractor_id.name.to_string(), stream.state.clone()))
842 .collect(),
843 );
844 if sync_tx.send(Ok(msg)).await.is_err() {
845 info!("Receiver closed, block synchronizer terminating..");
846 return;
847 };
848
849 if let Some(max_messages) = self.max_messages {
851 if n_iter >= max_messages {
852 info!(max_messages, "StreamEnd");
853 return;
854 }
855 }
856 n_iter += 1;
857
858 let res = self
859 .handle_next_message(
860 &mut sync_streams,
861 &mut ready_sync_msgs,
862 &mut block_history,
863 )
864 .await;
865
866 if let Err(e) = res {
867 let _ = sync_tx.send(Err(e)).await;
869 return;
870 }
871 }
872 });
873
874 let nanny_jh = tokio::spawn(async move {
879 let _ = main_loop_jh.await.map_err(|e| {
881 if e.is_panic() {
882 error!("BlockSynchornizer main loop panicked: {e}")
883 }
884 });
885 debug!("Main loop exited. Closing synchronizers");
886 Self::cleanup_synchronizers(state_sync_tasks, sync_close_senders).await;
887 debug!("Shutdown complete");
888 });
889 Ok((nanny_jh, sync_rx))
890 }
891
892 async fn handle_next_message(
897 &self,
898 sync_streams: &mut [SynchronizerStream],
899 ready_sync_msgs: &mut HashMap<String, StateSyncMessage<BlockHeader>>,
900 block_history: &mut BlockHistory,
901 ) -> BlockSyncResult<()> {
902 let any_advanced = sync_streams
906 .iter()
907 .any(SynchronizerStream::is_advanced);
908 let mut recv_futures = Vec::new();
909 for stream in sync_streams.iter_mut() {
910 if stream.has_ended() {
913 continue;
914 }
915 recv_futures.push(async {
925 let res = stream
926 .try_advance(
927 block_history,
928 self.block_time,
929 self.latency_buffer,
930 self.block_time
931 .mul_f64(self.max_missed_blocks as f64),
932 any_advanced,
933 )
934 .await?;
935 Ok::<_, BlockSynchronizerError>(
936 res.map(|msg| (stream.extractor_id.name.clone(), msg)),
937 )
938 });
939 }
940 ready_sync_msgs.extend(
941 join_all(recv_futures)
942 .await
943 .into_iter()
944 .collect::<Result<Vec<_>, _>>()?
945 .into_iter()
946 .flatten(),
947 );
948
949 Self::check_streams(sync_streams)?;
952
953 if sync_streams
956 .iter()
957 .any(SynchronizerStream::is_advanced)
958 {
959 *block_history = Self::reinit_block_history(sync_streams, block_history)?;
960 } else if let Some(header) = sync_streams
961 .iter()
962 .filter_map(SynchronizerStream::get_current_header)
963 .max_by_key(|b| b.number)
964 {
965 block_history.push(header.clone())?;
966 }
967 Ok(())
970 }
971
972 fn reinit_block_history(
981 sync_streams: &mut [SynchronizerStream],
982 block_history: &mut BlockHistory,
983 ) -> Result<BlockHistory, BlockSynchronizerError> {
984 let previous = block_history
985 .latest()
986 .ok_or(BlockHistoryError::EmptyHistory)?
988 .clone();
989 let mut blocks: Vec<BlockHeader> = block_history
997 .blocks()
998 .cloned()
999 .collect();
1000 blocks.extend(
1001 sync_streams
1002 .iter()
1003 .filter_map(SynchronizerStream::get_current_header)
1004 .cloned(),
1005 );
1006 let new_block_history = BlockHistory::new(blocks, BLOCK_HISTORY_SIZE)?;
1007 let latest = new_block_history
1008 .latest()
1009 .ok_or(BlockHistoryError::EmptyHistory)?;
1011 info!(
1012 %previous,
1013 %latest,
1014 "Advanced synchronizer detected. Reinitialized block history."
1015 );
1016 sync_streams
1017 .iter_mut()
1018 .for_each(|stream| {
1019 if let Some(header) = stream.get_current_header() {
1022 if header.number < latest.number {
1023 stream.state = SynchronizerState::Delayed(header.clone());
1024 } else if header.number == latest.number {
1025 stream.state = SynchronizerState::Ready(header.clone());
1026 }
1027 }
1028 });
1029 Ok(new_block_history)
1030 }
1031
1032 fn require_active_stream(sync_streams: &[SynchronizerStream]) -> BlockSyncResult<()> {
1037 if sync_streams
1038 .iter()
1039 .any(|s| !s.has_ended() && !s.is_stale())
1040 {
1041 return Ok(());
1042 }
1043 let reason: Vec<String> = sync_streams
1044 .iter()
1045 .map(|s| format!("{} reported as {} at {}", s.extractor_id, s.state, s.modify_ts))
1046 .collect();
1047 Err(BlockSynchronizerError::NoReadySynchronizers(reason.join(", ")))
1048 }
1049
1050 fn check_streams(sync_streams: &[SynchronizerStream]) -> BlockSyncResult<()> {
1060 let mut has_any_ended = false;
1061 let mut latest_ended_stream: Option<&SynchronizerStream> = None;
1062
1063 for stream in sync_streams.iter() {
1064 if !stream.has_ended() && !stream.is_stale() {
1066 return Ok(());
1067 }
1068
1069 if stream.has_ended() {
1070 has_any_ended = true;
1071 if latest_ended_stream.is_none() ||
1072 stream.modify_ts >
1073 latest_ended_stream
1074 .as_ref()
1075 .unwrap()
1076 .modify_ts
1077 {
1078 latest_ended_stream = Some(stream);
1079 }
1080 }
1081 }
1082
1083 if !has_any_ended {
1086 return Ok(());
1087 }
1088
1089 let last_error_reason = if let Some(stream) = latest_ended_stream {
1091 if let Some(err) = &stream.error {
1092 format!("Synchronizer for {} errored with: {err}", stream.extractor_id)
1093 } else {
1094 format!("Synchronizer for {} became: {}", stream.extractor_id, stream.state)
1095 }
1096 } else {
1097 return Err(BlockSynchronizerError::NoSynchronizers);
1098 };
1099
1100 let mut reason = vec![last_error_reason];
1101
1102 sync_streams.iter().for_each(|stream| {
1103 reason.push(format!(
1104 "{} reported as {} at {}",
1105 stream.extractor_id, stream.state, stream.modify_ts
1106 ))
1107 });
1108
1109 Err(BlockSynchronizerError::NoReadySynchronizers(reason.join(", ")))
1110 }
1111}
1112
1113#[cfg(test)]
1114mod tests {
1115 use std::sync::Arc;
1116
1117 use async_trait::async_trait;
1118 use test_log::test;
1119 use tokio::sync::{oneshot, Mutex};
1120 use tycho_common::models::Chain;
1121
1122 use super::*;
1123 use crate::feed::synchronizer::{SyncResult, SynchronizerTaskHandle};
1124
1125 #[test]
1126 fn block_header_from_block_maps_block_fields() {
1127 let block = Block {
1128 number: 42,
1129 chain: Chain::Ethereum,
1130 hash: Bytes::from([1; 32]),
1131 parent_hash: Bytes::from([2; 32]),
1132 ts: chrono::DateTime::from_timestamp(1_700_000_000, 0)
1133 .expect("valid timestamp")
1134 .naive_utc(),
1135 };
1136
1137 let header = BlockHeader::from(&block);
1138
1139 assert_eq!(header.hash, block.hash);
1140 assert_eq!(header.number, block.number);
1141 assert_eq!(header.parent_hash, block.parent_hash);
1142 assert_eq!(header.timestamp, 1_700_000_000);
1143 assert!(!header.revert);
1144 assert_eq!(header.partial_block_index, None);
1145 }
1146
1147 #[derive(Clone, Debug)]
1148 enum MockBehavior {
1149 Normal, IgnoreClose, ExitImmediately, }
1153
1154 type HeaderReceiver = Receiver<SyncResult<StateSyncMessage<BlockHeader>>>;
1155
1156 #[derive(Clone)]
1157 struct MockStateSync {
1158 header_tx: mpsc::Sender<SyncResult<StateSyncMessage<BlockHeader>>>,
1159 header_rx: Arc<Mutex<Option<HeaderReceiver>>>,
1160 close_received: Arc<Mutex<bool>>,
1161 behavior: MockBehavior,
1162 close_tx: Arc<Mutex<Option<oneshot::Sender<()>>>>,
1164 }
1165
1166 impl MockStateSync {
1167 fn new() -> Self {
1168 Self::with_behavior(MockBehavior::Normal)
1169 }
1170
1171 fn with_behavior(behavior: MockBehavior) -> Self {
1172 let (tx, rx) = mpsc::channel(1);
1173 Self {
1174 header_tx: tx,
1175 header_rx: Arc::new(Mutex::new(Some(rx))),
1176 close_received: Arc::new(Mutex::new(false)),
1177 behavior,
1178 close_tx: Arc::new(Mutex::new(None)),
1179 }
1180 }
1181
1182 async fn was_close_received(&self) -> bool {
1183 *self.close_received.lock().await
1184 }
1185
1186 async fn send_header(&self, header: StateSyncMessage<BlockHeader>) -> Result<(), String> {
1187 self.header_tx
1188 .send(Ok(header))
1189 .await
1190 .map_err(|e| format!("sending header failed: {e}"))
1191 }
1192
1193 async fn trigger_close(&self) {
1195 if let Some(close_tx) = self.close_tx.lock().await.take() {
1196 let _ = close_tx.send(());
1197 }
1198 }
1199 }
1200
1201 #[async_trait]
1202 impl StateSynchronizer for MockStateSync {
1203 async fn initialize(&mut self) -> SyncResult<()> {
1204 Ok(())
1205 }
1206
1207 async fn start(
1208 mut self,
1209 ) -> (SynchronizerTaskHandle, Receiver<SyncResult<StateSyncMessage<BlockHeader>>>) {
1210 let block_rx = {
1211 let mut guard = self.header_rx.lock().await;
1212 guard
1213 .take()
1214 .expect("Block receiver was not set!")
1215 };
1216
1217 let (close_tx_for_handle, close_rx) = oneshot::channel();
1220 let (close_tx_for_test, close_rx_for_test) = oneshot::channel();
1221
1222 {
1224 let mut guard = self.close_tx.lock().await;
1225 *guard = Some(close_tx_for_test);
1226 }
1227
1228 let behavior = self.behavior.clone();
1229 let close_received_clone = self.close_received.clone();
1230 let tx = self.header_tx.clone();
1231
1232 let jh = tokio::spawn(async move {
1233 match behavior {
1234 MockBehavior::IgnoreClose => {
1235 loop {
1238 tokio::time::sleep(tokio::time::Duration::from_secs(1)).await;
1239 }
1240 }
1241 MockBehavior::ExitImmediately => {
1242 tx.send(SyncResult::Err(SynchronizerError::ConnectionError(
1244 "Simulated immediate task failure".to_string(),
1245 )))
1246 .await
1247 .unwrap();
1248 }
1249 MockBehavior::Normal => {
1250 let _ = tokio::select! {
1253 result = close_rx => result,
1254 result = close_rx_for_test => result,
1255 };
1256 let mut guard = close_received_clone.lock().await;
1257 *guard = true;
1258 }
1259 }
1260 });
1261
1262 let handle = SynchronizerTaskHandle::new(jh, close_tx_for_handle);
1263 (handle, block_rx)
1264 }
1265 }
1266
1267 fn header_message(block: u8) -> StateSyncMessage<BlockHeader> {
1268 StateSyncMessage {
1269 header: BlockHeader {
1270 number: block as u64,
1271 hash: Bytes::from(vec![block]),
1272 parent_hash: Bytes::from(vec![block - 1]),
1273 revert: false,
1274 timestamp: 1000,
1275 partial_block_index: None,
1276 },
1277 ..Default::default()
1278 }
1279 }
1280
1281 fn partial_header_message(block: u8, partial_idx: u32) -> StateSyncMessage<BlockHeader> {
1284 let hash_bytes =
1286 [(block as u64).to_be_bytes().as_slice(), partial_idx.to_be_bytes().as_slice()]
1287 .concat();
1288 StateSyncMessage {
1289 header: BlockHeader {
1290 number: block as u64,
1291 hash: Bytes::from(hash_bytes),
1292 parent_hash: Bytes::from(vec![block - 1]),
1293 revert: false,
1294 timestamp: 1000,
1295 partial_block_index: Some(partial_idx),
1296 },
1297 ..Default::default()
1298 }
1299 }
1300
1301 fn revert_header_message(block: u8) -> StateSyncMessage<BlockHeader> {
1302 StateSyncMessage {
1303 header: BlockHeader {
1304 number: block as u64,
1305 hash: Bytes::from(vec![block]),
1306 parent_hash: Bytes::from(vec![block - 1]),
1307 revert: true,
1308 timestamp: 1000,
1309 partial_block_index: None,
1310 },
1311 ..Default::default()
1312 }
1313 }
1314
1315 fn full_header(number: u64, hash: u64, parent: u64) -> BlockHeader {
1318 BlockHeader {
1319 number,
1320 hash: Bytes::from(hash.to_be_bytes()),
1321 parent_hash: Bytes::from(parent.to_be_bytes()),
1322 revert: false,
1323 timestamp: 1000,
1324 partial_block_index: None,
1325 }
1326 }
1327
1328 fn partial_header(number: u64, partial_idx: u32, parent: u64) -> BlockHeader {
1332 BlockHeader {
1333 number,
1334 hash: Bytes::from(
1335 [number.to_be_bytes().as_slice(), partial_idx.to_be_bytes().as_slice()].concat(),
1336 ),
1337 parent_hash: Bytes::from(parent.to_be_bytes()),
1338 revert: false,
1339 timestamp: 1000,
1340 partial_block_index: Some(partial_idx),
1341 }
1342 }
1343
1344 fn sealed_partial_header(number: u64, partial_idx: u32, parent: u64) -> BlockHeader {
1347 BlockHeader {
1348 number,
1349 hash: Bytes::from(number.to_be_bytes()),
1350 parent_hash: Bytes::from(parent.to_be_bytes()),
1351 revert: false,
1352 timestamp: 1000,
1353 partial_block_index: Some(partial_idx),
1354 }
1355 }
1356
1357 fn stream_in_state(name: &str, state: SynchronizerState) -> SynchronizerStream {
1360 let (_tx, rx) = mpsc::channel(1);
1361 let id = ExtractorIdentity { chain: Chain::Ethereum, name: name.to_string() };
1362 let mut stream = SynchronizerStream::new(&id, rx);
1363 stream.state = state;
1364 stream
1365 }
1366
1367 async fn receive_message(rx: &mut Receiver<BlockSyncResult<FeedMessage>>) -> FeedMessage {
1368 timeout(Duration::from_millis(100), rx.recv())
1369 .await
1370 .expect("Responds in time")
1371 .expect("Should receive first message")
1372 .expect("No error")
1373 }
1374
1375 async fn setup_block_sync(
1376 ) -> (MockStateSync, MockStateSync, JoinHandle<()>, Receiver<BlockSyncResult<FeedMessage>>)
1377 {
1378 setup_block_sync_with_behaviour(MockBehavior::Normal, MockBehavior::Normal).await
1379 }
1380
1381 async fn setup_block_sync_with_behaviour(
1383 v2_behavior: MockBehavior,
1384 v3_behavior: MockBehavior,
1385 ) -> (MockStateSync, MockStateSync, JoinHandle<()>, Receiver<BlockSyncResult<FeedMessage>>)
1386 {
1387 let v2_sync = MockStateSync::with_behavior(v2_behavior);
1388 let v3_sync = MockStateSync::with_behavior(v3_behavior);
1389
1390 let mut block_sync = BlockSynchronizer::new(
1392 Duration::from_millis(20), Duration::from_millis(10), 3, );
1396 block_sync.max_messages(10); let block_sync = block_sync
1399 .register_synchronizer(
1400 ExtractorIdentity { chain: Chain::Ethereum, name: "uniswap-v2".to_string() },
1401 v2_sync.clone(),
1402 )
1403 .register_synchronizer(
1404 ExtractorIdentity { chain: Chain::Ethereum, name: "uniswap-v3".to_string() },
1405 v3_sync.clone(),
1406 );
1407
1408 let block1_msg = header_message(1);
1410 let _ = v2_sync
1411 .send_header(block1_msg.clone())
1412 .await;
1413 let _ = v3_sync
1414 .send_header(block1_msg.clone())
1415 .await;
1416
1417 let (nanny_handle, mut rx) = block_sync
1419 .run()
1420 .await
1421 .expect("BlockSynchronizer failed to start");
1422
1423 let first_feed_msg = receive_message(&mut rx).await;
1424 assert_eq!(first_feed_msg.state_msgs.len(), 2);
1425 assert!(matches!(
1426 first_feed_msg
1427 .sync_states
1428 .get("uniswap-v2")
1429 .unwrap(),
1430 SynchronizerState::Ready(_)
1431 ));
1432 assert!(matches!(
1433 first_feed_msg
1434 .sync_states
1435 .get("uniswap-v3")
1436 .unwrap(),
1437 SynchronizerState::Ready(_)
1438 ));
1439
1440 (v2_sync, v3_sync, nanny_handle, rx)
1441 }
1442
1443 async fn shutdown_block_synchronizer(
1444 nanny_handle: JoinHandle<()>,
1445 rx: Receiver<BlockSyncResult<FeedMessage>>,
1446 ) {
1447 drop(rx);
1451 timeout(Duration::from_secs(2), nanny_handle)
1452 .await
1453 .expect("Nanny failed to exit within time")
1454 .expect("Nanny panicked");
1455 }
1456
1457 async fn send_and_assert_ready(
1459 sync: &MockStateSync,
1460 sync_name: &str,
1461 rx: &mut Receiver<BlockSyncResult<FeedMessage>>,
1462 msg: StateSyncMessage<BlockHeader>,
1463 expected_block: u64,
1464 expected_partial: Option<u32>,
1465 ) {
1466 sync.send_header(msg)
1467 .await
1468 .expect("send failed");
1469 let feed_msg = receive_message(rx).await;
1470 let state = feed_msg
1471 .sync_states
1472 .get(sync_name)
1473 .unwrap();
1474 match state {
1475 SynchronizerState::Ready(h) => {
1476 assert_eq!(h.number, expected_block, "wrong block number");
1477 assert_eq!(h.partial_block_index, expected_partial, "wrong partial index");
1478 }
1479 other => panic!("expected Ready, got {:?}", other),
1480 }
1481 }
1482
1483 #[test(tokio::test)]
1484 async fn test_two_ready_synchronizers() {
1485 let (v2_sync, v3_sync, nanny_handle, mut rx) = setup_block_sync().await;
1486
1487 let second_msg = header_message(2);
1488 v2_sync
1489 .send_header(second_msg.clone())
1490 .await
1491 .expect("send_header failed");
1492 v3_sync
1493 .send_header(second_msg.clone())
1494 .await
1495 .expect("send_header failed");
1496 let second_feed_msg = receive_message(&mut rx).await;
1497
1498 let exp2 = FeedMessage {
1499 state_msgs: [
1500 ("uniswap-v2".to_string(), second_msg.clone()),
1501 ("uniswap-v3".to_string(), second_msg.clone()),
1502 ]
1503 .into_iter()
1504 .collect(),
1505 sync_states: [
1506 ("uniswap-v3".to_string(), SynchronizerState::Ready(second_msg.header.clone())),
1507 ("uniswap-v2".to_string(), SynchronizerState::Ready(second_msg.header.clone())),
1508 ]
1509 .into_iter()
1510 .collect(),
1511 };
1512 assert_eq!(second_feed_msg, exp2);
1513
1514 shutdown_block_synchronizer(nanny_handle, rx).await;
1515 }
1516
1517 #[test]
1526 fn test_reinit_preserves_history_for_revert_below_advanced_tip() {
1527 let old_blocks = vec![
1529 full_header(323, 323, 322),
1530 full_header(324, 324, 323),
1531 full_header(325, 325, 324),
1532 ];
1533 let mut old_history = BlockHistory::new(old_blocks, 15).unwrap();
1534
1535 let mut streams = vec![
1538 stream_in_state("aerodrome", SynchronizerState::Advanced(full_header(326, 326, 325))),
1539 stream_in_state("uniswap_v3", SynchronizerState::Delayed(full_header(325, 325, 324))),
1540 ];
1541
1542 let mut new_history = BlockSynchronizer::<MockStateSync>::reinit_block_history(
1543 &mut streams,
1544 &mut old_history,
1545 )
1546 .expect("reinit failed");
1547
1548 let revert = BlockHeader {
1550 number: 325,
1551 hash: Bytes::from(325u64.to_be_bytes()),
1552 parent_hash: Bytes::from(324u64.to_be_bytes()),
1553 revert: true,
1554 timestamp: 1000,
1555 partial_block_index: None,
1556 };
1557 new_history
1558 .push(revert)
1559 .expect("revert below advanced tip must not exceed history");
1560 }
1561
1562 #[test]
1567 fn test_reinit_discards_retained_history_on_detached_advanced_block() {
1568 let old_blocks = vec![
1569 full_header(323, 323, 322),
1570 full_header(324, 324, 323),
1571 full_header(325, 325, 324),
1572 ];
1573 let mut old_history = BlockHistory::new(old_blocks, BLOCK_HISTORY_SIZE).unwrap();
1574
1575 let mut streams = vec![stream_in_state(
1577 "aerodrome",
1578 SynchronizerState::Advanced(full_header(400, 400, 399)),
1579 )];
1580
1581 let new_history = BlockSynchronizer::<MockStateSync>::reinit_block_history(
1582 &mut streams,
1583 &mut old_history,
1584 )
1585 .expect("reinit failed");
1586
1587 let retained: Vec<u64> = new_history
1588 .blocks()
1589 .map(|b| b.number)
1590 .collect();
1591 assert_eq!(
1592 retained,
1593 vec![400],
1594 "detached advanced block must not be stitched to old history"
1595 );
1596 }
1597
1598 #[test]
1607 fn test_reinit_preserves_history_for_partial_advanced_block() {
1608 let old_blocks = vec![
1610 full_header(323, 323, 322),
1611 full_header(324, 324, 323),
1612 partial_header(325, 2, 324),
1613 ];
1614 let mut old_history = BlockHistory::new(old_blocks, BLOCK_HISTORY_SIZE).unwrap();
1615
1616 let advanced = partial_header(326, 0, 325);
1619 assert_eq!(
1620 old_history
1621 .determine_block_position(&advanced)
1622 .unwrap(),
1623 BlockPosition::Advanced
1624 );
1625
1626 let mut streams = vec![
1627 stream_in_state("aerodrome", SynchronizerState::Advanced(advanced)),
1628 stream_in_state("uniswap_v3", SynchronizerState::Delayed(partial_header(325, 2, 324))),
1629 ];
1630
1631 let new_history = BlockSynchronizer::<MockStateSync>::reinit_block_history(
1632 &mut streams,
1633 &mut old_history,
1634 )
1635 .expect("reinit failed");
1636
1637 let retained: Vec<u64> = new_history
1638 .blocks()
1639 .map(|b| b.number)
1640 .collect();
1641 assert_eq!(retained, vec![323, 324, 325, 326]);
1642 }
1643
1644 #[test]
1649 fn test_revert_below_tip_resolves_after_partial_advanced_reinit() {
1650 let old_blocks = vec![
1651 full_header(323, 323, 322),
1652 full_header(324, 324, 323),
1653 partial_header(325, 2, 324),
1654 ];
1655 let mut old_history = BlockHistory::new(old_blocks, BLOCK_HISTORY_SIZE).unwrap();
1656
1657 let mut streams = vec![
1658 stream_in_state("aerodrome", SynchronizerState::Advanced(partial_header(326, 0, 325))),
1659 stream_in_state("uniswap_v3", SynchronizerState::Delayed(partial_header(325, 2, 324))),
1660 ];
1661
1662 let mut new_history = BlockSynchronizer::<MockStateSync>::reinit_block_history(
1663 &mut streams,
1664 &mut old_history,
1665 )
1666 .expect("reinit failed");
1667
1668 new_history
1671 .push(sealed_partial_header(326, 5, 325))
1672 .expect("sealed partial push failed");
1673 new_history
1674 .push(partial_header(327, 0, 326))
1675 .expect("next partial push failed");
1676
1677 let revert = BlockHeader {
1680 number: 326,
1681 hash: Bytes::from(326u64.to_be_bytes()),
1682 parent_hash: Bytes::from(325u64.to_be_bytes()),
1683 revert: true,
1684 timestamp: 1000,
1685 partial_block_index: None,
1686 };
1687 new_history
1688 .push(revert)
1689 .expect("1-block revert after partial-advanced reinit must resolve");
1690
1691 let retained: Vec<u64> = new_history
1694 .blocks()
1695 .map(|b| b.number)
1696 .collect();
1697 assert_eq!(retained, vec![323, 324, 325, 326]);
1698 let latest = new_history.latest().unwrap();
1699 assert_eq!(latest.number, 326);
1700 assert!(latest.revert);
1701 assert!(!latest.is_partial());
1702 }
1703
1704 #[test(tokio::test)]
1705 async fn test_delayed_synchronizer_catches_up() {
1706 let (v2_sync, v3_sync, nanny_handle, mut rx) = setup_block_sync().await;
1707
1708 let block2_msg = header_message(2);
1710 v2_sync
1711 .send_header(block2_msg.clone())
1712 .await
1713 .expect("send_header failed");
1714
1715 let second_feed_msg = receive_message(&mut rx).await;
1717 debug!("Consumed second message for v2");
1718
1719 assert!(second_feed_msg
1720 .state_msgs
1721 .contains_key("uniswap-v2"));
1722 assert!(matches!(
1723 second_feed_msg.sync_states.get("uniswap-v2").unwrap(),
1724 SynchronizerState::Ready(header) if header.number == 2
1725 ));
1726 assert!(!second_feed_msg
1727 .state_msgs
1728 .contains_key("uniswap-v3"));
1729 assert!(matches!(
1730 second_feed_msg.sync_states.get("uniswap-v3").unwrap(),
1731 SynchronizerState::Delayed(header) if header.number == 1
1732 ));
1733
1734 v3_sync
1736 .send_header(block2_msg.clone())
1737 .await
1738 .expect("send_header failed");
1739
1740 let block3_msg = header_message(3);
1742 v2_sync
1743 .send_header(block3_msg.clone())
1744 .await
1745 .expect("send_header failed");
1746 v3_sync
1747 .send_header(block3_msg)
1748 .await
1749 .expect("send_header failed");
1750
1751 let mut third_feed_msg = receive_message(&mut rx).await;
1754
1755 if !third_feed_msg
1758 .state_msgs
1759 .contains_key("uniswap-v2")
1760 {
1761 third_feed_msg = rx
1762 .recv()
1763 .await
1764 .expect("header channel was closed")
1765 .expect("no error");
1766 }
1767 assert!(third_feed_msg
1768 .state_msgs
1769 .contains_key("uniswap-v2"));
1770 assert!(third_feed_msg
1771 .state_msgs
1772 .contains_key("uniswap-v3"));
1773 assert!(matches!(
1774 third_feed_msg.sync_states.get("uniswap-v2").unwrap(),
1775 SynchronizerState::Ready(header) if header.number == 3
1776 ));
1777 assert!(matches!(
1778 third_feed_msg.sync_states.get("uniswap-v3").unwrap(),
1779 SynchronizerState::Ready(header) if header.number == 3
1780 ));
1781
1782 shutdown_block_synchronizer(nanny_handle, rx).await;
1783 }
1784
1785 #[test(tokio::test)]
1786 async fn test_different_start_blocks() {
1787 let v2_sync = MockStateSync::new();
1788 let v3_sync = MockStateSync::new();
1789 let block_sync = BlockSynchronizer::with_short_timeouts()
1790 .register_synchronizer(
1791 ExtractorIdentity { chain: Chain::Ethereum, name: "uniswap-v2".to_string() },
1792 v2_sync.clone(),
1793 )
1794 .register_synchronizer(
1795 ExtractorIdentity { chain: Chain::Ethereum, name: "uniswap-v3".to_string() },
1796 v3_sync.clone(),
1797 );
1798
1799 let block1_msg = header_message(1);
1801 let block2_msg = header_message(2);
1802
1803 let _ = v2_sync
1804 .send_header(block1_msg.clone())
1805 .await;
1806 v3_sync
1807 .send_header(block2_msg.clone())
1808 .await
1809 .expect("send_header failed");
1810
1811 let (jh, mut rx) = block_sync
1813 .run()
1814 .await
1815 .expect("BlockSynchronizer failed to start.");
1816
1817 let first_feed_msg = receive_message(&mut rx).await;
1819 assert!(matches!(
1820 first_feed_msg.sync_states.get("uniswap-v2").unwrap(),
1821 SynchronizerState::Delayed(header) if header.number == 1
1822 ));
1823 assert!(matches!(
1824 first_feed_msg.sync_states.get("uniswap-v3").unwrap(),
1825 SynchronizerState::Ready(header) if header.number == 2
1826 ));
1827
1828 v2_sync
1830 .send_header(block2_msg.clone())
1831 .await
1832 .expect("send_header failed");
1833
1834 let block3_msg = header_message(3);
1836 let _ = v2_sync
1837 .send_header(block3_msg.clone())
1838 .await;
1839 v3_sync
1840 .send_header(block3_msg.clone())
1841 .await
1842 .expect("send_header failed");
1843
1844 let second_feed_msg = receive_message(&mut rx).await;
1846 assert_eq!(second_feed_msg.state_msgs.len(), 2);
1847 assert!(matches!(
1848 second_feed_msg.sync_states.get("uniswap-v2").unwrap(),
1849 SynchronizerState::Ready(header) if header.number == 3
1850 ));
1851 assert!(matches!(
1852 second_feed_msg.sync_states.get("uniswap-v3").unwrap(),
1853 SynchronizerState::Ready(header) if header.number == 3
1854 ));
1855
1856 shutdown_block_synchronizer(jh, rx).await;
1857 }
1858
1859 #[test(tokio::test)]
1860 async fn test_synchronizer_fails_other_goes_stale() {
1861 let (_v2_sync, v3_sync, nanny_handle, mut sync_rx) =
1862 setup_block_sync_with_behaviour(MockBehavior::ExitImmediately, MockBehavior::Normal)
1863 .await;
1864
1865 let mut error_reported = false;
1866 for _ in 0..3 {
1867 if let Some(msg) = sync_rx.recv().await {
1868 match msg {
1869 Err(_) => error_reported = true,
1870 Ok(msg) => {
1871 assert!(matches!(
1872 msg.sync_states
1873 .get("uniswap-v3")
1874 .unwrap(),
1875 SynchronizerState::Delayed(_)
1876 ));
1877 assert!(matches!(
1878 msg.sync_states
1879 .get("uniswap-v2")
1880 .unwrap(),
1881 SynchronizerState::Ended(_)
1882 ));
1883 }
1884 }
1885 }
1886 }
1887 assert!(error_reported, "BlockSynchronizer did not report final error");
1888
1889 let result = timeout(Duration::from_secs(2), nanny_handle).await;
1891 assert!(result.is_ok(), "Nanny should complete when synchronizer task exits");
1892
1893 assert!(
1895 v3_sync.was_close_received().await,
1896 "v3_sync should have received close signal during cleanup"
1897 );
1898 }
1899
1900 #[test(tokio::test)]
1901 async fn test_cleanup_timeout_warning() {
1902 let (_v2_sync, _v3_sync, nanny_handle, _rx) = setup_block_sync_with_behaviour(
1905 MockBehavior::ExitImmediately,
1906 MockBehavior::IgnoreClose,
1907 )
1908 .await;
1909
1910 let result = timeout(Duration::from_secs(10), nanny_handle).await;
1912 assert!(
1913 result.is_ok(),
1914 "Nanny should complete even when some synchronizers timeout during cleanup"
1915 );
1916
1917 }
1921
1922 #[test(tokio::test)]
1923 async fn test_one_synchronizer_goes_stale_while_other_works() {
1924 let (_v2_sync, v3_sync, nanny_handle, mut rx) = setup_block_sync().await;
1926
1927 let block2_msg = header_message(2);
1929 let _ = v3_sync
1930 .send_header(block2_msg.clone())
1931 .await;
1932 let second_feed_msg = receive_message(&mut rx).await;
1936 assert!(second_feed_msg
1937 .state_msgs
1938 .contains_key("uniswap-v3"));
1939 assert!(!second_feed_msg
1940 .state_msgs
1941 .contains_key("uniswap-v2"));
1942 assert!(matches!(
1943 second_feed_msg
1944 .sync_states
1945 .get("uniswap-v3")
1946 .unwrap(),
1947 SynchronizerState::Ready(_)
1948 ));
1949 if let Some(v2_state) = second_feed_msg
1951 .sync_states
1952 .get("uniswap-v2")
1953 {
1954 if matches!(v2_state, SynchronizerState::Delayed(_)) {
1955 assert!(
1957 !nanny_handle.is_finished(),
1958 "Nanny should still be running when synchronizer is delayed (not stale yet)"
1959 );
1960 }
1961 }
1962
1963 tokio::time::sleep(Duration::from_millis(15)).await;
1965
1966 let block3_msg = header_message(3);
1968 let _ = v3_sync
1969 .send_header(block3_msg.clone())
1970 .await;
1971
1972 tokio::time::sleep(Duration::from_millis(40)).await;
1973
1974 let mut stale_found = false;
1975 for _ in 0..2 {
1976 if let Some(Ok(msg)) = rx.recv().await {
1977 if let Some(SynchronizerState::Stale(_)) = msg.sync_states.get("uniswap-v2") {
1978 stale_found = true;
1979 }
1980 }
1981 }
1982 assert!(stale_found, "v2 synchronizer should be stale");
1983
1984 shutdown_block_synchronizer(nanny_handle, rx).await;
1985 }
1986
1987 #[test(tokio::test)]
1988 async fn test_all_synchronizers_stale_loop_continues() {
1989 let (v2_sync, v3_sync, nanny_handle, mut rx) = setup_block_sync().await;
1992
1993 let mut seen_delayed = false;
1995 let mut seen_stale = false;
1996 let start_time = tokio::time::Instant::now();
1997
1998 while let Ok(Some(Ok(msg))) =
1999 tokio::time::timeout(Duration::from_millis(50), rx.recv()).await
2000 {
2001 let v2_state = msg.sync_states.get("uniswap-v2");
2002 let v3_state = msg.sync_states.get("uniswap-v3");
2003
2004 if !seen_delayed &&
2005 (matches!(v2_state, Some(SynchronizerState::Delayed(_))) ||
2006 matches!(v3_state, Some(SynchronizerState::Delayed(_))))
2007 {
2008 seen_delayed = true;
2009 assert!(
2010 !nanny_handle.is_finished(),
2011 "Nanny must still run when synchronizers are Delayed"
2012 );
2013 }
2014
2015 if matches!(v2_state, Some(SynchronizerState::Stale(_))) &&
2016 matches!(v3_state, Some(SynchronizerState::Stale(_)))
2017 {
2018 seen_stale = true;
2019 assert!(
2020 !nanny_handle.is_finished(),
2021 "Main loop must not exit when all synchronizers are Stale (awaiting recovery)"
2022 );
2023 break;
2024 }
2025
2026 if start_time.elapsed() > Duration::from_millis(500) {
2027 break;
2028 }
2029 }
2030
2031 assert!(seen_delayed, "Synchronizers should transition through Delayed first");
2032 assert!(seen_stale, "Both synchronizers should reach Stale state");
2033
2034 v2_sync.trigger_close().await;
2039 v3_sync.trigger_close().await;
2040 drop(v2_sync);
2041 drop(v3_sync);
2042
2043 let mut error_reported = false;
2045 while let Some(msg) = rx.recv().await {
2046 if msg.is_err() {
2047 error_reported = true;
2048 }
2049 }
2050 assert!(error_reported, "Expected an error after all synchronizers ended");
2051
2052 let nanny_result = timeout(Duration::from_secs(2), nanny_handle).await;
2053 assert!(nanny_result.is_ok(), "Nanny should complete after all synchronizers ended");
2054 }
2055
2056 #[test(tokio::test)]
2057 async fn test_all_synchronizers_recover_after_going_stale() {
2058 let v2_sync = MockStateSync::new();
2061 let v3_sync = MockStateSync::new();
2062 let block_sync =
2063 BlockSynchronizer::new(Duration::from_millis(20), Duration::from_millis(10), 3)
2064 .register_synchronizer(
2065 ExtractorIdentity { chain: Chain::Ethereum, name: "uniswap-v2".to_string() },
2066 v2_sync.clone(),
2067 )
2068 .register_synchronizer(
2069 ExtractorIdentity { chain: Chain::Ethereum, name: "uniswap-v3".to_string() },
2070 v3_sync.clone(),
2071 );
2072
2073 v2_sync
2074 .send_header(header_message(1))
2075 .await
2076 .unwrap();
2077 v3_sync
2078 .send_header(header_message(1))
2079 .await
2080 .unwrap();
2081
2082 let (nanny_handle, mut rx) = block_sync
2083 .run()
2084 .await
2085 .expect("BlockSynchronizer start failed");
2086
2087 let first_msg = receive_message(&mut rx).await;
2088 assert!(matches!(
2089 first_msg.sync_states.get("uniswap-v2").unwrap(),
2090 SynchronizerState::Ready(h) if h.number == 1
2091 ));
2092 assert!(matches!(
2093 first_msg.sync_states.get("uniswap-v3").unwrap(),
2094 SynchronizerState::Ready(h) if h.number == 1
2095 ));
2096
2097 let mut seen_stale = false;
2099 let start_time = tokio::time::Instant::now();
2100 while let Ok(Some(Ok(msg))) =
2101 tokio::time::timeout(Duration::from_millis(50), rx.recv()).await
2102 {
2103 let v2 = msg
2104 .sync_states
2105 .get("uniswap-v2")
2106 .unwrap();
2107 let v3 = msg
2108 .sync_states
2109 .get("uniswap-v3")
2110 .unwrap();
2111 if matches!(v2, SynchronizerState::Stale(_)) &&
2112 matches!(v3, SynchronizerState::Stale(_))
2113 {
2114 seen_stale = true;
2115 assert!(
2116 !nanny_handle.is_finished(),
2117 "Main loop must not exit while synchronizers are Stale"
2118 );
2119 break;
2120 }
2121 if start_time.elapsed() > Duration::from_millis(500) {
2122 break;
2123 }
2124 }
2125 assert!(seen_stale, "Both synchronizers should go Stale");
2126
2127 v2_sync
2129 .send_header(header_message(5))
2130 .await
2131 .unwrap();
2132 v3_sync
2133 .send_header(header_message(5))
2134 .await
2135 .unwrap();
2136
2137 let mut recovered = false;
2140 for _ in 0..20 {
2141 let msg = receive_message(&mut rx).await;
2142 let v2 = msg
2143 .sync_states
2144 .get("uniswap-v2")
2145 .unwrap();
2146 let v3 = msg
2147 .sync_states
2148 .get("uniswap-v3")
2149 .unwrap();
2150 if matches!(v2, SynchronizerState::Ready(h) if h.number == 5) &&
2151 matches!(v3, SynchronizerState::Ready(h) if h.number == 5)
2152 {
2153 recovered = true;
2154 break;
2155 }
2156 }
2157 assert!(recovered, "Both synchronizers should recover to Ready at block 5");
2158
2159 drop(rx);
2161 timeout(Duration::from_secs(2), nanny_handle)
2162 .await
2163 .expect("Nanny timed out")
2164 .expect("Nanny panicked");
2165 }
2166
2167 #[test(tokio::test)]
2168 async fn test_stale_synchronizer_recovers() {
2169 let (v2_sync, v3_sync, nanny_handle, mut rx) = setup_block_sync().await;
2171
2172 tokio::time::sleep(Duration::from_millis(50)).await;
2174 let block2_msg = header_message(2);
2175 let _ = v2_sync
2176 .send_header(block2_msg.clone())
2177 .await;
2178
2179 for _ in 0..2 {
2181 if let Some(msg) = rx.recv().await {
2182 if let Ok(msg) = msg {
2183 if matches!(
2184 msg.sync_states
2185 .get("uniswap-v2")
2186 .unwrap(),
2187 SynchronizerState::Ready(_)
2188 ) {
2189 assert!(matches!(
2190 msg.sync_states
2191 .get("uniswap-v3")
2192 .unwrap(),
2193 SynchronizerState::Delayed(_)
2194 ));
2195 break;
2196 };
2197 }
2198 } else {
2199 panic!("Channel closed unexpectedly")
2200 }
2201 }
2202
2203 tokio::time::sleep(Duration::from_millis(15)).await;
2205 let block3_msg = header_message(3);
2206 let _ = v2_sync
2207 .send_header(block3_msg.clone())
2208 .await;
2209 let third_msg = receive_message(&mut rx).await;
2210 dbg!(&third_msg);
2211 assert!(matches!(
2212 third_msg
2213 .sync_states
2214 .get("uniswap-v2")
2215 .unwrap(),
2216 SynchronizerState::Ready(_)
2217 ));
2218 assert!(matches!(
2219 third_msg
2220 .sync_states
2221 .get("uniswap-v3")
2222 .unwrap(),
2223 SynchronizerState::Stale(_)
2224 ));
2225
2226 let block4_msg = header_message(4);
2227 let _ = v3_sync
2228 .send_header(block2_msg.clone())
2229 .await;
2230 let _ = v3_sync
2231 .send_header(block3_msg.clone())
2232 .await;
2233 let _ = v3_sync
2234 .send_header(block4_msg.clone())
2235 .await;
2236 let _ = v2_sync
2237 .send_header(block4_msg.clone())
2238 .await;
2239 let fourth_msg = receive_message(&mut rx).await;
2240 assert!(matches!(
2241 fourth_msg
2242 .sync_states
2243 .get("uniswap-v2")
2244 .unwrap(),
2245 SynchronizerState::Ready(_)
2246 ));
2247 assert!(matches!(
2248 fourth_msg
2249 .sync_states
2250 .get("uniswap-v3")
2251 .unwrap(),
2252 SynchronizerState::Ready(_)
2253 ));
2254
2255 shutdown_block_synchronizer(nanny_handle, rx).await;
2256
2257 assert!(
2259 v2_sync.was_close_received().await,
2260 "v2_sync should have received close signal during cleanup"
2261 );
2262 assert!(
2263 v3_sync.was_close_received().await,
2264 "v3_sync should have received close signal during cleanup"
2265 );
2266 }
2267
2268 #[test(tokio::test)]
2269 async fn test_all_synchronizer_advanced() {
2270 let (v2_sync, v3_sync, nanny_handle, mut rx) = setup_block_sync().await;
2274
2275 let block3 = header_message(3);
2276 v2_sync
2277 .send_header(block3.clone())
2278 .await
2279 .unwrap();
2280 v3_sync
2281 .send_header(block3)
2282 .await
2283 .unwrap();
2284
2285 let msg = receive_message(&mut rx).await;
2286 matches!(
2287 msg.sync_states
2288 .get("uniswap-v2")
2289 .unwrap(),
2290 SynchronizerState::Ready(_)
2291 );
2292 matches!(
2293 msg.sync_states
2294 .get("uniswap-v3")
2295 .unwrap(),
2296 SynchronizerState::Ready(_)
2297 );
2298
2299 shutdown_block_synchronizer(nanny_handle, rx).await;
2300 }
2301
2302 #[test(tokio::test)]
2303 async fn test_one_synchronizer_advanced() {
2304 let (v2_sync, v3_sync, nanny_handle, mut rx) = setup_block_sync().await;
2305
2306 let block2 = header_message(2);
2307 let block4 = header_message(4);
2308 v2_sync
2309 .send_header(block4.clone())
2310 .await
2311 .unwrap();
2312 v3_sync
2313 .send_header(block2.clone())
2314 .await
2315 .unwrap();
2316
2317 let msg = receive_message(&mut rx).await;
2318 matches!(
2319 msg.sync_states
2320 .get("uniswap-v2")
2321 .unwrap(),
2322 SynchronizerState::Ready(_)
2323 );
2324 matches!(
2325 msg.sync_states
2326 .get("uniswap-v3")
2327 .unwrap(),
2328 SynchronizerState::Delayed(_)
2329 );
2330
2331 shutdown_block_synchronizer(nanny_handle, rx).await;
2332 }
2333
2334 #[test(tokio::test)]
2335 async fn test_partial_blocks_normal_operation() {
2336 let (v2_sync, _v3_sync, nanny_handle, mut rx) = setup_block_sync().await;
2339
2340 send_and_assert_ready(
2342 &v2_sync,
2343 "uniswap-v2",
2344 &mut rx,
2345 partial_header_message(2, 0),
2346 2,
2347 Some(0),
2348 )
2349 .await;
2350 send_and_assert_ready(
2351 &v2_sync,
2352 "uniswap-v2",
2353 &mut rx,
2354 partial_header_message(2, 3),
2355 2,
2356 Some(3),
2357 )
2358 .await;
2359 send_and_assert_ready(
2360 &v2_sync,
2361 "uniswap-v2",
2362 &mut rx,
2363 partial_header_message(2, 7),
2364 2,
2365 Some(7),
2366 )
2367 .await;
2368
2369 send_and_assert_ready(
2371 &v2_sync,
2372 "uniswap-v2",
2373 &mut rx,
2374 partial_header_message(3, 0),
2375 3,
2376 Some(0),
2377 )
2378 .await;
2379 send_and_assert_ready(
2380 &v2_sync,
2381 "uniswap-v2",
2382 &mut rx,
2383 partial_header_message(3, 2),
2384 3,
2385 Some(2),
2386 )
2387 .await;
2388
2389 shutdown_block_synchronizer(nanny_handle, rx).await;
2390 }
2391
2392 #[test(tokio::test)]
2393 async fn test_partial_blocks_handles_reverts() {
2394 let (v2_sync, _v3_sync, nanny_handle, mut rx) = setup_block_sync().await;
2397
2398 send_and_assert_ready(&v2_sync, "uniswap-v2", &mut rx, header_message(2), 2, None).await;
2400
2401 send_and_assert_ready(
2403 &v2_sync,
2404 "uniswap-v2",
2405 &mut rx,
2406 partial_header_message(3, 0),
2407 3,
2408 Some(0),
2409 )
2410 .await;
2411 send_and_assert_ready(
2412 &v2_sync,
2413 "uniswap-v2",
2414 &mut rx,
2415 partial_header_message(3, 2),
2416 3,
2417 Some(2),
2418 )
2419 .await;
2420
2421 send_and_assert_ready(&v2_sync, "uniswap-v2", &mut rx, revert_header_message(2), 2, None)
2423 .await;
2424
2425 send_and_assert_ready(&v2_sync, "uniswap-v2", &mut rx, header_message(3), 3, None).await;
2427
2428 shutdown_block_synchronizer(nanny_handle, rx).await;
2429 }
2430
2431 #[test(tokio::test)]
2432 async fn test_partial_blocks_delayed_synchronizer_catches_up() {
2433 let (v2_sync, v3_sync, nanny_handle, mut rx) = setup_block_sync().await;
2436
2437 let partial_0 = partial_header_message(2, 0);
2439 v2_sync
2440 .send_header(partial_0.clone())
2441 .await
2442 .expect("send partial 0 failed");
2443
2444 let msg = receive_message(&mut rx).await;
2445 assert!(msg
2447 .state_msgs
2448 .contains_key("uniswap-v2"));
2449 assert!(!msg
2450 .state_msgs
2451 .contains_key("uniswap-v3"));
2452 assert!(matches!(
2453 msg.sync_states.get("uniswap-v2").unwrap(),
2454 SynchronizerState::Ready(h) if h.partial_block_index == Some(0)
2455 ));
2456 assert!(matches!(
2457 msg.sync_states
2458 .get("uniswap-v3")
2459 .unwrap(),
2460 SynchronizerState::Delayed(_)
2461 ));
2462
2463 let partial_2 = partial_header_message(2, 2);
2465 v2_sync
2466 .send_header(partial_2.clone())
2467 .await
2468 .expect("send partial 2 failed");
2469 v3_sync
2470 .send_header(partial_0.clone())
2471 .await
2472 .expect("v3 catch up partial 0 failed");
2473 v3_sync
2474 .send_header(partial_2.clone())
2475 .await
2476 .expect("v3 catch up partial 2 failed");
2477
2478 let mut v3_ready = false;
2480 for _ in 0..3 {
2481 let msg = receive_message(&mut rx).await;
2482 if matches!(
2483 msg.sync_states.get("uniswap-v3").unwrap(),
2484 SynchronizerState::Ready(h) if h.partial_block_index == Some(2)
2485 ) {
2486 v3_ready = true;
2487 break;
2488 }
2489 }
2490 assert!(v3_ready, "v3 caught up to partial 2");
2491
2492 shutdown_block_synchronizer(nanny_handle, rx).await;
2493 }
2494}