1use std::time::Duration;
4
5pub type NodeId = u64;
7
8pub type Term = u64;
10
11pub type LogIndex = u64;
13
14#[derive(Debug, Clone, Copy, PartialEq, Eq)]
16pub enum NodeState {
17 Follower,
19 Candidate,
21 Leader,
23}
24
25impl NodeState {
26 pub fn as_str(&self) -> &'static str {
28 match self {
29 NodeState::Follower => "Follower",
30 NodeState::Candidate => "Candidate",
31 NodeState::Leader => "Leader",
32 }
33 }
34}
35
36#[derive(Debug, Clone)]
38pub struct RaftConfig {
39 pub node_id: NodeId,
41 pub peers: Vec<NodeId>,
43 pub election_timeout_range: (u64, u64),
45 pub heartbeat_interval: u64,
47 pub max_entries_per_message: usize,
49 pub enable_compaction: bool,
51 pub snapshot_threshold: usize,
53}
54
55impl RaftConfig {
56 pub fn new(node_id: NodeId, peers: Vec<NodeId>) -> Self {
58 Self {
59 node_id,
60 peers,
61 election_timeout_range: (150, 300),
62 heartbeat_interval: 50,
63 max_entries_per_message: 100,
64 enable_compaction: true,
65 snapshot_threshold: 10000,
66 }
67 }
68
69 pub fn random_election_timeout(&self) -> Duration {
71 use std::collections::hash_map::RandomState;
72 use std::hash::BuildHasher;
73
74 let (min, max) = self.election_timeout_range;
75 let range = max - min;
76
77 let now = std::time::SystemTime::now()
79 .duration_since(std::time::UNIX_EPOCH)
80 .map(|d| d.as_nanos())
81 .unwrap_or(0);
82
83 let random_value = RandomState::new().hash_one(now);
84
85 let timeout_ms = min + (random_value % range);
86 Duration::from_millis(timeout_ms)
87 }
88
89 pub fn heartbeat_interval(&self) -> Duration {
91 Duration::from_millis(self.heartbeat_interval)
92 }
93
94 pub fn validate(&self) -> Result<(), String> {
96 if !self.peers.contains(&self.node_id) {
98 return Err(format!("Node ID {} not found in peers list", self.node_id));
99 }
100
101 if self.peers.len() % 2 == 0 {
103 return Err(format!(
104 "Raft requires odd number of nodes, got {}",
105 self.peers.len()
106 ));
107 }
108
109 if self.peers.len() < 3 {
111 return Err(format!(
112 "Raft requires at least 3 nodes for fault tolerance, got {}",
113 self.peers.len()
114 ));
115 }
116
117 let (min, max) = self.election_timeout_range;
119 if min >= max {
120 return Err(format!(
121 "Election timeout min ({}) must be less than max ({})",
122 min, max
123 ));
124 }
125
126 if self.heartbeat_interval >= min {
128 return Err(format!(
129 "Heartbeat interval ({}) must be less than election timeout min ({})",
130 self.heartbeat_interval, min
131 ));
132 }
133
134 Ok(())
135 }
136
137 pub fn quorum_size(&self) -> usize {
139 self.peers.len() / 2 + 1
140 }
141}
142
143#[cfg(test)]
144mod tests {
145 use super::*;
146
147 #[test]
148 fn test_node_state_as_str() {
149 assert_eq!(NodeState::Follower.as_str(), "Follower");
150 assert_eq!(NodeState::Candidate.as_str(), "Candidate");
151 assert_eq!(NodeState::Leader.as_str(), "Leader");
152 }
153
154 #[test]
155 fn test_raft_config_new() {
156 let config = RaftConfig::new(1, vec![1, 2, 3]);
157 assert_eq!(config.node_id, 1);
158 assert_eq!(config.peers, vec![1, 2, 3]);
159 assert_eq!(config.election_timeout_range, (150, 300));
160 assert_eq!(config.heartbeat_interval, 50);
161 }
162
163 #[test]
164 fn test_raft_config_validate_valid() {
165 let config = RaftConfig::new(1, vec![1, 2, 3]);
166 assert!(config.validate().is_ok());
167 }
168
169 #[test]
170 fn test_raft_config_validate_node_not_in_peers() {
171 let config = RaftConfig::new(4, vec![1, 2, 3]);
172 assert!(config.validate().is_err());
173 }
174
175 #[test]
176 fn test_raft_config_validate_even_number_of_nodes() {
177 let config = RaftConfig::new(1, vec![1, 2, 3, 4]);
178 assert!(config.validate().is_err());
179 }
180
181 #[test]
182 fn test_raft_config_validate_too_few_nodes() {
183 let config = RaftConfig::new(1, vec![1]);
184 assert!(config.validate().is_err());
185 }
186
187 #[test]
188 fn test_raft_config_quorum_size() {
189 let config = RaftConfig::new(1, vec![1, 2, 3]);
190 assert_eq!(config.quorum_size(), 2);
191
192 let config = RaftConfig::new(1, vec![1, 2, 3, 4, 5]);
193 assert_eq!(config.quorum_size(), 3);
194 }
195
196 #[test]
197 fn test_random_election_timeout() {
198 let config = RaftConfig::new(1, vec![1, 2, 3]);
199 let timeout1 = config.random_election_timeout();
200 let timeout2 = config.random_election_timeout();
201
202 assert!(timeout1.as_millis() >= 150);
204 assert!(timeout1.as_millis() <= 300);
205 assert!(timeout2.as_millis() >= 150);
206 assert!(timeout2.as_millis() <= 300);
207 }
208}