de_mls/conversation/
config.rs1use std::time::Duration;
4
5use crate::DEFAULT_MAX_RETRIES;
6use crate::ProposalKind;
7use crate::protos::de_mls::messages::v1::TimingConfig;
8
9pub const DEFAULT_COMMIT_INACTIVITY_DURATION: Duration = Duration::from_secs(60);
12
13pub const DEFAULT_PROPOSAL_EXPIRATION: Duration = Duration::from_secs(600);
16
17pub const DEFAULT_CONSENSUS_TIMEOUT: Duration = Duration::from_secs(30);
20
21pub const DEFAULT_RECOVERY_INACTIVITY_DURATION: Duration = Duration::from_secs(5);
25
26pub const DEFAULT_VOTING_DELAY: Duration = Duration::from_secs(10);
29
30pub const DEFAULT_ELECTION_VOTING_DELAY: Duration = Duration::from_secs(5);
33
34pub const DEFAULT_LIVENESS_CRITERIA_YES: bool = true;
35
36pub const DEFAULT_PENDING_UPDATE_MAX_EPOCHS: u32 = 3;
37
38#[derive(Debug, Clone)]
42pub struct ConversationConfig {
43 pub commit_inactivity_duration: Duration,
46 pub freeze_duration: Duration,
50 pub recovery_inactivity_duration: Duration,
53 pub proposal_expiration: Duration,
55 pub consensus_timeout: Duration,
56 pub pending_update_max_epochs: u32,
60 pub max_reelection_attempts: u32,
63 pub voting_delay: Duration,
68 pub election_voting_delay: Duration,
71 pub liveness_criteria_yes: bool,
75}
76
77impl Default for ConversationConfig {
78 fn default() -> Self {
79 Self {
80 commit_inactivity_duration: DEFAULT_COMMIT_INACTIVITY_DURATION,
81 freeze_duration: DEFAULT_COMMIT_INACTIVITY_DURATION / 2,
82 recovery_inactivity_duration: DEFAULT_RECOVERY_INACTIVITY_DURATION,
83 proposal_expiration: DEFAULT_PROPOSAL_EXPIRATION,
84 consensus_timeout: DEFAULT_CONSENSUS_TIMEOUT,
85 pending_update_max_epochs: DEFAULT_PENDING_UPDATE_MAX_EPOCHS,
86 max_reelection_attempts: DEFAULT_MAX_RETRIES,
87 voting_delay: DEFAULT_VOTING_DELAY,
88 election_voting_delay: DEFAULT_ELECTION_VOTING_DELAY,
89 liveness_criteria_yes: DEFAULT_LIVENESS_CRITERIA_YES,
90 }
91 }
92}
93
94impl ConversationConfig {
95 pub fn voting_delay_for(&self, kind: ProposalKind) -> Duration {
97 if kind.is_steward_election() {
98 self.election_voting_delay
99 } else {
100 self.voting_delay
101 }
102 }
103
104 pub fn apply_timing(&mut self, timing: &TimingConfig) {
108 apply_nonzero_ms(
109 &mut self.commit_inactivity_duration,
110 timing.commit_inactivity_duration_ms,
111 );
112 apply_nonzero_ms(&mut self.freeze_duration, timing.freeze_duration_ms);
113 apply_nonzero_ms(
114 &mut self.recovery_inactivity_duration,
115 timing.recovery_inactivity_duration_ms,
116 );
117 apply_nonzero_ms(&mut self.proposal_expiration, timing.proposal_expiration_ms);
118 apply_nonzero_ms(&mut self.consensus_timeout, timing.consensus_timeout_ms);
119 }
120}
121
122fn apply_nonzero_ms(field: &mut Duration, wire_ms: u64) {
126 if wire_ms != 0 {
127 *field = Duration::from_millis(wire_ms);
128 }
129}
130
131impl From<&ConversationConfig> for TimingConfig {
134 fn from(config: &ConversationConfig) -> Self {
135 Self {
136 commit_inactivity_duration_ms: config.commit_inactivity_duration.as_millis() as u64,
137 freeze_duration_ms: config.freeze_duration.as_millis() as u64,
138 recovery_inactivity_duration_ms: config.recovery_inactivity_duration.as_millis() as u64,
139 proposal_expiration_ms: config.proposal_expiration.as_millis() as u64,
140 consensus_timeout_ms: config.consensus_timeout.as_millis() as u64,
141 }
142 }
143}
144
145#[cfg(test)]
146mod tests {
147 use super::*;
148
149 #[test]
150 fn timing_config_round_trip() {
151 let original = ConversationConfig {
152 commit_inactivity_duration: Duration::from_millis(100),
153 freeze_duration: Duration::from_millis(200),
154 recovery_inactivity_duration: Duration::from_millis(300),
155 proposal_expiration: Duration::from_millis(400),
156 consensus_timeout: Duration::from_millis(500),
157 ..ConversationConfig::default()
158 };
159 let timing = TimingConfig::from(&original);
160 let mut applied = ConversationConfig::default();
161 applied.apply_timing(&timing);
162 assert_eq!(
163 applied.commit_inactivity_duration,
164 Duration::from_millis(100)
165 );
166 assert_eq!(applied.freeze_duration, Duration::from_millis(200));
167 assert_eq!(
168 applied.recovery_inactivity_duration,
169 Duration::from_millis(300)
170 );
171 assert_eq!(applied.proposal_expiration, Duration::from_millis(400));
172 assert_eq!(applied.consensus_timeout, Duration::from_millis(500));
173 }
174
175 #[test]
176 fn apply_timing_ignores_zero_durations() {
177 let mut config = ConversationConfig {
178 consensus_timeout: Duration::from_secs(30),
179 commit_inactivity_duration: Duration::from_secs(60),
180 ..ConversationConfig::default()
181 };
182 let timing = TimingConfig {
183 consensus_timeout_ms: 0,
184 commit_inactivity_duration_ms: 0,
185 freeze_duration_ms: 250,
186 recovery_inactivity_duration_ms: 0,
187 proposal_expiration_ms: 0,
188 };
189 config.apply_timing(&timing);
190 assert_eq!(config.consensus_timeout, Duration::from_secs(30));
192 assert_eq!(config.commit_inactivity_duration, Duration::from_secs(60));
193 assert_eq!(config.freeze_duration, Duration::from_millis(250));
195 }
196
197 #[test]
198 fn voting_delay_dispatch_on_proposal_kind() {
199 let config = ConversationConfig {
200 voting_delay: Duration::from_secs(7),
201 election_voting_delay: Duration::from_secs(3),
202 ..ConversationConfig::default()
203 };
204 assert_eq!(
205 config.voting_delay_for(ProposalKind::Commit),
206 Duration::from_secs(7)
207 );
208 assert_eq!(
209 config.voting_delay_for(ProposalKind::StewardElection),
210 Duration::from_secs(3)
211 );
212 }
213}