Skip to main content

subspace_farmer/single_disk_farm/
plotting.rs

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