1use crate::{
13 backend,
14 metrics::METRICS,
15 rlpx::{connection::server::PeerConnection, p2p::Capability},
16 types::{Node, NodeRecord},
17 utils::distance,
18};
19use bytes::Bytes;
20use ethrex_common::{H256, U256};
21use ethrex_storage::Store;
22use indexmap::IndexMap;
23use rand::distributions::WeightedIndex;
24use rand::prelude::Distribution;
25use rand::seq::{IteratorRandom, SliceRandom};
26use rustc_hash::{FxHashMap, FxHashSet};
27use spawned_concurrency::{
28 actor,
29 error::ActorError,
30 protocol,
31 tasks::{Actor, ActorRef, ActorStart as _, Context, Handler, Response, send_message_on},
32};
33use std::{
34 net::IpAddr,
35 time::{Duration, Instant},
36};
37
38const MAX_SCORE: i64 = 50;
39const MIN_SCORE: i64 = -50;
40const MIN_SCORE_CRITICAL: i64 = MIN_SCORE * 3;
42const SCORE_WEIGHT: i64 = 1;
44const REQUESTS_WEIGHT: i64 = 1;
46const MAX_CONCURRENT_REQUESTS_PER_PEER: i64 = 100;
48pub const TARGET_PEERS: usize = 100;
50pub(crate) const MAX_NODES_IN_NEIGHBORS_PACKET: usize = 16;
52const MAX_ENRS_PER_FINDNODE_RESPONSE: usize = 16;
54
55const NUMBER_OF_BUCKETS: usize = 256;
57pub const MAX_NODES_PER_BUCKET: usize = 16;
59const MAX_REPLACEMENTS_PER_BUCKET: usize = 10;
61const MAX_CONNECTION_POOL_SIZE: usize = 10_000;
67
68#[derive(Debug, Clone, Default)]
71pub struct KBucket {
72 pub(crate) contacts: Vec<(H256, Contact)>,
73 pub(crate) replacements: Vec<(H256, Contact)>,
74}
75
76impl KBucket {
77 fn get(&self, node_id: &H256) -> Option<&Contact> {
79 self.contacts
80 .iter()
81 .find(|(id, _)| id == node_id)
82 .map(|(_, c)| c)
83 }
84
85 fn get_any(&self, node_id: &H256) -> Option<&Contact> {
87 self.get(node_id).or_else(|| {
88 self.replacements
89 .iter()
90 .find(|(id, _)| id == node_id)
91 .map(|(_, c)| c)
92 })
93 }
94
95 fn get_mut(&mut self, node_id: &H256) -> Option<&mut Contact> {
97 if let Some((_, c)) = self.contacts.iter_mut().find(|(id, _)| id == node_id) {
98 return Some(c);
99 }
100 self.replacements
101 .iter_mut()
102 .find(|(id, _)| id == node_id)
103 .map(|(_, c)| c)
104 }
105
106 fn contains(&self, node_id: &H256) -> bool {
108 self.contacts.iter().any(|(id, _)| id == node_id)
109 || self.replacements.iter().any(|(id, _)| id == node_id)
110 }
111
112 fn insert(&mut self, node_id: H256, contact: Contact) -> bool {
115 if self.contacts.len() < MAX_NODES_PER_BUCKET {
116 self.contacts.push((node_id, contact));
117 true
118 } else {
119 self.insert_replacement(node_id, contact);
120 false
121 }
122 }
123
124 fn insert_replacement(&mut self, node_id: H256, contact: Contact) {
126 if self.replacements.len() >= MAX_REPLACEMENTS_PER_BUCKET {
127 self.replacements.remove(0);
128 }
129 self.replacements.push((node_id, contact));
130 }
131
132 fn remove_and_promote(&mut self, node_id: &H256) -> Option<H256> {
135 let idx = self.contacts.iter().position(|(id, _)| id == node_id)?;
136 self.contacts.remove(idx);
137 if !self.replacements.is_empty() {
138 let (replacement_id, replacement) = self.replacements.remove(0);
139 self.contacts.push((replacement_id, replacement));
140 Some(replacement_id)
141 } else {
142 None
143 }
144 }
145}
146
147fn bucket_index(local_node_id: &H256, node_id: &H256) -> Option<usize> {
152 let xor = *local_node_id ^ *node_id;
153 let dist = U256::from_big_endian(xor.as_bytes());
154 if dist.is_zero() {
155 None
156 } else {
157 Some(dist.bits() - 1)
158 }
159}
160
161pub(crate) fn xor_distance(a: &H256, b: &H256) -> H256 {
165 *a ^ *b
166}
167
168#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
171pub enum DiscoveryProtocol {
172 Discv4,
174 Discv5,
176}
177
178pub use crate::discv5::session::Session;
181
182#[derive(Debug, Clone)]
183pub struct Contact {
184 pub node: Node,
185 pub is_discv4: bool,
187 pub is_discv5: bool,
189 pub validation_timestamp: Option<Instant>,
192 pub ping_id: Option<Bytes>,
197
198 pub enr_request_hash: Option<H256>,
201
202 pub record: Option<NodeRecord>,
204 pub disposable: bool,
206 pub knows_us: bool,
208 pub unwanted: bool,
210 pub is_fork_id_valid: Option<bool>,
212 session: Option<Session>,
214}
215
216impl Contact {
217 pub fn was_validated(&self) -> bool {
218 self.validation_timestamp.is_some() && !self.has_pending_ping()
219 }
220
221 pub fn has_pending_ping(&self) -> bool {
222 self.ping_id.is_some()
223 }
224
225 pub fn record_ping_sent(&mut self, ping_id: Bytes) {
226 self.validation_timestamp = Some(Instant::now());
227 self.ping_id = Some(ping_id);
228 }
229
230 pub fn record_enr_request_sent(&mut self, request_hash: H256) {
231 self.enr_request_hash = Some(request_hash);
232 }
233
234 pub fn record_enr_response_received(&mut self, request_hash: H256, record: NodeRecord) {
236 if self
237 .enr_request_hash
238 .take_if(|h| *h == request_hash)
239 .is_some()
240 {
241 self.record = Some(record);
242 }
243 }
244
245 pub fn has_pending_enr_request(&self) -> bool {
246 self.enr_request_hash.is_some()
247 }
248}
249
250impl Contact {
251 pub fn new(node: Node, protocol: DiscoveryProtocol) -> Self {
252 Self {
253 node,
254 is_discv4: protocol == DiscoveryProtocol::Discv4,
255 is_discv5: protocol == DiscoveryProtocol::Discv5,
256 validation_timestamp: None,
257 ping_id: None,
258 enr_request_hash: None,
259 record: None,
260 disposable: false,
261 knows_us: true,
262 unwanted: false,
263 is_fork_id_valid: None,
264 session: None,
265 }
266 }
267
268 pub fn supports_protocol(&self, protocol: DiscoveryProtocol) -> bool {
270 match protocol {
271 DiscoveryProtocol::Discv4 => self.is_discv4,
272 DiscoveryProtocol::Discv5 => self.is_discv5,
273 }
274 }
275
276 pub fn add_protocol(&mut self, protocol: DiscoveryProtocol) {
278 match protocol {
279 DiscoveryProtocol::Discv4 => self.is_discv4 = true,
280 DiscoveryProtocol::Discv5 => self.is_discv5 = true,
281 }
282 }
283}
284
285#[derive(Debug, Clone)]
286pub struct PeerData {
287 pub node: Node,
288 pub record: Option<NodeRecord>,
289 pub supported_capabilities: Vec<Capability>,
290 pub is_connection_inbound: bool,
293 pub connection: Option<PeerConnection>,
295 score: i64,
297 requests: i64,
299 pub last_response_time: Option<u64>,
301}
302
303impl PeerData {
304 pub fn new(
305 node: Node,
306 record: Option<NodeRecord>,
307 connection: Option<PeerConnection>,
308 capabilities: Vec<Capability>,
309 ) -> Self {
310 Self {
311 node,
312 record,
313 supported_capabilities: capabilities,
314 is_connection_inbound: false,
315 connection,
316 score: Default::default(),
317 requests: Default::default(),
318 last_response_time: None,
319 }
320 }
321}
322
323#[derive(Debug, Clone, serde::Serialize)]
325pub struct PeerDiagnostics {
326 pub peer_id: H256,
327 pub score: i64,
328 pub inflight_requests: i64,
329 pub eligible: bool,
330 pub capabilities: Vec<String>,
331 pub ip: IpAddr,
332 pub client_version: String,
333 pub connection_direction: String,
334 pub last_response_time: Option<u64>,
335}
336
337#[derive(Debug, Clone)]
339pub enum ContactValidation {
340 Valid(Box<Contact>),
341 InvalidContact,
342 UnknownContact,
343 IpMismatch,
344}
345
346#[must_use = "dropping this permit immediately releases the peer's request slot"]
358pub struct RequestPermit {
359 peer_table: PeerTable,
360 peer_id: H256,
361}
362
363impl RequestPermit {
364 pub(crate) fn new(peer_table: PeerTable, peer_id: H256) -> Self {
365 Self {
366 peer_table,
367 peer_id,
368 }
369 }
370}
371
372impl std::fmt::Debug for RequestPermit {
373 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
374 f.debug_struct("RequestPermit")
375 .field("peer_id", &self.peer_id)
376 .finish_non_exhaustive()
377 }
378}
379
380impl Drop for RequestPermit {
381 fn drop(&mut self) {
382 let _ = self.peer_table.dec_requests(self.peer_id);
385 }
386}
387
388#[protocol]
389pub trait PeerTableServerProtocol: Send + Sync {
390 fn new_contacts(&self, nodes: Vec<Node>, protocol: DiscoveryProtocol)
392 -> Result<(), ActorError>;
393 fn new_contact_records(&self, node_records: Vec<NodeRecord>) -> Result<(), ActorError>;
394 fn new_connected_peer(
395 &self,
396 node: Node,
397 connection: PeerConnection,
398 capabilities: Vec<Capability>,
399 ) -> Result<(), ActorError>;
400 fn set_session_info(&self, node_id: H256, session: Session) -> Result<(), ActorError>;
401 fn remove_peer(&self, node_id: H256) -> Result<(), ActorError>;
402 fn dec_requests(&self, node_id: H256) -> Result<(), ActorError>;
403 fn set_unwanted(&self, node_id: H256) -> Result<(), ActorError>;
404 fn set_is_fork_id_valid(&self, node_id: H256, valid: bool) -> Result<(), ActorError>;
405 fn record_success(&self, node_id: H256) -> Result<(), ActorError>;
406 fn record_failure(&self, node_id: H256) -> Result<(), ActorError>;
407 fn record_critical_failure(&self, node_id: H256) -> Result<(), ActorError>;
408 fn record_ping_sent(&self, node_id: H256, ping_id: Bytes) -> Result<(), ActorError>;
409 fn record_pong_received(&self, node_id: H256, ping_id: Bytes) -> Result<(), ActorError>;
410 fn record_enr_request_sent(&self, node_id: H256, request_hash: H256) -> Result<(), ActorError>;
411 fn record_enr_response_received(
412 &self,
413 node_id: H256,
414 request_hash: H256,
415 record: NodeRecord,
416 ) -> Result<(), ActorError>;
417 fn set_disposable(&self, node_id: H256) -> Result<(), ActorError>;
418 fn mark_knows_us(&self, node_id: H256) -> Result<(), ActorError>;
419 fn prune_table(&self) -> Result<(), ActorError>;
420 fn shutdown(&self) -> Result<(), ActorError>;
421
422 fn peer_count(&self) -> Response<usize>;
424 fn peer_count_by_capabilities(&self, capabilities: Vec<Capability>) -> Response<usize>;
425 fn target_reached(&self) -> Response<bool>;
426 fn target_peers_reached(&self) -> Response<bool>;
427 fn target_peers_completion(&self) -> Response<f64>;
428 fn get_contact_to_initiate(&self) -> Response<Option<Box<Contact>>>;
429 fn get_contact_for_enr_lookup(&self) -> Response<Option<Box<Contact>>>;
430 fn get_closest_from_pool(&self, target: H256, count: usize) -> Response<Vec<(H256, Node)>>;
431 fn get_contact(&self, node_id: H256) -> Response<Option<Box<Contact>>>;
432 fn get_contact_to_revalidate(
433 &self,
434 revalidation_interval: Duration,
435 protocol: DiscoveryProtocol,
436 ) -> Response<Option<Box<Contact>>>;
437 fn get_best_peer(
438 &self,
439 capabilities: Vec<Capability>,
440 ) -> Response<Option<(H256, PeerConnection, RequestPermit)>>;
441 fn get_best_peer_excluding(
442 &self,
443 capabilities: Vec<Capability>,
444 excluded: Vec<H256>,
445 ) -> Response<Option<(H256, PeerConnection, RequestPermit)>>;
446 fn get_best_n_peers(
447 &self,
448 capabilities: Vec<Capability>,
449 n: usize,
450 ) -> Response<Vec<(H256, PeerConnection, RequestPermit)>>;
451 fn has_eligible_peer(&self, capabilities: Vec<Capability>) -> Response<bool>;
454 fn get_score(&self, node_id: H256) -> Response<i64>;
455 fn get_connected_nodes(&self) -> Response<Vec<Node>>;
456 fn get_peers_with_capabilities(&self)
457 -> Response<Vec<(H256, PeerConnection, Vec<Capability>)>>;
458 fn insert_if_new(&self, node: Node, protocol: DiscoveryProtocol) -> Response<bool>;
459 fn validate_contact(&self, node_id: H256, sender_ip: IpAddr) -> Response<ContactValidation>;
460 fn get_closest_nodes(&self, node_id: H256) -> Response<Vec<Node>>;
461 fn get_nodes_at_distances(&self, distances: Vec<u32>) -> Response<Vec<NodeRecord>>;
462 fn get_peers_data(&self) -> Response<Vec<PeerData>>;
463 fn get_random_peer(
464 &self,
465 capabilities: Vec<Capability>,
466 ) -> Response<Option<(H256, PeerConnection, RequestPermit)>>;
467 fn get_session_info(&self, node_id: H256) -> Response<Option<Session>>;
468 fn get_peer_diagnostics(&self) -> Response<Vec<PeerDiagnostics>>;
469 fn get_peer_connection(&self, peer_id: H256) -> Response<Option<PeerConnection>>;
470}
471
472#[derive(Debug)]
473pub struct PeerTableServer {
474 local_node_id: H256,
475 buckets: Vec<KBucket>,
476 peers: IndexMap<H256, PeerData>,
477 already_tried_peers: FxHashSet<H256>,
478 target_peers: usize,
479 store: Store,
480 sessions: FxHashMap<H256, Session>,
483 connection_pool: IndexMap<H256, Node>,
489}
490
491#[actor(protocol = PeerTableServerProtocol)]
492impl PeerTableServer {
493 pub fn spawn(local_node_id: H256, target_peers: usize, store: Store) -> PeerTable {
494 PeerTableServer::new(local_node_id, target_peers, store).start()
495 }
496
497 pub(crate) fn new(local_node_id: H256, target_peers: usize, store: Store) -> Self {
498 Self {
499 local_node_id,
500 buckets: vec![KBucket::default(); NUMBER_OF_BUCKETS],
501 peers: Default::default(),
502 already_tried_peers: Default::default(),
503 target_peers,
504 store,
505 sessions: Default::default(),
506 connection_pool: IndexMap::with_capacity(MAX_CONNECTION_POOL_SIZE),
507 }
508 }
509
510 #[started]
511 async fn started(&mut self, ctx: &Context<Self>) {
512 send_message_on(
513 ctx.clone(),
514 tokio::signal::ctrl_c(),
515 peer_table_server_protocol::Shutdown,
516 );
517 }
518
519 #[send_handler]
522 async fn handle_new_contacts(
523 &mut self,
524 msg: peer_table_server_protocol::NewContacts,
525 _ctx: &Context<Self>,
526 ) {
527 self.do_new_contacts(msg.nodes, msg.protocol).await;
528 }
529
530 #[send_handler]
531 async fn handle_new_contact_records(
532 &mut self,
533 msg: peer_table_server_protocol::NewContactRecords,
534 _ctx: &Context<Self>,
535 ) {
536 self.do_new_contact_records(msg.node_records).await;
537 }
538
539 #[send_handler]
540 async fn handle_new_connected_peer(
541 &mut self,
542 msg: peer_table_server_protocol::NewConnectedPeer,
543 _ctx: &Context<Self>,
544 ) {
545 let new_peer_id = msg.node.node_id();
546 let new_peer = PeerData::new(msg.node, None, Some(msg.connection), msg.capabilities);
547 self.peers.insert(new_peer_id, new_peer);
548 }
549
550 #[send_handler]
551 async fn handle_set_session_info(
552 &mut self,
553 msg: peer_table_server_protocol::SetSessionInfo,
554 _ctx: &Context<Self>,
555 ) {
556 self.sessions.insert(msg.node_id, msg.session.clone());
558 if let Some(contact) = self.get_contact_mut(&msg.node_id) {
560 contact.session = Some(msg.session);
561 }
562 }
563
564 #[send_handler]
565 async fn handle_remove_peer(
566 &mut self,
567 msg: peer_table_server_protocol::RemovePeer,
568 _ctx: &Context<Self>,
569 ) {
570 self.peers.swap_remove(&msg.node_id);
571 self.sessions.remove(&msg.node_id);
574 }
575
576 #[send_handler]
577 async fn handle_dec_requests(
578 &mut self,
579 msg: peer_table_server_protocol::DecRequests,
580 _ctx: &Context<Self>,
581 ) {
582 self.peers.entry(msg.node_id).and_modify(|peer_data| {
583 if peer_data.requests <= 0 {
584 tracing::debug!(
588 peer_id = ?msg.node_id,
589 requests = peer_data.requests,
590 "dec_requests with counter already <= 0",
591 );
592 }
593 peer_data.requests = peer_data.requests.saturating_sub(1).max(0)
594 });
595 }
596
597 #[send_handler]
598 async fn handle_set_unwanted(
599 &mut self,
600 msg: peer_table_server_protocol::SetUnwanted,
601 _ctx: &Context<Self>,
602 ) {
603 if let Some(contact) = self.get_contact_mut(&msg.node_id) {
604 contact.unwanted = true;
605 }
606 }
607
608 #[send_handler]
609 async fn handle_set_is_fork_id_valid(
610 &mut self,
611 msg: peer_table_server_protocol::SetIsForkIdValid,
612 _ctx: &Context<Self>,
613 ) {
614 if let Some(contact) = self.get_contact_mut(&msg.node_id) {
615 contact.is_fork_id_valid = Some(msg.valid);
616 }
617 }
618
619 #[send_handler]
620 async fn handle_record_success(
621 &mut self,
622 msg: peer_table_server_protocol::RecordSuccess,
623 _ctx: &Context<Self>,
624 ) {
625 let now = std::time::SystemTime::now()
626 .duration_since(std::time::UNIX_EPOCH)
627 .unwrap_or_default()
628 .as_secs();
629 self.peers.entry(msg.node_id).and_modify(|peer_data| {
630 peer_data.score = (peer_data.score + 1).min(MAX_SCORE);
631 peer_data.last_response_time = Some(now);
632 });
633 }
634
635 #[send_handler]
636 async fn handle_record_failure(
637 &mut self,
638 msg: peer_table_server_protocol::RecordFailure,
639 _ctx: &Context<Self>,
640 ) {
641 self.peers
642 .entry(msg.node_id)
643 .and_modify(|peer_data| peer_data.score = (peer_data.score - 1).max(MIN_SCORE));
644 }
645
646 #[send_handler]
647 async fn handle_record_critical_failure(
648 &mut self,
649 msg: peer_table_server_protocol::RecordCriticalFailure,
650 _ctx: &Context<Self>,
651 ) {
652 self.peers
653 .entry(msg.node_id)
654 .and_modify(|peer_data| peer_data.score = MIN_SCORE_CRITICAL);
655 }
656
657 #[send_handler]
658 async fn handle_record_ping_sent(
659 &mut self,
660 msg: peer_table_server_protocol::RecordPingSent,
661 _ctx: &Context<Self>,
662 ) {
663 if let Some(contact) = self.get_contact_mut(&msg.node_id) {
664 contact.record_ping_sent(msg.ping_id);
665 }
666 }
667
668 #[send_handler]
669 async fn handle_record_pong_received(
670 &mut self,
671 msg: peer_table_server_protocol::RecordPongReceived,
672 _ctx: &Context<Self>,
673 ) {
674 if let Some(contact) = self.get_contact_mut(&msg.node_id)
675 && contact
676 .ping_id
677 .as_ref()
678 .map(|value| *value == msg.ping_id)
679 .unwrap_or(false)
680 {
681 contact.ping_id = None;
682 }
683 }
684
685 #[send_handler]
686 async fn handle_record_enr_request_sent(
687 &mut self,
688 msg: peer_table_server_protocol::RecordEnrRequestSent,
689 _ctx: &Context<Self>,
690 ) {
691 if let Some(contact) = self.get_contact_mut(&msg.node_id) {
692 contact.record_enr_request_sent(msg.request_hash);
693 }
694 }
695
696 #[send_handler]
697 async fn handle_record_enr_response_received(
698 &mut self,
699 msg: peer_table_server_protocol::RecordEnrResponseReceived,
700 _ctx: &Context<Self>,
701 ) {
702 if let Some(contact) = self.get_contact_mut(&msg.node_id) {
703 contact.record_enr_response_received(msg.request_hash, msg.record);
704 }
705 }
706
707 #[send_handler]
708 async fn handle_set_disposable(
709 &mut self,
710 msg: peer_table_server_protocol::SetDisposable,
711 _ctx: &Context<Self>,
712 ) {
713 if let Some(contact) = self.get_contact_mut(&msg.node_id) {
714 contact.disposable = true;
715 }
716 }
717
718 #[send_handler]
719 async fn handle_mark_knows_us(
720 &mut self,
721 msg: peer_table_server_protocol::MarkKnowsUs,
722 _ctx: &Context<Self>,
723 ) {
724 if let Some(contact) = self.get_contact_mut(&msg.node_id) {
725 contact.knows_us = true;
726 }
727 }
728
729 #[send_handler]
730 async fn handle_prune_table(
731 &mut self,
732 _msg: peer_table_server_protocol::PruneTable,
733 _ctx: &Context<Self>,
734 ) {
735 self.prune();
736 }
737
738 #[send_handler]
739 async fn handle_shutdown(
740 &mut self,
741 _msg: peer_table_server_protocol::Shutdown,
742 ctx: &Context<Self>,
743 ) {
744 ctx.stop();
745 }
746
747 #[request_handler]
750 async fn handle_peer_count(
751 &mut self,
752 _msg: peer_table_server_protocol::PeerCount,
753 _ctx: &Context<Self>,
754 ) -> usize {
755 self.peers.len()
756 }
757
758 #[request_handler]
759 async fn handle_peer_count_by_capabilities(
760 &mut self,
761 msg: peer_table_server_protocol::PeerCountByCapabilities,
762 _ctx: &Context<Self>,
763 ) -> usize {
764 self.do_peer_count_by_capabilities(msg.capabilities)
765 }
766
767 #[request_handler]
768 async fn handle_target_reached(
769 &mut self,
770 _msg: peer_table_server_protocol::TargetReached,
771 _ctx: &Context<Self>,
772 ) -> bool {
773 self.peers.len() >= self.target_peers
774 }
775
776 #[request_handler]
777 async fn handle_target_peers_reached(
778 &mut self,
779 _msg: peer_table_server_protocol::TargetPeersReached,
780 _ctx: &Context<Self>,
781 ) -> bool {
782 self.peers.len() >= self.target_peers
783 }
784
785 #[request_handler]
786 async fn handle_target_peers_completion(
787 &mut self,
788 _msg: peer_table_server_protocol::TargetPeersCompletion,
789 _ctx: &Context<Self>,
790 ) -> f64 {
791 self.peers.len() as f64 / self.target_peers as f64
792 }
793
794 #[request_handler]
795 async fn handle_get_contact_to_initiate(
796 &mut self,
797 _msg: peer_table_server_protocol::GetContactToInitiate,
798 _ctx: &Context<Self>,
799 ) -> Option<Box<Contact>> {
800 self.do_get_contact_to_initiate().map(Box::new)
801 }
802
803 #[request_handler]
804 async fn handle_get_closest_from_pool(
805 &mut self,
806 msg: peer_table_server_protocol::GetClosestFromPool,
807 _ctx: &Context<Self>,
808 ) -> Vec<(H256, Node)> {
809 self.do_get_closest_from_pool(msg.target, msg.count)
810 }
811
812 #[request_handler]
813 async fn handle_get_contact_for_enr_lookup(
814 &mut self,
815 _msg: peer_table_server_protocol::GetContactForEnrLookup,
816 _ctx: &Context<Self>,
817 ) -> Option<Box<Contact>> {
818 self.do_get_contact_for_enr_lookup().map(Box::new)
819 }
820
821 #[request_handler]
822 async fn handle_get_contact(
823 &mut self,
824 msg: peer_table_server_protocol::GetContact,
825 _ctx: &Context<Self>,
826 ) -> Option<Box<Contact>> {
827 self.get_contact(&msg.node_id).cloned().map(Box::new)
828 }
829
830 #[request_handler]
831 async fn handle_get_contact_to_revalidate(
832 &mut self,
833 msg: peer_table_server_protocol::GetContactToRevalidate,
834 _ctx: &Context<Self>,
835 ) -> Option<Box<Contact>> {
836 self.do_get_contact_to_revalidate(msg.revalidation_interval, msg.protocol)
837 }
838
839 #[request_handler]
840 async fn handle_get_best_peer(
841 &mut self,
842 msg: peer_table_server_protocol::GetBestPeer,
843 ctx: &Context<Self>,
844 ) -> Option<(H256, PeerConnection, RequestPermit)> {
845 let (peer_id, conn) = self.do_get_best_peer(&msg.capabilities)?;
846 self.peers
847 .get_mut(&peer_id)
848 .expect("peer returned by do_get_best_peer must be present in self.peers")
849 .requests += 1;
850 Some((peer_id, conn, RequestPermit::new(ctx.actor_ref(), peer_id)))
851 }
852
853 #[request_handler]
854 async fn handle_get_best_peer_excluding(
855 &mut self,
856 msg: peer_table_server_protocol::GetBestPeerExcluding,
857 ctx: &Context<Self>,
858 ) -> Option<(H256, PeerConnection, RequestPermit)> {
859 let (peer_id, conn) = self.do_get_best_peer_excluding(&msg.capabilities, &msg.excluded)?;
860 self.peers
861 .get_mut(&peer_id)
862 .expect("peer returned by do_get_best_peer_excluding must be present in self.peers")
863 .requests += 1;
864 Some((peer_id, conn, RequestPermit::new(ctx.actor_ref(), peer_id)))
865 }
866
867 #[request_handler]
868 async fn handle_get_best_n_peers(
869 &mut self,
870 msg: peer_table_server_protocol::GetBestNPeers,
871 ctx: &Context<Self>,
872 ) -> Vec<(H256, PeerConnection, RequestPermit)> {
873 let picks = self.do_get_best_n_peers(&msg.capabilities, msg.n);
874 let mut out = Vec::with_capacity(picks.len());
875 for (peer_id, conn) in picks {
876 self.peers
877 .get_mut(&peer_id)
878 .expect("peer returned by do_get_best_n_peers must be present in self.peers")
879 .requests += 1;
880 out.push((peer_id, conn, RequestPermit::new(ctx.actor_ref(), peer_id)));
881 }
882 out
883 }
884
885 #[request_handler]
886 async fn handle_has_eligible_peer(
887 &mut self,
888 msg: peer_table_server_protocol::HasEligiblePeer,
889 _ctx: &Context<Self>,
890 ) -> bool {
891 self.peers.values().any(|peer_data| {
892 peer_data.connection.is_some()
893 && self.can_try_more_requests(&peer_data.score, &peer_data.requests)
894 && msg
895 .capabilities
896 .iter()
897 .any(|cap| peer_data.supported_capabilities.contains(cap))
898 })
899 }
900
901 #[request_handler]
902 async fn handle_get_score(
903 &mut self,
904 msg: peer_table_server_protocol::GetScore,
905 _ctx: &Context<Self>,
906 ) -> i64 {
907 self.peers
908 .get(&msg.node_id)
909 .map(|peer_data| peer_data.score)
910 .unwrap_or_default()
911 }
912
913 #[request_handler]
914 async fn handle_get_connected_nodes(
915 &mut self,
916 _msg: peer_table_server_protocol::GetConnectedNodes,
917 _ctx: &Context<Self>,
918 ) -> Vec<Node> {
919 self.peers
920 .values()
921 .map(|peer_data| peer_data.node.clone())
922 .collect()
923 }
924
925 #[request_handler]
926 async fn handle_get_peers_with_capabilities(
927 &mut self,
928 _msg: peer_table_server_protocol::GetPeersWithCapabilities,
929 _ctx: &Context<Self>,
930 ) -> Vec<(H256, PeerConnection, Vec<Capability>)> {
931 self.peers
932 .iter()
933 .filter_map(|(peer_id, peer_data)| {
934 peer_data.connection.clone().map(|connection| {
935 (
936 *peer_id,
937 connection,
938 peer_data.supported_capabilities.clone(),
939 )
940 })
941 })
942 .collect()
943 }
944
945 #[request_handler]
946 async fn handle_insert_if_new(
947 &mut self,
948 msg: peer_table_server_protocol::InsertIfNew,
949 _ctx: &Context<Self>,
950 ) -> bool {
951 let node_id = msg.node.node_id();
952 self.insert_to_connection_pool(node_id, msg.node.clone());
954 if self.contact_exists(&node_id) {
955 return false;
956 }
957 let contact = Contact::new(msg.node, msg.protocol);
958 self.insert_contact(node_id, contact);
962 METRICS.record_new_discovery().await;
963 true
964 }
965
966 #[request_handler]
967 async fn handle_validate_contact(
968 &mut self,
969 msg: peer_table_server_protocol::ValidateContact,
970 _ctx: &Context<Self>,
971 ) -> ContactValidation {
972 self.do_validate_contact(msg.node_id, msg.sender_ip)
973 }
974
975 #[request_handler]
976 async fn handle_get_closest_nodes(
977 &mut self,
978 msg: peer_table_server_protocol::GetClosestNodes,
979 _ctx: &Context<Self>,
980 ) -> Vec<Node> {
981 self.do_get_closest_nodes(msg.node_id)
982 }
983
984 #[request_handler]
985 async fn handle_get_nodes_at_distances(
986 &mut self,
987 msg: peer_table_server_protocol::GetNodesAtDistances,
988 _ctx: &Context<Self>,
989 ) -> Vec<NodeRecord> {
990 self.do_get_nodes_at_distances(&msg.distances)
991 }
992
993 #[request_handler]
994 async fn handle_get_peers_data(
995 &mut self,
996 _msg: peer_table_server_protocol::GetPeersData,
997 _ctx: &Context<Self>,
998 ) -> Vec<PeerData> {
999 self.peers.values().cloned().collect()
1000 }
1001
1002 #[request_handler]
1003 async fn handle_get_random_peer(
1004 &mut self,
1005 msg: peer_table_server_protocol::GetRandomPeer,
1006 ctx: &Context<Self>,
1007 ) -> Option<(H256, PeerConnection, RequestPermit)> {
1008 let (peer_id, conn) = self.do_get_random_peer(msg.capabilities)?;
1009 self.peers
1010 .get_mut(&peer_id)
1011 .expect("peer returned by do_get_random_peer must be present in self.peers")
1012 .requests += 1;
1013 Some((peer_id, conn, RequestPermit::new(ctx.actor_ref(), peer_id)))
1014 }
1015
1016 #[request_handler]
1017 async fn handle_get_session_info(
1018 &mut self,
1019 msg: peer_table_server_protocol::GetSessionInfo,
1020 _ctx: &Context<Self>,
1021 ) -> Option<Session> {
1022 self.sessions
1024 .get(&msg.node_id)
1025 .cloned()
1026 .or_else(|| self.get_contact(&msg.node_id)?.session.clone())
1027 }
1028
1029 #[request_handler]
1030 async fn handle_get_peer_connection(
1031 &mut self,
1032 msg: peer_table_server_protocol::GetPeerConnection,
1033 _ctx: &Context<Self>,
1034 ) -> Option<PeerConnection> {
1035 self.peers
1036 .get(&msg.peer_id)
1037 .and_then(|peer_data| peer_data.connection.clone())
1038 }
1039
1040 #[request_handler]
1041 async fn handle_get_peer_diagnostics(
1042 &mut self,
1043 _msg: peer_table_server_protocol::GetPeerDiagnostics,
1044 _ctx: &Context<Self>,
1045 ) -> Vec<PeerDiagnostics> {
1046 self.peers
1047 .iter()
1048 .map(|(id, peer_data)| PeerDiagnostics {
1049 peer_id: *id,
1050 score: peer_data.score,
1051 inflight_requests: peer_data.requests,
1052 eligible: self.can_try_more_requests(&peer_data.score, &peer_data.requests),
1053 capabilities: peer_data
1054 .supported_capabilities
1055 .iter()
1056 .map(|c| format!("{}/{}", c.protocol(), c.version))
1057 .collect(),
1058 ip: peer_data.node.ip,
1059 client_version: peer_data.node.version.clone().unwrap_or_default(),
1060 connection_direction: if peer_data.is_connection_inbound {
1061 "inbound".to_string()
1062 } else {
1063 "outbound".to_string()
1064 },
1065 last_response_time: peer_data.last_response_time,
1066 })
1067 .collect()
1068 }
1069
1070 fn bucket_for(&self, node_id: &H256) -> Option<usize> {
1076 bucket_index(&self.local_node_id, node_id)
1077 }
1078
1079 fn get_contact(&self, node_id: &H256) -> Option<&Contact> {
1081 let idx = self.bucket_for(node_id)?;
1082 self.buckets[idx].get_any(node_id)
1083 }
1084
1085 fn get_contact_mut(&mut self, node_id: &H256) -> Option<&mut Contact> {
1087 let idx = self.bucket_for(node_id)?;
1088 self.buckets[idx].get_mut(node_id)
1089 }
1090
1091 fn contact_exists(&self, node_id: &H256) -> bool {
1093 let Some(idx) = self.bucket_for(node_id) else {
1094 return false;
1095 };
1096 self.buckets[idx].contains(node_id)
1097 }
1098
1099 fn insert_contact(&mut self, node_id: H256, contact: Contact) -> bool {
1103 #[cfg(feature = "metrics")]
1104 let start = std::time::Instant::now();
1105
1106 let Some(idx) = self.bucket_for(&node_id) else {
1107 return false;
1108 };
1109 let result = self.buckets[idx].insert(node_id, contact);
1110
1111 #[cfg(feature = "metrics")]
1112 {
1113 use ethrex_metrics::p2p::METRICS_P2P;
1114 METRICS_P2P.observe_insert_contact_duration(start.elapsed().as_secs_f64());
1115 }
1116
1117 result
1118 }
1119
1120 fn insert_to_connection_pool(&mut self, node_id: H256, node: Node) {
1123 if self.connection_pool.contains_key(&node_id) {
1124 return;
1125 }
1126 if self.connection_pool.len() >= MAX_CONNECTION_POOL_SIZE {
1127 self.connection_pool.shift_remove_index(0);
1128 }
1129 self.connection_pool.insert(node_id, node);
1130 }
1131
1132 fn get_contact_or_replacement(&self, node_id: &H256) -> Option<&Contact> {
1134 let idx = self.bucket_for(node_id)?;
1135 self.buckets[idx].get_any(node_id)
1136 }
1137
1138 fn get_contact_or_replacement_mut(&mut self, node_id: &H256) -> Option<&mut Contact> {
1140 let idx = self.bucket_for(node_id)?;
1141 let bucket = &mut self.buckets[idx];
1142 if let Some(pos) = bucket.contacts.iter().position(|(id, _)| id == node_id) {
1145 return Some(&mut bucket.contacts[pos].1);
1146 }
1147 if let Some(pos) = bucket.replacements.iter().position(|(id, _)| id == node_id) {
1148 return Some(&mut bucket.replacements[pos].1);
1149 }
1150 None
1151 }
1152
1153 fn iter_contacts(&self) -> impl Iterator<Item = (&H256, &Contact)> {
1155 self.buckets.iter().flat_map(|bucket| {
1156 bucket
1157 .contacts
1158 .iter()
1159 .chain(bucket.replacements.iter())
1160 .map(|(id, c)| (id, c))
1161 })
1162 }
1163
1164 fn weight_peer(&self, score: &i64, requests: &i64) -> i64 {
1167 score * SCORE_WEIGHT - requests * REQUESTS_WEIGHT
1168 }
1169
1170 fn can_try_more_requests(&self, score: &i64, requests: &i64) -> bool {
1171 let score_ratio = (score - MIN_SCORE) as f64 / (MAX_SCORE - MIN_SCORE) as f64;
1172 let max_requests = (MAX_CONCURRENT_REQUESTS_PER_PEER as f64 * score_ratio).max(1.0);
1173 (*requests as f64) < max_requests
1174 }
1175
1176 fn do_get_best_peer(&self, capabilities: &[Capability]) -> Option<(H256, PeerConnection)> {
1177 self.do_get_best_peer_excluding(capabilities, &[])
1178 }
1179
1180 fn do_get_best_peer_excluding(
1183 &self,
1184 capabilities: &[Capability],
1185 excluded: &[H256],
1186 ) -> Option<(H256, PeerConnection)> {
1187 self.peers
1188 .iter()
1189 .filter_map(|(id, peer_data)| {
1190 if excluded.contains(id)
1191 || !self.can_try_more_requests(&peer_data.score, &peer_data.requests)
1192 || !capabilities
1193 .iter()
1194 .any(|cap| peer_data.supported_capabilities.contains(cap))
1195 {
1196 None
1197 } else {
1198 let connection = peer_data.connection.clone()?;
1199 Some((*id, peer_data.score, peer_data.requests, connection))
1200 }
1201 })
1202 .max_by_key(|(_, score, reqs, _)| self.weight_peer(score, reqs))
1203 .map(|(k, _, _, v)| (k, v))
1204 }
1205
1206 fn do_get_best_n_peers(
1212 &self,
1213 capabilities: &[Capability],
1214 n: usize,
1215 ) -> Vec<(H256, PeerConnection)> {
1216 let mut candidates: Vec<(H256, i64, i64, PeerConnection)> = self
1217 .peers
1218 .iter()
1219 .filter_map(|(id, peer_data)| {
1220 if !self.can_try_more_requests(&peer_data.score, &peer_data.requests)
1221 || !capabilities
1222 .iter()
1223 .any(|cap| peer_data.supported_capabilities.contains(cap))
1224 {
1225 None
1226 } else {
1227 let connection = peer_data.connection.clone()?;
1228 Some((*id, peer_data.score, peer_data.requests, connection))
1229 }
1230 })
1231 .collect();
1232
1233 candidates.sort_by_key(|(_, score, reqs, _)| -self.weight_peer(score, reqs));
1234 candidates
1235 .into_iter()
1236 .take(n)
1237 .map(|(id, _, _, conn)| (id, conn))
1238 .collect()
1239 }
1240
1241 fn prune(&mut self) {
1248 for bucket in &mut self.buckets {
1249 let main_disposable: Vec<H256> = bucket
1251 .contacts
1252 .iter()
1253 .filter(|(_, c)| c.disposable)
1254 .map(|(id, _)| *id)
1255 .collect();
1256
1257 for node_id in main_disposable {
1259 bucket.remove_and_promote(&node_id);
1260 }
1261
1262 bucket.replacements.retain(|(_, c)| !c.disposable);
1265 }
1266 }
1267
1268 fn do_get_contact_to_initiate(&mut self) -> Option<Contact> {
1269 let pool_len = self.connection_pool.len();
1273 if pool_len == 0 {
1274 return None;
1275 }
1276
1277 let start = rand::random::<usize>() % pool_len;
1278 for offset in 0..pool_len {
1279 let idx = (start + offset) % pool_len;
1280 let Some((node_id, node)) = self.connection_pool.get_index(idx) else {
1281 continue;
1282 };
1283 let node_id = *node_id;
1284
1285 if self.peers.contains_key(&node_id)
1286 || self.already_tried_peers.contains(&node_id)
1287 || self
1288 .get_contact_or_replacement(&node_id)
1289 .map(|c| !c.knows_us || c.unwanted || c.is_fork_id_valid == Some(false))
1290 .unwrap_or(false)
1291 {
1292 continue;
1293 }
1294
1295 let node = node.clone();
1296 self.already_tried_peers.insert(node_id);
1297 let contact = self
1298 .get_contact_or_replacement(&node_id)
1299 .cloned()
1300 .unwrap_or_else(|| Contact::new(node, DiscoveryProtocol::Discv4));
1301 return Some(contact);
1302 }
1303
1304 tracing::trace!("Resetting list of tried peers.");
1306 self.already_tried_peers.clear();
1307 None
1308 }
1309
1310 fn do_get_closest_from_pool(&self, target: H256, count: usize) -> Vec<(H256, Node)> {
1312 let mut nodes: Vec<(H256, Node, H256)> = Vec::with_capacity(count);
1313
1314 for (node_id, node) in &self.connection_pool {
1315 let dist = xor_distance(&target, node_id);
1316 if nodes.len() < count {
1317 nodes.push((*node_id, node.clone(), dist));
1318 } else if let Some((farthest_idx, _)) =
1319 nodes.iter().enumerate().max_by_key(|(_, (_, _, d))| *d)
1320 && dist < nodes[farthest_idx].2
1321 {
1322 nodes[farthest_idx] = (*node_id, node.clone(), dist);
1323 }
1324 }
1325
1326 nodes.sort_by(|a, b| a.2.cmp(&b.2));
1327 nodes.into_iter().map(|(id, node, _)| (id, node)).collect()
1328 }
1329
1330 fn do_get_contact_for_enr_lookup(&mut self) -> Option<Contact> {
1332 self.iter_contacts()
1333 .filter(|(_, c)| {
1334 c.is_discv4
1335 && c.was_validated()
1336 && !c.has_pending_enr_request()
1337 && c.record.is_none()
1338 && !c.disposable
1339 })
1340 .map(|(_, c)| c)
1341 .collect::<Vec<_>>()
1342 .choose(&mut rand::rngs::OsRng)
1343 .cloned()
1344 .cloned()
1345 }
1346
1347 fn do_get_contact_to_revalidate(
1348 &self,
1349 revalidation_interval: Duration,
1350 protocol: DiscoveryProtocol,
1351 ) -> Option<Box<Contact>> {
1352 self.iter_contacts()
1353 .filter(|(_, c)| {
1354 c.supports_protocol(protocol)
1355 && Self::is_validation_needed(c, revalidation_interval)
1356 })
1357 .map(|(_, c)| c)
1358 .choose(&mut rand::rngs::OsRng)
1359 .cloned()
1360 .map(Box::new)
1361 }
1362
1363 fn do_validate_contact(&self, node_id: H256, sender_ip: IpAddr) -> ContactValidation {
1364 let Some(contact) = self.get_contact(&node_id) else {
1365 return ContactValidation::UnknownContact;
1366 };
1367 if !contact.was_validated() {
1368 return ContactValidation::InvalidContact;
1369 }
1370
1371 if sender_ip != contact.node.ip {
1374 return ContactValidation::IpMismatch;
1375 }
1376 ContactValidation::Valid(Box::new(contact.clone()))
1377 }
1378
1379 fn do_get_closest_nodes(&self, node_id: H256) -> Vec<Node> {
1381 #[cfg(feature = "metrics")]
1382 let scan_start = std::time::Instant::now();
1383
1384 let mut nodes: Vec<(Node, H256)> = vec![];
1385
1386 for (contact_id, contact) in self.iter_contacts() {
1387 let dist = xor_distance(&node_id, contact_id);
1388 if nodes.len() < MAX_NODES_IN_NEIGHBORS_PACKET {
1389 nodes.push((contact.node.clone(), dist));
1390 } else if let Some((farthest_idx, _)) =
1391 nodes.iter().enumerate().max_by_key(|(_, (_, d))| *d)
1392 && dist < nodes[farthest_idx].1
1393 {
1394 nodes[farthest_idx] = (contact.node.clone(), dist);
1395 }
1396 }
1397
1398 #[cfg(feature = "metrics")]
1399 {
1400 use ethrex_metrics::p2p::METRICS_P2P;
1401 METRICS_P2P.observe_iter_contacts_duration(scan_start.elapsed().as_secs_f64());
1402 }
1403
1404 nodes.into_iter().map(|(node, _)| node).collect()
1405 }
1406
1407 fn do_get_nodes_at_distances(&self, distances: &[u32]) -> Vec<NodeRecord> {
1412 self.iter_contacts()
1413 .filter_map(|(contact_id, contact)| {
1414 let dist = distance(&self.local_node_id, contact_id) as u32;
1415 if distances.contains(&dist) {
1416 contact.record.clone()
1417 } else {
1418 None
1419 }
1420 })
1421 .take(MAX_ENRS_PER_FINDNODE_RESPONSE)
1422 .collect()
1423 }
1424
1425 async fn do_new_contacts(&mut self, nodes: Vec<Node>, protocol: DiscoveryProtocol) {
1426 for node in nodes {
1427 let node_id = node.node_id();
1428 if node_id == self.local_node_id {
1429 continue;
1430 }
1431 #[cfg(feature = "metrics")]
1432 let insert_start = std::time::Instant::now();
1433
1434 self.insert_to_connection_pool(node_id, node.clone());
1436
1437 if self.contact_exists(&node_id) {
1438 if let Some(contact) = self.get_contact_or_replacement_mut(&node_id) {
1440 contact.add_protocol(protocol);
1441 }
1442 } else {
1443 let contact = Contact::new(node, protocol);
1444 self.insert_contact(node_id, contact);
1445 METRICS.record_new_discovery().await;
1446 }
1447
1448 #[cfg(feature = "metrics")]
1449 {
1450 use ethrex_metrics::p2p::METRICS_P2P;
1451 METRICS_P2P.observe_insert_contact_duration(insert_start.elapsed().as_secs_f64());
1452 }
1453 }
1454 }
1455
1456 async fn do_new_contact_records(&mut self, node_records: Vec<NodeRecord>) {
1457 for node_record in node_records {
1458 if !node_record.verify_signature() {
1459 continue;
1460 }
1461 if let Ok(node) = Node::from_enr(&node_record) {
1462 let node_id = node.node_id();
1463 if node_id == self.local_node_id {
1464 continue;
1465 }
1466
1467 self.insert_to_connection_pool(node_id, node.clone());
1469
1470 if self.contact_exists(&node_id) {
1471 let should_update = self
1474 .get_contact_or_replacement(&node_id)
1475 .map(|c| match c.record.as_ref() {
1476 None => true,
1477 Some(r) => node_record.seq > r.seq,
1478 })
1479 .unwrap_or(false);
1480 let is_fork_id_valid = if should_update {
1481 Self::evaluate_fork_id(&node_record, &self.store).await
1482 } else {
1483 None
1484 };
1485 if let Some(contact) = self.get_contact_or_replacement_mut(&node_id) {
1486 contact.add_protocol(DiscoveryProtocol::Discv5);
1487 if should_update {
1488 if contact.node.ip != node.ip || contact.node.udp_port != node.udp_port
1489 {
1490 contact.validation_timestamp = None;
1491 contact.ping_id = None;
1492 }
1493 contact.node = node;
1494 contact.record = Some(node_record);
1495 contact.is_fork_id_valid = is_fork_id_valid;
1496 }
1497 }
1498 } else {
1499 let is_fork_id_valid = Self::evaluate_fork_id(&node_record, &self.store).await;
1500 let mut contact = Contact::new(node, DiscoveryProtocol::Discv5);
1501 contact.is_fork_id_valid = is_fork_id_valid;
1502 contact.record = Some(node_record);
1503 self.insert_contact(node_id, contact);
1504 METRICS.record_new_discovery().await;
1505 }
1506 }
1507 }
1508 }
1509
1510 async fn evaluate_fork_id(record: &NodeRecord, store: &Store) -> Option<bool> {
1511 if let Some(remote_fork_id) = record.get_fork_id() {
1512 backend::is_fork_id_valid(store, remote_fork_id)
1513 .await
1514 .ok()
1515 .or(Some(false))
1516 } else {
1517 Some(false)
1518 }
1519 }
1520
1521 fn do_peer_count_by_capabilities(&self, capabilities: Vec<Capability>) -> usize {
1522 self.peers
1523 .values()
1524 .filter(|peer_data| {
1525 capabilities
1526 .iter()
1527 .any(|cap| peer_data.supported_capabilities.contains(cap))
1528 })
1529 .count()
1530 }
1531
1532 fn do_get_random_peer(&self, capabilities: Vec<Capability>) -> Option<(H256, PeerConnection)> {
1533 let peers: Vec<(H256, &PeerConnection, i64)> = self
1534 .peers
1535 .iter()
1536 .filter_map(|(node_id, peer_data)| {
1537 if !capabilities
1538 .iter()
1539 .any(|cap| peer_data.supported_capabilities.contains(cap))
1540 {
1541 return None;
1542 }
1543 peer_data
1544 .connection
1545 .as_ref()
1546 .map(|connection| (*node_id, connection, peer_data.score))
1547 })
1548 .collect();
1549 if peers.is_empty() {
1550 return None;
1551 }
1552 let weights: Vec<u64> = peers
1554 .iter()
1555 .map(|(_, _, score)| (score.max(&MIN_SCORE_CRITICAL) - MIN_SCORE_CRITICAL + 1) as u64)
1556 .collect();
1557 let dist = WeightedIndex::new(&weights).ok()?;
1558 let idx = dist.sample(&mut rand::rngs::OsRng);
1559 Some((peers[idx].0, peers[idx].1.clone()))
1560 }
1561
1562 fn is_validation_needed(contact: &Contact, revalidation_interval: Duration) -> bool {
1563 if contact.disposable {
1564 return false;
1565 }
1566
1567 let sent_ping_ttl = Duration::from_secs(30);
1568
1569 if contact.has_pending_ping() {
1570 contact
1572 .validation_timestamp
1573 .map(|ts| Instant::now().saturating_duration_since(ts) > sent_ping_ttl)
1574 .unwrap_or(false)
1575 } else {
1576 !contact.was_validated()
1578 || contact
1579 .validation_timestamp
1580 .map(|ts| Instant::now().saturating_duration_since(ts) > revalidation_interval)
1581 .unwrap_or(false)
1582 }
1583 }
1584}
1585
1586pub type PeerTable = ActorRef<PeerTableServer>;
1587
1588#[cfg(test)]
1589mod tests {
1590 use super::*;
1591 use ethrex_common::H512;
1592 use std::net::Ipv4Addr;
1593
1594 fn dummy_contact(seed: u8) -> (H256, Contact) {
1596 let pk = H512::from_low_u64_be(seed as u64 + 1);
1597 let node = Node::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, seed)), 30303, 30303, pk);
1598 let node_id = node.node_id();
1599 let contact = Contact::new(node, DiscoveryProtocol::Discv4);
1600 (node_id, contact)
1601 }
1602
1603 #[test]
1606 fn insert_into_empty_bucket() {
1607 let mut bucket = KBucket::default();
1608 let (id, contact) = dummy_contact(1);
1609 assert!(bucket.insert(id, contact));
1610 assert_eq!(bucket.contacts.len(), 1);
1611 assert!(bucket.replacements.is_empty());
1612 }
1613
1614 #[test]
1615 fn insert_fills_bucket_then_goes_to_replacements() {
1616 let mut bucket = KBucket::default();
1617
1618 for i in 0..MAX_NODES_PER_BUCKET as u8 {
1620 let (id, contact) = dummy_contact(i);
1621 assert!(bucket.insert(id, contact), "contact {i} should go to main");
1622 }
1623 assert_eq!(bucket.contacts.len(), MAX_NODES_PER_BUCKET);
1624
1625 let (id, contact) = dummy_contact(200);
1627 assert!(!bucket.insert(id, contact));
1628 assert_eq!(bucket.contacts.len(), MAX_NODES_PER_BUCKET);
1629 assert_eq!(bucket.replacements.len(), 1);
1630 }
1631
1632 #[test]
1635 fn contains_checks_main_and_replacement() {
1636 let mut bucket = KBucket::default();
1637
1638 let (id_main, contact_main) = dummy_contact(1);
1639 bucket.insert(id_main, contact_main);
1640 assert!(bucket.contains(&id_main));
1641
1642 for i in 2..=(MAX_NODES_PER_BUCKET as u8) {
1644 let (id, c) = dummy_contact(i);
1645 bucket.insert(id, c);
1646 }
1647 let (id_repl, contact_repl) = dummy_contact(100);
1648 bucket.insert(id_repl, contact_repl);
1649
1650 assert!(bucket.contains(&id_repl));
1651 assert!(!bucket.contains(&H256::zero()));
1652 }
1653
1654 #[test]
1657 fn get_returns_main_list_only() {
1658 let mut bucket = KBucket::default();
1659 let (id, contact) = dummy_contact(1);
1660 bucket.insert(id, contact);
1661 assert!(bucket.get(&id).is_some());
1662 assert!(bucket.get(&H256::zero()).is_none());
1663 }
1664
1665 #[test]
1666 fn get_any_returns_from_replacement() {
1667 let mut bucket = KBucket::default();
1668 for i in 0..MAX_NODES_PER_BUCKET as u8 {
1670 let (id, c) = dummy_contact(i);
1671 bucket.insert(id, c);
1672 }
1673 let (id_repl, c_repl) = dummy_contact(200);
1675 bucket.insert(id_repl, c_repl);
1676
1677 assert!(bucket.get(&id_repl).is_none()); assert!(bucket.get_any(&id_repl).is_some()); }
1680
1681 #[test]
1684 fn remove_and_promote_with_replacement() {
1685 let mut bucket = KBucket::default();
1686
1687 let mut main_ids = Vec::new();
1689 for i in 0..MAX_NODES_PER_BUCKET as u8 {
1690 let (id, c) = dummy_contact(i);
1691 main_ids.push(id);
1692 bucket.insert(id, c);
1693 }
1694
1695 let (repl_id, repl_contact) = dummy_contact(200);
1697 bucket.insert(repl_id, repl_contact);
1698
1699 let promoted = bucket.remove_and_promote(&main_ids[0]);
1701 assert_eq!(promoted, Some(repl_id));
1702 assert_eq!(bucket.contacts.len(), MAX_NODES_PER_BUCKET);
1703 assert!(bucket.replacements.is_empty());
1704 assert!(!bucket.contains(&main_ids[0]));
1705 assert!(bucket.contains(&repl_id));
1706 }
1707
1708 #[test]
1709 fn remove_and_promote_without_replacement() {
1710 let mut bucket = KBucket::default();
1711 let (id, c) = dummy_contact(1);
1712 bucket.insert(id, c);
1713
1714 let promoted = bucket.remove_and_promote(&id);
1715 assert!(promoted.is_none());
1716 assert!(bucket.contacts.is_empty());
1717 }
1718
1719 #[test]
1720 fn remove_nonexistent_returns_none() {
1721 let mut bucket = KBucket::default();
1722 assert!(bucket.remove_and_promote(&H256::zero()).is_none());
1723 }
1724
1725 #[test]
1728 fn replacement_list_evicts_oldest_when_full() {
1729 let mut bucket = KBucket::default();
1730 for i in 0..MAX_NODES_PER_BUCKET as u8 {
1732 let (id, c) = dummy_contact(i);
1733 bucket.insert(id, c);
1734 }
1735
1736 let mut repl_ids = Vec::new();
1738 for i in 0..(MAX_REPLACEMENTS_PER_BUCKET + 2) as u8 {
1739 let seed = 100 + i;
1740 let (id, c) = dummy_contact(seed);
1741 repl_ids.push(id);
1742 bucket.insert(id, c);
1743 }
1744
1745 assert_eq!(bucket.replacements.len(), MAX_REPLACEMENTS_PER_BUCKET);
1746 assert!(!bucket.contains(&repl_ids[0]));
1748 assert!(!bucket.contains(&repl_ids[1]));
1749 assert!(bucket.contains(repl_ids.last().unwrap()));
1751 }
1752
1753 #[test]
1756 fn bucket_index_self_is_none() {
1757 let id = H256::random();
1758 assert_eq!(bucket_index(&id, &id), None);
1759 }
1760
1761 #[test]
1762 fn bucket_index_minimal_distance() {
1763 let local = H256::zero();
1764 let mut remote = H256::zero();
1766 remote.0[31] = 1;
1767 assert_eq!(bucket_index(&local, &remote), Some(0));
1768 }
1769
1770 #[test]
1771 fn bucket_index_maximal_distance() {
1772 let local = H256::zero();
1773 let mut remote = H256::zero();
1775 remote.0[0] = 0x80;
1776 assert_eq!(bucket_index(&local, &remote), Some(255));
1777 }
1778}