Skip to main content

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