1use std::iter;
4#[cfg(feature = "uniffi")]
5use std::sync::Arc;
6
7use serde::{Deserialize, Serialize};
8#[cfg(all(feature = "wasm-bindgen", target_arch = "wasm32"))]
9use wasm_bindgen::prelude::*;
10
11mod commitment;
12mod msg_pay_for_blobs;
13
14use crate::consts::appconsts;
15#[cfg(feature = "uniffi")]
16use crate::error::UniffiResult;
17use crate::nmt::Namespace;
18use crate::state::{AccAddress, AddressTrait};
19use crate::{Error, Result, Share, bail_validation};
20
21pub use self::commitment::Commitment;
22pub use self::msg_pay_for_blobs::MsgPayForBlobs;
23pub use celestia_proto::celestia::blob::v1::MsgPayForBlobs as RawMsgPayForBlobs;
24pub use celestia_proto::proto::blob::v2::BlobProto as RawBlob;
25pub use celestia_proto::proto::blob::v2::BlobTx as RawBlobTx;
26
27#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
32#[serde(try_from = "custom_serde::SerdeBlob", into = "custom_serde::SerdeBlob")]
33#[cfg_attr(
34 all(feature = "wasm-bindgen", target_arch = "wasm32"),
35 wasm_bindgen(getter_with_clone, inspectable)
36)]
37#[cfg_attr(feature = "uniffi", derive(uniffi::Object))]
38pub struct Blob {
39 pub namespace: Namespace,
41 pub data: Vec<u8>,
43 pub share_version: u8,
45 pub commitment: Commitment,
47 pub index: Option<u64>,
49 pub signer: Option<AccAddress>,
53}
54
55#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
57#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
58#[cfg_attr(
59 all(feature = "wasm-bindgen", target_arch = "wasm32"),
60 wasm_bindgen(getter_with_clone, inspectable)
61)]
62pub struct BlobParams {
63 pub gas_per_blob_byte: u32,
65 pub gov_max_square_size: u64,
67}
68
69#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)]
71#[cfg_attr(
72 all(feature = "wasm-bindgen", target_arch = "wasm32"),
73 wasm_bindgen(getter_with_clone, inspectable)
74)]
75pub struct BlobsAtHeight {
76 pub height: u64,
78 pub blobs: Vec<Blob>,
80}
81
82impl Blob {
83 pub fn new(namespace: Namespace, data: Vec<u8>, signer: Option<AccAddress>) -> Result<Blob> {
122 let share_version = if signer.is_none() {
123 appconsts::SHARE_VERSION_ZERO
124 } else {
125 appconsts::SHARE_VERSION_ONE
126 };
127
128 let commitment =
129 Commitment::from_blob(namespace, &data[..], share_version, signer.as_ref())?;
130
131 Ok(Blob {
132 namespace,
133 data,
134 share_version,
135 commitment,
136 index: None,
137 signer,
138 })
139 }
140
141 pub fn from_raw(raw: RawBlob) -> Result<Blob> {
143 let namespace = Namespace::new(raw.namespace_version as u8, &raw.namespace_id)?;
144 let share_version =
145 u8::try_from(raw.share_version).map_err(|_| Error::UnsupportedShareVersion(u8::MAX))?;
146 let signer = raw.signer.try_into().map(AccAddress::new).ok();
147 let commitment =
148 Commitment::from_blob(namespace, &raw.data[..], share_version, signer.as_ref())?;
149
150 Ok(Blob {
151 namespace,
152 data: raw.data,
153 share_version,
154 commitment,
155 index: None,
156 signer,
157 })
158 }
159
160 pub fn validate(&self) -> Result<()> {
184 let computed_commitment = Commitment::from_blob(
185 self.namespace,
186 &self.data,
187 self.share_version,
188 self.signer.as_ref(),
189 )?;
190
191 if self.commitment != computed_commitment {
192 bail_validation!("blob commitment != localy computed commitment")
193 }
194
195 Ok(())
196 }
197
198 pub fn validate_with_commitment(&self, commitment: &Commitment) -> Result<()> {
227 self.validate()?;
228
229 if self.commitment != *commitment {
230 bail_validation!("blob commitment != commitment");
231 }
232
233 Ok(())
234 }
235
236 pub fn to_shares(&self) -> Result<Vec<Share>> {
261 commitment::split_blob_to_shares(
262 self.namespace,
263 self.share_version,
264 &self.data,
265 self.signer.as_ref(),
266 )
267 }
268
269 pub fn reconstruct<'a, I>(shares: I) -> Result<Self>
294 where
295 I: IntoIterator<Item = &'a Share>,
296 {
297 let mut shares = shares.into_iter();
298 let first_share = shares.next().ok_or(Error::MissingShares)?;
299 let blob_len = first_share
300 .sequence_length()
301 .ok_or(Error::ExpectedShareWithSequenceStart)?;
302 let namespace = first_share.namespace();
303 if namespace.is_reserved() {
304 return Err(Error::UnexpectedReservedNamespace);
305 }
306 let share_version = first_share.info_byte().expect("non parity").version();
307 let signer = first_share.signer();
308
309 let shares_needed = shares_needed_for_blob(blob_len as usize, signer.is_some());
310 let mut data =
311 Vec::with_capacity(shares_needed * appconsts::CONTINUATION_SPARSE_SHARE_CONTENT_SIZE);
312 data.extend_from_slice(first_share.payload().expect("non parity"));
313
314 for _ in 1..shares_needed {
315 let share = shares.next().ok_or(Error::MissingShares)?;
316 if share.namespace() != namespace {
317 return Err(Error::BlobSharesMetadataMismatch(format!(
318 "expected namespace ({:?}) got ({:?})",
319 namespace,
320 share.namespace()
321 )));
322 }
323 let version = share.info_byte().expect("non parity").version();
324 if version != share_version {
325 return Err(Error::BlobSharesMetadataMismatch(format!(
326 "expected share version ({share_version}) got ({version})"
327 )));
328 }
329 if share.sequence_length().is_some() {
330 return Err(Error::UnexpectedSequenceStart);
331 }
332 data.extend_from_slice(share.payload().expect("non parity"));
333 }
334
335 data.truncate(blob_len as usize);
337
338 if share_version == appconsts::SHARE_VERSION_ZERO {
339 Self::new(namespace, data, None)
340 } else if share_version == appconsts::SHARE_VERSION_ONE {
341 let signer = signer.ok_or(Error::MissingSigner)?;
343 Self::new(namespace, data, Some(signer))
344 } else {
345 Err(Error::UnsupportedShareVersion(share_version))
346 }
347 }
348
349 pub fn reconstruct_all<'a, I>(shares: I) -> Result<Vec<Self>>
382 where
383 I: IntoIterator<Item = &'a Share>,
384 {
385 let mut shares = shares
386 .into_iter()
387 .filter(|shr| !shr.namespace().is_reserved());
388 let mut blobs = Vec::with_capacity(2);
389
390 loop {
391 let mut blob = {
392 let Some(start) = shares.find(|&shr| shr.sequence_length().is_some()) else {
394 break;
395 };
396 iter::once(start).chain(&mut shares)
397 };
398 blobs.push(Blob::reconstruct(&mut blob)?);
399 }
400
401 Ok(blobs)
402 }
403
404 pub fn shares_len(&self) -> usize {
421 let Some(without_first_share) = self
422 .data
423 .len()
424 .checked_sub(appconsts::FIRST_SPARSE_SHARE_CONTENT_SIZE)
425 else {
426 return 1;
427 };
428 1 + without_first_share.div_ceil(appconsts::CONTINUATION_SPARSE_SHARE_CONTENT_SIZE)
429 }
430}
431
432#[cfg(feature = "uniffi")]
433#[uniffi::export]
434impl Blob {
435 #[uniffi::constructor(name = "create")]
442 pub fn uniffi_new(namespace: Arc<Namespace>, data: Vec<u8>) -> UniffiResult<Self> {
443 let namespace = Arc::unwrap_or_clone(namespace);
444 Ok(Blob::new(namespace, data, None)?)
445 }
446
447 #[uniffi::constructor(name = "create_with_signer")]
453 pub fn uniffi_new_with_signer(
454 namespace: Arc<Namespace>,
455 data: Vec<u8>,
456 signer: AccAddress,
457 ) -> UniffiResult<Blob> {
458 let namespace = Arc::unwrap_or_clone(namespace);
459 Ok(Blob::new(namespace, data, Some(signer))?)
460 }
461
462 #[uniffi::method(name = "namespace")]
464 pub fn get_namespace(&self) -> Namespace {
465 self.namespace
466 }
467
468 #[uniffi::method(name = "data")]
470 pub fn get_data(&self) -> Vec<u8> {
471 self.data.clone()
472 }
473
474 #[uniffi::method(name = "share_version")]
476 pub fn get_share_version(&self) -> u8 {
477 self.share_version
478 }
479
480 #[uniffi::method(name = "commitment")]
482 pub fn get_commitment(&self) -> Commitment {
483 self.commitment
484 }
485
486 #[uniffi::method(name = "index")]
488 pub fn get_index(&self) -> Option<u64> {
489 self.index
490 }
491
492 #[uniffi::method(name = "signer")]
496 pub fn get_signer(&self) -> Option<AccAddress> {
497 self.signer
498 }
499}
500
501#[cfg(all(feature = "wasm-bindgen", target_arch = "wasm32"))]
502#[wasm_bindgen]
503impl Blob {
504 #[wasm_bindgen(constructor)]
506 pub fn js_new(namespace: &Namespace, data: Vec<u8>) -> Result<Blob> {
507 Self::new(*namespace, data, None)
508 }
509
510 #[wasm_bindgen(js_name = clone)]
512 pub fn js_clone(&self) -> Blob {
513 self.clone()
514 }
515}
516
517impl From<Blob> for RawBlob {
518 fn from(value: Blob) -> RawBlob {
519 RawBlob {
520 namespace_id: value.namespace.id().to_vec(),
521 namespace_version: value.namespace.version() as u32,
522 data: value.data,
523 share_version: value.share_version as u32,
524 signer: value
525 .signer
526 .map(|addr| addr.as_bytes().to_vec())
527 .unwrap_or_default(),
528 }
529 }
530}
531
532fn shares_needed_for_blob(blob_len: usize, has_signer: bool) -> usize {
533 let mut first_share_content = appconsts::FIRST_SPARSE_SHARE_CONTENT_SIZE;
534 if has_signer {
535 first_share_content -= appconsts::SIGNER_SIZE;
536 }
537
538 let Some(without_first_share) = blob_len.checked_sub(first_share_content) else {
539 return 1;
540 };
541 1 + without_first_share.div_ceil(appconsts::CONTINUATION_SPARSE_SHARE_CONTENT_SIZE)
542}
543
544mod custom_serde {
545 use serde::de::Error as _;
546 use serde::ser::Error as _;
547 use serde::{Deserialize, Deserializer, Serialize, Serializer};
548 use tendermint_proto::serializers::bytes::base64string;
549
550 use crate::nmt::Namespace;
551 use crate::state::{AccAddress, AddressTrait};
552 use crate::{Error, Result};
553
554 use super::{Blob, Commitment, commitment};
555
556 mod index_serde {
557 use super::*;
558 pub fn serialize<S>(value: &Option<u64>, serializer: S) -> Result<S::Ok, S::Error>
560 where
561 S: Serializer,
562 {
563 let x = value
564 .map(i64::try_from)
565 .transpose()
566 .map_err(S::Error::custom)?
567 .unwrap_or(-1);
568 serializer.serialize_i64(x)
569 }
570
571 pub fn deserialize<'de, D>(deserializer: D) -> Result<Option<u64>, D::Error>
573 where
574 D: Deserializer<'de>,
575 {
576 i64::deserialize(deserializer).map(|val| if val >= 0 { Some(val as u64) } else { None })
577 }
578 }
579
580 mod signer_serde {
581 use super::*;
582
583 pub fn serialize<S>(value: &Option<AccAddress>, serializer: S) -> Result<S::Ok, S::Error>
585 where
586 S: Serializer,
587 {
588 if let Some(ref addr) = value.as_ref().map(|addr| addr.as_bytes()) {
589 base64string::serialize(addr, serializer)
590 } else {
591 serializer.serialize_none()
592 }
593 }
594
595 pub fn deserialize<'de, D>(deserializer: D) -> Result<Option<AccAddress>, D::Error>
597 where
598 D: Deserializer<'de>,
599 {
600 let bytes: Vec<u8> = base64string::deserialize(deserializer)?;
601 if bytes.is_empty() {
602 Ok(None)
603 } else {
604 let addr = AccAddress::new(bytes.try_into().map_err(D::Error::custom)?);
605 Ok(Some(addr))
606 }
607 }
608 }
609
610 #[derive(Serialize, Deserialize)]
612 pub(super) struct SerdeBlob {
613 namespace: Namespace,
614 #[serde(with = "base64string")]
615 data: Vec<u8>,
616 share_version: u8,
617 commitment: Commitment,
618 #[serde(default, with = "index_serde")]
620 index: Option<u64>,
621 #[serde(default, with = "signer_serde")]
622 signer: Option<AccAddress>,
623 }
624
625 impl From<Blob> for SerdeBlob {
626 fn from(value: Blob) -> Self {
627 Self {
628 namespace: value.namespace,
629 data: value.data,
630 share_version: value.share_version,
631 commitment: value.commitment,
632 index: value.index,
633 signer: value.signer,
634 }
635 }
636 }
637
638 impl TryFrom<SerdeBlob> for Blob {
639 type Error = Error;
640
641 fn try_from(value: SerdeBlob) -> Result<Self> {
642 commitment::validate_blob(value.share_version, value.signer.is_some())?;
643
644 Ok(Blob {
645 namespace: value.namespace,
646 data: value.data,
647 share_version: value.share_version,
648 commitment: value.commitment,
649 index: value.index,
650 signer: value.signer,
651 })
652 }
653 }
654}
655
656#[cfg(test)]
657mod tests {
658 use super::*;
659 use crate::nmt::{NS_ID_SIZE, NS_SIZE};
660 use crate::test_utils::random_bytes;
661
662 #[cfg(target_arch = "wasm32")]
663 use wasm_bindgen_test::wasm_bindgen_test as test;
664
665 fn sample_blob() -> Blob {
666 serde_json::from_str(
667 r#"{
668 "namespace": "AAAAAAAAAAAAAAAAAAAAAAAAAAAADCBNOWAP3dM=",
669 "data": "8fIMqAB+kQo7+LLmHaDya8oH73hxem6lQWX1",
670 "share_version": 0,
671 "commitment": "D6YGsPWdxR8ju2OcOspnkgPG2abD30pSHxsFdiPqnVk=",
672 "index": -1
673 }"#,
674 )
675 .unwrap()
676 }
677
678 fn sample_blob_with_signer() -> Blob {
679 serde_json::from_str(
680 r#"{
681 "namespace": "AAAAAAAAAAAAAAAAAAAAAAAAALwwSWpxCuQb5+A=",
682 "data": "lQnnMKE=",
683 "share_version": 1,
684 "commitment": "dujykaNN+Ey7ET3QNdPG0g2uveriBvZusA3fLSOdMKU=",
685 "index": -1,
686 "signer": "Yjc3XldhbdYke5i8aSlggYxCCLE="
687 }"#,
688 )
689 .unwrap()
690 }
691
692 #[test]
693 fn create_new_blob_unsigned() {
694 use crate::consts::appconsts;
695
696 let ns = Namespace::new_v0(&[1, 2, 3, 4, 5]).expect("Invalid namespace");
697 let blob = Blob::new(ns, b"some data to store on blockchain".to_vec(), None)
698 .expect("Failed to create blob");
699
700 let shares = blob.to_shares().expect("to_shares failed");
701 assert!(!shares.is_empty(), "expected at least one share");
702
703 let first = &shares[0];
704
705 assert!(
707 first.sequence_length().is_some(),
708 "first share must be a sequence start"
709 );
710
711 let version = first.info_byte().expect("non parity share").version();
713 assert_eq!(
714 version,
715 appconsts::SHARE_VERSION_ZERO,
716 "unexpected share version for unsigned blob"
717 );
718 assert_eq!(
719 first.signer(),
720 None,
721 "unsigned blob must not carry a signer"
722 );
723 }
724
725 #[test]
726 fn create_new_blob_signed() {
727 use crate::consts::appconsts;
728
729 let ns = Namespace::new_v0(&[1, 2, 3, 4, 5]).expect("Invalid namespace");
730 let signer: AccAddress = "celestia1377k5an3f94v6wyaceu0cf4nq6gk2jtpc46g7h"
731 .parse()
732 .expect("invalid signer");
733
734 let blob = Blob::new(
735 ns,
736 b"some data to store on blockchain".to_vec(),
737 Some(signer),
738 )
739 .expect("Failed to create signed blob");
740
741 let shares = blob.to_shares().expect("to_shares failed");
742 assert!(!shares.is_empty(), "expected at least one share");
743
744 let first = &shares[0];
745
746 assert!(
748 first.sequence_length().is_some(),
749 "first share must be a sequence start"
750 );
751
752 let version = first.info_byte().expect("non parity share").version();
754 assert_eq!(
755 version,
756 appconsts::SHARE_VERSION_ONE,
757 "unexpected share version for signed blob"
758 );
759 assert_eq!(
760 first.signer(),
761 Some(signer),
762 "first share must carry the expected signer"
763 );
764 }
765
766 #[test]
767 fn create_from_raw() {
768 let expected = sample_blob();
769 let raw = RawBlob::from(expected.clone());
770 let created = Blob::from_raw(raw).unwrap();
771
772 assert_eq!(created, expected);
773 }
774
775 #[test]
776 fn create_from_raw_with_signer() {
777 let expected = sample_blob_with_signer();
778 let raw = RawBlob::from(expected.clone());
779 let created = Blob::from_raw(raw).unwrap();
780
781 assert_eq!(created, expected);
782 }
783
784 #[test]
785 fn validate_blob() {
786 sample_blob().validate().unwrap();
787 }
788
789 #[test]
790 fn validate_blob_with_signer() {
791 sample_blob_with_signer().validate().unwrap();
792 }
793
794 #[test]
795 fn validate_blob_commitment_mismatch() {
796 let mut blob = sample_blob();
797 blob.commitment = Commitment::new([7; 32]);
798
799 blob.validate().unwrap_err();
800 }
801
802 #[test]
803 fn deserialize_blob_with_missing_index() {
804 serde_json::from_str::<Blob>(
805 r#"{
806 "namespace": "AAAAAAAAAAAAAAAAAAAAAAAAAAAADCBNOWAP3dM=",
807 "data": "8fIMqAB+kQo7+LLmHaDya8oH73hxem6lQWX1",
808 "share_version": 0,
809 "commitment": "D6YGsPWdxR8ju2OcOspnkgPG2abD30pSHxsFdiPqnVk="
810 }"#,
811 )
812 .unwrap();
813 }
814
815 #[test]
816 fn deserialize_blob_with_share_version_and_signer_mismatch() {
817 serde_json::from_str::<Blob>(
819 r#"{
820 "namespace": "AAAAAAAAAAAAAAAAAAAAAAAAALwwSWpxCuQb5+A=",
821 "data": "lQnnMKE=",
822 "share_version": 0,
823 "commitment": "dujykaNN+Ey7ET3QNdPG0g2uveriBvZusA3fLSOdMKU=",
824 "signer": "Yjc3XldhbdYke5i8aSlggYxCCLE="
825 }"#,
826 )
827 .unwrap_err();
828
829 serde_json::from_str::<Blob>(
831 r#"{
832 "namespace": "AAAAAAAAAAAAAAAAAAAAAAAAALwwSWpxCuQb5+A=",
833 "data": "lQnnMKE=",
834 "share_version": 1,
835 "commitment": "dujykaNN+Ey7ET3QNdPG0g2uveriBvZusA3fLSOdMKU=",
836 }"#,
837 )
838 .unwrap_err();
839 }
840
841 #[test]
842 fn reconstruct() {
843 for _ in 0..10 {
844 let len = rand::random::<usize>() % (1024 * 1024) + 1;
845 let data = random_bytes(len);
846 let ns = Namespace::const_v0(rand::random());
847 let blob = Blob::new(ns, data, None).unwrap();
848
849 let shares = blob.to_shares().unwrap();
850 assert_eq!(blob, Blob::reconstruct(&shares).unwrap());
851 }
852 }
853
854 #[test]
855 fn reconstruct_with_signer() {
856 for _ in 0..10 {
857 let len = rand::random::<usize>() % (1024 * 1024) + 1;
858 let data = random_bytes(len);
859 let ns = Namespace::const_v0(rand::random());
860 let signer = rand::random::<[u8; 20]>().into();
861
862 let blob = Blob::new(ns, data, Some(signer)).unwrap();
863 let shares = blob.to_shares().unwrap();
864
865 assert_eq!(blob, Blob::reconstruct(&shares).unwrap());
866 }
867 }
868
869 #[test]
870 fn reconstruct_empty() {
871 assert!(matches!(
872 Blob::reconstruct(&Vec::<Share>::new()),
873 Err(Error::MissingShares)
874 ));
875 }
876
877 #[test]
878 fn reconstruct_not_sequence_start() {
879 let len = rand::random::<usize>() % (1024 * 1024) + 1;
880 let data = random_bytes(len);
881 let ns = Namespace::const_v0(rand::random());
882 let mut shares = Blob::new(ns, data, None).unwrap().to_shares().unwrap();
883
884 shares[0].as_mut()[NS_SIZE] &= 0b11111110;
886
887 assert!(matches!(
888 Blob::reconstruct(&shares),
889 Err(Error::ExpectedShareWithSequenceStart)
890 ));
891 }
892
893 #[test]
894 fn reconstruct_reserved_namespace() {
895 for ns in (0..255).flat_map(|n| {
896 let mut v0 = [0; NS_ID_SIZE];
897 *v0.last_mut().unwrap() = n;
898 let mut v255 = [0xff; NS_ID_SIZE];
899 *v255.last_mut().unwrap() = n;
900
901 [Namespace::new_v0(&v0), Namespace::new_v255(&v255)]
902 }) {
903 let len = (rand::random::<usize>() % 1023 + 1) * 2;
904 let data = random_bytes(len);
905 let shares = Blob::new(ns.unwrap(), data, None)
906 .unwrap()
907 .to_shares()
908 .unwrap();
909
910 assert!(matches!(
911 Blob::reconstruct(&shares),
912 Err(Error::UnexpectedReservedNamespace)
913 ));
914 }
915 }
916
917 #[test]
918 fn reconstruct_not_enough_shares() {
919 let len = rand::random::<usize>() % 1024 * 1024 + 2048;
920 let data = random_bytes(len);
921 let ns = Namespace::const_v0(rand::random());
922 let shares = Blob::new(ns, data, None).unwrap().to_shares().unwrap();
923
924 assert!(matches!(
925 Blob::reconstruct(&shares[..2]),
927 Err(Error::MissingShares)
928 ));
929 }
930
931 #[test]
932 fn reconstruct_inconsistent_share_version() {
933 let len = rand::random::<usize>() % (1024 * 1024) + 512;
934 let data = random_bytes(len);
935 let ns = Namespace::const_v0(rand::random());
936 let mut shares = Blob::new(ns, data, None).unwrap().to_shares().unwrap();
937
938 shares[1].as_mut()[NS_SIZE] = 0b11111110;
940
941 assert!(matches!(
942 Blob::reconstruct(&shares),
943 Err(Error::BlobSharesMetadataMismatch(..))
944 ));
945 }
946
947 #[test]
948 fn reconstruct_inconsistent_namespace() {
949 let len = rand::random::<usize>() % (1024 * 1024) + 512;
950 let data = random_bytes(len);
951 let ns = Namespace::const_v0(rand::random());
952 let ns2 = Namespace::const_v0(rand::random());
953 let mut shares = Blob::new(ns, data, None).unwrap().to_shares().unwrap();
954
955 shares[1].as_mut()[..NS_SIZE].copy_from_slice(ns2.as_bytes());
957
958 assert!(matches!(
959 Blob::reconstruct(&shares),
960 Err(Error::BlobSharesMetadataMismatch(..))
961 ));
962 }
963
964 #[test]
965 fn reconstruct_unexpected_sequence_start() {
966 let len = rand::random::<usize>() % (1024 * 1024) + 512;
967 let data = random_bytes(len);
968 let ns = Namespace::const_v0(rand::random());
969 let mut shares = Blob::new(ns, data, None).unwrap().to_shares().unwrap();
970
971 shares[1].as_mut()[NS_SIZE] |= 0b00000001;
973
974 assert!(matches!(
975 Blob::reconstruct(&shares),
976 Err(Error::UnexpectedSequenceStart)
977 ));
978 }
979
980 #[test]
981 fn reconstruct_all() {
982 let blobs: Vec<_> = (0..rand::random::<usize>() % 16 + 3)
983 .map(|_| {
984 let len = rand::random::<usize>() % (1024 * 1024) + 512;
985 let data = random_bytes(len);
986 let ns = Namespace::const_v0(rand::random());
987 Blob::new(ns, data, None).unwrap()
988 })
989 .collect();
990
991 let shares: Vec<_> = blobs
992 .iter()
993 .flat_map(|blob| blob.to_shares().unwrap())
994 .collect();
995 let reconstructed = Blob::reconstruct_all(&shares).unwrap();
996
997 assert_eq!(blobs, reconstructed);
998 }
999}