Skip to main content

commonware_consensus/
reporter.rs

1//! Reporter implementations for various standard types.
2
3use crate::Reporter;
4use commonware_actor::Feedback;
5use std::marker::PhantomData;
6
7/// An implementation of [Reporter] for an optional [Reporter].
8///
9/// This is useful for reporting activity to a [Reporter] that may not be present.
10/// Reporting is ignored if the [Reporter] is `None`.
11impl<A: Send, R: Reporter<Activity = A>> Reporter for Option<R> {
12    type Activity = A;
13
14    fn report(&mut self, activity: Self::Activity) -> Feedback {
15        let Some(reporter) = self else {
16            return Feedback::Ok;
17        };
18        reporter.report(activity)
19    }
20}
21
22/// A struct used to report activity to multiple [Reporter]s (which may or may not be present).
23#[derive(Clone)]
24pub struct Reporters<A, R1, R2> {
25    r1: Option<R1>,
26    r2: Option<R2>,
27
28    _phantom: PhantomData<A>,
29}
30
31impl<A, R1, R2> Reporter for Reporters<A, R1, R2>
32where
33    A: Clone + Send + 'static,
34    R1: Reporter<Activity = A>,
35    R2: Reporter<Activity = A>,
36{
37    type Activity = A;
38
39    fn report(&mut self, activity: Self::Activity) -> Feedback {
40        // This approach avoids cloning activity, if possible.
41        match (&mut self.r1, &mut self.r2) {
42            (Some(r1), Some(r2)) => combine(r1.report(activity.clone()), r2.report(activity)),
43            (Some(r1), None) => r1.report(activity),
44            (None, Some(r2)) => r2.report(activity),
45            (None, None) => Feedback::Ok,
46        }
47    }
48}
49
50const fn combine(a: Feedback, b: Feedback) -> Feedback {
51    match (a, b) {
52        (Feedback::Closed, _) | (_, Feedback::Closed) => Feedback::Closed,
53        (Feedback::Backoff, _) | (_, Feedback::Backoff) => Feedback::Backoff,
54        (Feedback::Ok, Feedback::Ok) => Feedback::Ok,
55    }
56}
57
58impl<A, R1, R2> From<(Option<R1>, Option<R2>)> for Reporters<A, R1, R2> {
59    fn from((r1, r2): (Option<R1>, Option<R2>)) -> Self {
60        Self {
61            r1,
62            r2,
63            _phantom: PhantomData,
64        }
65    }
66}
67
68impl<A, R1, R2> From<(Option<R1>, R2)> for Reporters<A, R1, R2> {
69    fn from((r1, r2): (Option<R1>, R2)) -> Self {
70        Self {
71            r1,
72            r2: Some(r2),
73            _phantom: PhantomData,
74        }
75    }
76}
77
78impl<A, R1, R2> From<(R1, Option<R2>)> for Reporters<A, R1, R2> {
79    fn from((r1, r2): (R1, Option<R2>)) -> Self {
80        Self {
81            r1: Some(r1),
82            r2,
83            _phantom: PhantomData,
84        }
85    }
86}
87
88impl<A, R1, R2> From<(R1, R2)> for Reporters<A, R1, R2> {
89    fn from((r1, r2): (R1, R2)) -> Self {
90        Self {
91            r1: Some(r1),
92            r2: Some(r2),
93            _phantom: PhantomData,
94        }
95    }
96}
97
98#[cfg(test)]
99mod tests {
100    use super::*;
101    use commonware_actor::Feedback;
102    use commonware_utils::acknowledgement::{Acknowledgement, Exact};
103    use futures::FutureExt;
104
105    #[derive(Clone, Debug)]
106    struct SimpleAcknowledger;
107
108    impl crate::Reporter for SimpleAcknowledger {
109        type Activity = Exact;
110
111        fn report(&mut self, activity: Self::Activity) -> Feedback {
112            activity.acknowledge();
113            Feedback::Ok
114        }
115    }
116
117    #[test]
118    fn optional_branch_acknowledges() {
119        let mut reporters = Reporters::<Exact, SimpleAcknowledger, SimpleAcknowledger>::from((
120            Some(SimpleAcknowledger),
121            None,
122        ));
123
124        let (ack, waiter) = Exact::handle();
125        assert_eq!(reporters.report(ack), Feedback::Ok);
126
127        assert!(
128            waiter.now_or_never().unwrap().is_ok(),
129            "Waiter did not resolve successfully"
130        );
131    }
132
133    #[test]
134    fn absent_reporter_ignores_activity() {
135        let mut reporter = Option::<SimpleAcknowledger>::None;
136        let (ack, waiter) = Exact::handle();
137        assert_eq!(reporter.report(ack), Feedback::Ok);
138        assert!(waiter.now_or_never().unwrap().is_err());
139    }
140
141    #[test]
142    fn combine_returns_worst_feedback() {
143        assert_eq!(
144            combine(Feedback::Closed, Feedback::Backoff),
145            Feedback::Closed
146        );
147        assert_eq!(combine(Feedback::Backoff, Feedback::Ok), Feedback::Backoff);
148        assert_eq!(combine(Feedback::Ok, Feedback::Ok), Feedback::Ok);
149    }
150}