Skip to main content

dht_rpc/query/
mod.rs

1use std::{
2    fmt::Display,
3    num::NonZeroUsize,
4    sync::{Arc, RwLock},
5    task::Waker,
6    time::Duration,
7};
8
9use closest::ClosestPeersIter;
10use fnv::FnvHashMap;
11use futures::{
12    channel::mpsc::{self},
13    task::Poll,
14};
15use tracing::{debug, instrument, trace, warn};
16use wasm_timer::Instant;
17
18use crate::{
19    IdBytes, PeerId,
20    commit::{Commit, CommitEvent},
21    constants::DEFAULT_COMMIT_CHANNEL_SIZE,
22    io::InResponse,
23    kbucket::{ALPHA_VALUE, K_VALUE},
24};
25
26mod closest;
27mod peers;
28pub mod table;
29
30use self::{
31    peers::PeersIterState,
32    table::{PeerState, QueryTable},
33};
34use super::{Command, Peer, message::ReplyMsgData};
35
36/// A `QueryPool` provides an aggregate state machine for driving `Query`s to
37/// completion.
38#[derive(Debug)]
39pub struct QueryPool {
40    local_id: IdBytes,
41    queries: FnvHashMap<QueryId, Arc<RwLock<Query>>>,
42    config: QueryConfig,
43    next_id: usize,
44    waker: Option<Waker>,
45}
46
47/// The configuration for queries in a `QueryPool`.
48#[derive(Debug, Clone)]
49pub struct QueryConfig {
50    /// Timeout of a single query.
51    pub timeout: Duration,
52
53    /// The replication factor to use.
54    pub replication_factor: NonZeroUsize,
55
56    /// Allowed level of parallelism for iterative queries.
57    pub parallelism: NonZeroUsize,
58}
59
60impl Default for QueryConfig {
61    fn default() -> Self {
62        QueryConfig {
63            timeout: Duration::from_secs(60),
64            replication_factor: NonZeroUsize::new(K_VALUE.get()).expect("K_VALUE > 0"),
65            parallelism: ALPHA_VALUE,
66        }
67    }
68}
69
70impl QueryPool {
71    /// Creates a new `QueryPool` with the given configuration.
72    pub fn new(local_id: IdBytes, config: QueryConfig) -> Self {
73        Self {
74            local_id,
75            next_id: 0,
76            config,
77            queries: Default::default(),
78            waker: Default::default(),
79        }
80    }
81
82    /// Gets the current size of the pool, i.e. the number of running queries.
83    pub fn len(&self) -> usize {
84        self.queries.len()
85    }
86
87    pub fn is_empty(&self) -> bool {
88        self.queries.is_empty()
89    }
90
91    pub(crate) fn next_query_id(&mut self) -> QueryId {
92        let id = QueryId(self.next_id);
93        self.next_id = self.next_id.wrapping_add(1);
94        id
95    }
96
97    pub fn bootstrap(
98        &mut self,
99        target: IdBytes,
100        peers: Vec<PeerId>,
101        bootstrap: Vec<Peer>,
102    ) -> QueryId {
103        self.add_stream(
104            Command::Internal(crate::InternalCommand::FindNode),
105            peers,
106            target,
107            None,
108            bootstrap,
109            Commit::No,
110        )
111    }
112
113    /// Adds a query to the pool.
114    pub fn add_stream(
115        &mut self,
116        cmd: Command,
117        peers: Vec<PeerId>,
118        target: IdBytes,
119        value: Option<Vec<u8>>,
120        bootstrap: Vec<Peer>,
121        commit: Commit,
122    ) -> QueryId {
123        let id = self.next_query_id();
124        debug!(
125            id = id.0,
126            cmd = tracing::field::display(cmd),
127            n_peers = peers.len(),
128            n_bootstrap = bootstrap.len(),
129            "new Query"
130        );
131        let query = Query::new(
132            id,
133            cmd,
134            self.config.parallelism,
135            self.local_id,
136            target,
137            value,
138            peers,
139            bootstrap,
140            commit,
141        );
142        self.queries.insert(id, Arc::new(RwLock::new(query)));
143        if let Some(waker) = &self.waker {
144            waker.wake_by_ref();
145        }
146        id
147    }
148
149    // Returns a reference to a query with the given ID, if it is in the pool.
150    // pub fn get(&self, id: &QueryId) -> Option<&Query> {
151    //     self.queries.get(id)
152    // }
153
154    /// Returns a mutable reference to a query with the given ID, if it is in
155    /// the pool.
156    pub fn get(&self, id: &QueryId) -> Option<Arc<RwLock<Query>>> {
157        self.queries.get(id).cloned()
158    }
159
160    /// Polls the pool to advance the queries.
161    pub fn poll(&mut self, now: Instant, waker: Waker) -> QueryPoolEvent {
162        let mut finished = None;
163        let mut timeout = None;
164        let mut waiting = None;
165
166        _ = self.waker.insert(waker);
167
168        for (&query_id, query) in self.queries.iter() {
169            let mut w_query = query.write().unwrap();
170            w_query.stats.start = w_query.stats.start.or(Some(now));
171            match w_query.poll(now) {
172                Poll::Ready(Some(ev)) => {
173                    match ev {
174                        QueryEvent::Commit(cev) => {
175                            return QueryPoolEvent::Commit((query.clone(), cev));
176                        }
177                        qev => waiting = Some((qev, query_id)),
178                    }
179                    break;
180                }
181                Poll::Ready(None) => {
182                    finished = Some(query_id);
183                    break;
184                }
185                Poll::Pending => {
186                    let elapsed = now - w_query.stats.start.unwrap_or(now);
187                    if elapsed >= self.config.timeout {
188                        timeout = Some(query_id);
189                        break;
190                    }
191                }
192            }
193        }
194
195        if let Some((event, query_id)) = waiting {
196            let query = self.queries.get(&query_id).expect("s.a.");
197            return QueryPoolEvent::Waiting(Some((query.clone(), event)));
198        }
199
200        if let Some(query_id) = finished {
201            let query = self.queries.remove(&query_id).expect("s.a.");
202            query.write().unwrap().stats.end = Some(now);
203            return QueryPoolEvent::Finished(query.clone());
204        }
205
206        if let Some(query_id) = timeout {
207            let query = self.queries.remove(&query_id).expect("s.a.");
208            query.write().unwrap().stats.end = Some(now);
209            return QueryPoolEvent::Timeout(query);
210        }
211
212        if self.queries.is_empty() {
213            QueryPoolEvent::Idle
214        } else {
215            QueryPoolEvent::Waiting(None)
216        }
217    }
218}
219
220/// The observable states emitted by [`QueryPool::poll`].
221#[derive(Debug)]
222pub enum QueryPoolEvent {
223    /// The pool is idle, i.e. there are no queries to process.
224    Idle,
225    /// At least one query is waiting for results. `Some(request)` indicates
226    /// that a new request is now being waited on.
227    Waiting(Option<(Arc<RwLock<Query>>, QueryEvent)>),
228    /// A query is ready to commit
229    Commit((Arc<RwLock<Query>>, CommitEvent)),
230    /// A query has finished.
231    Finished(Arc<RwLock<Query>>),
232    /// A query has timed out.
233    Timeout(Arc<RwLock<Query>>),
234}
235
236#[derive(Debug)]
237pub struct Query {
238    /// identifier for this stream
239    pub id: QueryId,
240    /// The permitted parallelism, i.e. number of pending results.
241    #[expect(unused)] // TODO
242    parallelism: NonZeroUsize,
243    /// The peer iterator that drives the query state.
244    pub(crate) peer_iter: ClosestPeersIter,
245    /// The rpc command of this stream
246    pub cmd: Command,
247    /// Stats about this query
248    stats: QueryStats,
249    /// The value to include in each message
250    pub value: Option<Vec<u8>>,
251    /// The inner query state.
252    inner: QueryTable,
253    /// Whether to send commits when query completes
254    pub commit: Commit,
255    /// Closest replies are store for commiting data
256    pub closest_replies: Vec<Arc<InResponse>>,
257}
258
259impl Query {
260    // TODO use a builder struct instead
261    #[expect(clippy::too_many_arguments)]
262    pub fn new(
263        id: QueryId,
264        cmd: Command,
265        parallelism: NonZeroUsize,
266        local_id: IdBytes,
267        target: IdBytes,
268        value: Option<Vec<u8>>,
269        peers: Vec<PeerId>,
270        bootstrap: Vec<Peer>,
271        commit: Commit,
272    ) -> Self {
273        Self {
274            id,
275            parallelism,
276            peer_iter: ClosestPeersIter::new(target, bootstrap),
277            cmd,
278            stats: QueryStats::empty(),
279            value,
280            inner: QueryTable::new(local_id, target, peers),
281            commit,
282            closest_replies: vec![],
283        }
284    }
285
286    // TODO use binary search ot insert
287    // TODO in theory, new elements distances get smaller. So maybe reverse the list.
288    #[instrument(skip_all)]
289    fn maybe_update_closest_replies(&mut self, data: &Arc<InResponse>) -> Option<usize> {
290        let reply_distance = self.peer_iter.target.distance(data.response.id?);
291        let replace = self.closest_replies.len() >= K_VALUE.into();
292
293        if self.closest_replies.is_empty() {
294            self.closest_replies.insert(0, data.clone());
295            return Some(0);
296        }
297        for (i, cur) in self.closest_replies.iter().enumerate() {
298            if reply_distance
299                < self
300                    .peer_iter
301                    .target
302                    .distance(cur.response.id.expect("TODO this should be a PeerId"))
303            {
304                if replace {
305                    self.closest_replies[i] = data.clone();
306                } else {
307                    self.closest_replies.insert(i, data.clone());
308                }
309                trace!(
310                    closest_replies.len = self.closest_replies.len(),
311                    "Inerted response at [{i}]"
312                );
313                return Some(i);
314            }
315        }
316        trace!("Response farther than all current replies");
317        None
318    }
319    pub fn command(&self) -> Command {
320        self.cmd
321    }
322
323    pub fn target(&self) -> &IdBytes {
324        self.inner.target()
325    }
326    pub fn value(&self) -> Option<&Vec<u8>> {
327        self.value.as_ref()
328    }
329
330    pub fn id(&self) -> QueryId {
331        self.id
332    }
333
334    // TODO unused
335    pub(crate) fn _on_timeout(&mut self, peer: &Peer) {
336        self.stats.failure += 1;
337        for (p, state) in self.inner.peers_mut() {
338            if p.addr == peer.addr {
339                *state = PeerState::Failed;
340                break;
341            }
342        }
343        self.peer_iter.on_failure(peer);
344    }
345
346    /// Received a response to a requested driven by this query.
347    #[instrument(skip(self, data))]
348    pub(crate) fn inject_response(&mut self, data: Arc<InResponse>) -> Option<Arc<InResponse>> {
349        use crate::Progress as P;
350        use Commit as C;
351
352        match &mut self.commit {
353            // Commit is in progress
354            C::Auto(prog @ (P::AwaitingReplies(_) | P::Sending(_)))
355            | C::Custom(prog @ (P::AwaitingReplies(_) | P::Sending(_))) => {
356                warn!("recieved a response! for tid {}", data.response.tid);
357                prog.recieved_tid(data.response.tid);
358            }
359            // Before commit
360            _ => {
361                self.maybe_update_closest_replies(&data);
362                let remote = data
363                    .response
364                    .id
365                    .map(|id| PeerId::new(data.peer.addr, IdBytes::from(id)));
366
367                if data.response.is_error() {
368                    warn!(error = data.response.error, "Error in peer response");
369                    self.stats.failure += 1;
370                    self.peer_iter.on_failure(&data.peer);
371                    if let Some(ref remote) = remote
372                        && let Some(state) = self.inner.peers_mut().get_mut(remote)
373                    {
374                        *state = PeerState::Failed;
375                    }
376                    return None;
377                }
378
379                self.stats.success += 1;
380                self.peer_iter
381                    .on_success(&data.peer, &data.response.closer_nodes);
382
383                if let Some(token) = &data.response.token
384                    && let Some(remote) = remote
385                {
386                    self.inner
387                        .add_verified(remote, token.to_vec(), Some(data.response.to.addr));
388                }
389            }
390        }
391
392        Some(data)
393    }
394
395    fn send(&self, peer: Peer) -> QueryEvent {
396        QueryEvent::Query {
397            command: self.cmd,
398            target: *self.target(),
399            value: self.value.clone(),
400            peer,
401        }
402    }
403
404    #[instrument(skip_all)]
405    fn poll(&mut self, now: Instant) -> Poll<Option<QueryEvent>> {
406        let pis = self.peer_iter.next(now);
407        match pis {
408            PeersIterState::Waiting(peer) => {
409                return if let Some(peer) = peer {
410                    Poll::Ready(Some(self.send(peer)))
411                } else {
412                    Poll::Pending
413                };
414            }
415            PeersIterState::WaitingAtCapacity => return Poll::Pending,
416            PeersIterState::Finished => {}
417        };
418
419        let out = match poll_commit(&mut self.commit, self.id) {
420            Poll::Ready(op) => Poll::Ready(op.map(QueryEvent::Commit)),
421            Poll::Pending => Poll::Pending,
422        };
423
424        trace!("Query::poll result {out:?}");
425        out
426    }
427
428    /// Create the final result from the query
429    pub fn get_result(&self) -> QueryResult {
430        QueryResult {
431            peers: self.inner.peers_iter(),
432            query_id: self.id,
433            stats: self.stats.clone(),
434            cmd: self.cmd,
435            closest_replies: self.closest_replies.clone(),
436        }
437    }
438}
439
440/// NB this is not idempotent
441#[instrument(skip_all)]
442fn poll_commit(commit: &mut Commit, query_id: QueryId) -> Poll<Option<CommitEvent>> {
443    use crate::Progress as P;
444    use Commit as C;
445    trace!("polling commit: {commit:?}");
446    match commit {
447        C::No | C::Auto(P::Done) | C::Custom(P::Done) => Poll::Ready(None),
448        C::Auto(P::BeforeStart) => {
449            let (tx, rx) = mpsc::channel(DEFAULT_COMMIT_CHANNEL_SIZE);
450            *commit = C::Auto(P::Sending((rx, Default::default())));
451            // RpcDht should recieve this and send the query messages
452            Poll::Ready(Some(CommitEvent::AutoStart((tx, query_id))))
453        }
454        C::Custom(P::BeforeStart) => {
455            let (tx, rx) = mpsc::channel(DEFAULT_COMMIT_CHANNEL_SIZE);
456            *commit = C::Custom(P::Sending((rx, Default::default())));
457            Poll::Ready(Some(CommitEvent::CustomStart((tx, query_id))))
458        }
459        C::Auto(P::Sending(_)) => Poll::Pending,
460        C::Custom(P::Sending((rx, _tids))) => {
461            let mut out = vec![];
462            while let Some(e) = rx.try_next().ok().flatten() {
463                out.push(e)
464            }
465            if !out.is_empty() {
466                let sending_done = CommitEvent::SendRequests((out, query_id));
467                return Poll::Ready(Some(sending_done));
468            }
469            Poll::Pending
470        }
471        C::Custom(P::AwaitingReplies(ids_waiting_on)) => {
472            debug!("Custom still waiting on [{}] replies", ids_waiting_on.len());
473            Poll::Pending
474        }
475        C::Auto(P::AwaitingReplies(ids_waiting_on)) => {
476            debug!("Auto still waiting on [{}] replies", ids_waiting_on.len());
477            Poll::Pending
478        }
479    }
480}
481
482#[derive(Debug)]
483pub enum QueryEvent {
484    Commit(CommitEvent),
485    Query {
486        peer: Peer,
487        command: Command,
488        target: IdBytes,
489        value: Option<Vec<u8>>,
490    },
491    RemoveNode {
492        id: IdBytes,
493    },
494    MissingRoundtripToken {
495        peer: Peer,
496    },
497}
498
499/// Represents an incoming query with a custom command.
500#[derive(Debug, Clone)]
501pub struct CommandQuery {
502    /// The Id of the query
503    pub tid: u16,
504    /// Command def
505    pub command: usize,
506    /// the node who sent the query
507    pub peer: Peer,
508    /// the query target (32 byte target)
509    pub target: IdBytes,
510    /// the query payload decoded with the inputEncoding
511    pub value: Option<Vec<u8>>,
512}
513
514impl CommandQuery {
515    pub fn into_response_with_error(self, err: impl Into<usize>) -> CommandQueryResponse {
516        let mut resp = CommandQueryResponse::from(self);
517        resp.msg.error = err.into();
518        resp
519    }
520}
521
522/// Outgoing response to a `CommandQuery`
523#[derive(Debug, Clone)]
524pub struct CommandQueryResponse {
525    pub msg: ReplyMsgData,
526    pub peer: Peer,
527    pub target: IdBytes,
528}
529
530impl From<CommandQuery> for CommandQueryResponse {
531    fn from(q: CommandQuery) -> Self {
532        let msg = ReplyMsgData {
533            tid: q.tid,
534            to: q.peer.clone(),
535            id: None,
536            token: None,
537            closer_nodes: vec![],
538            value: q.value,
539            error: 0,
540        };
541
542        Self {
543            msg,
544            peer: q.peer,
545            target: q.target,
546        }
547    }
548}
549/// The result of a `Query`.
550#[derive(Debug)]
551pub struct QueryResult {
552    /// The ID of the query that finished.
553    pub query_id: QueryId,
554    /// The Command of the query.
555    pub cmd: Command,
556    /// The collected query statistics.
557    pub stats: QueryStats,
558    /// The successfully contacted peers.
559    pub peers: Vec<(PeerId, PeerState)>,
560    /// Closest replies to the target
561    pub closest_replies: Vec<Arc<InResponse>>,
562}
563
564/// Execution statistics of a query.
565#[derive(Clone, Debug, PartialEq, Eq)]
566pub struct QueryStats {
567    requests: u32,
568    success: u32,
569    failure: u32,
570    start: Option<Instant>,
571    end: Option<Instant>,
572}
573
574impl QueryStats {
575    pub fn empty() -> Self {
576        QueryStats {
577            requests: 0,
578            success: 0,
579            failure: 0,
580            start: None,
581            end: None,
582        }
583    }
584
585    /// Gets the total number of requests initiated by the query.
586    pub fn num_requests(&self) -> u32 {
587        self.requests
588    }
589
590    /// Gets the number of successful requests.
591    pub fn num_successes(&self) -> u32 {
592        self.success
593    }
594
595    /// Gets the number of failed requests.
596    pub fn num_failures(&self) -> u32 {
597        self.failure
598    }
599
600    /// Gets the number of pending requests.
601    ///
602    /// > **Note**: A query can finish while still having pending
603    /// > requests, if the termination conditions are already met.
604    pub fn num_pending(&self) -> u32 {
605        self.requests - (self.success + self.failure)
606    }
607
608    /// Gets the duration of the query.
609    ///
610    /// If the query has not yet finished, the duration is measured from the
611    /// start of the query to the current instant.
612    ///
613    /// If the query did not yet start (i.e. yield the first peer to contact),
614    /// `None` is returned.
615    pub fn duration(&self) -> Option<Duration> {
616        if let Some(s) = self.start {
617            if let Some(e) = self.end {
618                Some(e - s)
619            } else {
620                Some(Instant::now() - s)
621            }
622        } else {
623            None
624        }
625    }
626
627    /// Merges these stats with the given stats of another query,
628    /// e.g. to accumulate statistics from a multi-phase query.
629    ///
630    /// Counters are merged cumulatively while the instants for
631    /// start and end of the queries are taken as the minimum and
632    /// maximum, respectively.
633    pub fn merge(self, other: &QueryStats) -> Self {
634        QueryStats {
635            requests: self.requests + other.requests,
636            success: self.success + other.success,
637            failure: self.failure + other.failure,
638            start: match (self.start, other.start) {
639                (Some(a), Some(b)) => Some(std::cmp::min(a, b)),
640                (a, b) => a.or(b),
641            },
642            end: std::cmp::max(self.end, other.end),
643        }
644    }
645}
646/// Unique identifier for an active query.
647#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq, PartialOrd, Ord)]
648pub struct QueryId(pub usize);
649
650impl Display for QueryId {
651    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
652        write!(f, "QueryId({})", self.0)
653    }
654}