karyon_p2p/discovery/kademlia/
mod.rs1mod bloom;
2mod lookup;
3mod messages;
4mod refresh;
5pub mod routing_table;
6
7use std::sync::Arc;
8
9use async_trait::async_trait;
10use log::error;
11use rand::seq::IndexedRandom;
12
13use karyon_core::{
14 async_runtime::Executor,
15 async_util::{AsyncQueue, Backoff, TaskGroup, TaskResult},
16 crypto::KeyPair,
17};
18
19use karyon_net::Endpoint;
20
21use crate::{
22 config::Config,
23 discovery::{DiscoveredPeer, Discovery, PeerConnectionEvent},
24 message::{pick_endpoint, PeerAddr, Protocol},
25 monitor::Monitor,
26 protocol::ProtocolFlags,
27 PeerID, Result,
28};
29
30use bloom::BloomRef;
31use lookup::{LookupEndpoints, LookupService};
32use refresh::RefreshService;
33use routing_table::{
34 RoutingTable, CONNECTED_ENTRY, DISCONNECTED_ENTRY, PENDING_ENTRY, UNREACHABLE_ENTRY,
35 UNSTABLE_ENTRY,
36};
37
38const DISCOVERED_PEER_QUEUE_SIZE: usize = 128;
40
41#[cfg(feature = "quic")]
44pub(crate) const SUPPORTED_LOOKUP_PROTOCOLS: &[Protocol] = &[Protocol::Tcp, Protocol::Quic];
45#[cfg(not(feature = "quic"))]
46pub(crate) const SUPPORTED_LOOKUP_PROTOCOLS: &[Protocol] = &[Protocol::Tcp];
47
48pub struct KademliaDiscovery {
49 table: Arc<RoutingTable>,
51
52 lookup_service: Arc<LookupService>,
54
55 refresh_service: Arc<RefreshService>,
57
58 peer_queue: Arc<AsyncQueue<DiscoveredPeer>>,
62
63 task_group: TaskGroup,
65
66 config: Arc<Config>,
68
69 bloom: BloomRef,
74}
75
76impl KademliaDiscovery {
77 pub fn new(
79 key_pair: &KeyPair,
80 peer_id: &PeerID,
81 config: Arc<Config>,
82 monitor: Arc<Monitor>,
83 ex: Executor,
84 ) -> Arc<Self> {
85 let table = Arc::new(RoutingTable::new(peer_id.0));
86 let bloom = BloomRef::new();
87
88 let lookup_ep = config
91 .discovery_endpoints
92 .iter()
93 .find(|e| is_lookup_proto(e))
94 .cloned();
95 let refresh_ep = config
96 .discovery_endpoints
97 .iter()
98 .find(|e| e.is_udp())
99 .cloned();
100
101 let refresh_service = Arc::new(RefreshService::new(
102 config.clone(),
103 table.clone(),
104 monitor.clone(),
105 refresh_ep.clone(),
106 ex.clone(),
107 ));
108
109 let lookup_endpoints = LookupEndpoints {
110 listen: config.listen_endpoints.clone(),
111 lookup: lookup_ep,
112 refresh: refresh_ep,
113 };
114 let lookup_service = Arc::new(LookupService::new(
115 key_pair,
116 table.clone(),
117 config.clone(),
118 monitor.clone(),
119 bloom.clone(),
120 lookup_endpoints,
121 ex.clone(),
122 ));
123
124 let task_group = TaskGroup::with_executor(ex);
125
126 let peer_queue = AsyncQueue::new(DISCOVERED_PEER_QUEUE_SIZE);
127
128 Arc::new(Self {
129 refresh_service,
130 lookup_service,
131 table,
132 peer_queue,
133 task_group,
134 config,
135 bloom,
136 })
137 }
138
139 async fn connect_loop(self: Arc<Self>) -> Result<()> {
143 let backoff = Backoff::new(500, self.config.seeding_interval * 1000);
144 loop {
145 let local = self.bloom.snapshot();
146 match self.table.random_entry_filtered(PENDING_ENTRY, &local) {
147 Some(entry) => {
148 backoff.reset();
149 let key = entry.key;
150 let peer = DiscoveredPeer {
151 peer_id: Some(key.into()),
152 addrs: entry.addrs.clone(),
153 discovery_addrs: entry.discovery_addrs.clone(),
154 };
155 self.table.update_entry(&key, CONNECTED_ENTRY);
158 self.peer_queue.push(peer).await;
159 }
160 None => {
161 backoff.sleep().await;
162 self.start_seeding().await;
163 }
164 }
165 }
166 }
167
168 async fn start_seeding(&self) {
175 match self.table.random_entry(PENDING_ENTRY | CONNECTED_ENTRY) {
176 Some(entry) => {
177 let endpoint =
178 match pick_endpoint(&entry.discovery_addrs, SUPPORTED_LOOKUP_PROTOCOLS) {
179 Some(ep) => ep,
180 None => return,
181 };
182 let peer_id = Some(entry.key.into());
183 if let Err(err) = self.lookup_service.start_lookup(&endpoint, peer_id).await {
184 self.table.update_entry(&entry.key, UNSTABLE_ENTRY);
185 error!("Failed to do lookup: {endpoint}: {err}");
186 }
187 }
188 None => {
189 let peers = &self.config.bootstrap_peers;
190 let shuffled: Vec<_> = peers
191 .choose_multiple(&mut rand::rng(), peers.len())
192 .collect();
193 for endpoint in shuffled {
194 if let Err(err) = self.lookup_service.start_lookup(endpoint, None).await {
195 error!("Failed to do lookup: {endpoint}: {err}");
196 }
197 }
198 }
199 }
200 }
201}
202
203#[async_trait]
204impl Discovery for KademliaDiscovery {
205 async fn start(self: Arc<Self>) -> Result<()> {
206 self.lookup_service.start().await?;
208 self.refresh_service.start().await?;
210
211 for endpoint in self.config.dial_peers.iter() {
213 if let Some(addr) = PeerAddr::from_endpoint(endpoint, 0) {
214 let peer = DiscoveredPeer {
215 peer_id: None,
216 addrs: vec![addr],
217 discovery_addrs: vec![],
218 };
219 self.peer_queue.push(peer).await;
220 }
221 }
222
223 self.task_group.spawn_then(
225 {
226 let this = self.clone();
227 async move { this.connect_loop().await }
228 },
229 |res| async move {
230 if let TaskResult::Completed(Err(err)) = res {
231 error!("Connect loop stopped: {err}");
232 }
233 },
234 );
235
236 Ok(())
237 }
238
239 async fn shutdown(&self) {
240 self.task_group.cancel().await;
241 self.refresh_service.shutdown().await;
242 self.lookup_service.shutdown().await;
243 }
244
245 async fn recv(&self) -> DiscoveredPeer {
246 self.peer_queue.recv().await
247 }
248
249 fn on_event(&self, event: PeerConnectionEvent) {
250 match event {
251 PeerConnectionEvent::Connected(pid) => {
252 self.table.update_entry(&pid.0, CONNECTED_ENTRY);
253 }
254 PeerConnectionEvent::Disconnected(pid) => {
255 self.table.update_entry(&pid.0, DISCONNECTED_ENTRY);
256 }
257 PeerConnectionEvent::ConnectFailed(Some(pid)) => {
258 self.table.update_entry(&pid.0, UNREACHABLE_ENTRY);
259 }
260 PeerConnectionEvent::ConnectFailed(None) => {}
261 }
262 }
263
264 fn advertise(&self, item: &[u8], flags: ProtocolFlags) {
265 self.bloom.add(item, flags);
266 }
267
268 fn find_peers_with(&self, item: &[u8]) -> Vec<DiscoveredPeer> {
269 self.table
270 .entries_with_item(item)
271 .into_iter()
272 .map(|e| DiscoveredPeer {
273 peer_id: Some(e.key.into()),
274 addrs: e.addrs,
275 discovery_addrs: e.discovery_addrs,
276 })
277 .collect()
278 }
279}
280
281fn is_lookup_proto(e: &Endpoint) -> bool {
283 if e.is_tcp() {
284 return true;
285 }
286 #[cfg(feature = "quic")]
287 if e.is_quic() {
288 return true;
289 }
290 false
291}