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, ¶ms);
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, ¶ms);
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