Skip to main content

ab_farmer/cluster/
controller.rs

1//! Farming cluster controller
2//!
3//! Controller is responsible for managing farming cluster.
4//!
5//! This module exposes some data structures for NATS communication, custom piece getter and node
6//! client implementations designed to work with cluster controller and a service function to drive
7//! the backend part of the controller.
8
9pub mod caches;
10pub mod farms;
11mod stream_map;
12
13use crate::cluster::cache::{ClusterCacheReadPieceRequest, ClusterCacheReadPiecesRequest};
14use crate::cluster::nats_client::{
15    GenericBroadcast, GenericNotification, GenericRequest, GenericStreamRequest, NatsClient,
16};
17use crate::farm::{PieceCacheId, PieceCacheOffset};
18use crate::farmer_cache::FarmerCache;
19use crate::node_client::NodeClient;
20use ab_core_primitives::pieces::{Piece, PieceIndex};
21use ab_core_primitives::segments::{
22    SegmentIndex, SuperSegmentHeader, SuperSegmentIndex, SuperSegmentRoot,
23};
24use ab_data_retrieval::piece_getter::PieceGetter;
25use ab_farmer_rpc_primitives::{
26    BlockSealInfo, BlockSealResponse, FarmerAppInfo, FarmerShardMembershipInfo, SlotInfo,
27    SolutionResponse,
28};
29use anyhow::anyhow;
30use async_nats::HeaderValue;
31use async_trait::async_trait;
32use futures::channel::mpsc;
33use futures::future::FusedFuture;
34use futures::stream::FuturesUnordered;
35use futures::{FutureExt, Stream, StreamExt, select, stream};
36use parity_scale_codec::{Decode, Encode};
37use parking_lot::Mutex;
38use rand::prelude::*;
39use std::collections::{HashMap, HashSet};
40use std::pin::Pin;
41use std::task::Poll;
42use tracing::{debug, error, trace, warn};
43use triomphe::Arc;
44
45/// Special "cache group" that all controllers subscribe to and that can be used to query any cache
46/// group. The cache group for each query is chosen at random.
47const GLOBAL_CACHE_GROUP: &str = "_";
48
49/// Broadcast sent by controllers requesting farmers to identify themselves
50#[derive(Debug, Copy, Clone, Encode, Decode)]
51pub struct ClusterControllerFarmerIdentifyBroadcast;
52
53impl GenericBroadcast for ClusterControllerFarmerIdentifyBroadcast {
54    const SUBJECT: &'static str = "ab.controller.farmer-identify";
55}
56
57/// Broadcast sent by controllers requesting caches in cache group to identify themselves
58#[derive(Debug, Copy, Clone, Encode, Decode)]
59pub struct ClusterControllerCacheIdentifyBroadcast;
60
61impl GenericBroadcast for ClusterControllerCacheIdentifyBroadcast {
62    /// `*` here stands for cache group
63    const SUBJECT: &'static str = "ab.controller.*.cache-identify";
64}
65
66/// Broadcast with slot info sent by controllers
67#[derive(Debug, Clone, Encode, Decode)]
68struct ClusterControllerSlotInfoBroadcast {
69    slot_info: SlotInfo,
70    instance: String,
71}
72
73impl GenericBroadcast for ClusterControllerSlotInfoBroadcast {
74    const SUBJECT: &'static str = "ab.controller.slot-info";
75
76    fn deterministic_message_id(&self) -> Option<HeaderValue> {
77        // TODO: Depending on answer in `https://github.com/nats-io/nats.docs/issues/663` this might
78        //  be simplified to just a slot number
79        Some(
80            HeaderValue::try_from(format!("slot-info-{}", self.slot_info.slot))
81                .expect("No `\r` or `\n`; qed"),
82        )
83    }
84}
85
86/// Broadcast with block sealing info by controllers
87#[derive(Debug, Clone, Encode, Decode)]
88struct ClusterControllerBlockSealingBroadcast {
89    block_sealing_info: BlockSealInfo,
90}
91
92impl GenericBroadcast for ClusterControllerBlockSealingBroadcast {
93    const SUBJECT: &'static str = "ab.controller.block-sealing-info";
94}
95
96/// Broadcast with new super segment headers by controllers
97#[derive(Debug, Clone, Encode, Decode)]
98struct ClusterControllerNewSuperSegmentHeaderBroadcast {
99    new_super_segment_header: SuperSegmentHeader,
100}
101
102impl GenericBroadcast for ClusterControllerNewSuperSegmentHeaderBroadcast {
103    const SUBJECT: &'static str = "ab.controller.new-super-segment-header";
104
105    fn deterministic_message_id(&self) -> Option<HeaderValue> {
106        // TODO: Depending on answer in `https://github.com/nats-io/nats.docs/issues/663` this might
107        //  be simplified to just a super segment index
108        Some(
109            HeaderValue::try_from(format!(
110                "new-super-segment-{}",
111                self.new_super_segment_header.index
112            ))
113            .expect("No `\r` or `\n`; qed"),
114        )
115    }
116}
117
118/// Notification messages with solution by farmers
119#[derive(Debug, Clone, Encode, Decode)]
120struct ClusterControllerSolutionNotification {
121    solution_response: SolutionResponse,
122}
123
124impl GenericNotification for ClusterControllerSolutionNotification {
125    const SUBJECT: &'static str = "ab.controller.*.solution";
126}
127
128/// Notification messages with block seal by farmers
129#[derive(Debug, Clone, Encode, Decode)]
130struct ClusterControllerBlockSealNotification {
131    block_seal: BlockSealResponse,
132}
133
134impl GenericNotification for ClusterControllerBlockSealNotification {
135    const SUBJECT: &'static str = "ab.controller.block-seal";
136}
137
138/// Request farmer app info from controller
139#[derive(Debug, Clone, Encode, Decode)]
140struct ClusterControllerFarmerAppInfoRequest;
141
142impl GenericRequest for ClusterControllerFarmerAppInfoRequest {
143    const SUBJECT: &'static str = "ab.controller.farmer-app-info";
144    type Response = Result<FarmerAppInfo, String>;
145}
146
147/// Request super segment headers with specified super segment indices
148#[derive(Debug, Clone, Encode, Decode)]
149struct ClusterControllerSuperSegmentHeadersRequest {
150    super_segment_indices: Vec<SuperSegmentIndex>,
151}
152
153impl GenericRequest for ClusterControllerSuperSegmentHeadersRequest {
154    const SUBJECT: &'static str = "ab.controller.super-segment-headers";
155    type Response = Vec<Option<SuperSegmentHeader>>;
156}
157
158/// Request super segment root for a specified segment index
159#[derive(Debug, Clone, Encode, Decode)]
160struct ClusterControllerSuperSegmentRootForSegmentIndexRequest {
161    segment_index: SegmentIndex,
162}
163
164impl GenericRequest for ClusterControllerSuperSegmentRootForSegmentIndexRequest {
165    const SUBJECT: &'static str = "ab.controller.super-segment-root-for-segment-index";
166    type Response = Option<SuperSegmentRoot>;
167}
168
169/// Find piece with specified index in cache
170#[derive(Debug, Clone, Encode, Decode)]
171struct ClusterControllerFindPieceInCacheRequest {
172    piece_index: PieceIndex,
173}
174
175impl GenericRequest for ClusterControllerFindPieceInCacheRequest {
176    const SUBJECT: &'static str = "ab.controller.*.find-piece-in-cache";
177    type Response = Option<(PieceCacheId, PieceCacheOffset)>;
178}
179
180/// Find pieces with specified indices in cache
181#[derive(Debug, Clone, Encode, Decode)]
182struct ClusterControllerFindPiecesInCacheRequest {
183    piece_indices: Vec<PieceIndex>,
184}
185
186impl GenericStreamRequest for ClusterControllerFindPiecesInCacheRequest {
187    const SUBJECT: &'static str = "ab.controller.*.find-pieces-in-cache";
188    /// Only pieces that were found are returned
189    type Response = (PieceIndex, PieceCacheId, PieceCacheOffset);
190}
191
192/// Request piece with specified index
193#[derive(Debug, Clone, Encode, Decode)]
194struct ClusterControllerPieceRequest {
195    piece_index: PieceIndex,
196}
197
198impl GenericRequest for ClusterControllerPieceRequest {
199    const SUBJECT: &'static str = "ab.controller.piece";
200    type Response = Option<Piece>;
201}
202
203/// Request pieces with specified index
204#[derive(Debug, Clone, Encode, Decode)]
205struct ClusterControllerPiecesRequest {
206    piece_indices: Vec<PieceIndex>,
207}
208
209impl GenericStreamRequest for ClusterControllerPiecesRequest {
210    const SUBJECT: &'static str = "ab.controller.pieces";
211    /// Only pieces that were found are returned
212    type Response = (PieceIndex, Piece);
213}
214
215/// Cluster piece getter
216#[derive(Debug, Clone)]
217pub struct ClusterPieceGetter {
218    nats_client: NatsClient,
219    cache_group: String,
220}
221
222#[async_trait]
223impl PieceGetter for ClusterPieceGetter {
224    async fn get_piece(&self, piece_index: PieceIndex) -> anyhow::Result<Option<Piece>> {
225        if let Some((piece_cache_id, piece_cache_offset)) = self
226            .nats_client
227            .request(
228                &ClusterControllerFindPieceInCacheRequest { piece_index },
229                Some(&self.cache_group),
230            )
231            .await?
232        {
233            trace!(
234                %piece_index,
235                %piece_cache_id,
236                %piece_cache_offset,
237                "Found piece in cache, retrieving"
238            );
239
240            match self
241                .nats_client
242                .request(
243                    &ClusterCacheReadPieceRequest {
244                        offset: piece_cache_offset,
245                    },
246                    Some(&piece_cache_id.to_string()),
247                )
248                .await
249                .map_err(|error| error.to_string())
250                .flatten()
251            {
252                Ok(Some((retrieved_piece_index, piece))) => {
253                    if retrieved_piece_index == piece_index {
254                        trace!(
255                            %piece_index,
256                            %piece_cache_id,
257                            %piece_cache_offset,
258                            "Retrieved piece from cache successfully"
259                        );
260
261                        return Ok(Some(piece));
262                    }
263
264                    trace!(
265                        %piece_index,
266                        %piece_cache_id,
267                        %piece_cache_offset,
268                        "Retrieving piece was replaced in cache during retrieval"
269                    );
270                }
271                Ok(None) => {
272                    trace!(
273                        %piece_index,
274                        %piece_cache_id,
275                        %piece_cache_offset,
276                        "Piece cache didn't have piece at offset"
277                    );
278                }
279                Err(error) => {
280                    debug!(
281                        %piece_index,
282                        %piece_cache_id,
283                        %piece_cache_offset,
284                        %error,
285                        "Retrieving piece from cache failed"
286                    );
287                }
288            }
289        } else {
290            trace!(%piece_index, "Piece not found in cache");
291        }
292
293        Ok(self
294            .nats_client
295            .request(&ClusterControllerPieceRequest { piece_index }, None)
296            .await?)
297    }
298
299    async fn get_pieces<'a>(
300        &'a self,
301        piece_indices: Vec<PieceIndex>,
302    ) -> anyhow::Result<
303        Box<dyn Stream<Item = (PieceIndex, anyhow::Result<Option<Piece>>)> + Send + Unpin + 'a>,
304    > {
305        let (tx, mut rx) = mpsc::unbounded();
306
307        let piece_indices_to_get =
308            Mutex::new(piece_indices.iter().copied().collect::<HashSet<_>>());
309
310        let mut cached_pieces_by_cache_id = HashMap::<PieceCacheId, Vec<PieceCacheOffset>>::new();
311
312        {
313            let mut cached_pieces = self
314                .nats_client
315                .stream_request(
316                    &ClusterControllerFindPiecesInCacheRequest { piece_indices },
317                    Some(&self.cache_group),
318                )
319                .await?;
320
321            while let Some((_piece_index, piece_cache_id, piece_cache_offset)) =
322                cached_pieces.next().await
323            {
324                cached_pieces_by_cache_id
325                    .entry(piece_cache_id)
326                    .or_default()
327                    .push(piece_cache_offset);
328            }
329        }
330
331        let fut = async move {
332            let tx = &tx;
333
334            cached_pieces_by_cache_id
335                .into_iter()
336                .map(|(piece_cache_id, offsets)| {
337                    let piece_indices_to_get = &piece_indices_to_get;
338
339                    async move {
340                        let mut pieces_stream = match self
341                            .nats_client
342                            .stream_request(
343                                &ClusterCacheReadPiecesRequest { offsets },
344                                Some(&piece_cache_id.to_string()),
345                            )
346                            .await
347                        {
348                            Ok(pieces) => pieces,
349                            Err(error) => {
350                                warn!(
351                                    %error,
352                                    %piece_cache_id,
353                                    "Failed to request pieces from cache"
354                                );
355
356                                return;
357                            }
358                        };
359
360                        while let Some(piece_result) = pieces_stream.next().await {
361                            let (piece_offset, maybe_piece) = match piece_result {
362                                Ok(result) => result,
363                                Err(error) => {
364                                    warn!(%error, "Failed to get piece from cache");
365                                    continue;
366                                }
367                            };
368
369                            if let Some((piece_index, piece)) = maybe_piece {
370                                piece_indices_to_get.lock().remove(&piece_index);
371
372                                tx.unbounded_send((piece_index, Ok(Some(piece)))).expect(
373                                    "This future isn't polled after receiver is dropped; qed",
374                                );
375                            } else {
376                                warn!(
377                                    %piece_cache_id,
378                                    %piece_offset,
379                                    "Failed to get piece from cache, it was missing or already gone"
380                                );
381                            }
382                        }
383                    }
384                })
385                .collect::<FuturesUnordered<_>>()
386                // Simply drain everything
387                .for_each(|()| async {})
388                .await;
389
390            let mut piece_indices_to_get = piece_indices_to_get.into_inner();
391            if piece_indices_to_get.is_empty() {
392                return;
393            }
394
395            let mut pieces_from_controller = match self
396                .nats_client
397                .stream_request(
398                    &ClusterControllerPiecesRequest {
399                        piece_indices: piece_indices_to_get.iter().copied().collect(),
400                    },
401                    None,
402                )
403                .await
404            {
405                Ok(pieces_from_controller) => pieces_from_controller,
406                Err(error) => {
407                    error!(%error, "Failed to get pieces from controller");
408
409                    for piece_index in piece_indices_to_get {
410                        tx.unbounded_send((
411                            piece_index,
412                            Err(anyhow::anyhow!("Failed to get piece from controller")),
413                        ))
414                        .expect("This future isn't polled after receiver is dropped; qed");
415                    }
416                    return;
417                }
418            };
419
420            while let Some((piece_index, piece)) = pieces_from_controller.next().await {
421                piece_indices_to_get.remove(&piece_index);
422                tx.unbounded_send((piece_index, Ok(Some(piece))))
423                    .expect("This future isn't polled after receiver is dropped; qed");
424            }
425
426            for piece_index in piece_indices_to_get {
427                tx.unbounded_send((piece_index, Err(anyhow::anyhow!("Failed to get piece"))))
428                    .expect("This future isn't polled after receiver is dropped; qed");
429            }
430        };
431        let mut fut = Box::pin(fut.fuse());
432
433        // Drive above future and stream back any pieces that were downloaded so far
434        Ok(Box::new(stream::poll_fn(move |cx| {
435            if !fut.is_terminated() {
436                // Result doesn't matter, we'll need to poll stream below anyway
437                let _: Poll<()> = fut.poll_unpin(cx);
438            }
439
440            if let Poll::Ready(maybe_result) = rx.poll_next_unpin(cx) {
441                return Poll::Ready(maybe_result);
442            }
443
444            // Exit will be done by the stream above
445            Poll::Pending
446        })))
447    }
448}
449
450impl ClusterPieceGetter {
451    /// Create a new instance
452    #[inline]
453    pub fn new(nats_client: NatsClient, cache_group: Option<String>) -> Self {
454        Self {
455            nats_client,
456            cache_group: cache_group.unwrap_or_else(|| GLOBAL_CACHE_GROUP.to_string()),
457        }
458    }
459}
460
461/// [`NodeClient`] used in cluster environment that connects to node through a controller instead
462/// of to the node directly
463#[derive(Debug, Clone)]
464pub struct ClusterNodeClient {
465    nats_client: NatsClient,
466    // Store last slot info instance that can be used to send solution response to (some instances
467    // may be not synced and not able to receive solution responses)
468    last_slot_info_instance: Arc<Mutex<String>>,
469}
470
471impl ClusterNodeClient {
472    /// Create a new instance
473    pub fn new(nats_client: NatsClient) -> Self {
474        Self {
475            nats_client,
476            last_slot_info_instance: Arc::default(),
477        }
478    }
479}
480
481#[async_trait]
482impl NodeClient for ClusterNodeClient {
483    async fn farmer_app_info(&self) -> anyhow::Result<FarmerAppInfo> {
484        Ok(self
485            .nats_client
486            .request(&ClusterControllerFarmerAppInfoRequest, None)
487            .await?
488            .map_err(anyhow::Error::msg)?)
489    }
490
491    async fn subscribe_slot_info(
492        &self,
493    ) -> anyhow::Result<Pin<Box<dyn Stream<Item = SlotInfo> + Send + 'static>>> {
494        let subscription = self
495            .nats_client
496            .subscribe_to_broadcasts::<ClusterControllerSlotInfoBroadcast>(None, None)
497            .await?
498            .filter_map({
499                let mut last_slot_number = None;
500                let last_slot_info_instance = Arc::clone(&self.last_slot_info_instance);
501
502                move |broadcast| {
503                    let slot_info = broadcast.slot_info;
504
505                    let maybe_slot_info = if let Some(last_slot_number) = last_slot_number
506                        && last_slot_number >= slot_info.slot
507                    {
508                        None
509                    } else {
510                        last_slot_number.replace(slot_info.slot);
511                        *last_slot_info_instance.lock() = broadcast.instance;
512
513                        Some(slot_info)
514                    };
515
516                    async move { maybe_slot_info }
517                }
518            });
519
520        Ok(Box::pin(subscription))
521    }
522
523    async fn submit_solution_response(
524        &self,
525        solution_response: SolutionResponse,
526    ) -> anyhow::Result<()> {
527        let last_slot_info_instance = self.last_slot_info_instance.lock().clone();
528        Ok(self
529            .nats_client
530            .notification(
531                &ClusterControllerSolutionNotification { solution_response },
532                Some(&last_slot_info_instance),
533            )
534            .await?)
535    }
536
537    async fn subscribe_block_sealing(
538        &self,
539    ) -> anyhow::Result<Pin<Box<dyn Stream<Item = BlockSealInfo> + Send + 'static>>> {
540        let subscription = self
541            .nats_client
542            .subscribe_to_broadcasts::<ClusterControllerBlockSealingBroadcast>(None, None)
543            .await?
544            .map(|broadcast| broadcast.block_sealing_info);
545
546        Ok(Box::pin(subscription))
547    }
548
549    async fn submit_block_seal(&self, block_seal: BlockSealResponse) -> anyhow::Result<()> {
550        Ok(self
551            .nats_client
552            .notification(&ClusterControllerBlockSealNotification { block_seal }, None)
553            .await?)
554    }
555
556    async fn subscribe_new_super_segment_headers(
557        &self,
558    ) -> anyhow::Result<Pin<Box<dyn Stream<Item = SuperSegmentHeader> + Send + 'static>>> {
559        let subscription = self
560            .nats_client
561            .subscribe_to_broadcasts::<ClusterControllerNewSuperSegmentHeaderBroadcast>(None, None)
562            .await?
563            .filter_map({
564                let mut last_super_segment_index = None;
565
566                move |broadcast| {
567                    let new_super_segment_header = broadcast.new_super_segment_header;
568                    let super_segment_index = new_super_segment_header.index.as_inner();
569
570                    let maybe_super_segment_header = if let Some(last_super_segment_index) =
571                        last_super_segment_index
572                        && last_super_segment_index >= super_segment_index
573                    {
574                        None
575                    } else {
576                        last_super_segment_index.replace(super_segment_index);
577
578                        Some(new_super_segment_header)
579                    };
580
581                    async move { maybe_super_segment_header }
582                }
583            });
584
585        Ok(Box::pin(subscription))
586    }
587
588    async fn super_segment_headers(
589        &self,
590        super_segment_indices: Vec<SuperSegmentIndex>,
591    ) -> anyhow::Result<Vec<Option<SuperSegmentHeader>>> {
592        Ok(self
593            .nats_client
594            .request(
595                &ClusterControllerSuperSegmentHeadersRequest {
596                    super_segment_indices,
597                },
598                None,
599            )
600            .await?)
601    }
602
603    async fn super_segment_root_for_segment_index(
604        &self,
605        segment_index: SegmentIndex,
606    ) -> anyhow::Result<Option<SuperSegmentRoot>> {
607        Ok(self
608            .nats_client
609            .request(
610                &ClusterControllerSuperSegmentRootForSegmentIndexRequest { segment_index },
611                None,
612            )
613            .await?)
614    }
615
616    async fn piece(&self, piece_index: PieceIndex) -> anyhow::Result<Option<Piece>> {
617        Ok(self
618            .nats_client
619            .request(&ClusterControllerPieceRequest { piece_index }, None)
620            .await?)
621    }
622
623    async fn update_shard_membership_info(
624        &self,
625        _info: FarmerShardMembershipInfo,
626    ) -> anyhow::Result<()> {
627        // Controller aggregates these on its own
628        Ok(())
629    }
630}
631
632/// Create controller service that handles things like broadcasting information (for example slot
633/// notifications) as well as responding to incoming requests (like piece requests).
634///
635/// Implementation is using concurrency with multiple tokio tasks, but can be started multiple times
636/// per controller instance in order to parallelize more work across threads if needed.
637pub async fn controller_service<NC, PG>(
638    nats_client: &NatsClient,
639    node_client: &NC,
640    piece_getter: &PG,
641    farmer_caches: &[(&str, &FarmerCache)],
642    instance: &str,
643    primary_instance: bool,
644) -> anyhow::Result<()>
645where
646    NC: NodeClient,
647    PG: PieceGetter + Sync,
648{
649    if primary_instance {
650        select! {
651            result = slot_info_broadcaster(nats_client, node_client, instance).fuse() => {
652                result
653            },
654            result = block_sealing_broadcaster(nats_client, node_client, instance).fuse() => {
655                result
656            },
657            result = new_super_segment_headers_broadcaster(nats_client, node_client, instance).fuse() => {
658                result
659            },
660            result = solution_response_forwarder(nats_client, node_client, instance).fuse() => {
661                result
662            },
663            result = block_seal_forwarder(nats_client, node_client, instance).fuse() => {
664                result
665            },
666            result = farmer_app_info_responder(nats_client, node_client).fuse() => {
667                result
668            },
669            result = super_segment_headers_responder(nats_client, node_client).fuse() => {
670                result
671            },
672            result = super_segment_root_for_segment_index_responder(nats_client, node_client).fuse() => {
673                result
674            },
675            result = find_piece_responder(nats_client, farmer_caches).fuse() => {
676                result
677            },
678            result = find_pieces_responder(nats_client, farmer_caches).fuse() => {
679                result
680            },
681            result = piece_responder(nats_client, piece_getter).fuse() => {
682                result
683            },
684            result = pieces_responder(nats_client, piece_getter).fuse() => {
685                result
686            },
687        }
688    } else {
689        select! {
690            result = farmer_app_info_responder(nats_client, node_client).fuse() => {
691                result
692            },
693            result = super_segment_headers_responder(nats_client, node_client).fuse() => {
694                result
695            },
696            result = super_segment_root_for_segment_index_responder(nats_client, node_client).fuse() => {
697                result
698            },
699            result = find_piece_responder(nats_client, farmer_caches).fuse() => {
700                result
701            },
702            result = find_pieces_responder(nats_client, farmer_caches).fuse() => {
703                result
704            },
705            result = piece_responder(nats_client, piece_getter).fuse() => {
706                result
707            },
708            result = pieces_responder(nats_client, piece_getter).fuse() => {
709                result
710            },
711        }
712    }
713}
714
715async fn slot_info_broadcaster<NC>(
716    nats_client: &NatsClient,
717    node_client: &NC,
718    instance: &str,
719) -> anyhow::Result<()>
720where
721    NC: NodeClient,
722{
723    let mut slot_info_notifications = node_client
724        .subscribe_slot_info()
725        .await
726        .map_err(|error| anyhow!("Failed to subscribe to slot info notifications: {error}"))?;
727
728    while let Some(slot_info) = slot_info_notifications.next().await {
729        debug!(?slot_info, "New slot");
730
731        let slot = slot_info.slot;
732
733        if let Err(error) = nats_client
734            .broadcast(
735                &ClusterControllerSlotInfoBroadcast {
736                    slot_info,
737                    instance: instance.to_string(),
738                },
739                instance,
740            )
741            .await
742        {
743            warn!(%slot, %error, "Failed to broadcast slot info");
744        }
745    }
746
747    Ok(())
748}
749
750async fn block_sealing_broadcaster<NC>(
751    nats_client: &NatsClient,
752    node_client: &NC,
753    instance: &str,
754) -> anyhow::Result<()>
755where
756    NC: NodeClient,
757{
758    let mut block_sealing_subscription = node_client
759        .subscribe_block_sealing()
760        .await
761        .map_err(|error| anyhow!("Failed to subscribe to block sealing notifications: {error}"))?;
762
763    while let Some(block_sealing_info) = block_sealing_subscription.next().await {
764        trace!(?block_sealing_info, "New block sealing notification");
765
766        if let Err(error) = nats_client
767            .broadcast(
768                &ClusterControllerBlockSealingBroadcast { block_sealing_info },
769                instance,
770            )
771            .await
772        {
773            warn!(%error, "Failed to broadcast block sealing info");
774        }
775    }
776
777    Ok(())
778}
779
780async fn new_super_segment_headers_broadcaster<NC>(
781    nats_client: &NatsClient,
782    node_client: &NC,
783    instance: &str,
784) -> anyhow::Result<()>
785where
786    NC: NodeClient,
787{
788    let mut new_super_segments_notifications = node_client
789        .subscribe_new_super_segment_headers()
790        .await
791        .map_err(|error| {
792            anyhow!("Failed to subscribe to new super segment header notifications: {error}")
793        })?;
794
795    while let Some(new_super_segment_header) = new_super_segments_notifications.next().await {
796        trace!(
797            ?new_super_segment_header,
798            "New archived new super segment header notification"
799        );
800
801        if let Err(error) = nats_client
802            .broadcast(
803                &ClusterControllerNewSuperSegmentHeaderBroadcast {
804                    new_super_segment_header,
805                },
806                instance,
807            )
808            .await
809        {
810            warn!(%error, "Failed to broadcast new super segment header info");
811        }
812    }
813
814    Ok(())
815}
816
817async fn solution_response_forwarder<NC>(
818    nats_client: &NatsClient,
819    node_client: &NC,
820    instance: &str,
821) -> anyhow::Result<()>
822where
823    NC: NodeClient,
824{
825    let mut subscription = nats_client
826        .subscribe_to_notifications::<ClusterControllerSolutionNotification>(
827            Some(instance),
828            Some(instance.to_string()),
829        )
830        .await
831        .map_err(|error| anyhow!("Failed to subscribe to solution notifications: {error}"))?;
832
833    while let Some(notification) = subscription.next().await {
834        debug!(?notification, "Solution notification");
835
836        let slot = notification.solution_response.slot_number;
837        let public_key_hash = notification.solution_response.solution.public_key_hash;
838        let sector_index = notification.solution_response.solution.sector_index;
839
840        if let Err(error) = node_client
841            .submit_solution_response(notification.solution_response)
842            .await
843        {
844            warn!(
845                %error,
846                %slot,
847                %public_key_hash,
848                %sector_index,
849                "Failed to send solution response"
850            );
851        }
852    }
853
854    Ok(())
855}
856
857async fn block_seal_forwarder<NC>(
858    nats_client: &NatsClient,
859    node_client: &NC,
860    instance: &str,
861) -> anyhow::Result<()>
862where
863    NC: NodeClient,
864{
865    let mut subscription = nats_client
866        .subscribe_to_notifications::<ClusterControllerBlockSealNotification>(
867            None,
868            Some(instance.to_string()),
869        )
870        .await
871        .map_err(|error| anyhow!("Failed to subscribe to block seal notifications: {error}"))?;
872
873    while let Some(notification) = subscription.next().await {
874        debug!(?notification, "Block seal notification");
875
876        if let Err(error) = node_client.submit_block_seal(notification.block_seal).await {
877            warn!(%error, "Failed to send block seal");
878        }
879    }
880
881    Ok(())
882}
883
884async fn farmer_app_info_responder<NC>(
885    nats_client: &NatsClient,
886    node_client: &NC,
887) -> anyhow::Result<()>
888where
889    NC: NodeClient,
890{
891    nats_client
892        .request_responder(
893            None,
894            Some("ab.controller".to_string()),
895            |_: ClusterControllerFarmerAppInfoRequest| async move {
896                Some(
897                    node_client
898                        .farmer_app_info()
899                        .await
900                        .map_err(|error| error.to_string()),
901                )
902            },
903        )
904        .await
905}
906
907async fn super_segment_headers_responder<NC>(
908    nats_client: &NatsClient,
909    node_client: &NC,
910) -> anyhow::Result<()>
911where
912    NC: NodeClient,
913{
914    nats_client
915        .request_responder(
916            None,
917            Some("ab.controller".to_string()),
918            |ClusterControllerSuperSegmentHeadersRequest { super_segment_indices }| async move {
919                node_client
920                    .super_segment_headers(super_segment_indices.clone())
921                    .await
922                    .inspect_err(|error| {
923                        warn!(%error, ?super_segment_indices, "Failed to get super segment headers");
924                    })
925                    .ok()
926            },
927        )
928        .await
929}
930
931async fn super_segment_root_for_segment_index_responder<NC>(
932    nats_client: &NatsClient,
933    node_client: &NC,
934) -> anyhow::Result<()>
935where
936    NC: NodeClient,
937{
938    nats_client
939        .request_responder(
940            None,
941            Some("ab.controller".to_string()),
942            |ClusterControllerSuperSegmentRootForSegmentIndexRequest { segment_index }| async move {
943                node_client
944                    .super_segment_root_for_segment_index(segment_index)
945                    .await
946                    .inspect_err(|error| {
947                        warn!(
948                            %error,
949                            %segment_index,
950                            "Failed to get super segment root for segment index"
951                        );
952                    })
953                    .ok()
954            },
955        )
956        .await
957}
958
959async fn find_piece_responder(
960    nats_client: &NatsClient,
961    farmer_caches: &[(&str, &FarmerCache)],
962) -> anyhow::Result<()> {
963    futures::future::try_join(
964        farmer_caches
965            .iter()
966            .map(|(cache_group, farmer_cache)| {
967                nats_client.request_responder(
968                    Some(cache_group),
969                    Some("ab.controller".to_string()),
970                    move |ClusterControllerFindPieceInCacheRequest { piece_index }| async move {
971                        Some(farmer_cache.find_piece(piece_index).await)
972                    },
973                )
974            })
975            .collect::<FuturesUnordered<_>>()
976            .next()
977            .map(|result| result.unwrap_or(Ok(()))),
978        nats_client.request_responder(
979            Some(GLOBAL_CACHE_GROUP),
980            Some("ab.controller".to_string()),
981            |ClusterControllerFindPieceInCacheRequest { piece_index }| async move {
982                let (_cache_group, farmer_cache) = farmer_caches.iter().choose(&mut rand::rng())?;
983                Some(farmer_cache.find_piece(piece_index).await)
984            },
985        ),
986    )
987    .await
988    .map(|((), ())| ())
989}
990
991async fn find_pieces_responder(
992    nats_client: &NatsClient,
993    farmer_caches: &[(&str, &FarmerCache)],
994) -> anyhow::Result<()> {
995    futures::future::try_join(
996        farmer_caches
997            .iter()
998            .map(|(cache_group, farmer_cache)| {
999                nats_client.stream_request_responder(
1000                    Some(cache_group),
1001                    Some("ab.controller".to_string()),
1002                    move |ClusterControllerFindPiecesInCacheRequest { piece_indices }| async move {
1003                        Some(stream::iter(farmer_cache.find_pieces(piece_indices).await))
1004                    },
1005                )
1006            })
1007            .collect::<FuturesUnordered<_>>()
1008            .next()
1009            .map(|result| result.unwrap_or(Ok(()))),
1010        nats_client.stream_request_responder(
1011            Some(GLOBAL_CACHE_GROUP),
1012            Some("ab.controller".to_string()),
1013            |ClusterControllerFindPiecesInCacheRequest { piece_indices }| async move {
1014                let (_cache_group, farmer_cache) = farmer_caches.iter().choose(&mut rand::rng())?;
1015                Some(stream::iter(farmer_cache.find_pieces(piece_indices).await))
1016            },
1017        ),
1018    )
1019    .await
1020    .map(|((), ())| ())
1021}
1022
1023async fn piece_responder<PG>(nats_client: &NatsClient, piece_getter: &PG) -> anyhow::Result<()>
1024where
1025    PG: PieceGetter + Sync,
1026{
1027    nats_client
1028        .request_responder(
1029            None,
1030            Some("ab.controller".to_string()),
1031            |ClusterControllerPieceRequest { piece_index }| async move {
1032                piece_getter
1033                    .get_piece(piece_index)
1034                    .await
1035                    .inspect_err(|error| warn!(%error, %piece_index, "Failed to get piece"))
1036                    .ok()
1037            },
1038        )
1039        .await
1040}
1041
1042async fn pieces_responder<PG>(nats_client: &NatsClient, piece_getter: &PG) -> anyhow::Result<()>
1043where
1044    PG: PieceGetter + Sync,
1045{
1046    nats_client
1047        .stream_request_responder(
1048            None,
1049            Some("ab.controller".to_string()),
1050            |ClusterControllerPiecesRequest { piece_indices }| async move {
1051                piece_getter
1052                    .get_pieces(piece_indices)
1053                    .await
1054                    .map(|stream| {
1055                        Box::pin(stream.filter_map(
1056                            |(piece_index, maybe_piece_result)| async move {
1057                                match maybe_piece_result {
1058                                    Ok(Some(piece)) => Some((piece_index, piece)),
1059                                    Ok(None) => None,
1060                                    Err(error) => {
1061                                        warn!(%error, %piece_index, "Failed to get piece");
1062                                        None
1063                                    }
1064                                }
1065                            },
1066                        ))
1067                    })
1068                    .inspect_err(|error| warn!(%error, "Failed to get pieces"))
1069                    .ok()
1070            },
1071        )
1072        .await
1073}