laminar_core/cluster/control/topology/
preparation.rs1use super::{
4 TopologyAdmissionPhase, TopologyAdmissionStatus, TopologyCompatibilityRef, TopologyError,
5};
6use crate::checkpoint::{CheckpointParticipant, MAX_CHECKPOINT_PARTICIPANTS};
7use serde::{Deserialize, Serialize};
8
9pub const TOPOLOGY_PREPARATION_PROTOCOL_VERSION: u16 = 2;
11
12pub const TOPOLOGY_SUBMISSION_PROTOCOL_VERSION: u16 = 6;
15
16#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
18#[serde(deny_unknown_fields)]
19pub struct TopologyParticipantCertificate {
20 pub participant: CheckpointParticipant,
22 pub process_term: u64,
24 pub protocol_version: u16,
26 pub authority_sequence: u64,
28}
29
30#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
32#[serde(deny_unknown_fields)]
33pub struct TopologyPreparation {
34 pub compatibility: TopologyCompatibilityRef,
36 pub certificates: Vec<TopologyParticipantCertificate>,
38 pub complete_sequence: Option<u64>,
40}
41
42impl TopologyAdmissionStatus {
43 pub(crate) fn validate_preparation(&self, head: u64) -> Result<(), TopologyError> {
44 let Some(preparation) = &self.preparation else {
45 return if self.phase == TopologyAdmissionPhase::Preparing {
46 Err(TopologyError::Invalid(
47 "preparing operation has no descriptor".into(),
48 ))
49 } else {
50 Ok(())
51 };
52 };
53 preparation.compatibility.validate()?;
54 if preparation.certificates.len() > MAX_CHECKPOINT_PARTICIPANTS
55 || !preparation
56 .certificates
57 .windows(2)
58 .all(|pair| pair[0].participant.node_id < pair[1].participant.node_id)
59 || preparation.certificates.iter().any(|certificate| {
60 certificate.participant.node_id == 0
61 || certificate.participant.boot_incarnation.is_nil()
62 || certificate.process_term == 0
63 || !matches!(
64 certificate.protocol_version,
65 TOPOLOGY_PREPARATION_PROTOCOL_VERSION
66 | TOPOLOGY_SUBMISSION_PROTOCOL_VERSION
67 )
68 || certificate.authority_sequence <= self.admitted_sequence
69 || certificate.authority_sequence > self.status_sequence
70 || certificate.authority_sequence > head
71 })
72 || !preparation
73 .certificates
74 .windows(2)
75 .all(|pair| pair[0].protocol_version == pair[1].protocol_version)
76 || preparation.complete_sequence.is_some_and(|sequence| {
77 preparation
78 .certificates
79 .iter()
80 .map(|cert| cert.authority_sequence)
81 .max()
82 != Some(sequence)
83 })
84 || (self.is_planned()
85 && (!preparation.certificates.is_empty()
86 || preparation.complete_sequence.is_some()))
87 || (self.phase == TopologyAdmissionPhase::Preparing
88 && preparation.certificates.is_empty())
89 || (self.cut.is_some()
90 && preparation.complete_sequence.is_none_or(|sequence| {
91 sequence >= self.cut.as_ref().map_or(0, |cut| cut.bound_sequence)
92 }))
93 {
94 return Err(TopologyError::Invalid(
95 "invalid participant preparation evidence".into(),
96 ));
97 }
98 Ok(())
99 }
100
101 pub(crate) fn validate_preparation_successor(
102 &self,
103 after: &Self,
104 sequence: u64,
105 ) -> Result<(), TopologyError> {
106 match (&self.preparation, &after.preparation) {
107 (None, None) => Ok(()),
108 (Some(prior), Some(next)) if prior.compatibility == next.compatibility => {
109 if prior
110 .certificates
111 .iter()
112 .any(|cert| !next.certificates.contains(cert))
113 || prior
114 .complete_sequence
115 .is_some_and(|complete| next.complete_sequence != Some(complete))
116 || (prior != next
117 && (!matches!(
118 self.phase,
119 TopologyAdmissionPhase::Planned | TopologyAdmissionPhase::Preparing
120 ) || after.phase != TopologyAdmissionPhase::Preparing
121 || next.certificates.len() != prior.certificates.len() + 1
122 || next
123 .certificates
124 .iter()
125 .filter(|cert| !prior.certificates.contains(cert))
126 .any(|cert| cert.authority_sequence != sequence)
127 || next
128 .complete_sequence
129 .is_some_and(|complete| complete != sequence)))
130 {
131 return Err(TopologyError::Invalid(
132 "authority cannot rewrite, forget or append stale preparation certificates"
133 .into(),
134 ));
135 }
136 Ok(())
137 }
138 _ => Err(TopologyError::Invalid(
139 "authority cannot replace an admitted descriptor binding".into(),
140 )),
141 }
142 }
143}