1use crate::{Consumer, Delivery, Outcome};
10use ahash::AHashMap;
11use commonware_macros::select;
12use commonware_utils::{channel::oneshot, futures::Pool};
13use futures::future::Aborted;
14use std::collections::hash_map::Entry as HashMapEntry;
15use tracing::debug;
16
17#[derive(Clone, Debug, Eq, PartialEq)]
19pub struct Completion<K, S, Context = ()> {
20 pub context: Context,
22
23 pub delivery: Delivery<K, S>,
25
26 pub outcome: Option<Outcome>,
29
30 pub canceled: bool,
33
34 pub previously_accepted: bool,
36}
37
38type DeliveryPool<K, S, Context> = Pool<'static, Result<Completion<K, S, Context>, Aborted>>;
41
42struct Response<Context, V> {
44 context: Context,
45 value: V,
46 accepted: bool,
47}
48
49struct Entry<Context, V, State> {
51 delivery: Option<oneshot::Sender<()>>,
52 response: Option<Response<Context, V>>,
53 state: Option<State>,
54}
55
56impl<Context, V, State> Entry<Context, V, State> {
57 const fn new(state: State) -> Self {
58 Self {
59 delivery: None,
60 response: None,
61 state: Some(state),
62 }
63 }
64}
65
66pub struct Tracker<Con, Context = (), State = ()>
74where
75 Con: Consumer,
76 Con::Value: Clone + Send + 'static,
77 Context: Clone + Send + 'static,
78{
79 entries: AHashMap<Con::Key, Entry<Context, Con::Value, State>>,
80 deliveries: DeliveryPool<Con::Key, Con::Subscriber, Context>,
81 consumer: Con,
82}
83
84impl<Con, Context, State> Tracker<Con, Context, State>
85where
86 Con: Consumer,
87 Con::Value: Clone + Send + 'static,
88 Context: Clone + Send + 'static,
89{
90 pub fn new(consumer: Con) -> Self {
92 Self {
93 entries: AHashMap::new(),
94 deliveries: Pool::default(),
95 consumer,
96 }
97 }
98
99 pub fn contains(&self, key: &Con::Key) -> bool {
101 self.entries.contains_key(key)
102 }
103
104 pub(crate) fn insert_with_state(&mut self, key: Con::Key, state: State) -> bool {
109 match self.entries.entry(key) {
110 HashMapEntry::Vacant(entry) => {
111 entry.insert(Entry::new(state));
112 true
113 }
114 HashMapEntry::Occupied(_) => false,
115 }
116 }
117
118 pub fn remove(&mut self, key: &Con::Key) -> bool {
123 self.entries.remove(key).is_some()
124 }
125
126 pub(crate) fn remove_with_state(&mut self, key: &Con::Key) -> Option<Option<State>> {
131 self.entries.remove(key).map(|entry| entry.state)
132 }
133
134 pub(crate) fn take_state(&mut self, key: &Con::Key) -> Option<State> {
138 self.entries
139 .get_mut(key)
140 .and_then(|entry| entry.state.take())
141 }
142
143 pub(crate) fn state_mut(&mut self, key: &Con::Key) -> Option<&mut State> {
147 self.entries
148 .get_mut(key)
149 .and_then(|entry| entry.state.as_mut())
150 }
151
152 pub fn retain<F: FnMut(&Con::Key) -> bool>(&mut self, mut predicate: F) -> usize {
157 self.entries.extract_if(|key, _| !predicate(key)).count()
158 }
159
160 pub fn drain(&mut self) -> usize {
164 let count = self.entries.len();
165 self.entries.clear();
166 count
167 }
168
169 pub fn deliver(
175 &mut self,
176 delivery: Delivery<Con::Key, Con::Subscriber>,
177 context: Context,
178 value: Con::Value,
179 ) {
180 let entry = self.entries.get_mut(&delivery.key).expect("delivery entry");
181 entry.response = Some(Response {
182 context: context.clone(),
183 value: value.clone(),
184 accepted: false,
185 });
186 self.push_delivery(delivery, context, value);
187 }
188
189 pub fn redeliver(&mut self, delivery: Delivery<Con::Key, Con::Subscriber>) {
196 let (context, value) = {
197 let entry = self.entries.get(&delivery.key).expect("delivery entry");
198 let response = entry.response.as_ref().expect("response");
199 (response.context.clone(), response.value.clone())
200 };
201 self.push_delivery(delivery, context, value);
202 }
203
204 pub fn response_accepted(&self, key: &Con::Key) -> bool {
206 self.entries
207 .get(key)
208 .and_then(|entry| entry.response.as_ref())
209 .is_some_and(|response| response.accepted)
210 }
211
212 pub fn accept_response(&mut self, key: &Con::Key) {
216 let entry = self.entries.get_mut(key).expect("delivery entry");
217 let response = entry.response.as_mut().expect("response");
218 response.accepted = true;
219 }
220
221 pub fn discard_response(&mut self, key: &Con::Key) {
226 if let Some(entry) = self.entries.get_mut(key) {
227 entry.response = None;
228 }
229 }
230
231 pub async fn next_completion(
239 &mut self,
240 ) -> Result<Completion<Con::Key, Con::Subscriber, Context>, Aborted> {
241 let completed = self.deliveries.next_completed().await?;
242
243 if !completed.canceled {
246 let entry = self
247 .entries
248 .get_mut(&completed.delivery.key)
249 .expect("delivery entry");
250 entry.delivery = None;
251 }
252 Ok(completed)
253 }
254
255 fn push_delivery(
257 &mut self,
258 delivery: Delivery<Con::Key, Con::Subscriber>,
259 context: Context,
260 value: Con::Value,
261 ) {
262 let entry = self.entries.get_mut(&delivery.key).expect("delivery entry");
263 let previously_accepted = entry.response.as_ref().expect("response").accepted;
264 let completed = delivery.clone();
265 let mut consumer = self.consumer.clone();
266 let mut receiver = consumer.deliver(delivery, value);
267 let (cancel, canceled) = oneshot::channel::<()>();
268 self.deliveries.push(async move {
269 let (outcome, canceled) = select! {
273 _ = canceled => {
274 receiver.close();
275 (receiver.try_recv().ok().map(Into::into), true)
276 },
277 outcome = &mut receiver => {
278 let outcome = match outcome {
279 Ok(outcome) => Some(outcome.into()),
280 Err(_) => {
281 debug!(key = ?completed.key, "consumer dropped delivery without a verdict");
282 None
283 }
284 };
285 (outcome, false)
286 },
287 };
288 if canceled && outcome.is_none() {
289 return Err(Aborted);
290 }
291 Ok(Completion {
292 context,
293 delivery: completed,
294 outcome,
295 canceled,
296 previously_accepted,
297 })
298 });
299 assert!(entry.delivery.replace(cancel).is_none());
300 }
301}
302
303impl<Con, Context> Tracker<Con, Context>
304where
305 Con: Consumer,
306 Con::Value: Clone + Send + 'static,
307 Context: Clone + Send + 'static,
308{
309 pub fn insert(&mut self, key: Con::Key) -> bool {
314 self.insert_with_state(key, ())
315 }
316}
317
318#[cfg(test)]
319mod tests {
320 use super::*;
321 use crate::{
322 Resolution,
323 p2p::mocks::{Consumer as MockConsumer, Key as MockKey},
324 };
325 use bytes::Bytes;
326 use commonware_runtime::{Runner as _, deterministic::Runner};
327 use commonware_utils::{
328 channel::{fallible::FallibleExt, mpsc, oneshot},
329 non_empty_vec,
330 };
331 use futures::FutureExt as _;
332
333 type TestTracker = Tracker<MockConsumer<MockKey, Bytes>, u8>;
334 type StateTracker = Tracker<MockConsumer<MockKey, Bytes>, u8, u8>;
335
336 fn delivery(key: MockKey) -> Delivery<MockKey, ()> {
337 Delivery {
338 key,
339 subscribers: non_empty_vec![((), tracing::Span::none())],
340 }
341 }
342
343 #[derive(Clone)]
344 struct PendingConsumer {
345 sender: mpsc::UnboundedSender<oneshot::Sender<bool>>,
346 }
347
348 impl PendingConsumer {
349 fn new() -> (Self, mpsc::UnboundedReceiver<oneshot::Sender<bool>>) {
350 let (sender, receiver) = mpsc::unbounded_channel();
351 (Self { sender }, receiver)
352 }
353 }
354
355 impl Consumer for PendingConsumer {
356 type Key = MockKey;
357 type Value = Bytes;
358 type Subscriber = ();
359 type Outcome = bool;
360
361 fn deliver(
362 &mut self,
363 _delivery: Delivery<Self::Key, Self::Subscriber>,
364 _value: Self::Value,
365 ) -> oneshot::Receiver<bool> {
366 let (sender, receiver) = oneshot::channel();
367 self.sender.send_lossy(sender);
368 receiver
369 }
370 }
371
372 #[test]
373 fn test_insert_contains_remove_round_trip() {
374 let runner = Runner::default();
375 runner.start(|_| async move {
376 let mut tracker = TestTracker::new(MockConsumer::dummy());
377
378 assert!(!tracker.contains(&MockKey(1)));
379 assert!(tracker.insert(MockKey(1)));
380 assert!(tracker.contains(&MockKey(1)));
381
382 assert!(!tracker.insert(MockKey(1)));
383 assert!(tracker.remove(&MockKey(1)));
384 assert!(!tracker.contains(&MockKey(1)));
385 assert!(!tracker.remove(&MockKey(1)));
386 });
387 }
388
389 #[test]
390 fn test_state_round_trip() {
391 let runner = Runner::default();
392 runner.start(|_| async move {
393 let mut tracker = StateTracker::new(MockConsumer::dummy());
395 assert_eq!(tracker.state_mut(&MockKey(1)).map(|state| *state), None);
396
397 assert!(tracker.insert_with_state(MockKey(1), 5));
399 assert_eq!(tracker.state_mut(&MockKey(1)).map(|state| *state), Some(5));
400 *tracker
401 .state_mut(&MockKey(1))
402 .expect("inserted key must have state") = 7;
403 assert_eq!(tracker.state_mut(&MockKey(1)).map(|state| *state), Some(7));
404
405 assert_eq!(tracker.take_state(&MockKey(1)), Some(7));
408 assert!(tracker.contains(&MockKey(1)));
409 assert_eq!(tracker.state_mut(&MockKey(1)), None);
410 assert_eq!(tracker.remove_with_state(&MockKey(1)), Some(None));
411 assert_eq!(tracker.state_mut(&MockKey(1)).map(|state| *state), None);
412 });
413 }
414
415 #[test]
416 fn test_deliver_completes_with_context_and_consumer_result() {
417 let runner = Runner::default();
418 runner.start(|_| async move {
419 let (consumer, mut events) = MockConsumer::<MockKey, Bytes>::new();
420 let mut tracker = TestTracker::new(consumer);
421 let key = MockKey(7);
422 let value = Bytes::from("data");
423
424 tracker.insert(key.clone());
425 tracker.deliver(delivery(key.clone()), 9, value.clone());
426
427 let completed = tracker
428 .next_completion()
429 .await
430 .expect("delivery should complete");
431 assert_eq!(completed.context, 9);
432 assert_eq!(completed.delivery.key, key);
433 assert!(!completed.canceled);
434 assert!(!completed.previously_accepted);
435 assert_eq!(
436 completed.outcome,
437 Some(Outcome::Resolved(Resolution::Complete))
438 );
439
440 let (delivered_key, delivered_value) = events.recv().await.unwrap();
441 assert_eq!(delivered_key, key);
442 assert_eq!(delivered_value, value);
443 });
444 }
445
446 #[test]
447 fn test_remove_aborts_in_flight_delivery() {
448 let runner = Runner::default();
449 runner.start(|_| async move {
450 let (consumer, mut senders) = PendingConsumer::new();
451 let mut tracker = Tracker::<_, u8>::new(consumer);
452 let key = MockKey(1);
453
454 tracker.insert(key.clone());
455 tracker.deliver(delivery(key.clone()), 2, Bytes::from("v"));
456 let sender = senders.recv().await.unwrap();
457 assert!(tracker.remove(&key));
458
459 assert!(matches!(tracker.next_completion().await, Err(Aborted)));
460 assert!(sender.is_closed());
461 });
462 }
463
464 #[test]
465 fn test_canceled_delivery_cannot_complete_reinserted_key() {
466 #[derive(Clone, Copy)]
467 enum VerdictTiming {
468 BeforeRemoval,
469 AfterRemoval,
470 AfterClose,
471 }
472
473 for removal in 0..4 {
475 for timing in [
476 VerdictTiming::BeforeRemoval,
477 VerdictTiming::AfterRemoval,
478 VerdictTiming::AfterClose,
479 ] {
480 for polled in [false, true] {
481 Runner::default().start(move |_| async move {
482 let (consumer, mut senders) = PendingConsumer::new();
484 let mut tracker = Tracker::<PendingConsumer, u8, u8>::new(consumer);
485 let key = MockKey(1);
486 assert!(tracker.insert_with_state(key.clone(), 10));
487 tracker.deliver(delivery(key.clone()), 1, Bytes::from("old"));
488 let mut old_sender = Some(senders.recv().await.unwrap());
489 if polled {
490 assert!(tracker.next_completion().now_or_never().is_none());
491 }
492 if matches!(timing, VerdictTiming::BeforeRemoval) {
493 old_sender.take().unwrap().send(true).unwrap();
494 }
495
496 match removal {
498 0 => assert!(tracker.remove(&key)),
499 1 => assert_eq!(tracker.remove_with_state(&key), Some(Some(10))),
500 2 => assert_eq!(tracker.retain(|_| false), 1),
501 _ => assert_eq!(tracker.drain(), 1),
502 }
503 if matches!(timing, VerdictTiming::AfterRemoval) {
504 old_sender.take().unwrap().send(true).unwrap();
505 }
506 assert!(tracker.insert_with_state(key.clone(), 20));
507 tracker.deliver(delivery(key.clone()), 2, Bytes::from("new"));
508 let new_sender = senders.recv().await.unwrap();
509
510 let result = tracker.next_completion().await;
513 if matches!(timing, VerdictTiming::AfterClose) {
514 assert!(matches!(result, Err(Aborted)));
515 assert!(old_sender.take().unwrap().send(true).is_err());
516 } else {
517 let completed = result.unwrap();
518 assert!(completed.canceled);
519 assert!(!completed.previously_accepted);
520 assert_eq!(completed.context, 1);
521 assert_eq!(
522 completed.outcome,
523 Some(Outcome::Resolved(Resolution::Complete))
524 );
525 }
526 assert_eq!(tracker.state_mut(&key).map(|state| *state), Some(20));
527 assert!(tracker.entries[&key].delivery.is_some());
528 assert!(tracker.next_completion().now_or_never().is_none());
529
530 new_sender.send(true).unwrap();
532 let completed = tracker.next_completion().await.unwrap();
533 assert!(!completed.canceled);
534 assert_eq!(completed.context, 2);
535 assert_eq!(completed.delivery.key, key);
536 assert_eq!(
537 completed.outcome,
538 Some(Outcome::Resolved(Resolution::Complete))
539 );
540 assert!(tracker.deliveries.is_empty());
541 });
542 }
543 }
544 }
545 }
546
547 #[test]
548 fn test_dropped_verdict_completes_without_outcome_and_redelivers() {
549 let runner = Runner::default();
550 runner.start(|_| async move {
551 let (consumer, mut senders) = PendingConsumer::new();
552 let mut tracker = Tracker::<PendingConsumer, u8>::new(consumer);
553 let key = MockKey(3);
554
555 tracker.insert(key.clone());
556 tracker.deliver(delivery(key.clone()), 4, Bytes::from("unjudged"));
557 drop(senders.recv().await.unwrap());
558
559 let completed = tracker
560 .next_completion()
561 .await
562 .expect("dropped verdict should complete");
563 assert_eq!(completed.context, 4);
564 assert_eq!(completed.delivery.key, key);
565 assert_eq!(completed.outcome, None);
566 assert!(!tracker.response_accepted(&key));
567
568 tracker.redeliver(delivery(key.clone()));
570 senders.recv().await.unwrap().send(true).unwrap();
571 let judged = tracker
572 .next_completion()
573 .await
574 .expect("redelivery should complete");
575 assert_eq!(judged.context, 4);
576 assert_eq!(
577 judged.outcome,
578 Some(Outcome::Resolved(Resolution::Complete))
579 );
580 });
581 }
582
583 #[test]
584 fn test_redeliver_reuses_accepted_response_for_new_subscribers() {
585 let runner = Runner::default();
586 runner.start(|_| async move {
587 let (consumer, mut events) = MockConsumer::<MockKey, Bytes>::new();
588 let mut tracker = TestTracker::new(consumer);
589 let key = MockKey(5);
590 let value = Bytes::from("first");
591
592 tracker.insert(key.clone());
593 tracker.deliver(delivery(key.clone()), 3, value.clone());
594
595 let completed = tracker
596 .next_completion()
597 .await
598 .expect("first delivery should complete");
599 assert!(!completed.previously_accepted);
600 assert_eq!(
601 completed.outcome,
602 Some(Outcome::Resolved(Resolution::Complete))
603 );
604 tracker.accept_response(&key);
605 assert!(tracker.response_accepted(&key));
606
607 tracker.redeliver(delivery(key.clone()));
608 let redelivered = tracker
609 .next_completion()
610 .await
611 .expect("redelivery should complete");
612 assert_eq!(redelivered.context, 3);
613 assert_eq!(redelivered.delivery.key, key);
614 assert!(redelivered.previously_accepted);
615 assert_eq!(
616 redelivered.outcome,
617 Some(Outcome::Resolved(Resolution::Complete))
618 );
619
620 let first = events.recv().await.unwrap();
621 let second = events.recv().await.unwrap();
622 assert_eq!(first, (key.clone(), value.clone()));
623 assert_eq!(second, (key, value));
624 });
625 }
626
627 #[test]
628 #[should_panic(expected = "response")]
629 fn test_redeliver_requires_cached_response() {
630 let runner = Runner::default();
631 runner.start(|_| async move {
632 let (consumer, _events) = MockConsumer::<MockKey, Bytes>::new();
633 let mut tracker = TestTracker::new(consumer);
634 let key = MockKey(7);
635
636 tracker.insert(key.clone());
637 tracker.deliver(delivery(key.clone()), 3, Bytes::from("first"));
638 let completed = tracker
639 .next_completion()
640 .await
641 .expect("first delivery should complete");
642 assert_eq!(
643 completed.outcome,
644 Some(Outcome::Resolved(Resolution::Complete))
645 );
646
647 tracker.discard_response(&key);
648 tracker.redeliver(delivery(key));
649 });
650 }
651
652 #[test]
653 fn test_rejected_response_can_be_discarded_and_replaced() {
654 let runner = Runner::default();
655 runner.start(|_| async move {
656 let (mut consumer, _events) = MockConsumer::<MockKey, Bytes>::new();
657 let key = MockKey(8);
658 consumer.add_expected(key.clone(), Bytes::from("good"));
659 let mut tracker = TestTracker::new(consumer);
660
661 tracker.insert(key.clone());
662 tracker.deliver(delivery(key.clone()), 1, Bytes::from("bad"));
663 let rejected = tracker
664 .next_completion()
665 .await
666 .expect("rejected delivery should complete");
667 assert_eq!(rejected.outcome, Some(Outcome::Invalid));
668
669 tracker.discard_response(&key);
670 assert!(!tracker.response_accepted(&key));
671 tracker.deliver(delivery(key.clone()), 2, Bytes::from("good"));
672
673 let accepted = tracker
674 .next_completion()
675 .await
676 .expect("accepted delivery should complete");
677 assert_eq!(accepted.context, 2);
678 assert_eq!(
679 accepted.outcome,
680 Some(Outcome::Resolved(Resolution::Complete))
681 );
682 });
683 }
684}