1use crate::{NodeCount, NodeIndex, Round, SessionId};
2use log::error;
3use std::{
4 fmt::{Debug, Formatter},
5 sync::Arc,
6 time::Duration,
7};
8
9#[derive(Debug)]
10pub struct InvalidConfigError;
11
12pub type DelaySchedule = Arc<dyn Fn(usize) -> Duration + Sync + Send + 'static>;
14
15pub type RecipientCountSchedule = Arc<dyn Fn(usize) -> usize + Sync + Send + 'static>;
17
18#[derive(Clone)]
20pub struct DelayConfig {
21 pub tick_interval: Duration,
23 pub unit_rebroadcast_interval_min: Duration,
26 pub unit_rebroadcast_interval_max: Duration,
28 pub unit_creation_delay: DelaySchedule,
30 pub coord_request_delay: DelaySchedule,
33 pub coord_request_recipients: RecipientCountSchedule,
36 pub parent_request_delay: DelaySchedule,
39 pub parent_request_recipients: RecipientCountSchedule,
42 pub newest_request_delay: DelaySchedule,
45}
46
47impl Debug for DelayConfig {
48 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
49 f.debug_struct("DelayConfig")
50 .field("tick interval", &self.tick_interval)
51 .field(
52 "min unit rebroadcast interval",
53 &self.unit_rebroadcast_interval_min,
54 )
55 .field(
56 "max unit rebroadcast interval",
57 &self.unit_rebroadcast_interval_max,
58 )
59 .finish()
60 }
61}
62
63#[derive(Clone, Debug)]
66pub struct Config {
67 node_ix: NodeIndex,
69 session_id: SessionId,
71 n_members: NodeCount,
73 delay_config: DelayConfig,
75 max_round: Round,
77}
78
79impl Config {
80 pub fn node_ix(&self) -> NodeIndex {
81 self.node_ix
82 }
83 pub fn session_id(&self) -> SessionId {
84 self.session_id
85 }
86 pub fn n_members(&self) -> NodeCount {
87 self.n_members
88 }
89 pub fn delay_config(&self) -> &DelayConfig {
90 &self.delay_config
91 }
92 pub fn max_round(&self) -> Round {
93 self.max_round
94 }
95}
96
97pub fn exponential_slowdown(
98 t: usize,
99 base_delay: f64,
100 start_exp_delay: usize,
101 exp_base: f64,
102) -> Duration {
103 let delay = if t < start_exp_delay {
107 base_delay
108 } else {
109 let power = t - start_exp_delay;
110 base_delay * exp_base.powf(power as f64)
111 };
112 let delay = delay.round() as u64;
113 Duration::from_millis(delay)
115}
116
117pub fn create_config(
120 n_members: NodeCount,
121 node_ix: NodeIndex,
122 session_id: SessionId,
123 max_round: Round,
124 delay_config: DelayConfig,
125 time_to_reach_max_round: Duration,
126) -> Result<Config, InvalidConfigError> {
127 if time_to_reach_round(max_round, &delay_config.unit_creation_delay) < time_to_reach_max_round {
128 error!(
129 target: "AlephBFT-config",
130 "Reaching max_round will happen too fast with the given Config. Consider increasing max_round or lowering time_to_reach_max_round."
131 );
132 return Err(InvalidConfigError);
133 }
134
135 Ok(Config {
136 node_ix,
137 session_id,
138 n_members,
139 delay_config,
140 max_round,
141 })
142}
143
144pub fn default_config(
148 n_members: NodeCount,
149 node_ix: NodeIndex,
150 session_id: SessionId,
151 max_round: Round,
152 time_to_reach_max_round: Duration,
153) -> Result<Config, InvalidConfigError> {
154 let delay_config = default_delay_config();
155 create_config(
156 n_members,
157 node_ix,
158 session_id,
159 max_round,
160 delay_config,
161 time_to_reach_max_round,
162 )
163}
164
165pub fn default_delay_config() -> DelayConfig {
167 DelayConfig {
168 tick_interval: Duration::from_millis(10),
169 unit_rebroadcast_interval_min: Duration::from_millis(15000),
170 unit_rebroadcast_interval_max: Duration::from_millis(20000),
171 unit_creation_delay: default_unit_creation_delay(),
172 coord_request_delay: default_coord_request_delay(),
173 coord_request_recipients: default_coord_request_recipients(),
174 parent_request_delay: Arc::new(|_| Duration::from_millis(3000)),
175 parent_request_recipients: Arc::new(|_| 1),
176 newest_request_delay: Arc::new(|_| Duration::from_millis(3000)),
177 }
178}
179
180fn default_unit_creation_delay() -> DelaySchedule {
182 Arc::new(|t| match t {
183 0 => Duration::from_millis(5000),
184 _ => exponential_slowdown(t, 500.0, 3000, 1.005),
185 })
186}
187
188fn default_coord_request_delay() -> DelaySchedule {
190 Arc::new(|t| match t {
191 0 => Duration::from_millis(0),
192 1 => Duration::from_millis(50),
193 2 => Duration::from_millis(1000),
194 _ => Duration::from_millis(3000 * (t as u64 - 2)),
195 })
196}
197
198fn default_coord_request_recipients() -> RecipientCountSchedule {
200 Arc::new(|t| if t <= 2 { 3 } else { 1 })
201}
202
203fn time_to_reach_round(round: Round, delay_schedule: &DelaySchedule) -> Duration {
204 let mut total_time = Duration::from_millis(0);
205 for r in 0..round {
206 total_time += delay_schedule(r as usize);
207 }
208 total_time
209}
210
211#[cfg(test)]
212mod tests {
213 use crate::{
214 config::{
215 default_coord_request_delay, default_coord_request_recipients, time_to_reach_round,
216 DelaySchedule,
217 },
218 create_config, exponential_slowdown, DelayConfig, NodeCount, NodeIndex,
219 };
220 use std::{sync::Arc, time::Duration};
221
222 const MILLIS_IN_WEEK: u64 = 1000 * 60 * 60 * 24 * 7;
223
224 fn delay_config_for_tests() -> DelayConfig {
225 DelayConfig {
226 tick_interval: Duration::from_millis(10),
227 unit_rebroadcast_interval_min: Duration::from_millis(15000),
228 unit_rebroadcast_interval_max: Duration::from_millis(20000),
229 unit_creation_delay: Arc::new(move |t| match t {
230 0 => Duration::from_millis(2000),
231 _ => exponential_slowdown(t, 300.0, 5000, 1.005),
232 }),
233 coord_request_delay: default_coord_request_delay(),
234 coord_request_recipients: default_coord_request_recipients(),
235 parent_request_delay: Arc::new(|_| Duration::from_millis(3000)),
236 parent_request_recipients: Arc::new(|_| 1),
237 newest_request_delay: Arc::new(|_| Duration::from_millis(3000)),
238 }
239 }
240
241 #[test]
242 fn time_to_reach_delay_is_correct() {
243 let delay_schedule: DelaySchedule = Arc::new(|r| {
244 Duration::from_millis(match r {
245 0 => 2,
246 1 => 3,
247 2 => 5,
248 3 => 7,
249 4 => 11,
250 _ => 13,
251 })
252 });
253
254 assert_eq!(
255 time_to_reach_round(0, &delay_schedule),
256 Duration::from_millis(0)
257 );
258 assert_eq!(
259 time_to_reach_round(1, &delay_schedule),
260 Duration::from_millis(2)
261 );
262 assert_eq!(
263 time_to_reach_round(5, &delay_schedule),
264 Duration::from_millis(28)
265 );
266 assert_eq!(
267 time_to_reach_round(10, &delay_schedule),
268 Duration::from_millis(93)
269 );
270 }
271
272 #[test]
273 fn low_round_not_causing_slowdown_fails_the_check() {
274 let config = create_config(
275 NodeCount(5),
276 NodeIndex(1),
277 3,
278 5000,
279 delay_config_for_tests(),
280 Duration::from_millis(MILLIS_IN_WEEK),
281 );
282
283 assert!(config.is_err());
284 }
285
286 #[test]
287 fn high_round_causing_slowdown_passes_the_check() {
288 let config = create_config(
289 NodeCount(5),
290 NodeIndex(1),
291 3,
292 7000,
293 delay_config_for_tests(),
294 Duration::from_millis(MILLIS_IN_WEEK),
295 );
296
297 assert!(config.is_ok());
298 }
299}