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