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