1use serde::{Deserialize, Serialize};
7use std::marker::PhantomData;
8
9pub mod proof_states {
11 #[derive(Debug)]
13 pub struct Created;
14 #[derive(Debug)]
16 pub struct Submitted;
17 #[derive(Debug)]
19 pub struct Verified;
20 #[derive(Debug)]
22 pub struct Rejected;
23}
24
25#[derive(Debug, Clone, Serialize, Deserialize)]
47pub struct BandwidthProofState<S> {
48 pub id: String,
50 pub bytes_transferred: u64,
52 pub latency_ms: u64,
54 pub points: u64,
56 pub rejection_reason: Option<String>,
58 #[serde(skip)]
60 _state: PhantomData<S>,
61}
62
63impl BandwidthProofState<proof_states::Created> {
64 #[must_use]
66 pub fn new(id: impl Into<String>, bytes_transferred: u64, latency_ms: u64) -> Self {
67 Self {
68 id: id.into(),
69 bytes_transferred,
70 latency_ms,
71 points: 0,
72 rejection_reason: None,
73 _state: PhantomData,
74 }
75 }
76
77 #[must_use]
79 pub fn submit(self) -> BandwidthProofState<proof_states::Submitted> {
80 BandwidthProofState {
81 id: self.id,
82 bytes_transferred: self.bytes_transferred,
83 latency_ms: self.latency_ms,
84 points: self.points,
85 rejection_reason: self.rejection_reason,
86 _state: PhantomData,
87 }
88 }
89}
90
91impl BandwidthProofState<proof_states::Submitted> {
92 pub fn verify(
98 self,
99 valid: bool,
100 ) -> Result<
101 BandwidthProofState<proof_states::Verified>,
102 BandwidthProofState<proof_states::Rejected>,
103 > {
104 if valid {
105 let points = self.bytes_transferred / 1_000_000;
107 Ok(BandwidthProofState {
108 id: self.id,
109 bytes_transferred: self.bytes_transferred,
110 latency_ms: self.latency_ms,
111 points,
112 rejection_reason: None,
113 _state: PhantomData,
114 })
115 } else {
116 Err(BandwidthProofState {
117 id: self.id,
118 bytes_transferred: self.bytes_transferred,
119 latency_ms: self.latency_ms,
120 points: 0,
121 rejection_reason: Some("Verification failed".to_string()),
122 _state: PhantomData,
123 })
124 }
125 }
126}
127
128impl BandwidthProofState<proof_states::Verified> {
129 #[must_use]
131 pub fn awarded_points(&self) -> u64 {
132 self.points
133 }
134}
135
136impl BandwidthProofState<proof_states::Rejected> {
137 #[must_use]
139 pub fn reason(&self) -> &str {
140 self.rejection_reason
141 .as_deref()
142 .unwrap_or("Unknown rejection reason")
143 }
144}
145
146pub mod content_states {
148 #[derive(Debug)]
150 pub struct Uploading;
151 #[derive(Debug)]
153 pub struct Processing;
154 #[derive(Debug)]
156 pub struct Published;
157 #[derive(Debug)]
159 pub struct Archived;
160}
161
162#[derive(Debug, Clone)]
164pub struct ContentUpload<S> {
165 pub content_id: String,
167 pub uploaded_bytes: u64,
169 pub total_bytes: u64,
171 pub cid: Option<String>,
173 _state: PhantomData<S>,
174}
175
176impl ContentUpload<content_states::Uploading> {
177 #[must_use]
179 pub fn new(content_id: impl Into<String>, total_bytes: u64) -> Self {
180 Self {
181 content_id: content_id.into(),
182 uploaded_bytes: 0,
183 total_bytes,
184 cid: None,
185 _state: PhantomData,
186 }
187 }
188
189 pub fn update_progress(&mut self, bytes: u64) {
191 self.uploaded_bytes = self
192 .uploaded_bytes
193 .saturating_add(bytes)
194 .min(self.total_bytes);
195 }
196
197 #[must_use]
199 pub fn is_complete(&self) -> bool {
200 self.uploaded_bytes >= self.total_bytes
201 }
202
203 #[must_use]
205 pub fn complete_upload(self) -> ContentUpload<content_states::Processing> {
206 ContentUpload {
207 content_id: self.content_id,
208 uploaded_bytes: self.uploaded_bytes,
209 total_bytes: self.total_bytes,
210 cid: None,
211 _state: PhantomData,
212 }
213 }
214}
215
216impl ContentUpload<content_states::Processing> {
217 #[must_use]
219 pub fn publish(self, cid: impl Into<String>) -> ContentUpload<content_states::Published> {
220 ContentUpload {
221 content_id: self.content_id,
222 uploaded_bytes: self.uploaded_bytes,
223 total_bytes: self.total_bytes,
224 cid: Some(cid.into()),
225 _state: PhantomData,
226 }
227 }
228}
229
230impl ContentUpload<content_states::Published> {
231 #[must_use]
233 pub fn cid(&self) -> &str {
234 self.cid.as_deref().unwrap_or("")
235 }
236
237 #[must_use]
239 pub fn archive(self) -> ContentUpload<content_states::Archived> {
240 ContentUpload {
241 content_id: self.content_id,
242 uploaded_bytes: self.uploaded_bytes,
243 total_bytes: self.total_bytes,
244 cid: self.cid,
245 _state: PhantomData,
246 }
247 }
248}
249
250impl ContentUpload<content_states::Archived> {
251 #[must_use]
253 pub fn archived_cid(&self) -> &str {
254 self.cid.as_deref().unwrap_or("")
255 }
256}
257
258#[cfg(test)]
259mod tests {
260 use super::*;
261
262 #[test]
263 fn test_proof_state_machine_happy_path() {
264 let proof = BandwidthProofState::<proof_states::Created>::new("proof1", 10_000_000, 100);
266 assert_eq!(proof.id, "proof1");
267 assert_eq!(proof.bytes_transferred, 10_000_000);
268
269 let submitted = proof.submit();
271 assert_eq!(submitted.id, "proof1");
272
273 let verified = submitted.verify(true).unwrap();
275 assert_eq!(verified.id, "proof1");
276 assert_eq!(verified.awarded_points(), 10); }
278
279 #[test]
280 fn test_proof_state_machine_rejection() {
281 let proof = BandwidthProofState::<proof_states::Created>::new("proof1", 1024, 100);
282 let submitted = proof.submit();
283 let rejected = submitted.verify(false).unwrap_err();
284
285 assert_eq!(rejected.id, "proof1");
286 assert!(rejected.reason().contains("Verification failed"));
287 }
288
289 #[test]
290 fn test_content_upload_state_machine() {
291 let mut upload = ContentUpload::<content_states::Uploading>::new("content1", 1000);
293 assert_eq!(upload.uploaded_bytes, 0);
294 assert!(!upload.is_complete());
295
296 upload.update_progress(500);
298 assert_eq!(upload.uploaded_bytes, 500);
299 assert!(!upload.is_complete());
300
301 upload.update_progress(500);
303 assert!(upload.is_complete());
304
305 let processing = upload.complete_upload();
306 assert_eq!(processing.content_id, "content1");
307
308 let published = processing.publish("QmXXX123");
310 assert_eq!(published.cid(), "QmXXX123");
311
312 let archived = published.archive();
314 assert_eq!(archived.archived_cid(), "QmXXX123");
315 }
316
317 #[test]
318 fn test_content_upload_progress_clamping() {
319 let mut upload = ContentUpload::<content_states::Uploading>::new("content1", 100);
320 upload.update_progress(150); assert_eq!(upload.uploaded_bytes, 100); }
323
324 #[test]
325 fn test_proof_serde() {
326 let proof = BandwidthProofState::<proof_states::Created>::new("proof1", 1024, 50);
327 let json = serde_json::to_string(&proof).unwrap();
328 let _decoded: BandwidthProofState<proof_states::Created> =
329 serde_json::from_str(&json).unwrap();
330 }
332
333 }