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_consensus_version_ahead() => {
686                            let err: anyhow::Error = err.into();
687                            let err = err.context(format!(
688                                "Peer sent a block response with a newer consensus version '{peer_ip}'"
689                            ));
690                            warn!("{}", flatten_error(&err));
691                            Ok(true)
692                        }
693                        Err(err) if err.is_consensus_version_behind() => {
694                            let err: anyhow::Error = err.into();
695                            let err = err.context(format!("Peer sent an invalid block response '{peer_ip}'"));
696
697                            let msg = flatten_error(&err);
698                            error!("{msg}");
699                            self.ip_ban_peer(peer_ip, Some(&msg));
700                            Err(err)
701                        }
702                        Err(err) => {
703                            let err: anyhow::Error = err.into();
704                            let err = err.context(format!("Peer '{peer_ip}' sent an invalid block response"));
705                            warn!("{}", flatten_error(err));
706
707                            // TODO(kaimast): This needs more testing to ensure disconnect is the correct action.
708                            Ok(true)
709                        }
710                    }
711                } else {
712                    debug!("Ignoring block response from '{peer_ip}' - no sync sender");
713                    Ok(true)
714                }
715            }
716            Event::CertificateRequest(certificate_request) => {
717                // Send the certificate request to the sync module.
718                // Except for some tests, there is always a sync sender.
719                if let Some(sync_sender) = self.sync_sender.get() {
720                    // Send the certificate request to the sync module.
721                    let _ = sync_sender.tx_certificate_request.send((peer_ip, certificate_request)).await;
722                }
723                Ok(true)
724            }
725            Event::CertificateResponse(certificate_response) => {
726                // Send the certificate response to the sync module.
727                // Except for some tests, there is always a sync sender.
728                if let Some(sync_sender) = self.sync_sender.get() {
729                    // Send the certificate response to the sync module.
730                    let _ = sync_sender.tx_certificate_response.send((peer_ip, certificate_response)).await;
731                }
732                Ok(true)
733            }
734            Event::ChallengeRequest(..) | Event::ChallengeResponse(..) => {
735                // Disconnect as the peer is not following the protocol.
736                bail!("{CONTEXT} Peer '{peer_ip}' is not following the protocol")
737            }
738            Event::Disconnect(message) => {
739                // The peer informs us that they had disconnected. Disconnect from them too.
740                debug!("Peer '{peer_ip}' decided to disconnect due to '{}'", message.reason);
741                self.disconnect(peer_ip);
742                Ok(false)
743            }
744            Event::PrimaryPing(ping) => {
745                let PrimaryPing { version, block_locators, primary_certificate } = ping;
746
747                // Ensure the event version is not outdated.
748                if version < Event::<N>::VERSION {
749                    bail!("Dropping '{peer_ip}' on event version {version} (outdated)");
750                }
751
752                // Log the validator's height.
753                debug!("Validator '{peer_ip}' is at height {}", block_locators.latest_locator_height());
754
755                // Update the peer locators. Except for some tests, there is always a sync sender.
756                if let Some(sync_sender) = self.sync_sender.get() {
757                    // Check the block locators are valid, and update the validators in the sync module.
758                    if let Err(error) = sync_sender.update_peer_locators(peer_ip, block_locators).await {
759                        bail!("Validator '{peer_ip}' sent invalid block locators - {error}");
760                    }
761                }
762
763                // Send the batch certificates to the primary.
764                let _ = self.primary_sender().tx_primary_ping.send((peer_ip, primary_certificate)).await;
765                Ok(true)
766            }
767            Event::TransmissionRequest(request) => {
768                // TODO (howardwu): Add rate limiting checks on this event, on a per-peer basis.
769                // Determine the worker ID.
770                let Ok(worker_id) = assign_to_worker(request.transmission_id, self.num_workers()) else {
771                    warn!("{CONTEXT} Unable to assign transmission ID '{}' to a worker", request.transmission_id);
772                    return Ok(true);
773                };
774                // Send the transmission request to the worker.
775                if let Some(sender) = self.get_worker_sender(worker_id) {
776                    // Send the transmission request to the worker.
777                    let _ = sender.tx_transmission_request.send((peer_ip, request)).await;
778                }
779                Ok(true)
780            }
781            Event::TransmissionResponse(response) => {
782                // Determine the worker ID.
783                let Ok(worker_id) = assign_to_worker(response.transmission_id, self.num_workers()) else {
784                    warn!("{CONTEXT} Unable to assign transmission ID '{}' to a worker", response.transmission_id);
785                    return Ok(true);
786                };
787                // Send the transmission response to the worker.
788                if let Some(sender) = self.get_worker_sender(worker_id) {
789                    // Send the transmission response to the worker.
790                    let _ = sender.tx_transmission_response.send((peer_ip, response)).await;
791                }
792                Ok(true)
793            }
794            Event::ValidatorsRequest(_) => {
795                let mut connected_peers = self.get_best_connected_peers(Some(MAX_VALIDATORS_TO_SEND));
796                connected_peers.shuffle(&mut rand::rng());
797
798                let self_ = self.clone();
799                tokio::spawn(async move {
800                    // Initialize the validators.
801                    let mut validators = IndexMap::with_capacity(MAX_VALIDATORS_TO_SEND);
802                    // Iterate over the validators.
803                    for validator in connected_peers.into_iter() {
804                        // Add the validator to the list of validators.
805                        validators.insert(validator.listener_addr, validator.aleo_addr);
806                    }
807                    // Send the validators response to the peer.
808                    let event = Event::ValidatorsResponse(ValidatorsResponse { validators });
809                    Transport::send(&self_, peer_ip, event).await;
810                });
811                Ok(true)
812            }
813            Event::ValidatorsResponse(response) => {
814                if self.trusted_peers_only {
815                    bail!("{CONTEXT} Not accepting validators response from '{peer_ip}' (trusted peers only)");
816                }
817                let ValidatorsResponse { validators } = response;
818                // Ensure the number of validators is not too large.
819                ensure!(validators.len() <= MAX_VALIDATORS_TO_SEND, "{CONTEXT} Received too many validators");
820                // Ensure the cache contains a validators request for this peer.
821                if !self.cache.contains_outbound_validators_request(peer_ip) {
822                    bail!("{CONTEXT} Received validators response from '{peer_ip}' without a validators request")
823                }
824                // Decrement the number of validators requests for this peer.
825                self.cache.decrement_outbound_validators_requests(peer_ip);
826
827                // Add valid validators as candidates to the peer pool; only validator-related
828                // filters need to be applied, the rest is handled by `PeerPoolHandling`.
829                let valid_addrs = validators
830                    .into_iter()
831                    .filter_map(|(listener_addr, aleo_addr)| {
832                        (self.account.address() != aleo_addr
833                            && !self.is_connected_address(aleo_addr)
834                            && self.is_authorized_validator_address(aleo_addr))
835                        .then_some((listener_addr, None))
836                    })
837                    .collect::<Vec<_>>();
838                if !valid_addrs.is_empty() {
839                    self.insert_candidate_peers(valid_addrs);
840                }
841
842                Ok(true)
843            }
844            Event::WorkerPing(ping) => {
845                // Ensure the number of transmissions is not too large.
846                ensure!(
847                    ping.transmission_ids.len() <= Worker::<N>::MAX_TRANSMISSIONS_PER_WORKER_PING,
848                    "{CONTEXT} Received too many transmissions"
849                );
850                // Retrieve the number of workers.
851                let num_workers = self.num_workers();
852                // Iterate over the transmission IDs.
853                for transmission_id in ping.transmission_ids.into_iter() {
854                    // Determine the worker ID.
855                    let Ok(worker_id) = assign_to_worker(transmission_id, num_workers) else {
856                        warn!("{CONTEXT} Unable to assign transmission ID '{transmission_id}' to a worker");
857                        continue;
858                    };
859                    // Send the transmission ID to the worker.
860                    if let Some(sender) = self.get_worker_sender(worker_id) {
861                        // Send the transmission ID to the worker.
862                        let _ = sender.tx_worker_ping.send((peer_ip, transmission_id)).await;
863                    }
864                }
865                Ok(true)
866            }
867        }
868    }
869
870    /// Initialize a new instance of the heartbeat.
871    fn initialize_heartbeat(&self) {
872        let self_clone = self.clone();
873        self.spawn(async move {
874            // Sleep briefly to ensure the other nodes are ready to connect.
875            tokio::time::sleep(Duration::from_millis(1000)).await;
876            info!("Starting the heartbeat of the gateway...");
877            loop {
878                // Process a heartbeat in the gateway.
879                self_clone.heartbeat().await;
880                // Sleep for the heartbeat interval.
881                tokio::time::sleep(Duration::from_secs(15)).await;
882            }
883        });
884    }
885
886    /// Spawns a task with the given future; it should only be used for long-running tasks.
887    #[allow(dead_code)]
888    fn spawn<T: Future<Output = ()> + Send + 'static>(&self, future: T) {
889        self.handles.lock().push(tokio::spawn(future));
890    }
891
892    /// Shuts down the gateway.
893    pub async fn shut_down(&self) {
894        info!("Shutting down the gateway...");
895        // Save the best peers for future use.
896        if let Err(e) = self.save_best_peers(&self.node_data_dir.gateway_peer_cache_path(), None, true) {
897            warn!("Failed to persist best validators to disk: {e}");
898        }
899        // Abort the tasks.
900        self.handles.lock().iter().for_each(|handle| handle.abort());
901        // Close the listener.
902        self.tcp.shut_down().await;
903    }
904}
905
906impl<N: Network> Gateway<N> {
907    /// The minimum time between connection attempts to a peer.
908    const MINIMUM_TIME_BETWEEN_CONNECTION_ATTEMPTS: Duration = Duration::from_secs(10);
909    /// The uptime after which nodes log a warning about missing validator connections.
910    const MISSING_VALIDATOR_CONNECTIONS_GRACE_PERIOD: Duration = Duration::from_secs(60);
911
912    /// Handles the heartbeat request.
913    async fn heartbeat(&self) {
914        // Log the connected validators.
915        self.log_connected_validators();
916        // Log the validator participation scores.
917        #[cfg(feature = "telemetry")]
918        self.log_participation_scores();
919        // Keep the trusted validators connected.
920        self.handle_trusted_validators();
921        // Keep the bootstrap peers within the allowed range.
922        self.handle_bootstrap_peers().await;
923        // Removes any validators that not in the current committee.
924        self.handle_unauthorized_validators();
925        // If the number of connected validators is less than the minimum, send a `ValidatorsRequest`.
926        self.handle_min_connected_validators().await;
927        // Unban any addresses whose ban time has expired.
928        self.handle_banned_ips();
929    }
930
931    /// Logs the connected validators.
932    fn log_connected_validators(&self) {
933        // Retrieve the connected validators and current committee.
934        // The gatway may also be connected to bootstrap clients, which we should not log as connected validators.
935        let connected_validators = self.filter_connected_peers(|peer| peer.node_type == NodeType::Validator);
936
937        let committee = match self.ledger.current_committee() {
938            Ok(c) => c,
939            Err(err) => {
940                error!("Failed to get current committee: {err}");
941                return;
942            }
943        };
944
945        // Resolve the total number of connectable validators.
946        let validators_total = committee.num_members().saturating_sub(1);
947        // Format the total validators message.
948        let total_validators = format!("(of {validators_total} bonded validators)").dimmed();
949        // Construct the connections message.
950        let connections_msg = match connected_validators.len() {
951            0 => "No connected validators".to_string(),
952            num_connected => format!("Connected to {num_connected} validators {total_validators}"),
953        };
954        info!("{connections_msg}");
955
956        // Collect the connected validator addresses and stake.
957        let mut connected_validator_addresses = HashSet::with_capacity(connected_validators.len());
958        let mut connected_validator_shas: HashMap<SmolStr, u64> = HashMap::with_capacity(connected_validators.len());
959        // Insert our sha.
960        let our_sha = shorten_snarkos_sha(&get_repo_commit_hash());
961        let our_stake = committee.get_stake(self.account.address());
962        connected_validator_shas.insert(our_sha.clone(), our_stake);
963        // Include our own address.
964        connected_validator_addresses.insert(self.account.address());
965        // Include and log the connected validators.
966        for peer in &connected_validators {
967            // Register the Aleo address.
968            let address = peer.aleo_addr;
969            connected_validator_addresses.insert(address);
970            // Register the snarkOS commit SHA and the associated stake.
971            let address_stake = committee.get_stake(address);
972            let short_peer_sha = shorten_snarkos_sha(&peer.snarkos_sha);
973            *connected_validator_shas.entry(short_peer_sha.clone()).or_default() += address_stake;
974
975            debug!(
976                "{}",
977                format!(
978                    "  Connected to: {} - {} (connection age {:?})",
979                    peer.listener_addr,
980                    peer.aleo_addr,
981                    peer.first_seen.elapsed()
982                )
983                .dimmed()
984            );
985        }
986
987        // Log how much of the stake uses our git commit hash.
988        if let Some(combined_stake) = connected_validator_shas.get(&our_sha) {
989            let percentage = *combined_stake as f64 / committee.total_stake() as f64 * 100.0;
990            debug!("{}", format!("  Combined stake @ {our_sha}: {percentage:.2}%").dimmed());
991            #[cfg(feature = "metrics")]
992            metrics::gauge(metrics::bft::CONNECTED_STAKE_WITH_MATCHING_SHA, percentage);
993        }
994
995        // Log the validators that are not connected.
996        let num_not_connected = validators_total.saturating_sub(connected_validators.len());
997        if num_not_connected > 0 && self.tcp().uptime() > Self::MISSING_VALIDATOR_CONNECTIONS_GRACE_PERIOD {
998            // Cache the total stake for computing percentages.
999            let total_stake = committee.total_stake();
1000            let total_stake_f64 = total_stake as f64;
1001
1002            // Collect the committee members.
1003            let committee_members: HashSet<_> =
1004                self.ledger.current_committee().map(|c| c.members().keys().copied().collect()).unwrap_or_default();
1005
1006            let not_connected_stake: u64 = committee_members
1007                .difference(&connected_validator_addresses)
1008                .map(|address| {
1009                    let address_stake = committee.get_stake(*address);
1010                    let address_stake_as_percentage =
1011                        if total_stake == 0 { 0.0 } else { address_stake as f64 / total_stake_f64 * 100.0 };
1012                    debug!(
1013                        "{}",
1014                        format!("  Not connected to {address} ({address_stake_as_percentage:.2}% of total stake)")
1015                            .dimmed()
1016                    );
1017                    address_stake
1018                })
1019                .sum();
1020
1021            let not_connected_stake_as_percentage =
1022                if total_stake == 0 { 0.0 } else { not_connected_stake as f64 / total_stake_f64 * 100.0 };
1023            warn!(
1024                "Not connected to {num_not_connected} validators {total_validators} ({not_connected_stake_as_percentage:.2}% of total stake not connected)"
1025            );
1026            #[cfg(feature = "metrics")]
1027            {
1028                let connected_stake_as_percentage = 100.0 - not_connected_stake_as_percentage;
1029                metrics::gauge(metrics::bft::CONNECTED_STAKE, connected_stake_as_percentage);
1030            }
1031        } else {
1032            #[cfg(feature = "metrics")]
1033            metrics::gauge(metrics::bft::CONNECTED_STAKE, 100.0);
1034        };
1035
1036        if !committee.is_quorum_threshold_reached(&connected_validator_addresses) {
1037            // Not being connected to a quorum of validators is begning during startup.
1038            if self.tcp().uptime() > Self::MISSING_VALIDATOR_CONNECTIONS_GRACE_PERIOD {
1039                error!("Not connected to a quorum of validators");
1040            } else {
1041                debug!("Not connected to a quorum of validators");
1042            }
1043        }
1044    }
1045
1046    // Logs the validator participation scores.
1047    #[cfg(feature = "telemetry")]
1048    fn log_participation_scores(&self) {
1049        if let Ok(committee_lookback) = self.ledger.get_committee_lookback_for_round(self.storage.current_round()) {
1050            // Retrieve the participation scores.
1051            let participation_scores = self.validator_telemetry().get_participation_scores(&committee_lookback);
1052
1053            // Log the participation scores.
1054            debug!("Participation Scores (in the last {} rounds):", self.storage.max_gc_rounds());
1055            for (address, (cert_score, sig_score)) in participation_scores {
1056                debug!(
1057                    "{}",
1058                    format!("  {address} - certificates: {cert_score:.2}%  signatures: {sig_score:.2}%").dimmed()
1059                );
1060            }
1061        }
1062    }
1063
1064    /// This function attempts to connect to any disconnected trusted validators.
1065    fn handle_trusted_validators(&self) {
1066        let trusted_peers = self.trusted_peers();
1067
1068        // Attempt to re-establish connections with any trusted peer that is not connected already.
1069        let handles: Vec<JoinHandle<_>> = trusted_peers
1070            .iter()
1071            .filter_map(|validator_ip| {
1072                // Attempt to connect to the trusted validator.
1073                match self.connect(*validator_ip) {
1074                    Ok(hdl) => Some(hdl),
1075                    Err(ConnectError::SelfConnect { .. })
1076                    | Err(ConnectError::AlreadyConnected { .. })
1077                    | Err(ConnectError::AlreadyConnecting { .. }) => None,
1078                    Err(err) => {
1079                        warn!("Could not initiate connection to trusted validator at '{validator_ip}' - {err}");
1080                        None
1081                    }
1082                }
1083            })
1084            .collect();
1085
1086        if !handles.is_empty() {
1087            info!("Reconnecting to {} out of {} trusted validators", handles.len(), trusted_peers.len());
1088        }
1089    }
1090
1091    /// This function keeps the number of bootstrap peers within the allowed range.
1092    async fn handle_bootstrap_peers(&self) {
1093        // Return early if we are in trusted peers only mode.
1094        if self.trusted_peers_only {
1095            return;
1096        }
1097        // Split the bootstrap peers into connected and candidate lists.
1098        let mut candidate_bootstrap = Vec::new();
1099        let connected_bootstrap = self.filter_connected_peers(|peer| peer.node_type == NodeType::BootstrapClient);
1100        for bootstrap_ip in bootstrap_peers::<N>(self.is_dev()) {
1101            if !connected_bootstrap.iter().any(|peer| peer.listener_addr == bootstrap_ip) {
1102                candidate_bootstrap.push(bootstrap_ip);
1103            }
1104        }
1105        // If there are not enough connected bootstrap peers, connect to more.
1106        if connected_bootstrap.is_empty() {
1107            // Sample a random bootstrap peer to connect to (drop rng before any await).
1108            let peer_to_connect = candidate_bootstrap.into_iter().choose(&mut rand::rng());
1109            if let Some(peer_ip) = peer_to_connect {
1110                match self.connect(peer_ip) {
1111                    Ok(hdl) => {
1112                        debug!("{CONTEXT} (Re-)connecting to bootstrap peer at '{peer_ip}'");
1113                        let result = hdl.await;
1114                        if let Err(err) = result {
1115                            warn!("{CONTEXT} Failed to connect to bootstrap peer at '{peer_ip}' - {err}");
1116                        }
1117                    }
1118                    Err(ConnectError::AlreadyConnected { .. }) | Err(ConnectError::AlreadyConnecting { .. }) => {}
1119                    Err(err) => {
1120                        warn!("{CONTEXT} Could not initiate connection to bootstrap peer at '{peer_ip}' - {err}")
1121                    }
1122                }
1123            }
1124        }
1125        // Determine if the node is connected to more bootstrap peers than allowed.
1126        let num_surplus = connected_bootstrap.len().saturating_sub(1);
1127        if num_surplus > 0 {
1128            // Sample peers to disconnect (drop rng before any await).
1129            let peers_to_disconnect = connected_bootstrap.into_iter().sample(&mut rand::rng(), num_surplus);
1130            for peer in peers_to_disconnect {
1131                info!("{CONTEXT} Disconnecting from '{}' (exceeded maximum bootstrap)", peer.listener_addr);
1132                <Self as Transport<N>>::send(
1133                    self,
1134                    peer.listener_addr,
1135                    Event::Disconnect(DisconnectReason::NoReasonGiven.into()),
1136                )
1137                .await;
1138                // Disconnect from this peer.
1139                self.disconnect(peer.listener_addr);
1140            }
1141        }
1142    }
1143
1144    /// This function attempts to disconnect any validators that are not in the current committee.
1145    fn handle_unauthorized_validators(&self) {
1146        let self_ = self.clone();
1147        tokio::spawn(async move {
1148            // Retrieve the connected validators.
1149            let validators = self_.get_connected_peers();
1150            // Iterate over the validator IPs.
1151            for peer in validators {
1152                // Skip bootstrapper peers.
1153                if peer.node_type == NodeType::BootstrapClient {
1154                    continue;
1155                }
1156                // Disconnect any validator that is not in the current committee.
1157                if !self_.is_authorized_validator_ip(peer.listener_addr) {
1158                    warn!(
1159                        "{CONTEXT} Disconnecting from '{}' - Validator is not in the current committee",
1160                        peer.listener_addr
1161                    );
1162                    Transport::send(&self_, peer.listener_addr, DisconnectReason::ProtocolViolation.into()).await;
1163                    // Disconnect from this peer.
1164                    self_.disconnect(peer.listener_addr);
1165                }
1166            }
1167        });
1168    }
1169
1170    /// This function sends a `ValidatorsRequest` to a random validator,
1171    /// if the number of connected validators is less than the minimum.
1172    /// It also attempts to connect to known unconnected validators.
1173    async fn handle_min_connected_validators(&self) {
1174        // Attempt to connect to untrusted validators we're not connected to yet.
1175        // The trusted ones are already handled by `handle_trusted_validators`.
1176        let trusted_validators = self.trusted_peers();
1177        if self.number_of_connected_peers() < N::LATEST_MAX_CERTIFICATES() as usize {
1178            let (addrs, handles): (Vec<_>, Vec<_>) = self
1179                .get_candidate_peers()
1180                .iter()
1181                .filter_map(|peer| {
1182                    if trusted_validators.contains(&peer.listener_addr) {
1183                        return None;
1184                    }
1185
1186                    if let Some(previous_attempt) = peer.last_connection_attempt
1187                        && previous_attempt.elapsed() < Self::MINIMUM_TIME_BETWEEN_CONNECTION_ATTEMPTS
1188                    {
1189                        return None;
1190                    }
1191
1192                    match self.connect(peer.listener_addr) {
1193                        Ok(hdl) => Some((peer.listener_addr, hdl)),
1194                        Err(ConnectError::AlreadyConnected { .. })
1195                        | Err(ConnectError::AlreadyConnecting { .. })
1196                        | Err(ConnectError::SelfConnect { .. }) => None,
1197                        Err(err) => {
1198                            warn!(
1199                                "{CONTEXT} Could not initiate connection to validator at '{}' - {err}",
1200                                peer.listener_addr
1201                            );
1202                            None
1203                        }
1204                    }
1205                })
1206                .unzip();
1207
1208            for (addr, result) in addrs.into_iter().zip(join_all(handles).await) {
1209                if let Err(err) = result {
1210                    warn!("{CONTEXT} Failed to connect to validator at '{addr}' - {err}");
1211                }
1212            }
1213
1214            // Retrieve the connected validators.
1215            let validators = self.connected_peers();
1216            // If there are no validator IPs to connect to, return early.
1217            if validators.is_empty() {
1218                return;
1219            }
1220            // Select a random validator IP.
1221            if let Some(validator_ip) = validators.into_iter().choose(&mut rand::rng()) {
1222                let self_ = self.clone();
1223                tokio::spawn(async move {
1224                    // Increment the number of outbound validators requests for this validator.
1225                    self_.cache.increment_outbound_validators_requests(validator_ip);
1226                    // Send a `ValidatorsRequest` to the validator.
1227                    let _ = Transport::send(&self_, validator_ip, Event::ValidatorsRequest(ValidatorsRequest)).await;
1228                });
1229            }
1230        }
1231    }
1232
1233    /// Processes a message received from the network.
1234    async fn process_message_inner(&self, peer_addr: SocketAddr, message: Event<N>) {
1235        // Process the message. Disconnect if the peer violated the protocol.
1236        if let Err(error) = self.inbound(peer_addr, message).await
1237            && let Some(peer_ip) = self.resolver.read().get_listener(peer_addr)
1238        {
1239            warn!("{CONTEXT} Disconnecting from '{peer_ip}' - {error}");
1240            let self_ = self.clone();
1241            tokio::spawn(async move {
1242                Transport::send(&self_, peer_ip, DisconnectReason::ProtocolViolation.into()).await;
1243                // Disconnect from this peer.
1244                self_.disconnect(peer_ip);
1245            });
1246        }
1247    }
1248
1249    // Remove addresses whose ban time has expired.
1250    fn handle_banned_ips(&self) {
1251        self.tcp.banned_peers().remove_old_bans(IP_BAN_TIME_IN_SECS);
1252    }
1253}
1254
1255#[async_trait]
1256impl<N: Network> Transport<N> for Gateway<N> {
1257    /// Sends the given event to specified peer.
1258    ///
1259    /// This method is rate limited to prevent spamming the peer.
1260    ///
1261    /// This function returns as soon as the event is queued to be sent,
1262    /// without waiting for the actual delivery; instead, the caller is provided with a [`oneshot::Receiver`]
1263    /// which can be used to determine when and whether the event has been delivered.
1264    async fn send(&self, peer_ip: SocketAddr, event: Event<N>) -> Option<oneshot::Receiver<io::Result<()>>> {
1265        macro_rules! send {
1266            ($self:ident, $cache_map:ident, $interval:expr, $freq:ident) => {{
1267                // Rate limit the number of certificate requests sent to the peer.
1268                while $self.cache.$cache_map(peer_ip, $interval) > $self.$freq() {
1269                    // Sleep for a short period of time to allow the cache to clear.
1270                    tokio::time::sleep(Duration::from_millis(10)).await;
1271                }
1272                // Send the event to the peer.
1273                $self.send_inner(peer_ip, event)
1274            }};
1275        }
1276
1277        // Increment the cache for certificate, transmission and block events.
1278        match event {
1279            Event::CertificateRequest(_) | Event::CertificateResponse(_) => {
1280                // Update the outbound event cache. This is necessary to ensure we don't under count the outbound events.
1281                self.cache.insert_outbound_event(peer_ip, CACHE_EVENTS_INTERVAL);
1282                // Send the event to the peer.
1283                send!(self, insert_outbound_certificate, CACHE_REQUESTS_INTERVAL, max_cache_certificates)
1284            }
1285            Event::TransmissionRequest(_) | Event::TransmissionResponse(_) => {
1286                // Update the outbound event cache. This is necessary to ensure we don't under count the outbound events.
1287                self.cache.insert_outbound_event(peer_ip, CACHE_EVENTS_INTERVAL);
1288                // Send the event to the peer.
1289                send!(self, insert_outbound_transmission, CACHE_REQUESTS_INTERVAL, max_cache_transmissions)
1290            }
1291            Event::BlockRequest(request) => {
1292                // Insert the outbound request so we can match it to responses.
1293                self.cache.insert_outbound_block_request(peer_ip, request);
1294                // Send the event to the peer and update the outbound event cache, use the general rate limit.
1295                send!(self, insert_outbound_event, CACHE_EVENTS_INTERVAL, max_cache_events)
1296            }
1297            _ => {
1298                // Send the event to the peer, use the general rate limit.
1299                send!(self, insert_outbound_event, CACHE_EVENTS_INTERVAL, max_cache_events)
1300            }
1301        }
1302    }
1303
1304    /// Broadcasts the given event to all connected peers.
1305    // TODO(ljedrz): the event should be checked for the presence of Data::Object, and
1306    // serialized in advance if it's there.
1307    fn broadcast(&self, event: Event<N>) {
1308        // Ensure there are connected peers.
1309        if self.number_of_connected_peers() > 0 {
1310            let self_ = self.clone();
1311            let connected_peers = self.connected_peers();
1312            tokio::spawn(async move {
1313                // Iterate through all connected peers.
1314                for peer_ip in connected_peers {
1315                    // Send the event to the peer.
1316                    let _ = Transport::send(&self_, peer_ip, event.clone()).await;
1317                }
1318            });
1319        }
1320    }
1321}
1322
1323impl<N: Network> P2P for Gateway<N> {
1324    /// Returns a reference to the TCP instance.
1325    fn tcp(&self) -> &Tcp {
1326        &self.tcp
1327    }
1328}
1329
1330#[async_trait]
1331impl<N: Network> Reading for Gateway<N> {
1332    type Codec = EventCodec<N>;
1333    type Message = Event<N>;
1334
1335    /// Creates a [`Decoder`] used to interpret messages from the network.
1336    /// The `side` param indicates the connection side **from the node's perspective**.
1337    fn codec(&self, _peer_addr: SocketAddr, _side: ConnectionSide) -> Self::Codec {
1338        Default::default()
1339    }
1340
1341    /// Processes a message received from the network.
1342    async fn process_message(&self, peer_addr: SocketAddr, message: Self::Message) -> io::Result<()> {
1343        if matches!(message, Event::BlockRequest(_) | Event::BlockResponse(_)) {
1344            let self_ = self.clone();
1345            // Handle BlockRequest and BlockResponse messages in a separate task to not block the
1346            // inbound queue.
1347            tokio::spawn(async move {
1348                self_.process_message_inner(peer_addr, message).await;
1349            });
1350        } else {
1351            self.process_message_inner(peer_addr, message).await;
1352        }
1353        Ok(())
1354    }
1355
1356    /// Computes the depth of per-connection queues used to process inbound messages, sufficient to process the maximum expected load at any givent moment.
1357    /// 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.
1358    fn message_queue_depth(&self) -> usize {
1359        2 * BatchHeader::<N>::MAX_GC_ROUNDS
1360            * N::LATEST_MAX_CERTIFICATES() as usize
1361            * BatchHeader::<N>::MAX_TRANSMISSIONS_PER_BATCH
1362    }
1363}
1364
1365#[async_trait]
1366impl<N: Network> Writing for Gateway<N> {
1367    type Codec = EventCodec<N>;
1368    type Message = Event<N>;
1369
1370    /// Creates an [`Encoder`] used to write the outbound messages to the target stream.
1371    /// The `side` parameter indicates the connection side **from the node's perspective**.
1372    fn codec(&self, _peer_addr: SocketAddr, _side: ConnectionSide) -> Self::Codec {
1373        Default::default()
1374    }
1375
1376    /// Computes the depth of per-connection queues used to send outbound messages, sufficient to process the maximum expected load at any givent moment.
1377    /// 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
1378    /// (like slow serialization) or network.
1379    fn message_queue_depth(&self) -> usize {
1380        2 * BatchHeader::<N>::MAX_GC_ROUNDS
1381            * N::LATEST_MAX_CERTIFICATES() as usize
1382            * BatchHeader::<N>::MAX_TRANSMISSIONS_PER_BATCH
1383    }
1384}
1385
1386#[async_trait]
1387impl<N: Network> Disconnect for Gateway<N> {
1388    /// Any extra operations to be performed during a disconnect.
1389    async fn handle_disconnect(&self, peer_addr: SocketAddr, origin: DisconnectOrigin) {
1390        debug!("Physically disconnecting from {peer_addr}; origin: {origin:?}");
1391
1392        if let Some(peer_ip) = self.resolve_to_listener(&peer_addr) {
1393            // TODO(kaimast): This can, in theory, still lead to race conditions, if we immediately reconnect to the same peer.
1394            // In practice, there should always be a significant delay between those two delays, so it is not an immediate issue.
1395            //
1396            // To properly fix this, we either needk hold a lock here, or add a dedicated "disconnecting" state, so that
1397            // a peer is not re-added while the rest of the disconnect logic is running.
1398            let was_fully_connected = self.downgrade_peer_to_candidate(peer_ip);
1399
1400            // Remove the peer from the sync module. Except for some tests, there is always a sync sender.
1401            if was_fully_connected && let Some(sync_sender) = self.sync_sender.get() {
1402                let (tx, rx) = oneshot::channel();
1403
1404                if let Err(err) = sync_sender.tx_block_sync_remove_peer.send((peer_ip, tx)).await {
1405                    let err: anyhow::Error = err.into();
1406                    let err =
1407                        err.context(format!("Unable to remove disconnecting peer '{peer_ip}' from the sync module"));
1408                    warn!("{CONTEXT} {}", flatten_error(err));
1409                }
1410
1411                if let Err(err) = rx.await {
1412                    let err: anyhow::Error = err.into();
1413                    let err =
1414                        err.context(format!("Unable to remove disconnecting peer '{peer_ip}' from the sync module"));
1415                    warn!("{CONTEXT} {}", flatten_error(err));
1416                }
1417            }
1418            // We don't clear this map based on time but only on peer disconnect.
1419            // This is sufficient to avoid infinite growth as the committee has a fixed number
1420            // of members.
1421            self.cache.clear_outbound_validators_requests(peer_ip);
1422            self.cache.clear_outbound_block_requests(peer_ip);
1423        } else {
1424            warn!("{CONTEXT} Got disconnect for a peer '{peer_addr}' that is not in the peer pool");
1425        }
1426    }
1427}
1428
1429#[async_trait]
1430impl<N: Network> OnConnect for Gateway<N> {
1431    async fn on_connect(&self, peer_addr: SocketAddr) {
1432        if let Some(listener_addr) = self.resolve_to_listener(&peer_addr) {
1433            if let Some(peer) = self.get_connected_peer(listener_addr) {
1434                if peer.node_type == NodeType::BootstrapClient {
1435                    self.cache.increment_outbound_validators_requests(listener_addr);
1436                    let _ =
1437                        <Self as Transport<N>>::send(self, listener_addr, Event::ValidatorsRequest(ValidatorsRequest))
1438                            .await;
1439                }
1440            }
1441        }
1442    }
1443}
1444
1445#[async_trait]
1446impl<N: Network> Handshake for Gateway<N> {
1447    /// Performs the handshake protocol.
1448    async fn perform_handshake(&self, mut connection: Connection) -> Result<Connection, ConnectError> {
1449        // Perform the handshake.
1450        let peer_addr = connection.addr();
1451        let peer_side = connection.side();
1452
1453        // Check (or impose) IP-level bans.
1454        #[cfg(not(test))]
1455        if self.dev().is_none() && peer_side == ConnectionSide::Initiator {
1456            // If the IP is already banned reject the connection.
1457            if self.is_ip_banned(peer_addr.ip()) {
1458                trace!("{CONTEXT} Rejected a connection request from banned IP '{}'", peer_addr.ip());
1459                return Err(ConnectError::BannedIp { ip: peer_addr.ip() });
1460            }
1461
1462            let num_attempts = self.cache.insert_inbound_connection(peer_addr.ip(), CONNECTION_ATTEMPTS_SINCE_SECS);
1463
1464            debug!("Number of connection attempts from '{}': {}", peer_addr.ip(), num_attempts);
1465            if num_attempts > MAX_CONNECTION_ATTEMPTS {
1466                self.update_ip_ban(peer_addr.ip());
1467                trace!("{CONTEXT} Rejected a consecutive connection request from IP '{}'", peer_addr.ip());
1468                return Err(ConnectError::other(anyhow!("'{}' appears to be spamming connections", peer_addr.ip())));
1469            }
1470        }
1471
1472        let stream = self.borrow_stream(&mut connection);
1473
1474        // If this is an inbound connection, we log it, but don't know the listening address yet.
1475        // Otherwise, we can immediately register the listening address.
1476        let mut listener_addr = if peer_side == ConnectionSide::Initiator {
1477            debug!("{CONTEXT} Received a connection request from '{peer_addr}'");
1478            None
1479        } else {
1480            debug!("{CONTEXT} Shaking hands with {peer_addr}...");
1481            Some(peer_addr)
1482        };
1483
1484        // Retrieve the restrictions ID.
1485        let restrictions_id = self.ledger.latest_restrictions_id();
1486
1487        // Perform the handshake; we pass on a mutable reference to peer_ip in case the process is broken at any point in time.
1488        let handshake_result = if peer_side == ConnectionSide::Responder {
1489            self.handshake_inner_initiator(peer_addr, restrictions_id, stream).await
1490        } else {
1491            self.handshake_inner_responder(peer_addr, &mut listener_addr, restrictions_id, stream).await
1492        };
1493
1494        if let Some(addr) = listener_addr {
1495            match handshake_result {
1496                Ok(ref cr) => {
1497                    let node_type = if bootstrap_peers::<N>(self.is_dev()).contains(&addr) {
1498                        NodeType::BootstrapClient
1499                    } else {
1500                        NodeType::Validator
1501                    };
1502
1503                    let mut peer_pool = self.peer_pool.write();
1504
1505                    // Validators may change their listening address, but not the Aleo address; traverse
1506                    // the peer pool, and retain previously connected (the prior Aleo address is known)
1507                    // candidate peers with the same Aleo address only if their listening address is the
1508                    // same; otherwise, it may be concluded that a known validator has changed their
1509                    // listening address, and thus the old entry should be removed as outdated.
1510                    peer_pool.retain(|_, peer| {
1511                        if let Peer::Candidate(peer) = peer
1512                            && let Some(old_aleo_addr) = peer.last_known_aleo_addr
1513                        {
1514                            old_aleo_addr != cr.address || peer.listener_addr == addr
1515                        } else {
1516                            true
1517                        }
1518                    });
1519
1520                    if let Some(peer) = peer_pool.get_mut(&addr) {
1521                        self.resolver.write().insert_peer(addr, peer_addr, Some(cr.address));
1522                        peer.upgrade_to_connected(
1523                            peer_addr,
1524                            cr.listener_port,
1525                            cr.address,
1526                            node_type,
1527                            cr.version,
1528                            cr.snarkos_sha,
1529                            ConnectionMode::Gateway,
1530                        );
1531                    }
1532                    info!("{CONTEXT} Connected to '{addr}'");
1533                }
1534                Err(error) => {
1535                    if let Some(peer) = self.peer_pool.write().get_mut(&addr) {
1536                        // The peer may only be downgraded if it's a ConnectingPeer.
1537                        if peer.is_connecting() {
1538                            peer.downgrade_to_candidate(addr);
1539                        }
1540                    }
1541                    return Err(error);
1542                }
1543            }
1544        }
1545
1546        Ok(connection)
1547    }
1548}
1549
1550/// A macro unwrapping the expected handshake event or returning an error for unexpected events.
1551macro_rules! expect_event {
1552    ($event_ty:path, $framed:expr, $peer_addr:expr) => {
1553        match $framed.try_next().await? {
1554            // Received the expected event, proceed.
1555            Some($event_ty(data)) => {
1556                trace!("{CONTEXT} Received '{}' from '{}'", data.name(), $peer_addr);
1557                data
1558            }
1559            // Received a disconnect event, abort.
1560            Some(Event::Disconnect($crate::events::Disconnect { reason })) => {
1561                return Err(ConnectError::other(format!("'{}' disconnected with reason \"{reason}\"", $peer_addr)));
1562            }
1563            // Received an unexpected event, abort.
1564            Some(ty) => {
1565                return Err(ConnectError::other(format!(
1566                    "'{}' did not follow the handshake protocol: received {:?} instead of {}",
1567                    $peer_addr,
1568                    ty.name(),
1569                    stringify!($msg_ty),
1570                )));
1571            }
1572            // Received nothing.
1573            None => return Err(ConnectError::IoError(io::ErrorKind::BrokenPipe.into())),
1574        }
1575    };
1576}
1577
1578/// Send the given message to the peer.
1579async fn send_event<N: Network>(
1580    framed: &mut Framed<&mut TcpStream, EventCodec<N>>,
1581    peer_addr: SocketAddr,
1582    event: Event<N>,
1583) -> io::Result<()> {
1584    trace!("{CONTEXT} Sending '{}' to '{peer_addr}'", event.name());
1585    framed.send(event).await
1586}
1587
1588impl<N: Network> Gateway<N> {
1589    /// The connection initiator side of the handshake.
1590    async fn handshake_inner_initiator<'a>(
1591        &'a self,
1592        peer_addr: SocketAddr,
1593        restrictions_id: Field<N>,
1594        stream: &'a mut TcpStream,
1595    ) -> Result<ChallengeRequest<N>, ConnectError> {
1596        // Introduce the peer into the peer pool.
1597        self.add_connecting_peer(peer_addr)?;
1598
1599        // Construct the stream.
1600        let mut framed = Framed::new(stream, EventCodec::<N>::handshake());
1601
1602        /* Step 1: Send the challenge request. */
1603
1604        // Sample a random nonce.
1605        let our_nonce: u64 = rand::random();
1606        // Determine the snarkOS SHA to send to the peer.
1607        let current_block_height = self.ledger.latest_block_height();
1608        let consensus_version = N::CONSENSUS_VERSION(current_block_height).unwrap();
1609        let snarkos_sha = match (self.is_dev(), consensus_version >= ConsensusVersion::V12, get_repo_commit_hash()) {
1610            (true, _, Some(sha)) => Some(sha),
1611            (_, true, Some(sha)) => Some(sha),
1612            _ => None,
1613        };
1614        // Send a challenge request to the peer.
1615        let our_request = ChallengeRequest::new(self.local_ip().port(), self.account.address(), our_nonce, snarkos_sha);
1616        send_event(&mut framed, peer_addr, Event::ChallengeRequest(our_request)).await?;
1617
1618        /* Step 2: Receive the peer's challenge response followed by the challenge request. */
1619
1620        // Listen for the challenge response message.
1621        let peer_response = expect_event!(Event::ChallengeResponse, framed, peer_addr);
1622        // Listen for the challenge request message.
1623        let peer_request = expect_event!(Event::ChallengeRequest, framed, peer_addr);
1624
1625        // Verify the challenge response. If a disconnect reason was returned, send the disconnect message and abort.
1626        if let Some(reason) = self
1627            .verify_challenge_response(peer_addr, peer_request.address, peer_response, restrictions_id, our_nonce)
1628            .await
1629        {
1630            send_event(&mut framed, peer_addr, reason.into()).await?;
1631            return Err(ConnectError::application(reason));
1632        }
1633
1634        // Verify the challenge request. If a disconnect reason was returned, send the disconnect message and abort.
1635        if let Some(reason) = self.verify_challenge_request(peer_addr, &peer_request) {
1636            send_event(&mut framed, peer_addr, reason.into()).await?;
1637            return Err(reason.into_connect_error(peer_addr));
1638        }
1639
1640        /* Step 3: Send the challenge response. */
1641
1642        // Sign the counterparty nonce.
1643        let response_nonce: u64 = rand::random();
1644        let data = [peer_request.nonce.to_le_bytes(), response_nonce.to_le_bytes()].concat();
1645        let Ok(our_signature) = self.account.sign_bytes(&data, &mut rand::rng()) else {
1646            return Err(ConnectError::other(anyhow!("Failed to sign the challenge request nonce")));
1647        };
1648        // Send the challenge response.
1649        let our_response =
1650            ChallengeResponse { restrictions_id, signature: Data::Object(our_signature), nonce: response_nonce };
1651        send_event(&mut framed, peer_addr, Event::ChallengeResponse(our_response)).await?;
1652
1653        Ok(peer_request)
1654    }
1655
1656    /// The connection responder side of the handshake.
1657    async fn handshake_inner_responder<'a>(
1658        &'a self,
1659        peer_addr: SocketAddr,
1660        peer_ip: &mut Option<SocketAddr>,
1661        restrictions_id: Field<N>,
1662        stream: &'a mut TcpStream,
1663    ) -> Result<ChallengeRequest<N>, ConnectError> {
1664        // Construct the stream.
1665        let mut framed = Framed::new(stream, EventCodec::<N>::handshake());
1666
1667        /* Step 1: Receive the challenge request. */
1668
1669        // Listen for the challenge request message.
1670        let peer_request = expect_event!(Event::ChallengeRequest, framed, peer_addr);
1671
1672        // Ensure the address is not the same as this node.
1673        if self.account.address() == peer_request.address {
1674            return Err(ConnectError::SelfConnect { address: peer_addr });
1675        }
1676
1677        // Obtain the peer's listening address.
1678        *peer_ip = Some(SocketAddr::new(peer_addr.ip(), peer_request.listener_port));
1679        let peer_ip = peer_ip.unwrap();
1680
1681        // Knowing the peer's listening address, ensure it is allowed to connect.
1682        if let Err(reason) = self.ensure_peer_is_allowed(peer_ip) {
1683            send_event(&mut framed, peer_addr, reason.into()).await?;
1684            return Err(reason.into_connect_error(peer_addr));
1685        }
1686
1687        // Introduce the peer into the peer pool.
1688        self.add_connecting_peer(peer_ip)?;
1689
1690        // Verify the challenge request. If a disconnect reason was returned, send the disconnect message and abort.
1691        if let Some(reason) = self.verify_challenge_request(peer_addr, &peer_request) {
1692            send_event(&mut framed, peer_addr, reason.into()).await?;
1693            return Err(reason.into_connect_error(peer_addr));
1694        }
1695
1696        /* Step 2: Send the challenge response followed by own challenge request. */
1697
1698        // Sign the counterparty nonce.
1699        let response_nonce: u64 = rand::random();
1700        let data = [peer_request.nonce.to_le_bytes(), response_nonce.to_le_bytes()].concat();
1701        let Ok(our_signature) = self.account.sign_bytes(&data, &mut rand::rng()) else {
1702            return Err(ConnectError::other(anyhow!("Failed to sign the challenge request nonce")));
1703        };
1704        // Send the challenge response.
1705        let our_response =
1706            ChallengeResponse { restrictions_id, signature: Data::Object(our_signature), nonce: response_nonce };
1707        send_event(&mut framed, peer_addr, Event::ChallengeResponse(our_response)).await?;
1708
1709        // Sample a random nonce.
1710        let our_nonce: u64 = rand::random();
1711        // Determine the snarkOS SHA to send to the peer.
1712        let current_block_height = self.ledger.latest_block_height();
1713        let consensus_version = N::CONSENSUS_VERSION(current_block_height).unwrap();
1714        let snarkos_sha = match (self.is_dev(), consensus_version >= ConsensusVersion::V12, get_repo_commit_hash()) {
1715            (true, _, Some(sha)) => Some(sha),
1716            (_, true, Some(sha)) => Some(sha),
1717            _ => None,
1718        };
1719        // Send the challenge request.
1720        let our_request = ChallengeRequest::new(self.local_ip().port(), self.account.address(), our_nonce, snarkos_sha);
1721        send_event(&mut framed, peer_addr, Event::ChallengeRequest(our_request)).await?;
1722
1723        /* Step 3: Receive the challenge response. */
1724
1725        // Listen for the challenge response message.
1726        let peer_response = expect_event!(Event::ChallengeResponse, framed, peer_addr);
1727        // Verify the challenge response. If a disconnect reason was returned, send the disconnect message and abort.
1728        if let Some(reason) = self
1729            .verify_challenge_response(peer_addr, peer_request.address, peer_response, restrictions_id, our_nonce)
1730            .await
1731        {
1732            send_event(&mut framed, peer_addr, reason.into()).await?;
1733            Err(reason.into_connect_error(peer_addr))
1734        } else {
1735            Ok(peer_request)
1736        }
1737    }
1738
1739    /// Verifies the given challenge request. Returns a disconnect reason if the request is invalid.
1740    #[must_use]
1741    fn verify_challenge_request(&self, peer_addr: SocketAddr, event: &ChallengeRequest<N>) -> Option<DisconnectReason> {
1742        // Retrieve the components of the challenge request.
1743        let &ChallengeRequest { version, listener_port, address, nonce: _, ref snarkos_sha } = event;
1744        log_repo_sha_comparison(peer_addr, snarkos_sha, CONTEXT);
1745
1746        let listener_addr = SocketAddr::new(peer_addr.ip(), listener_port);
1747
1748        // Ensure the event protocol version is not outdated.
1749        if version < Event::<N>::VERSION {
1750            return Some(DisconnectReason::OutdatedClientVersion);
1751        }
1752        // If the node is in trusted peers only mode, ensure the peer is trusted.
1753        if self.trusted_peers_only && !self.is_trusted(listener_addr) {
1754            warn!("{CONTEXT} Dropping '{peer_addr}' for being an untrusted validator ({address})");
1755            return Some(DisconnectReason::NoExternalPeersAllowed);
1756        }
1757        if !bootstrap_peers::<N>(self.dev().is_some()).contains(&listener_addr) {
1758            // Ensure the address is a current committee member.
1759            if !self.is_authorized_validator_address(address) {
1760                return Some(DisconnectReason::UnauthorizedValidator);
1761            }
1762        }
1763
1764        // Ensure the address is not already connected.
1765        if self.is_connected_address(address) {
1766            return Some(DisconnectReason::AlreadyConnectedToAleoAddress);
1767        }
1768
1769        None
1770    }
1771
1772    /// Verifies the given challenge response. Returns a disconnect reason if the response is invalid.
1773    #[must_use]
1774    async fn verify_challenge_response(
1775        &self,
1776        peer_addr: SocketAddr,
1777        peer_address: Address<N>,
1778        response: ChallengeResponse<N>,
1779        expected_restrictions_id: Field<N>,
1780        expected_nonce: u64,
1781    ) -> Option<DisconnectReason> {
1782        // Retrieve the components of the challenge response.
1783        let ChallengeResponse { restrictions_id, signature, nonce } = response;
1784
1785        // Verify the restrictions ID.
1786        if restrictions_id != expected_restrictions_id {
1787            warn!("{CONTEXT} Handshake with '{peer_addr}' failed (incorrect restrictions ID)");
1788            return Some(DisconnectReason::InvalidChallengeResponse);
1789        }
1790        // Perform the deferred non-blocking deserialization of the signature.
1791        let Ok(signature) = spawn_blocking!(signature.deserialize_blocking()) else {
1792            warn!("{CONTEXT} Handshake with '{peer_addr}' failed (cannot deserialize the signature)");
1793            return Some(DisconnectReason::InvalidChallengeResponse);
1794        };
1795        // Verify the signature.
1796        if !signature.verify_bytes(&peer_address, &[expected_nonce.to_le_bytes(), nonce.to_le_bytes()].concat()) {
1797            warn!("{CONTEXT} Handshake with '{peer_addr}' failed (invalid signature)");
1798            return Some(DisconnectReason::InvalidChallengeResponse);
1799        }
1800        None
1801    }
1802}
1803
1804#[cfg(test)]
1805mod prop_tests {
1806    use crate::{
1807        Gateway,
1808        MAX_WORKERS,
1809        MEMORY_POOL_PORT,
1810        Worker,
1811        helpers::{Storage, init_primary_channels, init_worker_channels},
1812    };
1813
1814    use snarkos_account::Account;
1815    use snarkos_node_bft_ledger_service::MockLedgerService;
1816    use snarkos_node_bft_storage_service::BFTMemoryService;
1817    use snarkos_node_network::PeerPoolHandling;
1818    use snarkos_node_tcp::P2P;
1819    use snarkos_utilities::NodeDataDir;
1820
1821    use snarkos_node_bft_events::committee_prop_tests::{CommitteeContext, ValidatorSet};
1822    use snarkvm::{
1823        ledger::{
1824            committee::{Committee, test_helpers::sample_committee_for_round_and_members},
1825            narwhal::{BatchHeader, batch_certificate::test_helpers::sample_batch_certificate_for_round},
1826        },
1827        prelude::{MainnetV0, PrivateKey},
1828        utilities::TestRng,
1829    };
1830
1831    use indexmap::{IndexMap, IndexSet};
1832    use proptest::{
1833        prelude::{Arbitrary, BoxedStrategy, Just, Strategy, any, any_with},
1834        sample::Selector,
1835    };
1836    use std::{
1837        fmt::{Debug, Formatter},
1838        net::{IpAddr, Ipv4Addr, SocketAddr},
1839        sync::Arc,
1840    };
1841    use test_strategy::proptest;
1842
1843    type CurrentNetwork = MainnetV0;
1844
1845    impl Debug for Gateway<CurrentNetwork> {
1846        fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
1847            // TODO implement Debug properly and move it over to production code
1848            f.debug_tuple("Gateway").field(&self.account.address()).field(&self.tcp.config()).finish()
1849        }
1850    }
1851
1852    #[derive(Debug, test_strategy::Arbitrary)]
1853    enum GatewayAddress {
1854        Dev(u8),
1855        Prod(Option<SocketAddr>),
1856    }
1857
1858    impl GatewayAddress {
1859        fn ip(&self) -> Option<SocketAddr> {
1860            if let GatewayAddress::Prod(ip) = self {
1861                return *ip;
1862            }
1863            None
1864        }
1865
1866        fn port(&self) -> Option<u16> {
1867            if let GatewayAddress::Dev(port) = self {
1868                return Some(*port as u16);
1869            }
1870            None
1871        }
1872    }
1873
1874    impl Arbitrary for Gateway<CurrentNetwork> {
1875        type Parameters = ();
1876        type Strategy = BoxedStrategy<Gateway<CurrentNetwork>>;
1877
1878        fn arbitrary_with(_: Self::Parameters) -> Self::Strategy {
1879            any_valid_dev_gateway()
1880                .prop_map(|(storage, _, private_key, address)| {
1881                    Gateway::new(
1882                        Account::try_from(private_key).unwrap(),
1883                        storage.clone(),
1884                        storage.ledger().clone(),
1885                        address.ip(),
1886                        &[],
1887                        false,
1888                        NodeDataDir::new_test(None),
1889                        address.port(),
1890                    )
1891                    .unwrap()
1892                })
1893                .boxed()
1894        }
1895    }
1896
1897    type GatewayInput = (Storage<CurrentNetwork>, CommitteeContext, PrivateKey<CurrentNetwork>, GatewayAddress);
1898
1899    fn any_valid_dev_gateway() -> BoxedStrategy<GatewayInput> {
1900        (any::<CommitteeContext>(), any::<Selector>())
1901            .prop_flat_map(|(context, account_selector)| {
1902                let CommitteeContext(_, ValidatorSet(validators)) = context.clone();
1903                (
1904                    any_with::<Storage<CurrentNetwork>>(context.clone()),
1905                    Just(context),
1906                    Just(account_selector.select(validators)),
1907                    0u8..,
1908                )
1909                    .prop_map(|(a, b, c, d)| (a, b, c.private_key, GatewayAddress::Dev(d)))
1910            })
1911            .boxed()
1912    }
1913
1914    fn any_valid_prod_gateway() -> BoxedStrategy<GatewayInput> {
1915        (any::<CommitteeContext>(), any::<Selector>())
1916            .prop_flat_map(|(context, account_selector)| {
1917                let CommitteeContext(_, ValidatorSet(validators)) = context.clone();
1918                (
1919                    any_with::<Storage<CurrentNetwork>>(context.clone()),
1920                    Just(context),
1921                    Just(account_selector.select(validators)),
1922                    any::<Option<SocketAddr>>(),
1923                )
1924                    .prop_map(|(a, b, c, d)| (a, b, c.private_key, GatewayAddress::Prod(d)))
1925            })
1926            .boxed()
1927    }
1928
1929    #[proptest]
1930    fn gateway_dev_initialization(#[strategy(any_valid_dev_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        assert_eq!(tcp_config.listener_ip, Some(IpAddr::V4(Ipv4Addr::LOCALHOST)));
1947        assert_eq!(tcp_config.desired_listening_port, Some(MEMORY_POOL_PORT + dev.port().unwrap()));
1948
1949        let tcp_config = gateway.tcp().config();
1950        assert_eq!(tcp_config.max_connections, Committee::<CurrentNetwork>::max_committee_size() * 10);
1951        assert_eq!(gateway.account().address(), account.address());
1952    }
1953
1954    #[proptest]
1955    fn gateway_prod_initialization(#[strategy(any_valid_prod_gateway())] input: GatewayInput) {
1956        let (storage, _, private_key, dev) = input;
1957        let account = Account::try_from(private_key).unwrap();
1958
1959        let gateway = Gateway::new(
1960            account.clone(),
1961            storage.clone(),
1962            storage.ledger().clone(),
1963            dev.ip(),
1964            &[],
1965            false,
1966            NodeDataDir::new_test(None),
1967            dev.port(),
1968        )
1969        .unwrap();
1970        let tcp_config = gateway.tcp().config();
1971        if let Some(socket_addr) = dev.ip() {
1972            assert_eq!(tcp_config.listener_ip, Some(socket_addr.ip()));
1973            assert_eq!(tcp_config.desired_listening_port, Some(socket_addr.port()));
1974        } else {
1975            assert_eq!(tcp_config.listener_ip, Some(IpAddr::V4(Ipv4Addr::UNSPECIFIED)));
1976            assert_eq!(tcp_config.desired_listening_port, Some(MEMORY_POOL_PORT));
1977        }
1978
1979        let tcp_config = gateway.tcp().config();
1980        assert_eq!(tcp_config.max_connections, Committee::<CurrentNetwork>::max_committee_size() * 10);
1981        assert_eq!(gateway.account().address(), account.address());
1982    }
1983
1984    #[proptest(async = "tokio")]
1985    async fn gateway_start(
1986        #[strategy(any_valid_dev_gateway())] input: GatewayInput,
1987        #[strategy(0..MAX_WORKERS)] workers_count: u8,
1988    ) {
1989        let (storage, committee, private_key, dev) = input;
1990        let committee = committee.0;
1991        let worker_storage = storage.clone();
1992        let account = Account::try_from(private_key).unwrap();
1993
1994        let gateway = Gateway::new(
1995            account,
1996            storage.clone(),
1997            storage.ledger().clone(),
1998            dev.ip(),
1999            &[],
2000            false,
2001            NodeDataDir::new_test(None),
2002            dev.port(),
2003        )
2004        .unwrap();
2005
2006        let (primary_sender, _) = init_primary_channels();
2007
2008        let (workers, worker_senders) = {
2009            // Construct a map of the worker senders.
2010            let mut tx_workers = IndexMap::new();
2011            let mut workers = IndexMap::new();
2012
2013            // Initialize the workers.
2014            for id in 0..workers_count {
2015                // Construct the worker channels.
2016                let (tx_worker, rx_worker) = init_worker_channels();
2017                // Construct the worker instance.
2018                let ledger = Arc::new(MockLedgerService::new(committee.clone()));
2019                let worker =
2020                    Worker::new(id, Arc::new(gateway.clone()), worker_storage.clone(), ledger, Default::default())
2021                        .unwrap();
2022                // Run the worker instance.
2023                worker.run(rx_worker);
2024
2025                // Add the worker and the worker sender to maps
2026                workers.insert(id, worker);
2027                tx_workers.insert(id, tx_worker);
2028            }
2029            (workers, tx_workers)
2030        };
2031
2032        gateway.run(primary_sender, worker_senders, None).await;
2033        assert_eq!(
2034            gateway.local_ip(),
2035            SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), MEMORY_POOL_PORT + dev.port().unwrap())
2036        );
2037        assert_eq!(gateway.num_workers(), workers.len() as u8);
2038    }
2039
2040    #[proptest]
2041    fn test_is_authorized_validator(#[strategy(any_valid_dev_gateway())] input: GatewayInput) {
2042        let rng = &mut TestRng::default();
2043
2044        // Initialize the round parameters.
2045        let current_round = 2;
2046        let committee_size = 4;
2047        let max_gc_rounds = BatchHeader::<CurrentNetwork>::MAX_GC_ROUNDS as u64;
2048        let (_, _, private_key, dev) = input;
2049        let account = Account::try_from(private_key).unwrap();
2050
2051        // Sample the certificates.
2052        let mut certificates = IndexSet::new();
2053        for _ in 0..committee_size {
2054            certificates.insert(sample_batch_certificate_for_round(current_round, rng));
2055        }
2056        let addresses: Vec<_> = certificates.iter().map(|certificate| certificate.author()).collect();
2057        // Initialize the committee.
2058        let committee = sample_committee_for_round_and_members(current_round, addresses, rng);
2059        // Sample extra certificates from non-committee members.
2060        for _ in 0..committee_size {
2061            certificates.insert(sample_batch_certificate_for_round(current_round, rng));
2062        }
2063        // Initialize the ledger.
2064        let ledger = Arc::new(MockLedgerService::new(committee.clone()));
2065        // Initialize the storage.
2066        let storage = Storage::new(ledger.clone(), Arc::new(BFTMemoryService::new()), max_gc_rounds).unwrap();
2067        // Initialize the gateway.
2068        let gateway = Gateway::new(
2069            account.clone(),
2070            storage.clone(),
2071            ledger.clone(),
2072            dev.ip(),
2073            &[],
2074            false,
2075            NodeDataDir::new_test(None),
2076            dev.port(),
2077        )
2078        .unwrap();
2079        // Insert certificate to the storage.
2080        for certificate in certificates.iter() {
2081            storage.testing_only_insert_certificate_testing_only(certificate.clone());
2082        }
2083        // Check that the current committee members are authorized validators.
2084        for i in 0..certificates.clone().len() {
2085            let is_authorized = gateway.is_authorized_validator_address(certificates[i].author());
2086            if i < committee_size {
2087                assert!(is_authorized);
2088            } else {
2089                assert!(!is_authorized);
2090            }
2091        }
2092    }
2093}