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#[derive(Clone, Debug)]
73pub struct SimplexClient {
74 client: PrefixedStoreClient,
75}
76
77impl SimplexClient {
78 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(¬arized.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}