Skip to content

Commit 5c99e8b

Browse files
authored
Merge pull request #4183 from ProvableHQ/fix/bft-connected-metric
[Fix] Only count validators for `SnarkOS BFT Connected Total` metric
2 parents 2965cd6 + b55302a commit 5c99e8b

File tree

4 files changed

+201
-3
lines changed

4 files changed

+201
-3
lines changed

.circleci/config.yml

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -504,6 +504,14 @@ jobs:
504504
workspace_member: node/consensus
505505
cache_key: node-consensus
506506

507+
node-network:
508+
executor: rust-docker
509+
resource_class: << pipeline.parameters.large >>
510+
steps:
511+
- run_serial:
512+
workspace_member: node/network
513+
cache_key: node-network
514+
507515
node-rest:
508516
executor: rust-docker
509517
resource_class: << pipeline.parameters.medium >>
@@ -647,6 +655,7 @@ jobs:
647655
name: Check snarkos-display crate
648656
no_output_timeout: 10m
649657
command: cargo check --package=snarkos-node-metrics --all-features
658+
650659
- clear_environment:
651660
cache_key: v4.2.0-rust-1.88.0-check-other-crates-cache
652661

node/bft/src/gateway.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -479,7 +479,8 @@ impl<N: Network> Gateway<N> {
479479
/// Updates the connection metrics for the gateway.
480480
#[cfg(feature = "metrics")]
481481
fn update_metrics(&self) {
482-
metrics::gauge(metrics::bft::CONNECTED, self.number_of_connected_peers() as f64);
482+
// Ignore the bootstrap clients for this metrics.
483+
metrics::gauge(metrics::bft::CONNECTED, self.number_of_connected_validators() as f64);
483484
metrics::gauge(metrics::bft::CONNECTING, self.number_of_connecting_peers() as f64);
484485
}
485486

node/network/src/peer.rs

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -210,4 +210,20 @@ impl<N: Network> Peer<N> {
210210
*last_seen = Instant::now();
211211
}
212212
}
213+
214+
/// Returns a reference to the underlying `ConnectedPeer` if it is connedcted,
215+
/// otherwise `None`.
216+
pub fn as_connected(&self) -> Option<&ConnectedPeer<N>> {
217+
match self {
218+
Self::Connected(peer) => Some(peer),
219+
_ => None,
220+
}
221+
}
222+
}
223+
224+
impl<N: Network> ConnectedPeer<N> {
225+
/// Returns `true` if this peer is validator.
226+
pub fn is_validator(&self) -> bool {
227+
self.node_type == NodeType::Validator
228+
}
213229
}

node/network/src/peering.rs

Lines changed: 174 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,169 @@
1515

1616
use crate::{CandidatePeer, ConnectedPeer, ConnectionMode, NodeType, Peer, Resolver};
1717

