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::{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, 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: u64,
47        outbound_bytes: u64,
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, u64, 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 u64;
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 u64;
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, u64, 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"))]
408#[serde(tag = "type", content = "value")]
409pub enum LogSyncMessage<L>
410where
411    L: LogId,
412{
413    Have(LogHeights<VerifyingKey, L>),
414    PreSync {
415        total_operations: u64,
416        total_bytes: u64,
417    },
418    Operation(Vec<u8>, Option<Vec<u8>>),
419    Done,
420}
421
422impl<L> Display for LogSyncMessage<L>
423where
424    L: LogId,
425{
426    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
427        let value = match self {
428            LogSyncMessage::Have(_) => "have",
429            LogSyncMessage::PreSync { .. } => "pre_sync",
430            LogSyncMessage::Operation(_, _) => "operation",
431            LogSyncMessage::Done => "done",
432        };
433
434        write!(f, "{value}")
435    }
436}
437
438/// Events emitted from log sync sessions.
439#[derive(Clone, Debug, PartialEq)]
440pub enum LogSyncEvent<E> {
441    /// We have calculated and sent the estimated bytes and operations to be sent during this sync
442    /// session and received the remotes' estimates.
443    ///
444    /// This is the first event to occur and will only be sent once.
445    MetricsExchanged { metrics: LogSyncMetrics },
446
447    /// An operation has been received from the remote.
448    OperationReceived {
449        operation: Box<Operation<E>>,
450        metrics: LogSyncMetrics,
451    },
452}
453
454/// Sync metrics emitted in event messages.
455#[derive(Clone, Debug, PartialEq, Default)]
456pub struct LogSyncMetrics {
457    pub outbound_operations: u64,
458    pub outbound_bytes: u64,
459    pub inbound_operations: u64,
460    pub inbound_bytes: u64,
461    pub sent_operations: u64,
462    pub sent_bytes: u64,
463    pub received_operations: u64,
464    pub received_bytes: u64,
465}
466
467/// Protocol error types.
468#[derive(Debug, Error)]
469pub enum LogSyncError {
470    #[error(transparent)]
471    Decode(#[from] DecodeError),
472
473    #[error("log store error: {0}")]
474    LogStore(String),
475
476    #[error("operation store error: {0}")]
477    OperationStore(String),
478
479    #[error("no active receivers when broadcasting")]
480    BroadcastSend,
481
482    #[error("log sync error sending on message sink: {0}")]
483    MessageSink(String),
484
485    #[error("log sync error receiving from message stream: {0}")]
486    MessageStream(String),
487
488    #[error("remote unexpectedly closed stream during initial sync")]
489    UnexpectedStreamClosure,
490
491    #[error("log sync received unexpected protocol message: {0}")]
492    UnexpectedMessage(String),
493}
494
495/// Returns a displayable string representing the underlying value in a short format, easy to read
496/// during debugging and logging.
497pub trait ShortFormat {
498    fn fmt_short(&self) -> String;
499}
500
501impl ShortFormat for VerifyingKey {
502    fn fmt_short(&self) -> String {
503        self.to_hex()[0..10].to_string()
504    }
505}
506
507impl ShortFormat for Hash {
508    fn fmt_short(&self) -> String {
509        self.to_hex()[0..5].to_string()
510    }
511}
512
513#[cfg(test)]
514mod tests {
515    use std::collections::BTreeMap;
516
517    use assert_matches::assert_matches;
518    use futures::StreamExt;
519    use futures::channel::mpsc;
520    use p2panda_core::test_utils::setup_logging;
521    use p2panda_core::{Body, Hash};
522    use p2panda_store::operations::OperationStore;
523    use p2panda_store::{SqliteStore, tx_unwrap};
524
525    use crate::protocols::log_sync::{LogSyncError, LogSyncEvent, Logs, Operation};
526    use crate::test_utils::{Peer, TestLogSyncMessage, run_protocol, run_protocol_uni};
527    use crate::traits::Protocol;
528
529    #[tokio::test]
530    async fn log_sync_no_operations() {
531        let mut peer: Peer = Peer::new(0).await;
532
533        let (session, mut event_rx) = peer.log_sync_protocol(&Logs::default());
534        let ((_, metrics), mut remote_message_rx) = run_protocol_uni(
535            session,
536            &[
537                TestLogSyncMessage::Have(BTreeMap::default()),
538                TestLogSyncMessage::Done,
539            ],
540        )
541        .await
542        .unwrap();
543
544        let event_metrics = assert_matches!(
545            event_rx.recv().await.unwrap(),
546            LogSyncEvent::MetricsExchanged { metrics } => metrics
547        );
548
549        assert_eq!(metrics.outbound_operations, 0);
550        assert_eq!(metrics.outbound_bytes, 0);
551        assert_eq!(metrics.inbound_operations, 0);
552        assert_eq!(metrics.inbound_bytes, 0);
553
554        assert_eq!(event_metrics, metrics);
555
556        assert_eq!(
557            remote_message_rx.recv().await.unwrap(),
558            TestLogSyncMessage::Have(BTreeMap::default())
559        );
560
561        assert_eq!(
562            remote_message_rx.recv().await.unwrap(),
563            TestLogSyncMessage::Done
564        );
565    }
566
567    #[tokio::test]
568    async fn log_sync_some_operations() {
569        let mut peer = Peer::new(0).await;
570        let log_id = 0;
571
572        let body = Body::new("Hello, Sloth!".as_bytes());
573        let (header_0, header_bytes_0) = peer.create_operation(&body, log_id).await;
574        let (header_1, header_bytes_1) = peer.create_operation(&body, log_id).await;
575        let (header_2, header_bytes_2) = peer.create_operation(&body, log_id).await;
576
577        let mut logs = Logs::default();
578        logs.insert(peer.id(), vec![log_id]);
579
580        let (session, mut event_rx) = peer.log_sync_protocol(&logs);
581        let ((_, final_metrics), remote_message_rx) = run_protocol_uni(
582            session,
583            &[
584                TestLogSyncMessage::Have(BTreeMap::default()),
585                TestLogSyncMessage::Done,
586            ],
587        )
588        .await
589        .unwrap();
590
591        let expected_bytes = header_0.payload_size
592            + header_bytes_0.len() as u64
593            + header_1.payload_size
594            + header_bytes_1.len() as u64
595            + header_2.payload_size
596            + header_bytes_2.len() as u64;
597
598        let metrics = assert_matches!(
599            event_rx.recv().await.unwrap(),
600            LogSyncEvent::MetricsExchanged { metrics } => metrics
601        );
602
603        assert_eq!(metrics.outbound_operations, 3);
604        assert_eq!(metrics.outbound_bytes, expected_bytes);
605        assert_eq!(metrics.inbound_operations, 0);
606        assert_eq!(metrics.inbound_bytes, 0);
607
608        assert_eq!(final_metrics.sent_operations, 3);
609        assert_eq!(final_metrics.sent_bytes, expected_bytes);
610        assert_eq!(final_metrics.received_operations, 0);
611        assert_eq!(final_metrics.received_bytes, 0);
612
613        let messages = remote_message_rx.collect::<Vec<_>>().await;
614
615        assert_eq!(messages.len(), 6);
616
617        assert_eq!(
618            messages[0],
619            TestLogSyncMessage::Have(BTreeMap::from([(peer.id(), BTreeMap::from([(0, 2)]))]))
620        );
621
622        assert_eq!(
623            messages[1],
624            TestLogSyncMessage::PreSync {
625                total_operations: 3,
626                total_bytes: expected_bytes
627            }
628        );
629
630        let (header, body_inner) = assert_matches!(
631            &messages[2],
632            TestLogSyncMessage::Operation(header, Some(body)) => (header.clone(), body.clone())
633        );
634        assert_eq!(header, header_bytes_0);
635        assert_eq!(Body::new(&body_inner), body);
636
637        let (header, body_inner) = assert_matches!(
638            &messages[3],
639            TestLogSyncMessage::Operation(header, Some(body)) => (header.clone(), body.clone())
640        );
641        assert_eq!(header, header_bytes_1);
642        assert_eq!(Body::new(&body_inner), body);
643
644        let (header, body_inner) = assert_matches!(
645            &messages[4],
646            TestLogSyncMessage::Operation(header, Some(body)) => (header.clone(), body.clone())
647        );
648        assert_eq!(header, header_bytes_2);
649        assert_eq!(Body::new(&body_inner), body);
650
651        assert_eq!(messages[5], TestLogSyncMessage::Done);
652    }
653
654    #[tokio::test]
655    async fn log_sync_bidirectional_exchange() {
656        setup_logging();
657
658        const LOG_ID: u64 = 0;
659
660        let mut peer_a = Peer::new(0).await;
661        let mut peer_b = Peer::new(1).await;
662
663        let body_a = Body::new("From Alice".as_bytes());
664        let body_b = Body::new("From Bob".as_bytes());
665
666        let (header_a0, _) = peer_a.create_operation(&body_a, LOG_ID).await;
667        let (header_a1, _) = peer_a.create_operation(&body_a, LOG_ID).await;
668
669        let (header_b0, _) = peer_b.create_operation(&body_b, LOG_ID).await;
670        let (header_b1, _) = peer_b.create_operation(&body_b, LOG_ID).await;
671
672        let mut logs = Logs::default();
673        logs.insert(peer_a.id(), vec![LOG_ID]);
674        logs.insert(peer_b.id(), vec![LOG_ID]);
675
676        let (a_session, mut peer_a_event_rx) = peer_a.log_sync_protocol(&logs);
677        let (b_session, mut peer_b_event_rx) = peer_b.log_sync_protocol(&logs);
678
679        let ((_, local_metrics), (_, remote_metrics)) =
680            run_protocol(a_session, b_session).await.unwrap();
681
682        assert_matches!(
683            peer_a_event_rx.recv().await.unwrap(),
684            LogSyncEvent::MetricsExchanged { .. }
685        );
686
687        let (header, body_inner) = assert_matches!(
688            peer_a_event_rx.recv().await.unwrap(),
689            LogSyncEvent::OperationReceived { operation, .. } => {
690                let Operation { header, body, .. } = *operation;
691                (header, body)
692            }
693        );
694        assert_eq!(header, header_b0);
695        assert_eq!(body_inner.unwrap(), body_b);
696
697        let (header, body_inner) = assert_matches!(
698            peer_a_event_rx.recv().await.unwrap(),
699            LogSyncEvent::OperationReceived { operation, .. } => {
700                let Operation { header, body, .. } = *operation;
701                (header, body)
702            }
703        );
704        assert_eq!(header, header_b1);
705        assert_eq!(body_inner.unwrap(), body_b);
706
707        assert_matches!(
708            peer_b_event_rx.recv().await.unwrap(),
709            LogSyncEvent::MetricsExchanged { .. }
710        );
711
712        let (header, body_inner) = assert_matches!(
713            peer_b_event_rx.recv().await.unwrap(),
714            LogSyncEvent::OperationReceived { operation, .. } => {
715                let Operation { header, body, .. } = *operation;
716                (header, body)
717            }
718        );
719        assert_eq!(header, header_a0);
720        assert_eq!(body_inner.unwrap(), body_a);
721
722        let (header, body_inner) = assert_matches!(
723            peer_b_event_rx.recv().await.unwrap(),
724            LogSyncEvent::OperationReceived { operation, .. } => {
725                let Operation { header, body, .. } = *operation;
726                (header, body)
727            }
728        );
729        assert_eq!(header, header_a1);
730        assert_eq!(body_inner.unwrap(), body_a);
731
732        assert_eq!(
733            local_metrics.inbound_operations,
734            local_metrics.received_operations
735        );
736        assert_eq!(local_metrics.inbound_bytes, local_metrics.received_bytes);
737        assert_eq!(
738            local_metrics.outbound_operations,
739            local_metrics.sent_operations
740        );
741        assert_eq!(local_metrics.outbound_bytes, local_metrics.sent_bytes);
742        assert_eq!(local_metrics.outbound_bytes, remote_metrics.inbound_bytes);
743        assert_eq!(
744            local_metrics.outbound_operations,
745            remote_metrics.inbound_operations
746        );
747    }
748
749    #[tokio::test]
750    async fn log_sync_unexpected_operation_before_presend() {
751        let mut peer = Peer::new(0).await;
752        const LOG_ID: u64 = 1;
753
754        let body = Body::new(b"unexpected op before presend");
755        let (_, header_bytes) = peer.create_operation(&body, LOG_ID).await;
756
757        let mut logs = Logs::default();
758        logs.insert(peer.id(), vec![LOG_ID]);
759
760        let (session, _event_rx) = peer.log_sync_protocol(&logs);
761
762        let messages = vec![
763            TestLogSyncMessage::Have(BTreeMap::from([(peer.id(), BTreeMap::from([(LOG_ID, 0)]))])),
764            TestLogSyncMessage::Operation(header_bytes.clone(), Some(body.to_bytes())),
765            TestLogSyncMessage::PreSync {
766                total_operations: 1,
767                total_bytes: 100,
768            },
769            TestLogSyncMessage::Done,
770        ];
771
772        let result = run_protocol_uni(session, &messages).await;
773
774        assert!(matches!(result, Err(LogSyncError::UnexpectedMessage(_))));
775    }
776
777    #[tokio::test]
778    async fn log_sync_unexpected_presend_twice() {
779        let mut peer = Peer::new(0).await;
780        const LOG_ID: u64 = 1;
781
782        let body = Body::new(b"two presends");
783        peer.create_operation(&body, LOG_ID).await;
784
785        let mut logs = Logs::default();
786        logs.insert(peer.id(), vec![LOG_ID]);
787
788        let (session, _event_rx) = peer.log_sync_protocol(&logs);
789
790        let messages = vec![
791            TestLogSyncMessage::Have(BTreeMap::from([(peer.id(), BTreeMap::from([(LOG_ID, 0)]))])),
792            TestLogSyncMessage::PreSync {
793                total_operations: 1,
794                total_bytes: 32,
795            },
796            TestLogSyncMessage::PreSync {
797                total_operations: 1,
798                total_bytes: 32,
799            },
800            TestLogSyncMessage::Done,
801        ];
802
803        let result = run_protocol_uni(session, &messages).await;
804
805        assert!(matches!(result, Err(LogSyncError::UnexpectedMessage(_))));
806    }
807
808    #[tokio::test]
809    async fn log_sync_unexpected_done_before_anything() {
810        let mut peer = Peer::new(0).await;
811        let logs = Logs::default();
812
813        let (session, _event_rx) = peer.log_sync_protocol(&logs);
814
815        let messages = vec![TestLogSyncMessage::Done];
816        let result = run_protocol_uni(session, &messages).await;
817
818        assert!(matches!(result, Err(LogSyncError::UnexpectedMessage(_))));
819    }
820
821    #[tokio::test]
822    async fn log_sync_unexpected_have_after_presend() {
823        let mut peer = Peer::new(0).await;
824        const LOG_ID: u64 = 1;
825
826        let body = Body::new(b"bad have order");
827        peer.create_operation(&body, LOG_ID).await;
828
829        let mut logs = Logs::default();
830        logs.insert(peer.id(), vec![LOG_ID]);
831
832        let (session, _event_rx) = peer.log_sync_protocol(&logs);
833
834        let messages = vec![
835            TestLogSyncMessage::Have(BTreeMap::from([(peer.id(), BTreeMap::from([(LOG_ID, 0)]))])),
836            TestLogSyncMessage::PreSync {
837                total_operations: 1,
838                total_bytes: 64,
839            },
840            TestLogSyncMessage::Have(BTreeMap::from([(
841                peer.id(),
842                BTreeMap::from([(LOG_ID, 99)]),
843            )])),
844            TestLogSyncMessage::Done,
845        ];
846
847        let result = run_protocol_uni(session, &messages).await;
848
849        assert!(matches!(result, Err(LogSyncError::UnexpectedMessage(_))));
850    }
851
852    #[tokio::test]
853    async fn log_sync_with_concurrently_pruned_log() {
854        setup_logging();
855
856        let mut peer_a = Peer::new(0).await;
857        let mut peer_b = Peer::new(1).await;
858        let mut peer_c = Peer::new(2).await;
859
860        let body = Body::new(&[0; 1000]);
861
862        for _ in 0..100 {
863            let _ = peer_a.create_operation(&body, 0).await;
864        }
865
866        for _ in 0..100 {
867            let _ = peer_a.create_operation(&body, 1).await;
868        }
869
870        let mut to_be_pruned_log = vec![];
871
872        for _ in 0..50 {
873            let (header, _) = peer_c.create_operation(&body, 0).await;
874            let id = header.hash();
875
876            let operation = Operation {
877                hash: header.hash(),
878                header,
879                body: Some(body.clone()),
880            };
881
882            tx_unwrap!(
883                &peer_a.store,
884                peer_a
885                    .store
886                    .insert_operation(&id, &operation, &0)
887                    .await
888                    .unwrap()
889            );
890
891            to_be_pruned_log.push(id);
892        }
893
894        let mut logs = Logs::default();
895        logs.insert(peer_a.id(), vec![0, 1]);
896        logs.insert(peer_c.id(), vec![0]);
897
898        let (a_session, _peer_a_event_rx) = peer_a.log_sync_protocol(&logs);
899        let (b_session, mut peer_b_event_rx) = peer_b.log_sync_protocol(&logs);
900
901        let _peer_b_event_tx_clone = b_session.event_tx.clone();
902
903        // Spawn a task to run the sync session.
904        let session_b_handle = {
905            let (mut local_message_tx, local_message_rx) = mpsc::channel(512);
906            let (mut remote_message_tx, remote_message_rx) = mpsc::channel(512);
907            let mut local_message_rx = local_message_rx.map(Ok::<_, ()>);
908            let mut remote_message_rx = remote_message_rx.map(Ok::<_, ()>);
909            tokio::spawn(async move {
910                a_session
911                    .run(&mut local_message_tx, &mut remote_message_rx)
912                    .await
913                    .unwrap();
914            });
915            tokio::spawn(async move {
916                b_session
917                    .run(&mut remote_message_tx, &mut local_message_rx)
918                    .await
919                    .unwrap()
920            })
921        };
922
923        loop {
924            let event = peer_b_event_rx.recv().await.unwrap();
925
926            if let LogSyncEvent::OperationReceived { .. } = event {
927                tx_unwrap!(&peer_a.store, {
928                    <SqliteStore as OperationStore<Operation<()>, Hash, ()>>::delete_operation(
929                        &peer_a.store,
930                        &to_be_pruned_log[0],
931                    )
932                    .await
933                    .unwrap();
934                });
935
936                break;
937            }
938        }
939
940        let (_, metrics) = session_b_handle.await.unwrap();
941        assert_eq!(metrics.inbound_operations, 250);
942        assert_eq!(metrics.received_operations, 249);
943    }
944}