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