coven_database/store/store_session/
observed_store_publication.rs1use crate::DbError;
2use coven_protocol::objects::ExactObjectVersion;
3use coven_protocol::store_commit::StoreCurrentPublicationRecord;
4use rusqlite::OptionalExtension;
5
6use super::StoreSession;
7
8#[derive(Debug, Clone, PartialEq, Eq)]
9pub struct ObservedStorePublication {
10 record: StoreCurrentPublicationRecord,
11 version: ExactObjectVersion,
12}
13
14impl ObservedStorePublication {
15 pub fn verified_genesis(
16 record: StoreCurrentPublicationRecord,
17 version: ExactObjectVersion,
18 expected_store_root_hash: coven_protocol::store_commit::ObjectHash,
19 founder_pubkey: &str,
20 ) -> Result<Self, coven_protocol::store_commit::StoreProtocolError> {
21 record.verify_genesis(expected_store_root_hash, founder_pubkey)?;
22 Ok(Self { record, version })
23 }
24
25 pub fn record(&self) -> &StoreCurrentPublicationRecord {
26 &self.record
27 }
28
29 pub fn version(&self) -> &ExactObjectVersion {
30 &self.version
31 }
32
33 pub fn verified_commit_successor(
34 previous: &Self,
35 record: StoreCurrentPublicationRecord,
36 version: ExactObjectVersion,
37 entry: &coven_protocol::store_commit::StorePublicationEntry,
38 reference: &coven_protocol::store_commit::StorePublicationRef,
39 commit: &coven_protocol::store_commit::VerifiedStoreBatchCommit,
40 publisher_signing_pubkey: &str,
41 ) -> Result<Self, coven_protocol::store_commit::StoreProtocolError> {
42 record.verify_commit_transition(
43 &previous.record,
44 entry,
45 reference,
46 commit,
47 publisher_signing_pubkey,
48 )?;
49 Ok(Self { record, version })
50 }
51}
52
53pub(super) fn load_store_current_publication_on(
54 connection: &rusqlite::Connection,
55) -> Result<ObservedStorePublication, DbError> {
56 let (hash, bytes, version) = connection
57 .query_row(
58 "SELECT record_hash, record_bytes, provider_version
59 FROM store_publication_current WHERE singleton = 1",
60 [],
61 |row| {
62 Ok((
63 row.get::<_, String>(0)?,
64 row.get::<_, Vec<u8>>(1)?,
65 row.get::<_, String>(2)?,
66 ))
67 },
68 )
69 .optional()
70 .map_err(DbError::from)?
71 .ok_or_else(|| {
72 DbError::Message("Store publication current record is absent".to_string())
73 })?;
74 let record: StoreCurrentPublicationRecord = serde_json::from_slice(&bytes)
75 .map_err(|error| DbError::context("Store current publication record", error))?;
76 if record.to_bytes() != bytes || record.record_hash().to_string() != hash {
77 return Err(DbError::Message(
78 "Store current publication row differs from its canonical record".to_string(),
79 ));
80 }
81 Ok(ObservedStorePublication {
82 record,
83 version: ExactObjectVersion::from_provider(version).map_err(DbError::from)?,
84 })
85}
86
87pub(super) fn install_genesis_store_publication_on(
88 transaction: &rusqlite::Transaction<'_>,
89 record: &StoreCurrentPublicationRecord,
90 version: &ExactObjectVersion,
91) -> Result<(), DbError> {
92 if record.accepted().is_some() {
93 return Err(DbError::Message(
94 "initial Store publication record is not genesis".to_string(),
95 ));
96 }
97 let bytes = record.to_bytes();
98 let inserted = transaction
99 .execute(
100 "INSERT INTO store_publication_current
101 (singleton, record_hash, record_bytes, provider_version)
102 VALUES (1, ?1, ?2, ?3)
103 ON CONFLICT(singleton) DO NOTHING",
104 rusqlite::params![
105 record.record_hash().to_string(),
106 bytes,
107 version.as_provider()
108 ],
109 )
110 .map_err(DbError::from)?;
111 if inserted == 0 {
112 let current = load_store_current_publication_on(transaction)?;
113 if current.record != *record || current.version != *version {
114 return Err(DbError::Message(
115 "Store publication genesis differs from installed current record".to_string(),
116 ));
117 }
118 }
119 Ok(())
120}
121
122impl StoreSession<'_> {
123 pub(crate) fn store_current_publication(&self) -> Result<ObservedStorePublication, DbError> {
124 load_store_current_publication_on(self.conn)
125 }
126}