1use crate::cluster::controller::ClusterControllerFarmerIdentifyBroadcast;
11use crate::cluster::nats_client::{
12 GenericBroadcast, GenericRequest, GenericStreamRequest, NatsClient,
13};
14use crate::farm::{
15 Farm, FarmError, FarmId, FarmingNotification, HandlerFn, HandlerId, PieceReader,
16 PlottedSectors, SectorUpdate,
17};
18use crate::utils::AsyncJoinOnDrop;
19use ab_core_primitives::pieces::{Piece, PieceOffset};
20use ab_core_primitives::sectors::SectorIndex;
21use ab_farmer_components::plotting::PlottedSector;
22use ab_farmer_rpc_primitives::SolutionResponse;
23use anyhow::anyhow;
24use async_trait::async_trait;
25use derive_more::{Display, From};
26use event_listener_primitives::Bag;
27use futures::channel::mpsc;
28use futures::stream::FuturesUnordered;
29use futures::{FutureExt, Stream, StreamExt, select, stream};
30use parity_scale_codec::{Decode, Encode, EncodeLike, Input, Output};
31use std::future::Future;
32use std::pin::{Pin, pin};
33use std::sync::Arc;
34use std::time::{Duration, Instant};
35use tokio::time::MissedTickBehavior;
36use tracing::{Instrument, debug, error, info_span, trace, warn};
37use ulid::Ulid;
38
39const BROADCAST_NOTIFICATIONS_BUFFER: usize = 1000;
40const MIN_FARMER_IDENTIFICATION_INTERVAL: Duration = Duration::from_secs(1);
41
42type Handler<A> = Bag<HandlerFn<A>, A>;
43
44#[derive(Debug, Copy, Clone, Ord, PartialOrd, Eq, PartialEq, Hash, Display, From)]
46pub struct ClusterFarmerId(Ulid);
47
48impl Encode for ClusterFarmerId {
49 #[inline]
50 fn size_hint(&self) -> usize {
51 Encode::size_hint(&self.0.0)
52 }
53
54 #[inline]
55 fn encode_to<O>(&self, dest: &mut O)
56 where
57 O: Output + ?Sized,
58 {
59 Encode::encode_to(&self.0.0, dest);
60 }
61}
62
63impl EncodeLike for ClusterFarmerId {}
64
65impl Decode for ClusterFarmerId {
66 #[inline]
67 fn decode<I>(input: &mut I) -> Result<Self, parity_scale_codec::Error>
68 where
69 I: Input,
70 {
71 u128::decode(input)
72 .map(|ulid| Self(Ulid(ulid)))
73 .map_err(|e| e.chain("Could not decode `ClusterFarmerId.0.0`"))
74 }
75}
76
77#[expect(
78 clippy::new_without_default,
79 reason = "Default has different semantics"
80)]
81impl ClusterFarmerId {
82 pub fn new() -> Self {
84 Self(Ulid::generate())
85 }
86}
87
88#[derive(Debug, Clone, Encode, Decode)]
90pub struct ClusterFarmerIdentifyBroadcast {
91 pub farmer_id: ClusterFarmerId,
93}
94
95impl GenericBroadcast for ClusterFarmerIdentifyBroadcast {
96 const SUBJECT: &'static str = "ab.farmer.*.farmer-identify";
98}
99
100#[derive(Debug, Clone, Encode, Decode)]
102pub struct ClusterFarmerFarmDetailsRequest;
103
104impl GenericStreamRequest for ClusterFarmerFarmDetailsRequest {
105 const SUBJECT: &'static str = "ab.farmer.*.farm.details";
107 type Response = ClusterFarmerFarmDetails;
108}
109
110#[derive(Debug, Clone, Encode, Decode)]
112pub struct ClusterFarmerFarmDetails {
113 pub farm_id: FarmId,
115 pub total_sectors_count: u16,
117}
118
119#[derive(Debug, Clone, Encode, Decode)]
121struct ClusterFarmerSectorUpdateBroadcast {
122 farm_id: FarmId,
124 sector_index: SectorIndex,
126 sector_update: SectorUpdate,
128}
129
130impl GenericBroadcast for ClusterFarmerSectorUpdateBroadcast {
131 const SUBJECT: &'static str = "ab.farmer.*.sector-update";
133}
134
135#[derive(Debug, Clone, Encode, Decode)]
137struct ClusterFarmerFarmingNotificationBroadcast {
138 farm_id: FarmId,
140 farming_notification: FarmingNotification,
142}
143
144impl GenericBroadcast for ClusterFarmerFarmingNotificationBroadcast {
145 const SUBJECT: &'static str = "ab.farmer.*.farming-notification";
147}
148
149#[derive(Debug, Clone, Encode, Decode)]
151struct ClusterFarmerSolutionBroadcast {
152 farm_id: FarmId,
154 solution_response: SolutionResponse,
156}
157
158impl GenericBroadcast for ClusterFarmerSolutionBroadcast {
159 const SUBJECT: &'static str = "ab.farmer.*.solution-response";
161}
162
163#[derive(Debug, Clone, Encode, Decode)]
165struct ClusterFarmerReadPieceRequest {
166 sector_index: SectorIndex,
167 piece_offset: PieceOffset,
168}
169
170impl GenericRequest for ClusterFarmerReadPieceRequest {
171 const SUBJECT: &'static str = "ab.farmer.*.farm.read-piece";
173 type Response = Result<Option<Piece>, String>;
174}
175
176#[derive(Debug, Clone, Encode, Decode)]
178struct ClusterFarmerPlottedSectorsRequest;
179
180impl GenericStreamRequest for ClusterFarmerPlottedSectorsRequest {
181 const SUBJECT: &'static str = "ab.farmer.*.farm.plotted-sectors";
183 type Response = Result<PlottedSector, String>;
184}
185
186#[derive(Debug)]
187struct ClusterPlottedSectors {
188 farm_id_string: String,
189 nats_client: NatsClient,
190}
191
192#[async_trait]
193impl PlottedSectors for ClusterPlottedSectors {
194 async fn get(
195 &self,
196 ) -> Result<
197 Box<dyn Stream<Item = Result<PlottedSector, FarmError>> + Unpin + Send + '_>,
198 FarmError,
199 > {
200 Ok(Box::new(
201 self.nats_client
202 .stream_request(
203 &ClusterFarmerPlottedSectorsRequest,
204 Some(&self.farm_id_string),
205 )
206 .await?
207 .map(|response| response.map_err(FarmError::from)),
208 ))
209 }
210}
211
212#[derive(Debug)]
213struct ClusterPieceReader {
214 farm_id_string: String,
215 nats_client: NatsClient,
216}
217
218#[async_trait]
219impl PieceReader for ClusterPieceReader {
220 async fn read_piece(
221 &self,
222 sector_index: SectorIndex,
223 piece_offset: PieceOffset,
224 ) -> Result<Option<Piece>, FarmError> {
225 Ok(self
226 .nats_client
227 .request(
228 &ClusterFarmerReadPieceRequest {
229 sector_index,
230 piece_offset,
231 },
232 Some(&self.farm_id_string),
233 )
234 .await??)
235 }
236}
237
238#[derive(Default, Debug)]
239struct Handlers {
240 sector_update: Handler<(SectorIndex, SectorUpdate)>,
241 farming_notification: Handler<FarmingNotification>,
242 solution: Handler<SolutionResponse>,
243}
244
245#[derive(Debug)]
247pub struct ClusterFarm {
248 farm_id: FarmId,
249 farm_id_string: String,
250 total_sectors_count: u16,
251 nats_client: NatsClient,
252 handlers: Arc<Handlers>,
253 background_tasks: AsyncJoinOnDrop<()>,
254}
255
256#[async_trait(?Send)]
257impl Farm for ClusterFarm {
258 fn id(&self) -> &FarmId {
259 &self.farm_id
260 }
261
262 fn total_sectors_count(&self) -> u16 {
263 self.total_sectors_count
264 }
265
266 fn plotted_sectors(&self) -> Arc<dyn PlottedSectors + 'static> {
267 Arc::new(ClusterPlottedSectors {
268 farm_id_string: self.farm_id_string.clone(),
269 nats_client: self.nats_client.clone(),
270 })
271 }
272
273 fn piece_reader(&self) -> Arc<dyn PieceReader + 'static> {
274 Arc::new(ClusterPieceReader {
275 farm_id_string: self.farm_id_string.clone(),
276 nats_client: self.nats_client.clone(),
277 })
278 }
279
280 fn on_sector_update(
281 &self,
282 callback: HandlerFn<(SectorIndex, SectorUpdate)>,
283 ) -> Box<dyn HandlerId> {
284 Box::new(self.handlers.sector_update.add(callback))
285 }
286
287 fn on_farming_notification(
288 &self,
289 callback: HandlerFn<FarmingNotification>,
290 ) -> Box<dyn HandlerId> {
291 Box::new(self.handlers.farming_notification.add(callback))
292 }
293
294 fn on_solution(&self, callback: HandlerFn<SolutionResponse>) -> Box<dyn HandlerId> {
295 Box::new(self.handlers.solution.add(callback))
296 }
297
298 fn run(self: Box<Self>) -> Pin<Box<dyn Future<Output = anyhow::Result<()>> + Send>> {
299 Box::pin((*self).run())
300 }
301}
302
303impl ClusterFarm {
304 pub async fn new(
307 farm_id: FarmId,
308 total_sectors_count: u16,
309 nats_client: NatsClient,
310 ) -> anyhow::Result<Self> {
311 let farm_id_string = farm_id.to_string();
312
313 let sector_updates_subscription = nats_client
314 .subscribe_to_broadcasts::<ClusterFarmerSectorUpdateBroadcast>(
315 Some(&farm_id_string),
316 None,
317 )
318 .await
319 .map_err(|error| anyhow!("Failed to subscribe to sector updates broadcast: {error}"))?;
320 let farming_notifications_subscription = nats_client
321 .subscribe_to_broadcasts::<ClusterFarmerFarmingNotificationBroadcast>(
322 Some(&farm_id_string),
323 None,
324 )
325 .await
326 .map_err(|error| {
327 anyhow!("Failed to subscribe to farming notifications broadcast: {error}")
328 })?;
329 let solution_subscription = nats_client
330 .subscribe_to_broadcasts::<ClusterFarmerSolutionBroadcast>(Some(&farm_id_string), None)
331 .await
332 .map_err(|error| {
333 anyhow!("Failed to subscribe to solution responses broadcast: {error}")
334 })?;
335
336 let handlers = Arc::<Handlers>::default();
337 let background_tasks = {
339 let handlers = Arc::clone(&handlers);
340
341 async move {
342 let mut sector_updates_subscription = pin!(sector_updates_subscription);
343 let mut farming_notifications_subscription =
344 pin!(farming_notifications_subscription);
345 let mut solution_subscription = pin!(solution_subscription);
346
347 let sector_updates_fut = async {
348 #[expect(
349 clippy::rest_pattern_accessible_field,
350 reason = "Do not need other fields"
351 )]
352 while let Some(ClusterFarmerSectorUpdateBroadcast {
353 sector_index,
354 sector_update,
355 ..
356 }) = sector_updates_subscription.next().await
357 {
358 handlers
359 .sector_update
360 .call_simple(&(sector_index, sector_update));
361 }
362 };
363 let farming_notifications_fut = async {
364 #[expect(
365 clippy::rest_pattern_accessible_field,
366 reason = "Do not need other fields"
367 )]
368 while let Some(ClusterFarmerFarmingNotificationBroadcast {
369 farming_notification,
370 ..
371 }) = farming_notifications_subscription.next().await
372 {
373 handlers
374 .farming_notification
375 .call_simple(&farming_notification);
376 }
377 };
378 let solutions_fut = async {
379 #[expect(
380 clippy::rest_pattern_accessible_field,
381 reason = "Do not need other fields"
382 )]
383 while let Some(ClusterFarmerSolutionBroadcast {
384 solution_response, ..
385 }) = solution_subscription.next().await
386 {
387 handlers.solution.call_simple(&solution_response);
388 }
389 };
390
391 select! {
392 () = sector_updates_fut.fuse() => {}
393 () = farming_notifications_fut.fuse() => {}
394 () = solutions_fut.fuse() => {}
395 }
396 }
397 };
398
399 Ok(Self {
400 farm_id,
401 farm_id_string,
402 total_sectors_count,
403 nats_client,
404 handlers,
405 background_tasks: AsyncJoinOnDrop::new(tokio::spawn(background_tasks), true),
406 })
407 }
408
409 pub async fn run(self) -> anyhow::Result<()> {
411 Ok(self.background_tasks.await?)
412 }
413}
414
415#[derive(Debug)]
416struct FarmDetails {
417 farm_id: FarmId,
418 farm_id_string: String,
419 total_sectors_count: u16,
420 piece_reader: Arc<dyn PieceReader + 'static>,
421 plotted_sectors: Arc<dyn PlottedSectors + 'static>,
422 _background_tasks: Option<AsyncJoinOnDrop<()>>,
423}
424
425pub fn farmer_service<F>(
431 nats_client: NatsClient,
432 farms: &[F],
433 identification_broadcast_interval: Duration,
434 primary_instance: bool,
435) -> impl Future<Output = anyhow::Result<()>> + Send + 'static
436where
437 F: Farm,
438{
439 let farmer_id = ClusterFarmerId::new();
440 let farmer_id_string = farmer_id.to_string();
441
442 let farms_details = farms
445 .iter()
446 .map(|farm| {
447 let farm_id = *farm.id();
448 let nats_client = nats_client.clone();
449
450 let background_tasks = if primary_instance {
451 let (sector_updates_sender, mut sector_updates_receiver) =
452 mpsc::channel(BROADCAST_NOTIFICATIONS_BUFFER);
453 let (farming_notifications_sender, mut farming_notifications_receiver) =
454 mpsc::channel(BROADCAST_NOTIFICATIONS_BUFFER);
455 let (solutions_sender, mut solutions_receiver) =
456 mpsc::channel(BROADCAST_NOTIFICATIONS_BUFFER);
457
458 let sector_updates_handler_id =
459 farm.on_sector_update(Arc::new(move |(sector_index, sector_update)| {
460 if let Err(error) = sector_updates_sender.clone().try_send(
461 ClusterFarmerSectorUpdateBroadcast {
462 farm_id,
463 sector_index: *sector_index,
464 sector_update: sector_update.clone(),
465 },
466 ) {
467 warn!(%farm_id, %error, "Failed to send sector update notification");
468 }
469 }));
470
471 let farming_notifications_handler_id =
472 farm.on_farming_notification(Arc::new(move |farming_notification| {
473 if let Err(error) = farming_notifications_sender.clone().try_send(
474 ClusterFarmerFarmingNotificationBroadcast {
475 farm_id,
476 farming_notification: farming_notification.clone(),
477 },
478 ) {
479 warn!(%farm_id, %error, "Failed to send farming notification");
480 }
481 }));
482
483 let solutions_handler_id = farm.on_solution(Arc::new(move |solution_response| {
484 if let Err(error) =
485 solutions_sender
486 .clone()
487 .try_send(ClusterFarmerSolutionBroadcast {
488 farm_id,
489 solution_response: solution_response.clone(),
490 })
491 {
492 warn!(%farm_id, %error, "Failed to send solution notification");
493 }
494 }));
495
496 Some(AsyncJoinOnDrop::new(
497 tokio::spawn(async move {
498 let farm_id_string = farm_id.to_string();
499
500 let sector_updates_fut = async {
501 while let Some(broadcast) = sector_updates_receiver.next().await {
502 if let Err(error) =
503 nats_client.broadcast(&broadcast, &farm_id_string).await
504 {
505 warn!(%farm_id, %error, "Failed to broadcast sector update");
506 }
507 }
508 };
509 let farming_notifications_fut = async {
510 while let Some(broadcast) = farming_notifications_receiver.next().await
511 {
512 if let Err(error) =
513 nats_client.broadcast(&broadcast, &farm_id_string).await
514 {
515 warn!(
516 %farm_id,
517 %error,
518 "Failed to broadcast farming notification"
519 );
520 }
521 }
522 };
523 let solutions_fut = async {
524 while let Some(broadcast) = solutions_receiver.next().await {
525 if let Err(error) =
526 nats_client.broadcast(&broadcast, &farm_id_string).await
527 {
528 warn!(%farm_id, %error, "Failed to broadcast solution");
529 }
530 }
531 };
532
533 select! {
534 () = sector_updates_fut.fuse() => {}
535 () = farming_notifications_fut.fuse() => {}
536 () = solutions_fut.fuse() => {}
537 }
538
539 drop(sector_updates_handler_id);
540 drop(farming_notifications_handler_id);
541 drop(solutions_handler_id);
542 }),
543 true,
544 ))
545 } else {
546 None
547 };
548
549 FarmDetails {
550 farm_id,
551 farm_id_string: farm_id.to_string(),
552 total_sectors_count: farm.total_sectors_count(),
553 piece_reader: farm.piece_reader(),
554 plotted_sectors: farm.plotted_sectors(),
555 _background_tasks: background_tasks,
556 }
557 })
558 .collect::<Vec<_>>();
559
560 async move {
561 if primary_instance {
562 select! {
563 result = identify_responder(
564 &nats_client,
565 farmer_id,
566 &farmer_id_string,
567 identification_broadcast_interval
568 ).fuse() => {
569 result
570 },
571 result = farms_details_responder(
572 &nats_client,
573 &farmer_id_string,
574 &farms_details
575 ).fuse() => {
576 result
577 },
578 result = plotted_sectors_responder(&nats_client, &farms_details).fuse() => {
579 result
580 },
581 result = read_piece_responder(&nats_client, &farms_details).fuse() => {
582 result
583 },
584 }
585 } else {
586 select! {
587 result = plotted_sectors_responder(&nats_client, &farms_details).fuse() => {
588 result
589 },
590 result = read_piece_responder(&nats_client, &farms_details).fuse() => {
591 result
592 },
593 }
594 }
595 }
596}
597
598async fn identify_responder(
601 nats_client: &NatsClient,
602 farmer_id: ClusterFarmerId,
603 farmer_id_string: &str,
604 identification_broadcast_interval: Duration,
605) -> anyhow::Result<()> {
606 let mut subscription = nats_client
607 .subscribe_to_broadcasts::<ClusterControllerFarmerIdentifyBroadcast>(None, None)
608 .await
609 .map_err(|error| {
610 anyhow!("Failed to subscribe to farmer identify broadcast requests: {error}")
611 })?
612 .fuse();
613
614 let mut interval = tokio::time::interval(identification_broadcast_interval);
616 interval.set_missed_tick_behavior(MissedTickBehavior::Delay);
617
618 let mut last_identification = Instant::now();
619
620 loop {
621 select! {
622 maybe_message = subscription.next() => {
623 let Some(message) = maybe_message else {
624 debug!("Identify broadcast stream ended");
625 break;
626 };
627
628 trace!(?message, "Farmer received identify broadcast message");
629
630 if last_identification.elapsed() < MIN_FARMER_IDENTIFICATION_INTERVAL {
631 continue;
633 }
634
635 last_identification = Instant::now();
636 send_identify_broadcast(nats_client, farmer_id, farmer_id_string).await;
637 interval.reset();
638 }
639 _ = interval.tick().fuse() => {
640 last_identification = Instant::now();
641 trace!("Farmer self-identification");
642
643 send_identify_broadcast(nats_client, farmer_id, farmer_id_string).await;
644 }
645 }
646 }
647
648 Ok(())
649}
650
651async fn send_identify_broadcast(
652 nats_client: &NatsClient,
653 farmer_id: ClusterFarmerId,
654 farmer_id_string: &str,
655) {
656 if let Err(error) = nats_client
657 .broadcast(&new_identify_message(farmer_id), farmer_id_string)
658 .await
659 {
660 warn!(%farmer_id, %error, "Failed to send farmer identify notification");
661 }
662}
663
664fn new_identify_message(farmer_id: ClusterFarmerId) -> ClusterFarmerIdentifyBroadcast {
665 ClusterFarmerIdentifyBroadcast { farmer_id }
666}
667
668async fn farms_details_responder(
669 nats_client: &NatsClient,
670 farmer_id_string: &str,
671 farms_details: &[FarmDetails],
672) -> anyhow::Result<()> {
673 nats_client
674 .stream_request_responder(
675 Some(farmer_id_string),
676 Some(farmer_id_string.to_string()),
677 |_request: ClusterFarmerFarmDetailsRequest| async {
678 Some(stream::iter(farms_details.iter().map(|farm_details| {
679 ClusterFarmerFarmDetails {
680 farm_id: farm_details.farm_id,
681 total_sectors_count: farm_details.total_sectors_count,
682 }
683 })))
684 },
685 )
686 .await
687}
688
689async fn plotted_sectors_responder(
690 nats_client: &NatsClient,
691 farms_details: &[FarmDetails],
692) -> anyhow::Result<()> {
693 farms_details
694 .iter()
695 .map(|farm_details| async move {
696 nats_client
697 .stream_request_responder::<_, _, Pin<Box<dyn Stream<Item = _> + Send>>, _>(
698 Some(&farm_details.farm_id_string),
699 Some(farm_details.farm_id_string.clone()),
700 |_request: ClusterFarmerPlottedSectorsRequest| async move {
701 Some(match farm_details.plotted_sectors.get().await {
702 Ok(plotted_sectors) => {
703 Box::pin(plotted_sectors.map(|maybe_plotted_sector| {
704 maybe_plotted_sector.map_err(|error| error.to_string())
705 })) as _
706 }
707 Err(error) => {
708 error!(
709 %error,
710 farm_id = %farm_details.farm_id,
711 "Failed to get plotted sectors"
712 );
713
714 Box::pin(stream::once(async move {
715 Err(format!("Failed to get plotted sectors: {error}"))
716 })) as _
717 }
718 })
719 },
720 )
721 .instrument(info_span!("", cache_id = %farm_details.farm_id))
722 .await
723 })
724 .collect::<FuturesUnordered<_>>()
725 .next()
726 .await
727 .ok_or_else(|| anyhow!("No farms"))?
728}
729
730async fn read_piece_responder(
731 nats_client: &NatsClient,
732 farms_details: &[FarmDetails],
733) -> anyhow::Result<()> {
734 farms_details
735 .iter()
736 .map(|farm_details| async move {
737 nats_client
738 .request_responder(
739 Some(farm_details.farm_id_string.as_str()),
740 Some(farm_details.farm_id_string.clone()),
741 |request: ClusterFarmerReadPieceRequest| async move {
742 Some(
743 farm_details
744 .piece_reader
745 .read_piece(request.sector_index, request.piece_offset)
746 .await
747 .map_err(|error| error.to_string()),
748 )
749 },
750 )
751 .instrument(info_span!("", cache_id = %farm_details.farm_id))
752 .await
753 })
754 .collect::<FuturesUnordered<_>>()
755 .next()
756 .await
757 .ok_or_else(|| anyhow!("No farms"))?
758}