Skip to main content

connexa/behaviour/
request_response.rs

1use futures::{FutureExt, StreamExt};
2mod codec;
3
4use crate::behaviour::request_response::codec::Codec;
5use crate::error::{ConnexaResult, Error};
6use bytes::Bytes;
7use futures::channel::mpsc::Sender as MpscSender;
8use futures::channel::oneshot::Sender as OneshotSender;
9use futures::future::BoxFuture;
10use futures::stream::BoxStream;
11use futures_timer::Delay;
12use libp2p::core::Endpoint;
13use libp2p::core::transport::PortUse;
14use libp2p::request_response::{
15    InboundFailure, InboundRequestId, OutboundFailure, OutboundRequestId, ProtocolSupport,
16    ResponseChannel,
17};
18use libp2p::swarm::{
19    ConnectionDenied, ConnectionId, FromSwarm, NetworkBehaviour, THandler, THandlerInEvent,
20    THandlerOutEvent, ToSwarm,
21};
22use libp2p::{Multiaddr, PeerId, StreamProtocol, request_response};
23use pollable_map::futures::FutureMap;
24use pollable_map::optional::Optional;
25use std::collections::HashMap;
26use std::collections::hash_map::Entry;
27use std::io::{Error as IoError, ErrorKind as IoErrorKind};
28use std::task::{Context, Poll};
29use std::time::Duration;
30
31const CHANNEL_LIMIT: usize = 256;
32const TIMEOUT: Duration = Duration::from_secs(30);
33
34#[derive(Debug, Clone)]
35pub struct RequestResponseConfig {
36    pub protocol: String,
37    pub timeout: Option<Duration>,
38    pub max_request_size: usize,
39    pub max_response_size: usize,
40    pub concurrent_streams: Option<usize>,
41    pub channel_buffer: usize,
42    pub protocol_direction: RequestResponseDirection,
43}
44
45#[derive(Debug, Clone, Default)]
46pub enum RequestResponseDirection {
47    /// Support inbound requests
48    In,
49    /// Support outbound requests
50    Out,
51    /// Support both inbound and outbound requests
52    #[default]
53    Both,
54}
55
56impl From<RequestResponseDirection> for ProtocolSupport {
57    fn from(direction: RequestResponseDirection) -> Self {
58        match direction {
59            RequestResponseDirection::In => ProtocolSupport::Inbound,
60            RequestResponseDirection::Out => ProtocolSupport::Outbound,
61            RequestResponseDirection::Both => ProtocolSupport::Full,
62        }
63    }
64}
65
66impl Default for RequestResponseConfig {
67    fn default() -> Self {
68        Self {
69            protocol: "/libp2p/request-response".into(),
70            timeout: None,
71            max_request_size: 512 * 1024,
72            max_response_size: 2 * 1024 * 1024,
73            concurrent_streams: None,
74            channel_buffer: 128,
75            protocol_direction: RequestResponseDirection::default(),
76        }
77    }
78}
79
80impl RequestResponseConfig {
81    pub fn new(protocol: impl Into<String>) -> Self {
82        let protocol = protocol.into();
83        RequestResponseConfig {
84            protocol,
85            ..Default::default()
86        }
87    }
88
89    /// Set a duration which would indicate on how long it should take for a response before the request times out.
90    pub fn set_timeout(mut self, timeout: Duration) -> Self {
91        self.timeout = Some(timeout);
92        self
93    }
94
95    /// Set the maximum size of a request.
96    pub fn set_max_request_size(mut self, size: usize) -> Self {
97        self.max_request_size = size;
98        self
99    }
100
101    /// Set the maximum size of a response.
102    pub fn set_max_response_size(mut self, size: usize) -> Self {
103        self.max_response_size = size;
104        self
105    }
106
107    /// Set the upper bound of inbound and outbound streams.
108    pub fn set_concurrent_streams(mut self, size: usize) -> Self {
109        self.concurrent_streams = Some(size);
110        self
111    }
112
113    /// Set the max slot available to send requests over channels.
114    pub fn set_channel_buffer(mut self, size: usize) -> Self {
115        self.channel_buffer = size;
116        self
117    }
118
119    pub fn set_protocol_direction(mut self, direction: RequestResponseDirection) -> Self {
120        self.protocol_direction = direction;
121        self
122    }
123}
124
125pub struct Behaviour {
126    pending_request: HashMap<PeerId, HashMap<InboundRequestId, ResponseChannel<Bytes>>>,
127
128    pending_response:
129        HashMap<PeerId, HashMap<OutboundRequestId, OneshotSender<ConnexaResult<Bytes>>>>,
130    broadcast_request: Vec<MpscSender<(PeerId, InboundRequestId, Bytes)>>,
131    rr_behaviour: request_response::Behaviour<Codec>,
132
133    gc_timer: Optional<Delay>,
134
135    channel_buffer: usize,
136}
137
138impl Behaviour {
139    pub fn new(config: RequestResponseConfig) -> Self {
140        let mut cfg = request_response::Config::default()
141            .with_request_timeout(config.timeout.unwrap_or(Duration::from_secs(120)));
142        if let Some(size) = config.concurrent_streams {
143            cfg = cfg.with_max_concurrent_streams(size);
144        }
145
146        let protocol = config.protocol;
147
148        let st_protocol = StreamProtocol::try_from_owned(protocol).expect("valid protocol");
149
150        let protocol = vec![(st_protocol, config.protocol_direction.into())];
151
152        let codec = Codec::new(config.max_request_size, config.max_response_size);
153
154        let rr_behaviour = request_response::Behaviour::with_codec(codec, protocol, cfg);
155
156        Self {
157            pending_response: HashMap::new(),
158            pending_request: HashMap::new(),
159            broadcast_request: Vec::new(),
160            rr_behaviour,
161            gc_timer: Optional::default(),
162            channel_buffer: config.channel_buffer,
163        }
164    }
165
166    pub fn subscribe(
167        &mut self,
168    ) -> futures::channel::mpsc::Receiver<(PeerId, InboundRequestId, Bytes)> {
169        let (tx, rx) = futures::channel::mpsc::channel(self.channel_buffer);
170        self.broadcast_request.push(tx);
171        rx
172    }
173
174    pub fn send_request(
175        &mut self,
176        peer_id: PeerId,
177        request: impl Into<Bytes>,
178    ) -> BoxFuture<'static, ConnexaResult<Bytes>> {
179        // Since we are only requesting from a single peer, we will only accept one response, if any, from the stream
180        let mut st = self.send_requests([peer_id], request);
181        Box::pin(async move {
182            match st.next().await {
183                // Since we are accepting from a single peer, thus would be tracking the peer,
184                // we can exclude the peer id from the result.
185                Some((_, result)) => result,
186                None => Err(std::io::Error::from(std::io::ErrorKind::BrokenPipe).into()),
187            }
188        })
189    }
190
191    pub fn send_response(
192        &mut self,
193        peer_id: PeerId,
194        id: InboundRequestId,
195        response: impl Into<Bytes>,
196    ) -> ConnexaResult<()> {
197        let pending_list = self
198            .pending_request
199            .get_mut(&peer_id)
200            .ok_or_else(|| Error::NotFound("pending request".into()))?;
201
202        let ch = pending_list
203            .remove(&id)
204            .ok_or_else(|| Error::NotFound("pending request".into()))?;
205
206        let response = response.into();
207
208        if self.rr_behaviour.send_response(ch, response).is_err() {
209            return Err(IoError::new(
210                IoErrorKind::BrokenPipe,
211                "unable to send response. request either timed out, connection dropped, or unexpected behaviour occurred",
212            )
213            .into());
214        }
215
216        Ok(())
217    }
218
219    pub fn send_requests(
220        &mut self,
221        peers: impl IntoIterator<Item = PeerId>,
222        request: impl Into<Bytes>,
223    ) -> BoxStream<'static, (PeerId, ConnexaResult<Bytes>)> {
224        let request = request.into();
225        let mut oneshots = FutureMap::new();
226        for peer_id in peers {
227            let id = self.rr_behaviour.send_request(&peer_id, request.clone());
228            let (tx, rx) = futures::channel::oneshot::channel();
229            self.pending_response
230                .entry(peer_id)
231                .or_default()
232                .insert(id, tx);
233            oneshots.insert(
234                id,
235                rx.map(move |r| {
236                    let result = match r {
237                        Ok(Ok(bytes)) => Ok(bytes),
238                        Ok(Err(e)) => Err(e),
239                        Err(e) => {
240                            Err(std::io::Error::new(std::io::ErrorKind::BrokenPipe, e).into())
241                        }
242                    };
243                    (peer_id, result)
244                }),
245            );
246        }
247        oneshots.map(|(_id, item)| item).boxed()
248    }
249
250    fn process_request(
251        &mut self,
252        id: InboundRequestId,
253        peer_id: PeerId,
254        request: Bytes,
255        response_channel: ResponseChannel<Bytes>,
256    ) {
257        if self.broadcast_request.is_empty() {
258            // If the node is not listening to the requests, then we should drop the response so it would time out
259            tracing::warn!(%peer_id, request_id=%id, "no subscribers listening to request. dropping request");
260            return;
261        }
262
263        //
264        match self.broadcast_request.len() > CHANNEL_LIMIT {
265            true => {
266                // Because the channels exceed a specific threshold, we will begin do cleanup with a timer instead
267                if self.gc_timer.is_none() {
268                    self.gc_timer.replace(Delay::new(TIMEOUT));
269                }
270            }
271            false => {
272                self.gc_timer.take();
273                self.broadcast_request.retain(|ch| !ch.is_closed());
274            }
275        }
276
277        self.pending_request
278            .entry(peer_id)
279            .or_default()
280            .insert(id, response_channel);
281
282        for tx in self.broadcast_request.iter_mut() {
283            if let Err(e) = tx.try_send((peer_id, id, request.clone())) {
284                tracing::warn!(%peer_id, request_id=%id, error=%e, "unable to send request to subscriber");
285                continue;
286            }
287        }
288    }
289
290    fn process_response(&mut self, id: OutboundRequestId, peer_id: PeerId, response: Bytes) {
291        let Some(list) = self.pending_response.get_mut(&peer_id) else {
292            return;
293        };
294
295        let Some(ch) = list.remove(&id) else {
296            tracing::warn!(%peer_id, request_id=%id, "no pending request available that is awaiting for a response. dropping response");
297            return;
298        };
299
300        if list.is_empty() {
301            self.pending_response.remove(&peer_id);
302        }
303
304        if ch.send(Ok(response)).is_err() {
305            tracing::warn!(%peer_id, request_id=%id, "unable to send response");
306        }
307    }
308
309    fn process_outbound_failure(
310        &mut self,
311        id: OutboundRequestId,
312        peer_id: PeerId,
313        error: OutboundFailure,
314    ) {
315        tracing::error!(%peer_id, request_id=%id, %error, "outbound failure");
316
317        if let Entry::Occupied(mut entry) = self.pending_response.entry(peer_id) {
318            let list = entry.get_mut();
319
320            if let Some(tx) = list.remove(&id) {
321                _ = tx.send(Err(Error::RequestResponse(error)));
322            }
323
324            if list.is_empty() {
325                entry.remove();
326            }
327        }
328    }
329
330    fn process_inbound_failure(
331        &mut self,
332        id: InboundRequestId,
333        peer_id: PeerId,
334        inbound_failure: InboundFailure,
335    ) {
336        tracing::error!(%peer_id, request_id=%id, error=%inbound_failure, "inbound failure");
337        if let Entry::Occupied(mut entry) = self.pending_request.entry(peer_id) {
338            let list = entry.get_mut();
339            list.remove(&id);
340            if list.is_empty() {
341                entry.remove();
342            }
343        }
344    }
345}
346
347impl NetworkBehaviour for Behaviour {
348    type ConnectionHandler =
349        <request_response::Behaviour<Codec> as NetworkBehaviour>::ConnectionHandler;
350    type ToSwarm = ();
351
352    fn handle_pending_inbound_connection(
353        &mut self,
354        connection_id: ConnectionId,
355        local_addr: &Multiaddr,
356        remote_addr: &Multiaddr,
357    ) -> Result<(), ConnectionDenied> {
358        self.rr_behaviour
359            .handle_pending_inbound_connection(connection_id, local_addr, remote_addr)
360    }
361
362    fn handle_pending_outbound_connection(
363        &mut self,
364        connection_id: ConnectionId,
365        maybe_peer: Option<PeerId>,
366        addresses: &[Multiaddr],
367        effective_role: Endpoint,
368    ) -> Result<Vec<Multiaddr>, ConnectionDenied> {
369        self.rr_behaviour.handle_pending_outbound_connection(
370            connection_id,
371            maybe_peer,
372            addresses,
373            effective_role,
374        )
375    }
376
377    fn handle_established_inbound_connection(
378        &mut self,
379        connection_id: ConnectionId,
380        peer: PeerId,
381        local_addr: &Multiaddr,
382        remote_addr: &Multiaddr,
383    ) -> Result<THandler<Self>, ConnectionDenied> {
384        self.rr_behaviour.handle_established_inbound_connection(
385            connection_id,
386            peer,
387            local_addr,
388            remote_addr,
389        )
390    }
391
392    fn handle_established_outbound_connection(
393        &mut self,
394        connection_id: ConnectionId,
395        peer: PeerId,
396        addr: &Multiaddr,
397        role_override: Endpoint,
398        reuse: PortUse,
399    ) -> Result<THandler<Self>, ConnectionDenied> {
400        self.rr_behaviour.handle_established_outbound_connection(
401            connection_id,
402            peer,
403            addr,
404            role_override,
405            reuse,
406        )
407    }
408
409    fn on_connection_handler_event(
410        &mut self,
411        peer_id: PeerId,
412        connection_id: ConnectionId,
413        event: THandlerOutEvent<Self>,
414    ) {
415        self.rr_behaviour
416            .on_connection_handler_event(peer_id, connection_id, event)
417    }
418
419    fn on_swarm_event(&mut self, event: FromSwarm) {
420        self.rr_behaviour.on_swarm_event(event);
421    }
422
423    fn poll(&mut self, cx: &mut Context) -> Poll<ToSwarm<Self::ToSwarm, THandlerInEvent<Self>>> {
424        if self.gc_timer.poll_unpin(cx).is_ready() {
425            self.broadcast_request.retain(|ch| !ch.is_closed());
426            if self.broadcast_request.len() > CHANNEL_LIMIT {
427                self.gc_timer.replace(Delay::new(TIMEOUT));
428            }
429        }
430
431        while let Poll::Ready(event) = self.rr_behaviour.poll(cx) {
432            match event {
433                ToSwarm::GenerateEvent(request_response::Event::Message {
434                    connection_id: _,
435                    peer: peer_id,
436                    message,
437                }) => match message {
438                    request_response::Message::Response {
439                        request_id,
440                        response,
441                    } => self.process_response(request_id, peer_id, response),
442                    request_response::Message::Request {
443                        request_id,
444                        request,
445                        channel,
446                    } => {
447                        self.process_request(request_id, peer_id, request, channel);
448                    }
449                },
450                ToSwarm::GenerateEvent(request_response::Event::ResponseSent {
451                    connection_id: _,
452                    peer: peer_id,
453                    request_id,
454                }) => {
455                    tracing::trace!(%peer_id, %request_id, "response sent");
456                }
457                ToSwarm::GenerateEvent(request_response::Event::OutboundFailure {
458                    connection_id: _,
459                    peer,
460                    request_id,
461                    error,
462                }) => {
463                    tracing::error!(peer_id = %peer, %request_id, ?error, direction="outbound");
464                    self.process_outbound_failure(request_id, peer, error);
465                }
466                ToSwarm::GenerateEvent(request_response::Event::InboundFailure {
467                    connection_id: _,
468                    peer,
469                    request_id,
470                    error,
471                }) => {
472                    tracing::error!(peer_id = %peer, %request_id, ?error, direction="inbound");
473                    self.process_inbound_failure(request_id, peer, error);
474                }
475                other => {
476                    let new_to_swarm =
477                        other.map_out(|_| unreachable!("we manually map `GenerateEvent` variants"));
478                    return Poll::Ready(new_to_swarm);
479                }
480            };
481        }
482
483        Poll::Pending
484    }
485}
486
487#[cfg(test)]
488mod tests {
489    use super::*;
490    use libp2p::core::muxing::StreamMuxerBox;
491    use libp2p::core::transport::MemoryTransport;
492    use libp2p::core::upgrade::Version;
493    use libp2p::multiaddr::Protocol;
494    use libp2p::swarm::SwarmEvent;
495    use libp2p::{Swarm, SwarmBuilder};
496    use libp2p::{Transport, noise, yamux};
497
498    #[tokio::test]
499    async fn send_single_request() {
500        let mut node_a = swarm_node().await;
501        let mut node_b = swarm_node().await;
502
503        let peer_id_a = *node_a.local_peer_id();
504
505        let peer_id_b = *node_b.local_peer_id();
506        let addr_b = node_b.listeners().collect::<Vec<_>>()[0];
507
508        node_a.add_peer_address(peer_id_b, addr_b.clone());
509
510        let mut response_fut = node_a.behaviour_mut().send_request(peer_id_b, "ping");
511        let mut node_b_request_listener = node_b.behaviour_mut().subscribe();
512
513        let mut received_request = false;
514        let mut received_response = false;
515
516        loop {
517            tokio::select! {
518                _event = node_a.select_next_some() => {},
519                _event = node_b.select_next_some() => {},
520                Some((sender_peer_id, id, request)) = node_b_request_listener.next() => {
521                    assert_eq!(sender_peer_id, peer_id_a);
522                    assert_eq!(request, "ping");
523                    received_request = true;
524                    node_b.behaviour_mut().send_response(peer_id_a, id, "pong").expect("channel still active");
525                }
526                response = &mut response_fut => {
527                    let response = response.expect("valid response");
528                    assert_eq!(response, "pong");
529                    received_response = true;
530                }
531            }
532
533            if received_request && received_response {
534                break;
535            }
536        }
537    }
538
539    #[tokio::test]
540    async fn send_single_request_to_multiple_peers() {
541        let mut node_a = swarm_node().await;
542        let mut node_b = swarm_node().await;
543        let mut node_c = swarm_node().await;
544
545        let peer_id_a = *node_a.local_peer_id();
546
547        let peer_id_b = *node_b.local_peer_id();
548        let addr_b = node_b.listeners().collect::<Vec<_>>()[0];
549
550        let peer_id_c = *node_c.local_peer_id();
551        let addr_c = node_c.listeners().collect::<Vec<_>>()[0];
552
553        node_a.add_peer_address(peer_id_b, addr_b.clone());
554        node_a.add_peer_address(peer_id_c, addr_c.clone());
555
556        let mut response_st = node_a
557            .behaviour_mut()
558            .send_requests([peer_id_b, peer_id_c], "ping");
559        let mut node_b_request_listener = node_b.behaviour_mut().subscribe();
560        let mut node_c_request_listener = node_c.behaviour_mut().subscribe();
561
562        let mut received_request_for_b = false;
563        let mut received_request_for_c = false;
564        let mut received_response_from_b = false;
565        let mut received_response_from_c = false;
566
567        loop {
568            tokio::select! {
569                _event = node_a.select_next_some() => {},
570                _event = node_b.select_next_some() => {},
571                _event = node_c.select_next_some() => {},
572                Some((sender_peer_id, id, request)) = node_b_request_listener.next() => {
573                    assert_eq!(sender_peer_id, peer_id_a);
574                    assert_eq!(request, "ping");
575                    received_request_for_b = true;
576                    node_b.behaviour_mut().send_response(peer_id_a, id, "pong_b").expect("channel still active");
577                }
578                Some((sender_peer_id, id, request)) = node_c_request_listener.next() => {
579                    assert_eq!(sender_peer_id, peer_id_a);
580                    assert_eq!(request, "ping");
581                    received_request_for_c = true;
582                    node_c.behaviour_mut().send_response(peer_id_a, id, "pong_c").expect("channel still active");
583                }
584                Some((peer_id, response)) = response_st.next() => {
585                    match response {
586                        Ok(response) if response == "pong_b" => {
587                            assert_eq!(peer_id, peer_id_b);
588                            received_response_from_b = true;
589                        },
590                        Ok(response) if response == "pong_c" => {
591                            assert_eq!(peer_id, peer_id_c);
592                            received_response_from_c = true;
593                        }
594                        _ => unreachable!(),
595                    }
596                }
597            }
598
599            if received_request_for_b
600                && received_request_for_c
601                && received_response_from_b
602                && received_response_from_c
603            {
604                break;
605            }
606        }
607    }
608
609    async fn swarm_node() -> Swarm<Behaviour> {
610        let mut swarm = SwarmBuilder::with_new_identity()
611            .with_tokio()
612            .with_other_transport(|k| {
613                let auth_upgrade = noise::Config::new(k)?;
614                let multiplex_upgrade = yamux::Config::default();
615                let memory_transport = MemoryTransport::new();
616                let transport = memory_transport
617                    .upgrade(Version::V1)
618                    .authenticate(auth_upgrade)
619                    .multiplex(multiplex_upgrade)
620                    .map(|(peer, muxer), _| (peer, StreamMuxerBox::new(muxer)))
621                    .boxed();
622                Ok(transport)
623            })
624            .unwrap()
625            .with_behaviour(|_| Ok(Behaviour::new(Default::default())))
626            .unwrap()
627            .build();
628
629        let id = swarm
630            .listen_on(Multiaddr::empty().with(Protocol::Memory(0)))
631            .expect("valid listener");
632
633        if let SwarmEvent::NewListenAddr {
634            listener_id,
635            address: _,
636        } = swarm.next().await.expect("swarm havent dropped")
637        {
638            assert_eq!(listener_id, id);
639        }
640
641        swarm
642    }
643}