1mod event_stream;
8mod session_map;
9
10pub use event_stream::ManagerEventStream;
11use p2panda_store::topics::TopicStore;
12pub use session_map::SessionTopicMap;
13
14use std::collections::HashMap;
15use std::fmt::Debug;
16use std::hash::Hash as StdHash;
17use std::marker::PhantomData;
18use std::pin::Pin;
19
20use futures::channel::mpsc;
21use futures::future::ready;
22use futures::stream::SelectAll;
23use futures::{Sink, SinkExt, Stream, StreamExt};
24use p2panda_core::{Extensions, Hash, LogId, Operation, VerifyingKey};
25use p2panda_store::logs::LogStore;
26use serde::{Deserialize, Serialize};
27use thiserror::Error;
28use tokio::sync::broadcast;
29use tokio_stream::wrappers::BroadcastStream;
30use tokio_stream::wrappers::errors::BroadcastStreamRecvError;
31use tracing::debug;
32
33use crate::dedup::DeduplicationBuffer;
34use crate::manager::event_stream::{ManagerEventStreamState, StreamDebug};
35use crate::protocols::{TopicLogSync, TopicLogSyncError, TopicLogSyncEvent};
36use crate::traits::Manager;
37use crate::{FromSync, SessionConfig, ToSync};
38
39static CHANNEL_BUFFER: usize = 1028;
40
41pub type ToTopicSync<E> = ToSync<Operation<E>>;
42
43#[allow(clippy::type_complexity)]
60#[derive(Debug)]
61pub struct TopicSyncManager<T, S, L, E>
62where
63 T: Clone,
64 E: Extensions,
65{
66 store: S,
67 session_topic_map: SessionTopicMap<T, mpsc::Sender<ToTopicSync<E>>>,
68 from_session_tx: HashMap<(u64, VerifyingKey), broadcast::Sender<TopicLogSyncEvent<E>>>,
69 from_session_rx: HashMap<(u64, VerifyingKey), broadcast::Receiver<TopicLogSyncEvent<E>>>,
70 manager_tx: Vec<mpsc::Sender<SessionStream<T, E>>>,
71 _phantom: PhantomData<L>,
72}
73
74#[derive(Debug)]
75pub(crate) struct SessionStream<T, E>
76where
77 T: Clone,
78 E: Clone,
79{
80 pub session_id: u64,
81 pub topic: T,
82 pub remote: VerifyingKey,
83 pub event_rx: broadcast::Receiver<TopicLogSyncEvent<E>>,
84 pub live_tx: mpsc::Sender<ToTopicSync<E>>,
85}
86
87impl<T, S, L, E> TopicSyncManager<T, S, L, E>
88where
89 T: Clone,
90 E: Extensions,
91{
92 pub fn new(store: S) -> Self {
93 Self {
94 store,
95 manager_tx: Default::default(),
96 session_topic_map: Default::default(),
97 from_session_tx: Default::default(),
98 from_session_rx: Default::default(),
99 _phantom: PhantomData,
100 }
101 }
102}
103
104impl<T, S, L, E> Manager<T> for TopicSyncManager<T, S, L, E>
105where
106 T: Clone + Debug + Eq + StdHash + Serialize + for<'a> Deserialize<'a> + Send + 'static,
107 S: LogStore<Operation<E>, VerifyingKey, L, u64, Hash>
108 + TopicStore<T, VerifyingKey, L>
109 + Clone
110 + Send
111 + 'static,
112 L: LogId + Debug + Send + 'static,
113 E: Extensions + Send + 'static,
114{
115 type Protocol = TopicLogSync<T, S, L, E>;
116 type Args = S;
117 type Event = TopicLogSyncEvent<E>;
118 type Message = Operation<E>;
119 type Error = TopicSyncManagerError;
120
121 fn from_args(store: Self::Args) -> Self {
123 Self::new(store)
124 }
125
126 async fn session(&mut self, session_id: u64, config: &SessionConfig<T>) -> Self::Protocol {
128 let (live_tx, live_rx) = mpsc::channel(CHANNEL_BUFFER);
129 let (event_tx, event_rx) = broadcast::channel::<Self::Event>(CHANNEL_BUFFER);
130
131 self.from_session_tx
132 .insert((session_id, config.remote), event_tx.clone());
133
134 self.from_session_rx
135 .insert((session_id, config.remote), event_rx);
136
137 self.session_topic_map
138 .insert_with_topic(session_id, config.topic.clone(), live_tx.clone());
139
140 for manager_tx in self.manager_tx.iter_mut() {
141 if let Err(err) = manager_tx
142 .send(SessionStream {
143 session_id,
144 topic: config.topic.clone(),
145 remote: config.remote,
146 event_rx: event_tx.subscribe(),
147 live_tx: live_tx.clone(),
148 })
149 .await
150 {
151 debug!("manager handle dropped: {err:?}");
152 };
153 }
154
155 let live_rx = if config.live_mode {
156 Some(live_rx)
157 } else {
158 None
159 };
160
161 TopicLogSync::new(config.topic.clone(), self.store.clone(), live_rx, event_tx)
162 }
163
164 async fn session_handle(
166 &self,
167 session_id: u64,
168 ) -> Option<Pin<Box<dyn Sink<ToTopicSync<E>, Error = Self::Error>>>> {
169 let map_fn = |to_sync: ToSync<Self::Message>| {
170 ready({
171 match to_sync {
172 ToSync::Payload(operation) => Ok::<_, Self::Error>(ToSync::Payload(operation)),
173 ToSync::Close => Ok::<_, Self::Error>(ToSync::Close),
174 }
175 })
176 };
177
178 self.session_topic_map.sender(session_id).map(|tx| {
179 Box::pin(tx.clone().with(map_fn))
180 as Pin<Box<dyn Sink<ToTopicSync<E>, Error = Self::Error>>>
181 })
182 }
183
184 fn subscribe(&mut self) -> impl Stream<Item = FromSync<Self::Event>> + Send + Unpin + 'static {
186 let (manager_tx, manager_rx) = mpsc::channel(CHANNEL_BUFFER);
187 self.manager_tx.push(manager_tx);
188
189 let mut session_rx_set = SelectAll::new();
190 for ((id, remote), tx) in self.from_session_tx.iter() {
191 let session_id = *id;
192 let remote = *remote;
193 let stream = BroadcastStream::new(tx.subscribe());
194
195 #[allow(clippy::type_complexity)]
196 let stream: Pin<
197 Box<dyn StreamDebug<Option<FromSync<TopicLogSyncEvent<E>>>>>,
198 > = Box::pin(stream.map(Box::new(
199 move |event: Result<TopicLogSyncEvent<E>, BroadcastStreamRecvError>| {
200 event.ok().map(|event| FromSync {
201 session_id,
202 remote,
203 event,
204 })
205 },
206 )));
207 session_rx_set.push(stream);
208 }
209
210 let state = ManagerEventStreamState {
211 manager_rx,
212 session_rx_set,
213 session_topic_map: self.session_topic_map.clone(),
214 dedup: DeduplicationBuffer::default(),
215 };
216
217 let stream = ManagerEventStream {
218 state: Some(state),
219 pending: Default::default(),
220 };
221
222 Box::pin(stream)
223 }
224}
225
226#[derive(Debug, Error)]
228pub enum TopicSyncManagerError {
229 #[error(transparent)]
230 TopicLogSync(#[from] TopicLogSyncError),
231
232 #[error("received operation before topic agreed")]
233 OperationBeforeTopic,
234
235 #[error(transparent)]
236 Send(#[from] mpsc::SendError),
237}
238#[cfg(test)]
239mod tests {
240 use std::collections::BTreeMap;
241 use std::time::Duration;
242
243 use assert_matches::assert_matches;
244 use futures::channel::mpsc;
245 use futures::{SinkExt, StreamExt};
246 use p2panda_core::test_utils::setup_logging;
247 use p2panda_core::{Body, Operation, Topic};
248 use p2panda_store::SqliteStore;
249
250 use crate::protocols::TopicLogSyncEvent;
251 use crate::test_utils::{Peer, TestTopicSyncManager, drain_stream, run_protocol};
252 use crate::traits::{Manager, Protocol};
253 use crate::{FromSync, SessionConfig, ToSync};
254
255 #[tokio::test]
256 async fn from_args() {
257 let store = SqliteStore::temporary().await;
258 let _: TestTopicSyncManager = Manager::from_args(store);
259 }
260
261 #[tokio::test]
262 async fn manager_e2e() {
263 setup_logging();
264
265 const LOG_ID: u64 = 0;
266 const SESSION_ID: u64 = 0;
267
268 let topic = Topic::random();
269
270 let mut peer_a = Peer::new(0).await;
272 let body = Body::new("Hello from Peer A".as_bytes());
273 let _ = peer_a.create_operation(&body, LOG_ID).await;
274 let logs = BTreeMap::from([(peer_a.id(), vec![LOG_ID])]);
275 peer_a.associate(&topic, &logs).await;
276 let mut peer_a_manager = TestTopicSyncManager::new(peer_a.store.clone());
277
278 let mut peer_b = Peer::new(1).await;
280 let body = Body::new("Hello from Peer B".as_bytes());
281 let _ = peer_b.create_operation(&body, LOG_ID).await;
282 let logs = BTreeMap::from([(peer_b.id(), vec![LOG_ID])]);
283 peer_b.associate(&topic, &logs).await;
284 let mut peer_b_manager = TestTopicSyncManager::new(peer_b.store.clone());
285
286 let config = SessionConfig {
287 topic,
288 remote: peer_b.id(),
289 live_mode: true,
290 };
291
292 let mut event_stream_a = peer_a_manager.subscribe();
294 let mut event_stream_b = peer_b_manager.subscribe();
295
296 let peer_a_session = peer_a_manager.session(SESSION_ID, &config).await;
298 let peer_b_session = peer_b_manager.session(SESSION_ID, &config).await;
299
300 let mut peer_a_handle = peer_a_manager.session_handle(SESSION_ID).await.unwrap();
302
303 let (header_1, _) = peer_a.create_operation_no_insert(&body, LOG_ID).await;
305 peer_a_handle
306 .send(ToSync::Payload(Operation {
307 hash: header_1.hash(),
308 header: header_1.clone(),
309 body: Some(body.clone()),
310 }))
311 .await
312 .unwrap();
313 peer_a_handle.send(ToSync::Close).await.unwrap();
314
315 run_protocol(peer_a_session, peer_b_session).await.unwrap();
317
318 for index in 0..=4 {
320 let event = event_stream_a.next().await.unwrap();
321 assert_eq!(event.session_id(), 0);
322 match index {
323 0 => assert_matches!(
324 event,
325 FromSync {
326 event: TopicLogSyncEvent::SyncStarted { .. },
327 ..
328 }
329 ),
330 1 => assert_matches!(
331 event,
332 FromSync {
333 event: TopicLogSyncEvent::OperationReceived { .. },
334 ..
335 }
336 ),
337 2 => assert_matches!(
338 event,
339 FromSync {
340 event: TopicLogSyncEvent::SyncFinished { .. },
341 ..
342 }
343 ),
344 3 => assert_matches!(
345 event,
346 FromSync {
347 event: TopicLogSyncEvent::LiveModeStarted,
348 ..
349 }
350 ),
351 4 => assert_matches!(
352 event,
353 FromSync {
354 event: TopicLogSyncEvent::SessionFinished { .. },
355 ..
356 }
357 ),
358 _ => panic!(),
359 }
360 }
361
362 for index in 0..=5 {
364 let event = event_stream_b.next().await.unwrap();
365 match index {
366 0 => assert_matches!(
367 event,
368 FromSync {
369 session_id: 0,
370 event: TopicLogSyncEvent::SyncStarted { .. },
371 ..
372 }
373 ),
374 1 => assert_matches!(
375 event,
376 FromSync {
377 session_id: 0,
378 event: TopicLogSyncEvent::OperationReceived { .. },
379 ..
380 }
381 ),
382 2 => assert_matches!(
383 event,
384 FromSync {
385 session_id: 0,
386 event: TopicLogSyncEvent::SyncFinished { .. },
387 ..
388 }
389 ),
390 3 => assert_matches!(
391 event,
392 FromSync {
393 event: TopicLogSyncEvent::LiveModeStarted,
394 ..
395 }
396 ),
397 4 => assert_matches!(
398 event,
399 FromSync {
400 session_id: 0,
401 event: TopicLogSyncEvent::OperationReceived { .. },
402 ..
403 }
404 ),
405 5 => assert_matches!(
406 event,
407 FromSync {
408 event: TopicLogSyncEvent::SessionFinished { .. },
409 ..
410 }
411 ),
412 _ => panic!(),
413 }
414 }
415 }
416
417 #[tokio::test]
418 async fn live_mode_three_peer_forwarding() {
419 setup_logging();
420
421 const LOG_ID: u64 = 0;
422 const SESSION_AB: u64 = 0;
423 const SESSION_AC: u64 = 1;
424 const SESSION_BA: u64 = 2;
425 const SESSION_CA: u64 = 3;
426
427 let topic = Topic::random();
428
429 let mut peer_a = Peer::new(0).await;
431 let body_a = Body::new("Hello from A".as_bytes());
432 let (peer_a_header_0, _) = peer_a.create_operation(&body_a, LOG_ID).await;
433 let mut manager_a = TestTopicSyncManager::new(peer_a.store.clone());
434
435 let mut peer_b = Peer::new(1).await;
437 let body_b = Body::new("Hello from B".as_bytes());
438 let (peer_b_header_0, _) = peer_b.create_operation(&body_b, LOG_ID).await;
439 let mut manager_b = TestTopicSyncManager::new(peer_b.store.clone());
440
441 let mut peer_c = Peer::new(2).await;
443 let body_c = Body::new("Hello from C".as_bytes());
444 let (peer_c_header_0, _) = peer_c.create_operation(&body_c, LOG_ID).await;
445 let mut manager_c = TestTopicSyncManager::new(peer_c.store.clone());
446
447 let logs = BTreeMap::from([
448 (peer_a.id(), vec![LOG_ID]),
449 (peer_b.id(), vec![LOG_ID]),
450 (peer_c.id(), vec![LOG_ID]),
451 ]);
452 peer_a.associate(&topic, &logs).await;
453 peer_b.associate(&topic, &logs).await;
454 peer_c.associate(&topic, &logs).await;
455
456 let mut config = SessionConfig {
458 topic: topic.clone(),
459 remote: peer_b.id(),
460 live_mode: true,
461 };
462 let session_ab = manager_a.session(SESSION_AB, &config).await;
463 config.remote = peer_a.id();
464 let session_ba = manager_b.session(SESSION_BA, &config).await;
465
466 let mut config = SessionConfig {
468 topic: topic.clone(),
469 remote: peer_c.id(),
470 live_mode: true,
471 };
472 let session_ac = manager_a.session(SESSION_AC, &config).await;
473 config.remote = peer_a.id();
474 let session_ca = manager_c.session(SESSION_CA, &config).await;
475
476 let mut event_stream_a = manager_a.subscribe();
477 let mut event_stream_b = manager_b.subscribe();
478 let mut event_stream_c = manager_c.subscribe();
479
480 {
482 let (mut local_message_tx, local_message_rx) = mpsc::channel(128);
483 let (mut remote_message_tx, remote_message_rx) = mpsc::channel(128);
484 let mut local_message_rx = local_message_rx.map(Ok::<_, ()>);
485 let mut remote_message_rx = remote_message_rx.map(Ok::<_, ()>);
486 tokio::spawn(async move {
487 session_ab
488 .run(&mut local_message_tx, &mut remote_message_rx)
489 .await
490 .unwrap();
491 });
492 tokio::spawn(async move {
493 session_ba
494 .run(&mut remote_message_tx, &mut local_message_rx)
495 .await
496 .unwrap();
497 });
498 }
499
500 {
501 let (mut local_message_tx, local_message_rx) = mpsc::channel(128);
502 let (mut remote_message_tx, remote_message_rx) = mpsc::channel(128);
503 let mut local_message_rx = local_message_rx.map(Ok::<_, ()>);
504 let mut remote_message_rx = remote_message_rx.map(Ok::<_, ()>);
505 tokio::spawn(async move {
506 session_ac
507 .run(&mut local_message_tx, &mut remote_message_rx)
508 .await
509 .unwrap();
510 });
511 tokio::spawn(async move {
512 session_ca
513 .run(&mut remote_message_tx, &mut local_message_rx)
514 .await
515 .unwrap();
516 });
517 }
518
519 let mut handle_ab = manager_a.session_handle(SESSION_AB).await.unwrap();
521 let mut handle_ac = manager_a.session_handle(SESSION_AC).await.unwrap();
522 let mut handle_ba = manager_b.session_handle(SESSION_BA).await.unwrap();
523 let mut handle_ca = manager_c.session_handle(SESSION_CA).await.unwrap();
524
525 let body_a = Body::new("Hello again from A".as_bytes());
526 let body_b = Body::new("Hello again from B".as_bytes());
527 let body_c = Body::new("Hello again from C".as_bytes());
528 let (peer_a_header_1, _) = peer_a.create_operation(&body_a, LOG_ID).await;
529 let (peer_b_header_1, _) = peer_b.create_operation(&body_b, LOG_ID).await;
530 let (peer_c_header_1, _) = peer_c.create_operation(&body_c, LOG_ID).await;
531
532 let operation_a = Operation {
533 hash: peer_a_header_1.hash(),
534 header: peer_a_header_1.clone(),
535 body: Some(body_a.clone()),
536 };
537 let operation_b = Operation {
538 hash: peer_b_header_1.hash(),
539 header: peer_b_header_1.clone(),
540 body: Some(body_b.clone()),
541 };
542 let operation_c = Operation {
543 hash: peer_c_header_1.hash(),
544 header: peer_c_header_1.clone(),
545 body: Some(body_c.clone()),
546 };
547
548 handle_ab
549 .send(ToSync::Payload(operation_a.clone()))
550 .await
551 .unwrap();
552 handle_ac.send(ToSync::Payload(operation_a)).await.unwrap();
553 handle_ba.send(ToSync::Payload(operation_b)).await.unwrap();
554 handle_ca.send(ToSync::Payload(operation_c)).await.unwrap();
555
556 let mut operations_a = vec![];
558 let mut operations_b = vec![];
559 let mut operations_c = vec![];
560 let _ = tokio::time::timeout(Duration::from_millis(500), async {
561 loop {
562 tokio::select! {
563 Some(event) = event_stream_a.next() => {
564 if let TopicLogSyncEvent::OperationReceived { operation, .. } = event.event() {
565 operations_a.push(operation.header().clone());
566 }
567 }
568 Some(event) = event_stream_b.next() => {
569 if let TopicLogSyncEvent::OperationReceived { operation, .. } = event.event() {
570 operations_b.push(operation.header().clone());
571 }
572 }
573 Some(event) = event_stream_c.next() => {
574 if let TopicLogSyncEvent::OperationReceived { operation, .. } = event.event() {
575 operations_c.push(operation.header().clone());
576 }
577 }
578 else => tokio::time::sleep(Duration::from_millis(20)).await
579 }
580 }
581 })
582 .await;
583
584 assert_eq!(operations_a.len(), 4);
586 assert_eq!(operations_b.len(), 4);
588 assert_eq!(operations_c.len(), 4);
590
591 assert!(!operations_a.contains(&peer_a_header_0));
593 assert!(!operations_a.contains(&peer_a_header_1));
594 assert!(!operations_b.contains(&peer_b_header_0));
595 assert!(!operations_b.contains(&peer_b_header_1));
596 assert!(!operations_c.contains(&peer_c_header_0));
597 assert!(!operations_c.contains(&peer_c_header_1));
598 }
599
600 #[tokio::test]
601 async fn non_blocking_manager_stream() {
602 const LOG_ID: u64 = 0;
603 const SESSION_ID: u64 = 0;
604
605 let topic = Topic::random();
606
607 let mut peer_a = Peer::new(0).await;
609 let body = Body::new("Hello from Peer A".as_bytes());
610 let _ = peer_a.create_operation(&body, LOG_ID).await;
611 let logs = BTreeMap::from([(peer_a.id(), vec![LOG_ID])]);
612 peer_a.associate(&topic, &logs).await;
613 let mut peer_a_manager = TestTopicSyncManager::new(peer_a.store.clone());
614
615 let mut peer_a_stream = peer_a_manager.subscribe();
617 tokio::task::spawn(async move {
618 loop {
619 peer_a_stream.next().await;
620 }
621 });
622
623 let mut peer_b = Peer::new(1).await;
625 let body = Body::new("Hello from Peer B".as_bytes());
626 let _ = peer_b.create_operation(&body, LOG_ID).await;
627 let logs = BTreeMap::from([(peer_b.id(), vec![LOG_ID])]);
628 peer_b.associate(&topic, &logs).await;
629 let mut peer_b_manager = TestTopicSyncManager::new(peer_b.store.clone());
630
631 let config = SessionConfig {
632 topic,
633 remote: peer_b.id(),
634 live_mode: true,
635 };
636
637 let peer_a_session = peer_a_manager.session(SESSION_ID, &config).await;
638
639 let event_stream = peer_b_manager.subscribe();
640 let peer_b_session = peer_b_manager.session(SESSION_ID, &config).await;
641
642 let mut peer_a_handle = peer_a_manager.session_handle(SESSION_ID).await.unwrap();
644
645 let (header_1, _) = peer_a.create_operation_no_insert(&body, LOG_ID).await;
647 peer_a_handle
648 .send(ToSync::Payload(Operation {
649 hash: header_1.hash(),
650 header: header_1.clone(),
651 body: Some(body.clone()),
652 }))
653 .await
654 .unwrap();
655 peer_a_handle.send(ToSync::Close).await.unwrap();
656
657 run_protocol(peer_a_session, peer_b_session).await.unwrap();
659
660 let events = drain_stream(event_stream).await;
662 assert_eq!(events.len(), 6);
663 for (index, event) in events.into_iter().enumerate() {
664 match index {
665 0 => assert_matches!(
666 event,
667 FromSync {
668 session_id: 0,
669 event: TopicLogSyncEvent::SyncStarted { .. },
670 ..
671 }
672 ),
673 1 => assert_matches!(
674 event,
675 FromSync {
676 session_id: 0,
677 event: TopicLogSyncEvent::OperationReceived { .. },
678 ..
679 }
680 ),
681 2 => assert_matches!(
682 event,
683 FromSync {
684 session_id: 0,
685 event: TopicLogSyncEvent::SyncFinished { .. },
686 ..
687 }
688 ),
689 3 => assert_matches!(
690 event,
691 FromSync {
692 event: TopicLogSyncEvent::LiveModeStarted,
693 ..
694 }
695 ),
696 4 => assert_matches!(
697 event,
698 FromSync {
699 session_id: 0,
700 event: TopicLogSyncEvent::OperationReceived { .. },
701 ..
702 }
703 ),
704 5 => assert_matches!(
705 event,
706 FromSync {
707 event: TopicLogSyncEvent::SessionFinished { .. },
708 ..
709 }
710 ),
711 _ => panic!(),
712 }
713 }
714 }
715}