1use crate::query_mapped_rows;
2use coven_protocol::store_commit::CircleAck;
3
4use super::*;
5
6pub(crate) fn verify_next_local_store_ack_on(
7 conn: &Connection,
8 authority: &coven_protocol::store_commit::ReferencedStoreDeviceRegistration,
9 bytes: &[u8],
10 prepared: &PreparedExactObject,
11) -> Result<(StoreAckRef, StoreAck), DbError> {
12 let registration_ref = authority.reference();
13 let registration = authority.value();
14 let root = ®istration.store_root;
15 let unverified: StoreAck = serde_json::from_slice(bytes)
16 .map_err(|error| DbError::context("parse Store acknowledgement", error))?;
17 if &unverified.registration != registration_ref {
18 return Err(DbError::Message(
19 "Store acknowledgement author differs from local activation".to_string(),
20 ));
21 }
22 let reference = StoreAckRef {
23 registration: registration_ref.clone(),
24 sequence: unverified.sequence,
25 ack_hash: unverified.ack_hash(),
26 object: prepared.reference().clone(),
27 };
28 let ack = StoreAck::parse_at(bytes, root, &reference, registration)
29 .map_err(|error| DbError::context("verify Store acknowledgement", error))?;
30 let previous = load_published_store_ack_on(conn)?;
31 let (expected_sequence, expected_predecessor, expected_slot) = match &previous {
32 Some(previous) => (
33 previous.reference.sequence.checked_add(1).ok_or_else(|| {
34 DbError::Message("Store acknowledgement sequence overflow".to_string())
35 })?,
36 Some(previous.reference.object.clone()),
37 previous.successor_slot.clone(),
38 ),
39 None => (1, None, store_ack_first_slot(registration)?.clone()),
40 };
41 if ack.sequence != expected_sequence
42 || ack.successor.predecessor != expected_predecessor
43 || prepared.reference().slot() != &expected_slot
44 {
45 return Err(DbError::Message(
46 "Store acknowledgement does not extend the exact local stream".to_string(),
47 ));
48 }
49 let next_sequence = ack
50 .sequence
51 .checked_add(1)
52 .ok_or_else(|| DbError::Message("Store acknowledgement sequence overflow".to_string()))?;
53 if ack.successor.activation
54 != registration
55 .store_acknowledgement_activation(registration_ref)
56 .map_err(DbError::from)?
57 .activation_id()
58 || ack.successor.next_slot.logical_key()
59 != format!(
60 "{}.json",
61 ack_slot_prefix(®istration.device_id.to_string(), next_sequence)
62 )
63 {
64 return Err(DbError::Message(
65 "Store acknowledgement successor is outside its activated exact stream".to_string(),
66 ));
67 }
68 Ok((reference, ack))
69}
70
71pub(crate) fn store_ack_first_slot(
72 registration: &StoreDeviceRegistration,
73) -> Result<&coven_protocol::objects::ObjectSlot, DbError> {
74 match ®istration.acknowledgements {
75 coven_protocol::store_commit::DeviceStreamAnchor::StoreAcknowledgements { first_slot } => {
76 Ok(first_slot)
77 }
78 _ => Err(DbError::Message(
79 "local Store registration has no acknowledgement stream anchor".to_string(),
80 )),
81 }
82}
83
84pub fn store_snapshot_first_slot(
85 registration: &StoreDeviceRegistration,
86) -> Result<&coven_protocol::objects::ObjectSlot, DbError> {
87 match ®istration.snapshots {
88 coven_protocol::store_commit::DeviceStreamAnchor::StoreSnapshots { first_slot } => {
89 Ok(first_slot)
90 }
91 _ => Err(DbError::Message(
92 "local Store registration has no snapshot stream anchor".to_string(),
93 )),
94 }
95}
96
97pub(crate) fn load_published_store_ack_on(
98 conn: &Connection,
99) -> Result<Option<PublishedStoreAck>, DbError> {
100 conn.query_row(
101 "SELECT ack_ref, successor_slot, standing FROM published_store_acks \
102 WHERE singleton = 1",
103 [],
104 |row| {
105 Ok((
106 row.get::<_, String>(0)?,
107 row.get::<_, String>(1)?,
108 row.get::<_, Option<String>>(2)?,
109 ))
110 },
111 )
112 .optional()
113 .map_err(DbError::from)?
114 .map(|(reference, successor_slot, standing)| {
115 let reference: StoreAckRef = serde_json::from_str(&reference)
116 .map_err(|error| DbError::context("published Store acknowledgement ref", error))?;
117 if reference.sequence == 0 {
118 return Err(DbError::Message(
119 "published Store acknowledgement uses sequence zero".to_string(),
120 ));
121 }
122 Ok(PublishedStoreAck {
123 reference,
124 successor_slot: serde_json::from_str(&successor_slot).map_err(|error| {
125 DbError::context("published Store acknowledgement successor slot", error)
126 })?,
127 standing: standing
128 .map(|state| {
129 serde_json::from_str(&state)
130 .map_err(|error| DbError::context("standing Store acknowledgement", error))
131 })
132 .transpose()?,
133 })
134 })
135 .transpose()
136}
137
138pub(crate) fn finish_outbound_store_ack_on(
139 conn: &Connection,
140 reference: &StoreAckRef,
141 successor_slot: &coven_protocol::objects::ObjectSlot,
142 standing: &coven_protocol::store_commit::StandingStoreAck,
143) -> Result<(), DbError> {
144 let removed = conn
145 .execute(
146 "DELETE FROM outbound_store_acks WHERE singleton = 1 AND ack_ref = ?1",
147 [serde_json::to_string(reference)
148 .map_err(|error| DbError::context("serialize Store acknowledgement ref", error))?],
149 )
150 .map_err(DbError::from)?;
151 if removed != 1 {
152 return Err(DbError::Message(
153 "outbound Store acknowledgement disappeared".to_string(),
154 ));
155 }
156 let successor_slot = serde_json::to_string(successor_slot).map_err(|error| {
157 DbError::context("serialize Store acknowledgement successor slot", error)
158 })?;
159 let standing = serde_json::to_string(standing)
160 .map_err(|error| DbError::context("serialize standing Store acknowledgement", error))?;
161 conn.execute(
162 "INSERT INTO published_store_acks (singleton, ack_ref, successor_slot, standing) \
163 VALUES (1, ?1, ?2, ?3) \
164 ON CONFLICT(singleton) DO UPDATE SET \
165 ack_ref = excluded.ack_ref, successor_slot = excluded.successor_slot, \
166 standing = excluded.standing",
167 (
168 serde_json::to_string(reference).map_err(|error| {
169 DbError::context("serialize published Store acknowledgement ref", error)
170 })?,
171 successor_slot,
172 standing,
173 ),
174 )
175 .map(|_| ())
176 .map_err(DbError::from)
177}
178
179pub(crate) fn load_outbound_circle_acks_on(
180 conn: &Connection,
181 authority: &coven_protocol::store_commit::ReferencedStoreDeviceRegistration,
182) -> Result<Vec<coven_protocol::prepared_commit::CircleAckActivation>, DbError> {
183 let registration = authority.value();
184 let root = ®istration.store_root;
185 let rows = query_mapped_rows(
186 conn,
187 "SELECT ack_ref, ack_bytes, prepared_object FROM outbound_circle_acks
188 ORDER BY circle_id",
189 [],
190 |row| {
191 Ok((
192 row.get::<_, String>(0)?,
193 row.get::<_, Vec<u8>>(1)?,
194 row.get::<_, String>(2)?,
195 ))
196 },
197 )?;
198 let mut activations = Vec::with_capacity(rows.len());
199 for (reference, bytes, prepared) in rows {
200 let reference: coven_protocol::store_commit::CircleAckRef =
201 serde_json::from_str(&reference)
202 .map_err(|error| DbError::context("outbound Circle acknowledgement ref", error))?;
203 let prepared: PreparedExactObject = serde_json::from_str(&prepared)
204 .map_err(|error| DbError::context("outbound prepared Circle acknowledgement", error))?;
205 if prepared.reference() != &reference.object {
206 return Err(DbError::Message(
207 "outbound Circle acknowledgement ref differs from its prepared object".to_string(),
208 ));
209 }
210 let value = CircleAck::parse_at(&bytes, root, &reference, registration)
211 .map_err(|error| DbError::context("outbound Circle acknowledgement", error))?;
212 activations.push(coven_protocol::prepared_commit::CircleAckActivation {
213 reference,
214 ack: ExactProtocolObject {
215 value,
216 bytes,
217 prepared,
218 },
219 });
220 }
221 Ok(activations)
222}
223
224pub(crate) fn load_expected_outbound_store_ack_on(
227 conn: &Connection,
228 authority: &coven_protocol::store_commit::ReferencedStoreDeviceRegistration,
229 expected: &coven_protocol::store_commit::StoreAckRef,
230 mismatch: &str,
231) -> Result<OutboundStoreAck, DbError> {
232 let outbound = load_outbound_store_ack_on(conn, authority)?
233 .ok_or_else(|| DbError::Message("outbound Store acknowledgement is absent".to_string()))?;
234 if &outbound.reference != expected {
235 return Err(DbError::Message(mismatch.to_string()));
236 }
237 Ok(outbound)
238}
239
240pub(crate) fn set_outbound_store_ack_activation_on(
244 conn: &Connection,
245 expected: &coven_protocol::store_commit::StoreAckRef,
246 activation: &crate::OutboundStoreAckActivation,
247 missing: &str,
248) -> Result<(), DbError> {
249 let activation = serde_json::to_string(activation).map_err(|error| {
250 DbError::context("serialize Merge Store acknowledgement activation", error)
251 })?;
252 let updated = conn
253 .execute(
254 "UPDATE outbound_store_acks SET activation = ?2 \
255 WHERE singleton = 1 AND ack_ref = ?1",
256 rusqlite::params![
257 serde_json::to_string(expected).map_err(|error| DbError::context(
258 "serialize Store acknowledgement ref",
259 error
260 ))?,
261 activation,
262 ],
263 )
264 .map_err(DbError::from)?;
265 if updated != 1 {
266 return Err(DbError::Message(missing.to_string()));
267 }
268 Ok(())
269}
270
271pub(crate) fn load_outbound_store_ack_on(
272 conn: &Connection,
273 authority: &coven_protocol::store_commit::ReferencedStoreDeviceRegistration,
274) -> Result<Option<OutboundStoreAck>, DbError> {
275 conn.query_row(
276 "SELECT ack_ref, ack_bytes, prepared_object, activation \
277 FROM outbound_store_acks WHERE singleton = 1",
278 [],
279 |row| {
280 Ok((
281 row.get::<_, String>(0)?,
282 row.get::<_, Vec<u8>>(1)?,
283 row.get::<_, String>(2)?,
284 row.get::<_, String>(3)?,
285 ))
286 },
287 )
288 .optional()
289 .map_err(DbError::from)?
290 .map(|(reference, bytes, prepared, activation)| {
291 let reference: StoreAckRef = serde_json::from_str(&reference)
292 .map_err(|error| DbError::context("outbound Store acknowledgement ref", error))?;
293 let prepared: PreparedExactObject = serde_json::from_str(&prepared)
294 .map_err(|error| DbError::context("outbound prepared Store acknowledgement", error))?;
295 let activation: OutboundStoreAckActivation =
296 serde_json::from_str(&activation).map_err(|error| {
297 DbError::context("outbound Store acknowledgement activation", error)
298 })?;
299 if prepared.reference() != &reference.object {
300 return Err(DbError::Message(
301 "outbound Store acknowledgement ref differs from its prepared object".to_string(),
302 ));
303 }
304 let author_ref = authority.reference();
305 let author = authority.value();
306 let value = StoreAck::parse_at(&bytes, &author.store_root, &reference, author)
307 .map_err(|error| DbError::context("outbound Store acknowledgement", error))?;
308 if &value.registration != author_ref {
309 return Err(DbError::Message(
310 "outbound Store acknowledgement author differs from local activation".to_string(),
311 ));
312 }
313 Ok(OutboundStoreAck {
314 reference,
315 ack: ExactProtocolObject {
316 value,
317 bytes,
318 prepared,
319 },
320 circle_acknowledgements: load_outbound_circle_acks_on(conn, authority)?,
321 activation,
322 })
323 })
324 .transpose()
325}