Skip to main content

snarkos_node_network/
peer.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
16use crate::NodeType;
17use snarkvm::prelude::{Address, Network};
18use tracing::*;
19
20use std::{fmt, net::SocketAddr, time::Instant};
21
22/// A peer of any connection status.
23#[derive(Clone, Debug)]
24pub enum Peer<N: Network> {
25    /// A candidate peer that's currently not connected to.
26    Candidate(CandidatePeer<N>),
27    /// A peer that's currently being connected to (the handshake is in progress).
28    Connecting(ConnectingPeer),
29    /// A fully connected (post-handshake) peer.
30    Connected(ConnectedPeer<N>),
31}
32
33/// A connecting peer.
34#[derive(Clone, Debug)]
35pub struct ConnectingPeer {
36    /// The listening address of a connecting peer.
37    pub listener_addr: SocketAddr,
38    /// Indicates whether the peer is considered trusted.
39    pub trusted: bool,
40}
41
42/// A candidate peer.
43#[derive(Clone, Debug)]
44pub struct CandidatePeer<N: Network> {
45    /// The listening address of a candidate peer.
46    pub listener_addr: SocketAddr,
47    /// Indicates whether the peer is considered trusted.
48    pub trusted: bool,
49    /// The latest block height known to be associated with the peer.
50    pub last_height_seen: Option<u32>,
51    /// The last time we attempted to connect to the peer.
52    /// `None` if there was no attempt to connect since the peer was last connected, or no attempt at all.
53    pub last_connection_attempt: Option<Instant>,
54    /// The total number of connection attempts, since the peer was last connected.
55    pub total_connection_attempts: u32,
56    /// The last known Aleo address of this peer, carried over from a prior connection.
57    /// Used to detect when a validator reconnects from a different IP address.
58    pub last_known_aleo_addr: Option<Address<N>>,
59}
60
61/// A fully connected peer.
62#[derive(Clone, Debug)]
63pub struct ConnectedPeer<N: Network> {
64    /// The listener address of the peer.
65    pub listener_addr: SocketAddr,
66    /// The connected address of the peer.
67    pub connected_addr: SocketAddr,
68    /// Indicates whether this is a Router or a Gateway connection for the peer.
69    pub connection_mode: ConnectionMode,
70    /// Indicates whether the peer is considered trusted.
71    pub trusted: bool,
72    /// The Aleo address of the peer.
73    pub aleo_addr: Address<N>,
74    /// The node type of the peer.
75    pub node_type: NodeType,
76    /// The message version of the peer.
77    pub version: u32,
78    /// The snarkOS commit hash of the peer.
79    pub snarkos_sha: Option<[u8; 40]>,
80    /// The latest block height known to be associated with the peer.
81    pub last_height_seen: Option<u32>,
82    /// The timestamp of the first message received from the peer.
83    pub first_seen: Instant,
84    /// The timestamp of the last message received from this peer.
85    pub last_seen: Instant,
86}
87
88/// Indicates whether a peer is connected via the Gateway or the Router.
89#[derive(Clone, Copy, Debug, PartialEq, Eq)]
90pub enum ConnectionMode {
91    Gateway,
92    Router,
93}
94
95impl fmt::Display for ConnectionMode {
96    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
97        match self {
98            ConnectionMode::Gateway => write!(f, "Gateway"),
99            ConnectionMode::Router => write!(f, "Router"),
100        }
101    }
102}
103
104impl<N: Network> Peer<N> {
105    /// Create a candidate peer.
106    pub fn new_candidate(listener_addr: SocketAddr, trusted: bool) -> Self {
107        Self::Candidate(CandidatePeer {
108            listener_addr,
109            trusted,
110            last_height_seen: None,
111            last_connection_attempt: None,
112            total_connection_attempts: 0,
113            last_known_aleo_addr: None,
114        })
115    }
116
117    /// Create a connecting peer.
118    pub const fn new_connecting(listener_addr: SocketAddr, trusted: bool) -> Self {
119        Self::Connecting(ConnectingPeer { listener_addr, trusted })
120    }
121
122    /// Promote a connecting peer to a fully connected one.
123    #[allow(clippy::too_many_arguments)]
124    pub fn upgrade_to_connected(
125        &mut self,
126        connected_addr: SocketAddr,
127        listener_port: u16,
128        aleo_address: Address<N>,
129        node_type: NodeType,
130        node_version: u32,
131        snarkos_sha: Option<[u8; 40]>,
132        connection_mode: ConnectionMode,
133    ) {
134        let timestamp = Instant::now();
135        let listener_addr = SocketAddr::from((connected_addr.ip(), listener_port));
136
137        // Logic check: this can only happen during the handshake. This isn't a fatal
138        // error, but should not be triggered.
139        if !matches!(self, Self::Connecting(_)) {
140            warn!(
141                "Peer '{listener_addr}' is being upgraded to Connected, but isn't Connecting \
142                - it is {}",
143                if self.is_connected() { "already Connected" } else { "only a Candidate" }
144            );
145        }
146
147        *self = Self::Connected(ConnectedPeer {
148            listener_addr,
149            connected_addr,
150            connection_mode,
151            aleo_addr: aleo_address,
152            node_type,
153            trusted: self.is_trusted(),
154            version: node_version,
155            snarkos_sha,
156            last_height_seen: None,
157            first_seen: timestamp,
158            last_seen: timestamp,
159        });
160    }
161
162    /// Demote a peer to candidate status, marking it as disconnected.
163    pub fn downgrade_to_candidate(&mut self, listener_addr: SocketAddr) {
164        let last_known_aleo_addr = match self {
165            Self::Connected(p) => Some(p.aleo_addr),
166            _ => None,
167        };
168
169        *self = Self::Candidate(CandidatePeer {
170            listener_addr,
171            trusted: self.is_trusted(),
172            last_height_seen: self.last_height_seen(),
173            last_connection_attempt: None,
174            total_connection_attempts: 0,
175            last_known_aleo_addr,
176        });
177    }
178
179    /// Returns the type of the node (only applicable to connected peers).
180    pub fn node_type(&self) -> Option<NodeType> {
181        match self {
182            Self::Candidate(_) => None,
183            Self::Connecting(_) => None,
184            Self::Connected(peer) => Some(peer.node_type),
185        }
186    }
187
188    /// The listener (public) address of this peer.
189    pub fn listener_addr(&self) -> SocketAddr {
190        match self {
191            Self::Candidate(p) => p.listener_addr,
192            Self::Connecting(p) => p.listener_addr,
193            Self::Connected(p) => p.listener_addr,
194        }
195    }
196
197    /// The listener (public) address of this peer.
198    pub fn last_height_seen(&self) -> Option<u32> {
199        match self {
200            Self::Candidate(_) => None,
201            Self::Connecting(_) => None,
202            Self::Connected(peer) => peer.last_height_seen,
203        }
204    }
205
206    /// Returns `true` if the peer is not connected or connecting.
207    pub fn is_candidate(&self) -> bool {
208        matches!(self, Peer::Candidate(_))
209    }
210
211    /// Returns `true` if the peer is currently undergoing the network handshake.
212    pub fn is_connecting(&self) -> bool {
213        matches!(self, Peer::Connecting(_))
214    }
215
216    /// Returns `true` if the peer has concluded the network handshake.
217    pub fn is_connected(&self) -> bool {
218        matches!(self, Peer::Connected(_))
219    }
220
221    /// Returns `true` if the peer is considered trusted.
222    pub fn is_trusted(&self) -> bool {
223        match self {
224            Self::Candidate(peer) => peer.trusted,
225            Self::Connecting(peer) => peer.trusted,
226            Self::Connected(peer) => peer.trusted,
227        }
228    }
229
230    /// Updates the peer's `last_seen` timestamp.
231    pub fn update_last_seen(&mut self) {
232        if let Self::Connected(ConnectedPeer { last_seen, .. }) = self {
233            *last_seen = Instant::now();
234        }
235    }
236
237    /// Returns a reference to the underlying `ConnectedPeer` if it is connedcted,
238    /// otherwise `None`.
239    pub fn as_connected(&self) -> Option<&ConnectedPeer<N>> {
240        match self {
241            Self::Connected(peer) => Some(peer),
242            _ => None,
243        }
244    }
245}
246
247impl<N: Network> ConnectedPeer<N> {
248    /// Returns `true` if this peer is validator.
249    pub fn is_validator(&self) -> bool {
250        self.node_type == NodeType::Validator
251    }
252}