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