ant_core/data/client/file.rs
1// Copyright 2026 Saorsa Labs Limited
2// SPDX-License-Identifier: MIT OR Apache-2.0
3
4//! Portable file metadata and events. Filesystem staging and finalization are native-only.
5
6#[cfg(feature = "native")]
7mod native;
8use crate::data::client::merkle::PaymentMode;
9use ant_protocol::transport::{MultiAddr, PeerId};
10use ant_protocol::XorName as ChunkAddress;
11#[cfg(feature = "native")]
12pub use native::{
13 ExternalChunkStore, ExternalPaymentInfo, FinalizeOutcome, FinalizeResume, MerkleFinalizeResume,
14 PreparedUpload, WaveFinalizeResume,
15};
16use self_encryption::DataMap;
17
18/// Progress events emitted during file upload for UI feedback.
19#[derive(Debug, Clone)]
20pub enum UploadEvent {
21 /// A chunk has been encrypted and spilled to disk.
22 Encrypting { chunks_done: usize },
23 /// File encryption complete.
24 Encrypted { total_chunks: usize },
25 /// Starting quote collection for a wave.
26 QuotingChunks {
27 wave: usize,
28 total_waves: usize,
29 chunks_in_wave: usize,
30 },
31 /// A chunk has been quoted (peer discovery + price received).
32 /// This is the slow phase — each quote involves network round-trips.
33 ChunkQuoted { quoted: usize, total: usize },
34 /// A chunk has been stored on the network.
35 ChunkStored { stored: usize, total: usize },
36}
37
38/// Progress events emitted during file download for UI feedback.
39#[derive(Debug, Clone)]
40pub enum DownloadEvent {
41 /// Resolving hierarchical DataMap to discover real chunk count.
42 ResolvingDataMap { total_map_chunks: usize },
43 /// A DataMap chunk has been fetched during resolution.
44 MapChunkFetched { fetched: usize },
45 /// DataMap resolved — total data chunk count now known.
46 DataMapResolved { total_chunks: usize },
47 /// Data chunks are being fetched from the network.
48 ChunksFetched { fetched: usize, total: usize },
49}
50
51/// File download result when peer-health diagnostics are enabled.
52#[derive(Debug, Clone)]
53pub struct FileDownloadWithPeerReport {
54 /// Number of plaintext bytes written to the destination.
55 pub bytes_written: u64,
56 /// Per-file-chunk closest-peer GET results collected during the actual download.
57 pub chunk_reports: Vec<FileChunkPeerReport>,
58}
59
60/// Closest-peer GET results for one file chunk.
61#[derive(Debug, Clone)]
62pub struct FileChunkPeerReport {
63 /// 1-based chunk index in the resolved file DataMap.
64 pub index: usize,
65 /// Chunk address.
66 pub address: ChunkAddress,
67 /// All diagnostic GET sweeps attempted for this chunk.
68 pub sweeps: Vec<FileChunkPeerSweepReport>,
69}
70
71/// One all-peer diagnostic GET sweep for a file chunk.
72#[derive(Debug, Clone)]
73pub struct FileChunkPeerSweepReport {
74 /// 1-based attempt number for this chunk.
75 pub attempt: usize,
76 /// Whether this sweep happened during a deferred retry round.
77 pub deferred_retry: bool,
78 /// DHT lookup / sweep-level error, if the closest-peer group could not be queried.
79 pub error: Option<String>,
80 /// Per-peer results, sorted closest first.
81 pub peers: Vec<FileChunkPeerReportPeer>,
82}
83
84/// One peer result in a [`FileChunkPeerReport`].
85#[derive(Debug, Clone)]
86pub struct FileChunkPeerReportPeer {
87 /// Peer queried for the chunk.
88 pub peer_id: PeerId,
89 /// Known network addresses used for the peer.
90 pub peer_addrs: Vec<MultiAddr>,
91 /// XOR distance from `peer_id` to the chunk address.
92 pub xor_distance: ChunkAddress,
93 /// Whether this peer returned the chunk or why it did not.
94 pub status: FileChunkPeerStatus,
95}
96
97/// Peer-level file chunk GET diagnostic status.
98#[derive(Debug, Clone)]
99pub enum FileChunkPeerStatus {
100 /// The peer returned the chunk.
101 Found { bytes: usize },
102 /// The peer responded authoritatively that it does not store the chunk.
103 NotFound,
104 /// The peer did not respond before the timeout.
105 Timeout { message: String },
106 /// The transport/network path to the peer failed.
107 NetworkError { message: String },
108 /// Any other per-peer error.
109 Error { message: String },
110}
111
112/// Whether the data map is published to the network for address-based retrieval.
113///
114/// A private upload stores only the data chunks and returns the `DataMap` to
115/// the caller — only someone holding that `DataMap` can reconstruct the file.
116/// A public upload additionally stores the serialized `DataMap` as a chunk on
117/// the network, yielding a single chunk address that anyone can use to
118/// retrieve the `DataMap` (via [`crate::data::Client::data_map_fetch`]) and then the file.
119#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
120pub enum Visibility {
121 /// Keep the data map local; only the holder can retrieve the file.
122 #[default]
123 Private,
124 /// Publish the data map as a network chunk so anyone with the returned
125 /// address can retrieve and decrypt the file.
126 Public,
127}
128
129/// Confidence attached to an [`UploadCostEstimate`]'s `storage_cost_atto`.
130///
131/// `estimate_upload_cost` prices a file by sampling a few of its chunk
132/// addresses and extrapolating. When every sampled chunk is already stored
133/// there is no live price to extrapolate from, so a `"0"` cost can mean either
134/// "provably free" (the whole file was sampled) or only "probably free" (the
135/// tail was unsampled). This lets callers tell those apart instead of treating
136/// every `"0"` as unconditionally free.
137#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, serde::Serialize, serde::Deserialize)]
138#[serde(rename_all = "snake_case")]
139pub enum CostEstimateConfidence {
140 /// At least one sampled chunk returned a live quote; `storage_cost_atto`
141 /// is extrapolated from a real per-chunk price. The normal case.
142 #[default]
143 PricedSample,
144 /// Every chunk in the file was sampled and every one was already stored.
145 /// `storage_cost_atto` is exactly `"0"` — the upload is genuinely free.
146 VerifiedAllAlreadyStored,
147 /// Every *sampled* chunk was already stored, but not all chunks were
148 /// sampled. `storage_cost_atto` is `"0"` as a best-effort guess; the real
149 /// upload reconciles the true cost at payment time. Render this as "likely
150 /// already stored", not a guaranteed-free price.
151 AllSamplesAlreadyStoredIncomplete,
152}
153
154/// Estimated cost of uploading a file, returned by
155/// [`crate::data::Client::estimate_upload_cost`].
156///
157/// Marked `#[non_exhaustive]` so adding a field later is not a breaking change
158/// for downstream consumers that construct or pattern-match on this struct.
159#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
160#[non_exhaustive]
161pub struct UploadCostEstimate {
162 /// Original file size in bytes.
163 pub file_size: u64,
164 /// Number of chunks the file would be split into (data chunks only,
165 /// does not include the DataMap chunk added during public uploads).
166 pub chunk_count: usize,
167 /// Estimated total storage cost in atto (token smallest unit).
168 pub storage_cost_atto: String,
169 /// Estimated gas cost in wei as a string. This is a rough heuristic
170 /// based on chunk count and payment mode, NOT a live gas price query.
171 pub estimated_gas_cost_wei: String,
172 /// Payment mode that would be used.
173 pub payment_mode: PaymentMode,
174 /// How much to trust `storage_cost_atto`. See [`CostEstimateConfidence`].
175 #[serde(default)]
176 pub confidence: CostEstimateConfidence,
177}
178
179/// Result of a file upload: the `DataMap` needed to retrieve the file.
180///
181/// Marked `#[non_exhaustive]` so adding a new field in future is not a
182/// breaking change for downstream consumers that construct or pattern-match
183/// on this struct.
184#[derive(Debug, Clone)]
185#[non_exhaustive]
186pub struct FileUploadResult {
187 /// The data map containing chunk metadata for reconstruction.
188 pub data_map: DataMap,
189 /// Number of chunks stored on the network.
190 pub chunks_stored: usize,
191 /// Number of chunks that failed to store. Always 0 for a successful
192 /// upload — partial-failure information is conveyed via
193 /// [`crate::data::Error::PartialUpload`] instead.
194 pub chunks_failed: usize,
195 /// Total number of chunks in the upload, including chunks that were
196 /// already stored and skipped. On full success this equals `chunks_stored`.
197 pub total_chunks: usize,
198 /// Which payment mode was actually used (not just requested).
199 pub payment_mode_used: PaymentMode,
200 /// Total storage cost paid in token units (atto). "0" if all chunks already existed.
201 pub storage_cost_atto: String,
202 /// Total gas cost in wei. 0 if no on-chain transactions were made.
203 pub gas_cost_wei: u128,
204 /// Chunk address of the serialized `DataMap`, set only for
205 /// [`Visibility::Public`] uploads. **`Some` means this address is
206 /// retrievable from the network (via [`crate::data::Client::data_map_fetch`])**, not
207 /// necessarily that *this* upload paid to store it — if the serialized
208 /// `DataMap` hashed to a chunk that was already on the network (same
209 /// file uploaded before; deterministic via self-encryption), the address
210 /// is still returned but no storage payment was made for it.
211 pub data_map_address: Option<[u8; 32]>,
212 /// Sum of chunk-store RPC attempts across the upload
213 /// (`>= chunks_stored` on full success; more if any chunk retried).
214 /// `0` for paths that don't run the wave store loop.
215 pub chunk_attempts_total: usize,
216 /// Per-chunk store wall-clock in ms (length == `chunks_stored` on full
217 /// success, empty for paths that don't run the wave store loop).
218 pub store_durations_ms: Vec<u64>,
219 /// Count of stored chunks that succeeded on each retry round
220 /// (index 0 = first attempt, 1 = first retry, etc.). All zeros for
221 /// paths that don't run the wave store loop.
222 pub retries_histogram: [usize; 4],
223}