Skip to main content

exoware_simplex/
client.rs

1use bytes::Bytes;
2use commonware_codec::{Decode, Encode};
3use commonware_consensus::{Block, Viewable};
4use commonware_cryptography::{certificate, Digest};
5use exoware_sdk::keys::Key;
6use exoware_sdk::{ClientError, PrefixedStoreClient, RangeMode, StoreBatchUpload, StoreWriteBatch};
7use futures::future::BoxFuture;
8
9use crate::error::SimplexError;
10use crate::keys::{self, RecordKind};
11use crate::types::{
12    encode_block_data, BlockData, Finalized, Notarized, UploadReceipt, UploadSummary,
13};
14
15#[derive(Clone, Debug)]
16pub struct PreparedEntry {
17    pub key: Key,
18    pub value: Bytes,
19}
20
21#[derive(Clone, Debug, Default)]
22#[must_use]
23pub struct PreparedUpload {
24    entries: Vec<PreparedEntry>,
25    summary: UploadSummary,
26}
27
28impl PreparedUpload {
29    pub fn new() -> Self {
30        Self::default()
31    }
32
33    pub fn len(&self) -> usize {
34        self.entries.len()
35    }
36
37    pub fn is_empty(&self) -> bool {
38        self.entries.is_empty()
39    }
40
41    pub fn summary(&self) -> UploadSummary {
42        self.summary
43    }
44
45    pub fn entries(&self) -> &[PreparedEntry] {
46        &self.entries
47    }
48
49    pub fn extend(&mut self, other: PreparedUpload) {
50        self.summary.headers += other.summary.headers;
51        self.summary.blocks += other.summary.blocks;
52        self.summary.notarizations += other.summary.notarizations;
53        self.summary.finalizations += other.summary.finalizations;
54        self.summary.finalized_height_indexes += other.summary.finalized_height_indexes;
55        self.entries.extend(other.entries);
56    }
57
58    fn push(&mut self, key: Key, value: Bytes) {
59        self.entries.push(PreparedEntry { key, value });
60    }
61}
62
63/// Store-backed writer for Commonware Simplex blocks and certificates.
64///
65/// The writer stores five logical indexes:
66///
67/// - header bytes by header digest
68/// - full `{ header, body }` bytes by header digest
69/// - notarized `{ proof, header }` bytes by Simplex view
70/// - finalized `{ proof, header }` bytes by Simplex view
71/// - finalized `{ proof, header }` bytes by header height
72#[derive(Clone, Debug)]
73pub struct SimplexClient {
74    client: PrefixedStoreClient,
75}
76
77impl SimplexClient {
78    /// Build a client over `client`'s namespace prefix.
79    pub fn new(client: PrefixedStoreClient) -> Self {
80        Self { client }
81    }
82
83    pub fn store_client(&self) -> &PrefixedStoreClient {
84        &self.client
85    }
86
87    pub fn into_store_client(self) -> PrefixedStoreClient {
88        self.client
89    }
90
91    pub fn prepare_header<B>(&self, header: &B) -> PreparedUpload
92    where
93        B: Block,
94    {
95        let mut prepared = PreparedUpload::new();
96        prepared.summary.headers = 1;
97        prepared.push(keys::header_by_digest(&header.digest()), header.encode());
98        prepared
99    }
100
101    pub fn prepare_block<B>(&self, header: &B, body: impl Into<Bytes>) -> PreparedUpload
102    where
103        B: Block,
104    {
105        let body = body.into();
106        let mut prepared = self.prepare_header(header);
107        prepared.summary.blocks = 1;
108        prepared.push(
109            keys::block_by_digest(&header.digest()),
110            encode_block_data(header, &body),
111        );
112        prepared
113    }
114
115    pub fn prepare_block_data<B>(&self, data: &BlockData<B>) -> PreparedUpload
116    where
117        B: Block,
118    {
119        self.prepare_block(&data.header, data.body.clone())
120    }
121
122    pub fn prepare_notarized<B, S, D>(
123        &self,
124        notarized: &Notarized<B, S, D>,
125    ) -> Result<PreparedUpload, SimplexError>
126    where
127        B: Block<Digest = D>,
128        S: certificate::Scheme,
129        D: Digest,
130    {
131        if notarized.proof.proposal.payload != notarized.header.digest() {
132            return Err(SimplexError::ProofBlockMismatch);
133        }
134
135        let mut prepared = self.prepare_header(&notarized.header);
136        prepared.summary.notarizations = 1;
137        prepared.push(
138            keys::notarization_by_view(notarized.proof.view()),
139            notarized.encode(),
140        );
141        Ok(prepared)
142    }
143
144    pub fn prepare_finalized<B, S, D>(
145        &self,
146        finalized: &Finalized<B, S, D>,
147    ) -> Result<PreparedUpload, SimplexError>
148    where
149        B: Block<Digest = D>,
150        S: certificate::Scheme,
151        D: Digest,
152    {
153        if finalized.proof.proposal.payload != finalized.header.digest() {
154            return Err(SimplexError::ProofBlockMismatch);
155        }
156
157        let mut prepared = self.prepare_header(&finalized.header);
158        let encoded = finalized.encode();
159        prepared.summary.finalizations = 1;
160        prepared.summary.finalized_height_indexes = 1;
161        prepared.push(
162            keys::finalization_by_view(finalized.proof.view()),
163            encoded.clone(),
164        );
165        prepared.push(
166            keys::finalized_by_height(finalized.header.height()),
167            encoded,
168        );
169        Ok(prepared)
170    }
171
172    pub fn stage_upload(
173        &self,
174        prepared: &PreparedUpload,
175        batch: &mut StoreWriteBatch,
176    ) -> Result<(), SimplexError> {
177        if prepared.is_empty() {
178            return Err(SimplexError::EmptyUpload);
179        }
180        for entry in prepared.entries() {
181            batch.push(&self.client, &entry.key, entry.value.clone())?;
182        }
183        Ok(())
184    }
185
186    pub async fn mark_upload_persisted(
187        &self,
188        prepared: PreparedUpload,
189        sequence_number: u64,
190    ) -> UploadReceipt {
191        UploadReceipt {
192            store_sequence_number: sequence_number,
193            summary: prepared.summary,
194        }
195    }
196
197    pub async fn mark_upload_failed(&self, _prepared: PreparedUpload, _err: impl ToString) {}
198
199    pub async fn upload_header<B>(&self, header: &B) -> Result<UploadReceipt, SimplexError>
200    where
201        B: Block,
202    {
203        let prepared = self.prepare_header(header);
204        self.commit_upload(prepared).await
205    }
206
207    pub async fn upload_block<B>(
208        &self,
209        header: &B,
210        body: impl Into<Bytes>,
211    ) -> Result<UploadReceipt, SimplexError>
212    where
213        B: Block,
214    {
215        let prepared = self.prepare_block(header, body);
216        self.commit_upload(prepared).await
217    }
218
219    pub async fn upload_notarized<B, S, D>(
220        &self,
221        notarized: &Notarized<B, S, D>,
222    ) -> Result<UploadReceipt, SimplexError>
223    where
224        B: Block<Digest = D>,
225        S: certificate::Scheme,
226        D: Digest,
227    {
228        let prepared = self.prepare_notarized(notarized)?;
229        self.commit_upload(prepared).await
230    }
231
232    pub async fn upload_finalized<B, S, D>(
233        &self,
234        finalized: &Finalized<B, S, D>,
235    ) -> Result<UploadReceipt, SimplexError>
236    where
237        B: Block<Digest = D>,
238        S: certificate::Scheme,
239        D: Digest,
240    {
241        let prepared = self.prepare_finalized(finalized)?;
242        self.commit_upload(prepared).await
243    }
244
245    pub async fn get_header_raw<D: Digest>(
246        &self,
247        digest: &D,
248    ) -> Result<Option<Bytes>, SimplexError> {
249        self.get_raw(keys::header_by_digest(digest)).await
250    }
251
252    pub async fn get_block_raw<D: Digest>(
253        &self,
254        digest: &D,
255    ) -> Result<Option<Bytes>, SimplexError> {
256        self.get_raw(keys::block_by_digest(digest)).await
257    }
258
259    pub async fn get_notarized_raw(
260        &self,
261        view: commonware_consensus::types::View,
262    ) -> Result<Option<Bytes>, SimplexError> {
263        self.get_raw(keys::notarization_by_view(view)).await
264    }
265
266    pub async fn get_finalized_by_view_raw(
267        &self,
268        view: commonware_consensus::types::View,
269    ) -> Result<Option<Bytes>, SimplexError> {
270        self.get_raw(keys::finalization_by_view(view)).await
271    }
272
273    pub async fn get_finalized_by_height_raw(
274        &self,
275        height: commonware_consensus::types::Height,
276    ) -> Result<Option<Bytes>, SimplexError> {
277        self.get_raw(keys::finalized_by_height(height)).await
278    }
279
280    pub async fn latest_finalized_raw(&self) -> Result<Option<Bytes>, SimplexError> {
281        self.latest_raw(RecordKind::FinalizedByHeight).await
282    }
283
284    pub async fn get_header<B, D>(
285        &self,
286        digest: &D,
287        cfg: &<B as commonware_codec::Read>::Cfg,
288    ) -> Result<Option<B>, SimplexError>
289    where
290        B: Block<Digest = D>,
291        D: Digest,
292    {
293        self.decode_optional(self.get_header_raw(digest).await?, cfg)
294    }
295
296    pub async fn get_block<B, D>(
297        &self,
298        digest: &D,
299        cfg: &<BlockData<B> as commonware_codec::Read>::Cfg,
300    ) -> Result<Option<BlockData<B>>, SimplexError>
301    where
302        B: Block<Digest = D>,
303        D: Digest,
304    {
305        self.decode_optional(self.get_block_raw(digest).await?, cfg)
306    }
307
308    pub async fn get_notarized<B, S, D>(
309        &self,
310        view: commonware_consensus::types::View,
311        cfg: &<Notarized<B, S, D> as commonware_codec::Read>::Cfg,
312    ) -> Result<Option<Notarized<B, S, D>>, SimplexError>
313    where
314        B: Block<Digest = D>,
315        S: certificate::Scheme,
316        D: Digest,
317        <S::Certificate as commonware_codec::Read>::Cfg: Clone,
318    {
319        self.decode_optional(self.get_notarized_raw(view).await?, cfg)
320    }
321
322    pub async fn get_finalized_by_height<B, S, D>(
323        &self,
324        height: commonware_consensus::types::Height,
325        cfg: &<Finalized<B, S, D> as commonware_codec::Read>::Cfg,
326    ) -> Result<Option<Finalized<B, S, D>>, SimplexError>
327    where
328        B: Block<Digest = D>,
329        S: certificate::Scheme,
330        D: Digest,
331        <S::Certificate as commonware_codec::Read>::Cfg: Clone,
332    {
333        self.decode_optional(self.get_finalized_by_height_raw(height).await?, cfg)
334    }
335
336    pub async fn get_finalized_by_view<B, S, D>(
337        &self,
338        view: commonware_consensus::types::View,
339        cfg: &<Finalized<B, S, D> as commonware_codec::Read>::Cfg,
340    ) -> Result<Option<Finalized<B, S, D>>, SimplexError>
341    where
342        B: Block<Digest = D>,
343        S: certificate::Scheme,
344        D: Digest,
345        <S::Certificate as commonware_codec::Read>::Cfg: Clone,
346    {
347        self.decode_optional(self.get_finalized_by_view_raw(view).await?, cfg)
348    }
349
350    pub async fn latest_finalized<B, S, D>(
351        &self,
352        cfg: &<Finalized<B, S, D> as commonware_codec::Read>::Cfg,
353    ) -> Result<Option<Finalized<B, S, D>>, SimplexError>
354    where
355        B: Block<Digest = D>,
356        S: certificate::Scheme,
357        D: Digest,
358        <S::Certificate as commonware_codec::Read>::Cfg: Clone,
359    {
360        self.decode_optional(self.latest_finalized_raw().await?, cfg)
361    }
362
363    async fn get_raw(&self, key: Key) -> Result<Option<Bytes>, SimplexError> {
364        Ok(self.client.query().get(&key).await?)
365    }
366
367    async fn latest_raw(&self, kind: RecordKind) -> Result<Option<Bytes>, SimplexError> {
368        let (start, end) = keys::range_for_kind(kind);
369        let rows = self
370            .client
371            .query()
372            .range_with_mode(&start, &end, 1, RangeMode::Reverse)
373            .await?;
374        Ok(rows.into_iter().next().map(|(_, value)| value))
375    }
376
377    fn decode_optional<T: Decode>(
378        &self,
379        value: Option<Bytes>,
380        cfg: &T::Cfg,
381    ) -> Result<Option<T>, SimplexError> {
382        value
383            .map(|bytes| T::decode_cfg(bytes, cfg).map_err(SimplexError::from))
384            .transpose()
385    }
386}
387
388impl StoreBatchUpload for SimplexClient {
389    type Prepared = PreparedUpload;
390    type Receipt = UploadReceipt;
391    type Error = SimplexError;
392
393    fn store_client(&self) -> &PrefixedStoreClient {
394        &self.client
395    }
396
397    fn stage_upload(
398        &self,
399        prepared: &mut Self::Prepared,
400        batch: &mut StoreWriteBatch,
401    ) -> Result<(), Self::Error> {
402        SimplexClient::stage_upload(self, prepared, batch)
403    }
404
405    fn commit_error(&self, error: ClientError) -> Self::Error {
406        SimplexError::Client(error)
407    }
408
409    fn mark_upload_persisted<'a>(
410        &'a self,
411        prepared: Self::Prepared,
412        sequence_number: u64,
413    ) -> BoxFuture<'a, Self::Receipt>
414    where
415        Self: Sync + 'a,
416        Self::Prepared: 'a,
417    {
418        Box::pin(async move {
419            SimplexClient::mark_upload_persisted(self, prepared, sequence_number).await
420        })
421    }
422
423    fn mark_upload_failed<'a>(
424        &'a self,
425        prepared: Self::Prepared,
426        error: String,
427    ) -> BoxFuture<'a, ()>
428    where
429        Self: Sync + 'a,
430        Self::Prepared: 'a,
431    {
432        Box::pin(async move {
433            SimplexClient::mark_upload_failed(self, prepared, error).await;
434        })
435    }
436}