1use crate::farm::{SectorExpirationDetails, SectorPlottingDetails, SectorUpdate};
2use crate::node_client::NodeClient;
3use crate::plotter::{Plotter, SectorPlottingProgress};
4use crate::single_disk_farm::direct_io_file_wrapper::DirectIoFileWrapper;
5use crate::single_disk_farm::metrics::{SectorState, SingleDiskFarmMetrics};
6use crate::single_disk_farm::{
7 BackgroundTaskError, Handlers, PlotMetadataHeader, RESERVED_PLOT_METADATA,
8};
9use ab_core_primitives::ed25519::Ed25519PublicKey;
10use ab_core_primitives::hashes::Blake3Hash;
11use ab_core_primitives::pieces::PieceOffset;
12use ab_core_primitives::sectors::{SectorId, SectorIndex};
13use ab_core_primitives::segments::{HistorySize, SegmentIndex};
14use ab_farmer_components::file_ext::FileExt;
15use ab_farmer_components::plotting::PlottedSector;
16use ab_farmer_components::sector::SectorMetadataChecksummed;
17use ab_farmer_components::shard_commitment::ShardCommitmentsRootsCache;
18use async_lock::{Mutex as AsyncMutex, RwLock as AsyncRwLock, Semaphore, SemaphoreGuard};
19use futures::channel::{mpsc, oneshot};
20use futures::stream::FuturesOrdered;
21use futures::{FutureExt, SinkExt, StreamExt, select};
22use parity_scale_codec::Encode;
23use parking_lot::RwLock;
24use rand::prelude::*;
25use std::collections::HashSet;
26use std::future::Future;
27use std::io;
28use std::num::NonZeroUsize;
29use std::ops::Range;
30use std::pin::pin;
31use std::sync::Arc as StdArc;
32use std::time::{Duration, Instant};
33use thiserror::Error;
34use tokio::sync::watch;
35use tokio::task;
36use tracing::{Instrument, debug, info, info_span, trace, warn};
37use triomphe::Arc;
38
39const FARMER_APP_INFO_RETRY_INTERVAL: Duration = Duration::from_millis(500);
40const PLOTTING_RETRY_DELAY: Duration = Duration::from_secs(1);
41
42pub(super) struct SectorToPlot {
43 sector_index: SectorIndex,
44 progress: f32,
46 last_queued: bool,
48 acknowledgement_sender: oneshot::Sender<()>,
49}
50
51#[derive(Debug, Error)]
53pub enum PlottingError {
54 #[error("Failed to retrieve farmer info: {error}")]
56 FailedToGetFarmerInfo {
57 error: anyhow::Error,
59 },
60 #[error("Failed to get super segment root: {error}")]
62 FailedToGetSuperSegmentRoot {
63 error: anyhow::Error,
65 },
66 #[error("Missing archived segment header: {segment_index}")]
68 MissingArchivedSegmentHeader {
69 segment_index: SegmentIndex,
71 },
72 #[error("Failed to subscribe to archived segments: {error}")]
74 FailedToSubscribeArchivedSegments {
75 error: anyhow::Error,
77 },
78 #[error("Low-level plotting error: {0}")]
80 LowLevel(String),
81 #[error("Plotting I/O error: {0}")]
83 Io(#[from] io::Error),
84 #[error("Background downloading panicked")]
86 BackgroundDownloadingPanicked,
87}
88
89pub(super) struct SectorPlottingOptions<'a, NC> {
90 pub(super) public_key: Ed25519PublicKey,
91 pub(super) shard_commitments_roots_cache: ShardCommitmentsRootsCache,
92 pub(super) node_client: &'a NC,
93 pub(super) pieces_in_sector: u16,
94 pub(super) sector_size: usize,
95 pub(super) plot_file: StdArc<DirectIoFileWrapper>,
96 pub(super) metadata_file: Arc<DirectIoFileWrapper>,
97 pub(super) handlers: &'a Handlers,
98 pub(super) global_mutex: &'a AsyncMutex<()>,
99 pub(super) plotter: StdArc<dyn Plotter>,
100 pub(super) metrics: Option<Arc<SingleDiskFarmMetrics>>,
101}
102
103pub(super) struct PlottingOptions<'a, NC> {
104 pub(super) metadata_header: PlotMetadataHeader,
105 pub(super) sectors_metadata: &'a AsyncRwLock<Vec<SectorMetadataChecksummed>>,
106 pub(super) sectors_being_modified: &'a AsyncRwLock<HashSet<SectorIndex>>,
107 pub(super) sectors_to_plot_receiver: mpsc::Receiver<SectorToPlot>,
108 pub(super) sector_plotting_options: SectorPlottingOptions<'a, NC>,
109 pub(super) max_plotting_sectors_per_farm: NonZeroUsize,
110}
111
112pub(super) async fn plotting<NC>(
117 plotting_options: PlottingOptions<'_, NC>,
118) -> Result<(), PlottingError>
119where
120 NC: NodeClient,
121{
122 let PlottingOptions {
123 mut metadata_header,
124 sectors_metadata,
125 sectors_being_modified,
126 mut sectors_to_plot_receiver,
127 sector_plotting_options,
128 max_plotting_sectors_per_farm,
129 } = plotting_options;
130
131 let sector_plotting_options = §or_plotting_options;
132 let plotting_semaphore = Semaphore::new(max_plotting_sectors_per_farm.get());
133 let max_used_history_size = RwLock::new(
134 sectors_metadata
135 .read()
136 .await
137 .iter()
138 .map(|sector_metadata| sector_metadata.history_size)
139 .max()
140 .unwrap_or(HistorySize::ONE),
141 );
142 let mut sectors_being_plotted = FuturesOrdered::new();
143 let (sector_plotting_result_sender, mut sector_plotting_result_receiver) = mpsc::unbounded();
146 let process_plotting_result_fut = async move {
147 while let Some(sector_plotting_result) = sector_plotting_result_receiver.next().await {
148 process_plotting_result(
149 sector_plotting_result,
150 sectors_metadata,
151 sectors_being_modified,
152 &mut metadata_header,
153 Arc::clone(§or_plotting_options.metadata_file),
154 )
155 .await?;
156 }
157
158 unreachable!(
159 "Stream will not end before the rest of the plotting process is shutting down"
160 );
161 };
162 let process_plotting_result_fut = process_plotting_result_fut.fuse();
163 let mut process_plotting_result_fut = pin!(process_plotting_result_fut);
164
165 loop {
168 select! {
169 maybe_sector_to_plot = sectors_to_plot_receiver.next() => {
170 let Some(sector_to_plot) = maybe_sector_to_plot else {
171 break;
172 };
173
174 let sector_index = sector_to_plot.sector_index;
175 let sector_plotting_init_fut = plot_single_sector(
176 sector_to_plot,
177 sector_plotting_options,
178 sectors_metadata,
179 sectors_being_modified,
180 &plotting_semaphore,
181 &max_used_history_size,
182 )
183 .instrument(info_span!("", %sector_index))
184 .fuse();
185 let mut sector_plotting_init_fut = pin!(sector_plotting_init_fut);
186
187 loop {
191 select! {
192 sector_plotting_init_result = sector_plotting_init_fut => {
193 let sector_plotting_fut = match sector_plotting_init_result {
194 PlotSingleSectorResult::Scheduled(future) => future,
195 PlotSingleSectorResult::Skipped => {
196 break;
197 }
198 PlotSingleSectorResult::FatalError(error) => {
199 return Err(error);
200 }
201 };
202 sectors_being_plotted.push_back(
203 sector_plotting_fut.instrument(info_span!("", %sector_index))
204 );
205 break;
206 }
207 maybe_sector_plotting_result = sectors_being_plotted.select_next_some() => {
208 sector_plotting_result_sender
209 .unbounded_send(maybe_sector_plotting_result?)
210 .expect("Sending means receiver is not dropped yet; qed");
211 }
212 result = process_plotting_result_fut => {
213 return result;
214 }
215 }
216 }
217 }
218 maybe_sector_plotting_result = sectors_being_plotted.select_next_some() => {
219 sector_plotting_result_sender
220 .unbounded_send(maybe_sector_plotting_result?)
221 .expect("Sending means receiver is not dropped yet; qed");
222 }
223 result = process_plotting_result_fut => {
224 return result;
225 }
226 }
227 }
228
229 Ok(())
230}
231
232async fn process_plotting_result(
233 sector_plotting_result: SectorPlottingResult<'_>,
234 sectors_metadata: &AsyncRwLock<Vec<SectorMetadataChecksummed>>,
235 sectors_being_modified: &AsyncRwLock<HashSet<SectorIndex>>,
236 metadata_header: &mut PlotMetadataHeader,
237 metadata_file: Arc<DirectIoFileWrapper>,
238) -> Result<(), PlottingError> {
239 let SectorPlottingResult {
240 sector_metadata,
241 replotting,
242 last_queued,
243 plotting_permit,
244 } = sector_plotting_result;
245
246 let sector_index = sector_metadata.sector_index;
247
248 {
249 let mut sectors_metadata = sectors_metadata.write().await;
250 if let Some(existing_sector_metadata) = sectors_metadata.get_mut(usize::from(sector_index))
252 {
253 *existing_sector_metadata = sector_metadata;
254 } else {
255 sectors_metadata.push(sector_metadata);
256 }
257 }
258
259 sectors_being_modified.write().await.remove(§or_index);
261
262 if u16::from(sector_index) + 1 > metadata_header.plotted_sector_count {
263 metadata_header.plotted_sector_count = u16::from(sector_index) + 1;
264
265 let encoded_metadata_header = metadata_header.encode();
266 let write_fut =
267 task::spawn_blocking(move || metadata_file.write_all_at(&encoded_metadata_header, 0));
268 write_fut.await.map_err(|error| {
269 PlottingError::LowLevel(format!("Failed to spawn blocking tokio task: {error}"))
270 })??;
271 }
272
273 if last_queued {
274 if replotting {
275 info!("Replotting complete");
276 } else {
277 info!("Initial plotting complete");
278 }
279 }
280
281 drop(plotting_permit);
282
283 Ok(())
284}
285
286enum PlotSingleSectorResult<F> {
287 Scheduled(F),
288 Skipped,
289 FatalError(PlottingError),
290}
291
292struct SectorPlottingResult<'a> {
293 sector_metadata: SectorMetadataChecksummed,
294 replotting: bool,
295 last_queued: bool,
296 plotting_permit: SemaphoreGuard<'a>,
297}
298
299async fn plot_single_sector<'a, NC>(
300 sector_to_plot: SectorToPlot,
301 sector_plotting_options: &'a SectorPlottingOptions<'a, NC>,
302 sectors_metadata: &'a AsyncRwLock<Vec<SectorMetadataChecksummed>>,
303 sectors_being_modified: &'a AsyncRwLock<HashSet<SectorIndex>>,
304 plotting_semaphore: &'a Semaphore,
305 max_used_history_size: &RwLock<HistorySize>,
306) -> PlotSingleSectorResult<
307 impl Future<Output = Result<SectorPlottingResult<'a>, PlottingError>> + 'a,
308>
309where
310 NC: NodeClient,
311{
312 let SectorPlottingOptions {
313 public_key,
314 shard_commitments_roots_cache,
315 node_client,
316 pieces_in_sector,
317 sector_size,
318 plot_file,
319 metadata_file,
320 handlers,
321 global_mutex,
322 plotter,
323 metrics,
324 } = sector_plotting_options;
325
326 let SectorToPlot {
327 sector_index,
328 progress,
329 last_queued,
330 acknowledgement_sender: _acknowledgement_sender,
331 } = sector_to_plot;
332 trace!("Preparing to plot sector");
333
334 {
336 let mut sectors_being_modified = sectors_being_modified.write().await;
337 if !sectors_being_modified.insert(sector_index) {
338 debug!("Skipped sector plotting, it is already in progress");
339 return PlotSingleSectorResult::Skipped;
340 }
341 }
342
343 let plotting_permit = plotting_semaphore.acquire().await;
344
345 let maybe_old_sector_metadata = sectors_metadata
346 .read()
347 .await
348 .get(usize::from(sector_index))
349 .cloned();
350 let replotting = maybe_old_sector_metadata.is_some();
351
352 if let Some(metrics) = metrics {
353 metrics.sector_plotting.inc();
354 }
355 let sector_state = SectorUpdate::Plotting(SectorPlottingDetails::Starting {
356 progress,
357 replotting,
358 last_queued,
359 });
360 handlers
361 .sector_update
362 .call_simple(&(sector_index, sector_state));
363
364 let start = Instant::now();
365
366 let mut protocol_info = loop {
372 let protocol_info = match node_client.farmer_app_info().await {
373 Ok(farmer_app_info) => farmer_app_info.protocol_info,
374 Err(error) => {
375 return PlotSingleSectorResult::FatalError(PlottingError::FailedToGetFarmerInfo {
376 error,
377 });
378 }
379 };
380
381 if let Some(old_sector_metadata) = &maybe_old_sector_metadata
382 && protocol_info.history_size <= old_sector_metadata.history_size
383 {
384 if protocol_info.min_sector_lifetime == HistorySize::ONE {
385 debug!(
386 current_history_size = %protocol_info.history_size,
387 old_sector_history_size = %old_sector_metadata.history_size,
388 "Latest protocol history size is not yet newer than old sector history \
389 size, wait for a bit and try again"
390 );
391 tokio::time::sleep(FARMER_APP_INFO_RETRY_INTERVAL).await;
392 continue;
393 }
394
395 debug!(
396 current_history_size = %protocol_info.history_size,
397 old_sector_history_size = %old_sector_metadata.history_size,
398 "Skipped sector plotting, likely redundant due to redundant archived \
399 segment notification"
400 );
401 return PlotSingleSectorResult::Skipped;
402 }
403
404 break protocol_info;
405 };
406
407 {
410 let mut max_used_history_size = max_used_history_size.write();
411 if maybe_old_sector_metadata
412 .as_ref()
413 .map(|sector_metadata| sector_metadata.history_size)
414 == Some(*max_used_history_size)
415 {
416 *max_used_history_size = protocol_info.history_size;
417 } else {
418 protocol_info.history_size = *max_used_history_size;
421 }
422 }
423
424 let (progress_sender, mut progress_receiver) = mpsc::channel(10);
425 let shard_commitments_root = shard_commitments_roots_cache.get(protocol_info.history_size);
426
427 plotter
429 .plot_sector(
430 *public_key,
431 shard_commitments_root,
432 sector_index,
433 protocol_info,
434 *pieces_in_sector,
435 replotting,
436 progress_sender,
437 )
438 .await;
439
440 if replotting {
441 info!("Replotting sector ({progress:.2}% complete)");
442 } else {
443 info!("Plotting sector ({progress:.2}% complete)");
444 }
445
446 PlotSingleSectorResult::Scheduled(async move {
447 let plotted_sector = loop {
448 match plot_single_sector_internal(
449 sector_index,
450 *sector_size,
451 plot_file,
452 metadata_file,
453 handlers,
454 global_mutex,
455 progress_receiver,
456 metrics.as_ref(),
457 )
458 .await?
459 {
460 Ok(plotted_sector) => {
461 break plotted_sector;
462 }
463 Err(error) => {
464 warn!(
465 %error,
466 "Failed to plot sector, retrying in {PLOTTING_RETRY_DELAY:?}"
467 );
468
469 tokio::time::sleep(PLOTTING_RETRY_DELAY).await;
470 }
471 }
472
473 let (retry_progress_sender, retry_progress_receiver) = mpsc::channel(10);
474 progress_receiver = retry_progress_receiver;
475
476 plotter
478 .plot_sector(
479 *public_key,
480 shard_commitments_root,
481 sector_index,
482 protocol_info,
483 *pieces_in_sector,
484 replotting,
485 retry_progress_sender,
486 )
487 .await;
488
489 if replotting {
490 info!("Replotting sector retry");
491 } else {
492 info!("Plotting sector retry");
493 }
494 };
495
496 let maybe_old_plotted_sector = maybe_old_sector_metadata.map(|old_sector_metadata| {
497 let old_history_size = old_sector_metadata.history_size;
498
499 PlottedSector {
500 sector_id: plotted_sector.sector_id,
501 sector_index: plotted_sector.sector_index,
502 sector_metadata: old_sector_metadata,
503 piece_indexes: {
504 let mut piece_indexes = Vec::with_capacity(usize::from(*pieces_in_sector));
505 (PieceOffset::ZERO..)
506 .take(usize::from(*pieces_in_sector))
507 .map(|piece_offset| {
508 plotted_sector.sector_id.derive_piece_index(
509 piece_offset,
510 old_history_size,
511 protocol_info.max_pieces_in_sector,
512 protocol_info.recent_segments,
513 protocol_info.recent_history_fraction,
514 )
515 })
516 .collect_into(&mut piece_indexes);
517 piece_indexes
518 },
519 }
520 });
521
522 if replotting {
523 debug!("Sector replotted successfully");
524 } else {
525 debug!("Sector plotted successfully");
526 }
527
528 let sector_metadata = plotted_sector.sector_metadata.clone();
529
530 let time = start.elapsed();
531 if let Some(metrics) = metrics {
532 metrics.sector_plotting_time.observe(time.as_secs_f64());
533 metrics.sector_plotted.inc();
534 metrics.update_sector_state(SectorState::Plotted);
535 }
536 let sector_state = SectorUpdate::Plotting(SectorPlottingDetails::Finished {
537 plotted_sector,
538 old_plotted_sector: maybe_old_plotted_sector,
539 time,
540 });
541 handlers
542 .sector_update
543 .call_simple(&(sector_index, sector_state));
544
545 Ok(SectorPlottingResult {
546 sector_metadata,
547 replotting,
548 last_queued,
549 plotting_permit,
550 })
551 })
552}
553
554#[expect(clippy::too_many_arguments, reason = "Internal API")]
557async fn plot_single_sector_internal(
558 sector_index: SectorIndex,
559 sector_size: usize,
560 plot_file: &StdArc<DirectIoFileWrapper>,
561 metadata_file: &Arc<DirectIoFileWrapper>,
562 handlers: &Handlers,
563 global_mutex: &AsyncMutex<()>,
564 mut progress_receiver: mpsc::Receiver<SectorPlottingProgress>,
565 metrics: Option<&Arc<SingleDiskFarmMetrics>>,
566) -> Result<Result<PlottedSector, PlottingError>, PlottingError> {
567 let progress_processor_fut = async {
569 while let Some(progress) = progress_receiver.next().await {
570 match progress {
571 SectorPlottingProgress::Downloading => {
572 if let Some(metrics) = metrics {
573 metrics.sector_downloading.inc();
574 }
575 handlers.sector_update.call_simple(&(
576 sector_index,
577 SectorUpdate::Plotting(SectorPlottingDetails::Downloading),
578 ));
579 }
580 SectorPlottingProgress::Downloaded(time) => {
581 if let Some(metrics) = metrics {
582 metrics.sector_downloading_time.observe(time.as_secs_f64());
583 metrics.sector_downloaded.inc();
584 }
585 handlers.sector_update.call_simple(&(
586 sector_index,
587 SectorUpdate::Plotting(SectorPlottingDetails::Downloaded(time)),
588 ));
589 }
590 SectorPlottingProgress::Encoding => {
591 if let Some(metrics) = metrics {
592 metrics.sector_encoding.inc();
593 }
594 handlers.sector_update.call_simple(&(
595 sector_index,
596 SectorUpdate::Plotting(SectorPlottingDetails::Encoding),
597 ));
598 }
599 SectorPlottingProgress::Encoded(time) => {
600 if let Some(metrics) = metrics {
601 metrics.sector_encoding_time.observe(time.as_secs_f64());
602 metrics.sector_encoded.inc();
603 }
604 handlers.sector_update.call_simple(&(
605 sector_index,
606 SectorUpdate::Plotting(SectorPlottingDetails::Encoded(time)),
607 ));
608 }
609 SectorPlottingProgress::Finished {
610 plotted_sector,
611 time: _,
612 sector,
613 } => {
614 return Ok((plotted_sector, sector));
615 }
616 SectorPlottingProgress::Error { error } => {
617 if let Some(metrics) = metrics {
618 metrics.sector_plotting_error.inc();
619 }
620 handlers.sector_update.call_simple(&(
621 sector_index,
622 SectorUpdate::Plotting(SectorPlottingDetails::Error(error.clone())),
623 ));
624 return Err(error);
625 }
626 }
627 }
628
629 Err("Plotting progress stream ended before plotting finished".to_string())
630 };
631
632 let (plotted_sector, mut sector) = match progress_processor_fut.await {
633 Ok(result) => result,
634 Err(error) => {
635 return Ok(Err(PlottingError::LowLevel(error)));
636 }
637 };
638
639 {
640 global_mutex.lock().await;
642
643 if let Some(metrics) = metrics {
644 metrics.sector_writing.inc();
645 }
646 handlers.sector_update.call_simple(&(
647 sector_index,
648 SectorUpdate::Plotting(SectorPlottingDetails::Writing),
649 ));
650
651 let start = Instant::now();
652
653 {
654 let sector_write_base_offset = u64::from(sector_index) * sector_size as u64;
655 let mut total_received = 0;
656 let mut sector_write_offset = sector_write_base_offset;
657 while let Some(maybe_sector_chunk) = sector.next().await {
658 let sector_chunk = match maybe_sector_chunk {
659 Ok(sector_chunk) => sector_chunk,
660 Err(error) => {
661 return Ok(Err(PlottingError::LowLevel(format!(
662 "Sector chunk receive error: {error}"
663 ))));
664 }
665 };
666
667 total_received += sector_chunk.len();
668
669 if total_received > sector_size {
670 return Ok(Err(PlottingError::LowLevel(format!(
671 "Received too many bytes {total_received} instead of expected \
672 {sector_size} bytes"
673 ))));
674 }
675
676 let sector_chunk_size = sector_chunk.len() as u64;
677
678 trace!(sector_chunk_size, "Writing sector chunk to disk");
679 let write_fut = task::spawn_blocking({
680 let plot_file = StdArc::clone(plot_file);
681
682 move || plot_file.write_all_at(§or_chunk, sector_write_offset)
683 });
684 write_fut.await.map_err(|error| {
685 PlottingError::LowLevel(format!("Failed to spawn blocking tokio task: {error}"))
686 })??;
687
688 sector_write_offset += sector_chunk_size;
689 }
690 drop(sector);
691
692 if total_received != sector_size {
693 return Ok(Err(PlottingError::LowLevel(format!(
694 "Received only {total_received} sector bytes out of {sector_size} \
695 expected bytes"
696 ))));
697 }
698 }
699 {
700 let encoded_sector_metadata = plotted_sector.sector_metadata.encode();
701 let write_fut = task::spawn_blocking({
702 let metadata_file = Arc::clone(metadata_file);
703
704 move || {
705 metadata_file.write_all_at(
706 &encoded_sector_metadata,
707 RESERVED_PLOT_METADATA
708 + (u64::from(sector_index) * encoded_sector_metadata.len() as u64),
709 )
710 }
711 });
712 write_fut.await.map_err(|error| {
713 PlottingError::LowLevel(format!("Failed to spawn blocking tokio task: {error}"))
714 })??;
715 }
716
717 let time = start.elapsed();
718 if let Some(metrics) = metrics {
719 metrics.sector_writing_time.observe(time.as_secs_f64());
720 metrics.sector_written.inc();
721 }
722 handlers.sector_update.call_simple(&(
723 sector_index,
724 SectorUpdate::Plotting(SectorPlottingDetails::Written(time)),
725 ));
726 }
727
728 Ok(Ok(plotted_sector))
729}
730
731pub(super) struct PlottingSchedulerOptions<NC> {
732 pub(super) public_key_hash: Blake3Hash,
733 pub(super) shard_commitments_roots_cache: ShardCommitmentsRootsCache,
734 pub(super) sectors_indices_left_to_plot: Range<SectorIndex>,
735 pub(super) target_sector_count: u16,
736 pub(super) last_archived_segment_index: SegmentIndex,
737 pub(super) min_sector_lifetime: HistorySize,
738 pub(super) node_client: NC,
739 pub(super) handlers: Arc<Handlers>,
740 pub(super) sectors_metadata: StdArc<AsyncRwLock<Vec<SectorMetadataChecksummed>>>,
741 pub(super) sectors_to_plot_sender: mpsc::Sender<SectorToPlot>,
742 pub(super) new_segment_processing_delay: Duration,
745 pub(super) metrics: Option<Arc<SingleDiskFarmMetrics>>,
746}
747
748pub(super) async fn plotting_scheduler<NC>(
749 plotting_scheduler_options: PlottingSchedulerOptions<NC>,
750) -> Result<(), BackgroundTaskError>
751where
752 NC: NodeClient,
753{
754 let PlottingSchedulerOptions {
755 public_key_hash,
756 shard_commitments_roots_cache,
757 sectors_indices_left_to_plot,
758 target_sector_count,
759 last_archived_segment_index,
760 min_sector_lifetime,
761 node_client,
762 handlers,
763 sectors_metadata,
764 sectors_to_plot_sender,
765 new_segment_processing_delay,
766 metrics,
767 } = plotting_scheduler_options;
768
769 let (new_segment_index_sender, new_segment_index_receiver) =
772 watch::channel(last_archived_segment_index);
773
774 let read_new_super_segments_notifications_fut = read_new_segment_index_notifications(
775 &node_client,
776 new_segment_index_sender,
777 new_segment_processing_delay,
778 );
779
780 let send_plotting_notifications_fut = send_plotting_notifications(
781 public_key_hash,
782 &shard_commitments_roots_cache,
783 sectors_indices_left_to_plot,
784 target_sector_count,
785 min_sector_lifetime,
786 &node_client,
787 &handlers,
788 sectors_metadata,
789 new_segment_index_receiver,
790 sectors_to_plot_sender,
791 metrics.as_ref(),
792 );
793
794 select! {
795 result = read_new_super_segments_notifications_fut.fuse() => {
796 result
797 }
798 result = send_plotting_notifications_fut.fuse() => {
799 result
800 }
801 }
802}
803
804async fn read_new_segment_index_notifications<NC>(
805 node_client: &NC,
806 new_super_segments_sender: watch::Sender<SegmentIndex>,
807 new_segment_processing_delay: Duration,
808) -> Result<(), BackgroundTaskError>
809where
810 NC: NodeClient,
811{
812 info!("Subscribing to new super segments");
813
814 let mut super_segment_headers_notifications = node_client
815 .subscribe_new_super_segment_headers()
816 .await
817 .map_err(|error| PlottingError::FailedToSubscribeArchivedSegments { error })?;
818
819 while let Some(super_segment_header) = super_segment_headers_notifications.next().await {
820 debug!(?super_segment_header, "New super segment");
821
822 let delay = Duration::from_secs(rand::rng().random_range(
825 new_segment_processing_delay.as_secs() / 10..=new_segment_processing_delay.as_secs(),
826 ));
827 tokio::time::sleep(delay).await;
828
829 if new_super_segments_sender
830 .send(super_segment_header.max_segment_index.as_inner())
831 .is_err()
832 {
833 break;
834 }
835 }
836
837 Ok(())
838}
839
840struct SectorToReplot {
841 sector_index: SectorIndex,
842 expires_at: SegmentIndex,
843}
844
845#[expect(clippy::too_many_arguments, reason = "Internal API")]
846async fn send_plotting_notifications<NC>(
847 public_key_hash: Blake3Hash,
848 shard_commitments_roots_cache: &ShardCommitmentsRootsCache,
849 sectors_indices_left_to_plot: Range<SectorIndex>,
850 target_sector_count: u16,
851 min_sector_lifetime: HistorySize,
852 node_client: &NC,
853 handlers: &Handlers,
854 sectors_metadata: StdArc<AsyncRwLock<Vec<SectorMetadataChecksummed>>>,
855 mut new_segment_index_receiver: watch::Receiver<SegmentIndex>,
856 mut sectors_to_plot_sender: mpsc::Sender<SectorToPlot>,
857 metrics: Option<&Arc<SingleDiskFarmMetrics>>,
858) -> Result<(), BackgroundTaskError>
859where
860 NC: NodeClient,
861{
862 for sector_index in sectors_indices_left_to_plot {
864 let (acknowledgement_sender, acknowledgement_receiver) = oneshot::channel();
865 if let Err(error) = sectors_to_plot_sender
866 .send(SectorToPlot {
867 sector_index,
868 progress: f32::from(u16::from(sector_index)) / f32::from(target_sector_count)
869 * 100.0,
870 last_queued: u16::from(sector_index) + 1 == target_sector_count,
871 acknowledgement_sender,
872 })
873 .await
874 {
875 warn!(%error, "Failed to send sector index for initial plotting");
876 return Ok(());
877 }
878
879 let _: Result<(), _> = acknowledgement_receiver.await;
881 }
882
883 let mut sectors_expire_at = vec![None::<SegmentIndex>; usize::from(target_sector_count)];
884 let mut sectors_to_replot = Vec::with_capacity(usize::from(target_sector_count) / 10);
886
887 loop {
888 let segment_index = *new_segment_index_receiver.borrow_and_update();
889 trace!(%segment_index, "New archived segment received");
890
891 let sectors_metadata = sectors_metadata.read().await;
892 let sectors_to_check = sectors_metadata
893 .iter()
894 .map(|sector_metadata| (sector_metadata.sector_index, sector_metadata.history_size));
895 for (sector_index, history_size) in sectors_to_check {
896 if let Some(Some(expires_at)) =
897 sectors_expire_at.get(usize::from(sector_index)).copied()
898 {
899 trace!(
900 %sector_index,
901 %history_size,
902 %expires_at,
903 "Checking sector for expiration"
904 );
905 if expires_at <= (segment_index + SegmentIndex::ONE) {
908 debug!(
909 %sector_index,
910 %history_size,
911 %expires_at,
912 "Sector expires soon #1, scheduling replotting"
913 );
914
915 let expiration_details = if expires_at <= segment_index {
916 if let Some(metrics) = metrics {
917 metrics.update_sector_state(SectorState::Expired);
918 }
919 SectorExpirationDetails::Expired
920 } else {
921 if let Some(metrics) = metrics {
922 metrics.update_sector_state(SectorState::AboutToExpire);
923 }
924 SectorExpirationDetails::AboutToExpire
925 };
926 handlers
927 .sector_update
928 .call_simple(&(sector_index, SectorUpdate::Expiration(expiration_details)));
929
930 sectors_to_replot.push(SectorToReplot {
932 sector_index,
933 expires_at,
934 });
935 }
936 continue;
937 }
938
939 let shard_commitments_root = shard_commitments_roots_cache.get(history_size);
940 if let Some(expiration_check_segment_index) = history_size
941 .sector_expiration_check(min_sector_lifetime)
942 .map(|expiration_check_history_size| expiration_check_history_size.segment_index())
943 {
944 trace!(
945 %sector_index,
946 %history_size,
947 %expiration_check_segment_index,
948 "Determined sector expiration check segment index"
949 );
950 let maybe_sector_expiration_check_super_segment_root = node_client
951 .super_segment_root_for_segment_index(expiration_check_segment_index)
952 .await
953 .map_err(|error| PlottingError::FailedToGetSuperSegmentRoot { error })?;
954
955 if let Some(sector_expiration_check_super_segment_root) =
956 maybe_sector_expiration_check_super_segment_root
957 {
958 let sector_id = SectorId::new(
959 &public_key_hash,
960 &shard_commitments_root,
961 sector_index,
962 history_size,
963 );
964 let expiration_history_size = sector_id
965 .derive_expiration_history_size(
966 history_size,
967 §or_expiration_check_super_segment_root,
968 min_sector_lifetime,
969 )
970 .expect(
971 "Farmers internally stores correct history size in sector \
972 metadata; qed",
973 );
974
975 let expires_at = expiration_history_size.segment_index();
976
977 trace!(
978 %sector_index,
979 %history_size,
980 sector_expire_at = %expires_at,
981 "Determined sector expiration segment index"
982 );
983 if expires_at <= (segment_index + SegmentIndex::ONE) {
986 debug!(
987 %sector_index,
988 %history_size,
989 %expires_at,
990 "Sector expires soon #2, scheduling replotting"
991 );
992
993 let expiration_details = if expires_at <= segment_index {
994 if let Some(metrics) = metrics {
995 metrics.update_sector_state(SectorState::Expired);
996 }
997 SectorExpirationDetails::Expired
998 } else {
999 if let Some(metrics) = metrics {
1000 metrics.update_sector_state(SectorState::AboutToExpire);
1001 }
1002 SectorExpirationDetails::AboutToExpire
1003 };
1004 handlers.sector_update.call_simple(&(
1005 sector_index,
1006 SectorUpdate::Expiration(expiration_details),
1007 ));
1008
1009 sectors_to_replot.push(SectorToReplot {
1011 sector_index,
1012 expires_at,
1013 });
1014 } else {
1015 trace!(
1016 %sector_index,
1017 %history_size,
1018 sector_expire_at = %expires_at,
1019 "Sector expires later, remembering sector expiration"
1020 );
1021
1022 handlers.sector_update.call_simple(&(
1023 sector_index,
1024 SectorUpdate::Expiration(SectorExpirationDetails::Determined {
1025 expires_at,
1026 }),
1027 ));
1028
1029 if let Some(expires_at_entry) =
1031 sectors_expire_at.get_mut(usize::from(sector_index))
1032 {
1033 expires_at_entry.replace(expires_at);
1034 }
1035 }
1036 }
1037 }
1038 }
1039 drop(sectors_metadata);
1040
1041 let sectors_queued = sectors_to_replot.len();
1042 sectors_to_replot.sort_by_key(|sector_to_replot| sector_to_replot.expires_at);
1043 for (
1044 index,
1045 SectorToReplot {
1046 sector_index,
1047 expires_at: _,
1048 },
1049 ) in sectors_to_replot.drain(..).enumerate()
1050 {
1051 let (acknowledgement_sender, acknowledgement_receiver) = oneshot::channel();
1052 if let Err(error) = sectors_to_plot_sender
1053 .send(SectorToPlot {
1054 sector_index,
1055 progress: index as f32 / sectors_queued as f32 * 100.0,
1056 last_queued: index + 1 == sectors_queued,
1057 acknowledgement_sender,
1058 })
1059 .await
1060 {
1061 warn!(%error, "Failed to send sector index for replotting");
1062 return Ok(());
1063 }
1064
1065 let _: Result<(), _> = acknowledgement_receiver.await;
1067
1068 if let Some(expires_at_entry) = sectors_expire_at.get_mut(usize::from(sector_index)) {
1069 expires_at_entry.take();
1070 }
1071 }
1072
1073 if new_segment_index_receiver.changed().await.is_err() {
1074 break;
1075 }
1076 }
1077
1078 Ok(())
1079}