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