1use super::*;
2
3pub fn store_current_publication_semantic_prefix() -> &'static str {
4 "store-v1/publications/current"
5}
6
7pub fn store_current_publication_logical_key() -> &'static str {
8 "store-v1/publications/current.json"
9}
10
11pub fn store_publication_entry_semantic_prefix(entry: &StorePublicationEntry) -> String {
12 publication_entry_prefix(entry.position, entry.entry_hash())
13}
14
15fn publication_entry_prefix(position: StorePublicationPosition, hash: ObjectHash) -> String {
16 format!("store-v1/publications/entries/{}/{hash}", position.get())
17}
18
19#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
20#[serde(transparent)]
21pub struct StorePublicationPosition(u64);
22
23impl StorePublicationPosition {
24 pub fn new(value: u64) -> Result<Self, StoreProtocolError> {
25 if value == 0 {
26 return Err(StoreProtocolError::InvalidSequence(value));
27 }
28 Ok(Self(value))
29 }
30
31 pub fn get(self) -> u64 {
32 self.0
33 }
34
35 fn successor(self) -> Result<Self, StoreProtocolError> {
36 self.0
37 .checked_add(1)
38 .ok_or_else(|| {
39 StoreProtocolError::Malformed("Store publication position overflow".to_string())
40 })
41 .and_then(Self::new)
42 }
43}
44
45#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
46#[serde(deny_unknown_fields)]
47pub struct StorePublicationRef {
48 pub store_root_hash: ObjectHash,
49 pub position: StorePublicationPosition,
50 pub entry_hash: ObjectHash,
51 pub object: ExactObjectRef,
52}
53
54impl StorePublicationRef {
55 pub fn validate_slot(&self) -> Result<(), StoreProtocolError> {
56 let expected = format!(
57 "{}.json",
58 publication_entry_prefix(self.position, self.entry_hash)
59 );
60 if self.object.slot().logical_key() != expected {
61 return Err(StoreProtocolError::RelocatedSlot {
62 expected,
63 actual: self.object.slot().logical_key().into(),
64 });
65 }
66 Ok(())
67 }
68
69 pub fn from_entry(
70 entry: &StorePublicationEntry,
71 object: ExactObjectRef,
72 ) -> Result<Self, StoreProtocolError> {
73 entry.validate_shape()?;
74 object.verify(&entry.to_bytes())?;
75 let expected_key = format!("{}.json", store_publication_entry_semantic_prefix(entry));
76 if object.slot().logical_key() != expected_key {
77 return Err(StoreProtocolError::RelocatedSlot {
78 expected: expected_key,
79 actual: object.slot().logical_key().to_string(),
80 });
81 }
82 Ok(Self {
83 store_root_hash: entry.store_root_hash,
84 position: entry.position,
85 entry_hash: entry.entry_hash(),
86 object,
87 })
88 }
89
90 fn verify_entry(&self, entry: &StorePublicationEntry) -> Result<(), StoreProtocolError> {
91 self.object.verify(&entry.to_bytes())?;
92 if self.store_root_hash != entry.store_root_hash
93 || self.position != entry.position
94 || self.entry_hash != entry.entry_hash()
95 {
96 return Err(StoreProtocolError::Malformed(
97 "Store publication reference differs from its entry".to_string(),
98 ));
99 }
100 Ok(())
101 }
102}
103
104#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
105#[serde(deny_unknown_fields)]
106pub struct AcceptedStoreSnapshotRef {
107 pub snapshot: StoreSnapshotRef,
108 pub publication: StorePublicationRef,
109}
110
111#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
112#[serde(rename_all = "snake_case", deny_unknown_fields)]
113pub enum StorePublicationBase {
114 Genesis,
115 Snapshot(AcceptedStoreSnapshotRef),
116}
117
118impl StorePublicationBase {
119 pub fn validate_for_store(
120 &self,
121 expected_store_root_hash: ObjectHash,
122 ) -> Result<(), StoreProtocolError> {
123 if let Self::Snapshot(snapshot) = self {
124 crate::objects::verify_store_root(
125 expected_store_root_hash,
126 snapshot.publication.store_root_hash,
127 )?;
128 }
129 Ok(())
130 }
131}
132
133#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
134#[serde(rename_all = "snake_case", deny_unknown_fields)]
135pub enum StorePublicationPayload {
136 Commit(StoreBatchCommitRef),
137 Snapshot(StoreSnapshotRef),
138}
139
140#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
141#[serde(deny_unknown_fields)]
142pub struct StorePublicationEntryBody {
143 pub store_root_hash: ObjectHash,
144 pub position: StorePublicationPosition,
145 pub predecessor: Option<StorePublicationRef>,
146 pub previous_state_hash: ObjectHash,
147 pub author_registration: StoreDeviceRegistrationRef,
148 pub payload: StorePublicationPayload,
149}
150
151impl SignedBody for StorePublicationEntryBody {
152 const DOMAIN: &'static [u8] = STORE_PUBLICATION_ENTRY_DOMAIN;
153}
154
155pub type StorePublicationEntry = Signed<StorePublicationEntryBody>;
156
157impl StorePublicationEntry {
158 pub fn signed_commit(
159 current: &StoreCurrentPublicationRecord,
160 commit: &VerifiedStoreBatchCommit,
161 signer: &UserKeypair,
162 ) -> Result<Self, StoreProtocolError> {
163 let entry = Self::signed_payload(
164 current,
165 commit.author_registration.clone(),
166 StorePublicationPayload::Commit(commit.reference().clone()),
167 signer,
168 )?;
169 entry.validate_commit_against(current, commit, &keys::public_key_hex(signer))?;
170 Ok(entry)
171 }
172
173 pub fn signed_snapshot(
174 current: &StoreCurrentPublicationRecord,
175 author_registration: StoreDeviceRegistrationRef,
176 snapshot: StoreSnapshotRef,
177 signer: &UserKeypair,
178 ) -> Result<Self, StoreProtocolError> {
179 Self::signed_payload(
180 current,
181 author_registration,
182 StorePublicationPayload::Snapshot(snapshot),
183 signer,
184 )
185 }
186
187 fn signed_payload(
188 current: &StoreCurrentPublicationRecord,
189 author_registration: StoreDeviceRegistrationRef,
190 payload: StorePublicationPayload,
191 signer: &UserKeypair,
192 ) -> Result<Self, StoreProtocolError> {
193 let position = current.next_position()?;
194 let entry = Signed::sign(
195 StorePublicationEntryBody {
196 store_root_hash: current.store_root_hash,
197 position,
198 predecessor: current.accepted().cloned(),
199 previous_state_hash: current.state_hash(),
200 author_registration,
201 payload,
202 },
203 signer,
204 );
205 entry.validate_against(current.body())?;
206 Ok(entry)
207 }
208
209 pub fn entry_hash(&self) -> ObjectHash {
210 self.hash()
211 }
212
213 pub fn parse_at(
214 bytes: &[u8],
215 expected_store_root_hash: ObjectHash,
216 reference: &StorePublicationRef,
217 expected_signing_pubkey: &str,
218 ) -> Result<Self, StoreProtocolError> {
219 let entry: Self = crate::objects::decode_protocol_object(bytes)?;
220 entry.require_version()?;
221 entry.verify_by(expected_signing_pubkey)?;
222 if entry.store_root_hash != expected_store_root_hash {
223 return Err(StoreProtocolError::StoreRootMismatch {
224 expected: expected_store_root_hash,
225 actual: entry.store_root_hash,
226 });
227 }
228 reference.verify_entry(&entry)?;
229 entry.validate_shape()?;
230 Ok(entry)
231 }
232
233 fn validate_against(
234 &self,
235 current: &StoreCurrentPublicationRecordBody,
236 ) -> Result<(), StoreProtocolError> {
237 self.validate_shape()?;
238 if self.store_root_hash != current.store_root_hash
239 || self.predecessor.as_ref() != current.accepted()
240 || self.position != current.next_position()?
241 || self.previous_state_hash != current.state_hash()
242 {
243 return Err(StoreProtocolError::Malformed(
244 "Store publication entry does not extend the current accepted boundary".to_string(),
245 ));
246 }
247 Ok(())
248 }
249
250 fn validate_commit_against(
251 &self,
252 current: &StoreCurrentPublicationRecord,
253 commit: &VerifiedStoreBatchCommit,
254 publisher_signing_pubkey: &str,
255 ) -> Result<(), StoreProtocolError> {
256 self.validate_against(current.body())?;
257 let StorePublicationPayload::Commit(reference) = &self.payload else {
258 return Err(StoreProtocolError::Malformed(
259 "Store publication entry is not a commit".to_string(),
260 ));
261 };
262 reference.verify_commit(commit.value())?;
263 commit.value().verify_by(publisher_signing_pubkey)?;
264 if commit.store_root_hash() != self.store_root_hash
265 || commit.author_registration != self.author_registration
266 || commit.publication_base() != ¤t.publication_base()
267 {
268 return Err(StoreProtocolError::Malformed(
269 "Store commit differs from its accepted publication boundary".to_string(),
270 ));
271 }
272 Ok(())
273 }
274
275 fn validate_commit_against_record(
276 &self,
277 commit: &VerifiedStoreBatchCommit,
278 publisher_signing_pubkey: &str,
279 ) -> Result<(), StoreProtocolError> {
280 self.validate_shape()?;
281 let StorePublicationPayload::Commit(reference) = &self.payload else {
282 return Err(StoreProtocolError::Malformed(
283 "Store publication entry is not a commit".to_string(),
284 ));
285 };
286 reference.verify_commit(commit.value())?;
287 commit.value().verify_by(publisher_signing_pubkey)?;
288 commit
289 .publication_base()
290 .validate_for_store(self.store_root_hash)?;
291 if commit.store_root_hash() != self.store_root_hash
292 || commit.author_registration != self.author_registration
293 {
294 return Err(StoreProtocolError::Malformed(
295 "Store commit differs from its publication entry".to_string(),
296 ));
297 }
298 Ok(())
299 }
300
301 pub fn verify_published_commit(
302 &self,
303 commit: &VerifiedStoreBatchCommit,
304 publisher_signing_pubkey: &str,
305 ) -> Result<(), StoreProtocolError> {
306 self.validate_commit_against_record(commit, publisher_signing_pubkey)
307 }
308
309 fn validate_shape(&self) -> Result<(), StoreProtocolError> {
310 self.position.get().checked_add(1).ok_or_else(|| {
311 StoreProtocolError::Malformed("Store publication position overflow".to_string())
312 })?;
313 let expected_position = match &self.predecessor {
314 Some(predecessor) => {
315 if predecessor.store_root_hash != self.store_root_hash {
316 return Err(StoreProtocolError::StoreRootMismatch {
317 expected: self.store_root_hash,
318 actual: predecessor.store_root_hash,
319 });
320 }
321 predecessor.position.successor()?
322 }
323 None => StorePublicationPosition::new(1)?,
324 };
325 if self.position != expected_position {
326 return Err(StoreProtocolError::Malformed(
327 "Store publication entry position is not its predecessor's successor".to_string(),
328 ));
329 }
330 Ok(())
331 }
332}
333
334#[derive(Debug, Clone, PartialEq, Eq)]
335pub struct StoreCommitPublication {
336 entry: StorePublicationEntry,
337 reference: StorePublicationRef,
338}
339
340impl StoreCommitPublication {
341 pub fn verified(
342 entry: StorePublicationEntry,
343 reference: StorePublicationRef,
344 commit: &VerifiedStoreBatchCommit,
345 publisher_signing_pubkey: &str,
346 ) -> Result<Self, StoreProtocolError> {
347 reference.verify_entry(&entry)?;
348 entry.verify_published_commit(commit, publisher_signing_pubkey)?;
349 Ok(Self { entry, reference })
350 }
351
352 pub fn entry(&self) -> &StorePublicationEntry {
353 &self.entry
354 }
355
356 pub fn reference(&self) -> &StorePublicationRef {
357 &self.reference
358 }
359
360 pub fn into_parts(self) -> (StorePublicationEntry, StorePublicationRef) {
361 (self.entry, self.reference)
362 }
363}
364
365#[derive(Debug, Clone, PartialEq, Eq)]
366pub struct StorePublicationIntervalEntry {
367 entry: StorePublicationEntry,
368 reference: StorePublicationRef,
369 author: ReferencedStoreDeviceRegistration,
370}
371
372impl StorePublicationIntervalEntry {
373 pub fn new(
374 entry: StorePublicationEntry,
375 reference: StorePublicationRef,
376 author: ReferencedStoreDeviceRegistration,
377 ) -> Self {
378 Self {
379 entry,
380 reference,
381 author,
382 }
383 }
384}
385
386#[derive(Debug, Clone, PartialEq, Eq)]
387pub struct AcceptedStorePublicationEntry {
388 entry: StorePublicationEntry,
389 reference: StorePublicationRef,
390 author: ReferencedStoreDeviceRegistration,
391}
392
393impl AcceptedStorePublicationEntry {
394 pub fn entry(&self) -> &StorePublicationEntry {
395 &self.entry
396 }
397
398 pub fn reference(&self) -> &StorePublicationRef {
399 &self.reference
400 }
401
402 pub fn author(&self) -> &ReferencedStoreDeviceRegistration {
403 &self.author
404 }
405
406 fn accepted_commit(
407 &self,
408 commit: &VerifiedStoreBatchCommit,
409 ) -> Result<AcceptedStoreCommitPublication, StoreProtocolError> {
410 let publication = StoreCommitPublication::verified(
411 self.entry.clone(),
412 self.reference.clone(),
413 commit,
414 &self.author.value().device_signing_pubkey,
415 )?;
416 Ok(AcceptedStoreCommitPublication { publication })
417 }
418}
419
420#[derive(Debug, Clone, PartialEq, Eq)]
421pub struct AcceptedStoreCommitPublication {
422 publication: StoreCommitPublication,
423}
424
425impl AcceptedStoreCommitPublication {
426 pub fn entry(&self) -> &StorePublicationEntry {
427 self.publication.entry()
428 }
429
430 pub fn reference(&self) -> &StorePublicationRef {
431 self.publication.reference()
432 }
433
434 pub fn into_publication(self) -> StoreCommitPublication {
435 self.publication
436 }
437}
438
439#[derive(Debug, Clone, PartialEq, Eq)]
440pub struct VerifiedStorePublicationInterval {
441 previous: StoreCurrentPublicationRecordBody,
442 current: StoreCurrentPublicationRecord,
443 entries: Vec<AcceptedStorePublicationEntry>,
444}
445
446impl VerifiedStorePublicationInterval {
447 pub fn verified(
450 previous: StoreCurrentPublicationRecord,
451 current: StoreCurrentPublicationRecord,
452 entries: Vec<StorePublicationIntervalEntry>,
453 ) -> Result<Self, StoreProtocolError> {
454 if entries.is_empty() {
455 if current != previous {
456 return Err(StoreProtocolError::Malformed(
457 "empty Store publication interval changes the accepted boundary".to_string(),
458 ));
459 }
460 return Ok(Self {
461 previous: previous.body().clone(),
462 current,
463 entries: Vec::new(),
464 });
465 }
466
467 Self::from_nonempty_history(previous.body().clone(), current, entries)
468 }
469
470 pub fn from_nonempty_history(
474 previous: StoreCurrentPublicationRecordBody,
475 current: StoreCurrentPublicationRecord,
476 entries: Vec<StorePublicationIntervalEntry>,
477 ) -> Result<Self, StoreProtocolError> {
478 if entries.is_empty() {
479 return Err(StoreProtocolError::Malformed(
480 "carried Store publication history has no entries".into(),
481 ));
482 }
483 Self::fold(previous, current, entries)
484 }
485
486 pub fn from_genesis(
489 store_root_hash: ObjectHash,
490 founder_pubkey: &str,
491 current: StoreCurrentPublicationRecord,
492 entries: Vec<StorePublicationIntervalEntry>,
493 ) -> Result<Self, StoreProtocolError> {
494 let previous = StoreCurrentPublicationRecordBody::genesis(store_root_hash);
495 if entries.is_empty() {
496 current.verify_genesis(store_root_hash, founder_pubkey)?;
497 return Ok(Self {
498 previous,
499 current,
500 entries: Vec::new(),
501 });
502 }
503 Self::fold(previous, current, entries)
504 }
505
506 fn fold(
507 previous: StoreCurrentPublicationRecordBody,
508 current: StoreCurrentPublicationRecord,
509 entries: Vec<StorePublicationIntervalEntry>,
510 ) -> Result<Self, StoreProtocolError> {
511 let mut folded = previous.clone();
512 let mut accepted = Vec::with_capacity(entries.len());
513 let mut commit_coordinates = std::collections::BTreeSet::new();
514 let mut final_publisher = None;
515 for candidate in entries {
516 let registration_bytes = candidate.author.value().to_bytes();
517 candidate
518 .author
519 .reference()
520 .object
521 .verify(®istration_bytes)?;
522 let parsed_registration = StoreDeviceRegistration::parse_at(
523 ®istration_bytes,
524 &candidate.author.value().store_root,
525 candidate.author.reference().device_id,
526 )?;
527 candidate
528 .author
529 .reference()
530 .verify_registration(&parsed_registration)?;
531 if parsed_registration != *candidate.author.value()
532 || candidate.author.value().store_root.store_root_hash != folded.store_root_hash
533 || candidate.entry.author_registration != *candidate.author.reference()
534 {
535 return Err(StoreProtocolError::Malformed(
536 "Store publication entry differs from its exact author registration"
537 .to_string(),
538 ));
539 }
540 let parsed_entry = StorePublicationEntry::parse_at(
541 &candidate.entry.to_bytes(),
542 folded.store_root_hash,
543 &candidate.reference,
544 &candidate.author.value().device_signing_pubkey,
545 )?;
546 if parsed_entry != candidate.entry {
547 return Err(StoreProtocolError::Malformed(
548 "Store publication entry differs from its canonical bytes".to_string(),
549 ));
550 }
551 if let StorePublicationPayload::Commit(commit) = &candidate.entry.payload {
552 if !commit_coordinates.insert(commit.coord.clone()) {
553 return Err(StoreProtocolError::Malformed(
554 "Store publication interval repeats an author sequence".to_string(),
555 ));
556 }
557 }
558 folded = folded.advance(&candidate.entry, candidate.reference.clone())?;
559 final_publisher = Some(candidate.author.value().device_signing_pubkey.clone());
560 accepted.push(AcceptedStorePublicationEntry {
561 entry: candidate.entry,
562 reference: candidate.reference,
563 author: candidate.author,
564 });
565 }
566
567 let final_publisher = final_publisher.expect("a non-empty interval has a publisher");
568 current.verify_by(&final_publisher)?;
569 if current.body() != &folded {
570 return Err(StoreProtocolError::Malformed(
571 "Store current publication record differs from its verified interval".to_string(),
572 ));
573 }
574 Ok(Self {
575 previous,
576 current,
577 entries: accepted,
578 })
579 }
580
581 pub fn previous(&self) -> &StoreCurrentPublicationRecordBody {
583 &self.previous
584 }
585
586 pub fn current(&self) -> &StoreCurrentPublicationRecord {
587 &self.current
588 }
589
590 pub fn entries(&self) -> &[AcceptedStorePublicationEntry] {
591 &self.entries
592 }
593
594 pub fn accepted_commit(
595 &self,
596 commit: &VerifiedStoreBatchCommit,
597 ) -> Result<AcceptedStoreCommitPublication, StoreProtocolError> {
598 let mut base = self.previous.publication_base();
599 for entry in &self.entries {
600 match &entry.entry.payload {
601 StorePublicationPayload::Snapshot(snapshot) => {
602 base = StorePublicationBase::Snapshot(AcceptedStoreSnapshotRef {
603 snapshot: snapshot.clone(),
604 publication: entry.reference.clone(),
605 });
606 }
607 StorePublicationPayload::Commit(reference) if reference == commit.reference() => {
608 if commit.publication_base() != &base {
609 return Err(StoreProtocolError::Malformed(
610 "Store commit differs from the snapshot base at its accepted publication"
611 .to_string(),
612 ));
613 }
614 return entry.accepted_commit(commit);
615 }
616 StorePublicationPayload::Commit(_) => {}
617 }
618 }
619 Err(StoreProtocolError::Malformed(
620 "Store commit is absent from the accepted publication interval".to_string(),
621 ))
622 }
623}
624
625#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
626#[serde(rename_all = "snake_case", deny_unknown_fields)]
627pub enum StorePublicationState {
628 Genesis,
629 Accepted {
630 entry: StorePublicationRef,
631 latest_snapshot: Option<AcceptedStoreSnapshotRef>,
632 },
633}
634
635#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
636#[serde(deny_unknown_fields)]
637pub struct StoreCurrentPublicationRecordBody {
638 pub store_root_hash: ObjectHash,
639 pub state: StorePublicationState,
640}
641
642impl StoreCurrentPublicationRecordBody {
643 pub fn genesis(store_root_hash: ObjectHash) -> Self {
644 Self {
645 store_root_hash,
646 state: StorePublicationState::Genesis,
647 }
648 }
649
650 pub fn state_hash(&self) -> ObjectHash {
651 super::signed::signed_body_hash(STORE_PROTOCOL_VERSION, self)
652 }
653
654 pub fn accepted(&self) -> Option<&StorePublicationRef> {
655 match &self.state {
656 StorePublicationState::Genesis => None,
657 StorePublicationState::Accepted { entry, .. } => Some(entry),
658 }
659 }
660
661 pub fn latest_snapshot(&self) -> Option<&AcceptedStoreSnapshotRef> {
662 match &self.state {
663 StorePublicationState::Genesis => None,
664 StorePublicationState::Accepted {
665 latest_snapshot, ..
666 } => latest_snapshot.as_ref(),
667 }
668 }
669
670 pub fn publication_base(&self) -> StorePublicationBase {
671 match self.latest_snapshot() {
672 Some(snapshot) => StorePublicationBase::Snapshot(snapshot.clone()),
673 None => StorePublicationBase::Genesis,
674 }
675 }
676
677 pub fn next_position(&self) -> Result<StorePublicationPosition, StoreProtocolError> {
678 match self.accepted() {
679 Some(reference) => reference.position.successor(),
680 None => StorePublicationPosition::new(1),
681 }
682 }
683
684 fn advance(
685 &self,
686 entry: &StorePublicationEntry,
687 reference: StorePublicationRef,
688 ) -> Result<Self, StoreProtocolError> {
689 entry.validate_against(self)?;
690 reference.verify_entry(entry)?;
691 let latest_snapshot = match &entry.payload {
692 StorePublicationPayload::Commit(_) => self.latest_snapshot().cloned(),
693 StorePublicationPayload::Snapshot(snapshot) => Some(AcceptedStoreSnapshotRef {
694 snapshot: snapshot.clone(),
695 publication: reference.clone(),
696 }),
697 };
698 Ok(Self {
699 store_root_hash: self.store_root_hash,
700 state: StorePublicationState::Accepted {
701 entry: reference,
702 latest_snapshot,
703 },
704 })
705 }
706}
707
708impl SignedBody for StoreCurrentPublicationRecordBody {
709 const DOMAIN: &'static [u8] = STORE_CURRENT_PUBLICATION_DOMAIN;
710}
711
712pub type StoreCurrentPublicationRecord = Signed<StoreCurrentPublicationRecordBody>;
713
714impl StoreCurrentPublicationRecord {
715 pub fn state_hash(&self) -> ObjectHash {
716 self.body().state_hash()
717 }
718
719 pub fn genesis(store_root_hash: ObjectHash, founder: &UserKeypair) -> Self {
720 Signed::sign(
721 StoreCurrentPublicationRecordBody::genesis(store_root_hash),
722 founder,
723 )
724 }
725
726 pub fn advance_commit(
727 previous: &Self,
728 entry: &StorePublicationEntry,
729 reference: StorePublicationRef,
730 commit: &VerifiedStoreBatchCommit,
731 signer: &UserKeypair,
732 ) -> Result<Self, StoreProtocolError> {
733 entry.validate_commit_against(previous, commit, &keys::public_key_hex(signer))?;
734 Self::advance(previous, entry, reference, signer)
735 }
736
737 pub fn advance_snapshot(
738 previous: &Self,
739 entry: &StorePublicationEntry,
740 reference: StorePublicationRef,
741 signer: &UserKeypair,
742 ) -> Result<Self, StoreProtocolError> {
743 if !matches!(entry.payload, StorePublicationPayload::Snapshot(_)) {
744 return Err(StoreProtocolError::Malformed(
745 "Store publication entry is not a snapshot".to_string(),
746 ));
747 }
748 Self::advance(previous, entry, reference, signer)
749 }
750
751 fn advance(
752 previous: &Self,
753 entry: &StorePublicationEntry,
754 reference: StorePublicationRef,
755 signer: &UserKeypair,
756 ) -> Result<Self, StoreProtocolError> {
757 Ok(Signed::sign(
758 previous.body().advance(entry, reference)?,
759 signer,
760 ))
761 }
762
763 pub fn record_hash(&self) -> ObjectHash {
764 self.hash()
765 }
766
767 pub fn accepted(&self) -> Option<&StorePublicationRef> {
768 self.body().accepted()
769 }
770
771 pub fn latest_snapshot(&self) -> Option<&AcceptedStoreSnapshotRef> {
772 self.body().latest_snapshot()
773 }
774
775 pub fn publication_base(&self) -> StorePublicationBase {
776 self.body().publication_base()
777 }
778
779 pub fn next_position(&self) -> Result<StorePublicationPosition, StoreProtocolError> {
780 self.body().next_position()
781 }
782
783 pub fn verify_genesis(
784 &self,
785 expected_store_root_hash: ObjectHash,
786 founder_pubkey: &str,
787 ) -> Result<(), StoreProtocolError> {
788 if self.store_root_hash != expected_store_root_hash
789 || self.state != StorePublicationState::Genesis
790 {
791 return Err(StoreProtocolError::Malformed(
792 "Store genesis publication record differs from its descriptor".to_string(),
793 ));
794 }
795 self.verify_by(founder_pubkey)
796 }
797
798 pub fn verify_commit_transition(
799 &self,
800 previous: &Self,
801 entry: &StorePublicationEntry,
802 reference: &StorePublicationRef,
803 commit: &VerifiedStoreBatchCommit,
804 publisher_signing_pubkey: &str,
805 ) -> Result<(), StoreProtocolError> {
806 entry.validate_commit_against(previous, commit, publisher_signing_pubkey)?;
807 self.verify_transition(previous, entry, reference, publisher_signing_pubkey)
808 }
809
810 fn verify_transition(
811 &self,
812 previous: &Self,
813 entry: &StorePublicationEntry,
814 reference: &StorePublicationRef,
815 publisher_signing_pubkey: &str,
816 ) -> Result<(), StoreProtocolError> {
817 entry.validate_against(previous.body())?;
818 reference.verify_entry(entry)?;
819 self.verify_by(publisher_signing_pubkey)?;
820 let expected_latest = match &entry.payload {
821 StorePublicationPayload::Commit(_) => previous.latest_snapshot().cloned(),
822 StorePublicationPayload::Snapshot(snapshot) => Some(AcceptedStoreSnapshotRef {
823 snapshot: snapshot.clone(),
824 publication: reference.clone(),
825 }),
826 };
827 let expected_state = StorePublicationState::Accepted {
828 entry: reference.clone(),
829 latest_snapshot: expected_latest,
830 };
831 if self.store_root_hash != previous.store_root_hash || self.state != expected_state {
832 return Err(StoreProtocolError::Malformed(
833 "Store current publication record differs from its accepted transition".to_string(),
834 ));
835 }
836 Ok(())
837 }
838
839 pub fn verify_accepted_commit(
840 &self,
841 entry: &StorePublicationEntry,
842 reference: &StorePublicationRef,
843 commit: &VerifiedStoreBatchCommit,
844 publisher_signing_pubkey: &str,
845 ) -> Result<(), StoreProtocolError> {
846 let base = commit.publication_base();
847 entry.validate_commit_against_record(commit, publisher_signing_pubkey)?;
848 self.verify_accepted_with_latest(
849 entry,
850 reference,
851 match base {
852 StorePublicationBase::Genesis => None,
853 StorePublicationBase::Snapshot(snapshot) => Some(snapshot.clone()),
854 },
855 publisher_signing_pubkey,
856 )
857 }
858
859 fn verify_accepted_with_latest(
860 &self,
861 entry: &StorePublicationEntry,
862 reference: &StorePublicationRef,
863 expected_latest: Option<AcceptedStoreSnapshotRef>,
864 publisher_signing_pubkey: &str,
865 ) -> Result<(), StoreProtocolError> {
866 entry.validate_shape()?;
867 reference.verify_entry(entry)?;
868 self.verify_by(publisher_signing_pubkey)?;
869 let expected_state = StorePublicationState::Accepted {
870 entry: reference.clone(),
871 latest_snapshot: expected_latest,
872 };
873 if self.store_root_hash != entry.store_root_hash || self.state != expected_state {
874 return Err(StoreProtocolError::Malformed(
875 "Store current publication record differs from its accepted entry".to_string(),
876 ));
877 }
878 Ok(())
879 }
880}
881
882#[cfg(test)]
883#[path = "publication_tests.rs"]
884mod tests;