Skip to main content

reth_network/
state.rs

1//! Keeps track of the state of the network.
2
3use crate::{
4    cache::LruCache,
5    discovery::Discovery,
6    fetch::{BlockResponseOutcome, FetchAction, NewPeerInfo, StateFetcher},
7    message::{BlockRequest, NewBlockMessage, PeerResponse, PeerResponseResult},
8    peers::{PeerAction, PeersManager},
9    session::BlockRangeInfo,
10    FetchClient,
11};
12use alloy_consensus::BlockHeader;
13use alloy_primitives::{
14    map::{FbBuildHasher, HashMap},
15    B256,
16};
17use rand::seq::SliceRandom;
18use reth_eth_wire::{
19    BlockHashNumber, Capabilities, DisconnectReason, EthNetworkPrimitives, EthVersion,
20    GetReceipts70, NetworkPrimitives, NewBlockHashes, NewBlockPayload, UnifiedStatus,
21};
22use reth_ethereum_forks::ForkId;
23use reth_network_api::{DiscoveredEvent, DiscoveryEvent, PeerRequest, PeerRequestSender};
24use reth_network_p2p::receipts::client::ReceiptsResponse;
25use reth_network_peers::PeerId;
26use reth_network_types::{PeerAddr, PeerKind};
27use reth_primitives_traits::Block;
28use std::{
29    collections::VecDeque,
30    fmt,
31    net::{IpAddr, SocketAddr},
32    ops::Deref,
33    sync::{
34        atomic::{AtomicU64, AtomicUsize},
35        Arc,
36    },
37    task::{Context, Poll},
38};
39use tokio::sync::oneshot;
40use tracing::{debug, trace};
41
42/// Cache limit of blocks to keep track of for a single peer.
43const PEER_BLOCK_CACHE_LIMIT: u32 = 512;
44
45/// Wrapper type for the [`BlockNumReader`] trait.
46pub(crate) struct BlockNumReader(Box<dyn reth_storage_api::BlockNumReader>);
47
48impl BlockNumReader {
49    /// Create a new instance with the given reader.
50    pub fn new(reader: impl reth_storage_api::BlockNumReader + 'static) -> Self {
51        Self(Box::new(reader))
52    }
53}
54
55impl fmt::Debug for BlockNumReader {
56    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
57        f.debug_struct("BlockNumReader").field("inner", &"<dyn BlockNumReader>").finish()
58    }
59}
60
61impl Deref for BlockNumReader {
62    type Target = Box<dyn reth_storage_api::BlockNumReader>;
63
64    fn deref(&self) -> &Self::Target {
65        &self.0
66    }
67}
68
69/// The [`NetworkState`] keeps track of the state of all peers in the network.
70///
71/// This includes:
72///   - [`Discovery`]: manages the discovery protocol, essentially a stream of discovery updates
73///   - [`PeersManager`]: keeps track of connected peers and issues new outgoing connections
74///     depending on the configured capacity.
75///   - [`StateFetcher`]: streams download request (received from outside via channel) which are
76///     then send to the session of the peer.
77///
78/// This type is also responsible for responding for received request.
79#[derive(Debug)]
80pub struct NetworkState<N: NetworkPrimitives = EthNetworkPrimitives> {
81    /// All active peers and their state.
82    active_peers: HashMap<PeerId, ActivePeer<N>, FbBuildHasher<64>>,
83    /// Manages connections to peers.
84    peers_manager: PeersManager,
85    /// Buffered messages until polled.
86    queued_messages: VecDeque<StateAction<N>>,
87    /// The client type that can interact with the chain.
88    ///
89    /// This type is used to fetch the block number after we established a session and received the
90    /// [`UnifiedStatus`] block hash.
91    client: BlockNumReader,
92    /// Network discovery.
93    discovery: Discovery,
94    /// The type that handles requests.
95    ///
96    /// The fetcher streams `RLPx` related requests on a per-peer basis to this type. This type
97    /// will then queue in the request and notify the fetcher once the result has been
98    /// received.
99    state_fetcher: StateFetcher<N>,
100}
101
102impl<N: NetworkPrimitives> NetworkState<N> {
103    /// Create a new state instance with the given params
104    pub(crate) fn new(
105        client: BlockNumReader,
106        discovery: Discovery,
107        peers_manager: PeersManager,
108        num_active_peers: Arc<AtomicUsize>,
109    ) -> Self {
110        let state_fetcher = StateFetcher::new(peers_manager.handle(), num_active_peers);
111        Self {
112            active_peers: Default::default(),
113            peers_manager,
114            queued_messages: Default::default(),
115            client,
116            discovery,
117            state_fetcher,
118        }
119    }
120
121    /// Returns mutable access to the [`PeersManager`]
122    pub(crate) const fn peers_mut(&mut self) -> &mut PeersManager {
123        &mut self.peers_manager
124    }
125
126    /// Returns mutable access to the [`Discovery`]
127    pub(crate) const fn discovery_mut(&mut self) -> &mut Discovery {
128        &mut self.discovery
129    }
130
131    /// Returns access to the [`PeersManager`]
132    pub(crate) const fn peers(&self) -> &PeersManager {
133        &self.peers_manager
134    }
135
136    /// Returns a new [`FetchClient`]
137    pub(crate) fn fetch_client(&self) -> FetchClient<N> {
138        self.state_fetcher.client()
139    }
140
141    /// How many peers we're currently connected to.
142    pub fn num_active_peers(&self) -> usize {
143        self.active_peers.len()
144    }
145
146    /// Event hook for an activated session for the peer.
147    ///
148    /// Returns `Ok` if the session is valid, returns an `Err` if the session is not accepted and
149    /// should be rejected.
150    pub(crate) fn on_session_activated(&mut self, activation: SessionActivation<N>) {
151        let SessionActivation {
152            peer,
153            capabilities,
154            version,
155            status,
156            request_tx,
157            timeout,
158            range_info,
159            supports_snap,
160        } = activation;
161
162        debug_assert!(!self.active_peers.contains_key(&peer), "Already connected; not possible");
163
164        // Use the block number from the peer's status (eth/69+) if available,
165        // otherwise fall back to a local lookup by hash.
166        let block_number = status.latest_block.unwrap_or_else(|| {
167            self.client.block_number(status.blockhash).ok().flatten().unwrap_or_default()
168        });
169        self.state_fetcher.new_active_peer(NewPeerInfo {
170            peer_id: peer,
171            best_hash: status.blockhash,
172            best_number: block_number,
173            capabilities: Arc::clone(&capabilities),
174            timeout,
175            range_info,
176            supports_snap,
177        });
178
179        self.active_peers.insert(
180            peer,
181            ActivePeer {
182                best_hash: status.blockhash,
183                version,
184                request_tx,
185                pending_response: None,
186                blocks: LruCache::new(PEER_BLOCK_CACHE_LIMIT),
187            },
188        );
189    }
190
191    /// Event hook for a disconnected session for the given peer.
192    ///
193    /// This will remove the peer from the available set of peers and close all inflight requests.
194    pub(crate) fn on_session_closed(&mut self, peer: PeerId) {
195        self.active_peers.remove(&peer);
196        self.state_fetcher.on_session_closed(&peer);
197    }
198
199    /// Starts propagating the new block to peers that haven't reported the block yet.
200    ///
201    /// This is supposed to be invoked after the block was validated.
202    ///
203    /// > It then sends the block to a small fraction of connected peers (usually the square root of
204    /// > the total number of peers) using the `NewBlock` message.
205    ///
206    /// See also <https://github.com/ethereum/devp2p/blob/master/caps/eth.md>
207    pub(crate) fn announce_new_block(&mut self, msg: NewBlockMessage<N::NewBlockPayload>) {
208        // send a `NewBlock` message to a fraction of the connected peers (square root of the total
209        // number of peers)
210        let num_propagate = (self.active_peers.len() as f64).sqrt() as u64 + 1;
211
212        let number = msg.block.block().header().number();
213        let mut count = 0;
214
215        // Shuffle to propagate to a random sample of peers on every block announcement
216        let mut peers: Vec<_> = self.active_peers.iter_mut().collect();
217        peers.shuffle(&mut rand::rng());
218
219        for (peer_id, peer) in peers {
220            if peer.blocks.contains(&msg.hash) {
221                // skip peers which already reported the block
222                continue
223            }
224
225            // Queue a `NewBlock` message for the peer
226            if count < num_propagate {
227                self.queued_messages
228                    .push_back(StateAction::NewBlock { peer_id: *peer_id, block: msg.clone() });
229
230                // update peer block info
231                if self.state_fetcher.update_peer_block(peer_id, msg.hash, number) {
232                    peer.best_hash = msg.hash;
233                }
234
235                // mark the block as seen by the peer
236                peer.blocks.insert(msg.hash);
237
238                count += 1;
239            }
240
241            if count >= num_propagate {
242                break
243            }
244        }
245    }
246
247    /// Completes the block propagation process started in [`NetworkState::announce_new_block()`]
248    /// but sending `NewBlockHash` broadcast to all peers that haven't seen it yet.
249    pub(crate) fn announce_new_block_hash(&mut self, msg: NewBlockMessage<N::NewBlockPayload>) {
250        let number = msg.block.block().header().number();
251        let hashes = NewBlockHashes(vec![BlockHashNumber { hash: msg.hash, number }]);
252        for (peer_id, peer) in &mut self.active_peers {
253            if peer.blocks.contains(&msg.hash) {
254                // skip peers which already reported the block
255                continue
256            }
257
258            if self.state_fetcher.update_peer_block(peer_id, msg.hash, number) {
259                peer.best_hash = msg.hash;
260            }
261
262            self.queued_messages.push_back(StateAction::NewBlockHashes {
263                peer_id: *peer_id,
264                hashes: hashes.clone(),
265            });
266        }
267    }
268
269    /// Updates the block information for the peer.
270    pub(crate) fn update_peer_block(&mut self, peer_id: &PeerId, hash: B256, number: u64) {
271        if let Some(peer) = self.active_peers.get_mut(peer_id) {
272            peer.best_hash = hash;
273        }
274        self.state_fetcher.update_peer_block(peer_id, hash, number);
275    }
276
277    /// Invoked when a new [`ForkId`] is activated.
278    pub(crate) fn update_fork_id(&self, fork_id: ForkId) {
279        self.discovery.update_fork_id(fork_id)
280    }
281
282    /// Invoked after a `NewBlock` message was received by the peer.
283    ///
284    /// This will keep track of blocks we know a peer has
285    pub(crate) fn on_new_block(&mut self, peer_id: PeerId, hash: B256) {
286        // Mark the blocks as seen
287        if let Some(peer) = self.active_peers.get_mut(&peer_id) {
288            peer.blocks.insert(hash);
289        }
290    }
291
292    /// Invoked for a `NewBlockHashes` broadcast message.
293    pub(crate) fn on_new_block_hashes(&mut self, peer_id: PeerId, hashes: Vec<BlockHashNumber>) {
294        // Mark the blocks as seen
295        if let Some(peer) = self.active_peers.get_mut(&peer_id) {
296            peer.blocks.extend(hashes.into_iter().map(|b| b.hash));
297        }
298    }
299
300    /// Bans the [`IpAddr`] in the discovery service.
301    pub(crate) fn ban_ip_discovery(&self, ip: IpAddr) {
302        trace!(target: "net", ?ip, "Banning discovery");
303        self.discovery.ban_ip(ip)
304    }
305
306    /// Bans the [`PeerId`] and [`IpAddr`] in the discovery service.
307    pub(crate) fn ban_discovery(&self, peer_id: PeerId, ip: IpAddr) {
308        trace!(target: "net", ?peer_id, ?ip, "Banning discovery");
309        self.discovery.ban(peer_id, ip)
310    }
311
312    /// Marks the given peer as trusted.
313    pub(crate) fn add_trusted_peer_id(&mut self, peer_id: PeerId) {
314        self.peers_manager.add_trusted_peer_id(peer_id)
315    }
316
317    /// Adds a trusted peer that may use a hostname, with periodic DNS re-resolution.
318    pub(crate) fn add_trusted_peer_node(&mut self, trusted: reth_network_peers::TrustedPeer) {
319        self.peers_manager.add_trusted_peer_node(trusted)
320    }
321
322    /// Adds a peer and its address with the given kind to the peerset and dials it if there's an
323    /// outbound slot.
324    pub(crate) fn add_peer_kind(
325        &mut self,
326        peer_id: PeerId,
327        kind: Option<PeerKind>,
328        addr: PeerAddr,
329    ) {
330        self.peers_manager.add_requested_peer(peer_id, kind, addr)
331    }
332
333    /// Connects a peer and its address with the given kind
334    pub(crate) fn add_and_connect(&mut self, peer_id: PeerId, kind: PeerKind, addr: PeerAddr) {
335        self.peers_manager.add_and_connect_kind(peer_id, kind, addr, None)
336    }
337
338    /// Removes a peer and its address with the given kind from the peerset.
339    pub(crate) fn remove_peer_kind(&mut self, peer_id: PeerId, kind: PeerKind) {
340        match kind {
341            PeerKind::Basic | PeerKind::Static => self.peers_manager.remove_peer(peer_id),
342            PeerKind::Trusted => self.peers_manager.remove_peer_from_trusted_set(peer_id),
343        }
344    }
345
346    /// Event hook for events received from the discovery service.
347    fn on_discovery_event(&mut self, event: DiscoveryEvent) {
348        match event {
349            DiscoveryEvent::NewNode(DiscoveredEvent::EventQueued { peer_id, addr, fork_id }) => {
350                self.queued_messages.push_back(StateAction::DiscoveredNode {
351                    peer_id,
352                    addr,
353                    fork_id,
354                });
355            }
356            DiscoveryEvent::EnrForkId(record, fork_id) => {
357                let peer_id = record.id;
358                let tcp_addr = record.tcp_addr();
359                if tcp_addr.port() == 0 {
360                    return
361                }
362                let udp_addr = record.udp_addr();
363                let addr = PeerAddr::new(tcp_addr, Some(udp_addr));
364                self.queued_messages.push_back(StateAction::DiscoveredEnrForkId {
365                    peer_id,
366                    addr,
367                    fork_id,
368                });
369            }
370        }
371    }
372
373    /// Event hook for new actions derived from the peer management set.
374    fn on_peer_action(&mut self, action: PeerAction) {
375        match action {
376            PeerAction::Connect { peer_id, remote_addr } => {
377                self.queued_messages.push_back(StateAction::Connect { peer_id, remote_addr });
378            }
379            PeerAction::Disconnect { peer_id, reason } => {
380                self.state_fetcher.on_pending_disconnect(&peer_id);
381                self.queued_messages.push_back(StateAction::Disconnect { peer_id, reason });
382            }
383            PeerAction::DisconnectBannedIncoming { peer_id } |
384            PeerAction::DisconnectUntrustedIncoming { peer_id } => {
385                self.state_fetcher.on_pending_disconnect(&peer_id);
386                self.queued_messages.push_back(StateAction::Disconnect { peer_id, reason: None });
387            }
388            PeerAction::DiscoveryBanPeerId { peer_id, ip_addr } => {
389                self.ban_discovery(peer_id, ip_addr)
390            }
391            PeerAction::DiscoveryBanIp { ip_addr } => self.ban_ip_discovery(ip_addr),
392            PeerAction::PeerAdded(peer_id) => {
393                self.queued_messages.push_back(StateAction::PeerAdded(peer_id))
394            }
395            PeerAction::PeerRemoved(peer_id) => {
396                self.queued_messages.push_back(StateAction::PeerRemoved(peer_id))
397            }
398            PeerAction::BanPeer { .. } | PeerAction::UnBanPeer { .. } => {}
399        }
400    }
401
402    /// Sends The message to the peer's session and queues in a response.
403    ///
404    /// Caution: this will replace an already pending response. It's the responsibility of the
405    /// caller to select the peer.
406    fn handle_block_request(&mut self, peer_id: PeerId, request: BlockRequest) {
407        if let Some(ref mut peer) = self.active_peers.get_mut(&peer_id) {
408            let (request, response) = match request {
409                BlockRequest::GetBlockHeaders(request) => {
410                    let (response, rx) = oneshot::channel();
411                    let request = PeerRequest::GetBlockHeaders { request, response };
412                    let response = PeerResponse::BlockHeaders { response: rx };
413                    (request, response)
414                }
415                BlockRequest::GetBlockBodies(request) => {
416                    let (response, rx) = oneshot::channel();
417                    let request = PeerRequest::GetBlockBodies { request, response };
418                    let response = PeerResponse::BlockBodies { response: rx };
419                    (request, response)
420                }
421                BlockRequest::GetBlockAccessLists(request) => {
422                    let (response, rx) = oneshot::channel();
423                    let request = PeerRequest::GetBlockAccessLists { request, response };
424                    let response = PeerResponse::BlockAccessLists { response: rx };
425                    (request, response)
426                }
427                BlockRequest::GetReceipts(request) => {
428                    if peer.version >= EthVersion::Eth70 {
429                        let (response, rx) = oneshot::channel();
430                        let request = PeerRequest::GetReceipts70 {
431                            request: GetReceipts70 {
432                                first_block_receipt_index: 0,
433                                block_hashes: request.0,
434                            },
435                            response,
436                        };
437                        let response = PeerResponse::Receipts70 { response: rx };
438                        (request, response)
439                    } else if peer.version == EthVersion::Eth69 {
440                        let (response, rx) = oneshot::channel();
441                        let request = PeerRequest::GetReceipts69 { request, response };
442                        let response = PeerResponse::Receipts69 { response: rx };
443                        (request, response)
444                    } else {
445                        let (response, rx) = oneshot::channel();
446                        let request = PeerRequest::GetReceipts { request, response };
447                        let response = PeerResponse::Receipts { response: rx };
448                        (request, response)
449                    }
450                }
451                BlockRequest::GetSnap(request) => {
452                    let (response, rx) = oneshot::channel();
453                    let request = PeerRequest::GetSnap { request: *request, response };
454                    let response = PeerResponse::Snap { response: rx };
455                    (request, response)
456                }
457            };
458            let _ = peer.request_tx.to_session_tx.try_send(request);
459            peer.pending_response = Some(response);
460        }
461    }
462
463    /// Handle the outcome of processed response, for example directly queue another request.
464    fn on_block_response_outcome(&mut self, outcome: BlockResponseOutcome) {
465        match outcome {
466            BlockResponseOutcome::Request(peer, request) => {
467                self.handle_block_request(peer, request);
468            }
469            BlockResponseOutcome::BadResponse(peer, reputation_change) => {
470                self.peers_manager.apply_reputation_change(&peer, reputation_change);
471            }
472        }
473    }
474
475    /// Invoked when received a response from a connected peer.
476    ///
477    /// Delegates the response result to the fetcher which may return an outcome specific
478    /// instruction that needs to be handled in [`Self::on_block_response_outcome`]. This could be
479    /// a follow-up request or an instruction to slash the peer's reputation.
480    fn on_eth_response(&mut self, peer: PeerId, resp: PeerResponseResult<N>) {
481        let outcome = match resp {
482            PeerResponseResult::BlockHeaders(res) => {
483                self.state_fetcher.on_block_headers_response(peer, res)
484            }
485            PeerResponseResult::BlockBodies(res) => {
486                self.state_fetcher.on_block_bodies_response(peer, res)
487            }
488            PeerResponseResult::Receipts(res) => {
489                // Legacy eth/66-68: strip bloom filters and wrap in ReceiptsResponse
490                let normalized = res.map(|blocks| {
491                    let receipts = blocks
492                        .into_iter()
493                        .map(|block_receipts| {
494                            block_receipts.into_iter().map(|rwb| rwb.receipt).collect()
495                        })
496                        .collect();
497                    ReceiptsResponse::new(receipts)
498                });
499                self.state_fetcher.on_receipts_response(peer, normalized)
500            }
501            PeerResponseResult::Receipts69(res) => {
502                let normalized = res.map(ReceiptsResponse::new);
503                self.state_fetcher.on_receipts_response(peer, normalized)
504            }
505            PeerResponseResult::Receipts70(res) => {
506                let normalized = res.map(ReceiptsResponse::from);
507                self.state_fetcher.on_receipts_response(peer, normalized)
508            }
509            PeerResponseResult::BlockAccessLists(res) => {
510                self.state_fetcher.on_block_access_lists_response(peer, res)
511            }
512            PeerResponseResult::Snap(res) => self.state_fetcher.on_snap_response(peer, res),
513            _ => None,
514        };
515
516        if let Some(outcome) = outcome {
517            self.on_block_response_outcome(outcome);
518        }
519    }
520
521    /// Advances the state
522    pub(crate) fn poll(&mut self, cx: &mut Context<'_>) -> Poll<StateAction<N>> {
523        loop {
524            // drain buffered messages
525            if let Some(message) = self.queued_messages.pop_front() {
526                return Poll::Ready(message)
527            }
528
529            while let Poll::Ready(discovery) = self.discovery.poll(cx) {
530                self.on_discovery_event(discovery);
531            }
532
533            while let Poll::Ready(action) = self.state_fetcher.poll(cx) {
534                match action {
535                    FetchAction::BlockRequest { peer_id, request } => {
536                        self.handle_block_request(peer_id, request)
537                    }
538                }
539            }
540
541            loop {
542                // need to buffer results here to make borrow checker happy
543                let mut closed_sessions = Vec::new();
544                let mut received_responses = Vec::new();
545
546                // poll all connected peers for responses
547                for (id, peer) in &mut self.active_peers {
548                    let Some(mut response) = peer.pending_response.take() else { continue };
549                    match response.poll(cx) {
550                        Poll::Ready(res) => {
551                            // check if the error is due to a closed channel to the session
552                            if res.err().is_some_and(|err| err.is_channel_closed()) {
553                                debug!(
554                                    target: "net",
555                                    ?id,
556                                    "Request canceled, response channel from session closed."
557                                );
558                                // if the channel is closed, this means the peer session is also
559                                // closed, in which case we can invoke the
560                                // [Self::on_closed_session]
561                                // immediately, preventing followup requests and propagate the
562                                // connection dropped error
563                                closed_sessions.push(*id);
564                            } else {
565                                received_responses.push((*id, res));
566                            }
567                        }
568                        Poll::Pending => {
569                            // not ready yet, store again.
570                            peer.pending_response = Some(response);
571                        }
572                    };
573                }
574
575                for peer in closed_sessions {
576                    self.on_session_closed(peer)
577                }
578
579                if received_responses.is_empty() {
580                    break;
581                }
582
583                for (peer_id, resp) in received_responses {
584                    self.on_eth_response(peer_id, resp);
585                }
586            }
587
588            // poll peer manager
589            while let Poll::Ready(action) = self.peers_manager.poll(cx) {
590                self.on_peer_action(action);
591            }
592
593            // We need to poll again in case we have received any responses because they may have
594            // triggered follow-up requests.
595            if self.queued_messages.is_empty() {
596                return Poll::Pending
597            }
598        }
599    }
600}
601
602/// Tracks the state of a Peer with an active Session.
603///
604/// For example known blocks,so we can decide what to announce.
605#[derive(Debug)]
606pub(crate) struct ActivePeer<N: NetworkPrimitives> {
607    /// Best block of the peer.
608    pub(crate) best_hash: B256,
609    /// The negotiated eth protocol version of the session.
610    pub(crate) version: EthVersion,
611    /// A communication channel directly to the session task.
612    pub(crate) request_tx: PeerRequestSender<PeerRequest<N>>,
613    /// The response receiver for a currently active request to that peer.
614    pub(crate) pending_response: Option<PeerResponse<N>>,
615    /// Blocks we know the peer has.
616    pub(crate) blocks: LruCache<B256>,
617}
618
619/// Everything [`NetworkState::on_session_activated`] needs to register a newly established
620/// session.
621pub(crate) struct SessionActivation<N: NetworkPrimitives> {
622    /// The remote peer's identifier.
623    pub(crate) peer: PeerId,
624    /// The capabilities the peer announced.
625    pub(crate) capabilities: Arc<Capabilities>,
626    /// The negotiated eth protocol version of the session.
627    pub(crate) version: EthVersion,
628    /// The `Status` message the peer sent during the `eth` handshake.
629    pub(crate) status: Arc<UnifiedStatus>,
630    /// A communication channel directly to the session task.
631    pub(crate) request_tx: PeerRequestSender<PeerRequest<N>>,
632    /// The maximum time the session waits for a response from the peer.
633    pub(crate) timeout: Arc<AtomicU64>,
634    /// The range info for the peer.
635    pub(crate) range_info: Option<BlockRangeInfo>,
636    /// Whether the connection negotiated `snap/2` and can serve [`PeerRequest::GetSnap`].
637    pub(crate) supports_snap: bool,
638}
639
640/// Message variants triggered by the [`NetworkState`]
641#[derive(Debug)]
642pub(crate) enum StateAction<N: NetworkPrimitives> {
643    /// Dispatch a `NewBlock` message to the peer
644    NewBlock {
645        /// Target of the message
646        peer_id: PeerId,
647        /// The `NewBlock` message
648        block: NewBlockMessage<N::NewBlockPayload>,
649    },
650    NewBlockHashes {
651        /// Target of the message
652        peer_id: PeerId,
653        /// `NewBlockHashes` message to send to the peer.
654        hashes: NewBlockHashes,
655    },
656    /// Create a new connection to the given node.
657    Connect { remote_addr: SocketAddr, peer_id: PeerId },
658    /// Disconnect an existing connection
659    Disconnect {
660        peer_id: PeerId,
661        /// Why the disconnect was initiated
662        reason: Option<DisconnectReason>,
663    },
664    /// Retrieved a [`ForkId`] from the peer via ENR request, See <https://eips.ethereum.org/EIPS/eip-868>
665    DiscoveredEnrForkId {
666        peer_id: PeerId,
667        /// The address of the peer.
668        addr: PeerAddr,
669        /// The reported [`ForkId`] by this peer.
670        fork_id: ForkId,
671    },
672    /// A new node was found through the discovery, possibly with a `ForkId`
673    DiscoveredNode { peer_id: PeerId, addr: PeerAddr, fork_id: Option<ForkId> },
674    /// A peer was added
675    PeerAdded(PeerId),
676    /// A peer was dropped
677    PeerRemoved(PeerId),
678}
679
680#[cfg(test)]
681mod tests {
682    use crate::{
683        discovery::Discovery,
684        fetch::StateFetcher,
685        peers::PeersManager,
686        state::{BlockNumReader, NetworkState, SessionActivation},
687        PeerRequest,
688    };
689    use alloy_consensus::Header;
690    use alloy_primitives::B256;
691    use reth_eth_wire::{BlockBodies, Capabilities, Capability, EthNetworkPrimitives, EthVersion};
692    use reth_ethereum_primitives::BlockBody;
693    use reth_network_api::PeerRequestSender;
694    use reth_network_p2p::{bodies::client::BodiesClient, error::RequestError};
695    use reth_network_peers::PeerId;
696    use reth_storage_api::noop::NoopProvider;
697    use std::{
698        future::poll_fn,
699        sync::{atomic::AtomicU64, Arc},
700    };
701    use tokio::sync::mpsc;
702    use tokio_stream::{wrappers::ReceiverStream, StreamExt};
703
704    /// Returns a testing instance of the [`NetworkState`].
705    fn state() -> NetworkState<EthNetworkPrimitives> {
706        let peers = PeersManager::default();
707        let handle = peers.handle();
708        NetworkState {
709            active_peers: Default::default(),
710            peers_manager: Default::default(),
711            queued_messages: Default::default(),
712            client: BlockNumReader(Box::new(NoopProvider::default())),
713            discovery: Discovery::noop(),
714            state_fetcher: StateFetcher::new(handle, Default::default()),
715        }
716    }
717
718    fn capabilities() -> Arc<Capabilities> {
719        Arc::new(vec![Capability::from(EthVersion::Eth67)].into())
720    }
721
722    // tests that ongoing requests are answered with connection dropped if the session that received
723    // that request is drops the request object.
724    #[tokio::test(flavor = "multi_thread")]
725    async fn test_dropped_active_session() {
726        let mut state = state();
727        let client = state.fetch_client();
728
729        let peer_id = PeerId::random();
730        let (tx, session_rx) = mpsc::channel(1);
731        let peer_tx = PeerRequestSender::new(peer_id, tx);
732
733        state.on_session_activated(SessionActivation {
734            peer: peer_id,
735            capabilities: capabilities(),
736            version: EthVersion::Eth67,
737            status: Arc::default(),
738            request_tx: peer_tx,
739            timeout: Arc::new(AtomicU64::new(1)),
740            range_info: None,
741            supports_snap: false,
742        });
743
744        assert!(state.active_peers.contains_key(&peer_id));
745
746        let body = BlockBody { ommers: vec![Header::default()], ..Default::default() };
747
748        let body_response = body.clone();
749
750        // this mimics an active session that receives the requests from the state
751        tokio::task::spawn(async move {
752            let mut stream = ReceiverStream::new(session_rx);
753            let resp = stream.next().await.unwrap();
754            match resp {
755                PeerRequest::GetBlockBodies { response, .. } => {
756                    response.send(Ok(BlockBodies(vec![body_response]))).unwrap();
757                }
758                _ => unreachable!(),
759            }
760
761            // wait for the next request, then drop
762            let _resp = stream.next().await.unwrap();
763        });
764
765        // spawn the state as future
766        tokio::task::spawn(async move {
767            loop {
768                poll_fn(|cx| state.poll(cx)).await;
769            }
770        });
771
772        // send requests to the state via the client
773        let (peer, bodies) = client.get_block_bodies(vec![B256::random()]).await.unwrap().split();
774        assert_eq!(peer, peer_id);
775        assert_eq!(bodies, vec![body]);
776
777        let resp = client.get_block_bodies(vec![B256::random()]).await;
778        assert!(resp.is_err());
779        assert_eq!(resp.unwrap_err(), RequestError::ConnectionDropped);
780    }
781}