1pub 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
89const_assert!(mem::size_of::<usize>() >= mem::size_of::<u64>());
92
93const RESERVED_PLOT_METADATA: u64 = 1024 * 1024;
95const RESERVED_FARM_INFO: u64 = 1024 * 1024;
97const NEW_SEGMENT_PROCESSING_DELAY: Duration = Duration::from_mins(10);
98
99#[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#[derive(Debug, Copy, Clone, Serialize, Deserialize)]
108#[serde(rename_all = "camelCase")]
109pub enum SingleDiskFarmInfo {
110 #[serde(rename_all = "camelCase")]
112 V0 {
113 id: FarmId,
115 #[serde(with = "hex")]
117 genesis_hash: [u8; 32],
118 public_key: PublicKey,
120 pieces_in_sector: u16,
122 allocated_space: u64,
124 },
125}
126
127impl SingleDiskFarmInfo {
128 const FILE_NAME: &'static str = "single_disk_farm.json";
129
130 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 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 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 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 pub fn id(&self) -> &FarmId {
199 let Self::V0 { id, .. } = self;
200 id
201 }
202
203 pub fn genesis_hash(&self) -> &[u8; 32] {
205 let Self::V0 { genesis_hash, .. } = self;
206 genesis_hash
207 }
208
209 pub fn public_key(&self) -> &PublicKey {
211 let Self::V0 { public_key, .. } = self;
212 public_key
213 }
214
215 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 pub fn allocated_space(&self) -> u64 {
226 match self {
227 SingleDiskFarmInfo::V0 {
228 allocated_space, ..
229 } => *allocated_space,
230 }
231 }
232}
233
234#[derive(Debug)]
236pub enum SingleDiskFarmSummary {
237 Found {
239 info: SingleDiskFarmInfo,
241 directory: PathBuf,
243 },
244 NotFound {
246 directory: PathBuf,
248 },
249 Error {
251 directory: PathBuf,
253 error: io::Error,
255 },
256}
257
258#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Encode, Decode)]
259enum PlotMetadataVersion {
260 #[codec(index = 0)]
262 V0,
263 #[codec(index = 1)]
265 V1,
266}
267
268impl PlotMetadataVersion {
269 const LATEST: Self = Self::V1;
271}
272
273#[derive(Debug, Encode)]
274struct PlotMetadataHeader {
275 version: PlotMetadataVersion,
276 plotted_sector_count: SectorIndex,
277 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 cutover: Some(HistorySize::from(SegmentIndex::ZERO)),
292 };
293
294 default.encoded_size()
295 }
296}
297
298impl Decode for PlotMetadataHeader {
302 fn decode<I: parity_scale_codec::Input>(
303 input: &mut I,
304 ) -> Result<Self, parity_scale_codec::Error> {
305 let version = PlotMetadataVersion::decode(input)?;
307 let plotted_sector_count = SectorIndex::decode(input)?;
308 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
322fn is_post_cutover(cutover: Option<HistorySize>, history_size: HistorySize) -> bool {
324 cutover.is_none_or(|cutover| history_size > cutover)
325}
326
327#[derive(Debug)]
329pub struct SingleDiskFarmOptions<'a, NC>
330where
331 NC: Clone,
332{
333 pub directory: PathBuf,
335 pub farmer_app_info: FarmerAppInfo,
337 pub allocated_space: u64,
339 pub max_pieces_in_sector: u16,
341 pub node_client: NC,
343 pub reward_address: PublicKey,
345 pub plotter: Arc<dyn Plotter + Send + Sync>,
347 pub kzg: Kzg,
349 pub erasure_coding: ErasureCoding,
351 pub cache_percentage: u8,
353 pub farming_thread_pool_size: usize,
356 pub plotting_delay: Option<oneshot::Receiver<()>>,
359 pub global_mutex: Arc<AsyncMutex<()>>,
363 pub max_plotting_sectors_per_farm: NonZeroUsize,
365 pub disable_farm_locking: bool,
367 pub read_sector_record_chunks_mode: ReadSectorRecordChunksMode,
369 pub registry: Option<&'a Mutex<&'a mut Registry>>,
371 pub create: bool,
373}
374
375#[derive(Debug, Error)]
377pub enum SingleDiskFarmError {
378 #[error("Failed to open or create identity: {0}")]
380 FailedToOpenIdentity(#[from] IdentityError),
381 #[error("Farm is likely already in use, make sure no other farmer is using it: {0}")]
383 LikelyAlreadyInUse(io::Error),
384 #[error("Single disk farm I/O error: {0}")]
386 Io(#[from] io::Error),
387 #[error("Failed to spawn task for blocking thread: {0}")]
389 TokioJoinError(#[from] task::JoinError),
390 #[error("Piece cache error: {0}")]
392 PieceCacheError(#[from] DiskPieceCacheError),
393 #[error("Can't preallocate metadata file, probably not enough space on disk: {0}")]
395 CantPreallocateMetadataFile(io::Error),
396 #[error("Can't preallocate plot file, probably not enough space on disk: {0}")]
398 CantPreallocatePlotFile(io::Error),
399 #[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 id: FarmId,
407 correct_chain: String,
410 wrong_chain: String,
412 },
413 #[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 id: FarmId,
421 correct_public_key: PublicKey,
423 wrong_public_key: PublicKey,
425 },
426 #[error(
428 "Invalid number pieces in sector: max supported {max_supported}, farm initialized with \
429 {initialized_with}"
430 )]
431 InvalidPiecesInSector {
432 id: FarmId,
434 max_supported: u16,
436 initialized_with: u16,
438 },
439 #[error("Failed to decode metadata header: {0}")]
441 FailedToDecodeMetadataHeader(parity_scale_codec::Error),
442 #[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 min_space: u64,
451 allocated_space: u64,
453 },
454 #[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: u64,
463 allocated_sectors: u64,
465 max_space: u64,
467 max_sectors: u16,
469 },
470 #[error("Failed to create thread pool: {0}")]
472 FailedToCreateThreadPool(ThreadPoolBuildError),
473}
474
475#[derive(Debug, Error)]
477pub enum SingleDiskFarmScrubError {
478 #[error("Farm is likely already in use, make sure no other farmer is using it: {0}")]
480 LikelyAlreadyInUse(io::Error),
481 #[error("Failed to file size of {file}: {error}")]
483 FailedToDetermineFileSize {
484 file: PathBuf,
486 error: io::Error,
488 },
489 #[error("Failed to read {size} bytes from {file} at offset {offset}: {error}")]
491 FailedToReadBytes {
492 file: PathBuf,
494 size: u64,
496 offset: u64,
498 error: io::Error,
500 },
501 #[error("Failed to write {size} bytes from {file} at offset {offset}: {error}")]
503 FailedToWriteBytes {
504 file: PathBuf,
506 size: u64,
508 offset: u64,
510 error: io::Error,
512 },
513 #[error("Farm info file does not exist at {file}")]
515 FarmInfoFileDoesNotExist {
516 file: PathBuf,
518 },
519 #[error("Farm info at {file} can't be opened: {error}")]
521 FarmInfoCantBeOpened {
522 file: PathBuf,
524 error: io::Error,
526 },
527 #[error("Identity file does not exist at {file}")]
529 IdentityFileDoesNotExist {
530 file: PathBuf,
532 },
533 #[error("Identity at {file} can't be opened: {error}")]
535 IdentityCantBeOpened {
536 file: PathBuf,
538 error: IdentityError,
540 },
541 #[error("Identity public key {identity} doesn't match public key in the disk farm info {info}")]
543 PublicKeyMismatch {
544 identity: PublicKey,
546 info: PublicKey,
548 },
549 #[error("Metadata file does not exist at {file}")]
551 MetadataFileDoesNotExist {
552 file: PathBuf,
554 },
555 #[error("Metadata at {file} can't be opened: {error}")]
557 MetadataCantBeOpened {
558 file: PathBuf,
560 error: io::Error,
562 },
563 #[error(
565 "Metadata file at {file} is too small: reserved size is {reserved_size} bytes, file size \
566 is {size}"
567 )]
568 MetadataFileTooSmall {
569 file: PathBuf,
571 reserved_size: u64,
573 size: u64,
575 },
576 #[error("Failed to decode metadata header: {0}")]
578 FailedToDecodeMetadataHeader(parity_scale_codec::Error),
579 #[error("Cache at {file} can't be opened: {error}")]
581 CacheCantBeOpened {
582 file: PathBuf,
584 error: io::Error,
586 },
587}
588
589#[derive(Debug, Error)]
591pub enum BackgroundTaskError {
592 #[error(transparent)]
594 Plotting(#[from] PlottingError),
595 #[error(transparent)]
597 Farming(#[from] FarmingError),
598 #[error(transparent)]
600 RewardSigning(#[from] anyhow::Error),
601 #[error("Background task {task} panicked")]
603 BackgroundTaskPanicked {
604 task: String,
606 },
607}
608
609type BackgroundTask = Pin<Box<dyn Future<Output = Result<(), BackgroundTaskError>> + Send>>;
610
611#[derive(Debug, Copy, Clone)]
613pub enum ScrubTarget {
614 All,
616 Metadata,
618 Plot,
620 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 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 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 (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 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 let plot_file_size =
723 plot_file_size.div_ceil(DISK_SECTOR_SIZE as u64) * DISK_SECTOR_SIZE as u64;
724
725 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 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#[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 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 start_sender: Option<broadcast::Sender<()>>,
807 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 self.start_sender.take();
818 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 pub const PLOT_FILE: &'static str = "plot.bin";
867 pub const METADATA_FILE: &'static str = "metadata.bin";
869
870 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 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 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 let cutover = metadata_header.cutover;
1019
1020 let plotting_join_handle = task::spawn_blocking({
1021 let sectors_metadata = Arc::clone(§ors_metadata);
1022 let handlers = Arc::clone(&handlers);
1023 let sectors_being_modified = Arc::clone(§ors_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: §ors_metadata,
1037 sectors_being_modified: §ors_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 return Ok(());
1058 }
1059
1060 if let Some(plotting_delay) = plotting_delay
1061 && plotting_delay.await.is_err()
1062 {
1063 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 }
1086 }
1087 });
1088 }
1089 });
1090 let plotting_join_handle = AsyncJoinOnDrop::new(plotting_join_handle, false);
1091
1092 tasks.push(Box::pin(async move {
1093 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(§ors_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(§ors_being_modified);
1133 let sectors_metadata = Arc::clone(§ors_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 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 }
1187 }
1188 });
1189 }
1190 });
1191 let farming_join_handle = AsyncJoinOnDrop::new(farming_join_handle, false);
1192
1193 tasks.push(Box::pin(async move {
1194 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(§ors_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 }
1224 _ = stop_receiver.recv().fuse() => {
1225 }
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 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 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 metadata_file
1420 .preallocate(expected_metadata_size)
1421 .map_err(SingleDiskFarmError::CantPreallocateMetadataFile)?;
1422 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 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 plot_file
1494 .preallocate(allocated_space_distribution.plot_file_size)
1495 .map_err(SingleDiskFarmError::CantPreallocatePlotFile)?;
1496 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 §ors_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 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 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 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 } 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 } 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 } else {
1619 return Err(error.into());
1620 }
1621 }
1622 };
1623
1624 Ok(effective_disk_usage)
1625 }
1626
1627 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 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 pub fn id(&self) -> &FarmId {
1673 self.single_disk_farm_info.id()
1674 }
1675
1676 pub fn info(&self) -> &SingleDiskFarmInfo {
1678 &self.single_disk_farm_info
1679 }
1680
1681 pub fn total_sectors_count(&self) -> SectorIndex {
1683 self.total_sectors_count
1684 }
1685
1686 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 pub fn piece_cache(&self) -> SingleDiskPieceCache {
1698 self.piece_cache.clone()
1699 }
1700
1701 pub fn plot_cache(&self) -> DiskPlotCache {
1703 self.plot_cache.clone()
1704 }
1705
1706 pub fn piece_reader(&self) -> DiskPieceReader {
1708 self.piece_reader.clone()
1709 }
1710
1711 pub fn on_sector_update(&self, callback: HandlerFn<(SectorIndex, SectorUpdate)>) -> HandlerId {
1713 self.handlers.sector_update.add(callback)
1714 }
1715
1716 pub fn on_farming_notification(&self, callback: HandlerFn<FarmingNotification>) -> HandlerId {
1718 self.handlers.farming_notification.add(callback)
1719 }
1720
1721 pub fn on_solution(&self, callback: HandlerFn<SolutionResponse>) -> HandlerId {
1723 self.handlers.solution.add(callback)
1724 }
1725
1726 pub async fn run(mut self) -> anyhow::Result<()> {
1728 if let Some(start_sender) = self.start_sender.take() {
1729 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 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 {
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 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 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 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 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 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 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 {
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 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 #[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 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 assert!(is_post_cutover(None, h(1)));
2453 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}