Skip to main content

coven_database/store/store_session/
observed_store_publication.rs

1use 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}