1use super::*;
2
3#[derive(Debug, Clone)]
4pub struct StoreOwnerAnchor {
5 root: coven_protocol::objects::VerifiedObject<StoreProtocolRoot>,
6 founder: coven_protocol::objects::VerifiedObject<StoreDeviceRegistration>,
7 authority: RetainedReplayGenesisAuthority,
8 genesis: ResolvedStoreDeviceState,
9}
10
11impl StoreOwnerAnchor {
12 pub fn new(
13 root_reference: coven_protocol::store_commit::StoreRootRef,
14 root: coven_protocol::objects::VerifiedObject<StoreProtocolRoot>,
15 founder_reference: StoreDeviceRegistrationRef,
16 founder: coven_protocol::objects::VerifiedObject<StoreDeviceRegistration>,
17 ) -> Result<Self, DbError> {
18 let parsed_root_reference = coven_protocol::store_commit::StoreRootRef {
19 store_root_id: root.value.descriptor.store_root_id(),
20 store_root_hash: root.value.object_hash(),
21 object: root.object.clone(),
22 };
23 if root.bytes != root.value.to_bytes()
24 || root.semantic_hash != root.value.object_hash()
25 || parsed_root_reference != root_reference
26 {
27 return Err(DbError::Message(
28 "Store owner root differs from its verified exact object".to_string(),
29 ));
30 }
31 let parsed_founder_reference =
32 StoreDeviceRegistrationRef::from_registration(&founder.value, founder.object.clone());
33 if founder.bytes != founder.value.to_bytes()
34 || founder.semantic_hash != founder.value.registration_hash()
35 || parsed_founder_reference != founder_reference
36 || founder.value.author_pubkey != root.value.descriptor.founder_pubkey
37 || founder.object.slot() != &root.value.descriptor.founder_registration
38 || founder.value.provider != root.value.descriptor.founder_provider_admin.provider
39 || !matches!(
40 founder.value.origin,
41 coven_protocol::store_commit::StoreDeviceRegistrationOrigin::Founder { .. }
42 )
43 {
44 return Err(DbError::Message(
45 "Store owner founder registration differs from its root or verified exact object"
46 .to_string(),
47 ));
48 }
49 let genesis = ResolvedStoreDeviceState::founder(
50 &root_reference,
51 founder_reference.clone(),
52 &root.value.descriptor.founder_pubkey,
53 root.value.descriptor.founder_grant.clone(),
54 &root.value.descriptor.founder_recovery,
55 )
56 .map_err(DbError::from)?;
57 Ok(Self {
58 root,
59 founder,
60 authority: RetainedReplayGenesisAuthority {
61 store_root: root_reference,
62 founder_registration: founder_reference,
63 },
64 genesis,
65 })
66 }
67
68 pub(crate) fn authority(&self) -> &RetainedReplayGenesisAuthority {
69 &self.authority
70 }
71
72 pub(crate) fn root(&self) -> &coven_protocol::objects::VerifiedObject<StoreProtocolRoot> {
73 &self.root
74 }
75
76 pub(crate) fn founder(
77 &self,
78 ) -> &coven_protocol::objects::VerifiedObject<StoreDeviceRegistration> {
79 &self.founder
80 }
81
82 pub(crate) fn genesis(&self) -> &ResolvedStoreDeviceState {
83 &self.genesis
84 }
85
86 pub(crate) fn owner(&self) -> &str {
87 &self.root.value.descriptor.founder_pubkey
88 }
89}
90
91#[derive(Debug, Clone)]
92pub struct DurableFounderGraph {
93 pub root: ExactProtocolObject<StoreProtocolRoot>,
94 pub registration: ExactProtocolObject<StoreDeviceRegistration>,
95 pub initial_ack: ExactProtocolObject<StoreAck>,
96 pub initial_ack_ref: StoreAckRef,
97 pub membership: DurableFounderMembership,
98 pub registration_state: LocalDeviceRegistrationState,
99}
100
101impl DurableFounderGraph {
102 pub fn validate(&self) -> Result<(), DbError> {
103 let root = StoreProtocolRoot::parse(&self.root.bytes)
104 .map_err(|error| DbError::context("founder Store root", error))?;
105 if root != self.root.value || root.object_hash() != self.root.value.object_hash() {
106 return Err(DbError::Message(
107 "founder Store root differs from its prepared exact object".to_string(),
108 ));
109 }
110 let root_ref = coven_protocol::store_commit::StoreRootRef {
111 store_root_id: root.descriptor.store_root_id(),
112 store_root_hash: root.object_hash(),
113 object: self.root.prepared.reference().clone(),
114 };
115 let registration = StoreDeviceRegistration::parse_at(
116 &self.registration.bytes,
117 &root_ref,
118 self.registration.value.device_id,
119 )
120 .map_err(|error| DbError::context("founder Store registration", error))?;
121 if registration != self.registration.value
122 || registration.author_pubkey != root.descriptor.founder_pubkey
123 || self.registration.prepared.reference().slot()
124 != &root.descriptor.founder_registration
125 || registration.provider != root.descriptor.founder_provider_admin.provider
126 || !matches!(
127 registration.origin,
128 coven_protocol::store_commit::StoreDeviceRegistrationOrigin::Founder { .. }
129 )
130 {
131 return Err(DbError::Message(
132 "founder registration differs from its root or prepared exact object".to_string(),
133 ));
134 }
135 let registration_ref = StoreDeviceRegistrationRef::from_registration(
136 ®istration,
137 self.registration.prepared.reference().clone(),
138 );
139 let initial_ack = StoreAck::parse_at(
140 &self.initial_ack.bytes,
141 &root_ref,
142 &self.initial_ack_ref,
143 ®istration,
144 )
145 .map_err(|error| DbError::context("founder initial acknowledgement", error))?;
146 if initial_ack != self.initial_ack.value
147 || self.initial_ack_ref.registration != registration_ref
148 || self.initial_ack_ref.sequence != 1
149 || &self.initial_ack_ref.object != self.initial_ack.prepared.reference()
150 || initial_ack.successor.predecessor.is_some()
151 || initial_ack.registration != registration_ref
152 || !initial_ack.store_cut.0.is_empty()
153 {
154 return Err(DbError::Message(
155 "founder initial acknowledgement differs from its exact root graph".to_string(),
156 ));
157 }
158 {
159 let entry = &self.membership.entry;
160 let entry_ref = &self.membership.entry_ref;
161 let head = &self.membership.head;
162 let head_ref = &self.membership.head_ref;
163 let parsed_entry: MembershipEntry = serde_json::from_slice(&entry.bytes)
164 .map_err(|error| DbError::context("founder membership entry", error))?;
165 if parsed_entry != entry.value
166 || root
167 .descriptor
168 .validate_merge_founder_entry(&parsed_entry)
169 .is_err()
170 || entry_ref.coord != parsed_entry.coord()
171 || &entry_ref.object != entry.prepared.reference()
172 {
173 return Err(DbError::Message(
174 "founder membership entry differs from its root or exact reference".to_string(),
175 ));
176 }
177 let parsed_head: AuthorHead = serde_json::from_slice(&head.bytes)
178 .map_err(|error| DbError::context("founder membership head", error))?;
179 let anchor = parsed_entry.change.membership_anchor().ok_or_else(|| {
180 DbError::Message("founder entry has no Store membership anchor".to_string())
181 })?;
182 let coven_protocol::store_commit::GrantStreamAnchor::StoreMembership { first_slot } =
183 anchor
184 else {
185 return Err(DbError::Message(
186 "founder membership entry uses a recovery anchor".to_string(),
187 ));
188 };
189 if parsed_head != head.value
190 || !parsed_head.verify(®istration)
191 || parsed_head.body.author_registration != registration_ref
192 || parsed_head.body.entry != *entry_ref
193 || parsed_head.body.predecessor.is_some()
194 || parsed_head.entry_coord() != parsed_entry.coord()
195 || head_ref.coord != parsed_entry.coord()
196 || head_ref.head_hash != parsed_head.head_hash()
197 || &head_ref.object != head.prepared.reference()
198 || head.prepared.reference().slot() != &first_slot
199 || parsed_head.body.successor.activation
200 != coven_protocol::store_commit::StreamActivation::grant_authorized(
201 root_ref.store_root_hash,
202 registration_ref.clone(),
203 parsed_entry.author_owner_grant.clone(),
204 coven_protocol::store_commit::GrantStreamAnchor::StoreMembership {
205 first_slot: first_slot.clone(),
206 },
207 )
208 .activation_id()
209 {
210 return Err(DbError::Message(
211 "founder membership head differs from its exact root graph".to_string(),
212 ));
213 }
214 }
215 Ok(())
216 }
217}
218
219#[derive(Debug, Clone)]
220pub struct DurableFounderMembership {
221 pub entry: ExactProtocolObject<MembershipEntry>,
222 pub entry_ref: MembershipEntryRef,
223 pub head: ExactProtocolObject<AuthorHead>,
224 pub head_ref: MembershipHeadRef,
225}
226
227#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
228#[serde(deny_unknown_fields)]
229pub struct DurableFounderMembershipJournal {
230 entry_ref: MembershipEntryRef,
231 entry_bytes: Vec<u8>,
232 entry_prepared: PreparedExactObject,
233 head_ref: MembershipHeadRef,
234 head_bytes: Vec<u8>,
235 head_prepared: PreparedExactObject,
236}
237
238impl DurableFounderMembershipJournal {
239 pub fn from_graph(graph: &DurableFounderMembership) -> Self {
240 Self {
241 entry_ref: graph.entry_ref.clone(),
242 entry_bytes: graph.entry.bytes.clone(),
243 entry_prepared: graph.entry.prepared.clone(),
244 head_ref: graph.head_ref.clone(),
245 head_bytes: graph.head.bytes.clone(),
246 head_prepared: graph.head.prepared.clone(),
247 }
248 }
249
250 pub fn into_graph(self) -> Result<DurableFounderMembership, DbError> {
251 let entry_value: MembershipEntry = serde_json::from_slice(&self.entry_bytes)
252 .map_err(|error| DbError::context("local founder membership entry", error))?;
253 let head_value: AuthorHead = serde_json::from_slice(&self.head_bytes)
254 .map_err(|error| DbError::context("local founder membership head", error))?;
255 Ok(DurableFounderMembership {
256 entry: ExactProtocolObject {
257 value: entry_value,
258 bytes: self.entry_bytes,
259 prepared: self.entry_prepared,
260 },
261 entry_ref: self.entry_ref,
262 head: ExactProtocolObject {
263 value: head_value,
264 bytes: self.head_bytes,
265 prepared: self.head_prepared,
266 },
267 head_ref: self.head_ref,
268 })
269 }
270}
271
272#[derive(Debug, Clone, PartialEq, Eq)]
273pub struct FounderMembershipRefs {
274 pub entry: MembershipEntryRef,
275 pub head: MembershipHeadRef,
276}
277
278pub(crate) fn founder_graph_identity(graph: &DurableFounderGraph) -> ObjectHash {
279 let membership = serde_json::to_vec(&(
280 &graph.membership.entry_ref,
281 &graph.membership.entry.bytes,
282 &graph.membership.entry.prepared,
283 &graph.membership.head_ref,
284 &graph.membership.head.bytes,
285 &graph.membership.head.prepared,
286 ))
287 .expect("founder membership graph serialization cannot fail");
288 ObjectHash::digest(
289 &serde_json::to_vec(&(
290 &graph.root.bytes,
291 &graph.root.prepared,
292 &graph.registration.bytes,
293 &graph.registration.prepared,
294 &graph.initial_ack_ref,
295 &graph.initial_ack.bytes,
296 &graph.initial_ack.prepared,
297 membership,
298 ))
299 .expect("founder graph serialization cannot fail"),
300 )
301}
302
303pub(crate) fn load_store_root_authority_on(
304 conn: &Connection,
305) -> Result<
306 Option<(
307 coven_protocol::store_commit::StoreRootRef,
308 StoreProtocolRoot,
309 )>,
310 DbError,
311> {
312 conn.query_row(
313 "SELECT store_root_hash, store_protocol_root_bytes, store_root_object \
314 FROM store_protocol_root_authority WHERE singleton = 1",
315 [],
316 |row| {
317 Ok((
318 row.get::<_, String>(0)?,
319 row.get::<_, Vec<u8>>(1)?,
320 row.get::<_, String>(2)?,
321 ))
322 },
323 )
324 .optional()
325 .map_err(DbError::from)?
326 .map(|(hash, bytes, object)| {
327 let value = StoreProtocolRoot::parse(&bytes)
328 .map_err(|error| DbError::context("Store root authority bytes", error))?;
329 let store_root_hash: ObjectHash = hash
330 .parse()
331 .map_err(|error| DbError::context("Store root authority semantic hash", error))?;
332 let object: ExactObjectRef = serde_json::from_str(&object)
333 .map_err(|error| DbError::context("Store root authority object", error))?;
334 if value.object_hash() != store_root_hash {
335 return Err(DbError::Message(
336 "Store root authority hash differs from its signed bytes".to_string(),
337 ));
338 }
339 Ok((
340 coven_protocol::store_commit::StoreRootRef {
341 store_root_id: value.descriptor.store_root_id(),
342 store_root_hash,
343 object,
344 },
345 value,
346 ))
347 })
348 .transpose()
349}
350
351pub(crate) fn install_store_root_authority_on(
352 conn: &Connection,
353 reference: &coven_protocol::store_commit::StoreRootRef,
354 bytes: &[u8],
355) -> Result<StoreProtocolRoot, DbError> {
356 let value = StoreProtocolRoot::parse(bytes)
357 .map_err(|error| DbError::context("install Store root authority", error))?;
358 if value.object_hash() != reference.store_root_hash {
359 return Err(DbError::Message(
360 "installed Store root reference differs from its signed bytes".to_string(),
361 ));
362 }
363 let object = serde_json::to_string(&reference.object)
364 .map_err(|error| DbError::context("serialize Store root authority", error))?;
365 let existing = load_store_root_authority_on(conn)?;
366 if let Some((existing_reference, existing_value)) = existing {
367 if existing_reference == *reference && existing_value == value {
368 return Ok(value);
369 }
370 return Err(DbError::Message(
371 "database already trusts a different exact Store root".to_string(),
372 ));
373 }
374 conn.execute(
375 "INSERT INTO store_protocol_root_authority \
376 (singleton, store_root_hash, store_protocol_root_bytes, store_root_object) \
377 VALUES (1, ?1, ?2, ?3)",
378 rusqlite::params![reference.store_root_hash.to_string(), bytes, object],
379 )
380 .map_err(DbError::from)?;
381 Ok(value)
382}
383
384pub(crate) fn validate_replay_authority_on(
385 conn: &Connection,
386 baseline: &RetainedReplayBaseline,
387) -> Result<(), DbError> {
388 let (root_ref, root) = load_store_root_authority_on(conn)?.ok_or_else(|| {
389 DbError::Message("retained replay image has no Store root authority".to_string())
390 })?;
391 let (authority_root, founder_registration) = match &baseline.authority {
392 RetainedReplayAuthority::Genesis(authority) => {
393 (&authority.store_root, &authority.founder_registration)
394 }
395 RetainedReplayAuthority::InstalledSnapshot(authority) => {
396 authority.validate()?;
397 (&authority.store_root, &authority.founder_registration)
398 }
399 };
400 if &root_ref != authority_root || root.descriptor.sync_routing_hash != baseline.routing_hash {
401 return Err(DbError::Message(
402 "retained replay authority differs from its Store root".to_string(),
403 ));
404 }
405 let founder = load_activated_registration_on(conn, &root_ref, founder_registration)?;
406 let authority: String = conn
407 .query_row(
408 "SELECT activation_authority
409 FROM store_device_registration_activations
410 WHERE device_id = ?1 AND registration_hash = ?2",
411 (
412 founder_registration.device_id.to_string(),
413 founder_registration.registration_hash.to_string(),
414 ),
415 |row| row.get(0),
416 )
417 .map_err(DbError::from)?;
418 let authority: coven_protocol::store_commit::StoreDeviceRegistrationActivation =
419 serde_json::from_str(&authority).map_err(|error| {
420 DbError::context("retained replay founder activation authority", error)
421 })?;
422 if founder.store_root != root_ref
423 || authority
424 != (coven_protocol::store_commit::StoreDeviceRegistrationActivation::Founder {
425 root: root_ref.clone(),
426 })
427 {
428 return Err(DbError::Message(
429 "retained replay founder differs from its exact activation".to_string(),
430 ));
431 }
432 if let RetainedReplayAuthority::InstalledSnapshot(authority) = &baseline.authority {
433 for registration in authority.active_registrations.values() {
434 let installed =
435 load_activated_registration_on(conn, &root_ref, registration.reference())?;
436 if &installed != registration.value() {
437 return Err(DbError::Message(
438 "retained snapshot active registration differs from its installed authority"
439 .to_string(),
440 ));
441 }
442 }
443 }
444 Ok(())
445}
446
447pub(crate) fn install_store_founder_state_on(
448 conn: &Connection,
449 root: &coven_protocol::store_commit::StoreRootRef,
450 founder_reference: &StoreDeviceRegistrationRef,
451 founder: &StoreDeviceRegistration,
452 founder_bytes: &[u8],
453 genesis: &ResolvedStoreDeviceState,
454) -> Result<(), DbError> {
455 if founder.store_root != *root {
456 return Err(DbError::Message(
457 "Store founder registration belongs to another exact root".to_string(),
458 ));
459 }
460 founder_reference
461 .verify_registration(founder)
462 .map_err(DbError::from)?;
463 if founder.to_bytes() != founder_bytes {
464 return Err(DbError::Message(
465 "Store founder registration differs from its exact bytes".to_string(),
466 ));
467 }
468 let founder_authority =
469 coven_protocol::store_commit::StoreDeviceRegistrationActivation::Founder {
470 root: root.clone(),
471 };
472 let founder_values = (
473 founder_reference.registration_hash.to_string(),
474 founder.author_pubkey.clone(),
475 founder.device_signing_pubkey.clone(),
476 founder_bytes.to_vec(),
477 serde_json::to_string(founder_reference)
478 .map_err(|error| DbError::context("serialize Store founder registration ref", error))?,
479 serde_json::to_string(&founder_authority)
480 .map_err(|error| DbError::context("serialize Store founder activation", error))?,
481 );
482 conn.execute(
483 "INSERT INTO store_device_registration_activations
484 (device_id, registration_hash, author_pubkey, device_signing_pubkey,
485 registration_bytes, registration_object, activation_authority)
486 VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7)
487 ON CONFLICT(device_id) DO NOTHING",
488 rusqlite::params![
489 founder.device_id.to_string(),
490 &founder_values.0,
491 &founder_values.1,
492 &founder_values.2,
493 &founder_values.3,
494 &founder_values.4,
495 &founder_values.5,
496 ],
497 )
498 .map_err(DbError::from)?;
499 let stored_founder: (String, String, String, Vec<u8>, String, String) = conn
500 .query_row(
501 "SELECT registration_hash, author_pubkey, device_signing_pubkey,
502 registration_bytes, registration_object, activation_authority
503 FROM store_device_registration_activations WHERE device_id = ?1",
504 [founder.device_id.to_string()],
505 |row| {
506 Ok((
507 row.get(0)?,
508 row.get(1)?,
509 row.get(2)?,
510 row.get(3)?,
511 row.get(4)?,
512 row.get(5)?,
513 ))
514 },
515 )
516 .map_err(DbError::from)?;
517 if stored_founder != founder_values {
518 return Err(DbError::Message(
519 "Store founder activation differs from installed exact authority".to_string(),
520 ));
521 }
522 let genesis = serde_json::to_string(genesis)
523 .map_err(|error| DbError::context("serialize Store device genesis", error))?;
524 conn.execute(
525 "INSERT OR IGNORE INTO protocol_state (key, value) VALUES (?1, ?2)",
526 (STORE_DEVICE_GENESIS_STATE_KEY, &genesis),
527 )
528 .map_err(DbError::from)?;
529 let stored_genesis = required_protocol_state_on(conn, STORE_DEVICE_GENESIS_STATE_KEY)?;
530 if stored_genesis != genesis {
531 return Err(DbError::Message(
532 "Store device genesis differs from installed exact authority".to_string(),
533 ));
534 }
535 Ok(())
536}
537
538pub(crate) fn load_local_store_founder_graph_on(
539 conn: &Connection,
540) -> Result<Option<Box<DurableFounderGraph>>, DbError> {
541 let owned_rows: i64 = conn
542 .query_row(
543 "SELECT EXISTS(SELECT 1 FROM local_store_protocol_root) \
544 + EXISTS(SELECT 1 FROM local_store_device_registration) \
545 + EXISTS(SELECT 1 FROM local_store_founder_graph)",
546 [],
547 |row| row.get(0),
548 )
549 .map_err(DbError::from)?;
550 if owned_rows == 0 {
551 return Ok(None);
552 }
553 if owned_rows != 3 {
554 return Err(DbError::Message(
555 "local Store founder graph is only partially durable".to_string(),
556 ));
557 }
558 let raw = conn
559 .query_row(
560 "SELECT r.store_root_hash, r.store_protocol_root_bytes, r.prepared_object, \
561 d.device_id, d.registration_hash, d.registration_bytes, d.prepared_object, \
562 d.initial_ack_ref, d.initial_ack_bytes, d.initial_ack_prepared, d.state, \
563 g.membership_graph \
564 FROM local_store_protocol_root r \
565 CROSS JOIN local_store_device_registration d \
566 CROSS JOIN local_store_founder_graph g \
567 WHERE r.singleton = 1 AND d.singleton = 1 AND g.singleton = 1",
568 [],
569 |row| {
570 Ok((
571 row.get::<_, String>(0)?,
572 row.get::<_, Vec<u8>>(1)?,
573 row.get::<_, String>(2)?,
574 row.get::<_, String>(3)?,
575 row.get::<_, String>(4)?,
576 row.get::<_, Vec<u8>>(5)?,
577 row.get::<_, String>(6)?,
578 row.get::<_, String>(7)?,
579 row.get::<_, Vec<u8>>(8)?,
580 row.get::<_, String>(9)?,
581 row.get::<_, String>(10)?,
582 row.get::<_, String>(11)?,
583 ))
584 },
585 )
586 .map_err(DbError::from)?;
587 let (
588 root_hash,
589 root_bytes,
590 root_prepared,
591 device_id,
592 registration_hash,
593 registration_bytes,
594 registration_prepared,
595 initial_ack_ref,
596 initial_ack_bytes,
597 initial_ack_prepared,
598 registration_state,
599 membership_graph,
600 ) = raw;
601 let registration_state: LocalDeviceRegistrationState =
602 serde_json::from_str(®istration_state)
603 .map_err(|error| DbError::context("local registration journal state", error))?;
604 let root_value = StoreProtocolRoot::parse(&root_bytes)
605 .map_err(|error| DbError::context("local founder Store root", error))?;
606 let root_prepared: PreparedExactObject = serde_json::from_str(&root_prepared)
607 .map_err(|error| DbError::context("local founder Store root object", error))?;
608 let store_root_hash: ObjectHash = root_hash
609 .parse()
610 .map_err(|error| DbError::context("local founder Store root hash", error))?;
611 if store_root_hash != root_value.object_hash() {
612 return Err(DbError::Message(
613 "local founder Store root hash differs from its bytes".to_string(),
614 ));
615 }
616 let root_ref = coven_protocol::store_commit::StoreRootRef {
617 store_root_id: root_value.descriptor.store_root_id(),
618 store_root_hash,
619 object: root_prepared.reference().clone(),
620 };
621 let parsed_device_id = device_id
622 .parse()
623 .map_err(|error| DbError::context("local founder device id", error))?;
624 let registration_value =
625 StoreDeviceRegistration::parse_at(®istration_bytes, &root_ref, parsed_device_id)
626 .map_err(|error| DbError::context("local founder Store registration", error))?;
627 let parsed_registration_hash: ObjectHash = registration_hash
628 .parse()
629 .map_err(|error| DbError::context("local founder Store registration hash", error))?;
630 if parsed_registration_hash != registration_value.registration_hash() {
631 return Err(DbError::Message(
632 "local founder registration hash differs from its bytes".to_string(),
633 ));
634 }
635 let registration_prepared: PreparedExactObject =
636 serde_json::from_str(®istration_prepared)
637 .map_err(|error| DbError::context("local founder registration object", error))?;
638 let initial_ack_ref: StoreAckRef = serde_json::from_str(&initial_ack_ref)
639 .map_err(|error| DbError::context("local founder initial ack ref", error))?;
640 let initial_ack_value = StoreAck::parse_at(
641 &initial_ack_bytes,
642 &root_ref,
643 &initial_ack_ref,
644 ®istration_value,
645 )
646 .map_err(|error| DbError::context("local founder initial ack", error))?;
647 let initial_ack_prepared: PreparedExactObject = serde_json::from_str(&initial_ack_prepared)
648 .map_err(|error| DbError::context("local founder initial ack object", error))?;
649 let membership = serde_json::from_str::<DurableFounderMembershipJournal>(&membership_graph)
650 .map_err(|error| DbError::context("local founder membership graph", error))?
651 .into_graph()?;
652 let graph = DurableFounderGraph {
653 root: ExactProtocolObject {
654 value: root_value,
655 bytes: root_bytes,
656 prepared: root_prepared,
657 },
658 registration: ExactProtocolObject {
659 value: registration_value,
660 bytes: registration_bytes,
661 prepared: registration_prepared,
662 },
663 initial_ack: ExactProtocolObject {
664 value: initial_ack_value,
665 bytes: initial_ack_bytes,
666 prepared: initial_ack_prepared,
667 },
668 initial_ack_ref,
669 membership,
670 registration_state,
671 };
672 graph.validate()?;
673 Ok(Some(Box::new(graph)))
674}