Skip to content

Commit a5e4e17

Browse files
Merge pull request #1070 from subspace/refactor-piece-receiving
DSN. Refactor piece receiving.
2 parents 6dd74f6 + 559ca3b commit a5e4e17

File tree

8 files changed

+255
-184
lines changed

8 files changed

+255
-184
lines changed

Cargo.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

crates/subspace-farmer-components/src/plotting.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@ use subspace_core_primitives::{
1515
use thiserror::Error;
1616
use tracing::{debug, info};
1717

18+
/// Duplicate trait for the subspace_networking::PieceReceiver. The goal of this trait is
19+
/// simplifying dependency graph.
1820
#[async_trait]
1921
pub trait PieceReceiver {
2022
async fn get_piece(

crates/subspace-farmer/src/single_disk_plot.rs

Lines changed: 36 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,10 @@ use crate::reward_signing::reward_signing;
99
use crate::single_disk_plot::farming::audit_sector;
1010
use crate::single_disk_plot::piece_publisher::PieceSectorPublisher;
1111
use crate::single_disk_plot::piece_reader::{read_piece, PieceReader, ReadPieceRequest};
12+
use crate::single_disk_plot::piece_receiver::RecordsRootPieceValidator;
1213
use crate::single_disk_plot::plotting::{plot_sector, PlottedSector};
1314
use crate::utils::JoinOnDrop;
15+
use async_trait::async_trait;
1416
use bytesize::ByteSize;
1517
use derive_more::{Display, From};
1618
use event_listener_primitives::{Bag, HandlerId};
@@ -21,9 +23,9 @@ use lru::LruCache;
2123
use memmap2::{Mmap, MmapMut, MmapOptions};
2224
use parity_scale_codec::{Decode, Encode};
2325
use parking_lot::Mutex;
24-
use piece_receiver::MultiChannelPieceReceiver;
2526
use serde::{Deserialize, Serialize};
2627
use static_assertions::const_assert;
28+
use std::error::Error;
2729
use std::fs::OpenOptions;
2830
use std::future::Future;
2931
use std::io::{Seek, SeekFrom};
@@ -38,11 +40,12 @@ use std_semaphore::{Semaphore, SemaphoreGuard};
3840
use subspace_core_primitives::crypto::kzg::{test_public_parameters, Kzg};
3941
use subspace_core_primitives::sector_codec::SectorCodec;
4042
use subspace_core_primitives::{
41-
PieceIndex, PublicKey, SectorId, SectorIndex, Solution, PIECES_IN_SECTOR, PLOT_SECTOR_SIZE,
43+
Piece, PieceIndex, PublicKey, SectorId, SectorIndex, Solution, PIECES_IN_SECTOR,
44+
PLOT_SECTOR_SIZE,
4245
};
4346
use subspace_farmer_components::file_ext::FileExt;
4447
use subspace_farmer_components::{farming, plotting, SectorMetadata};
45-
use subspace_networking::Node;
48+
use subspace_networking::{Node, PieceProvider};
4649
use subspace_rpc_primitives::{SlotInfo, SolutionResponse};
4750
use thiserror::Error;
4851
use tokio::runtime::Handle;
@@ -420,6 +423,26 @@ struct Handlers {
420423
solution: Handler<SolutionResponse>,
421424
}
422425

426+
/// Adapter struct for the PieceReceiver trait for subspace-networking
427+
/// and subspace-farmer-components crates.
428+
struct PieceReceiverWrapper<PR>(PR);
429+
430+
impl<PR: subspace_networking::PieceReceiver> PieceReceiverWrapper<PR> {
431+
fn new(piece_getter: PR) -> Self {
432+
Self(piece_getter)
433+
}
434+
}
435+
436+
#[async_trait]
437+
impl<PR: subspace_networking::PieceReceiver> plotting::PieceReceiver for PieceReceiverWrapper<PR> {
438+
async fn get_piece(
439+
&self,
440+
piece_index: PieceIndex,
441+
) -> Result<Option<Piece>, Box<dyn Error + Send + Sync + 'static>> {
442+
self.0.get_piece(piece_index).await
443+
}
444+
}
445+
423446
/// Single disk plot abstraction is a container for everything necessary to plot/farm with a single
424447
/// disk plot.
425448
///
@@ -683,14 +706,19 @@ impl SingleDiskPlot {
683706
let records_roots_cache =
684707
Mutex::new(LruCache::new(RECORDS_ROOTS_CACHE_SIZE));
685708

686-
let piece_receiver = MultiChannelPieceReceiver::new(
709+
let piece_receiver = PieceProvider::new(
687710
&dsn_node,
688-
&node_client,
689-
&kzg,
690-
&records_roots_cache,
711+
Some(RecordsRootPieceValidator::new(
712+
&dsn_node,
713+
&node_client,
714+
&kzg,
715+
&records_roots_cache,
716+
)),
691717
&shutting_down,
692718
);
693719

720+
let piece_receiver_wrapper = PieceReceiverWrapper::new(piece_receiver);
721+
694722
// TODO: Concurrency
695723
for (sector_index, sector, sector_metadata) in plot_initial_sector {
696724
trace!(%sector_index, "Preparing to plot sector");
@@ -726,7 +754,7 @@ impl SingleDiskPlot {
726754
let plot_sector_fut = plot_sector(
727755
&public_key,
728756
sector_index,
729-
&piece_receiver,
757+
&piece_receiver_wrapper,
730758
&shutting_down,
731759
&farmer_app_info.protocol_info,
732760
&kzg,
Lines changed: 11 additions & 176 deletions
Original file line numberDiff line numberDiff line change
@@ -1,149 +1,48 @@
11
use crate::NodeClient;
22
use async_trait::async_trait;
3-
use backoff::future::retry;
4-
use backoff::ExponentialBackoff;
5-
use futures::stream::FuturesUnordered;
6-
use futures::StreamExt;
73
use lru::LruCache;
84
use parking_lot::Mutex;
9-
use std::error::Error;
10-
use std::future::Future;
11-
use std::pin::Pin;
12-
use std::sync::atomic::{AtomicBool, Ordering};
13-
use std::time::Duration;
145
use subspace_archiving::archiver::is_piece_valid;
156
use subspace_core_primitives::crypto::kzg::Kzg;
167
use subspace_core_primitives::{
17-
Piece, PieceIndex, PieceIndexHash, RecordsRoot, SegmentIndex, PIECES_IN_SEGMENT, RECORD_SIZE,
8+
Piece, PieceIndex, RecordsRoot, SegmentIndex, PIECES_IN_SEGMENT, RECORD_SIZE,
189
};
19-
use subspace_farmer_components::plotting::PieceReceiver;
20-
use subspace_networking::libp2p::multihash::Multihash;
2110
use subspace_networking::libp2p::PeerId;
22-
use subspace_networking::utils::multihash::MultihashCode;
23-
use subspace_networking::{Node, PieceByHashRequest, PieceByHashResponse, PieceKey, ToMultihash};
24-
use tokio::time::{sleep, timeout};
25-
use tracing::{debug, error, trace, warn};
11+
use subspace_networking::{Node, PieceValidator};
12+
use tracing::error;
2613

27-
/// Defines initial duration between get_piece calls.
28-
const GET_PIECE_INITIAL_INTERVAL: Duration = Duration::from_secs(1);
29-
/// Defines max duration between get_piece calls.
30-
const GET_PIECE_MAX_INTERVAL: Duration = Duration::from_secs(5);
31-
/// Delay for getting piece from cache before resorting to archival storage
32-
const GET_PIECE_ARCHIVAL_STORAGE_DELAY: Duration = Duration::from_secs(2);
33-
/// Max time allocated for getting piece from DSN before attempt is considered to fail
34-
const GET_PIECE_TIMEOUT: Duration = Duration::from_secs(5);
35-
36-
// Defines target storage type for requets.
37-
#[derive(Debug, Copy, Clone)]
38-
enum StorageType {
39-
// L2 piece cache
40-
Cache,
41-
// L1 archival storage for pieces
42-
ArchivalStorage,
43-
}
44-
45-
impl From<StorageType> for MultihashCode {
46-
fn from(storage_type: StorageType) -> Self {
47-
match storage_type {
48-
StorageType::Cache => MultihashCode::PieceIndex,
49-
StorageType::ArchivalStorage => MultihashCode::Sector,
50-
}
51-
}
52-
}
53-
54-
// Temporary struct serving pieces from different providers using configuration arguments.
55-
pub(crate) struct MultiChannelPieceReceiver<'a, NC> {
14+
pub(crate) struct RecordsRootPieceValidator<'a, NC> {
5615
dsn_node: &'a Node,
5716
node_client: &'a NC,
5817
kzg: &'a Kzg,
5918
records_root_cache: &'a Mutex<LruCache<SegmentIndex, RecordsRoot>>,
60-
cancelled: &'a AtomicBool,
6119
}
6220

63-
impl<'a, NC> MultiChannelPieceReceiver<'a, NC>
64-
where
65-
NC: NodeClient,
66-
{
21+
impl<'a, NC> RecordsRootPieceValidator<'a, NC> {
6722
pub(crate) fn new(
6823
dsn_node: &'a Node,
6924
node_client: &'a NC,
7025
kzg: &'a Kzg,
7126
records_root_cache: &'a Mutex<LruCache<SegmentIndex, RecordsRoot>>,
72-
cancelled: &'a AtomicBool,
7327
) -> Self {
7428
Self {
7529
dsn_node,
7630
node_client,
7731
kzg,
7832
records_root_cache,
79-
cancelled,
80-
}
81-
}
82-
83-
fn check_cancellation(&self) -> Result<(), Box<dyn Error + Send + Sync + 'static>> {
84-
if self.cancelled.load(Ordering::Acquire) {
85-
debug!("Getting a piece was cancelled.");
86-
87-
return Err("Getting a piece was cancelled.".into());
88-
}
89-
90-
Ok(())
91-
}
92-
93-
// Get from piece cache (L2) or archival storage (L1)
94-
async fn get_piece_from_storage(
95-
&self,
96-
piece_index: PieceIndex,
97-
storage_type: StorageType,
98-
) -> Option<Piece> {
99-
let piece_index_hash = PieceIndexHash::from_index(piece_index);
100-
let key = piece_index_hash.to_multihash_by_code(storage_type.into());
101-
let piece_key = match storage_type {
102-
StorageType::Cache => PieceKey::Cache(piece_index_hash),
103-
StorageType::ArchivalStorage => PieceKey::ArchivalStorage(piece_index_hash),
104-
};
105-
106-
let get_providers_result = self.dsn_node.get_providers(key).await;
107-
108-
match get_providers_result {
109-
Ok(mut get_providers_stream) => {
110-
while let Some(provider_id) = get_providers_stream.next().await {
111-
trace!(%piece_index, %provider_id, "get_providers returned an item");
112-
113-
let request_result = self
114-
.dsn_node
115-
.send_generic_request(provider_id, PieceByHashRequest { key: piece_key })
116-
.await;
117-
118-
match request_result {
119-
Ok(PieceByHashResponse { piece: Some(piece) }) => {
120-
trace!(%provider_id, %piece_index, ?key, "Piece request succeeded.");
121-
return self
122-
.validate_piece(provider_id, piece_index, key, piece)
123-
.await;
124-
}
125-
Ok(PieceByHashResponse { piece: None }) => {
126-
debug!(%provider_id, %piece_index, ?key, "Piece request returned empty piece.");
127-
}
128-
Err(error) => {
129-
warn!(%provider_id, %piece_index, ?key, ?error, "Piece request failed.");
130-
}
131-
}
132-
}
133-
}
134-
Err(err) => {
135-
warn!(%piece_index,?key, ?err, "get_providers returned an error");
136-
}
13733
}
138-
139-
None
14034
}
35+
}
14136

37+
#[async_trait]
38+
impl<'a, NC> PieceValidator for RecordsRootPieceValidator<'a, NC>
39+
where
40+
NC: NodeClient,
41+
{
14242
async fn validate_piece(
14343
&self,
14444
source_peer_id: PeerId,
14545
piece_index: PieceIndex,
146-
key: Multihash,
14746
piece: Piece,
14847
) -> Option<Piece> {
14948
if source_peer_id != self.dsn_node.id() {
@@ -159,7 +58,6 @@ where
15958
Err(error) => {
16059
error!(
16160
%piece_index,
162-
?key,
16361
?error,
16462
"Failed tor retrieve records root from node"
16563
);
@@ -172,7 +70,6 @@ where
17270
None => {
17371
error!(
17472
%piece_index,
175-
?key,
17673
%segment_index,
17774
"Records root for segment index wasn't found on node"
17875
);
@@ -199,7 +96,6 @@ where
19996
) {
20097
error!(
20198
%piece_index,
202-
?key,
20399
%source_peer_id,
204100
"Received invalid piece from peer"
205101
);
@@ -213,64 +109,3 @@ where
213109
Some(piece)
214110
}
215111
}
216-
217-
#[async_trait]
218-
impl<'a, NC> PieceReceiver for MultiChannelPieceReceiver<'a, NC>
219-
where
220-
NC: NodeClient,
221-
{
222-
async fn get_piece(
223-
&self,
224-
piece_index: PieceIndex,
225-
) -> Result<Option<Piece>, Box<dyn Error + Send + Sync + 'static>> {
226-
trace!(%piece_index, "Piece request.");
227-
228-
let backoff = ExponentialBackoff {
229-
initial_interval: GET_PIECE_INITIAL_INTERVAL,
230-
max_interval: GET_PIECE_MAX_INTERVAL,
231-
// Try until we get a valid piece
232-
max_elapsed_time: None,
233-
..ExponentialBackoff::default()
234-
};
235-
236-
retry(backoff, || async {
237-
self.check_cancellation()
238-
.map_err(backoff::Error::Permanent)?;
239-
240-
// Try to pull pieces in two ways, whichever is faster
241-
let mut piece_attempts = [
242-
timeout(
243-
GET_PIECE_TIMEOUT,
244-
Box::pin(self.get_piece_from_storage(piece_index, StorageType::Cache))
245-
as Pin<Box<dyn Future<Output = _> + Send>>,
246-
),
247-
//TODO: verify "broken pipe" error cause
248-
timeout(
249-
GET_PIECE_TIMEOUT,
250-
Box::pin(async {
251-
// Prefer cache if it can return quickly, otherwise fall back to archival storage
252-
sleep(GET_PIECE_ARCHIVAL_STORAGE_DELAY).await;
253-
self.get_piece_from_storage(piece_index, StorageType::ArchivalStorage)
254-
.await
255-
}) as Pin<Box<dyn Future<Output = _> + Send>>,
256-
),
257-
]
258-
.into_iter()
259-
.collect::<FuturesUnordered<_>>();
260-
261-
while let Some(maybe_piece) = piece_attempts.next().await {
262-
if let Ok(Some(piece)) = maybe_piece {
263-
trace!(%piece_index, "Got piece");
264-
return Ok(Some(piece));
265-
}
266-
}
267-
268-
warn!(%piece_index, "Couldn't get a piece from DSN. Retrying...");
269-
270-
Err(backoff::Error::transient(
271-
"Couldn't get piece from DSN".into(),
272-
))
273-
})
274-
.await
275-
}
276-
}

crates/subspace-networking/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ include = [
1919
actix-web = "4.2.1"
2020
anyhow = "1.0.66"
2121
async-trait = "0.1.58"
22+
backoff = { version = "0.4.0", features = ["futures", "tokio"] }
2223
bytes = "1.2.1"
2324
bytesize = "1.1.0"
2425
chrono = {version = "0.4.21", features = ["clock", "serde", "std",]}

crates/subspace-networking/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,5 +60,6 @@ pub use request_handlers::pieces_by_range::{
6060
};
6161
pub use utils::deconstruct_record_key;
6262
pub use utils::multihash::ToMultihash;
63+
pub use utils::piece_receiver::{PieceProvider, PieceReceiver, PieceValidator};
6364
pub use utils::prometheus::start_prometheus_metrics_server;
6465
pub use utils::record_binary_heap::RecordBinaryHeap;

crates/subspace-networking/src/utils.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
pub mod multihash;
2+
pub mod piece_receiver;
23
pub(crate) mod prometheus;
34
pub(crate) mod record_binary_heap;
45
#[cfg(test)]

0 commit comments

Comments
 (0)