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::{Body, Hash};
521    use p2panda_store::operations::OperationStore;
522    use p2panda_store::{SqliteStore, tx_unwrap};
523
524    use crate::protocols::log_sync::{LogSyncError, LogSyncEvent, Logs, Operation};
525    use crate::test_utils::{
526        Peer, TestLogSyncMessage, run_protocol, run_protocol_uni, setup_logging,
527    };
528    use crate::traits::Protocol;
529
530    #[tokio::test]
531    async fn log_sync_no_operations() {
532        let mut peer: Peer = Peer::new(0).await;
533
534        let (session, mut event_rx) = peer.log_sync_protocol(&Logs::default());
535        let ((_, metrics), mut remote_message_rx) = run_protocol_uni(
536            session,
537            &[
538                TestLogSyncMessage::Have(BTreeMap::default()),
539                TestLogSyncMessage::Done,
540            ],
541        )
542        .await
543        .unwrap();
544
545        let event_metrics = assert_matches!(
546            event_rx.recv().await.unwrap(),
547            LogSyncEvent::MetricsExchanged { metrics } => metrics
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 u64
594            + header_1.payload_size
595            + header_bytes_1.len() as u64
596            + header_2.payload_size
597            + header_bytes_2.len() as u64;
598
599        let metrics = assert_matches!(
600            event_rx.recv().await.unwrap(),
601            LogSyncEvent::MetricsExchanged { metrics } => metrics
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 (header, body_inner) = assert_matches!(
632            &messages[2],
633            TestLogSyncMessage::Operation(header, Some(body)) => (header.clone(), body.clone())
634        );
635        assert_eq!(header, header_bytes_0);
636        assert_eq!(Body::new(&body_inner), body);
637
638        let (header, body_inner) = assert_matches!(
639            &messages[3],
640            TestLogSyncMessage::Operation(header, Some(body)) => (header.clone(), body.clone())
641        );
642        assert_eq!(header, header_bytes_1);
643        assert_eq!(Body::new(&body_inner), body);
644
645        let (header, body_inner) = assert_matches!(
646            &messages[4],
647            TestLogSyncMessage::Operation(header, Some(body)) => (header.clone(), body.clone())
648        );
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: u64 = 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        assert_matches!(
684            peer_a_event_rx.recv().await.unwrap(),
685            LogSyncEvent::MetricsExchanged { .. }
686        );
687
688        let (header, body_inner) = assert_matches!(
689            peer_a_event_rx.recv().await.unwrap(),
690            LogSyncEvent::OperationReceived { operation, .. } => {
691                let Operation { header, body, .. } = *operation;
692                (header, body)
693            }
694        );
695        assert_eq!(header, header_b0);
696        assert_eq!(body_inner.unwrap(), body_b);
697
698        let (header, body_inner) = assert_matches!(
699            peer_a_event_rx.recv().await.unwrap(),
700            LogSyncEvent::OperationReceived { operation, .. } => {
701                let Operation { header, body, .. } = *operation;
702                (header, body)
703            }
704        );
705        assert_eq!(header, header_b1);
706        assert_eq!(body_inner.unwrap(), body_b);
707
708        assert_matches!(
709            peer_b_event_rx.recv().await.unwrap(),
710            LogSyncEvent::MetricsExchanged { .. }
711        );
712
713        let (header, body_inner) = assert_matches!(
714            peer_b_event_rx.recv().await.unwrap(),
715            LogSyncEvent::OperationReceived { operation, .. } => {
716                let Operation { header, body, .. } = *operation;
717                (header, body)
718            }
719        );
720        assert_eq!(header, header_a0);
721        assert_eq!(body_inner.unwrap(), body_a);
722
723        let (header, body_inner) = assert_matches!(
724            peer_b_event_rx.recv().await.unwrap(),
725            LogSyncEvent::OperationReceived { operation, .. } => {
726                let Operation { header, body, .. } = *operation;
727                (header, body)
728            }
729        );
730        assert_eq!(header, header_a1);
731        assert_eq!(body_inner.unwrap(), body_a);
732
733        assert_eq!(
734            local_metrics.inbound_operations,
735            local_metrics.received_operations
736        );
737        assert_eq!(local_metrics.inbound_bytes, local_metrics.received_bytes);
738        assert_eq!(
739            local_metrics.outbound_operations,
740            local_metrics.sent_operations
741        );
742        assert_eq!(local_metrics.outbound_bytes, local_metrics.sent_bytes);
743        assert_eq!(local_metrics.outbound_bytes, remote_metrics.inbound_bytes);
744        assert_eq!(
745            local_metrics.outbound_operations,
746            remote_metrics.inbound_operations
747        );
748    }
749
750    #[tokio::test]
751    async fn log_sync_unexpected_operation_before_presend() {
752        let mut peer = Peer::new(0).await;
753        const LOG_ID: u64 = 1;
754
755        let body = Body::new(b"unexpected op before presend");
756        let (_, header_bytes) = peer.create_operation(&body, LOG_ID).await;
757
758        let mut logs = Logs::default();
759        logs.insert(peer.id(), vec![LOG_ID]);
760
761        let (session, _event_rx) = peer.log_sync_protocol(&logs);
762
763        let messages = vec![
764            TestLogSyncMessage::Have(BTreeMap::from([(peer.id(), BTreeMap::from([(LOG_ID, 0)]))])),
765            TestLogSyncMessage::Operation(header_bytes.clone(), Some(body.to_bytes())),
766            TestLogSyncMessage::PreSync {
767                total_operations: 1,
768                total_bytes: 100,
769            },
770            TestLogSyncMessage::Done,
771        ];
772
773        let result = run_protocol_uni(session, &messages).await;
774
775        assert!(matches!(result, Err(LogSyncError::UnexpectedMessage(_))));
776    }
777
778    #[tokio::test]
779    async fn log_sync_unexpected_presend_twice() {
780        let mut peer = Peer::new(0).await;
781        const LOG_ID: u64 = 1;
782
783        let body = Body::new(b"two presends");
784        peer.create_operation(&body, LOG_ID).await;
785
786        let mut logs = Logs::default();
787        logs.insert(peer.id(), vec![LOG_ID]);
788
789        let (session, _event_rx) = peer.log_sync_protocol(&logs);
790
791        let messages = vec![
792            TestLogSyncMessage::Have(BTreeMap::from([(peer.id(), BTreeMap::from([(LOG_ID, 0)]))])),
793            TestLogSyncMessage::PreSync {
794                total_operations: 1,
795                total_bytes: 32,
796            },
797            TestLogSyncMessage::PreSync {
798                total_operations: 1,
799                total_bytes: 32,
800            },
801            TestLogSyncMessage::Done,
802        ];
803
804        let result = run_protocol_uni(session, &messages).await;
805
806        assert!(matches!(result, Err(LogSyncError::UnexpectedMessage(_))));
807    }
808
809    #[tokio::test]
810    async fn log_sync_unexpected_done_before_anything() {
811        let mut peer = Peer::new(0).await;
812        let logs = Logs::default();
813
814        let (session, _event_rx) = peer.log_sync_protocol(&logs);
815
816        let messages = vec![TestLogSyncMessage::Done];
817        let result = run_protocol_uni(session, &messages).await;
818
819        assert!(matches!(result, Err(LogSyncError::UnexpectedMessage(_))));
820    }
821
822    #[tokio::test]
823    async fn log_sync_unexpected_have_after_presend() {
824        let mut peer = Peer::new(0).await;
825        const LOG_ID: u64 = 1;
826
827        let body = Body::new(b"bad have order");
828        peer.create_operation(&body, LOG_ID).await;
829
830        let mut logs = Logs::default();
831        logs.insert(peer.id(), vec![LOG_ID]);
832
833        let (session, _event_rx) = peer.log_sync_protocol(&logs);
834
835        let messages = vec![
836            TestLogSyncMessage::Have(BTreeMap::from([(peer.id(), BTreeMap::from([(LOG_ID, 0)]))])),
837            TestLogSyncMessage::PreSync {
838                total_operations: 1,
839                total_bytes: 64,
840            },
841            TestLogSyncMessage::Have(BTreeMap::from([(
842                peer.id(),
843                BTreeMap::from([(LOG_ID, 99)]),
844            )])),
845            TestLogSyncMessage::Done,
846        ];
847
848        let result = run_protocol_uni(session, &messages).await;
849
850        assert!(matches!(result, Err(LogSyncError::UnexpectedMessage(_))));
851    }
852
853    #[tokio::test]
854    async fn log_sync_with_concurrently_pruned_log() {
855        setup_logging();
856
857        let mut peer_a = Peer::new(0).await;
858        let mut peer_b = Peer::new(1).await;
859        let mut peer_c = Peer::new(2).await;
860
861        let body = Body::new(&[0; 1000]);
862
863        for _ in 0..100 {
864            let _ = peer_a.create_operation(&body, 0).await;
865        }
866
867        for _ in 0..100 {
868            let _ = peer_a.create_operation(&body, 1).await;
869        }
870
871        let mut to_be_pruned_log = vec![];
872
873        for _ in 0..50 {
874            let (header, _) = peer_c.create_operation(&body, 0).await;
875            let id = header.hash();
876
877            let operation = Operation {
878                hash: header.hash(),
879                header,
880                body: Some(body.clone()),
881            };
882
883            tx_unwrap!(
884                &peer_a.store,
885                peer_a
886                    .store
887                    .insert_operation(&id, &operation, &0)
888                    .await
889                    .unwrap()
890            );
891
892            to_be_pruned_log.push(id);
893        }
894
895        let mut logs = Logs::default();
896        logs.insert(peer_a.id(), vec![0, 1]);
897        logs.insert(peer_c.id(), vec![0]);
898
899        let (a_session, _peer_a_event_rx) = peer_a.log_sync_protocol(&logs);
900        let (b_session, mut peer_b_event_rx) = peer_b.log_sync_protocol(&logs);
901
902        let _peer_b_event_tx_clone = b_session.event_tx.clone();
903
904        // Spawn a task to run the sync session.
905        let session_b_handle = {
906            let (mut local_message_tx, local_message_rx) = mpsc::channel(512);
907            let (mut remote_message_tx, remote_message_rx) = mpsc::channel(512);
908            let mut local_message_rx = local_message_rx.map(Ok::<_, ()>);
909            let mut remote_message_rx = remote_message_rx.map(Ok::<_, ()>);
910            tokio::spawn(async move {
911                a_session
912                    .run(&mut local_message_tx, &mut remote_message_rx)
913                    .await
914                    .unwrap();
915            });
916            tokio::spawn(async move {
917                b_session
918                    .run(&mut remote_message_tx, &mut local_message_rx)
919                    .await
920                    .unwrap()
921            })
922        };
923
924        loop {
925            let event = peer_b_event_rx.recv().await.unwrap();
926
927            if let LogSyncEvent::OperationReceived { .. } = event {
928                tx_unwrap!(&peer_a.store, {
929                    <SqliteStore as OperationStore<Operation<()>, Hash, ()>>::delete_operation(
930                        &peer_a.store,
931                        &to_be_pruned_log[0],
932                    )
933                    .await
934                    .unwrap();
935                });
936
937                break;
938            }
939        }
940
941        let (_, metrics) = session_b_handle.await.unwrap();
942        assert_eq!(metrics.inbound_operations, 250);
943        assert_eq!(metrics.received_operations, 249);
944    }
945}