1 //! Functions for applying the correct weights to relays when choosing
2 //! a relay at random.
3 //!
4 //! The weight to use when picking a relay depends on several factors:
5 //!
6 //! - The relay's *apparent bandwidth*.  (This is ideally measured by a set of
7 //!   bandwidth authorities, but if no bandwidth authorities are running (as on
8 //!   a test network), we might fall back either to relays' self-declared
9 //!   values, or we might treat all relays as having equal bandwidth.)
10 //! - The role that we're selecting a relay to play.  (See [`WeightRole`]).
11 //! - The flags that a relay has in the consensus, and their scarcity.  If a
12 //!   relay provides particularly scarce functionality, we might choose not to
13 //!   use it for other roles, or to use it less commonly for them.
14 
15 use crate::params::NetParameters;
16 use tor_netdoc::doc::netstatus::{self, MdConsensus, MdConsensusRouterStatus, NetParams};
17 
18 /// Helper: Calculate the function we should use to find initial relay
19 /// bandwidths.
pick_bandwidth_fn<'a, I>(mut weights: I) -> BandwidthFn where I: Clone + Iterator<Item = &'a netstatus::RelayWeight>,20 fn pick_bandwidth_fn<'a, I>(mut weights: I) -> BandwidthFn
21 where
22     I: Clone + Iterator<Item = &'a netstatus::RelayWeight>,
23 {
24     let has_measured = weights.clone().any(|w| w.is_measured());
25     let has_nonzero = weights.clone().any(|w| w.is_nonzero());
26     let has_nonzero_measured = weights.any(|w| w.is_measured() && w.is_nonzero());
27 
28     if !has_nonzero {
29         // If every value is zero, we should just pretend everything has
30         // bandwidth == 1.
31         BandwidthFn::Uniform
32     } else if !has_measured {
33         // If there are no measured values, then we can look at unmeasured
34         // weights.
35         BandwidthFn::IncludeUnmeasured
36     } else if has_nonzero_measured {
37         // Otherwise, there are measured values; we should look at those only, if
38         // any of them is nonzero.
39         BandwidthFn::MeasuredOnly
40     } else {
41         // This is a bit of an ugly case: We have measured values, but they're
42         // all zero.  If this happens, the bandwidth authorities exist but they
43         // very confused: we should fall back to uniform weighting.
44         BandwidthFn::Uniform
45     }
46 }
47 
48 /// Internal: how should we find the base bandwidth of each relay?  This
49 /// value is global over a whole directory, and depends on the bandwidth
50 /// weights in the consensus.
51 #[derive(Copy, Clone, Debug, PartialEq)]
52 enum BandwidthFn {
53     /// There are no weights at all in the consensus: weight every
54     /// relay as 1.
55     Uniform,
56     /// There are no measured weights in the consensus: count
57     /// unmeasured weights as the weights for relays.
58     IncludeUnmeasured,
59     /// There are measured relays in the consensus; only use those.
60     MeasuredOnly,
61 }
62 
63 impl BandwidthFn {
64     /// Apply this function to the measured or unmeasured bandwidth
65     /// of a single relay.
apply(&self, w: &netstatus::RelayWeight) -> u3266     fn apply(&self, w: &netstatus::RelayWeight) -> u32 {
67         use netstatus::RelayWeight::*;
68         use BandwidthFn::*;
69         match (self, w) {
70             (Uniform, _) => 1,
71             (IncludeUnmeasured, Unmeasured(u)) => *u,
72             (IncludeUnmeasured, Measured(m)) => *m,
73             (MeasuredOnly, Unmeasured(_)) => 0,
74             (MeasuredOnly, Measured(m)) => *m,
75             (_, _) => 0,
76         }
77     }
78 }
79 
80 /// Possible ways to weight relays when selecting them a random.
81 ///
82 /// Relays are weighted by a function of their bandwidth that
83 /// depends on how scarce that "kind" of bandwidth is.  For
84 /// example, if Exit bandwidth is rare, then Exits should be
85 /// less likely to get chosen for the middle hop of a path.
86 #[derive(Clone, Debug, Copy)]
87 #[non_exhaustive]
88 pub enum WeightRole {
89     /// Selecting a relay to use as a guard
90     Guard,
91     /// Selecting a relay to use as a middle relay in a circuit.
92     Middle,
93     /// Selecting a relay to use to deliver traffic to the internet.
94     Exit,
95     /// Selecting a relay for a one-hop BEGIN_DIR directory request.
96     BeginDir,
97     /// Selecting a relay with no additional weight beyond its bandwidth.
98     Unweighted,
99 }
100 
101 /// Description for how to weight a single kind of relay for each WeightRole.
102 #[derive(Clone, Debug, Copy)]
103 struct RelayWeight {
104     /// How to weight this kind of relay when picking a guard relay.
105     as_guard: u32,
106     /// How to weight this kind of relay when picking a middle relay.
107     as_middle: u32,
108     /// How to weight this kind of relay when picking a exit relay.
109     as_exit: u32,
110     /// How to weight this kind of relay when picking a one-hop BEGIN_DIR.
111     as_dir: u32,
112 }
113 
114 impl std::ops::Mul<u32> for RelayWeight {
115     type Output = Self;
mul(self, rhs: u32) -> Self116     fn mul(self, rhs: u32) -> Self {
117         RelayWeight {
118             as_guard: self.as_guard * rhs,
119             as_middle: self.as_middle * rhs,
120             as_exit: self.as_exit * rhs,
121             as_dir: self.as_dir * rhs,
122         }
123     }
124 }
125 impl std::ops::Div<u32> for RelayWeight {
126     type Output = Self;
div(self, rhs: u32) -> Self127     fn div(self, rhs: u32) -> Self {
128         RelayWeight {
129             as_guard: self.as_guard / rhs,
130             as_middle: self.as_middle / rhs,
131             as_exit: self.as_exit / rhs,
132             as_dir: self.as_dir / rhs,
133         }
134     }
135 }
136 
137 impl RelayWeight {
138     /// Return the largest weight that we give for this kind of relay.
139     // The unwrap() is safe because array is nonempty.
140     #[allow(clippy::unwrap_used)]
max_weight(&self) -> u32141     fn max_weight(&self) -> u32 {
142         [self.as_guard, self.as_middle, self.as_exit, self.as_dir]
143             .iter()
144             .max()
145             .copied()
146             .unwrap()
147     }
148     /// Return the weight we should give this kind of relay's
149     /// bandwidth for a given role.
for_role(&self, role: WeightRole) -> u32150     fn for_role(&self, role: WeightRole) -> u32 {
151         match role {
152             WeightRole::Guard => self.as_guard,
153             WeightRole::Middle => self.as_middle,
154             WeightRole::Exit => self.as_exit,
155             WeightRole::BeginDir => self.as_dir,
156             WeightRole::Unweighted => 1,
157         }
158     }
159 }
160 
161 /// A kind of relay, for the purposes of selecting a relay by weight.
162 ///
163 /// Relays can have or lack the Guard flag, the Exit flag, and the V2Dir flag.
164 /// All together, this makes 8 kinds of relays.
165 // TODO: use bitflags here?
166 struct WeightKind(u8);
167 /// Flag in weightkind for Guard relays.
168 const FLG_GUARD: u8 = 1 << 0;
169 /// Flag in weightkind for Exit relays.
170 const FLG_EXIT: u8 = 1 << 1;
171 /// Flag in weightkind for V2Dir relays.
172 const FLG_DIR: u8 = 1 << 2;
173 
174 impl WeightKind {
175     /// Return the appropriate WeightKind for a relay.
for_rs(rs: &MdConsensusRouterStatus) -> Self176     fn for_rs(rs: &MdConsensusRouterStatus) -> Self {
177         let mut r = 0;
178         if rs.is_flagged_guard() {
179             r |= FLG_GUARD;
180         }
181         if rs.is_flagged_exit() {
182             r |= FLG_EXIT;
183         }
184         if rs.is_flagged_v2dir() {
185             r |= FLG_DIR;
186         }
187         WeightKind(r)
188     }
189     /// Return the index to use for this kind of a relay within a WeightSet.
idx(self) -> usize190     fn idx(self) -> usize {
191         self.0 as usize
192     }
193 }
194 
195 /// Information derived from a consensus to use when picking relays by
196 /// weighted bandwidth.
197 #[derive(Debug, Clone)]
198 pub(crate) struct WeightSet {
199     /// How to find the bandwidth to use when picking a relay by weighted
200     /// bandwidth.
201     ///
202     /// (This tells us us whether to count unmeasured relays, whether
203     /// to look at bandwidths at all, etc.)
204     bandwidth_fn: BandwidthFn,
205     /// Number of bits that we need to right-shift our weighted products
206     /// so that their sum won't overflow u64::MAX.
207     shift: u8,
208     /// A set of RelayWeight values, indexed by [`WeightKind::idx`], used
209     /// to weight different kinds of relays.
210     w: [RelayWeight; 8],
211 }
212 
213 impl WeightSet {
214     /// Find the actual 64-bit weight to use for a given routerstatus when
215     /// considering it for a given role.
216     ///
217     /// NOTE: This function _does not_ consider whether the relay in question
218     /// actually matches the given role.  For example, if `role` is Guard
219     /// we don't check whether or not `rs` actually has the Guard flag.
weight_rs_for_role(&self, rs: &MdConsensusRouterStatus, role: WeightRole) -> u64220     pub(crate) fn weight_rs_for_role(&self, rs: &MdConsensusRouterStatus, role: WeightRole) -> u64 {
221         self.weight_bw_for_role(WeightKind::for_rs(rs), rs.weight(), role)
222     }
223 
224     /// Find the 64-bit weight to report for a relay of `kind` whose weight in
225     /// the consensus is `relay_weight` when using it for `role`.
weight_bw_for_role( &self, kind: WeightKind, relay_weight: &netstatus::RelayWeight, role: WeightRole, ) -> u64226     fn weight_bw_for_role(
227         &self,
228         kind: WeightKind,
229         relay_weight: &netstatus::RelayWeight,
230         role: WeightRole,
231     ) -> u64 {
232         let ws = &self.w[kind.idx()];
233 
234         let router_bw = self.bandwidth_fn.apply(relay_weight);
235         // Note a subtlety here: we multiply the two values _before_
236         // we shift, to improve accuracy.  We know that this will be
237         // safe, since the inputs are both u32, and so cannot overflow
238         // a u64.
239         let router_weight = u64::from(router_bw) * u64::from(ws.for_role(role));
240         router_weight >> self.shift
241     }
242 
243     /// Compute the correct WeightSet for a provided MdConsensus.
from_consensus(consensus: &MdConsensus, params: &NetParameters) -> Self244     pub(crate) fn from_consensus(consensus: &MdConsensus, params: &NetParameters) -> Self {
245         let bandwidth_fn = pick_bandwidth_fn(consensus.relays().iter().map(|rs| rs.weight()));
246         let weight_scale = params.bw_weight_scale.get(); //TODO Escapes the type. XXXXXXX
247 
248         let total_bw = consensus
249             .relays()
250             .iter()
251             .map(|rs| u64::from(bandwidth_fn.apply(rs.weight())))
252             .sum();
253         let p = consensus.bandwidth_weights();
254 
255         Self::from_parts(bandwidth_fn, total_bw, weight_scale, p).validate(consensus)
256     }
257 
258     /// Compute the correct WeightSet given a bandwidth function, a
259     /// weight-scaling parameter, a total amount of bandwidth for all
260     /// relays in the consensus, and a set of bandwidth parameters.
from_parts( bandwidth_fn: BandwidthFn, total_bw: u64, weight_scale: i32, p: &NetParams<i32>, ) -> Self261     fn from_parts(
262         bandwidth_fn: BandwidthFn,
263         total_bw: u64,
264         weight_scale: i32,
265         p: &NetParams<i32>,
266     ) -> Self {
267         /// Find a single RelayWeight, given the names that its bandwidth
268         /// parameters have. The `g` parameter is the weight as a guard, the
269         /// `m` parameter is the weight as a middle relay, the `e` parameter is
270         /// the weight as an exit, and the `d` parameter is the weight as a
271         /// directory.
272         #[allow(clippy::many_single_char_names)]
273         fn single(p: &NetParams<i32>, g: &str, m: &str, e: &str, d: &str) -> RelayWeight {
274             RelayWeight {
275                 as_guard: w_param(p, g),
276                 as_middle: w_param(p, m),
277                 as_exit: w_param(p, e),
278                 as_dir: w_param(p, d),
279             }
280         }
281 
282         assert!(weight_scale >= 0);
283         let weight_scale = weight_scale as u32;
284 
285         // For non-V2Dir relays, we have names for most of their weights.
286         //
287         // (There is no Wge, since we only use Guard relays as guards.  By the
288         // same logic, Wme has no reason to exist, but according to the spec it
289         // does.)
290         let w_none = single(p, "Wgm", "Wmm", "Wem", "Wbm");
291         let w_guard = single(p, "Wgg", "Wmg", "Weg", "Wbg");
292         let w_exit = single(p, "---", "Wme", "Wee", "Wbe");
293         let w_both = single(p, "Wgd", "Wmd", "Wed", "Wbd");
294 
295         // Note that the positions of the elements in this array need to
296         // match the values returned by WeightKind.as_idx().
297         let w = [
298             w_none,
299             w_guard,
300             w_exit,
301             w_both,
302             // The V2Dir values are the same as the non-V2Dir values, except
303             // each is multiplied by an additional factor.
304             //
305             // (We don't need to check for overflow here, since the
306             // authorities make sure that the inputs don't get too big.)
307             (w_none * w_param(p, "Wmb")) / weight_scale,
308             (w_guard * w_param(p, "Wgb")) / weight_scale,
309             (w_exit * w_param(p, "Web")) / weight_scale,
310             (w_both * w_param(p, "Wdb")) / weight_scale,
311         ];
312 
313         // This is the largest weight value.
314         // The unwrap() is safe because `w` is nonempty.
315         #[allow(clippy::unwrap_used)]
316         let w_max = w.iter().map(RelayWeight::max_weight).max().unwrap();
317 
318         // We want "shift" such that (total * w_max) >> shift <= u64::max
319         let shift = calculate_shift(total_bw, u64::from(w_max)) as u8;
320 
321         WeightSet {
322             bandwidth_fn,
323             shift,
324             w,
325         }
326     }
327 
328     /// Assert that we have correctly computed our shift values so that
329     /// our total weighted bws do not exceed u64::MAX.
validate(self, consensus: &MdConsensus) -> Self330     fn validate(self, consensus: &MdConsensus) -> Self {
331         use WeightRole::*;
332         for role in [Guard, Middle, Exit, BeginDir, Unweighted] {
333             let _: u64 = consensus
334                 .relays()
335                 .iter()
336                 .map(|rs| self.weight_rs_for_role(rs, role))
337                 .fold(0_u64, |a, b| {
338                     a.checked_add(b)
339                         .expect("Incorrect relay weight calculation: total exceeded u64::MAX!")
340                 });
341         }
342         self
343     }
344 }
345 
346 /// The value to return if a weight parameter is absent.
347 ///
348 /// (If there are no weights at all, then it's correct to set them all to 1,
349 /// and just use the bandwidths.  If _some_ are present and some are absent,
350 /// then the spec doesn't say what to do, but this behavior appears
351 /// reasonable.)
352 const DFLT_WEIGHT: i32 = 1;
353 
354 /// Return the weight param named 'kwd' in p.
355 ///
356 /// Returns DFLT_WEIGHT if there is no such parameter, and 0
357 /// if `kwd` is "---".
w_param(p: &NetParams<i32>, kwd: &str) -> u32358 fn w_param(p: &NetParams<i32>, kwd: &str) -> u32 {
359     if kwd == "---" {
360         0
361     } else {
362         clamp_to_pos(*p.get(kwd).unwrap_or(&DFLT_WEIGHT))
363     }
364 }
365 
366 /// If `inp` is less than 0, return 0.  Otherwise return `inp` as a u32.
clamp_to_pos(inp: i32) -> u32367 fn clamp_to_pos(inp: i32) -> u32 {
368     // (The spec says that we might encounter negative values here, though
369     // we never actually generate them, and don't plan to generate them.)
370     if inp < 0 {
371         0
372     } else {
373         inp as u32
374     }
375 }
376 
377 /// Compute a 'shift' value such that `(a * b) >> shift` will be contained
378 /// inside 64 bits.
calculate_shift(a: u64, b: u64) -> u32379 fn calculate_shift(a: u64, b: u64) -> u32 {
380     let bits_for_product = log2_upper(a) + log2_upper(b);
381     if bits_for_product < 64 {
382         0
383     } else {
384         bits_for_product - 64
385     }
386 }
387 
388 /// Return an upper bound for the log2 of n.
389 ///
390 /// This function overestimates whenever n is a power of two, but that doesn't
391 /// much matter for the uses we're giving it here.
log2_upper(n: u64) -> u32392 fn log2_upper(n: u64) -> u32 {
393     64 - n.leading_zeros()
394 }
395 
396 #[cfg(test)]
397 mod test {
398     #![allow(clippy::unwrap_used)]
399     use super::*;
400     use netstatus::RelayWeight as RW;
401     use std::net::SocketAddr;
402     use std::time::{Duration, SystemTime};
403     use tor_netdoc::doc::netstatus::{Lifetime, RelayFlags, RouterStatusBuilder};
404 
405     #[test]
t_clamp()406     fn t_clamp() {
407         assert_eq!(clamp_to_pos(32), 32);
408         assert_eq!(clamp_to_pos(std::i32::MAX), std::i32::MAX as u32);
409         assert_eq!(clamp_to_pos(0), 0);
410         assert_eq!(clamp_to_pos(-1), 0);
411         assert_eq!(clamp_to_pos(std::i32::MIN), 0);
412     }
413 
414     #[test]
t_log2()415     fn t_log2() {
416         assert_eq!(log2_upper(std::u64::MAX), 64);
417         assert_eq!(log2_upper(0), 0);
418         assert_eq!(log2_upper(1), 1);
419         assert_eq!(log2_upper(63), 6);
420         assert_eq!(log2_upper(64), 7); // a little buggy but harmless.
421     }
422 
423     #[test]
t_calc_shift()424     fn t_calc_shift() {
425         assert_eq!(calculate_shift(1 << 20, 1 << 20), 0);
426         assert_eq!(calculate_shift(1 << 50, 1 << 10), 0);
427         assert_eq!(calculate_shift(1 << 32, 1 << 33), 3);
428         assert!(((1_u64 << 32) >> 3).checked_mul(1_u64 << 33).is_some());
429         assert_eq!(calculate_shift(432 << 40, 7777 << 40), 38);
430         assert!(((432_u64 << 40) >> 38)
431             .checked_mul(7777_u64 << 40)
432             .is_some());
433     }
434 
435     #[test]
t_pick_bwfunc()436     fn t_pick_bwfunc() {
437         let empty = [];
438         assert_eq!(pick_bandwidth_fn(empty.iter()), BandwidthFn::Uniform);
439 
440         let all_zero = [RW::Unmeasured(0), RW::Measured(0), RW::Unmeasured(0)];
441         assert_eq!(pick_bandwidth_fn(all_zero.iter()), BandwidthFn::Uniform);
442 
443         let all_unmeasured = [RW::Unmeasured(9), RW::Unmeasured(2222)];
444         assert_eq!(
445             pick_bandwidth_fn(all_unmeasured.iter()),
446             BandwidthFn::IncludeUnmeasured
447         );
448 
449         let some_measured = [
450             RW::Unmeasured(10),
451             RW::Measured(7),
452             RW::Measured(4),
453             RW::Unmeasured(0),
454         ];
455         assert_eq!(
456             pick_bandwidth_fn(some_measured.iter()),
457             BandwidthFn::MeasuredOnly
458         );
459 
460         // This corresponds to an open question in
461         // `pick_bandwidth_fn`, about what to do when the only nonzero
462         // weights are unmeasured.
463         let measured_all_zero = [RW::Unmeasured(10), RW::Measured(0)];
464         assert_eq!(
465             pick_bandwidth_fn(measured_all_zero.iter()),
466             BandwidthFn::Uniform
467         );
468     }
469 
470     #[test]
t_apply_bwfn()471     fn t_apply_bwfn() {
472         use netstatus::RelayWeight::*;
473         use BandwidthFn::*;
474 
475         assert_eq!(Uniform.apply(&Measured(7)), 1);
476         assert_eq!(Uniform.apply(&Unmeasured(0)), 1);
477 
478         assert_eq!(IncludeUnmeasured.apply(&Measured(7)), 7);
479         assert_eq!(IncludeUnmeasured.apply(&Unmeasured(8)), 8);
480 
481         assert_eq!(MeasuredOnly.apply(&Measured(9)), 9);
482         assert_eq!(MeasuredOnly.apply(&Unmeasured(10)), 0);
483     }
484 
485     // From a fairly recent Tor consensus.
486     const TESTVEC_PARAMS: &str =
487         "Wbd=0 Wbe=0 Wbg=4096 Wbm=10000 Wdb=10000 Web=10000 Wed=10000 Wee=10000 Weg=10000 Wem=10000 Wgb=10000 Wgd=0 Wgg=5904 Wgm=5904 Wmb=10000 Wmd=0 Wme=0 Wmg=4096 Wmm=10000";
488 
489     #[test]
t_weightset_basic()490     fn t_weightset_basic() {
491         let total_bandwidth = 1_000_000_000;
492         let params = TESTVEC_PARAMS.parse().unwrap();
493         let ws = WeightSet::from_parts(BandwidthFn::MeasuredOnly, total_bandwidth, 10000, &params);
494 
495         assert_eq!(ws.bandwidth_fn, BandwidthFn::MeasuredOnly);
496         assert_eq!(ws.shift, 0);
497 
498         assert_eq!(ws.w[0].as_guard, 5904);
499         assert_eq!(ws.w[(FLG_GUARD) as usize].as_guard, 5904);
500         assert_eq!(ws.w[(FLG_EXIT) as usize].as_exit, 10000);
501         assert_eq!(ws.w[(FLG_EXIT | FLG_GUARD) as usize].as_dir, 0);
502         assert_eq!(ws.w[(FLG_GUARD) as usize].as_dir, 4096);
503         assert_eq!(ws.w[(FLG_GUARD | FLG_DIR) as usize].as_dir, 4096);
504 
505         assert_eq!(
506             ws.weight_bw_for_role(
507                 WeightKind(FLG_GUARD | FLG_DIR),
508                 &RW::Unmeasured(7777),
509                 WeightRole::Guard
510             ),
511             0
512         );
513 
514         assert_eq!(
515             ws.weight_bw_for_role(
516                 WeightKind(FLG_GUARD | FLG_DIR),
517                 &RW::Measured(7777),
518                 WeightRole::Guard
519             ),
520             7777 * 5904
521         );
522 
523         assert_eq!(
524             ws.weight_bw_for_role(
525                 WeightKind(FLG_GUARD | FLG_DIR),
526                 &RW::Measured(7777),
527                 WeightRole::Middle
528             ),
529             7777 * 4096
530         );
531 
532         assert_eq!(
533             ws.weight_bw_for_role(
534                 WeightKind(FLG_GUARD | FLG_DIR),
535                 &RW::Measured(7777),
536                 WeightRole::Exit
537             ),
538             7777 * 10000
539         );
540 
541         assert_eq!(
542             ws.weight_bw_for_role(
543                 WeightKind(FLG_GUARD | FLG_DIR),
544                 &RW::Measured(7777),
545                 WeightRole::BeginDir
546             ),
547             7777 * 4096
548         );
549 
550         assert_eq!(
551             ws.weight_bw_for_role(
552                 WeightKind(FLG_GUARD | FLG_DIR),
553                 &RW::Measured(7777),
554                 WeightRole::Unweighted
555             ),
556             7777
557         );
558 
559         // Now try those last few with routerstatuses.
560         let rs = rs_builder()
561             .set_flags(RelayFlags::GUARD | RelayFlags::V2DIR)
562             .weight(RW::Measured(7777))
563             .build()
564             .unwrap();
565         assert_eq!(ws.weight_rs_for_role(&rs, WeightRole::Exit), 7777 * 10000);
566         assert_eq!(
567             ws.weight_rs_for_role(&rs, WeightRole::BeginDir),
568             7777 * 4096
569         );
570         assert_eq!(ws.weight_rs_for_role(&rs, WeightRole::Unweighted), 7777);
571     }
572 
573     /// Return a routerstatus builder set up to deliver a routerstatus
574     /// with most features disabled.
rs_builder() -> RouterStatusBuilder<[u8; 32]>575     fn rs_builder() -> RouterStatusBuilder<[u8; 32]> {
576         MdConsensus::builder()
577             .rs()
578             .identity([9; 20].into())
579             .add_or_port(SocketAddr::from(([127, 0, 0, 1], 9001)))
580             .doc_digest([9; 32])
581             .protos("".parse().unwrap())
582             .clone()
583     }
584 
585     #[test]
weight_flags()586     fn weight_flags() {
587         let rs1 = rs_builder().set_flags(RelayFlags::EXIT).build().unwrap();
588         assert_eq!(WeightKind::for_rs(&rs1).0, FLG_EXIT);
589 
590         let rs1 = rs_builder().set_flags(RelayFlags::GUARD).build().unwrap();
591         assert_eq!(WeightKind::for_rs(&rs1).0, FLG_GUARD);
592 
593         let rs1 = rs_builder().set_flags(RelayFlags::V2DIR).build().unwrap();
594         assert_eq!(WeightKind::for_rs(&rs1).0, FLG_DIR);
595 
596         let rs1 = rs_builder().build().unwrap();
597         assert_eq!(WeightKind::for_rs(&rs1).0, 0);
598 
599         let rs1 = rs_builder().set_flags(RelayFlags::all()).build().unwrap();
600         assert_eq!(WeightKind::for_rs(&rs1).0, FLG_EXIT | FLG_GUARD | FLG_DIR);
601     }
602 
603     #[test]
weightset_from_consensus()604     fn weightset_from_consensus() {
605         use rand::Rng;
606         let now = SystemTime::now();
607         let one_hour = Duration::new(3600, 0);
608         let mut rng = rand::thread_rng();
609         let mut bld = MdConsensus::builder();
610         bld.consensus_method(34)
611             .lifetime(Lifetime::new(now, now + one_hour, now + 2 * one_hour).unwrap())
612             .weights(TESTVEC_PARAMS.parse().unwrap());
613 
614         // We're going to add a huge amount of unmeasured bandwidth,
615         // and a reasonable amount of  measured bandwidth.
616         for _ in 0..10 {
617             rs_builder()
618                 .identity(rng.gen::<[u8; 20]>().into()) // random id
619                 .weight(RW::Unmeasured(1_000_000))
620                 .set_flags(RelayFlags::GUARD | RelayFlags::EXIT)
621                 .build_into(&mut bld)
622                 .unwrap();
623         }
624         for n in 0..30 {
625             rs_builder()
626                 .identity(rng.gen::<[u8; 20]>().into()) // random id
627                 .weight(RW::Measured(1_000 * n))
628                 .set_flags(RelayFlags::GUARD | RelayFlags::EXIT)
629                 .build_into(&mut bld)
630                 .unwrap();
631         }
632 
633         let consensus = bld.testing_consensus().unwrap();
634         let params = NetParameters::default();
635         let ws = WeightSet::from_consensus(&consensus, &params);
636 
637         assert_eq!(ws.bandwidth_fn, BandwidthFn::MeasuredOnly);
638         assert_eq!(ws.shift, 0);
639         assert_eq!(ws.w[0].as_guard, 5904);
640         assert_eq!(ws.w[5].as_guard, 5904);
641         assert_eq!(ws.w[5].as_middle, 4096);
642     }
643 }
644