18+
#[cfg(test)]
19+
mod tests {
20+
use super::*;
21+
use crate::Peer;
22+
use snarkos_node_tcp::{Config, P2P, Tcp};
23+
use snarkvm::{prelude::Rng, utilities::TestRng};
24+
25+
use std::{collections::HashMap, net::SocketAddr, time::Instant};
26+
27+
type CurrentNetwork = snarkvm::prelude::MainnetV0;
28+
29+
struct MockPeerPool<N: Network> {
30+
tcp: Tcp,
31+
peer_pool: RwLock<HashMap<SocketAddr, Peer<N>>>,
32+
resolver: RwLock<Resolver<N>>,
33+
}
34+
35+
impl<N: Network> MockPeerPool<N> {
36+
fn new() -> Self {
37+
let config = Config { listener_ip: None, ..Default::default() };
38+
Self { tcp: Tcp::new(config), peer_pool: Default::default(), resolver: Default::default() }
39+
}
40+
}
41+
42+
impl<N: Network> P2P for MockPeerPool<N> {
43+
fn tcp(&self) -> &Tcp {
44+
&self.tcp
45+
}
46+
}
47+
48+
impl<N: Network> PeerPoolHandling<N> for MockPeerPool<N> {
49+
const MAXIMUM_POOL_SIZE: usize = 100;
50+
const OWNER: &str = "MockPeerPool";
51+
const PEER_SLASHING_COUNT: usize = 10;
52+
53+
fn peer_pool(&self) -> &RwLock<HashMap<SocketAddr, Peer<N>>> {
54+
&self.peer_pool
55+
}
56+
57+
fn resolver(&self) -> &RwLock<Resolver<N>> {
58+
&self.resolver
59+
}
60+
61+
fn is_dev(&self) -> bool {
62+
false
63+
}
64+
65+
fn trusted_peers_only(&self) -> bool {
66+
false
67+
}
68+
69+
fn node_type(&self) -> NodeType {
70+
NodeType::Client
71+
}
72+
}
73+
74+
fn make_connected_peer(port: u16, node_type: NodeType, rng: &mut TestRng) -> (SocketAddr, Peer<CurrentNetwork>) {
75+
use snarkvm::prelude::Address;
76+
let listener_addr = SocketAddr::from(([127, 0, 0, 1], port));
77+
let connected_addr = SocketAddr::from(([127, 0, 0, 1], port + 10000));
78+
let now = Instant::now();
79+
let peer = Peer::Connected(ConnectedPeer {
80+
listener_addr,
81+
connected_addr,
82+
connection_mode: ConnectionMode::Router,
83+
trusted: false,
84+
aleo_addr: Address::<CurrentNetwork>::new(rng.r#gen()),
85+
node_type,
86+
version: 1,
87+
snarkos_sha: None,
88+
last_height_seen: None,
89+
first_seen: now,
90+
last_seen: now,
91+
});
92+
(listener_addr, peer)
93+
}
94+
95+
#[test]
96+
fn test_peer_state_transitions() {
97+
use snarkvm::prelude::Address;
98+
99+
let pool = MockPeerPool::<CurrentNetwork>::new();
100+
let mut rng = TestRng::default();
101+
102+
let listener_addr = SocketAddr::from(([192, 0, 2, 1], 4000));
103+
let connected_addr = SocketAddr::from(([192, 0, 2, 1], 14000));
104+
let aleo_addr = Address::<CurrentNetwork>::new(rng.r#gen());
105+
106+
// Step 1: insert as a candidate.
107+
pool.peer_pool().write().insert(listener_addr, Peer::new_candidate(listener_addr, false));
108+
109+
assert_eq!(pool.number_of_candidate_peers(), 1);
110+
assert_eq!(pool.number_of_connecting_peers(), 0);
111+
assert_eq!(pool.number_of_connected_peers(), 0);
112+
assert!(!pool.is_connecting(listener_addr));
113+
assert!(!pool.is_connected(listener_addr));
114+
115+
// Step 2: promote to connecting.
116+
assert!(pool.add_connecting_peer(listener_addr).is_ok());
117+
118+
assert_eq!(pool.number_of_candidate_peers(), 0);
119+
assert_eq!(pool.number_of_connecting_peers(), 1);
120+
assert_eq!(pool.number_of_connected_peers(), 0);
121+
assert!(pool.is_connecting(listener_addr));
122+
assert!(!pool.is_connected(listener_addr));
123+
124+
// Step 3: complete the handshake — upgrade to connected.
125+
pool.peer_pool().write().get_mut(&listener_addr).unwrap().upgrade_to_connected(
126+
connected_addr,
127+
listener_addr.port(),
128+
aleo_addr,
129+
NodeType::Validator,
130+
1,
131+
None,
132+
ConnectionMode::Router,
133+
);
134+
135+
assert_eq!(pool.number_of_candidate_peers(), 0);
136+
assert_eq!(pool.number_of_connecting_peers(), 0);
137+
assert_eq!(pool.number_of_connected_peers(), 1);
138+
assert!(!pool.is_connecting(listener_addr));
139+
assert!(pool.is_connected(listener_addr));
140+
assert_eq!(pool.number_of_connected_validators(), 1);
141+
142+
// Verify the connected peer's fields.
143+
let connected = pool.get_connected_peer(listener_addr).expect("peer should be connected");
144+
assert_eq!(connected.listener_addr, listener_addr);
145+
assert_eq!(connected.connected_addr, connected_addr);
146+
assert_eq!(connected.aleo_addr, aleo_addr);
147+
assert_eq!(connected.node_type, NodeType::Validator);
148+
}
149+
150+
#[test]
151+
fn test_number_of_connected_validators() {
152+
let pool = MockPeerPool::<CurrentNetwork>::new();
153+
let mut rng = TestRng::default();
154+
155+
// Empty pool: no validators.
156+
assert_eq!(pool.number_of_connected_validators(), 0);
157+
158+
// Insert 2 validators and 1 client.
159+
let (addr1, peer1) = make_connected_peer(3000, NodeType::Validator, &mut rng);
160+
let (addr2, peer2) = make_connected_peer(3001, NodeType::Validator, &mut rng);
161+
let (addr3, peer3) = make_connected_peer(3002, NodeType::Client, &mut rng);
162+
{
163+
let mut pool_write = pool.peer_pool().write();
164+
pool_write.insert(addr1, peer1);
165+
pool_write.insert(addr2, peer2);
166+
pool_write.insert(addr3, peer3);
167+
}
168+
169+
assert_eq!(pool.number_of_connected_validators(), 2);
170+
assert_eq!(pool.number_of_connected_peers(), 3);
171+
172+
// A candidate peer should not be counted as a validator.
173+
let candidate_addr = SocketAddr::from(([127, 0, 0, 1], 3003));
174+
pool.peer_pool().write().insert(candidate_addr, Peer::new_candidate(candidate_addr, false));
175+
176+
assert_eq!(pool.number_of_connected_validators(), 2);
177+
assert_eq!(pool.number_of_connected_peers(), 3);
178+
}
179+
}
180+
18181
use snarkos_node_tcp::{ConnectError, P2P, is_bogon_ip, is_unspecified_or_broadcast_ip};
19182
use snarkvm::prelude::{Address, Network};
20183

@@ -345,12 +508,21 @@ pub trait PeerPoolHandling<N: Network>: P2P {
345508

346509
/// Returns the number of connected peers.
347510
fn number_of_connected_peers(&self) -> usize {
348-
self.peer_pool().read().iter().filter(|(_, peer)| peer.is_connected()).count()
511+
self.peer_pool().read().values().filter(|peer| peer.is_connected()).count()
512+
}
513+
514+
/// Returns the number of connected validators.
515+
fn number_of_connected_validators(&self) -> usize {
516+
self.peer_pool()
517+
.read()
518+
.values()
519+
.filter(|peer| peer.as_connected().is_some_and(|peer| peer.is_validator()))
520+
.count()
349521
}
350522

351523
/// Returns the number of connecting peers.
352524
fn number_of_connecting_peers(&self) -> usize {
353-
self.peer_pool().read().iter().filter(|(_, peer)| peer.is_connecting()).count()
525+
self.peer_pool().read().values().filter(|peer| peer.is_connecting()).count()
354526
}
355527

356528
/// Returns the number of candidate peers.

0 commit comments

Comments
 (0)