1use std::cmp;
4use std::time::{Duration, SystemTime};
5
6use derive_deftly::{derive_deftly_adhoc, Deftly};
7use rand::{seq::IndexedRandom as _, RngCore};
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
18use crate::{VanguardMgrError, VanguardMode};
19
20use super::VanguardParams;
21
22#[derive(Clone, amplify::Getters)]
24pub struct Vanguard<'a> {
25 relay: Relay<'a>,
27}
28
29#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] pub(crate) struct TimeBoundVanguard {
40 pub(super) id: RelayIds,
42 pub(super) when: SystemTime,
44}
45
46#[derive(Default, Debug, Clone, PartialEq)] #[derive(Serialize, Deserialize)] #[serde(transparent)]
55pub(super) struct VanguardSet {
56 vanguards: Vec<TimeBoundVanguard>,
58 #[serde(skip)]
63 target: usize,
64}
65
66#[derive(Default, Debug, Clone, PartialEq)] #[derive(Deftly, Serialize, Deserialize)] #[derive_deftly_adhoc]
71pub(super) struct VanguardSets {
72 l2_vanguards: VanguardSet,
74 l3_vanguards: VanguardSet,
78}
79
80impl VanguardSets {
81 pub(super) fn next_expiry(&self) -> Option<SystemTime> {
83 let l2_expiry = self.l2_vanguards.next_expiry();
84 let l3_expiry = self.l3_vanguards.next_expiry();
85 match (l2_expiry, l3_expiry) {
86 (Some(e), None) | (None, Some(e)) => Some(e),
87 (Some(e1), Some(e2)) => Some(cmp::min(e1, e2)),
88 (None, None) => {
89 None
91 }
92 }
93 }
94
95 pub(super) fn l2(&self) -> &VanguardSet {
97 &self.l2_vanguards
98 }
99
100 pub(super) fn l3(&self) -> &VanguardSet {
102 &self.l3_vanguards
103 }
104
105 pub(super) fn remove_expired(&mut self, now: SystemTime) -> usize {
109 let l2_expired = self.l2_vanguards.remove_expired(now);
110 let l3_expired = self.l3_vanguards.remove_expired(now);
111
112 l2_expired + l3_expired
113 }
114
115 pub(super) fn remove_unlisted(&mut self, netdir: &NetDir) {
119 self.l2_vanguards.remove_unlisted(netdir);
120 self.l3_vanguards.remove_unlisted(netdir);
121 }
122
123 pub(super) fn replenish_vanguards<R: Runtime>(
128 &mut self,
129 runtime: &R,
130 netdir: &NetDir,
131 params: &VanguardParams,
132 mode: VanguardMode,
133 ) -> Result<(), VanguardMgrError> {
134 trace!("Replenishing vanguard sets");
135
136 self.l2_vanguards.update_target(params.l2_pool_size());
138
139 let mut rng = rand::rng();
140 Self::replenish_set(
141 runtime,
142 &mut rng,
143 netdir,
144 &mut self.l2_vanguards,
145 params.l2_lifetime_min(),
146 params.l2_lifetime_max(),
147 )?;
148
149 if mode == VanguardMode::Full {
150 self.l3_vanguards.update_target(params.l3_pool_size());
151 Self::replenish_set(
152 runtime,
153 &mut rng,
154 netdir,
155 &mut self.l3_vanguards,
156 params.l3_lifetime_min(),
157 params.l3_lifetime_max(),
158 )?;
159 }
160
161 Ok(())
162 }
163
164 fn replenish_set<R: Runtime, Rng: RngCore>(
166 runtime: &R,
167 rng: &mut Rng,
168 netdir: &NetDir,
169 vanguard_set: &mut VanguardSet,
170 min_lifetime: Duration,
171 max_lifetime: Duration,
172 ) -> Result<bool, VanguardMgrError> {
173 let mut set_changed = false;
174 let deficit = vanguard_set.deficit();
175 if deficit > 0 {
176 let exclude_ids = RelayIdSet::from(&*vanguard_set);
178 let exclude = RelayExclusion::exclude_identities(exclude_ids);
179 let new_vanguards = Self::add_n_vanguards(
181 runtime,
182 rng,
183 netdir,
184 deficit,
185 exclude,
186 min_lifetime,
187 max_lifetime,
188 )?;
189
190 if !new_vanguards.is_empty() {
191 set_changed = true;
192 }
193
194 for v in new_vanguards {
195 vanguard_set.add_vanguard(v);
196 }
197 }
198
199 Ok(set_changed)
200 }
201
202 fn add_n_vanguards<R: Runtime, Rng: RngCore>(
207 runtime: &R,
208 rng: &mut Rng,
209 netdir: &NetDir,
210 n: usize,
211 exclude: RelayExclusion,
212 min_lifetime: Duration,
213 max_lifetime: Duration,
214 ) -> Result<Vec<TimeBoundVanguard>, VanguardMgrError> {
215 trace!(relay_count = n, "selecting relays to use as vanguards");
216
217 let vanguard_sel = RelaySelector::new(RelayUsage::vanguard(), exclude);
218
219 let (relays, _outcome) = vanguard_sel.select_n_relays(rng, n, netdir);
220
221 relays
222 .into_iter()
223 .map(|relay| {
224 let duration = select_lifetime(rng, min_lifetime, max_lifetime)?;
226 let when = runtime.wallclock() + duration;
227
228 Ok(TimeBoundVanguard {
229 id: RelayIds::from_relay_ids(&relay),
230 when,
231 })
232 })
233 .collect::<Result<Vec<_>, _>>()
234 }
235}
236
237fn select_lifetime<Rng: RngCore>(
248 rng: &mut Rng,
249 min_lifetime: Duration,
250 max_lifetime: Duration,
251) -> Result<Duration, VanguardMgrError> {
252 let err = || internal!("invalid consensus: vanguard min_lifetime > max_lifetime");
253
254 let l1 = rng
255 .gen_range_checked(min_lifetime..=max_lifetime)
256 .ok_or_else(err)?;
257
258 let l2 = rng
259 .gen_range_checked(min_lifetime..=max_lifetime)
260 .ok_or_else(err)?;
261
262 Ok(std::cmp::max(l1, l2))
263}
264
265impl VanguardSet {
266 pub(super) fn pick_relay<'a, R: RngCore>(
271 &self,
272 rng: &mut R,
273 netdir: &'a NetDir,
274 relay_selector: &RelaySelector<'a>,
275 ) -> Option<Vanguard<'a>> {
276 let good_relays = self
277 .vanguards
278 .iter()
279 .filter_map(|vanguard| {
280 let relay = netdir.by_ids(&vanguard.id)?;
282 relay_selector
283 .low_level_predicate_permits_relay(&relay)
284 .then_some(relay)
285 })
286 .collect::<Vec<_>>();
287
288 good_relays.choose(rng).map(|relay| Vanguard {
292 relay: relay.clone(),
293 })
294 }
295
296 pub(super) fn is_empty(&self) -> bool {
298 self.vanguards.is_empty()
299 }
300
301 fn deficit(&self) -> usize {
303 self.target.saturating_sub(self.vanguards.len())
304 }
305
306 fn add_vanguard(&mut self, v: TimeBoundVanguard) {
308 self.vanguards.push(v);
309 }
310
311 fn remove_unlisted(&mut self, netdir: &NetDir) -> usize {
315 self.retain(|v| {
316 let cond = netdir.ids_listed(&v.id) != Some(false);
317
318 if !cond {
319 debug!(id=?v.id, "Removing newly-unlisted vanguard");
320 }
321
322 cond
323 })
324 }
325
326 fn remove_expired(&mut self, now: SystemTime) -> usize {
330 self.retain(|v| {
331 let cond = v.when > now;
332
333 if !cond {
334 debug!(id=?v.id, "Removing expired vanguard");
335 }
336
337 cond
338 })
339 }
340
341 fn retain<F>(&mut self, f: F) -> usize
343 where
344 F: FnMut(&TimeBoundVanguard) -> bool,
345 {
346 let old_len = self.vanguards.len();
347 self.vanguards.retain(f);
348 old_len - self.vanguards.len()
349 }
350
351 fn next_expiry(&self) -> Option<SystemTime> {
353 self.vanguards.iter().map(|v| v.when).min()
354 }
355
356 fn update_target(&mut self, target: usize) {
358 self.target = target;
359 }
360}
361
362impl From<&VanguardSet> for RelayIdSet {
363 fn from(vanguard_set: &VanguardSet) -> Self {
364 vanguard_set
365 .vanguards
366 .iter()
367 .flat_map(|vanguard| {
368 vanguard
369 .id
370 .clone()
371 .identities()
372 .map(|id| id.to_owned())
373 .collect::<Vec<_>>()
374 })
375 .collect()
376 }
377}
378
379#[cfg(test)]
381derive_deftly_adhoc! {
382 VanguardSets expect items:
383
384 impl VanguardSets {
385 $(
386 #[doc = concat!("Return the ", stringify!($fname))]
387 pub(super) fn $fname(&self) -> &Vec<TimeBoundVanguard> {
388 &self.$fname.vanguards
389 }
390
391 #[doc = concat!("Return the target size of the ", stringify!($fname), " set")]
392 pub(super) fn $<$fname _target>(&self) -> usize {
393 self.$fname.target
394 }
395
396 #[doc = concat!("Return the deficit of the ", stringify!($fname), " set")]
397 pub(super) fn $<$fname _deficit>(&self) -> usize {
398 self.$fname.deficit()
399 }
400
401 )
402 }
403}