1mod metrics;
7mod piece_cache_state;
8#[cfg(not(miri))]
10#[cfg(test)]
11mod tests;
12
13use crate::farm::{MaybePieceStoredResult, PieceCache, PieceCacheId, PieceCacheOffset, PlotCache};
14use crate::farmer_cache::metrics::FarmerCacheMetrics;
15use crate::farmer_cache::piece_cache_state::PieceCachesState;
16use crate::node_client::NodeClient;
17use crate::utils::run_future_in_dedicated_thread;
18use ab_core_primitives::pieces::{Piece, PieceIndex};
19use ab_core_primitives::segments::SegmentIndex;
20use ab_data_retrieval::piece_getter::PieceGetter;
21use ab_networking::KeyWithDistance;
22use ab_networking::libp2p::PeerId;
23use ab_networking::libp2p::kad::RecordKey;
24use ab_networking::utils::multihash::ToMultihash;
25use async_lock::RwLock as AsyncRwLock;
26use bytesize::ByteSize;
27use event_listener_primitives::{Bag, HandlerId};
28use futures::channel::mpsc;
29use futures::future::{Either, FusedFuture};
30use futures::stream::{FuturesOrdered, FuturesUnordered};
31use futures::{FutureExt, SinkExt, Stream, StreamExt, select, stream};
32use parking_lot::{Mutex, RwLock};
33use prometheus_client::registry::Registry;
34use rand::prelude::*;
35use rayon::prelude::*;
36use std::collections::hash_map::Entry;
37use std::collections::{HashMap, HashSet};
38use std::future::join;
39use std::sync::Arc as StdArc;
40use std::sync::atomic::{AtomicUsize, Ordering};
41use std::task::Poll;
42use std::time::Duration;
43use std::{fmt, mem};
44use tokio::sync::Semaphore;
45use tokio::task::yield_now;
46use tracing::{Instrument, debug, error, info, info_span, trace, warn};
47use triomphe::Arc;
48
49const WORKER_CHANNEL_CAPACITY: usize = 100;
50const SYNC_BATCH_SIZE: usize = 256;
51const SYNC_CONCURRENT_BATCHES: usize = 4;
52const INTERMEDIATE_CACHE_UPDATE_INTERVAL: usize = 100;
55const INITIAL_SYNC_FARM_INFO_CHECK_INTERVAL: Duration = Duration::from_secs(1);
56
57type HandlerFn<A> = StdArc<dyn Fn(&A) + Send + Sync + 'static>;
58type Handler<A> = Bag<HandlerFn<A>, A>;
59type CacheIndex = u8;
60
61#[derive(Default, Debug)]
62struct Handlers {
63 progress: Handler<f32>,
64}
65
66#[derive(Debug, Clone, Copy)]
67struct FarmerCacheOffset {
68 cache_index: CacheIndex,
69 piece_offset: PieceCacheOffset,
70}
71
72impl FarmerCacheOffset {
73 fn new(cache_index: CacheIndex, piece_offset: PieceCacheOffset) -> Self {
74 Self {
75 cache_index,
76 piece_offset,
77 }
78 }
79}
80
81#[derive(Debug, Clone)]
82struct CacheBackend {
83 backend: StdArc<dyn PieceCache>,
84 used_capacity: u32,
85 total_capacity: u32,
86}
87
88impl std::ops::Deref for CacheBackend {
89 type Target = StdArc<dyn PieceCache>;
90
91 fn deref(&self) -> &Self::Target {
92 &self.backend
93 }
94}
95
96impl CacheBackend {
97 fn new(backend: StdArc<dyn PieceCache>, total_capacity: u32) -> Self {
98 Self {
99 backend,
100 used_capacity: 0,
101 total_capacity,
102 }
103 }
104
105 fn next_free(&mut self) -> Option<PieceCacheOffset> {
106 let offset = self.used_capacity;
107 if offset < self.total_capacity {
108 self.used_capacity += 1;
109 Some(PieceCacheOffset(offset))
110 } else {
111 debug!(?offset, total_capacity = ?self.total_capacity, "No free space in cache backend");
112 None
113 }
114 }
115
116 fn free_size(&self) -> u32 {
117 self.total_capacity - self.used_capacity
118 }
119}
120
121#[derive(Debug)]
122struct CacheState {
123 cache_stored_pieces: HashMap<KeyWithDistance, FarmerCacheOffset>,
124 cache_free_offsets: Vec<FarmerCacheOffset>,
125 backend: CacheBackend,
126}
127
128#[derive(Debug)]
129enum WorkerCommand {
130 ReplaceBackingCaches {
131 new_piece_caches: Vec<StdArc<dyn PieceCache>>,
132 },
133 ForgetKey {
134 key: RecordKey,
135 },
136}
137
138#[derive(Debug)]
140#[must_use = "Farmer cache will not work unless its worker is running"]
141pub struct FarmerCacheWorker<NC>
142where
143 NC: fmt::Debug,
144{
145 peer_id: PeerId,
146 node_client: NC,
147 piece_caches: Arc<AsyncRwLock<PieceCachesState>>,
148 plot_caches: Arc<PlotCaches>,
149 handlers: Arc<Handlers>,
150 worker_receiver: Option<mpsc::Receiver<WorkerCommand>>,
151 metrics: Option<Arc<FarmerCacheMetrics>>,
152}
153
154impl<NC> FarmerCacheWorker<NC>
155where
156 NC: NodeClient,
157{
158 pub async fn run<PG>(mut self, piece_getter: PG)
162 where
163 PG: PieceGetter,
164 {
165 let mut last_segment_index_internal = SegmentIndex::ZERO;
167
168 let mut worker_receiver = self
169 .worker_receiver
170 .take()
171 .expect("Always set during worker instantiation");
172
173 if let Some(WorkerCommand::ReplaceBackingCaches { new_piece_caches }) =
174 worker_receiver.next().await
175 {
176 self.initialize(
177 &piece_getter,
178 &mut last_segment_index_internal,
179 new_piece_caches,
180 )
181 .await;
182 } else {
183 return;
185 }
186
187 let mut super_segment_headers_notifications = match self
188 .node_client
189 .subscribe_new_super_segment_headers()
190 .await
191 {
192 Ok(super_segment_headers_notifications) => super_segment_headers_notifications,
193 Err(error) => {
194 error!(%error, "Failed to subscribe to new super segment headers notifications");
195 return;
196 }
197 };
198
199 self.keep_up_after_initial_sync(&piece_getter, &mut last_segment_index_internal)
201 .await;
202
203 loop {
204 select! {
205 maybe_command = worker_receiver.next() => {
206 let Some(command) = maybe_command else {
207 return;
209 };
210
211 self.handle_command(command, &piece_getter, &mut last_segment_index_internal).await;
212 }
213 maybe_super_segment_header = super_segment_headers_notifications.next().fuse() => {
214 if let Some(super_segment_header) = maybe_super_segment_header {
215 self
216 .process_new_segment_index(
217 &piece_getter,
218 super_segment_header.max_segment_index.as_inner(),
219 &mut last_segment_index_internal
220 )
221 .await;
222 } else {
223 return;
226 }
227 }
228 }
229 }
230 }
231
232 async fn handle_command<PG>(
233 &self,
234 command: WorkerCommand,
235 piece_getter: &PG,
236 last_segment_index_internal: &mut SegmentIndex,
237 ) where
238 PG: PieceGetter,
239 {
240 match command {
241 WorkerCommand::ReplaceBackingCaches { new_piece_caches } => {
242 self.initialize(piece_getter, last_segment_index_internal, new_piece_caches)
243 .await;
244 }
245 WorkerCommand::ForgetKey { key } => {
247 let mut caches = self.piece_caches.write().await;
248 let key = KeyWithDistance::new_with_record_key(self.peer_id, key);
249 let Some(offset) = caches.remove_stored_piece(&key) else {
250 return;
252 };
253
254 let cache_index = offset.cache_index;
255 let piece_offset = offset.piece_offset;
256 let Some(backend) = caches.get_backend(cache_index).cloned() else {
257 return;
259 };
260
261 caches.push_dangling_free_offset(offset);
262 match backend.read_piece_index(piece_offset).await {
263 Ok(Some(piece_index)) => {
264 trace!(%piece_index, %cache_index, %piece_offset, "Forget piece");
265 }
266 Ok(None) => {
267 warn!(
268 %cache_index,
269 %piece_offset,
270 "Piece index out of range, this is likely an implementation bug, \
271 not freeing heap element"
272 );
273 }
274 Err(error) => {
275 error!(
276 %error,
277 %cache_index,
278 ?key,
279 %piece_offset,
280 "Error while reading piece from cache"
281 );
282 }
283 }
284 }
285 }
286 }
287
288 async fn initialize<PG>(
289 &self,
290 piece_getter: &PG,
291 last_segment_index_internal: &mut SegmentIndex,
292 new_piece_caches: Vec<StdArc<dyn PieceCache>>,
293 ) where
294 PG: PieceGetter,
295 {
296 info!("Initializing piece cache");
297
298 let (mut stored_pieces, mut dangling_free_offsets) =
300 mem::take(&mut *self.piece_caches.write().await).reuse();
301
302 debug!("Collecting pieces that were in the cache before");
303
304 if let Some(metrics) = &self.metrics {
305 metrics.piece_cache_capacity_total.set(0);
306 metrics.piece_cache_capacity_used.set(0);
307 }
308
309 let peer_id = self.peer_id;
310
311 let piece_caches_number = new_piece_caches.len();
313 let maybe_caches_futures = new_piece_caches
314 .into_iter()
315 .enumerate()
316 .filter_map(|(cache_index, new_cache)| {
317 let total_capacity = new_cache.max_num_elements();
318 let mut backend = CacheBackend::new(new_cache, total_capacity);
319 let Ok(cache_index) = CacheIndex::try_from(cache_index) else {
320 warn!(
321 ?piece_caches_number,
322 "Too many piece caches provided, {cache_index} cache will be ignored",
323 );
324 return None;
325 };
326
327 if let Some(metrics) = &self.metrics {
328 metrics
329 .piece_cache_capacity_total
330 .inc_by(i64::from(total_capacity));
331 }
332
333 let init_fut = async move {
334 let used_capacity = &mut backend.used_capacity;
335
336 let mut maybe_contents = match backend.backend.contents().await {
340 Ok(contents) => Some(contents),
341 Err(error) => {
342 warn!(%error, "Failed to get cache contents");
343
344 None
345 }
346 };
347
348 let mut cache_stored_pieces = HashMap::new();
349 let mut cache_free_offsets = Vec::new();
350
351 let Some(mut contents) = maybe_contents.take() else {
352 drop(maybe_contents);
353
354 return CacheState {
355 cache_stored_pieces,
356 cache_free_offsets,
357 backend,
358 };
359 };
360
361 while let Some(maybe_element_details) = contents.next().await {
362 let (piece_offset, maybe_piece_index) = match maybe_element_details {
363 Ok(element_details) => element_details,
364 Err(error) => {
365 warn!(%error, "Failed to get cache contents element details");
366 break;
367 }
368 };
369 let offset = FarmerCacheOffset::new(cache_index, piece_offset);
370 match maybe_piece_index {
371 Some(piece_index) => {
372 *used_capacity = piece_offset.0 + 1;
373 let record_key = RecordKey::from(piece_index.to_multihash());
374 let key = KeyWithDistance::new_with_record_key(peer_id, record_key);
375 cache_stored_pieces.insert(key, offset);
376 }
377 None => {
378 cache_free_offsets.push(offset);
381 }
382 }
383
384 yield_now().await;
386 }
387
388 drop(maybe_contents);
389 drop(contents);
390
391 CacheState {
392 cache_stored_pieces,
393 cache_free_offsets,
394 backend,
395 }
396 };
397
398 Some(run_future_in_dedicated_thread(
399 move || init_fut.instrument(info_span!("", %cache_index)),
400 format!("piece-cache.{cache_index}"),
401 ))
402 })
403 .collect::<Result<Vec<_>, _>>();
404
405 let caches_futures = match maybe_caches_futures {
406 Ok(caches_futures) => caches_futures,
407 Err(error) => {
408 error!(%error, "Failed to spawn piece cache reading thread");
409
410 return;
411 }
412 };
413
414 let mut backends = Vec::with_capacity(caches_futures.len());
415 let mut caches_futures = caches_futures.into_iter().collect::<FuturesOrdered<_>>();
416
417 while let Some(maybe_cache) = caches_futures.next().await {
418 match maybe_cache {
419 Ok(cache) => {
420 let backend = cache.backend;
421 for (key, cache_offset) in cache.cache_stored_pieces {
422 if let Some(old_cache_offset) = stored_pieces.insert(key, cache_offset) {
423 dangling_free_offsets.push_front(old_cache_offset);
424 }
425 }
426 dangling_free_offsets.extend(
427 cache.cache_free_offsets.into_iter().filter(|free_offset| {
428 free_offset.piece_offset.0 < backend.used_capacity
429 }),
430 );
431 backends.push(backend);
432 }
433 Err(_cancelled) => {
434 error!("Piece cache reading thread panicked");
435
436 return;
437 }
438 }
439 }
440
441 let mut caches = PieceCachesState::new(stored_pieces, dangling_free_offsets, backends);
442
443 info!("Synchronizing piece cache");
444
445 let last_segment_index = loop {
446 match self.node_client.farmer_app_info().await {
447 Ok(farmer_app_info) => {
448 let last_segment_index =
449 farmer_app_info.protocol_info.history_size.segment_index();
450 if !farmer_app_info.syncing || last_segment_index > SegmentIndex::ZERO {
458 break last_segment_index;
459 }
460 }
461 Err(error) => {
462 error!(
463 %error,
464 "Failed to get farmer app info from node, keeping old cache state without \
465 updates"
466 );
467
468 *self.piece_caches.write().await = caches;
470 return;
471 }
472 }
473
474 tokio::time::sleep(INITIAL_SYNC_FARM_INFO_CHECK_INTERVAL).await;
475 };
476
477 debug!(%last_segment_index, "Identified last segment index");
478
479 let segment_indices = Vec::from_iter(SegmentIndex::ZERO..=last_segment_index);
481 let mut piece_indices_to_store = segment_indices
484 .into_par_iter()
485 .flat_map(|segment_index| {
486 segment_index
487 .segment_piece_indexes()
488 .into_par_iter()
489 .map(|piece_index| {
490 (
491 KeyWithDistance::new(self.peer_id, piece_index.to_multihash()),
492 piece_index,
493 )
494 })
495 })
496 .collect::<Vec<_>>();
497
498 piece_indices_to_store.par_sort_unstable_by(|(a_key, _), (b_key, _)| a_key.cmp(b_key));
501
502 let mut piece_indices_to_store = piece_indices_to_store
504 .into_iter()
505 .take(caches.total_capacity())
506 .collect::<HashMap<_, _>>();
507
508 let mut piece_caches_capacity_used = vec![0u32; caches.backends().len()];
509 caches.free_unneeded_stored_pieces(&mut piece_indices_to_store);
513
514 if let Some(metrics) = &self.metrics {
515 for offset in caches.stored_pieces_offsets() {
516 piece_caches_capacity_used[usize::from(offset.cache_index)] += 1;
517 }
518
519 for cache_used in piece_caches_capacity_used {
520 metrics
521 .piece_cache_capacity_used
522 .inc_by(i64::from(cache_used));
523 }
524 }
525
526 self.piece_caches.write().await.clone_from(&caches);
528 let stored_count = caches.stored_pieces_offsets().len();
529
530 debug!(
531 %stored_count,
532 count = %piece_indices_to_store.len(),
533 "Identified piece indices that should be cached",
534 );
535
536 let pieces_to_download_total = piece_indices_to_store.len() + stored_count;
537 let piece_indices_to_store = piece_indices_to_store
538 .into_values()
539 .collect::<Vec<_>>()
540 .chunks(SYNC_BATCH_SIZE)
544 .map(<[PieceIndex]>::to_vec)
545 .collect::<Vec<_>>();
546
547 let downloaded_pieces_count = AtomicUsize::new(stored_count);
548 let caches = Mutex::new(caches);
549 self.handlers.progress.call_simple(&0.0);
550 let batch_count = piece_indices_to_store.len();
551 let piece_indices_to_store = piece_indices_to_store.into_iter().enumerate();
552
553 let downloading_semaphore = &Semaphore::new(SYNC_BATCH_SIZE * SYNC_CONCURRENT_BATCHES);
554 let ignored_cache_indices = &RwLock::new(HashSet::new());
555
556 let downloading_pieces_stream =
557 stream::iter(piece_indices_to_store.map(|(batch, piece_indices)| {
558 let downloaded_pieces_count = &downloaded_pieces_count;
559 let caches = &caches;
560 let num_pieces = piece_indices.len();
561
562 trace!(
563 %num_pieces,
564 %batch,
565 %batch_count,
566 first_piece_index = ?piece_indices.first().expect("chunks are never empty"),
567 last_piece_index = ?piece_indices.last().expect("chunks are never empty"),
568 downloaded_pieces_count = %downloaded_pieces_count.load(Ordering::Relaxed),
569 %pieces_to_download_total,
570 available_permits = %downloading_semaphore.available_permits(),
571 "Started piece cache sync batch",
572 );
573
574 async move {
575 let mut permit = downloading_semaphore
576 .acquire_many(SYNC_BATCH_SIZE as u32)
577 .await
578 .expect("Semaphore is never closed; qed");
579 debug!(%batch, %num_pieces, "Downloading pieces");
580
581 let pieces_stream = match piece_getter.get_pieces(piece_indices).await {
582 Ok(pieces_stream) => pieces_stream,
583 Err(error) => {
584 error!(
585 %error,
586 "Failed to get pieces from piece getter"
587 );
588 return;
589 }
590 };
591 let mut pieces_stream = pieces_stream.enumerate();
592
593 while let Some((index, (piece_index, result))) = pieces_stream.next().await {
594 debug!(%batch, %index, %piece_index, "Downloaded piece");
595 let _permit = permit.split(1);
597
598 let piece = match result {
599 Ok(Some(piece)) => {
600 trace!(%batch, %piece_index, "Downloaded piece successfully");
601 piece
602 }
603 Ok(None) => {
604 debug!(%batch, %piece_index, "Couldn't find piece");
605 continue;
606 }
607 Err(error) => {
608 debug!(
609 %batch,
610 %error,
611 %piece_index,
612 "Failed to get piece for piece cache"
613 );
614 continue;
615 }
616 };
617
618 let (offset, maybe_backend) = {
619 let mut caches = caches.lock();
620
621 let Some(offset) = caches.pop_free_offset() else {
623 error!(
624 %batch,
625 %piece_index,
626 "Failed to store piece in cache, there was no space"
627 );
628 break;
629 };
630
631 (offset, caches.get_backend(offset.cache_index).cloned())
632 };
633
634 let cache_index = offset.cache_index;
635 let piece_offset = offset.piece_offset;
636
637 let skip_write = ignored_cache_indices.read().contains(&cache_index);
638 if skip_write {
639 trace!(
640 %batch,
641 %cache_index,
642 %piece_index,
643 %piece_offset,
644 "Skipping known problematic cache index"
645 );
646 } else {
647 if let Some(backend) = maybe_backend
648 && let Err(error) =
649 backend.write_piece(piece_offset, piece_index, &piece).await
650 {
651 error!(
652 %error,
653 %batch,
654 %cache_index,
655 %piece_index,
656 %piece_offset,
657 "Failed to write piece into cache, ignoring this cache going \
658 forward"
659 );
660 ignored_cache_indices.write().insert(cache_index);
661 continue;
662 }
663
664 let key =
665 KeyWithDistance::new(self.peer_id, piece_index.to_multihash());
666 caches.lock().push_stored_piece(key, offset);
667 }
668
669 let prev_downloaded_pieces_count =
670 downloaded_pieces_count.fetch_add(1, Ordering::Relaxed);
671 if prev_downloaded_pieces_count != pieces_to_download_total {
674 let progress = prev_downloaded_pieces_count as f32
675 / pieces_to_download_total as f32
676 * 100.0;
677 if prev_downloaded_pieces_count
678 .is_multiple_of(INTERMEDIATE_CACHE_UPDATE_INTERVAL)
679 {
680 let mut piece_caches = self.piece_caches.write().await;
681 piece_caches.clone_from(&caches.lock());
682
683 info!(
684 "Piece cache sync {progress:.2}% complete ({} / {})",
685 ByteSize::b(
686 (prev_downloaded_pieces_count * Piece::SIZE) as u64,
687 )
688 .display()
689 .iec(),
690 ByteSize::b((pieces_to_download_total * Piece::SIZE) as u64,)
691 .display()
692 .iec(),
693 );
694 }
695
696 self.handlers.progress.call_simple(&progress);
697 }
698 }
699
700 trace!(
701 %num_pieces,
702 %batch,
703 %batch_count,
704 downloaded_pieces_count = %downloaded_pieces_count.load(Ordering::Relaxed),
705 %pieces_to_download_total,
706 available_permits = %downloading_semaphore.available_permits(),
707 "Finished piece cache sync batch",
708 );
709 }
710 }));
711
712 downloading_pieces_stream
715 .buffer_unordered(SYNC_CONCURRENT_BATCHES * 10)
718 .for_each(|()| async {})
720 .await;
721
722 *self.piece_caches.write().await = caches.into_inner();
723 self.handlers.progress.call_simple(&100.0);
724 *last_segment_index_internal = last_segment_index;
725
726 info!("Finished piece cache synchronization");
727 }
728
729 async fn process_new_segment_index<PG>(
730 &self,
731 piece_getter: &PG,
732 segment_index: SegmentIndex,
733 last_segment_index_internal: &mut SegmentIndex,
734 ) where
735 PG: PieceGetter,
736 {
737 debug!(
738 %last_segment_index_internal,
739 %segment_index,
740 "Starting to process new segment index"
741 );
742
743 let segment_index_range =
744 (*last_segment_index_internal + SegmentIndex::ONE)..=segment_index;
745
746 if segment_index_range.is_empty() {
747 return;
748 }
749
750 debug!(
751 ?segment_index_range,
752 "Downloading potentially useful pieces"
753 );
754
755 let pieces_to_maybe_include = segment_index_range
761 .flat_map(|segment_index| segment_index.segment_piece_indexes())
762 .map(|piece_index| async move {
763 let should_store_in_piece_cache = self
764 .piece_caches
765 .read()
766 .await
767 .should_include_key(self.peer_id, piece_index);
768
769 let key = RecordKey::from(piece_index.to_multihash());
770 let should_store_in_plot_cache =
771 self.plot_caches.should_store(piece_index, &key).await;
772
773 if !(should_store_in_piece_cache || should_store_in_plot_cache) {
774 trace!(%piece_index, "Piece doesn't need to be cached #1");
775
776 return None;
777 }
778
779 let maybe_piece_result =
780 self.node_client
781 .piece(piece_index)
782 .await
783 .inspect_err(|error| {
784 debug!(
785 %error,
786 %segment_index,
787 %piece_index,
788 "Failed to retrieve piece from node right after archiving"
789 );
790 });
791
792 if let Ok(Some(piece)) = maybe_piece_result {
793 return Some((piece_index, piece));
794 }
795
796 match piece_getter.get_piece(piece_index).await {
797 Ok(Some(piece)) => Some((piece_index, piece)),
798 Ok(None) => {
799 warn!(
800 %segment_index,
801 %piece_index,
802 "Failed to retrieve piece right after archiving"
803 );
804
805 None
806 }
807 Err(error) => {
808 warn!(
809 %error,
810 %segment_index,
811 %piece_index,
812 "Failed to retrieve piece right after archiving"
813 );
814
815 None
816 }
817 }
818 })
819 .collect::<FuturesUnordered<_>>()
820 .filter_map(|maybe_piece| async move { maybe_piece })
821 .collect::<Vec<_>>()
822 .await;
823
824 debug!(%segment_index, "Downloaded potentially useful pieces");
825
826 for (piece_index, piece) in pieces_to_maybe_include {
829 if !self
830 .plot_caches
831 .store_additional_piece(piece_index, &piece)
832 .await
833 {
834 trace!(%piece_index, "Piece could not be cached in plot cache");
835 }
836
837 if !self
838 .piece_caches
839 .read()
840 .await
841 .should_include_key(self.peer_id, piece_index)
842 {
843 trace!(%piece_index, "Piece doesn't need to be cached #2");
844
845 continue;
846 }
847
848 trace!(%piece_index, "Piece needs to be cached #1");
849
850 self.persist_piece_in_cache(piece_index, piece).await;
851 }
852
853 *last_segment_index_internal = segment_index;
854
855 debug!(%segment_index, "Finished processing new segment indices");
856 }
857
858 async fn keep_up_after_initial_sync<PG>(
859 &self,
860 piece_getter: &PG,
861 last_segment_index_internal: &mut SegmentIndex,
862 ) where
863 PG: PieceGetter,
864 {
865 let last_segment_index = match self.node_client.farmer_app_info().await {
866 Ok(farmer_app_info) => farmer_app_info.protocol_info.history_size.segment_index(),
867 Err(error) => {
868 error!(
869 %error,
870 "Failed to get farmer app info from node, keeping old cache state without \
871 updates"
872 );
873 return;
874 }
875 };
876
877 if last_segment_index <= *last_segment_index_internal {
878 return;
879 }
880
881 info!(
882 "Syncing piece cache to the latest history size, this may pause block production if \
883 takes too long"
884 );
885
886 let piece_indices = (*last_segment_index_internal..=last_segment_index)
888 .flat_map(|segment_index| segment_index.segment_piece_indexes());
889
890 for piece_index in piece_indices {
892 if !self
893 .piece_caches
894 .read()
895 .await
896 .should_include_key(self.peer_id, piece_index)
897 {
898 trace!(%piece_index, "Piece doesn't need to be cached #3");
899
900 continue;
901 }
902
903 trace!(%piece_index, "Piece needs to be cached #2");
904
905 let result = piece_getter.get_piece(piece_index).await;
906
907 let piece = match result {
908 Ok(Some(piece)) => piece,
909 Ok(None) => {
910 debug!(%piece_index, "Couldn't find piece");
911 continue;
912 }
913 Err(error) => {
914 debug!(
915 %error,
916 %piece_index,
917 "Failed to get piece for piece cache"
918 );
919 continue;
920 }
921 };
922
923 self.persist_piece_in_cache(piece_index, piece).await;
924 }
925
926 info!("Finished syncing piece cache to the latest history size");
927
928 *last_segment_index_internal = last_segment_index;
929 }
930
931 async fn persist_piece_in_cache(&self, piece_index: PieceIndex, piece: Piece) {
934 let key = KeyWithDistance::new(self.peer_id, piece_index.to_multihash());
935 let mut caches = self.piece_caches.write().await;
936 if let Some((old_key, offset)) = caches.should_replace(&key) {
937 let cache_index = offset.cache_index;
939 let piece_offset = offset.piece_offset;
940 let Some(backend) = caches.get_backend(cache_index) else {
941 warn!(
943 %cache_index,
944 %piece_index,
945 "Should have a cached backend, but it didn't exist, this is an \
946 implementation bug"
947 );
948 return;
949 };
950 if let Err(error) = backend.write_piece(piece_offset, piece_index, &piece).await {
951 error!(
952 %error,
953 %cache_index,
954 %piece_index,
955 %piece_offset,
956 "Failed to write piece into cache"
957 );
958 } else {
959 let old_piece_index = decode_piece_index_from_record_key(old_key.record_key());
960 trace!(
961 %cache_index,
962 %old_piece_index,
963 %piece_index,
964 %piece_offset,
965 "Successfully replaced old cached piece"
966 );
967 caches.push_stored_piece(key, offset);
968 }
969 } else {
970 let Some(offset) = caches.pop_free_offset() else {
973 warn!(
974 %piece_index,
975 "Should have inserted piece into cache, but it didn't happen, this is an \
976 implementation bug"
977 );
978 return;
979 };
980 let cache_index = offset.cache_index;
981 let piece_offset = offset.piece_offset;
982 let Some(backend) = caches.get_backend(cache_index) else {
983 warn!(
985 %cache_index,
986 %piece_index,
987 "Should have a cached backend, but it didn't exist, this is an \
988 implementation bug"
989 );
990 return;
991 };
992
993 if let Err(error) = backend.write_piece(piece_offset, piece_index, &piece).await {
994 error!(
995 %error,
996 %cache_index,
997 %piece_index,
998 %piece_offset,
999 "Failed to write piece into cache"
1000 );
1001 } else {
1002 trace!(
1003 %cache_index,
1004 %piece_index,
1005 %piece_offset,
1006 "Successfully stored piece in cache"
1007 );
1008 if let Some(metrics) = &self.metrics {
1009 metrics.piece_cache_capacity_used.inc();
1010 }
1011 caches.push_stored_piece(key, offset);
1012 }
1013 }
1014 }
1015}
1016
1017#[derive(Debug)]
1018struct PlotCaches {
1019 caches: AsyncRwLock<Vec<StdArc<dyn PlotCache>>>,
1021 next_plot_cache: AtomicUsize,
1023}
1024
1025impl PlotCaches {
1026 async fn should_store(&self, piece_index: PieceIndex, key: &RecordKey) -> bool {
1032 for (cache_index, cache) in self.caches.read().await.iter().enumerate() {
1033 match cache.is_piece_maybe_stored(key).await {
1034 Ok(MaybePieceStoredResult::No) => {
1035 }
1037 Ok(MaybePieceStoredResult::Vacant) => {
1038 return true;
1039 }
1040 Ok(MaybePieceStoredResult::Yes) => {
1041 return false;
1043 }
1044 Err(error) => {
1045 warn!(
1046 %cache_index,
1047 %piece_index,
1048 %error,
1049 "Failed to check piece stored in cache"
1050 );
1051 }
1052 }
1053 }
1054
1055 false
1056 }
1057
1058 async fn store_additional_piece(&self, piece_index: PieceIndex, piece: &Piece) -> bool {
1062 let plot_caches = self.caches.read().await;
1063 let plot_caches_len = plot_caches.len();
1064
1065 for _ in 0..plot_caches_len {
1067 let plot_cache_index =
1068 self.next_plot_cache.fetch_add(1, Ordering::Relaxed) % plot_caches_len;
1069
1070 match plot_caches[plot_cache_index]
1071 .try_store_piece(piece_index, piece)
1072 .await
1073 {
1074 Ok(true) => {
1075 return true;
1076 }
1077 Ok(false) => {
1078 }
1080 Err(error) => {
1081 error!(
1082 %error,
1083 %piece_index,
1084 %plot_cache_index,
1085 "Failed to store additional piece in cache"
1086 );
1087 }
1088 }
1089 }
1090
1091 false
1092 }
1093}
1094
1095#[derive(Debug, Clone)]
1106pub struct FarmerCache {
1107 peer_id: PeerId,
1108 piece_caches: Arc<AsyncRwLock<PieceCachesState>>,
1110 plot_caches: Arc<PlotCaches>,
1112 handlers: Arc<Handlers>,
1113 worker_sender: mpsc::Sender<WorkerCommand>,
1115 metrics: Option<Arc<FarmerCacheMetrics>>,
1116}
1117
1118impl FarmerCache {
1119 pub fn new<NC>(
1124 node_client: NC,
1125 peer_id: PeerId,
1126 registry: Option<&mut Registry>,
1127 ) -> (Self, FarmerCacheWorker<NC>)
1128 where
1129 NC: NodeClient,
1130 {
1131 let caches = Arc::default();
1132 let (worker_sender, worker_receiver) = mpsc::channel(WORKER_CHANNEL_CAPACITY);
1133 let handlers = Arc::new(Handlers::default());
1134
1135 let plot_caches = Arc::new(PlotCaches {
1136 caches: AsyncRwLock::default(),
1137 next_plot_cache: AtomicUsize::new(0),
1138 });
1139 let metrics = registry.map(|registry| Arc::new(FarmerCacheMetrics::new(registry)));
1140
1141 let instance = Self {
1142 peer_id,
1143 piece_caches: Arc::clone(&caches),
1144 plot_caches: Arc::clone(&plot_caches),
1145 handlers: Arc::clone(&handlers),
1146 worker_sender,
1147 metrics: metrics.clone(),
1148 };
1149 let worker = FarmerCacheWorker {
1150 peer_id,
1151 node_client,
1152 piece_caches: caches,
1153 plot_caches,
1154 handlers,
1155 worker_receiver: Some(worker_receiver),
1156 metrics,
1157 };
1158
1159 (instance, worker)
1160 }
1161
1162 pub async fn get_piece<Key>(&self, key: Key) -> Option<Piece>
1164 where
1165 RecordKey: From<Key>,
1166 {
1167 let key = RecordKey::from(key);
1168 let maybe_piece_found = {
1169 let key = KeyWithDistance::new_with_record_key(self.peer_id, key.clone());
1170 let caches = self.piece_caches.read().await;
1171
1172 caches.get_stored_piece(&key).and_then(|offset| {
1173 let cache_index = offset.cache_index;
1174 let piece_offset = offset.piece_offset;
1175 Some((
1176 piece_offset,
1177 cache_index,
1178 caches.get_backend(cache_index)?.clone(),
1179 ))
1180 })
1181 };
1182
1183 if let Some((piece_offset, cache_index, backend)) = maybe_piece_found {
1184 match backend.read_piece(piece_offset).await {
1185 Ok(maybe_piece) => {
1186 return if let Some((_piece_index, piece)) = maybe_piece {
1187 if let Some(metrics) = &self.metrics {
1188 metrics.cache_get_hit.inc();
1189 }
1190 Some(piece)
1191 } else {
1192 error!(
1193 %cache_index,
1194 %piece_offset,
1195 ?key,
1196 "Piece was expected to be in cache, but wasn't found there"
1197 );
1198 if let Some(metrics) = &self.metrics {
1199 metrics.cache_get_error.inc();
1200 }
1201 None
1202 };
1203 }
1204 Err(error) => {
1205 error!(
1206 %error,
1207 %cache_index,
1208 ?key,
1209 %piece_offset,
1210 "Error while reading piece from cache"
1211 );
1212
1213 if let Err(error) = self
1214 .worker_sender
1215 .clone()
1216 .send(WorkerCommand::ForgetKey { key })
1217 .await
1218 {
1219 trace!(%error, "Failed to send ForgetKey command to worker");
1220 }
1221
1222 if let Some(metrics) = &self.metrics {
1223 metrics.cache_get_error.inc();
1224 }
1225 return None;
1226 }
1227 }
1228 }
1229
1230 for cache in self.plot_caches.caches.read().await.iter() {
1231 if let Ok(Some(piece)) = cache.read_piece(&key).await {
1232 if let Some(metrics) = &self.metrics {
1233 metrics.cache_get_hit.inc();
1234 }
1235 return Some(piece);
1236 }
1237 }
1238
1239 if let Some(metrics) = &self.metrics {
1240 metrics.cache_get_miss.inc();
1241 }
1242 None
1243 }
1244
1245 pub async fn get_pieces<'a, PieceIndices>(
1249 &'a self,
1250 piece_indices: PieceIndices,
1251 ) -> impl Stream<Item = (PieceIndex, Option<Piece>)> + Send + Unpin + 'a
1252 where
1253 PieceIndices: IntoIterator<Item = PieceIndex, IntoIter: Send + 'a> + Send + 'a,
1254 {
1255 let mut pieces_to_get_from_plot_cache = Vec::new();
1256
1257 let pieces_to_read_from_piece_cache = {
1258 let caches = self.piece_caches.read().await;
1259 let mut pieces_to_read_from_piece_cache =
1261 HashMap::<CacheIndex, (CacheBackend, HashMap<_, _>)>::new();
1262
1263 for piece_index in piece_indices {
1264 let key = RecordKey::from(piece_index.to_multihash());
1265
1266 let Some(offset) = caches.get_stored_piece(&KeyWithDistance::new_with_record_key(
1267 self.peer_id,
1268 key.clone(),
1269 )) else {
1270 pieces_to_get_from_plot_cache.push((piece_index, key));
1271 continue;
1272 };
1273
1274 let cache_index = offset.cache_index;
1275 let piece_offset = offset.piece_offset;
1276
1277 match pieces_to_read_from_piece_cache.entry(cache_index) {
1278 Entry::Occupied(mut entry) => {
1279 let (_backend, pieces) = entry.get_mut();
1280 pieces.insert(piece_offset, (piece_index, key));
1281 }
1282 Entry::Vacant(entry) => {
1283 let backend = if let Some(backend) = caches.get_backend(cache_index) {
1284 backend.clone()
1285 } else {
1286 pieces_to_get_from_plot_cache.push((piece_index, key));
1287 continue;
1288 };
1289 entry
1290 .insert((backend, HashMap::from([(piece_offset, (piece_index, key))])));
1291 }
1292 }
1293 }
1294
1295 pieces_to_read_from_piece_cache
1296 };
1297
1298 let (tx, mut rx) = mpsc::unbounded();
1299
1300 let fut = async move {
1301 let tx = &tx;
1302
1303 let mut reading_from_piece_cache = pieces_to_read_from_piece_cache
1304 .into_iter()
1305 .map(|(cache_index, (backend, mut pieces_to_get))| async move {
1306 let mut pieces_stream = match backend
1307 .read_pieces(Box::new(
1308 pieces_to_get
1309 .keys()
1310 .copied()
1311 .collect::<Vec<_>>()
1312 .into_iter(),
1313 ))
1314 .await
1315 {
1316 Ok(pieces_stream) => pieces_stream,
1317 Err(error) => {
1318 error!(
1319 %error,
1320 %cache_index,
1321 "Error while reading pieces from cache"
1322 );
1323
1324 if let Some(metrics) = &self.metrics {
1325 metrics.cache_get_error.inc_by(pieces_to_get.len() as u64);
1326 }
1327 for (piece_index, _key) in pieces_to_get.into_values() {
1328 tx.unbounded_send((piece_index, None)).expect(
1329 "This future isn't polled after receiver is dropped; qed",
1330 );
1331 }
1332 return;
1333 }
1334 };
1335
1336 while let Some(maybe_piece) = pieces_stream.next().await {
1337 let result = match maybe_piece {
1338 Ok((piece_offset, Some((piece_index, piece)))) => {
1339 pieces_to_get.remove(&piece_offset);
1340
1341 if let Some(metrics) = &self.metrics {
1342 metrics.cache_get_hit.inc();
1343 }
1344 (piece_index, Some(piece))
1345 }
1346 Ok((piece_offset, None)) => {
1347 let Some((piece_index, key)) = pieces_to_get.remove(&piece_offset)
1348 else {
1349 debug!(
1350 %cache_index,
1351 %piece_offset,
1352 "Received piece offset that was not expected"
1353 );
1354 continue;
1355 };
1356
1357 error!(
1358 %cache_index,
1359 %piece_index,
1360 %piece_offset,
1361 ?key,
1362 "Piece was expected to be in cache, but wasn't found there"
1363 );
1364 if let Some(metrics) = &self.metrics {
1365 metrics.cache_get_error.inc();
1366 }
1367 (piece_index, None)
1368 }
1369 Err(error) => {
1370 error!(
1371 %error,
1372 %cache_index,
1373 "Error while reading piece from cache"
1374 );
1375
1376 if let Some(metrics) = &self.metrics {
1377 metrics.cache_get_error.inc();
1378 }
1379 continue;
1380 }
1381 };
1382
1383 tx.unbounded_send(result)
1384 .expect("This future isn't polled after receiver is dropped; qed");
1385 }
1386
1387 if pieces_to_get.is_empty() {
1388 return;
1389 }
1390
1391 if let Some(metrics) = &self.metrics {
1392 metrics.cache_get_error.inc_by(pieces_to_get.len() as u64);
1393 }
1394 for (piece_offset, (piece_index, key)) in pieces_to_get {
1395 error!(
1396 %cache_index,
1397 %piece_index,
1398 %piece_offset,
1399 ?key,
1400 "Piece cache didn't return an entry for offset"
1401 );
1402
1403 tx.unbounded_send((piece_index, None))
1406 .expect("This future isn't polled after receiver is dropped; qed");
1407 }
1408 })
1409 .collect::<FuturesUnordered<_>>();
1410 let reading_from_piece_cache_fut = async move {
1416 while let Some(()) = reading_from_piece_cache.next().await {
1417 }
1419 };
1420
1421 let reading_from_plot_cache_fut = async {
1422 if pieces_to_get_from_plot_cache.is_empty() {
1423 return;
1424 }
1425
1426 for cache in self.plot_caches.caches.read().await.iter() {
1427 for offset in (0..pieces_to_get_from_plot_cache.len()).rev() {
1430 let (piece_index, key) = &pieces_to_get_from_plot_cache[offset];
1431
1432 if let Ok(Some(piece)) = cache.read_piece(key).await {
1433 if let Some(metrics) = &self.metrics {
1434 metrics.cache_get_hit.inc();
1435 }
1436 tx.unbounded_send((*piece_index, Some(piece)))
1437 .expect("This future isn't polled after receiver is dropped; qed");
1438
1439 pieces_to_get_from_plot_cache.swap_remove(offset);
1442 }
1443 }
1444
1445 if pieces_to_get_from_plot_cache.is_empty() {
1446 return;
1447 }
1448 }
1449
1450 if let Some(metrics) = &self.metrics {
1451 metrics
1452 .cache_get_miss
1453 .inc_by(pieces_to_get_from_plot_cache.len() as u64);
1454 }
1455
1456 for (piece_index, _key) in pieces_to_get_from_plot_cache {
1457 tx.unbounded_send((piece_index, None))
1458 .expect("This future isn't polled after receiver is dropped; qed");
1459 }
1460 };
1461
1462 join!(reading_from_piece_cache_fut, reading_from_plot_cache_fut).await
1463 };
1464 let mut fut = Box::pin(fut.fuse());
1465
1466 stream::poll_fn(move |cx| {
1468 if !fut.is_terminated() {
1469 let _: Poll<_> = fut.poll_unpin(cx);
1471 }
1472
1473 if let Poll::Ready(maybe_result) = rx.poll_next_unpin(cx) {
1474 return Poll::Ready(maybe_result);
1475 }
1476
1477 Poll::Pending
1479 })
1480 }
1481
1482 pub async fn has_pieces(&self, mut piece_indices: Vec<PieceIndex>) -> Vec<PieceIndex> {
1484 let mut pieces_to_find = HashMap::<PieceIndex, RecordKey>::from_iter(
1485 piece_indices
1486 .iter()
1487 .map(|piece_index| (*piece_index, RecordKey::from(piece_index.to_multihash()))),
1488 );
1489
1490 {
1492 let piece_caches = self.piece_caches.read().await;
1493 pieces_to_find.retain(|_piece_index, key| {
1494 let distance_key = KeyWithDistance::new(self.peer_id, key.clone());
1495 !piece_caches.contains_stored_piece(&distance_key)
1496 });
1497 }
1498
1499 if pieces_to_find.is_empty() {
1501 return piece_indices;
1502 }
1503
1504 if let Some(plot_caches) = self.plot_caches.caches.try_read() {
1506 let plot_caches = &plot_caches;
1507 let not_found = pieces_to_find
1508 .into_iter()
1509 .map(|(piece_index, key)| async move {
1510 let key = &key;
1511
1512 let found = plot_caches
1513 .iter()
1514 .map(|plot_cache| async {
1515 matches!(
1516 plot_cache.is_piece_maybe_stored(key).await,
1517 Ok(MaybePieceStoredResult::Yes)
1518 )
1519 })
1520 .collect::<FuturesUnordered<_>>()
1521 .any(|found| async move { found })
1522 .await;
1523
1524 if found { None } else { Some(piece_index) }
1525 })
1526 .collect::<FuturesUnordered<_>>()
1527 .filter_map(|maybe_piece_index| async move { maybe_piece_index })
1528 .collect::<HashSet<_>>()
1529 .await;
1530 piece_indices.retain(|piece_index| !not_found.contains(piece_index));
1531 }
1532 piece_indices
1533 }
1534
1535 pub async fn find_piece(
1537 &self,
1538 piece_index: PieceIndex,
1539 ) -> Option<(PieceCacheId, PieceCacheOffset)> {
1540 let caches = self.piece_caches.read().await;
1541
1542 self.find_piece_internal(&caches, piece_index)
1543 }
1544
1545 pub async fn find_pieces<PieceIndices>(
1547 &self,
1548 piece_indices: PieceIndices,
1549 ) -> Vec<(PieceIndex, PieceCacheId, PieceCacheOffset)>
1550 where
1551 PieceIndices: IntoIterator<Item = PieceIndex>,
1552 {
1553 let caches = self.piece_caches.read().await;
1554
1555 piece_indices
1556 .into_iter()
1557 .filter_map(|piece_index| {
1558 self.find_piece_internal(&caches, piece_index)
1559 .map(|(cache_id, piece_offset)| (piece_index, cache_id, piece_offset))
1560 })
1561 .collect()
1562 }
1563
1564 fn find_piece_internal(
1565 &self,
1566 caches: &PieceCachesState,
1567 piece_index: PieceIndex,
1568 ) -> Option<(PieceCacheId, PieceCacheOffset)> {
1569 let key = KeyWithDistance::new(self.peer_id, piece_index.to_multihash());
1570
1571 let Some(offset) = caches.get_stored_piece(&key) else {
1572 if let Some(metrics) = &self.metrics {
1573 metrics.cache_find_miss.inc();
1574 }
1575
1576 return None;
1577 };
1578 let piece_offset = offset.piece_offset;
1579
1580 if let Some(backend) = caches.get_backend(offset.cache_index) {
1581 if let Some(metrics) = &self.metrics {
1582 metrics.cache_find_hit.inc();
1583 }
1584 return Some((*backend.id(), piece_offset));
1585 }
1586
1587 if let Some(metrics) = &self.metrics {
1588 metrics.cache_find_miss.inc();
1589 }
1590 None
1591 }
1592
1593 pub async fn maybe_store_additional_piece(
1597 &self,
1598 piece_index: PieceIndex,
1599 piece: &Piece,
1600 ) -> bool {
1601 let key = RecordKey::from(piece_index.to_multihash());
1602
1603 let should_store = self.plot_caches.should_store(piece_index, &key).await;
1604
1605 if !should_store {
1606 return false;
1607 }
1608
1609 self.plot_caches
1610 .store_additional_piece(piece_index, piece)
1611 .await
1612 }
1613
1614 pub async fn replace_backing_caches(
1616 &self,
1617 new_piece_caches: Vec<StdArc<dyn PieceCache>>,
1618 new_plot_caches: Vec<StdArc<dyn PlotCache>>,
1619 ) {
1620 if let Err(error) = self
1621 .worker_sender
1622 .clone()
1623 .send(WorkerCommand::ReplaceBackingCaches { new_piece_caches })
1624 .await
1625 {
1626 warn!(%error, "Failed to replace backing caches, worker exited");
1627 }
1628
1629 *self.plot_caches.caches.write().await = new_plot_caches;
1630 }
1631
1632 pub fn on_sync_progress(&self, callback: HandlerFn<f32>) -> HandlerId {
1634 self.handlers.progress.add(callback)
1635 }
1636}
1637
1638#[derive(Debug, Clone)]
1640pub struct FarmerCaches {
1641 caches: Arc<[FarmerCache]>,
1642}
1643
1644impl From<Arc<[FarmerCache]>> for FarmerCaches {
1645 fn from(caches: Arc<[FarmerCache]>) -> Self {
1646 Self { caches }
1647 }
1648}
1649
1650impl From<FarmerCache> for FarmerCaches {
1651 fn from(cache: FarmerCache) -> Self {
1652 Self {
1653 caches: Arc::from_iter([cache]),
1654 }
1655 }
1656}
1657
1658impl FarmerCaches {
1659 pub async fn get_piece<Key>(&self, key: Key) -> Option<Piece>
1661 where
1662 RecordKey: From<Key>,
1663 {
1664 let farmer_cache = self.caches.choose(&mut rand::rng())?;
1665 farmer_cache.get_piece(key).await
1666 }
1667
1668 pub async fn get_pieces<'a, PieceIndices>(
1672 &'a self,
1673 piece_indices: PieceIndices,
1674 ) -> impl Stream<Item = (PieceIndex, Option<Piece>)> + Send + Unpin + 'a
1675 where
1676 PieceIndices: IntoIterator<Item = PieceIndex, IntoIter: Send + 'a> + Send + 'a,
1677 {
1678 let Some(farmer_cache) = self.caches.choose(&mut rand::rng()) else {
1679 return Either::Left(stream::iter(
1680 piece_indices
1681 .into_iter()
1682 .map(|piece_index| (piece_index, None)),
1683 ));
1684 };
1685
1686 Either::Right(farmer_cache.get_pieces(piece_indices).await)
1687 }
1688
1689 pub async fn has_pieces(&self, piece_indices: Vec<PieceIndex>) -> Vec<PieceIndex> {
1691 let Some(farmer_cache) = self.caches.choose(&mut rand::rng()) else {
1692 return Vec::new();
1693 };
1694
1695 farmer_cache.has_pieces(piece_indices).await
1696 }
1697
1698 pub async fn maybe_store_additional_piece(
1702 &self,
1703 piece_index: PieceIndex,
1704 piece: &Piece,
1705 ) -> bool {
1706 self.caches
1708 .iter()
1709 .map(|farmer_cache| farmer_cache.maybe_store_additional_piece(piece_index, piece))
1710 .collect::<FuturesUnordered<_>>()
1711 .fold::<bool, _, _>(false, |acc, stored| async move { acc || stored })
1712 .await
1713 }
1714}
1715
1716fn decode_piece_index_from_record_key(key: &RecordKey) -> PieceIndex {
1718 let len = key.as_ref().len();
1719 let s = len - PieceIndex::SIZE;
1720
1721 let mut piece_index_bytes = [0u8; PieceIndex::SIZE];
1722 piece_index_bytes.copy_from_slice(&key.as_ref()[s..]);
1723
1724 PieceIndex::from_bytes(piece_index_bytes)
1725}