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