Skip to main content

commonware_collector/p2p/
engine.rs

1use super::{
2    Config,
3    ingress::{Mailbox, Message},
4};
5use crate::p2p::{Handler, Monitor};
6use commonware_actor::mailbox;
7use commonware_codec::Codec;
8use commonware_cryptography::{Committable, Digestible, PublicKey};
9use commonware_macros::select_loop;
10use commonware_p2p::{Blocker, Receiver, Recipients, Sender, utils::codec::wrap};
11use commonware_runtime::{
12    BufferPooler, Clock, ContextCell, Handle, Metrics, Spawner, spawn_cell,
13    telemetry::metrics::{Counter, Gauge, GaugeExt, MetricsExt as _},
14};
15use commonware_utils::{channel::oneshot, futures::Pool};
16use std::collections::{HashMap, HashSet};
17use tracing::{debug, error};
18
19/// Engine that will disperse messages and collect responses.
20pub struct Engine<E, B, Rq, Rs, P, M, H>
21where
22    E: BufferPooler + Clock + Spawner,
23    P: PublicKey,
24    B: Blocker<PublicKey = P>,
25    Rq: Committable + Digestible + Codec,
26    Rs: Committable<Commitment = Rq::Commitment> + Digestible<Digest = Rq::Digest> + Codec,
27    M: Monitor<Response = Rs, PublicKey = P>,
28    H: Handler<Request = Rq, Response = Rs, PublicKey = P>,
29{
30    // Configuration
31    context: ContextCell<E>,
32    blocker: B,
33    priority_request: bool,
34    request_codec: Rq::Cfg,
35    priority_response: bool,
36    response_codec: Rs::Cfg,
37
38    // Message passing
39    monitor: M,
40    handler: H,
41    mailbox: mailbox::Receiver<Message<P, Rq>>,
42
43    // State
44    tracked: HashMap<Rq::Commitment, (HashSet<P>, HashSet<P>)>,
45
46    // Metrics
47    outstanding: Gauge,
48    requests: Counter,
49    responses: Counter,
50}
51
52impl<E, B, Rq, Rs, P, M, H> Engine<E, B, Rq, Rs, P, M, H>
53where
54    E: BufferPooler + Clock + Spawner + Metrics,
55    P: PublicKey,
56    B: Blocker<PublicKey = P>,
57    Rq: Committable + Digestible + Codec,
58    Rs: Committable<Commitment = Rq::Commitment> + Digestible<Digest = Rq::Digest> + Codec,
59    M: Monitor<Response = Rs, PublicKey = P>,
60    H: Handler<Request = Rq, Response = Rs, PublicKey = P>,
61{
62    /// Creates a new engine with the given configuration.
63    ///
64    /// Returns a tuple of the engine and the mailbox for sending messages.
65    pub fn new(context: E, cfg: Config<B, M, H, Rq::Cfg, Rs::Cfg>) -> (Self, Mailbox<P, Rq>) {
66        // Create mailbox
67        let (tx, rx) = mailbox::new(context.child("mailbox"), cfg.mailbox_size);
68        let mailbox: Mailbox<P, Rq> = Mailbox::new(tx);
69
70        // Create metrics
71        let outstanding = context.gauge("outstanding", "outstanding commitments");
72        let requests = context.counter("requests", "processed requests");
73        let responses = context.counter("responses", "sent responses");
74
75        (
76            Self {
77                context: ContextCell::new(context),
78                blocker: cfg.blocker,
79                priority_request: cfg.priority_request,
80                request_codec: cfg.request_codec,
81                priority_response: cfg.priority_response,
82                response_codec: cfg.response_codec,
83                monitor: cfg.monitor,
84                handler: cfg.handler,
85                mailbox: rx,
86                tracked: HashMap::new(),
87                outstanding,
88                requests,
89                responses,
90            },
91            mailbox,
92        )
93    }
94
95    /// Starts the engine with the given network channels.
96    ///
97    /// Returns a handle that can be used to wait for the engine to complete.
98    pub fn start(
99        mut self,
100        requests: (impl Sender<PublicKey = P>, impl Receiver<PublicKey = P>),
101        responses: (impl Sender<PublicKey = P>, impl Receiver<PublicKey = P>),
102    ) -> Handle<()> {
103        spawn_cell!(self.context, self.run(requests, responses))
104    }
105
106    async fn run(
107        mut self,
108        requests: (impl Sender<PublicKey = P>, impl Receiver<PublicKey = P>),
109        responses: (impl Sender<PublicKey = P>, impl Receiver<PublicKey = P>),
110    ) {
111        // Wrap channels
112        let (mut req_tx, mut req_rx) = wrap(
113            self.request_codec,
114            self.context.network_buffer_pool().clone(),
115            requests.0,
116            requests.1,
117        );
118        let (mut res_tx, mut res_rx) = wrap(
119            self.response_codec,
120            self.context.network_buffer_pool().clone(),
121            responses.0,
122            responses.1,
123        );
124
125        // Create futures pool
126        let mut processed: Pool<'_, Result<(P, Rs), oneshot::error::RecvError>> = Pool::default();
127        select_loop! {
128            self.context,
129            on_stopped => {
130                debug!("context shutdown, stopping engine");
131            },
132            // Command from the mailbox
133            Some(command) = self.mailbox.recv() else continue => match command {
134                Message::Send {
135                    request,
136                    recipients,
137                } => {
138                    let commitment = request.commitment();
139                    let recipients = req_tx.send(recipients, request, self.priority_request);
140                    if !recipients.is_empty() {
141                        let entry = self.tracked.entry(commitment).or_insert_with(|| {
142                            self.outstanding.inc();
143                            (HashSet::new(), HashSet::new())
144                        });
145                        entry.0.extend(recipients);
146                    }
147                }
148                Message::Cancel { commitment } => {
149                    if self.tracked.remove(&commitment).is_none() {
150                        debug!(?commitment, "ignoring removal of unknown commitment");
151                    }
152                    let _ = self.outstanding.try_set(self.tracked.len());
153                }
154            },
155
156            // Response from a handler
157            Ok((peer, reply)) = processed.next_completed() else continue => {
158                self.responses.inc();
159
160                // Send the response
161                let _ = res_tx.send(Recipients::One(peer), reply, self.priority_response);
162            },
163
164            // Request from an originator
165            message = req_rx.recv() => {
166                self.requests.inc();
167
168                // Error handling
169                let (peer, msg) = match message {
170                    Ok(r) => r,
171                    Err(err) => {
172                        error!(?err, "request receiver failed");
173                        break;
174                    }
175                };
176                let msg = match msg {
177                    Ok(msg) => msg,
178                    Err(err) => {
179                        commonware_p2p::block!(self.blocker, peer, ?err, "invalid request");
180                        continue;
181                    }
182                };
183
184                // Handle the request
185                let (tx, rx) = oneshot::channel();
186                self.handler.process(peer.clone(), msg, tx);
187                processed.push(async move { Ok((peer, rx.await?)) });
188            },
189
190            // Response from a handler
191            response = res_rx.recv() => {
192                // Error handling
193                let (peer, msg) = match response {
194                    Ok(r) => r,
195                    Err(err) => {
196                        error!(?err, "response receiver failed");
197                        break;
198                    }
199                };
200                let msg = match msg {
201                    Ok(msg) => msg,
202                    Err(err) => {
203                        commonware_p2p::block!(self.blocker, peer, ?err, "invalid response");
204                        continue;
205                    }
206                };
207
208                // Handle the response
209                let commitment = msg.commitment();
210                let Some(responses) = self.tracked.get_mut(&commitment) else {
211                    debug!(?commitment, ?peer, "response for unknown commitment");
212                    continue;
213                };
214                if !responses.0.contains(&peer) {
215                    debug!(?commitment, ?peer, "never sent request");
216                    continue;
217                }
218                if !responses.1.insert(peer.clone()) {
219                    debug!(?commitment, ?peer, "duplicate response");
220                    continue;
221                }
222
223                // Send the response to the monitor
224                self.monitor.collected(peer, msg, responses.1.len());
225            },
226        }
227    }
228}