Skip to main content

ab_farmer/single_disk_farm/
piece_reader.rs

1//! Piece reader for single disk farm
2
3use crate::farm::{FarmError, PieceReader};
4use crate::single_disk_farm::direct_io_file_wrapper::DirectIoFileWrapper;
5use ab_core_primitives::hashes::Blake3Hash;
6use ab_core_primitives::pieces::{Piece, PieceOffset};
7use ab_core_primitives::sectors::{SectorId, SectorIndex};
8use ab_core_primitives::solutions::ShardCommitmentHash;
9use ab_erasure_coding::ErasureCoding;
10use ab_farmer_components::sector::{SectorMetadataChecksummed, sector_size};
11use ab_farmer_components::shard_commitment::ShardCommitmentsRootsCache;
12use ab_farmer_components::{ReadAt, ReadAtAsync, ReadAtSync, reading};
13use ab_proof_of_space::Table;
14use async_lock::{Mutex as AsyncMutex, RwLock as AsyncRwLock};
15use async_trait::async_trait;
16use futures::channel::{mpsc, oneshot};
17use futures::{SinkExt, StreamExt};
18use std::collections::HashSet;
19use std::future::Future;
20use std::sync::Arc as StdArc;
21use tracing::{error, warn};
22use triomphe::Arc;
23
24#[derive(Debug)]
25struct ReadPieceRequest {
26    sector_index: SectorIndex,
27    piece_offset: PieceOffset,
28    response_sender: oneshot::Sender<Option<Piece>>,
29}
30
31/// Wrapper data structure that can be used to read pieces from single disk farm
32#[derive(Debug, Clone)]
33pub struct DiskPieceReader {
34    read_piece_sender: mpsc::Sender<ReadPieceRequest>,
35}
36
37#[async_trait]
38impl PieceReader for DiskPieceReader {
39    #[inline]
40    async fn read_piece(
41        &self,
42        sector_index: SectorIndex,
43        piece_offset: PieceOffset,
44    ) -> Result<Option<Piece>, FarmError> {
45        Ok(self.read_piece(sector_index, piece_offset).await)
46    }
47}
48
49impl DiskPieceReader {
50    /// Creates new piece reader instance and background future that handles reads internally.
51    ///
52    /// NOTE: Background future is async, but does blocking operations and should be running in
53    /// dedicated thread.
54    #[expect(clippy::too_many_arguments, reason = "Internal API")]
55    pub(super) fn new<PosTable>(
56        public_key_hash: Blake3Hash,
57        shard_commitments_roots_cache: ShardCommitmentsRootsCache,
58        pieces_in_sector: u16,
59        plot_file: StdArc<DirectIoFileWrapper>,
60        sectors_metadata: StdArc<AsyncRwLock<Vec<SectorMetadataChecksummed>>>,
61        erasure_coding: ErasureCoding,
62        sectors_being_modified: Arc<AsyncRwLock<HashSet<SectorIndex>>>,
63        global_mutex: Arc<AsyncMutex<()>>,
64    ) -> (Self, impl Future<Output = ()>)
65    where
66        PosTable: Table,
67    {
68        let (read_piece_sender, read_piece_receiver) = mpsc::channel(10);
69
70        let reading_fut = async move {
71            read_pieces::<PosTable, _>(
72                public_key_hash,
73                shard_commitments_roots_cache,
74                pieces_in_sector,
75                &*plot_file,
76                sectors_metadata,
77                erasure_coding,
78                sectors_being_modified,
79                read_piece_receiver,
80                global_mutex,
81            )
82            .await;
83        };
84
85        (Self { read_piece_sender }, reading_fut)
86    }
87
88    pub(super) fn close_all_readers(&mut self) {
89        self.read_piece_sender.close_channel();
90    }
91
92    /// Read piece from sector by offset, `None` means input parameters are incorrect or piece
93    /// reader was shut down
94    pub async fn read_piece(
95        &self,
96        sector_index: SectorIndex,
97        piece_offset: PieceOffset,
98    ) -> Option<Piece> {
99        let (response_sender, response_receiver) = oneshot::channel();
100        self.read_piece_sender
101            .clone()
102            .send(ReadPieceRequest {
103                sector_index,
104                piece_offset,
105                response_sender,
106            })
107            .await
108            .ok()?;
109        response_receiver.await.ok()?
110    }
111}
112
113#[expect(clippy::too_many_arguments, reason = "Internal API")]
114async fn read_pieces<PosTable, S>(
115    public_key_hash: Blake3Hash,
116    shard_commitments_roots_cache: ShardCommitmentsRootsCache,
117    pieces_in_sector: u16,
118    plot_file: S,
119    sectors_metadata: StdArc<AsyncRwLock<Vec<SectorMetadataChecksummed>>>,
120    erasure_coding: ErasureCoding,
121    sectors_being_modified: Arc<AsyncRwLock<HashSet<SectorIndex>>>,
122    mut read_piece_receiver: mpsc::Receiver<ReadPieceRequest>,
123    global_mutex: Arc<AsyncMutex<()>>,
124) where
125    PosTable: Table,
126    S: ReadAtSync,
127{
128    // TODO: Reuse global table generator (this comment is in many files)
129    let table_generator = PosTable::generator();
130
131    while let Some(read_piece_request) = read_piece_receiver.next().await {
132        let ReadPieceRequest {
133            sector_index,
134            piece_offset,
135            response_sender,
136        } = read_piece_request;
137
138        if response_sender.is_canceled() {
139            continue;
140        }
141
142        let sectors_being_modified = &*sectors_being_modified.read().await;
143
144        if sectors_being_modified.contains(&sector_index) {
145            // Skip sector that is being modified right now
146            continue;
147        }
148
149        let (sector_metadata, sector_count) = {
150            let sectors_metadata = sectors_metadata.read().await;
151
152            let sector_count = sectors_metadata.len() as u16;
153
154            let sector_metadata =
155                if let Some(sector_metadata) = sectors_metadata.get(usize::from(sector_index)) {
156                    sector_metadata.clone()
157                } else {
158                    error!(
159                        %sector_index,
160                        %sector_count,
161                        "Tried to read piece from sector that is not yet plotted"
162                    );
163                    continue;
164                };
165
166            (sector_metadata, sector_count)
167        };
168
169        // Sector must be plotted
170        if u16::from(sector_index) >= sector_count {
171            warn!(
172                %sector_index,
173                %piece_offset,
174                %sector_count,
175                "Incorrect sector offset"
176            );
177            // Doesn't matter if receiver still cares about it
178            let _: Result<(), _> = response_sender.send(None);
179            continue;
180        }
181        // Piece must be within sector
182        if u16::from(piece_offset) >= pieces_in_sector {
183            warn!(
184                %sector_index,
185                %piece_offset,
186                %sector_count,
187                "Incorrect piece offset"
188            );
189            // Doesn't matter if receiver still cares about it
190            let _: Result<(), _> = response_sender.send(None);
191            continue;
192        }
193
194        let sector_size = sector_size(pieces_in_sector);
195        let sector = plot_file.offset(u64::from(sector_index) * sector_size as u64);
196
197        // Take mutex briefly to make sure piece reading is allowed right now
198        global_mutex.lock().await;
199
200        let maybe_piece = read_piece::<PosTable, _, _>(
201            &public_key_hash,
202            &shard_commitments_roots_cache.get(sector_metadata.history_size),
203            piece_offset,
204            &sector_metadata,
205            // TODO: Async
206            &ReadAt::from_sync(&sector),
207            &erasure_coding,
208            &table_generator,
209        )
210        .await;
211
212        // Doesn't matter if receiver still cares about it
213        let _: Result<(), _> = response_sender.send(maybe_piece);
214    }
215}
216
217async fn read_piece<PosTable, S, A>(
218    public_key_hash: &Blake3Hash,
219    shard_commitments_root: &ShardCommitmentHash,
220    piece_offset: PieceOffset,
221    sector_metadata: &SectorMetadataChecksummed,
222    sector: &ReadAt<S, A>,
223    erasure_coding: &ErasureCoding,
224    table_generator: &PosTable::Generator,
225) -> Option<Piece>
226where
227    PosTable: Table,
228    S: ReadAtSync,
229    A: ReadAtAsync,
230{
231    let sector_index = sector_metadata.sector_index;
232
233    let sector_id = SectorId::new(
234        public_key_hash,
235        shard_commitments_root,
236        sector_index,
237        sector_metadata.history_size,
238    );
239
240    let piece = match reading::read_piece::<PosTable, _, _>(
241        piece_offset,
242        &sector_id,
243        sector_metadata,
244        sector,
245        erasure_coding,
246        table_generator,
247    )
248    .await
249    {
250        Ok(piece) => piece,
251        Err(error) => {
252            error!(
253                %sector_index,
254                %piece_offset,
255                %error,
256                "Failed to read piece from sector"
257            );
258            return None;
259        }
260    };
261
262    Some(piece)
263}