fedimint_aleph_bft/
config.rs

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
12/// A function answering the question of how long to delay the n-th retry.
13pub type DelaySchedule = Arc<dyn Fn(usize) -> Duration + Sync + Send + 'static>;
14
15/// A function answering the question of how many nodes to query on the n-th (0-based) try.
16pub type RecipientCountSchedule = Arc<dyn Fn(usize) -> usize + Sync + Send + 'static>;
17
18/// Configuration of several parameters related to delaying various tasks.
19#[derive(Clone)]
20pub struct DelayConfig {
21    /// Tick frequency of the Member. Governs internal task queue of the Member.
22    pub tick_interval: Duration,
23    /// Minimum frequency of broadcast of top known units. Units have to be at least this old to be
24    /// rebroadcast at all.
25    pub unit_rebroadcast_interval_min: Duration,
26    /// Maximum frequency of broadcast of top known units.
27    pub unit_rebroadcast_interval_max: Duration,
28    /// unit_creation_delay(k) represents the delay between creating the (k-1)th and kth unit.
29    pub unit_creation_delay: DelaySchedule,
30    /// coord_request_delay(k) represents the delay between the kth and (k+1)st try when requesting
31    /// a unit by coords.
32    pub coord_request_delay: DelaySchedule,
33    /// coord_request_recipients(k) represents the number of nodes to ask at the kth try when
34    /// requesting a unit by coords.
35    pub coord_request_recipients: RecipientCountSchedule,
36    /// parent_request_delay(k) represents the delay between the kth and (k+1)st try when requesting
37    /// unknown parents of a unit.
38    pub parent_request_delay: DelaySchedule,
39    /// parent_request_recipients(k) represents the number of nodes to ask at the kth try when
40    /// requesting unknown parents of a unit.
41    pub parent_request_recipients: RecipientCountSchedule,
42    /// newest_request_delay(k) represents the delay between the kth and (k+1)st try when sending
43    /// a broadcast request for newest units
44    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/// Main configuration of the consensus. We refer to [the documentation](https://cardinal-cryptography.github.io/AlephBFT/aleph_bft_api.html#34-alephbft-sessions)
64/// Section 3.4 for a discussion of some of these parameters and their significance.
65#[derive(Clone, Debug)]
66pub struct Config {
67    /// Identification number of the Member=0,..,(n_members-1).
68    node_ix: NodeIndex,
69    /// Id of the session for which this instance is run.
70    session_id: SessionId,
71    /// The size of the committee running the consensus.
72    n_members: NodeCount,
73    /// Configuration of several parameters related to delaying various tasks.
74    delay_config: DelayConfig,
75    /// Maximum allowable round of a unit.
76    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    // This gives:
104    // base_delay, for t <= start_exp_delay,
105    // base_delay * exp_base^(t - start_exp_delay), for t > start_exp_delay.
106    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    // the above will make it u64::MAX if it exceeds u64
114    Duration::from_millis(delay)
115}
116
117/// Creates a [`Config`] which wraps the passed arguments. `time_to_reach_max_round` is a lower bound
118/// on the time needed to reach the maximum round expected by the user and is only used for verification.
119pub 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
144/// Creates a [`Config`], allowing the user to omit specifying the `delay_config` in which case it will be
145/// set to default, suggested by the creators of this package. `time_to_reach_max_round` is a lower bound
146/// on the time needed to reach the maximum round expected by the user and is only used for verification.
147pub 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
165/// Creates a [`DelayConfig`] with default parameters, suggested by the creators of this package.
166pub 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
180/// 5000, 500, 500, 500, ... (till step 3000), 500, 500*1.005, 500*(1.005)^2, 500*(1.005)^3, ..., 10742207 (last step)
181fn 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
188/// 0, 50, 1000, 3000, 6000, 9000, ...
189fn 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
198/// 3, 3, 3, 1, 1, 1, 1, ...
199fn 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}