1use std::iter;
4#[cfg(feature = "uniffi")]
5use std::sync::Arc;
6
7use serde::{Deserialize, Serialize};
8
9mod commitment;
10mod msg_pay_for_blobs;
11
12use crate::consts::appconsts;
13use crate::consts::appconsts::AppVersion;
14#[cfg(feature = "uniffi")]
15use crate::error::UniffiResult;
16use crate::nmt::Namespace;
17use crate::state::{AccAddress, AddressTrait};
18use crate::{bail_validation, Error, Result, Share};
19
20pub use self::commitment::Commitment;
21pub use self::msg_pay_for_blobs::MsgPayForBlobs;
22pub use celestia_proto::celestia::blob::v1::MsgPayForBlobs as RawMsgPayForBlobs;
23pub use celestia_proto::proto::blob::v1::BlobProto as RawBlob;
24pub use celestia_proto::proto::blob::v1::BlobTx as RawBlobTx;
25#[cfg(all(feature = "wasm-bindgen", target_arch = "wasm32"))]
26use wasm_bindgen::prelude::*;
27
28#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
33#[serde(try_from = "custom_serde::SerdeBlob", into = "custom_serde::SerdeBlob")]
34#[cfg_attr(
35 all(feature = "wasm-bindgen", target_arch = "wasm32"),
36 wasm_bindgen(getter_with_clone, inspectable)
37)]
38#[cfg_attr(feature = "uniffi", derive(uniffi::Object))]
39pub struct Blob {
40 pub namespace: Namespace,
42 pub data: Vec<u8>,
44 pub share_version: u8,
46 pub commitment: Commitment,
48 pub index: Option<u64>,
50 pub signer: Option<AccAddress>,
54}
55
56#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
58#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
59#[cfg_attr(
60 all(feature = "wasm-bindgen", target_arch = "wasm32"),
61 wasm_bindgen(getter_with_clone, inspectable)
62)]
63pub struct BlobParams {
64 pub gas_per_blob_byte: u32,
66 pub gov_max_square_size: u64,
68}
69
70impl Blob {
71 pub fn new(
112 namespace: Namespace,
113 data: Vec<u8>,
114 signer: Option<AccAddress>,
115 app_version: AppVersion,
116 ) -> Result<Blob> {
117 let share_version = if signer.is_none() {
118 appconsts::SHARE_VERSION_ZERO
119 } else {
120 appconsts::SHARE_VERSION_ONE
121 };
122
123 let commitment = Commitment::from_blob(
124 namespace,
125 &data[..],
126 share_version,
127 signer.as_ref(),
128 app_version,
129 )?;
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, app_version: AppVersion) -> 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 = Commitment::from_blob(
148 namespace,
149 &raw.data[..],
150 share_version,
151 signer.as_ref(),
152 app_version,
153 )?;
154
155 Ok(Blob {
156 namespace,
157 data: raw.data,
158 share_version,
159 commitment,
160 index: None,
161 signer,
162 })
163 }
164
165 pub fn validate(&self, app_version: AppVersion) -> Result<()> {
190 let computed_commitment = Commitment::from_blob(
191 self.namespace,
192 &self.data,
193 self.share_version,
194 self.signer.as_ref(),
195 app_version,
196 )?;
197
198 if self.commitment != computed_commitment {
199 bail_validation!("blob commitment != localy computed commitment")
200 }
201
202 Ok(())
203 }
204
205 pub fn validate_with_commitment(
235 &self,
236 commitment: &Commitment,
237 app_version: AppVersion,
238 ) -> Result<()> {
239 self.validate(app_version)?;
240
241 if self.commitment != *commitment {
242 bail_validation!("blob commitment != commitment");
243 }
244
245 Ok(())
246 }
247
248 pub fn to_shares(&self) -> Result<Vec<Share>> {
274 commitment::split_blob_to_shares(
275 self.namespace,
276 self.share_version,
277 &self.data,
278 self.signer.as_ref(),
279 )
280 }
281
282 pub fn reconstruct<'a, I>(shares: I, app_version: AppVersion) -> Result<Self>
307 where
308 I: IntoIterator<Item = &'a Share>,
309 {
310 let mut shares = shares.into_iter();
311 let first_share = shares.next().ok_or(Error::MissingShares)?;
312 let blob_len = first_share
313 .sequence_length()
314 .ok_or(Error::ExpectedShareWithSequenceStart)?;
315 let namespace = first_share.namespace();
316 if namespace.is_reserved() {
317 return Err(Error::UnexpectedReservedNamespace);
318 }
319 let share_version = first_share.info_byte().expect("non parity").version();
320 let signer = first_share.signer();
321
322 let shares_needed = shares_needed_for_blob(blob_len as usize, signer.is_some());
323 let mut data =
324 Vec::with_capacity(shares_needed * appconsts::CONTINUATION_SPARSE_SHARE_CONTENT_SIZE);
325 data.extend_from_slice(first_share.payload().expect("non parity"));
326
327 for _ in 1..shares_needed {
328 let share = shares.next().ok_or(Error::MissingShares)?;
329 if share.namespace() != namespace {
330 return Err(Error::BlobSharesMetadataMismatch(format!(
331 "expected namespace ({:?}) got ({:?})",
332 namespace,
333 share.namespace()
334 )));
335 }
336 let version = share.info_byte().expect("non parity").version();
337 if version != share_version {
338 return Err(Error::BlobSharesMetadataMismatch(format!(
339 "expected share version ({share_version}) got ({version})"
340 )));
341 }
342 if share.sequence_length().is_some() {
343 return Err(Error::UnexpectedSequenceStart);
344 }
345 data.extend_from_slice(share.payload().expect("non parity"));
346 }
347
348 data.truncate(blob_len as usize);
350
351 if share_version == appconsts::SHARE_VERSION_ZERO {
352 Self::new(namespace, data, None, app_version)
353 } else if share_version == appconsts::SHARE_VERSION_ONE {
354 let signer = signer.ok_or(Error::MissingSigner)?;
356 Self::new(namespace, data, Some(signer), app_version)
357 } else {
358 Err(Error::UnsupportedShareVersion(share_version))
359 }
360 }
361
362 pub fn reconstruct_all<'a, I>(shares: I, app_version: AppVersion) -> Result<Vec<Self>>
395 where
396 I: IntoIterator<Item = &'a Share>,
397 {
398 let mut shares = shares
399 .into_iter()
400 .filter(|shr| !shr.namespace().is_reserved());
401 let mut blobs = Vec::with_capacity(2);
402
403 loop {
404 let mut blob = {
405 let Some(start) = shares.find(|&shr| shr.sequence_length().is_some()) else {
407 break;
408 };
409 iter::once(start).chain(&mut shares)
410 };
411 blobs.push(Blob::reconstruct(&mut blob, app_version)?);
412 }
413
414 Ok(blobs)
415 }
416
417 pub fn shares_len(&self) -> usize {
434 let Some(without_first_share) = self
435 .data
436 .len()
437 .checked_sub(appconsts::FIRST_SPARSE_SHARE_CONTENT_SIZE)
438 else {
439 return 1;
440 };
441 1 + without_first_share.div_ceil(appconsts::CONTINUATION_SPARSE_SHARE_CONTENT_SIZE)
442 }
443}
444
445#[cfg(feature = "uniffi")]
446#[uniffi::export]
447impl Blob {
448 #[uniffi::constructor(name = "create")]
455 pub fn uniffi_new(
456 namespace: Arc<Namespace>,
457 data: Vec<u8>,
458 app_version: AppVersion,
459 ) -> UniffiResult<Self> {
460 let namespace = Arc::unwrap_or_clone(namespace);
461 Ok(Blob::new(namespace, data, None, app_version)?)
462 }
463
464 #[uniffi::constructor(name = "create_with_signer")]
471 pub fn uniffi_new_with_signer(
472 namespace: Arc<Namespace>,
473 data: Vec<u8>,
474 signer: AccAddress,
475 app_version: AppVersion,
476 ) -> UniffiResult<Blob> {
477 let namespace = Arc::unwrap_or_clone(namespace);
478 Ok(Blob::new(namespace, data, Some(signer), app_version)?)
479 }
480
481 #[uniffi::method(name = "namespace")]
483 pub fn get_namespace(&self) -> Namespace {
484 self.namespace
485 }
486
487 #[uniffi::method(name = "data")]
489 pub fn get_data(&self) -> Vec<u8> {
490 self.data.clone()
491 }
492
493 #[uniffi::method(name = "share_version")]
495 pub fn get_share_version(&self) -> u8 {
496 self.share_version
497 }
498
499 #[uniffi::method(name = "commitment")]
501 pub fn get_commitment(&self) -> Commitment {
502 self.commitment
503 }
504
505 #[uniffi::method(name = "index")]
507 pub fn get_index(&self) -> Option<u64> {
508 self.index
509 }
510
511 #[uniffi::method(name = "signer")]
515 pub fn get_signer(&self) -> Option<AccAddress> {
516 self.signer.clone()
517 }
518}
519
520impl From<Blob> for RawBlob {
521 fn from(value: Blob) -> RawBlob {
522 RawBlob {
523 namespace_id: value.namespace.id().to_vec(),
524 namespace_version: value.namespace.version() as u32,
525 data: value.data,
526 share_version: value.share_version as u32,
527 signer: value
528 .signer
529 .map(|addr| addr.as_bytes().to_vec())
530 .unwrap_or_default(),
531 }
532 }
533}
534
535#[cfg(all(feature = "wasm-bindgen", target_arch = "wasm32"))]
536#[wasm_bindgen]
537impl Blob {
538 #[wasm_bindgen(constructor)]
540 pub fn js_new(
541 namespace: &Namespace,
542 data: Vec<u8>,
543 app_version: &appconsts::JsAppVersion,
544 ) -> Result<Blob> {
545 Self::new(*namespace, data, None, (*app_version).into())
546 }
547
548 #[wasm_bindgen(js_name = clone)]
550 pub fn js_clone(&self) -> Blob {
551 self.clone()
552 }
553}
554
555fn shares_needed_for_blob(blob_len: usize, has_signer: bool) -> usize {
556 let mut first_share_content = appconsts::FIRST_SPARSE_SHARE_CONTENT_SIZE;
557 if has_signer {
558 first_share_content -= appconsts::SIGNER_SIZE;
559 }
560
561 let Some(without_first_share) = blob_len.checked_sub(first_share_content) else {
562 return 1;
563 };
564 1 + without_first_share.div_ceil(appconsts::CONTINUATION_SPARSE_SHARE_CONTENT_SIZE)
565}
566
567mod custom_serde {
568 use serde::de::Error as _;
569 use serde::ser::Error as _;
570 use serde::{Deserialize, Deserializer, Serialize, Serializer};
571 use tendermint_proto::serializers::bytes::base64string;
572
573 use crate::nmt::Namespace;
574 use crate::state::{AccAddress, AddressTrait};
575 use crate::{Error, Result};
576
577 use super::{commitment, Blob, Commitment};
578
579 mod index_serde {
580 use super::*;
581 pub fn serialize<S>(value: &Option<u64>, serializer: S) -> Result<S::Ok, S::Error>
583 where
584 S: Serializer,
585 {
586 let x = value
587 .map(i64::try_from)
588 .transpose()
589 .map_err(S::Error::custom)?
590 .unwrap_or(-1);
591 serializer.serialize_i64(x)
592 }
593
594 pub fn deserialize<'de, D>(deserializer: D) -> Result<Option<u64>, D::Error>
596 where
597 D: Deserializer<'de>,
598 {
599 i64::deserialize(deserializer).map(|val| if val >= 0 { Some(val as u64) } else { None })
600 }
601 }
602
603 mod signer_serde {
604 use super::*;
605
606 pub fn serialize<S>(value: &Option<AccAddress>, serializer: S) -> Result<S::Ok, S::Error>
608 where
609 S: Serializer,
610 {
611 if let Some(ref addr) = value.as_ref().map(|addr| addr.as_bytes()) {
612 base64string::serialize(addr, serializer)
613 } else {
614 serializer.serialize_none()
615 }
616 }
617
618 pub fn deserialize<'de, D>(deserializer: D) -> Result<Option<AccAddress>, D::Error>
620 where
621 D: Deserializer<'de>,
622 {
623 let bytes: Vec<u8> = base64string::deserialize(deserializer)?;
624 if bytes.is_empty() {
625 Ok(None)
626 } else {
627 let addr = AccAddress::new(bytes.try_into().map_err(D::Error::custom)?);
628 Ok(Some(addr))
629 }
630 }
631 }
632
633 #[derive(Serialize, Deserialize)]
635 pub(super) struct SerdeBlob {
636 namespace: Namespace,
637 #[serde(with = "base64string")]
638 data: Vec<u8>,
639 share_version: u8,
640 commitment: Commitment,
641 #[serde(default, with = "index_serde")]
643 index: Option<u64>,
644 #[serde(default, with = "signer_serde")]
645 signer: Option<AccAddress>,
646 }
647
648 impl From<Blob> for SerdeBlob {
649 fn from(value: Blob) -> Self {
650 Self {
651 namespace: value.namespace,
652 data: value.data,
653 share_version: value.share_version,
654 commitment: value.commitment,
655 index: value.index,
656 signer: value.signer,
657 }
658 }
659 }
660
661 impl TryFrom<SerdeBlob> for Blob {
662 type Error = Error;
663
664 fn try_from(value: SerdeBlob) -> Result<Self> {
665 commitment::validate_blob(value.share_version, value.signer.is_some(), None)?;
668
669 Ok(Blob {
670 namespace: value.namespace,
671 data: value.data,
672 share_version: value.share_version,
673 commitment: value.commitment,
674 index: value.index,
675 signer: value.signer,
676 })
677 }
678 }
679}
680
681#[cfg(test)]
682mod tests {
683 use super::*;
684 use crate::nmt::{NS_ID_SIZE, NS_SIZE};
685 use crate::test_utils::random_bytes;
686
687 #[cfg(target_arch = "wasm32")]
688 use wasm_bindgen_test::wasm_bindgen_test as test;
689
690 fn sample_blob() -> Blob {
691 serde_json::from_str(
692 r#"{
693 "namespace": "AAAAAAAAAAAAAAAAAAAAAAAAAAAADCBNOWAP3dM=",
694 "data": "8fIMqAB+kQo7+LLmHaDya8oH73hxem6lQWX1",
695 "share_version": 0,
696 "commitment": "D6YGsPWdxR8ju2OcOspnkgPG2abD30pSHxsFdiPqnVk=",
697 "index": -1
698 }"#,
699 )
700 .unwrap()
701 }
702
703 fn sample_blob_with_signer() -> Blob {
704 serde_json::from_str(
705 r#"{
706 "namespace": "AAAAAAAAAAAAAAAAAAAAAAAAALwwSWpxCuQb5+A=",
707 "data": "lQnnMKE=",
708 "share_version": 1,
709 "commitment": "dujykaNN+Ey7ET3QNdPG0g2uveriBvZusA3fLSOdMKU=",
710 "index": -1,
711 "signer": "Yjc3XldhbdYke5i8aSlggYxCCLE="
712 }"#,
713 )
714 .unwrap()
715 }
716
717 #[test]
718 fn create_new_blob_unsigned() {
719 use crate::consts::appconsts;
720
721 let ns = Namespace::new_v0(&[1, 2, 3, 4, 5]).expect("Invalid namespace");
722 let blob = Blob::new(
723 ns,
724 b"some data to store on blockchain".to_vec(),
725 None,
726 AppVersion::V2,
727 )
728 .expect("Failed to create blob");
729
730 let shares = blob.to_shares().expect("to_shares failed");
731 assert!(!shares.is_empty(), "expected at least one share");
732
733 let first = &shares[0];
734
735 assert!(
737 first.sequence_length().is_some(),
738 "first share must be a sequence start"
739 );
740
741 let version = first.info_byte().expect("non parity share").version();
743 assert_eq!(
744 version,
745 appconsts::SHARE_VERSION_ZERO,
746 "unexpected share version for unsigned blob"
747 );
748 assert_eq!(
749 first.signer(),
750 None,
751 "unsigned blob must not carry a signer"
752 );
753 }
754
755 #[test]
756 fn create_new_blob_signed() {
757 use crate::consts::appconsts;
758
759 let ns = Namespace::new_v0(&[1, 2, 3, 4, 5]).expect("Invalid namespace");
760 let signer: AccAddress = "celestia1377k5an3f94v6wyaceu0cf4nq6gk2jtpc46g7h"
761 .parse()
762 .expect("invalid signer");
763
764 let blob = Blob::new(
766 ns,
767 b"some data to store on blockchain".to_vec(),
768 Some(signer.clone()),
769 AppVersion::V5,
770 )
771 .expect("Failed to create signed blob");
772
773 let shares = blob.to_shares().expect("to_shares failed");
774 assert!(!shares.is_empty(), "expected at least one share");
775
776 let first = &shares[0];
777
778 assert!(
780 first.sequence_length().is_some(),
781 "first share must be a sequence start"
782 );
783
784 let version = first.info_byte().expect("non parity share").version();
786 assert_eq!(
787 version,
788 appconsts::SHARE_VERSION_ONE,
789 "unexpected share version for signed blob"
790 );
791 assert_eq!(
792 first.signer(),
793 Some(signer),
794 "first share must carry the expected signer"
795 );
796 }
797
798 #[test]
799 fn create_from_raw() {
800 let expected = sample_blob();
801 let raw = RawBlob::from(expected.clone());
802 let created = Blob::from_raw(raw, AppVersion::V2).unwrap();
803
804 assert_eq!(created, expected);
805 }
806
807 #[test]
808 fn create_from_raw_with_signer() {
809 let expected = sample_blob_with_signer();
810
811 let raw = RawBlob::from(expected.clone());
812
813 Blob::from_raw(raw.clone(), AppVersion::V2).unwrap_err();
814 let created = Blob::from_raw(raw, AppVersion::V3).unwrap();
815
816 assert_eq!(created, expected);
817 }
818
819 #[test]
820 fn validate_blob() {
821 sample_blob().validate(AppVersion::V2).unwrap();
822 }
823
824 #[test]
825 fn validate_blob_with_signer() {
826 sample_blob_with_signer()
827 .validate(AppVersion::V2)
828 .unwrap_err();
829 sample_blob_with_signer().validate(AppVersion::V3).unwrap();
830 }
831
832 #[test]
833 fn validate_blob_commitment_mismatch() {
834 let mut blob = sample_blob();
835 blob.commitment = Commitment::new([7; 32]);
836
837 blob.validate(AppVersion::V2).unwrap_err();
838 }
839
840 #[test]
841 fn deserialize_blob_with_missing_index() {
842 serde_json::from_str::<Blob>(
843 r#"{
844 "namespace": "AAAAAAAAAAAAAAAAAAAAAAAAAAAADCBNOWAP3dM=",
845 "data": "8fIMqAB+kQo7+LLmHaDya8oH73hxem6lQWX1",
846 "share_version": 0,
847 "commitment": "D6YGsPWdxR8ju2OcOspnkgPG2abD30pSHxsFdiPqnVk="
848 }"#,
849 )
850 .unwrap();
851 }
852
853 #[test]
854 fn deserialize_blob_with_share_version_and_signer_mismatch() {
855 serde_json::from_str::<Blob>(
857 r#"{
858 "namespace": "AAAAAAAAAAAAAAAAAAAAAAAAALwwSWpxCuQb5+A=",
859 "data": "lQnnMKE=",
860 "share_version": 0,
861 "commitment": "dujykaNN+Ey7ET3QNdPG0g2uveriBvZusA3fLSOdMKU=",
862 "signer": "Yjc3XldhbdYke5i8aSlggYxCCLE="
863 }"#,
864 )
865 .unwrap_err();
866
867 serde_json::from_str::<Blob>(
869 r#"{
870 "namespace": "AAAAAAAAAAAAAAAAAAAAAAAAALwwSWpxCuQb5+A=",
871 "data": "lQnnMKE=",
872 "share_version": 1,
873 "commitment": "dujykaNN+Ey7ET3QNdPG0g2uveriBvZusA3fLSOdMKU=",
874 }"#,
875 )
876 .unwrap_err();
877 }
878
879 #[test]
880 fn reconstruct() {
881 for _ in 0..10 {
882 let len = rand::random::<usize>() % (1024 * 1024) + 1;
883 let data = random_bytes(len);
884 let ns = Namespace::const_v0(rand::random());
885 let blob = Blob::new(ns, data, None, AppVersion::V2).unwrap();
886
887 let shares = blob.to_shares().unwrap();
888 assert_eq!(blob, Blob::reconstruct(&shares, AppVersion::V2).unwrap());
889 }
890 }
891
892 #[test]
893 fn reconstruct_with_signer() {
894 for _ in 0..10 {
895 let len = rand::random::<usize>() % (1024 * 1024) + 1;
896 let data = random_bytes(len);
897 let ns = Namespace::const_v0(rand::random());
898 let signer = rand::random::<[u8; 20]>().into();
899
900 let blob = Blob::new(ns, data, Some(signer), AppVersion::V3).unwrap();
901 let shares = blob.to_shares().unwrap();
902
903 Blob::reconstruct(&shares, AppVersion::V2).unwrap_err();
904 assert_eq!(blob, Blob::reconstruct(&shares, AppVersion::V3).unwrap());
905 }
906 }
907
908 #[test]
909 fn reconstruct_empty() {
910 assert!(matches!(
911 Blob::reconstruct(&Vec::<Share>::new(), AppVersion::V2),
912 Err(Error::MissingShares)
913 ));
914 }
915
916 #[test]
917 fn reconstruct_not_sequence_start() {
918 let len = rand::random::<usize>() % (1024 * 1024) + 1;
919 let data = random_bytes(len);
920 let ns = Namespace::const_v0(rand::random());
921 let mut shares = Blob::new(ns, data, None, AppVersion::V2)
922 .unwrap()
923 .to_shares()
924 .unwrap();
925
926 shares[0].as_mut()[NS_SIZE] &= 0b11111110;
928
929 assert!(matches!(
930 Blob::reconstruct(&shares, AppVersion::V2),
931 Err(Error::ExpectedShareWithSequenceStart)
932 ));
933 }
934
935 #[test]
936 fn reconstruct_reserved_namespace() {
937 for ns in (0..255).flat_map(|n| {
938 let mut v0 = [0; NS_ID_SIZE];
939 *v0.last_mut().unwrap() = n;
940 let mut v255 = [0xff; NS_ID_SIZE];
941 *v255.last_mut().unwrap() = n;
942
943 [Namespace::new_v0(&v0), Namespace::new_v255(&v255)]
944 }) {
945 let len = (rand::random::<usize>() % 1023 + 1) * 2;
946 let data = random_bytes(len);
947 let shares = Blob::new(ns.unwrap(), data, None, AppVersion::V2)
948 .unwrap()
949 .to_shares()
950 .unwrap();
951
952 assert!(matches!(
953 Blob::reconstruct(&shares, AppVersion::V2),
954 Err(Error::UnexpectedReservedNamespace)
955 ));
956 }
957 }
958
959 #[test]
960 fn reconstruct_not_enough_shares() {
961 let len = rand::random::<usize>() % 1024 * 1024 + 2048;
962 let data = random_bytes(len);
963 let ns = Namespace::const_v0(rand::random());
964 let shares = Blob::new(ns, data, None, AppVersion::V2)
965 .unwrap()
966 .to_shares()
967 .unwrap();
968
969 assert!(matches!(
970 Blob::reconstruct(&shares[..2], AppVersion::V2),
972 Err(Error::MissingShares)
973 ));
974 }
975
976 #[test]
977 fn reconstruct_inconsistent_share_version() {
978 let len = rand::random::<usize>() % (1024 * 1024) + 512;
979 let data = random_bytes(len);
980 let ns = Namespace::const_v0(rand::random());
981 let mut shares = Blob::new(ns, data, None, AppVersion::V2)
982 .unwrap()
983 .to_shares()
984 .unwrap();
985
986 shares[1].as_mut()[NS_SIZE] = 0b11111110;
988
989 assert!(matches!(
990 Blob::reconstruct(&shares, AppVersion::V2),
991 Err(Error::BlobSharesMetadataMismatch(..))
992 ));
993 }
994
995 #[test]
996 fn reconstruct_inconsistent_namespace() {
997 let len = rand::random::<usize>() % (1024 * 1024) + 512;
998 let data = random_bytes(len);
999 let ns = Namespace::const_v0(rand::random());
1000 let ns2 = Namespace::const_v0(rand::random());
1001 let mut shares = Blob::new(ns, data, None, AppVersion::V2)
1002 .unwrap()
1003 .to_shares()
1004 .unwrap();
1005
1006 shares[1].as_mut()[..NS_SIZE].copy_from_slice(ns2.as_bytes());
1008
1009 assert!(matches!(
1010 Blob::reconstruct(&shares, AppVersion::V2),
1011 Err(Error::BlobSharesMetadataMismatch(..))
1012 ));
1013 }
1014
1015 #[test]
1016 fn reconstruct_unexpected_sequence_start() {
1017 let len = rand::random::<usize>() % (1024 * 1024) + 512;
1018 let data = random_bytes(len);
1019 let ns = Namespace::const_v0(rand::random());
1020 let mut shares = Blob::new(ns, data, None, AppVersion::V2)
1021 .unwrap()
1022 .to_shares()
1023 .unwrap();
1024
1025 shares[1].as_mut()[NS_SIZE] |= 0b00000001;
1027
1028 assert!(matches!(
1029 Blob::reconstruct(&shares, AppVersion::V2),
1030 Err(Error::UnexpectedSequenceStart)
1031 ));
1032 }
1033
1034 #[test]
1035 fn reconstruct_all() {
1036 let blobs: Vec<_> = (0..rand::random::<usize>() % 16 + 3)
1037 .map(|_| {
1038 let len = rand::random::<usize>() % (1024 * 1024) + 512;
1039 let data = random_bytes(len);
1040 let ns = Namespace::const_v0(rand::random());
1041 Blob::new(ns, data, None, AppVersion::V2).unwrap()
1042 })
1043 .collect();
1044
1045 let shares: Vec<_> = blobs
1046 .iter()
1047 .flat_map(|blob| blob.to_shares().unwrap())
1048 .collect();
1049 let reconstructed = Blob::reconstruct_all(&shares, AppVersion::V2).unwrap();
1050
1051 assert_eq!(blobs, reconstructed);
1052 }
1053}