1use crate::{Consumer, Delivery, Outcome};
10use commonware_utils::futures::{AbortablePool, Aborter};
11use futures::future::Aborted;
12use std::collections::{HashMap, hash_map::Entry as HashMapEntry};
13use tracing::debug;
14
15#[derive(Clone, Debug, Eq, PartialEq)]
17pub struct Completion<K, S, Context = ()> {
18 pub context: Context,
20
21 pub delivery: Delivery<K, S>,
23
24 pub outcome: Option<Outcome>,
27}
28
29struct Response<Context, V> {
31 context: Context,
32 value: V,
33 accepted: bool,
34}
35
36struct ActiveDelivery {
38 generation: u64,
39 _aborter: Aborter,
40}
41
42struct PooledCompletion<K, S, Context> {
44 generation: u64,
45 completion: Completion<K, S, Context>,
46}
47
48struct Entry<Context, V, State> {
50 delivery: Option<ActiveDelivery>,
51 response: Option<Response<Context, V>>,
52 state: Option<State>,
53}
54
55impl<Context, V, State> Entry<Context, V, State> {
56 const fn new(state: State) -> Self {
57 Self {
58 delivery: None,
59 response: None,
60 state: Some(state),
61 }
62 }
63}
64
65pub struct Tracker<Con, Context = (), State = ()>
73where
74 Con: Consumer,
75 Con::Value: Clone + Send + 'static,
76 Context: Clone + Send + 'static,
77{
78 entries: HashMap<Con::Key, Entry<Context, Con::Value, State>>,
79 deliveries: AbortablePool<'static, PooledCompletion<Con::Key, Con::Subscriber, Context>>,
80 next_generation: u64,
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: HashMap::new(),
94 deliveries: AbortablePool::default(),
95 next_generation: 0,
96 consumer,
97 }
98 }
99
100 pub fn contains(&self, key: &Con::Key) -> bool {
102 self.entries.contains_key(key)
103 }
104
105 pub(crate) fn insert_with_state(&mut self, key: Con::Key, state: State) -> bool {
110 match self.entries.entry(key) {
111 HashMapEntry::Vacant(entry) => {
112 entry.insert(Entry::new(state));
113 true
114 }
115 HashMapEntry::Occupied(_) => false,
116 }
117 }
118
119 pub fn remove(&mut self, key: &Con::Key) -> bool {
124 self.entries.remove(key).is_some()
125 }
126
127 pub(crate) fn remove_with_state(&mut self, key: &Con::Key) -> Option<Option<State>> {
132 self.entries.remove(key).map(|entry| entry.state)
133 }
134
135 pub(crate) fn take_state(&mut self, key: &Con::Key) -> Option<State> {
139 self.entries
140 .get_mut(key)
141 .and_then(|entry| entry.state.take())
142 }
143
144 pub fn retain<F: FnMut(&Con::Key) -> bool>(&mut self, mut predicate: F) -> usize {
149 self.entries.extract_if(|key, _| !predicate(key)).count()
150 }
151
152 pub fn drain(&mut self) -> usize {
156 let count = self.entries.len();
157 self.entries.clear();
158 count
159 }
160
161 pub fn deliver(
167 &mut self,
168 delivery: Delivery<Con::Key, Con::Subscriber>,
169 context: Context,
170 value: Con::Value,
171 ) {
172 let key = delivery.key.clone();
173 let entry = self.entries.get_mut(&key).expect("delivery entry");
174 entry.response = Some(Response {
175 context: context.clone(),
176 value: value.clone(),
177 accepted: false,
178 });
179 self.push_delivery(delivery, context, value);
180 }
181
182 pub fn redeliver(&mut self, delivery: Delivery<Con::Key, Con::Subscriber>) {
189 let key = delivery.key.clone();
190 let (context, value) = {
191 let entry = self.entries.get(&key).expect("delivery entry");
192 let response = entry.response.as_ref().expect("response");
193 (response.context.clone(), response.value.clone())
194 };
195 self.push_delivery(delivery, context, value);
196 }
197
198 pub fn response_accepted(&self, key: &Con::Key) -> bool {
200 self.entries
201 .get(key)
202 .and_then(|entry| entry.response.as_ref())
203 .is_some_and(|response| response.accepted)
204 }
205
206 pub fn accept_response(&mut self, key: &Con::Key) {
210 let entry = self.entries.get_mut(key).expect("delivery entry");
211 let response = entry.response.as_mut().expect("response");
212 response.accepted = true;
213 }
214
215 pub fn discard_response(&mut self, key: &Con::Key) {
220 if let Some(entry) = self.entries.get_mut(key) {
221 entry.response = None;
222 }
223 }
224
225 pub async fn next_completion(
233 &mut self,
234 ) -> Result<Completion<Con::Key, Con::Subscriber, Context>, Aborted> {
235 let completed = self.deliveries.next_completed().await?;
236 let Some(entry) = self.entries.get_mut(&completed.completion.delivery.key) else {
237 return Err(Aborted);
238 };
239 if entry
240 .delivery
241 .as_ref()
242 .is_none_or(|delivery| delivery.generation != completed.generation)
243 {
244 return Err(Aborted);
245 }
246 entry.delivery = None;
247 Ok(completed.completion)
248 }
249
250 fn push_delivery(
252 &mut self,
253 delivery: Delivery<Con::Key, Con::Subscriber>,
254 context: Context,
255 value: Con::Value,
256 ) {
257 let generation = self.next_generation;
258 self.next_generation = self
259 .next_generation
260 .checked_add(1)
261 .expect("delivery generation overflow");
262 let key = delivery.key.clone();
263 let completed = delivery.clone();
264 let mut consumer = self.consumer.clone();
265 let receiver = consumer.deliver(delivery, value);
266 let aborter = self.deliveries.push(async move {
267 let outcome = match receiver.await {
268 Ok(outcome) => Some(outcome.into()),
269 Err(_) => {
270 debug!(key = ?completed.key, "consumer dropped delivery without a verdict");
271 None
272 }
273 };
274 PooledCompletion {
275 generation,
276 completion: Completion {
277 context,
278 delivery: completed,
279 outcome,
280 },
281 }
282 });
283 let entry = self.entries.get_mut(&key).expect("delivery entry");
284 assert!(
285 entry
286 .delivery
287 .replace(ActiveDelivery {
288 generation,
289 _aborter: aborter,
290 })
291 .is_none()
292 );
293 }
294}
295
296impl<Con, Context> Tracker<Con, Context>
297where
298 Con: Consumer,
299 Con::Value: Clone + Send + 'static,
300 Context: Clone + Send + 'static,
301{
302 pub fn insert(&mut self, key: Con::Key) -> bool {
307 self.insert_with_state(key, ())
308 }
309}
310
311#[cfg(test)]
312mod tests {
313 use super::*;
314 use crate::p2p::mocks::{Consumer as MockConsumer, Key as MockKey};
315 use bytes::Bytes;
316 use commonware_runtime::{Runner as _, deterministic::Runner};
317 use commonware_utils::{
318 channel::{fallible::FallibleExt, mpsc, oneshot},
319 non_empty_vec,
320 };
321
322 type TestTracker = Tracker<MockConsumer<MockKey, Bytes>, u8>;
323
324 fn delivery(key: MockKey) -> Delivery<MockKey, ()> {
325 Delivery {
326 key,
327 subscribers: non_empty_vec![((), tracing::Span::none())],
328 }
329 }
330
331 #[derive(Clone)]
332 struct PendingConsumer {
333 sender: mpsc::UnboundedSender<oneshot::Sender<bool>>,
334 }
335
336 impl PendingConsumer {
337 fn new() -> (Self, mpsc::UnboundedReceiver<oneshot::Sender<bool>>) {
338 let (sender, receiver) = mpsc::unbounded_channel();
339 (Self { sender }, receiver)
340 }
341 }
342
343 impl Consumer for PendingConsumer {
344 type Key = MockKey;
345 type Value = Bytes;
346 type Subscriber = ();
347 type Outcome = bool;
348
349 fn deliver(
350 &mut self,
351 _delivery: Delivery<Self::Key, Self::Subscriber>,
352 _value: Self::Value,
353 ) -> oneshot::Receiver<bool> {
354 let (sender, receiver) = oneshot::channel();
355 self.sender.send_lossy(sender);
356 receiver
357 }
358 }
359
360 #[test]
361 fn test_insert_contains_remove_round_trip() {
362 let runner = Runner::default();
363 runner.start(|_| async move {
364 let mut tracker = TestTracker::new(MockConsumer::dummy());
365
366 assert!(!tracker.contains(&MockKey(1)));
367 assert!(tracker.insert(MockKey(1)));
368 assert!(tracker.contains(&MockKey(1)));
369
370 assert!(!tracker.insert(MockKey(1)));
371 assert!(tracker.remove(&MockKey(1)));
372 assert!(!tracker.contains(&MockKey(1)));
373 assert!(!tracker.remove(&MockKey(1)));
374 });
375 }
376
377 #[test]
378 fn test_deliver_completes_with_context_and_consumer_result() {
379 let runner = Runner::default();
380 runner.start(|_| async move {
381 let (consumer, mut events) = MockConsumer::<MockKey, Bytes>::new();
382 let mut tracker = TestTracker::new(consumer);
383 let key = MockKey(7);
384 let value = Bytes::from("data");
385
386 tracker.insert(key.clone());
387 tracker.deliver(delivery(key.clone()), 9, value.clone());
388
389 let completed = tracker
390 .next_completion()
391 .await
392 .expect("delivery should complete");
393 assert_eq!(completed.context, 9);
394 assert_eq!(completed.delivery.key, key);
395 assert_eq!(completed.outcome, Some(Outcome::Complete));
396
397 let (delivered_key, delivered_value) = events.recv().await.unwrap();
398 assert_eq!(delivered_key, key);
399 assert_eq!(delivered_value, value);
400 });
401 }
402
403 #[test]
404 fn test_remove_aborts_in_flight_delivery() {
405 let runner = Runner::default();
406 runner.start(|_| async move {
407 let (consumer, _events) = MockConsumer::<MockKey, Bytes>::new();
408 let mut tracker = TestTracker::new(consumer);
409 let key = MockKey(1);
410
411 tracker.insert(key.clone());
412 tracker.deliver(delivery(key.clone()), 2, Bytes::from("v"));
413 assert!(tracker.remove(&key));
414
415 assert!(matches!(tracker.next_completion().await, Err(Aborted)));
416 });
417 }
418
419 #[test]
420 fn test_stale_same_key_completion_does_not_clear_new_delivery() {
421 let runner = Runner::default();
422 runner.start(|_| async move {
423 let (consumer, mut senders) = PendingConsumer::new();
424 let mut tracker = Tracker::<PendingConsumer, u8>::new(consumer);
425 let key = MockKey(1);
426
427 tracker.insert(key.clone());
428 tracker.deliver(delivery(key.clone()), 1, Bytes::from("old"));
429 let old_sender = senders.recv().await.unwrap();
430 old_sender.send(true).unwrap();
431 let stale = tracker.deliveries.next_completed().await.unwrap();
432
433 assert!(tracker.remove(&key));
434 tracker.insert(key.clone());
435 tracker.deliver(delivery(key.clone()), 2, Bytes::from("new"));
436 let new_sender = senders.recv().await.unwrap();
437
438 let _stale_aborter = tracker.deliveries.push(async move { stale });
439 assert!(matches!(tracker.next_completion().await, Err(Aborted)));
440
441 new_sender.send(true).unwrap();
442 let completed = tracker
443 .next_completion()
444 .await
445 .expect("new delivery should complete");
446 assert_eq!(completed.context, 2);
447 assert_eq!(completed.delivery.key, key);
448 assert_eq!(completed.outcome, Some(Outcome::Complete));
449 });
450 }
451
452 #[test]
453 fn test_dropped_verdict_completes_without_outcome_and_redelivers() {
454 let runner = Runner::default();
455 runner.start(|_| async move {
456 let (consumer, mut senders) = PendingConsumer::new();
457 let mut tracker = Tracker::<PendingConsumer, u8>::new(consumer);
458 let key = MockKey(3);
459
460 tracker.insert(key.clone());
461 tracker.deliver(delivery(key.clone()), 4, Bytes::from("unjudged"));
462 drop(senders.recv().await.unwrap());
463
464 let completed = tracker
465 .next_completion()
466 .await
467 .expect("dropped verdict should complete");
468 assert_eq!(completed.context, 4);
469 assert_eq!(completed.delivery.key, key);
470 assert_eq!(completed.outcome, None);
471 assert!(!tracker.response_accepted(&key));
472
473 tracker.redeliver(delivery(key.clone()));
475 senders.recv().await.unwrap().send(true).unwrap();
476 let judged = tracker
477 .next_completion()
478 .await
479 .expect("redelivery should complete");
480 assert_eq!(judged.context, 4);
481 assert_eq!(judged.outcome, Some(Outcome::Complete));
482 });
483 }
484
485 #[test]
486 fn test_redeliver_reuses_accepted_response_for_new_subscribers() {
487 let runner = Runner::default();
488 runner.start(|_| async move {
489 let (consumer, mut events) = MockConsumer::<MockKey, Bytes>::new();
490 let mut tracker = TestTracker::new(consumer);
491 let key = MockKey(5);
492 let value = Bytes::from("first");
493
494 tracker.insert(key.clone());
495 tracker.deliver(delivery(key.clone()), 3, value.clone());
496
497 let completed = tracker
498 .next_completion()
499 .await
500 .expect("first delivery should complete");
501 assert_eq!(completed.outcome, Some(Outcome::Complete));
502 tracker.accept_response(&key);
503 assert!(tracker.response_accepted(&key));
504
505 tracker.redeliver(delivery(key.clone()));
506 let redelivered = tracker
507 .next_completion()
508 .await
509 .expect("redelivery should complete");
510 assert_eq!(redelivered.context, 3);
511 assert_eq!(redelivered.delivery.key, key);
512 assert_eq!(redelivered.outcome, Some(Outcome::Complete));
513
514 let first = events.recv().await.unwrap();
515 let second = events.recv().await.unwrap();
516 assert_eq!(first, (key.clone(), value.clone()));
517 assert_eq!(second, (key, value));
518 });
519 }
520
521 #[test]
522 #[should_panic(expected = "response")]
523 fn test_redeliver_requires_cached_response() {
524 let runner = Runner::default();
525 runner.start(|_| async move {
526 let (consumer, _events) = MockConsumer::<MockKey, Bytes>::new();
527 let mut tracker = TestTracker::new(consumer);
528 let key = MockKey(7);
529
530 tracker.insert(key.clone());
531 tracker.deliver(delivery(key.clone()), 3, Bytes::from("first"));
532 let completed = tracker
533 .next_completion()
534 .await
535 .expect("first delivery should complete");
536 assert_eq!(completed.outcome, Some(Outcome::Complete));
537
538 tracker.discard_response(&key);
539 tracker.redeliver(delivery(key));
540 });
541 }
542
543 #[test]
544 fn test_rejected_response_can_be_discarded_and_replaced() {
545 let runner = Runner::default();
546 runner.start(|_| async move {
547 let (mut consumer, _events) = MockConsumer::<MockKey, Bytes>::new();
548 let key = MockKey(8);
549 consumer.add_expected(key.clone(), Bytes::from("good"));
550 let mut tracker = TestTracker::new(consumer);
551
552 tracker.insert(key.clone());
553 tracker.deliver(delivery(key.clone()), 1, Bytes::from("bad"));
554 let rejected = tracker
555 .next_completion()
556 .await
557 .expect("rejected delivery should complete");
558 assert_eq!(rejected.outcome, Some(Outcome::Invalid));
559
560 tracker.discard_response(&key);
561 assert!(!tracker.response_accepted(&key));
562 tracker.deliver(delivery(key.clone()), 2, Bytes::from("good"));
563
564 let accepted = tracker
565 .next_completion()
566 .await
567 .expect("accepted delivery should complete");
568 assert_eq!(accepted.context, 2);
569 assert_eq!(accepted.outcome, Some(Outcome::Complete));
570 });
571 }
572}