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