minbft/peer_message/
req_view_change.rs

1//! Defines a message of type [ReqViewChange].
2//! A replica should broadcast a [ReqViewChange] when the current primary turns
3//! out to be faulty.
4//! The primary is seen as faulty if it does not respond to a client request
5//! within the set timeout duration.
6//! For further explanation, see the paragraph "Servers: view change operation"
7//! of section four of ["Efficient Byzantine Fault-Tolerance" by Veronese et al](https://doi.org/10.1109/TC.2011.221).
8
9use anyhow::Result;
10use serde::{Deserialize, Serialize};
11use shared_ids::ReplicaId;
12use tracing::{error, trace};
13
14use crate::{error::InnerError, Config, View};
15
16/// Defines a message of type [ReqViewChange].
17/// Contains the previous [View] and the next [View] ([View] to be changed to).
18#[derive(Serialize, Deserialize, Clone, Debug)]
19pub(crate) struct ReqViewChange {
20    /// The previous [View] that turned out to be faulty.
21    pub(crate) prev_view: View,
22    /// The next [View] to which is to be changed to.
23    pub(crate) next_view: View,
24}
25
26impl ReqViewChange {
27    /// Validates the [ReqViewChange].
28    /// The previous [View] must be smaller than the next [View].
29    ///
30    /// # Arguments
31    ///
32    /// * `origin`- The ID of the replica from which the [ReqViewChange]
33    /// originates.
34    /// * `config` - The config of the replica.
35    ///
36    /// # Return Value
37    ///
38    /// [Ok] if the validation succeeds, otherwise [InnerError].
39    pub(crate) fn validate(&self, origin: ReplicaId, config: &Config) -> Result<(), InnerError> {
40        trace!(
41            "Validating ReqViewChange (previous view: {:?}, next view: {:?}) ...",
42            self.prev_view,
43            self.next_view
44        );
45        if self.prev_view < self.next_view {
46            trace!(
47                "Successfully validated ReqViewChange (previous view: {:?}, next view: {:?}).",
48                self.prev_view,
49                self.next_view
50            );
51            Ok(())
52        } else {
53            error!(
54                "Failed validating ReqViewChange (previous view: {:?}, next view: {:?}): Previous view set is not smaller than next view set.",
55                self.prev_view, self.next_view
56            );
57            Err(InnerError::ReqViewChangeIncompatiblePrevNextView {
58                receiver: config.id,
59                origin,
60            })
61        }
62    }
63}
64
65#[cfg(test)]
66
67mod tests {
68    use std::num::NonZeroU64;
69
70    use crate::{
71        tests::{
72            create_default_configs_for_replicas, create_random_valid_req_vc_next_dir_subsequent,
73            create_random_valid_req_vc_next_jump, get_random_replica_id,
74        },
75        View,
76    };
77
78    use rand::{thread_rng, Rng};
79
80    use rstest::rstest;
81    use shared_ids::ReplicaId;
82
83    use super::ReqViewChange;
84
85    /// Tests if the validation of a [ReqViewChange], in which the next [View]
86    /// is bigger and directly subsequent to the previous [View], succeeds.
87    ///
88    /// # Arguments
89    ///
90    /// * `n` - The number of replicas.
91    #[rstest]
92    fn validate_valid_req_view_change_next_dir_subsequent(
93        #[values(3, 4, 5, 6, 7, 8, 9, 10)] n: u64,
94    ) {
95        let n_parsed = NonZeroU64::new(n).unwrap();
96        let mut rng = thread_rng();
97        let origin = get_random_replica_id(n_parsed, &mut rng);
98
99        let t = n / 2;
100
101        let configs = create_default_configs_for_replicas(n_parsed, t);
102
103        let req_view_change = create_random_valid_req_vc_next_dir_subsequent(n_parsed, &mut rng);
104
105        for i in 0..n {
106            let rep_id = ReplicaId::from_u64(i);
107            let config = configs.get(&rep_id).unwrap();
108            assert!((req_view_change.validate(origin, config)).is_ok());
109        }
110    }
111
112    /// Tests if the validation of a [ReqViewChange], in which the next [View] is bigger
113    /// but not subsequent to the previous [View], succeeds.
114    ///
115    /// # Arguments
116    ///
117    /// * `n` - The number of replicas.
118    #[rstest]
119    fn validate_valid_req_view_change_next_jump(#[values(3, 4, 5, 6, 7, 8, 9, 10)] n: u64) {
120        let n_parsed = NonZeroU64::new(n).unwrap();
121        let mut rng = thread_rng();
122        let origin = get_random_replica_id(n_parsed, &mut rng);
123
124        let t = n / 2;
125
126        let configs = create_default_configs_for_replicas(n_parsed, t);
127
128        let req_view_change = create_random_valid_req_vc_next_jump(n_parsed, &mut rng);
129
130        for i in 0..n {
131            let rep_id = ReplicaId::from_u64(i);
132            let config = configs.get(&rep_id).unwrap();
133            assert!((req_view_change.validate(origin, config)).is_ok());
134        }
135    }
136
137    /// Tests if a [ReqViewChange], in which the next [View] is smaller
138    /// and subsequent to the previous [View], is validated to false.
139    ///
140    /// # Arguments
141    ///
142    /// * `n` - The number of replicas.
143    #[rstest]
144    fn validate_invalid_req_view_change_prev_dir_subsequent(
145        #[values(3, 4, 5, 6, 7, 8, 9, 10)] n: u64,
146    ) {
147        use rand::thread_rng;
148
149        use crate::tests::{
150            create_default_configs_for_replicas, get_random_included_index, get_random_replica_id,
151        };
152
153        let n_parsed = NonZeroU64::new(n).unwrap();
154        let mut rng = thread_rng();
155        let origin = get_random_replica_id(n_parsed, &mut rng);
156
157        let rand_factor_0 = get_random_included_index(n as usize * 10, None, &mut rng);
158
159        let next_view_nr = rng.gen_range(0..=rand_factor_0 as u64 * n);
160        let prev_view_nr = next_view_nr + 1;
161
162        let t = n / 2;
163
164        let prev_view = View(prev_view_nr);
165        let next_view = View(next_view_nr);
166
167        let configs = create_default_configs_for_replicas(n_parsed, t);
168
169        let req_view_change = ReqViewChange {
170            prev_view,
171            next_view,
172        };
173
174        for i in 0..n {
175            let rep_id = ReplicaId::from_u64(i);
176            let config = configs.get(&rep_id).unwrap();
177            assert!((req_view_change.validate(origin, config)).is_err());
178        }
179    }
180
181    /// Tests if the validation of a [ReqViewChange], in which the next [View] is smaller
182    /// and subsequent to the previous [View], results in an error.
183    ///
184    /// # Arguments
185    ///
186    /// * `n` - The number of replicas.
187    #[rstest]
188    fn validate_invalid_req_view_change_jump(#[values(3, 4, 5, 6, 7, 8, 9, 10)] n: u64) {
189        use rand::thread_rng;
190
191        use crate::tests::{
192            create_default_configs_for_replicas, get_random_included_index, get_random_replica_id,
193        };
194
195        let n_parsed = NonZeroU64::new(n).unwrap();
196        let mut rng = thread_rng();
197        let origin = get_random_replica_id(n_parsed, &mut rng);
198
199        let rand_factor_0 = get_random_included_index(n as usize * 10, None, &mut rng);
200        let rand_summand = rng.gen_range(1..=n * 10);
201
202        let next_view_nr = rng.gen_range(0..=rand_factor_0 as u64 * n);
203        let prev_view_nr = next_view_nr + rand_summand;
204
205        let t = n / 2;
206
207        let prev_view = View(prev_view_nr);
208        let next_view = View(next_view_nr);
209
210        let configs = create_default_configs_for_replicas(n_parsed, t);
211
212        let req_view_change = ReqViewChange {
213            prev_view,
214            next_view,
215        };
216
217        for i in 0..n {
218            let rep_id = ReplicaId::from_u64(i);
219            let config = configs.get(&rep_id).unwrap();
220            assert!((req_view_change.validate(origin, config)).is_err());
221        }
222    }
223}