subspace_farmer/node_client/
caching_proxy_node_client.rs1use 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 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 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 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 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 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 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 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#[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 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 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 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 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 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 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 let segment_headers = self.segment_headers.upgradable_read_arc().await;
403
404 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 if segment_headers.is_sync_rate_limited() {
413 return Ok(retrieved_segment_headers);
414 }
415
416 let extra_segment_headers = segment_headers
418 .request_uncached_headers(&self.inner)
419 .await?;
420
421 if extra_segment_headers.is_empty() {
422 return Ok(retrieved_segment_headers);
424 }
425
426 let mut segment_headers =
428 AsyncRwLockUpgradableReadGuard::upgrade(segment_headers).await;
429 segment_headers.write_cache(extra_segment_headers);
430
431 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 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 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}