1use std::collections::HashSet;
13
14use anyhow::Result;
15use tokio::sync::watch;
16use uuid::Uuid;
17
18use crate::BlockId;
19
20use super::cancel::{CancelConfirmation, CancelStateUpdater, CancellationToken};
21
22#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
24pub struct TransferId(Uuid);
25
26impl TransferId {
27 pub fn new() -> Self {
29 TransferId(Uuid::new_v4())
30 }
31
32 pub fn as_uuid(&self) -> Uuid {
34 self.0
35 }
36}
37
38impl Default for TransferId {
39 fn default() -> Self {
40 Self::new()
41 }
42}
43
44impl std::fmt::Display for TransferId {
45 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
46 write!(f, "{}", self.0)
47 }
48}
49
50impl From<Uuid> for TransferId {
51 fn from(uuid: Uuid) -> Self {
52 TransferId(uuid)
53 }
54}
55
56#[derive(Debug, Clone, Copy, PartialEq, Eq)]
58pub enum TransferStatus {
59 Evaluating,
61 Queued,
63 Transferring,
65 Complete,
67 Cancelled,
69 Failed,
71}
72
73impl TransferStatus {
74 pub fn is_terminal(&self) -> bool {
76 matches!(
77 self,
78 TransferStatus::Complete | TransferStatus::Cancelled | TransferStatus::Failed
79 )
80 }
81
82 pub fn is_active(&self) -> bool {
84 !self.is_terminal()
85 }
86}
87
88#[derive(Debug, Clone)]
90pub struct TransferResult {
91 pub id: TransferId,
93 pub status: TransferStatus,
95 pub passed_blocks: Vec<BlockId>,
97 pub completed_blocks: Vec<BlockId>,
99 pub failed_blocks: Vec<BlockId>,
101 pub filtered_blocks: Vec<BlockId>,
103 pub error: Option<String>,
105}
106
107#[derive(Clone)]
114pub struct TransferHandle {
115 id: TransferId,
116 status_rx: watch::Receiver<TransferStatus>,
117 passed_blocks_rx: watch::Receiver<Vec<BlockId>>,
118 completed_rx: watch::Receiver<Vec<BlockId>>,
119 failed_rx: watch::Receiver<Vec<BlockId>>,
120 remaining_rx: watch::Receiver<Vec<BlockId>>,
121 cancel_token: CancellationToken,
122 result_rx: watch::Receiver<Option<TransferResult>>,
123}
124
125impl TransferHandle {
126 pub fn id(&self) -> TransferId {
128 self.id
129 }
130
131 pub fn status(&self) -> TransferStatus {
133 *self.status_rx.borrow()
134 }
135
136 pub fn passed_blocks(&self) -> Vec<BlockId> {
138 self.passed_blocks_rx.borrow().clone()
139 }
140
141 pub fn completed_blocks(&self) -> Vec<BlockId> {
143 self.completed_rx.borrow().clone()
144 }
145
146 pub fn failed_blocks(&self) -> Vec<BlockId> {
148 self.failed_rx.borrow().clone()
149 }
150
151 pub fn remaining_blocks(&self) -> Vec<BlockId> {
153 self.remaining_rx.borrow().clone()
154 }
155
156 pub fn is_complete(&self) -> bool {
158 self.status().is_terminal()
159 }
160
161 pub fn cancel(&self) -> CancelConfirmation {
173 self.cancel_token.request();
174 self.cancel_token.wait_confirmed()
175 }
176
177 pub fn is_cancelled(&self) -> bool {
179 self.cancel_token.is_requested()
180 }
181
182 pub async fn wait(&mut self) -> Result<TransferResult> {
186 loop {
188 {
189 let result = self.result_rx.borrow();
190 if let Some(r) = result.as_ref() {
191 return Ok(r.clone());
192 }
193 }
194
195 if self.result_rx.changed().await.is_err() {
196 return Err(anyhow::anyhow!("Transfer channel closed unexpectedly"));
198 }
199 }
200 }
201
202 pub fn subscribe_status(&self) -> watch::Receiver<TransferStatus> {
204 self.status_rx.clone()
205 }
206
207 pub fn subscribe_completed(&self) -> watch::Receiver<Vec<BlockId>> {
209 self.completed_rx.clone()
210 }
211
212 pub fn subscribe_failed(&self) -> watch::Receiver<Vec<BlockId>> {
214 self.failed_rx.clone()
215 }
216}
217
218impl std::fmt::Debug for TransferHandle {
219 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
220 f.debug_struct("TransferHandle")
221 .field("id", &self.id)
222 .field("status", &self.status())
223 .field("passed_count", &self.passed_blocks().len())
224 .field("completed_count", &self.completed_blocks().len())
225 .field("failed_count", &self.failed_blocks().len())
226 .field("remaining_count", &self.remaining_blocks().len())
227 .finish()
228 }
229}
230
231#[allow(dead_code)]
233pub(crate) struct TransferState {
234 pub(crate) id: TransferId,
235 pub(crate) status: TransferStatus,
237 pub(crate) input_blocks: Vec<BlockId>,
239 pub(crate) passed_blocks: Vec<BlockId>,
241 pub(crate) in_flight: HashSet<BlockId>,
243 pub(crate) completed: Vec<BlockId>,
245 pub(crate) failed: Vec<BlockId>,
247 pub(crate) filtered_out: Vec<BlockId>,
249 pub(crate) error: Option<String>,
251 pub(crate) notifiers: TransferNotifiers,
253 pub(crate) cancel_updater: CancelStateUpdater,
255 pub(crate) total_expected_blocks: usize,
257 pub(crate) blocks_processed: usize,
259 pub(crate) precondition: Option<velo::EventHandle>,
264}
265
266#[allow(dead_code)]
267impl TransferState {
268 pub(crate) fn new(id: TransferId, input_blocks: Vec<BlockId>) -> (Self, TransferHandle) {
270 let (status_tx, status_rx) = watch::channel(TransferStatus::Evaluating);
271 let (passed_tx, passed_rx) = watch::channel(Vec::new());
272 let (completed_tx, completed_rx) = watch::channel(Vec::new());
273 let (failed_tx, failed_rx) = watch::channel(Vec::new());
274 let (remaining_tx, remaining_rx) = watch::channel(input_blocks.clone());
275 let (result_tx, result_rx) = watch::channel(None);
276 let (cancel_token, cancel_updater) = CancellationToken::new();
277
278 let notifiers = TransferNotifiers {
279 status_tx,
280 passed_tx,
281 completed_tx,
282 failed_tx,
283 remaining_tx,
284 result_tx,
285 };
286
287 let state = TransferState {
288 id,
289 status: TransferStatus::Evaluating,
290 input_blocks: input_blocks.clone(),
291 passed_blocks: Vec::new(),
292 in_flight: HashSet::new(),
293 completed: Vec::new(),
294 failed: Vec::new(),
295 filtered_out: Vec::new(),
296 error: None,
297 notifiers,
298 cancel_updater,
299 total_expected_blocks: 0, blocks_processed: 0,
301 precondition: None, };
303
304 let handle = TransferHandle {
305 id,
306 status_rx,
307 passed_blocks_rx: passed_rx,
308 completed_rx,
309 failed_rx,
310 remaining_rx,
311 cancel_token,
312 result_rx,
313 };
314
315 (state, handle)
316 }
317
318 pub(crate) fn is_cancel_requested(&self) -> bool {
320 self.cancel_updater.is_requested()
321 }
322
323 pub(crate) fn set_status(&mut self, status: TransferStatus) {
325 self.status = status;
326 let _ = self.notifiers.status_tx.send(status);
327 }
328
329 pub(crate) fn add_passed(&mut self, block_ids: impl IntoIterator<Item = BlockId>) {
331 self.passed_blocks.extend(block_ids);
332 let _ = self.notifiers.passed_tx.send(self.passed_blocks.clone());
333 self.update_remaining();
334 }
335
336 pub(crate) fn add_filtered(&mut self, block_ids: impl IntoIterator<Item = BlockId>) {
338 self.filtered_out.extend(block_ids);
339 self.update_remaining();
340 }
341
342 pub(crate) fn mark_in_flight(&mut self, block_ids: impl IntoIterator<Item = BlockId>) {
344 self.in_flight.extend(block_ids);
345 }
346
347 pub(crate) fn mark_completed(&mut self, block_ids: impl IntoIterator<Item = BlockId>) {
349 for id in block_ids {
350 self.in_flight.remove(&id);
351 self.completed.push(id);
352 }
353 let _ = self.notifiers.completed_tx.send(self.completed.clone());
354 self.update_remaining();
355 }
356
357 pub(crate) fn mark_failed(&mut self, block_ids: impl IntoIterator<Item = BlockId>) {
359 for id in block_ids {
360 self.in_flight.remove(&id);
361 self.failed.push(id);
362 }
363 let _ = self.notifiers.failed_tx.send(self.failed.clone());
364 self.update_remaining();
365 }
366
367 fn update_remaining(&self) {
369 let remaining: Vec<BlockId> = self
370 .passed_blocks
371 .iter()
372 .filter(|id| !self.completed.contains(id) && !self.failed.contains(id))
373 .copied()
374 .collect();
375 let _ = self.notifiers.remaining_tx.send(remaining);
376 }
377
378 pub(crate) fn set_error(&mut self, error: String) {
380 self.error = Some(error);
381 self.set_status(TransferStatus::Failed);
382 self.finalize();
383 }
384
385 pub(crate) fn set_cancelled(&mut self) {
387 self.set_status(TransferStatus::Cancelled);
388 self.cancel_updater.set_confirmed();
389 self.finalize();
390 }
391
392 pub(crate) fn set_complete(&mut self) {
394 self.set_status(TransferStatus::Complete);
395 self.finalize();
396 }
397
398 fn finalize(&mut self) {
400 let result = TransferResult {
401 id: self.id,
402 status: self.status,
403 passed_blocks: self.passed_blocks.clone(),
404 completed_blocks: self.completed.clone(),
405 failed_blocks: self.failed.clone(),
406 filtered_blocks: self.filtered_out.clone(),
407 error: self.error.clone(),
408 };
409 let _ = self.notifiers.result_tx.send(Some(result));
410 }
411
412 pub(crate) fn in_flight_count(&self) -> usize {
414 self.in_flight.len()
415 }
416
417 pub(crate) fn begin_draining(&self) {
419 self.cancel_updater.set_draining(self.in_flight.len());
420 }
421
422 pub(crate) fn update_draining(&self) {
424 self.cancel_updater.update_draining(self.in_flight.len());
425 }
426}
427
428#[allow(dead_code)]
430pub(crate) struct TransferNotifiers {
431 pub(crate) status_tx: watch::Sender<TransferStatus>,
432 pub(crate) passed_tx: watch::Sender<Vec<BlockId>>,
433 pub(crate) completed_tx: watch::Sender<Vec<BlockId>>,
434 pub(crate) failed_tx: watch::Sender<Vec<BlockId>>,
435 pub(crate) remaining_tx: watch::Sender<Vec<BlockId>>,
436 pub(crate) result_tx: watch::Sender<Option<TransferResult>>,
437}
438
439#[cfg(test)]
440mod tests {
441 use super::*;
442
443 #[test]
444 fn test_transfer_id() {
445 let id1 = TransferId::new();
446 let id2 = TransferId::new();
447 assert_ne!(id1, id2);
448 }
449
450 #[test]
451 fn test_transfer_status() {
452 assert!(!TransferStatus::Evaluating.is_terminal());
453 assert!(!TransferStatus::Queued.is_terminal());
454 assert!(!TransferStatus::Transferring.is_terminal());
455 assert!(TransferStatus::Complete.is_terminal());
456 assert!(TransferStatus::Cancelled.is_terminal());
457 assert!(TransferStatus::Failed.is_terminal());
458 }
459
460 #[test]
461 fn test_transfer_state_creation() {
462 let id = TransferId::new();
463 let blocks = vec![1, 2, 3];
464 let (state, handle) = TransferState::new(id, blocks.clone());
465
466 assert_eq!(state.id, id);
467 assert_eq!(state.status, TransferStatus::Evaluating);
468 assert_eq!(state.input_blocks, blocks);
469 assert!(state.passed_blocks.is_empty());
470 assert!(state.completed.is_empty());
471
472 assert_eq!(handle.id(), id);
473 assert_eq!(handle.status(), TransferStatus::Evaluating);
474 assert_eq!(handle.remaining_blocks(), blocks);
475 }
476
477 #[test]
478 fn test_transfer_state_progress() {
479 let id = TransferId::new();
480 let blocks = vec![1, 2, 3, 4, 5];
481 let (mut state, handle) = TransferState::new(id, blocks);
482
483 state.add_passed(vec![1, 2, 3]);
485 state.add_filtered(vec![4, 5]);
486 assert_eq!(handle.passed_blocks(), vec![1, 2, 3]);
487
488 state.set_status(TransferStatus::Transferring);
490 state.mark_in_flight(vec![1, 2]);
491 assert_eq!(handle.status(), TransferStatus::Transferring);
492
493 state.mark_completed(vec![1]);
495 assert_eq!(handle.completed_blocks(), vec![1]);
496 assert_eq!(state.in_flight_count(), 1);
497
498 state.mark_completed(vec![2, 3]);
500 state.set_complete();
501
502 assert_eq!(handle.status(), TransferStatus::Complete);
503 assert_eq!(handle.completed_blocks(), vec![1, 2, 3]);
504 }
505
506 #[tokio::test]
507 async fn test_transfer_handle_wait() {
508 let id = TransferId::new();
509 let blocks = vec![1, 2, 3];
510 let (mut state, mut handle) = TransferState::new(id, blocks);
511
512 tokio::spawn(async move {
514 tokio::time::sleep(tokio::time::Duration::from_millis(10)).await;
515 state.add_passed(vec![1, 2, 3]);
516 state.mark_completed(vec![1, 2, 3]);
517 state.set_complete();
518 });
519
520 let result = tokio::time::timeout(tokio::time::Duration::from_millis(100), handle.wait())
522 .await
523 .expect("Should complete within timeout")
524 .expect("Should succeed");
525
526 assert_eq!(result.status, TransferStatus::Complete);
527 assert_eq!(result.completed_blocks, vec![1, 2, 3]);
528 }
529
530 #[test]
531 fn test_mark_failed_removes_from_in_flight() {
532 let id = TransferId::new();
533 let blocks = vec![1, 2, 3];
534 let (mut state, handle) = TransferState::new(id, blocks);
535
536 state.add_passed(vec![1, 2, 3]);
537 state.mark_in_flight(vec![1, 2, 3]);
538 assert_eq!(state.in_flight_count(), 3);
539
540 state.mark_failed(vec![2]);
541 assert_eq!(state.in_flight_count(), 2);
542 assert_eq!(handle.failed_blocks(), vec![2]);
543 assert!(handle.completed_blocks().is_empty());
544 }
545
546 #[test]
547 fn test_mark_failed_updates_remaining() {
548 let id = TransferId::new();
549 let blocks = vec![1, 2, 3];
550 let (mut state, handle) = TransferState::new(id, blocks);
551
552 state.add_passed(vec![1, 2, 3]);
553 state.mark_in_flight(vec![1, 2, 3]);
554
555 state.mark_failed(vec![2]);
557 let remaining = handle.remaining_blocks();
558 assert!(remaining.contains(&1));
559 assert!(!remaining.contains(&2));
560 assert!(remaining.contains(&3));
561 }
562
563 #[test]
564 fn test_partial_failure_result() {
565 let id = TransferId::new();
566 let blocks = vec![1, 2, 3, 4, 5];
567 let (mut state, _handle) = TransferState::new(id, blocks);
568
569 state.add_passed(vec![1, 2, 3]);
570 state.add_filtered(vec![4, 5]);
571 state.mark_in_flight(vec![1, 2, 3]);
572
573 state.mark_completed(vec![1, 3]);
575 state.mark_failed(vec![2]);
576
577 assert_eq!(state.completed, vec![1, 3]);
578 assert_eq!(state.failed, vec![2]);
579 assert_eq!(state.in_flight_count(), 0);
580
581 let total = state.passed_blocks.len() + state.filtered_out.len();
583 let done = state.completed.len() + state.failed.len() + state.filtered_out.len();
584 assert_eq!(done, total);
585
586 let failed_count = state.failed.len();
588 assert!(failed_count > 0);
589 state.set_error(format!(
590 "{failed_count} blocks failed to transfer to object storage",
591 ));
592 assert_eq!(state.status, TransferStatus::Failed);
593 }
594
595 #[tokio::test]
596 async fn test_partial_failure_wait_result() {
597 let id = TransferId::new();
598 let blocks = vec![1, 2, 3];
599 let (mut state, mut handle) = TransferState::new(id, blocks);
600
601 tokio::spawn(async move {
602 tokio::time::sleep(tokio::time::Duration::from_millis(10)).await;
603 state.add_passed(vec![1, 2, 3]);
604 state.mark_in_flight(vec![1, 2, 3]);
605 state.mark_completed(vec![1, 3]);
606 state.mark_failed(vec![2]);
607 state.set_error("1 blocks failed to transfer to object storage".to_string());
608 });
609
610 let result = tokio::time::timeout(tokio::time::Duration::from_millis(100), handle.wait())
611 .await
612 .expect("Should complete within timeout")
613 .expect("Should succeed");
614
615 assert_eq!(result.status, TransferStatus::Failed);
616 assert_eq!(result.completed_blocks, vec![1, 3]);
617 assert_eq!(result.failed_blocks, vec![2]);
618 assert!(result.error.is_some());
619 }
620}