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!(
137                "Peer '{listener_addr}' is being upgraded to Connected, but isn't Connecting \
138                - it is {}",
139                if self.is_connected() { "already Connected" } else { "only a Candidate" }
140            );
141        }
142
143        *self = Self::Connected(ConnectedPeer {
144            listener_addr,
145            connected_addr,
146            connection_mode,
147            aleo_addr: aleo_address,
148            node_type,
149            trusted: self.is_trusted(),
150            version: node_version,
151            snarkos_sha,
152            last_height_seen: None,
153            first_seen: timestamp,
154            last_seen: timestamp,
155        });
156    }
157
158    /// Demote a peer to candidate status, marking it as disconnected.
159    pub fn downgrade_to_candidate(&mut self, listener_addr: SocketAddr) {
160        *self = Self::Candidate(CandidatePeer {
161            listener_addr,
162            trusted: self.is_trusted(),
163            last_height_seen: self.last_height_seen(),
164            last_connection_attempt: None,
165            total_connection_attempts: 0,
166        });
167    }
168
169    /// Returns the type of the node (only applicable to connected peers).
170    pub fn node_type(&self) -> Option<NodeType> {
171        match self {
172            Self::Candidate(_) => None,
173            Self::Connecting(_) => None,
174            Self::Connected(peer) => Some(peer.node_type),
175        }
176    }
177
178    /// The listener (public) address of this peer.
179    pub fn listener_addr(&self) -> SocketAddr {
180        match self {
181            Self::Candidate(p) => p.listener_addr,
182            Self::Connecting(p) => p.listener_addr,
183            Self::Connected(p) => p.listener_addr,
184        }
185    }
186
187    /// The listener (public) address of this peer.
188    pub fn last_height_seen(&self) -> Option<u32> {
189        match self {
190            Self::Candidate(_) => None,
191            Self::Connecting(_) => None,
192            Self::Connected(peer) => peer.last_height_seen,
193        }
194    }
195
196    /// Returns `true` if the peer is not connected or connecting.
197    pub fn is_candidate(&self) -> bool {
198        matches!(self, Peer::Candidate(_))
199    }
200
201    /// Returns `true` if the peer is currently undergoing the network handshake.
202    pub fn is_connecting(&self) -> bool {
203        matches!(self, Peer::Connecting(_))
204    }
205
206    /// Returns `true` if the peer has concluded the network handshake.
207    pub fn is_connected(&self) -> bool {
208        matches!(self, Peer::Connected(_))
209    }
210
211    /// Returns `true` if the peer is considered trusted.
212    pub fn is_trusted(&self) -> bool {
213        match self {
214            Self::Candidate(peer) => peer.trusted,
215            Self::Connecting(peer) => peer.trusted,
216            Self::Connected(peer) => peer.trusted,
217        }
218    }
219
220    /// Updates the peer's `last_seen` timestamp.
221    pub fn update_last_seen(&mut self) {
222        if let Self::Connected(ConnectedPeer { last_seen, .. }) = self {
223            *last_seen = Instant::now();
224        }
225    }
226
227    /// Returns a reference to the underlying `ConnectedPeer` if it is connedcted,
228    /// otherwise `None`.
229    pub fn as_connected(&self) -> Option<&ConnectedPeer<N>> {
230        match self {
231            Self::Connected(peer) => Some(peer),
232            _ => None,
233        }
234    }
235}
236
237impl<N: Network> ConnectedPeer<N> {
238    /// Returns `true` if this peer is validator.
239    pub fn is_validator(&self) -> bool {
240        self.node_type == NodeType::Validator
241    }
242}