1use std::cmp;
4use std::time::{Duration, SystemTime};
5
6use derive_deftly::Deftly;
7use rand::{RngCore, seq::IndexedRandom as _};
8use serde::{Deserialize, Serialize};
9
10use tor_basic_utils::RngExt as _;
11use tor_error::internal;
12use tor_linkspec::{HasRelayIds as _, RelayIdSet, RelayIds};
13use tor_netdir::{NetDir, Relay};
14use tor_relay_selection::{LowLevelRelayPredicate as _, RelayExclusion, RelaySelector, RelayUsage};
15use tor_rtcompat::Runtime;
16use tracing::{debug, trace};
17
18#[cfg(test)]
19use derive_deftly::derive_deftly_adhoc;
20
21use crate::{VanguardMgrError, VanguardMode};
22
23use super::VanguardParams;
24
25#[derive(Clone, amplify::Getters)]
27pub struct Vanguard<'a> {
28 relay: Relay<'a>,
30}
31
32#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] pub(crate) struct TimeBoundVanguard {
43 pub(super) id: RelayIds,
45 pub(super) when: SystemTime,
47}
48
49#[derive(Default, Debug, Clone, PartialEq)] #[derive(Serialize, Deserialize)] #[serde(transparent)]
58pub(super) struct VanguardSet {
59 vanguards: Vec<TimeBoundVanguard>,
61 #[serde(skip)]
66 target: usize,
67}
68
69#[derive(Default, Debug, Clone, PartialEq)] #[derive(Deftly, Serialize, Deserialize)] #[derive_deftly_adhoc]
74pub(super) struct VanguardSets {
75 l2_vanguards: VanguardSet,
77 l3_vanguards: VanguardSet,
81}
82
83impl VanguardSets {
84 pub(super) fn next_expiry(&self) -> Option<SystemTime> {
86 let l2_expiry = self.l2_vanguards.next_expiry();
87 let l3_expiry = self.l3_vanguards.next_expiry();
88 match (l2_expiry, l3_expiry) {
89 (Some(e), None) | (None, Some(e)) => Some(e),
90 (Some(e1), Some(e2)) => Some(cmp::min(e1, e2)),
91 (None, None) => {
92 None
94 }
95 }
96 }
97
98 pub(super) fn l2(&self) -> &VanguardSet {
100 &self.l2_vanguards
101 }
102
103 pub(super) fn l3(&self) -> &VanguardSet {
105 &self.l3_vanguards
106 }
107
108 pub(super) fn remove_expired(&mut self, now: SystemTime) -> usize {
112 let l2_expired = self.l2_vanguards.remove_expired(now);
113 let l3_expired = self.l3_vanguards.remove_expired(now);
114
115 l2_expired + l3_expired
116 }
117
118 pub(super) fn remove_unlisted(&mut self, netdir: &NetDir) {
122 self.l2_vanguards.remove_unlisted(netdir);
123 self.l3_vanguards.remove_unlisted(netdir);
124 }
125
126 pub(super) fn replenish_vanguards<R: Runtime>(
131 &mut self,
132 runtime: &R,
133 netdir: &NetDir,
134 params: &VanguardParams,
135 mode: VanguardMode,
136 ) -> Result<(), VanguardMgrError> {
137 trace!("Replenishing vanguard sets");
138
139 self.l2_vanguards.update_target(params.l2_pool_size());
141
142 let mut rng = rand::rng();
143 Self::replenish_set(
144 runtime,
145 &mut rng,
146 netdir,
147 &mut self.l2_vanguards,
148 params.l2_lifetime_min(),
149 params.l2_lifetime_max(),
150 )?;
151
152 if mode == VanguardMode::Full {
153 self.l3_vanguards.update_target(params.l3_pool_size());
154 Self::replenish_set(
155 runtime,
156 &mut rng,
157 netdir,
158 &mut self.l3_vanguards,
159 params.l3_lifetime_min(),
160 params.l3_lifetime_max(),
161 )?;
162 }
163
164 Ok(())
165 }
166
167 fn replenish_set<R: Runtime, Rng: RngCore>(
169 runtime: &R,
170 rng: &mut Rng,
171 netdir: &NetDir,
172 vanguard_set: &mut VanguardSet,
173 min_lifetime: Duration,
174 max_lifetime: Duration,
175 ) -> Result<bool, VanguardMgrError> {
176 let mut set_changed = false;
177 let deficit = vanguard_set.deficit();
178 if deficit > 0 {
179 let exclude_ids = RelayIdSet::from(&*vanguard_set);
181 let exclude = RelayExclusion::exclude_identities(exclude_ids);
182 let new_vanguards = Self::add_n_vanguards(
184 runtime,
185 rng,
186 netdir,
187 deficit,
188 exclude,
189 min_lifetime,
190 max_lifetime,
191 )?;
192
193 if !new_vanguards.is_empty() {
194 set_changed = true;
195 }
196
197 for v in new_vanguards {
198 vanguard_set.add_vanguard(v);
199 }
200 }
201
202 Ok(set_changed)
203 }
204
205 fn add_n_vanguards<R: Runtime, Rng: RngCore>(
210 runtime: &R,
211 rng: &mut Rng,
212 netdir: &NetDir,
213 n: usize,
214 exclude: RelayExclusion,
215 min_lifetime: Duration,
216 max_lifetime: Duration,
217 ) -> Result<Vec<TimeBoundVanguard>, VanguardMgrError> {
218 trace!(relay_count = n, "selecting relays to use as vanguards");
219
220 let vanguard_sel = RelaySelector::new(RelayUsage::vanguard(), exclude);
221
222 let (relays, _outcome) = vanguard_sel.select_n_relays(rng, n, netdir);
223
224 relays
225 .into_iter()
226 .map(|relay| {
227 let duration = select_lifetime(rng, min_lifetime, max_lifetime)?;
229 let when = runtime.wallclock() + duration;
230
231 Ok(TimeBoundVanguard {
232 id: RelayIds::from_relay_ids(&relay),
233 when,
234 })
235 })
236 .collect::<Result<Vec<_>, _>>()
237 }
238}
239
240fn select_lifetime<Rng: RngCore>(
251 rng: &mut Rng,
252 min_lifetime: Duration,
253 max_lifetime: Duration,
254) -> Result<Duration, VanguardMgrError> {
255 let err = || internal!("invalid consensus: vanguard min_lifetime > max_lifetime");
256
257 let l1 = rng
258 .gen_range_checked(min_lifetime..=max_lifetime)
259 .ok_or_else(err)?;
260
261 let l2 = rng
262 .gen_range_checked(min_lifetime..=max_lifetime)
263 .ok_or_else(err)?;
264
265 Ok(std::cmp::max(l1, l2))
266}
267
268impl VanguardSet {
269 pub(super) fn pick_relay<'a, R: RngCore>(
274 &self,
275 rng: &mut R,
276 netdir: &'a NetDir,
277 relay_selector: &RelaySelector<'a>,
278 ) -> Option<Vanguard<'a>> {
279 let good_relays = self
280 .vanguards
281 .iter()
282 .filter_map(|vanguard| {
283 let relay = netdir.by_ids(&vanguard.id)?;
285 relay_selector
286 .low_level_predicate_permits_relay(&relay)
287 .then_some(relay)
288 })
289 .collect::<Vec<_>>();
290
291 good_relays.choose(rng).map(|relay| Vanguard {
295 relay: relay.clone(),
296 })
297 }
298
299 pub(super) fn is_empty(&self) -> bool {
301 self.vanguards.is_empty()
302 }
303
304 fn deficit(&self) -> usize {
306 self.target.saturating_sub(self.vanguards.len())
307 }
308
309 fn add_vanguard(&mut self, v: TimeBoundVanguard) {
311 self.vanguards.push(v);
312 }
313
314 fn remove_unlisted(&mut self, netdir: &NetDir) -> usize {
318 self.retain(|v| {
319 let cond = netdir.ids_listed(&v.id) != Some(false);
320
321 if !cond {
322 debug!(id=?v.id, "Removing newly-unlisted vanguard");
323 }
324
325 cond
326 })
327 }
328
329 fn remove_expired(&mut self, now: SystemTime) -> usize {
333 self.retain(|v| {
334 let cond = v.when > now;
335
336 if !cond {
337 debug!(id=?v.id, "Removing expired vanguard");
338 }
339
340 cond
341 })
342 }
343
344 fn retain<F>(&mut self, f: F) -> usize
346 where
347 F: FnMut(&TimeBoundVanguard) -> bool,
348 {
349 let old_len = self.vanguards.len();
350 self.vanguards.retain(f);
351 old_len - self.vanguards.len()
352 }
353
354 fn next_expiry(&self) -> Option<SystemTime> {
356 self.vanguards.iter().map(|v| v.when).min()
357 }
358
359 fn update_target(&mut self, target: usize) {
361 self.target = target;
362 }
363}
364
365impl From<&VanguardSet> for RelayIdSet {
366 fn from(vanguard_set: &VanguardSet) -> Self {
367 vanguard_set
368 .vanguards
369 .iter()
370 .flat_map(|vanguard| {
371 vanguard
372 .id
373 .clone()
374 .identities()
375 .map(|id| id.to_owned())
376 .collect::<Vec<_>>()
377 })
378 .collect()
379 }
380}
381
382#[cfg(test)]
384derive_deftly_adhoc! {
385 VanguardSets expect items:
386
387 impl VanguardSets {
388 $(
389 #[doc = concat!("Return the ", stringify!($fname))]
390 pub(super) fn $fname(&self) -> &Vec<TimeBoundVanguard> {
391 &self.$fname.vanguards
392 }
393
394 #[doc = concat!("Return the target size of the ", stringify!($fname), " set")]
395 pub(super) fn $<$fname _target>(&self) -> usize {
396 self.$fname.target
397 }
398
399 #[doc = concat!("Return the deficit of the ", stringify!($fname), " set")]
400 pub(super) fn $<$fname _deficit>(&self) -> usize {
401 self.$fname.deficit()
402 }
403
404 )
405 }
406}