use std::path::PathBuf;
use bytes::Bytes;
use super::archive_private::PrivateArchive;
use super::archive_public::PublicArchive;
use super::fs_public::metadata_from_entry;
use super::{FileCostError, estimate_directory_chunks};
use crate::Client;
use crate::client::config::MERKLE_PAYMENT_THRESHOLD;
use crate::client::data_types::chunk::{Chunk, DataMapChunk};
use crate::client::high_level::data::DataAddress;
use crate::client::merkle_payments::MerklePaymentError;
use crate::client::quote::add_costs;
use crate::self_encryption::{MAX_CHUNK_SIZE, encrypt_directory_files};
use ant_evm::AttoTokens;
use ant_evm::merkle_payments::MAX_LEAVES;
use ant_protocol::storage::DataTypes;
use xor_name::XorName;
impl Client {
pub async fn file_cost(
&self,
path: &PathBuf,
is_public: bool,
include_archive: bool,
) -> Result<AttoTokens, FileCostError> {
let estimated_chunks = estimate_directory_chunks(path)?;
let content_cost = if estimated_chunks >= MERKLE_PAYMENT_THRESHOLD {
crate::loud_info!(
"Using merkle cost estimation for ~{estimated_chunks} chunks (threshold: {MERKLE_PAYMENT_THRESHOLD})"
);
self.file_cost_merkle(path.clone(), is_public).await?
} else {
crate::loud_info!(
"Using regular cost estimation for ~{estimated_chunks} chunks (threshold: {MERKLE_PAYMENT_THRESHOLD})"
);
self.file_cost_regular(path, is_public).await?
};
if include_archive && !path.is_file() {
let archive_cost = self.estimate_archive_cost(path, is_public).await?;
let total_cost = add_costs(content_cost, archive_cost)?;
debug!("Total cost (content + archive): {total_cost:?}");
Ok(total_cost)
} else {
debug!("Total cost (content only): {content_cost:?}");
Ok(content_cost)
}
}
pub async fn file_cost_regular(
&self,
path: &PathBuf,
is_public: bool,
) -> Result<AttoTokens, FileCostError> {
let mut content_addrs = vec![];
for entry in walkdir::WalkDir::new(path) {
let entry = entry?;
if !entry.file_type().is_file() {
continue;
}
let file_path = entry.path().to_path_buf();
tracing::info!("Cost for file: {file_path:?}");
let data = tokio::fs::read(&file_path).await?;
let file_bytes = Bytes::from(data);
let addrs = self.get_content_addrs(file_bytes)?;
if is_public {
content_addrs.extend(addrs);
} else {
content_addrs.extend(addrs.into_iter().skip(1));
}
}
let total_cost = self.get_cost_estimation(content_addrs).await?;
debug!("Content cost for {path:?}: {total_cost:?}");
Ok(total_cost)
}
pub async fn file_cost_merkle(
&self,
path: PathBuf,
is_public: bool,
) -> Result<AttoTokens, FileCostError> {
debug!(
"merkle payment: file_cost_merkle starting for path: {path:?}, is_public: {is_public}"
);
crate::loud_info!("Encrypting files to calculate cost...");
let all_xor_names = self.collect_xornames_for_cost(path, is_public).await?;
let total_chunks = all_xor_names.len();
crate::loud_info!("Encrypted into {total_chunks} chunks");
let batches: Vec<Vec<XorName>> = all_xor_names
.chunks(MAX_LEAVES)
.map(|c| c.to_vec())
.collect();
let num_batches = batches.len();
crate::loud_info!("Estimating cost for {num_batches} batch(es)...");
let mut total_cost = ant_evm::U256::ZERO;
for (batch_idx, batch_xornames) in batches.into_iter().enumerate() {
let batch_num = batch_idx + 1;
debug!("Estimating batch {batch_num}/{num_batches}");
let (tree, _candidate_pools, pool_commitments, merkle_payment_timestamp) = self
.prepare_merkle_batch(DataTypes::Chunk, batch_xornames, MAX_CHUNK_SIZE)
.await?;
let batch_cost = self
.evm_network()
.estimate_merkle_payment_cost(
tree.depth(),
&pool_commitments,
merkle_payment_timestamp,
)
.await
.map_err(MerklePaymentError::MerklePaymentVault)?;
total_cost = total_cost.saturating_add(batch_cost);
}
let estimated_cost = AttoTokens::from_atto(total_cost);
crate::loud_info!("Total estimated cost: {estimated_cost}");
Ok(estimated_cost)
}
async fn collect_xornames_for_cost(
&self,
path: PathBuf,
is_public: bool,
) -> Result<Vec<XorName>, FileCostError> {
let streams = encrypt_directory_files(path, is_public).await?;
let mut all_xor_names = Vec::new();
for stream_result in streams {
let mut stream = stream_result.map_err(FileCostError::Encryption)?;
let batch_size: usize = 32;
while let Some(batch) = stream.next_batch(batch_size) {
for chunk in batch {
all_xor_names.push(*chunk.name());
}
}
}
Ok(all_xor_names)
}
pub async fn estimate_archive_cost(
&self,
path: &PathBuf,
is_public: bool,
) -> Result<AttoTokens, FileCostError> {
if path.is_file() {
return Ok(AttoTokens::zero());
}
let mut public_archive = PublicArchive::new();
let mut private_archive = PrivateArchive::new();
for entry in walkdir::WalkDir::new(path) {
let entry = entry?;
if !entry.file_type().is_file() {
continue;
}
let file_path = entry.path().to_path_buf();
let metadata = metadata_from_entry(&entry);
if is_public {
let placeholder_addr = DataAddress::new(xor_name::XorName::default());
public_archive.add_file(file_path, placeholder_addr, metadata);
} else {
let data = tokio::fs::read(&file_path).await?;
let file_bytes = Bytes::from(data);
let addrs = self.get_content_addrs(file_bytes)?;
let datamap_size = addrs.first().map(|(_, size)| *size).unwrap_or(0);
let placeholder_bytes = vec![0u8; datamap_size];
let placeholder_chunk = DataMapChunk(Chunk::new(Bytes::from(placeholder_bytes)));
private_archive.add_file(file_path, placeholder_chunk, metadata);
}
}
let archive_bytes = if is_public {
public_archive.to_bytes()?
} else {
private_archive.to_bytes()?
};
let archive_addrs = self.get_content_addrs(archive_bytes)?;
let archive_addrs: Vec<_> = if is_public {
archive_addrs
} else {
archive_addrs.into_iter().skip(1).collect()
};
let archive_cost = self.get_cost_estimation(archive_addrs).await?;
debug!("Archive cost for {path:?}: {archive_cost:?}");
Ok(archive_cost)
}
}