Skip to main content

ab_farmer/
farm.rs

1//! Abstract farm API
2//!
3//! This module provides a bunch of traits and simple data structures that serve as a layer of
4//! abstraction that improves composition without having assumptions about implementation details.
5//!
6//! Implementations can be local (backed by local disk) and remote (connected via network in some
7//! way). This crate provides a few of such implementations, but more can be created externally as
8//! well if needed without modifying the library itself.
9
10use ab_core_primitives::pieces::{Piece, PieceIndex, PieceOffset};
11use ab_core_primitives::sectors::SectorIndex;
12use ab_core_primitives::segments::SegmentIndex;
13use ab_farmer_components::auditing::AuditingError;
14use ab_farmer_components::plotting::PlottedSector;
15use ab_farmer_components::proving::ProvingError;
16use ab_farmer_rpc_primitives::SolutionResponse;
17use ab_networking::libp2p::kad::RecordKey;
18use async_trait::async_trait;
19use derive_more::{Display, From};
20use futures::Stream;
21use parity_scale_codec::{Decode, Encode, EncodeLike, Input, Output};
22use serde::{Deserialize, Serialize};
23use std::future::Future;
24use std::pin::Pin;
25use std::sync::Arc;
26use std::time::Duration;
27use std::{fmt, io};
28use thiserror::Error;
29use ulid::Ulid;
30
31pub mod plotted_pieces;
32
33/// Erased error type
34pub type FarmError = Box<dyn std::error::Error + Send + Sync + 'static>;
35/// Type alias used for event handlers
36pub type HandlerFn<A> = Arc<dyn Fn(&A) + Send + Sync + 'static>;
37
38/// Getter for plotted sectors
39#[async_trait]
40pub trait PlottedSectors: Send + Sync + fmt::Debug {
41    /// Get already plotted sectors
42    async fn get(
43        &self,
44    ) -> Result<
45        Box<dyn Stream<Item = Result<PlottedSector, FarmError>> + Unpin + Send + '_>,
46        FarmError,
47    >;
48}
49
50/// An identifier for a cache, can be used for in logs, thread names, etc.
51#[derive(
52    Debug, Copy, Clone, Ord, PartialOrd, Eq, PartialEq, Hash, Serialize, Deserialize, Display, From,
53)]
54#[serde(untagged)]
55pub enum PieceCacheId {
56    /// Cache ID
57    Ulid(Ulid),
58}
59
60impl Encode for PieceCacheId {
61    #[inline]
62    fn size_hint(&self) -> usize {
63        1_usize
64            + match self {
65                PieceCacheId::Ulid(ulid) => 0_usize.saturating_add(Encode::size_hint(&ulid.0)),
66            }
67    }
68
69    #[inline]
70    fn encode_to<O>(&self, dest: &mut O)
71    where
72        O: Output + ?Sized,
73    {
74        match self {
75            PieceCacheId::Ulid(ulid) => {
76                dest.push_byte(0);
77                Encode::encode_to(&ulid.0, dest);
78            }
79        }
80    }
81}
82
83impl EncodeLike for PieceCacheId {}
84
85impl Decode for PieceCacheId {
86    #[inline]
87    fn decode<I>(input: &mut I) -> Result<Self, parity_scale_codec::Error>
88    where
89        I: Input,
90    {
91        match input
92            .read_byte()
93            .map_err(|e| e.chain("Could not decode `PieceCacheId`, failed to read variant byte"))?
94        {
95            0 => u128::decode(input)
96                .map(|ulid| PieceCacheId::Ulid(Ulid(ulid)))
97                .map_err(|e| e.chain("Could not decode `PieceCacheId::Ulid.0`")),
98            _ => Err("Could not decode `PieceCacheId`, variant doesn't exist".into()),
99        }
100    }
101}
102
103#[expect(
104    clippy::new_without_default,
105    reason = "Default has different semantics"
106)]
107impl PieceCacheId {
108    /// Creates new ID
109    #[inline]
110    pub fn new() -> Self {
111        Self::Ulid(Ulid::generate())
112    }
113}
114
115/// Offset wrapper for pieces in [`PieceCache`]
116#[derive(Debug, Display, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Encode, Decode)]
117#[repr(transparent)]
118pub struct PieceCacheOffset(pub(crate) u32);
119
120/// Abstract piece cache implementation.
121///
122/// Piece cache is a simple container that stores concatenated pieces in a flat file at specific
123/// offsets. Implementation doesn't have to be local though, cache can be remote somewhere on the
124/// network, APIs are intentionally async to account for that.
125#[async_trait]
126pub trait PieceCache: Send + Sync + fmt::Debug {
127    /// ID of this cache
128    fn id(&self) -> &PieceCacheId;
129
130    /// Max number of elements in this cache
131    fn max_num_elements(&self) -> u32;
132
133    /// Contents of this piece cache.
134    ///
135    /// NOTE: it is possible to do concurrent reads and writes, higher level logic must ensure this
136    /// doesn't happen for the same piece being accessed!
137    async fn contents(
138        &self,
139    ) -> Result<
140        Box<
141            dyn Stream<Item = Result<(PieceCacheOffset, Option<PieceIndex>), FarmError>>
142                + Unpin
143                + Send
144                + '_,
145        >,
146        FarmError,
147    >;
148
149    /// Store piece in cache at specified offset, replacing existing piece if there is one.
150    ///
151    /// NOTE: it is possible to do concurrent reads and writes, higher level logic must ensure this
152    /// doesn't happen for the same piece being accessed!
153    async fn write_piece(
154        &self,
155        offset: PieceCacheOffset,
156        piece_index: PieceIndex,
157        piece: &Piece,
158    ) -> Result<(), FarmError>;
159
160    /// Read piece index from cache at specified offset.
161    ///
162    /// Returns `None` if offset is out of range.
163    ///
164    /// NOTE: it is possible to do concurrent reads and writes, higher level logic must ensure this
165    /// doesn't happen for the same piece being accessed!
166    async fn read_piece_index(
167        &self,
168        offset: PieceCacheOffset,
169    ) -> Result<Option<PieceIndex>, FarmError>;
170
171    /// Read piece from cache at specified offset.
172    ///
173    /// Returns `None` if offset is out of range.
174    ///
175    /// NOTE: it is possible to do concurrent reads and writes, higher level logic must ensure this
176    /// doesn't happen for the same piece being accessed!
177    async fn read_piece(
178        &self,
179        offset: PieceCacheOffset,
180    ) -> Result<Option<(PieceIndex, Piece)>, FarmError>;
181
182    /// Read pieces from cache at specified offsets.
183    ///
184    /// Number of elements in returned stream is the same as number of unique `offsets`.
185    /// Returns `None` for offsets that are out of range.
186    ///
187    /// NOTE: it is possible to do concurrent reads and writes, higher level logic must ensure this
188    /// doesn't happen for the same pieces being accessed!
189    async fn read_pieces(
190        &self,
191        offsets: Box<dyn Iterator<Item = PieceCacheOffset> + Send>,
192    ) -> Result<
193        Box<
194            dyn Stream<Item = Result<(PieceCacheOffset, Option<(PieceIndex, Piece)>), FarmError>>
195                + Send
196                + Unpin
197                + '_,
198        >,
199        FarmError,
200    >;
201}
202
203/// Result of piece storing check
204#[derive(Debug, Copy, Clone, Encode, Decode)]
205pub enum MaybePieceStoredResult {
206    /// Piece is not stored already, and can't be added because the cache/plot is full.
207    No,
208    /// Cache might have a vacant slot to store this piece.
209    /// Vacant slots are not guaranteed, they can be overwritten by another piece or newly plotted
210    /// sector at any time.
211    Vacant,
212    /// Piece is already stored in the cache.
213    Yes,
214}
215
216/// Abstract plot cache implementation.
217///
218/// Plot cache is a cache that exploits space towards the end of the plot that is not yet occupied
219/// by sectors in order to increase effective caching space, which helps with plotting speed for
220/// small farmers since they don't need to retrieve the same pieces from the network over and over
221/// again, which is slower and uses a lot of Internet bandwidth.
222#[async_trait]
223pub trait PlotCache: Send + Sync + fmt::Debug {
224    /// Check if a piece is already stored in this cache, or it can be added to this cache.
225    /// The piece is not guaranteed to be stored, because it might be overwritten with a new
226    /// sector any time.
227    async fn is_piece_maybe_stored(
228        &self,
229        key: &RecordKey,
230    ) -> Result<MaybePieceStoredResult, FarmError>;
231
232    /// Store piece in cache if there is free space, and return `Ok(true)`.
233    /// Returns `Ok(false)` if there is no free space, or the farm or process is shutting down.
234    async fn try_store_piece(
235        &self,
236        piece_index: PieceIndex,
237        piece: &Piece,
238    ) -> Result<bool, FarmError>;
239
240    /// Read piece from cache.
241    ///
242    /// Returns `None` if not cached.
243    async fn read_piece(&self, key: &RecordKey) -> Result<Option<Piece>, FarmError>;
244}
245
246/// Auditing details
247#[derive(Debug, Copy, Clone, Encode, Decode)]
248pub struct AuditingDetails {
249    /// Number of sectors that were audited
250    pub sectors_count: u16,
251    /// Audit duration
252    pub time: Duration,
253}
254
255/// Result of the proving
256#[derive(Debug, Copy, Clone, Encode, Decode)]
257pub enum ProvingResult {
258    /// Proved successfully and accepted by the node
259    Success,
260    /// Proving took too long
261    Timeout,
262    /// Managed to prove within time limit, but node rejected solution, likely due to timeout on its
263    /// end
264    Rejected,
265    /// Proving failed altogether
266    Failed,
267}
268
269impl fmt::Display for ProvingResult {
270    #[inline]
271    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
272        f.write_str(match self {
273            Self::Success => "Success",
274            Self::Timeout => "Timeout",
275            Self::Rejected => "Rejected",
276            Self::Failed => "Failed",
277        })
278    }
279}
280
281/// Proving details
282#[derive(Debug, Copy, Clone, Encode, Decode)]
283pub struct ProvingDetails {
284    /// Whether proving ended up being successful
285    pub result: ProvingResult,
286    /// Audit duration
287    pub time: Duration,
288}
289
290/// Special decoded farming error
291#[derive(Debug, Encode, Decode)]
292pub struct DecodedFarmingError {
293    /// String representation of an error
294    error: String,
295    /// Whether error is fatal
296    is_fatal: bool,
297}
298
299impl fmt::Display for DecodedFarmingError {
300    #[inline]
301    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
302        fmt::Display::fmt(&self.error, f)
303    }
304}
305
306/// Errors that happen during farming
307#[derive(Debug, Error)]
308pub enum FarmingError {
309    /// Failed to subscribe to slot info notifications
310    #[error("Failed to subscribe to slot info notifications: {error}")]
311    FailedToSubscribeSlotInfo {
312        /// Lower-level error
313        error: anyhow::Error,
314    },
315    /// Failed to retrieve farmer info
316    #[error("Failed to retrieve farmer info: {error}")]
317    FailedToGetFarmerInfo {
318        /// Lower-level error
319        error: anyhow::Error,
320    },
321    /// Slot info notification stream ended
322    #[error("Slot info notification stream ended")]
323    SlotNotificationStreamEnded,
324    /// Low-level auditing error
325    #[error("Low-level auditing error: {0}")]
326    LowLevelAuditing(#[from] AuditingError),
327    /// Low-level proving error
328    #[error("Low-level proving error: {0}")]
329    LowLevelProving(#[from] ProvingError),
330    /// I/O error occurred
331    #[error("Farming I/O error: {0}")]
332    Io(#[from] io::Error),
333    /// Decoded farming error
334    #[error("Decoded farming error {0}")]
335    Decoded(DecodedFarmingError),
336}
337
338impl Encode for FarmingError {
339    #[inline]
340    fn encode_to<O>(&self, dest: &mut O)
341    where
342        O: Output + ?Sized,
343    {
344        let error = DecodedFarmingError {
345            error: self.to_string(),
346            is_fatal: self.is_fatal(),
347        };
348
349        error.encode_to(dest);
350    }
351}
352
353impl Decode for FarmingError {
354    #[inline]
355    fn decode<I>(input: &mut I) -> Result<Self, parity_scale_codec::Error>
356    where
357        I: Input,
358    {
359        DecodedFarmingError::decode(input).map(FarmingError::Decoded)
360    }
361}
362
363impl FarmingError {
364    /// String variant of the error, primarily for monitoring purposes
365    #[inline]
366    pub fn str_variant(&self) -> &str {
367        #[expect(
368            clippy::rest_pattern_accessible_field,
369            reason = "Do not care about fields"
370        )]
371        match self {
372            FarmingError::FailedToSubscribeSlotInfo { .. } => "FailedToSubscribeSlotInfo",
373            FarmingError::FailedToGetFarmerInfo { .. } => "FailedToGetFarmerInfo",
374            FarmingError::LowLevelAuditing(_) => "LowLevelAuditing",
375            FarmingError::LowLevelProving(_) => "LowLevelProving",
376            FarmingError::Io(_) => "Io",
377            FarmingError::Decoded(_) => "Decoded",
378            FarmingError::SlotNotificationStreamEnded => "SlotNotificationStreamEnded",
379        }
380    }
381
382    /// Whether this error is fatal and makes farm unusable
383    pub fn is_fatal(&self) -> bool {
384        #[expect(
385            clippy::rest_pattern_accessible_field,
386            reason = "Do not care about fields"
387        )]
388        match self {
389            FarmingError::FailedToSubscribeSlotInfo { .. } => true,
390            FarmingError::FailedToGetFarmerInfo { .. } => true,
391            FarmingError::LowLevelAuditing(_) => true,
392            FarmingError::LowLevelProving(error) => error.is_fatal(),
393            FarmingError::Io(_) => true,
394            FarmingError::Decoded(error) => error.is_fatal,
395            FarmingError::SlotNotificationStreamEnded => true,
396        }
397    }
398}
399
400/// Various farming notifications
401#[derive(Debug, Clone, Encode, Decode)]
402pub enum FarmingNotification {
403    /// Auditing
404    Auditing(AuditingDetails),
405    /// Proving
406    Proving(ProvingDetails),
407    /// Non-fatal farming error
408    NonFatalError(Arc<FarmingError>),
409}
410
411/// Details about sector currently being plotted
412#[derive(Debug, Clone, Encode, Decode)]
413pub enum SectorPlottingDetails {
414    /// Starting plotting of a sector
415    Starting {
416        /// Progress so far in % (not including this sector)
417        progress: f32,
418        /// Whether sector is being replotted
419        replotting: bool,
420        /// Whether this is the last sector queued so far
421        last_queued: bool,
422    },
423    /// Downloading sector pieces
424    Downloading,
425    /// Downloaded sector pieces
426    Downloaded(Duration),
427    /// Encoding sector pieces
428    Encoding,
429    /// Encoded sector pieces
430    Encoded(Duration),
431    /// Writing sector
432    Writing,
433    /// Written sector
434    Written(Duration),
435    /// Finished plotting
436    Finished {
437        /// Information about plotted sector
438        plotted_sector: PlottedSector,
439        /// Information about old plotted sector that was replaced
440        old_plotted_sector: Option<PlottedSector>,
441        /// How much time it took to plot a sector
442        time: Duration,
443    },
444    /// Plotting failed
445    Error(String),
446}
447
448/// Details about sector expiration
449#[derive(Debug, Clone, Encode, Decode)]
450pub enum SectorExpirationDetails {
451    /// Sector expiration became known
452    Determined {
453        /// Segment index at which sector expires
454        expires_at: SegmentIndex,
455    },
456    /// Sector will expire at the next segment index and should be replotted
457    AboutToExpire,
458    /// Sector already expired
459    Expired,
460}
461
462/// Various sector updates
463#[derive(Debug, Clone, Encode, Decode)]
464pub enum SectorUpdate {
465    /// Sector is being plotted
466    Plotting(SectorPlottingDetails),
467    /// Sector expiration information updated
468    Expiration(SectorExpirationDetails),
469}
470
471/// Abstract piece reader implementation
472#[async_trait]
473pub trait PieceReader: Send + Sync + fmt::Debug {
474    /// Read piece from sector by offset, `None` means input parameters are incorrect or piece
475    /// reader was shut down
476    async fn read_piece(
477        &self,
478        sector_index: SectorIndex,
479        piece_offset: PieceOffset,
480    ) -> Result<Option<Piece>, FarmError>;
481}
482
483/// Opaque handler ID for event handlers, once dropped handler will be removed automatically
484pub trait HandlerId: Send + Sync + fmt::Debug {
485    /// Consumes [`HandlerId`] and prevents handler from being removed automatically.
486    fn detach(&self);
487}
488
489impl HandlerId for event_listener_primitives::HandlerId {
490    #[inline]
491    fn detach(&self) {
492        self.detach();
493    }
494}
495
496/// An identifier for a farm, can be used for in logs, thread names, etc.
497#[derive(
498    Debug, Copy, Clone, Ord, PartialOrd, Eq, PartialEq, Hash, Serialize, Deserialize, Display, From,
499)]
500#[serde(untagged)]
501pub enum FarmId {
502    /// Farm ID
503    Ulid(Ulid),
504}
505
506impl Encode for FarmId {
507    #[inline]
508    fn size_hint(&self) -> usize {
509        1_usize
510            + match self {
511                FarmId::Ulid(ulid) => 0_usize.saturating_add(Encode::size_hint(&ulid.0)),
512            }
513    }
514
515    #[inline]
516    fn encode_to<O>(&self, dest: &mut O)
517    where
518        O: Output + ?Sized,
519    {
520        match self {
521            FarmId::Ulid(ulid) => {
522                dest.push_byte(0);
523                Encode::encode_to(&ulid.0, dest);
524            }
525        }
526    }
527}
528
529impl EncodeLike for FarmId {}
530
531impl Decode for FarmId {
532    #[inline]
533    fn decode<I>(input: &mut I) -> Result<Self, parity_scale_codec::Error>
534    where
535        I: Input,
536    {
537        match input
538            .read_byte()
539            .map_err(|e| e.chain("Could not decode `FarmId`, failed to read variant byte"))?
540        {
541            0 => u128::decode(input)
542                .map(|ulid| FarmId::Ulid(Ulid(ulid)))
543                .map_err(|e| e.chain("Could not decode `FarmId::Ulid.0`")),
544            _ => Err("Could not decode `FarmId`, variant doesn't exist".into()),
545        }
546    }
547}
548
549#[expect(
550    clippy::new_without_default,
551    reason = "Default has different semantics"
552)]
553impl FarmId {
554    /// Creates new ID
555    #[inline]
556    pub fn new() -> Self {
557        Self::Ulid(Ulid::generate())
558    }
559}
560
561/// Abstract farm implementation
562#[async_trait(?Send)]
563pub trait Farm {
564    /// ID of this farm
565    fn id(&self) -> &FarmId;
566
567    /// Number of sectors in this farm
568    fn total_sectors_count(&self) -> u16;
569
570    /// Get plotted sectors instance
571    fn plotted_sectors(&self) -> Arc<dyn PlottedSectors + 'static>;
572
573    /// Get piece reader to read plotted pieces later
574    fn piece_reader(&self) -> Arc<dyn PieceReader + 'static>;
575
576    /// Subscribe to sector updates
577    fn on_sector_update(
578        &self,
579        callback: HandlerFn<(SectorIndex, SectorUpdate)>,
580    ) -> Box<dyn HandlerId>;
581
582    /// Subscribe to farming notifications
583    fn on_farming_notification(
584        &self,
585        callback: HandlerFn<FarmingNotification>,
586    ) -> Box<dyn HandlerId>;
587
588    /// Subscribe to new solution notification
589    fn on_solution(&self, callback: HandlerFn<SolutionResponse>) -> Box<dyn HandlerId>;
590
591    /// Run and wait for background threads to exit or return an error
592    fn run(self: Box<Self>) -> Pin<Box<dyn Future<Output = anyhow::Result<()>> + Send>>;
593}
594
595#[async_trait]
596impl<T> Farm for Box<T>
597where
598    T: Farm + ?Sized,
599{
600    #[inline]
601    fn id(&self) -> &FarmId {
602        self.as_ref().id()
603    }
604
605    #[inline]
606    fn total_sectors_count(&self) -> u16 {
607        self.as_ref().total_sectors_count()
608    }
609
610    #[inline]
611    fn plotted_sectors(&self) -> Arc<dyn PlottedSectors + 'static> {
612        self.as_ref().plotted_sectors()
613    }
614
615    #[inline]
616    fn piece_reader(&self) -> Arc<dyn PieceReader + 'static> {
617        self.as_ref().piece_reader()
618    }
619
620    #[inline]
621    fn on_sector_update(
622        &self,
623        callback: HandlerFn<(SectorIndex, SectorUpdate)>,
624    ) -> Box<dyn HandlerId> {
625        self.as_ref().on_sector_update(callback)
626    }
627
628    #[inline]
629    fn on_farming_notification(
630        &self,
631        callback: HandlerFn<FarmingNotification>,
632    ) -> Box<dyn HandlerId> {
633        self.as_ref().on_farming_notification(callback)
634    }
635
636    #[inline]
637    fn on_solution(&self, callback: HandlerFn<SolutionResponse>) -> Box<dyn HandlerId> {
638        self.as_ref().on_solution(callback)
639    }
640
641    #[inline]
642    fn run(self: Box<Self>) -> Pin<Box<dyn Future<Output = anyhow::Result<()>> + Send>> {
643        (*self).run()
644    }
645}