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::{Body, Operation, Topic};
247 use p2panda_store::SqliteStore;
248
249 use crate::protocols::TopicLogSyncEvent;
250 use crate::test_utils::{
251 Peer, TestTopicSyncManager, drain_stream, run_protocol, setup_logging,
252 };
253 use crate::traits::{Manager, Protocol};
254 use crate::{FromSync, SessionConfig, ToSync};
255
256 #[tokio::test]
257 async fn from_args() {
258 let store = SqliteStore::temporary().await;
259 let _: TestTopicSyncManager = Manager::from_args(store);
260 }
261
262 #[tokio::test]
263 async fn manager_e2e() {
264 setup_logging();
265
266 const LOG_ID: u64 = 0;
267 const SESSION_ID: u64 = 0;
268
269 let topic = Topic::random();
270
271 let mut peer_a = Peer::new(0).await;
273 let body = Body::new("Hello from Peer A".as_bytes());
274 let _ = peer_a.create_operation(&body, LOG_ID).await;
275 let logs = BTreeMap::from([(peer_a.id(), vec![LOG_ID])]);
276 peer_a.associate(&topic, &logs).await;
277 let mut peer_a_manager = TestTopicSyncManager::new(peer_a.store.clone());
278
279 let mut peer_b = Peer::new(1).await;
281 let body = Body::new("Hello from Peer B".as_bytes());
282 let _ = peer_b.create_operation(&body, LOG_ID).await;
283 let logs = BTreeMap::from([(peer_b.id(), vec![LOG_ID])]);
284 peer_b.associate(&topic, &logs).await;
285 let mut peer_b_manager = TestTopicSyncManager::new(peer_b.store.clone());
286
287 let config = SessionConfig {
288 topic,
289 remote: peer_b.id(),
290 live_mode: true,
291 };
292
293 let mut event_stream_a = peer_a_manager.subscribe();
295 let mut event_stream_b = peer_b_manager.subscribe();
296
297 let peer_a_session = peer_a_manager.session(SESSION_ID, &config).await;
299 let peer_b_session = peer_b_manager.session(SESSION_ID, &config).await;
300
301 let mut peer_a_handle = peer_a_manager.session_handle(SESSION_ID).await.unwrap();
303
304 let (header_1, _) = peer_a.create_operation_no_insert(&body, LOG_ID).await;
306 peer_a_handle
307 .send(ToSync::Payload(Operation {
308 hash: header_1.hash(),
309 header: header_1.clone(),
310 body: Some(body.clone()),
311 }))
312 .await
313 .unwrap();
314 peer_a_handle.send(ToSync::Close).await.unwrap();
315
316 run_protocol(peer_a_session, peer_b_session).await.unwrap();
318
319 for index in 0..=4 {
321 let event = event_stream_a.next().await.unwrap();
322 assert_eq!(event.session_id(), 0);
323 match index {
324 0 => assert_matches!(
325 event,
326 FromSync {
327 event: TopicLogSyncEvent::SyncStarted { .. },
328 ..
329 }
330 ),
331 1 => assert_matches!(
332 event,
333 FromSync {
334 event: TopicLogSyncEvent::OperationReceived { .. },
335 ..
336 }
337 ),
338 2 => assert_matches!(
339 event,
340 FromSync {
341 event: TopicLogSyncEvent::SyncFinished { .. },
342 ..
343 }
344 ),
345 3 => assert_matches!(
346 event,
347 FromSync {
348 event: TopicLogSyncEvent::LiveModeStarted,
349 ..
350 }
351 ),
352 4 => assert_matches!(
353 event,
354 FromSync {
355 event: TopicLogSyncEvent::SessionFinished { .. },
356 ..
357 }
358 ),
359 _ => panic!(),
360 }
361 }
362
363 for index in 0..=5 {
365 let event = event_stream_b.next().await.unwrap();
366 match index {
367 0 => assert_matches!(
368 event,
369 FromSync {
370 session_id: 0,
371 event: TopicLogSyncEvent::SyncStarted { .. },
372 ..
373 }
374 ),
375 1 => assert_matches!(
376 event,
377 FromSync {
378 session_id: 0,
379 event: TopicLogSyncEvent::OperationReceived { .. },
380 ..
381 }
382 ),
383 2 => assert_matches!(
384 event,
385 FromSync {
386 session_id: 0,
387 event: TopicLogSyncEvent::SyncFinished { .. },
388 ..
389 }
390 ),
391 3 => assert_matches!(
392 event,
393 FromSync {
394 event: TopicLogSyncEvent::LiveModeStarted,
395 ..
396 }
397 ),
398 4 => assert_matches!(
399 event,
400 FromSync {
401 session_id: 0,
402 event: TopicLogSyncEvent::OperationReceived { .. },
403 ..
404 }
405 ),
406 5 => assert_matches!(
407 event,
408 FromSync {
409 event: TopicLogSyncEvent::SessionFinished { .. },
410 ..
411 }
412 ),
413 _ => panic!(),
414 }
415 }
416 }
417
418 #[tokio::test]
419 async fn live_mode_three_peer_forwarding() {
420 setup_logging();
421
422 const LOG_ID: u64 = 0;
423 const SESSION_AB: u64 = 0;
424 const SESSION_AC: u64 = 1;
425 const SESSION_BA: u64 = 2;
426 const SESSION_CA: u64 = 3;
427
428 let topic = Topic::random();
429
430 let mut peer_a = Peer::new(0).await;
432 let body_a = Body::new("Hello from A".as_bytes());
433 let (peer_a_header_0, _) = peer_a.create_operation(&body_a, LOG_ID).await;
434 let mut manager_a = TestTopicSyncManager::new(peer_a.store.clone());
435
436 let mut peer_b = Peer::new(1).await;
438 let body_b = Body::new("Hello from B".as_bytes());
439 let (peer_b_header_0, _) = peer_b.create_operation(&body_b, LOG_ID).await;
440 let mut manager_b = TestTopicSyncManager::new(peer_b.store.clone());
441
442 let mut peer_c = Peer::new(2).await;
444 let body_c = Body::new("Hello from C".as_bytes());
445 let (peer_c_header_0, _) = peer_c.create_operation(&body_c, LOG_ID).await;
446 let mut manager_c = TestTopicSyncManager::new(peer_c.store.clone());
447
448 let logs = BTreeMap::from([
449 (peer_a.id(), vec![LOG_ID]),
450 (peer_b.id(), vec![LOG_ID]),
451 (peer_c.id(), vec![LOG_ID]),
452 ]);
453 peer_a.associate(&topic, &logs).await;
454 peer_b.associate(&topic, &logs).await;
455 peer_c.associate(&topic, &logs).await;
456
457 let mut config = SessionConfig {
459 topic: topic.clone(),
460 remote: peer_b.id(),
461 live_mode: true,
462 };
463 let session_ab = manager_a.session(SESSION_AB, &config).await;
464 config.remote = peer_a.id();
465 let session_ba = manager_b.session(SESSION_BA, &config).await;
466
467 let mut config = SessionConfig {
469 topic: topic.clone(),
470 remote: peer_c.id(),
471 live_mode: true,
472 };
473 let session_ac = manager_a.session(SESSION_AC, &config).await;
474 config.remote = peer_a.id();
475 let session_ca = manager_c.session(SESSION_CA, &config).await;
476
477 let mut event_stream_a = manager_a.subscribe();
478 let mut event_stream_b = manager_b.subscribe();
479 let mut event_stream_c = manager_c.subscribe();
480
481 {
483 let (mut local_message_tx, local_message_rx) = mpsc::channel(128);
484 let (mut remote_message_tx, remote_message_rx) = mpsc::channel(128);
485 let mut local_message_rx = local_message_rx.map(Ok::<_, ()>);
486 let mut remote_message_rx = remote_message_rx.map(Ok::<_, ()>);
487 tokio::spawn(async move {
488 session_ab
489 .run(&mut local_message_tx, &mut remote_message_rx)
490 .await
491 .unwrap();
492 });
493 tokio::spawn(async move {
494 session_ba
495 .run(&mut remote_message_tx, &mut local_message_rx)
496 .await
497 .unwrap();
498 });
499 }
500
501 {
502 let (mut local_message_tx, local_message_rx) = mpsc::channel(128);
503 let (mut remote_message_tx, remote_message_rx) = mpsc::channel(128);
504 let mut local_message_rx = local_message_rx.map(Ok::<_, ()>);
505 let mut remote_message_rx = remote_message_rx.map(Ok::<_, ()>);
506 tokio::spawn(async move {
507 session_ac
508 .run(&mut local_message_tx, &mut remote_message_rx)
509 .await
510 .unwrap();
511 });
512 tokio::spawn(async move {
513 session_ca
514 .run(&mut remote_message_tx, &mut local_message_rx)
515 .await
516 .unwrap();
517 });
518 }
519
520 let mut handle_ab = manager_a.session_handle(SESSION_AB).await.unwrap();
522 let mut handle_ac = manager_a.session_handle(SESSION_AC).await.unwrap();
523 let mut handle_ba = manager_b.session_handle(SESSION_BA).await.unwrap();
524 let mut handle_ca = manager_c.session_handle(SESSION_CA).await.unwrap();
525
526 let body_a = Body::new("Hello again from A".as_bytes());
527 let body_b = Body::new("Hello again from B".as_bytes());
528 let body_c = Body::new("Hello again from C".as_bytes());
529 let (peer_a_header_1, _) = peer_a.create_operation(&body_a, LOG_ID).await;
530 let (peer_b_header_1, _) = peer_b.create_operation(&body_b, LOG_ID).await;
531 let (peer_c_header_1, _) = peer_c.create_operation(&body_c, LOG_ID).await;
532
533 let operation_a = Operation {
534 hash: peer_a_header_1.hash(),
535 header: peer_a_header_1.clone(),
536 body: Some(body_a.clone()),
537 };
538 let operation_b = Operation {
539 hash: peer_b_header_1.hash(),
540 header: peer_b_header_1.clone(),
541 body: Some(body_b.clone()),
542 };
543 let operation_c = Operation {
544 hash: peer_c_header_1.hash(),
545 header: peer_c_header_1.clone(),
546 body: Some(body_c.clone()),
547 };
548
549 handle_ab
550 .send(ToSync::Payload(operation_a.clone()))
551 .await
552 .unwrap();
553 handle_ac.send(ToSync::Payload(operation_a)).await.unwrap();
554 handle_ba.send(ToSync::Payload(operation_b)).await.unwrap();
555 handle_ca.send(ToSync::Payload(operation_c)).await.unwrap();
556
557 let mut operations_a = vec![];
559 let mut operations_b = vec![];
560 let mut operations_c = vec![];
561 let _ = tokio::time::timeout(Duration::from_millis(500), async {
562 loop {
563 tokio::select! {
564 Some(event) = event_stream_a.next() => {
565 if let TopicLogSyncEvent::OperationReceived { operation, .. } = event.event() {
566 println!("A received operation: {}", operation.hash);
567 operations_a.push(operation.header().clone());
568 }
569 }
570 Some(event) = event_stream_b.next() => {
571 if let TopicLogSyncEvent::OperationReceived { operation, .. } = event.event() {
572 println!("B received operation: {}", operation.hash);
573 operations_b.push(operation.header().clone());
574 }
575 }
576 Some(event) = event_stream_c.next() => {
577 if let TopicLogSyncEvent::OperationReceived { operation, .. } = event.event() {
578 operations_c.push(operation.header().clone());
579 }
580 }
581 else => tokio::time::sleep(Duration::from_millis(20)).await
582 }
583 }
584 })
585 .await;
586
587 assert_eq!(operations_a.len(), 4);
589 assert_eq!(operations_b.len(), 4);
591 assert_eq!(operations_c.len(), 4);
593
594 assert!(!operations_a.contains(&peer_a_header_0));
596 assert!(!operations_a.contains(&peer_a_header_1));
597 assert!(!operations_b.contains(&peer_b_header_0));
598 assert!(!operations_b.contains(&peer_b_header_1));
599 assert!(!operations_c.contains(&peer_c_header_0));
600 assert!(!operations_c.contains(&peer_c_header_1));
601 }
602
603 #[tokio::test]
604 async fn non_blocking_manager_stream() {
605 const LOG_ID: u64 = 0;
606 const SESSION_ID: u64 = 0;
607
608 let topic = Topic::random();
609
610 let mut peer_a = Peer::new(0).await;
612 let body = Body::new("Hello from Peer A".as_bytes());
613 let _ = peer_a.create_operation(&body, LOG_ID).await;
614 let logs = BTreeMap::from([(peer_a.id(), vec![LOG_ID])]);
615 peer_a.associate(&topic, &logs).await;
616 let mut peer_a_manager = TestTopicSyncManager::new(peer_a.store.clone());
617
618 let mut peer_a_stream = peer_a_manager.subscribe();
620 tokio::task::spawn(async move {
621 loop {
622 peer_a_stream.next().await;
623 }
624 });
625
626 let mut peer_b = Peer::new(1).await;
628 let body = Body::new("Hello from Peer B".as_bytes());
629 let _ = peer_b.create_operation(&body, LOG_ID).await;
630 let logs = BTreeMap::from([(peer_b.id(), vec![LOG_ID])]);
631 peer_b.associate(&topic, &logs).await;
632 let mut peer_b_manager = TestTopicSyncManager::new(peer_b.store.clone());
633
634 let config = SessionConfig {
635 topic,
636 remote: peer_b.id(),
637 live_mode: true,
638 };
639
640 let peer_a_session = peer_a_manager.session(SESSION_ID, &config).await;
641
642 let event_stream = peer_b_manager.subscribe();
643 let peer_b_session = peer_b_manager.session(SESSION_ID, &config).await;
644
645 let mut peer_a_handle = peer_a_manager.session_handle(SESSION_ID).await.unwrap();
647
648 let (header_1, _) = peer_a.create_operation_no_insert(&body, LOG_ID).await;
650 peer_a_handle
651 .send(ToSync::Payload(Operation {
652 hash: header_1.hash(),
653 header: header_1.clone(),
654 body: Some(body.clone()),
655 }))
656 .await
657 .unwrap();
658 peer_a_handle.send(ToSync::Close).await.unwrap();
659
660 run_protocol(peer_a_session, peer_b_session).await.unwrap();
662
663 let events = drain_stream(event_stream).await;
665 assert_eq!(events.len(), 6);
666 for (index, event) in events.into_iter().enumerate() {
667 match index {
668 0 => assert_matches!(
669 event,
670 FromSync {
671 session_id: 0,
672 event: TopicLogSyncEvent::SyncStarted { .. },
673 ..
674 }
675 ),
676 1 => assert_matches!(
677 event,
678 FromSync {
679 session_id: 0,
680 event: TopicLogSyncEvent::OperationReceived { .. },
681 ..
682 }
683 ),
684 2 => assert_matches!(
685 event,
686 FromSync {
687 session_id: 0,
688 event: TopicLogSyncEvent::SyncFinished { .. },
689 ..
690 }
691 ),
692 3 => assert_matches!(
693 event,
694 FromSync {
695 event: TopicLogSyncEvent::LiveModeStarted,
696 ..
697 }
698 ),
699 4 => assert_matches!(
700 event,
701 FromSync {
702 session_id: 0,
703 event: TopicLogSyncEvent::OperationReceived { .. },
704 ..
705 }
706 ),
707 5 => assert_matches!(
708 event,
709 FromSync {
710 event: TopicLogSyncEvent::SessionFinished { .. },
711 ..
712 }
713 ),
714 _ => panic!(),
715 }
716 }
717 }
718}