1#[cfg(feature = "telemetry")]
17use crate::helpers::Telemetry;
18use crate::{
19 CONTEXT,
20 MAX_BATCH_DELAY,
21 MEMORY_POOL_PORT,
22 Worker,
23 events::{DisconnectReason, EventCodec, PrimaryPing},
24 helpers::{Cache, PrimarySender, Storage, SyncSender, WorkerSender, assign_to_worker},
25 spawn_blocking,
26};
27use smol_str::SmolStr;
28use snarkos_account::Account;
29use snarkos_node_bft_events::{
30 BlockRequest,
31 BlockResponse,
32 CertificateRequest,
33 CertificateResponse,
34 ChallengeRequest,
35 ChallengeResponse,
36 DataBlocks,
37 Event,
38 EventTrait,
39 TransmissionRequest,
40 TransmissionResponse,
41 ValidatorsRequest,
42 ValidatorsResponse,
43};
44use snarkos_node_bft_ledger_service::LedgerService;
45use snarkos_node_network::{
46 ConnectionMode,
47 NodeType,
48 Peer,
49 PeerPoolHandling,
50 Resolver,
51 bootstrap_peers,
52 get_repo_commit_hash,
53 log_repo_sha_comparison,
54 shorten_snarkos_sha,
55};
56use snarkos_node_sync::{MAX_BLOCKS_BEHIND, communication_service::CommunicationService};
57use snarkos_node_tcp::{
58 Config,
59 ConnectError,
60 Connection,
61 ConnectionSide,
62 P2P,
63 Tcp,
64 connections::DisconnectOrigin,
65 protocols::{Disconnect, Handshake, OnConnect, Reading, Writing},
66};
67use snarkos_utilities::NodeDataDir;
68use snarkvm::{
69 console::prelude::*,
70 ledger::{
71 committee::Committee,
72 narwhal::{BatchHeader, Data},
73 },
74 prelude::{Address, Field},
75 utilities::flatten_error,
76};
77
78use colored::Colorize;
79use futures::{SinkExt, future::join_all};
80use indexmap::IndexMap;
81#[cfg(feature = "locktick")]
82use locktick::parking_lot::{Mutex, RwLock};
83#[cfg(not(feature = "locktick"))]
84use parking_lot::{Mutex, RwLock};
85use rand::seq::{IteratorRandom, SliceRandom};
86use std::{
87 collections::{HashMap, HashSet},
88 future::Future,
89 io,
90 net::{Ipv4Addr, SocketAddr, SocketAddrV4},
91 sync::Arc,
92 time::Duration,
93};
94use tokio::{
95 net::TcpStream,
96 sync::{OnceCell, oneshot},
97 task::{self, JoinHandle},
98};
99use tokio_stream::StreamExt;
100use tokio_util::codec::Framed;
101
102const CACHE_EVENTS_INTERVAL: i64 = (MAX_BATCH_DELAY.as_secs()) as i64; const CACHE_REQUESTS_INTERVAL: i64 = (MAX_BATCH_DELAY.as_secs()) as i64; #[cfg(not(test))]
109const MAX_CONNECTION_ATTEMPTS: usize = 10;
110
111pub const MAX_VALIDATORS_TO_SEND: usize = 200;
113
114#[cfg(not(test))]
116const CONNECTION_ATTEMPTS_SINCE_SECS: i64 = 10;
117
118const IP_BAN_TIME_IN_SECS: u64 = 300;
120
121#[async_trait]
124pub trait Transport<N: Network>: Send + Sync {
125 async fn send(&self, peer_ip: SocketAddr, event: Event<N>) -> Option<oneshot::Receiver<io::Result<()>>>;
126 fn broadcast(&self, event: Event<N>);
127}
128
129#[derive(Clone)]
132pub struct Gateway<N: Network>(Arc<InnerGateway<N>>);
133
134impl<N: Network> Deref for Gateway<N> {
135 type Target = Arc<InnerGateway<N>>;
136
137 fn deref(&self) -> &Self::Target {
138 &self.0
139 }
140}
141
142pub struct InnerGateway<N: Network> {
143 account: Account<N>,
145 storage: Storage<N>,
147 ledger: Arc<dyn LedgerService<N>>,
149 tcp: Tcp,
151 cache: Cache<N>,
153 resolver: RwLock<Resolver<N>>,
155 peer_pool: RwLock<HashMap<SocketAddr, Peer<N>>>,
157 #[cfg(feature = "telemetry")]
158 validator_telemetry: Telemetry<N>,
159 primary_sender: OnceCell<PrimarySender<N>>,
161 worker_senders: OnceCell<IndexMap<u8, WorkerSender<N>>>,
163 sync_sender: OnceCell<SyncSender<N>>,
165 handles: Mutex<Vec<JoinHandle<()>>>,
167 node_data_dir: NodeDataDir,
169 trusted_peers_only: bool,
171 dev: Option<u16>,
173}
174
175impl<N: Network> PeerPoolHandling<N> for Gateway<N> {
176 const MAXIMUM_POOL_SIZE: usize = 200;
177 const OWNER: &str = CONTEXT;
178 const PEER_SLASHING_COUNT: usize = 20;
179
180 fn peer_pool(&self) -> &RwLock<HashMap<SocketAddr, Peer<N>>> {
181 &self.peer_pool
182 }
183
184 fn resolver(&self) -> &RwLock<Resolver<N>> {
185 &self.resolver
186 }
187
188 fn is_dev(&self) -> bool {
189 self.dev.is_some()
190 }
191
192 fn trusted_peers_only(&self) -> bool {
193 self.trusted_peers_only
194 }
195
196 fn node_type(&self) -> NodeType {
197 NodeType::Validator
198 }
199}
200
201impl<N: Network> Gateway<N> {
202 #[allow(clippy::too_many_arguments)]
204 pub fn new(
205 account: Account<N>,
206 storage: Storage<N>,
207 ledger: Arc<dyn LedgerService<N>>,
208 ip: Option<SocketAddr>,
209 trusted_validators: &[SocketAddr],
210 trusted_peers_only: bool,
211 node_data_dir: NodeDataDir,
212 dev: Option<u16>,
213 ) -> Result<Self> {
214 let ip = match (ip, dev) {
216 (None, Some(dev)) => SocketAddr::V4(SocketAddrV4::new(Ipv4Addr::LOCALHOST, MEMORY_POOL_PORT + dev)),
217 (None, None) => SocketAddr::V4(SocketAddrV4::new(Ipv4Addr::UNSPECIFIED, MEMORY_POOL_PORT)),
218 (Some(ip), _) => ip,
219 };
220 let tcp = Tcp::new(Config::new(ip, Committee::<N>::max_committee_size() * 10));
228
229 let mut initial_peers = HashMap::new();
231
232 if !trusted_peers_only {
234 let cached_peers = Self::load_cached_peers(&node_data_dir.gateway_peer_cache_path())?;
235 for addr in cached_peers {
236 initial_peers.insert(addr, Peer::new_candidate(addr, false));
237 }
238 }
239
240 initial_peers.extend(trusted_validators.iter().copied().map(|addr| (addr, Peer::new_candidate(addr, true))));
243
244 Ok(Self(Arc::new(InnerGateway {
246 account,
247 storage,
248 ledger,
249 tcp,
250 cache: Default::default(),
251 resolver: Default::default(),
252 peer_pool: RwLock::new(initial_peers),
253 #[cfg(feature = "telemetry")]
254 validator_telemetry: Default::default(),
255 primary_sender: Default::default(),
256 worker_senders: Default::default(),
257 sync_sender: Default::default(),
258 handles: Default::default(),
259 node_data_dir,
260 trusted_peers_only,
261 dev,
262 })))
263 }
264
265 pub async fn run(
267 &self,
268 primary_sender: PrimarySender<N>,
269 worker_senders: IndexMap<u8, WorkerSender<N>>,
270 sync_sender: Option<SyncSender<N>>,
271 ) {
272 debug!("Starting the gateway for the memory pool...");
273
274 self.primary_sender.set(primary_sender).expect("Primary sender already set in gateway");
276
277 self.worker_senders.set(worker_senders).expect("The worker senders are already set");
279
280 if let Some(sync_sender) = sync_sender {
282 self.sync_sender.set(sync_sender).expect("Sync sender already set in gateway");
283 }
284
285 self.enable_handshake().await;
287 self.enable_reading().await;
288 self.enable_writing().await;
289 self.enable_disconnect().await;
290 self.enable_on_connect().await;
291
292 #[cfg(feature = "metrics")]
294 {
295 let gateway = self.clone();
296 self.spawn(async move {
297 loop {
298 tokio::time::sleep(Duration::from_secs(1)).await;
299 gateway.update_metrics();
300 }
301 });
302 }
303
304 let listen_addr = self.tcp.enable_listener().await.expect("Failed to enable the TCP listener");
306 debug!("Listening for validator connections at address {listen_addr:?}");
307
308 self.initialize_heartbeat();
310
311 info!("Started the gateway for the memory pool at '{}'", self.local_ip());
312 }
313}
314
315impl<N: Network> Gateway<N> {
317 fn max_committee_size(&self) -> usize {
319 self.ledger
320 .current_committee()
321 .map_or_else(|_e| Committee::<N>::max_committee_size() as usize, |committee| committee.num_members())
322 }
323
324 fn max_cache_events(&self) -> usize {
326 self.max_cache_transmissions()
327 }
328
329 fn max_cache_certificates(&self) -> usize {
331 2 * BatchHeader::<N>::MAX_GC_ROUNDS * self.max_committee_size()
332 }
333
334 fn max_cache_transmissions(&self) -> usize {
336 self.max_cache_certificates() * BatchHeader::<N>::MAX_TRANSMISSIONS_PER_BATCH
337 }
338
339 fn max_cache_duplicates(&self) -> usize {
341 self.max_committee_size().pow(2)
342 }
343}
344
345#[async_trait]
346impl<N: Network> CommunicationService for Gateway<N> {
347 type Message = Event<N>;
349
350 fn prepare_block_request(start_height: u32, end_height: u32) -> Self::Message {
352 debug_assert!(start_height < end_height, "Invalid block request format");
353 Event::BlockRequest(BlockRequest { start_height, end_height })
354 }
355
356 async fn send(&self, peer_ip: SocketAddr, message: Self::Message) -> Option<oneshot::Receiver<io::Result<()>>> {
362 Transport::send(self, peer_ip, message).await
363 }
364}
365
366impl<N: Network> Gateway<N> {
367 pub fn account(&self) -> &Account<N> {
369 &self.account
370 }
371
372 pub fn dev(&self) -> Option<u16> {
374 self.dev
375 }
376
377 pub fn ledger(&self) -> &Arc<dyn LedgerService<N>> {
379 &self.ledger
380 }
381
382 pub fn resolver(&self) -> &RwLock<Resolver<N>> {
384 &self.resolver
385 }
386
387 pub fn resolve_to_listener(&self, connected_addr: &SocketAddr) -> Option<SocketAddr> {
389 self.resolver.read().get_listener(*connected_addr)
390 }
391
392 #[cfg(feature = "telemetry")]
394 pub fn validator_telemetry(&self) -> &Telemetry<N> {
395 &self.validator_telemetry
396 }
397
398 pub fn primary_sender(&self) -> &PrimarySender<N> {
400 self.primary_sender.get().expect("Primary sender not set in gateway")
401 }
402
403 pub fn num_workers(&self) -> u8 {
405 u8::try_from(self.worker_senders.get().expect("Missing worker senders in gateway").len())
406 .expect("Too many workers")
407 }
408
409 pub fn get_worker_sender(&self, worker_id: u8) -> Option<&WorkerSender<N>> {
411 self.worker_senders.get().and_then(|senders| senders.get(&worker_id))
412 }
413
414 pub fn is_authorized_validator_ip(&self, ip: SocketAddr) -> bool {
416 if self.trusted_peers().contains(&ip) {
418 return true;
419 }
420 match self.resolve_to_aleo_addr(ip) {
422 Some(address) => self.is_authorized_validator_address(address),
424 None => {
425 warn!("{CONTEXT} Could not resolve the Aleo address for '{ip}'");
426 false
427 }
428 }
429 }
430
431 pub fn is_authorized_validator_address(&self, validator_address: Address<N>) -> bool {
433 if self
442 .ledger
443 .get_committee_lookback_for_round(self.storage.current_round())
444 .is_ok_and(|committee| committee.is_committee_member(validator_address))
445 {
446 return true;
447 }
448
449 if self.ledger.current_committee().is_ok_and(|committee| committee.is_committee_member(validator_address)) {
451 return true;
452 }
453
454 let previous_block_height = self.ledger.latest_block_height().saturating_sub(MAX_BLOCKS_BEHIND);
456 match self.ledger.get_block_round(previous_block_height) {
458 Ok(block_round) => (block_round..self.storage.current_round()).step_by(2).any(|round| {
459 self.ledger
460 .get_committee_lookback_for_round(round)
461 .is_ok_and(|committee| committee.is_committee_member(validator_address))
462 }),
463 Err(_) => false,
464 }
465 }
466
467 pub fn connected_addresses(&self) -> HashSet<Address<N>> {
469 self.get_connected_peers().into_iter().map(|peer| peer.aleo_addr).collect()
470 }
471
472 fn ensure_peer_is_allowed(&self, listener_addr: SocketAddr) -> Result<(), DisconnectReason> {
474 if self.is_local_ip(listener_addr) {
476 return Err(DisconnectReason::SelfConnect);
477 }
478
479 Ok(())
480 }
481
482 #[cfg(feature = "metrics")]
484 fn update_metrics(&self) {
485 if let Some(count) = self.number_of_connected_validators() {
486 metrics::gauge(metrics::bft::CONNECTED, count as f64);
487 }
488 if let Some(count) = self.number_of_connecting_peers() {
489 metrics::gauge(metrics::bft::CONNECTING, count as f64);
490 }
491 }
492
493 #[cfg(test)]
495 pub fn insert_connected_peer(&self, peer_ip: SocketAddr, peer_addr: SocketAddr, address: Address<N>) {
496 self.resolver.write().insert_peer(peer_ip, peer_addr, Some(address));
498 self.peer_pool.write().insert(peer_ip, Peer::new_connecting(peer_ip, false));
500 if let Some(peer) = self.peer_pool.write().get_mut(&peer_ip) {
501 peer.upgrade_to_connected(
502 peer_addr,
503 peer_ip.port(),
504 address,
505 NodeType::Validator,
506 0,
507 get_repo_commit_hash(),
508 ConnectionMode::Gateway,
509 );
510 }
511 }
512
513 fn send_inner(&self, peer_ip: SocketAddr, event: Event<N>) -> Option<oneshot::Receiver<io::Result<()>>> {
519 let Some(peer_addr) = self.resolve_to_ambiguous(peer_ip) else {
521 warn!("Unable to resolve the listener IP address '{peer_ip}'");
522 return None;
523 };
524 let name = event.name();
526 trace!("{CONTEXT} Sending '{name}' to '{peer_ip}'");
528 let result = self.unicast(peer_addr, event);
529 if let Err(err) = &result {
531 warn!("{CONTEXT} Failed to send '{name}' to '{peer_ip}': {err:?}");
532 debug!("{CONTEXT} Disconnecting from '{peer_ip}' (unable to send)");
533 self.disconnect(peer_ip);
534 }
535 result.ok()
536 }
537
538 async fn inbound(&self, peer_addr: SocketAddr, event: Event<N>) -> Result<bool> {
542 let Some(peer_ip) = self.resolver.read().get_listener(peer_addr) else {
544 trace!("Dropping a {} from {peer_addr} - no longer connected.", event.name());
546 return Ok(false);
547 };
548 if !(self.is_authorized_validator_ip(peer_ip)
550 || self
551 .get_connected_peer(peer_ip)
552 .map(|peer| peer.node_type == NodeType::BootstrapClient)
553 .unwrap_or(false))
554 {
555 bail!("{CONTEXT} Dropping '{}' from '{peer_ip}' (not authorized)", event.name())
556 }
557 let num_events = self.cache.insert_inbound_event(peer_ip, CACHE_EVENTS_INTERVAL);
559 if num_events >= self.max_cache_events() {
560 bail!("Dropping '{peer_ip}' for spamming events (num_events = {num_events})")
561 }
562 match event {
564 Event::CertificateRequest(_) | Event::CertificateResponse(_) => {
565 let certificate_id = match &event {
567 Event::CertificateRequest(CertificateRequest { certificate_id }) => *certificate_id,
568 Event::CertificateResponse(CertificateResponse { certificate }) => certificate.id(),
569 _ => unreachable!(),
570 };
571 let num_events = self.cache.insert_inbound_certificate(certificate_id, CACHE_REQUESTS_INTERVAL);
573 if num_events >= self.max_cache_duplicates() {
574 return Ok(true);
575 }
576 }
577 Event::TransmissionRequest(TransmissionRequest { transmission_id })
578 | Event::TransmissionResponse(TransmissionResponse { transmission_id, .. }) => {
579 let num_events = self.cache.insert_inbound_transmission(transmission_id, CACHE_REQUESTS_INTERVAL);
581 if num_events >= self.max_cache_duplicates() {
582 return Ok(true);
583 }
584 }
585 Event::BlockRequest(_) => {
586 let num_events = self.cache.insert_inbound_block_request(peer_ip, CACHE_REQUESTS_INTERVAL);
587 if num_events >= self.max_cache_duplicates() {
588 return Ok(true);
589 }
590 }
591 _ => {}
592 }
593 trace!("{CONTEXT} Received '{}' from '{peer_ip}'", event.name());
594
595 match event {
598 Event::BatchPropose(batch_propose) => {
599 let _ = self.primary_sender().tx_batch_propose.send((peer_ip, batch_propose)).await;
601 Ok(true)
602 }
603 Event::BatchSignature(batch_signature) => {
604 let _ = self.primary_sender().tx_batch_signature.send((peer_ip, batch_signature)).await;
606 Ok(true)
607 }
608 Event::BatchCertified(batch_certified) => {
609 let _ = self.primary_sender().tx_batch_certified.send((peer_ip, batch_certified.certificate)).await;
611 Ok(true)
612 }
613 Event::BlockRequest(block_request) => {
614 let BlockRequest { start_height, end_height } = block_request;
615
616 if start_height >= end_height {
618 bail!("Block request from '{peer_ip}' has an invalid range ({start_height}..{end_height})")
619 }
620 if end_height - start_height > DataBlocks::<N>::MAXIMUM_NUMBER_OF_BLOCKS as u32 {
622 bail!("Block request from '{peer_ip}' has an excessive range ({start_height}..{end_height})")
623 }
624
625 let latest_consensus_version = N::CONSENSUS_VERSION(end_height - 1)?;
627
628 let self_ = self.clone();
629 let blocks = match task::spawn_blocking(move || {
630 match self_.ledger.get_blocks(start_height..end_height) {
632 Ok(blocks) => Ok(DataBlocks(blocks)),
633 Err(error) => bail!("Missing blocks {start_height} to {end_height} from ledger - {error}"),
634 }
635 })
636 .await
637 {
638 Ok(Ok(blocks)) => blocks,
639 Ok(Err(error)) => return Err(error),
640 Err(error) => return Err(anyhow!("[BlockRequest] {error}")),
641 };
642
643 let self_ = self.clone();
644 tokio::spawn(async move {
645 let event =
647 Event::BlockResponse(BlockResponse::new(block_request, blocks, latest_consensus_version));
648 Transport::send(&self_, peer_ip, event).await;
649 });
650 Ok(true)
651 }
652 Event::BlockResponse(BlockResponse { request, latest_consensus_version, blocks, .. }) => {
653 if let Some(sync_sender) = self.sync_sender.get() {
655 if !self.cache.remove_outbound_block_request(peer_ip, &request) {
657 bail!("Unsolicited block response from '{peer_ip}'")
658 }
659
660 let (send, recv) = tokio::sync::oneshot::channel();
664 rayon::spawn_fifo(move || {
665 let blocks = blocks.deserialize_blocking().map_err(|error| anyhow!("[BlockResponse] {error}"));
666 let _ = send.send(blocks);
667 });
668 let blocks = match recv.await {
669 Ok(Ok(blocks)) => blocks,
670 Ok(Err(error)) => bail!("Peer '{peer_ip}' sent an invalid block response - {error}"),
671 Err(error) => bail!("Peer '{peer_ip}' sent an invalid block response - {error}"),
672 };
673
674 blocks.ensure_response_is_well_formed(peer_ip, request.start_height, request.end_height)?;
676 match sync_sender.insert_block_response(peer_ip, blocks.0, latest_consensus_version).await {
678 Ok(_) => Ok(true),
679 Err(err) if err.is_benign() => {
680 let err: anyhow::Error = err.into();
681 let err = err.context(format!("Ignoring block response from peer '{peer_ip}'"));
682 debug!("{}", flatten_error(err));
683 Ok(true)
684 }
685 Err(err) if err.is_consensus_version_ahead() => {
686 let err: anyhow::Error = err.into();
687 let err = err.context(format!(
688 "Peer sent a block response with a newer consensus version '{peer_ip}'"
689 ));
690 warn!("{}", flatten_error(&err));
691 Ok(true)
692 }
693 Err(err) if err.is_consensus_version_behind() => {
694 let err: anyhow::Error = err.into();
695 let err = err.context(format!("Peer sent an invalid block response '{peer_ip}'"));
696
697 let msg = flatten_error(&err);
698 error!("{msg}");
699 self.ip_ban_peer(peer_ip, Some(&msg));
700 Err(err)
701 }
702 Err(err) => {
703 let err: anyhow::Error = err.into();
704 let err = err.context(format!("Peer '{peer_ip}' sent an invalid block response"));
705 warn!("{}", flatten_error(err));
706
707 Ok(true)
709 }
710 }
711 } else {
712 debug!("Ignoring block response from '{peer_ip}' - no sync sender");
713 Ok(true)
714 }
715 }
716 Event::CertificateRequest(certificate_request) => {
717 if let Some(sync_sender) = self.sync_sender.get() {
720 let _ = sync_sender.tx_certificate_request.send((peer_ip, certificate_request)).await;
722 }
723 Ok(true)
724 }
725 Event::CertificateResponse(certificate_response) => {
726 if let Some(sync_sender) = self.sync_sender.get() {
729 let _ = sync_sender.tx_certificate_response.send((peer_ip, certificate_response)).await;
731 }
732 Ok(true)
733 }
734 Event::ChallengeRequest(..) | Event::ChallengeResponse(..) => {
735 bail!("{CONTEXT} Peer '{peer_ip}' is not following the protocol")
737 }
738 Event::Disconnect(message) => {
739 debug!("Peer '{peer_ip}' decided to disconnect due to '{}'", message.reason);
741 self.disconnect(peer_ip);
742 Ok(false)
743 }
744 Event::PrimaryPing(ping) => {
745 let PrimaryPing { version, block_locators, primary_certificate } = ping;
746
747 if version < Event::<N>::VERSION {
749 bail!("Dropping '{peer_ip}' on event version {version} (outdated)");
750 }
751
752 debug!("Validator '{peer_ip}' is at height {}", block_locators.latest_locator_height());
754
755 if let Some(sync_sender) = self.sync_sender.get() {
757 if let Err(error) = sync_sender.update_peer_locators(peer_ip, block_locators).await {
759 bail!("Validator '{peer_ip}' sent invalid block locators - {error}");
760 }
761 }
762
763 let _ = self.primary_sender().tx_primary_ping.send((peer_ip, primary_certificate)).await;
765 Ok(true)
766 }
767 Event::TransmissionRequest(request) => {
768 let Ok(worker_id) = assign_to_worker(request.transmission_id, self.num_workers()) else {
771 warn!("{CONTEXT} Unable to assign transmission ID '{}' to a worker", request.transmission_id);
772 return Ok(true);
773 };
774 if let Some(sender) = self.get_worker_sender(worker_id) {
776 let _ = sender.tx_transmission_request.send((peer_ip, request)).await;
778 }
779 Ok(true)
780 }
781 Event::TransmissionResponse(response) => {
782 let Ok(worker_id) = assign_to_worker(response.transmission_id, self.num_workers()) else {
784 warn!("{CONTEXT} Unable to assign transmission ID '{}' to a worker", response.transmission_id);
785 return Ok(true);
786 };
787 if let Some(sender) = self.get_worker_sender(worker_id) {
789 let _ = sender.tx_transmission_response.send((peer_ip, response)).await;
791 }
792 Ok(true)
793 }
794 Event::ValidatorsRequest(_) => {
795 let mut connected_peers = self.get_best_connected_peers(Some(MAX_VALIDATORS_TO_SEND));
796 connected_peers.shuffle(&mut rand::rng());
797
798 let self_ = self.clone();
799 tokio::spawn(async move {
800 let mut validators = IndexMap::with_capacity(MAX_VALIDATORS_TO_SEND);
802 for validator in connected_peers.into_iter() {
804 validators.insert(validator.listener_addr, validator.aleo_addr);
806 }
807 let event = Event::ValidatorsResponse(ValidatorsResponse { validators });
809 Transport::send(&self_, peer_ip, event).await;
810 });
811 Ok(true)
812 }
813 Event::ValidatorsResponse(response) => {
814 if self.trusted_peers_only {
815 bail!("{CONTEXT} Not accepting validators response from '{peer_ip}' (trusted peers only)");
816 }
817 let ValidatorsResponse { validators } = response;
818 ensure!(validators.len() <= MAX_VALIDATORS_TO_SEND, "{CONTEXT} Received too many validators");
820 if !self.cache.contains_outbound_validators_request(peer_ip) {
822 bail!("{CONTEXT} Received validators response from '{peer_ip}' without a validators request")
823 }
824 self.cache.decrement_outbound_validators_requests(peer_ip);
826
827 let valid_addrs = validators
830 .into_iter()
831 .filter_map(|(listener_addr, aleo_addr)| {
832 (self.account.address() != aleo_addr
833 && !self.is_connected_address(aleo_addr)
834 && self.is_authorized_validator_address(aleo_addr))
835 .then_some((listener_addr, None))
836 })
837 .collect::<Vec<_>>();
838 if !valid_addrs.is_empty() {
839 self.insert_candidate_peers(valid_addrs);
840 }
841
842 Ok(true)
843 }
844 Event::WorkerPing(ping) => {
845 ensure!(
847 ping.transmission_ids.len() <= Worker::<N>::MAX_TRANSMISSIONS_PER_WORKER_PING,
848 "{CONTEXT} Received too many transmissions"
849 );
850 let num_workers = self.num_workers();
852 for transmission_id in ping.transmission_ids.into_iter() {
854 let Ok(worker_id) = assign_to_worker(transmission_id, num_workers) else {
856 warn!("{CONTEXT} Unable to assign transmission ID '{transmission_id}' to a worker");
857 continue;
858 };
859 if let Some(sender) = self.get_worker_sender(worker_id) {
861 let _ = sender.tx_worker_ping.send((peer_ip, transmission_id)).await;
863 }
864 }
865 Ok(true)
866 }
867 }
868 }
869
870 fn initialize_heartbeat(&self) {
872 let self_clone = self.clone();
873 self.spawn(async move {
874 tokio::time::sleep(Duration::from_millis(1000)).await;
876 info!("Starting the heartbeat of the gateway...");
877 loop {
878 self_clone.heartbeat().await;
880 tokio::time::sleep(Duration::from_secs(15)).await;
882 }
883 });
884 }
885
886 #[allow(dead_code)]
888 fn spawn<T: Future<Output = ()> + Send + 'static>(&self, future: T) {
889 self.handles.lock().push(tokio::spawn(future));
890 }
891
892 pub async fn shut_down(&self) {
894 info!("Shutting down the gateway...");
895 if let Err(e) = self.save_best_peers(&self.node_data_dir.gateway_peer_cache_path(), None, true) {
897 warn!("Failed to persist best validators to disk: {e}");
898 }
899 self.handles.lock().iter().for_each(|handle| handle.abort());
901 self.tcp.shut_down().await;
903 }
904}
905
906impl<N: Network> Gateway<N> {
907 const MINIMUM_TIME_BETWEEN_CONNECTION_ATTEMPTS: Duration = Duration::from_secs(10);
909 const MISSING_VALIDATOR_CONNECTIONS_GRACE_PERIOD: Duration = Duration::from_secs(60);
911
912 async fn heartbeat(&self) {
914 self.log_connected_validators();
916 #[cfg(feature = "telemetry")]
918 self.log_participation_scores();
919 self.handle_trusted_validators();
921 self.handle_bootstrap_peers().await;
923 self.handle_unauthorized_validators();
925 self.handle_min_connected_validators().await;
927 self.handle_banned_ips();
929 }
930
931 fn log_connected_validators(&self) {
933 let connected_validators = self.filter_connected_peers(|peer| peer.node_type == NodeType::Validator);
936
937 let committee = match self.ledger.current_committee() {
938 Ok(c) => c,
939 Err(err) => {
940 error!("Failed to get current committee: {err}");
941 return;
942 }
943 };
944
945 let validators_total = committee.num_members().saturating_sub(1);
947 let total_validators = format!("(of {validators_total} bonded validators)").dimmed();
949 let connections_msg = match connected_validators.len() {
951 0 => "No connected validators".to_string(),
952 num_connected => format!("Connected to {num_connected} validators {total_validators}"),
953 };
954 info!("{connections_msg}");
955
956 let mut connected_validator_addresses = HashSet::with_capacity(connected_validators.len());
958 let mut connected_validator_shas: HashMap<SmolStr, u64> = HashMap::with_capacity(connected_validators.len());
959 let our_sha = shorten_snarkos_sha(&get_repo_commit_hash());
961 let our_stake = committee.get_stake(self.account.address());
962 connected_validator_shas.insert(our_sha.clone(), our_stake);
963 connected_validator_addresses.insert(self.account.address());
965 for peer in &connected_validators {
967 let address = peer.aleo_addr;
969 connected_validator_addresses.insert(address);
970 let address_stake = committee.get_stake(address);
972 let short_peer_sha = shorten_snarkos_sha(&peer.snarkos_sha);
973 *connected_validator_shas.entry(short_peer_sha.clone()).or_default() += address_stake;
974
975 debug!(
976 "{}",
977 format!(
978 " Connected to: {} - {} (connection age {:?})",
979 peer.listener_addr,
980 peer.aleo_addr,
981 peer.first_seen.elapsed()
982 )
983 .dimmed()
984 );
985 }
986
987 if let Some(combined_stake) = connected_validator_shas.get(&our_sha) {
989 let percentage = *combined_stake as f64 / committee.total_stake() as f64 * 100.0;
990 debug!("{}", format!(" Combined stake @ {our_sha}: {percentage:.2}%").dimmed());
991 #[cfg(feature = "metrics")]
992 metrics::gauge(metrics::bft::CONNECTED_STAKE_WITH_MATCHING_SHA, percentage);
993 }
994
995 let num_not_connected = validators_total.saturating_sub(connected_validators.len());
997 if num_not_connected > 0 && self.tcp().uptime() > Self::MISSING_VALIDATOR_CONNECTIONS_GRACE_PERIOD {
998 let total_stake = committee.total_stake();
1000 let total_stake_f64 = total_stake as f64;
1001
1002 let committee_members: HashSet<_> =
1004 self.ledger.current_committee().map(|c| c.members().keys().copied().collect()).unwrap_or_default();
1005
1006 let not_connected_stake: u64 = committee_members
1007 .difference(&connected_validator_addresses)
1008 .map(|address| {
1009 let address_stake = committee.get_stake(*address);
1010 let address_stake_as_percentage =
1011 if total_stake == 0 { 0.0 } else { address_stake as f64 / total_stake_f64 * 100.0 };
1012 debug!(
1013 "{}",
1014 format!(" Not connected to {address} ({address_stake_as_percentage:.2}% of total stake)")
1015 .dimmed()
1016 );
1017 address_stake
1018 })
1019 .sum();
1020
1021 let not_connected_stake_as_percentage =
1022 if total_stake == 0 { 0.0 } else { not_connected_stake as f64 / total_stake_f64 * 100.0 };
1023 warn!(
1024 "Not connected to {num_not_connected} validators {total_validators} ({not_connected_stake_as_percentage:.2}% of total stake not connected)"
1025 );
1026 #[cfg(feature = "metrics")]
1027 {
1028 let connected_stake_as_percentage = 100.0 - not_connected_stake_as_percentage;
1029 metrics::gauge(metrics::bft::CONNECTED_STAKE, connected_stake_as_percentage);
1030 }
1031 } else {
1032 #[cfg(feature = "metrics")]
1033 metrics::gauge(metrics::bft::CONNECTED_STAKE, 100.0);
1034 };
1035
1036 if !committee.is_quorum_threshold_reached(&connected_validator_addresses) {
1037 if self.tcp().uptime() > Self::MISSING_VALIDATOR_CONNECTIONS_GRACE_PERIOD {
1039 error!("Not connected to a quorum of validators");
1040 } else {
1041 debug!("Not connected to a quorum of validators");
1042 }
1043 }
1044 }
1045
1046 #[cfg(feature = "telemetry")]
1048 fn log_participation_scores(&self) {
1049 if let Ok(committee_lookback) = self.ledger.get_committee_lookback_for_round(self.storage.current_round()) {
1050 let participation_scores = self.validator_telemetry().get_participation_scores(&committee_lookback);
1052
1053 debug!("Participation Scores (in the last {} rounds):", self.storage.max_gc_rounds());
1055 for (address, (cert_score, sig_score)) in participation_scores {
1056 debug!(
1057 "{}",
1058 format!(" {address} - certificates: {cert_score:.2}% signatures: {sig_score:.2}%").dimmed()
1059 );
1060 }
1061 }
1062 }
1063
1064 fn handle_trusted_validators(&self) {
1066 let trusted_peers = self.trusted_peers();
1067
1068 let handles: Vec<JoinHandle<_>> = trusted_peers
1070 .iter()
1071 .filter_map(|validator_ip| {
1072 match self.connect(*validator_ip) {
1074 Ok(hdl) => Some(hdl),
1075 Err(ConnectError::SelfConnect { .. })
1076 | Err(ConnectError::AlreadyConnected { .. })
1077 | Err(ConnectError::AlreadyConnecting { .. }) => None,
1078 Err(err) => {
1079 warn!("Could not initiate connection to trusted validator at '{validator_ip}' - {err}");
1080 None
1081 }
1082 }
1083 })
1084 .collect();
1085
1086 if !handles.is_empty() {
1087 info!("Reconnecting to {} out of {} trusted validators", handles.len(), trusted_peers.len());
1088 }
1089 }
1090
1091 async fn handle_bootstrap_peers(&self) {
1093 if self.trusted_peers_only {
1095 return;
1096 }
1097 let mut candidate_bootstrap = Vec::new();
1099 let connected_bootstrap = self.filter_connected_peers(|peer| peer.node_type == NodeType::BootstrapClient);
1100 for bootstrap_ip in bootstrap_peers::<N>(self.is_dev()) {
1101 if !connected_bootstrap.iter().any(|peer| peer.listener_addr == bootstrap_ip) {
1102 candidate_bootstrap.push(bootstrap_ip);
1103 }
1104 }
1105 if connected_bootstrap.is_empty() {
1107 let peer_to_connect = candidate_bootstrap.into_iter().choose(&mut rand::rng());
1109 if let Some(peer_ip) = peer_to_connect {
1110 match self.connect(peer_ip) {
1111 Ok(hdl) => {
1112 debug!("{CONTEXT} (Re-)connecting to bootstrap peer at '{peer_ip}'");
1113 let result = hdl.await;
1114 if let Err(err) = result {
1115 warn!("{CONTEXT} Failed to connect to bootstrap peer at '{peer_ip}' - {err}");
1116 }
1117 }
1118 Err(ConnectError::AlreadyConnected { .. }) | Err(ConnectError::AlreadyConnecting { .. }) => {}
1119 Err(err) => {
1120 warn!("{CONTEXT} Could not initiate connection to bootstrap peer at '{peer_ip}' - {err}")
1121 }
1122 }
1123 }
1124 }
1125 let num_surplus = connected_bootstrap.len().saturating_sub(1);
1127 if num_surplus > 0 {
1128 let peers_to_disconnect = connected_bootstrap.into_iter().sample(&mut rand::rng(), num_surplus);
1130 for peer in peers_to_disconnect {
1131 info!("{CONTEXT} Disconnecting from '{}' (exceeded maximum bootstrap)", peer.listener_addr);
1132 <Self as Transport<N>>::send(
1133 self,
1134 peer.listener_addr,
1135 Event::Disconnect(DisconnectReason::NoReasonGiven.into()),
1136 )
1137 .await;
1138 self.disconnect(peer.listener_addr);
1140 }
1141 }
1142 }
1143
1144 fn handle_unauthorized_validators(&self) {
1146 let self_ = self.clone();
1147 tokio::spawn(async move {
1148 let validators = self_.get_connected_peers();
1150 for peer in validators {
1152 if peer.node_type == NodeType::BootstrapClient {
1154 continue;
1155 }
1156 if !self_.is_authorized_validator_ip(peer.listener_addr) {
1158 warn!(
1159 "{CONTEXT} Disconnecting from '{}' - Validator is not in the current committee",
1160 peer.listener_addr
1161 );
1162 Transport::send(&self_, peer.listener_addr, DisconnectReason::ProtocolViolation.into()).await;
1163 self_.disconnect(peer.listener_addr);
1165 }
1166 }
1167 });
1168 }
1169
1170 async fn handle_min_connected_validators(&self) {
1174 let trusted_validators = self.trusted_peers();
1177 if self.number_of_connected_peers() < N::LATEST_MAX_CERTIFICATES() as usize {
1178 let (addrs, handles): (Vec<_>, Vec<_>) = self
1179 .get_candidate_peers()
1180 .iter()
1181 .filter_map(|peer| {
1182 if trusted_validators.contains(&peer.listener_addr) {
1183 return None;
1184 }
1185
1186 if let Some(previous_attempt) = peer.last_connection_attempt
1187 && previous_attempt.elapsed() < Self::MINIMUM_TIME_BETWEEN_CONNECTION_ATTEMPTS
1188 {
1189 return None;
1190 }
1191
1192 match self.connect(peer.listener_addr) {
1193 Ok(hdl) => Some((peer.listener_addr, hdl)),
1194 Err(ConnectError::AlreadyConnected { .. })
1195 | Err(ConnectError::AlreadyConnecting { .. })
1196 | Err(ConnectError::SelfConnect { .. }) => None,
1197 Err(err) => {
1198 warn!(
1199 "{CONTEXT} Could not initiate connection to validator at '{}' - {err}",
1200 peer.listener_addr
1201 );
1202 None
1203 }
1204 }
1205 })
1206 .unzip();
1207
1208 for (addr, result) in addrs.into_iter().zip(join_all(handles).await) {
1209 if let Err(err) = result {
1210 warn!("{CONTEXT} Failed to connect to validator at '{addr}' - {err}");
1211 }
1212 }
1213
1214 let validators = self.connected_peers();
1216 if validators.is_empty() {
1218 return;
1219 }
1220 if let Some(validator_ip) = validators.into_iter().choose(&mut rand::rng()) {
1222 let self_ = self.clone();
1223 tokio::spawn(async move {
1224 self_.cache.increment_outbound_validators_requests(validator_ip);
1226 let _ = Transport::send(&self_, validator_ip, Event::ValidatorsRequest(ValidatorsRequest)).await;
1228 });
1229 }
1230 }
1231 }
1232
1233 async fn process_message_inner(&self, peer_addr: SocketAddr, message: Event<N>) {
1235 if let Err(error) = self.inbound(peer_addr, message).await
1237 && let Some(peer_ip) = self.resolver.read().get_listener(peer_addr)
1238 {
1239 warn!("{CONTEXT} Disconnecting from '{peer_ip}' - {error}");
1240 let self_ = self.clone();
1241 tokio::spawn(async move {
1242 Transport::send(&self_, peer_ip, DisconnectReason::ProtocolViolation.into()).await;
1243 self_.disconnect(peer_ip);
1245 });
1246 }
1247 }
1248
1249 fn handle_banned_ips(&self) {
1251 self.tcp.banned_peers().remove_old_bans(IP_BAN_TIME_IN_SECS);
1252 }
1253}
1254
1255#[async_trait]
1256impl<N: Network> Transport<N> for Gateway<N> {
1257 async fn send(&self, peer_ip: SocketAddr, event: Event<N>) -> Option<oneshot::Receiver<io::Result<()>>> {
1265 macro_rules! send {
1266 ($self:ident, $cache_map:ident, $interval:expr, $freq:ident) => {{
1267 while $self.cache.$cache_map(peer_ip, $interval) > $self.$freq() {
1269 tokio::time::sleep(Duration::from_millis(10)).await;
1271 }
1272 $self.send_inner(peer_ip, event)
1274 }};
1275 }
1276
1277 match event {
1279 Event::CertificateRequest(_) | Event::CertificateResponse(_) => {
1280 self.cache.insert_outbound_event(peer_ip, CACHE_EVENTS_INTERVAL);
1282 send!(self, insert_outbound_certificate, CACHE_REQUESTS_INTERVAL, max_cache_certificates)
1284 }
1285 Event::TransmissionRequest(_) | Event::TransmissionResponse(_) => {
1286 self.cache.insert_outbound_event(peer_ip, CACHE_EVENTS_INTERVAL);
1288 send!(self, insert_outbound_transmission, CACHE_REQUESTS_INTERVAL, max_cache_transmissions)
1290 }
1291 Event::BlockRequest(request) => {
1292 self.cache.insert_outbound_block_request(peer_ip, request);
1294 send!(self, insert_outbound_event, CACHE_EVENTS_INTERVAL, max_cache_events)
1296 }
1297 _ => {
1298 send!(self, insert_outbound_event, CACHE_EVENTS_INTERVAL, max_cache_events)
1300 }
1301 }
1302 }
1303
1304 fn broadcast(&self, event: Event<N>) {
1308 if self.number_of_connected_peers() > 0 {
1310 let self_ = self.clone();
1311 let connected_peers = self.connected_peers();
1312 tokio::spawn(async move {
1313 for peer_ip in connected_peers {
1315 let _ = Transport::send(&self_, peer_ip, event.clone()).await;
1317 }
1318 });
1319 }
1320 }
1321}
1322
1323impl<N: Network> P2P for Gateway<N> {
1324 fn tcp(&self) -> &Tcp {
1326 &self.tcp
1327 }
1328}
1329
1330#[async_trait]
1331impl<N: Network> Reading for Gateway<N> {
1332 type Codec = EventCodec<N>;
1333 type Message = Event<N>;
1334
1335 fn codec(&self, _peer_addr: SocketAddr, _side: ConnectionSide) -> Self::Codec {
1338 Default::default()
1339 }
1340
1341 async fn process_message(&self, peer_addr: SocketAddr, message: Self::Message) -> io::Result<()> {
1343 if matches!(message, Event::BlockRequest(_) | Event::BlockResponse(_)) {
1344 let self_ = self.clone();
1345 tokio::spawn(async move {
1348 self_.process_message_inner(peer_addr, message).await;
1349 });
1350 } else {
1351 self.process_message_inner(peer_addr, message).await;
1352 }
1353 Ok(())
1354 }
1355
1356 fn message_queue_depth(&self) -> usize {
1359 2 * BatchHeader::<N>::MAX_GC_ROUNDS
1360 * N::LATEST_MAX_CERTIFICATES() as usize
1361 * BatchHeader::<N>::MAX_TRANSMISSIONS_PER_BATCH
1362 }
1363}
1364
1365#[async_trait]
1366impl<N: Network> Writing for Gateway<N> {
1367 type Codec = EventCodec<N>;
1368 type Message = Event<N>;
1369
1370 fn codec(&self, _peer_addr: SocketAddr, _side: ConnectionSide) -> Self::Codec {
1373 Default::default()
1374 }
1375
1376 fn message_queue_depth(&self) -> usize {
1380 2 * BatchHeader::<N>::MAX_GC_ROUNDS
1381 * N::LATEST_MAX_CERTIFICATES() as usize
1382 * BatchHeader::<N>::MAX_TRANSMISSIONS_PER_BATCH
1383 }
1384}
1385
1386#[async_trait]
1387impl<N: Network> Disconnect for Gateway<N> {
1388 async fn handle_disconnect(&self, peer_addr: SocketAddr, origin: DisconnectOrigin) {
1390 debug!("Physically disconnecting from {peer_addr}; origin: {origin:?}");
1391
1392 if let Some(peer_ip) = self.resolve_to_listener(&peer_addr) {
1393 let was_fully_connected = self.downgrade_peer_to_candidate(peer_ip);
1399
1400 if was_fully_connected && let Some(sync_sender) = self.sync_sender.get() {
1402 let (tx, rx) = oneshot::channel();
1403
1404 if let Err(err) = sync_sender.tx_block_sync_remove_peer.send((peer_ip, tx)).await {
1405 let err: anyhow::Error = err.into();
1406 let err =
1407 err.context(format!("Unable to remove disconnecting peer '{peer_ip}' from the sync module"));
1408 warn!("{CONTEXT} {}", flatten_error(err));
1409 }
1410
1411 if let Err(err) = rx.await {
1412 let err: anyhow::Error = err.into();
1413 let err =
1414 err.context(format!("Unable to remove disconnecting peer '{peer_ip}' from the sync module"));
1415 warn!("{CONTEXT} {}", flatten_error(err));
1416 }
1417 }
1418 self.cache.clear_outbound_validators_requests(peer_ip);
1422 self.cache.clear_outbound_block_requests(peer_ip);
1423 } else {
1424 warn!("{CONTEXT} Got disconnect for a peer '{peer_addr}' that is not in the peer pool");
1425 }
1426 }
1427}
1428
1429#[async_trait]
1430impl<N: Network> OnConnect for Gateway<N> {
1431 async fn on_connect(&self, peer_addr: SocketAddr) {
1432 if let Some(listener_addr) = self.resolve_to_listener(&peer_addr) {
1433 if let Some(peer) = self.get_connected_peer(listener_addr) {
1434 if peer.node_type == NodeType::BootstrapClient {
1435 self.cache.increment_outbound_validators_requests(listener_addr);
1436 let _ =
1437 <Self as Transport<N>>::send(self, listener_addr, Event::ValidatorsRequest(ValidatorsRequest))
1438 .await;
1439 }
1440 }
1441 }
1442 }
1443}
1444
1445#[async_trait]
1446impl<N: Network> Handshake for Gateway<N> {
1447 async fn perform_handshake(&self, mut connection: Connection) -> Result<Connection, ConnectError> {
1449 let peer_addr = connection.addr();
1451 let peer_side = connection.side();
1452
1453 #[cfg(not(test))]
1455 if self.dev().is_none() && peer_side == ConnectionSide::Initiator {
1456 if self.is_ip_banned(peer_addr.ip()) {
1458 trace!("{CONTEXT} Rejected a connection request from banned IP '{}'", peer_addr.ip());
1459 return Err(ConnectError::BannedIp { ip: peer_addr.ip() });
1460 }
1461
1462 let num_attempts = self.cache.insert_inbound_connection(peer_addr.ip(), CONNECTION_ATTEMPTS_SINCE_SECS);
1463
1464 debug!("Number of connection attempts from '{}': {}", peer_addr.ip(), num_attempts);
1465 if num_attempts > MAX_CONNECTION_ATTEMPTS {
1466 self.update_ip_ban(peer_addr.ip());
1467 trace!("{CONTEXT} Rejected a consecutive connection request from IP '{}'", peer_addr.ip());
1468 return Err(ConnectError::other(anyhow!("'{}' appears to be spamming connections", peer_addr.ip())));
1469 }
1470 }
1471
1472 let stream = self.borrow_stream(&mut connection);
1473
1474 let mut listener_addr = if peer_side == ConnectionSide::Initiator {
1477 debug!("{CONTEXT} Received a connection request from '{peer_addr}'");
1478 None
1479 } else {
1480 debug!("{CONTEXT} Shaking hands with {peer_addr}...");
1481 Some(peer_addr)
1482 };
1483
1484 let restrictions_id = self.ledger.latest_restrictions_id();
1486
1487 let handshake_result = if peer_side == ConnectionSide::Responder {
1489 self.handshake_inner_initiator(peer_addr, restrictions_id, stream).await
1490 } else {
1491 self.handshake_inner_responder(peer_addr, &mut listener_addr, restrictions_id, stream).await
1492 };
1493
1494 if let Some(addr) = listener_addr {
1495 match handshake_result {
1496 Ok(ref cr) => {
1497 let node_type = if bootstrap_peers::<N>(self.is_dev()).contains(&addr) {
1498 NodeType::BootstrapClient
1499 } else {
1500 NodeType::Validator
1501 };
1502
1503 let mut peer_pool = self.peer_pool.write();
1504
1505 peer_pool.retain(|_, peer| {
1511 if let Peer::Candidate(peer) = peer
1512 && let Some(old_aleo_addr) = peer.last_known_aleo_addr
1513 {
1514 old_aleo_addr != cr.address || peer.listener_addr == addr
1515 } else {
1516 true
1517 }
1518 });
1519
1520 if let Some(peer) = peer_pool.get_mut(&addr) {
1521 self.resolver.write().insert_peer(addr, peer_addr, Some(cr.address));
1522 peer.upgrade_to_connected(
1523 peer_addr,
1524 cr.listener_port,
1525 cr.address,
1526 node_type,
1527 cr.version,
1528 cr.snarkos_sha,
1529 ConnectionMode::Gateway,
1530 );
1531 }
1532 info!("{CONTEXT} Connected to '{addr}'");
1533 }
1534 Err(error) => {
1535 if let Some(peer) = self.peer_pool.write().get_mut(&addr) {
1536 if peer.is_connecting() {
1538 peer.downgrade_to_candidate(addr);
1539 }
1540 }
1541 return Err(error);
1542 }
1543 }
1544 }
1545
1546 Ok(connection)
1547 }
1548}
1549
1550macro_rules! expect_event {
1552 ($event_ty:path, $framed:expr, $peer_addr:expr) => {
1553 match $framed.try_next().await? {
1554 Some($event_ty(data)) => {
1556 trace!("{CONTEXT} Received '{}' from '{}'", data.name(), $peer_addr);
1557 data
1558 }
1559 Some(Event::Disconnect($crate::events::Disconnect { reason })) => {
1561 return Err(ConnectError::other(format!("'{}' disconnected with reason \"{reason}\"", $peer_addr)));
1562 }
1563 Some(ty) => {
1565 return Err(ConnectError::other(format!(
1566 "'{}' did not follow the handshake protocol: received {:?} instead of {}",
1567 $peer_addr,
1568 ty.name(),
1569 stringify!($msg_ty),
1570 )));
1571 }
1572 None => return Err(ConnectError::IoError(io::ErrorKind::BrokenPipe.into())),
1574 }
1575 };
1576}
1577
1578async fn send_event<N: Network>(
1580 framed: &mut Framed<&mut TcpStream, EventCodec<N>>,
1581 peer_addr: SocketAddr,
1582 event: Event<N>,
1583) -> io::Result<()> {
1584 trace!("{CONTEXT} Sending '{}' to '{peer_addr}'", event.name());
1585 framed.send(event).await
1586}
1587
1588impl<N: Network> Gateway<N> {
1589 async fn handshake_inner_initiator<'a>(
1591 &'a self,
1592 peer_addr: SocketAddr,
1593 restrictions_id: Field<N>,
1594 stream: &'a mut TcpStream,
1595 ) -> Result<ChallengeRequest<N>, ConnectError> {
1596 self.add_connecting_peer(peer_addr)?;
1598
1599 let mut framed = Framed::new(stream, EventCodec::<N>::handshake());
1601
1602 let our_nonce: u64 = rand::random();
1606 let current_block_height = self.ledger.latest_block_height();
1608 let consensus_version = N::CONSENSUS_VERSION(current_block_height).unwrap();
1609 let snarkos_sha = match (self.is_dev(), consensus_version >= ConsensusVersion::V12, get_repo_commit_hash()) {
1610 (true, _, Some(sha)) => Some(sha),
1611 (_, true, Some(sha)) => Some(sha),
1612 _ => None,
1613 };
1614 let our_request = ChallengeRequest::new(self.local_ip().port(), self.account.address(), our_nonce, snarkos_sha);
1616 send_event(&mut framed, peer_addr, Event::ChallengeRequest(our_request)).await?;
1617
1618 let peer_response = expect_event!(Event::ChallengeResponse, framed, peer_addr);
1622 let peer_request = expect_event!(Event::ChallengeRequest, framed, peer_addr);
1624
1625 if let Some(reason) = self
1627 .verify_challenge_response(peer_addr, peer_request.address, peer_response, restrictions_id, our_nonce)
1628 .await
1629 {
1630 send_event(&mut framed, peer_addr, reason.into()).await?;
1631 return Err(ConnectError::application(reason));
1632 }
1633
1634 if let Some(reason) = self.verify_challenge_request(peer_addr, &peer_request) {
1636 send_event(&mut framed, peer_addr, reason.into()).await?;
1637 return Err(reason.into_connect_error(peer_addr));
1638 }
1639
1640 let response_nonce: u64 = rand::random();
1644 let data = [peer_request.nonce.to_le_bytes(), response_nonce.to_le_bytes()].concat();
1645 let Ok(our_signature) = self.account.sign_bytes(&data, &mut rand::rng()) else {
1646 return Err(ConnectError::other(anyhow!("Failed to sign the challenge request nonce")));
1647 };
1648 let our_response =
1650 ChallengeResponse { restrictions_id, signature: Data::Object(our_signature), nonce: response_nonce };
1651 send_event(&mut framed, peer_addr, Event::ChallengeResponse(our_response)).await?;
1652
1653 Ok(peer_request)
1654 }
1655
1656 async fn handshake_inner_responder<'a>(
1658 &'a self,
1659 peer_addr: SocketAddr,
1660 peer_ip: &mut Option<SocketAddr>,
1661 restrictions_id: Field<N>,
1662 stream: &'a mut TcpStream,
1663 ) -> Result<ChallengeRequest<N>, ConnectError> {
1664 let mut framed = Framed::new(stream, EventCodec::<N>::handshake());
1666
1667 let peer_request = expect_event!(Event::ChallengeRequest, framed, peer_addr);
1671
1672 if self.account.address() == peer_request.address {
1674 return Err(ConnectError::SelfConnect { address: peer_addr });
1675 }
1676
1677 *peer_ip = Some(SocketAddr::new(peer_addr.ip(), peer_request.listener_port));
1679 let peer_ip = peer_ip.unwrap();
1680
1681 if let Err(reason) = self.ensure_peer_is_allowed(peer_ip) {
1683 send_event(&mut framed, peer_addr, reason.into()).await?;
1684 return Err(reason.into_connect_error(peer_addr));
1685 }
1686
1687 self.add_connecting_peer(peer_ip)?;
1689
1690 if let Some(reason) = self.verify_challenge_request(peer_addr, &peer_request) {
1692 send_event(&mut framed, peer_addr, reason.into()).await?;
1693 return Err(reason.into_connect_error(peer_addr));
1694 }
1695
1696 let response_nonce: u64 = rand::random();
1700 let data = [peer_request.nonce.to_le_bytes(), response_nonce.to_le_bytes()].concat();
1701 let Ok(our_signature) = self.account.sign_bytes(&data, &mut rand::rng()) else {
1702 return Err(ConnectError::other(anyhow!("Failed to sign the challenge request nonce")));
1703 };
1704 let our_response =
1706 ChallengeResponse { restrictions_id, signature: Data::Object(our_signature), nonce: response_nonce };
1707 send_event(&mut framed, peer_addr, Event::ChallengeResponse(our_response)).await?;
1708
1709 let our_nonce: u64 = rand::random();
1711 let current_block_height = self.ledger.latest_block_height();
1713 let consensus_version = N::CONSENSUS_VERSION(current_block_height).unwrap();
1714 let snarkos_sha = match (self.is_dev(), consensus_version >= ConsensusVersion::V12, get_repo_commit_hash()) {
1715 (true, _, Some(sha)) => Some(sha),
1716 (_, true, Some(sha)) => Some(sha),
1717 _ => None,
1718 };
1719 let our_request = ChallengeRequest::new(self.local_ip().port(), self.account.address(), our_nonce, snarkos_sha);
1721 send_event(&mut framed, peer_addr, Event::ChallengeRequest(our_request)).await?;
1722
1723 let peer_response = expect_event!(Event::ChallengeResponse, framed, peer_addr);
1727 if let Some(reason) = self
1729 .verify_challenge_response(peer_addr, peer_request.address, peer_response, restrictions_id, our_nonce)
1730 .await
1731 {
1732 send_event(&mut framed, peer_addr, reason.into()).await?;
1733 Err(reason.into_connect_error(peer_addr))
1734 } else {
1735 Ok(peer_request)
1736 }
1737 }
1738
1739 #[must_use]
1741 fn verify_challenge_request(&self, peer_addr: SocketAddr, event: &ChallengeRequest<N>) -> Option<DisconnectReason> {
1742 let &ChallengeRequest { version, listener_port, address, nonce: _, ref snarkos_sha } = event;
1744 log_repo_sha_comparison(peer_addr, snarkos_sha, CONTEXT);
1745
1746 let listener_addr = SocketAddr::new(peer_addr.ip(), listener_port);
1747
1748 if version < Event::<N>::VERSION {
1750 return Some(DisconnectReason::OutdatedClientVersion);
1751 }
1752 if self.trusted_peers_only && !self.is_trusted(listener_addr) {
1754 warn!("{CONTEXT} Dropping '{peer_addr}' for being an untrusted validator ({address})");
1755 return Some(DisconnectReason::NoExternalPeersAllowed);
1756 }
1757 if !bootstrap_peers::<N>(self.dev().is_some()).contains(&listener_addr) {
1758 if !self.is_authorized_validator_address(address) {
1760 return Some(DisconnectReason::UnauthorizedValidator);
1761 }
1762 }
1763
1764 if self.is_connected_address(address) {
1766 return Some(DisconnectReason::AlreadyConnectedToAleoAddress);
1767 }
1768
1769 None
1770 }
1771
1772 #[must_use]
1774 async fn verify_challenge_response(
1775 &self,
1776 peer_addr: SocketAddr,
1777 peer_address: Address<N>,
1778 response: ChallengeResponse<N>,
1779 expected_restrictions_id: Field<N>,
1780 expected_nonce: u64,
1781 ) -> Option<DisconnectReason> {
1782 let ChallengeResponse { restrictions_id, signature, nonce } = response;
1784
1785 if restrictions_id != expected_restrictions_id {
1787 warn!("{CONTEXT} Handshake with '{peer_addr}' failed (incorrect restrictions ID)");
1788 return Some(DisconnectReason::InvalidChallengeResponse);
1789 }
1790 let Ok(signature) = spawn_blocking!(signature.deserialize_blocking()) else {
1792 warn!("{CONTEXT} Handshake with '{peer_addr}' failed (cannot deserialize the signature)");
1793 return Some(DisconnectReason::InvalidChallengeResponse);
1794 };
1795 if !signature.verify_bytes(&peer_address, &[expected_nonce.to_le_bytes(), nonce.to_le_bytes()].concat()) {
1797 warn!("{CONTEXT} Handshake with '{peer_addr}' failed (invalid signature)");
1798 return Some(DisconnectReason::InvalidChallengeResponse);
1799 }
1800 None
1801 }
1802}
1803
1804#[cfg(test)]
1805mod prop_tests {
1806 use crate::{
1807 Gateway,
1808 MAX_WORKERS,
1809 MEMORY_POOL_PORT,
1810 Worker,
1811 helpers::{Storage, init_primary_channels, init_worker_channels},
1812 };
1813
1814 use snarkos_account::Account;
1815 use snarkos_node_bft_ledger_service::MockLedgerService;
1816 use snarkos_node_bft_storage_service::BFTMemoryService;
1817 use snarkos_node_network::PeerPoolHandling;
1818 use snarkos_node_tcp::P2P;
1819 use snarkos_utilities::NodeDataDir;
1820
1821 use snarkos_node_bft_events::committee_prop_tests::{CommitteeContext, ValidatorSet};
1822 use snarkvm::{
1823 ledger::{
1824 committee::{Committee, test_helpers::sample_committee_for_round_and_members},
1825 narwhal::{BatchHeader, batch_certificate::test_helpers::sample_batch_certificate_for_round},
1826 },
1827 prelude::{MainnetV0, PrivateKey},
1828 utilities::TestRng,
1829 };
1830
1831 use indexmap::{IndexMap, IndexSet};
1832 use proptest::{
1833 prelude::{Arbitrary, BoxedStrategy, Just, Strategy, any, any_with},
1834 sample::Selector,
1835 };
1836 use std::{
1837 fmt::{Debug, Formatter},
1838 net::{IpAddr, Ipv4Addr, SocketAddr},
1839 sync::Arc,
1840 };
1841 use test_strategy::proptest;
1842
1843 type CurrentNetwork = MainnetV0;
1844
1845 impl Debug for Gateway<CurrentNetwork> {
1846 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
1847 f.debug_tuple("Gateway").field(&self.account.address()).field(&self.tcp.config()).finish()
1849 }
1850 }
1851
1852 #[derive(Debug, test_strategy::Arbitrary)]
1853 enum GatewayAddress {
1854 Dev(u8),
1855 Prod(Option<SocketAddr>),
1856 }
1857
1858 impl GatewayAddress {
1859 fn ip(&self) -> Option<SocketAddr> {
1860 if let GatewayAddress::Prod(ip) = self {
1861 return *ip;
1862 }
1863 None
1864 }
1865
1866 fn port(&self) -> Option<u16> {
1867 if let GatewayAddress::Dev(port) = self {
1868 return Some(*port as u16);
1869 }
1870 None
1871 }
1872 }
1873
1874 impl Arbitrary for Gateway<CurrentNetwork> {
1875 type Parameters = ();
1876 type Strategy = BoxedStrategy<Gateway<CurrentNetwork>>;
1877
1878 fn arbitrary_with(_: Self::Parameters) -> Self::Strategy {
1879 any_valid_dev_gateway()
1880 .prop_map(|(storage, _, private_key, address)| {
1881 Gateway::new(
1882 Account::try_from(private_key).unwrap(),
1883 storage.clone(),
1884 storage.ledger().clone(),
1885 address.ip(),
1886 &[],
1887 false,
1888 NodeDataDir::new_test(None),
1889 address.port(),
1890 )
1891 .unwrap()
1892 })
1893 .boxed()
1894 }
1895 }
1896
1897 type GatewayInput = (Storage<CurrentNetwork>, CommitteeContext, PrivateKey<CurrentNetwork>, GatewayAddress);
1898
1899 fn any_valid_dev_gateway() -> BoxedStrategy<GatewayInput> {
1900 (any::<CommitteeContext>(), any::<Selector>())
1901 .prop_flat_map(|(context, account_selector)| {
1902 let CommitteeContext(_, ValidatorSet(validators)) = context.clone();
1903 (
1904 any_with::<Storage<CurrentNetwork>>(context.clone()),
1905 Just(context),
1906 Just(account_selector.select(validators)),
1907 0u8..,
1908 )
1909 .prop_map(|(a, b, c, d)| (a, b, c.private_key, GatewayAddress::Dev(d)))
1910 })
1911 .boxed()
1912 }
1913
1914 fn any_valid_prod_gateway() -> BoxedStrategy<GatewayInput> {
1915 (any::<CommitteeContext>(), any::<Selector>())
1916 .prop_flat_map(|(context, account_selector)| {
1917 let CommitteeContext(_, ValidatorSet(validators)) = context.clone();
1918 (
1919 any_with::<Storage<CurrentNetwork>>(context.clone()),
1920 Just(context),
1921 Just(account_selector.select(validators)),
1922 any::<Option<SocketAddr>>(),
1923 )
1924 .prop_map(|(a, b, c, d)| (a, b, c.private_key, GatewayAddress::Prod(d)))
1925 })
1926 .boxed()
1927 }
1928
1929 #[proptest]
1930 fn gateway_dev_initialization(#[strategy(any_valid_dev_gateway())] input: GatewayInput) {
1931 let (storage, _, private_key, dev) = input;
1932 let account = Account::try_from(private_key).unwrap();
1933
1934 let gateway = Gateway::new(
1935 account.clone(),
1936 storage.clone(),
1937 storage.ledger().clone(),
1938 dev.ip(),
1939 &[],
1940 false,
1941 NodeDataDir::new_test(None),
1942 dev.port(),
1943 )
1944 .unwrap();
1945 let tcp_config = gateway.tcp().config();
1946 assert_eq!(tcp_config.listener_ip, Some(IpAddr::V4(Ipv4Addr::LOCALHOST)));
1947 assert_eq!(tcp_config.desired_listening_port, Some(MEMORY_POOL_PORT + dev.port().unwrap()));
1948
1949 let tcp_config = gateway.tcp().config();
1950 assert_eq!(tcp_config.max_connections, Committee::<CurrentNetwork>::max_committee_size() * 10);
1951 assert_eq!(gateway.account().address(), account.address());
1952 }
1953
1954 #[proptest]
1955 fn gateway_prod_initialization(#[strategy(any_valid_prod_gateway())] input: GatewayInput) {
1956 let (storage, _, private_key, dev) = input;
1957 let account = Account::try_from(private_key).unwrap();
1958
1959 let gateway = Gateway::new(
1960 account.clone(),
1961 storage.clone(),
1962 storage.ledger().clone(),
1963 dev.ip(),
1964 &[],
1965 false,
1966 NodeDataDir::new_test(None),
1967 dev.port(),
1968 )
1969 .unwrap();
1970 let tcp_config = gateway.tcp().config();
1971 if let Some(socket_addr) = dev.ip() {
1972 assert_eq!(tcp_config.listener_ip, Some(socket_addr.ip()));
1973 assert_eq!(tcp_config.desired_listening_port, Some(socket_addr.port()));
1974 } else {
1975 assert_eq!(tcp_config.listener_ip, Some(IpAddr::V4(Ipv4Addr::UNSPECIFIED)));
1976 assert_eq!(tcp_config.desired_listening_port, Some(MEMORY_POOL_PORT));
1977 }
1978
1979 let tcp_config = gateway.tcp().config();
1980 assert_eq!(tcp_config.max_connections, Committee::<CurrentNetwork>::max_committee_size() * 10);
1981 assert_eq!(gateway.account().address(), account.address());
1982 }
1983
1984 #[proptest(async = "tokio")]
1985 async fn gateway_start(
1986 #[strategy(any_valid_dev_gateway())] input: GatewayInput,
1987 #[strategy(0..MAX_WORKERS)] workers_count: u8,
1988 ) {
1989 let (storage, committee, private_key, dev) = input;
1990 let committee = committee.0;
1991 let worker_storage = storage.clone();
1992 let account = Account::try_from(private_key).unwrap();
1993
1994 let gateway = Gateway::new(
1995 account,
1996 storage.clone(),
1997 storage.ledger().clone(),
1998 dev.ip(),
1999 &[],
2000 false,
2001 NodeDataDir::new_test(None),
2002 dev.port(),
2003 )
2004 .unwrap();
2005
2006 let (primary_sender, _) = init_primary_channels();
2007
2008 let (workers, worker_senders) = {
2009 let mut tx_workers = IndexMap::new();
2011 let mut workers = IndexMap::new();
2012
2013 for id in 0..workers_count {
2015 let (tx_worker, rx_worker) = init_worker_channels();
2017 let ledger = Arc::new(MockLedgerService::new(committee.clone()));
2019 let worker =
2020 Worker::new(id, Arc::new(gateway.clone()), worker_storage.clone(), ledger, Default::default())
2021 .unwrap();
2022 worker.run(rx_worker);
2024
2025 workers.insert(id, worker);
2027 tx_workers.insert(id, tx_worker);
2028 }
2029 (workers, tx_workers)
2030 };
2031
2032 gateway.run(primary_sender, worker_senders, None).await;
2033 assert_eq!(
2034 gateway.local_ip(),
2035 SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), MEMORY_POOL_PORT + dev.port().unwrap())
2036 );
2037 assert_eq!(gateway.num_workers(), workers.len() as u8);
2038 }
2039
2040 #[proptest]
2041 fn test_is_authorized_validator(#[strategy(any_valid_dev_gateway())] input: GatewayInput) {
2042 let rng = &mut TestRng::default();
2043
2044 let current_round = 2;
2046 let committee_size = 4;
2047 let max_gc_rounds = BatchHeader::<CurrentNetwork>::MAX_GC_ROUNDS as u64;
2048 let (_, _, private_key, dev) = input;
2049 let account = Account::try_from(private_key).unwrap();
2050
2051 let mut certificates = IndexSet::new();
2053 for _ in 0..committee_size {
2054 certificates.insert(sample_batch_certificate_for_round(current_round, rng));
2055 }
2056 let addresses: Vec<_> = certificates.iter().map(|certificate| certificate.author()).collect();
2057 let committee = sample_committee_for_round_and_members(current_round, addresses, rng);
2059 for _ in 0..committee_size {
2061 certificates.insert(sample_batch_certificate_for_round(current_round, rng));
2062 }
2063 let ledger = Arc::new(MockLedgerService::new(committee.clone()));
2065 let storage = Storage::new(ledger.clone(), Arc::new(BFTMemoryService::new()), max_gc_rounds).unwrap();
2067 let gateway = Gateway::new(
2069 account.clone(),
2070 storage.clone(),
2071 ledger.clone(),
2072 dev.ip(),
2073 &[],
2074 false,
2075 NodeDataDir::new_test(None),
2076 dev.port(),
2077 )
2078 .unwrap();
2079 for certificate in certificates.iter() {
2081 storage.testing_only_insert_certificate_testing_only(certificate.clone());
2082 }
2083 for i in 0..certificates.clone().len() {
2085 let is_authorized = gateway.is_authorized_validator_address(certificates[i].author());
2086 if i < committee_size {
2087 assert!(is_authorized);
2088 } else {
2089 assert!(!is_authorized);
2090 }
2091 }
2092 }
2093}