ossa_core/store/ecg/v0.rs
1use ossa_crdt::{time::CausalState, CRDT};
2use ossa_typeable::Typeable;
3use rand::Rng;
4use serde::{
5 de::{MapAccess, Visitor},
6 ser::{SerializeStruct, Serializer},
7 Deserialize, Serialize,
8};
9use std::{
10 collections::{BTreeMap, BTreeSet},
11 fmt::Debug,
12 marker::PhantomData,
13};
14
15use crate::{
16 store::ecg::{self, ECGBody, ECGHeader},
17 time::{CausalTime, ConcretizeTime},
18 util,
19};
20
21#[derive(Clone, Copy, Debug, Eq, PartialEq, PartialOrd, Ord, Typeable, Deserialize, Serialize)]
22pub struct HeaderId<Hash>(Hash);
23
24// TODO: Move this to the right location.
25/// An ECG header.
26#[derive(Clone, Debug, Deserialize, Serialize)]
27// #[derive(Clone, Typeable)]
28// #[tag = "v1"]
29pub struct Header<Hash> {
30 // , T> {
31 /// A nonce to randomize the header.
32 nonce: u8,
33
34 /// The header ids of our parents in the ECG graph.
35 parent_ids: Vec<HeaderId<Hash>>,
36
37 /// The number of operations in the corresponding body.
38 /// The maximum number of operations is 255.
39 operations_count: u8,
40
41 /// The hash of (batched) operations in the corresponding body.
42 /// TODO: Eventually this should be of the encrypted body..
43 operations_hash: Hash,
44 // // TODO: DeviceId and UserId of device signing? Maybe whole auth chain?
45 // phantom: PhantomData<T>,
46}
47
48#[derive(Debug)]
49pub struct Body<Hash, SerializedOp> {
50 /// The operations in this ECG body.
51 /// Invariant: <= 256 operations
52 operations: Vec<SerializedOp>, // <CausalTime<T::Time>>>,
53 phantom: PhantomData<fn(Hash)>,
54}
55
56// TODO: Define CBOR or rkyv properly
57impl<Hash, SerializedOp> Serialize for Body<Hash, SerializedOp>
58where
59 SerializedOp: Serialize,
60{
61 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
62 where
63 S: Serializer,
64 {
65 let mut s = serializer.serialize_struct("Body", 1)?;
66 s.serialize_field("operations", &self.operations)?;
67 s.end()
68 }
69}
70
71impl<'d, Hash, SerializedOp> Deserialize<'d> for Body<Hash, SerializedOp>
72where
73 SerializedOp: Deserialize<'d>,
74{
75 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
76 where
77 D: serde::Deserializer<'d>,
78 {
79 struct SVisitor<Hash, T>(PhantomData<(Hash, T)>);
80
81 #[derive(Deserialize)]
82 #[serde(field_identifier, rename_all = "lowercase")]
83 enum Field {
84 Operations,
85 }
86
87 impl<'d, Hash, SerializedOp> Visitor<'d> for SVisitor<Hash, SerializedOp>
88 where
89 SerializedOp: Deserialize<'d>,
90 {
91 type Value = Body<Hash, SerializedOp>;
92
93 fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
94 formatter.write_str("struct Body")
95 }
96
97 fn visit_map<M>(self, mut m: M) -> Result<Body<Hash, SerializedOp>, M::Error>
98 where
99 M: MapAccess<'d>,
100 {
101 let mut operations = None;
102 while let Some(key) = m.next_key()? {
103 match key {
104 Field::Operations => {
105 if operations.is_some() {
106 return Err(serde::de::Error::duplicate_field("operations"));
107 }
108 operations = Some(m.next_value()?);
109 }
110 }
111 }
112
113 let operations =
114 operations.ok_or_else(|| serde::de::Error::missing_field("operations"))?;
115 Ok(Body {
116 operations,
117 phantom: PhantomData,
118 })
119 }
120 }
121
122 deserializer.deserialize_struct("Body", &["operations"], SVisitor(PhantomData))
123 }
124}
125
126impl<
127 Hash: Clone + Copy + Debug + Ord + util::Hash,
128 // T : CRDT<Time = OperationId<HeaderId<Hash>>>,
129 > ECGHeader for Header<Hash>
130where
131 // <T as CRDT>::Op: Serialize,
132 Hash: Serialize, // TODO
133{
134 type HeaderId = HeaderId<Hash>;
135 // type Body = Body<Hash, T>;
136
137 fn get_parent_ids(&self) -> &[HeaderId<Hash>] {
138 &self.parent_ids
139 }
140
141 fn get_header_id(&self) -> HeaderId<Hash> {
142 HeaderId(tmp_hash(self))
143 }
144
145 fn validate_header(&self, header_id: HeaderId<Hash>) -> bool {
146 // TODO: Actually check this.
147 true
148 }
149
150 // // TODO: Move this to ECG state?
151 // fn new_header(parents: BTreeSet<Self::HeaderId>, body: &Body<Hash, T>) -> Self {
152 // let mut rng = rand::thread_rng();
153 // let nonce = rng.gen();
154
155 // // Sort parent headers.
156 // let parents = parents.into_iter().collect();
157
158 // // TODO: Check for hash conflicts and generate another nonce?
159
160 // Header {
161 // parent_ids: parents,
162 // nonce,
163 // operations_count: body.operations_count(),
164 // operations_hash: body.get_hash(),
165 // phantom: PhantomData,
166 // }
167 // }
168
169 // // Replace OperationId<H> with T::Time? Or add another associated type to ECGHeader?
170 // fn zip_operations_with_time<A>(&self, body: Self::Body) -> Vec<(A::Time, A::Op)> where A: CRDT {
171 // // let times = self.get_operation_times(&body);
172 // // let ops = body.operations();
173 // // times.into_iter().zip(ops).collect();
174 // todo!()
175
176 // }
177
178 // fn get_operation_times<A>(&self, body: &Self::Body) -> Vec<A::Time> where A: CRDT {
179 // // let header_id = Some(self.get_header_id());
180 // // let operations_c = body.operations_count();
181 // // (0..operations_c)
182 // // .map(move |i| OperationId {
183 // // header_id,
184 // // operation_position: i,
185 // // })
186 // // .collect();
187 // todo!()
188 // }
189}
190
191const MAX_OPERATION_COUNT: usize = 256;
192
193// impl<Hash, T> ECGBody<T::Op, <T::Op as ConcretizeTime<T::Time>>::Serialized> for Body<Hash, <T::Op as ConcretizeTime<T::Time>>::Serialized>
194impl<Hash, Op> ECGBody<Op, Op::Serialized> for Body<Hash, Op::Serialized>
195where
196 // T: CRDT<Time = OperationId<HeaderId<Hash>>>,
197 Op: ConcretizeTime<HeaderId<Hash>>,
198 Op::Serialized: Serialize,
199 // T::Op: ConcretizeTime<OperationId<HeaderId<Hash>>>,
200 // <T::Op as ConcretizeTime<T::Time>>::Serialized: Serialize,
201 Hash: Clone + Copy + Debug + Ord + util::Hash + Serialize,
202{
203 type Header = Header<Hash>;
204
205 fn new_body(operations: Vec<Op::Serialized>) -> Self {
206 if operations.len() > MAX_OPERATION_COUNT {
207 panic!("Exceeded the maximum number of batched operations.");
208 }
209
210 Body {
211 operations,
212 phantom: PhantomData,
213 }
214 }
215
216 fn operations(self, header_id: HeaderId<Hash>) -> impl Iterator<Item = Op> {
217 // let header_id = Some(header_id);
218 self.operations.into_iter().map(move |op| {
219 // let operation_id = OperationId {
220 // header_id,
221 // operation_position: i as u8,
222 // };
223 Op::concretize_time(op, header_id)
224 })
225 // self.operations.into_iter().map(move |op| op.concretize_time(|t| {
226 // match t {
227 // CausalTime::Time(t) => t,
228 // CausalTime::Current { operation_position } => OperationId {
229 // header_id: Some(header_id),
230 // operation_position,
231 // }
232 // }
233 // }))
234 }
235
236 fn operations_count(&self) -> u8 {
237 self.operations
238 .len()
239 .try_into()
240 .expect("Unreachable: Length is bound by MAX_OPERATION_COUNT.")
241 }
242
243 fn new_header(&self, parents: BTreeSet<<Self::Header as ECGHeader>::HeaderId>) -> Self::Header {
244 let mut rng = rand::thread_rng();
245 let nonce = rng.gen();
246
247 // Sort parent headers.
248 let parents = parents.into_iter().collect();
249
250 // TODO: Check for hash conflicts and generate another nonce?
251
252 Header {
253 parent_ids: parents,
254 nonce,
255 operations_count: <Self as ECGBody<Op, Op::Serialized>>::operations_count(self),
256 operations_hash: self.get_hash(),
257 // phantom: PhantomData,
258 }
259 }
260
261 // fn zip_operations_with_time(
262 // self,
263 // header: &Self::Header,
264 // ) -> Vec<(<T as CRDT>::Time, <T as CRDT>::Op<T::Time>)> {
265 // let times = self.get_operation_times(header);
266 // let ops = self.operations(header.get_header_id());
267 // times.into_iter().zip(ops).collect()
268 // }
269
270 // fn get_operation_times(&self, header: &Self::Header) -> Vec<<T as CRDT>::Time> {
271 // let header_id = Some(header.get_header_id());
272 // let operations_c = self.operations_count();
273 // (0..operations_c)
274 // .map(move |i| OperationId {
275 // header_id,
276 // operation_position: i,
277 // })
278 // .collect()
279 // }
280}
281
282impl<Hash: util::Hash, SerializedOp> Body<Hash, SerializedOp>
283where
284 SerializedOp: Serialize,
285{
286 fn get_hash(&self) -> Hash {
287 tmp_hash(self)
288 }
289}
290
291// TODO: Standardize how to hash/serialize. XXX
292fn tmp_hash<T: Serialize, Hash: util::Hash>(x: &T) -> Hash {
293 // JP: Better way to do this? Just serialize once?
294 let mut h = Hash::new();
295 let serialized = serde_cbor::ser::to_vec(&x).unwrap();
296 Hash::update(&mut h, serialized);
297 Hash::finalize(h)
298}
299
300// OperationID's are header ids and index (HeaderId, u8)
301// TODO: Move this to ossa-crdt::time??
302#[derive(Serialize, Deserialize, Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Typeable)]
303pub struct OperationId<HeaderId> {
304 pub header_id: Option<HeaderId>, // None when in the initial state?
305 pub operation_position: u8,
306}
307
308impl<HeaderId> ConcretizeTime<HeaderId> for OperationId<HeaderId> {
309 type Serialized = CausalTime<OperationId<HeaderId>>;
310
311 fn concretize_time(src: Self::Serialized, current_header: HeaderId) -> Self {
312 match src {
313 CausalTime::Current { operation_position } => OperationId {
314 header_id: Some(current_header),
315 operation_position,
316 },
317 CausalTime::Time(t) => t,
318 }
319 }
320}
321
322impl<HeaderId> OperationId<HeaderId> {
323 pub fn new(header_id: Option<HeaderId>, operation_position: u8) -> Self {
324 OperationId {
325 header_id,
326 operation_position,
327 }
328 }
329}
330
331impl<Header: ECGHeader, T: CRDT> CausalState for ecg::State<Header, T> {
332 type Time = OperationId<Header::HeaderId>;
333
334 fn happens_before(&self, a: &Self::Time, b: &Self::Time) -> bool {
335 if a.header_id == b.header_id {
336 a.operation_position < b.operation_position
337 } else {
338 if let Some(a_header_id) = &a.header_id {
339 if let Some(b_header_id) = &b.header_id {
340 self.is_ancestor_of(a_header_id, b_header_id)
341 .expect("Invariant violated. Unknown header id.")
342 } else {
343 // a.header_id.is_some() && b.header_id == None
344 false
345 }
346 } else {
347 // a.header_id == None
348 true
349 }
350 }
351 }
352}
353
354#[derive(Clone, Debug)]
355pub struct TestHeader<T> {
356 pub header_id: u32,
357 pub parent_ids: Vec<u32>,
358 pub phantom: PhantomData<T>,
359}
360
361pub struct TestBody<SerializedOp> {
362 operations: Vec<SerializedOp>,
363}
364
365/*
366impl<T: CRDT<Time = u32>> ECGBody<T> for TestBody<<T::Op as ConcretizeTime<u32>>::Serialized>
367where
368 T::Op: ConcretizeTime<u32>,
369// T::Op<CausalTime<T::Time>>: Serialize + ConcretizeTime<CausalTime<T::Time>, T::Time, Target<T::Time> = T::Op<T::Time>>,
370{
371 type Header = TestHeader<T>;
372
373 fn new_body(operations: Vec<<T::Op as ConcretizeTime<u32>>::Serialized>) -> Self {
374 TestBody { operations }
375 }
376
377 fn operations(self, header_id: u32) -> impl Iterator<Item = T::Op> {
378 self.operations.into_iter().map(move |op| T::Op::concretize_time(op, header_id))
379 // self.operations.into_iter().map(|op| op.concretize_time(|t| {
380 // match t {
381 // CausalTime::Time(t) => t,
382 // CausalTime::Current { operation_position } => todo!(),
383 // }
384 // }))
385 }
386
387 fn operations_count(&self) -> u8 {
388 self.operations
389 .len()
390 .try_into()
391 .expect("Unreachable: Length is bound by MAX_OPERATION_COUNT.")
392 }
393
394 // fn zip_operations_with_time(
395 // self,
396 // header: &Self::Header,
397 // ) -> Vec<(<T as CRDT>::Time, <T as CRDT>::Op<T::Time>)> {
398 // todo!()
399 // }
400
401 // fn get_operation_times(&self, header: &Self::Header) -> Vec<<T as CRDT>::Time> {
402 // todo!()
403 // }
404
405 fn new_header(&self, parents: BTreeSet<<Self::Header as ECGHeader>::HeaderId>) -> Self::Header {
406 todo!()
407 }
408}
409*/
410
411// For testing, just have the header store the parent ids.
412impl<A: CRDT> ECGHeader for TestHeader<A> {
413 type HeaderId = u32;
414 // type Body = TestBody<A>;
415
416 fn get_parent_ids(&self) -> &[u32] {
417 &self.parent_ids
418 }
419
420 fn get_header_id(&self) -> u32 {
421 self.header_id
422 }
423
424 fn validate_header(&self, header_id: Self::HeaderId) -> bool {
425 true
426 }
427
428 // fn new_header(parents: BTreeSet<Self::HeaderId>, _body: &Self::Body) -> Self {
429 // todo!()
430 // }
431
432 // // JP: TODO: Move this to ECGBody?
433 // fn zip_operations_with_time<T>(&self, body: Self::Body) -> Vec<(T::Time, T::Op)>
434 // where
435 // T: CRDT,
436 // Self::Body: ECGBody<T>,
437 // {
438 // let v: Vec<_> = todo!();
439 // v
440 // }
441
442 // fn get_operation_times<T>(&self, body: &Self::Body) -> Vec<T::Time> where T: CRDT, {
443 // let v: Vec<_> = todo!();
444 // v
445 // }
446}