1
//! Calculate consensus routerstatus entries for one relay - select RS entries
2

            
3
use super::*;
4
use rs_common::*;
5

            
6
impl ConsensusesFromVotes<PlainPreamble> for Vec<RouterStatusVote> {
7
    type PlainOutput = Vec<netstatus::plain::RouterStatus>;
8
    type MdOutput = Vec<netstatus::md::RouterStatus>;
9

            
10
    fn consensuses<'i>(
11
        context: ConsensusContextRefs<PlainPreamble>,
12
        inputs: impl ComponentInVotes<&'i Self>,
13
    ) -> Result<(Self::PlainOutput, Self::MdOutput), ConsensusError>
14
    where
15
        Self: 'i,
16
    {
17
        // ---------- Calculate IdTuples to include ----------
18
        //
19
        // https://spec.torproject.org/dir-spec/computing-consensus.html#choosing-relay-ids
20

            
21
        let output_ids;
22
        let voters_per_id; // buffer; TODO want to use super let instead
23
        {
24
            let mut per_id = HashMap::<IdTuple, VoterSet>::new();
25
            for (vnum, voter_rs_list) in inputs.clone() {
26
                for rs in voter_rs_list {
27
                    let id = IdTuple::from_rs(rs);
28
                    per_id.entry(id).or_default().insert(vnum);
29
                }
30
            }
31
            voters_per_id = per_id;
32

            
33
            output_ids = voters_per_id
34
                .iter()
35
                .filter(|(_id, voters)| context.is_more_than_half_all_auths(voters.len()))
36
                .map(|(id, _voters)| id)
37
                .collect::<HashSet<&IdTuple>>();
38

            
39
            let discrep_report_filter = DiscrepReportFilter::from_outputs(&output_ids);
40

            
41
            for (id, voters) in &voters_per_id {
42
                if discrep_report_filter.id_is_discrepant(id) {
43
                    // Spec says to log the loose identities.  We log them in their pairs.
44
                    info!("discrepant relay identity {id} ({} voters)", voters.len());
45
                }
46
            }
47
        };
48

            
49
        // ---------- Choose StatusTuple for each output identity ----------
50
        //
51
        // https://spec.torproject.org/dir-spec/computing-consensus.html#choosing-relay-descs
52

            
53
        let status_tuple_cmp_key = |(status_tuple, voter_set): &'_ (
54
            StatusTuple,
55
            Box<RouterStatusPerRelevantVoter<'_>>,
56
        )| {
57
            (
58
                // matches the largest set
59
                voter_set.iter().filter(|rs| rs.is_some()).count(),
60
                // breaking ties in favor of the most recently published
61
                status_tuple.publication,
62
                // and then in favor of the smaller server descriptor digest
63
                cmp::Reverse(status_tuple.doc_digest),
64
            )
65
        };
66

            
67
        let output_rs_inputs = {
68
            let mut candidate_status_tuples = HashMap::<
69
                IdTuple,
70
                // for each possible output status-tuple, who voted for it:
71
                HashMap<StatusTuple, Box<RouterStatusPerRelevantVoter>>,
72
            >::new();
73
            for (vnum, voter_rs_list) in inputs.clone() {
74
                for rs in voter_rs_list {
75
                    let id = IdTuple::from_rs(rs);
76
                    if !output_ids.contains(&id) {
77
                        continue;
78
                    }
79
                    let status_tuple = StatusTuple::from_rs(rs);
80
                    *candidate_status_tuples
81
                        .entry(id)
82
                        .or_default()
83
                        .entry(status_tuple)
84
                        .or_insert_with(|| ti_vec![None; context.votes.len()].into_boxed_slice())
85
                        // If multiple routerstatuses in the vote for the same id,
86
                        // we choose arbitrarily
87
                        .get_mut(vnum)
88
                        .ok_or_else(|| internal!("{vnum:?} out of range"))?
89
                        // Store in the slot for this voter
90
                        = Some(rs);
91
                }
92
            }
93

            
94
            candidate_status_tuples
95
                .into_iter()
96
                .map(|(id, status_tuples)| {
97
                    // Determine the inputs to the per-routerstatus calculation.
98

            
99
                    // Select the preferred status-tuple.
100
                    let (status_tuple, per_voter) = status_tuples
101
                        .into_iter()
102
                        .max_by_key(status_tuple_cmp_key)
103
                        .expect("entry with no content, impossible");
104

            
105
                    ResolvedRouterStatusInputs {
106
                        id,
107
                        status_tuple,
108
                        per_voter,
109
                    }
110
                })
111
        };
112

            
113
        output_rs_inputs
114
            .map(|resolved_rs_input| resolved_rs_input.consensuses(context))
115
            .process_results(|rss_iter| {
116
                rss_iter
117
                    .map(|both| {
118
                        both.map(|(plain, md)| (Some(plain), md))
119
                            .unwrap_or_default()
120
                    })
121
                    .filter_collect_unzip()
122
            })
123
    }
124
}