1mod stage;
4
5use ozzy_proto::NodeId;
6
7use crate::{
8 Configuration, JournalGeneration, LogSource, NormalReplica, Prefix, PreparedOperation,
9 PromiseTicket, ReplicationError, Scope, ViewChange, ViewChangeError,
10};
11
12#[derive(Debug, Clone, Copy, PartialEq, Eq)]
15pub struct StartView {
16 pub scope: Scope,
18 pub generation: JournalGeneration,
20 pub accepted: Prefix,
22 pub committed: Prefix,
24}
25
26#[derive(Debug, Clone, Copy, PartialEq, Eq)]
29pub struct ReceivedStart {
30 pub from: NodeId,
32 pub start: StartView,
34}
35
36#[derive(Debug, Clone, Copy, PartialEq, Eq)]
44pub struct InstallTicket {
45 scope: Scope,
46 previous_generation: JournalGeneration,
47 generation: JournalGeneration,
48 source: LogSource,
49 protected_committed: Prefix,
50 retained: Prefix,
51 committed: Prefix,
52}
53
54impl InstallTicket {
55 pub const fn scope(self) -> Scope {
57 self.scope
58 }
59 pub const fn previous_generation(self) -> JournalGeneration {
61 self.previous_generation
62 }
63 pub const fn generation(self) -> JournalGeneration {
65 self.generation
66 }
67 pub const fn source(self) -> LogSource {
69 self.source
70 }
71 pub const fn protected_committed(self) -> Prefix {
73 self.protected_committed
74 }
75 pub const fn retained(self) -> Prefix {
79 self.retained
80 }
81 pub const fn accepted(self) -> Prefix {
83 self.source.accepted
84 }
85 pub const fn committed(self) -> Prefix {
87 self.committed
88 }
89}
90
91#[derive(Debug)]
93#[expect(
94 clippy::large_enum_variant,
95 reason = "rare ownership transition moves preallocated state without boxing"
96)]
97pub enum InstallOutcome {
98 Normal(NormalReplica),
101 ViewChanging(ViewChange),
103}
104
105#[derive(Debug)]
110pub struct RejectedInstallation {
111 view_change: ViewChange,
112 error: ViewChangeError,
113}
114
115impl RejectedInstallation {
116 pub const fn error(&self) -> ViewChangeError {
118 self.error
119 }
120 pub fn into_view_change(self) -> ViewChange {
122 self.view_change
123 }
124}
125
126impl std::fmt::Display for RejectedInstallation {
127 fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
128 std::fmt::Display::fmt(&self.error, formatter)
129 }
130}
131
132impl std::error::Error for RejectedInstallation {
133 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
134 Some(&self.error)
135 }
136}
137
138#[derive(Debug)]
140pub struct InstallingView {
141 ticket: InstallTicket,
142 previous_promise: PromiseTicket,
143 state: InstallState,
144 configuration: Configuration,
145 requested_view: u64,
146 validated_through: Prefix,
147}
148
149#[derive(Debug)]
151#[expect(
152 clippy::large_enum_variant,
153 reason = "the staged role moves without boxing"
154)]
155enum InstallState {
156 Pending(NormalReplica),
157 Failed,
159 Completed,
161}
162
163impl InstallState {
164 fn pending(&self) -> Result<&NormalReplica, ViewChangeError> {
165 match self {
166 Self::Pending(normal) => Ok(normal),
167 Self::Failed => Err(ViewChangeError::Faulted),
168 Self::Completed => Err(ViewChangeError::InstallationCompleted),
169 }
170 }
171
172 fn take(&mut self) -> Result<NormalReplica, ViewChangeError> {
174 match std::mem::replace(self, Self::Completed) {
175 Self::Pending(normal) => Ok(normal),
176 Self::Failed => {
177 *self = Self::Failed;
178 Err(ViewChangeError::Faulted)
179 }
180 Self::Completed => Err(ViewChangeError::InstallationCompleted),
181 }
182 }
183}
184
185impl InstallingView {
186 pub(crate) fn requested_scope(&self) -> Scope {
187 Scope {
188 view: self.requested_view,
189 ..self.ticket.scope()
190 }
191 }
192
193 pub const fn ticket(&self) -> InstallTicket {
195 self.ticket
196 }
197
198 pub fn validate_suffix(
206 &mut self,
207 operations: &[PreparedOperation],
208 ) -> Result<(), ViewChangeError> {
209 let (through, _) = self.state.pending()?.validate_install_chunk(
210 &self.ticket,
211 self.validated_through,
212 operations,
213 )?;
214 self.validated_through = through;
215 Ok(())
216 }
217
218 pub fn observe_view(&mut self, from: NodeId, scope: Scope) -> Result<(), ViewChangeError> {
221 self.require_pending()?;
222 self.configuration.voter_index(from)?;
223 if (Scope {
224 view: scope.view,
225 ..self.ticket.scope
226 }) != scope
227 {
228 return Err(ReplicationError::ScopeMismatch.into());
229 }
230 self.requested_view = self.requested_view.max(scope.view);
231 Ok(())
232 }
233
234 pub fn request_view(&mut self, view: u64) -> Result<(), ViewChangeError> {
236 self.require_pending()?;
237 if view <= self.requested_view {
238 return Err(ViewChangeError::ViewNotHigher);
239 }
240 self.requested_view = view;
241 Ok(())
242 }
243
244 pub fn complete(
250 &mut self,
251 ticket: InstallTicket,
252 applied: Prefix,
253 ) -> Result<InstallOutcome, ViewChangeError> {
254 self.require_ticket(&ticket)?;
255 if self.validated_through != ticket.accepted() {
256 return Err(ViewChangeError::HistoryMissing);
257 }
258 if applied != ticket.committed {
259 return Err(ViewChangeError::ApplicationPending);
260 }
261 let mut normal = self.state.take()?;
262 normal.activate_installed();
263 if self.requested_view > ticket.scope.view {
264 Ok(InstallOutcome::ViewChanging(
265 normal.into_view_change(self.requested_view)?,
266 ))
267 } else {
268 Ok(InstallOutcome::Normal(normal))
269 }
270 }
271
272 pub fn complete_abandon(
279 &mut self,
280 ticket: InstallTicket,
281 ) -> Result<ViewChange, ViewChangeError> {
282 self.require_ticket(&ticket)?;
283 if self.requested_view <= ticket.scope.view {
284 return Err(ViewChangeError::ViewNotHigher);
285 }
286 ViewChange::after_abandon(
287 self.state.take()?,
288 self.previous_promise,
289 self.requested_view,
290 )
291 }
292
293 pub fn fail(&mut self, ticket: InstallTicket) -> Result<(), ViewChangeError> {
295 self.require_ticket(&ticket)?;
296 self.state = InstallState::Failed;
297 Ok(())
298 }
299
300 pub(crate) fn require_pending(&self) -> Result<(), ViewChangeError> {
301 self.state.pending().map(|_| ())
302 }
303
304 fn require_ticket(&self, ticket: &InstallTicket) -> Result<(), ViewChangeError> {
305 self.require_pending()?;
306 if *ticket != self.ticket {
307 return Err(ViewChangeError::StaleInstallation);
308 }
309 Ok(())
310 }
311}