use tape_core::encoding::{EncodingProfile, EncodingType};
use tape_core::erasure::{GROUP_SIZE, slice_root};
use tape_core::types::SpoolIndex;
use tape_crypto::merkle::MerkleLeafTree;
use tape_crypto::Hash;
use tape_slicer::{
ClayCoder, ReedSolomonCoder, Slicer, ErasureCoder, SLICE_TREE_HEIGHT,
build_blob_merkle_tree, BlobMerkleRoot, STRIPE_CAP,
};
use crate::error::UploadError;
use crate::transfer::uploader::SliceWithProof;
pub type SliceMerkleProof = [Hash; SLICE_TREE_HEIGHT];
pub struct BlobEncoder {
profile: EncodingProfile,
basic: Option<ReedSolomonCoder>,
clay: Option<Slicer<ClayCoder>>,
}
impl Default for BlobEncoder {
fn default() -> Self {
Self::new()
}
}
impl BlobEncoder {
pub fn new() -> Self {
Self::with_profile(EncodingProfile::clay_default())
}
pub fn with_profile(profile: EncodingProfile) -> Self {
let encoding_type = profile.encoding_type().unwrap_or(EncodingType::Unknown);
let mut encoder = Self {
profile,
basic: None,
clay: None,
};
match encoding_type {
EncodingType::Basic => {
let params = profile.rs_params();
encoder.basic = Some(ReedSolomonCoder::new(params.k() as usize, params.m() as usize));
}
EncodingType::Clay | EncodingType::Unknown => {
encoder.clay = Some(Slicer::with_profile(
ClayCoder::from_params(profile.clay_params()),
true, profile,
));
}
}
encoder
}
pub fn with_encoding(encoding_type: EncodingType) -> Self {
let profile = match encoding_type {
EncodingType::Basic => EncodingProfile::basic_default(),
EncodingType::Clay | EncodingType::Unknown => EncodingProfile::clay_default(),
};
Self::with_profile(profile)
}
pub fn encoding_type(&self) -> EncodingType {
self.profile.encoding_type().unwrap_or(EncodingType::Unknown)
}
pub fn profile(&self) -> EncodingProfile {
self.profile
}
pub fn stripe_size(&self) -> usize {
match &self.clay {
Some(slicer) => slicer.stripe_size(),
None => STRIPE_CAP,
}
}
fn encode_internal(&mut self, data: &[u8]) -> Result<Vec<Vec<u8>>, UploadError> {
match self.encoding_type() {
EncodingType::Basic => {
self.basic.as_mut().unwrap()
.encode(data)
.map_err(|e| UploadError::Encoding(e.to_string()))
}
EncodingType::Clay | EncodingType::Unknown => {
self.clay.as_mut().unwrap()
.encode(data)
.map_err(|e| UploadError::Encoding(e.to_string()))
}
}
}
pub fn encode(&mut self, data: Vec<u8>) -> Result<Vec<(SpoolIndex, Vec<u8>)>, UploadError> {
let chunks = self.encode_internal(&data)?;
let output: Vec<(SpoolIndex, Vec<u8>)> = chunks
.into_iter()
.enumerate()
.map(|(i, data)| (SpoolIndex::from(i as u64), data))
.collect();
Ok(output)
}
pub fn encode_to_vec(&mut self, data: Vec<u8>) -> Result<Vec<Vec<u8>>, UploadError> {
self.encode_internal(&data)
}
pub fn encode_with_root(
&mut self,
data: Vec<u8>,
) -> Result<(Vec<(SpoolIndex, Vec<u8>)>, BlobMerkleRoot), UploadError> {
let chunks = self.encode_internal(&data)?;
let tree = build_blob_merkle_tree(&chunks);
let root = tree.root();
let output: Vec<(SpoolIndex, Vec<u8>)> = chunks
.into_iter()
.enumerate()
.map(|(i, data)| (SpoolIndex::from(i as u64), data))
.collect();
Ok((output, root))
}
pub fn encode_to_vec_with_root(
&mut self,
data: Vec<u8>,
) -> Result<(Vec<Vec<u8>>, BlobMerkleRoot), UploadError> {
let chunks = self.encode_internal(&data)?;
let tree = build_blob_merkle_tree(&chunks);
let root = tree.root();
Ok((chunks, root))
}
pub fn encode_with_proofs(
&mut self,
data: Vec<u8>,
) -> Result<(Vec<SliceWithProof>, BlobMerkleRoot), UploadError> {
let chunks = self.encode_internal(&data)?;
let leaf_hashes: Vec<Hash> = chunks
.iter()
.map(|chunk| slice_root(chunk))
.collect::<Option<Vec<Hash>>>()
.ok_or_else(|| {
UploadError::Encoding("slice exceeds sub-leaf tree capacity".to_string())
})?;
let tree = MerkleLeafTree::new(&leaf_hashes, SLICE_TREE_HEIGHT)
.map_err(|error| UploadError::Encoding(format!("{error:?}")))?;
let root = tree.root();
let mut output = Vec::with_capacity(chunks.len());
for (idx, (chunk, leaf_hash)) in chunks
.into_iter()
.zip(leaf_hashes.iter().copied())
.enumerate()
{
let proof_arr = tree
.proof_at_n::<SLICE_TREE_HEIGHT>(idx)
.map_err(|error| UploadError::Encoding(format!("{error:?}")))?;
output.push(SliceWithProof::new(
SpoolIndex::from(idx as u64),
chunk,
leaf_hash,
proof_arr,
));
}
Ok((output, root))
}
pub fn encode_with_leaves(
&mut self,
data: Vec<u8>,
) -> Result<(Vec<SliceWithProof>, BlobMerkleRoot, [Hash; GROUP_SIZE]), UploadError> {
let (slices, root) = self.encode_with_proofs(data)?;
let mut leaves = [Hash::default(); GROUP_SIZE];
for s in &slices {
leaves[s.index.as_usize()] = s.leaf_hash;
}
Ok((slices, root, leaves))
}
}
#[cfg(test)]
mod tests {
use super::*;
use tape_core::erasure::GROUP_SIZE;
fn test_encoder() -> BlobEncoder {
BlobEncoder::with_encoding(EncodingType::Basic)
}
#[test]
fn test_encode_basic() {
let mut encoder = test_encoder();
let data = vec![0u8; 10_000];
let slices = encoder.encode(data).unwrap();
assert_eq!(slices.len(), GROUP_SIZE);
for (idx, (slice_idx, _)) in slices.iter().enumerate() {
assert_eq!(slice_idx.as_usize(), idx);
}
}
#[test]
fn test_encode_to_vec() {
let mut encoder = test_encoder();
let data = vec![42u8; 5_000];
let slices = encoder.encode_to_vec(data).unwrap();
assert_eq!(slices.len(), GROUP_SIZE);
}
#[test]
fn test_encode_with_root() {
let mut encoder = test_encoder();
let data = vec![0xAB; 20_000];
let (slices, root) = encoder.encode_with_root(data).unwrap();
assert_eq!(slices.len(), GROUP_SIZE);
assert_ne!(root.as_ref(), &[0u8; 32]);
}
#[test]
fn test_encode_same_data_same_root() {
let mut encoder = test_encoder();
let data1 = vec![0xCD; 15_000];
let data2 = data1.clone();
let (_, root1) = encoder.encode_with_root(data1).unwrap();
let (_, root2) = encoder.encode_with_root(data2).unwrap();
assert_eq!(root1, root2);
}
#[test]
fn test_encode_different_data_different_root() {
let mut encoder = test_encoder();
let data1 = vec![0xAA; 10_000];
let data2 = vec![0xBB; 10_000];
let (_, root1) = encoder.encode_with_root(data1).unwrap();
let (_, root2) = encoder.encode_with_root(data2).unwrap();
assert_ne!(root1, root2);
}
#[test]
fn test_encode_empty_blob() {
let mut encoder = test_encoder();
let data = vec![];
let slices = encoder.encode(data).unwrap();
assert_eq!(slices.len(), GROUP_SIZE);
}
#[test]
fn test_encode_with_proofs() {
use tape_crypto::merkle::verify_proof_hash;
let mut encoder = test_encoder();
let data = vec![0x42; 20_000];
let (slices_with_proofs, root) = encoder.encode_with_proofs(data).unwrap();
assert_eq!(slices_with_proofs.len(), GROUP_SIZE);
for slice in &slices_with_proofs {
let valid = verify_proof_hash(
slice_root(&slice.data).unwrap(),
&root,
&slice.merkle_proof,
slice.index.as_u64(),
SLICE_TREE_HEIGHT,
);
assert!(valid, "Proof verification failed for slice {}", slice.index);
}
}
#[test]
fn test_encode_with_proofs_indices_sequential() {
let mut encoder = test_encoder();
let data = vec![0xAB; 15_000];
let (slices_with_proofs, _) = encoder.encode_with_proofs(data).unwrap();
for (expected_idx, slice) in slices_with_proofs.iter().enumerate() {
assert_eq!(slice.index.as_usize(), expected_idx);
}
}
#[test]
fn test_encode_with_proofs_root_matches() {
let mut encoder = test_encoder();
let data = vec![0xCD; 20_000];
let (_, root1) = encoder.encode_with_root(data.clone()).unwrap();
let (_, root2) = encoder.encode_with_proofs(data).unwrap();
assert_eq!(root1, root2);
}
#[test]
fn test_encode_with_proofs_has_leaf_hash() {
let mut encoder = test_encoder();
let data = vec![0xEF; 10_000];
let (slices_with_proofs, _) = encoder.encode_with_proofs(data).unwrap();
for slice in &slices_with_proofs {
let expected_leaf = slice_root(&slice.data).unwrap();
assert_eq!(slice.leaf_hash, expected_leaf);
}
}
#[test]
fn test_encoding_type_default() {
let encoder = BlobEncoder::new();
assert_eq!(encoder.encoding_type(), EncodingType::Clay);
}
#[test]
fn test_encoding_type_basic() {
let encoder = BlobEncoder::with_encoding(EncodingType::Basic);
assert_eq!(encoder.encoding_type(), EncodingType::Basic);
}
#[test]
fn test_encoding_type_clay() {
let encoder = BlobEncoder::with_encoding(EncodingType::Clay);
assert_eq!(encoder.encoding_type(), EncodingType::Clay);
}
#[test]
fn test_clay_roundtrip_with_decoder() {
use crate::codec::decoder::BlobDecoder;
let original = vec![0xAB; 10_000];
let mut encoder = BlobEncoder::with_encoding(EncodingType::Clay);
let mut decoder = BlobDecoder::with_encoding(EncodingType::Clay);
let slices = encoder.encode(original.clone()).unwrap();
let recovered = decoder.decode(slices).unwrap();
assert_eq!(original, recovered);
}
}