|
15 | 15 |
|
16 | 16 | use crate::{CandidatePeer, ConnectedPeer, ConnectionMode, NodeType, Peer, Resolver}; |
17 | 17 |
|
| 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 | + |
18 | 181 | use snarkos_node_tcp::{ConnectError, P2P, is_bogon_ip, is_unspecified_or_broadcast_ip}; |
19 | 182 | use snarkvm::prelude::{Address, Network}; |
20 | 183 |
|
@@ -345,12 +508,21 @@ pub trait PeerPoolHandling<N: Network>: P2P { |
345 | 508 |
|
346 | 509 | /// Returns the number of connected peers. |
347 | 510 | 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() |
349 | 521 | } |
350 | 522 |
|
351 | 523 | /// Returns the number of connecting peers. |
352 | 524 | 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() |
354 | 526 | } |
355 | 527 |
|
356 | 528 | /// Returns the number of candidate peers. |
|
0 commit comments