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