1pub mod rayon_files;
7
8use crate::farm::{
9 AuditingDetails, FarmingError, FarmingNotification, ProvingDetails, ProvingResult,
10};
11use crate::node_client::NodeClient;
12use crate::single_disk_farm::Handlers;
13use crate::single_disk_farm::metrics::SingleDiskFarmMetrics;
14use async_lock::{Mutex as AsyncMutex, RwLock as AsyncRwLock};
15use futures::StreamExt;
16use futures::channel::mpsc;
17use rayon::ThreadPool;
18use std::collections::HashSet;
19use std::sync::Arc;
20use std::time::Instant;
21use subspace_core_primitives::PublicKey;
22use subspace_core_primitives::pieces::Record;
23use subspace_core_primitives::pos::PosSeed;
24use subspace_core_primitives::sectors::SectorIndex;
25use subspace_core_primitives::segments::{HistorySize, SegmentIndex};
26use subspace_core_primitives::solutions::{Solution, SolutionRange};
27use subspace_erasure_coding::ErasureCoding;
28use subspace_farmer_components::ReadAtSync;
29use subspace_farmer_components::auditing::{AuditingError, audit_plot_sync};
30use subspace_farmer_components::proving::{ProvableSolutions, ProvingError};
31use subspace_farmer_components::reading::ReadSectorRecordChunksMode;
32use subspace_farmer_components::sector::{SectorMetadata, SectorMetadataChecksummed};
33use subspace_kzg::Kzg;
34use subspace_proof_of_space::{Table, TableGenerator};
35use subspace_rpc_primitives::{SlotInfo, SolutionResponse};
36use tracing::{Span, debug, error, info, trace, warn};
37
38const NON_FATAL_ERROR_LIMIT: usize = 10;
40
41pub(super) async fn slot_notification_forwarder<NC>(
42 node_client: &NC,
43 mut slot_info_forwarder_sender: mpsc::Sender<SlotInfo>,
44 metrics: Option<Arc<SingleDiskFarmMetrics>>,
45) -> Result<(), FarmingError>
46where
47 NC: NodeClient,
48{
49 info!("Subscribing to slot info notifications");
50
51 let mut slot_info_notifications = node_client
52 .subscribe_slot_info()
53 .await
54 .map_err(|error| FarmingError::FailedToSubscribeSlotInfo { error })?;
55
56 while let Some(slot_info) = slot_info_notifications.next().await {
57 debug!(?slot_info, "New slot");
58
59 let slot = slot_info.slot_number;
60
61 if slot_info_forwarder_sender.try_send(slot_info).is_err() {
64 if let Some(metrics) = &metrics {
65 metrics.skipped_slots.inc();
66 }
67 debug!(%slot, "Slow farming, skipping slot");
68 }
69 }
70
71 Err(FarmingError::SlotNotificationStreamEnded)
72}
73
74#[derive(Debug, Clone, Copy)]
76pub struct PlotAuditOptions<'a, 'b> {
77 pub public_key: &'a PublicKey,
79 pub reward_address: &'a PublicKey,
81 pub slot_info: SlotInfo,
83 pub sectors_metadata: &'a [SectorMetadataChecksummed],
85 pub kzg: &'a Kzg,
87 pub erasure_coding: &'a ErasureCoding,
89 pub sectors_being_modified: &'b HashSet<SectorIndex>,
92 pub read_sector_record_chunks_mode: ReadSectorRecordChunksMode,
94 pub cutover: Option<HistorySize>,
97}
98
99#[derive(Debug)]
101pub struct PlotAudit<Plot>(Plot)
102where
103 Plot: ReadAtSync;
104
105impl<'a, Plot> PlotAudit<Plot>
106where
107 Plot: ReadAtSync + 'a,
108{
109 pub fn new(plot: Plot) -> Self {
111 Self(plot)
112 }
113
114 #[allow(clippy::type_complexity)]
116 pub fn audit<'b, PosTable>(
117 &'a self,
118 options: PlotAuditOptions<'a, 'b>,
119 ) -> Result<
120 Vec<(
121 SectorIndex,
122 impl ProvableSolutions<Item = Result<Solution<PublicKey>, ProvingError>>
123 + use<'a, PosTable, Plot>,
124 )>,
125 AuditingError,
126 >
127 where
128 PosTable: Table,
129 {
130 let PlotAuditOptions {
131 public_key,
132 reward_address,
133 slot_info,
134 sectors_metadata,
135 kzg,
136 erasure_coding,
137 sectors_being_modified,
138 read_sector_record_chunks_mode: mode,
139 cutover,
140 } = options;
141
142 let audit_results = audit_plot_sync(
143 public_key,
144 &slot_info.global_challenge,
145 slot_info.voting_solution_range,
146 &self.0,
147 sectors_metadata,
148 sectors_being_modified,
149 )?;
150
151 Ok(audit_results
152 .into_iter()
153 .filter_map(|audit_results| {
154 let sector_index = audit_results.sector_index;
155
156 let solution_candidates = audit_results.solution_candidates;
157 let is_post_cutover =
158 super::is_post_cutover(cutover, solution_candidates.history_size());
159 let table_generator = PosTable::generator_for(is_post_cutover);
160
161 let sector_solutions = solution_candidates.into_solutions(
162 reward_address,
163 kzg,
164 erasure_coding,
165 mode,
166 move |seed: &PosSeed| table_generator.generate_parallel(seed),
167 );
168
169 let sector_solutions = match sector_solutions {
170 Ok(solutions) => solutions,
171 Err(error) => {
172 warn!(
173 %error,
174 %sector_index,
175 "Failed to turn solution candidates into solutions",
176 );
177
178 return None;
179 }
180 };
181
182 if sector_solutions.len() == 0 {
183 return None;
184 }
185
186 Some((sector_index, sector_solutions))
187 })
188 .collect())
189 }
190}
191
192pub(super) struct FarmingOptions<NC, PlotAudit> {
193 pub(super) public_key: PublicKey,
194 pub(super) reward_address: PublicKey,
195 pub(super) node_client: NC,
196 pub(super) plot_audit: PlotAudit,
197 pub(super) sectors_metadata: Arc<AsyncRwLock<Vec<SectorMetadataChecksummed>>>,
198 pub(super) kzg: Kzg,
199 pub(super) erasure_coding: ErasureCoding,
200 pub(super) handlers: Arc<Handlers>,
201 pub(super) sectors_being_modified: Arc<AsyncRwLock<HashSet<SectorIndex>>>,
202 pub(super) slot_info_notifications: mpsc::Receiver<SlotInfo>,
203 pub(super) thread_pool: ThreadPool,
204 pub(super) read_sector_record_chunks_mode: ReadSectorRecordChunksMode,
205 pub(super) global_mutex: Arc<AsyncMutex<()>>,
206 pub(super) metrics: Option<Arc<SingleDiskFarmMetrics>>,
207 pub(super) cutover: Option<HistorySize>,
208}
209
210pub(super) async fn farming<'a, PosTable, NC, Plot>(
215 farming_options: FarmingOptions<NC, PlotAudit<Plot>>,
216) -> Result<(), FarmingError>
217where
218 PosTable: Table,
219 NC: NodeClient,
220 Plot: ReadAtSync + 'a,
221{
222 let FarmingOptions {
223 public_key,
224 reward_address,
225 node_client,
226 plot_audit,
227 sectors_metadata,
228 kzg,
229 erasure_coding,
230 handlers,
231 sectors_being_modified,
232 mut slot_info_notifications,
233 thread_pool,
234 read_sector_record_chunks_mode,
235 global_mutex,
236 metrics,
237 cutover,
238 } = farming_options;
239
240 let farmer_app_info = node_client
241 .farmer_app_info()
242 .await
243 .map_err(|error| FarmingError::FailedToGetFarmerInfo { error })?;
244
245 let farming_timeout = farmer_app_info.farming_timeout;
247
248 let span = Span::current();
249
250 let mut non_fatal_errors = 0;
251
252 while let Some(slot_info) = slot_info_notifications.next().await {
253 let slot = slot_info.slot_number;
254
255 global_mutex.lock().await;
257
258 let mut problematic_sectors = Vec::new();
259 let result = try {
260 let start = Instant::now();
261 let sectors_metadata = sectors_metadata.read().await;
262
263 debug!(%slot, sector_count = %sectors_metadata.len(), "Reading sectors");
264
265 let mut sectors_solutions = {
266 let sectors_being_modified = &*sectors_being_modified.read().await;
267
268 thread_pool
269 .install(|| {
270 let _span_guard = span.enter();
271
272 plot_audit.audit::<PosTable>(PlotAuditOptions {
273 public_key: &public_key,
274 reward_address: &reward_address,
275 slot_info,
276 sectors_metadata: §ors_metadata,
277 kzg: &kzg,
278 erasure_coding: &erasure_coding,
279 sectors_being_modified,
280 read_sector_record_chunks_mode,
281 cutover,
282 })
283 })
284 .map_err(FarmingError::LowLevelAuditing)?
285 };
286
287 sectors_solutions.sort_by(|a, b| {
288 let a_solution_distance =
289 a.1.best_solution_distance().unwrap_or(SolutionRange::MAX);
290 let b_solution_distance =
291 b.1.best_solution_distance().unwrap_or(SolutionRange::MAX);
292
293 a_solution_distance.cmp(&b_solution_distance)
294 });
295
296 {
297 let time = start.elapsed();
298 if let Some(metrics) = &metrics {
299 metrics.auditing_time.observe(time.as_secs_f64());
300 }
301 handlers
302 .farming_notification
303 .call_simple(&FarmingNotification::Auditing(AuditingDetails {
304 sectors_count: sectors_metadata.len() as SectorIndex,
305 time,
306 }));
307 }
308
309 let _proving_guard = global_mutex.lock().await;
312
313 'solutions_processing: for (sector_index, mut sector_solutions) in sectors_solutions {
314 if sector_solutions.is_empty() {
315 continue;
316 }
317 let mut start = Instant::now();
318 while let Some(maybe_solution) = thread_pool.install(|| {
319 let _span_guard = span.enter();
320
321 sector_solutions.next()
322 }) {
323 let solution = match maybe_solution {
324 Ok(solution) => solution,
325 Err(error) => {
326 if let Some(metrics) = &metrics {
327 metrics
328 .observe_proving_time(&start.elapsed(), ProvingResult::Failed);
329 }
330 error!(
331 %slot,
332 %sector_index,
333 %error,
334 "Failed to prove, scheduling sector for replotting"
335 );
336 problematic_sectors.push(sector_index);
337 start = Instant::now();
340 continue;
341 }
342 };
343
344 debug!(%slot, %sector_index, "Solution found");
345 trace!(?solution, "Solution found");
346
347 {
348 let time = start.elapsed();
349 if time >= farming_timeout {
350 if let Some(metrics) = &metrics {
351 metrics.observe_proving_time(&time, ProvingResult::Timeout);
352 }
353 handlers.farming_notification.call_simple(
354 &FarmingNotification::Proving(ProvingDetails {
355 result: ProvingResult::Timeout,
356 time,
357 }),
358 );
359 warn!(
360 %slot,
361 %sector_index,
362 "Proving for solution skipped due to farming time limit",
363 );
364
365 break 'solutions_processing;
366 }
367 }
368
369 let response = SolutionResponse {
370 slot_number: slot,
371 solution,
372 };
373
374 handlers.solution.call_simple(&response);
375
376 if let Err(error) = node_client.submit_solution_response(response).await {
377 let time = start.elapsed();
378 if let Some(metrics) = &metrics {
379 metrics.observe_proving_time(&time, ProvingResult::Rejected);
380 }
381 handlers
382 .farming_notification
383 .call_simple(&FarmingNotification::Proving(ProvingDetails {
384 result: ProvingResult::Rejected,
385 time,
386 }));
387 warn!(
388 %slot,
389 %sector_index,
390 %error,
391 "Failed to send solution to node, skipping further proving for this slot",
392 );
393 break 'solutions_processing;
394 }
395
396 let time = start.elapsed();
397 if let Some(metrics) = &metrics {
398 metrics.observe_proving_time(&time, ProvingResult::Success);
399 }
400 handlers
401 .farming_notification
402 .call_simple(&FarmingNotification::Proving(ProvingDetails {
403 result: ProvingResult::Success,
404 time,
405 }));
406 start = Instant::now();
407 }
408 }
409 };
410
411 if let Err(error) = result {
412 if error.is_fatal() {
413 return Err(error);
414 }
415
416 non_fatal_errors += 1;
417
418 if non_fatal_errors >= NON_FATAL_ERROR_LIMIT {
419 return Err(error);
420 }
421
422 warn!(
423 %error,
424 "Non-fatal farming error"
425 );
426
427 if let Some(metrics) = &metrics {
428 metrics.note_farming_error(&error);
429 }
430 handlers
431 .farming_notification
432 .call_simple(&FarmingNotification::NonFatalError(Arc::new(error)));
433
434 for sector_index in problematic_sectors.drain(..) {
435 sectors_being_modified.write().await.insert(sector_index);
437 if let Some(existing_sector_metadata) = sectors_metadata
439 .write()
440 .await
441 .get_mut(sector_index as usize)
442 {
443 *existing_sector_metadata = SectorMetadataChecksummed::from(SectorMetadata {
444 sector_index,
445 pieces_in_sector: existing_sector_metadata.pieces_in_sector,
446 s_bucket_sizes: Box::new([0; Record::NUM_S_BUCKETS]),
447 history_size: HistorySize::from(SegmentIndex::ZERO),
448 });
449 }
450 sectors_being_modified.write().await.remove(§or_index);
452 }
453 } else {
454 non_fatal_errors = 0;
455 }
456 }
457
458 Ok(())
459}