Skip to main content

ab_farmer/
single_disk_farm.rs

1//! Primary [`Farm`] implementation that deals with hardware directly
2//!
3//! Single disk farm is an abstraction that contains an identity, associated plot with metadata and
4//! a small piece cache. It fully manages farming and plotting process, including listening to node
5//! notifications, producing solutions and sealing blocks.
6
7mod block_sealing;
8pub mod direct_io_file_wrapper;
9pub mod farming;
10pub mod identity;
11mod metrics;
12pub mod piece_cache;
13pub mod piece_reader;
14pub mod plot_cache;
15mod plotted_sectors;
16mod plotting;
17#[cfg(test)]
18mod tests;
19
20use crate::disk_piece_cache::{DiskPieceCache, DiskPieceCacheError};
21use crate::farm::{
22    Farm, FarmId, FarmingError, FarmingNotification, HandlerFn, PieceCacheId, PieceReader,
23    PlottedSectors, SectorUpdate,
24};
25use crate::node_client::NodeClient;
26use crate::plotter::Plotter;
27use crate::single_disk_farm::block_sealing::block_sealing;
28use crate::single_disk_farm::direct_io_file_wrapper::DirectIoFileWrapper;
29use crate::single_disk_farm::farming::rayon_files::RayonFiles;
30use crate::single_disk_farm::farming::{
31    FarmingOptions, PlotAudit, farming, slot_notification_forwarder,
32};
33use crate::single_disk_farm::identity::{Identity, IdentityError};
34use crate::single_disk_farm::metrics::SingleDiskFarmMetrics;
35use crate::single_disk_farm::piece_cache::SingleDiskPieceCache;
36use crate::single_disk_farm::piece_reader::DiskPieceReader;
37use crate::single_disk_farm::plot_cache::DiskPlotCache;
38use crate::single_disk_farm::plotted_sectors::SingleDiskPlottedSectors;
39pub use crate::single_disk_farm::plotting::PlottingError;
40use crate::single_disk_farm::plotting::{
41    PlottingOptions, PlottingSchedulerOptions, SectorPlottingOptions, plotting, plotting_scheduler,
42};
43use crate::utils::{AsyncJoinOnDrop, tokio_rayon_spawn_handler};
44use crate::{KNOWN_PEERS_CACHE_SIZE, farm};
45use ab_core_primitives::address::Address;
46use ab_core_primitives::block::BlockRoot;
47use ab_core_primitives::ed25519::Ed25519PublicKey;
48use ab_core_primitives::hashes::Blake3Hash;
49use ab_core_primitives::pieces::{Piece, PieceIndex, Record};
50use ab_core_primitives::sectors::SectorIndex;
51use ab_core_primitives::segments::{HistorySize, SegmentIndex};
52use ab_direct_io_file::AlignedPage;
53use ab_erasure_coding::ErasureCoding;
54use ab_farmer_components::FarmerProtocolInfo;
55use ab_farmer_components::file_ext::FileExt;
56use ab_farmer_components::sector::{SectorMetadata, SectorMetadataChecksummed, sector_size};
57use ab_farmer_components::shard_commitment::ShardCommitmentsRootsCache;
58use ab_farmer_rpc_primitives::{FarmerAppInfo, SolutionResponse};
59use ab_networking::KnownPeersManager;
60use ab_proof_of_space::Table;
61use async_lock::{Mutex as AsyncMutex, RwLock as AsyncRwLock};
62use async_trait::async_trait;
63use bytesize::ByteSize;
64use event_listener_primitives::{Bag, HandlerId};
65use futures::channel::{mpsc, oneshot};
66use futures::stream::FuturesUnordered;
67use futures::{FutureExt, StreamExt, select};
68use parity_scale_codec::{Decode, Encode};
69use parking_lot::Mutex;
70use prometheus_client::registry::Registry;
71use rayon::prelude::*;
72use rayon::{ThreadPoolBuildError, ThreadPoolBuilder};
73use serde::{Deserialize, Serialize};
74use std::collections::HashSet;
75use std::fs::{File, OpenOptions};
76use std::future::Future;
77use std::io::Write;
78use std::num::{NonZeroU32, NonZeroUsize};
79use std::path::{Path, PathBuf};
80use std::pin::Pin;
81use std::str::FromStr;
82use std::sync::Arc as StdArc;
83use std::sync::atomic::{AtomicUsize, Ordering};
84use std::time::Duration;
85use std::{fmt, fs, io};
86use thiserror::Error;
87use tokio::runtime::Handle;
88use tokio::sync::broadcast;
89use tokio::task;
90use tracing::{Instrument, Span, error, info, trace, warn};
91use triomphe::Arc;
92
93// Refuse to compile on non-64-bit platforms, offsets may fail on those when converting from u64 to
94// usize depending on chain parameters
95const {
96    assert!(size_of::<usize>() >= size_of::<u64>());
97}
98
99/// Reserve 1M of space for plot metadata (for potential future expansion)
100const RESERVED_PLOT_METADATA: u64 = 1024 * 1024;
101/// Reserve 1M of space for farm info (for potential future expansion)
102const RESERVED_FARM_INFO: u64 = 1024 * 1024;
103const NEW_SEGMENT_PROCESSING_DELAY: Duration = Duration::from_mins(10);
104
105/// Exclusive lock for single disk farm info file, ensuring no concurrent edits by cooperating
106/// processes is done
107#[derive(Debug)]
108#[must_use = "Lock file must be kept around or as long as farm is used"]
109pub struct SingleDiskFarmInfoLock {
110    _file: File,
111}
112
113/// Important information about the contents of the `SingleDiskFarm`
114#[derive(Debug, Copy, Clone, Serialize, Deserialize)]
115#[serde(rename_all = "camelCase")]
116pub enum SingleDiskFarmInfo {
117    /// V0 of the info
118    #[serde(rename_all = "camelCase")]
119    V0 {
120        /// ID of the farm
121        id: FarmId,
122        /// Genesis root of the beacon chain used for farm creation
123        genesis_root: BlockRoot,
124        /// Public key of identity used for farm creation
125        public_key: Ed25519PublicKey,
126        /// Seed used for deriving shard commitments
127        shard_commitments_seed: Blake3Hash,
128        /// How many pieces does one sector contain.
129        pieces_in_sector: u16,
130        /// How much space in bytes is allocated for this farm
131        allocated_space: u64,
132    },
133}
134
135impl SingleDiskFarmInfo {
136    const FILE_NAME: &'static str = "single_disk_farm.json";
137
138    /// Create a new instance
139    pub fn new(
140        id: FarmId,
141        genesis_root: BlockRoot,
142        public_key: Ed25519PublicKey,
143        shard_commitments_seed: Blake3Hash,
144        pieces_in_sector: u16,
145        allocated_space: u64,
146    ) -> Self {
147        Self::V0 {
148            id,
149            genesis_root,
150            public_key,
151            shard_commitments_seed,
152            pieces_in_sector,
153            allocated_space,
154        }
155    }
156
157    /// Load `SingleDiskFarm` from path is supposed to be stored, `None` means no info file was
158    /// found, happens during first start.
159    pub fn load_from(directory: &Path) -> io::Result<Option<Self>> {
160        let bytes = match fs::read(directory.join(Self::FILE_NAME)) {
161            Ok(bytes) => bytes,
162            Err(error) => {
163                return if error.kind() == io::ErrorKind::NotFound {
164                    Ok(None)
165                } else {
166                    Err(error)
167                };
168            }
169        };
170
171        serde_json::from_slice(&bytes)
172            .map(Some)
173            .map_err(|error| io::Error::new(io::ErrorKind::InvalidData, error))
174    }
175
176    /// Store `SingleDiskFarm` info to path, so it can be loaded again upon restart.
177    ///
178    /// Can optionally return a lock.
179    pub fn store_to(
180        &self,
181        directory: &Path,
182        lock: bool,
183    ) -> io::Result<Option<SingleDiskFarmInfoLock>> {
184        let mut file = OpenOptions::new()
185            .write(true)
186            .create(true)
187            .truncate(false)
188            .open(directory.join(Self::FILE_NAME))?;
189        if lock {
190            fs4::FileExt::try_lock(&file)?;
191        }
192        file.set_len(0)?;
193        file.write_all(&serde_json::to_vec(self).expect("Info serialization never fails; qed"))?;
194
195        Ok(lock.then_some(SingleDiskFarmInfoLock { _file: file }))
196    }
197
198    /// Try to acquire exclusive lock on the single disk farm info file, ensuring no concurrent
199    /// edits by cooperating processes is done
200    pub fn try_lock(directory: &Path) -> io::Result<SingleDiskFarmInfoLock> {
201        let file = File::open(directory.join(Self::FILE_NAME))?;
202        fs4::FileExt::try_lock(&file)?;
203
204        Ok(SingleDiskFarmInfoLock { _file: file })
205    }
206
207    /// ID of the farm
208    #[expect(
209        clippy::rest_pattern_accessible_field,
210        reason = "Do not need other fields"
211    )]
212    pub fn id(&self) -> &FarmId {
213        let Self::V0 { id, .. } = self;
214        id
215    }
216
217    /// Genesis hash of the chain used for farm creation
218    #[expect(
219        clippy::rest_pattern_accessible_field,
220        reason = "Do not need other fields"
221    )]
222    pub fn genesis_root(&self) -> &BlockRoot {
223        let Self::V0 { genesis_root, .. } = self;
224        genesis_root
225    }
226
227    /// Public key of identity used for farm creation
228    #[expect(
229        clippy::rest_pattern_accessible_field,
230        reason = "Do not need other fields"
231    )]
232    pub fn public_key(&self) -> &Ed25519PublicKey {
233        let Self::V0 { public_key, .. } = self;
234        public_key
235    }
236
237    /// Seed used for deriving shard commitments
238    #[expect(
239        clippy::rest_pattern_accessible_field,
240        reason = "Do not need other fields"
241    )]
242    pub fn shard_commitments_seed(&self) -> &Blake3Hash {
243        let Self::V0 {
244            shard_commitments_seed,
245            ..
246        } = self;
247        shard_commitments_seed
248    }
249
250    /// How many pieces does one sector contain
251    #[expect(
252        clippy::rest_pattern_accessible_field,
253        reason = "Do not need other fields"
254    )]
255    pub fn pieces_in_sector(&self) -> u16 {
256        match self {
257            SingleDiskFarmInfo::V0 {
258                pieces_in_sector, ..
259            } => *pieces_in_sector,
260        }
261    }
262
263    /// How much space in bytes is allocated for this farm
264    #[expect(
265        clippy::rest_pattern_accessible_field,
266        reason = "Do not need other fields"
267    )]
268    pub fn allocated_space(&self) -> u64 {
269        match self {
270            SingleDiskFarmInfo::V0 {
271                allocated_space, ..
272            } => *allocated_space,
273        }
274    }
275}
276
277/// Summary of single disk farm for presentational purposes
278#[derive(Debug)]
279pub enum SingleDiskFarmSummary {
280    /// Farm was found and read successfully
281    Found {
282        /// Farm info
283        info: SingleDiskFarmInfo,
284        /// Path to directory where farm is stored.
285        directory: PathBuf,
286    },
287    /// Farm was not found
288    NotFound {
289        /// Path to directory where farm is stored.
290        directory: PathBuf,
291    },
292    /// Failed to open farm
293    Error {
294        /// Path to directory where farm is stored.
295        directory: PathBuf,
296        /// Error itself
297        error: io::Error,
298    },
299}
300
301#[derive(Debug, Encode, Decode)]
302struct PlotMetadataHeader {
303    version: u8,
304    plotted_sector_count: u16,
305}
306
307impl PlotMetadataHeader {
308    #[inline]
309    fn encoded_size() -> usize {
310        let default = PlotMetadataHeader {
311            version: 0,
312            plotted_sector_count: 0,
313        };
314
315        default.encoded_size()
316    }
317
318    /// Encode header into a whole disk page, padded with zeroes, such that it can be written
319    /// without read-modify-write
320    fn encode_page(&self) -> [u8; AlignedPage::SIZE] {
321        let mut page = [0; AlignedPage::SIZE];
322        self.encode_to(&mut page.as_mut_slice());
323        page
324    }
325}
326
327/// Options used to open single disk farm
328#[derive(Debug)]
329pub struct SingleDiskFarmOptions<'a, NC>
330where
331    NC: Clone,
332{
333    /// Path to directory where farm is stored.
334    pub directory: PathBuf,
335    /// Information necessary for farmer application
336    pub farmer_app_info: FarmerAppInfo,
337    /// The amount of space in bytes that was allocated
338    pub allocated_space: u64,
339    /// How many pieces one sector is supposed to contain (max)
340    pub max_pieces_in_sector: u16,
341    /// RPC client connected to the node
342    pub node_client: NC,
343    /// Address where farming rewards should go
344    pub reward_address: Address,
345    /// Plotter
346    pub plotter: StdArc<dyn Plotter + Send + Sync>,
347    /// Erasure coding instance to use.
348    pub erasure_coding: ErasureCoding,
349    /// Percentage of allocated space dedicated for caching purposes
350    pub cache_percentage: u8,
351    /// Thread pool size used for farming (mostly for blocking I/O, but also for some
352    /// compute-intensive operations during proving)
353    pub farming_thread_pool_size: usize,
354    /// Notification for plotter to start, can be used to delay plotting until some initialization
355    /// has happened externally
356    pub plotting_delay: Option<oneshot::Receiver<()>>,
357    /// Global mutex that can restrict concurrency of resource-intensive operations and make sure
358    /// that those operations that are very sensitive (like proving) have all the resources
359    /// available to them for the highest probability of success
360    pub global_mutex: Arc<AsyncMutex<()>>,
361    /// How many sectors a will be plotted concurrently per farm
362    pub max_plotting_sectors_per_farm: NonZeroUsize,
363    /// Disable farm locking, for example if file system doesn't support it
364    pub disable_farm_locking: bool,
365    /// Prometheus registry
366    pub registry: Option<&'a Mutex<&'a mut Registry>>,
367    /// Whether to create a farm if it doesn't yet exist
368    pub create: bool,
369}
370
371/// Errors happening when trying to create/open single disk farm
372#[derive(Debug, Error)]
373pub enum SingleDiskFarmError {
374    /// Failed to open or create identity
375    #[error("Failed to open or create identity: {0}")]
376    FailedToOpenIdentity(#[from] IdentityError),
377    /// Farm is likely already in use, make sure no other farmer is using it
378    #[error("Farm is likely already in use, make sure no other farmer is using it: {0}")]
379    LikelyAlreadyInUse(io::Error),
380    /// I/O error occurred
381    #[error("Single disk farm I/O error: {0}")]
382    Io(#[from] io::Error),
383    /// Failed to spawn task for blocking thread
384    #[error("Failed to spawn task for blocking thread: {0}")]
385    TokioJoinError(#[from] task::JoinError),
386    /// Piece cache error
387    #[error("Piece cache error: {0}")]
388    PieceCacheError(#[from] DiskPieceCacheError),
389    /// Can't preallocate metadata file, probably not enough space on disk
390    #[error("Can't preallocate metadata file, probably not enough space on disk: {0}")]
391    CantPreallocateMetadataFile(io::Error),
392    /// Can't preallocate plot file, probably not enough space on disk
393    #[error("Can't preallocate plot file, probably not enough space on disk: {0}")]
394    CantPreallocatePlotFile(io::Error),
395    /// Wrong chain (genesis hash)
396    #[error(
397        "Genesis hash of farm {id} {wrong_chain} is different from {correct_chain} when farm was \
398        created, it is not possible to use farm on a different chain"
399    )]
400    WrongChain {
401        /// Farm ID
402        id: FarmId,
403        /// Hex-encoded genesis hash during farm creation
404        // TODO: Wrapper type with `Display` impl for genesis hash
405        correct_chain: String,
406        /// Hex-encoded current genesis hash
407        wrong_chain: String,
408    },
409    /// Public key in identity doesn't match metadata
410    #[error(
411        "Public key of farm {id} {wrong_public_key} is different from {correct_public_key} when \
412        farm was created, something went wrong, likely due to manual edits"
413    )]
414    IdentityMismatch {
415        /// Farm ID
416        id: FarmId,
417        /// Public key used during farm creation
418        correct_public_key: Ed25519PublicKey,
419        /// Current public key
420        wrong_public_key: Ed25519PublicKey,
421    },
422    /// Invalid number pieces in sector
423    #[error(
424        "Invalid number pieces in sector: max supported {max_supported}, farm initialized with \
425        {initialized_with}"
426    )]
427    InvalidPiecesInSector {
428        /// Farm ID
429        id: FarmId,
430        /// Max supported pieces in sector
431        max_supported: u16,
432        /// Number of pieces in sector farm is initialized with
433        initialized_with: u16,
434    },
435    /// Failed to decode metadata header
436    #[error("Failed to decode metadata header: {0}")]
437    FailedToDecodeMetadataHeader(parity_scale_codec::Error),
438    /// Unexpected metadata version
439    #[error("Unexpected metadata version {0}")]
440    UnexpectedMetadataVersion(u8),
441    /// Allocated space is not enough for one sector
442    #[error(
443        "Allocated space is not enough for one sector. \
444        The lowest acceptable value for allocated space is {min_space} bytes, \
445        provided {allocated_space} bytes."
446    )]
447    InsufficientAllocatedSpace {
448        /// Minimal allocated space
449        min_space: u64,
450        /// Current allocated space
451        allocated_space: u64,
452    },
453    /// Farm is too large
454    #[error(
455        "Farm is too large: allocated {allocated_sectors} sectors ({allocated_space} bytes), max \
456        supported is {max_sectors} ({max_space} bytes). Consider creating multiple smaller farms \
457        instead."
458    )]
459    FarmTooLarge {
460        /// Allocated space
461        allocated_space: u64,
462        /// Allocated space in sectors
463        allocated_sectors: u64,
464        /// Max supported allocated space
465        max_space: u64,
466        /// Max supported allocated space in sectors
467        max_sectors: u16,
468    },
469    /// Failed to create thread pool
470    #[error("Failed to create thread pool: {0}")]
471    FailedToCreateThreadPool(ThreadPoolBuildError),
472}
473
474/// Errors happening during scrubbing
475#[derive(Debug, Error)]
476pub enum SingleDiskFarmScrubError {
477    /// Farm is likely already in use, make sure no other farmer is using it
478    #[error("Farm is likely already in use, make sure no other farmer is using it: {0}")]
479    LikelyAlreadyInUse(io::Error),
480    /// Failed to determine file size
481    #[error("Failed to file size of {file}: {error}")]
482    FailedToDetermineFileSize {
483        /// Affected file
484        file: PathBuf,
485        /// Low-level error
486        error: io::Error,
487    },
488    /// Failed to read bytes from file
489    #[error("Failed to read {size} bytes from {file} at offset {offset}: {error}")]
490    FailedToReadBytes {
491        /// Affected file
492        file: PathBuf,
493        /// Number of bytes to read
494        size: u64,
495        /// Offset in the file
496        offset: u64,
497        /// Low-level error
498        error: io::Error,
499    },
500    /// Failed to write bytes from file
501    #[error("Failed to write {size} bytes from {file} at offset {offset}: {error}")]
502    FailedToWriteBytes {
503        /// Affected file
504        file: PathBuf,
505        /// Number of bytes to read
506        size: u64,
507        /// Offset in the file
508        offset: u64,
509        /// Low-level error
510        error: io::Error,
511    },
512    /// Farm info file does not exist
513    #[error("Farm info file does not exist at {file}")]
514    FarmInfoFileDoesNotExist {
515        /// Info file
516        file: PathBuf,
517    },
518    /// Farm info can't be opened
519    #[error("Farm info at {file} can't be opened: {error}")]
520    FarmInfoCantBeOpened {
521        /// Info file
522        file: PathBuf,
523        /// Low-level error
524        error: io::Error,
525    },
526    /// Identity file does not exist
527    #[error("Identity file does not exist at {file}")]
528    IdentityFileDoesNotExist {
529        /// Identity file
530        file: PathBuf,
531    },
532    /// Identity can't be opened
533    #[error("Identity at {file} can't be opened: {error}")]
534    IdentityCantBeOpened {
535        /// Identity file
536        file: PathBuf,
537        /// Low-level error
538        error: IdentityError,
539    },
540    /// Identity public key doesn't match public key in the disk farm info
541    #[error("Identity public key {identity} doesn't match public key in the disk farm info {info}")]
542    PublicKeyMismatch {
543        /// Identity public key
544        identity: Ed25519PublicKey,
545        /// Disk farm info public key
546        info: Ed25519PublicKey,
547    },
548    /// Metadata file does not exist
549    #[error("Metadata file does not exist at {file}")]
550    MetadataFileDoesNotExist {
551        /// Metadata file
552        file: PathBuf,
553    },
554    /// Metadata can't be opened
555    #[error("Metadata at {file} can't be opened: {error}")]
556    MetadataCantBeOpened {
557        /// Metadata file
558        file: PathBuf,
559        /// Low-level error
560        error: io::Error,
561    },
562    /// Metadata file too small
563    #[error(
564        "Metadata file at {file} is too small: reserved size is {reserved_size} bytes, file size \
565        is {size}"
566    )]
567    MetadataFileTooSmall {
568        /// Metadata file
569        file: PathBuf,
570        /// Reserved size
571        reserved_size: u64,
572        /// File size
573        size: u64,
574    },
575    /// Failed to decode metadata header
576    #[error("Failed to decode metadata header: {0}")]
577    FailedToDecodeMetadataHeader(parity_scale_codec::Error),
578    /// Unexpected metadata version
579    #[error("Unexpected metadata version {0}")]
580    UnexpectedMetadataVersion(u8),
581    /// Cache can't be opened
582    #[error("Cache at {file} can't be opened: {error}")]
583    CacheCantBeOpened {
584        /// Cache file
585        file: PathBuf,
586        /// Low-level error
587        error: io::Error,
588    },
589}
590
591/// Errors that happen in background tasks
592#[derive(Debug, Error)]
593pub enum BackgroundTaskError {
594    /// Plotting error
595    #[error(transparent)]
596    Plotting(#[from] PlottingError),
597    /// Farming error
598    #[error(transparent)]
599    Farming(#[from] FarmingError),
600    /// Block sealing
601    #[error(transparent)]
602    BlockSealing(#[from] anyhow::Error),
603    /// Background task panicked
604    #[error("Background task {task} panicked")]
605    BackgroundTaskPanicked {
606        /// Name of the task
607        task: String,
608    },
609}
610
611type BackgroundTask = Pin<Box<dyn Future<Output = Result<(), BackgroundTaskError>> + Send>>;
612
613/// Scrub target
614#[derive(Debug, Copy, Clone)]
615pub enum ScrubTarget {
616    /// Scrub everything
617    All,
618    /// Scrub just metadata
619    Metadata,
620    /// Scrub metadata and corresponding plot
621    Plot,
622    /// Only scrub cache
623    Cache,
624}
625
626impl fmt::Display for ScrubTarget {
627    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
628        match self {
629            Self::All => f.write_str("all"),
630            Self::Metadata => f.write_str("metadata"),
631            Self::Plot => f.write_str("plot"),
632            Self::Cache => f.write_str("cache"),
633        }
634    }
635}
636
637impl FromStr for ScrubTarget {
638    type Err = String;
639
640    fn from_str(s: &str) -> Result<Self, Self::Err> {
641        match s {
642            "all" => Ok(Self::All),
643            "metadata" => Ok(Self::Metadata),
644            "plot" => Ok(Self::Plot),
645            "cache" => Ok(Self::Cache),
646            s => Err(format!("Can't parse {s} as `ScrubTarget`")),
647        }
648    }
649}
650
651impl ScrubTarget {
652    fn metadata(self) -> bool {
653        match self {
654            Self::All | Self::Metadata | Self::Plot => true,
655            Self::Cache => false,
656        }
657    }
658
659    fn plot(self) -> bool {
660        match self {
661            Self::All | Self::Plot => true,
662            Self::Metadata | Self::Cache => false,
663        }
664    }
665
666    fn cache(self) -> bool {
667        match self {
668            Self::All | Self::Cache => true,
669            Self::Metadata | Self::Plot => false,
670        }
671    }
672}
673
674struct AllocatedSpaceDistribution {
675    piece_cache_file_size: u64,
676    piece_cache_capacity: u32,
677    plot_file_size: u64,
678    target_sector_count: u16,
679    metadata_file_size: u64,
680}
681
682impl AllocatedSpaceDistribution {
683    fn new(
684        allocated_space: u64,
685        sector_size: u64,
686        cache_percentage: u8,
687        sector_metadata_size: u64,
688    ) -> Result<Self, SingleDiskFarmError> {
689        let single_sector_overhead = sector_size + sector_metadata_size;
690        // Fixed space usage regardless of plot size
691        let fixed_space_usage = RESERVED_PLOT_METADATA
692            + RESERVED_FARM_INFO
693            + Identity::file_size() as u64
694            + KnownPeersManager::file_size(KNOWN_PEERS_CACHE_SIZE) as u64;
695        // Calculate how many sectors can fit
696        let target_sector_count = {
697            let potentially_plottable_space = allocated_space.saturating_sub(fixed_space_usage)
698                / 100
699                * (100 - u64::from(cache_percentage));
700            // Do the rounding to make sure we have exactly as much space as fits whole number of
701            // sectors, account for disk sector size just in case
702            (potentially_plottable_space - AlignedPage::SIZE as u64) / single_sector_overhead
703        };
704
705        if target_sector_count == 0 {
706            let mut single_plot_with_cache_space =
707                single_sector_overhead.div_ceil(100 - u64::from(cache_percentage)) * 100;
708            // Cache must not be empty, ensure it contains at least one element even if
709            // percentage-wise it will use more space
710            if single_plot_with_cache_space - single_sector_overhead
711                < u64::from(DiskPieceCache::element_size())
712            {
713                single_plot_with_cache_space =
714                    single_sector_overhead + u64::from(DiskPieceCache::element_size());
715            }
716
717            return Err(SingleDiskFarmError::InsufficientAllocatedSpace {
718                min_space: fixed_space_usage + single_plot_with_cache_space,
719                allocated_space,
720            });
721        }
722        let plot_file_size = target_sector_count * sector_size;
723        // Align plot file size for disk sector size
724        let plot_file_size =
725            plot_file_size.div_ceil(AlignedPage::SIZE as u64) * AlignedPage::SIZE as u64;
726
727        // Remaining space will be used for caching purposes
728        let piece_cache_capacity = if cache_percentage > 0 {
729            let cache_space = allocated_space
730                - fixed_space_usage
731                - plot_file_size
732                - (sector_metadata_size * target_sector_count);
733            (cache_space / u64::from(DiskPieceCache::element_size())) as u32
734        } else {
735            0
736        };
737        let target_sector_count = match u16::try_from(target_sector_count).map(SectorIndex::from) {
738            Ok(target_sector_count) if target_sector_count < SectorIndex::MAX => {
739                u16::from(target_sector_count)
740            }
741            _ => {
742                // We use `u16` for both count and index, hence index must not reach actual `MAX`
743                // (consensus doesn't care about this, just farmer implementation detail)
744                let max_sectors = u16::from(SectorIndex::MAX) - 1;
745                return Err(SingleDiskFarmError::FarmTooLarge {
746                    allocated_space: target_sector_count * sector_size,
747                    allocated_sectors: target_sector_count,
748                    max_space: u64::from(max_sectors) * sector_size,
749                    max_sectors,
750                });
751            }
752        };
753
754        Ok(Self {
755            piece_cache_file_size: u64::from(piece_cache_capacity)
756                * u64::from(DiskPieceCache::element_size()),
757            piece_cache_capacity,
758            plot_file_size,
759            target_sector_count,
760            metadata_file_size: RESERVED_PLOT_METADATA
761                + sector_metadata_size * u64::from(target_sector_count),
762        })
763    }
764}
765
766type Handler<A> = Bag<HandlerFn<A>, A>;
767
768#[derive(Default, Debug)]
769struct Handlers {
770    sector_update: Handler<(SectorIndex, SectorUpdate)>,
771    farming_notification: Handler<FarmingNotification>,
772    solution: Handler<SolutionResponse>,
773}
774
775struct SingleDiskFarmInit {
776    identity: Identity,
777    single_disk_farm_info: SingleDiskFarmInfo,
778    single_disk_farm_info_lock: Option<SingleDiskFarmInfoLock>,
779    plot_file: StdArc<DirectIoFileWrapper>,
780    metadata_file: DirectIoFileWrapper,
781    metadata_header: PlotMetadataHeader,
782    target_sector_count: u16,
783    sectors_metadata: StdArc<AsyncRwLock<Vec<SectorMetadataChecksummed>>>,
784    piece_cache_capacity: u32,
785    plot_cache: DiskPlotCache,
786}
787
788/// Single disk farm abstraction is a container for everything necessary to plot/farm with a single
789/// disk.
790///
791/// Farm starts operating during creation and doesn't stop until dropped (or error happens).
792#[derive(Debug)]
793#[must_use = "Plot does not function properly unless run() method is called"]
794pub struct SingleDiskFarm {
795    farmer_protocol_info: FarmerProtocolInfo,
796    single_disk_farm_info: SingleDiskFarmInfo,
797    shard_commitments_roots_cache: ShardCommitmentsRootsCache,
798    /// Metadata of all sectors plotted so far
799    sectors_metadata: StdArc<AsyncRwLock<Vec<SectorMetadataChecksummed>>>,
800    pieces_in_sector: u16,
801    total_sectors_count: u16,
802    span: Span,
803    tasks: FuturesUnordered<BackgroundTask>,
804    handlers: Arc<Handlers>,
805    piece_cache: SingleDiskPieceCache,
806    plot_cache: DiskPlotCache,
807    piece_reader: DiskPieceReader,
808    /// Sender that will be used to signal to background threads that they should start
809    start_sender: Option<broadcast::Sender<()>>,
810    /// Sender that will be used to signal to background threads that they must stop
811    stop_sender: Option<broadcast::Sender<()>>,
812    _single_disk_farm_info_lock: Option<SingleDiskFarmInfoLock>,
813}
814
815impl Drop for SingleDiskFarm {
816    #[inline]
817    fn drop(&mut self) {
818        self.piece_reader.close_all_readers();
819        // Make background threads that are waiting to do something exit immediately
820        self.start_sender.take();
821        // Notify background tasks that they must stop
822        self.stop_sender.take();
823    }
824}
825
826#[async_trait(?Send)]
827impl Farm for SingleDiskFarm {
828    fn id(&self) -> &FarmId {
829        self.id()
830    }
831
832    fn total_sectors_count(&self) -> u16 {
833        self.total_sectors_count
834    }
835
836    fn plotted_sectors(&self) -> StdArc<dyn PlottedSectors + 'static> {
837        StdArc::new(self.plotted_sectors())
838    }
839
840    fn piece_reader(&self) -> StdArc<dyn PieceReader + 'static> {
841        StdArc::new(self.piece_reader())
842    }
843
844    fn on_sector_update(
845        &self,
846        callback: HandlerFn<(SectorIndex, SectorUpdate)>,
847    ) -> Box<dyn farm::HandlerId> {
848        Box::new(self.on_sector_update(callback))
849    }
850
851    fn on_farming_notification(
852        &self,
853        callback: HandlerFn<FarmingNotification>,
854    ) -> Box<dyn farm::HandlerId> {
855        Box::new(self.on_farming_notification(callback))
856    }
857
858    fn on_solution(&self, callback: HandlerFn<SolutionResponse>) -> Box<dyn farm::HandlerId> {
859        Box::new(self.on_solution(callback))
860    }
861
862    fn run(self: Box<Self>) -> Pin<Box<dyn Future<Output = anyhow::Result<()>> + Send>> {
863        Box::pin((*self).run())
864    }
865}
866
867impl SingleDiskFarm {
868    /// Name of the plot file
869    pub const PLOT_FILE: &'static str = "plot.bin";
870    /// Name of the metadata file
871    pub const METADATA_FILE: &'static str = "metadata.bin";
872    const SUPPORTED_PLOT_VERSION: u8 = 0;
873
874    /// Create new single disk farm instance
875    pub async fn new<NC, PosTable>(
876        options: SingleDiskFarmOptions<'_, NC>,
877        farm_index: usize,
878    ) -> Result<Self, SingleDiskFarmError>
879    where
880        NC: NodeClient + Clone,
881        PosTable: Table,
882    {
883        let span = Span::current();
884
885        let SingleDiskFarmOptions {
886            directory,
887            farmer_app_info,
888            allocated_space,
889            max_pieces_in_sector,
890            node_client,
891            reward_address,
892            plotter,
893            erasure_coding,
894            cache_percentage,
895            farming_thread_pool_size,
896            plotting_delay,
897            global_mutex,
898            max_plotting_sectors_per_farm,
899            disable_farm_locking,
900            registry,
901            create,
902        } = options;
903
904        let single_disk_farm_init_fut = task::spawn_blocking({
905            let directory = directory.clone();
906            let farmer_app_info = farmer_app_info.clone();
907            let span = span.clone();
908
909            move || {
910                let _span_guard = span.enter();
911                Self::init(
912                    &directory,
913                    &farmer_app_info,
914                    allocated_space,
915                    max_pieces_in_sector,
916                    cache_percentage,
917                    disable_farm_locking,
918                    create,
919                )
920            }
921        });
922
923        let single_disk_farm_init =
924            AsyncJoinOnDrop::new(single_disk_farm_init_fut, false).await??;
925
926        let SingleDiskFarmInit {
927            identity,
928            single_disk_farm_info,
929            single_disk_farm_info_lock,
930            plot_file,
931            metadata_file,
932            metadata_header,
933            target_sector_count,
934            sectors_metadata,
935            piece_cache_capacity,
936            plot_cache,
937        } = single_disk_farm_init;
938
939        let piece_cache = {
940            // Convert farm ID into cache ID for single disk farm
941            let FarmId::Ulid(id) = *single_disk_farm_info.id();
942            let id = PieceCacheId::Ulid(id);
943
944            SingleDiskPieceCache::new(
945                id,
946                if let Some(piece_cache_capacity) = NonZeroU32::new(piece_cache_capacity) {
947                    Some(task::block_in_place(|| {
948                        if let Some(registry) = registry {
949                            DiskPieceCache::open(
950                                &directory,
951                                piece_cache_capacity,
952                                Some(id),
953                                Some(*registry.lock()),
954                            )
955                        } else {
956                            DiskPieceCache::open(&directory, piece_cache_capacity, Some(id), None)
957                        }
958                    })?)
959                } else {
960                    None
961                },
962            )
963        };
964
965        let public_key = *single_disk_farm_info.public_key();
966        let shard_commitments_roots_cache =
967            ShardCommitmentsRootsCache::new(*single_disk_farm_info.shard_commitments_seed());
968        let pieces_in_sector = single_disk_farm_info.pieces_in_sector();
969        let sector_size = sector_size(pieces_in_sector);
970
971        let metrics = registry.map(|registry| {
972            Arc::new(SingleDiskFarmMetrics::new(
973                *registry.lock(),
974                single_disk_farm_info.id(),
975                target_sector_count,
976                sectors_metadata.read_blocking().len() as u16,
977            ))
978        });
979
980        let (error_sender, error_receiver) = oneshot::channel();
981        let error_sender = Arc::new(Mutex::new(Some(error_sender)));
982
983        let tasks = FuturesUnordered::<BackgroundTask>::new();
984
985        tasks.push(Box::pin(async move {
986            if let Ok(error) = error_receiver.await {
987                return Err(error);
988            }
989
990            Ok(())
991        }));
992
993        let handlers = Arc::<Handlers>::default();
994        let (start_sender, mut start_receiver) = broadcast::channel::<()>(1);
995        let (stop_sender, mut stop_receiver) = broadcast::channel::<()>(1);
996        let sectors_being_modified = Arc::<AsyncRwLock<HashSet<SectorIndex>>>::default();
997        let (sectors_to_plot_sender, sectors_to_plot_receiver) = mpsc::channel(1);
998        // Some sectors may already be plotted, skip them
999        let sectors_indices_left_to_plot = SectorIndex::from(metadata_header.plotted_sector_count)
1000            ..SectorIndex::from(target_sector_count);
1001
1002        let farming_thread_pool = ThreadPoolBuilder::new()
1003            .thread_name(move |thread_index| format!("farming-{farm_index}.{thread_index}"))
1004            .num_threads(farming_thread_pool_size)
1005            .spawn_handler(tokio_rayon_spawn_handler())
1006            .build()
1007            .map_err(SingleDiskFarmError::FailedToCreateThreadPool)?;
1008        let farming_plot_fut = task::spawn_blocking(|| {
1009            farming_thread_pool
1010                .install(move || {
1011                    RayonFiles::open_with(directory.join(Self::PLOT_FILE), |path| {
1012                        DirectIoFileWrapper::open(path)
1013                    })
1014                })
1015                .map(|farming_plot| (farming_plot, farming_thread_pool))
1016        });
1017
1018        let (farming_plot, farming_thread_pool) =
1019            AsyncJoinOnDrop::new(farming_plot_fut, false).await??;
1020
1021        let plotting_join_handle = task::spawn_blocking({
1022            let shard_commitments_roots_cache = shard_commitments_roots_cache.clone();
1023            let sectors_metadata = StdArc::clone(&sectors_metadata);
1024            let handlers = Arc::clone(&handlers);
1025            let sectors_being_modified = Arc::clone(&sectors_being_modified);
1026            let node_client = node_client.clone();
1027            let plot_file = StdArc::clone(&plot_file);
1028            let error_sender = Arc::clone(&error_sender);
1029            let span = span.clone();
1030            let global_mutex = Arc::clone(&global_mutex);
1031            let metrics = metrics.clone();
1032
1033            move || {
1034                let _span_guard = span.enter();
1035
1036                let plotting_options = PlottingOptions {
1037                    metadata_header,
1038                    sectors_metadata: &sectors_metadata,
1039                    sectors_being_modified: &sectors_being_modified,
1040                    sectors_to_plot_receiver,
1041                    sector_plotting_options: SectorPlottingOptions {
1042                        public_key,
1043                        shard_commitments_roots_cache,
1044                        node_client: &node_client,
1045                        pieces_in_sector,
1046                        sector_size,
1047                        plot_file,
1048                        metadata_file: Arc::new(metadata_file),
1049                        handlers: &handlers,
1050                        global_mutex: &global_mutex,
1051                        plotter,
1052                        metrics,
1053                    },
1054                    max_plotting_sectors_per_farm,
1055                };
1056
1057                let plotting_fut = async {
1058                    if start_receiver.recv().await.is_err() {
1059                        // Dropped before starting
1060                        return Ok(());
1061                    }
1062
1063                    if let Some(plotting_delay) = plotting_delay
1064                        && plotting_delay.await.is_err()
1065                    {
1066                        // Dropped before resolving
1067                        return Ok(());
1068                    }
1069
1070                    plotting(plotting_options).await
1071                };
1072
1073                Handle::current().block_on(async {
1074                    select! {
1075                        plotting_result = plotting_fut.fuse() => {
1076                            if let Err(error) = plotting_result
1077                                && let Some(error_sender) = error_sender.lock().take()
1078                                && let Err(error) = error_sender.send(error.into())
1079                            {
1080                                error!(
1081                                    %error,
1082                                    "Plotting failed to send error to background task"
1083                                );
1084                            }
1085                        }
1086                        _ = stop_receiver.recv().fuse() => {
1087                            // Nothing, just exit
1088                        }
1089                    }
1090                });
1091            }
1092        });
1093        let plotting_join_handle = AsyncJoinOnDrop::new(plotting_join_handle, false);
1094
1095        tasks.push(Box::pin(async move {
1096            // Panic will already be printed by now
1097            plotting_join_handle.await.map_err(|_error| {
1098                BackgroundTaskError::BackgroundTaskPanicked {
1099                    task: format!("plotting-{farm_index}"),
1100                }
1101            })
1102        }));
1103
1104        let public_key_hash = public_key.hash();
1105
1106        let plotting_scheduler_options = PlottingSchedulerOptions {
1107            public_key_hash,
1108            shard_commitments_roots_cache: shard_commitments_roots_cache.clone(),
1109            sectors_indices_left_to_plot,
1110            target_sector_count,
1111            last_archived_segment_index: farmer_app_info.protocol_info.history_size.segment_index(),
1112            min_sector_lifetime: farmer_app_info.protocol_info.min_sector_lifetime,
1113            node_client: node_client.clone(),
1114            handlers: Arc::clone(&handlers),
1115            sectors_metadata: StdArc::clone(&sectors_metadata),
1116            sectors_to_plot_sender,
1117            new_segment_processing_delay: NEW_SEGMENT_PROCESSING_DELAY,
1118            metrics: metrics.clone(),
1119        };
1120        tasks.push(Box::pin(plotting_scheduler(plotting_scheduler_options)));
1121
1122        let (slot_info_forwarder_sender, slot_info_forwarder_receiver) = mpsc::channel(0);
1123
1124        tasks.push(Box::pin({
1125            let node_client = node_client.clone();
1126            let metrics = metrics.clone();
1127
1128            async move {
1129                slot_notification_forwarder(&node_client, slot_info_forwarder_sender, metrics)
1130                    .await
1131                    .map_err(BackgroundTaskError::Farming)
1132            }
1133        }));
1134
1135        let farming_join_handle = task::spawn_blocking({
1136            let shard_commitments_roots_cache = shard_commitments_roots_cache.clone();
1137            let erasure_coding = erasure_coding.clone();
1138            let handlers = Arc::clone(&handlers);
1139            let sectors_being_modified = Arc::clone(&sectors_being_modified);
1140            let sectors_metadata = StdArc::clone(&sectors_metadata);
1141            let mut start_receiver = start_sender.subscribe();
1142            let mut stop_receiver = stop_sender.subscribe();
1143            let node_client = node_client.clone();
1144            let span = span.clone();
1145            let global_mutex = Arc::clone(&global_mutex);
1146
1147            move || {
1148                let _span_guard = span.enter();
1149
1150                let farming_fut = async move {
1151                    if start_receiver.recv().await.is_err() {
1152                        // Dropped before starting
1153                        return Ok(());
1154                    }
1155
1156                    let plot_audit = PlotAudit::new(&farming_plot);
1157
1158                    let farming_options = FarmingOptions {
1159                        public_key_hash,
1160                        shard_commitments_roots_cache,
1161                        reward_address,
1162                        node_client,
1163                        plot_audit,
1164                        sectors_metadata,
1165                        erasure_coding,
1166                        handlers,
1167                        sectors_being_modified,
1168                        slot_info_notifications: slot_info_forwarder_receiver,
1169                        thread_pool: farming_thread_pool,
1170                        global_mutex,
1171                        metrics,
1172                    };
1173                    farming::<PosTable, _, _>(farming_options).await
1174                };
1175
1176                Handle::current().block_on(async {
1177                    select! {
1178                        farming_result = farming_fut.fuse() => {
1179                            if let Err(error) = farming_result
1180                                && let Some(error_sender) = error_sender.lock().take()
1181                                && let Err(error) = error_sender.send(error.into())
1182                            {
1183                                error!(
1184                                    %error,
1185                                    "Farming failed to send error to background task",
1186                                );
1187                            }
1188                        }
1189                        _ = stop_receiver.recv().fuse() => {
1190                            // Nothing, just exit
1191                        }
1192                    }
1193                });
1194            }
1195        });
1196        let farming_join_handle = AsyncJoinOnDrop::new(farming_join_handle, false);
1197
1198        tasks.push(Box::pin(async move {
1199            // Panic will already be printed by now
1200            farming_join_handle.await.map_err(|_error| {
1201                BackgroundTaskError::BackgroundTaskPanicked {
1202                    task: format!("farming-{farm_index}"),
1203                }
1204            })
1205        }));
1206
1207        let (piece_reader, reading_fut) = DiskPieceReader::new::<PosTable>(
1208            public_key_hash,
1209            shard_commitments_roots_cache.clone(),
1210            pieces_in_sector,
1211            plot_file,
1212            StdArc::clone(&sectors_metadata),
1213            erasure_coding,
1214            sectors_being_modified,
1215            global_mutex,
1216        );
1217
1218        let reading_join_handle = task::spawn_blocking({
1219            let mut stop_receiver = stop_sender.subscribe();
1220            let reading_fut = reading_fut.instrument(span.clone());
1221
1222            move || {
1223                Handle::current().block_on(async {
1224                    select! {
1225                        () = reading_fut.fuse() => {
1226                            // Nothing, just exit
1227                        }
1228                        _ = stop_receiver.recv().fuse() => {
1229                            // Nothing, just exit
1230                        }
1231                    }
1232                });
1233            }
1234        });
1235
1236        let reading_join_handle = AsyncJoinOnDrop::new(reading_join_handle, false);
1237
1238        tasks.push(Box::pin(async move {
1239            // Panic will already be printed by now
1240            reading_join_handle.await.map_err(|_error| {
1241                BackgroundTaskError::BackgroundTaskPanicked {
1242                    task: format!("reading-{farm_index}"),
1243                }
1244            })
1245        }));
1246
1247        tasks.push(Box::pin(async move {
1248            match block_sealing(node_client, identity).await {
1249                Ok(block_sealing_fut) => {
1250                    block_sealing_fut.await;
1251                }
1252                Err(error) => {
1253                    return Err(BackgroundTaskError::BlockSealing(anyhow::anyhow!(
1254                        "Failed to subscribe to block sealing notifications: {error}"
1255                    )));
1256                }
1257            }
1258
1259            Ok(())
1260        }));
1261
1262        let farm = Self {
1263            farmer_protocol_info: farmer_app_info.protocol_info,
1264            single_disk_farm_info,
1265            shard_commitments_roots_cache,
1266            sectors_metadata,
1267            pieces_in_sector,
1268            total_sectors_count: target_sector_count,
1269            span,
1270            tasks,
1271            handlers,
1272            piece_cache,
1273            plot_cache,
1274            piece_reader,
1275            start_sender: Some(start_sender),
1276            stop_sender: Some(stop_sender),
1277            _single_disk_farm_info_lock: single_disk_farm_info_lock,
1278        };
1279        Ok(farm)
1280    }
1281
1282    fn init(
1283        directory: &PathBuf,
1284        farmer_app_info: &FarmerAppInfo,
1285        allocated_space: u64,
1286        max_pieces_in_sector: u16,
1287        cache_percentage: u8,
1288        disable_farm_locking: bool,
1289        create: bool,
1290    ) -> Result<SingleDiskFarmInit, SingleDiskFarmError> {
1291        fs::create_dir_all(directory)?;
1292
1293        let identity = if create {
1294            Identity::open_or_create(directory)?
1295        } else {
1296            Identity::open(directory)?.ok_or_else(|| {
1297                IdentityError::Io(io::Error::new(
1298                    io::ErrorKind::NotFound,
1299                    "Farm does not exist and creation was explicitly disabled",
1300                ))
1301            })?
1302        };
1303        let public_key = identity.public_key();
1304
1305        let (single_disk_farm_info, single_disk_farm_info_lock) = if let Some(
1306            mut single_disk_farm_info,
1307        ) =
1308            SingleDiskFarmInfo::load_from(directory)?
1309        {
1310            if &farmer_app_info.genesis_root != single_disk_farm_info.genesis_root() {
1311                return Err(SingleDiskFarmError::WrongChain {
1312                    id: *single_disk_farm_info.id(),
1313                    correct_chain: hex::encode(single_disk_farm_info.genesis_root()),
1314                    wrong_chain: hex::encode(farmer_app_info.genesis_root),
1315                });
1316            }
1317
1318            if &public_key != single_disk_farm_info.public_key() {
1319                return Err(SingleDiskFarmError::IdentityMismatch {
1320                    id: *single_disk_farm_info.id(),
1321                    correct_public_key: *single_disk_farm_info.public_key(),
1322                    wrong_public_key: public_key,
1323                });
1324            }
1325
1326            let pieces_in_sector = single_disk_farm_info.pieces_in_sector();
1327
1328            if max_pieces_in_sector < pieces_in_sector {
1329                return Err(SingleDiskFarmError::InvalidPiecesInSector {
1330                    id: *single_disk_farm_info.id(),
1331                    max_supported: max_pieces_in_sector,
1332                    initialized_with: pieces_in_sector,
1333                });
1334            }
1335
1336            if max_pieces_in_sector > pieces_in_sector {
1337                info!(
1338                    pieces_in_sector,
1339                    max_pieces_in_sector,
1340                    "Farm initialized with smaller number of pieces in sector, farm needs \
1341                        to be re-created for increase"
1342                );
1343            }
1344
1345            let mut single_disk_farm_info_lock = None;
1346
1347            if allocated_space != single_disk_farm_info.allocated_space() {
1348                info!(
1349                    old_space = %ByteSize::b(single_disk_farm_info.allocated_space()).display().iec(),
1350                    new_space = %ByteSize::b(allocated_space).display().iec(),
1351                    "Farm size has changed"
1352                );
1353
1354                let new_allocated_space = allocated_space;
1355                #[expect(
1356                    clippy::rest_pattern_accessible_field,
1357                    reason = "Do not need other fields"
1358                )]
1359                match &mut single_disk_farm_info {
1360                    SingleDiskFarmInfo::V0 {
1361                        allocated_space, ..
1362                    } => {
1363                        *allocated_space = new_allocated_space;
1364                    }
1365                }
1366
1367                single_disk_farm_info_lock =
1368                    single_disk_farm_info.store_to(directory, !disable_farm_locking)?;
1369            } else if !disable_farm_locking {
1370                single_disk_farm_info_lock = Some(
1371                    SingleDiskFarmInfo::try_lock(directory)
1372                        .map_err(SingleDiskFarmError::LikelyAlreadyInUse)?,
1373                );
1374            }
1375
1376            (single_disk_farm_info, single_disk_farm_info_lock)
1377        } else {
1378            let single_disk_farm_info = SingleDiskFarmInfo::new(
1379                FarmId::new(),
1380                farmer_app_info.genesis_root,
1381                public_key,
1382                identity.shard_commitments_seed(),
1383                max_pieces_in_sector,
1384                allocated_space,
1385            );
1386
1387            let single_disk_farm_info_lock =
1388                single_disk_farm_info.store_to(directory, !disable_farm_locking)?;
1389
1390            (single_disk_farm_info, single_disk_farm_info_lock)
1391        };
1392
1393        let pieces_in_sector = single_disk_farm_info.pieces_in_sector();
1394        let sector_size = sector_size(pieces_in_sector) as u64;
1395        let sector_metadata_size = SectorMetadataChecksummed::disk_size();
1396        let allocated_space_distribution = AllocatedSpaceDistribution::new(
1397            allocated_space,
1398            sector_size,
1399            cache_percentage,
1400            sector_metadata_size as u64,
1401        )?;
1402        let target_sector_count = allocated_space_distribution.target_sector_count;
1403
1404        let metadata_file_path = directory.join(Self::METADATA_FILE);
1405        let metadata_file = DirectIoFileWrapper::open(&metadata_file_path)?;
1406
1407        let metadata_size = metadata_file.size()?;
1408        let expected_metadata_size = allocated_space_distribution.metadata_file_size;
1409        // Align plot file size for disk sector size
1410        let expected_metadata_size =
1411            expected_metadata_size.div_ceil(AlignedPage::SIZE as u64) * AlignedPage::SIZE as u64;
1412        let metadata_header = if metadata_size == 0 {
1413            let metadata_header = PlotMetadataHeader {
1414                version: SingleDiskFarm::SUPPORTED_PLOT_VERSION,
1415                plotted_sector_count: 0,
1416            };
1417
1418            metadata_file
1419                .preallocate(expected_metadata_size)
1420                .map_err(SingleDiskFarmError::CantPreallocateMetadataFile)?;
1421            metadata_file.write_all_at(&metadata_header.encode_page(), 0)?;
1422
1423            metadata_header
1424        } else {
1425            let mut metadata_header_bytes = vec![0; PlotMetadataHeader::encoded_size()];
1426            metadata_file.read_exact_at(&mut metadata_header_bytes, 0)?;
1427
1428            let mut metadata_header =
1429                PlotMetadataHeader::decode(&mut metadata_header_bytes.as_ref())
1430                    .map_err(SingleDiskFarmError::FailedToDecodeMetadataHeader)?;
1431
1432            // Check version before resizing, such that metadata file of unsupported version is not
1433            // modified
1434            if metadata_header.version != SingleDiskFarm::SUPPORTED_PLOT_VERSION {
1435                return Err(SingleDiskFarmError::UnexpectedMetadataVersion(
1436                    metadata_header.version,
1437                ));
1438            }
1439
1440            if metadata_size != expected_metadata_size {
1441                // Allocating the whole file (`set_len` below can create a sparse file, which will
1442                // cause writes to fail later)
1443                metadata_file
1444                    .preallocate(expected_metadata_size)
1445                    .map_err(SingleDiskFarmError::CantPreallocateMetadataFile)?;
1446                // Truncating file (if necessary)
1447                metadata_file.set_len(expected_metadata_size)?;
1448            }
1449
1450            if metadata_header.plotted_sector_count > target_sector_count {
1451                metadata_header.plotted_sector_count = target_sector_count;
1452                metadata_file.write_all_at(&metadata_header.encode_page(), 0)?;
1453            }
1454
1455            metadata_header
1456        };
1457
1458        let sectors_metadata = {
1459            let mut sectors_metadata =
1460                Vec::<SectorMetadataChecksummed>::with_capacity(usize::from(target_sector_count));
1461
1462            let mut sector_metadata_bytes = vec![0; sector_metadata_size];
1463            for sector_index in
1464                SectorIndex::ZERO..SectorIndex::from(metadata_header.plotted_sector_count)
1465            {
1466                let sector_offset =
1467                    RESERVED_PLOT_METADATA + sector_metadata_size as u64 * u64::from(sector_index);
1468                metadata_file.read_exact_at(&mut sector_metadata_bytes, sector_offset)?;
1469
1470                let sector_metadata = match SectorMetadataChecksummed::decode_from_disk(
1471                    sector_metadata_bytes
1472                        .as_array()
1473                        .expect("Allocated with the size of sector metadata; qed"),
1474                ) {
1475                    Ok(sector_metadata) => sector_metadata,
1476                    Err(error) => {
1477                        warn!(
1478                            path = %metadata_file_path.display(),
1479                            %error,
1480                            %sector_index,
1481                            "Failed to decode sector metadata, replacing with dummy expired \
1482                            sector metadata"
1483                        );
1484
1485                        let dummy_sector = SectorMetadataChecksummed::from(SectorMetadata {
1486                            sector_index,
1487                            pieces_in_sector,
1488                            s_bucket_sizes: Box::new([0; Record::NUM_S_BUCKETS]),
1489                            history_size: HistorySize::from(SegmentIndex::ZERO),
1490                        });
1491                        metadata_file.write_all_at(
1492                            dummy_sector.encode_for_disk().as_slice(),
1493                            sector_offset,
1494                        )?;
1495
1496                        dummy_sector
1497                    }
1498                };
1499                sectors_metadata.push(sector_metadata);
1500            }
1501
1502            StdArc::new(AsyncRwLock::new(sectors_metadata))
1503        };
1504
1505        let plot_file = DirectIoFileWrapper::open(directory.join(Self::PLOT_FILE))?;
1506
1507        if plot_file.size()? != allocated_space_distribution.plot_file_size {
1508            // Allocating the whole file (`set_len` below can create a sparse file, which will cause
1509            // writes to fail later)
1510            plot_file
1511                .preallocate(allocated_space_distribution.plot_file_size)
1512                .map_err(SingleDiskFarmError::CantPreallocatePlotFile)?;
1513            // Truncating file (if necessary)
1514            plot_file.set_len(allocated_space_distribution.plot_file_size)?;
1515        }
1516
1517        let plot_file = StdArc::new(plot_file);
1518
1519        let plot_cache = DiskPlotCache::new(
1520            &plot_file,
1521            &sectors_metadata,
1522            target_sector_count,
1523            sector_size,
1524        );
1525
1526        Ok(SingleDiskFarmInit {
1527            identity,
1528            single_disk_farm_info,
1529            single_disk_farm_info_lock,
1530            plot_file,
1531            metadata_file,
1532            metadata_header,
1533            target_sector_count,
1534            sectors_metadata,
1535            piece_cache_capacity: allocated_space_distribution.piece_cache_capacity,
1536            plot_cache,
1537        })
1538    }
1539
1540    /// Collect summary of single disk farm for presentational purposes
1541    pub fn collect_summary(directory: PathBuf) -> SingleDiskFarmSummary {
1542        let single_disk_farm_info = match SingleDiskFarmInfo::load_from(&directory) {
1543            Ok(Some(single_disk_farm_info)) => single_disk_farm_info,
1544            Ok(None) => {
1545                return SingleDiskFarmSummary::NotFound { directory };
1546            }
1547            Err(error) => {
1548                return SingleDiskFarmSummary::Error { directory, error };
1549            }
1550        };
1551
1552        SingleDiskFarmSummary::Found {
1553            info: single_disk_farm_info,
1554            directory,
1555        }
1556    }
1557
1558    /// Effective on-disk allocation of the files related to the farm (takes some buffer space
1559    /// into consideration).
1560    ///
1561    /// This is a helpful number in case some files were not allocated properly or were removed and
1562    /// do not correspond to allocated space in the farm info accurately.
1563    pub fn effective_disk_usage(
1564        directory: &Path,
1565        cache_percentage: u8,
1566    ) -> Result<u64, SingleDiskFarmError> {
1567        let mut effective_disk_usage;
1568        match SingleDiskFarmInfo::load_from(directory)? {
1569            Some(single_disk_farm_info) => {
1570                let allocated_space_distribution = AllocatedSpaceDistribution::new(
1571                    single_disk_farm_info.allocated_space(),
1572                    sector_size(single_disk_farm_info.pieces_in_sector()) as u64,
1573                    cache_percentage,
1574                    SectorMetadataChecksummed::disk_size() as u64,
1575                )?;
1576
1577                effective_disk_usage = single_disk_farm_info.allocated_space();
1578                effective_disk_usage -= Identity::file_size() as u64;
1579                effective_disk_usage -= allocated_space_distribution.metadata_file_size;
1580                effective_disk_usage -= allocated_space_distribution.plot_file_size;
1581                effective_disk_usage -= allocated_space_distribution.piece_cache_file_size;
1582            }
1583            None => {
1584                // No farm info, try to collect actual file sizes is any
1585                effective_disk_usage = 0;
1586            }
1587        }
1588
1589        if Identity::open(directory)?.is_some() {
1590            effective_disk_usage += Identity::file_size() as u64;
1591        }
1592
1593        match OpenOptions::new()
1594            .read(true)
1595            .open(directory.join(Self::METADATA_FILE))
1596        {
1597            Ok(metadata_file) => {
1598                effective_disk_usage += metadata_file.size()?;
1599            }
1600            Err(error) => {
1601                if error.kind() == io::ErrorKind::NotFound {
1602                    // File is not stored on disk
1603                } else {
1604                    return Err(error.into());
1605                }
1606            }
1607        }
1608
1609        match OpenOptions::new()
1610            .read(true)
1611            .open(directory.join(Self::PLOT_FILE))
1612        {
1613            Ok(plot_file) => {
1614                effective_disk_usage += plot_file.size()?;
1615            }
1616            Err(error) => {
1617                if error.kind() == io::ErrorKind::NotFound {
1618                    // File is not stored on disk
1619                } else {
1620                    return Err(error.into());
1621                }
1622            }
1623        }
1624
1625        match OpenOptions::new()
1626            .read(true)
1627            .open(directory.join(DiskPieceCache::FILE_NAME))
1628        {
1629            Ok(piece_cache) => {
1630                effective_disk_usage += piece_cache.size()?;
1631            }
1632            Err(error) => {
1633                if error.kind() == io::ErrorKind::NotFound {
1634                    // File is not stored on disk
1635                } else {
1636                    return Err(error.into());
1637                }
1638            }
1639        }
1640
1641        Ok(effective_disk_usage)
1642    }
1643
1644    /// Read all sectors metadata
1645    pub fn read_all_sectors_metadata(
1646        directory: &Path,
1647    ) -> io::Result<Vec<SectorMetadataChecksummed>> {
1648        let metadata_file = DirectIoFileWrapper::open(directory.join(Self::METADATA_FILE))?;
1649
1650        let metadata_size = metadata_file.size()?;
1651        let sector_metadata_size = SectorMetadataChecksummed::disk_size();
1652
1653        let mut metadata_header_bytes = vec![0; PlotMetadataHeader::encoded_size()];
1654        metadata_file.read_exact_at(&mut metadata_header_bytes, 0)?;
1655
1656        let metadata_header = PlotMetadataHeader::decode(&mut metadata_header_bytes.as_ref())
1657            .map_err(|error| {
1658                io::Error::other(format!("Failed to decode metadata header: {error}"))
1659            })?;
1660
1661        if metadata_header.version != SingleDiskFarm::SUPPORTED_PLOT_VERSION {
1662            return Err(io::Error::other(format!(
1663                "Unsupported metadata version {}",
1664                metadata_header.version
1665            )));
1666        }
1667
1668        let mut sectors_metadata = Vec::<SectorMetadataChecksummed>::with_capacity(
1669            ((metadata_size - RESERVED_PLOT_METADATA) / sector_metadata_size as u64) as usize,
1670        );
1671
1672        let mut sector_metadata_bytes = vec![0; sector_metadata_size];
1673        for sector_index in 0..metadata_header.plotted_sector_count {
1674            metadata_file.read_exact_at(
1675                &mut sector_metadata_bytes,
1676                RESERVED_PLOT_METADATA + sector_metadata_size as u64 * u64::from(sector_index),
1677            )?;
1678            sectors_metadata.push(
1679                SectorMetadataChecksummed::decode_from_disk(
1680                    sector_metadata_bytes
1681                        .as_array()
1682                        .expect("Allocated with the size of sector metadata; qed"),
1683                )
1684                .map_err(|error| {
1685                    io::Error::other(format!("Failed to decode sector metadata: {error}"))
1686                })?,
1687            );
1688        }
1689
1690        Ok(sectors_metadata)
1691    }
1692
1693    /// ID of this farm
1694    pub fn id(&self) -> &FarmId {
1695        self.single_disk_farm_info.id()
1696    }
1697
1698    /// Info of this farm
1699    pub fn info(&self) -> &SingleDiskFarmInfo {
1700        &self.single_disk_farm_info
1701    }
1702
1703    /// Number of sectors in this farm
1704    pub fn total_sectors_count(&self) -> u16 {
1705        self.total_sectors_count
1706    }
1707
1708    /// Read information about sectors plotted so far
1709    pub fn plotted_sectors(&self) -> SingleDiskPlottedSectors {
1710        SingleDiskPlottedSectors {
1711            public_key_hash: self.single_disk_farm_info.public_key().hash(),
1712            shard_commitments_roots_cache: self.shard_commitments_roots_cache.clone(),
1713            pieces_in_sector: self.pieces_in_sector,
1714            farmer_protocol_info: self.farmer_protocol_info,
1715            sectors_metadata: StdArc::clone(&self.sectors_metadata),
1716        }
1717    }
1718
1719    /// Get piece cache instance
1720    pub fn piece_cache(&self) -> SingleDiskPieceCache {
1721        self.piece_cache.clone()
1722    }
1723
1724    /// Get plot cache instance
1725    pub fn plot_cache(&self) -> DiskPlotCache {
1726        self.plot_cache.clone()
1727    }
1728
1729    /// Get piece reader to read plotted pieces later
1730    pub fn piece_reader(&self) -> DiskPieceReader {
1731        self.piece_reader.clone()
1732    }
1733
1734    /// Subscribe to sector updates
1735    pub fn on_sector_update(&self, callback: HandlerFn<(SectorIndex, SectorUpdate)>) -> HandlerId {
1736        self.handlers.sector_update.add(callback)
1737    }
1738
1739    /// Subscribe to farming notifications
1740    pub fn on_farming_notification(&self, callback: HandlerFn<FarmingNotification>) -> HandlerId {
1741        self.handlers.farming_notification.add(callback)
1742    }
1743
1744    /// Subscribe to new solution notification
1745    pub fn on_solution(&self, callback: HandlerFn<SolutionResponse>) -> HandlerId {
1746        self.handlers.solution.add(callback)
1747    }
1748
1749    /// Run and wait for background threads to exit or return an error
1750    pub async fn run(mut self) -> anyhow::Result<()> {
1751        if let Some(start_sender) = self.start_sender.take() {
1752            // Do not care if anyone is listening on the other side
1753            let _: Result<_, _> = start_sender.send(());
1754        }
1755
1756        while let Some(result) = self.tasks.next().instrument(self.span.clone()).await {
1757            result?;
1758        }
1759
1760        Ok(())
1761    }
1762
1763    /// Wipe everything that belongs to this single disk farm
1764    pub fn wipe(directory: &Path) -> io::Result<()> {
1765        let single_disk_info_info_path = directory.join(SingleDiskFarmInfo::FILE_NAME);
1766        match SingleDiskFarmInfo::load_from(directory) {
1767            Ok(Some(single_disk_farm_info)) => {
1768                info!("Found single disk farm {}", single_disk_farm_info.id());
1769            }
1770            Ok(None) => {
1771                return Err(io::Error::new(
1772                    io::ErrorKind::NotFound,
1773                    format!(
1774                        "Single disk farm info not found at {}",
1775                        single_disk_info_info_path.display()
1776                    ),
1777                ));
1778            }
1779            Err(error) => {
1780                warn!("Found unknown single disk farm: {}", error);
1781            }
1782        }
1783
1784        {
1785            let plot = directory.join(Self::PLOT_FILE);
1786            if plot.exists() {
1787                info!("Deleting plot file at {}", plot.display());
1788                fs::remove_file(plot)?;
1789            }
1790        }
1791        {
1792            let metadata = directory.join(Self::METADATA_FILE);
1793            if metadata.exists() {
1794                info!("Deleting metadata file at {}", metadata.display());
1795                fs::remove_file(metadata)?;
1796            }
1797        }
1798        // TODO: Identity should be able to wipe itself instead of assuming a specific file name
1799        //  here
1800        {
1801            let identity = directory.join("identity.bin");
1802            if identity.exists() {
1803                info!("Deleting identity file at {}", identity.display());
1804                fs::remove_file(identity)?;
1805            }
1806        }
1807
1808        DiskPieceCache::wipe(directory)?;
1809
1810        info!(
1811            "Deleting info file at {}",
1812            single_disk_info_info_path.display()
1813        );
1814        fs::remove_file(single_disk_info_info_path)
1815    }
1816
1817    /// Check the farm for corruption and repair errors (caused by disk errors or something else),
1818    /// returns an error when irrecoverable errors occur.
1819    pub fn scrub(
1820        directory: &Path,
1821        disable_farm_locking: bool,
1822        target: ScrubTarget,
1823        dry_run: bool,
1824    ) -> Result<(), SingleDiskFarmScrubError> {
1825        let span = Span::current();
1826
1827        if dry_run {
1828            info!("Dry run is used, no changes will be written to disk");
1829        }
1830
1831        if target.metadata() || target.plot() {
1832            let info = {
1833                let file = directory.join(SingleDiskFarmInfo::FILE_NAME);
1834                info!(path = %file.display(), "Checking info file");
1835
1836                match SingleDiskFarmInfo::load_from(directory) {
1837                    Ok(Some(info)) => info,
1838                    Ok(None) => {
1839                        return Err(SingleDiskFarmScrubError::FarmInfoFileDoesNotExist { file });
1840                    }
1841                    Err(error) => {
1842                        return Err(SingleDiskFarmScrubError::FarmInfoCantBeOpened { file, error });
1843                    }
1844                }
1845            };
1846
1847            let _single_disk_farm_info_lock = if disable_farm_locking {
1848                None
1849            } else {
1850                Some(
1851                    SingleDiskFarmInfo::try_lock(directory)
1852                        .map_err(SingleDiskFarmScrubError::LikelyAlreadyInUse)?,
1853                )
1854            };
1855
1856            let identity = {
1857                let file = directory.join(Identity::FILE_NAME);
1858                info!(path = %file.display(), "Checking identity file");
1859
1860                match Identity::open(directory) {
1861                    Ok(Some(identity)) => identity,
1862                    Ok(None) => {
1863                        return Err(SingleDiskFarmScrubError::IdentityFileDoesNotExist { file });
1864                    }
1865                    Err(error) => {
1866                        return Err(SingleDiskFarmScrubError::IdentityCantBeOpened { file, error });
1867                    }
1868                }
1869            };
1870
1871            if &identity.public_key() != info.public_key() {
1872                return Err(SingleDiskFarmScrubError::PublicKeyMismatch {
1873                    identity: identity.public_key(),
1874                    info: *info.public_key(),
1875                });
1876            }
1877
1878            let sector_metadata_size = SectorMetadataChecksummed::disk_size();
1879
1880            let metadata_file_path = directory.join(Self::METADATA_FILE);
1881            let (metadata_file, mut metadata_header) = {
1882                info!(path = %metadata_file_path.display(), "Checking metadata file");
1883
1884                let metadata_file = match OpenOptions::new()
1885                    .read(true)
1886                    .write(!dry_run)
1887                    .open(&metadata_file_path)
1888                {
1889                    Ok(metadata_file) => metadata_file,
1890                    Err(error) => {
1891                        return Err(if error.kind() == io::ErrorKind::NotFound {
1892                            SingleDiskFarmScrubError::MetadataFileDoesNotExist {
1893                                file: metadata_file_path,
1894                            }
1895                        } else {
1896                            SingleDiskFarmScrubError::MetadataCantBeOpened {
1897                                file: metadata_file_path,
1898                                error,
1899                            }
1900                        });
1901                    }
1902                };
1903
1904                // Error doesn't matter here
1905                let _: Result<(), _> = metadata_file.advise_sequential_access();
1906
1907                let metadata_size = match metadata_file.size() {
1908                    Ok(metadata_size) => metadata_size,
1909                    Err(error) => {
1910                        return Err(SingleDiskFarmScrubError::FailedToDetermineFileSize {
1911                            file: metadata_file_path,
1912                            error,
1913                        });
1914                    }
1915                };
1916
1917                if metadata_size < RESERVED_PLOT_METADATA {
1918                    return Err(SingleDiskFarmScrubError::MetadataFileTooSmall {
1919                        file: metadata_file_path,
1920                        reserved_size: RESERVED_PLOT_METADATA,
1921                        size: metadata_size,
1922                    });
1923                }
1924
1925                let mut metadata_header = {
1926                    let mut reserved_metadata = vec![0; RESERVED_PLOT_METADATA as usize];
1927
1928                    if let Err(error) = metadata_file.read_exact_at(&mut reserved_metadata, 0) {
1929                        return Err(SingleDiskFarmScrubError::FailedToReadBytes {
1930                            file: metadata_file_path,
1931                            size: RESERVED_PLOT_METADATA,
1932                            offset: 0,
1933                            error,
1934                        });
1935                    }
1936
1937                    PlotMetadataHeader::decode(&mut reserved_metadata.as_slice())
1938                        .map_err(SingleDiskFarmScrubError::FailedToDecodeMetadataHeader)?
1939                };
1940
1941                if metadata_header.version != SingleDiskFarm::SUPPORTED_PLOT_VERSION {
1942                    return Err(SingleDiskFarmScrubError::UnexpectedMetadataVersion(
1943                        metadata_header.version,
1944                    ));
1945                }
1946
1947                let plotted_sector_count = metadata_header.plotted_sector_count;
1948
1949                let expected_metadata_size = RESERVED_PLOT_METADATA
1950                    + sector_metadata_size as u64 * u64::from(plotted_sector_count);
1951
1952                if metadata_size < expected_metadata_size {
1953                    warn!(
1954                        %metadata_size,
1955                        %expected_metadata_size,
1956                        "Metadata file size is smaller than expected, shrinking number of plotted \
1957                        sectors to correct value"
1958                    );
1959
1960                    metadata_header.plotted_sector_count =
1961                        ((metadata_size - RESERVED_PLOT_METADATA) / sector_metadata_size as u64)
1962                            as u16;
1963                    let metadata_header_bytes = metadata_header.encode_page();
1964
1965                    if !dry_run
1966                        && let Err(error) = metadata_file.write_all_at(&metadata_header_bytes, 0)
1967                    {
1968                        return Err(SingleDiskFarmScrubError::FailedToWriteBytes {
1969                            file: metadata_file_path,
1970                            size: metadata_header_bytes.len() as u64,
1971                            offset: 0,
1972                            error,
1973                        });
1974                    }
1975                }
1976
1977                (metadata_file, metadata_header)
1978            };
1979
1980            let pieces_in_sector = info.pieces_in_sector();
1981            let sector_size = sector_size(pieces_in_sector) as u64;
1982
1983            let plot_file_path = directory.join(Self::PLOT_FILE);
1984            let plot_file = {
1985                let plot_file_path = directory.join(Self::PLOT_FILE);
1986                info!(path = %plot_file_path.display(), "Checking plot file");
1987
1988                let plot_file = match OpenOptions::new()
1989                    .read(true)
1990                    .write(!dry_run)
1991                    .open(&plot_file_path)
1992                {
1993                    Ok(plot_file) => plot_file,
1994                    Err(error) => {
1995                        return Err(if error.kind() == io::ErrorKind::NotFound {
1996                            SingleDiskFarmScrubError::MetadataFileDoesNotExist {
1997                                file: plot_file_path,
1998                            }
1999                        } else {
2000                            SingleDiskFarmScrubError::MetadataCantBeOpened {
2001                                file: plot_file_path,
2002                                error,
2003                            }
2004                        });
2005                    }
2006                };
2007
2008                // Error doesn't matter here
2009                let _: Result<(), _> = plot_file.advise_sequential_access();
2010
2011                let plot_size = match plot_file.size() {
2012                    Ok(metadata_size) => metadata_size,
2013                    Err(error) => {
2014                        return Err(SingleDiskFarmScrubError::FailedToDetermineFileSize {
2015                            file: plot_file_path,
2016                            error,
2017                        });
2018                    }
2019                };
2020
2021                let min_expected_plot_size =
2022                    u64::from(metadata_header.plotted_sector_count) * sector_size;
2023                if plot_size < min_expected_plot_size {
2024                    warn!(
2025                        %plot_size,
2026                        %min_expected_plot_size,
2027                        "Plot file size is smaller than expected, shrinking number of plotted \
2028                        sectors to correct value"
2029                    );
2030
2031                    metadata_header.plotted_sector_count = (plot_size / sector_size) as u16;
2032                    let metadata_header_bytes = metadata_header.encode_page();
2033
2034                    if !dry_run
2035                        && let Err(error) = metadata_file.write_all_at(&metadata_header_bytes, 0)
2036                    {
2037                        return Err(SingleDiskFarmScrubError::FailedToWriteBytes {
2038                            file: plot_file_path,
2039                            size: metadata_header_bytes.len() as u64,
2040                            offset: 0,
2041                            error,
2042                        });
2043                    }
2044                }
2045
2046                plot_file
2047            };
2048
2049            let sector_bytes_range = 0..(sector_size as usize - Blake3Hash::SIZE);
2050
2051            info!("Checking sectors and corresponding metadata");
2052            (0..metadata_header.plotted_sector_count)
2053                .into_par_iter()
2054                .map(SectorIndex::from)
2055                .map_init(
2056                    || vec![0u8; Record::SIZE],
2057                    |scratch_buffer, sector_index| {
2058                        let _span_guard = span.enter();
2059
2060                        let offset = RESERVED_PLOT_METADATA
2061                            + u64::from(sector_index) * sector_metadata_size as u64;
2062                        if let Err(error) = metadata_file
2063                            .read_exact_at(&mut scratch_buffer[..sector_metadata_size], offset)
2064                        {
2065                            warn!(
2066                                path = %metadata_file_path.display(),
2067                                %error,
2068                                %offset,
2069                                size = %sector_metadata_size,
2070                                %sector_index,
2071                                "Failed to read sector metadata, replacing with dummy expired \
2072                                sector metadata"
2073                            );
2074
2075                            if !dry_run {
2076                                write_dummy_sector_metadata(
2077                                    &metadata_file,
2078                                    &metadata_file_path,
2079                                    sector_index,
2080                                    pieces_in_sector,
2081                                )?;
2082                            }
2083                            return Ok(());
2084                        }
2085
2086                        let sector_metadata = match SectorMetadataChecksummed::decode_from_disk(
2087                            scratch_buffer
2088                                .first_chunk()
2089                                .expect("Scratch buffer is larger than sector metadata; qed"),
2090                        ) {
2091                            Ok(sector_metadata) => sector_metadata,
2092                            Err(error) => {
2093                                warn!(
2094                                    path = %metadata_file_path.display(),
2095                                    %error,
2096                                    %sector_index,
2097                                    "Failed to decode sector metadata, replacing with dummy \
2098                                    expired sector metadata"
2099                                );
2100
2101                                if !dry_run {
2102                                    write_dummy_sector_metadata(
2103                                        &metadata_file,
2104                                        &metadata_file_path,
2105                                        sector_index,
2106                                        pieces_in_sector,
2107                                    )?;
2108                                }
2109                                return Ok(());
2110                            }
2111                        };
2112
2113                        if sector_metadata.sector_index != sector_index {
2114                            warn!(
2115                                path = %metadata_file_path.display(),
2116                                %sector_index,
2117                                found_sector_index = %sector_metadata.sector_index,
2118                                "Sector index mismatch, replacing with dummy expired sector \
2119                                metadata"
2120                            );
2121
2122                            if !dry_run {
2123                                write_dummy_sector_metadata(
2124                                    &metadata_file,
2125                                    &metadata_file_path,
2126                                    sector_index,
2127                                    pieces_in_sector,
2128                                )?;
2129                            }
2130                            return Ok(());
2131                        }
2132
2133                        if sector_metadata.pieces_in_sector != pieces_in_sector {
2134                            warn!(
2135                                path = %metadata_file_path.display(),
2136                                %sector_index,
2137                                %pieces_in_sector,
2138                                found_pieces_in_sector = sector_metadata.pieces_in_sector,
2139                                "Pieces in sector mismatch, replacing with dummy expired sector \
2140                                metadata"
2141                            );
2142
2143                            if !dry_run {
2144                                write_dummy_sector_metadata(
2145                                    &metadata_file,
2146                                    &metadata_file_path,
2147                                    sector_index,
2148                                    pieces_in_sector,
2149                                )?;
2150                            }
2151                            return Ok(());
2152                        }
2153
2154                        if target.plot() {
2155                            let mut hasher = blake3::Hasher::new();
2156                            // Read sector bytes and compute checksum
2157                            for offset_in_sector in
2158                                sector_bytes_range.clone().step_by(scratch_buffer.len())
2159                            {
2160                                let offset =
2161                                    u64::from(sector_index) * sector_size + offset_in_sector as u64;
2162                                let bytes_to_read = (offset_in_sector + scratch_buffer.len())
2163                                    .min(sector_bytes_range.end)
2164                                    - offset_in_sector;
2165
2166                                let bytes = &mut scratch_buffer[..bytes_to_read];
2167
2168                                if let Err(error) = plot_file.read_exact_at(bytes, offset) {
2169                                    warn!(
2170                                        path = %plot_file_path.display(),
2171                                        %error,
2172                                        %sector_index,
2173                                        %offset,
2174                                        size = %bytes.len() as u64,
2175                                        "Failed to read sector bytes"
2176                                    );
2177
2178                                    continue;
2179                                }
2180
2181                                hasher.update(bytes);
2182                            }
2183
2184                            let actual_checksum = *hasher.finalize().as_bytes();
2185                            let mut expected_checksum = [0; Blake3Hash::SIZE];
2186                            {
2187                                let offset = u64::from(sector_index) * sector_size
2188                                    + sector_bytes_range.end as u64;
2189                                if let Err(error) =
2190                                    plot_file.read_exact_at(&mut expected_checksum, offset)
2191                                {
2192                                    warn!(
2193                                        path = %plot_file_path.display(),
2194                                        %error,
2195                                        %sector_index,
2196                                        %offset,
2197                                        size = %expected_checksum.len() as u64,
2198                                        "Failed to read sector checksum bytes"
2199                                    );
2200                                }
2201                            }
2202
2203                            // Verify checksum
2204                            if actual_checksum != expected_checksum {
2205                                warn!(
2206                                    path = %plot_file_path.display(),
2207                                    %sector_index,
2208                                    actual_checksum = %hex::encode(actual_checksum),
2209                                    expected_checksum = %hex::encode(expected_checksum),
2210                                    "Plotted sector checksum mismatch, replacing with dummy \
2211                                    expired sector"
2212                                );
2213
2214                                if !dry_run {
2215                                    write_dummy_sector_metadata(
2216                                        &metadata_file,
2217                                        &metadata_file_path,
2218                                        sector_index,
2219                                        pieces_in_sector,
2220                                    )?;
2221                                }
2222
2223                                scratch_buffer.fill(0);
2224
2225                                hasher.reset();
2226                                // Fill sector with zeroes and compute checksum
2227                                for offset_in_sector in
2228                                    sector_bytes_range.clone().step_by(scratch_buffer.len())
2229                                {
2230                                    let offset = u64::from(sector_index) * sector_size
2231                                        + offset_in_sector as u64;
2232                                    let bytes_to_write = (offset_in_sector + scratch_buffer.len())
2233                                        .min(sector_bytes_range.end)
2234                                        - offset_in_sector;
2235                                    let bytes = &mut scratch_buffer[..bytes_to_write];
2236
2237                                    if !dry_run
2238                                        && let Err(error) = plot_file.write_all_at(bytes, offset)
2239                                    {
2240                                        return Err(SingleDiskFarmScrubError::FailedToWriteBytes {
2241                                            file: plot_file_path.clone(),
2242                                            size: scratch_buffer.len() as u64,
2243                                            offset,
2244                                            error,
2245                                        });
2246                                    }
2247
2248                                    hasher.update(bytes);
2249                                }
2250                                // Write checksum
2251                                {
2252                                    let checksum = *hasher.finalize().as_bytes();
2253                                    let offset = u64::from(sector_index) * sector_size
2254                                        + sector_bytes_range.end as u64;
2255                                    if !dry_run
2256                                        && let Err(error) =
2257                                            plot_file.write_all_at(&checksum, offset)
2258                                    {
2259                                        return Err(SingleDiskFarmScrubError::FailedToWriteBytes {
2260                                            file: plot_file_path.clone(),
2261                                            size: checksum.len() as u64,
2262                                            offset,
2263                                            error,
2264                                        });
2265                                    }
2266                                }
2267
2268                                return Ok(());
2269                            }
2270                        }
2271
2272                        trace!(%sector_index, "Sector is in good shape");
2273
2274                        Ok(())
2275                    },
2276                )
2277                .try_for_each({
2278                    let span = &span;
2279                    let checked_sectors = AtomicUsize::new(0);
2280
2281                    move |result| {
2282                        let _span_guard = span.enter();
2283
2284                        let checked_sectors = checked_sectors.fetch_add(1, Ordering::Relaxed);
2285                        if checked_sectors > 1 && checked_sectors.is_multiple_of(10) {
2286                            info!(
2287                                "Checked {}/{} sectors",
2288                                checked_sectors, metadata_header.plotted_sector_count
2289                            );
2290                        }
2291
2292                        result
2293                    }
2294                })?;
2295        }
2296
2297        if target.cache() {
2298            Self::scrub_cache(directory, dry_run)?;
2299        }
2300
2301        info!("Farm check completed");
2302
2303        Ok(())
2304    }
2305
2306    fn scrub_cache(directory: &Path, dry_run: bool) -> Result<(), SingleDiskFarmScrubError> {
2307        let span = Span::current();
2308
2309        let file = directory.join(DiskPieceCache::FILE_NAME);
2310        info!(path = %file.display(), "Checking cache file");
2311
2312        let cache_file = match OpenOptions::new().read(true).write(!dry_run).open(&file) {
2313            Ok(plot_file) => plot_file,
2314            Err(error) => {
2315                return if error.kind() == io::ErrorKind::NotFound {
2316                    warn!(
2317                        file = %file.display(),
2318                        "Cache file does not exist, this is expected in farming cluster"
2319                    );
2320                    Ok(())
2321                } else {
2322                    Err(SingleDiskFarmScrubError::CacheCantBeOpened { file, error })
2323                };
2324            }
2325        };
2326
2327        // Error doesn't matter here
2328        let _: Result<(), _> = cache_file.advise_sequential_access();
2329
2330        let cache_size = match cache_file.size() {
2331            Ok(cache_size) => cache_size,
2332            Err(error) => {
2333                return Err(SingleDiskFarmScrubError::FailedToDetermineFileSize { file, error });
2334            }
2335        };
2336
2337        let element_size = DiskPieceCache::element_size();
2338        let number_of_cached_elements = cache_size / u64::from(element_size);
2339        let dummy_element = vec![0; element_size as usize];
2340        (0..number_of_cached_elements)
2341            .into_par_iter()
2342            .map_with(vec![0; element_size as usize], |element, cache_offset| {
2343                let _span_guard = span.enter();
2344
2345                let offset = cache_offset * u64::from(element_size);
2346                if let Err(error) = cache_file.read_exact_at(element, offset) {
2347                    warn!(
2348                        path = %file.display(),
2349                        %cache_offset,
2350                        size = %element.len() as u64,
2351                        %offset,
2352                        %error,
2353                        "Failed to read cached piece, replacing with dummy element"
2354                    );
2355
2356                    if !dry_run && let Err(error) = cache_file.write_all_at(&dummy_element, offset)
2357                    {
2358                        return Err(SingleDiskFarmScrubError::FailedToWriteBytes {
2359                            file: file.clone(),
2360                            size: u64::from(element_size),
2361                            offset,
2362                            error,
2363                        });
2364                    }
2365
2366                    return Ok(());
2367                }
2368
2369                let (index_and_piece_bytes, remaining_bytes) =
2370                    element.split_at(PieceIndex::SIZE + Piece::SIZE);
2371                // The rest of the element after the checksum is zero padding
2372                let (expected_checksum, _padding) = remaining_bytes.split_at(Blake3Hash::SIZE);
2373                let actual_checksum = *blake3::hash(index_and_piece_bytes).as_bytes();
2374                if actual_checksum != expected_checksum && element != &dummy_element {
2375                    warn!(
2376                        %cache_offset,
2377                        actual_checksum = %hex::encode(actual_checksum),
2378                        expected_checksum = %hex::encode(expected_checksum),
2379                        "Cached piece checksum mismatch, replacing with dummy element"
2380                    );
2381
2382                    if !dry_run && let Err(error) = cache_file.write_all_at(&dummy_element, offset)
2383                    {
2384                        return Err(SingleDiskFarmScrubError::FailedToWriteBytes {
2385                            file: file.clone(),
2386                            size: u64::from(element_size),
2387                            offset,
2388                            error,
2389                        });
2390                    }
2391
2392                    return Ok(());
2393                }
2394
2395                Ok(())
2396            })
2397            .try_for_each({
2398                let span = &span;
2399                let checked_elements = AtomicUsize::new(0);
2400
2401                move |result| {
2402                    let _span_guard = span.enter();
2403
2404                    let checked_elements = checked_elements.fetch_add(1, Ordering::Relaxed);
2405                    if checked_elements > 1 && checked_elements.is_multiple_of(1000) {
2406                        info!(
2407                            "Checked {}/{} cache elements",
2408                            checked_elements, number_of_cached_elements
2409                        );
2410                    }
2411
2412                    result
2413                }
2414            })?;
2415
2416        Ok(())
2417    }
2418}
2419
2420fn write_dummy_sector_metadata(
2421    metadata_file: &File,
2422    metadata_file_path: &Path,
2423    sector_index: SectorIndex,
2424    pieces_in_sector: u16,
2425) -> Result<(), SingleDiskFarmScrubError> {
2426    let dummy_sector_bytes = SectorMetadataChecksummed::from(SectorMetadata {
2427        sector_index,
2428        pieces_in_sector,
2429        s_bucket_sizes: Box::new([0; Record::NUM_S_BUCKETS]),
2430        history_size: HistorySize::from(SegmentIndex::ZERO),
2431    })
2432    .encode_for_disk();
2433    let sector_offset = RESERVED_PLOT_METADATA
2434        + u64::from(sector_index) * SectorMetadataChecksummed::disk_size() as u64;
2435    metadata_file
2436        .write_all_at(dummy_sector_bytes.as_slice(), sector_offset)
2437        .map_err(|error| SingleDiskFarmScrubError::FailedToWriteBytes {
2438            file: metadata_file_path.to_path_buf(),
2439            size: dummy_sector_bytes.len() as u64,
2440            offset: sector_offset,
2441            error,
2442        })
2443}