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,
63 pub timestamp: u64,
64 #[serde(default, skip_serializing_if = "Option::is_none")]
69 pub partial_block_index: Option<u32>,
70}
71
72impl BlockHeader {
73 fn is_partial(&self) -> bool {
74 self.partial_block_index.is_some()
75 }
76}
77
78impl Display for BlockHeader {
79 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
80 let short_hash = if self.hash.len() >= 4 {
82 hex::encode(&self.hash[..4]) } else {
84 hex::encode(&self.hash)
85 };
86
87 write!(f, "Block #{} [0x{short_hash}..]", self.number)?;
88 if let Some(idx) = self.partial_block_index {
89 write!(f, " (partial {idx})")?;
90 }
91 if self.revert {
92 write!(f, " (revert)")?;
93 }
94 Ok(())
95 }
96}
97
98impl From<&Block> for BlockHeader {
99 fn from(block: &Block) -> Self {
100 Self {
101 hash: block.hash.clone(),
102 number: block.number,
103 parent_hash: block.parent_hash.clone(),
104 timestamp: block.ts.and_utc().timestamp() as u64,
105 ..Default::default()
106 }
107 }
108}
109
110impl From<&BlockAggregatedChanges> for BlockHeader {
111 fn from(block_changes: &BlockAggregatedChanges) -> Self {
112 Self {
113 revert: block_changes.revert,
114 partial_block_index: block_changes.partial_block_index,
115 ..Self::from(&block_changes.block)
116 }
117 }
118}
119
120impl HeaderLike for BlockHeader {
121 fn block(self) -> Option<BlockHeader> {
122 Some(self)
123 }
124
125 fn block_number_or_timestamp(self) -> u64 {
126 self.number
127 }
128}
129
130#[derive(Error, Debug)]
131pub enum BlockSynchronizerError {
132 #[error("Failed to initialize extractor '{extractor}': {source}")]
133 InitializationError {
134 extractor: ExtractorIdentity,
135 #[source]
136 source: SynchronizerError,
137 },
138
139 #[error("Failed to process new block: {0}")]
140 BlockHistoryError(#[from] BlockHistoryError),
141
142 #[error("Not a single synchronizer was ready: {0}")]
143 NoReadySynchronizers(String),
144
145 #[error("No synchronizers were set")]
146 NoSynchronizers,
147
148 #[error("Failed to convert duration: {0}")]
149 DurationConversionError(String),
150}
151
152type BlockSyncResult<T> = Result<T, BlockSynchronizerError>;
153
154pub struct BlockSynchronizer<S> {
203 synchronizers: Option<HashMap<ExtractorIdentity, S>>,
204 block_time: std::time::Duration,
206 latency_buffer: std::time::Duration,
208 startup_timeout: std::time::Duration,
210 max_messages: Option<usize>,
212 max_missed_blocks: u64,
214}
215
216#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
217#[serde(tag = "status", rename_all = "lowercase")]
218pub enum SynchronizerState {
219 Started,
221 Ready(BlockHeader),
223 Delayed(BlockHeader),
226 Stale(BlockHeader),
230 Advanced(BlockHeader),
235 Ended(String),
237}
238
239impl Display for SynchronizerState {
240 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
241 match self {
242 SynchronizerState::Started => write!(f, "Started"),
243 SynchronizerState::Ready(b) => write!(f, "Started({})", b.number),
244 SynchronizerState::Delayed(b) => write!(f, "Delayed({})", b.number),
245 SynchronizerState::Stale(b) => write!(f, "Stale({})", b.number),
246 SynchronizerState::Advanced(b) => write!(f, "Advanced({})", b.number),
247 SynchronizerState::Ended(reason) => write!(f, "Ended({})", reason),
248 }
249 }
250}
251
252pub struct SynchronizerStream {
253 extractor_id: ExtractorIdentity,
254 state: SynchronizerState,
255 error: Option<SynchronizerError>,
256 modify_ts: NaiveDateTime,
257 rx: Receiver<SyncResult<StateSyncMessage<BlockHeader>>>,
258}
259
260impl SynchronizerStream {
261 fn new(
262 extractor_id: &ExtractorIdentity,
263 rx: Receiver<SyncResult<StateSyncMessage<BlockHeader>>>,
264 ) -> Self {
265 Self {
266 extractor_id: extractor_id.clone(),
267 state: SynchronizerState::Started,
268 error: None,
269 modify_ts: Local::now().naive_utc(),
270 rx,
271 }
272 }
273
274 async fn try_advance(
284 &mut self,
285 block_history: &BlockHistory,
286 block_time: std::time::Duration,
287 latency_buffer: std::time::Duration,
288 stale_threshold: std::time::Duration,
289 skip_wait: bool,
290 ) -> BlockSyncResult<Option<StateSyncMessage<BlockHeader>>> {
291 let extractor_id = self.extractor_id.clone();
292 let latest_block = block_history.latest();
293
294 match &self.state {
295 SynchronizerState::Started | SynchronizerState::Ended(_) => {
296 warn!(state=?&self.state, "Advancing Synchronizer in this state not supported!");
297 Ok(None)
298 }
299 SynchronizerState::Advanced(b) => {
300 let future_block = b.clone();
301 self.transition(future_block, block_history, stale_threshold)?;
303 Ok(None)
304 }
305 SynchronizerState::Ready(previous_block) => {
306 self.try_recv_next_expected(
308 block_time + latency_buffer,
309 block_history,
310 previous_block.clone(),
311 stale_threshold,
312 )
313 .await
314 }
315 SynchronizerState::Delayed(old_block) => {
316 debug!(
318 %old_block,
319 latest_block=opt(&latest_block),
320 %extractor_id,
321 "Trying to catch up to latest block"
322 );
323 let timeout =
324 if skip_wait { std::time::Duration::ZERO } else { block_time + latency_buffer };
325 self.try_catch_up(block_history, timeout, stale_threshold)
326 .await
327 }
328 SynchronizerState::Stale(old_block) => {
329 debug!(
331 %old_block,
332 latest_block=opt(&latest_block),
333 %extractor_id,
334 "Trying to catch up stale synchronizer to latest block"
335 );
336 let timeout = if skip_wait { std::time::Duration::ZERO } else { block_time };
337 self.try_catch_up(block_history, timeout, stale_threshold)
338 .await
339 }
340 }
341 }
342
343 async fn try_recv_next_expected(
351 &mut self,
352 max_wait: std::time::Duration,
353 block_history: &BlockHistory,
354 previous_block: BlockHeader,
355 stale_threshold: std::time::Duration,
356 ) -> BlockSyncResult<Option<StateSyncMessage<BlockHeader>>> {
357 let extractor_id = self.extractor_id.clone();
358 match timeout(max_wait, self.rx.recv()).await {
359 Ok(Some(Ok(msg))) => {
360 self.transition(msg.header.clone(), block_history, stale_threshold)?;
361 Ok(Some(msg))
362 }
363 Ok(Some(Err(e))) => {
364 self.mark_errored(e);
366 Ok(None)
367 }
368 Ok(None) => {
369 warn!(
372 %extractor_id,
373 "Tried to poll from closed synchronizer.",
374 );
375 self.mark_closed();
376 Ok(None)
377 }
378 Err(_) => {
379 debug!(%extractor_id, %previous_block, "No block received within time limit.");
381 self.state = SynchronizerState::Delayed(previous_block.clone());
384 self.modify_ts = Local::now().naive_utc();
385 Ok(None)
386 }
387 }
388 }
389
390 async fn try_catch_up(
397 &mut self,
398 block_history: &BlockHistory,
399 max_wait: std::time::Duration,
400 stale_threshold: std::time::Duration,
401 ) -> BlockSyncResult<Option<StateSyncMessage<BlockHeader>>> {
402 let mut results = Vec::new();
403 let extractor_id = self.extractor_id.clone();
404
405 let deadline = std::time::Instant::now() + max_wait;
407
408 while std::time::Instant::now() < deadline {
409 match timeout(
410 deadline.saturating_duration_since(std::time::Instant::now()),
411 self.rx.recv(),
412 )
413 .await
414 {
415 Ok(Some(Ok(msg))) => {
416 debug!(%extractor_id, block=%msg.header, "Received new message during catch-up");
417 let block_pos = block_history.determine_block_position(&msg.header)?;
418 results.push(msg);
419 if matches!(
422 block_pos,
423 BlockPosition::NextExpected |
424 BlockPosition::NextPartial |
425 BlockPosition::Advanced
426 ) {
427 break;
428 }
429 }
430 Ok(Some(Err(e))) => {
431 self.mark_errored(e);
433 return Ok(None);
434 }
435 Ok(None) => {
436 warn!(
439 %extractor_id,
440 "Tried to poll from closed synchronizer during catch up.",
441 );
442 self.mark_closed();
443 return Ok(None);
444 }
445 Err(_) => {
446 debug!(%extractor_id, "Timed out waiting for catch-up");
447 break;
448 }
449 }
450 }
451
452 let merged = results
453 .into_iter()
454 .reduce(|l, r| l.merge(r));
455
456 if let Some(msg) = merged {
457 debug!(%extractor_id, "Delayed extractor made progress!");
459 self.transition(msg.header.clone(), block_history, stale_threshold)?;
460 Ok(Some(msg))
461 } else {
462 self.check_and_transition_to_stale_if_needed(stale_threshold, None)?;
464 Ok(None)
465 }
466 }
467
468 fn check_and_transition_to_stale_if_needed(
470 &mut self,
471 stale_threshold: std::time::Duration,
472 fallback_header: Option<BlockHeader>,
473 ) -> Result<bool, BlockSynchronizerError> {
474 let now = Local::now().naive_utc();
475 let wait_duration = now.signed_duration_since(self.modify_ts);
476 let stale_threshold_chrono = ChronoDuration::from_std(stale_threshold)
477 .map_err(|e| BlockSynchronizerError::DurationConversionError(e.to_string()))?;
478
479 if wait_duration > stale_threshold_chrono {
480 let header_to_use = match (&self.state, fallback_header) {
481 (SynchronizerState::Ready(h), _) |
482 (SynchronizerState::Delayed(h), _) |
483 (SynchronizerState::Stale(h), _) => h.clone(),
484 (_, Some(h)) => h,
485 _ => BlockHeader::default(),
486 };
487
488 warn!(
489 extractor_id=%self.extractor_id,
490 last_message_at=?self.modify_ts,
491 "SynchronizerStream transition to stale due to timeout."
492 );
493 self.state = SynchronizerState::Stale(header_to_use);
494 self.modify_ts = now;
495 Ok(true)
496 } else {
497 Ok(false)
498 }
499 }
500
501 fn transition(
508 &mut self,
509 latest_retrieved: BlockHeader,
510 block_history: &BlockHistory,
511 stale_threshold: std::time::Duration,
512 ) -> Result<(), BlockSynchronizerError> {
513 let extractor_id = self.extractor_id.clone();
514 let last_message_at = self.modify_ts;
515 let block = &latest_retrieved;
516
517 match block_history.determine_block_position(&latest_retrieved)? {
518 BlockPosition::NextExpected | BlockPosition::NextPartial => {
519 self.state = SynchronizerState::Ready(latest_retrieved.clone());
520 trace!(
521 next = %latest_retrieved,
522 extractor = %extractor_id,
523 "SynchronizerStream transition to next expected"
524 )
525 }
526 BlockPosition::Latest | BlockPosition::Delayed => {
527 if !self.check_and_transition_to_stale_if_needed(
528 stale_threshold,
529 Some(latest_retrieved.clone()),
530 )? {
531 warn!(
532 %extractor_id,
533 ?last_message_at,
534 %block,
535 "SynchronizerStream transition transition to delayed."
536 );
537 self.state = SynchronizerState::Delayed(latest_retrieved.clone());
538 }
539 }
540 BlockPosition::Advanced => {
541 info!(
542 %extractor_id,
543 ?last_message_at,
544 latest = opt(&block_history.latest()),
545 %block,
546 "SynchronizerStream transition to advanced."
547 );
548 self.state = SynchronizerState::Advanced(latest_retrieved.clone());
549 }
550 }
551 self.modify_ts = Local::now().naive_utc();
552 Ok(())
553 }
554
555 fn mark_errored(&mut self, error: SynchronizerError) {
560 self.state = SynchronizerState::Ended(error.to_string());
561 self.modify_ts = Local::now().naive_utc();
562 self.error = Some(error);
563 }
564
565 fn mark_closed(&mut self) {
571 if !matches!(self.state, SynchronizerState::Ended(_)) {
572 self.state = SynchronizerState::Ended("Closed".to_string());
573 self.modify_ts = Local::now().naive_utc();
574 }
575 }
576
577 fn mark_stale(&mut self, header: &BlockHeader) {
579 self.state = SynchronizerState::Stale(header.clone());
580 self.modify_ts = Local::now().naive_utc();
581 }
582
583 fn mark_ready(&mut self, header: &BlockHeader) {
585 self.state = SynchronizerState::Ready(header.clone());
586 self.modify_ts = Local::now().naive_utc();
587 }
588
589 fn has_ended(&self) -> bool {
590 matches!(self.state, SynchronizerState::Ended(_))
591 }
592
593 fn is_stale(&self) -> bool {
594 matches!(self.state, SynchronizerState::Stale(_))
595 }
596
597 fn is_advanced(&self) -> bool {
598 matches!(self.state, SynchronizerState::Advanced(_))
599 }
600
601 fn get_current_header(&self) -> Option<&BlockHeader> {
605 match &self.state {
606 SynchronizerState::Ready(b) |
607 SynchronizerState::Delayed(b) |
608 SynchronizerState::Advanced(b) => Some(b),
609 _ => None,
610 }
611 }
612}
613
614#[derive(Debug, PartialEq, Clone)]
615pub struct FeedMessage<H = BlockHeader>
616where
617 H: HeaderLike,
618{
619 pub state_msgs: HashMap<String, StateSyncMessage<H>>,
620 pub sync_states: HashMap<String, SynchronizerState>,
621}
622
623impl<H> FeedMessage<H>
624where
625 H: HeaderLike,
626{
627 fn new(
628 state_msgs: HashMap<String, StateSyncMessage<H>>,
629 sync_states: HashMap<String, SynchronizerState>,
630 ) -> Self {
631 Self { state_msgs, sync_states }
632 }
633}
634
635impl<S> BlockSynchronizer<S>
636where
637 S: StateSynchronizer,
638{
639 pub fn new(
640 block_time: std::time::Duration,
641 latency_buffer: std::time::Duration,
642 max_missed_blocks: u64,
643 ) -> Self {
644 Self {
645 synchronizers: None,
646 max_messages: None,
647 block_time,
648 latency_buffer,
649 startup_timeout: block_time.mul_f64(max_missed_blocks as f64),
650 max_missed_blocks,
651 }
652 }
653
654 pub fn max_messages(&mut self, val: usize) {
660 self.max_messages = Some(val);
661 }
662
663 pub fn startup_timeout(mut self, val: Duration) {
667 self.startup_timeout = val;
668 }
669
670 pub fn register_synchronizer(mut self, id: ExtractorIdentity, synchronizer: S) -> Self {
671 let mut registered = self.synchronizers.unwrap_or_default();
672 registered.insert(id, synchronizer);
673 self.synchronizers = Some(registered);
674 self
675 }
676
677 #[cfg(test)]
678 pub fn with_short_timeouts() -> Self {
679 Self::new(Duration::from_millis(10), Duration::from_millis(10), 3)
680 }
681
682 async fn cleanup_synchronizers(
685 mut state_sync_tasks: FuturesUnordered<JoinHandle<()>>,
686 sync_close_senders: Vec<oneshot::Sender<()>>,
687 ) {
688 for close_sender in sync_close_senders {
690 let _ = close_sender.send(());
691 }
692
693 let mut completed_tasks = 0;
695 while let Ok(Some(_)) = timeout(Duration::from_secs(5), state_sync_tasks.next()).await {
696 completed_tasks += 1;
697 }
698
699 let remaining_tasks = state_sync_tasks.len();
701 if remaining_tasks > 0 {
702 warn!(
703 completed = completed_tasks,
704 timed_out = remaining_tasks,
705 "Some synchronizers timed out during cleanup and may not have shut down cleanly"
706 );
707 }
708 }
709
710 pub async fn run(
715 mut self,
716 ) -> BlockSyncResult<(JoinHandle<()>, Receiver<BlockSyncResult<FeedMessage<BlockHeader>>>)>
717 {
718 trace!("Starting BlockSynchronizer...");
719 let state_sync_tasks = FuturesUnordered::new();
720 let mut synchronizers = self
721 .synchronizers
722 .take()
723 .ok_or(BlockSynchronizerError::NoSynchronizers)?;
724 let init_results = join_all(synchronizers.iter_mut().map(|(id, s)| {
727 s.initialize()
728 .map(|res| (id.clone(), res))
729 }))
730 .await;
731 let mut to_skip = Vec::new();
732 for (extractor_id, result) in init_results {
733 match result {
734 Ok(()) => {}
735 Err(SynchronizerError::RPCError(crate::rpc::RPCError::UnknownExtractor(
736 reason,
737 ))) => {
738 warn!(%extractor_id, %reason, "Extractor not recognised by server, skipping");
739 to_skip.push(extractor_id);
740 }
741 Err(e) => {
742 return Err(BlockSynchronizerError::InitializationError {
743 extractor: extractor_id,
744 source: e,
745 })
746 }
747 }
748 }
749 for id in &to_skip {
750 synchronizers.remove(id);
751 }
752 if synchronizers.is_empty() {
753 return Err(BlockSynchronizerError::NoSynchronizers);
754 }
755
756 let mut sync_streams = Vec::with_capacity(synchronizers.len());
757 let mut sync_close_senders = Vec::new();
758 for (extractor_id, synchronizer) in synchronizers.drain() {
759 let (handle, rx) = synchronizer.start().await;
760 let (join_handle, close_sender) = handle.split();
761 state_sync_tasks.push(join_handle);
762 sync_close_senders.push(close_sender);
763
764 sync_streams.push(SynchronizerStream::new(&extractor_id, rx));
765 }
766
767 debug!("Waiting for initial synchronizer messages...");
770 let mut startup_futures = Vec::new();
771 for synchronizer in sync_streams.iter_mut() {
772 let fut = async {
773 let res = timeout(self.startup_timeout, synchronizer.rx.recv()).await;
774 (synchronizer, res)
775 };
776 startup_futures.push(fut);
777 }
778
779 let mut ready_sync_msgs = HashMap::new();
780 let initial_headers = join_all(startup_futures)
781 .await
782 .into_iter()
783 .filter_map(|(synchronizer, res)| {
784 let extractor_id = synchronizer.extractor_id.clone();
785 match res {
786 Ok(Some(Ok(msg))) => {
787 debug!(%extractor_id, height=?&msg.header.number, "Synchronizer started successfully!");
788 synchronizer.mark_ready(&msg.header);
790 let header = msg.header.clone();
791 ready_sync_msgs.insert(extractor_id.name.clone(), msg);
792 Some(header)
793 }
794 Ok(Some(Err(e))) => {
795 synchronizer.mark_errored(e);
796 None
797 }
798 Ok(None) => {
799 warn!(%extractor_id, "Synchronizer closed during startup");
804 synchronizer.mark_closed();
805 None
806 }
807 Err(_) => {
808 warn!(%extractor_id, "Timed out waiting for first message");
810 synchronizer.mark_stale(&BlockHeader::default());
811 None
812 }
813 }
814 })
815 .collect::<HashSet<_>>() .into_iter()
817 .collect::<Vec<_>>();
818
819 Self::require_active_stream(&sync_streams)?;
821 let mut block_history = BlockHistory::new(initial_headers, BLOCK_HISTORY_SIZE)?;
822 let start_header = block_history
826 .latest()
827 .ok_or(BlockHistoryError::EmptyHistory)?;
828 info!(
829 start_block=%start_header,
830 n_healthy=ready_sync_msgs.len(),
831 n_total=sync_streams.len(),
832 "Block synchronization started successfully!"
833 );
834
835 for stream in sync_streams.iter_mut() {
838 if let SynchronizerState::Ready(header) = stream.state.clone() {
839 if header.number < start_header.number {
840 debug!(
841 extractor_id=%stream.extractor_id,
842 synchronizer_block=header.number,
843 current_block=start_header.number,
844 "Marking synchronizer as delayed during initialization"
845 );
846 stream.state = SynchronizerState::Delayed(header);
847 }
848 }
849 }
850
851 let (sync_tx, sync_rx) = mpsc::channel(30);
852 let main_loop_jh = tokio::spawn(async move {
853 let mut n_iter = 1;
854 loop {
855 let msg = FeedMessage::new(
857 std::mem::take(&mut ready_sync_msgs),
858 sync_streams
859 .iter()
860 .map(|stream| (stream.extractor_id.name.to_string(), stream.state.clone()))
861 .collect(),
862 );
863 if sync_tx.send(Ok(msg)).await.is_err() {
864 info!("Receiver closed, block synchronizer terminating..");
865 return;
866 };
867
868 if let Some(max_messages) = self.max_messages {
870 if n_iter >= max_messages {
871 info!(max_messages, "StreamEnd");
872 return;
873 }
874 }
875 n_iter += 1;
876
877 let res = self
878 .handle_next_message(
879 &mut sync_streams,
880 &mut ready_sync_msgs,
881 &mut block_history,
882 )
883 .await;
884
885 if let Err(e) = res {
886 let _ = sync_tx.send(Err(e)).await;
888 return;
889 }
890 }
891 });
892
893 let nanny_jh = tokio::spawn(async move {
898 let _ = main_loop_jh.await.map_err(|e| {
900 if e.is_panic() {
901 error!("BlockSynchornizer main loop panicked: {e}")
902 }
903 });
904 debug!("Main loop exited. Closing synchronizers");
905 Self::cleanup_synchronizers(state_sync_tasks, sync_close_senders).await;
906 debug!("Shutdown complete");
907 });
908 Ok((nanny_jh, sync_rx))
909 }
910
911 async fn handle_next_message(
916 &self,
917 sync_streams: &mut [SynchronizerStream],
918 ready_sync_msgs: &mut HashMap<String, StateSyncMessage<BlockHeader>>,
919 block_history: &mut BlockHistory,
920 ) -> BlockSyncResult<()> {
921 let any_advanced = sync_streams
925 .iter()
926 .any(SynchronizerStream::is_advanced);
927 let mut recv_futures = Vec::new();
928 for stream in sync_streams.iter_mut() {
929 if stream.has_ended() {
932 continue;
933 }
934 recv_futures.push(async {
944 let res = stream
945 .try_advance(
946 block_history,
947 self.block_time,
948 self.latency_buffer,
949 self.block_time
950 .mul_f64(self.max_missed_blocks as f64),
951 any_advanced,
952 )
953 .await?;
954 Ok::<_, BlockSynchronizerError>(
955 res.map(|msg| (stream.extractor_id.name.clone(), msg)),
956 )
957 });
958 }
959 ready_sync_msgs.extend(
960 join_all(recv_futures)
961 .await
962 .into_iter()
963 .collect::<Result<Vec<_>, _>>()?
964 .into_iter()
965 .flatten(),
966 );
967
968 Self::check_streams(sync_streams)?;
971
972 if sync_streams
975 .iter()
976 .any(SynchronizerStream::is_advanced)
977 {
978 *block_history = Self::reinit_block_history(sync_streams, block_history)?;
979 } else if let Some(header) = Self::round_header(sync_streams) {
980 if header.revert {
981 info!(%header, "RevertApplied");
982 }
983 block_history.push(header.clone())?;
984 }
985 Ok(())
988 }
989
990 fn reinit_block_history(
999 sync_streams: &mut [SynchronizerStream],
1000 block_history: &mut BlockHistory,
1001 ) -> Result<BlockHistory, BlockSynchronizerError> {
1002 let previous = block_history
1003 .latest()
1004 .ok_or(BlockHistoryError::EmptyHistory)?
1006 .clone();
1007 let mut blocks: Vec<BlockHeader> = block_history
1015 .blocks()
1016 .cloned()
1017 .collect();
1018 blocks.extend(
1019 sync_streams
1020 .iter()
1021 .filter_map(SynchronizerStream::get_current_header)
1022 .cloned(),
1023 );
1024 let new_block_history = BlockHistory::new(blocks, BLOCK_HISTORY_SIZE)?;
1025 let latest = new_block_history
1026 .latest()
1027 .ok_or(BlockHistoryError::EmptyHistory)?;
1029 info!(
1030 %previous,
1031 %latest,
1032 "Advanced synchronizer detected. Reinitialized block history."
1033 );
1034 sync_streams
1035 .iter_mut()
1036 .for_each(|stream| {
1037 if let Some(header) = stream.get_current_header() {
1040 if header.number < latest.number {
1041 stream.state = SynchronizerState::Delayed(header.clone());
1042 } else if header.number == latest.number {
1043 stream.state = SynchronizerState::Ready(header.clone());
1044 }
1045 }
1046 });
1047 Ok(new_block_history)
1048 }
1049
1050 fn round_header(sync_streams: &[SynchronizerStream]) -> Option<&BlockHeader> {
1056 let ready = sync_streams
1057 .iter()
1058 .filter_map(|stream| match &stream.state {
1059 SynchronizerState::Ready(header) => Some(header),
1060 _ => None,
1061 });
1062 ready
1063 .clone()
1064 .filter(|header| header.revert)
1065 .min_by_key(|header| header.number)
1066 .or_else(|| ready.max_by_key(|header| (header.number, header.partial_block_index)))
1067 }
1068
1069 fn require_active_stream(sync_streams: &[SynchronizerStream]) -> BlockSyncResult<()> {
1074 if sync_streams
1075 .iter()
1076 .any(|s| !s.has_ended() && !s.is_stale())
1077 {
1078 return Ok(());
1079 }
1080 let reason: Vec<String> = sync_streams
1081 .iter()
1082 .map(|s| format!("{} reported as {} at {}", s.extractor_id, s.state, s.modify_ts))
1083 .collect();
1084 Err(BlockSynchronizerError::NoReadySynchronizers(reason.join(", ")))
1085 }
1086
1087 fn check_streams(sync_streams: &[SynchronizerStream]) -> BlockSyncResult<()> {
1097 let mut has_any_ended = false;
1098 let mut latest_ended_stream: Option<&SynchronizerStream> = None;
1099
1100 for stream in sync_streams.iter() {
1101 if !stream.has_ended() && !stream.is_stale() {
1103 return Ok(());
1104 }
1105
1106 if stream.has_ended() {
1107 has_any_ended = true;
1108 if latest_ended_stream.is_none() ||
1109 stream.modify_ts >
1110 latest_ended_stream
1111 .as_ref()
1112 .unwrap()
1113 .modify_ts
1114 {
1115 latest_ended_stream = Some(stream);
1116 }
1117 }
1118 }
1119
1120 if !has_any_ended {
1123 return Ok(());
1124 }
1125
1126 let last_error_reason = if let Some(stream) = latest_ended_stream {
1128 if let Some(err) = &stream.error {
1129 format!("Synchronizer for {} errored with: {err}", stream.extractor_id)
1130 } else {
1131 format!("Synchronizer for {} became: {}", stream.extractor_id, stream.state)
1132 }
1133 } else {
1134 return Err(BlockSynchronizerError::NoSynchronizers);
1135 };
1136
1137 let mut reason = vec![last_error_reason];
1138
1139 sync_streams.iter().for_each(|stream| {
1140 reason.push(format!(
1141 "{} reported as {} at {}",
1142 stream.extractor_id, stream.state, stream.modify_ts
1143 ))
1144 });
1145
1146 Err(BlockSynchronizerError::NoReadySynchronizers(reason.join(", ")))
1147 }
1148}
1149
1150#[cfg(test)]
1151mod tests {
1152 use std::sync::Arc;
1153
1154 use async_trait::async_trait;
1155 use test_log::test;
1156 use tokio::sync::{oneshot, Mutex};
1157 use tycho_common::models::{token::Token, Chain};
1158
1159 use super::*;
1160 use crate::feed::synchronizer::{SyncResult, SynchronizerTaskHandle};
1161
1162 #[test]
1163 fn block_header_from_block_maps_block_fields() {
1164 let block = Block {
1165 number: 42,
1166 chain: Chain::Ethereum,
1167 hash: Bytes::from([1; 32]),
1168 parent_hash: Bytes::from([2; 32]),
1169 ts: chrono::DateTime::from_timestamp(1_700_000_000, 0)
1170 .expect("valid timestamp")
1171 .naive_utc(),
1172 };
1173
1174 let header = BlockHeader::from(&block);
1175
1176 assert_eq!(header.hash, block.hash);
1177 assert_eq!(header.number, block.number);
1178 assert_eq!(header.parent_hash, block.parent_hash);
1179 assert_eq!(header.timestamp, 1_700_000_000);
1180 assert!(!header.revert);
1181 assert_eq!(header.partial_block_index, None);
1182 }
1183
1184 #[derive(Clone, Debug)]
1185 enum MockBehavior {
1186 Normal, IgnoreClose, ExitImmediately, }
1190
1191 type HeaderReceiver = Receiver<SyncResult<StateSyncMessage<BlockHeader>>>;
1192
1193 #[derive(Clone)]
1194 struct MockStateSync {
1195 header_tx: mpsc::Sender<SyncResult<StateSyncMessage<BlockHeader>>>,
1196 header_rx: Arc<Mutex<Option<HeaderReceiver>>>,
1197 close_received: Arc<Mutex<bool>>,
1198 behavior: MockBehavior,
1199 close_tx: Arc<Mutex<Option<oneshot::Sender<()>>>>,
1201 }
1202
1203 impl MockStateSync {
1204 fn new() -> Self {
1205 Self::with_behavior(MockBehavior::Normal)
1206 }
1207
1208 fn with_behavior(behavior: MockBehavior) -> Self {
1209 let (tx, rx) = mpsc::channel(1);
1210 Self {
1211 header_tx: tx,
1212 header_rx: Arc::new(Mutex::new(Some(rx))),
1213 close_received: Arc::new(Mutex::new(false)),
1214 behavior,
1215 close_tx: Arc::new(Mutex::new(None)),
1216 }
1217 }
1218
1219 async fn was_close_received(&self) -> bool {
1220 *self.close_received.lock().await
1221 }
1222
1223 async fn send_header(&self, header: StateSyncMessage<BlockHeader>) -> Result<(), String> {
1224 self.header_tx
1225 .send(Ok(header))
1226 .await
1227 .map_err(|e| format!("sending header failed: {e}"))
1228 }
1229
1230 async fn trigger_close(&self) {
1232 if let Some(close_tx) = self.close_tx.lock().await.take() {
1233 let _ = close_tx.send(());
1234 }
1235 }
1236 }
1237
1238 #[async_trait]
1239 impl StateSynchronizer for MockStateSync {
1240 async fn initialize(&mut self) -> SyncResult<()> {
1241 Ok(())
1242 }
1243
1244 async fn start(
1245 mut self,
1246 ) -> (SynchronizerTaskHandle, Receiver<SyncResult<StateSyncMessage<BlockHeader>>>) {
1247 let block_rx = {
1248 let mut guard = self.header_rx.lock().await;
1249 guard
1250 .take()
1251 .expect("Block receiver was not set!")
1252 };
1253
1254 let (close_tx_for_handle, close_rx) = oneshot::channel();
1257 let (close_tx_for_test, close_rx_for_test) = oneshot::channel();
1258
1259 {
1261 let mut guard = self.close_tx.lock().await;
1262 *guard = Some(close_tx_for_test);
1263 }
1264
1265 let behavior = self.behavior.clone();
1266 let close_received_clone = self.close_received.clone();
1267 let tx = self.header_tx.clone();
1268
1269 let jh = tokio::spawn(async move {
1270 match behavior {
1271 MockBehavior::IgnoreClose => {
1272 loop {
1275 tokio::time::sleep(tokio::time::Duration::from_secs(1)).await;
1276 }
1277 }
1278 MockBehavior::ExitImmediately => {
1279 tx.send(SyncResult::Err(SynchronizerError::ConnectionError(
1281 "Simulated immediate task failure".to_string(),
1282 )))
1283 .await
1284 .unwrap();
1285 }
1286 MockBehavior::Normal => {
1287 let _ = tokio::select! {
1290 result = close_rx => result,
1291 result = close_rx_for_test => result,
1292 };
1293 let mut guard = close_received_clone.lock().await;
1294 *guard = true;
1295 }
1296 }
1297 });
1298
1299 let handle = SynchronizerTaskHandle::new(jh, close_tx_for_handle);
1300 (handle, block_rx)
1301 }
1302 }
1303
1304 fn header_message(block: u8) -> StateSyncMessage<BlockHeader> {
1305 StateSyncMessage {
1306 header: BlockHeader {
1307 number: block as u64,
1308 hash: Bytes::from(vec![block]),
1309 parent_hash: Bytes::from(vec![block - 1]),
1310 revert: false,
1311 timestamp: 1000,
1312 partial_block_index: None,
1313 },
1314 ..Default::default()
1315 }
1316 }
1317
1318 fn partial_header_message(block: u8, partial_idx: u32) -> StateSyncMessage<BlockHeader> {
1321 let hash_bytes =
1323 [(block as u64).to_be_bytes().as_slice(), partial_idx.to_be_bytes().as_slice()]
1324 .concat();
1325 StateSyncMessage {
1326 header: BlockHeader {
1327 number: block as u64,
1328 hash: Bytes::from(hash_bytes),
1329 parent_hash: Bytes::from(vec![block - 1]),
1330 revert: false,
1331 timestamp: 1000,
1332 partial_block_index: Some(partial_idx),
1333 },
1334 ..Default::default()
1335 }
1336 }
1337
1338 fn revert_header_message(block: u8) -> StateSyncMessage<BlockHeader> {
1339 StateSyncMessage {
1340 header: BlockHeader {
1341 number: block as u64,
1342 hash: Bytes::from(vec![block]),
1343 parent_hash: Bytes::from(vec![block - 1]),
1344 revert: true,
1345 timestamp: 1000,
1346 partial_block_index: None,
1347 },
1348 ..Default::default()
1349 }
1350 }
1351
1352 fn full_header(number: u64, hash: u64, parent: u64) -> BlockHeader {
1355 BlockHeader {
1356 number,
1357 hash: Bytes::from(hash.to_be_bytes()),
1358 parent_hash: Bytes::from(parent.to_be_bytes()),
1359 revert: false,
1360 timestamp: 1000,
1361 partial_block_index: None,
1362 }
1363 }
1364
1365 fn partial_header(number: u64, partial_idx: u32, parent: u64) -> BlockHeader {
1369 BlockHeader {
1370 number,
1371 hash: Bytes::from(
1372 [number.to_be_bytes().as_slice(), partial_idx.to_be_bytes().as_slice()].concat(),
1373 ),
1374 parent_hash: Bytes::from(parent.to_be_bytes()),
1375 revert: false,
1376 timestamp: 1000,
1377 partial_block_index: Some(partial_idx),
1378 }
1379 }
1380
1381 fn sealed_partial_header(number: u64, partial_idx: u32, parent: u64) -> BlockHeader {
1384 BlockHeader {
1385 number,
1386 hash: Bytes::from(number.to_be_bytes()),
1387 parent_hash: Bytes::from(parent.to_be_bytes()),
1388 revert: false,
1389 timestamp: 1000,
1390 partial_block_index: Some(partial_idx),
1391 }
1392 }
1393
1394 fn stream_in_state(name: &str, state: SynchronizerState) -> SynchronizerStream {
1397 let (_tx, rx) = mpsc::channel(1);
1398 let id = ExtractorIdentity { chain: Chain::Ethereum, name: name.to_string() };
1399 let mut stream = SynchronizerStream::new(&id, rx);
1400 stream.state = state;
1401 stream
1402 }
1403
1404 async fn receive_message(rx: &mut Receiver<BlockSyncResult<FeedMessage>>) -> FeedMessage {
1405 timeout(Duration::from_millis(100), rx.recv())
1406 .await
1407 .expect("Responds in time")
1408 .expect("Should receive first message")
1409 .expect("No error")
1410 }
1411
1412 async fn setup_block_sync(
1413 ) -> (MockStateSync, MockStateSync, JoinHandle<()>, Receiver<BlockSyncResult<FeedMessage>>)
1414 {
1415 setup_block_sync_with_behaviour(MockBehavior::Normal, MockBehavior::Normal).await
1416 }
1417
1418 async fn setup_block_sync_with_behaviour(
1420 v2_behavior: MockBehavior,
1421 v3_behavior: MockBehavior,
1422 ) -> (MockStateSync, MockStateSync, JoinHandle<()>, Receiver<BlockSyncResult<FeedMessage>>)
1423 {
1424 let v2_sync = MockStateSync::with_behavior(v2_behavior);
1425 let v3_sync = MockStateSync::with_behavior(v3_behavior);
1426
1427 let mut block_sync = BlockSynchronizer::new(
1429 Duration::from_millis(20), Duration::from_millis(10), 3, );
1433 block_sync.max_messages(10); let block_sync = block_sync
1436 .register_synchronizer(
1437 ExtractorIdentity { chain: Chain::Ethereum, name: "uniswap-v2".to_string() },
1438 v2_sync.clone(),
1439 )
1440 .register_synchronizer(
1441 ExtractorIdentity { chain: Chain::Ethereum, name: "uniswap-v3".to_string() },
1442 v3_sync.clone(),
1443 );
1444
1445 let block1_msg = header_message(1);
1447 let _ = v2_sync
1448 .send_header(block1_msg.clone())
1449 .await;
1450 let _ = v3_sync
1451 .send_header(block1_msg.clone())
1452 .await;
1453
1454 let (nanny_handle, mut rx) = block_sync
1456 .run()
1457 .await
1458 .expect("BlockSynchronizer failed to start");
1459
1460 let first_feed_msg = receive_message(&mut rx).await;
1461 assert_eq!(first_feed_msg.state_msgs.len(), 2);
1462 assert!(matches!(
1463 first_feed_msg
1464 .sync_states
1465 .get("uniswap-v2")
1466 .unwrap(),
1467 SynchronizerState::Ready(_)
1468 ));
1469 assert!(matches!(
1470 first_feed_msg
1471 .sync_states
1472 .get("uniswap-v3")
1473 .unwrap(),
1474 SynchronizerState::Ready(_)
1475 ));
1476
1477 (v2_sync, v3_sync, nanny_handle, rx)
1478 }
1479
1480 async fn shutdown_block_synchronizer(
1481 nanny_handle: JoinHandle<()>,
1482 rx: Receiver<BlockSyncResult<FeedMessage>>,
1483 ) {
1484 drop(rx);
1488 timeout(Duration::from_secs(2), nanny_handle)
1489 .await
1490 .expect("Nanny failed to exit within time")
1491 .expect("Nanny panicked");
1492 }
1493
1494 async fn send_and_assert_ready(
1496 sync: &MockStateSync,
1497 sync_name: &str,
1498 rx: &mut Receiver<BlockSyncResult<FeedMessage>>,
1499 msg: StateSyncMessage<BlockHeader>,
1500 expected_block: u64,
1501 expected_partial: Option<u32>,
1502 ) {
1503 sync.send_header(msg)
1504 .await
1505 .expect("send failed");
1506 let feed_msg = receive_message(rx).await;
1507 let state = feed_msg
1508 .sync_states
1509 .get(sync_name)
1510 .unwrap();
1511 match state {
1512 SynchronizerState::Ready(h) => {
1513 assert_eq!(h.number, expected_block, "wrong block number");
1514 assert_eq!(h.partial_block_index, expected_partial, "wrong partial index");
1515 }
1516 other => panic!("expected Ready, got {:?}", other),
1517 }
1518 }
1519
1520 #[test(tokio::test)]
1521 async fn test_two_ready_synchronizers() {
1522 let (v2_sync, v3_sync, nanny_handle, mut rx) = setup_block_sync().await;
1523
1524 let second_msg = header_message(2);
1525 v2_sync
1526 .send_header(second_msg.clone())
1527 .await
1528 .expect("send_header failed");
1529 v3_sync
1530 .send_header(second_msg.clone())
1531 .await
1532 .expect("send_header failed");
1533 let second_feed_msg = receive_message(&mut rx).await;
1534
1535 let exp2 = FeedMessage {
1536 state_msgs: [
1537 ("uniswap-v2".to_string(), second_msg.clone()),
1538 ("uniswap-v3".to_string(), second_msg.clone()),
1539 ]
1540 .into_iter()
1541 .collect(),
1542 sync_states: [
1543 ("uniswap-v3".to_string(), SynchronizerState::Ready(second_msg.header.clone())),
1544 ("uniswap-v2".to_string(), SynchronizerState::Ready(second_msg.header.clone())),
1545 ]
1546 .into_iter()
1547 .collect(),
1548 };
1549 assert_eq!(second_feed_msg, exp2);
1550
1551 shutdown_block_synchronizer(nanny_handle, rx).await;
1552 }
1553
1554 #[test]
1563 fn test_reinit_preserves_history_for_revert_below_advanced_tip() {
1564 let old_blocks = vec![
1566 full_header(323, 323, 322),
1567 full_header(324, 324, 323),
1568 full_header(325, 325, 324),
1569 ];
1570 let mut old_history = BlockHistory::new(old_blocks, 15).unwrap();
1571
1572 let mut streams = vec![
1575 stream_in_state("aerodrome", SynchronizerState::Advanced(full_header(326, 326, 325))),
1576 stream_in_state("uniswap_v3", SynchronizerState::Delayed(full_header(325, 325, 324))),
1577 ];
1578
1579 let mut new_history = BlockSynchronizer::<MockStateSync>::reinit_block_history(
1580 &mut streams,
1581 &mut old_history,
1582 )
1583 .expect("reinit failed");
1584
1585 let revert = BlockHeader {
1587 number: 325,
1588 hash: Bytes::from(325u64.to_be_bytes()),
1589 parent_hash: Bytes::from(324u64.to_be_bytes()),
1590 revert: true,
1591 timestamp: 1000,
1592 partial_block_index: None,
1593 };
1594 new_history
1595 .push(revert)
1596 .expect("revert below advanced tip must not exceed history");
1597 }
1598
1599 #[test]
1604 fn test_reinit_discards_retained_history_on_detached_advanced_block() {
1605 let old_blocks = vec![
1606 full_header(323, 323, 322),
1607 full_header(324, 324, 323),
1608 full_header(325, 325, 324),
1609 ];
1610 let mut old_history = BlockHistory::new(old_blocks, BLOCK_HISTORY_SIZE).unwrap();
1611
1612 let mut streams = vec![stream_in_state(
1614 "aerodrome",
1615 SynchronizerState::Advanced(full_header(400, 400, 399)),
1616 )];
1617
1618 let new_history = BlockSynchronizer::<MockStateSync>::reinit_block_history(
1619 &mut streams,
1620 &mut old_history,
1621 )
1622 .expect("reinit failed");
1623
1624 let retained: Vec<u64> = new_history
1625 .blocks()
1626 .map(|b| b.number)
1627 .collect();
1628 assert_eq!(
1629 retained,
1630 vec![399, 400],
1631 "detached advanced block must not be stitched to old history"
1632 );
1633 let parent = new_history.oldest().unwrap();
1634 assert!(parent.parent_hash.is_empty(), "block 399 must be the hash-only parent entry");
1635 }
1636
1637 #[test]
1646 fn test_reinit_preserves_history_for_partial_advanced_block() {
1647 let old_blocks = vec![
1649 full_header(323, 323, 322),
1650 full_header(324, 324, 323),
1651 partial_header(325, 2, 324),
1652 ];
1653 let mut old_history = BlockHistory::new(old_blocks, BLOCK_HISTORY_SIZE).unwrap();
1654
1655 let advanced = partial_header(326, 0, 325);
1658 assert_eq!(
1659 old_history
1660 .determine_block_position(&advanced)
1661 .unwrap(),
1662 BlockPosition::Advanced
1663 );
1664
1665 let mut streams = vec![
1666 stream_in_state("aerodrome", SynchronizerState::Advanced(advanced)),
1667 stream_in_state("uniswap_v3", SynchronizerState::Delayed(partial_header(325, 2, 324))),
1668 ];
1669
1670 let new_history = BlockSynchronizer::<MockStateSync>::reinit_block_history(
1671 &mut streams,
1672 &mut old_history,
1673 )
1674 .expect("reinit failed");
1675
1676 let retained: Vec<u64> = new_history
1677 .blocks()
1678 .map(|b| b.number)
1679 .collect();
1680 assert_eq!(retained, vec![322, 323, 324, 325, 326]);
1681 }
1682
1683 #[test]
1688 fn test_revert_below_tip_resolves_after_partial_advanced_reinit() {
1689 let old_blocks = vec![
1690 full_header(323, 323, 322),
1691 full_header(324, 324, 323),
1692 partial_header(325, 2, 324),
1693 ];
1694 let mut old_history = BlockHistory::new(old_blocks, BLOCK_HISTORY_SIZE).unwrap();
1695
1696 let mut streams = vec![
1697 stream_in_state("aerodrome", SynchronizerState::Advanced(partial_header(326, 0, 325))),
1698 stream_in_state("uniswap_v3", SynchronizerState::Delayed(partial_header(325, 2, 324))),
1699 ];
1700
1701 let mut new_history = BlockSynchronizer::<MockStateSync>::reinit_block_history(
1702 &mut streams,
1703 &mut old_history,
1704 )
1705 .expect("reinit failed");
1706
1707 new_history
1710 .push(sealed_partial_header(326, 5, 325))
1711 .expect("sealed partial push failed");
1712 new_history
1713 .push(partial_header(327, 0, 326))
1714 .expect("next partial push failed");
1715
1716 let revert = BlockHeader {
1719 number: 326,
1720 hash: Bytes::from(326u64.to_be_bytes()),
1721 parent_hash: Bytes::from(325u64.to_be_bytes()),
1722 revert: true,
1723 timestamp: 1000,
1724 partial_block_index: None,
1725 };
1726 new_history
1727 .push(revert)
1728 .expect("1-block revert after partial-advanced reinit must resolve");
1729
1730 let retained: Vec<u64> = new_history
1733 .blocks()
1734 .map(|b| b.number)
1735 .collect();
1736 assert_eq!(retained, vec![322, 323, 324, 325, 326]);
1737 let latest = new_history.latest().unwrap();
1738 assert_eq!(latest.number, 326);
1739 assert!(latest.revert);
1740 assert!(!latest.is_partial());
1741 }
1742
1743 #[test(tokio::test)]
1744 async fn test_delayed_synchronizer_catches_up() {
1745 let (v2_sync, v3_sync, nanny_handle, mut rx) = setup_block_sync().await;
1746
1747 let block2_msg = header_message(2);
1749 v2_sync
1750 .send_header(block2_msg.clone())
1751 .await
1752 .expect("send_header failed");
1753
1754 let second_feed_msg = receive_message(&mut rx).await;
1756 debug!("Consumed second message for v2");
1757
1758 assert!(second_feed_msg
1759 .state_msgs
1760 .contains_key("uniswap-v2"));
1761 assert!(matches!(
1762 second_feed_msg.sync_states.get("uniswap-v2").unwrap(),
1763 SynchronizerState::Ready(header) if header.number == 2
1764 ));
1765 assert!(!second_feed_msg
1766 .state_msgs
1767 .contains_key("uniswap-v3"));
1768 assert!(matches!(
1769 second_feed_msg.sync_states.get("uniswap-v3").unwrap(),
1770 SynchronizerState::Delayed(header) if header.number == 1
1771 ));
1772
1773 v3_sync
1775 .send_header(block2_msg.clone())
1776 .await
1777 .expect("send_header failed");
1778
1779 let block3_msg = header_message(3);
1781 v2_sync
1782 .send_header(block3_msg.clone())
1783 .await
1784 .expect("send_header failed");
1785 v3_sync
1786 .send_header(block3_msg)
1787 .await
1788 .expect("send_header failed");
1789
1790 let mut third_feed_msg = receive_message(&mut rx).await;
1793
1794 if !third_feed_msg
1797 .state_msgs
1798 .contains_key("uniswap-v2")
1799 {
1800 third_feed_msg = rx
1801 .recv()
1802 .await
1803 .expect("header channel was closed")
1804 .expect("no error");
1805 }
1806 assert!(third_feed_msg
1807 .state_msgs
1808 .contains_key("uniswap-v2"));
1809 assert!(third_feed_msg
1810 .state_msgs
1811 .contains_key("uniswap-v3"));
1812 assert!(matches!(
1813 third_feed_msg.sync_states.get("uniswap-v2").unwrap(),
1814 SynchronizerState::Ready(header) if header.number == 3
1815 ));
1816 assert!(matches!(
1817 third_feed_msg.sync_states.get("uniswap-v3").unwrap(),
1818 SynchronizerState::Ready(header) if header.number == 3
1819 ));
1820
1821 shutdown_block_synchronizer(nanny_handle, rx).await;
1822 }
1823
1824 #[test(tokio::test)]
1825 async fn test_different_start_blocks() {
1826 let v2_sync = MockStateSync::new();
1827 let v3_sync = MockStateSync::new();
1828 let block_sync = BlockSynchronizer::with_short_timeouts()
1829 .register_synchronizer(
1830 ExtractorIdentity { chain: Chain::Ethereum, name: "uniswap-v2".to_string() },
1831 v2_sync.clone(),
1832 )
1833 .register_synchronizer(
1834 ExtractorIdentity { chain: Chain::Ethereum, name: "uniswap-v3".to_string() },
1835 v3_sync.clone(),
1836 );
1837
1838 let block1_msg = header_message(1);
1840 let block2_msg = header_message(2);
1841
1842 let _ = v2_sync
1843 .send_header(block1_msg.clone())
1844 .await;
1845 v3_sync
1846 .send_header(block2_msg.clone())
1847 .await
1848 .expect("send_header failed");
1849
1850 let (jh, mut rx) = block_sync
1852 .run()
1853 .await
1854 .expect("BlockSynchronizer failed to start.");
1855
1856 let first_feed_msg = receive_message(&mut rx).await;
1858 assert!(matches!(
1859 first_feed_msg.sync_states.get("uniswap-v2").unwrap(),
1860 SynchronizerState::Delayed(header) if header.number == 1
1861 ));
1862 assert!(matches!(
1863 first_feed_msg.sync_states.get("uniswap-v3").unwrap(),
1864 SynchronizerState::Ready(header) if header.number == 2
1865 ));
1866
1867 v2_sync
1869 .send_header(block2_msg.clone())
1870 .await
1871 .expect("send_header failed");
1872
1873 let block3_msg = header_message(3);
1875 let _ = v2_sync
1876 .send_header(block3_msg.clone())
1877 .await;
1878 v3_sync
1879 .send_header(block3_msg.clone())
1880 .await
1881 .expect("send_header failed");
1882
1883 let second_feed_msg = receive_message(&mut rx).await;
1885 assert_eq!(second_feed_msg.state_msgs.len(), 2);
1886 assert!(matches!(
1887 second_feed_msg.sync_states.get("uniswap-v2").unwrap(),
1888 SynchronizerState::Ready(header) if header.number == 3
1889 ));
1890 assert!(matches!(
1891 second_feed_msg.sync_states.get("uniswap-v3").unwrap(),
1892 SynchronizerState::Ready(header) if header.number == 3
1893 ));
1894
1895 shutdown_block_synchronizer(jh, rx).await;
1896 }
1897
1898 #[test(tokio::test)]
1899 async fn test_synchronizer_fails_other_goes_stale() {
1900 let (_v2_sync, v3_sync, nanny_handle, mut sync_rx) =
1901 setup_block_sync_with_behaviour(MockBehavior::ExitImmediately, MockBehavior::Normal)
1902 .await;
1903
1904 let mut error_reported = false;
1905 for _ in 0..3 {
1906 if let Some(msg) = sync_rx.recv().await {
1907 match msg {
1908 Err(_) => error_reported = true,
1909 Ok(msg) => {
1910 assert!(matches!(
1911 msg.sync_states
1912 .get("uniswap-v3")
1913 .unwrap(),
1914 SynchronizerState::Delayed(_)
1915 ));
1916 assert!(matches!(
1917 msg.sync_states
1918 .get("uniswap-v2")
1919 .unwrap(),
1920 SynchronizerState::Ended(_)
1921 ));
1922 }
1923 }
1924 }
1925 }
1926 assert!(error_reported, "BlockSynchronizer did not report final error");
1927
1928 let result = timeout(Duration::from_secs(2), nanny_handle).await;
1930 assert!(result.is_ok(), "Nanny should complete when synchronizer task exits");
1931
1932 assert!(
1934 v3_sync.was_close_received().await,
1935 "v3_sync should have received close signal during cleanup"
1936 );
1937 }
1938
1939 #[test(tokio::test)]
1940 async fn test_cleanup_timeout_warning() {
1941 let (_v2_sync, _v3_sync, nanny_handle, _rx) = setup_block_sync_with_behaviour(
1944 MockBehavior::ExitImmediately,
1945 MockBehavior::IgnoreClose,
1946 )
1947 .await;
1948
1949 let result = timeout(Duration::from_secs(10), nanny_handle).await;
1951 assert!(
1952 result.is_ok(),
1953 "Nanny should complete even when some synchronizers timeout during cleanup"
1954 );
1955
1956 }
1960
1961 #[test(tokio::test)]
1962 async fn test_one_synchronizer_goes_stale_while_other_works() {
1963 let (_v2_sync, v3_sync, nanny_handle, mut rx) = setup_block_sync().await;
1965
1966 let block2_msg = header_message(2);
1968 let _ = v3_sync
1969 .send_header(block2_msg.clone())
1970 .await;
1971 let second_feed_msg = receive_message(&mut rx).await;
1975 assert!(second_feed_msg
1976 .state_msgs
1977 .contains_key("uniswap-v3"));
1978 assert!(!second_feed_msg
1979 .state_msgs
1980 .contains_key("uniswap-v2"));
1981 assert!(matches!(
1982 second_feed_msg
1983 .sync_states
1984 .get("uniswap-v3")
1985 .unwrap(),
1986 SynchronizerState::Ready(_)
1987 ));
1988 if let Some(v2_state) = second_feed_msg
1990 .sync_states
1991 .get("uniswap-v2")
1992 {
1993 if matches!(v2_state, SynchronizerState::Delayed(_)) {
1994 assert!(
1996 !nanny_handle.is_finished(),
1997 "Nanny should still be running when synchronizer is delayed (not stale yet)"
1998 );
1999 }
2000 }
2001
2002 tokio::time::sleep(Duration::from_millis(15)).await;
2004
2005 let block3_msg = header_message(3);
2007 let _ = v3_sync
2008 .send_header(block3_msg.clone())
2009 .await;
2010
2011 tokio::time::sleep(Duration::from_millis(40)).await;
2012
2013 let mut stale_found = false;
2014 for _ in 0..2 {
2015 if let Some(Ok(msg)) = rx.recv().await {
2016 if let Some(SynchronizerState::Stale(_)) = msg.sync_states.get("uniswap-v2") {
2017 stale_found = true;
2018 }
2019 }
2020 }
2021 assert!(stale_found, "v2 synchronizer should be stale");
2022
2023 shutdown_block_synchronizer(nanny_handle, rx).await;
2024 }
2025
2026 #[test(tokio::test)]
2027 async fn test_all_synchronizers_stale_loop_continues() {
2028 let (v2_sync, v3_sync, nanny_handle, mut rx) = setup_block_sync().await;
2031
2032 let mut seen_delayed = false;
2034 let mut seen_stale = false;
2035 let start_time = tokio::time::Instant::now();
2036
2037 while let Ok(Some(Ok(msg))) =
2038 tokio::time::timeout(Duration::from_millis(50), rx.recv()).await
2039 {
2040 let v2_state = msg.sync_states.get("uniswap-v2");
2041 let v3_state = msg.sync_states.get("uniswap-v3");
2042
2043 if !seen_delayed &&
2044 (matches!(v2_state, Some(SynchronizerState::Delayed(_))) ||
2045 matches!(v3_state, Some(SynchronizerState::Delayed(_))))
2046 {
2047 seen_delayed = true;
2048 assert!(
2049 !nanny_handle.is_finished(),
2050 "Nanny must still run when synchronizers are Delayed"
2051 );
2052 }
2053
2054 if matches!(v2_state, Some(SynchronizerState::Stale(_))) &&
2055 matches!(v3_state, Some(SynchronizerState::Stale(_)))
2056 {
2057 seen_stale = true;
2058 assert!(
2059 !nanny_handle.is_finished(),
2060 "Main loop must not exit when all synchronizers are Stale (awaiting recovery)"
2061 );
2062 break;
2063 }
2064
2065 if start_time.elapsed() > Duration::from_millis(500) {
2066 break;
2067 }
2068 }
2069
2070 assert!(seen_delayed, "Synchronizers should transition through Delayed first");
2071 assert!(seen_stale, "Both synchronizers should reach Stale state");
2072
2073 v2_sync.trigger_close().await;
2078 v3_sync.trigger_close().await;
2079 drop(v2_sync);
2080 drop(v3_sync);
2081
2082 let mut error_reported = false;
2084 while let Some(msg) = rx.recv().await {
2085 if msg.is_err() {
2086 error_reported = true;
2087 }
2088 }
2089 assert!(error_reported, "Expected an error after all synchronizers ended");
2090
2091 let nanny_result = timeout(Duration::from_secs(2), nanny_handle).await;
2092 assert!(nanny_result.is_ok(), "Nanny should complete after all synchronizers ended");
2093 }
2094
2095 #[test(tokio::test)]
2096 async fn test_all_synchronizers_recover_after_going_stale() {
2097 let v2_sync = MockStateSync::new();
2100 let v3_sync = MockStateSync::new();
2101 let block_sync =
2102 BlockSynchronizer::new(Duration::from_millis(20), Duration::from_millis(10), 3)
2103 .register_synchronizer(
2104 ExtractorIdentity { chain: Chain::Ethereum, name: "uniswap-v2".to_string() },
2105 v2_sync.clone(),
2106 )
2107 .register_synchronizer(
2108 ExtractorIdentity { chain: Chain::Ethereum, name: "uniswap-v3".to_string() },
2109 v3_sync.clone(),
2110 );
2111
2112 v2_sync
2113 .send_header(header_message(1))
2114 .await
2115 .unwrap();
2116 v3_sync
2117 .send_header(header_message(1))
2118 .await
2119 .unwrap();
2120
2121 let (nanny_handle, mut rx) = block_sync
2122 .run()
2123 .await
2124 .expect("BlockSynchronizer start failed");
2125
2126 let first_msg = receive_message(&mut rx).await;
2127 assert!(matches!(
2128 first_msg.sync_states.get("uniswap-v2").unwrap(),
2129 SynchronizerState::Ready(h) if h.number == 1
2130 ));
2131 assert!(matches!(
2132 first_msg.sync_states.get("uniswap-v3").unwrap(),
2133 SynchronizerState::Ready(h) if h.number == 1
2134 ));
2135
2136 let mut seen_stale = false;
2138 let start_time = tokio::time::Instant::now();
2139 while let Ok(Some(Ok(msg))) =
2140 tokio::time::timeout(Duration::from_millis(50), rx.recv()).await
2141 {
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::Stale(_)) &&
2151 matches!(v3, SynchronizerState::Stale(_))
2152 {
2153 seen_stale = true;
2154 assert!(
2155 !nanny_handle.is_finished(),
2156 "Main loop must not exit while synchronizers are Stale"
2157 );
2158 break;
2159 }
2160 if start_time.elapsed() > Duration::from_millis(500) {
2161 break;
2162 }
2163 }
2164 assert!(seen_stale, "Both synchronizers should go Stale");
2165
2166 v2_sync
2168 .send_header(header_message(5))
2169 .await
2170 .unwrap();
2171 v3_sync
2172 .send_header(header_message(5))
2173 .await
2174 .unwrap();
2175
2176 let mut recovered = false;
2179 for _ in 0..20 {
2180 let msg = receive_message(&mut rx).await;
2181 let v2 = msg
2182 .sync_states
2183 .get("uniswap-v2")
2184 .unwrap();
2185 let v3 = msg
2186 .sync_states
2187 .get("uniswap-v3")
2188 .unwrap();
2189 if matches!(v2, SynchronizerState::Ready(h) if h.number == 5) &&
2190 matches!(v3, SynchronizerState::Ready(h) if h.number == 5)
2191 {
2192 recovered = true;
2193 break;
2194 }
2195 }
2196 assert!(recovered, "Both synchronizers should recover to Ready at block 5");
2197
2198 drop(rx);
2200 timeout(Duration::from_secs(2), nanny_handle)
2201 .await
2202 .expect("Nanny timed out")
2203 .expect("Nanny panicked");
2204 }
2205
2206 #[test(tokio::test)]
2207 async fn test_stale_synchronizer_recovers() {
2208 let (v2_sync, v3_sync, nanny_handle, mut rx) = setup_block_sync().await;
2210
2211 tokio::time::sleep(Duration::from_millis(50)).await;
2213 let block2_msg = header_message(2);
2214 let _ = v2_sync
2215 .send_header(block2_msg.clone())
2216 .await;
2217
2218 for _ in 0..2 {
2220 if let Some(msg) = rx.recv().await {
2221 if let Ok(msg) = msg {
2222 if matches!(
2223 msg.sync_states
2224 .get("uniswap-v2")
2225 .unwrap(),
2226 SynchronizerState::Ready(_)
2227 ) {
2228 assert!(matches!(
2229 msg.sync_states
2230 .get("uniswap-v3")
2231 .unwrap(),
2232 SynchronizerState::Delayed(_)
2233 ));
2234 break;
2235 };
2236 }
2237 } else {
2238 panic!("Channel closed unexpectedly")
2239 }
2240 }
2241
2242 tokio::time::sleep(Duration::from_millis(15)).await;
2244 let block3_msg = header_message(3);
2245 let _ = v2_sync
2246 .send_header(block3_msg.clone())
2247 .await;
2248 let third_msg = receive_message(&mut rx).await;
2249 dbg!(&third_msg);
2250 assert!(matches!(
2251 third_msg
2252 .sync_states
2253 .get("uniswap-v2")
2254 .unwrap(),
2255 SynchronizerState::Ready(_)
2256 ));
2257 assert!(matches!(
2258 third_msg
2259 .sync_states
2260 .get("uniswap-v3")
2261 .unwrap(),
2262 SynchronizerState::Stale(_)
2263 ));
2264
2265 let block4_msg = header_message(4);
2266 let _ = v3_sync
2267 .send_header(block2_msg.clone())
2268 .await;
2269 let _ = v3_sync
2270 .send_header(block3_msg.clone())
2271 .await;
2272 let _ = v3_sync
2273 .send_header(block4_msg.clone())
2274 .await;
2275 let _ = v2_sync
2276 .send_header(block4_msg.clone())
2277 .await;
2278 let fourth_msg = receive_message(&mut rx).await;
2279 assert!(matches!(
2280 fourth_msg
2281 .sync_states
2282 .get("uniswap-v2")
2283 .unwrap(),
2284 SynchronizerState::Ready(_)
2285 ));
2286 assert!(matches!(
2287 fourth_msg
2288 .sync_states
2289 .get("uniswap-v3")
2290 .unwrap(),
2291 SynchronizerState::Ready(_)
2292 ));
2293
2294 shutdown_block_synchronizer(nanny_handle, rx).await;
2295
2296 assert!(
2298 v2_sync.was_close_received().await,
2299 "v2_sync should have received close signal during cleanup"
2300 );
2301 assert!(
2302 v3_sync.was_close_received().await,
2303 "v3_sync should have received close signal during cleanup"
2304 );
2305 }
2306
2307 #[test(tokio::test)]
2308 async fn test_all_synchronizer_advanced() {
2309 let (v2_sync, v3_sync, nanny_handle, mut rx) = setup_block_sync().await;
2313
2314 let block3 = header_message(3);
2315 v2_sync
2316 .send_header(block3.clone())
2317 .await
2318 .unwrap();
2319 v3_sync
2320 .send_header(block3)
2321 .await
2322 .unwrap();
2323
2324 let msg = receive_message(&mut rx).await;
2325 matches!(
2326 msg.sync_states
2327 .get("uniswap-v2")
2328 .unwrap(),
2329 SynchronizerState::Ready(_)
2330 );
2331 matches!(
2332 msg.sync_states
2333 .get("uniswap-v3")
2334 .unwrap(),
2335 SynchronizerState::Ready(_)
2336 );
2337
2338 shutdown_block_synchronizer(nanny_handle, rx).await;
2339 }
2340
2341 #[test(tokio::test)]
2342 async fn test_one_synchronizer_advanced() {
2343 let (v2_sync, v3_sync, nanny_handle, mut rx) = setup_block_sync().await;
2344
2345 let block2 = header_message(2);
2346 let block4 = header_message(4);
2347 v2_sync
2348 .send_header(block4.clone())
2349 .await
2350 .unwrap();
2351 v3_sync
2352 .send_header(block2.clone())
2353 .await
2354 .unwrap();
2355
2356 let msg = receive_message(&mut rx).await;
2357 matches!(
2358 msg.sync_states
2359 .get("uniswap-v2")
2360 .unwrap(),
2361 SynchronizerState::Ready(_)
2362 );
2363 matches!(
2364 msg.sync_states
2365 .get("uniswap-v3")
2366 .unwrap(),
2367 SynchronizerState::Delayed(_)
2368 );
2369
2370 shutdown_block_synchronizer(nanny_handle, rx).await;
2371 }
2372
2373 #[test(tokio::test)]
2374 async fn test_partial_blocks_normal_operation() {
2375 let (v2_sync, _v3_sync, nanny_handle, mut rx) = setup_block_sync().await;
2378
2379 send_and_assert_ready(
2381 &v2_sync,
2382 "uniswap-v2",
2383 &mut rx,
2384 partial_header_message(2, 0),
2385 2,
2386 Some(0),
2387 )
2388 .await;
2389 send_and_assert_ready(
2390 &v2_sync,
2391 "uniswap-v2",
2392 &mut rx,
2393 partial_header_message(2, 3),
2394 2,
2395 Some(3),
2396 )
2397 .await;
2398 send_and_assert_ready(
2399 &v2_sync,
2400 "uniswap-v2",
2401 &mut rx,
2402 partial_header_message(2, 7),
2403 2,
2404 Some(7),
2405 )
2406 .await;
2407
2408 send_and_assert_ready(
2410 &v2_sync,
2411 "uniswap-v2",
2412 &mut rx,
2413 partial_header_message(3, 0),
2414 3,
2415 Some(0),
2416 )
2417 .await;
2418 send_and_assert_ready(
2419 &v2_sync,
2420 "uniswap-v2",
2421 &mut rx,
2422 partial_header_message(3, 2),
2423 3,
2424 Some(2),
2425 )
2426 .await;
2427
2428 shutdown_block_synchronizer(nanny_handle, rx).await;
2429 }
2430
2431 #[test(tokio::test)]
2432 async fn test_partial_blocks_handles_reverts() {
2433 let (v2_sync, _v3_sync, nanny_handle, mut rx) = setup_block_sync().await;
2436
2437 send_and_assert_ready(&v2_sync, "uniswap-v2", &mut rx, header_message(2), 2, None).await;
2439
2440 send_and_assert_ready(
2442 &v2_sync,
2443 "uniswap-v2",
2444 &mut rx,
2445 partial_header_message(3, 0),
2446 3,
2447 Some(0),
2448 )
2449 .await;
2450 send_and_assert_ready(
2451 &v2_sync,
2452 "uniswap-v2",
2453 &mut rx,
2454 partial_header_message(3, 2),
2455 3,
2456 Some(2),
2457 )
2458 .await;
2459
2460 send_and_assert_ready(&v2_sync, "uniswap-v2", &mut rx, revert_header_message(2), 2, None)
2462 .await;
2463
2464 send_and_assert_ready(&v2_sync, "uniswap-v2", &mut rx, header_message(3), 3, None).await;
2466
2467 shutdown_block_synchronizer(nanny_handle, rx).await;
2468 }
2469
2470 #[test(tokio::test)]
2471 async fn test_partial_blocks_delayed_synchronizer_catches_up() {
2472 let (v2_sync, v3_sync, nanny_handle, mut rx) = setup_block_sync().await;
2475
2476 let partial_0 = partial_header_message(2, 0);
2478 v2_sync
2479 .send_header(partial_0.clone())
2480 .await
2481 .expect("send partial 0 failed");
2482
2483 let msg = receive_message(&mut rx).await;
2484 assert!(msg
2486 .state_msgs
2487 .contains_key("uniswap-v2"));
2488 assert!(!msg
2489 .state_msgs
2490 .contains_key("uniswap-v3"));
2491 assert!(matches!(
2492 msg.sync_states.get("uniswap-v2").unwrap(),
2493 SynchronizerState::Ready(h) if h.partial_block_index == Some(0)
2494 ));
2495 assert!(matches!(
2496 msg.sync_states
2497 .get("uniswap-v3")
2498 .unwrap(),
2499 SynchronizerState::Delayed(_)
2500 ));
2501
2502 let partial_2 = partial_header_message(2, 2);
2504 v2_sync
2505 .send_header(partial_2.clone())
2506 .await
2507 .expect("send partial 2 failed");
2508 v3_sync
2509 .send_header(partial_0.clone())
2510 .await
2511 .expect("v3 catch up partial 0 failed");
2512 v3_sync
2513 .send_header(partial_2.clone())
2514 .await
2515 .expect("v3 catch up partial 2 failed");
2516
2517 let mut v3_ready = false;
2519 for _ in 0..3 {
2520 let msg = receive_message(&mut rx).await;
2521 if matches!(
2522 msg.sync_states.get("uniswap-v3").unwrap(),
2523 SynchronizerState::Ready(h) if h.partial_block_index == Some(2)
2524 ) {
2525 v3_ready = true;
2526 break;
2527 }
2528 }
2529 assert!(v3_ready, "v3 caught up to partial 2");
2530
2531 shutdown_block_synchronizer(nanny_handle, rx).await;
2532 }
2533
2534 fn partial_revert_message(block: u8, dropped_idx: u32) -> StateSyncMessage<BlockHeader> {
2537 let mut msg = revert_header_message(block);
2538 msg.header.partial_block_index = Some(dropped_idx);
2539 msg
2540 }
2541
2542 async fn advance_both(
2544 v2: &MockStateSync,
2545 v3: &MockStateSync,
2546 rx: &mut Receiver<BlockSyncResult<FeedMessage>>,
2547 msg: StateSyncMessage<BlockHeader>,
2548 ) {
2549 v2.send_header(msg.clone())
2550 .await
2551 .expect("v2 send failed");
2552 v3.send_header(msg)
2553 .await
2554 .expect("v3 send failed");
2555 let feed = receive_message(rx).await;
2556 for name in ["uniswap-v2", "uniswap-v3"] {
2557 assert!(
2558 matches!(feed.sync_states.get(name).unwrap(), SynchronizerState::Ready(_)),
2559 "{name} not Ready: {:?}",
2560 feed.sync_states
2561 );
2562 }
2563 }
2564
2565 fn assert_ready_at(feed: &FeedMessage, name: &str, number: u64, partial: Option<u32>) {
2566 match feed.sync_states.get(name).unwrap() {
2567 SynchronizerState::Ready(h) => {
2568 assert_eq!((h.number, h.partial_block_index), (number, partial), "{name}");
2569 }
2570 other => panic!("{name}: expected Ready, got {other:?}"),
2571 }
2572 }
2573
2574 fn assert_at(feed: &FeedMessage, name: &str, number: u64, partial: Option<u32>) {
2577 match feed.sync_states.get(name).unwrap() {
2578 SynchronizerState::Ready(h) | SynchronizerState::Delayed(h) => {
2579 assert_eq!((h.number, h.partial_block_index), (number, partial), "{name}");
2580 }
2581 other => panic!("{name}: expected Ready or Delayed, got {other:?}"),
2582 }
2583 }
2584
2585 #[test(tokio::test)]
2587 async fn test_partial_revert_applied_when_streams_aligned() {
2588 let (v2, v3, nanny, mut rx) = setup_block_sync().await;
2589 advance_both(&v2, &v3, &mut rx, header_message(2)).await;
2590 advance_both(&v2, &v3, &mut rx, header_message(3)).await;
2591 advance_both(&v2, &v3, &mut rx, partial_header_message(4, 0)).await;
2592 advance_both(&v2, &v3, &mut rx, partial_header_message(4, 3)).await;
2593
2594 advance_both(&v2, &v3, &mut rx, partial_revert_message(3, 3)).await;
2595 advance_both(&v2, &v3, &mut rx, partial_header_message(4, 0)).await;
2597
2598 shutdown_block_synchronizer(nanny, rx).await;
2599 }
2600
2601 #[test(tokio::test)]
2606 async fn test_partial_revert_applied_when_one_stream_lags() {
2607 let (v2, v3, nanny, mut rx) = setup_block_sync().await;
2608 advance_both(&v2, &v3, &mut rx, header_message(2)).await;
2609 advance_both(&v2, &v3, &mut rx, header_message(3)).await;
2610 advance_both(&v2, &v3, &mut rx, partial_header_message(4, 0)).await;
2611 advance_both(&v2, &v3, &mut rx, partial_header_message(4, 3)).await;
2612
2613 v2.send_header(partial_revert_message(3, 3))
2614 .await
2615 .expect("v2 send failed");
2616 let feed = receive_message(&mut rx).await;
2617 assert_ready_at(&feed, "uniswap-v2", 3, Some(3));
2618 assert!(matches!(
2619 feed.sync_states
2620 .get("uniswap-v3")
2621 .unwrap(),
2622 SynchronizerState::Delayed(_)
2623 ));
2624
2625 v3.send_header(partial_revert_message(3, 3))
2627 .await
2628 .expect("v3 send failed");
2629 v2.send_header(partial_header_message(4, 0))
2630 .await
2631 .expect("v2 send failed");
2632 v3.send_header(partial_header_message(4, 0))
2633 .await
2634 .expect("v3 send failed");
2635 let feed = receive_message(&mut rx).await;
2636 assert_ready_at(&feed, "uniswap-v2", 4, Some(0));
2637 assert_ready_at(&feed, "uniswap-v3", 4, Some(0));
2638
2639 shutdown_block_synchronizer(nanny, rx).await;
2640 }
2641
2642 #[test(tokio::test)]
2645 async fn test_deeper_revert_wins_the_round() {
2646 let (v2, v3, nanny, mut rx) = setup_block_sync().await;
2647 advance_both(&v2, &v3, &mut rx, header_message(2)).await;
2648 advance_both(&v2, &v3, &mut rx, header_message(3)).await;
2649 advance_both(&v2, &v3, &mut rx, header_message(4)).await;
2650
2651 v2.send_header(revert_header_message(3))
2652 .await
2653 .expect("v2 send failed");
2654 v3.send_header(revert_header_message(2))
2655 .await
2656 .expect("v3 send failed");
2657 let feed = receive_message(&mut rx).await;
2658 assert_ready_at(&feed, "uniswap-v2", 3, None);
2659 assert_ready_at(&feed, "uniswap-v3", 2, None);
2660
2661 let mut new_3 = header_message(3);
2663 new_3.header.hash = Bytes::from(vec![0x33]);
2664 advance_both(&v2, &v3, &mut rx, new_3).await;
2665
2666 shutdown_block_synchronizer(nanny, rx).await;
2667 }
2668
2669 #[test(tokio::test)]
2673 async fn test_redelivered_revert_keeps_history() {
2674 let (v2, v3, nanny, mut rx) = setup_block_sync().await;
2675 advance_both(&v2, &v3, &mut rx, header_message(2)).await;
2676 advance_both(&v2, &v3, &mut rx, header_message(3)).await;
2677 advance_both(&v2, &v3, &mut rx, partial_header_message(4, 0)).await;
2678
2679 v2.send_header(partial_revert_message(3, 0))
2680 .await
2681 .expect("v2 send failed");
2682 let feed = receive_message(&mut rx).await;
2683 assert_ready_at(&feed, "uniswap-v2", 3, Some(0));
2684
2685 v3.send_header(partial_revert_message(3, 0))
2686 .await
2687 .expect("v3 send failed");
2688 let feed = receive_message(&mut rx).await;
2691 assert_at(&feed, "uniswap-v3", 3, Some(0));
2692
2693 advance_both(&v2, &v3, &mut rx, partial_header_message(4, 0)).await;
2695
2696 shutdown_block_synchronizer(nanny, rx).await;
2697 }
2698
2699 #[test(tokio::test)]
2703 async fn test_catch_up_stops_on_first_advanced_block() {
2704 let (v2, v3, nanny, mut rx) = setup_block_sync().await;
2705
2706 v2.send_header(header_message(2))
2708 .await
2709 .expect("v2 send failed");
2710 let feed = receive_message(&mut rx).await;
2711 assert_ready_at(&feed, "uniswap-v2", 2, None);
2712 assert!(matches!(
2713 feed.sync_states
2714 .get("uniswap-v3")
2715 .unwrap(),
2716 SynchronizerState::Delayed(_)
2717 ));
2718
2719 let mut detached_3 = header_message(3);
2721 detached_3.header.parent_hash = Bytes::from(vec![0xAA]);
2722 v3.send_header(detached_3)
2723 .await
2724 .expect("v3 send failed");
2725 v3.send_header(header_message(4))
2726 .await
2727 .expect("v3 send failed");
2728
2729 let feed = receive_message(&mut rx).await;
2733 assert_ready_at(&feed, "uniswap-v3", 3, None);
2734
2735 shutdown_block_synchronizer(nanny, rx).await;
2736 }
2737
2738 #[test(tokio::test)]
2743 async fn test_redelivered_revert_does_not_suppress_the_round() {
2744 let (v2, v3, nanny, mut rx) = setup_block_sync().await;
2745 advance_both(&v2, &v3, &mut rx, header_message(2)).await;
2746 advance_both(&v2, &v3, &mut rx, header_message(3)).await;
2747
2748 v2.send_header(revert_header_message(2))
2750 .await
2751 .expect("v2 send failed");
2752 let feed = receive_message(&mut rx).await;
2753 assert_ready_at(&feed, "uniswap-v2", 2, None);
2754
2755 let mut new_3 = header_message(3);
2757 new_3.header.hash = Bytes::from(vec![0x33]);
2758 v3.send_header(revert_header_message(2))
2759 .await
2760 .expect("v3 send failed");
2761 v2.send_header(new_3.clone())
2762 .await
2763 .expect("v2 send failed");
2764 let feed = receive_message(&mut rx).await;
2765 assert_ready_at(&feed, "uniswap-v2", 3, None);
2766
2767 let mut block_4 = header_message(4);
2768 block_4.header.parent_hash = new_3.header.hash.clone();
2769 v2.send_header(block_4)
2770 .await
2771 .expect("v2 send failed");
2772 let feed = receive_message(&mut rx).await;
2773 assert_ready_at(&feed, "uniswap-v2", 4, None);
2774
2775 let mut revert_3 = revert_header_message(3);
2777 revert_3.header.hash = new_3.header.hash.clone();
2778 revert_3.header.parent_hash = Bytes::from(vec![0x02]);
2779 v2.send_header(revert_3)
2780 .await
2781 .expect("v2 send failed");
2782 let feed = receive_message(&mut rx).await;
2783 assert_ready_at(&feed, "uniswap-v2", 3, None);
2784
2785 shutdown_block_synchronizer(nanny, rx).await;
2786 }
2787
2788 #[test]
2789 fn test_block_header_display_marks_reverts() {
2790 let mut header = revert_header_message(7).header;
2791 assert_eq!(header.to_string(), "Block #7 [0x07..] (revert)");
2792 header.partial_block_index = Some(2);
2793 assert_eq!(header.to_string(), "Block #7 [0x07..] (partial 2) (revert)");
2794 header.revert = false;
2795 assert_eq!(header.to_string(), "Block #7 [0x07..] (partial 2)");
2796 }
2797
2798 #[test(tokio::test)]
2802 async fn test_catch_up_revert_from_the_tip_is_fresh() {
2803 let (v2, v3, nanny, mut rx) = setup_block_sync().await;
2804 advance_both(&v2, &v3, &mut rx, header_message(2)).await;
2805 advance_both(&v2, &v3, &mut rx, header_message(3)).await;
2806
2807 v2.send_header(header_message(4))
2809 .await
2810 .expect("v2 send failed");
2811 let feed = receive_message(&mut rx).await;
2812 assert_ready_at(&feed, "uniswap-v2", 4, None);
2813 v2.send_header(header_message(5))
2814 .await
2815 .expect("v2 send failed");
2816 let feed = receive_message(&mut rx).await;
2817 assert_ready_at(&feed, "uniswap-v2", 5, None);
2818
2819 v3.send_header(header_message(4))
2821 .await
2822 .expect("v3 send failed");
2823 v3.send_header(header_message(5))
2824 .await
2825 .expect("v3 send failed");
2826 v3.send_header(revert_header_message(4))
2827 .await
2828 .expect("v3 send failed");
2829 let feed = receive_message(&mut rx).await;
2830 assert_ready_at(&feed, "uniswap-v3", 4, None);
2831
2832 let mut new_5 = header_message(5);
2834 new_5.header.hash = Bytes::from(vec![0x55]);
2835 advance_both(&v2, &v3, &mut rx, new_5).await;
2836
2837 shutdown_block_synchronizer(nanny, rx).await;
2838 }
2839
2840 #[test(tokio::test)]
2843 async fn test_replayed_revert_two_rounds_late_does_not_rewind_history() {
2844 let (v2, v3, nanny, mut rx) = setup_block_sync().await;
2845 advance_both(&v2, &v3, &mut rx, header_message(2)).await;
2846 advance_both(&v2, &v3, &mut rx, header_message(3)).await;
2847
2848 v2.send_header(revert_header_message(2))
2850 .await
2851 .expect("v2 send failed");
2852 let feed = receive_message(&mut rx).await;
2853 assert_ready_at(&feed, "uniswap-v2", 2, None);
2854
2855 let mut new_3 = header_message(3);
2857 new_3.header.hash = Bytes::from(vec![0x33]);
2858 v2.send_header(new_3.clone())
2859 .await
2860 .expect("v2 send failed");
2861 let feed = receive_message(&mut rx).await;
2862 assert_ready_at(&feed, "uniswap-v2", 3, None);
2863
2864 let mut new_4 = header_message(4);
2866 new_4.header.hash = Bytes::from(vec![0x44]);
2867 new_4.header.parent_hash = new_3.header.hash.clone();
2868 v3.send_header(revert_header_message(2))
2869 .await
2870 .expect("v3 send failed");
2871 v2.send_header(new_4.clone())
2872 .await
2873 .expect("v2 send failed");
2874 let feed = receive_message(&mut rx).await;
2875 assert_ready_at(&feed, "uniswap-v2", 4, None);
2876
2877 let mut new_5 = header_message(5);
2880 new_5.header.hash = Bytes::from(vec![0x55]);
2881 new_5.header.parent_hash = new_4.header.hash.clone();
2882 v2.send_header(new_5)
2883 .await
2884 .expect("v2 send failed");
2885 let feed = receive_message(&mut rx).await;
2886 assert_ready_at(&feed, "uniswap-v2", 5, None);
2887
2888 let mut revert_4 = revert_header_message(4);
2890 revert_4.header.hash = new_4.header.hash.clone();
2891 revert_4.header.parent_hash = new_3.header.hash.clone();
2892 v2.send_header(revert_4)
2893 .await
2894 .expect("v2 send failed");
2895 let feed = receive_message(&mut rx).await;
2896 assert_ready_at(&feed, "uniswap-v2", 4, None);
2897
2898 shutdown_block_synchronizer(nanny, rx).await;
2899 }
2900
2901 #[test(tokio::test)]
2905 async fn test_same_revert_with_another_dropped_index_does_not_hold_the_round() {
2906 let (v2, v3, nanny, mut rx) = setup_block_sync().await;
2907 advance_both(&v2, &v3, &mut rx, header_message(2)).await;
2908 advance_both(&v2, &v3, &mut rx, header_message(3)).await;
2909 advance_both(&v2, &v3, &mut rx, partial_header_message(4, 0)).await;
2910
2911 v2.send_header(partial_revert_message(3, 0))
2912 .await
2913 .expect("v2 send failed");
2914 let feed = receive_message(&mut rx).await;
2915 assert_ready_at(&feed, "uniswap-v2", 3, Some(0));
2916
2917 v3.send_header(partial_revert_message(3, 5))
2918 .await
2919 .expect("v3 send failed");
2920 v2.send_header(partial_header_message(4, 0))
2921 .await
2922 .expect("v2 send failed");
2923 let feed = receive_message(&mut rx).await;
2924 assert_ready_at(&feed, "uniswap-v2", 4, Some(0));
2925
2926 shutdown_block_synchronizer(nanny, rx).await;
2927 }
2928
2929 #[test(tokio::test)]
2933 async fn test_back_to_back_partial_undos_with_alternating_lag() {
2934 let (v2, v3, nanny, mut rx) = setup_block_sync().await;
2935 advance_both(&v2, &v3, &mut rx, header_message(2)).await;
2936 advance_both(&v2, &v3, &mut rx, header_message(3)).await;
2937 advance_both(&v2, &v3, &mut rx, partial_header_message(4, 0)).await;
2938 advance_both(&v2, &v3, &mut rx, partial_header_message(4, 3)).await;
2939
2940 v2.send_header(partial_revert_message(3, 3))
2942 .await
2943 .expect("v2 send failed");
2944 let feed = receive_message(&mut rx).await;
2945 assert_ready_at(&feed, "uniswap-v2", 3, Some(3));
2946
2947 let mut sealed_4 = header_message(4);
2949 sealed_4.header.partial_block_index = Some(0);
2950 v3.send_header(partial_revert_message(3, 3))
2951 .await
2952 .expect("v3 send failed");
2953 advance_both(&v2, &v3, &mut rx, sealed_4).await;
2954 advance_both(&v2, &v3, &mut rx, partial_header_message(5, 0)).await;
2955
2956 v3.send_header(partial_revert_message(4, 0))
2958 .await
2959 .expect("v3 send failed");
2960 let feed = receive_message(&mut rx).await;
2961 assert_ready_at(&feed, "uniswap-v3", 4, Some(0));
2962
2963 let mut sealed_5 = header_message(5);
2964 sealed_5.header.partial_block_index = Some(0);
2965 v2.send_header(partial_revert_message(4, 0))
2966 .await
2967 .expect("v2 send failed");
2968 v2.send_header(sealed_5.clone())
2969 .await
2970 .expect("v2 send failed");
2971 v3.send_header(sealed_5)
2972 .await
2973 .expect("v3 send failed");
2974 let feed = receive_message(&mut rx).await;
2975 assert_ready_at(&feed, "uniswap-v2", 5, Some(0));
2976 assert_ready_at(&feed, "uniswap-v3", 5, Some(0));
2977
2978 shutdown_block_synchronizer(nanny, rx).await;
2979 }
2980
2981 #[test(tokio::test)]
2984 async fn test_undo_to_the_first_block_after_a_jump_resolves() {
2985 let (v2, v3, nanny, mut rx) = setup_block_sync().await;
2986 advance_both(&v2, &v3, &mut rx, partial_header_message(5, 0)).await;
2988 let mut sealed_5 = header_message(5);
2990 sealed_5.header.partial_block_index = Some(1);
2991 advance_both(&v2, &v3, &mut rx, sealed_5).await;
2992 advance_both(&v2, &v3, &mut rx, partial_header_message(6, 0)).await;
2993 advance_both(&v2, &v3, &mut rx, partial_revert_message(5, 0)).await;
2995 let mut rebuilt_6 = partial_header_message(6, 0);
2996 rebuilt_6.header.hash = Bytes::from(vec![0x60]);
2997 advance_both(&v2, &v3, &mut rx, rebuilt_6).await;
2998
2999 shutdown_block_synchronizer(nanny, rx).await;
3000 }
3001
3002 #[test(tokio::test)]
3005 async fn test_revert_to_an_unseen_block_on_the_tip_resolves() {
3006 let (v2, v3, nanny, mut rx) = setup_block_sync().await;
3007 advance_both(&v2, &v3, &mut rx, header_message(2)).await;
3008 advance_both(&v2, &v3, &mut rx, header_message(3)).await;
3009 advance_both(&v2, &v3, &mut rx, header_message(4)).await;
3010 advance_both(&v2, &v3, &mut rx, revert_header_message(5)).await;
3011 advance_both(&v2, &v3, &mut rx, header_message(6)).await;
3012
3013 shutdown_block_synchronizer(nanny, rx).await;
3014 }
3015
3016 #[test(tokio::test)]
3021 async fn test_undo_to_the_snapshot_block_after_a_resubscribe_resolves() {
3022 let (v2, v3, nanny, mut rx) = setup_block_sync().await;
3023 advance_both(&v2, &v3, &mut rx, header_message(2)).await;
3024 advance_both(&v2, &v3, &mut rx, header_message(3)).await;
3025 receive_message(&mut rx).await;
3027 for sync in [&v2, &v3] {
3029 sync.send_header(partial_header_message(5, 0))
3030 .await
3031 .expect("send failed");
3032 sync.send_header(partial_revert_message(4, 0))
3033 .await
3034 .expect("send failed");
3035 }
3036 receive_message(&mut rx).await;
3037 let mut sealed_5 = header_message(5);
3038 sealed_5.header.partial_block_index = Some(0);
3039 advance_both(&v2, &v3, &mut rx, sealed_5).await;
3040 advance_both(&v2, &v3, &mut rx, partial_header_message(6, 0)).await;
3041 advance_both(&v2, &v3, &mut rx, partial_revert_message(5, 0)).await;
3042 let mut sealed_6 = header_message(6);
3043 sealed_6.header.partial_block_index = Some(0);
3044 advance_both(&v2, &v3, &mut rx, sealed_6).await;
3045
3046 shutdown_block_synchronizer(nanny, rx).await;
3047 }
3048
3049 #[test(tokio::test)]
3052 async fn test_one_block_reorg_right_after_a_jump_resolves() {
3053 let (v2, v3, nanny, mut rx) = setup_block_sync().await;
3054 advance_both(&v2, &v3, &mut rx, header_message(5)).await;
3055 advance_both(&v2, &v3, &mut rx, revert_header_message(4)).await;
3056 let mut new_5 = header_message(5);
3057 new_5.header.hash = Bytes::from(vec![0x55]);
3058 advance_both(&v2, &v3, &mut rx, new_5.clone()).await;
3059 let mut new_6 = header_message(6);
3060 new_6.header.parent_hash = new_5.header.hash.clone();
3061 advance_both(&v2, &v3, &mut rx, new_6).await;
3062
3063 shutdown_block_synchronizer(nanny, rx).await;
3064 }
3065
3066 fn with_new_token(
3068 mut msg: StateSyncMessage<BlockHeader>,
3069 token: Option<u8>,
3070 ) -> StateSyncMessage<BlockHeader> {
3071 let mut changes = BlockAggregatedChanges::default();
3072 if let Some(byte) = token {
3073 let addr = Bytes::from(vec![byte; 20]);
3074 changes.new_tokens = HashMap::from([(
3075 addr.clone(),
3076 Token::new(&addr, "T", 18, 0, &[], Chain::Ethereum, 100),
3077 )]);
3078 }
3079 msg.deltas = Some(changes);
3080 msg
3081 }
3082
3083 fn has_new_token(feed: &FeedMessage, name: &str, token: u8) -> bool {
3084 feed.state_msgs
3085 .get(name)
3086 .and_then(|msg| msg.deltas.as_ref())
3087 .is_some_and(|changes| {
3088 changes
3089 .new_tokens
3090 .contains_key(&Bytes::from(vec![token; 20]))
3091 })
3092 }
3093
3094 #[test(tokio::test)]
3097 async fn test_lagging_catch_up_keeps_new_tokens() {
3098 let (v2, v3, nanny, mut rx) = setup_block_sync().await;
3099 advance_both(&v2, &v3, &mut rx, with_new_token(header_message(2), None)).await;
3100
3101 v2.send_header(with_new_token(header_message(3), None))
3102 .await
3103 .expect("v2 send failed");
3104 receive_message(&mut rx).await;
3105
3106 v3.send_header(with_new_token(header_message(3), None))
3107 .await
3108 .expect("v3 send failed");
3109 v3.send_header(with_new_token(header_message(4), Some(0xee)))
3110 .await
3111 .expect("v3 send failed");
3112 v2.send_header(with_new_token(header_message(4), None))
3113 .await
3114 .expect("v2 send failed");
3115
3116 let mut found = false;
3117 for _ in 0..3 {
3118 if has_new_token(&receive_message(&mut rx).await, "uniswap-v3", 0xee) {
3119 found = true;
3120 break;
3121 }
3122 }
3123 assert!(found, "token 0xee never reached the feed");
3124
3125 shutdown_block_synchronizer(nanny, rx).await;
3126 }
3127
3128 #[test(tokio::test)]
3132 async fn test_applied_undo_merged_with_rebuilt_block_keeps_new_tokens() {
3133 let (v2, v3, nanny, mut rx) = setup_block_sync().await;
3134 for block in 2..=4 {
3135 advance_both(&v2, &v3, &mut rx, with_new_token(header_message(block), None)).await;
3136 }
3137
3138 v2.send_header(with_new_token(revert_header_message(3), None))
3139 .await
3140 .expect("v2 send failed");
3141 receive_message(&mut rx).await;
3142
3143 let mut rebuilt_4 = header_message(4);
3144 rebuilt_4.header.hash = Bytes::from(vec![0x34]);
3145 rebuilt_4.header.parent_hash = Bytes::from(vec![3]);
3146 v3.send_header(with_new_token(revert_header_message(3), None))
3147 .await
3148 .expect("v3 send failed");
3149 v3.send_header(with_new_token(rebuilt_4, Some(0xee)))
3150 .await
3151 .expect("v3 send failed");
3152
3153 let mut found = false;
3154 for _ in 0..3 {
3155 if has_new_token(&receive_message(&mut rx).await, "uniswap-v3", 0xee) {
3156 found = true;
3157 break;
3158 }
3159 }
3160 assert!(found, "token 0xee never reached the feed");
3161
3162 shutdown_block_synchronizer(nanny, rx).await;
3163 }
3164}