Skip to main content

nodedb_raft/
message.rs

1/// A single entry in the Raft log.
2///
3/// Each entry carries the term in which it was created and an opaque command
4/// payload. The state machine interprets the payload; Raft only cares about
5/// term and index for consistency.
6#[derive(
7    Debug,
8    Clone,
9    PartialEq,
10    Eq,
11    serde::Serialize,
12    serde::Deserialize,
13    rkyv::Archive,
14    rkyv::Serialize,
15    rkyv::Deserialize,
16    zerompk::ToMessagePack,
17    zerompk::FromMessagePack,
18)]
19pub struct LogEntry {
20    /// The term when this entry was received by the leader.
21    pub term: u64,
22    /// Log index (1-based, monotonically increasing).
23    pub index: u64,
24    /// Opaque command for the state machine. Empty for no-op entries
25    /// (appended by newly elected leaders per Raft paper §5.4.2).
26    pub data: Vec<u8>,
27}
28
29/// AppendEntries RPC (Raft paper Figure 2).
30///
31/// Invoked by leader to replicate log entries; also used as heartbeat
32/// (entries is empty).
33#[derive(
34    Debug,
35    Clone,
36    serde::Serialize,
37    serde::Deserialize,
38    rkyv::Archive,
39    rkyv::Serialize,
40    rkyv::Deserialize,
41    zerompk::ToMessagePack,
42    zerompk::FromMessagePack,
43)]
44pub struct AppendEntriesRequest {
45    /// Leader's term.
46    pub term: u64,
47    /// Leader's ID so followers can redirect clients.
48    pub leader_id: u64,
49    /// Index of log entry immediately preceding new ones.
50    pub prev_log_index: u64,
51    /// Term of prev_log_index entry.
52    pub prev_log_term: u64,
53    /// Log entries to store (empty for heartbeat).
54    pub entries: Vec<LogEntry>,
55    /// Leader's commit_index.
56    pub leader_commit: u64,
57    /// Raft group ID for Multi-Raft routing.
58    pub group_id: u64,
59}
60
61#[derive(
62    Debug,
63    Clone,
64    serde::Serialize,
65    serde::Deserialize,
66    rkyv::Archive,
67    rkyv::Serialize,
68    rkyv::Deserialize,
69    zerompk::ToMessagePack,
70    zerompk::FromMessagePack,
71)]
72pub struct AppendEntriesResponse {
73    /// Current term, for leader to update itself.
74    pub term: u64,
75    /// True if follower contained entry matching prev_log_index and prev_log_term.
76    pub success: bool,
77    /// Optimization: on rejection, the follower's last log index.
78    /// Allows leader to skip back faster than decrementing one-by-one.
79    pub last_log_index: u64,
80}
81
82/// RequestVote RPC (Raft paper Figure 2).
83#[derive(
84    Debug,
85    Clone,
86    serde::Serialize,
87    serde::Deserialize,
88    rkyv::Archive,
89    rkyv::Serialize,
90    rkyv::Deserialize,
91    zerompk::ToMessagePack,
92    zerompk::FromMessagePack,
93)]
94pub struct RequestVoteRequest {
95    /// Candidate's term.
96    pub term: u64,
97    /// Candidate requesting vote.
98    pub candidate_id: u64,
99    /// Index of candidate's last log entry.
100    pub last_log_index: u64,
101    /// Term of candidate's last log entry.
102    pub last_log_term: u64,
103    /// Raft group ID for Multi-Raft routing.
104    pub group_id: u64,
105}
106
107#[derive(
108    Debug,
109    Clone,
110    serde::Serialize,
111    serde::Deserialize,
112    rkyv::Archive,
113    rkyv::Serialize,
114    rkyv::Deserialize,
115    zerompk::ToMessagePack,
116    zerompk::FromMessagePack,
117)]
118pub struct RequestVoteResponse {
119    /// Current term, for candidate to update itself.
120    pub term: u64,
121    /// True means candidate received vote.
122    pub vote_granted: bool,
123}
124
125/// InstallSnapshot RPC (Raft paper Figure 13).
126///
127/// Used when a follower is too far behind for log-based catch-up.
128#[derive(
129    Debug,
130    Clone,
131    serde::Serialize,
132    serde::Deserialize,
133    rkyv::Archive,
134    rkyv::Serialize,
135    rkyv::Deserialize,
136    zerompk::ToMessagePack,
137    zerompk::FromMessagePack,
138)]
139pub struct InstallSnapshotRequest {
140    /// Leader's term.
141    pub term: u64,
142    /// Leader ID.
143    pub leader_id: u64,
144    /// The snapshot replaces all entries up through and including this index.
145    pub last_included_index: u64,
146    /// Term of last_included_index.
147    pub last_included_term: u64,
148    /// Byte offset where chunk is positioned in the snapshot file.
149    pub offset: u64,
150    /// Raw bytes of the snapshot chunk.
151    pub data: Vec<u8>,
152    /// True if this is the last chunk.
153    pub done: bool,
154    /// Raft group ID for Multi-Raft routing.
155    pub group_id: u64,
156}
157
158#[derive(
159    Debug,
160    Clone,
161    serde::Serialize,
162    serde::Deserialize,
163    rkyv::Archive,
164    rkyv::Serialize,
165    rkyv::Deserialize,
166    zerompk::ToMessagePack,
167    zerompk::FromMessagePack,
168)]
169pub struct InstallSnapshotResponse {
170    /// Current term, for leader to update itself.
171    pub term: u64,
172}
173
174#[cfg(test)]
175mod tests {
176    use super::*;
177
178    #[test]
179    fn log_entry_serde_roundtrip() {
180        let entry = LogEntry {
181            term: 5,
182            index: 42,
183            data: b"put key=val".to_vec(),
184        };
185        let json = sonic_rs::to_string(&entry).unwrap();
186        let decoded: LogEntry = sonic_rs::from_str(&json).unwrap();
187        assert_eq!(entry, decoded);
188    }
189
190    #[test]
191    fn append_entries_heartbeat() {
192        let req = AppendEntriesRequest {
193            term: 3,
194            leader_id: 1,
195            prev_log_index: 10,
196            prev_log_term: 2,
197            entries: vec![],
198            leader_commit: 8,
199            group_id: 0,
200        };
201        assert!(req.entries.is_empty());
202    }
203
204    #[test]
205    fn request_vote_serde_roundtrip() {
206        let req = RequestVoteRequest {
207            term: 7,
208            candidate_id: 2,
209            last_log_index: 100,
210            last_log_term: 6,
211            group_id: 5,
212        };
213        let json = sonic_rs::to_string(&req).unwrap();
214        let decoded: RequestVoteRequest = sonic_rs::from_str(&json).unwrap();
215        assert_eq!(req.term, decoded.term);
216        assert_eq!(req.candidate_id, decoded.candidate_id);
217    }
218}