1#![warn(missing_docs)]
2
3use anyhow::Result;
13use saorsa_gossip_types::{LogPeerId, PeerId};
14use serde::{de::DeserializeOwned, Deserialize, Serialize};
15use std::collections::{BTreeMap, HashMap, HashSet};
16use std::hash::Hash;
17
18#[derive(Debug, Clone, Serialize, Deserialize)]
20pub enum CrdtType {
21 OrSet,
23 LwwRegister,
25 Rga,
27}
28
29pub trait DeltaCrdt: Send + Sync {
31 type Delta: Clone + Serialize + for<'de> Deserialize<'de> + Send;
33
34 fn merge(&mut self, delta: &Self::Delta) -> Result<()>;
36
37 fn delta(&self, since_version: u64) -> Option<Self::Delta>;
42
43 fn version(&self) -> u64;
45}
46
47pub type UniqueTag = (PeerId, u64);
49
50#[derive(Debug, Clone, Serialize, Deserialize)]
55pub struct OrSet<T: Hash + Eq + Clone> {
56 elements: HashMap<T, HashSet<UniqueTag>>,
58 tombstones: HashMap<T, HashSet<UniqueTag>>,
60 version: u64,
62 #[serde(default)]
64 changelog_floor: u64,
65 changelog: HashMap<u64, ChangelogEntry<T>>,
67}
68
69#[derive(Debug, Clone, Serialize)]
75pub struct CanonicalRetainedOrSet<T: Ord> {
76 elements: BTreeMap<T, Vec<UniqueTag>>,
77 tombstones: BTreeMap<T, Vec<UniqueTag>>,
78 version: u64,
79 changelog_floor: u64,
80 changelog: CanonicalChangelog<T>,
81}
82
83type CanonicalChangelog<T> =
84 BTreeMap<u64, (BTreeMap<T, Vec<UniqueTag>>, BTreeMap<T, Vec<UniqueTag>>)>;
85
86type ChangelogEntry<T> = (
88 HashMap<T, HashSet<UniqueTag>>,
89 HashMap<T, HashSet<UniqueTag>>,
90);
91
92impl<T: Hash + Eq + Clone> OrSet<T> {
93 pub fn canonical_retained_wire(&self) -> CanonicalRetainedOrSet<T>
99 where
100 T: Ord,
101 {
102 let canonical_tags = |tags: &HashSet<UniqueTag>| {
103 let mut tags: Vec<_> = tags.iter().copied().collect();
104 tags.sort_unstable_by(|(left_peer, left_sequence), (right_peer, right_sequence)| {
105 left_peer
106 .as_bytes()
107 .cmp(right_peer.as_bytes())
108 .then_with(|| left_sequence.cmp(right_sequence))
109 });
110 tags
111 };
112 CanonicalRetainedOrSet {
113 elements: self
114 .elements
115 .iter()
116 .map(|(element, tags)| (element.clone(), canonical_tags(tags)))
117 .collect(),
118 tombstones: self
119 .tombstones
120 .iter()
121 .map(|(element, tags)| (element.clone(), canonical_tags(tags)))
122 .collect(),
123 version: 0,
124 changelog_floor: 0,
125 changelog: BTreeMap::new(),
126 }
127 }
128 pub fn new() -> Self {
130 Self {
131 elements: HashMap::new(),
132 tombstones: HashMap::new(),
133 version: 0,
134 changelog_floor: 0,
135 changelog: HashMap::new(),
136 }
137 }
138
139 pub fn add(&mut self, element: T, tag: UniqueTag) -> Result<()> {
144 self.elements
146 .entry(element.clone())
147 .or_default()
148 .insert(tag);
149
150 if let Some(ts) = self.tombstones.get_mut(&element) {
152 ts.remove(&tag);
153 if ts.is_empty() {
154 self.tombstones.remove(&element);
155 }
156 }
157
158 self.version += 1;
160 let changelog_entry = self
161 .changelog
162 .entry(self.version)
163 .or_insert((HashMap::new(), HashMap::new()));
164 changelog_entry.0.entry(element).or_default().insert(tag);
165
166 Ok(())
167 }
168
169 pub fn remove(&mut self, element: &T) -> Result<()> {
174 if let Some(tags) = self.elements.get(element).cloned() {
175 for tag in &tags {
177 self.tombstones
178 .entry(element.clone())
179 .or_default()
180 .insert(*tag);
181 }
182
183 self.elements.remove(element);
185
186 self.version += 1;
188 let changelog_entry = self
189 .changelog
190 .entry(self.version)
191 .or_insert((HashMap::new(), HashMap::new()));
192 changelog_entry
193 .1
194 .entry(element.clone())
195 .or_default()
196 .extend(tags);
197 }
198
199 Ok(())
200 }
201
202 pub fn contains(&self, element: &T) -> bool {
204 self.elements.contains_key(element)
205 }
206
207 pub fn elements(&self) -> Vec<&T> {
209 self.elements.keys().collect()
210 }
211
212 pub fn len(&self) -> usize {
214 self.elements.len()
215 }
216
217 pub fn is_empty(&self) -> bool {
219 self.elements.is_empty()
220 }
221
222 fn record_changelog_entry(
223 &mut self,
224 added: HashMap<T, HashSet<UniqueTag>>,
225 removed: HashMap<T, HashSet<UniqueTag>>,
226 ) {
227 if added.is_empty() && removed.is_empty() {
228 return;
229 }
230
231 self.version += 1;
232 self.changelog.insert(self.version, (added, removed));
233 }
234
235 fn apply_delta_parts(
236 &mut self,
237 incoming_added: &HashMap<T, HashSet<UniqueTag>>,
238 incoming_removed: &HashMap<T, HashSet<UniqueTag>>,
239 ) {
240 let mut added: HashMap<T, HashSet<UniqueTag>> = HashMap::new();
241 let mut removed: HashMap<T, HashSet<UniqueTag>> = HashMap::new();
242
243 for (elem, tags) in incoming_added {
245 let tombstoned_tags = self.tombstones.get(elem);
246 let mut live_tags = tags
247 .iter()
248 .filter(|tag| !tombstoned_tags.is_some_and(|tombstones| tombstones.contains(*tag)))
249 .copied()
250 .peekable();
251
252 if live_tags.peek().is_none() {
253 continue;
254 }
255
256 let our_tags = self.elements.entry(elem.clone()).or_default();
257 for tag in live_tags {
258 if our_tags.insert(tag) {
259 added.entry(elem.clone()).or_default().insert(tag);
260 }
261 }
262 }
263
264 for (elem, tags) in incoming_removed {
266 let mut new_tombstones = HashSet::new();
267 {
268 let our_tombstones = self.tombstones.entry(elem.clone()).or_default();
269 for tag in tags {
270 if our_tombstones.insert(*tag) {
271 new_tombstones.insert(*tag);
272 }
273 }
274 }
275
276 if !new_tombstones.is_empty() {
277 removed.insert(elem.clone(), new_tombstones);
278 }
279
280 let remove_element = if let Some(our_tags) = self.elements.get_mut(elem) {
282 if let Some(our_tombstones) = self.tombstones.get(elem) {
283 our_tags.retain(|tag| !our_tombstones.contains(tag));
284 }
285 our_tags.is_empty()
286 } else {
287 false
288 };
289
290 if remove_element {
291 self.elements.remove(elem);
292 }
293 }
294
295 self.record_changelog_entry(added, removed);
296 }
297
298 pub fn merge_state(&mut self, other: &OrSet<T>) -> Result<()> {
303 self.apply_delta_parts(&other.elements, &other.tombstones);
304
305 Ok(())
306 }
307
308 pub fn compact(&mut self, keep_versions: u64) {
310 if self.version > keep_versions {
311 let min_version = self.version - keep_versions;
312 self.changelog.retain(|v, _| *v > min_version);
313 self.changelog_floor = self.changelog_floor.max(min_version);
314 }
315 }
316
317 #[must_use]
325 pub fn max_retained_sequence_for_peer(&self, peer: &PeerId) -> Option<u64> {
326 self.elements
327 .values()
328 .chain(self.tombstones.values())
329 .flat_map(HashSet::iter)
330 .filter(|(tag_peer, _)| tag_peer == peer)
331 .map(|(_, sequence)| *sequence)
332 .max()
333 }
334}
335
336impl<T: Hash + Eq + Clone> Default for OrSet<T> {
337 fn default() -> Self {
338 Self::new()
339 }
340}
341
342#[derive(Debug, Clone, Serialize, Deserialize)]
344#[serde(bound = "T: Serialize + DeserializeOwned")]
345pub struct OrSetDelta<T: Hash + Eq + Clone + Serialize + DeserializeOwned> {
346 pub added: HashMap<T, HashSet<UniqueTag>>,
348 pub removed: HashMap<T, HashSet<UniqueTag>>,
350 pub version: u64,
352}
353
354impl<T: Hash + Eq + Clone + Send + Sync + Serialize + DeserializeOwned> DeltaCrdt for OrSet<T> {
355 type Delta = OrSetDelta<T>;
356
357 fn merge(&mut self, delta: &Self::Delta) -> Result<()> {
358 self.apply_delta_parts(&delta.added, &delta.removed);
359
360 Ok(())
361 }
362
363 fn delta(&self, since_version: u64) -> Option<Self::Delta> {
364 if since_version >= self.version {
365 return None;
366 }
367 if since_version < self.changelog_floor {
368 return None;
369 }
370
371 let mut added: HashMap<T, HashSet<UniqueTag>> = HashMap::new();
372 let mut removed: HashMap<T, HashSet<UniqueTag>> = HashMap::new();
373
374 for version in (since_version + 1)..=self.version {
376 let (adds, removes) = self.changelog.get(&version)?;
377 for (elem, tags) in adds {
378 added.entry(elem.clone()).or_default().extend(tags);
379 }
380 for (elem, tags) in removes {
381 removed.entry(elem.clone()).or_default().extend(tags);
382 }
383 }
384
385 Some(OrSetDelta {
386 added,
387 removed,
388 version: self.version,
389 })
390 }
391
392 fn version(&self) -> u64 {
393 self.version
394 }
395}
396
397#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
399pub struct VectorClock {
400 clocks: HashMap<PeerId, u64>,
401}
402
403impl VectorClock {
404 pub fn new() -> Self {
406 Self {
407 clocks: HashMap::new(),
408 }
409 }
410
411 pub fn increment(&mut self, peer: PeerId) {
413 *self.clocks.entry(peer).or_insert(0) += 1;
414 }
415
416 pub fn get(&self, peer: &PeerId) -> u64 {
418 self.clocks.get(peer).copied().unwrap_or(0)
419 }
420
421 pub fn merge(&mut self, other: &VectorClock) {
423 for (peer, &time) in &other.clocks {
424 let our_time = self.clocks.entry(*peer).or_insert(0);
425 *our_time = (*our_time).max(time);
426 }
427 }
428
429 pub fn happens_before(&self, other: &VectorClock) -> bool {
431 let mut strictly_less = false;
432
433 for (peer, &our_time) in &self.clocks {
435 let other_time = other.get(peer);
436 if our_time > other_time {
437 return false; }
439 if our_time < other_time {
440 strictly_less = true;
441 }
442 }
443
444 for peer in other.clocks.keys() {
446 if !self.clocks.contains_key(peer) && other.get(peer) > 0 {
447 strictly_less = true;
448 }
449 }
450
451 strictly_less
452 }
453
454 pub fn concurrent(&self, other: &VectorClock) -> bool {
456 !self.happens_before(other) && !other.happens_before(self) && self != other
457 }
458}
459
460impl Default for VectorClock {
461 fn default() -> Self {
462 Self::new()
463 }
464}
465
466#[derive(Debug, Clone, Serialize, Deserialize)]
468pub struct LwwRegister<T: Clone> {
469 value: T,
470 clock: VectorClock,
471}
472
473impl<T: Clone> LwwRegister<T> {
474 pub fn new(value: T) -> Self {
476 Self {
477 value,
478 clock: VectorClock::new(),
479 }
480 }
481
482 pub fn set(&mut self, value: T, peer: PeerId) {
484 self.clock.increment(peer);
485 self.value = value;
486 }
487
488 pub fn get(&self) -> &T {
490 &self.value
491 }
492
493 pub fn clock(&self) -> &VectorClock {
495 &self.clock
496 }
497
498 pub fn merge(&mut self, other: &LwwRegister<T>) {
500 if other.clock.happens_before(&self.clock) {
501 self.clock.merge(&other.clock);
503 } else if self.clock.happens_before(&other.clock) {
504 self.value = other.value.clone();
506 self.clock = other.clock.clone();
507 } else if self.clock.concurrent(&other.clock) {
508 let our_hash = self.hash_clock();
510 let their_hash = other.hash_clock();
511
512 if their_hash < our_hash {
513 self.value = other.value.clone();
514 }
515 self.clock.merge(&other.clock);
516 }
517 }
518
519 fn hash_clock(&self) -> u64 {
520 use std::collections::hash_map::DefaultHasher;
521 use std::hash::{Hash, Hasher};
522
523 let mut hasher = DefaultHasher::new();
524 let mut sorted: Vec<_> = self.clock.clocks.iter().collect();
526 sorted.sort_by_key(|(peer, _)| peer.as_bytes());
527 sorted.hash(&mut hasher);
528 hasher.finish()
529 }
530}
531
532pub struct AntiEntropyManager<T>
537where
538 T: DeltaCrdt + Clone + Send + Sync + 'static,
539{
540 crdt: std::sync::Arc<tokio::sync::RwLock<T>>,
542 peer_versions: std::sync::Arc<tokio::sync::RwLock<HashMap<PeerId, u64>>>,
544 interval_secs: u64,
546 task_handle: std::sync::Arc<tokio::sync::RwLock<Option<tokio::task::JoinHandle<()>>>>,
548 shutdown_tx: std::sync::Arc<tokio::sync::RwLock<Option<tokio::sync::mpsc::Sender<()>>>>,
550}
551
552impl<T> AntiEntropyManager<T>
553where
554 T: DeltaCrdt + Clone + Send + Sync + 'static,
555{
556 pub fn new(crdt: std::sync::Arc<tokio::sync::RwLock<T>>, interval_secs: u64) -> Self {
558 Self {
559 crdt,
560 peer_versions: std::sync::Arc::new(tokio::sync::RwLock::new(HashMap::new())),
561 interval_secs,
562 task_handle: std::sync::Arc::new(tokio::sync::RwLock::new(None)),
563 shutdown_tx: std::sync::Arc::new(tokio::sync::RwLock::new(None)),
564 }
565 }
566
567 pub async fn start<F>(&self, sync_callback: F) -> Result<()>
572 where
573 F: Fn(
574 PeerId,
575 T::Delta,
576 )
577 -> std::pin::Pin<Box<dyn std::future::Future<Output = Result<()>> + Send>>
578 + Send
579 + Sync
580 + 'static,
581 {
582 let (shutdown_tx, mut shutdown_rx) = tokio::sync::mpsc::channel::<()>(1);
583
584 {
586 let mut tx = self.shutdown_tx.write().await;
587 *tx = Some(shutdown_tx);
588 }
589
590 let crdt = self.crdt.clone();
591 let peer_versions = self.peer_versions.clone();
592 let interval_secs = self.interval_secs;
593 let sync_callback = std::sync::Arc::new(sync_callback);
594
595 let task = tokio::spawn(async move {
596 let mut interval =
597 tokio::time::interval(tokio::time::Duration::from_secs(interval_secs));
598
599 loop {
600 tokio::select! {
601 _ = interval.tick() => {
602 let peers_to_sync: Vec<(PeerId, u64)> = {
604 let versions = peer_versions.read().await;
605 versions.iter().map(|(p, v)| (*p, *v)).collect()
606 };
607
608 for (peer_id, peer_version) in peers_to_sync {
610 let delta = {
611 let crdt_lock = crdt.read().await;
612 crdt_lock
613 .delta(peer_version)
614 .map(|delta| (delta, crdt_lock.version()))
615 };
616
617 if let Some((delta, sent_version)) = delta {
618 let callback = sync_callback.clone();
619 if let Err(e) = callback(peer_id, delta).await {
620 tracing::warn!("Failed to sync with peer {}: {}", LogPeerId::from(peer_id), e);
621 } else {
622 let mut versions = peer_versions.write().await;
623 versions.insert(peer_id, sent_version);
624 }
625 }
626 }
627 }
628 _ = shutdown_rx.recv() => {
629 tracing::info!("Anti-entropy manager shutting down");
630 break;
631 }
632 }
633 }
634 });
635
636 {
638 let mut handle = self.task_handle.write().await;
639 *handle = Some(task);
640 }
641
642 Ok(())
643 }
644
645 pub async fn stop(&self) -> Result<()> {
647 if let Some(tx) = self.shutdown_tx.write().await.take() {
649 let _ = tx.send(()).await;
650 }
651
652 if let Some(handle) = self.task_handle.write().await.take() {
654 match tokio::time::timeout(tokio::time::Duration::from_secs(5), handle).await {
655 Ok(Ok(())) => Ok(()),
656 Ok(Err(e)) => Err(anyhow::anyhow!("Task panicked: {}", e)),
657 Err(_) => Err(anyhow::anyhow!("Shutdown timeout")),
658 }
659 } else {
660 Ok(())
661 }
662 }
663
664 pub async fn update_peer_version(&self, peer_id: PeerId, version: u64) {
668 let mut versions = self.peer_versions.write().await;
669 versions.insert(peer_id, version);
670 }
671
672 pub async fn apply_delta(
677 &self,
678 peer_id: PeerId,
679 delta: &T::Delta,
680 _version: u64,
681 ) -> Result<()> {
682 {
684 let mut crdt = self.crdt.write().await;
685 crdt.merge(delta)?;
686 }
687
688 let mut versions = self.peer_versions.write().await;
689 versions.entry(peer_id).or_insert(0);
690
691 Ok(())
692 }
693
694 pub async fn add_peer(&self, peer_id: PeerId) {
696 let mut versions = self.peer_versions.write().await;
697 versions.entry(peer_id).or_insert(0);
698 }
699
700 pub async fn remove_peer(&self, peer_id: &PeerId) {
702 let mut versions = self.peer_versions.write().await;
703 versions.remove(peer_id);
704 }
705
706 pub async fn get_crdt(&self) -> T {
708 self.crdt.read().await.clone()
709 }
710}
711
712#[cfg(test)]
713#[allow(clippy::unwrap_used, clippy::expect_used)]
714mod tests {
715 use super::*;
716
717 fn peer(n: u8) -> PeerId {
718 PeerId::new([n; 32])
719 }
720
721 #[test]
724 fn test_or_set_basic_add_remove() {
725 let mut set = OrSet::new();
726 let p1 = peer(1);
727
728 set.add("alice".to_string(), (p1, 1)).ok();
729 assert!(set.contains(&"alice".to_string()));
730 assert_eq!(set.len(), 1);
731
732 set.remove(&"alice".to_string()).ok();
733 assert!(!set.contains(&"alice".to_string()));
734 assert_eq!(set.len(), 0);
735 }
736
737 #[test]
738 fn test_or_set_concurrent_add() {
739 let mut set = OrSet::new();
740 let p1 = peer(1);
741 let p2 = peer(2);
742
743 set.add("alice".to_string(), (p1, 1)).ok();
745 set.add("alice".to_string(), (p2, 1)).ok();
746
747 assert!(set.contains(&"alice".to_string()));
748
749 assert_eq!(set.elements.get("alice").unwrap().len(), 2);
751 }
752
753 #[test]
754 fn test_or_set_add_remove_add() {
755 let mut set = OrSet::new();
756 let p1 = peer(1);
757
758 set.add("alice".to_string(), (p1, 1)).ok();
760 set.remove(&"alice".to_string()).ok();
761 set.add("alice".to_string(), (p1, 2)).ok(); assert!(set.contains(&"alice".to_string()));
764 }
765
766 #[test]
767 fn test_or_set_merge_commutativity() {
768 let mut set1 = OrSet::new();
769 let mut set2 = OrSet::new();
770 let p1 = peer(1);
771 let p2 = peer(2);
772
773 set1.add("alice".to_string(), (p1, 1)).ok();
774 set2.add("bob".to_string(), (p2, 1)).ok();
775
776 let mut merged_a = set1.clone();
777 merged_a.merge_state(&set2).ok();
778
779 let mut merged_b = set2.clone();
780 merged_b.merge_state(&set1).ok();
781
782 assert_eq!(merged_a.len(), merged_b.len());
784 assert!(merged_a.contains(&"alice".to_string()));
785 assert!(merged_a.contains(&"bob".to_string()));
786 assert!(merged_b.contains(&"alice".to_string()));
787 assert!(merged_b.contains(&"bob".to_string()));
788 }
789
790 #[test]
791 fn test_or_set_merge_idempotence() {
792 let mut set1 = OrSet::new();
793 let mut set2 = OrSet::new();
794 let p1 = peer(1);
795
796 set1.add("alice".to_string(), (p1, 1)).ok();
797 set2.add("bob".to_string(), (p1, 2)).ok();
798
799 set1.merge_state(&set2).ok();
800 let len_after_first = set1.len();
801
802 set1.merge_state(&set2).ok();
803 let len_after_second = set1.len();
804
805 assert_eq!(len_after_first, len_after_second);
807 }
808
809 #[test]
810 fn test_or_set_delta_generation() {
811 let mut set = OrSet::new();
812 let p1 = peer(1);
813
814 let v0 = set.version();
815 set.add("alice".to_string(), (p1, 1)).ok();
816 set.add("bob".to_string(), (p1, 2)).ok();
817
818 let delta = set.delta(v0).expect("should have delta");
819 assert_eq!(delta.added.len(), 2);
820 assert!(delta.added.contains_key("alice"));
821 assert!(delta.added.contains_key("bob"));
822 }
823
824 #[test]
825 fn test_or_set_delta_merge() {
826 let mut set1 = OrSet::new();
827 let mut set2 = OrSet::new();
828 let p1 = peer(1);
829 let p2 = peer(2);
830
831 set1.add("alice".to_string(), (p1, 1)).ok();
832
833 let v0 = set2.version();
834 set2.add("bob".to_string(), (p2, 1)).ok();
835 set2.add("charlie".to_string(), (p2, 2)).ok();
836
837 let delta = set2.delta(v0).expect("should have delta");
839 set1.merge(&delta).ok();
840
841 assert!(set1.contains(&"alice".to_string()));
842 assert!(set1.contains(&"bob".to_string()));
843 assert!(set1.contains(&"charlie".to_string()));
844 }
845
846 #[test]
847 fn test_or_set_delta_replay_does_not_resurrect_removed_tag() {
848 let mut source = OrSet::new();
849 let mut replica = OrSet::new();
850 let p1 = peer(1);
851 let element = "alice".to_string();
852
853 let v0 = source.version();
854 source.add(element.clone(), (p1, 1)).expect("add element");
855 let add_delta = source.delta(v0).expect("should have add delta");
856
857 replica.merge(&add_delta).expect("apply add delta");
858 assert!(replica.contains(&element));
859
860 replica.remove(&element).expect("remove element");
861 assert!(!replica.contains(&element));
862
863 replica.merge(&add_delta).expect("replay stale add delta");
864 assert!(!replica.contains(&element));
865 assert_eq!(replica.len(), 0);
866 }
867
868 #[test]
869 fn test_or_set_delta_merge_is_available_for_onward_delta() {
870 let mut relay = OrSet::new();
871 let mut source = OrSet::new();
872 let p2 = peer(2);
873
874 source.add("from-source".to_string(), (p2, 1)).ok();
875 let source_delta = source.delta(0).expect("should have source delta");
876
877 relay.merge(&source_delta).ok();
878
879 let onward_delta = relay.delta(0).expect("should have onward delta");
880 assert!(onward_delta
881 .added
882 .get("from-source")
883 .expect("forwarded add should be present")
884 .contains(&(p2, 1)));
885 }
886
887 #[test]
890 fn test_vector_clock_increment() {
891 let mut clock = VectorClock::new();
892 let p1 = peer(1);
893
894 assert_eq!(clock.get(&p1), 0);
895 clock.increment(p1);
896 assert_eq!(clock.get(&p1), 1);
897 clock.increment(p1);
898 assert_eq!(clock.get(&p1), 2);
899 }
900
901 #[test]
902 fn test_vector_clock_merge() {
903 let mut clock1 = VectorClock::new();
904 let mut clock2 = VectorClock::new();
905 let p1 = peer(1);
906 let p2 = peer(2);
907
908 clock1.increment(p1);
909 clock1.increment(p1);
910
911 clock2.increment(p2);
912 clock2.increment(p2);
913 clock2.increment(p2);
914
915 clock1.merge(&clock2);
916
917 assert_eq!(clock1.get(&p1), 2);
918 assert_eq!(clock1.get(&p2), 3);
919 }
920
921 #[test]
922 fn test_vector_clock_happens_before() {
923 let mut clock1 = VectorClock::new();
924 let p1 = peer(1);
925
926 clock1.increment(p1);
927 let mut clock2 = clock1.clone();
928 clock2.increment(p1);
929
930 assert!(clock1.happens_before(&clock2));
931 assert!(!clock2.happens_before(&clock1));
932 }
933
934 #[test]
935 fn test_vector_clock_concurrent() {
936 let mut clock1 = VectorClock::new();
937 let mut clock2 = VectorClock::new();
938 let p1 = peer(1);
939 let p2 = peer(2);
940
941 clock1.increment(p1);
942 clock2.increment(p2);
943
944 assert!(clock1.concurrent(&clock2));
945 assert!(clock2.concurrent(&clock1));
946 }
947
948 #[test]
951 fn test_lww_register_basic() {
952 let mut reg = LwwRegister::new(42);
953 let p1 = peer(1);
954
955 assert_eq!(*reg.get(), 42);
956
957 reg.set(100, p1);
958 assert_eq!(*reg.get(), 100);
959 }
960
961 #[test]
962 fn test_lww_register_merge_causality() {
963 let mut reg1 = LwwRegister::new(0);
964 let p1 = peer(1);
965 let p2 = peer(2);
966
967 reg1.set(10, p1);
968 let mut reg2 = reg1.clone();
969 reg2.set(20, p2);
970
971 reg1.merge(®2);
973 assert_eq!(*reg1.get(), 20);
974 }
975
976 #[test]
977 fn test_lww_register_concurrent_merge() {
978 let mut reg1 = LwwRegister::new(0);
979 let mut reg2 = LwwRegister::new(0);
980 let p1 = peer(1);
981 let p2 = peer(2);
982
983 reg1.set(10, p1);
984 reg2.set(20, p2);
985
986 let mut merged1 = reg1.clone();
988 merged1.merge(®2);
989
990 let mut merged2 = reg2.clone();
991 merged2.merge(®1);
992
993 assert_eq!(merged1.get(), merged2.get());
995 }
996
997 #[test]
998 fn test_or_set_compact() {
999 let mut set = OrSet::new();
1000 let p1 = peer(1);
1001
1002 for i in 0..100 {
1004 set.add(format!("elem-{}", i), (p1, i)).ok();
1005 }
1006
1007 let changelog_before = set.changelog.len();
1008 set.compact(10);
1009 let changelog_after = set.changelog.len();
1010
1011 assert!(changelog_after <= 10);
1012 assert!(changelog_after < changelog_before);
1013 }
1014
1015 #[test]
1016 fn test_or_set_compacted_delta_before_floor_is_unavailable() {
1017 let mut set = OrSet::new();
1018 let p1 = peer(1);
1019
1020 set.add("old".to_string(), (p1, 1)).ok();
1021 set.add("new".to_string(), (p1, 2)).ok();
1022
1023 set.compact(1);
1024
1025 assert!(
1026 set.delta(0).is_none(),
1027 "compacted history must not produce a partial current delta"
1028 );
1029
1030 let delta = set.delta(1).expect("retained history should produce delta");
1031 assert!(!delta.added.contains_key("old"));
1032 assert!(delta.added.contains_key("new"));
1033 assert_eq!(delta.version, set.version());
1034 }
1035
1036 #[test]
1037 fn test_max_retained_sequence_empty_other_peer_and_zero() {
1038 let mut set = OrSet::new();
1039 let local = peer(1);
1040 let foreign = peer(2);
1041
1042 assert_eq!(set.max_retained_sequence_for_peer(&local), None);
1043 set.add("foreign".to_string(), (foreign, 7)).unwrap();
1044 assert_eq!(set.max_retained_sequence_for_peer(&local), None);
1045 set.add("zero".to_string(), (local, 0)).unwrap();
1046 assert_eq!(set.max_retained_sequence_for_peer(&local), Some(0));
1047 }
1048
1049 #[test]
1050 fn test_max_retained_sequence_is_peer_scoped_and_order_independent() {
1051 let local = peer(1);
1052 let foreign = peer(2);
1053 let mut first = OrSet::new();
1054 first.add("local-high".to_string(), (local, 100)).unwrap();
1055 first
1056 .add("foreign-max".to_string(), (foreign, u64::MAX))
1057 .unwrap();
1058 first.add("local-low".to_string(), (local, 3)).unwrap();
1059
1060 let mut second = OrSet::new();
1061 second.add("local-low".to_string(), (local, 3)).unwrap();
1062 second
1063 .add("foreign-max".to_string(), (foreign, u64::MAX))
1064 .unwrap();
1065 second.add("local-high".to_string(), (local, 100)).unwrap();
1066
1067 assert_eq!(first.max_retained_sequence_for_peer(&local), Some(100));
1068 assert_eq!(second.max_retained_sequence_for_peer(&local), Some(100));
1069 }
1070
1071 #[test]
1072 fn test_max_retained_sequence_survives_remove_and_compaction() {
1073 let local = peer(1);
1074 let mut set = OrSet::new();
1075 set.add("removed".to_string(), (local, 100)).unwrap();
1076 set.remove(&"removed".to_string()).unwrap();
1077 assert!(!set.contains(&"removed".to_string()));
1078 assert_eq!(set.max_retained_sequence_for_peer(&local), Some(100));
1079
1080 set.compact(0);
1081 assert!(set.delta(0).is_none());
1082 assert_eq!(set.max_retained_sequence_for_peer(&local), Some(100));
1083 }
1084
1085 #[test]
1086 fn test_max_retained_sequence_merges_live_and_removed_tags() {
1087 let local = peer(1);
1088 let mut removed_source = OrSet::new();
1089 removed_source
1090 .add("removed".to_string(), (local, 9))
1091 .unwrap();
1092 removed_source.remove(&"removed".to_string()).unwrap();
1093
1094 let mut live_source = OrSet::new();
1095 live_source.add("live".to_string(), (local, 12)).unwrap();
1096
1097 let mut removed_then_live = removed_source.clone();
1098 removed_then_live.merge_state(&live_source).unwrap();
1099 let mut live_then_removed = live_source.clone();
1100 live_then_removed.merge_state(&removed_source).unwrap();
1101
1102 assert_eq!(
1103 removed_then_live.max_retained_sequence_for_peer(&local),
1104 Some(12)
1105 );
1106 assert_eq!(
1107 live_then_removed.max_retained_sequence_for_peer(&local),
1108 Some(12)
1109 );
1110
1111 let mut lower = OrSet::new();
1112 lower.add("lower".to_string(), (local, 4)).unwrap();
1113 removed_then_live.merge_state(&lower).unwrap();
1114 assert_eq!(
1115 removed_then_live.max_retained_sequence_for_peer(&local),
1116 Some(12)
1117 );
1118 }
1119
1120 #[test]
1121 fn test_max_retained_sequence_survives_serde_after_remove_and_compact() {
1122 let local = peer(1);
1123 let foreign = peer(2);
1124 let mut set = OrSet::new();
1125 set.add("removed".to_string(), (local, 44)).unwrap();
1126 set.remove(&"removed".to_string()).unwrap();
1127 set.add("foreign".to_string(), (foreign, 55)).unwrap();
1128 set.compact(0);
1129
1130 let encoded = postcard::to_stdvec(&set).unwrap();
1131 let restored: OrSet<String> = postcard::from_bytes(&encoded).unwrap();
1132 assert_eq!(restored.max_retained_sequence_for_peer(&local), Some(44));
1133 assert_eq!(restored.max_retained_sequence_for_peer(&foreign), Some(55));
1134 }
1135
1136 #[test]
1137 fn test_max_retained_sequence_preserves_max_without_foreign_poisoning() {
1138 let local = peer(1);
1139 let foreign = peer(2);
1140 let mut set = OrSet::new();
1141 set.add("local-max".to_string(), (local, u64::MAX)).unwrap();
1142 set.add("foreign-max".to_string(), (foreign, u64::MAX))
1143 .unwrap();
1144
1145 assert_eq!(set.max_retained_sequence_for_peer(&local), Some(u64::MAX));
1146 assert_eq!(set.max_retained_sequence_for_peer(&foreign), Some(u64::MAX));
1147 }
1148
1149 #[tokio::test]
1152 async fn test_anti_entropy_manager_creation() {
1153 let crdt = std::sync::Arc::new(tokio::sync::RwLock::new(OrSet::<String>::new()));
1154 let manager = AntiEntropyManager::new(crdt, 1);
1155
1156 let state = manager.get_crdt().await;
1158 assert_eq!(state.len(), 0);
1159 }
1160
1161 #[tokio::test]
1162 async fn test_anti_entropy_add_remove_peer() {
1163 let crdt = std::sync::Arc::new(tokio::sync::RwLock::new(OrSet::<String>::new()));
1164 let manager = AntiEntropyManager::new(crdt, 1);
1165
1166 let p1 = peer(1);
1167 let p2 = peer(2);
1168
1169 manager.add_peer(p1).await;
1171 manager.add_peer(p2).await;
1172
1173 manager.update_peer_version(p1, 5).await;
1175 manager.update_peer_version(p2, 10).await;
1176
1177 let versions = manager.peer_versions.read().await;
1179 assert_eq!(versions.get(&p1), Some(&5));
1180 assert_eq!(versions.get(&p2), Some(&10));
1181
1182 drop(versions);
1184 manager.remove_peer(&p1).await;
1185
1186 let versions = manager.peer_versions.read().await;
1187 assert!(!versions.contains_key(&p1));
1188 assert_eq!(versions.get(&p2), Some(&10));
1189 }
1190
1191 #[tokio::test]
1192 async fn test_anti_entropy_apply_delta() {
1193 let crdt = std::sync::Arc::new(tokio::sync::RwLock::new(OrSet::<String>::new()));
1194 let manager = AntiEntropyManager::new(crdt.clone(), 1);
1195
1196 let p1 = peer(1);
1197
1198 let mut temp_set = OrSet::new();
1200 temp_set.add("alice".to_string(), (p1, 1)).ok();
1201 temp_set.add("bob".to_string(), (p1, 2)).ok();
1202
1203 let delta = temp_set.delta(0).expect("should have delta");
1204
1205 manager.apply_delta(p1, &delta, 2).await.ok();
1207
1208 let state = manager.get_crdt().await;
1210 assert!(state.contains(&"alice".to_string()));
1211 assert!(state.contains(&"bob".to_string()));
1212
1213 let versions = manager.peer_versions.read().await;
1215 assert_eq!(versions.get(&p1), Some(&0));
1216 }
1217
1218 #[tokio::test]
1219 async fn test_apply_high_remote_version_does_not_ack_local_delta() {
1220 let crdt = std::sync::Arc::new(tokio::sync::RwLock::new(OrSet::<String>::new()));
1221 let manager = AntiEntropyManager::new(crdt.clone(), 1);
1222
1223 let local_peer = peer(1);
1224 let remote_peer = peer(2);
1225 manager.add_peer(remote_peer).await;
1226
1227 {
1228 let mut crdt_lock = crdt.write().await;
1229 crdt_lock.add("local".to_string(), (local_peer, 1)).ok();
1230 }
1231
1232 let mut remote = OrSet::new();
1233 for seq in 1..=10 {
1234 remote
1235 .add(format!("remote-{}", seq), (remote_peer, seq))
1236 .ok();
1237 }
1238 let remote_delta = remote.delta(0).expect("should have remote delta");
1239
1240 manager
1241 .apply_delta(remote_peer, &remote_delta, remote_delta.version)
1242 .await
1243 .ok();
1244
1245 let peer_version = {
1246 let versions = manager.peer_versions.read().await;
1247 *versions
1248 .get(&remote_peer)
1249 .expect("remote peer should be tracked")
1250 };
1251 assert_eq!(peer_version, 0);
1252
1253 let delta_to_remote = {
1254 let crdt_lock = crdt.read().await;
1255 crdt_lock
1256 .delta(peer_version)
1257 .expect("local add should still be sent")
1258 };
1259
1260 assert!(delta_to_remote
1261 .added
1262 .get("local")
1263 .expect("local add should be included")
1264 .contains(&(local_peer, 1)));
1265 }
1266
1267 #[tokio::test]
1268 async fn test_anti_entropy_periodic_sync() {
1269 use std::sync::atomic::{AtomicUsize, Ordering};
1270
1271 let crdt = std::sync::Arc::new(tokio::sync::RwLock::new(OrSet::<String>::new()));
1272 let manager = AntiEntropyManager::new(crdt.clone(), 1); let p1 = peer(1);
1275 manager.add_peer(p1).await;
1276
1277 {
1279 let mut crdt_lock = crdt.write().await;
1280 crdt_lock.add("test".to_string(), (p1, 1)).ok();
1281 }
1282
1283 let sync_count = std::sync::Arc::new(AtomicUsize::new(0));
1285 let sync_count_clone = sync_count.clone();
1286
1287 manager
1289 .start(move |peer_id, delta: <OrSet<String> as DeltaCrdt>::Delta| {
1290 let count = sync_count_clone.clone();
1291 Box::pin(async move {
1292 count.fetch_add(1, Ordering::SeqCst);
1293 assert_eq!(peer_id, peer(1));
1295 assert!(delta.added.contains_key("test"));
1297 Ok(())
1298 })
1299 })
1300 .await
1301 .ok();
1302
1303 tokio::time::sleep(tokio::time::Duration::from_millis(2100)).await;
1305
1306 manager.stop().await.ok();
1308
1309 assert!(sync_count.load(Ordering::SeqCst) >= 1);
1311 let versions = manager.peer_versions.read().await;
1312 assert_eq!(versions.get(&p1), Some(&1));
1313 }
1314
1315 #[tokio::test]
1316 async fn test_anti_entropy_shutdown() {
1317 let crdt = std::sync::Arc::new(tokio::sync::RwLock::new(OrSet::<String>::new()));
1318 let manager = AntiEntropyManager::new(crdt, 1);
1319
1320 manager
1322 .start(|_peer_id, _delta: <OrSet<String> as DeltaCrdt>::Delta| {
1323 Box::pin(async { Ok(()) })
1324 })
1325 .await
1326 .ok();
1327
1328 let result = manager.stop().await;
1330 assert!(result.is_ok());
1331
1332 let result = manager.stop().await;
1334 assert!(result.is_ok());
1335 }
1336
1337 #[tokio::test]
1338 async fn test_anti_entropy_multi_peer_sync() {
1339 let crdt = std::sync::Arc::new(tokio::sync::RwLock::new(OrSet::<String>::new()));
1340 let manager = AntiEntropyManager::new(crdt.clone(), 1);
1341
1342 let p1 = peer(1);
1343 let p2 = peer(2);
1344 let p3 = peer(3);
1345
1346 manager.add_peer(p1).await;
1347 manager.add_peer(p2).await;
1348 manager.add_peer(p3).await;
1349
1350 {
1352 let mut crdt_lock = crdt.write().await;
1353 crdt_lock.add("elem1".to_string(), (p1, 1)).ok();
1354 crdt_lock.add("elem2".to_string(), (p1, 2)).ok();
1355 crdt_lock.add("elem3".to_string(), (p1, 3)).ok();
1356 }
1357
1358 let synced_peers = std::sync::Arc::new(tokio::sync::Mutex::new(HashSet::new()));
1360 let synced_peers_clone = synced_peers.clone();
1361
1362 manager
1363 .start(
1364 move |peer_id, _delta: <OrSet<String> as DeltaCrdt>::Delta| {
1365 let peers = synced_peers_clone.clone();
1366 Box::pin(async move {
1367 peers.lock().await.insert(peer_id);
1368 Ok(())
1369 })
1370 },
1371 )
1372 .await
1373 .ok();
1374
1375 tokio::time::sleep(tokio::time::Duration::from_millis(1100)).await;
1377
1378 manager.stop().await.ok();
1379
1380 let peers = synced_peers.lock().await;
1382 assert!(peers.contains(&p1));
1383 assert!(peers.contains(&p2));
1384 assert!(peers.contains(&p3));
1385 }
1386
1387 #[test]
1388 fn canonical_retained_wire_roundtrips_and_blocks_stale_resurrection() {
1389 let origin = peer(7);
1390 let mut left = OrSet::new();
1391 left.add("visible".to_string(), (origin, 1))
1392 .expect("visible");
1393 left.add("removed".to_string(), (origin, 2))
1394 .expect("removed");
1395 let stale = left.clone();
1396 left.remove(&"removed".to_string()).expect("tombstone");
1397
1398 let encoded = bincode::serialize(&left.canonical_retained_wire()).expect("canonical wire");
1399 let mut restored: OrSet<String> = bincode::deserialize(&encoded).expect("old OrSet decode");
1400 assert!(restored.contains(&"visible".to_string()));
1401 assert!(!restored.contains(&"removed".to_string()));
1402
1403 restored.merge_state(&stale).expect("merge stale state");
1404 assert!(
1405 !restored.contains(&"removed".to_string()),
1406 "canonical tombstone must block a stale add tag"
1407 );
1408
1409 let mut right = OrSet::new();
1410 right
1411 .add("removed".to_string(), (origin, 2))
1412 .expect("removed");
1413 right
1414 .add("visible".to_string(), (origin, 1))
1415 .expect("visible");
1416 right.remove(&"removed".to_string()).expect("tombstone");
1417 assert_eq!(
1418 encoded,
1419 bincode::serialize(&right.canonical_retained_wire()).expect("reordered wire")
1420 );
1421 }
1422}