Skip to main content

p2panda_sync/protocols/
log_sync.rs

1// SPDX-License-Identifier: MIT OR Apache-2.0
2
3//! Two-party sync protocol over append-only logs.
4use std::collections::BTreeMap;
5use std::fmt::{Debug, Display};
6use std::marker::PhantomData;
7
8use futures_util::{Sink, SinkExt, Stream, StreamExt, stream};
9use p2panda_core::cbor::{DecodeError, decode_cbor};
10use p2panda_core::logs::{LogHeights, LogRanges, compare};
11use p2panda_core::{Body, Extensions, Hash, Header, LogId, Operation, SeqNum, VerifyingKey};
12use p2panda_store::logs::LogStore;
13use serde::{Deserialize, Serialize};
14use thiserror::Error;
15use tokio::select;
16use tokio::sync::broadcast;
17use tracing::{debug, trace, warn};
18
19use crate::dedup::{DEFAULT_BUFFER_CAPACITY, DeduplicationBuffer};
20use crate::traits::Protocol;
21
22/// A map of author logs.
23pub type Logs<L> = BTreeMap<VerifyingKey, Vec<L>>;
24
25/// Sync session life-cycle states.
26#[derive(Debug, Default)]
27enum State<L> {
28    /// Initialise session metrics and announce sync start on event stream.
29    #[default]
30    Start,
31
32    /// Calculate local log heights and send Have message to remote.
33    SendHave,
34
35    /// Receive have message from remote and calculate operation diff.
36    ReceiveHave { local: LogHeights<VerifyingKey, L> },
37
38    /// Send PreSync message to remote or Done if we have nothing to send.
39    SendPreSync {
40        remote_needs: LogRanges<VerifyingKey, L>,
41    },
42
43    /// Receive PreSync message from remote or Done if they have nothing to send.
44    ReceivePreSyncOrDone {
45        remote_needs: LogRanges<VerifyingKey, L>,
46        outbound_operations: u32,
47        outbound_bytes: u32,
48    },
49
50    /// Enter sync loop where we exchange operations with the remote, moves onto next state when
51    /// both peers have send Done messages.
52    Sync {
53        remote_needs: LogRanges<VerifyingKey, L>,
54        metrics: LogSyncMetrics,
55    },
56
57    /// Announce on the event stream that the sync session successfully completed.
58    End { metrics: LogSyncMetrics },
59}
60
61/// Efficient sync protocol for append-only log data types.
62#[derive(Debug)]
63pub struct LogSync<L, E, S, Evt> {
64    state: State<L>,
65    logs: Logs<L>,
66    store: S,
67    event_tx: broadcast::Sender<Evt>,
68    buffer_capacity: usize,
69    _marker: PhantomData<E>,
70}
71
72impl<L, E, S, Evt> LogSync<L, E, S, Evt> {
73    pub fn new(store: S, logs: Logs<L>, event_tx: broadcast::Sender<Evt>) -> Self {
74        Self::new_with_capacity(store, logs, event_tx, DEFAULT_BUFFER_CAPACITY)
75    }
76
77    pub fn new_with_capacity(
78        store: S,
79        logs: Logs<L>,
80        event_tx: broadcast::Sender<Evt>,
81        buffer_capacity: usize,
82    ) -> Self {
83        Self {
84            state: Default::default(),
85            store,
86            event_tx,
87            logs,
88            buffer_capacity,
89            _marker: PhantomData,
90        }
91    }
92}
93
94impl<L, E, S, Evt> Protocol for LogSync<L, E, S, Evt>
95where
96    L: LogId + Debug + Send + 'static,
97    E: Extensions + Send + 'static,
98    S: LogStore<Operation<E>, VerifyingKey, L, SeqNum, Hash> + Clone + Send + 'static,
99    Evt: Debug + From<LogSyncEvent<E>> + Send + 'static,
100{
101    type Error = LogSyncError;
102    type Output = (DeduplicationBuffer<Hash>, LogSyncMetrics);
103    type Message = LogSyncMessage<L>;
104
105    async fn run(
106        mut self,
107        sink: &mut (impl Sink<Self::Message, Error = impl Debug> + Unpin),
108        stream: &mut (impl Stream<Item = Result<Self::Message, impl Debug>> + Unpin),
109    ) -> Result<Self::Output, Self::Error> {
110        let mut sync_done_received = false;
111        let mut sync_done_sent = false;
112        let mut dedup = DeduplicationBuffer::new(self.buffer_capacity);
113
114        let metrics = loop {
115            match self.state {
116                State::Start => self.state = State::SendHave,
117                State::SendHave => {
118                    let local = get_log_heights(&self.store, &self.logs).await?;
119
120                    sink.send(LogSyncMessage::<L>::Have(local.clone()))
121                        .await
122                        .map_err(|err| LogSyncError::MessageSink(format!("{err:?}")))?;
123
124                    self.state = State::ReceiveHave { local };
125                }
126                State::ReceiveHave { local } => {
127                    let Some(message) = stream.next().await else {
128                        return Err(LogSyncError::UnexpectedStreamClosure);
129                    };
130                    let message =
131                        message.map_err(|err| LogSyncError::MessageStream(format!("{err:?}")))?;
132                    let LogSyncMessage::Have(remote) = message else {
133                        return Err(LogSyncError::UnexpectedMessage(message.to_string()));
134                    };
135
136                    let remote_needs = compare(&local, &remote);
137
138                    self.state = State::SendPreSync { remote_needs };
139                }
140                State::SendPreSync { remote_needs } => {
141                    let mut outbound_operations = 0;
142                    let mut outbound_bytes = 0;
143                    for (verifying_key, log_range) in remote_needs.iter() {
144                        for (log_id, (after, until)) in log_range.iter() {
145                            if let Some((inner_outbound_operations, inner_outbound_bytes)) = self
146                                .store
147                                .get_log_size(verifying_key, log_id, *after, *until)
148                                .await
149                                .map_err(|err| LogSyncError::OperationStore(format!("{err}")))?
150                            {
151                                outbound_operations += inner_outbound_operations;
152                                outbound_bytes += inner_outbound_bytes;
153                            };
154                        }
155                    }
156
157                    if outbound_bytes > 0 {
158                        sink.send(LogSyncMessage::PreSync {
159                            total_operations: outbound_operations,
160                            total_bytes: outbound_bytes,
161                        })
162                        .await
163                        .map_err(|err| LogSyncError::MessageSink(format!("{err:?}")))?;
164                    } else {
165                        sink.send(LogSyncMessage::Done)
166                            .await
167                            .map_err(|err| LogSyncError::MessageSink(format!("{err:?}")))?;
168                        sync_done_sent = true;
169                    }
170
171                    self.state = State::ReceivePreSyncOrDone {
172                        remote_needs,
173                        outbound_operations,
174                        outbound_bytes,
175                    };
176                }
177                State::ReceivePreSyncOrDone {
178                    remote_needs,
179                    outbound_operations,
180                    outbound_bytes,
181                } => {
182                    let Some(message) = stream.next().await else {
183                        return Err(LogSyncError::UnexpectedStreamClosure);
184                    };
185                    let message =
186                        message.map_err(|err| LogSyncError::MessageStream(format!("{err:?}")))?;
187
188                    let (inbound_operations, inbound_bytes) = match message {
189                        LogSyncMessage::PreSync {
190                            total_operations,
191                            total_bytes,
192                        } => (total_operations, total_bytes),
193                        LogSyncMessage::Done => {
194                            sync_done_received = true;
195                            (0, 0)
196                        }
197                        message => {
198                            return Err(LogSyncError::UnexpectedMessage(message.to_string()));
199                        }
200                    };
201
202                    debug!(
203                        local_ops = outbound_operations,
204                        remote_ops = inbound_operations,
205                        local_bytes = outbound_bytes,
206                        remote_bytes = inbound_bytes,
207                        "sync metrics exchanged",
208                    );
209
210                    let metrics = LogSyncMetrics {
211                        outbound_operations,
212                        outbound_bytes,
213                        inbound_operations,
214                        inbound_bytes,
215                        sent_operations: 0,
216                        sent_bytes: 0,
217                        received_operations: 0,
218                        received_bytes: 0,
219                    };
220
221                    self.event_tx
222                        .send(
223                            LogSyncEvent::MetricsExchanged {
224                                metrics: metrics.clone(),
225                            }
226                            .into(),
227                        )
228                        .map_err(|_| LogSyncError::BroadcastSend)?;
229
230                    self.state = State::Sync {
231                        remote_needs,
232                        metrics,
233                    };
234                }
235                State::Sync {
236                    remote_needs,
237                    mut metrics,
238                } => {
239                    let mut send_logs_len = remote_needs.len();
240                    let mut remote_needs = stream::iter(remote_needs);
241                    // We perform a loop awaiting futures on both the receiving stream and the list
242                    // of log ranges we have to send. This means that processing of both streams is
243                    // done concurrently.
244                    loop {
245                        select! {
246                            Some(message) = stream.next(), if !sync_done_received => {
247                                let message =
248                                    message.map_err(|err| LogSyncError::MessageStream(format!("{err:?}")))?;
249
250                                match message {
251                                    LogSyncMessage::Operation(header, body) => {
252                                        metrics.received_bytes += {
253                                            header.len()
254                                                + body.as_ref().map(|bytes| bytes.len()).unwrap_or_default()
255                                        } as u32;
256                                        metrics.received_operations += 1;
257
258                                        let header: Header<E> = decode_cbor(&header[..])?;
259                                        let body = body.map(|ref bytes| Body::new(bytes));
260
261                                        // Insert message hash into deduplication buffer.
262                                        if !dedup.insert(header.hash()) {
263                                            trace!(phase = "sync", operation_id = ?header.hash().fmt_short(), "ignore duplicate operation sent from remote");
264                                            continue;
265                                        }
266
267                                        trace!(
268                                            phase = "sync",
269                                            id = ?header.hash().fmt_short(),
270                                            received_ops = metrics.received_operations,
271                                            received_bytes = metrics.received_bytes,
272                                            "received operation"
273                                        );
274
275                                        // Forward data received from the remote to the app layer.
276                                        self.event_tx
277                                            .send(
278                                                LogSyncEvent::OperationReceived{operation:Box::new(Operation{hash:header.hash(),header,body,}), metrics: metrics.clone() }
279                                                .into(),
280                                            )
281                                            .map_err(|_| LogSyncError::BroadcastSend)?;
282                                    }
283                                    LogSyncMessage::Done => {
284                                        trace!(
285                                            phase = "sync",
286                                            "received done message"
287                                        );
288
289                                        sync_done_received = true;
290                                    }
291                                    message => {
292                                        return Err(LogSyncError::UnexpectedMessage(message.to_string()));
293                                    }
294                                }
295                            },
296                            Some((author, log_ranges)) = remote_needs.next() => {
297                                for (log_id, (after, until)) in log_ranges {
298                                    // Get all entries from the log we should send to the remote.
299                                    let Some(result) = self
300                                    .store
301                                    .get_log_entries(&author, &log_id, after, until)
302                                    .await
303                                    .map_err(|err| LogSyncError::OperationStore(format!("{err}")))? else {
304                                        warn!(
305                                            author = author.fmt_short(),
306                                            log_id = ?log_id,
307                                            after = after,
308                                            until = until,
309                                            "expected log missing from store"
310                                        );
311                                        continue;
312                                    };
313
314                                    for (operation, header_bytes) in result {
315                                        let header = operation.header;
316                                        let body = operation.body;
317                                        let hash = operation.hash;
318
319                                        metrics.sent_bytes += { header_bytes.len() +
320                                            body.as_ref().map(|body|
321                                        body.to_bytes().len()).unwrap_or_default() } as u32;
322                                        metrics.sent_operations += 1;
323
324                                        trace!(
325                                            phase = "sync",
326                                            verifying_key = %author.fmt_short(),
327                                            log_id = ?log_id,
328                                            seq_num = header.seq_num,
329                                            id = %hash.fmt_short(),
330                                            sent_ops = metrics.sent_operations,
331                                            sent_bytes = metrics.sent_bytes,
332                                            "send operation",
333                                        );
334
335                                        sink.send(LogSyncMessage::Operation(header_bytes, body.as_ref().map(|body|
336                                            body.to_bytes())))
337                                            .await
338                                            .map_err(|err|
339                                            LogSyncError::MessageSink(format!("{err:?}")))?;
340
341                                        dedup.insert(hash);
342                                    }
343                                }
344                                // We've finished sending all logs, send sync done message.
345                                send_logs_len -= 1;
346                                if send_logs_len == 0 {
347                                    trace!(
348                                        phase = "sync",
349                                        verifying_key = %author.fmt_short(),
350                                        "send sync done message",
351                                    );
352                                    sink.send(LogSyncMessage::Done)
353                                        .await
354                                        .map_err(|err| LogSyncError::MessageSink(format!("{err:?}")))?;
355                                    sync_done_sent = true;
356                                }
357                            },
358                            else => {
359                                // If both streams are empty (they return None), or we received a
360                                // sync done message and we sent all our pending operations, exit
361                                // the loop.
362                                if sync_done_received && sync_done_sent {
363                                    trace!("sync done sent and received");
364                                    break;
365                                }
366                            }
367                        }
368                    }
369                    self.state = State::End { metrics };
370                }
371                State::End { metrics } => {
372                    break metrics;
373                }
374            }
375        };
376
377        Ok((dedup, metrics))
378    }
379}
380
381/// Return the local log heights of all passed logs.
382async fn get_log_heights<L, E, S>(
383    store: &S,
384    logs: &Logs<L>,
385) -> Result<LogHeights<VerifyingKey, L>, LogSyncError>
386where
387    L: LogId,
388    S: LogStore<Operation<E>, VerifyingKey, L, SeqNum, Hash> + Clone + Send + 'static,
389{
390    let mut result = BTreeMap::new();
391    for (verifying_key, log_ids) in logs {
392        let Some(log_heights) = store
393            .get_log_heights(verifying_key, log_ids)
394            .await
395            .map_err(|err| LogSyncError::LogStore(format!("{err}")))?
396        else {
397            continue;
398        };
399        result.insert(*verifying_key, log_heights);
400    }
401
402    Ok(result)
403}
404
405/// Protocol messages.
406#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)]
407#[serde(bound(deserialize = "L: LogId"))]
408pub enum LogSyncMessage<L>
409where
410    L: LogId,
411{
412    Have(LogHeights<VerifyingKey, L>),
413    PreSync {
414        total_operations: u32,
415        total_bytes: u32,
416    },
417    Operation(Vec<u8>, Option<Vec<u8>>),
418    Done,
419}
420
421impl<L> Display for LogSyncMessage<L>
422where
423    L: LogId,
424{
425    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
426        let value = match self {
427            LogSyncMessage::Have(_) => "have",
428            LogSyncMessage::PreSync { .. } => "pre_sync",
429            LogSyncMessage::Operation(_, _) => "operation",
430            LogSyncMessage::Done => "done",
431        };
432
433        write!(f, "{value}")
434    }
435}
436
437/// Events emitted from log sync sessions.
438#[derive(Clone, Debug, PartialEq)]
439pub enum LogSyncEvent<E> {
440    /// We have calculated and sent the estimated bytes and operations to be sent during this sync
441    /// session and received the remotes' estimates.
442    ///
443    /// This is the first event to occur and will only be sent once.
444    MetricsExchanged { metrics: LogSyncMetrics },
445
446    /// An operation has been received from the remote.
447    OperationReceived {
448        operation: Box<Operation<E>>,
449        metrics: LogSyncMetrics,
450    },
451}
452
453/// Sync metrics emitted in event messages.
454#[derive(Clone, Debug, PartialEq, Default)]
455pub struct LogSyncMetrics {
456    pub outbound_operations: u32,
457    pub outbound_bytes: u32,
458    pub inbound_operations: u32,
459    pub inbound_bytes: u32,
460    pub sent_operations: u32,
461    pub sent_bytes: u32,
462    pub received_operations: u32,
463    pub received_bytes: u32,
464}
465
466/// Protocol error types.
467#[derive(Debug, Error)]
468pub enum LogSyncError {
469    #[error(transparent)]
470    Decode(#[from] DecodeError),
471
472    #[error("log store error: {0}")]
473    LogStore(String),
474
475    #[error("operation store error: {0}")]
476    OperationStore(String),
477
478    #[error("no active receivers when broadcasting")]
479    BroadcastSend,
480
481    #[error("log sync error sending on message sink: {0}")]
482    MessageSink(String),
483
484    #[error("log sync error receiving from message stream: {0}")]
485    MessageStream(String),
486
487    #[error("remote unexpectedly closed stream during initial sync")]
488    UnexpectedStreamClosure,
489
490    #[error("log sync received unexpected protocol message: {0}")]
491    UnexpectedMessage(String),
492}
493
494/// Returns a displayable string representing the underlying value in a short format, easy to read
495/// during debugging and logging.
496pub trait ShortFormat {
497    fn fmt_short(&self) -> String;
498}
499
500impl ShortFormat for VerifyingKey {
501    fn fmt_short(&self) -> String {
502        self.to_hex()[0..10].to_string()
503    }
504}
505
506impl ShortFormat for Hash {
507    fn fmt_short(&self) -> String {
508        self.to_hex()[0..5].to_string()
509    }
510}
511
512#[cfg(test)]
513mod tests {
514    use std::collections::BTreeMap;
515
516    use futures_channel::mpsc;
517    use futures_util::StreamExt;
518    use p2panda_core::test_utils::setup_logging;
519    use p2panda_core::{Body, Hash};
520    use p2panda_store::operations::OperationStore;
521    use p2panda_store::{SqliteStore, tx_unwrap};
522
523    use crate::protocols::log_sync::{LogSyncError, LogSyncEvent, Logs, Operation};
524    use crate::test_utils::{Peer, TestLogId, TestLogSyncMessage, run_protocol, run_protocol_uni};
525    use crate::traits::Protocol;
526
527    #[tokio::test]
528    async fn log_sync_no_operations() {
529        let mut peer: Peer = Peer::new(0).await;
530
531        let (session, mut event_rx) = peer.log_sync_protocol(&Logs::default());
532        let ((_, metrics), mut remote_message_rx) = run_protocol_uni(
533            session,
534            &[
535                TestLogSyncMessage::Have(BTreeMap::default()),
536                TestLogSyncMessage::Done,
537            ],
538        )
539        .await
540        .unwrap();
541
542        let recv = event_rx.recv().await.unwrap();
543        let LogSyncEvent::MetricsExchanged {
544            metrics: event_metrics,
545        } = recv
546        else {
547            panic!("Not a LogSyncEvent::MetricsExchanged: {recv:?}");
548        };
549
550        assert_eq!(metrics.outbound_operations, 0);
551        assert_eq!(metrics.outbound_bytes, 0);
552        assert_eq!(metrics.inbound_operations, 0);
553        assert_eq!(metrics.inbound_bytes, 0);
554
555        assert_eq!(event_metrics, metrics);
556
557        assert_eq!(
558            remote_message_rx.recv().await.unwrap(),
559            TestLogSyncMessage::Have(BTreeMap::default())
560        );
561
562        assert_eq!(
563            remote_message_rx.recv().await.unwrap(),
564            TestLogSyncMessage::Done
565        );
566    }
567
568    #[tokio::test]
569    async fn log_sync_some_operations() {
570        let mut peer = Peer::new(0).await;
571        let log_id = 0;
572
573        let body = Body::new("Hello, Sloth!".as_bytes());
574        let (header_0, header_bytes_0) = peer.create_operation(&body, log_id).await;
575        let (header_1, header_bytes_1) = peer.create_operation(&body, log_id).await;
576        let (header_2, header_bytes_2) = peer.create_operation(&body, log_id).await;
577
578        let mut logs = Logs::default();
579        logs.insert(peer.id(), vec![log_id]);
580
581        let (session, mut event_rx) = peer.log_sync_protocol(&logs);
582        let ((_, final_metrics), remote_message_rx) = run_protocol_uni(
583            session,
584            &[
585                TestLogSyncMessage::Have(BTreeMap::default()),
586                TestLogSyncMessage::Done,
587            ],
588        )
589        .await
590        .unwrap();
591
592        let expected_bytes = header_0.payload_size
593            + header_bytes_0.len() as u32
594            + header_1.payload_size
595            + header_bytes_1.len() as u32
596            + header_2.payload_size
597            + header_bytes_2.len() as u32;
598
599        let recv = event_rx.recv().await.unwrap();
600        let LogSyncEvent::MetricsExchanged { metrics } = recv else {
601            panic!("Not a LogSyncEvent::MetricsExchanged: {recv:?}");
602        };
603
604        assert_eq!(metrics.outbound_operations, 3);
605        assert_eq!(metrics.outbound_bytes, expected_bytes);
606        assert_eq!(metrics.inbound_operations, 0);
607        assert_eq!(metrics.inbound_bytes, 0);
608
609        assert_eq!(final_metrics.sent_operations, 3);
610        assert_eq!(final_metrics.sent_bytes, expected_bytes);
611        assert_eq!(final_metrics.received_operations, 0);
612        assert_eq!(final_metrics.received_bytes, 0);
613
614        let messages = remote_message_rx.collect::<Vec<_>>().await;
615
616        assert_eq!(messages.len(), 6);
617
618        assert_eq!(
619            messages[0],
620            TestLogSyncMessage::Have(BTreeMap::from([(peer.id(), BTreeMap::from([(0, 2)]))]))
621        );
622
623        assert_eq!(
624            messages[1],
625            TestLogSyncMessage::PreSync {
626                total_operations: 3,
627                total_bytes: expected_bytes
628            }
629        );
630
631        let TestLogSyncMessage::Operation(header, Some(body_inner)) = &messages[2] else {
632            panic!("Not a TestLogSyncMessage::Operation: {:?}", &messages[2]);
633        };
634        let (header, body_inner) = (header.clone(), body_inner.clone());
635        assert_eq!(header, header_bytes_0);
636        assert_eq!(Body::new(&body_inner), body);
637
638        let TestLogSyncMessage::Operation(header, Some(body_inner)) = &messages[3] else {
639            panic!("Not a TestLogSyncMessage::Operation: {:?}", &messages[3]);
640        };
641        let (header, body_inner) = (header.clone(), body_inner.clone());
642        assert_eq!(header, header_bytes_1);
643        assert_eq!(Body::new(&body_inner), body);
644
645        let TestLogSyncMessage::Operation(header, Some(body_inner)) = &messages[4] else {
646            panic!("Not a TestLogSyncMessage::Operation: {:?}", &messages[4]);
647        };
648        let (header, body_inner) = (header.clone(), body_inner.clone());
649        assert_eq!(header, header_bytes_2);
650        assert_eq!(Body::new(&body_inner), body);
651
652        assert_eq!(messages[5], TestLogSyncMessage::Done);
653    }
654
655    #[tokio::test]
656    async fn log_sync_bidirectional_exchange() {
657        setup_logging();
658
659        const LOG_ID: TestLogId = 0;
660
661        let mut peer_a = Peer::new(0).await;
662        let mut peer_b = Peer::new(1).await;
663
664        let body_a = Body::new("From Alice".as_bytes());
665        let body_b = Body::new("From Bob".as_bytes());
666
667        let (header_a0, _) = peer_a.create_operation(&body_a, LOG_ID).await;
668        let (header_a1, _) = peer_a.create_operation(&body_a, LOG_ID).await;
669
670        let (header_b0, _) = peer_b.create_operation(&body_b, LOG_ID).await;
671        let (header_b1, _) = peer_b.create_operation(&body_b, LOG_ID).await;
672
673        let mut logs = Logs::default();
674        logs.insert(peer_a.id(), vec![LOG_ID]);
675        logs.insert(peer_b.id(), vec![LOG_ID]);
676
677        let (a_session, mut peer_a_event_rx) = peer_a.log_sync_protocol(&logs);
678        let (b_session, mut peer_b_event_rx) = peer_b.log_sync_protocol(&logs);
679
680        let ((_, local_metrics), (_, remote_metrics)) =
681            run_protocol(a_session, b_session).await.unwrap();
682
683        std::assert_matches!(
684            peer_a_event_rx.recv().await.unwrap(),
685            LogSyncEvent::MetricsExchanged { .. }
686        );
687
688        let recv = peer_a_event_rx.recv().await.unwrap();
689        let LogSyncEvent::OperationReceived { operation, .. } = recv else {
690            panic!("Not a LogSyncEvent::OperationReceived: {recv:?}");
691        };
692        let Operation {
693            header,
694            body: body_inner,
695            ..
696        } = *operation;
697        assert_eq!(header, header_b0);
698        assert_eq!(body_inner.unwrap(), body_b);
699
700        let recv = peer_a_event_rx.recv().await.unwrap();
701        let LogSyncEvent::OperationReceived { operation, .. } = recv else {
702            panic!("Not a LogSyncEvent::OperationReceived: {recv:?}");
703        };
704        let Operation {
705            header,
706            body: body_inner,
707            ..
708        } = *operation;
709        assert_eq!(header, header_b1);
710        assert_eq!(body_inner.unwrap(), body_b);
711
712        std::assert_matches!(
713            peer_b_event_rx.recv().await.unwrap(),
714            LogSyncEvent::MetricsExchanged { .. }
715        );
716
717        let recv = peer_b_event_rx.recv().await.unwrap();
718        let LogSyncEvent::OperationReceived { operation, .. } = recv else {
719            panic!("Not a LogSyncEvent::OperationReceived: {recv:?}");
720        };
721        let Operation {
722            header,
723            body: body_inner,
724            ..
725        } = *operation;
726        assert_eq!(header, header_a0);
727        assert_eq!(body_inner.unwrap(), body_a);
728
729        let recv = peer_b_event_rx.recv().await.unwrap();
730        let LogSyncEvent::OperationReceived { operation, .. } = recv else {
731            panic!("Not a LogSyncEvent::OperationReceived: {recv:?}");
732        };
733        let Operation {
734            header,
735            body: body_inner,
736            ..
737        } = *operation;
738        assert_eq!(header, header_a1);
739        assert_eq!(body_inner.unwrap(), body_a);
740
741        assert_eq!(
742            local_metrics.inbound_operations,
743            local_metrics.received_operations
744        );
745        assert_eq!(local_metrics.inbound_bytes, local_metrics.received_bytes);
746        assert_eq!(
747            local_metrics.outbound_operations,
748            local_metrics.sent_operations
749        );
750        assert_eq!(local_metrics.outbound_bytes, local_metrics.sent_bytes);
751        assert_eq!(local_metrics.outbound_bytes, remote_metrics.inbound_bytes);
752        assert_eq!(
753            local_metrics.outbound_operations,
754            remote_metrics.inbound_operations
755        );
756    }
757
758    #[tokio::test]
759    async fn log_sync_unexpected_operation_before_presend() {
760        let mut peer = Peer::new(0).await;
761        const LOG_ID: TestLogId = 1;
762
763        let body = Body::new(b"unexpected op before presend");
764        let (_, header_bytes) = peer.create_operation(&body, LOG_ID).await;
765
766        let mut logs = Logs::default();
767        logs.insert(peer.id(), vec![LOG_ID]);
768
769        let (session, _event_rx) = peer.log_sync_protocol(&logs);
770
771        let messages = vec![
772            TestLogSyncMessage::Have(BTreeMap::from([(peer.id(), BTreeMap::from([(LOG_ID, 0)]))])),
773            TestLogSyncMessage::Operation(header_bytes.clone(), Some(body.to_bytes())),
774            TestLogSyncMessage::PreSync {
775                total_operations: 1,
776                total_bytes: 100,
777            },
778            TestLogSyncMessage::Done,
779        ];
780
781        let result = run_protocol_uni(session, &messages).await;
782
783        std::assert_matches!(result, Err(LogSyncError::UnexpectedMessage(_)));
784    }
785
786    #[tokio::test]
787    async fn log_sync_unexpected_presend_twice() {
788        let mut peer = Peer::new(0).await;
789        const LOG_ID: TestLogId = 1;
790
791        let body = Body::new(b"two presends");
792        peer.create_operation(&body, LOG_ID).await;
793
794        let mut logs = Logs::default();
795        logs.insert(peer.id(), vec![LOG_ID]);
796
797        let (session, _event_rx) = peer.log_sync_protocol(&logs);
798
799        let messages = vec![
800            TestLogSyncMessage::Have(BTreeMap::from([(peer.id(), BTreeMap::from([(LOG_ID, 0)]))])),
801            TestLogSyncMessage::PreSync {
802                total_operations: 1,
803                total_bytes: 32,
804            },
805            TestLogSyncMessage::PreSync {
806                total_operations: 1,
807                total_bytes: 32,
808            },
809            TestLogSyncMessage::Done,
810        ];
811
812        let result = run_protocol_uni(session, &messages).await;
813
814        std::assert_matches!(result, Err(LogSyncError::UnexpectedMessage(_)));
815    }
816
817    #[tokio::test]
818    async fn log_sync_unexpected_done_before_anything() {
819        let mut peer = Peer::new(0).await;
820        let logs = Logs::default();
821
822        let (session, _event_rx) = peer.log_sync_protocol(&logs);
823
824        let messages = vec![TestLogSyncMessage::Done];
825        let result = run_protocol_uni(session, &messages).await;
826
827        std::assert_matches!(result, Err(LogSyncError::UnexpectedMessage(_)));
828    }
829
830    #[tokio::test]
831    async fn log_sync_unexpected_have_after_presend() {
832        let mut peer = Peer::new(0).await;
833        const LOG_ID: TestLogId = 1;
834
835        let body = Body::new(b"bad have order");
836        peer.create_operation(&body, LOG_ID).await;
837
838        let mut logs = Logs::default();
839        logs.insert(peer.id(), vec![LOG_ID]);
840
841        let (session, _event_rx) = peer.log_sync_protocol(&logs);
842
843        let messages = vec![
844            TestLogSyncMessage::Have(BTreeMap::from([(peer.id(), BTreeMap::from([(LOG_ID, 0)]))])),
845            TestLogSyncMessage::PreSync {
846                total_operations: 1,
847                total_bytes: 64,
848            },
849            TestLogSyncMessage::Have(BTreeMap::from([(
850                peer.id(),
851                BTreeMap::from([(LOG_ID, 99)]),
852            )])),
853            TestLogSyncMessage::Done,
854        ];
855
856        let result = run_protocol_uni(session, &messages).await;
857
858        std::assert_matches!(result, Err(LogSyncError::UnexpectedMessage(_)));
859    }
860
861    #[tokio::test]
862    async fn log_sync_with_concurrently_pruned_log() {
863        setup_logging();
864
865        let mut peer_a = Peer::new(0).await;
866        let mut peer_b = Peer::new(1).await;
867        let mut peer_c = Peer::new(2).await;
868
869        let body = Body::new(&[0; 1000]);
870
871        for _ in 0..100 {
872            let _ = peer_a.create_operation(&body, 0).await;
873        }
874
875        for _ in 0..100 {
876            let _ = peer_a.create_operation(&body, 1).await;
877        }
878
879        let mut to_be_pruned_log = vec![];
880
881        for _ in 0..50 {
882            let (header, _) = peer_c.create_operation(&body, 0).await;
883            let id = header.hash();
884
885            let operation = Operation {
886                hash: header.hash(),
887                header,
888                body: Some(body.clone()),
889            };
890
891            tx_unwrap!(
892                &peer_a.store,
893                peer_a
894                    .store
895                    .insert_operation(&id, &operation, &0)
896                    .await
897                    .unwrap()
898            );
899
900            to_be_pruned_log.push(id);
901        }
902
903        let mut logs = Logs::default();
904        logs.insert(peer_a.id(), vec![0, 1]);
905        logs.insert(peer_c.id(), vec![0]);
906
907        let (a_session, _peer_a_event_rx) = peer_a.log_sync_protocol(&logs);
908        let (b_session, mut peer_b_event_rx) = peer_b.log_sync_protocol(&logs);
909
910        let _peer_b_event_tx_clone = b_session.event_tx.clone();
911
912        // Spawn a task to run the sync session.
913        let session_b_handle = {
914            let (mut local_message_tx, local_message_rx) = mpsc::channel(512);
915            let (mut remote_message_tx, remote_message_rx) = mpsc::channel(512);
916            let mut local_message_rx = local_message_rx.map(Ok::<_, ()>);
917            let mut remote_message_rx = remote_message_rx.map(Ok::<_, ()>);
918            tokio::spawn(async move {
919                a_session
920                    .run(&mut local_message_tx, &mut remote_message_rx)
921                    .await
922                    .unwrap();
923            });
924            tokio::spawn(async move {
925                b_session
926                    .run(&mut remote_message_tx, &mut local_message_rx)
927                    .await
928                    .unwrap()
929            })
930        };
931
932        loop {
933            let event = peer_b_event_rx.recv().await.unwrap();
934
935            if let LogSyncEvent::OperationReceived { .. } = event {
936                tx_unwrap!(&peer_a.store, {
937                    <SqliteStore as OperationStore<Operation<()>, Hash>>::delete_operation(
938                        &peer_a.store,
939                        &to_be_pruned_log[0],
940                    )
941                    .await
942                    .unwrap();
943                });
944
945                break;
946            }
947        }
948
949        let (_, metrics) = session_b_handle.await.unwrap();
950        assert_eq!(metrics.inbound_operations, 250);
951        assert_eq!(metrics.received_operations, 249);
952    }
953}