1use std::collections::BTreeMap;
5use std::fmt::Debug;
6use std::hash::Hash as StdHash;
7use std::marker::PhantomData;
8use std::pin::Pin;
9use std::task::{Context, Poll};
10
11use futures::channel::mpsc;
12use futures::{Sink, SinkExt, Stream, StreamExt};
13use p2panda_core::{Body, Extensions, Hash, Header, LogId, Operation, VerifyingKey};
14use p2panda_store::logs::LogStore;
15use p2panda_store::topics::TopicStore;
16use pin_project_lite::pin_project;
17use serde::{Deserialize, Serialize};
18use thiserror::Error;
19use tokio::sync::broadcast;
20use tracing::{Level, debug, enabled, trace, warn};
21
22use crate::ToSync;
23use crate::dedup::DEFAULT_BUFFER_CAPACITY;
24use crate::protocols::ShortFormat;
25use crate::protocols::log_sync::{
26 LogSync, LogSyncError, LogSyncEvent, LogSyncMessage, LogSyncMetrics,
27};
28use crate::traits::Protocol;
29
30#[derive(Debug)]
44pub struct TopicLogSync<T, S, L, E> {
45 pub topic: T,
46 pub store: S,
47 pub event_tx: broadcast::Sender<TopicLogSyncEvent<E>>,
48 pub live_mode_rx: Option<mpsc::Receiver<ToSync<Operation<E>>>>,
49 pub buffer_capacity: usize,
50 pub _phantom: PhantomData<L>,
51}
52
53impl<T, S, L, E> TopicLogSync<T, S, L, E>
54where
55 T: Eq + StdHash + Serialize + for<'a> Deserialize<'a>,
56 S: LogStore<Operation<E>, VerifyingKey, L, u64, Hash>
57 + TopicStore<T, VerifyingKey, L>
58 + Clone
59 + Send
60 + 'static,
61 L: LogId,
62 E: Extensions,
63{
64 pub fn new(
67 topic: T,
68 store: S,
69 live_mode_rx: Option<mpsc::Receiver<ToSync<Operation<E>>>>,
70 event_tx: broadcast::Sender<TopicLogSyncEvent<E>>,
71 ) -> Self {
72 Self::new_with_capacity(
73 topic,
74 store,
75 live_mode_rx,
76 event_tx,
77 DEFAULT_BUFFER_CAPACITY,
78 )
79 }
80
81 pub fn new_with_capacity(
83 topic: T,
84 store: S,
85 live_mode_rx: Option<mpsc::Receiver<ToSync<Operation<E>>>>,
86 event_tx: broadcast::Sender<TopicLogSyncEvent<E>>,
87 buffer_capacity: usize,
88 ) -> Self {
89 Self {
90 topic,
91 store,
92 event_tx,
93 live_mode_rx,
94 buffer_capacity,
95 _phantom: PhantomData,
96 }
97 }
98}
99
100impl<T, S, L, E> Protocol for TopicLogSync<T, S, L, E>
101where
102 T: Debug + Eq + StdHash + Serialize + for<'a> Deserialize<'a> + Send + 'static,
103 S: LogStore<Operation<E>, VerifyingKey, L, u64, Hash>
104 + TopicStore<T, VerifyingKey, L>
105 + Clone
106 + Send
107 + 'static,
108 L: LogId + Debug + Send + 'static,
109 E: Extensions + Send + 'static,
110{
111 type Error = TopicLogSyncError;
112 type Message = TopicLogSyncMessage<L, E>;
113 type Output = ();
114
115 async fn run(
116 self,
117 mut sink: &mut (impl Sink<Self::Message, Error = impl Debug> + Unpin),
118 mut stream: &mut (impl Stream<Item = Result<Self::Message, impl Debug>> + Unpin),
119 ) -> Result<Self::Output, Self::Error> {
120 debug!(
123 live_mode = self.live_mode_rx.is_some(),
124 "start sync session"
125 );
126
127 let logs = self
129 .store
130 .resolve(&self.topic)
131 .await
132 .map_err(|err| TopicLogSyncError::TopicStore(err.to_string()))?;
133
134 if enabled!(Level::DEBUG) {
135 let display_logs: BTreeMap<String, usize> =
136 logs.iter().map(|(k, v)| (k.fmt_short(), v.len())).collect();
137 debug!(logs = ?display_logs, "local topic logs retrieved");
138 }
139
140 if enabled!(Level::TRACE) {
141 let trace_logs: Vec<(String, &Vec<L>)> =
142 logs.iter().map(|(k, v)| (k.fmt_short(), v)).collect();
143 trace!(logs = ?trace_logs, "local topic logs retrieved");
144 }
145
146 let (mut dedup, sync_metrics) = {
148 let (mut log_sync_sink, mut log_sync_stream) = sync_channels(&mut sink, &mut stream);
149 let protocol = LogSync::new_with_capacity(
150 self.store.clone(),
151 logs,
152 self.event_tx.clone(),
153 self.buffer_capacity,
154 );
155 let result = protocol.run(&mut log_sync_sink, &mut log_sync_stream).await;
156
157 match result {
160 Ok((dedup, metrics)) => {
161 self.event_tx
162 .send(TopicLogSyncEvent::SyncFinished {
163 metrics: metrics.clone().into(),
164 })
165 .map_err(|_| TopicLogSyncChannelError::EventSend)?;
166
167 (dedup, metrics)
168 }
169 Err(err) => {
170 self.event_tx
171 .send(TopicLogSyncEvent::Failed {
172 error: err.to_string(),
173 })
174 .map_err(|_| TopicLogSyncChannelError::EventSend)?;
175
176 log_sync_sink
177 .close()
178 .await
179 .map_err(|err| TopicLogSyncChannelError::MessageSink(format!("{err:?}")))?;
180
181 return Err(err.into());
182 }
183 }
184 };
185
186 let mut metrics: Metrics = sync_metrics.into();
187
188 let result = match self.live_mode_rx {
189 None => Ok(()),
190 Some(mut live_mode_rx) => {
191 let mut close_sent = false;
198 self.event_tx
199 .send(TopicLogSyncEvent::LiveModeStarted)
200 .map_err(|_| TopicLogSyncChannelError::EventSend)?;
201
202 loop {
203 tokio::select! {
204 biased;
205 Some(message) = live_mode_rx.next() => {
206 match message {
207 ToSync::Payload(operation) => {
208 if !dedup.insert(operation.hash) {
209 trace!(id = ?operation.hash.fmt_short(), "ignore duplicate operation sent on live-mode channel");
210 continue;
211 }
212
213 metrics.sent_live_bytes +=
214 operation.header.to_bytes().len() as u64 + operation.header.payload_size;
215 metrics.sent_live_operations += 1;
216
217 trace!(
218 phase = "live",
219 id = ?operation.hash.fmt_short(),
220 sent_ops = ?metrics.sent_live_operations,
221 sent_bytes = ?metrics.sent_live_bytes,
222 "sent operation"
223 );
224
225 let result = sink
226 .send(TopicLogSyncMessage::Live(
227 operation.header,
228 operation.body,
229 ))
230 .await
231 .map_err(|err| TopicLogSyncChannelError::MessageSink(format!("{err:?}")).into());
232
233 if result.is_err() {
234 break result;
235 };
236 }
237 ToSync::Close => {
238 debug!("closing sync session");
242
243 let result = sink
244 .send(TopicLogSyncMessage::Close)
245 .await
246 .map_err(|err| TopicLogSyncChannelError::MessageSink(format!("{err:?}")).into());
247 if result.is_err() {
248 break result;
249 };
250 close_sent = true;
251 }
252 };
253 }
254 message = stream.next() => {
255 let Some(message) = message else {
256 if close_sent {
257 break Ok(());
258 }
259 break Err(TopicLogSyncError::UnexpectedStreamClosure);
260 };
261
262 match message {
263 Ok(message) => {
264 if let TopicLogSyncMessage::Close = message {
265 debug!("received close message from remote");
268 break Ok(());
269 };
270
271 let TopicLogSyncMessage::Live(header, body) = message else {
272 break Err(TopicLogSyncError::UnexpectedProtocolMessage(
273 message.to_string(),
274 ));
275 };
276
277 if !dedup.insert(header.hash()) {
283 trace!(phase = "live", operation_id = ?header.hash().fmt_short(), "ignore duplicate operation sent from remote");
284 continue;
285 }
286
287 metrics.received_live_bytes += header.to_bytes().len() as u64 + header.payload_size;
288 metrics.received_live_operations += 1;
289
290 trace!(
291 phase = "live",
292 operation_id = ?header.hash().fmt_short(),
293 received_ops = %metrics.received_live_operations,
294 received_bytes = %metrics.received_live_bytes,
295 "received operation"
296 );
297
298 self.event_tx
299 .send(TopicLogSyncEvent::OperationReceived{operation: Box::new(Operation {
300 hash: header.hash(),
301 header,
302 body,
303 }), metrics: metrics.clone()})
304 .map_err(|_| TopicLogSyncChannelError::EventSend)?;
305 }
306 Err(err) => {
307 if close_sent {
308 break Ok(());
309 }
310 break Err(TopicLogSyncError::DecodeMessage(format!("{err:?}")));
311 }
312 }
313 }
314 }
315 }
316 }
317 };
318
319 sink.close()
320 .await
321 .map_err(|err| TopicLogSyncChannelError::MessageSink(format!("{err:?}")))?;
322
323 let final_event = match result.as_ref() {
324 Ok(_) => {
325 debug!(
326 sent_ops = ?metrics.sent_operations(),
327 sent_bytes = ?metrics.sent_bytes(),
328 received_ops = %metrics.received_operations(),
329 received_bytes = %metrics.received_bytes(),
330 "sync session closed"
331 );
332 TopicLogSyncEvent::SessionFinished { metrics }
333 }
334 Err(err) => {
335 warn!(error = ?err, "sync session closed with error");
336 TopicLogSyncEvent::Failed {
337 error: err.to_string(),
338 }
339 }
340 };
341
342 self.event_tx
343 .send(final_event)
344 .map_err(|_| TopicLogSyncChannelError::EventSend)?;
345
346 result
347 }
348}
349
350#[allow(clippy::complexity)]
352fn sync_channels<'a, L, E>(
353 sink: &mut (impl Sink<TopicLogSyncMessage<L, E>, Error = impl Debug> + Unpin),
354 stream: &mut (impl Stream<Item = Result<TopicLogSyncMessage<L, E>, impl Debug>> + Unpin),
355) -> (
356 impl Sink<LogSyncMessage<L>, Error = TopicLogSyncChannelError> + Unpin,
357 impl Stream<Item = Result<LogSyncMessage<L>, TopicLogSyncChannelError>> + Unpin,
358)
359where
360 L: LogId,
361 E: Extensions,
362{
363 let log_sync_sink = LogSyncSink::new(sink);
364
365 let log_sync_stream = stream.by_ref().map(|message| match message {
366 Ok(TopicLogSyncMessage::Sync(message)) => Ok(message),
367 Ok(TopicLogSyncMessage::Live { .. }) | Ok(TopicLogSyncMessage::Close) => Err(
368 TopicLogSyncChannelError::MessageStream("non-protocol message received".to_string()),
369 ),
370 Err(err) => Err(TopicLogSyncChannelError::MessageStream(format!("{err:?}"))),
371 });
372
373 (log_sync_sink, log_sync_stream)
374}
375
376#[derive(Debug, Error)]
378pub enum TopicLogSyncChannelError {
379 #[error("error sending on message sink: {0}")]
380 MessageSink(String),
381
382 #[error("error receiving from message stream: {0}")]
383 MessageStream(String),
384
385 #[error("no active receivers for broadcast")]
386 EventSend,
387}
388
389#[derive(Debug, Error)]
391pub enum TopicLogSyncError {
392 #[error(transparent)]
393 Sync(#[from] LogSyncError),
394
395 #[error("topic store error: {0}")]
396 TopicStore(String),
397
398 #[error("unexpected protocol message: {0}")]
399 UnexpectedProtocolMessage(String),
400
401 #[error(transparent)]
402 Channel(#[from] TopicLogSyncChannelError),
403
404 #[error("remote unexpectedly closed stream in live-mode")]
405 UnexpectedStreamClosure,
406
407 #[error("{0}")]
408 DecodeMessage(String),
409}
410
411#[derive(Clone, Debug, Default, PartialEq)]
412pub struct Metrics {
413 pub outbound_sync_bytes: u64,
414 pub outbound_sync_operations: u64,
415 pub inbound_sync_bytes: u64,
416 pub inbound_sync_operations: u64,
417 pub sent_sync_bytes: u64,
418 pub sent_sync_operations: u64,
419 pub received_sync_bytes: u64,
420 pub received_sync_operations: u64,
421 pub sent_live_bytes: u64,
422 pub sent_live_operations: u64,
423 pub received_live_bytes: u64,
424 pub received_live_operations: u64,
425}
426
427impl Metrics {
428 pub fn sent_bytes(&self) -> u64 {
429 self.sent_sync_bytes + self.sent_live_bytes
430 }
431
432 pub fn received_bytes(&self) -> u64 {
433 self.received_sync_bytes + self.received_live_bytes
434 }
435
436 pub fn sent_operations(&self) -> u64 {
437 self.sent_sync_operations + self.sent_live_operations
438 }
439
440 pub fn received_operations(&self) -> u64 {
441 self.received_sync_operations + self.received_live_operations
442 }
443}
444
445impl From<LogSyncMetrics> for Metrics {
446 fn from(value: LogSyncMetrics) -> Metrics {
447 Metrics {
448 inbound_sync_bytes: value.inbound_bytes,
449 outbound_sync_bytes: value.outbound_bytes,
450 sent_sync_bytes: value.sent_bytes,
451 received_sync_bytes: value.received_bytes,
452 inbound_sync_operations: value.inbound_operations,
453 outbound_sync_operations: value.outbound_operations,
454 sent_sync_operations: value.sent_operations,
455 received_sync_operations: value.received_operations,
456 ..Default::default()
457 }
458 }
459}
460
461#[derive(Debug, Clone, PartialEq)]
463pub enum TopicLogSyncEvent<E = ()> {
464 SessionStarted,
468
469 SyncStarted { metrics: Metrics },
474
475 SyncFinished { metrics: Metrics },
480
481 LiveModeStarted,
485
486 OperationReceived {
488 operation: Box<Operation<E>>,
489 metrics: Metrics,
490 },
491
492 SessionFinished { metrics: Metrics },
496
497 Failed { error: String },
502}
503
504impl<E> From<LogSyncEvent<E>> for TopicLogSyncEvent<E> {
505 fn from(event: LogSyncEvent<E>) -> Self {
506 match event {
507 LogSyncEvent::MetricsExchanged { metrics } => TopicLogSyncEvent::SyncStarted {
508 metrics: metrics.into(),
509 },
510 LogSyncEvent::OperationReceived { operation, metrics } => {
511 TopicLogSyncEvent::OperationReceived {
512 operation,
513 metrics: metrics.into(),
514 }
515 }
516 }
517 }
518}
519
520#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)]
522#[serde(bound(deserialize = "L: LogId"))]
523#[serde(tag = "type", content = "value")]
524#[allow(clippy::large_enum_variant)]
525pub enum TopicLogSyncMessage<L, E>
526where
527 L: LogId,
528 E: Extensions,
529{
530 Sync(LogSyncMessage<L>),
531 Live(Header<E>, Option<Body>),
532 Close,
533}
534
535impl<L, E> std::fmt::Display for TopicLogSyncMessage<L, E>
536where
537 L: LogId,
538 E: Extensions,
539{
540 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
541 let value = match self {
542 TopicLogSyncMessage::Sync(_) => "sync",
543 TopicLogSyncMessage::Live(_, _) => "live",
544 TopicLogSyncMessage::Close => "close",
545 };
546 write!(f, "{value}")
547 }
548}
549
550pin_project! {
551 pub struct LogSyncSink<S, L, E> {
553 #[pin]
554 inner: S,
555 _phantom: std::marker::PhantomData<(L, E)>,
556 }
557}
558
559impl<S, L, E> LogSyncSink<S, L, E> {
560 pub fn new(inner: S) -> Self {
561 Self {
562 inner,
563 _phantom: std::marker::PhantomData,
564 }
565 }
566}
567
568impl<S, L, E> Sink<LogSyncMessage<L>> for LogSyncSink<S, L, E>
569where
570 L: LogId,
571 S: Sink<TopicLogSyncMessage<L, E>>,
572 S::Error: Debug,
573 E: Extensions,
574{
575 type Error = TopicLogSyncChannelError;
576
577 fn poll_ready(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
578 let this = self.project();
579 this.inner
580 .poll_ready(cx)
581 .map_err(|err| TopicLogSyncChannelError::MessageSink(format!("{err:?}")))
582 }
583
584 fn start_send(self: Pin<&mut Self>, item: LogSyncMessage<L>) -> Result<(), Self::Error> {
585 let this = self.project();
586 let msg = TopicLogSyncMessage::Sync(item);
587 this.inner
588 .start_send(msg)
589 .map_err(|err| TopicLogSyncChannelError::MessageSink(format!("{err:?}")))
590 }
591
592 fn poll_flush(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
593 let this = self.project();
594 this.inner
595 .poll_flush(cx)
596 .map_err(|err| TopicLogSyncChannelError::MessageSink(format!("{err:?}")))
597 }
598
599 fn poll_close(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
600 let this = self.project();
601 this.inner
602 .poll_close(cx)
603 .map_err(|err| TopicLogSyncChannelError::MessageSink(format!("{err:?}")))
604 }
605}
606#[cfg(test)]
607pub mod tests {
608 use std::collections::BTreeMap;
609
610 use assert_matches::assert_matches;
611 use futures::channel::mpsc;
612 use futures::{SinkExt, StreamExt};
613 use p2panda_core::{Body, Operation, Topic};
614
615 use crate::ToSync;
616 use crate::protocols::{LogSyncError, LogSyncMessage};
617 use crate::test_utils::{
618 Peer, TestTopicSyncMessage, run_protocol, run_protocol_uni, setup_logging,
619 };
620 use crate::traits::Protocol;
621
622 use super::{TopicLogSyncError, TopicLogSyncEvent};
623
624 #[tokio::test]
625 async fn sync_session_no_operations() {
626 let topic = Topic::random();
627 let mut peer = Peer::new(0).await;
628 peer.associate(&topic, &BTreeMap::default()).await;
629
630 let (session, mut events_rx, _) = peer.topic_sync_protocol(topic.clone(), false);
631
632 let (_, remote_rx) = run_protocol_uni(
633 session,
634 &[
635 TestTopicSyncMessage::Sync(LogSyncMessage::Have(BTreeMap::default())),
636 TestTopicSyncMessage::Sync(LogSyncMessage::Done),
637 ],
638 )
639 .await
640 .unwrap();
641
642 assert_matches!(
643 events_rx.recv().await.unwrap(),
644 TopicLogSyncEvent::SyncStarted { .. }
645 );
646 assert_matches!(
647 events_rx.recv().await.unwrap(),
648 TopicLogSyncEvent::SyncFinished { .. }
649 );
650 assert_matches!(
651 events_rx.recv().await.unwrap(),
652 TopicLogSyncEvent::SessionFinished { .. }
653 );
654
655 let messages = remote_rx.collect::<Vec<_>>().await;
656 assert_eq!(messages.len(), 2);
657 for (index, message) in messages.into_iter().enumerate() {
658 match index {
659 0 => assert_eq!(
660 message,
661 TestTopicSyncMessage::Sync(LogSyncMessage::Have(BTreeMap::default()))
662 ),
663 1 => {
664 assert_eq!(message, TestTopicSyncMessage::Sync(LogSyncMessage::Done));
665 break;
666 }
667 _ => panic!(),
668 };
669 }
670 }
671
672 #[tokio::test]
673 async fn sync_operations_accept() {
674 setup_logging();
675
676 let log_id = 0;
677 let topic = Topic::random();
678 let mut peer = Peer::new(0).await;
679
680 let body = Body::new("Hello, Sloth!".as_bytes());
681 let (header_0, header_bytes_0) = peer.create_operation(&body, log_id).await;
682 let (header_1, header_bytes_1) = peer.create_operation(&body, log_id).await;
683 let (header_2, header_bytes_2) = peer.create_operation(&body, log_id).await;
684
685 let logs = BTreeMap::from([(peer.id(), vec![log_id])]);
686 peer.associate(&topic, &logs).await;
687
688 let (session, mut events_rx, _) = peer.topic_sync_protocol(topic.clone(), false);
689
690 let (_, remote_rx) = run_protocol_uni(
691 session,
692 &[
693 TestTopicSyncMessage::Sync(LogSyncMessage::Have(BTreeMap::default())),
694 TestTopicSyncMessage::Sync(LogSyncMessage::Done),
695 ],
696 )
697 .await
698 .unwrap();
699
700 assert_matches!(
701 events_rx.recv().await.unwrap(),
702 TopicLogSyncEvent::SyncStarted { .. }
703 );
704 assert_matches!(
705 events_rx.recv().await.unwrap(),
706 TopicLogSyncEvent::SyncFinished { .. }
707 );
708 assert_matches!(
709 events_rx.recv().await.unwrap(),
710 TopicLogSyncEvent::SessionFinished { .. }
711 );
712
713 let messages = remote_rx.collect::<Vec<_>>().await;
714 assert_eq!(messages.len(), 6);
715 for (index, message) in messages.into_iter().enumerate() {
716 match index {
717 0 => assert_eq!(
718 message,
719 TestTopicSyncMessage::Sync(LogSyncMessage::Have(BTreeMap::from([(
720 peer.id(),
721 BTreeMap::from([(0, 2)])
722 )])))
723 ),
724 1 => {
725 let expected_bytes = header_0.payload_size
726 + header_bytes_0.len() as u64
727 + header_1.payload_size
728 + header_bytes_1.len() as u64
729 + header_2.payload_size
730 + header_bytes_2.len() as u64;
731
732 assert_eq!(
733 message,
734 TestTopicSyncMessage::Sync(LogSyncMessage::PreSync {
735 total_operations: 3,
736 total_bytes: expected_bytes
737 })
738 )
739 }
740 2 => {
741 let (header, body_inner) = assert_matches!(message, TestTopicSyncMessage::Sync(LogSyncMessage::Operation(
742 header,
743 Some(body),
744 )) => (header, body));
745 assert_eq!(header, header_bytes_0);
746 assert_eq!(Body::new(&body_inner), body)
747 }
748 3 => {
749 let (header, body_inner) = assert_matches!(message, TestTopicSyncMessage::Sync(LogSyncMessage::Operation(
750 header,
751 Some(body),
752 )) => (header, body));
753 assert_eq!(header, header_bytes_1);
754 assert_eq!(Body::new(&body_inner), body)
755 }
756 4 => {
757 let (header, body_inner) = assert_matches!(message, TestTopicSyncMessage::Sync(LogSyncMessage::Operation(
758 header,
759 Some(body),
760 )) => (header, body));
761 assert_eq!(header, header_bytes_2);
762 assert_eq!(Body::new(&body_inner), body)
763 }
764 5 => {
765 assert_eq!(message, TestTopicSyncMessage::Sync(LogSyncMessage::Done));
766 break;
767 }
768 _ => panic!(),
769 };
770 }
771 }
772
773 #[tokio::test]
774 async fn topic_log_sync_full_duplex() {
775 setup_logging();
776 let topic = Topic::random();
777 let log_id = 0;
778
779 let mut peer_a = Peer::new(0).await;
780 let mut peer_b = Peer::new(1).await;
781
782 let body = Body::new("Hello, Sloth!".as_bytes());
783 let (header_0, _) = peer_a.create_operation(&body, 0).await;
784 let (header_1, _) = peer_a.create_operation(&body, 0).await;
785 let (header_2, _) = peer_a.create_operation(&body, 0).await;
786
787 let logs = BTreeMap::from([(peer_a.id(), vec![log_id])]);
788 peer_a.associate(&topic, &logs).await;
789
790 let (peer_a_session, mut peer_a_events_rx, _) =
791 peer_a.topic_sync_protocol(topic.clone(), false);
792
793 let (peer_b_session, mut peer_b_events_rx, _) =
794 peer_b.topic_sync_protocol(topic.clone(), false);
795
796 run_protocol(peer_a_session, peer_b_session).await.unwrap();
797
798 assert_matches!(
800 peer_a_events_rx.recv().await.unwrap(),
801 TopicLogSyncEvent::SyncStarted { .. }
802 );
803 assert_matches!(
804 peer_a_events_rx.recv().await.unwrap(),
805 TopicLogSyncEvent::SyncFinished { .. }
806 );
807 assert_matches!(
808 peer_a_events_rx.recv().await.unwrap(),
809 TopicLogSyncEvent::SessionFinished { .. }
810 );
811
812 assert_matches!(
814 peer_b_events_rx.recv().await.unwrap(),
815 TopicLogSyncEvent::SyncStarted { .. }
816 );
817 let (header, body_inner) = assert_matches!(
818 peer_b_events_rx.recv().await.unwrap(),
819 TopicLogSyncEvent::OperationReceived { operation, .. } => {
820 let Operation { header, body, .. } = *operation;
821 (header, body)
822 }
823 );
824 assert_eq!(header, header_0);
825 assert_eq!(body_inner.unwrap(), body);
826 let (header, body_inner) = assert_matches!(
827 peer_b_events_rx.recv().await.unwrap(),
828 TopicLogSyncEvent::OperationReceived { operation, .. } => {
829 let Operation { header, body, .. } = *operation;
830 (header, body)
831 }
832 );
833 assert_eq!(header, header_1);
834 assert_eq!(body_inner.unwrap(), body);
835 let (header, body_inner) = assert_matches!(
836 peer_b_events_rx.recv().await.unwrap(),
837 TopicLogSyncEvent::OperationReceived { operation, .. } => {
838 let Operation { header, body, .. } = *operation;
839 (header, body)
840 }
841 );
842 assert_eq!(header, header_2);
843 assert_eq!(body_inner.unwrap(), body);
844 assert_matches!(
845 peer_b_events_rx.recv().await.unwrap(),
846 TopicLogSyncEvent::SyncFinished { .. }
847 );
848 assert_matches!(
849 peer_b_events_rx.recv().await.unwrap(),
850 TopicLogSyncEvent::SessionFinished { .. }
851 );
852 }
853
854 #[tokio::test]
855 async fn live_mode() {
856 let log_id = 0;
857 let topic = Topic::random();
858 let mut peer_a = Peer::new(0).await;
859 let mut peer_b = Peer::new(1).await;
860
861 let body = Body::new("Hello, Sloth!".as_bytes());
862 let (header_0, header_bytes_0) = peer_b.create_operation(&body, log_id).await;
863
864 let logs = BTreeMap::from([(peer_a.id(), vec![log_id])]);
865 peer_a.associate(&topic, &logs).await;
866
867 let logs = BTreeMap::default();
868 peer_a.associate(&topic, &logs).await;
869
870 let (header_1, _) = peer_b.create_operation_no_insert(&body, log_id).await;
871 let expected_bytes_received = header_0.payload_size
872 + header_0.to_bytes().len() as u64
873 + header_1.payload_size
874 + header_1.to_bytes().len() as u64;
875 let (header_2, _) = peer_a.create_operation_no_insert(&body, log_id).await;
876 let expected_bytes_sent = header_2.payload_size + header_2.to_bytes().len() as u64;
877
878 let (protocol, mut events_rx, mut live_mode_tx) =
879 peer_a.topic_sync_protocol(topic.clone(), true);
880
881 live_mode_tx
882 .send(ToSync::Payload(Operation {
883 hash: header_2.hash(),
884 header: header_2.clone(),
885 body: Some(body.clone()),
886 }))
887 .await
888 .unwrap();
889 live_mode_tx.send(ToSync::Close).await.unwrap();
890
891 let total_bytes = header_bytes_0.len() + body.to_bytes().len();
892 let (_, remote_rx) = run_protocol_uni(
893 protocol,
894 &[
895 TestTopicSyncMessage::Sync(LogSyncMessage::Have(BTreeMap::default())),
896 TestTopicSyncMessage::Sync(LogSyncMessage::PreSync {
897 total_operations: 1,
898 total_bytes: total_bytes as u64,
899 }),
900 TestTopicSyncMessage::Sync(LogSyncMessage::Operation(
901 header_bytes_0,
902 Some(body.to_bytes()),
903 )),
904 TestTopicSyncMessage::Sync(LogSyncMessage::Done),
905 TestTopicSyncMessage::Live(header_1.clone(), Some(body.clone())),
906 TestTopicSyncMessage::Close,
907 ],
908 )
909 .await
910 .unwrap();
911
912 assert_matches!(
913 events_rx.recv().await.unwrap(),
914 TopicLogSyncEvent::SyncStarted { .. }
915 );
916 assert_matches!(
917 events_rx.recv().await.unwrap(),
918 TopicLogSyncEvent::OperationReceived { .. }
919 );
920 assert_matches!(
921 events_rx.recv().await.unwrap(),
922 TopicLogSyncEvent::SyncFinished { .. }
923 );
924 assert_matches!(
925 events_rx.recv().await.unwrap(),
926 TopicLogSyncEvent::LiveModeStarted
927 );
928 let metrics = assert_matches!(
929 events_rx.recv().await.unwrap(),
930 TopicLogSyncEvent::OperationReceived { metrics, .. } => metrics
931 );
932 assert_eq!(metrics.received_operations(), 2);
933 assert_eq!(metrics.sent_operations(), 1);
934 assert_eq!(metrics.received_bytes(), expected_bytes_received);
935 assert_eq!(metrics.sent_bytes(), expected_bytes_sent);
936 assert_matches!(
937 events_rx.recv().await.unwrap(),
938 TopicLogSyncEvent::SessionFinished { .. }
939 );
940
941 let messages = remote_rx.collect::<Vec<_>>().await;
942 assert_eq!(messages.len(), 4);
943 for (index, message) in messages.into_iter().enumerate() {
944 match index {
945 0 => assert_matches!(message, TestTopicSyncMessage::Sync(LogSyncMessage::Have(_))),
946 1 => {
947 assert_matches!(message, TestTopicSyncMessage::Sync(LogSyncMessage::Done))
948 }
949 2 => {
950 let (header, body_inner) = assert_matches!(message, TestTopicSyncMessage::Live(
951 header,
952 Some(body)
953 ) => (header, body));
954 assert_eq!(header, header_2);
955 assert_eq!(body_inner, body);
956 }
957 3 => {
958 assert_matches!(message, TestTopicSyncMessage::Close)
959 }
960 _ => panic!(),
961 };
962 }
963 }
964
965 #[tokio::test]
966 async fn dedup_live_mode_messages() {
967 let log_id = 0;
968 let topic = Topic::random();
969 let mut peer_a = Peer::new(0).await;
970 let mut peer_b = Peer::new(1).await;
971
972 let body = Body::new("Hello, Sloth!".as_bytes());
973 let (header_0, header_bytes_0) = peer_b.create_operation(&body, log_id).await;
974
975 let logs = BTreeMap::from([(peer_a.id(), vec![log_id])]);
976 peer_a.associate(&topic, &logs).await;
977
978 let logs = BTreeMap::default();
979 peer_a.associate(&topic, &logs).await;
980
981 let (header_1, _) = peer_b.create_operation_no_insert(&body, log_id).await;
982 let expected_bytes_received = header_0.payload_size
983 + header_0.to_bytes().len() as u64
984 + header_1.payload_size
985 + header_1.to_bytes().len() as u64;
986 let (header_2, _) = peer_a.create_operation_no_insert(&body, log_id).await;
987 let expected_bytes_sent = header_2.payload_size + header_2.to_bytes().len() as u64;
988
989 let (protocol, mut events_rx, mut live_mode_tx) =
990 peer_a.topic_sync_protocol(topic.clone(), true);
991
992 live_mode_tx
993 .send(ToSync::Payload(Operation {
994 hash: header_2.hash(),
995 header: header_2.clone(),
996 body: Some(body.clone()),
997 }))
998 .await
999 .unwrap();
1000
1001 live_mode_tx
1003 .send(ToSync::Payload(Operation {
1004 hash: header_2.hash(),
1005 header: header_2.clone(),
1006 body: Some(body.clone()),
1007 }))
1008 .await
1009 .unwrap();
1010
1011 live_mode_tx.send(ToSync::Close).await.unwrap();
1012
1013 let total_bytes = header_bytes_0.len() + body.to_bytes().len();
1014 let (_, remote_rx) = run_protocol_uni(
1015 protocol,
1016 &[
1017 TestTopicSyncMessage::Sync(LogSyncMessage::Have(BTreeMap::default())),
1018 TestTopicSyncMessage::Sync(LogSyncMessage::PreSync {
1019 total_operations: 1,
1020 total_bytes: total_bytes as u64,
1021 }),
1022 TestTopicSyncMessage::Sync(LogSyncMessage::Operation(
1023 header_bytes_0,
1024 Some(body.to_bytes()),
1025 )),
1026 TestTopicSyncMessage::Sync(LogSyncMessage::Done),
1027 TestTopicSyncMessage::Live(header_1.clone(), Some(body.clone())),
1028 TestTopicSyncMessage::Live(header_0.clone(), Some(body.clone())),
1030 TestTopicSyncMessage::Live(header_1.clone(), Some(body.clone())),
1032 TestTopicSyncMessage::Close,
1033 ],
1034 )
1035 .await
1036 .unwrap();
1037
1038 assert_matches!(
1039 events_rx.recv().await.unwrap(),
1040 TopicLogSyncEvent::SyncStarted { .. }
1041 );
1042 assert_matches!(
1043 events_rx.recv().await.unwrap(),
1044 TopicLogSyncEvent::OperationReceived { .. }
1045 );
1046 assert_matches!(
1047 events_rx.recv().await.unwrap(),
1048 TopicLogSyncEvent::SyncFinished { .. }
1049 );
1050 assert_matches!(
1051 events_rx.recv().await.unwrap(),
1052 TopicLogSyncEvent::LiveModeStarted
1053 );
1054 let metrics = assert_matches!(
1055 events_rx.recv().await.unwrap(),
1056 TopicLogSyncEvent::OperationReceived { metrics, .. } => metrics
1057 );
1058 assert_eq!(metrics.received_operations(), 2);
1059 assert_eq!(metrics.sent_operations(), 1);
1060 assert_eq!(metrics.received_bytes(), expected_bytes_received);
1061 assert_eq!(metrics.sent_bytes(), expected_bytes_sent);
1062 assert_matches!(
1063 events_rx.recv().await.unwrap(),
1064 TopicLogSyncEvent::SessionFinished { .. }
1065 );
1066
1067 let messages = remote_rx.collect::<Vec<_>>().await;
1068 assert_eq!(messages.len(), 4);
1069 for (index, message) in messages.into_iter().enumerate() {
1070 match index {
1071 0 => assert_matches!(message, TestTopicSyncMessage::Sync(LogSyncMessage::Have(_))),
1072 1 => {
1073 assert_matches!(message, TestTopicSyncMessage::Sync(LogSyncMessage::Done))
1074 }
1075 2 => {
1076 let (header, body_inner) = assert_matches!(message, TestTopicSyncMessage::Live(header, Some(body)) => (header, body));
1077 assert_eq!(header, header_2);
1078 assert_eq!(body_inner, body);
1079 }
1080 3 => {
1081 assert_matches!(message, TestTopicSyncMessage::Close)
1082 }
1083 _ => panic!(),
1084 };
1085 }
1086 }
1087
1088 #[tokio::test]
1089 async fn unexpected_stream_closure_sync() {
1090 let topic = Topic::random();
1091 let mut peer = Peer::new(0).await;
1092 peer.associate(&topic, &Default::default()).await;
1093
1094 let (session, mut events_rx, _live_tx) = peer.topic_sync_protocol(topic.clone(), true);
1095
1096 let messages = [TestTopicSyncMessage::Sync(LogSyncMessage::Have(
1097 BTreeMap::default(),
1098 ))];
1099
1100 let (mut local_message_tx, _remote_message_rx) = mpsc::channel(128);
1101 let (mut remote_message_tx, local_message_rx) = mpsc::channel(128);
1102 let mut local_message_rx = local_message_rx.map(|message| Ok::<_, ()>(message));
1103
1104 for message in messages {
1105 remote_message_tx.send(message.to_owned()).await.unwrap();
1106 }
1107
1108 let handle = tokio::spawn(async move {
1109 session
1110 .run(&mut local_message_tx, &mut local_message_rx)
1111 .await
1112 .expect_err("expected unexpected stream closure")
1113 });
1114
1115 drop(remote_message_tx);
1116
1117 let err = handle.await.unwrap();
1118 assert_matches!(
1119 err,
1120 TopicLogSyncError::Sync(LogSyncError::UnexpectedStreamClosure)
1121 );
1122
1123 while let Ok(event) = events_rx.recv().await {
1124 if let TopicLogSyncEvent::Failed { error } = event {
1125 assert_eq!(
1126 error,
1127 "remote unexpectedly closed stream during initial sync".to_string()
1128 );
1129 break;
1130 }
1131 }
1132 }
1133
1134 #[tokio::test]
1135 async fn unexpected_stream_closure_live_mode() {
1136 let topic = Topic::random();
1137 let mut peer = Peer::new(0).await;
1138 peer.associate(&topic, &Default::default()).await;
1139
1140 let (session, mut events_rx, _live_tx) = peer.topic_sync_protocol(topic.clone(), true);
1141
1142 let messages = [
1143 TestTopicSyncMessage::Sync(LogSyncMessage::Have(BTreeMap::default())),
1144 TestTopicSyncMessage::Sync(LogSyncMessage::Done),
1145 ];
1146
1147 let (mut local_message_tx, _remote_message_rx) = mpsc::channel(128);
1148 let (mut remote_message_tx, local_message_rx) = mpsc::channel(128);
1149 let mut local_message_rx = local_message_rx.map(|message| Ok::<_, ()>(message));
1150
1151 for message in messages {
1152 remote_message_tx.send(message.to_owned()).await.unwrap();
1153 }
1154
1155 let handle = tokio::spawn(async move {
1156 session
1157 .run(&mut local_message_tx, &mut local_message_rx)
1158 .await
1159 .expect_err("expected unexpected stream closure")
1160 });
1161
1162 drop(remote_message_tx);
1163
1164 let err = handle.await.unwrap();
1165 assert_matches!(err, TopicLogSyncError::UnexpectedStreamClosure);
1166
1167 while let Ok(event) = events_rx.recv().await {
1168 if let TopicLogSyncEvent::Failed { error } = event {
1169 assert_eq!(
1170 error,
1171 "remote unexpectedly closed stream in live-mode".to_string()
1172 );
1173 break;
1174 }
1175 }
1176 }
1177}