1use crate::{
7 db::{
8 QueryError,
9 codec::{finalize_hash_sha256, new_hash_sha256_prefixed, write_hash_str_u32},
10 integrity::DatabaseIncarnationId,
11 schema::AcceptedSchemaRuntimeRootIdentity,
12 },
13 error::InternalError,
14};
15use candid::CandidType;
16use serde::Deserialize;
17use std::{error::Error as StdError, fmt};
18
19const READ_SET_STORE_IDENTITY_DOMAIN: &[u8] = b"icydb.read-set.store.v1";
20const READ_SET_PROOF_FIXED_BYTES: usize = 16 + 8 + 1 + 32 + 4;
21const READ_SET_STORE_ENTRY_BYTES: usize = 32 + 8 + 8;
22
23pub const MAX_READ_SET_PROOF_STORES: usize = 64;
25const MAX_READ_SET_PROOF_STORES_U32: u32 = 64;
26pub const MAX_READ_SET_PROOF_BYTES: usize = 8 * 1024;
28const MAX_READ_SET_PROOF_BYTES_U32: u32 = 8 * 1024;
29
30#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, Hash, Ord, PartialEq, PartialOrd)]
32pub struct ReadSetStoreIdentity([u8; 32]);
33
34impl ReadSetStoreIdentity {
35 pub(in crate::db) fn for_store_path(store_path: &str) -> Self {
36 let mut hasher = new_hash_sha256_prefixed(READ_SET_STORE_IDENTITY_DOMAIN);
37 write_hash_str_u32(&mut hasher, store_path);
38 Self(finalize_hash_sha256(hasher))
39 }
40
41 pub(in crate::db) const fn from_bytes(bytes: [u8; 32]) -> Self {
42 Self(bytes)
43 }
44
45 #[must_use]
47 pub const fn to_bytes(self) -> [u8; 32] {
48 self.0
49 }
50}
51
52#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq)]
54pub struct ReadSetStoreRevision {
55 store: ReadSetStoreIdentity,
56 data_revision: u64,
57 access_state_revision: u64,
58}
59
60impl ReadSetStoreRevision {
61 pub(in crate::db) const fn new(
62 store: ReadSetStoreIdentity,
63 data_revision: u64,
64 access_state_revision: u64,
65 ) -> Self {
66 Self {
67 store,
68 data_revision,
69 access_state_revision,
70 }
71 }
72
73 #[must_use]
75 pub const fn store(&self) -> ReadSetStoreIdentity {
76 self.store
77 }
78
79 #[must_use]
81 pub const fn data_revision(&self) -> u64 {
82 self.data_revision
83 }
84
85 #[must_use]
87 pub const fn access_state_revision(&self) -> u64 {
88 self.access_state_revision
89 }
90}
91
92#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq)]
94pub struct ReadSetRevisionProof {
95 database_incarnation: [u8; 16],
96 accepted_root_revision: u64,
97 accepted_root_fingerprint_method: u8,
98 accepted_root_fingerprint: [u8; 32],
99 stores: Vec<ReadSetStoreRevision>,
100}
101
102impl ReadSetRevisionProof {
103 pub(in crate::db) fn new(
104 root: AcceptedSchemaRuntimeRootIdentity,
105 stores: Vec<ReadSetStoreRevision>,
106 ) -> Result<Self, ReadSetRevisionError> {
107 let (accepted_root_fingerprint_method, accepted_root_fingerprint) = root.fingerprint();
108 let proof = Self {
109 database_incarnation: root.database_incarnation().to_bytes(),
110 accepted_root_revision: root.accepted_root_revision().get(),
111 accepted_root_fingerprint_method,
112 accepted_root_fingerprint,
113 stores,
114 };
115 proof.validate()?;
116 Ok(proof)
117 }
118
119 pub(in crate::db) fn from_parts(
120 database_incarnation: [u8; 16],
121 accepted_root_revision: u64,
122 accepted_root_fingerprint_method: u8,
123 accepted_root_fingerprint: [u8; 32],
124 stores: Vec<ReadSetStoreRevision>,
125 ) -> Result<Self, ReadSetRevisionError> {
126 let proof = Self {
127 database_incarnation,
128 accepted_root_revision,
129 accepted_root_fingerprint_method,
130 accepted_root_fingerprint,
131 stores,
132 };
133 proof.validate()?;
134 Ok(proof)
135 }
136
137 #[must_use]
139 pub const fn database_incarnation(&self) -> [u8; 16] {
140 self.database_incarnation
141 }
142
143 #[must_use]
145 pub const fn accepted_root_revision(&self) -> u64 {
146 self.accepted_root_revision
147 }
148
149 #[must_use]
151 pub const fn accepted_root_fingerprint_method(&self) -> u8 {
152 self.accepted_root_fingerprint_method
153 }
154
155 #[must_use]
157 pub const fn accepted_root_fingerprint(&self) -> [u8; 32] {
158 self.accepted_root_fingerprint
159 }
160
161 #[must_use]
163 pub const fn stores(&self) -> &[ReadSetStoreRevision] {
164 self.stores.as_slice()
165 }
166
167 #[must_use]
169 pub const fn encoded_len(&self) -> usize {
170 READ_SET_PROOF_FIXED_BYTES
171 .saturating_add(self.stores.len().saturating_mul(READ_SET_STORE_ENTRY_BYTES))
172 }
173
174 pub fn validate(&self) -> Result<(), ReadSetRevisionError> {
176 if self.stores.is_empty() {
177 return Err(ReadSetRevisionError::Empty);
178 }
179 if self.stores.len() > MAX_READ_SET_PROOF_STORES {
180 return Err(ReadSetRevisionError::TooManyStores {
181 limit: MAX_READ_SET_PROOF_STORES_U32,
182 actual: u32::try_from(self.stores.len()).unwrap_or(u32::MAX),
183 });
184 }
185 let encoded_len = self.encoded_len();
186 if encoded_len > MAX_READ_SET_PROOF_BYTES {
187 return Err(ReadSetRevisionError::EncodedBytesExceeded {
188 limit: MAX_READ_SET_PROOF_BYTES_U32,
189 actual: u32::try_from(encoded_len).unwrap_or(u32::MAX),
190 });
191 }
192 if self.database_incarnation == [0; 16]
193 || self.accepted_root_revision == 0
194 || self.accepted_root_fingerprint_method == 0
195 || self.accepted_root_fingerprint == [0; 32]
196 || self.stores.iter().any(|store| {
197 store.store.to_bytes() == [0; 32]
198 || store.data_revision == 0
199 || store.access_state_revision == 0
200 })
201 || self
202 .stores
203 .windows(2)
204 .any(|pair| pair[0].store >= pair[1].store)
205 {
206 return Err(ReadSetRevisionError::NonCanonical);
207 }
208 Ok(())
209 }
210
211 pub(in crate::db) fn contains_store(&self, store: ReadSetStoreIdentity) -> bool {
212 self.stores
213 .binary_search_by_key(&store, ReadSetStoreRevision::store)
214 .is_ok()
215 }
216
217 pub(in crate::db) fn signature_bytes(&self) -> Vec<u8> {
218 let mut bytes = Vec::with_capacity(self.encoded_len());
219 bytes.extend_from_slice(&self.database_incarnation);
220 bytes.extend_from_slice(&self.accepted_root_revision.to_be_bytes());
221 bytes.push(self.accepted_root_fingerprint_method);
222 bytes.extend_from_slice(&self.accepted_root_fingerprint);
223 let store_count = u32::try_from(self.stores.len()).unwrap_or(u32::MAX);
224 bytes.extend_from_slice(&store_count.to_be_bytes());
225 for store in &self.stores {
226 bytes.extend_from_slice(&store.store.to_bytes());
227 bytes.extend_from_slice(&store.data_revision.to_be_bytes());
228 bytes.extend_from_slice(&store.access_state_revision.to_be_bytes());
229 }
230 bytes
231 }
232
233 pub(in crate::db) fn root_matches(
234 &self,
235 incarnation: DatabaseIncarnationId,
236 root: AcceptedSchemaRuntimeRootIdentity,
237 ) -> bool {
238 let (method, fingerprint) = root.fingerprint();
239 self.database_incarnation == incarnation.to_bytes()
240 && self.accepted_root_revision == root.accepted_root_revision().get()
241 && self.accepted_root_fingerprint_method == method
242 && self.accepted_root_fingerprint == fingerprint
243 }
244}
245
246#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq)]
248pub enum ReadSetRevisionError {
249 Empty,
251 TooManyStores { limit: u32, actual: u32 },
253 EncodedBytesExceeded { limit: u32, actual: u32 },
255 NonCanonical,
257 UnknownEntity,
259 StoreMissingFromProof { store: ReadSetStoreIdentity },
261 ResumeProofRequired,
263 DatabaseIncarnationChanged,
265 AcceptedRootChanged,
267 StoreDataChanged { store: ReadSetStoreIdentity },
269 StoreAccessChanged { store: ReadSetStoreIdentity },
271 DurableStoreRequired { store: ReadSetStoreIdentity },
273}
274
275impl fmt::Display for ReadSetRevisionError {
276 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
277 formatter.write_str("exhaustive read source proof is invalid")
278 }
279}
280
281impl StdError for ReadSetRevisionError {}
282
283impl ReadSetRevisionError {
284 pub(in crate::db) const fn is_source_change(&self) -> bool {
285 matches!(
286 self,
287 Self::DatabaseIncarnationChanged
288 | Self::AcceptedRootChanged
289 | Self::StoreDataChanged { .. }
290 | Self::StoreAccessChanged { .. }
291 )
292 }
293}
294
295#[derive(Debug)]
297pub enum ExhaustiveReadError {
298 Query(QueryError),
300 Revision(ReadSetRevisionError),
302}
303
304impl fmt::Display for ExhaustiveReadError {
305 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
306 match self {
307 Self::Query(error) => error.fmt(formatter),
308 Self::Revision(error) => error.fmt(formatter),
309 }
310 }
311}
312
313impl StdError for ExhaustiveReadError {}
314
315impl From<QueryError> for ExhaustiveReadError {
316 fn from(error: QueryError) -> Self {
317 Self::Query(error)
318 }
319}
320
321impl From<InternalError> for ExhaustiveReadError {
322 fn from(error: InternalError) -> Self {
323 Self::Query(QueryError::execute(error))
324 }
325}
326
327impl From<ReadSetRevisionError> for ExhaustiveReadError {
328 fn from(error: ReadSetRevisionError) -> Self {
329 Self::Revision(error)
330 }
331}
332
333#[cfg(test)]
334mod tests {
335 use super::*;
336
337 fn store(byte: u8, data_revision: u64) -> ReadSetStoreRevision {
338 ReadSetStoreRevision::new(
339 ReadSetStoreIdentity::from_bytes([byte; 32]),
340 data_revision,
341 1,
342 )
343 }
344
345 fn proof(
346 stores: Vec<ReadSetStoreRevision>,
347 ) -> Result<ReadSetRevisionProof, ReadSetRevisionError> {
348 ReadSetRevisionProof::from_parts([1; 16], 1, 1, [2; 32], stores)
349 }
350
351 #[test]
352 fn read_set_proof_requires_bounded_canonical_distinct_store_order() {
353 assert_eq!(proof(Vec::new()), Err(ReadSetRevisionError::Empty));
354 assert_eq!(
355 proof(vec![store(2, 1), store(1, 1)]),
356 Err(ReadSetRevisionError::NonCanonical),
357 );
358 assert_eq!(
359 proof(vec![store(1, 1), store(1, 2)]),
360 Err(ReadSetRevisionError::NonCanonical),
361 );
362
363 let too_many = (1..=MAX_READ_SET_PROOF_STORES + 1)
364 .map(|index| {
365 let mut identity = [0; 32];
366 identity[24..].copy_from_slice(
367 &u64::try_from(index)
368 .expect("bounded test store count should fit u64")
369 .to_be_bytes(),
370 );
371 ReadSetStoreRevision::new(ReadSetStoreIdentity::from_bytes(identity), 1, 1)
372 })
373 .collect();
374 assert_eq!(
375 proof(too_many),
376 Err(ReadSetRevisionError::TooManyStores {
377 limit: MAX_READ_SET_PROOF_STORES_U32,
378 actual: u32::try_from(MAX_READ_SET_PROOF_STORES + 1)
379 .expect("bounded test store count should fit u32"),
380 }),
381 );
382 }
383
384 #[test]
385 fn read_set_proof_rejects_zero_authority_or_revision_components() {
386 let valid = proof(vec![store(1, 1)]).expect("nonzero canonical proof should admit");
387 assert_eq!(
388 valid.encoded_len(),
389 READ_SET_PROOF_FIXED_BYTES + READ_SET_STORE_ENTRY_BYTES
390 );
391
392 assert_eq!(
393 ReadSetRevisionProof::from_parts([1; 16], 0, 1, [2; 32], vec![store(1, 1)]),
394 Err(ReadSetRevisionError::NonCanonical),
395 );
396 assert_eq!(
397 proof(vec![store(1, 0)]),
398 Err(ReadSetRevisionError::NonCanonical),
399 );
400 }
401}