1
//! Calculate consensus preamble
2

            
3
use super::*;
4

            
5
// TODO DIRAUTH tests of consensus calculation
6

            
7
impl ConsensusesFromVotes<()> for netstatus::vote::Preamble {
8
    type PlainOutput = netstatus::plain::Preamble;
9
    type MdOutput = netstatus::md::Preamble;
10

            
11
1
    fn consensuses<'i>(
12
1
        context: ConsensusContextRefs<()>,
13
1
        inputs: impl Iterator<Item = (VoterNum, &'i Self)> + Clone,
14
1
    ) -> Result<(Self::PlainOutput, Self::MdOutput), ConsensusError>
15
1
    where
16
1
        Self: 'i,
17
    {
18
1
        calc! { both.lifetime }
19
1
        calc! { both.consensus_method = ((*context.method).into(),) }
20
1
        calc! { both.consensus_methods = NotPresent }
21
1
        calc! { both.published = NotPresent }
22
1
        calc! { md,plain .known_flags <+ doc_relay_flags_union }
23
1
        calc! { md,plain .params <+ netparams_preamble_aggregate }
24

            
25
        // TODO DIRAUTH replace dummy values
26
1
        calc! { both.proto_statuses = Default::default() }
27
1
        calc! { both.voting_delay }
28
1
        calc! { both.client_versions }
29
1
        calc! { both.server_versions }
30
1
        calc! { both.shared_rand = Default::default() }
31

            
32
1
        Ok(construct_both! {
33
1
            netstatus::plain::Preamble, netstatus::md::Preamble {
34
1
                both. lifetime, consensus_method, consensus_methods, published;
35
1
                both. known_flags, params, proto_statuses, voting_delay;
36
1
            } {
37
1
                both. client_versions, server_versions, shared_rand;
38
1
            }
39
1
        })
40
1
    }
41
}
42

            
43
impl<AC> Aggregate<AC> for netstatus::Lifetime {
44
    type Output = Self;
45

            
46
2
    fn aggregate<'i>(
47
2
        context: ConsensusContextRefs<AC>,
48
2
        inputs: impl ComponentInVotes<&'i Self>,
49
2
    ) -> Result<Self, ConsensusError>
50
2
    where
51
2
        Self: 'i,
52
    {
53
2
        calc! { out.valid_after <+ functions::low_median }
54
2
        calc! { out.fresh_until <+ functions::low_median }
55
2
        calc! { out.valid_until <+ functions::low_median }
56

            
57
        // We want these to be in increasing order, or the resulting consensus is nonsensical.
58
        // This could only be violated if some of the inputs votes didn't have them in
59
        // increasing order.   TODO arti#2786
60

            
61
2
        Ok(construct! {
62
2
            netstatus::Lifetime {
63
2
                out. valid_after, fresh_until, valid_until;
64
2
            } {
65
2
            }
66
2
        })
67
2
    }
68
}
69

            
70
impl<AC> Aggregate<AC> for netstatus::VotingDelay {
71
    type Output = Self;
72

            
73
2
    fn aggregate<'i>(
74
2
        context: ConsensusContextRefs<AC>,
75
2
        inputs: impl ComponentInVotes<&'i Self>,
76
2
    ) -> Result<Self, ConsensusError>
77
2
    where
78
2
        Self: 'i,
79
    {
80
        // Spec just says "median".  Low median will do; it's in seconds.
81
2
        calc! { out.vote_seconds <+ functions::low_median }
82
2
        calc! { out.dist_seconds <+ functions::low_median  }
83
2
        Ok(construct! {
84
2
            netstatus::VotingDelay {
85
2
                out. vote_seconds, dist_seconds;
86
2
            } {
87
2
            }
88
2
        })
89
2
    }
90
}
91

            
92
/// Calculate the union of two `DocRelayFlags`z
93
2
pub(super) fn doc_relay_flags_union<'i>(
94
2
    _context: ConsensusContextRefs<()>,
95
2
    inputs: impl ComponentInVotes<&'i DocRelayFlags>,
96
2
) -> Result<DocRelayFlags, ConsensusError> {
97
2
    Ok(inputs
98
10
        .map(|(_vnum, flags)| flags.iter_incl_unknown())
99
2
        .flatten_ok()
100
2
        .process_results(|flag_iters| flag_iters.unique().collect())?)
101
2
}
102

            
103
/// Calculate the `params` line in a consensus
104
//
105
// Not `impl Aggregate` because NetParams is a very general type used in all sorts of
106
// places, and we don't want them to get this impl by mistake.
107
#[allow(clippy::needless_pass_by_value, clippy::unnecessary_wraps)]
108
2
pub(super) fn netparams_preamble_aggregate<'i>(
109
2
    context: ConsensusContextRefs<()>,
110
2
    inputs: impl ComponentInVotes<&'i NetParams<i32>>,
111
2
) -> Result<NetParams<i32>, ConsensusError> {
112
2
    Ok(inputs
113
2
        .clone()
114
10
        .flat_map(|(_vnum, i)| i.iter().map(|(k, _v)| k))
115
2
        .unique()
116
90
        .filter_map(|k| {
117
            // "every keyword on which a majority of authorities (total
118
            // authorities, not just those participating in this vote) voted
119
            // on, or if at least three authorities voted for that parameter.
120
            const MIN_THRESH: usize = 3;
121

            
122
900
            let opinions = inputs.clone().filter_map(|(_vnum, i)| i.get(k)).copied();
123
90
            let n_opinions = opinions.clone().count();
124
90
            let want = context.is_more_than_half_all_auths(n_opinions) || n_opinions >= MIN_THRESH;
125
90
            if !want {
126
                return None;
127
90
            }
128

            
129
            const_assert!(MIN_THRESH > 0);
130
90
            let out = functions::low_median_raw(opinions)
131
90
                .expect("more than half is always >0, and so is MIN_THRESH");
132
90
            Some((String::from(k), out))
133
90
        })
134
2
        .collect())
135
2
}
136

            
137
impl<AC> Aggregate<AC> for netstatus::RecommendedTorVersions {
138
    type Output = Self;
139

            
140
4
    fn aggregate<'i>(
141
4
        _context: ConsensusContextRefs<AC>,
142
4
        inputs: impl ComponentInVotes<&'i Self>,
143
4
    ) -> Result<Self, ConsensusError>
144
4
    where
145
4
        Self: 'i,
146
    {
147
180
        let inputs = inputs.map(|(_vnum, i)| i).filter(|i| i.is_known());
148

            
149
4
        Ok(inputs
150
4
            .clone()
151
20
            .flat_map(|i| i.iter())
152
4
            .unique()
153
32
            .try_filter(|k| {
154
32
                is_true_for_more_than_half_of(
155
                    //
156
32
                    inputs.clone(),
157
160
                    |i| Ok::<_, Bug>(i.contains(&**k)),
158
                )
159
32
            })
160
4
            .process_results(|ks| netstatus::RecommendedTorVersions::from_iter(ks))?
161
4
            .map_err(into_internal!(
162
                "passed through software version was invalid"
163
            ))?)
164
4
    }
165
}