1use crate::{
4 types::{coding::Commitment, Height},
5 Block, CertifiableBlock, Heightable,
6};
7use commonware_codec::{EncodeSize, Read, ReadExt, Write};
8use commonware_coding::{Config as CodingConfig, Scheme};
9use commonware_cryptography::{Committable, Digestible, Hasher};
10use commonware_parallel::{Sequential, Strategy};
11use commonware_utils::{Faults, N3f1, NZU16};
12use std::{marker::PhantomData, ops::Deref};
13
14const STRONG_SHARD_TAG: u8 = 0;
15const WEAK_SHARD_TAG: u8 = 1;
16
17#[derive(Clone)]
22pub enum DistributionShard<C: Scheme> {
23 Strong(C::StrongShard),
26 Weak(C::WeakShard),
28}
29
30impl<C: Scheme> Write for DistributionShard<C> {
31 fn write(&self, buf: &mut impl bytes::BufMut) {
32 match self {
33 Self::Strong(shard) => {
34 buf.put_u8(STRONG_SHARD_TAG);
35 shard.write(buf);
36 }
37 Self::Weak(weak_shard) => {
38 buf.put_u8(WEAK_SHARD_TAG);
39 weak_shard.write(buf);
40 }
41 }
42 }
43}
44
45impl<C: Scheme> EncodeSize for DistributionShard<C> {
46 fn encode_size(&self) -> usize {
47 1 + match self {
48 Self::Strong(shard) => shard.encode_size(),
49 Self::Weak(weak_shard) => weak_shard.encode_size(),
50 }
51 }
52}
53
54impl<C: Scheme> Read for DistributionShard<C> {
55 type Cfg = commonware_coding::CodecConfig;
56
57 fn read_cfg(
58 buf: &mut impl bytes::Buf,
59 shard_cfg: &Self::Cfg,
60 ) -> Result<Self, commonware_codec::Error> {
61 match u8::read(buf)? {
62 STRONG_SHARD_TAG => {
63 let shard = C::StrongShard::read_cfg(buf, shard_cfg)?;
64 Ok(Self::Strong(shard))
65 }
66 WEAK_SHARD_TAG => {
67 let weak_shard = C::WeakShard::read_cfg(buf, shard_cfg)?;
68 Ok(Self::Weak(weak_shard))
69 }
70 _ => Err(commonware_codec::Error::Invalid(
71 "DistributionShard",
72 "invalid tag",
73 )),
74 }
75 }
76}
77
78impl<C: Scheme> PartialEq for DistributionShard<C> {
79 fn eq(&self, other: &Self) -> bool {
80 match (self, other) {
81 (Self::Strong(a), Self::Strong(b)) => a == b,
82 (Self::Weak(a), Self::Weak(b)) => a == b,
83 _ => false,
84 }
85 }
86}
87
88impl<C: Scheme> Eq for DistributionShard<C> {}
89
90#[cfg(feature = "arbitrary")]
91impl<C: Scheme> arbitrary::Arbitrary<'_> for DistributionShard<C>
92where
93 C::StrongShard: for<'a> arbitrary::Arbitrary<'a>,
94 C::WeakShard: for<'a> arbitrary::Arbitrary<'a>,
95{
96 fn arbitrary(u: &mut arbitrary::Unstructured<'_>) -> arbitrary::Result<Self> {
97 if u.arbitrary::<bool>()? {
98 Ok(Self::Strong(u.arbitrary()?))
99 } else {
100 Ok(Self::Weak(u.arbitrary()?))
101 }
102 }
103}
104
105pub struct Shard<C: Scheme, H: Hasher> {
108 pub(crate) commitment: Commitment,
110 pub(crate) index: u16,
112 pub(crate) inner: DistributionShard<C>,
114 _hasher: PhantomData<H>,
116}
117
118impl<C: Scheme, H: Hasher> Shard<C, H> {
119 pub const fn new(commitment: Commitment, index: u16, inner: DistributionShard<C>) -> Self {
120 Self {
121 commitment,
122 index,
123 inner,
124 _hasher: PhantomData,
125 }
126 }
127
128 pub const fn index(&self) -> u16 {
130 self.index
131 }
132
133 pub const fn commitment(&self) -> Commitment {
135 self.commitment
136 }
137
138 pub const fn is_strong(&self) -> bool {
140 matches!(self.inner, DistributionShard::Strong(_))
141 }
142
143 pub const fn is_weak(&self) -> bool {
145 matches!(self.inner, DistributionShard::Weak(_))
146 }
147
148 pub fn into_inner(self) -> DistributionShard<C> {
150 self.inner
151 }
152
153 pub fn verify_into_weak(self) -> Option<Self> {
158 let DistributionShard::Strong(shard) = self.inner else {
159 return None;
160 };
161
162 let weak_shard = C::weaken(
163 &self.commitment.config(),
164 &self.commitment.root(),
165 self.index,
166 shard,
167 )
168 .ok()
169 .map(|(_, _, weak_shard)| weak_shard)?;
170
171 Some(Self::new(
172 self.commitment,
173 self.index,
174 DistributionShard::Weak(weak_shard),
175 ))
176 }
177}
178
179impl<C: Scheme, H: Hasher> Clone for Shard<C, H> {
180 fn clone(&self) -> Self {
181 Self {
182 commitment: self.commitment,
183 index: self.index,
184 inner: self.inner.clone(),
185 _hasher: PhantomData,
186 }
187 }
188}
189
190impl<C: Scheme, H: Hasher> Deref for Shard<C, H> {
191 type Target = DistributionShard<C>;
192
193 fn deref(&self) -> &Self::Target {
194 &self.inner
195 }
196}
197
198impl<C: Scheme, H: Hasher> Committable for Shard<C, H> {
199 type Commitment = Commitment;
200
201 fn commitment(&self) -> Self::Commitment {
202 self.commitment
203 }
204}
205
206impl<C: Scheme, H: Hasher> Write for Shard<C, H> {
207 fn write(&self, buf: &mut impl bytes::BufMut) {
208 self.commitment.write(buf);
209 self.index.write(buf);
210 self.inner.write(buf);
211 }
212}
213
214impl<C: Scheme, H: Hasher> EncodeSize for Shard<C, H> {
215 fn encode_size(&self) -> usize {
216 self.commitment.encode_size() + self.index.encode_size() + self.inner.encode_size()
217 }
218}
219
220impl<C: Scheme, H: Hasher> Read for Shard<C, H> {
221 type Cfg = commonware_coding::CodecConfig;
222
223 fn read_cfg(
224 buf: &mut impl bytes::Buf,
225 cfg: &Self::Cfg,
226 ) -> Result<Self, commonware_codec::Error> {
227 let commitment = Commitment::read(buf)?;
228 let index = u16::read(buf)?;
229 let inner = DistributionShard::read_cfg(buf, cfg)?;
230
231 Ok(Self {
232 commitment,
233 index,
234 inner,
235 _hasher: PhantomData,
236 })
237 }
238}
239
240impl<C: Scheme, H: Hasher> PartialEq for Shard<C, H> {
241 fn eq(&self, other: &Self) -> bool {
242 self.commitment == other.commitment
243 && self.index == other.index
244 && self.inner == other.inner
245 }
246}
247
248impl<C: Scheme, H: Hasher> Eq for Shard<C, H> {}
249
250#[cfg(feature = "arbitrary")]
251impl<C: Scheme, H: Hasher> arbitrary::Arbitrary<'_> for Shard<C, H>
252where
253 DistributionShard<C>: for<'a> arbitrary::Arbitrary<'a>,
254{
255 fn arbitrary(u: &mut arbitrary::Unstructured<'_>) -> arbitrary::Result<Self> {
256 Ok(Self {
257 commitment: u.arbitrary()?,
258 index: u.arbitrary()?,
259 inner: u.arbitrary()?,
260 _hasher: PhantomData,
261 })
262 }
263}
264
265#[derive(Debug)]
267pub struct CodedBlock<B: Block, C: Scheme, H: Hasher> {
268 inner: B,
270 config: CodingConfig,
272 commitment: C::Commitment,
274 shards: Option<Vec<C::StrongShard>>,
280 _hasher: PhantomData<H>,
282}
283
284impl<B: Block, C: Scheme, H: Hasher> CodedBlock<B, C, H> {
285 fn encode(
287 inner: &B,
288 config: CodingConfig,
289 strategy: &impl Strategy,
290 ) -> (C::Commitment, Vec<C::StrongShard>) {
291 let mut buf = Vec::with_capacity(inner.encode_size() + config.encode_size());
292 inner.write(&mut buf);
293 config.write(&mut buf);
294
295 C::encode(&config, buf.as_slice(), strategy).expect("must encode block successfully")
296 }
297
298 pub fn new(inner: B, config: CodingConfig, strategy: &impl Strategy) -> Self {
300 let (commitment, shards) = Self::encode(&inner, config, strategy);
301 Self {
302 inner,
303 config,
304 commitment,
305 shards: Some(shards),
306 _hasher: PhantomData,
307 }
308 }
309
310 pub fn new_trusted(inner: B, commitment: Commitment) -> Self {
312 Self {
313 inner,
314 config: commitment.config(),
315 commitment: commitment.root(),
316 shards: None,
317 _hasher: PhantomData,
318 }
319 }
320
321 pub const fn config(&self) -> CodingConfig {
323 self.config
324 }
325
326 pub fn shards(&mut self, strategy: &impl Strategy) -> &[C::StrongShard] {
330 match self.shards {
331 Some(ref shards) => shards,
332 None => {
333 let (commitment, shards) = Self::encode(&self.inner, self.config, strategy);
334
335 assert_eq!(
336 commitment, self.commitment,
337 "coded block constructed with trusted commitment does not match commitment"
338 );
339
340 self.shards = Some(shards);
341 self.shards.as_ref().unwrap()
342 }
343 }
344 }
345
346 pub fn shard(&self, index: u16) -> Option<Shard<C, H>>
348 where
349 B: CertifiableBlock,
350 {
351 Some(Shard::new(
352 self.commitment(),
353 index,
354 DistributionShard::Strong(self.shards.as_ref()?.get(usize::from(index))?.clone()),
355 ))
356 }
357
358 pub const fn inner(&self) -> &B {
360 &self.inner
361 }
362
363 pub fn into_inner(self) -> B {
365 self.inner
366 }
367}
368
369impl<B: CertifiableBlock, C: Scheme, H: Hasher> From<CodedBlock<B, C, H>>
370 for StoredCodedBlock<B, C, H>
371{
372 fn from(block: CodedBlock<B, C, H>) -> Self {
373 Self::new(block)
374 }
375}
376
377impl<B: Block + Clone, C: Scheme, H: Hasher> Clone for CodedBlock<B, C, H> {
378 fn clone(&self) -> Self {
379 Self {
380 inner: self.inner.clone(),
381 config: self.config,
382 commitment: self.commitment,
383 shards: self.shards.clone(),
384 _hasher: PhantomData,
385 }
386 }
387}
388
389impl<B: CertifiableBlock, C: Scheme, H: Hasher> Committable for CodedBlock<B, C, H> {
390 type Commitment = Commitment;
391
392 fn commitment(&self) -> Self::Commitment {
393 Commitment::from((
394 self.digest(),
395 self.commitment,
396 hash_context::<H, _>(&self.inner.context()),
397 self.config,
398 ))
399 }
400}
401
402impl<B: Block, C: Scheme, H: Hasher> Digestible for CodedBlock<B, C, H> {
403 type Digest = B::Digest;
404
405 fn digest(&self) -> Self::Digest {
406 self.inner.digest()
407 }
408}
409
410impl<B: Block, C: Scheme, H: Hasher> Write for CodedBlock<B, C, H> {
411 fn write(&self, buf: &mut impl bytes::BufMut) {
412 self.inner.write(buf);
413 self.config.write(buf);
414 }
415}
416
417impl<B: Block, C: Scheme, H: Hasher> EncodeSize for CodedBlock<B, C, H> {
418 fn encode_size(&self) -> usize {
419 self.inner.encode_size() + self.config.encode_size()
420 }
421}
422
423impl<B: Block, C: Scheme, H: Hasher> Read for CodedBlock<B, C, H> {
424 type Cfg = <B as Read>::Cfg;
425
426 fn read_cfg(
427 buf: &mut impl bytes::Buf,
428 block_cfg: &Self::Cfg,
429 ) -> Result<Self, commonware_codec::Error> {
430 let inner = B::read_cfg(buf, block_cfg)?;
431 let config = CodingConfig::read(buf)?;
432
433 let mut buf = Vec::with_capacity(inner.encode_size() + config.encode_size());
434 inner.write(&mut buf);
435 config.write(&mut buf);
436 let (commitment, shards) =
437 C::encode(&config, buf.as_slice(), &Sequential).map_err(|_| {
438 commonware_codec::Error::Invalid("CodedBlock", "Failed to re-commit to block")
439 })?;
440
441 Ok(Self {
442 inner,
443 config,
444 commitment,
445 shards: Some(shards),
446 _hasher: PhantomData,
447 })
448 }
449}
450
451impl<B: CertifiableBlock, C: Scheme, H: Hasher> Block for CodedBlock<B, C, H> {
452 fn parent(&self) -> Self::Digest {
453 self.inner.parent()
454 }
455}
456
457impl<B: Block, C: Scheme, H: Hasher> Heightable for CodedBlock<B, C, H> {
458 fn height(&self) -> Height {
459 self.inner.height()
460 }
461}
462
463impl<B: CertifiableBlock, C: Scheme, H: Hasher> CertifiableBlock for CodedBlock<B, C, H> {
464 type Context = B::Context;
465
466 fn context(&self) -> Self::Context {
467 self.inner.context()
468 }
469}
470
471pub fn hash_context<H: Hasher, C: EncodeSize + Write>(context: &C) -> H::Digest {
473 let mut buf = Vec::with_capacity(context.encode_size());
474 context.write(&mut buf);
475 H::hash(&buf)
476}
477
478impl<B: Block + PartialEq, C: Scheme, H: Hasher> PartialEq for CodedBlock<B, C, H> {
479 fn eq(&self, other: &Self) -> bool {
480 self.inner == other.inner
481 && self.config == other.config
482 && self.commitment == other.commitment
483 && self.shards == other.shards
484 }
485}
486
487impl<B: Block + Eq, C: Scheme, H: Hasher> Eq for CodedBlock<B, C, H> {}
488
489pub struct StoredCodedBlock<B: Block, C: Scheme, H: Hasher> {
502 inner: B,
503 commitment: Commitment,
504 _scheme: PhantomData<(C, H)>,
505}
506
507impl<B: CertifiableBlock, C: Scheme, H: Hasher> StoredCodedBlock<B, C, H> {
508 pub fn new(block: CodedBlock<B, C, H>) -> Self {
513 Self {
514 commitment: block.commitment(),
515 inner: block.inner,
516 _scheme: PhantomData,
517 }
518 }
519
520 pub fn into_coded_block(self) -> CodedBlock<B, C, H> {
525 CodedBlock::new_trusted(self.inner, self.commitment)
526 }
527
528 pub const fn inner(&self) -> &B {
530 &self.inner
531 }
532}
533
534impl<B: Block, C: Scheme, H: Hasher> From<StoredCodedBlock<B, C, H>> for CodedBlock<B, C, H> {
536 fn from(stored: StoredCodedBlock<B, C, H>) -> Self {
537 Self::new_trusted(stored.inner, stored.commitment)
538 }
539}
540
541impl<B: Block + Clone, C: Scheme, H: Hasher> Clone for StoredCodedBlock<B, C, H> {
542 fn clone(&self) -> Self {
543 Self {
544 commitment: self.commitment,
545 inner: self.inner.clone(),
546 _scheme: PhantomData,
547 }
548 }
549}
550
551impl<B: Block, C: Scheme, H: Hasher> Committable for StoredCodedBlock<B, C, H> {
552 type Commitment = Commitment;
553
554 fn commitment(&self) -> Self::Commitment {
555 self.commitment
556 }
557}
558
559impl<B: Block, C: Scheme, H: Hasher> Digestible for StoredCodedBlock<B, C, H> {
560 type Digest = B::Digest;
561
562 fn digest(&self) -> Self::Digest {
563 self.inner.digest()
564 }
565}
566
567impl<B: Block, C: Scheme, H: Hasher> Write for StoredCodedBlock<B, C, H> {
568 fn write(&self, buf: &mut impl bytes::BufMut) {
569 self.inner.write(buf);
570 self.commitment.write(buf);
571 }
572}
573
574impl<B: Block, C: Scheme, H: Hasher> EncodeSize for StoredCodedBlock<B, C, H> {
575 fn encode_size(&self) -> usize {
576 self.inner.encode_size() + self.commitment.encode_size()
577 }
578}
579
580impl<B: Block, C: Scheme, H: Hasher> Read for StoredCodedBlock<B, C, H> {
581 type Cfg = B::Cfg;
583
584 fn read_cfg(
585 buf: &mut impl bytes::Buf,
586 block_cfg: &Self::Cfg,
587 ) -> Result<Self, commonware_codec::Error> {
588 let inner = B::read_cfg(buf, block_cfg)?;
589 let commitment = Commitment::read(buf)?;
590
591 if inner.digest() != commitment.block::<B::Digest>() {
593 return Err(commonware_codec::Error::Invalid(
594 "StoredCodedBlock",
595 "storage corruption: block digest mismatch",
596 ));
597 }
598
599 Ok(Self {
600 commitment,
601 inner,
602 _scheme: PhantomData,
603 })
604 }
605}
606
607impl<B: Block, C: Scheme, H: Hasher> Block for StoredCodedBlock<B, C, H> {
608 fn parent(&self) -> Self::Digest {
609 self.inner.parent()
610 }
611}
612
613impl<B: CertifiableBlock, C: Scheme, H: Hasher> CertifiableBlock for StoredCodedBlock<B, C, H> {
614 type Context = B::Context;
615
616 fn context(&self) -> Self::Context {
617 self.inner.context()
618 }
619}
620
621impl<B: Block, C: Scheme, H: Hasher> Heightable for StoredCodedBlock<B, C, H> {
622 fn height(&self) -> Height {
623 self.inner.height()
624 }
625}
626
627impl<B: Block + PartialEq, C: Scheme, H: Hasher> PartialEq for StoredCodedBlock<B, C, H> {
628 fn eq(&self, other: &Self) -> bool {
629 self.commitment == other.commitment && self.inner == other.inner
630 }
631}
632
633impl<B: Block + Eq, C: Scheme, H: Hasher> Eq for StoredCodedBlock<B, C, H> {}
634
635pub fn coding_config_for_participants(n_participants: u16) -> CodingConfig {
639 let max_faults = N3f1::max_faults(n_participants);
640 assert!(
641 max_faults >= 1,
642 "Need at least 4 participants to maintain fault tolerance"
643 );
644 let max_faults = u16::try_from(max_faults).expect("max_faults must fit in u16");
645 let minimum_shards = NZU16!(max_faults + 1);
646 CodingConfig {
647 minimum_shards,
648 extra_shards: NZU16!(n_participants - minimum_shards.get()),
649 }
650}
651
652#[cfg(test)]
653mod test {
654 use super::*;
655 use crate::{marshal::mocks::block::Block as MockBlock, Block as _};
656 use commonware_codec::{Decode, Encode};
657 use commonware_coding::{CodecConfig, ReedSolomon};
658 use commonware_cryptography::{sha256::Digest as Sha256Digest, Digest, Sha256};
659
660 const MAX_SHARD_SIZE: CodecConfig = CodecConfig {
661 maximum_shard_size: 1024 * 1024, };
663
664 type H = Sha256;
665 type RS = ReedSolomon<H>;
666 type RShard = Shard<RS, H>;
667 type Block = MockBlock<<H as Hasher>::Digest, ()>;
668
669 #[test]
670 fn test_distribution_shard_codec_roundtrip() {
671 const MOCK_BLOCK_DATA: &[u8] = b"commonware shape rotator club";
672 const CONFIG: CodingConfig = CodingConfig {
673 minimum_shards: NZU16!(1),
674 extra_shards: NZU16!(2),
675 };
676
677 let (_, shards) = RS::encode(&CONFIG, MOCK_BLOCK_DATA, &Sequential).unwrap();
678 let raw_shard = shards.first().cloned().unwrap();
679
680 let strong_shard = DistributionShard::<RS>::Strong(raw_shard.clone());
681 let encoded = strong_shard.encode();
682 let decoded =
683 DistributionShard::<RS>::decode_cfg(&mut encoded.as_ref(), &MAX_SHARD_SIZE).unwrap();
684 assert!(strong_shard == decoded);
685
686 let weak_shard = DistributionShard::<RS>::Weak(raw_shard);
687 let encoded = weak_shard.encode();
688 let decoded =
689 DistributionShard::<RS>::decode_cfg(&mut encoded.as_ref(), &MAX_SHARD_SIZE).unwrap();
690 assert!(weak_shard == decoded);
691 }
692
693 #[test]
694 fn test_distribution_shard_decode_truncated_returns_error() {
695 let decode = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
696 let mut buf = &[][..];
697 DistributionShard::<RS>::decode_cfg(&mut buf, &MAX_SHARD_SIZE)
698 }));
699 assert!(decode.is_ok(), "decode must not panic on truncated input");
700 assert!(decode.unwrap().is_err());
701 }
702
703 #[test]
704 fn test_coding_config_for_participants_valid_for_minimum_set() {
705 let config = coding_config_for_participants(4);
706 assert_eq!(config.minimum_shards.get(), 2);
707 assert_eq!(config.extra_shards.get(), 2);
708 }
709
710 #[test]
711 #[should_panic(expected = "Need at least 4 participants to maintain fault tolerance")]
712 fn test_coding_config_for_participants_panics_for_small_sets() {
713 let _ = coding_config_for_participants(3);
714 }
715
716 #[test]
717 fn test_shard_codec_roundtrip() {
718 const MOCK_BLOCK_DATA: &[u8] = b"deadc0de";
719 const CONFIG: CodingConfig = CodingConfig {
720 minimum_shards: NZU16!(1),
721 extra_shards: NZU16!(2),
722 };
723
724 let (commitment, shards) = RS::encode(&CONFIG, MOCK_BLOCK_DATA, &Sequential).unwrap();
725 let raw_shard = shards.first().cloned().unwrap();
726
727 let commitment =
728 Commitment::from((Sha256Digest::EMPTY, commitment, Sha256Digest::EMPTY, CONFIG));
729 let shard = RShard::new(commitment, 0, DistributionShard::Strong(raw_shard.clone()));
730 let encoded = shard.encode();
731 let decoded = RShard::decode_cfg(&mut encoded.as_ref(), &MAX_SHARD_SIZE).unwrap();
732 assert!(shard == decoded);
733
734 let shard = RShard::new(commitment, 0, DistributionShard::Weak(raw_shard));
735 let encoded = shard.encode();
736 let decoded = RShard::decode_cfg(&mut encoded.as_ref(), &MAX_SHARD_SIZE).unwrap();
737 assert!(shard == decoded);
738 }
739
740 #[test]
741 fn test_coded_block_codec_roundtrip() {
742 const CONFIG: CodingConfig = CodingConfig {
743 minimum_shards: NZU16!(1),
744 extra_shards: NZU16!(2),
745 };
746
747 let block = Block::new::<Sha256>((), Sha256::hash(b"parent"), Height::new(42), 1_234_567);
748 let coded_block = CodedBlock::<Block, RS, H>::new(block, CONFIG, &Sequential);
749
750 let encoded = coded_block.encode();
751 let decoded = CodedBlock::<Block, RS, H>::decode_cfg(encoded, &()).unwrap();
752
753 assert!(coded_block == decoded);
754 }
755
756 #[test]
757 fn test_stored_coded_block_codec_roundtrip() {
758 const CONFIG: CodingConfig = CodingConfig {
759 minimum_shards: NZU16!(1),
760 extra_shards: NZU16!(2),
761 };
762
763 let block = Block::new::<Sha256>((), Sha256::hash(b"parent"), Height::new(42), 1_234_567);
764 let coded_block = CodedBlock::<Block, RS, H>::new(block, CONFIG, &Sequential);
765 let stored = StoredCodedBlock::<Block, RS, H>::new(coded_block.clone());
766
767 assert_eq!(stored.commitment(), coded_block.commitment());
768 assert_eq!(stored.digest(), coded_block.digest());
769 assert_eq!(stored.height(), coded_block.height());
770 assert_eq!(stored.parent(), coded_block.parent());
771
772 let encoded = stored.encode();
773 let decoded = StoredCodedBlock::<Block, RS, H>::decode_cfg(encoded, &()).unwrap();
774
775 assert!(stored == decoded);
776 assert_eq!(decoded.commitment(), coded_block.commitment());
777 assert_eq!(decoded.digest(), coded_block.digest());
778 }
779
780 #[test]
781 fn test_stored_coded_block_into_coded_block() {
782 const CONFIG: CodingConfig = CodingConfig {
783 minimum_shards: NZU16!(1),
784 extra_shards: NZU16!(2),
785 };
786
787 let block = Block::new::<Sha256>((), Sha256::hash(b"parent"), Height::new(42), 1_234_567);
788 let coded_block = CodedBlock::<Block, RS, H>::new(block, CONFIG, &Sequential);
789 let original_commitment = coded_block.commitment();
790 let original_digest = coded_block.digest();
791
792 let stored = StoredCodedBlock::<Block, RS, H>::new(coded_block);
793 let encoded = stored.encode();
794 let decoded = StoredCodedBlock::<Block, RS, H>::decode_cfg(encoded, &()).unwrap();
795 let restored = decoded.into_coded_block();
796
797 assert_eq!(restored.commitment(), original_commitment);
798 assert_eq!(restored.digest(), original_digest);
799 }
800
801 #[test]
802 fn test_stored_coded_block_corruption_detection() {
803 const CONFIG: CodingConfig = CodingConfig {
804 minimum_shards: NZU16!(1),
805 extra_shards: NZU16!(2),
806 };
807
808 let block = Block::new::<Sha256>((), Sha256::hash(b"parent"), Height::new(42), 1_234_567);
809 let coded_block = CodedBlock::<Block, RS, H>::new(block, CONFIG, &Sequential);
810 let stored = StoredCodedBlock::<Block, RS, H>::new(coded_block);
811
812 let mut encoded = stored.encode().to_vec();
813
814 let block_size = stored.inner().encode_size();
816 encoded[block_size] ^= 0xFF;
817
818 let result = StoredCodedBlock::<Block, RS, H>::decode_cfg(&mut encoded.as_slice(), &());
820 assert!(result.is_err());
821 }
822
823 #[cfg(feature = "arbitrary")]
824 mod conformance {
825 use super::*;
826 use commonware_codec::conformance::CodecConformance;
827
828 commonware_conformance::conformance_tests! {
829 CodecConformance<DistributionShard<ReedSolomon<Sha256>>>,
830 CodecConformance<Shard<ReedSolomon<Sha256>, Sha256>>,
831 }
832 }
833}