1#[cfg(feature = "native")]
10use crate::data::client::adaptive::observe_op;
11#[cfg(feature = "native")]
12use crate::data::client::batch::{PaymentIntent, PreparedChunk};
13#[cfg(feature = "native")]
14use crate::data::client::classify_error;
15#[cfg(feature = "native")]
16use crate::data::client::file::{ExternalPaymentInfo, PreparedUpload, Visibility};
17use crate::data::client::merkle::PaymentMode;
18use crate::data::client::Client;
19use crate::data::error::{Error, Result};
20use ant_protocol::compute_address;
21use bytes::Bytes;
22#[cfg(feature = "native")]
23use futures::stream::StreamExt;
24use self_encryption::{encrypt, DataMap};
25use std::num::NonZeroUsize;
26use tracing::{debug, info};
27
28#[derive(Debug, Clone)]
30pub struct DataUploadResult {
31 pub data_map: DataMap,
33 pub chunks_stored: usize,
35 pub payment_mode_used: PaymentMode,
37}
38
39impl Client {
40 pub async fn data_upload(&self, content: Bytes) -> Result<DataUploadResult> {
50 let content_len = content.len();
51 debug!("Encrypting data ({content_len} bytes)");
52
53 let (data_map, encrypted_chunks) = encrypt(content)
54 .map_err(|e| Error::Encryption(format!("Failed to encrypt data: {e}")))?;
55
56 info!("Data encrypted into {} chunks", encrypted_chunks.len());
57
58 let chunk_contents: Vec<Bytes> = encrypted_chunks
59 .into_iter()
60 .map(|chunk| chunk.content)
61 .collect();
62
63 let (addresses, _storage_cost, _gas_cost) =
64 self.batch_upload_chunks(chunk_contents).await?;
65 let chunks_stored = addresses.len();
66
67 info!("Data uploaded: {chunks_stored} chunks stored ({content_len} bytes original)");
68
69 Ok(DataUploadResult {
70 data_map,
71 chunks_stored,
72 payment_mode_used: PaymentMode::Single,
73 })
74 }
75
76 pub async fn data_upload_with_mode(
86 &self,
87 content: Bytes,
88 mode: PaymentMode,
89 ) -> Result<DataUploadResult> {
90 let (data_map, encrypted) =
91 encrypt(content).map_err(|e| Error::Encryption(e.to_string()))?;
92 let chunks = encrypted
93 .into_iter()
94 .map(|chunk| chunk.content)
95 .collect::<Vec<_>>();
96 let records = chunks
97 .iter()
98 .enumerate()
99 .map(|(index, bytes)| super::upload::UploadRecord {
100 address: compute_address(bytes),
101 size: bytes.len() as u64,
102 index,
103 })
104 .collect();
105 let adapter = super::upload::MemoryUploadAdapter {
106 client: self,
107 chunks: &chunks,
108 progress: None,
109 stored_offset: 0,
110 file_total: chunks.len(),
111 resume_key: None,
112 };
113 let outcome = self
114 .upload_records(records, &mut Default::default(), &adapter, mode)
115 .await?;
116 Ok(DataUploadResult {
117 data_map,
118 chunks_stored: outcome.addresses.len(),
119 payment_mode_used: outcome.mode,
120 })
121 }
122
123 #[cfg(feature = "native")]
128 pub async fn data_prepare_upload(&self, content: Bytes) -> Result<PreparedUpload> {
129 self.data_prepare_upload_with_visibility(content, Visibility::Private)
130 .await
131 }
132
133 #[cfg(feature = "native")]
160 pub async fn data_prepare_upload_with_visibility(
161 &self,
162 content: Bytes,
163 visibility: Visibility,
164 ) -> Result<PreparedUpload> {
165 let content_len = content.len();
166 debug!("Preparing data upload for external signing (visibility={visibility:?}, {content_len} bytes)");
167
168 let (data_map, encrypted_chunks) = encrypt(content)
169 .map_err(|e| Error::Encryption(format!("Failed to encrypt data: {e}")))?;
170
171 let mut chunk_contents: Vec<Bytes> = encrypted_chunks
172 .into_iter()
173 .map(|chunk| chunk.content)
174 .collect();
175
176 info!("Data encrypted into {} chunks", chunk_contents.len());
177
178 let data_map_address = match visibility {
184 Visibility::Private => None,
185 Visibility::Public => {
186 let (address, bytes) = crate::client_engine::files::public_map_record(&data_map)
187 .map_err(Error::Serialization)?;
188 info!(
189 "Public upload: bundling DataMap chunk ({} bytes) at address {}",
190 bytes.len(),
191 hex::encode(address)
192 );
193 chunk_contents.push(bytes);
194 Some(address)
195 }
196 };
197
198 let chunk_count = chunk_contents.len();
199 let chunks_with_addr: Vec<(Bytes, [u8; 32])> = chunk_contents
200 .into_iter()
201 .map(|content| {
202 let address = compute_address(&content);
203 (content, address)
204 })
205 .collect();
206
207 let quote_limiter = self.controller().quote.clone();
208 let quote_concurrency = quote_limiter.current().min(chunk_count.max(1));
209 let results: Vec<([u8; 32], Result<Option<PreparedChunk>>)> =
210 crate::client_engine::bounded_unordered(
211 chunks_with_addr.into_iter().map(|(content, address)| {
212 let limiter = quote_limiter.clone();
213 async move {
214 let result = observe_op(
215 &limiter,
216 || async move { self.prepare_chunk_payment(content).await },
217 classify_error,
218 )
219 .await;
220 (address, result)
221 }
222 }),
223 quote_concurrency,
224 )
225 .collect()
226 .await;
227
228 let mut prepared_chunks = Vec::with_capacity(results.len());
229 let mut already_stored_addresses = Vec::new();
230 for (address, result) in results {
231 match result? {
232 Some(prepared) => prepared_chunks.push(prepared),
233 None => already_stored_addresses.push(address),
234 }
235 }
236
237 if let Some(addr) = data_map_address {
238 if already_stored_addresses.contains(&addr) {
239 info!(
240 "Public upload: DataMap chunk {} was already stored \
241 on the network — address is retrievable without a \
242 new payment",
243 hex::encode(addr)
244 );
245 }
246 }
247
248 let payment_intent = PaymentIntent::from_prepared_chunks(&prepared_chunks);
249
250 info!(
251 "Data prepared for external signing: {} chunks, {} already stored, total {} atto ({content_len} bytes)",
252 prepared_chunks.len(),
253 already_stored_addresses.len(),
254 payment_intent.total_amount,
255 );
256
257 Ok(PreparedUpload {
258 data_map,
259 payment_info: ExternalPaymentInfo::WaveBatch {
260 prepared_chunks,
261 payment_intent,
262 },
263 data_map_address,
264 already_stored_addresses,
265 total_chunks: chunk_count,
266 })
267 }
268
269 pub async fn data_map_store(&self, data_map: &DataMap) -> Result<[u8; 32]> {
279 let (_, serialized) = crate::client_engine::files::public_map_record(data_map)
280 .map_err(Error::Serialization)?;
281
282 info!(
283 "Storing DataMap as public chunk ({} bytes serialized)",
284 serialized.len()
285 );
286
287 self.chunk_put(serialized).await
288 }
289
290 pub async fn data_map_fetch(&self, address: &[u8; 32]) -> Result<DataMap> {
299 let chunk = self.chunk_get(address).await?.ok_or_else(|| {
300 Error::NotFound(format!(
301 "DataMap chunk not found at {}",
302 hex::encode(address)
303 ))
304 })?;
305
306 decode_data_map_chunk(&chunk.content)
307 }
308
309 pub async fn data_map_fetch_from_closest_peers(
317 &self,
318 address: &[u8; 32],
319 peer_count: NonZeroUsize,
320 ) -> Result<DataMap> {
321 let chunk = self
322 .chunk_get_from_closest_peers(address, peer_count.get())
323 .await?
324 .ok_or_else(|| {
325 Error::NotFound(format!(
326 "DataMap chunk not found at {}",
327 hex::encode(address)
328 ))
329 })?;
330
331 decode_data_map_chunk(&chunk.content)
332 }
333
334 pub async fn data_download(&self, data_map: &DataMap) -> Result<Bytes> {
353 self.data_download_with_concurrency(data_map, usize::MAX)
354 .await
355 }
356
357 pub async fn data_download_with_concurrency(
359 &self,
360 data_map: &DataMap,
361 concurrency: usize,
362 ) -> Result<Bytes> {
363 self.data_download_with_progress(data_map, concurrency, &|_, _| {})
364 .await
365 }
366
367 pub(crate) async fn data_download_with_progress(
369 &self,
370 data_map: &DataMap,
371 concurrency: usize,
372 progress: &impl Fn(usize, usize),
373 ) -> Result<Bytes> {
374 if concurrency == 0 {
375 return Err(Error::Config(
376 "download concurrency must be positive".into(),
377 ));
378 }
379 let received = std::sync::Mutex::new(std::collections::HashSet::new());
380 let total = data_map.infos().len();
381 progress(0, total);
382 crate::client_engine::files::download(
383 data_map,
384 &|address| {
385 let received = &received;
386 async move {
387 let bytes = self.fetch_data_record(address).await?;
388 let mut received = received.lock().unwrap_or_else(|error| error.into_inner());
389 if received.insert(address) {
390 let completed = data_map
391 .infos()
392 .iter()
393 .filter(|info| received.contains(&info.dst_hash.0))
394 .count();
395 drop(received);
396 progress(completed, total);
397 }
398 Ok(bytes)
399 }
400 },
401 &|| self.controller().fetch.current().min(concurrency),
402 &crate::runtime::sleep,
403 retry_data_fetch,
404 )
405 .await
406 .map_err(map_read_error)
407 }
408
409 pub async fn data_download_range(
416 &self,
417 data_map: &DataMap,
418 start: usize,
419 length: usize,
420 ) -> Result<Bytes> {
421 let fetch = |address| self.fetch_data_record(address);
422 let cap = || self.controller().fetch.current();
423 let root = crate::client_engine::files::resolve(data_map, &fetch, &cap)
424 .await
425 .map_err(map_read_error)?;
426 crate::client_engine::files::read_range(
427 &root,
428 start,
429 length,
430 &fetch,
431 &cap,
432 &crate::runtime::sleep,
433 retry_data_fetch,
434 )
435 .await
436 .map_err(map_read_error)
437 }
438
439 async fn fetch_data_record(&self, address: [u8; 32]) -> Result<Bytes> {
440 self.chunk_get_observed(&address)
441 .await?
442 .map(|chunk| chunk.content)
443 .ok_or_else(|| {
444 Error::NotFound(format!(
445 "Missing chunk {} required for data reconstruction",
446 hex::encode(address)
447 ))
448 })
449 }
450}
451
452fn retry_data_fetch(error: &Error) -> bool {
453 matches!(
454 error,
455 Error::NotFound(_)
456 | Error::Timeout(_)
457 | Error::Network(_)
458 | Error::Protocol(_)
459 | Error::Storage(_)
460 | Error::Io(_)
461 | Error::InsufficientPeers(_)
462 )
463}
464
465pub(super) fn map_read_error(error: crate::client_engine::files::ReadError<Error>) -> Error {
466 match error {
467 crate::client_engine::files::ReadError::Fetch(error) => error,
468 crate::client_engine::files::ReadError::Invalid(error) => Error::Encryption(error),
469 }
470}
471
472fn decode_data_map_chunk(content: &[u8]) -> Result<DataMap> {
473 crate::client_engine::files::decode_map(content).map_err(Error::Serialization)
474}
475
476#[cfg(test)]
484mod send_assertions {
485 use super::*;
486
487 fn _assert_send<T: Send>(_: &T) {}
488
489 #[allow(
490 dead_code,
491 unreachable_code,
492 unused_variables,
493 clippy::diverging_sub_expression
494 )]
495 async fn _data_download_is_send(client: &Client) {
496 let dm: DataMap = todo!();
497 let fut = client.data_download(&dm);
498 _assert_send(&fut);
499 }
500
501 #[allow(
502 dead_code,
503 unreachable_code,
504 unused_variables,
505 clippy::diverging_sub_expression
506 )]
507 async fn _data_download_range_is_send(client: &Client) {
508 let dm: DataMap = todo!();
509 _assert_send(&client.data_download_range(&dm, 0, 1024));
510 }
511
512 #[allow(dead_code, unreachable_code, clippy::diverging_sub_expression)]
513 async fn _data_upload_is_send(client: &Client) {
514 let fut = client.data_upload(Bytes::new());
515 _assert_send(&fut);
516 }
517
518 #[allow(dead_code, unreachable_code, clippy::diverging_sub_expression)]
519 async fn _data_upload_with_mode_is_send(client: &Client) {
520 let fut = client.data_upload_with_mode(Bytes::new(), PaymentMode::Auto);
521 _assert_send(&fut);
522 }
523
524 #[allow(dead_code, unreachable_code, clippy::diverging_sub_expression)]
525 async fn _data_prepare_upload_is_send(client: &Client) {
526 let fut = client.data_prepare_upload(Bytes::new());
527 _assert_send(&fut);
528 }
529
530 #[allow(dead_code, unreachable_code, clippy::diverging_sub_expression)]
531 async fn _data_prepare_upload_with_visibility_is_send(client: &Client) {
532 let fut = client.data_prepare_upload_with_visibility(Bytes::new(), Visibility::Public);
533 _assert_send(&fut);
534 }
535}