Skip to main content

subspace_farmer/node_client/
caching_proxy_node_client.rs

1//! Node client wrapper around another node client that caches some data for better performance and
2//! proxies other requests through
3
4use crate::node_client::{NodeClient, NodeClientExt};
5use async_lock::{
6    Mutex as AsyncMutex, RwLock as AsyncRwLock,
7    RwLockUpgradableReadGuardArc as AsyncRwLockUpgradableReadGuard,
8    RwLockWriteGuardArc as AsyncRwLockWriteGuard,
9};
10use async_trait::async_trait;
11use futures::{FutureExt, Stream, StreamExt, select};
12use std::pin::Pin;
13use std::sync::Arc;
14use std::time::{Duration, Instant};
15use subspace_core_primitives::pieces::{Piece, PieceIndex};
16use subspace_core_primitives::segments::{SegmentHeader, SegmentIndex};
17use subspace_process::AsyncJoinOnDrop;
18use subspace_rpc_primitives::{
19    FarmerAppInfo, MAX_SEGMENT_HEADERS_PER_REQUEST, RewardSignatureResponse, RewardSigningInfo,
20    SlotInfo, SolutionResponse,
21};
22use tokio::sync::watch;
23use tokio_stream::wrappers::WatchStream;
24use tracing::{debug, info, trace, warn};
25
26const SEGMENT_HEADERS_SYNC_INTERVAL: Duration = Duration::from_secs(1);
27const FARMER_APP_INFO_DEDUPLICATION_WINDOW: Duration = Duration::from_secs(1);
28
29#[derive(Debug, Default)]
30struct SegmentHeaders {
31    segment_headers: Vec<SegmentHeader>,
32    last_synced: Option<Instant>,
33}
34
35impl SegmentHeaders {
36    /// Push a new segment header to the cache, if it is the next segment header.
37    /// Otherwise, skip the push.
38    fn push(&mut self, archived_segment_header: SegmentHeader) {
39        if self.segment_headers.len() == u64::from(archived_segment_header.segment_index()) as usize
40        {
41            self.segment_headers.push(archived_segment_header);
42        }
43    }
44
45    /// Get cached segment headers for the given segment indices.
46    ///
47    /// Returns `None` for segment indices that are not in the cache.
48    fn get_segment_headers(&self, segment_indices: &[SegmentIndex]) -> Vec<Option<SegmentHeader>> {
49        segment_indices
50            .iter()
51            .map(|segment_index| {
52                self.segment_headers
53                    .get(u64::from(*segment_index) as usize)
54                    .copied()
55            })
56            .collect::<Vec<_>>()
57    }
58
59    /// Get the last `limit` segment headers from the cache.
60    fn last_segment_headers(&self, limit: u32) -> Vec<Option<SegmentHeader>> {
61        self.segment_headers
62            .iter()
63            .rev()
64            .take(limit as usize)
65            .rev()
66            .copied()
67            .map(Some)
68            .collect()
69    }
70
71    /// Whether the last sync was too recent to sync again.
72    fn is_sync_rate_limited(&self) -> bool {
73        self.last_synced
74            .is_some_and(|last_synced| last_synced.elapsed() < SEGMENT_HEADERS_SYNC_INTERVAL)
75    }
76
77    /// Get uncached headers from the node.
78    /// This only requires a read lock.
79    ///
80    /// Returns any extra segment headers if the download succeeds, or an error if it fails.
81    /// The caller must write the returned segment headers to the cache, and reset the sync
82    /// rate-limit timer.
83    async fn request_uncached_headers<NC>(&self, client: &NC) -> anyhow::Result<Vec<SegmentHeader>>
84    where
85        NC: NodeClient,
86    {
87        let mut extra_segment_headers = Vec::new();
88        let mut segment_index_offset = SegmentIndex::from(self.segment_headers.len() as u64);
89        let segment_index_step = SegmentIndex::from(MAX_SEGMENT_HEADERS_PER_REQUEST as u64);
90
91        'outer: loop {
92            let from = segment_index_offset;
93            let to = segment_index_offset + segment_index_step;
94            trace!(%from, %to, "Requesting segment headers");
95
96            for maybe_segment_header in client
97                .segment_headers((from..to).collect::<Vec<_>>())
98                .await
99                .map_err(|error| {
100                    anyhow::anyhow!(
101                        "Failed to download segment headers {from}..{to} from node: {error}"
102                    )
103                })?
104            {
105                let Some(segment_header) = maybe_segment_header else {
106                    // Reached non-existent segment header
107                    break 'outer;
108                };
109
110                extra_segment_headers.push(segment_header);
111            }
112
113            segment_index_offset += segment_index_step;
114        }
115
116        Ok(extra_segment_headers)
117    }
118
119    /// Write the sync results to the cache, and reset the sync rate-limit timer.
120    fn write_cache(&mut self, extra_segment_headers: Vec<SegmentHeader>) {
121        for segment_header in extra_segment_headers {
122            self.push(segment_header);
123        }
124        self.last_synced.replace(Instant::now());
125    }
126}
127
128/// Node client wrapper around another node client that caches some data for better performance and
129/// proxies other requests through.
130///
131/// NOTE: Archived segment acknowledgement is ignored in this client, all subscriptions are
132/// acknowledged implicitly and immediately.
133/// NOTE: Subscription messages that are not processed in time will be skipped for performance
134/// reasons!
135#[derive(Debug, Clone)]
136pub struct CachingProxyNodeClient<NC> {
137    inner: NC,
138    slot_info_receiver: watch::Receiver<Option<SlotInfo>>,
139    archived_segment_headers_receiver: watch::Receiver<Option<SegmentHeader>>,
140    reward_signing_receiver: watch::Receiver<Option<RewardSigningInfo>>,
141    segment_headers: Arc<AsyncRwLock<SegmentHeaders>>,
142    last_farmer_app_info: Arc<AsyncMutex<(FarmerAppInfo, Instant)>>,
143    _background_task: Arc<AsyncJoinOnDrop<()>>,
144}
145
146impl<NC> CachingProxyNodeClient<NC>
147where
148    NC: NodeClient + Clone,
149{
150    /// Create a new instance
151    pub async fn new(client: NC) -> anyhow::Result<Self> {
152        let mut segment_headers = SegmentHeaders::default();
153        let mut archived_segments_notifications =
154            client.subscribe_archived_segment_headers().await?;
155
156        info!("Downloading all segment headers from node...");
157        // No locking is needed, we are the first and only instance right now.
158        let headers = segment_headers.request_uncached_headers(&client).await?;
159        segment_headers.write_cache(headers);
160        info!("Downloaded all segment headers from node successfully");
161
162        let segment_headers = Arc::new(AsyncRwLock::new(segment_headers));
163
164        let (slot_info_sender, slot_info_receiver) = watch::channel(None::<SlotInfo>);
165        let slot_info_proxy_fut = {
166            let mut slot_info_subscription = client.subscribe_slot_info().await?;
167
168            async move {
169                let mut last_slot_number = None;
170                while let Some(slot_info) = slot_info_subscription.next().await {
171                    if let Some(last_slot_number) = last_slot_number
172                        && last_slot_number >= slot_info.slot_number
173                    {
174                        continue;
175                    }
176                    last_slot_number.replace(slot_info.slot_number);
177
178                    if let Err(error) = slot_info_sender.send(Some(slot_info)) {
179                        warn!(%error, "Failed to proxy slot info notification");
180                        return;
181                    }
182                }
183            }
184        };
185
186        let (archived_segment_headers_sender, archived_segment_headers_receiver) =
187            watch::channel(None::<SegmentHeader>);
188        let segment_headers_maintenance_fut = {
189            let client = client.clone();
190            let segment_headers = Arc::clone(&segment_headers);
191
192            async move {
193                let mut last_archived_segment_index = None;
194                while let Some(archived_segment_header) =
195                    archived_segments_notifications.next().await
196                {
197                    let segment_index = archived_segment_header.segment_index();
198                    trace!(
199                        ?archived_segment_header,
200                        "New archived archived segment header notification"
201                    );
202
203                    while let Err(error) = client
204                        .acknowledge_archived_segment_header(segment_index)
205                        .await
206                    {
207                        warn!(
208                            %error,
209                            "Failed to acknowledge archived segment header, trying again"
210                        );
211                    }
212
213                    if let Some(last_archived_segment_index) = last_archived_segment_index
214                        && last_archived_segment_index >= segment_index
215                    {
216                        continue;
217                    }
218                    last_archived_segment_index.replace(segment_index);
219
220                    // Notifications are not guaranteed to be delivered (for example, they are
221                    // skipped when not processed in time or lost while the source is restarting),
222                    // download missing headers before handling this one to not end up with a gap
223                    loop {
224                        let cached_count =
225                            segment_headers.read().await.segment_headers.len() as u64;
226                        if cached_count >= u64::from(segment_index) {
227                            break;
228                        }
229
230                        let result = segment_headers
231                            .read()
232                            .await
233                            .request_uncached_headers(&client)
234                            .await;
235                        match result {
236                            Ok(extra_segment_headers) => {
237                                if !extra_segment_headers.is_empty() {
238                                    segment_headers
239                                        .write()
240                                        .await
241                                        .write_cache(extra_segment_headers);
242                                    continue;
243                                }
244
245                                debug!(
246                                    %segment_index,
247                                    "Missing segment headers are not available yet, retrying"
248                                );
249                            }
250                            Err(error) => {
251                                warn!(
252                                    %error,
253                                    %segment_index,
254                                    "Failed to download missing segment headers, retrying"
255                                );
256                            }
257                        }
258
259                        tokio::time::sleep(SEGMENT_HEADERS_SYNC_INTERVAL).await;
260                    }
261
262                    // Usually already downloaded above, but the source might not have it yet
263                    segment_headers.write().await.push(archived_segment_header);
264
265                    if let Err(error) =
266                        archived_segment_headers_sender.send(Some(archived_segment_header))
267                    {
268                        warn!(%error, "Failed to proxy archived segment header notification");
269                        return;
270                    }
271                }
272            }
273        };
274
275        let (reward_signing_sender, reward_signing_receiver) =
276            watch::channel(None::<RewardSigningInfo>);
277        let reward_signing_proxy_fut = {
278            let mut reward_signing_subscription = client.subscribe_reward_signing().await?;
279
280            async move {
281                while let Some(reward_signing_info) = reward_signing_subscription.next().await {
282                    if let Err(error) = reward_signing_sender.send(Some(reward_signing_info)) {
283                        warn!(%error, "Failed to proxy reward signing notification");
284                        return;
285                    }
286                }
287            }
288        };
289
290        let farmer_app_info = client
291            .farmer_app_info()
292            .await
293            .map_err(|error| anyhow::anyhow!("Failed to get farmer app info: {error}"))?;
294        let last_farmer_app_info = Arc::new(AsyncMutex::new((farmer_app_info, Instant::now())));
295
296        let background_task = tokio::spawn(async move {
297            select! {
298                _ = slot_info_proxy_fut.fuse() => {},
299                _ = segment_headers_maintenance_fut.fuse() => {},
300                _ = reward_signing_proxy_fut.fuse() => {},
301            }
302        });
303
304        let node_client = Self {
305            inner: client,
306            slot_info_receiver,
307            archived_segment_headers_receiver,
308            reward_signing_receiver,
309            segment_headers,
310            last_farmer_app_info,
311            _background_task: Arc::new(AsyncJoinOnDrop::new(background_task, true)),
312        };
313
314        Ok(node_client)
315    }
316}
317
318#[async_trait]
319impl<NC> NodeClient for CachingProxyNodeClient<NC>
320where
321    NC: NodeClient,
322{
323    async fn farmer_app_info(&self) -> anyhow::Result<FarmerAppInfo> {
324        let (last_farmer_app_info, last_farmer_app_info_request) =
325            &mut *self.last_farmer_app_info.lock().await;
326
327        if last_farmer_app_info_request.elapsed() > FARMER_APP_INFO_DEDUPLICATION_WINDOW {
328            let new_last_farmer_app_info = self.inner.farmer_app_info().await?;
329
330            *last_farmer_app_info = new_last_farmer_app_info;
331            *last_farmer_app_info_request = Instant::now();
332        }
333
334        Ok(last_farmer_app_info.clone())
335    }
336
337    async fn subscribe_slot_info(
338        &self,
339    ) -> anyhow::Result<Pin<Box<dyn Stream<Item = SlotInfo> + Send + 'static>>> {
340        Ok(Box::pin(
341            WatchStream::new(self.slot_info_receiver.clone())
342                .filter_map(|maybe_slot_info| async move { maybe_slot_info }),
343        ))
344    }
345
346    async fn submit_solution_response(
347        &self,
348        solution_response: SolutionResponse,
349    ) -> anyhow::Result<()> {
350        self.inner.submit_solution_response(solution_response).await
351    }
352
353    async fn subscribe_reward_signing(
354        &self,
355    ) -> anyhow::Result<Pin<Box<dyn Stream<Item = RewardSigningInfo> + Send + 'static>>> {
356        Ok(Box::pin(
357            WatchStream::new(self.reward_signing_receiver.clone())
358                .filter_map(|maybe_reward_signing_info| async move { maybe_reward_signing_info }),
359        ))
360    }
361
362    /// Submit a block signature
363    async fn submit_reward_signature(
364        &self,
365        reward_signature: RewardSignatureResponse,
366    ) -> anyhow::Result<()> {
367        self.inner.submit_reward_signature(reward_signature).await
368    }
369
370    async fn subscribe_archived_segment_headers(
371        &self,
372    ) -> anyhow::Result<Pin<Box<dyn Stream<Item = SegmentHeader> + Send + 'static>>> {
373        Ok(Box::pin(
374            WatchStream::new(self.archived_segment_headers_receiver.clone())
375                .filter_map(|maybe_segment_header| async move { maybe_segment_header }),
376        ))
377    }
378
379    /// Gets segment headers for the given segment indices, updating the cache from the node if
380    /// needed.
381    ///
382    /// Returns `None` for segment indices that are not in the cache.
383    async fn segment_headers(
384        &self,
385        segment_indices: Vec<SegmentIndex>,
386    ) -> anyhow::Result<Vec<Option<SegmentHeader>>> {
387        let retrieved_segment_headers = self
388            .segment_headers
389            .read()
390            .await
391            .get_segment_headers(&segment_indices);
392
393        if retrieved_segment_headers.iter().all(Option::is_some) {
394            Ok(retrieved_segment_headers)
395        } else {
396            // We might be missing a requested segment header.
397            // Sync the cache with the node, applying a rate limit, and return cached segment headers.
398
399            // If we took a write lock here, a queue of writers could starve all the readers, even if
400            // those writers would be rate-limited. So we take an upgradable read lock for the rate
401            // limit check.
402            let segment_headers = self.segment_headers.upgradable_read_arc().await;
403
404            // Try again after acquiring the upgradeable read lock, in case another caller already
405            // synced the headers.
406            let retrieved_segment_headers = segment_headers.get_segment_headers(&segment_indices);
407            if retrieved_segment_headers.iter().all(Option::is_some) {
408                return Ok(retrieved_segment_headers);
409            }
410
411            // Skip the sync if we're still within the sync rate limit.
412            if segment_headers.is_sync_rate_limited() {
413                return Ok(retrieved_segment_headers);
414            }
415
416            // Try to sync the cache with the node.
417            let extra_segment_headers = segment_headers
418                .request_uncached_headers(&self.inner)
419                .await?;
420
421            if extra_segment_headers.is_empty() {
422                // No extra segment headers on the node, so just return what we have in the cache.
423                return Ok(retrieved_segment_headers);
424            }
425
426            // We need to update the cached segment headers, so take the write lock.
427            let mut segment_headers =
428                AsyncRwLockUpgradableReadGuard::upgrade(segment_headers).await;
429            segment_headers.write_cache(extra_segment_headers);
430
431            // Downgrade the write lock to a read lock to get the updated segment headers for the
432            // query.
433            Ok(AsyncRwLockWriteGuard::downgrade(segment_headers)
434                .get_segment_headers(&segment_indices))
435        }
436    }
437
438    async fn piece(&self, piece_index: PieceIndex) -> anyhow::Result<Option<Piece>> {
439        self.inner.piece(piece_index).await
440    }
441
442    async fn acknowledge_archived_segment_header(
443        &self,
444        _segment_index: SegmentIndex,
445    ) -> anyhow::Result<()> {
446        // Not supported
447        Ok(())
448    }
449}
450
451#[async_trait]
452impl<NC> NodeClientExt for CachingProxyNodeClient<NC>
453where
454    NC: NodeClientExt,
455{
456    async fn cached_segment_headers(
457        &self,
458        segment_indices: Vec<SegmentIndex>,
459    ) -> anyhow::Result<Vec<Option<SegmentHeader>>> {
460        // To avoid remote denial of service, we don't update the cache here, because it is called
461        // from network code.
462        Ok(self
463            .segment_headers
464            .read()
465            .await
466            .get_segment_headers(&segment_indices))
467    }
468
469    async fn last_segment_headers(&self, limit: u32) -> anyhow::Result<Vec<Option<SegmentHeader>>> {
470        Ok(self
471            .segment_headers
472            .read()
473            .await
474            .last_segment_headers(limit))
475    }
476}