Skip to main content

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}