ab_farmer/single_disk_farm/
piece_reader.rs1use 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#[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 #[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 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 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(§or_index) {
145 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 if u16::from(sector_index) >= sector_count {
171 warn!(
172 %sector_index,
173 %piece_offset,
174 %sector_count,
175 "Incorrect sector offset"
176 );
177 let _: Result<(), _> = response_sender.send(None);
179 continue;
180 }
181 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 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 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 §or_metadata,
205 &ReadAt::from_sync(§or),
207 &erasure_coding,
208 &table_generator,
209 )
210 .await;
211
212 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 §or_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}