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