Skip to main content

snarkos_node_bft/
gateway.rs

1// Copyright (c) 2019-2026 Provable Inc.
2// This file is part of the snarkOS library.
3
4// Licensed under the Apache License, Version 2.0 (the "License");
5// you may not use this file except in compliance with the License.
6// You may obtain a copy of the License at:
7
8// http://www.apache.org/licenses/LICENSE-2.0
9
10// Unless required by applicable law or agreed to in writing, software
11// distributed under the License is distributed on an "AS IS" BASIS,
12// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13// See the License for the specific language governing permissions and
14// limitations under the License.
15
16#[cfg(feature = "telemetry")]
17use crate::helpers::Telemetry;
18use crate::{
19    CONTEXT,
20    MAX_BATCH_DELAY,
21    MEMORY_POOL_PORT,
22    Worker,
23    events::{DisconnectReason, EventCodec, PrimaryPing},
24    helpers::{Cache, PrimarySender, Storage, SyncSender, WorkerSender, assign_to_worker},
25    spawn_blocking,
26};
27use smol_str::SmolStr;
28use snarkos_account::Account;
29use snarkos_node_bft_events::{
30    BlockRequest,
31    BlockResponse,
32    CertificateRequest,
33    CertificateResponse,
34    ChallengeRequest,
35    ChallengeResponse,
36    DataBlocks,
37    Event,
38    EventTrait,
39    TransmissionRequest,
40    TransmissionResponse,
41    ValidatorsRequest,
42    ValidatorsResponse,
43};
44use snarkos_node_bft_ledger_service::LedgerService;
45use snarkos_node_network::{
46    ConnectionMode,
47    NodeType,
48    Peer,
49    PeerPoolHandling,
50    Resolver,
51    bootstrap_peers,
52    get_repo_commit_hash,
53    log_repo_sha_comparison,
54    shorten_snarkos_sha,
55};
56use snarkos_node_sync::{MAX_BLOCKS_BEHIND, communication_service::CommunicationService};
57use snarkos_node_tcp::{
58    Config,
59    ConnectError,
60    Connection,
61    ConnectionSide,
62    P2P,
63    Tcp,
64    connections::DisconnectOrigin,
65    protocols::{Disconnect, Handshake, OnConnect, Reading, Writing},
66};
67use snarkos_utilities::NodeDataDir;
68use snarkvm::{
69    console::prelude::*,
70    ledger::{
71        committee::Committee,
72        narwhal::{BatchHeader, Data},
73    },
74    prelude::{Address, Field},
75    utilities::flatten_error,
76};
77
78use colored::Colorize;
79use futures::{SinkExt, future::join_all};
80use indexmap::IndexMap;
81#[cfg(feature = "locktick")]
82use locktick::parking_lot::{Mutex, RwLock};
83#[cfg(not(feature = "locktick"))]
84use parking_lot::{Mutex, RwLock};
85use rand::seq::{IteratorRandom, SliceRandom};
86use std::{
87    collections::{HashMap, HashSet},
88    future::Future,
89    io,
90    net::{Ipv4Addr, SocketAddr, SocketAddrV4},
91    sync::Arc,
92    time::Duration,
93};
94use tokio::{
95    net::TcpStream,
96    sync::{OnceCell, oneshot},
97    task::{self, JoinHandle},
98};
99use tokio_stream::StreamExt;
100use tokio_util::codec::Framed;
101
102/// The maximum interval of events to cache.
103const CACHE_EVENTS_INTERVAL: i64 = (MAX_BATCH_DELAY.as_secs()) as i64; // seconds
104/// The maximum interval of requests to cache.
105const CACHE_REQUESTS_INTERVAL: i64 = (MAX_BATCH_DELAY.as_secs()) as i64; // seconds
106
107/// The maximum number of connection attempts in an interval.
108#[cfg(not(test))]
109const MAX_CONNECTION_ATTEMPTS: usize = 10;
110
111/// The maximum number of validators to send in a validators response event.
112pub const MAX_VALIDATORS_TO_SEND: usize = 200;
113
114/// The minimum permitted interval between connection attempts for an IP; anything shorter is considered malicious.
115#[cfg(not(test))]
116const CONNECTION_ATTEMPTS_SINCE_SECS: i64 = 10;
117
118/// The amount of time an IP address is prohibited from connecting.
119const IP_BAN_TIME_IN_SECS: u64 = 300;
120
121/// Part of the Gateway API that deals with networking.
122/// This is a separate trait to allow for easier testing/mocking.
123#[async_trait]
124pub trait Transport<N: Network>: Send + Sync {
125    async fn send(&self, peer_ip: SocketAddr, event: Event<N>) -> Option<oneshot::Receiver<io::Result<()>>>;
126    fn broadcast(&self, event: Event<N>);
127}
128
129/// The gateway maintains connections to other validators.
130/// For connections with clients and provers, the Router logic is used.
131#[derive(Clone)]
132pub struct Gateway<N: Network>(Arc<InnerGateway<N>>);
133
134impl<N: Network> Deref for Gateway<N> {
135    type Target = Arc<InnerGateway<N>>;
136
137    fn deref(&self) -> &Self::Target {
138        &self.0
139    }
140}
141
142pub struct InnerGateway<N: Network> {
143    /// The account of the node.
144    account: Account<N>,
145    /// The storage.
146    storage: Storage<N>,
147    /// The ledger service.
148    ledger: Arc<dyn LedgerService<N>>,
149    /// The TCP stack.
150    tcp: Tcp,
151    /// The cache.
152    cache: Cache<N>,
153    /// The resolver.
154    resolver: RwLock<Resolver<N>>,
155    /// The collection of both candidate and connected peers.
156    peer_pool: RwLock<HashMap<SocketAddr, Peer<N>>>,
157    #[cfg(feature = "telemetry")]
158    validator_telemetry: Telemetry<N>,
159    /// The primary sender.
160    primary_sender: OnceCell<PrimarySender<N>>,
161    /// The worker senders.
162    worker_senders: OnceCell<IndexMap<u8, WorkerSender<N>>>,
163    /// The sync sender.
164    sync_sender: OnceCell<SyncSender<N>>,
165    /// The spawned handles.
166    handles: Mutex<Vec<JoinHandle<()>>>,
167    /// The storage mode.
168    node_data_dir: NodeDataDir,
169    /// If the flag is set, the node will only connect to trusted peers.
170    trusted_peers_only: bool,
171    /// The development mode.
172    dev: Option<u16>,
173}
174
175impl<N: Network> PeerPoolHandling<N> for Gateway<N> {
176    const MAXIMUM_POOL_SIZE: usize = 200;
177    const OWNER: &str = CONTEXT;
178    const PEER_SLASHING_COUNT: usize = 20;
179
180    fn peer_pool(&self) -> &RwLock<HashMap<SocketAddr, Peer<N>>> {
181        &self.peer_pool
182    }
183
184    fn resolver(&self) -> &RwLock<Resolver<N>> {
185        &self.resolver
186    }
187
188    fn is_dev(&self) -> bool {
189        self.dev.is_some()
190    }
191
192    fn trusted_peers_only(&self) -> bool {
193        self.trusted_peers_only
194    }
195
196    fn node_type(&self) -> NodeType {
197        NodeType::Validator
198    }
199}
200
201impl<N: Network> Gateway<N> {
202    /// Initializes a new gateway.
203    #[allow(clippy::too_many_arguments)]
204    pub fn new(
205        account: Account<N>,
206        storage: Storage<N>,
207        ledger: Arc<dyn LedgerService<N>>,
208        ip: Option<SocketAddr>,
209        trusted_validators: &[SocketAddr],
210        trusted_peers_only: bool,
211        node_data_dir: NodeDataDir,
212        dev: Option<u16>,
213    ) -> Result<Self> {
214        // Initialize the gateway IP.
215        let ip = match (ip, dev) {
216            (None, Some(dev)) => SocketAddr::V4(SocketAddrV4::new(Ipv4Addr::LOCALHOST, MEMORY_POOL_PORT + dev)),
217            (None, None) => SocketAddr::V4(SocketAddrV4::new(Ipv4Addr::UNSPECIFIED, MEMORY_POOL_PORT)),
218            (Some(ip), _) => ip,
219        };
220        // Initialize the TCP stack.
221        //
222        // The 10x multiplier allows for more TCP connections than the maximum
223        // committee size to prevent "connection refused" errors when two nodes
224        // simultaneous attempt to connect to each other. Note, that later,
225        // during handshake, the Gateway applies its own limit to the number of
226        // active connections and removes duplicates.
227        let tcp = Tcp::new(Config::new(ip, Committee::<N>::max_committee_size() * 10));
228
229        // Prepare the collection of the initial peers.
230        let mut initial_peers = HashMap::new();
231
232        // Load entries from the validator cache (if present and if we are not in trusted peers only mode).
233        if !trusted_peers_only {
234            let cached_peers = Self::load_cached_peers(&node_data_dir.gateway_peer_cache_path())?;
235            for addr in cached_peers {
236                initial_peers.insert(addr, Peer::new_candidate(addr, false));
237            }
238        }
239
240        // Add the trusted peers to the list of the initial peers; this may promote
241        // some of the cached validators to trusted ones.
242        initial_peers.extend(trusted_validators.iter().copied().map(|addr| (addr, Peer::new_candidate(addr, true))));
243
244        // Return the gateway.
245        Ok(Self(Arc::new(InnerGateway {
246            account,
247            storage,
248            ledger,
249            tcp,
250            cache: Default::default(),
251            resolver: Default::default(),
252            peer_pool: RwLock::new(initial_peers),
253            #[cfg(feature = "telemetry")]
254            validator_telemetry: Default::default(),
255            primary_sender: Default::default(),
256            worker_senders: Default::default(),
257            sync_sender: Default::default(),
258            handles: Default::default(),
259            node_data_dir,
260            trusted_peers_only,
261            dev,
262        })))
263    }
264
265    /// Run the gateway.
266    pub async fn run(
267        &self,
268        primary_sender: PrimarySender<N>,
269        worker_senders: IndexMap<u8, WorkerSender<N>>,
270        sync_sender: Option<SyncSender<N>>,
271    ) {
272        debug!("Starting the gateway for the memory pool...");
273
274        // Set the primary sender.
275        self.primary_sender.set(primary_sender).expect("Primary sender already set in gateway");
276
277        // Set the worker senders.
278        self.worker_senders.set(worker_senders).expect("The worker senders are already set");
279
280        // If the sync sender was provided, set the sync sender.
281        if let Some(sync_sender) = sync_sender {
282            self.sync_sender.set(sync_sender).expect("Sync sender already set in gateway");
283        }
284
285        // Enable the TCP protocols.
286        self.enable_handshake().await;
287        self.enable_reading().await;
288        self.enable_writing().await;
289        self.enable_disconnect().await;
290        self.enable_on_connect().await;
291
292        // Spawn a loop for periodic metrics.
293        #[cfg(feature = "metrics")]
294        {
295            let gateway = self.clone();
296            self.spawn(async move {
297                loop {
298                    tokio::time::sleep(Duration::from_secs(1)).await;
299                    gateway.update_metrics();
300                }
301            });
302        }
303
304        // Enable the TCP listener. Note: This must be called after the above protocols.
305        let listen_addr = self.tcp.enable_listener().await.expect("Failed to enable the TCP listener");
306        debug!("Listening for validator connections at address {listen_addr:?}");
307
308        // Initialize the heartbeat.
309        self.initialize_heartbeat();
310
311        info!("Started the gateway for the memory pool at '{}'", self.local_ip());
312    }
313}
314
315// Dynamic rate limiting.
316impl<N: Network> Gateway<N> {
317    /// The current maximum committee size.
318    fn max_committee_size(&self) -> usize {
319        self.ledger
320            .current_committee()
321            .map_or_else(|_e| Committee::<N>::max_committee_size() as usize, |committee| committee.num_members())
322    }
323
324    /// The maximum number of events to cache.
325    fn max_cache_events(&self) -> usize {
326        self.max_cache_transmissions()
327    }
328
329    /// The maximum number of certificate requests to cache.
330    fn max_cache_certificates(&self) -> usize {
331        2 * BatchHeader::<N>::MAX_GC_ROUNDS * self.max_committee_size()
332    }
333
334    /// The maximum number of transmission requests to cache.
335    fn max_cache_transmissions(&self) -> usize {
336        self.max_cache_certificates() * BatchHeader::<N>::MAX_TRANSMISSIONS_PER_BATCH
337    }
338
339    /// The maximum number of duplicates for any particular request.
340    fn max_cache_duplicates(&self) -> usize {
341        self.max_committee_size().pow(2)
342    }
343}
344
345#[async_trait]
346impl<N: Network> CommunicationService for Gateway<N> {
347    /// The message type.
348    type Message = Event<N>;
349
350    /// Prepares a block request to be sent.
351    fn prepare_block_request(start_height: u32, end_height: u32) -> Self::Message {
352        debug_assert!(start_height < end_height, "Invalid block request format");
353        Event::BlockRequest(BlockRequest { start_height, end_height })
354    }
355
356    /// Sends the given message to specified peer.
357    ///
358    /// This function returns as soon as the message is queued to be sent,
359    /// without waiting for the actual delivery; instead, the caller is provided with a [`oneshot::Receiver`]
360    /// which can be used to determine when and whether the message has been delivered.
361    async fn send(&self, peer_ip: SocketAddr, message: Self::Message) -> Option<oneshot::Receiver<io::Result<()>>> {
362        Transport::send(self, peer_ip, message).await
363    }
364}
365
366impl<N: Network> Gateway<N> {
367    /// Returns the account of the node.
368    pub fn account(&self) -> &Account<N> {
369        &self.account
370    }
371
372    /// Returns the dev identifier of the node.
373    pub fn dev(&self) -> Option<u16> {
374        self.dev
375    }
376
377    /// Returns a reference to the ledger.
378    pub fn ledger(&self) -> &Arc<dyn LedgerService<N>> {
379        &self.ledger
380    }
381
382    /// Returns the resolver.
383    pub fn resolver(&self) -> &RwLock<Resolver<N>> {
384        &self.resolver
385    }
386
387    /// Returns the listener IP address from the (ambiguous) peer address.
388    pub fn resolve_to_listener(&self, connected_addr: &SocketAddr) -> Option<SocketAddr> {
389        self.resolver.read().get_listener(*connected_addr)
390    }
391
392    /// Returns the validator telemetry.
393    #[cfg(feature = "telemetry")]
394    pub fn validator_telemetry(&self) -> &Telemetry<N> {
395        &self.validator_telemetry
396    }
397
398    /// Returns the primary sender.
399    pub fn primary_sender(&self) -> &PrimarySender<N> {
400        self.primary_sender.get().expect("Primary sender not set in gateway")
401    }
402
403    /// Returns the number of workers.
404    pub fn num_workers(&self) -> u8 {
405        u8::try_from(self.worker_senders.get().expect("Missing worker senders in gateway").len())
406            .expect("Too many workers")
407    }
408
409    /// Returns the worker sender for the given worker ID.
410    pub fn get_worker_sender(&self, worker_id: u8) -> Option<&WorkerSender<N>> {
411        self.worker_senders.get().and_then(|senders| senders.get(&worker_id))
412    }
413
414    /// Returns `true` if the given peer IP is an authorized validator.
415    pub fn is_authorized_validator_ip(&self, ip: SocketAddr) -> bool {
416        // If the peer IP is in the trusted validators, return early.
417        if self.trusted_peers().contains(&ip) {
418            return true;
419        }
420        // Retrieve the Aleo address of the peer IP.
421        match self.resolve_to_aleo_addr(ip) {
422            // Determine if the peer IP is an authorized validator.
423            Some(address) => self.is_authorized_validator_address(address),
424            None => {
425                warn!("{CONTEXT} Could not resolve the Aleo address for '{ip}'");
426                false
427            }
428        }
429    }
430
431    /// Returns `true` if the given address is an authorized validator.
432    pub fn is_authorized_validator_address(&self, validator_address: Address<N>) -> bool {
433        // Determine if the validator address is a member of the committee lookback,
434        // the current committee, or the previous committee lookbacks.
435        // We allow leniency in this validation check in order to accommodate these two scenarios:
436        //  1. New validators should be able to connect immediately once bonded as a committee member.
437        //  2. Existing validators must remain connected until they are no longer bonded as a committee member.
438        //     (i.e. meaning they must stay online until the next block has been produced)
439
440        // Determine if the validator is in the current committee with lookback.
441        if self
442            .ledger
443            .get_committee_lookback_for_round(self.storage.current_round())
444            .is_ok_and(|committee| committee.is_committee_member(validator_address))
445        {
446            return true;
447        }
448
449        // Determine if the validator is in the latest committee on the ledger.
450        if self.ledger.current_committee().is_ok_and(|committee| committee.is_committee_member(validator_address)) {
451            return true;
452        }
453
454        // Retrieve the previous block height to consider from the sync tolerance.
455        let previous_block_height = self.ledger.latest_block_height().saturating_sub(MAX_BLOCKS_BEHIND);
456        // Determine if the validator is in any of the previous committee lookbacks.
457        match self.ledger.get_block_round(previous_block_height) {
458            Ok(block_round) => (block_round..self.storage.current_round()).step_by(2).any(|round| {
459                self.ledger
460                    .get_committee_lookback_for_round(round)
461                    .is_ok_and(|committee| committee.is_committee_member(validator_address))
462            }),
463            Err(_) => false,
464        }
465    }
466
467    /// Returns the list of connected addresses.
468    pub fn connected_addresses(&self) -> HashSet<Address<N>> {
469        self.get_connected_peers().into_iter().map(|peer| peer.aleo_addr).collect()
470    }
471
472    /// Ensure the peer is allowed to connect.
473    fn ensure_peer_is_allowed(&self, listener_addr: SocketAddr) -> Result<(), DisconnectReason> {
474        // Ensure the peer IP is not this node.
475        if self.is_local_ip(listener_addr) {
476            return Err(DisconnectReason::SelfConnect);
477        }
478
479        Ok(())
480    }
481
482    /// Updates the connection metrics for the gateway. Ignores the bootstrap clients.
483    #[cfg(feature = "metrics")]
484    fn update_metrics(&self) {
485        if let Some(count) = self.number_of_connected_validators() {
486            metrics::gauge(metrics::bft::CONNECTED, count as f64);
487        }
488        if let Some(count) = self.number_of_connecting_peers() {
489            metrics::gauge(metrics::bft::CONNECTING, count as f64);
490        }
491    }
492
493    /// Inserts the given peer into the connected peers. This is only used in testing.
494    #[cfg(test)]
495    pub fn insert_connected_peer(&self, peer_ip: SocketAddr, peer_addr: SocketAddr, address: Address<N>) {
496        // Adds a bidirectional map between the listener address and (ambiguous) peer address.
497        self.resolver.write().insert_peer(peer_ip, peer_addr, Some(address));
498        // Add a transmission for this peer in the connected peers.
499        self.peer_pool.write().insert(peer_ip, Peer::new_connecting(peer_ip, false));
500        if let Some(peer) = self.peer_pool.write().get_mut(&peer_ip) {
501            peer.upgrade_to_connected(
502                peer_addr,
503                peer_ip.port(),
504                address,
505                NodeType::Validator,
506                0,
507                get_repo_commit_hash(),
508                ConnectionMode::Gateway,
509            );
510        }
511    }
512
513    /// Sends the given event to specified peer.
514    ///
515    /// This function returns as soon as the event is queued to be sent,
516    /// without waiting for the actual delivery; instead, the caller is provided with a [`oneshot::Receiver`]
517    /// which can be used to determine when and whether the event has been delivered.
518    fn send_inner(&self, peer_ip: SocketAddr, event: Event<N>) -> Option<oneshot::Receiver<io::Result<()>>> {
519        // Resolve the listener IP to the (ambiguous) peer address.
520        let Some(peer_addr) = self.resolve_to_ambiguous(peer_ip) else {
521            warn!("Unable to resolve the listener IP address '{peer_ip}'");
522            return None;
523        };
524        // Retrieve the event name.
525        let name = event.name();
526        // Send the event to the peer.
527        trace!("{CONTEXT} Sending '{name}' to '{peer_ip}'");
528        let result = self.unicast(peer_addr, event);
529        // If the event was unable to be sent, disconnect.
530        if let Err(err) = &result {
531            warn!("{CONTEXT} Failed to send '{name}' to '{peer_ip}': {err:?}");
532            debug!("{CONTEXT} Disconnecting from '{peer_ip}' (unable to send)");
533            self.disconnect(peer_ip);
534        }
535        result.ok()
536    }
537
538    /// Handles the inbound event from the peer. The returned value indicates whether
539    /// the connection is still active, and errors cause a disconnect once they are
540    /// propagated to the caller.
541    async fn inbound(&self, peer_addr: SocketAddr, event: Event<N>) -> Result<bool> {
542        // Retrieve the listener IP for the peer.
543        let Some(peer_ip) = self.resolver.read().get_listener(peer_addr) else {
544            // No longer connected to the peer.
545            trace!("Dropping a {} from {peer_addr} - no longer connected.", event.name());
546            return Ok(false);
547        };
548        // Ensure that the peer is an authorized committee member or a bootstrapper.
549        if !(self.is_authorized_validator_ip(peer_ip)
550            || self
551                .get_connected_peer(peer_ip)
552                .map(|peer| peer.node_type == NodeType::BootstrapClient)
553                .unwrap_or(false))
554        {
555            bail!("{CONTEXT} Dropping '{}' from '{peer_ip}' (not authorized)", event.name())
556        }
557        // Drop the peer, if they have exceeded the rate limit (i.e. they are requesting too much from us).
558        let num_events = self.cache.insert_inbound_event(peer_ip, CACHE_EVENTS_INTERVAL);
559        if num_events >= self.max_cache_events() {
560            bail!("Dropping '{peer_ip}' for spamming events (num_events = {num_events})")
561        }
562        // Rate limit for duplicate requests.
563        match event {
564            Event::CertificateRequest(_) | Event::CertificateResponse(_) => {
565                // Retrieve the certificate ID.
566                let certificate_id = match &event {
567                    Event::CertificateRequest(CertificateRequest { certificate_id }) => *certificate_id,
568                    Event::CertificateResponse(CertificateResponse { certificate }) => certificate.id(),
569                    _ => unreachable!(),
570                };
571                // Skip processing this certificate if the rate limit was exceed (i.e. someone is spamming a specific certificate).
572                let num_events = self.cache.insert_inbound_certificate(certificate_id, CACHE_REQUESTS_INTERVAL);
573                if num_events >= self.max_cache_duplicates() {
574                    return Ok(true);
575                }
576            }
577            Event::TransmissionRequest(TransmissionRequest { transmission_id })
578            | Event::TransmissionResponse(TransmissionResponse { transmission_id, .. }) => {
579                // Skip processing this certificate if the rate limit was exceeded (i.e. someone is spamming a specific certificate).
580                let num_events = self.cache.insert_inbound_transmission(transmission_id, CACHE_REQUESTS_INTERVAL);
581                if num_events >= self.max_cache_duplicates() {
582                    return Ok(true);
583                }
584            }
585            Event::BlockRequest(_) => {
586                let num_events = self.cache.insert_inbound_block_request(peer_ip, CACHE_REQUESTS_INTERVAL);
587                if num_events >= self.max_cache_duplicates() {
588                    return Ok(true);
589                }
590            }
591            _ => {}
592        }
593        trace!("{CONTEXT} Received '{}' from '{peer_ip}'", event.name());
594
595        // This match statement handles the inbound event by deserializing the event,
596        // checking the event is valid, and then calling the appropriate (trait) handler.
597        match event {
598            Event::BatchPropose(batch_propose) => {
599                // Send the batch propose to the primary.
600                let _ = self.primary_sender().tx_batch_propose.send((peer_ip, batch_propose)).await;
601                Ok(true)
602            }
603            Event::BatchSignature(batch_signature) => {
604                // Send the batch signature to the primary.
605                let _ = self.primary_sender().tx_batch_signature.send((peer_ip, batch_signature)).await;
606                Ok(true)
607            }
608            Event::BatchCertified(batch_certified) => {
609                // Send the batch certificate to the primary.
610                let _ = self.primary_sender().tx_batch_certified.send((peer_ip, batch_certified.certificate)).await;
611                Ok(true)
612            }
613            Event::BlockRequest(block_request) => {
614                let BlockRequest { start_height, end_height } = block_request;
615
616                // Ensure the block request is well-formed.
617                if start_height >= end_height {
618                    bail!("Block request from '{peer_ip}' has an invalid range ({start_height}..{end_height})")
619                }
620                // Ensure that the block request is within the allowed bounds.
621                if end_height - start_height > DataBlocks::<N>::MAXIMUM_NUMBER_OF_BLOCKS as u32 {
622                    bail!("Block request from '{peer_ip}' has an excessive range ({start_height}..{end_height})")
623                }
624
625                // End height is exclusive.
626                let latest_consensus_version = N::CONSENSUS_VERSION(end_height - 1)?;
627
628                let self_ = self.clone();
629                let blocks = match task::spawn_blocking(move || {
630                    // Retrieve the blocks within the requested range.
631                    match self_.ledger.get_blocks(start_height..end_height) {
632                        Ok(blocks) => Ok(DataBlocks(blocks)),
633                        Err(error) => bail!("Missing blocks {start_height} to {end_height} from ledger - {error}"),
634                    }
635                })
636                .await
637                {
638                    Ok(Ok(blocks)) => blocks,
639                    Ok(Err(error)) => return Err(error),
640                    Err(error) => return Err(anyhow!("[BlockRequest] {error}")),
641                };
642
643                let self_ = self.clone();
644                tokio::spawn(async move {
645                    // Send the `BlockResponse` message to the peer.
646                    let event =
647                        Event::BlockResponse(BlockResponse::new(block_request, blocks, latest_consensus_version));
648                    Transport::send(&self_, peer_ip, event).await;
649                });
650                Ok(true)
651            }
652            Event::BlockResponse(BlockResponse { request, latest_consensus_version, blocks, .. }) => {
653                // Process the block response. Except for some tests, there is always a sync sender.
654                if let Some(sync_sender) = self.sync_sender.get() {
655                    // Check the response corresponds to a request.
656                    if !self.cache.remove_outbound_block_request(peer_ip, &request) {
657                        bail!("Unsolicited block response from '{peer_ip}'")
658                    }
659
660                    // Perform the deferred non-blocking deserialization of the blocks.
661                    // The deserialization can take a long time (minutes). We should not be running
662                    // this on a blocking task, but on a rayon thread pool.
663                    let (send, recv) = tokio::sync::oneshot::channel();
664                    rayon::spawn_fifo(move || {
665                        let blocks = blocks.deserialize_blocking().map_err(|error| anyhow!("[BlockResponse] {error}"));
666                        let _ = send.send(blocks);
667                    });
668                    let blocks = match recv.await {
669                        Ok(Ok(blocks)) => blocks,
670                        Ok(Err(error)) => bail!("Peer '{peer_ip}' sent an invalid block response - {error}"),
671                        Err(error) => bail!("Peer '{peer_ip}' sent an invalid block response - {error}"),
672                    };
673
674                    // Ensure the block response is well-formed.
675                    blocks.ensure_response_is_well_formed(peer_ip, request.start_height, request.end_height)?;
676                    // Send the blocks to the sync module.
677                    match sync_sender.insert_block_response(peer_ip, blocks.0, latest_consensus_version).await {
678                        Ok(_) => Ok(true),
679                        Err(err) if err.is_benign() => {
680                            let err: anyhow::Error = err.into();
681                            let err = err.context(format!("Ignoring block response from peer '{peer_ip}'"));
682                            debug!("{}", flatten_error(err));
683                            Ok(true)
684                        }
685                        Err(err) if err.is_invalid_consensus_version() => {
686                            let err: anyhow::Error = err.into();
687                            let err = err.context(format!("Peer sent an invalid block response '{peer_ip}'"));
688
689                            let msg = flatten_error(&err);
690                            error!("{msg}");
691                            self.ip_ban_peer(peer_ip, Some(&msg));
692                            Err(err)
693                        }
694                        Err(err) => {
695                            let err: anyhow::Error = err.into();
696                            let err = err.context(format!("Peer '{peer_ip}' sent an invalid block response"));
697                            warn!("{}", flatten_error(err));
698
699                            // TODO(kaimast): This needs more testing to ensure disconnect is the correct action.
700                            Ok(true)
701                        }
702                    }
703                } else {
704                    debug!("Ignoring block response from '{peer_ip}' - no sync sender");
705                    Ok(true)
706                }
707            }
708            Event::CertificateRequest(certificate_request) => {
709                // Send the certificate request to the sync module.
710                // Except for some tests, there is always a sync sender.
711                if let Some(sync_sender) = self.sync_sender.get() {
712                    // Send the certificate request to the sync module.
713                    let _ = sync_sender.tx_certificate_request.send((peer_ip, certificate_request)).await;
714                }
715                Ok(true)
716            }
717            Event::CertificateResponse(certificate_response) => {
718                // Send the certificate response to the sync module.
719                // Except for some tests, there is always a sync sender.
720                if let Some(sync_sender) = self.sync_sender.get() {
721                    // Send the certificate response to the sync module.
722                    let _ = sync_sender.tx_certificate_response.send((peer_ip, certificate_response)).await;
723                }
724                Ok(true)
725            }
726            Event::ChallengeRequest(..) | Event::ChallengeResponse(..) => {
727                // Disconnect as the peer is not following the protocol.
728                bail!("{CONTEXT} Peer '{peer_ip}' is not following the protocol")
729            }
730            Event::Disconnect(message) => {
731                // The peer informs us that they had disconnected. Disconnect from them too.
732                debug!("Peer '{peer_ip}' decided to disconnect due to '{}'", message.reason);
733                self.disconnect(peer_ip);
734                Ok(false)
735            }
736            Event::PrimaryPing(ping) => {
737                let PrimaryPing { version, block_locators, primary_certificate } = ping;
738
739                // Ensure the event version is not outdated.
740                if version < Event::<N>::VERSION {
741                    bail!("Dropping '{peer_ip}' on event version {version} (outdated)");
742                }
743
744                // Log the validator's height.
745                debug!("Validator '{peer_ip}' is at height {}", block_locators.latest_locator_height());
746
747                // Update the peer locators. Except for some tests, there is always a sync sender.
748                if let Some(sync_sender) = self.sync_sender.get() {
749                    // Check the block locators are valid, and update the validators in the sync module.
750                    if let Err(error) = sync_sender.update_peer_locators(peer_ip, block_locators).await {
751                        bail!("Validator '{peer_ip}' sent invalid block locators - {error}");
752                    }
753                }
754
755                // Send the batch certificates to the primary.
756                let _ = self.primary_sender().tx_primary_ping.send((peer_ip, primary_certificate)).await;
757                Ok(true)
758            }
759            Event::TransmissionRequest(request) => {
760                // TODO (howardwu): Add rate limiting checks on this event, on a per-peer basis.
761                // Determine the worker ID.
762                let Ok(worker_id) = assign_to_worker(request.transmission_id, self.num_workers()) else {
763                    warn!("{CONTEXT} Unable to assign transmission ID '{}' to a worker", request.transmission_id);
764                    return Ok(true);
765                };
766                // Send the transmission request to the worker.
767                if let Some(sender) = self.get_worker_sender(worker_id) {
768                    // Send the transmission request to the worker.
769                    let _ = sender.tx_transmission_request.send((peer_ip, request)).await;
770                }
771                Ok(true)
772            }
773            Event::TransmissionResponse(response) => {
774                // Determine the worker ID.
775                let Ok(worker_id) = assign_to_worker(response.transmission_id, self.num_workers()) else {
776                    warn!("{CONTEXT} Unable to assign transmission ID '{}' to a worker", response.transmission_id);
777                    return Ok(true);
778                };
779                // Send the transmission response to the worker.
780                if let Some(sender) = self.get_worker_sender(worker_id) {
781                    // Send the transmission response to the worker.
782                    let _ = sender.tx_transmission_response.send((peer_ip, response)).await;
783                }
784                Ok(true)
785            }
786            Event::ValidatorsRequest(_) => {
787                let mut connected_peers = self.get_best_connected_peers(Some(MAX_VALIDATORS_TO_SEND));
788                connected_peers.shuffle(&mut rand::rng());
789
790                let self_ = self.clone();
791                tokio::spawn(async move {
792                    // Initialize the validators.
793                    let mut validators = IndexMap::with_capacity(MAX_VALIDATORS_TO_SEND);
794                    // Iterate over the validators.
795                    for validator in connected_peers.into_iter() {
796                        // Add the validator to the list of validators.
797                        validators.insert(validator.listener_addr, validator.aleo_addr);
798                    }
799                    // Send the validators response to the peer.
800                    let event = Event::ValidatorsResponse(ValidatorsResponse { validators });
801                    Transport::send(&self_, peer_ip, event).await;
802                });
803                Ok(true)
804            }
805            Event::ValidatorsResponse(response) => {
806                if self.trusted_peers_only {
807                    bail!("{CONTEXT} Not accepting validators response from '{peer_ip}' (trusted peers only)");
808                }
809                let ValidatorsResponse { validators } = response;
810                // Ensure the number of validators is not too large.
811                ensure!(validators.len() <= MAX_VALIDATORS_TO_SEND, "{CONTEXT} Received too many validators");
812                // Ensure the cache contains a validators request for this peer.
813                if !self.cache.contains_outbound_validators_request(peer_ip) {
814                    bail!("{CONTEXT} Received validators response from '{peer_ip}' without a validators request")
815                }
816                // Decrement the number of validators requests for this peer.
817                self.cache.decrement_outbound_validators_requests(peer_ip);
818
819                // Add valid validators as candidates to the peer pool; only validator-related
820                // filters need to be applied, the rest is handled by `PeerPoolHandling`.
821                let valid_addrs = validators
822                    .into_iter()
823                    .filter_map(|(listener_addr, aleo_addr)| {
824                        (self.account.address() != aleo_addr
825                            && !self.is_connected_address(aleo_addr)
826                            && self.is_authorized_validator_address(aleo_addr))
827                        .then_some((listener_addr, None))
828                    })
829                    .collect::<Vec<_>>();
830                if !valid_addrs.is_empty() {
831                    self.insert_candidate_peers(valid_addrs);
832                }
833
834                Ok(true)
835            }
836            Event::WorkerPing(ping) => {
837                // Ensure the number of transmissions is not too large.
838                ensure!(
839                    ping.transmission_ids.len() <= Worker::<N>::MAX_TRANSMISSIONS_PER_WORKER_PING,
840                    "{CONTEXT} Received too many transmissions"
841                );
842                // Retrieve the number of workers.
843                let num_workers = self.num_workers();
844                // Iterate over the transmission IDs.
845                for transmission_id in ping.transmission_ids.into_iter() {
846                    // Determine the worker ID.
847                    let Ok(worker_id) = assign_to_worker(transmission_id, num_workers) else {
848                        warn!("{CONTEXT} Unable to assign transmission ID '{transmission_id}' to a worker");
849                        continue;
850                    };
851                    // Send the transmission ID to the worker.
852                    if let Some(sender) = self.get_worker_sender(worker_id) {
853                        // Send the transmission ID to the worker.
854                        let _ = sender.tx_worker_ping.send((peer_ip, transmission_id)).await;
855                    }
856                }
857                Ok(true)
858            }
859        }
860    }
861
862    /// Initialize a new instance of the heartbeat.
863    fn initialize_heartbeat(&self) {
864        let self_clone = self.clone();
865        self.spawn(async move {
866            // Sleep briefly to ensure the other nodes are ready to connect.
867            tokio::time::sleep(Duration::from_millis(1000)).await;
868            info!("Starting the heartbeat of the gateway...");
869            loop {
870                // Process a heartbeat in the gateway.
871                self_clone.heartbeat().await;
872                // Sleep for the heartbeat interval.
873                tokio::time::sleep(Duration::from_secs(15)).await;
874            }
875        });
876    }
877
878    /// Spawns a task with the given future; it should only be used for long-running tasks.
879    #[allow(dead_code)]
880    fn spawn<T: Future<Output = ()> + Send + 'static>(&self, future: T) {
881        self.handles.lock().push(tokio::spawn(future));
882    }
883
884    /// Shuts down the gateway.
885    pub async fn shut_down(&self) {
886        info!("Shutting down the gateway...");
887        // Save the best peers for future use.
888        if let Err(e) = self.save_best_peers(&self.node_data_dir.gateway_peer_cache_path(), None, true) {
889            warn!("Failed to persist best validators to disk: {e}");
890        }
891        // Abort the tasks.
892        self.handles.lock().iter().for_each(|handle| handle.abort());
893        // Close the listener.
894        self.tcp.shut_down().await;
895    }
896}
897
898impl<N: Network> Gateway<N> {
899    /// The minimum time between connection attempts to a peer.
900    const MINIMUM_TIME_BETWEEN_CONNECTION_ATTEMPTS: Duration = Duration::from_secs(10);
901    /// The uptime after which nodes log a warning about missing validator connections.
902    const MISSING_VALIDATOR_CONNECTIONS_GRACE_PERIOD: Duration = Duration::from_secs(60);
903
904    /// Handles the heartbeat request.
905    async fn heartbeat(&self) {
906        // Log the connected validators.
907        self.log_connected_validators();
908        // Log the validator participation scores.
909        #[cfg(feature = "telemetry")]
910        self.log_participation_scores();
911        // Keep the trusted validators connected.
912        self.handle_trusted_validators();
913        // Keep the bootstrap peers within the allowed range.
914        self.handle_bootstrap_peers().await;
915        // Removes any validators that not in the current committee.
916        self.handle_unauthorized_validators();
917        // If the number of connected validators is less than the minimum, send a `ValidatorsRequest`.
918        self.handle_min_connected_validators().await;
919        // Unban any addresses whose ban time has expired.
920        self.handle_banned_ips();
921    }
922
923    /// Logs the connected validators.
924    fn log_connected_validators(&self) {
925        // Retrieve the connected validators and current committee.
926        // The gatway may also be connected to bootstrap clients, which we should not log as connected validators.
927        let connected_validators = self.filter_connected_peers(|peer| peer.node_type == NodeType::Validator);
928
929        let committee = match self.ledger.current_committee() {
930            Ok(c) => c,
931            Err(err) => {
932                error!("Failed to get current committee: {err}");
933                return;
934            }
935        };
936
937        // Resolve the total number of connectable validators.
938        let validators_total = committee.num_members().saturating_sub(1);
939        // Format the total validators message.
940        let total_validators = format!("(of {validators_total} bonded validators)").dimmed();
941        // Construct the connections message.
942        let connections_msg = match connected_validators.len() {
943            0 => "No connected validators".to_string(),
944            num_connected => format!("Connected to {num_connected} validators {total_validators}"),
945        };
946        info!("{connections_msg}");
947
948        // Collect the connected validator addresses and stake.
949        let mut connected_validator_addresses = HashSet::with_capacity(connected_validators.len());
950        let mut connected_validator_shas: HashMap<SmolStr, u64> = HashMap::with_capacity(connected_validators.len());
951        // Insert our sha.
952        let our_sha = shorten_snarkos_sha(&get_repo_commit_hash());
953        let our_stake = committee.get_stake(self.account.address());
954        connected_validator_shas.insert(our_sha.clone(), our_stake);
955        // Include our own address.
956        connected_validator_addresses.insert(self.account.address());
957        // Include and log the connected validators.
958        for peer in &connected_validators {
959            // Register the Aleo address.
960            let address = peer.aleo_addr;
961            connected_validator_addresses.insert(address);
962            // Register the snarkOS commit SHA and the associated stake.
963            let address_stake = committee.get_stake(address);
964            let short_peer_sha = shorten_snarkos_sha(&peer.snarkos_sha);
965            *connected_validator_shas.entry(short_peer_sha.clone()).or_default() += address_stake;
966
967            debug!(
968                "{}",
969                format!(
970                    "  Connected to: {} - {} (connection age {:?})",
971                    peer.listener_addr,
972                    peer.aleo_addr,
973                    peer.first_seen.elapsed()
974                )
975                .dimmed()
976            );
977        }
978
979        // Log how much of the stake uses our git commit hash.
980        if let Some(combined_stake) = connected_validator_shas.get(&our_sha) {
981            let percentage = *combined_stake as f64 / committee.total_stake() as f64 * 100.0;
982            debug!("{}", format!("  Combined stake @ {our_sha}: {percentage:.2}%").dimmed());
983            #[cfg(feature = "metrics")]
984            metrics::gauge(metrics::bft::CONNECTED_STAKE_WITH_MATCHING_SHA, percentage);
985        }
986
987        // Log the validators that are not connected.
988        let num_not_connected = validators_total.saturating_sub(connected_validators.len());
989        if num_not_connected > 0 && self.tcp().uptime() > Self::MISSING_VALIDATOR_CONNECTIONS_GRACE_PERIOD {
990            // Cache the total stake for computing percentages.
991            let total_stake = committee.total_stake();
992            let total_stake_f64 = total_stake as f64;
993
994            // Collect the committee members.
995            let committee_members: HashSet<_> =
996                self.ledger.current_committee().map(|c| c.members().keys().copied().collect()).unwrap_or_default();
997
998            let not_connected_stake: u64 = committee_members
999                .difference(&connected_validator_addresses)
1000                .map(|address| {
1001                    let address_stake = committee.get_stake(*address);
1002                    let address_stake_as_percentage =
1003                        if total_stake == 0 { 0.0 } else { address_stake as f64 / total_stake_f64 * 100.0 };
1004                    debug!(
1005                        "{}",
1006                        format!("  Not connected to {address} ({address_stake_as_percentage:.2}% of total stake)")
1007                            .dimmed()
1008                    );
1009                    address_stake
1010                })
1011                .sum();
1012
1013            let not_connected_stake_as_percentage =
1014                if total_stake == 0 { 0.0 } else { not_connected_stake as f64 / total_stake_f64 * 100.0 };
1015            warn!(
1016                "Not connected to {num_not_connected} validators {total_validators} ({not_connected_stake_as_percentage:.2}% of total stake not connected)"
1017            );
1018            #[cfg(feature = "metrics")]
1019            {
1020                let connected_stake_as_percentage = 100.0 - not_connected_stake_as_percentage;
1021                metrics::gauge(metrics::bft::CONNECTED_STAKE, connected_stake_as_percentage);
1022            }
1023        } else {
1024            #[cfg(feature = "metrics")]
1025            metrics::gauge(metrics::bft::CONNECTED_STAKE, 100.0);
1026        };
1027
1028        if !committee.is_quorum_threshold_reached(&connected_validator_addresses) {
1029            // Not being connected to a quorum of validators is begning during startup.
1030            if self.tcp().uptime() > Self::MISSING_VALIDATOR_CONNECTIONS_GRACE_PERIOD {
1031                error!("Not connected to a quorum of validators");
1032            } else {
1033                debug!("Not connected to a quorum of validators");
1034            }
1035        }
1036    }
1037
1038    // Logs the validator participation scores.
1039    #[cfg(feature = "telemetry")]
1040    fn log_participation_scores(&self) {
1041        if let Ok(committee_lookback) = self.ledger.get_committee_lookback_for_round(self.storage.current_round()) {
1042            // Retrieve the participation scores.
1043            let participation_scores = self.validator_telemetry().get_participation_scores(&committee_lookback);
1044
1045            // Log the participation scores.
1046            debug!("Participation Scores (in the last {} rounds):", self.storage.max_gc_rounds());
1047            for (address, (cert_score, sig_score)) in participation_scores {
1048                debug!(
1049                    "{}",
1050                    format!("  {address} - certificates: {cert_score:.2}%  signatures: {sig_score:.2}%").dimmed()
1051                );
1052            }
1053        }
1054    }
1055
1056    /// This function attempts to connect to any disconnected trusted validators.
1057    fn handle_trusted_validators(&self) {
1058        let trusted_peers = self.trusted_peers();
1059
1060        // Attempt to re-establish connections with any trusted peer that is not connected already.
1061        let handles: Vec<JoinHandle<_>> = trusted_peers
1062            .iter()
1063            .filter_map(|validator_ip| {
1064                // Attempt to connect to the trusted validator.
1065                match self.connect(*validator_ip) {
1066                    Ok(hdl) => Some(hdl),
1067                    Err(ConnectError::SelfConnect { .. })
1068                    | Err(ConnectError::AlreadyConnected { .. })
1069                    | Err(ConnectError::AlreadyConnecting { .. }) => None,
1070                    Err(err) => {
1071                        warn!("Could not initiate connection to trusted validator at '{validator_ip}' - {err}");
1072                        None
1073                    }
1074                }
1075            })
1076            .collect();
1077
1078        if !handles.is_empty() {
1079            info!("Reconnnecting to {} out of {} trusted validators", handles.len(), trusted_peers.len());
1080        }
1081    }
1082
1083    /// This function keeps the number of bootstrap peers within the allowed range.
1084    async fn handle_bootstrap_peers(&self) {
1085        // Return early if we are in trusted peers only mode.
1086        if self.trusted_peers_only {
1087            return;
1088        }
1089        // Split the bootstrap peers into connected and candidate lists.
1090        let mut candidate_bootstrap = Vec::new();
1091        let connected_bootstrap = self.filter_connected_peers(|peer| peer.node_type == NodeType::BootstrapClient);
1092        for bootstrap_ip in bootstrap_peers::<N>(self.is_dev()) {
1093            if !connected_bootstrap.iter().any(|peer| peer.listener_addr == bootstrap_ip) {
1094                candidate_bootstrap.push(bootstrap_ip);
1095            }
1096        }
1097        // If there are not enough connected bootstrap peers, connect to more.
1098        if connected_bootstrap.is_empty() {
1099            // Sample a random bootstrap peer to connect to (drop rng before any await).
1100            let peer_to_connect = candidate_bootstrap.into_iter().choose(&mut rand::rng());
1101            if let Some(peer_ip) = peer_to_connect {
1102                match self.connect(peer_ip) {
1103                    Ok(hdl) => {
1104                        debug!("{CONTEXT} (Re-)connecting to bootstrap peer at '{peer_ip}'");
1105                        let result = hdl.await;
1106                        if let Err(err) = result {
1107                            warn!("{CONTEXT} Failed to connect to bootstrap peer at '{peer_ip}' - {err}");
1108                        }
1109                    }
1110                    Err(ConnectError::AlreadyConnected { .. }) | Err(ConnectError::AlreadyConnecting { .. }) => {}
1111                    Err(err) => {
1112                        warn!("{CONTEXT} Could not initiate connection to bootstrap peer at '{peer_ip}' - {err}")
1113                    }
1114                }
1115            }
1116        }
1117        // Determine if the node is connected to more bootstrap peers than allowed.
1118        let num_surplus = connected_bootstrap.len().saturating_sub(1);
1119        if num_surplus > 0 {
1120            // Sample peers to disconnect (drop rng before any await).
1121            let peers_to_disconnect = connected_bootstrap.into_iter().sample(&mut rand::rng(), num_surplus);
1122            for peer in peers_to_disconnect {
1123                info!("{CONTEXT} Disconnecting from '{}' (exceeded maximum bootstrap)", peer.listener_addr);
1124                <Self as Transport<N>>::send(
1125                    self,
1126                    peer.listener_addr,
1127                    Event::Disconnect(DisconnectReason::NoReasonGiven.into()),
1128                )
1129                .await;
1130                // Disconnect from this peer.
1131                self.disconnect(peer.listener_addr);
1132            }
1133        }
1134    }
1135
1136    /// This function attempts to disconnect any validators that are not in the current committee.
1137    fn handle_unauthorized_validators(&self) {
1138        let self_ = self.clone();
1139        tokio::spawn(async move {
1140            // Retrieve the connected validators.
1141            let validators = self_.get_connected_peers();
1142            // Iterate over the validator IPs.
1143            for peer in validators {
1144                // Skip bootstrapper peers.
1145                if peer.node_type == NodeType::BootstrapClient {
1146                    continue;
1147                }
1148                // Disconnect any validator that is not in the current committee.
1149                if !self_.is_authorized_validator_ip(peer.listener_addr) {
1150                    warn!(
1151                        "{CONTEXT} Disconnecting from '{}' - Validator is not in the current committee",
1152                        peer.listener_addr
1153                    );
1154                    Transport::send(&self_, peer.listener_addr, DisconnectReason::ProtocolViolation.into()).await;
1155                    // Disconnect from this peer.
1156                    self_.disconnect(peer.listener_addr);
1157                }
1158            }
1159        });
1160    }
1161
1162    /// This function sends a `ValidatorsRequest` to a random validator,
1163    /// if the number of connected validators is less than the minimum.
1164    /// It also attempts to connect to known unconnected validators.
1165    async fn handle_min_connected_validators(&self) {
1166        // Attempt to connect to untrusted validators we're not connected to yet.
1167        // The trusted ones are already handled by `handle_trusted_validators`.
1168        let trusted_validators = self.trusted_peers();
1169        if self.number_of_connected_peers() < N::LATEST_MAX_CERTIFICATES() as usize {
1170            let (addrs, handles): (Vec<_>, Vec<_>) = self
1171                .get_candidate_peers()
1172                .iter()
1173                .filter_map(|peer| {
1174                    if trusted_validators.contains(&peer.listener_addr) {
1175                        return None;
1176                    }
1177
1178                    if let Some(previous_attempt) = peer.last_connection_attempt
1179                        && previous_attempt.elapsed() < Self::MINIMUM_TIME_BETWEEN_CONNECTION_ATTEMPTS
1180                    {
1181                        return None;
1182                    }
1183
1184                    match self.connect(peer.listener_addr) {
1185                        Ok(hdl) => Some((peer.listener_addr, hdl)),
1186                        Err(ConnectError::AlreadyConnected { .. })
1187                        | Err(ConnectError::AlreadyConnecting { .. })
1188                        | Err(ConnectError::SelfConnect { .. }) => None,
1189                        Err(err) => {
1190                            warn!(
1191                                "{CONTEXT} Could not initiate connection to validator at '{}' - {err}",
1192                                peer.listener_addr
1193                            );
1194                            None
1195                        }
1196                    }
1197                })
1198                .unzip();
1199
1200            for (addr, result) in addrs.into_iter().zip(join_all(handles).await) {
1201                if let Err(err) = result {
1202                    warn!("{CONTEXT} Failed to connect to validator at '{addr}' - {err}");
1203                }
1204            }
1205
1206            // Retrieve the connected validators.
1207            let validators = self.connected_peers();
1208            // If there are no validator IPs to connect to, return early.
1209            if validators.is_empty() {
1210                return;
1211            }
1212            // Select a random validator IP.
1213            if let Some(validator_ip) = validators.into_iter().choose(&mut rand::rng()) {
1214                let self_ = self.clone();
1215                tokio::spawn(async move {
1216                    // Increment the number of outbound validators requests for this validator.
1217                    self_.cache.increment_outbound_validators_requests(validator_ip);
1218                    // Send a `ValidatorsRequest` to the validator.
1219                    let _ = Transport::send(&self_, validator_ip, Event::ValidatorsRequest(ValidatorsRequest)).await;
1220                });
1221            }
1222        }
1223    }
1224
1225    /// Processes a message received from the network.
1226    async fn process_message_inner(&self, peer_addr: SocketAddr, message: Event<N>) {
1227        // Process the message. Disconnect if the peer violated the protocol.
1228        if let Err(error) = self.inbound(peer_addr, message).await
1229            && let Some(peer_ip) = self.resolver.read().get_listener(peer_addr)
1230        {
1231            warn!("{CONTEXT} Disconnecting from '{peer_ip}' - {error}");
1232            let self_ = self.clone();
1233            tokio::spawn(async move {
1234                Transport::send(&self_, peer_ip, DisconnectReason::ProtocolViolation.into()).await;
1235                // Disconnect from this peer.
1236                self_.disconnect(peer_ip);
1237            });
1238        }
1239    }
1240
1241    // Remove addresses whose ban time has expired.
1242    fn handle_banned_ips(&self) {
1243        self.tcp.banned_peers().remove_old_bans(IP_BAN_TIME_IN_SECS);
1244    }
1245}
1246
1247#[async_trait]
1248impl<N: Network> Transport<N> for Gateway<N> {
1249    /// Sends the given event to specified peer.
1250    ///
1251    /// This method is rate limited to prevent spamming the peer.
1252    ///
1253    /// This function returns as soon as the event is queued to be sent,
1254    /// without waiting for the actual delivery; instead, the caller is provided with a [`oneshot::Receiver`]
1255    /// which can be used to determine when and whether the event has been delivered.
1256    async fn send(&self, peer_ip: SocketAddr, event: Event<N>) -> Option<oneshot::Receiver<io::Result<()>>> {
1257        macro_rules! send {
1258            ($self:ident, $cache_map:ident, $interval:expr, $freq:ident) => {{
1259                // Rate limit the number of certificate requests sent to the peer.
1260                while $self.cache.$cache_map(peer_ip, $interval) > $self.$freq() {
1261                    // Sleep for a short period of time to allow the cache to clear.
1262                    tokio::time::sleep(Duration::from_millis(10)).await;
1263                }
1264                // Send the event to the peer.
1265                $self.send_inner(peer_ip, event)
1266            }};
1267        }
1268
1269        // Increment the cache for certificate, transmission and block events.
1270        match event {
1271            Event::CertificateRequest(_) | Event::CertificateResponse(_) => {
1272                // Update the outbound event cache. This is necessary to ensure we don't under count the outbound events.
1273                self.cache.insert_outbound_event(peer_ip, CACHE_EVENTS_INTERVAL);
1274                // Send the event to the peer.
1275                send!(self, insert_outbound_certificate, CACHE_REQUESTS_INTERVAL, max_cache_certificates)
1276            }
1277            Event::TransmissionRequest(_) | Event::TransmissionResponse(_) => {
1278                // Update the outbound event cache. This is necessary to ensure we don't under count the outbound events.
1279                self.cache.insert_outbound_event(peer_ip, CACHE_EVENTS_INTERVAL);
1280                // Send the event to the peer.
1281                send!(self, insert_outbound_transmission, CACHE_REQUESTS_INTERVAL, max_cache_transmissions)
1282            }
1283            Event::BlockRequest(request) => {
1284                // Insert the outbound request so we can match it to responses.
1285                self.cache.insert_outbound_block_request(peer_ip, request);
1286                // Send the event to the peer and update the outbound event cache, use the general rate limit.
1287                send!(self, insert_outbound_event, CACHE_EVENTS_INTERVAL, max_cache_events)
1288            }
1289            _ => {
1290                // Send the event to the peer, use the general rate limit.
1291                send!(self, insert_outbound_event, CACHE_EVENTS_INTERVAL, max_cache_events)
1292            }
1293        }
1294    }
1295
1296    /// Broadcasts the given event to all connected peers.
1297    // TODO(ljedrz): the event should be checked for the presence of Data::Object, and
1298    // serialized in advance if it's there.
1299    fn broadcast(&self, event: Event<N>) {
1300        // Ensure there are connected peers.
1301        if self.number_of_connected_peers() > 0 {
1302            let self_ = self.clone();
1303            let connected_peers = self.connected_peers();
1304            tokio::spawn(async move {
1305                // Iterate through all connected peers.
1306                for peer_ip in connected_peers {
1307                    // Send the event to the peer.
1308                    let _ = Transport::send(&self_, peer_ip, event.clone()).await;
1309                }
1310            });
1311        }
1312    }
1313}
1314
1315impl<N: Network> P2P for Gateway<N> {
1316    /// Returns a reference to the TCP instance.
1317    fn tcp(&self) -> &Tcp {
1318        &self.tcp
1319    }
1320}
1321
1322#[async_trait]
1323impl<N: Network> Reading for Gateway<N> {
1324    type Codec = EventCodec<N>;
1325    type Message = Event<N>;
1326
1327    /// Creates a [`Decoder`] used to interpret messages from the network.
1328    /// The `side` param indicates the connection side **from the node's perspective**.
1329    fn codec(&self, _peer_addr: SocketAddr, _side: ConnectionSide) -> Self::Codec {
1330        Default::default()
1331    }
1332
1333    /// Processes a message received from the network.
1334    async fn process_message(&self, peer_addr: SocketAddr, message: Self::Message) -> io::Result<()> {
1335        if matches!(message, Event::BlockRequest(_) | Event::BlockResponse(_)) {
1336            let self_ = self.clone();
1337            // Handle BlockRequest and BlockResponse messages in a separate task to not block the
1338            // inbound queue.
1339            tokio::spawn(async move {
1340                self_.process_message_inner(peer_addr, message).await;
1341            });
1342        } else {
1343            self.process_message_inner(peer_addr, message).await;
1344        }
1345        Ok(())
1346    }
1347
1348    /// Computes the depth of per-connection queues used to process inbound messages, sufficient to process the maximum expected load at any givent moment.
1349    /// The greater it is, the more inbound messages the node can enqueue, but a too large value can make the node more susceptible to DoS attacks.
1350    fn message_queue_depth(&self) -> usize {
1351        2 * BatchHeader::<N>::MAX_GC_ROUNDS
1352            * N::LATEST_MAX_CERTIFICATES() as usize
1353            * BatchHeader::<N>::MAX_TRANSMISSIONS_PER_BATCH
1354    }
1355}
1356
1357#[async_trait]
1358impl<N: Network> Writing for Gateway<N> {
1359    type Codec = EventCodec<N>;
1360    type Message = Event<N>;
1361
1362    /// Creates an [`Encoder`] used to write the outbound messages to the target stream.
1363    /// The `side` parameter indicates the connection side **from the node's perspective**.
1364    fn codec(&self, _peer_addr: SocketAddr, _side: ConnectionSide) -> Self::Codec {
1365        Default::default()
1366    }
1367
1368    /// Computes the depth of per-connection queues used to send outbound messages, sufficient to process the maximum expected load at any givent moment.
1369    /// The greater it is, the more outbound messages the node can enqueue. A too large value large value might obscure potential issues with your implementation
1370    /// (like slow serialization) or network.
1371    fn message_queue_depth(&self) -> usize {
1372        2 * BatchHeader::<N>::MAX_GC_ROUNDS
1373            * N::LATEST_MAX_CERTIFICATES() as usize
1374            * BatchHeader::<N>::MAX_TRANSMISSIONS_PER_BATCH
1375    }
1376}
1377
1378#[async_trait]
1379impl<N: Network> Disconnect for Gateway<N> {
1380    /// Any extra operations to be performed during a disconnect.
1381    async fn handle_disconnect(&self, peer_addr: SocketAddr, origin: DisconnectOrigin) {
1382        debug!("Physically disconnecting from {peer_addr}; origin: {origin:?}");
1383
1384        if let Some(peer_ip) = self.resolve_to_listener(&peer_addr) {
1385            // TODO(kaimast): This can, in theory, still lead to race conditions, if we immediately reconnect to the same peer.
1386            // In practice, there should always be a significant delay between those two delays, so it is not an immediate issue.
1387            //
1388            // To properly fix this, we either needk hold a lock here, or add a dedicated "disconnecting" state, so that
1389            // a peer is not re-added while the rest of the disconnect logic is running.
1390            let was_fully_connected = self.downgrade_peer_to_candidate(peer_ip);
1391
1392            // Remove the peer from the sync module. Except for some tests, there is always a sync sender.
1393            if was_fully_connected && let Some(sync_sender) = self.sync_sender.get() {
1394                let (tx, rx) = oneshot::channel();
1395
1396                if let Err(err) = sync_sender.tx_block_sync_remove_peer.send((peer_ip, tx)).await {
1397                    let err: anyhow::Error = err.into();
1398                    let err =
1399                        err.context(format!("Unable to remove disconnecting peer '{peer_ip}' from the sync module"));
1400                    warn!("{CONTEXT} {}", flatten_error(err));
1401                }
1402
1403                if let Err(err) = rx.await {
1404                    let err: anyhow::Error = err.into();
1405                    let err =
1406                        err.context(format!("Unable to remove disconnecting peer '{peer_ip}' from the sync module"));
1407                    warn!("{CONTEXT} {}", flatten_error(err));
1408                }
1409            }
1410            // We don't clear this map based on time but only on peer disconnect.
1411            // This is sufficient to avoid infinite growth as the committee has a fixed number
1412            // of members.
1413            self.cache.clear_outbound_validators_requests(peer_ip);
1414            self.cache.clear_outbound_block_requests(peer_ip);
1415        } else {
1416            warn!("{CONTEXT} Got disconnect for a peer '{peer_addr}' that is not in the peer pool");
1417        }
1418    }
1419}
1420
1421#[async_trait]
1422impl<N: Network> OnConnect for Gateway<N> {
1423    async fn on_connect(&self, peer_addr: SocketAddr) {
1424        if let Some(listener_addr) = self.resolve_to_listener(&peer_addr) {
1425            if let Some(peer) = self.get_connected_peer(listener_addr) {
1426                if peer.node_type == NodeType::BootstrapClient {
1427                    self.cache.increment_outbound_validators_requests(listener_addr);
1428                    let _ =
1429                        <Self as Transport<N>>::send(self, listener_addr, Event::ValidatorsRequest(ValidatorsRequest))
1430                            .await;
1431                }
1432            }
1433        }
1434    }
1435}
1436
1437#[async_trait]
1438impl<N: Network> Handshake for Gateway<N> {
1439    /// Performs the handshake protocol.
1440    async fn perform_handshake(&self, mut connection: Connection) -> Result<Connection, ConnectError> {
1441        // Perform the handshake.
1442        let peer_addr = connection.addr();
1443        let peer_side = connection.side();
1444
1445        // Check (or impose) IP-level bans.
1446        #[cfg(not(test))]
1447        if self.dev().is_none() && peer_side == ConnectionSide::Initiator {
1448            // If the IP is already banned reject the connection.
1449            if self.is_ip_banned(peer_addr.ip()) {
1450                trace!("{CONTEXT} Rejected a connection request from banned IP '{}'", peer_addr.ip());
1451                return Err(ConnectError::BannedIp { ip: peer_addr.ip() });
1452            }
1453
1454            let num_attempts = self.cache.insert_inbound_connection(peer_addr.ip(), CONNECTION_ATTEMPTS_SINCE_SECS);
1455
1456            debug!("Number of connection attempts from '{}': {}", peer_addr.ip(), num_attempts);
1457            if num_attempts > MAX_CONNECTION_ATTEMPTS {
1458                self.update_ip_ban(peer_addr.ip());
1459                trace!("{CONTEXT} Rejected a consecutive connection request from IP '{}'", peer_addr.ip());
1460                return Err(ConnectError::other(anyhow!("'{}' appears to be spamming connections", peer_addr.ip())));
1461            }
1462        }
1463
1464        let stream = self.borrow_stream(&mut connection);
1465
1466        // If this is an inbound connection, we log it, but don't know the listening address yet.
1467        // Otherwise, we can immediately register the listening address.
1468        let mut listener_addr = if peer_side == ConnectionSide::Initiator {
1469            debug!("{CONTEXT} Received a connection request from '{peer_addr}'");
1470            None
1471        } else {
1472            debug!("{CONTEXT} Shaking hands with {peer_addr}...");
1473            Some(peer_addr)
1474        };
1475
1476        // Retrieve the restrictions ID.
1477        let restrictions_id = self.ledger.latest_restrictions_id();
1478
1479        // Perform the handshake; we pass on a mutable reference to peer_ip in case the process is broken at any point in time.
1480        let handshake_result = if peer_side == ConnectionSide::Responder {
1481            self.handshake_inner_initiator(peer_addr, restrictions_id, stream).await
1482        } else {
1483            self.handshake_inner_responder(peer_addr, &mut listener_addr, restrictions_id, stream).await
1484        };
1485
1486        if let Some(addr) = listener_addr {
1487            match handshake_result {
1488                Ok(ref cr) => {
1489                    let node_type = if bootstrap_peers::<N>(self.is_dev()).contains(&addr) {
1490                        NodeType::BootstrapClient
1491                    } else {
1492                        NodeType::Validator
1493                    };
1494
1495                    if let Some(peer) = self.peer_pool.write().get_mut(&addr) {
1496                        self.resolver.write().insert_peer(addr, peer_addr, Some(cr.address));
1497                        peer.upgrade_to_connected(
1498                            peer_addr,
1499                            cr.listener_port,
1500                            cr.address,
1501                            node_type,
1502                            cr.version,
1503                            cr.snarkos_sha,
1504                            ConnectionMode::Gateway,
1505                        );
1506                    }
1507                    info!("{CONTEXT} Connected to '{addr}'");
1508                }
1509                Err(error) => {
1510                    if let Some(peer) = self.peer_pool.write().get_mut(&addr) {
1511                        // The peer may only be downgraded if it's a ConnectingPeer.
1512                        if peer.is_connecting() {
1513                            peer.downgrade_to_candidate(addr);
1514                        }
1515                    }
1516                    return Err(error);
1517                }
1518            }
1519        }
1520
1521        Ok(connection)
1522    }
1523}
1524
1525/// A macro unwrapping the expected handshake event or returning an error for unexpected events.
1526macro_rules! expect_event {
1527    ($event_ty:path, $framed:expr, $peer_addr:expr) => {
1528        match $framed.try_next().await? {
1529            // Received the expected event, proceed.
1530            Some($event_ty(data)) => {
1531                trace!("{CONTEXT} Received '{}' from '{}'", data.name(), $peer_addr);
1532                data
1533            }
1534            // Received a disconnect event, abort.
1535            Some(Event::Disconnect($crate::events::Disconnect { reason })) => {
1536                return Err(ConnectError::other(format!("'{}' disconnected with reason \"{reason}\"", $peer_addr)));
1537            }
1538            // Received an unexpected event, abort.
1539            Some(ty) => {
1540                return Err(ConnectError::other(format!(
1541                    "'{}' did not follow the handshake protocol: received {:?} instead of {}",
1542                    $peer_addr,
1543                    ty.name(),
1544                    stringify!($msg_ty),
1545                )));
1546            }
1547            // Received nothing.
1548            None => return Err(ConnectError::IoError(io::ErrorKind::BrokenPipe.into())),
1549        }
1550    };
1551}
1552
1553/// Send the given message to the peer.
1554async fn send_event<N: Network>(
1555    framed: &mut Framed<&mut TcpStream, EventCodec<N>>,
1556    peer_addr: SocketAddr,
1557    event: Event<N>,
1558) -> io::Result<()> {
1559    trace!("{CONTEXT} Sending '{}' to '{peer_addr}'", event.name());
1560    framed.send(event).await
1561}
1562
1563impl<N: Network> Gateway<N> {
1564    /// The connection initiator side of the handshake.
1565    async fn handshake_inner_initiator<'a>(
1566        &'a self,
1567        peer_addr: SocketAddr,
1568        restrictions_id: Field<N>,
1569        stream: &'a mut TcpStream,
1570    ) -> Result<ChallengeRequest<N>, ConnectError> {
1571        // Introduce the peer into the peer pool.
1572        self.add_connecting_peer(peer_addr)?;
1573
1574        // Construct the stream.
1575        let mut framed = Framed::new(stream, EventCodec::<N>::handshake());
1576
1577        /* Step 1: Send the challenge request. */
1578
1579        // Sample a random nonce.
1580        let our_nonce: u64 = rand::random();
1581        // Determine the snarkOS SHA to send to the peer.
1582        let current_block_height = self.ledger.latest_block_height();
1583        let consensus_version = N::CONSENSUS_VERSION(current_block_height).unwrap();
1584        let snarkos_sha = match (self.is_dev(), consensus_version >= ConsensusVersion::V12, get_repo_commit_hash()) {
1585            (true, _, Some(sha)) => Some(sha),
1586            (_, true, Some(sha)) => Some(sha),
1587            _ => None,
1588        };
1589        // Send a challenge request to the peer.
1590        let our_request = ChallengeRequest::new(self.local_ip().port(), self.account.address(), our_nonce, snarkos_sha);
1591        send_event(&mut framed, peer_addr, Event::ChallengeRequest(our_request)).await?;
1592
1593        /* Step 2: Receive the peer's challenge response followed by the challenge request. */
1594
1595        // Listen for the challenge response message.
1596        let peer_response = expect_event!(Event::ChallengeResponse, framed, peer_addr);
1597        // Listen for the challenge request message.
1598        let peer_request = expect_event!(Event::ChallengeRequest, framed, peer_addr);
1599
1600        // Verify the challenge response. If a disconnect reason was returned, send the disconnect message and abort.
1601        if let Some(reason) = self
1602            .verify_challenge_response(peer_addr, peer_request.address, peer_response, restrictions_id, our_nonce)
1603            .await
1604        {
1605            send_event(&mut framed, peer_addr, reason.into()).await?;
1606            return Err(ConnectError::application(reason));
1607        }
1608
1609        // Verify the challenge request. If a disconnect reason was returned, send the disconnect message and abort.
1610        if let Some(reason) = self.verify_challenge_request(peer_addr, &peer_request) {
1611            send_event(&mut framed, peer_addr, reason.into()).await?;
1612            return Err(reason.into_connect_error(peer_addr));
1613        }
1614
1615        /* Step 3: Send the challenge response. */
1616
1617        // Sign the counterparty nonce.
1618        let response_nonce: u64 = rand::random();
1619        let data = [peer_request.nonce.to_le_bytes(), response_nonce.to_le_bytes()].concat();
1620        let Ok(our_signature) = self.account.sign_bytes(&data, &mut rand::rng()) else {
1621            return Err(ConnectError::other(anyhow!("Failed to sign the challenge request nonce")));
1622        };
1623        // Send the challenge response.
1624        let our_response =
1625            ChallengeResponse { restrictions_id, signature: Data::Object(our_signature), nonce: response_nonce };
1626        send_event(&mut framed, peer_addr, Event::ChallengeResponse(our_response)).await?;
1627
1628        Ok(peer_request)
1629    }
1630
1631    /// The connection responder side of the handshake.
1632    async fn handshake_inner_responder<'a>(
1633        &'a self,
1634        peer_addr: SocketAddr,
1635        peer_ip: &mut Option<SocketAddr>,
1636        restrictions_id: Field<N>,
1637        stream: &'a mut TcpStream,
1638    ) -> Result<ChallengeRequest<N>, ConnectError> {
1639        // Construct the stream.
1640        let mut framed = Framed::new(stream, EventCodec::<N>::handshake());
1641
1642        /* Step 1: Receive the challenge request. */
1643
1644        // Listen for the challenge request message.
1645        let peer_request = expect_event!(Event::ChallengeRequest, framed, peer_addr);
1646
1647        // Ensure the address is not the same as this node.
1648        if self.account.address() == peer_request.address {
1649            return Err(ConnectError::SelfConnect { address: peer_addr });
1650        }
1651
1652        // Obtain the peer's listening address.
1653        *peer_ip = Some(SocketAddr::new(peer_addr.ip(), peer_request.listener_port));
1654        let peer_ip = peer_ip.unwrap();
1655
1656        // Knowing the peer's listening address, ensure it is allowed to connect.
1657        if let Err(reason) = self.ensure_peer_is_allowed(peer_ip) {
1658            send_event(&mut framed, peer_addr, reason.into()).await?;
1659            return Err(reason.into_connect_error(peer_addr));
1660        }
1661
1662        // Introduce the peer into the peer pool.
1663        self.add_connecting_peer(peer_ip)?;
1664
1665        // Verify the challenge request. If a disconnect reason was returned, send the disconnect message and abort.
1666        if let Some(reason) = self.verify_challenge_request(peer_addr, &peer_request) {
1667            send_event(&mut framed, peer_addr, reason.into()).await?;
1668            return Err(reason.into_connect_error(peer_addr));
1669        }
1670
1671        /* Step 2: Send the challenge response followed by own challenge request. */
1672
1673        // Sign the counterparty nonce.
1674        let response_nonce: u64 = rand::random();
1675        let data = [peer_request.nonce.to_le_bytes(), response_nonce.to_le_bytes()].concat();
1676        let Ok(our_signature) = self.account.sign_bytes(&data, &mut rand::rng()) else {
1677            return Err(ConnectError::other(anyhow!("Failed to sign the challenge request nonce")));
1678        };
1679        // Send the challenge response.
1680        let our_response =
1681            ChallengeResponse { restrictions_id, signature: Data::Object(our_signature), nonce: response_nonce };
1682        send_event(&mut framed, peer_addr, Event::ChallengeResponse(our_response)).await?;
1683
1684        // Sample a random nonce.
1685        let our_nonce: u64 = rand::random();
1686        // Determine the snarkOS SHA to send to the peer.
1687        let current_block_height = self.ledger.latest_block_height();
1688        let consensus_version = N::CONSENSUS_VERSION(current_block_height).unwrap();
1689        let snarkos_sha = match (self.is_dev(), consensus_version >= ConsensusVersion::V12, get_repo_commit_hash()) {
1690            (true, _, Some(sha)) => Some(sha),
1691            (_, true, Some(sha)) => Some(sha),
1692            _ => None,
1693        };
1694        // Send the challenge request.
1695        let our_request = ChallengeRequest::new(self.local_ip().port(), self.account.address(), our_nonce, snarkos_sha);
1696        send_event(&mut framed, peer_addr, Event::ChallengeRequest(our_request)).await?;
1697
1698        /* Step 3: Receive the challenge response. */
1699
1700        // Listen for the challenge response message.
1701        let peer_response = expect_event!(Event::ChallengeResponse, framed, peer_addr);
1702        // Verify the challenge response. If a disconnect reason was returned, send the disconnect message and abort.
1703        if let Some(reason) = self
1704            .verify_challenge_response(peer_addr, peer_request.address, peer_response, restrictions_id, our_nonce)
1705            .await
1706        {
1707            send_event(&mut framed, peer_addr, reason.into()).await?;
1708            Err(reason.into_connect_error(peer_addr))
1709        } else {
1710            Ok(peer_request)
1711        }
1712    }
1713
1714    /// Verifies the given challenge request. Returns a disconnect reason if the request is invalid.
1715    #[must_use]
1716    fn verify_challenge_request(&self, peer_addr: SocketAddr, event: &ChallengeRequest<N>) -> Option<DisconnectReason> {
1717        // Retrieve the components of the challenge request.
1718        let &ChallengeRequest { version, listener_port, address, nonce: _, ref snarkos_sha } = event;
1719        log_repo_sha_comparison(peer_addr, snarkos_sha, CONTEXT);
1720
1721        let listener_addr = SocketAddr::new(peer_addr.ip(), listener_port);
1722
1723        // Ensure the event protocol version is not outdated.
1724        if version < Event::<N>::VERSION {
1725            return Some(DisconnectReason::OutdatedClientVersion);
1726        }
1727        // If the node is in trusted peers only mode, ensure the peer is trusted.
1728        if self.trusted_peers_only && !self.is_trusted(listener_addr) {
1729            warn!("{CONTEXT} Dropping '{peer_addr}' for being an untrusted validator ({address})");
1730            return Some(DisconnectReason::NoExternalPeersAllowed);
1731        }
1732        if !bootstrap_peers::<N>(self.dev().is_some()).contains(&listener_addr) {
1733            // Ensure the address is a current committee member.
1734            if !self.is_authorized_validator_address(address) {
1735                return Some(DisconnectReason::UnauthorizedValidator);
1736            }
1737        }
1738
1739        // Ensure the address is not already connected.
1740        if self.is_connected_address(address) {
1741            return Some(DisconnectReason::AlreadyConnectedToAleoAddress);
1742        }
1743
1744        None
1745    }
1746
1747    /// Verifies the given challenge response. Returns a disconnect reason if the response is invalid.
1748    #[must_use]
1749    async fn verify_challenge_response(
1750        &self,
1751        peer_addr: SocketAddr,
1752        peer_address: Address<N>,
1753        response: ChallengeResponse<N>,
1754        expected_restrictions_id: Field<N>,
1755        expected_nonce: u64,
1756    ) -> Option<DisconnectReason> {
1757        // Retrieve the components of the challenge response.
1758        let ChallengeResponse { restrictions_id, signature, nonce } = response;
1759
1760        // Verify the restrictions ID.
1761        if restrictions_id != expected_restrictions_id {
1762            warn!("{CONTEXT} Handshake with '{peer_addr}' failed (incorrect restrictions ID)");
1763            return Some(DisconnectReason::InvalidChallengeResponse);
1764        }
1765        // Perform the deferred non-blocking deserialization of the signature.
1766        let Ok(signature) = spawn_blocking!(signature.deserialize_blocking()) else {
1767            warn!("{CONTEXT} Handshake with '{peer_addr}' failed (cannot deserialize the signature)");
1768            return Some(DisconnectReason::InvalidChallengeResponse);
1769        };
1770        // Verify the signature.
1771        if !signature.verify_bytes(&peer_address, &[expected_nonce.to_le_bytes(), nonce.to_le_bytes()].concat()) {
1772            warn!("{CONTEXT} Handshake with '{peer_addr}' failed (invalid signature)");
1773            return Some(DisconnectReason::InvalidChallengeResponse);
1774        }
1775        None
1776    }
1777}
1778
1779#[cfg(test)]
1780mod prop_tests {
1781    use crate::{
1782        Gateway,
1783        MAX_WORKERS,
1784        MEMORY_POOL_PORT,
1785        Worker,
1786        helpers::{Storage, init_primary_channels, init_worker_channels},
1787    };
1788
1789    use snarkos_account::Account;
1790    use snarkos_node_bft_ledger_service::MockLedgerService;
1791    use snarkos_node_bft_storage_service::BFTMemoryService;
1792    use snarkos_node_network::PeerPoolHandling;
1793    use snarkos_node_tcp::P2P;
1794    use snarkos_utilities::NodeDataDir;
1795
1796    use snarkos_node_bft_events::committee_prop_tests::{CommitteeContext, ValidatorSet};
1797    use snarkvm::{
1798        ledger::{
1799            committee::{Committee, test_helpers::sample_committee_for_round_and_members},
1800            narwhal::{BatchHeader, batch_certificate::test_helpers::sample_batch_certificate_for_round},
1801        },
1802        prelude::{MainnetV0, PrivateKey},
1803        utilities::TestRng,
1804    };
1805
1806    use indexmap::{IndexMap, IndexSet};
1807    use proptest::{
1808        prelude::{Arbitrary, BoxedStrategy, Just, Strategy, any, any_with},
1809        sample::Selector,
1810    };
1811    use std::{
1812        fmt::{Debug, Formatter},
1813        net::{IpAddr, Ipv4Addr, SocketAddr},
1814        sync::Arc,
1815    };
1816    use test_strategy::proptest;
1817
1818    type CurrentNetwork = MainnetV0;
1819
1820    impl Debug for Gateway<CurrentNetwork> {
1821        fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
1822            // TODO implement Debug properly and move it over to production code
1823            f.debug_tuple("Gateway").field(&self.account.address()).field(&self.tcp.config()).finish()
1824        }
1825    }
1826
1827    #[derive(Debug, test_strategy::Arbitrary)]
1828    enum GatewayAddress {
1829        Dev(u8),
1830        Prod(Option<SocketAddr>),
1831    }
1832
1833    impl GatewayAddress {
1834        fn ip(&self) -> Option<SocketAddr> {
1835            if let GatewayAddress::Prod(ip) = self {
1836                return *ip;
1837            }
1838            None
1839        }
1840
1841        fn port(&self) -> Option<u16> {
1842            if let GatewayAddress::Dev(port) = self {
1843                return Some(*port as u16);
1844            }
1845            None
1846        }
1847    }
1848
1849    impl Arbitrary for Gateway<CurrentNetwork> {
1850        type Parameters = ();
1851        type Strategy = BoxedStrategy<Gateway<CurrentNetwork>>;
1852
1853        fn arbitrary_with(_: Self::Parameters) -> Self::Strategy {
1854            any_valid_dev_gateway()
1855                .prop_map(|(storage, _, private_key, address)| {
1856                    Gateway::new(
1857                        Account::try_from(private_key).unwrap(),
1858                        storage.clone(),
1859                        storage.ledger().clone(),
1860                        address.ip(),
1861                        &[],
1862                        false,
1863                        NodeDataDir::new_test(None),
1864                        address.port(),
1865                    )
1866                    .unwrap()
1867                })
1868                .boxed()
1869        }
1870    }
1871
1872    type GatewayInput = (Storage<CurrentNetwork>, CommitteeContext, PrivateKey<CurrentNetwork>, GatewayAddress);
1873
1874    fn any_valid_dev_gateway() -> BoxedStrategy<GatewayInput> {
1875        (any::<CommitteeContext>(), any::<Selector>())
1876            .prop_flat_map(|(context, account_selector)| {
1877                let CommitteeContext(_, ValidatorSet(validators)) = context.clone();
1878                (
1879                    any_with::<Storage<CurrentNetwork>>(context.clone()),
1880                    Just(context),
1881                    Just(account_selector.select(validators)),
1882                    0u8..,
1883                )
1884                    .prop_map(|(a, b, c, d)| (a, b, c.private_key, GatewayAddress::Dev(d)))
1885            })
1886            .boxed()
1887    }
1888
1889    fn any_valid_prod_gateway() -> BoxedStrategy<GatewayInput> {
1890        (any::<CommitteeContext>(), any::<Selector>())
1891            .prop_flat_map(|(context, account_selector)| {
1892                let CommitteeContext(_, ValidatorSet(validators)) = context.clone();
1893                (
1894                    any_with::<Storage<CurrentNetwork>>(context.clone()),
1895                    Just(context),
1896                    Just(account_selector.select(validators)),
1897                    any::<Option<SocketAddr>>(),
1898                )
1899                    .prop_map(|(a, b, c, d)| (a, b, c.private_key, GatewayAddress::Prod(d)))
1900            })
1901            .boxed()
1902    }
1903
1904    #[proptest]
1905    fn gateway_dev_initialization(#[strategy(any_valid_dev_gateway())] input: GatewayInput) {
1906        let (storage, _, private_key, dev) = input;
1907        let account = Account::try_from(private_key).unwrap();
1908
1909        let gateway = Gateway::new(
1910            account.clone(),
1911            storage.clone(),
1912            storage.ledger().clone(),
1913            dev.ip(),
1914            &[],
1915            false,
1916            NodeDataDir::new_test(None),
1917            dev.port(),
1918        )
1919        .unwrap();
1920        let tcp_config = gateway.tcp().config();
1921        assert_eq!(tcp_config.listener_ip, Some(IpAddr::V4(Ipv4Addr::LOCALHOST)));
1922        assert_eq!(tcp_config.desired_listening_port, Some(MEMORY_POOL_PORT + dev.port().unwrap()));
1923
1924        let tcp_config = gateway.tcp().config();
1925        assert_eq!(tcp_config.max_connections, Committee::<CurrentNetwork>::max_committee_size() * 10);
1926        assert_eq!(gateway.account().address(), account.address());
1927    }
1928
1929    #[proptest]
1930    fn gateway_prod_initialization(#[strategy(any_valid_prod_gateway())] input: GatewayInput) {
1931        let (storage, _, private_key, dev) = input;
1932        let account = Account::try_from(private_key).unwrap();
1933
1934        let gateway = Gateway::new(
1935            account.clone(),
1936            storage.clone(),
1937            storage.ledger().clone(),
1938            dev.ip(),
1939            &[],
1940            false,
1941            NodeDataDir::new_test(None),
1942            dev.port(),
1943        )
1944        .unwrap();
1945        let tcp_config = gateway.tcp().config();
1946        if let Some(socket_addr) = dev.ip() {
1947            assert_eq!(tcp_config.listener_ip, Some(socket_addr.ip()));
1948            assert_eq!(tcp_config.desired_listening_port, Some(socket_addr.port()));
1949        } else {
1950            assert_eq!(tcp_config.listener_ip, Some(IpAddr::V4(Ipv4Addr::UNSPECIFIED)));
1951            assert_eq!(tcp_config.desired_listening_port, Some(MEMORY_POOL_PORT));
1952        }
1953
1954        let tcp_config = gateway.tcp().config();
1955        assert_eq!(tcp_config.max_connections, Committee::<CurrentNetwork>::max_committee_size() * 10);
1956        assert_eq!(gateway.account().address(), account.address());
1957    }
1958
1959    #[proptest(async = "tokio")]
1960    async fn gateway_start(
1961        #[strategy(any_valid_dev_gateway())] input: GatewayInput,
1962        #[strategy(0..MAX_WORKERS)] workers_count: u8,
1963    ) {
1964        let (storage, committee, private_key, dev) = input;
1965        let committee = committee.0;
1966        let worker_storage = storage.clone();
1967        let account = Account::try_from(private_key).unwrap();
1968
1969        let gateway = Gateway::new(
1970            account,
1971            storage.clone(),
1972            storage.ledger().clone(),
1973            dev.ip(),
1974            &[],
1975            false,
1976            NodeDataDir::new_test(None),
1977            dev.port(),
1978        )
1979        .unwrap();
1980
1981        let (primary_sender, _) = init_primary_channels();
1982
1983        let (workers, worker_senders) = {
1984            // Construct a map of the worker senders.
1985            let mut tx_workers = IndexMap::new();
1986            let mut workers = IndexMap::new();
1987
1988            // Initialize the workers.
1989            for id in 0..workers_count {
1990                // Construct the worker channels.
1991                let (tx_worker, rx_worker) = init_worker_channels();
1992                // Construct the worker instance.
1993                let ledger = Arc::new(MockLedgerService::new(committee.clone()));
1994                let worker =
1995                    Worker::new(id, Arc::new(gateway.clone()), worker_storage.clone(), ledger, Default::default())
1996                        .unwrap();
1997                // Run the worker instance.
1998                worker.run(rx_worker);
1999
2000                // Add the worker and the worker sender to maps
2001                workers.insert(id, worker);
2002                tx_workers.insert(id, tx_worker);
2003            }
2004            (workers, tx_workers)
2005        };
2006
2007        gateway.run(primary_sender, worker_senders, None).await;
2008        assert_eq!(
2009            gateway.local_ip(),
2010            SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), MEMORY_POOL_PORT + dev.port().unwrap())
2011        );
2012        assert_eq!(gateway.num_workers(), workers.len() as u8);
2013    }
2014
2015    #[proptest]
2016    fn test_is_authorized_validator(#[strategy(any_valid_dev_gateway())] input: GatewayInput) {
2017        let rng = &mut TestRng::default();
2018
2019        // Initialize the round parameters.
2020        let current_round = 2;
2021        let committee_size = 4;
2022        let max_gc_rounds = BatchHeader::<CurrentNetwork>::MAX_GC_ROUNDS as u64;
2023        let (_, _, private_key, dev) = input;
2024        let account = Account::try_from(private_key).unwrap();
2025
2026        // Sample the certificates.
2027        let mut certificates = IndexSet::new();
2028        for _ in 0..committee_size {
2029            certificates.insert(sample_batch_certificate_for_round(current_round, rng));
2030        }
2031        let addresses: Vec<_> = certificates.iter().map(|certificate| certificate.author()).collect();
2032        // Initialize the committee.
2033        let committee = sample_committee_for_round_and_members(current_round, addresses, rng);
2034        // Sample extra certificates from non-committee members.
2035        for _ in 0..committee_size {
2036            certificates.insert(sample_batch_certificate_for_round(current_round, rng));
2037        }
2038        // Initialize the ledger.
2039        let ledger = Arc::new(MockLedgerService::new(committee.clone()));
2040        // Initialize the storage.
2041        let storage = Storage::new(ledger.clone(), Arc::new(BFTMemoryService::new()), max_gc_rounds).unwrap();
2042        // Initialize the gateway.
2043        let gateway = Gateway::new(
2044            account.clone(),
2045            storage.clone(),
2046            ledger.clone(),
2047            dev.ip(),
2048            &[],
2049            false,
2050            NodeDataDir::new_test(None),
2051            dev.port(),
2052        )
2053        .unwrap();
2054        // Insert certificate to the storage.
2055        for certificate in certificates.iter() {
2056            storage.testing_only_insert_certificate_testing_only(certificate.clone());
2057        }
2058        // Check that the current committee members are authorized validators.
2059        for i in 0..certificates.clone().len() {
2060            let is_authorized = gateway.is_authorized_validator_address(certificates[i].author());
2061            if i < committee_size {
2062                assert!(is_authorized);
2063            } else {
2064                assert!(!is_authorized);
2065            }
2066        }
2067    }
2068}