1use crate::*;
2use coven_protocol::store_commit::{
3 StoreAck, StoreAckRef, StoreDeviceRegistration, StoreDeviceRegistrationRef,
4};
5
6use super::*;
7
8impl StoreDatabase {
9 pub async fn latest_local_store_device_registration(
10 &self,
11 ) -> Result<Option<DurableDeviceRegistration>, DbError> {
12 self.read_local_store_device_registration(
13 "SELECT device_id, registration_hash, registration_bytes, prepared_object, \
14 initial_ack_ref, initial_ack_bytes, initial_ack_prepared, state \
15 FROM local_store_device_registration WHERE singleton = 1",
16 )
17 .await
18 }
19
20 pub async fn export_activated_device_continuation(
21 &self,
22 identity_signer: &coven_keys::keys::UserKeypair,
23 ) -> Result<coven_protocol::recovery::ActivatedContinuation, DbError> {
24 let durable = self
25 .latest_local_store_device_registration()
26 .await?
27 .ok_or_else(|| DbError::Message("local Store device registration is absent".into()))?;
28 let LocalDeviceRegistrationState::Activated { authority } = durable.state else {
29 return Err(DbError::Message(
30 "local Store device registration is not activated".into(),
31 ));
32 };
33 let root = self
34 .local_store_root_ref()
35 .await?
36 .ok_or_else(|| DbError::Message("local Store root hash is absent".into()))?;
37 let registration = StoreDeviceRegistration::parse_at(
38 &durable.registration_bytes,
39 &root,
40 durable.device_id,
41 )
42 .map_err(|error| DbError::context("local Store registration", error))?;
43 let registration_ref = StoreDeviceRegistrationRef::from_registration(
44 ®istration,
45 durable.prepared.reference().clone(),
46 );
47 if registration_ref.registration_hash != durable.registration_hash {
48 return Err(DbError::Message(
49 "local Store registration hash differs from its exact object".into(),
50 ));
51 }
52 let device_signer = registration
53 .device_signer(identity_signer)
54 .map_err(|error| DbError::context("local device signer", error))?;
55 let latest_ack = self
56 .latest_local_store_ack()
57 .await?
58 .ok_or_else(|| DbError::Message("local Store acknowledgement is absent".into()))?;
59 let announcement_stream_id =
60 coven_protocol::store_commit::StreamActivation::device_authorized_stream_id(
61 root.store_root_hash,
62 ®istration_ref,
63 coven_protocol::store_commit::StreamAnchorDomain::StoreAnnouncements,
64 );
65 Ok(coven_protocol::recovery::ActivatedContinuation {
66 identity_signing_secret: hex::encode(identity_signer.to_keypair_bytes()),
67 device_signing_secret: hex::encode(device_signer.to_keypair_bytes()),
68 registration: registration_ref,
69 registration_bytes: durable.registration_bytes,
70 registration_prepared: durable.prepared,
71 initial_ack: durable.initial_ack_ref,
72 initial_ack_bytes: durable.initial_ack.bytes,
73 initial_ack_prepared: durable.initial_ack.prepared,
74 activation: authority,
75 latest_ack: latest_ack.reference,
76 latest_position: self
77 .latest_local_store_position(announcement_stream_id)
78 .await?,
79 })
80 }
81
82 pub async fn install_activated_device_continuation(
83 &self,
84 continuation: coven_protocol::recovery::ActivatedContinuation,
85 identity_signer: &coven_keys::keys::UserKeypair,
86 device_signer: &coven_keys::keys::UserKeypair,
87 ack_chain: Vec<(StoreAckRef, StoreAck)>,
88 ) -> Result<(), DbError> {
89 let root = self
90 .local_store_root_ref()
91 .await?
92 .ok_or_else(|| DbError::Message("local Store root hash is absent".into()))?;
93 let registration = StoreDeviceRegistration::parse_at(
94 &continuation.registration_bytes,
95 &root,
96 continuation.registration.device_id,
97 )
98 .map_err(|error| DbError::context("continued Store registration", error))?;
99 continuation
100 .registration
101 .verify_registration(®istration)
102 .map_err(DbError::from)?;
103 let derived_device = registration
104 .device_signer(identity_signer)
105 .map_err(|error| DbError::context("continued device signer", error))?;
106 if derived_device.to_keypair_bytes() != device_signer.to_keypair_bytes()
107 || continuation.registration_prepared.reference() != &continuation.registration.object
108 || continuation.initial_ack_prepared.reference() != &continuation.initial_ack.object
109 {
110 return Err(DbError::Message(
111 "continued device keys or exact registration objects differ".into(),
112 ));
113 }
114 let initial_ack = StoreAck::parse_at(
115 &continuation.initial_ack_bytes,
116 &root,
117 &continuation.initial_ack,
118 ®istration,
119 )
120 .map_err(|error| DbError::context("continued initial ack", error))?;
121 let Some((latest_ack_ref, latest_ack)) = ack_chain.first() else {
122 return Err(DbError::Message(
123 "continued acknowledgement chain is empty".into(),
124 ));
125 };
126 let pinned = ack_chain
130 .iter()
131 .find(|(reference, _)| reference.sequence == continuation.latest_ack.sequence)
132 .map(|(reference, _)| reference);
133 if initial_ack.sequence != 1
134 || initial_ack.successor.predecessor.is_some()
135 || latest_ack.registration != continuation.registration
136 || latest_ack_ref.sequence < continuation.latest_ack.sequence
137 || pinned != Some(&continuation.latest_ack)
138 || ack_chain.last().map(|(reference, _)| reference) != Some(&continuation.initial_ack)
139 || ack_chain.windows(2).any(|pair| {
140 pair[0].1.successor.predecessor.as_ref() != Some(&pair[1].0.object)
141 || pair[0].0.sequence != pair[0].1.sequence
142 || pair[1].0.sequence != pair[1].1.sequence
143 || pair[0].0.registration != pair[0].1.registration
144 || pair[1].0.registration != pair[1].1.registration
145 })
146 {
147 return Err(DbError::Message(
148 "continued acknowledgement chain differs from its exact authority".into(),
149 ));
150 }
151 let latest_successor_slot = latest_ack.successor.next_slot.clone();
152 self.call_store(move |session| {
153 session.install_activated_device_continuation(
154 continuation,
155 registration,
156 ack_chain,
157 latest_successor_slot,
158 )
159 })
160 .await
161 }
162}
163
164impl StoreSession<'_> {
165 fn install_activated_device_continuation(
166 &mut self,
167 continuation: coven_protocol::recovery::ActivatedContinuation,
168 registration: StoreDeviceRegistration,
169 ack_chain: Vec<(StoreAckRef, StoreAck)>,
170 latest_successor_slot: coven_protocol::objects::ObjectSlot,
171 ) -> Result<(), DbError> {
172 let activated = self.activated_registration(&continuation.registration)?;
173 if activated.value() != ®istration {
174 return Err(DbError::Message(
175 "continued registration differs from activated Store state".into(),
176 ));
177 }
178 let conn = self.conn;
179 let tx = conn.unchecked_transaction().map_err(DbError::from)?;
180 let stored_authority: String = tx
181 .query_row(
182 "SELECT activation_authority FROM store_device_registration_activations \
183 WHERE device_id = ?1 AND registration_hash = ?2",
184 (
185 continuation.registration.device_id.to_string(),
186 continuation.registration.registration_hash.to_string(),
187 ),
188 |row| row.get(0),
189 )
190 .map_err(DbError::from)?;
191 let stored_authority: coven_protocol::store_commit::StoreDeviceRegistrationActivation =
192 serde_json::from_str(&stored_authority)
193 .map_err(|error| DbError::context("continued activation authority", error))?;
194 if stored_authority != continuation.activation {
195 return Err(DbError::Message(
196 "continued registration has another activation authority".into(),
197 ));
198 }
199 if let Some(position) = &continuation.latest_position {
200 let stream_id = position.coord.stream_id.to_string();
201 let restored_position =
202 crate::store::materialized_commit_index::latest_position_for_device_on(
203 &tx, &stream_id,
204 )?;
205 if restored_position.as_ref() != Some(position) {
206 return Err(DbError::Message(
207 "continued device position is absent from restored history".into(),
208 ));
209 }
210 }
211 let existing_local: i64 = tx
212 .query_row(
213 "SELECT COUNT(*) FROM local_store_device_registration",
214 [],
215 |row| row.get(0),
216 )
217 .map_err(DbError::from)?;
218 let existing_ack: i64 = tx
219 .query_row("SELECT COUNT(*) FROM published_store_acks", [], |row| {
220 row.get(0)
221 })
222 .map_err(DbError::from)?;
223 let existing_device = crate::get_protocol_state_on(&tx, LOCAL_DEVICE_ID_STATE_KEY)?;
224 let state = serde_json::to_string(&LocalDeviceRegistrationState::Activated {
225 authority: continuation.activation.clone(),
226 })
227 .map_err(|error| DbError::context("continued activation", error))?;
228 let expected_local = (
229 continuation.registration.device_id.to_string(),
230 continuation.registration.registration_hash.to_string(),
231 continuation.registration_bytes.clone(),
232 serde_json::to_string(&continuation.registration_prepared)
233 .map_err(|error| DbError::context("continued registration object", error))?,
234 serde_json::to_string(&continuation.initial_ack)
235 .map_err(|error| DbError::context("continued initial ack ref", error))?,
236 continuation.initial_ack_bytes.clone(),
237 serde_json::to_string(&continuation.initial_ack_prepared)
238 .map_err(|error| DbError::context("continued initial ack object", error))?,
239 state,
240 );
241 match existing_local {
242 0 => {
243 tx.execute(
244 "INSERT INTO local_store_device_registration \
245 (singleton, device_id, registration_hash, registration_bytes, \
246 prepared_object, initial_ack_ref, initial_ack_bytes, \
247 initial_ack_prepared, state) \
248 VALUES (1, ?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8)",
249 rusqlite::params![
250 expected_local.0,
251 expected_local.1,
252 expected_local.2,
253 expected_local.3,
254 expected_local.4,
255 expected_local.5,
256 expected_local.6,
257 expected_local.7,
258 ],
259 )
260 .map_err(DbError::from)?;
261 }
262 1 => {
263 let actual = tx
264 .query_row(
265 "SELECT device_id, registration_hash, registration_bytes, \
266 prepared_object, initial_ack_ref, initial_ack_bytes, \
267 initial_ack_prepared, state FROM local_store_device_registration \
268 WHERE singleton = 1",
269 [],
270 |row| {
271 Ok((
272 row.get::<_, String>(0)?,
273 row.get::<_, String>(1)?,
274 row.get::<_, Vec<u8>>(2)?,
275 row.get::<_, String>(3)?,
276 row.get::<_, String>(4)?,
277 row.get::<_, Vec<u8>>(5)?,
278 row.get::<_, String>(6)?,
279 row.get::<_, String>(7)?,
280 ))
281 },
282 )
283 .map_err(DbError::from)?;
284 if actual != expected_local {
285 return Err(DbError::Message(
286 "restored local device state differs from continuation".into(),
287 ));
288 }
289 }
290 _ => {
291 return Err(DbError::Message(
292 "restored database carries multiple local device journals".into(),
293 ));
294 }
295 }
296 match existing_ack {
297 0 => {}
298 1 => {
299 let (stored_ref, stored_successor): (String, String) = tx
300 .query_row(
301 "SELECT ack_ref, successor_slot FROM published_store_acks \
302 WHERE singleton = 1",
303 [],
304 |row| Ok((row.get(0)?, row.get(1)?)),
305 )
306 .map_err(DbError::from)?;
307 let stored_ref: StoreAckRef = serde_json::from_str(&stored_ref)
308 .map_err(|error| DbError::context("restored acknowledgement", error))?;
309 let Some((_, stored_ack)) = ack_chain
310 .iter()
311 .find(|(reference, _)| reference == &stored_ref)
312 else {
313 return Err(DbError::Message(
314 "restored acknowledgement is outside the continuation chain".into(),
315 ));
316 };
317 if stored_successor
318 != serde_json::to_string(&stored_ack.successor.next_slot)
319 .map_err(|error| DbError::context("restored ack successor", error))?
320 {
321 return Err(DbError::Message(
322 "restored acknowledgement successor differs from its signature".into(),
323 ));
324 }
325 }
326 _ => {
327 return Err(DbError::Message(
328 "restored database carries multiple local acknowledgements".into(),
329 ));
330 }
331 }
332 let Some((head_ack_ref, _)) = ack_chain.first() else {
335 return Err(DbError::Message(
336 "continued acknowledgement chain is empty".into(),
337 ));
338 };
339 let latest_ref = serde_json::to_string(head_ack_ref)
340 .map_err(|error| DbError::context("continued latest ack", error))?;
341 let latest_successor = serde_json::to_string(&latest_successor_slot)
342 .map_err(|error| DbError::context("continued ack successor", error))?;
343 if existing_ack == 0 {
344 tx.execute(
345 "INSERT INTO published_store_acks (singleton, ack_ref, successor_slot) \
346 VALUES (1, ?1, ?2)",
347 (&latest_ref, &latest_successor),
348 )
349 .map_err(DbError::from)?;
350 } else {
351 tx.execute(
352 "UPDATE published_store_acks SET ack_ref = ?1, successor_slot = ?2 \
353 WHERE singleton = 1",
354 (&latest_ref, &latest_successor),
355 )
356 .map_err(DbError::from)?;
357 }
358 match existing_device {
359 Some(existing) if existing == continuation.registration.device_id.to_string() => {}
360 Some(_) => {
361 return Err(DbError::Message(
362 "restored local device id differs from continuation".into(),
363 ));
364 }
365 None => {
366 tx.execute(
367 "INSERT INTO protocol_state (key, value) VALUES (?1, ?2)",
368 (
369 LOCAL_DEVICE_ID_STATE_KEY,
370 continuation.registration.device_id.to_string(),
371 ),
372 )
373 .map_err(DbError::from)?;
374 }
375 }
376 tx.commit().map_err(DbError::from)
377 }
378}