1use async_trait::async_trait;
12pub use petgraph::graph::EdgeIndex;
13use petgraph::{graph::NodeIndex, stable_graph};
14use rustc_hash::{FxHashMap, FxHashSet};
15use tracing::{debug, trace};
16use tycho_simulation::tycho_common::models::Address;
17
18use super::GraphManager;
19use crate::{
20 feed::{
21 events::{EventError, MarketEvent, MarketEventHandler},
22 market_data::MarketDataView,
23 },
24 graph::GraphError,
25 types::ComponentId,
26};
27
28#[derive(Debug, Clone, Default)]
47pub struct EdgeData<D = ()> {
48 pub component_id: ComponentId,
50 pub data: Option<D>,
52}
53
54impl<M> EdgeData<M> {
55 pub fn new(component_id: ComponentId) -> Self {
57 Self { component_id, data: None }
58 }
59
60 pub fn with_data(component_id: ComponentId, data: M) -> Self {
62 Self { component_id, data: Some(data) }
63 }
64}
65
66pub type StableDiGraph<D> = stable_graph::StableDiGraph<Address, EdgeData<D>>;
68
69pub struct PetgraphStableDiGraphManager<D: Clone> {
76 graph: StableDiGraph<D>,
80 edge_map: FxHashMap<ComponentId, Vec<EdgeIndex>>,
82 node_map: FxHashMap<Address, NodeIndex>,
84}
85
86impl<D: Clone> PetgraphStableDiGraphManager<D> {
87 pub fn new() -> Self {
89 Self {
90 graph: StableDiGraph::default(),
91 edge_map: FxHashMap::default(),
92 node_map: FxHashMap::default(),
93 }
94 }
95
96 pub(crate) fn find_node(&self, addr: &Address) -> Result<NodeIndex, GraphError> {
98 self.node_map
99 .get(addr)
100 .copied()
101 .ok_or_else(|| GraphError::TokenNotFound(addr.clone()))
102 }
103
104 fn get_or_create_node(&mut self, addr: &Address) -> NodeIndex {
107 match self.find_node(addr) {
109 Ok(node_idx) => node_idx,
110 Err(_) => {
111 let node_idx = self.graph.add_node(addr.clone());
112 self.node_map
113 .insert(addr.clone(), node_idx);
114 node_idx
115 }
116 }
117 }
118
119 fn add_edge(&mut self, from_idx: NodeIndex, to_idx: NodeIndex, component_id: &ComponentId) {
127 let edge_idx = self
128 .graph
129 .add_edge(from_idx, to_idx, EdgeData::new(component_id.clone()));
130 self.edge_map
131 .entry(component_id.clone())
132 .or_default()
133 .push(edge_idx);
134 }
135
136 fn add_component_edges(&mut self, component_id: &ComponentId, node_indices: &[NodeIndex]) {
139 node_indices
141 .iter()
142 .enumerate()
143 .flat_map(|(i, &from_idx)| {
144 node_indices
145 .iter()
146 .skip(i + 1)
147 .map(move |&to_idx| (from_idx, to_idx))
148 })
149 .for_each(|(from_idx, to_idx)| {
150 self.add_edge(from_idx, to_idx, component_id);
152 self.add_edge(to_idx, from_idx, component_id);
153 });
154 }
155
156 fn add_components(
166 &mut self,
167 components: &FxHashMap<ComponentId, Vec<Address>>,
168 ) -> Result<(), GraphError> {
169 let mut invalid_components = Vec::new();
170 let mut skipped_duplicates = 0usize;
171
172 let mut sorted_components: Vec<_> = components.iter().collect();
174 sorted_components.sort_by_key(|(id, _)| *id);
175
176 for (comp_id, tokens) in sorted_components {
177 if self.edge_map.contains_key(comp_id) {
178 trace!(component_id = %comp_id, "skipping already-tracked component");
179 skipped_duplicates += 1;
180 continue;
181 }
182
183 if tokens.len() < 2 {
184 invalid_components.push(comp_id.clone());
185 continue;
186 }
187 let mut sorted_tokens: Vec<&Address> = tokens.iter().collect();
188 sorted_tokens.sort();
189 let node_indices: Vec<NodeIndex> = sorted_tokens
190 .iter()
191 .map(|token| self.get_or_create_node(token))
192 .collect();
193 self.add_component_edges(comp_id, &node_indices);
194 }
195
196 if skipped_duplicates > 0 {
197 debug!(skipped_duplicates, "skipped duplicate components during add");
198 }
199
200 if !invalid_components.is_empty() {
202 return Err(GraphError::InvalidComponents(invalid_components));
203 }
204
205 Ok(())
206 }
207
208 fn remove_components(&mut self, components: &[ComponentId]) -> Result<(), GraphError> {
218 let mut missing_components = Vec::new();
219
220 for comp_id in components {
221 if let Some(edge_indices) = self.edge_map.remove(comp_id) {
223 for edge_idx in edge_indices {
224 self.graph.remove_edge(edge_idx);
225 }
226 } else {
227 missing_components.push(comp_id.clone());
229 }
230 }
231
232 if !missing_components.is_empty() {
234 return Err(GraphError::ComponentsNotFound(missing_components));
235 }
236
237 Ok(())
238 }
239
240 #[cfg(test)]
255 pub(crate) fn set_edge_weight(
256 &mut self,
257 component_id: &ComponentId,
258 token_in: &Address,
259 token_out: &Address,
260 data: D,
261 bidirectional: bool,
262 ) -> Result<(), GraphError> {
263 let from_idx = self.find_node(token_in)?;
264 let to_idx = self.find_node(token_out)?;
265
266 let edge_indices = self
268 .edge_map
269 .get(component_id)
270 .ok_or_else(|| GraphError::ComponentsNotFound(vec![component_id.clone()]))?;
271
272 let mut updated = false;
273 for &edge_idx in edge_indices {
274 let (edge_from, edge_to) = match self.graph.edge_endpoints(edge_idx) {
276 Some(endpoints) => endpoints,
277 None => continue,
278 };
279
280 let should_update = if bidirectional {
282 (edge_from == from_idx && edge_to == to_idx) ||
284 (edge_from == to_idx && edge_to == from_idx)
285 } else {
286 edge_from == from_idx && edge_to == to_idx
288 };
289
290 if should_update {
291 let edge_data = self
293 .graph
294 .edge_weight_mut(edge_idx)
295 .ok_or_else(|| GraphError::ComponentsNotFound(vec![component_id.clone()]))?;
296 if edge_data.component_id == *component_id {
298 edge_data.data = Some(data.clone());
299 updated = true;
300 }
301 }
302 }
303
304 if !updated {
305 return Err(GraphError::MissingComponentBetweenTokens(
306 token_in.clone(),
307 token_out.clone(),
308 component_id.clone(),
309 ));
310 }
311
312 Ok(())
313 }
314}
315
316impl<D: Clone + super::EdgeWeightFromSimAndDerived> PetgraphStableDiGraphManager<D> {
317 pub fn update_edge_weights_with_derived(
332 &mut self,
333 market: MarketDataView<'_>,
334 derived: &crate::derived::DerivedData,
335 ) -> usize {
336 let tokens = market.token_registry_ref();
337
338 let updates: Vec<(EdgeIndex, Option<D>)> = self
342 .graph
343 .edge_indices()
344 .filter_map(|edge_idx| {
345 let edge_data = self.graph.edge_weight(edge_idx)?;
346 let component_id = &edge_data.component_id;
347
348 let sim_state = market.get_simulation_state(component_id)?;
349
350 let (source_idx, target_idx) = self.graph.edge_endpoints(edge_idx)?;
351 let source_addr = &self.graph[source_idx];
352 let target_addr = &self.graph[target_idx];
353
354 let token_in = tokens.get(source_addr)?;
355 let token_out = tokens.get(target_addr)?;
356
357 Some((
358 edge_idx,
359 D::from_sim_and_derived(sim_state, component_id, token_in, token_out, derived),
360 ))
361 })
362 .collect();
363
364 let updated = updates
366 .iter()
367 .filter(|(_, w)| w.is_some())
368 .count();
369 for (edge_idx, weight) in updates {
370 if let Some(edge_data) = self.graph.edge_weight_mut(edge_idx) {
371 edge_data.data = weight;
372 }
373 }
374
375 updated
376 }
377}
378
379impl<D: Clone + super::EdgeWeightFromSimAndDerived> super::EdgeWeightUpdaterWithDerived
380 for PetgraphStableDiGraphManager<D>
381{
382 fn update_edge_weights_with_derived(
383 &mut self,
384 market: MarketDataView<'_>,
385 derived: &crate::derived::DerivedData,
386 ) -> usize {
387 self.update_edge_weights_with_derived(market, derived)
388 }
389}
390
391impl<D: Clone> Default for PetgraphStableDiGraphManager<D> {
392 fn default() -> Self {
393 Self::new()
394 }
395}
396
397impl<D: Clone + Send + Sync> GraphManager<StableDiGraph<D>> for PetgraphStableDiGraphManager<D> {
398 fn initialize_graph(&mut self, component_topology: &FxHashMap<ComponentId, Vec<Address>>) {
399 self.graph = StableDiGraph::default();
401 self.edge_map.clear();
402 self.node_map.clear();
403
404 let mut unique_tokens: Vec<Address> = component_topology
409 .values()
410 .flat_map(|v| v.iter())
411 .cloned()
412 .collect::<FxHashSet<_>>()
413 .into_iter()
414 .collect();
415 unique_tokens.sort();
416
417 for token in unique_tokens {
418 let node_idx = self.graph.add_node(token.clone());
419 self.node_map.insert(token, node_idx);
420 }
421
422 let mut sorted_components: Vec<_> = component_topology.iter().collect();
424 sorted_components.sort_by_key(|(id, _)| *id);
425
426 for (comp_id, tokens) in sorted_components {
427 let mut sorted_tokens: Vec<&Address> = tokens.iter().collect();
428 sorted_tokens.sort();
429 let node_indices: Vec<NodeIndex> = sorted_tokens
430 .iter()
431 .map(|token| self.node_map[*token])
432 .collect();
433 self.add_component_edges(comp_id, &node_indices);
434 }
435 }
436
437 fn graph(&self) -> &StableDiGraph<D> {
438 &self.graph
439 }
440}
441
442#[async_trait]
443impl<D: Clone + Send> MarketEventHandler for PetgraphStableDiGraphManager<D> {
444 async fn handle_event(&mut self, event: &MarketEvent) -> Result<(), EventError> {
445 match event {
446 MarketEvent::MarketUpdated { added_components, removed_components, .. } => {
447 let mut errors = Vec::new();
449
450 if let Err(e) = self.add_components(added_components) {
452 errors.push(e);
453 }
454
455 if let Err(e) = self.remove_components(removed_components) {
457 errors.push(e);
458 }
459
460 match errors.len() {
462 0 => Ok(()),
463 _ => Err(EventError::GraphErrors(errors)),
464 }
465 }
466 }
467 }
468}
469
470#[cfg(test)]
471mod tests {
472 use std::str::FromStr;
473
474 use super::*;
475
476 fn addr(s: &str) -> Address {
478 Address::from_str(s).expect("Invalid address hex string")
479 }
480
481 #[test]
482 fn test_initialize_graph_empty() {
483 let mut manager = PetgraphStableDiGraphManager::<()>::new();
484 let topology = FxHashMap::default();
485
486 manager.initialize_graph(&topology);
487
488 let graph = manager.graph();
489 assert_eq!(graph.node_count(), 0);
490 assert_eq!(graph.edge_count(), 0);
491 }
492
493 #[test]
494 fn test_initialize_graph_comprehensive() {
495 let mut manager = PetgraphStableDiGraphManager::<()>::new();
496 let mut topology = FxHashMap::default();
497 let token_a = addr("0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2"); let token_b = addr("0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48"); let token_c = addr("0x6B175474E89094C44Da98b954EedeAC495271d0F"); let token_d = addr("0xdAC17F958D2ee523a2206206994597C13D831ec7"); topology.insert(
504 "component1".to_string(),
505 vec![token_a.clone(), token_b.clone(), token_c.clone()],
506 );
507 topology.insert("component2".to_string(), vec![token_c.clone(), token_d.clone()]);
509
510 manager.initialize_graph(&topology);
511
512 let graph = manager.graph();
513 assert_eq!(graph.node_count(), 4);
515 assert_eq!(graph.edge_count(), 8);
519
520 let node_a = manager.find_node(&token_a).unwrap();
522 let node_b = manager.find_node(&token_b).unwrap();
523 let node_c = manager.find_node(&token_c).unwrap();
524 let node_d = manager.find_node(&token_d).unwrap();
525
526 assert_eq!(
528 graph
529 .edge_weight(graph.find_edge(node_a, node_b).unwrap())
530 .unwrap()
531 .component_id,
532 "component1".to_string()
533 );
534 assert_eq!(
535 graph
536 .edge_weight(graph.find_edge(node_b, node_a).unwrap())
537 .unwrap()
538 .component_id,
539 "component1".to_string()
540 );
541 assert_eq!(
542 graph
543 .edge_weight(graph.find_edge(node_a, node_c).unwrap())
544 .unwrap()
545 .component_id,
546 "component1".to_string()
547 );
548 assert_eq!(
549 graph
550 .edge_weight(graph.find_edge(node_c, node_a).unwrap())
551 .unwrap()
552 .component_id,
553 "component1".to_string()
554 );
555 assert_eq!(
556 graph
557 .edge_weight(graph.find_edge(node_b, node_c).unwrap())
558 .unwrap()
559 .component_id,
560 "component1".to_string()
561 );
562 assert_eq!(
563 graph
564 .edge_weight(graph.find_edge(node_c, node_b).unwrap())
565 .unwrap()
566 .component_id,
567 "component1".to_string()
568 );
569
570 assert_eq!(
572 graph
573 .edge_weight(graph.find_edge(node_c, node_d).unwrap())
574 .unwrap()
575 .component_id,
576 "component2".to_string()
577 );
578 assert_eq!(
579 graph
580 .edge_weight(graph.find_edge(node_d, node_c).unwrap())
581 .unwrap()
582 .component_id,
583 "component2".to_string()
584 );
585 }
586
587 #[test]
588 fn test_initialize_graph_multiple_edges_same_pair() {
589 let mut manager = PetgraphStableDiGraphManager::<()>::new();
590 let mut topology = FxHashMap::default();
591 let token_a = addr("0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2"); let token_b = addr("0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48"); topology.insert("component1".to_string(), vec![token_a.clone(), token_b.clone()]);
596 topology.insert("component2".to_string(), vec![token_a.clone(), token_b.clone()]);
597 topology.insert("component3".to_string(), vec![token_a.clone(), token_b.clone()]);
598
599 manager.initialize_graph(&topology);
600
601 let graph = manager.graph();
602 assert_eq!(graph.node_count(), 2);
604 assert_eq!(graph.edge_count(), 6);
606
607 let node_a = manager.find_node(&token_a).unwrap();
608 let node_b = manager.find_node(&token_b).unwrap();
609
610 let edges: Vec<_> = graph
612 .edges_connecting(node_a, node_b)
613 .collect();
614 assert_eq!(edges.len(), 3);
615
616 let component_ids: Vec<_> = edges
617 .iter()
618 .map(|e| &e.weight().component_id)
619 .collect();
620
621 assert!(component_ids.contains(&&"component1".to_string()));
623 assert!(component_ids.contains(&&"component2".to_string()));
624 assert!(component_ids.contains(&&"component3".to_string()));
625 }
626
627 #[test]
628 fn test_add_components_shared_tokens() {
629 let mut manager = PetgraphStableDiGraphManager::<()>::new();
630 let mut components = FxHashMap::default();
631 let token_a = addr("0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2"); let token_b = addr("0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48"); components.insert("component1".to_string(), vec![token_a.clone(), token_b.clone()]);
636 manager
637 .add_components(&components)
638 .unwrap();
639
640 let initial_node_count = manager.graph().node_count();
641 assert_eq!(initial_node_count, 2);
642
643 components.clear();
645 components.insert("component2".to_string(), vec![token_a.clone(), token_b.clone()]);
646 manager
647 .add_components(&components)
648 .unwrap();
649
650 assert_eq!(manager.graph().node_count(), 2, "Should not create duplicate nodes");
652 }
653
654 #[test]
655 fn test_add_tokenless_components_error() {
656 let mut manager = PetgraphStableDiGraphManager::<()>::new();
657 let mut components = FxHashMap::default();
658 let token_a = addr("0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2"); let token_b = addr("0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48"); components.insert("component1".to_string(), vec![token_a.clone(), token_b.clone()]);
663 components.insert("component2".to_string(), vec![]);
664 components.insert("component3".to_string(), vec![]);
665 let result = manager.add_components(&components);
666
667 assert!(result.is_err());
668 match result.unwrap_err() {
669 GraphError::InvalidComponents(ids) => {
670 assert_eq!(ids.len(), 2);
671 assert!(ids.contains(&"component2".to_string()));
672 assert!(ids.contains(&"component3".to_string()));
673 }
674 _ => panic!("Expected InvalidComponents error"),
675 }
676
677 assert_eq!(manager.graph().node_count(), 2);
679 assert_eq!(manager.graph().edge_count(), 2); }
681
682 #[test]
683 fn test_remove_components_not_found_error() {
684 let mut manager = PetgraphStableDiGraphManager::<()>::new();
685 let mut components = FxHashMap::default();
686 let token_a = addr("0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2"); let token_b = addr("0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48"); components.insert("component1".to_string(), vec![token_a.clone(), token_b.clone()]);
691 components.insert("component2".to_string(), vec![token_a.clone(), token_b.clone()]);
692 manager
693 .add_components(&components)
694 .unwrap();
695
696 let result = manager.remove_components(&[
698 "component1".to_string(),
699 "component3".to_string(),
700 "component4".to_string(),
701 ]);
702
703 assert!(result.is_err());
704 match result.unwrap_err() {
705 GraphError::ComponentsNotFound(ids) => {
706 assert_eq!(ids.len(), 2, "Expected 2 missing components");
707 assert!(ids.contains(&"component3".to_string()));
708 assert!(ids.contains(&"component4".to_string()));
709 }
710 _ => panic!("Expected ComponentsNotFound error"),
711 }
712
713 for edge in manager.graph().edge_indices() {
715 assert_eq!(
716 manager
717 .graph()
718 .edge_weight(edge)
719 .unwrap()
720 .component_id,
721 "component2".to_string()
722 );
723 }
724 }
725
726 #[test]
727 fn test_set_edge_weight_errors() {
728 let mut manager = PetgraphStableDiGraphManager::<()>::new();
729 let mut topology = FxHashMap::default();
730 let token_a = addr("0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2"); let token_b = addr("0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48"); let token_c = addr("0x6B175474E89094C44Da98b954EedeAC495271d0F"); topology.insert("component1".to_string(), vec![token_a.clone(), token_b.clone()]);
736 topology.insert("component2".to_string(), vec![token_b.clone(), token_c.clone()]);
737 manager.initialize_graph(&topology);
738
739 let result =
741 manager.set_edge_weight(&"component3".to_string(), &token_a, &token_b, (), true);
742 assert!(result.is_err());
743 match result.unwrap_err() {
744 GraphError::ComponentsNotFound(ids) => {
745 assert_eq!(ids, vec!["component3".to_string()]);
746 }
747 _ => panic!("Expected ComponentsNotFound error"),
748 }
749
750 let non_existent_token = addr("0x0000000000000000000000000000000000000000");
752 let result = manager.set_edge_weight(
753 &"component1".to_string(),
754 &token_a,
755 &non_existent_token, (),
757 true,
758 );
759 assert!(result.is_err());
760 match result.unwrap_err() {
761 GraphError::TokenNotFound(found_addr) => {
762 assert_eq!(found_addr, non_existent_token);
763 }
764 _ => panic!("Expected TokenNotFound error"),
765 }
766
767 let result = manager.set_edge_weight(
769 &"component1".to_string(),
770 &token_a,
771 &token_c, (),
773 true,
774 );
775 assert!(result.is_err());
776 match result.unwrap_err() {
777 GraphError::MissingComponentBetweenTokens(in_token, out_token, comp_id) => {
778 assert_eq!(in_token, token_a);
779 assert_eq!(out_token, token_c);
780 assert_eq!(comp_id, "component1".to_string());
781 }
782 _ => panic!("Expected MissingComponentBetweenTokens error"),
783 }
784 }
785
786 #[tokio::test]
787 async fn test_handle_event_propagates_errors() {
788 let mut manager = PetgraphStableDiGraphManager::<()>::new();
789
790 use crate::feed::events::{EventError, MarketEvent};
791
792 let event = MarketEvent::MarketUpdated {
794 added_components: FxHashMap::from_iter([("component1".to_string(), vec![])]),
795 removed_components: vec!["component2".to_string()],
796 updated_components: vec![],
797 };
798
799 let result = manager.handle_event(&event).await;
800
801 assert!(result.is_err());
803 match result.unwrap_err() {
804 EventError::GraphErrors(errors) => {
805 assert_eq!(errors.len(), 2);
806 let has_add_error = errors
808 .iter()
809 .any(|e| matches!(e, GraphError::InvalidComponents(_)));
810 let has_remove_error = errors
811 .iter()
812 .any(|e| matches!(e, GraphError::ComponentsNotFound(_)));
813 assert!(has_add_error, "Should have InvalidComponents error");
814 assert!(has_remove_error, "Should have ComponentsNotFound error");
815 }
816 }
817 }
818
819 #[test]
820 fn test_add_components_skips_duplicates() {
821 let mut manager = PetgraphStableDiGraphManager::<()>::new();
822 let token_a = addr("0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2");
823 let token_b = addr("0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48");
824
825 let mut components = FxHashMap::default();
826 components.insert("component1".to_string(), vec![token_a.clone(), token_b.clone()]);
827
828 manager
829 .add_components(&components)
830 .unwrap();
831 let edge_count_after_first = manager.graph().edge_count();
832 assert_eq!(edge_count_after_first, 2); manager
836 .add_components(&components)
837 .unwrap();
838 let edge_count_after_second = manager.graph().edge_count();
839 assert_eq!(
840 edge_count_after_first, edge_count_after_second,
841 "Edge count should not change when re-adding the same component"
842 );
843 }
844}