Skip to content

Commit f9b1769

Browse files
authored
Merge pull request #3334 from autonomys/gateway-string-or-int
Update gateway HTTP server object mapping format
2 parents 0992ef6 + e88bb14 commit f9b1769

File tree

9 files changed

+299
-322
lines changed

9 files changed

+299
-322
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/sc-consensus-subspace-rpc/src/lib.rs

Lines changed: 2 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -55,11 +55,11 @@ use std::sync::{Arc, Weak};
5555
use std::time::Duration;
5656
use subspace_archiving::archiver::NewArchivedSegment;
5757
use subspace_core_primitives::hashes::Blake3Hash;
58-
use subspace_core_primitives::objects::GlobalObjectMapping;
58+
use subspace_core_primitives::objects::{GlobalObjectMapping, ObjectMappingResponse};
5959
use subspace_core_primitives::pieces::{Piece, PieceIndex};
6060
use subspace_core_primitives::segments::{HistorySize, SegmentHeader, SegmentIndex};
6161
use subspace_core_primitives::solutions::Solution;
62-
use subspace_core_primitives::{BlockHash, BlockNumber, PublicKey, SlotNumber};
62+
use subspace_core_primitives::{BlockHash, PublicKey, SlotNumber};
6363
use subspace_erasure_coding::ErasureCoding;
6464
use subspace_farmer_components::FarmerProtocolInfo;
6565
use subspace_kzg::Kzg;
@@ -223,19 +223,6 @@ impl CachedArchivedSegment {
223223
}
224224
}
225225

226-
/// Response to object mapping subscription, including a block height.
227-
/// Large responses are batched, so the block height can be repeated in different responses.
228-
#[derive(Debug, Clone, PartialEq, Eq, Ord, PartialOrd, serde::Serialize, serde::Deserialize)]
229-
#[serde(rename_all = "camelCase")]
230-
pub struct ObjectMappingResponse {
231-
/// The block number that the object mapping is from.
232-
pub block_number: BlockNumber,
233-
234-
/// The object mappings.
235-
#[serde(flatten)]
236-
pub objects: GlobalObjectMapping,
237-
}
238-
239226
/// Subspace RPC configuration
240227
pub struct SubspaceRpcConfig<Client, SO, AS>
241228
where

crates/subspace-core-primitives/src/objects.rs

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ extern crate alloc;
2424

2525
use crate::hashes::Blake3Hash;
2626
use crate::pieces::PieceIndex;
27+
use crate::BlockNumber;
2728
#[cfg(not(feature = "std"))]
2829
use alloc::vec::Vec;
2930
use core::default::Default;
@@ -172,3 +173,17 @@ impl GlobalObjectMapping {
172173
}
173174
}
174175
}
176+
177+
/// Response to object mapping subscription, including a block height.
178+
/// Large responses are batched, so the block height can be repeated in different responses.
179+
#[derive(Debug, Clone, PartialEq, Eq, Ord, PartialOrd)]
180+
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
181+
#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
182+
pub struct ObjectMappingResponse {
183+
/// The block number that the object mapping is from.
184+
pub block_number: BlockNumber,
185+
186+
/// The object mappings.
187+
#[cfg_attr(feature = "serde", serde(flatten))]
188+
pub objects: GlobalObjectMapping,
189+
}

crates/subspace-gateway-rpc/src/lib.rs

Lines changed: 3 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,10 @@ use jsonrpsee::proc_macros::rpc;
55
use jsonrpsee::types::{ErrorObject, ErrorObjectOwned};
66
use std::fmt;
77
use std::ops::{Deref, DerefMut};
8-
use subspace_core_primitives::hashes::{blake3_hash, Blake3Hash};
98
use subspace_core_primitives::objects::GlobalObjectMapping;
109
use subspace_data_retrieval::object_fetcher::{self, ObjectFetcher};
1110
use subspace_data_retrieval::piece_getter::PieceGetter;
12-
use tracing::{debug, error, trace};
11+
use tracing::{debug, error};
1312

1413
const SUBSPACE_ERROR: i32 = 9000;
1514

@@ -33,17 +32,6 @@ pub enum Error {
3332
/// The object fetcher failed.
3433
#[error(transparent)]
3534
ObjectFetcherError(#[from] object_fetcher::Error),
36-
37-
/// The returned object data did not match the hash in the mapping.
38-
#[error(
39-
"Invalid object hash, mapping had {mapping_hash:?}, but fetched data had {data_hash:?}"
40-
)]
41-
InvalidObjectHash {
42-
/// The expected hash from the mapping.
43-
mapping_hash: Blake3Hash,
44-
/// The actual hash of the returned object data.
45-
data_hash: Blake3Hash,
46-
},
4735
}
4836

4937
impl From<Error> for ErrorObjectOwned {
@@ -144,31 +132,8 @@ where
144132
return Err(Error::TooManyMappings { count });
145133
}
146134

147-
let mut objects = Vec::with_capacity(count);
148-
// TODO: fetch concurrently
149-
for mapping in mappings.objects() {
150-
let data = self
151-
.object_fetcher
152-
.fetch_object(mapping.piece_index, mapping.offset)
153-
.await?;
154-
155-
let data_hash = blake3_hash(&data);
156-
if data_hash != mapping.hash {
157-
error!(
158-
?data_hash,
159-
data_size = %data.len(),
160-
?mapping.hash,
161-
"Retrieved data did not match mapping hash",
162-
);
163-
trace!(data = %hex::encode(data), "Retrieved data");
164-
return Err(Error::InvalidObjectHash {
165-
mapping_hash: mapping.hash,
166-
data_hash,
167-
});
168-
}
169-
170-
objects.push(data.into());
171-
}
135+
let objects = self.object_fetcher.fetch_objects(mappings).await?;
136+
let objects = objects.into_iter().map(HexData::from).collect();
172137

173138
Ok(objects)
174139
}

crates/subspace-gateway/src/commands/http/server.rs

Lines changed: 59 additions & 69 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,9 @@
11
//! HTTP server which fetches objects from the DSN based on a hash, using a mapping indexer service.
22
33
use actix_web::{web, App, HttpResponse, HttpServer, Responder};
4-
use serde::{Deserialize, Deserializer, Serialize};
5-
use std::default::Default;
64
use std::sync::Arc;
7-
use subspace_core_primitives::hashes::{blake3_hash, Blake3Hash};
8-
use subspace_core_primitives::pieces::PieceIndex;
9-
use subspace_core_primitives::BlockNumber;
5+
use subspace_core_primitives::hashes::Blake3Hash;
6+
use subspace_core_primitives::objects::ObjectMappingResponse;
107
use subspace_data_retrieval::object_fetcher::ObjectFetcher;
118
use subspace_data_retrieval::piece_getter::PieceGetter;
129
use tracing::{debug, error, trace};
@@ -21,108 +18,101 @@ where
2118
pub(crate) http_endpoint: String,
2219
}
2320

24-
/// Object mapping format from the indexer service.
25-
#[derive(Serialize, Deserialize, Debug, Default)]
26-
#[serde(rename_all = "camelCase")]
27-
struct ObjectMapping {
28-
hash: Blake3Hash,
29-
piece_index: PieceIndex,
30-
piece_offset: u32,
31-
#[serde(deserialize_with = "string_to_u32")]
32-
block_number: BlockNumber,
33-
}
34-
35-
/// Utility function to deserialize a JSON string into a u32.
36-
fn string_to_u32<'de, D>(deserializer: D) -> Result<u32, D::Error>
37-
where
38-
D: Deserializer<'de>,
39-
{
40-
let s: String = Deserialize::deserialize(deserializer)?;
41-
s.parse::<u32>().map_err(serde::de::Error::custom)
42-
}
43-
44-
/// Requests an object mapping with `hash` from the indexer service.
45-
async fn request_object_mapping(endpoint: &str, hash: Blake3Hash) -> anyhow::Result<ObjectMapping> {
21+
/// Requests the object mappings for `hashes` from the indexer service.
22+
/// Multiple hashes are separated by `+`.
23+
async fn request_object_mapping(
24+
endpoint: &str,
25+
hashes: &Vec<Blake3Hash>,
26+
) -> anyhow::Result<ObjectMappingResponse> {
4627
let client = reqwest::Client::new();
47-
let object_mappings_url = format!("{}/objects/{}", endpoint, hex::encode(hash));
28+
let hash_list = hashes.iter().map(hex::encode).collect::<Vec<_>>();
29+
let object_mappings_url = format!("{}/objects/{}", endpoint, hash_list.join("+"));
4830

49-
debug!(?hash, ?object_mappings_url, "Requesting object mapping...");
31+
debug!(
32+
?hashes,
33+
?object_mappings_url,
34+
"Requesting object mappings..."
35+
);
36+
37+
let response = client.get(&object_mappings_url).send().await?.json().await;
5038

51-
let response = client
52-
.get(&object_mappings_url)
53-
.send()
54-
.await?
55-
.json::<ObjectMapping>()
56-
.await;
5739
match &response {
5840
Ok(json) => {
59-
trace!(?hash, ?json, "Received object mapping");
41+
trace!(?hashes, ?json, "Received object mappings");
6042
}
6143
Err(err) => {
62-
error!(?hash, ?err, ?object_mappings_url, "Request failed");
44+
error!(?hashes, ?err, ?object_mappings_url, "Request failed");
6345
}
6446
}
6547

6648
response.map_err(|err| err.into())
6749
}
6850

69-
/// Fetches a DSN object with `hash`, using the mapping indexer service.
51+
/// Fetches the DSN objects with `hashes`, using the mapping indexer service.
52+
/// Multiple hashes are separated by `+`.
7053
async fn serve_object<PG>(
71-
hash: web::Path<Blake3Hash>,
54+
hashes: web::Path<String>,
7255
additional_data: web::Data<Arc<ServerParameters<PG>>>,
7356
) -> impl Responder
7457
where
7558
PG: PieceGetter + Send + Sync + 'static,
7659
{
7760
let server_params = additional_data.into_inner();
78-
let hash = hash.into_inner();
61+
let hashes = hashes.into_inner();
62+
let hashes = hashes
63+
.split('+')
64+
.map(|s| {
65+
let mut hash = Blake3Hash::default();
66+
hex::decode_to_slice(s, hash.as_mut()).map(|()| hash)
67+
})
68+
.try_collect::<Vec<_>>();
69+
70+
let Ok(hashes) = hashes else {
71+
return HttpResponse::BadRequest().finish();
72+
};
7973

80-
let Ok(object_mapping) = request_object_mapping(&server_params.indexer_endpoint, hash).await
74+
let Ok(object_mappings) =
75+
request_object_mapping(&server_params.indexer_endpoint, &hashes).await
8176
else {
8277
return HttpResponse::BadRequest().finish();
8378
};
8479

85-
if object_mapping.hash != hash {
86-
error!(
87-
?object_mapping,
88-
?hash,
89-
"Returned object mapping doesn't match requested hash"
90-
);
91-
return HttpResponse::ServiceUnavailable().finish();
80+
for object_mapping in object_mappings.objects.objects() {
81+
if !hashes.contains(&object_mapping.hash) {
82+
error!(
83+
?object_mapping,
84+
?hashes,
85+
"Returned object mapping wasn't in requested hashes"
86+
);
87+
return HttpResponse::ServiceUnavailable().finish();
88+
}
9289
}
9390

9491
let object_fetcher_result = server_params
9592
.object_fetcher
96-
.fetch_object(object_mapping.piece_index, object_mapping.piece_offset)
93+
.fetch_objects(object_mappings.objects)
9794
.await;
9895

99-
let object = match object_fetcher_result {
100-
Ok(object) => {
101-
trace!(?hash, size = %object.len(), "Object fetched successfully");
102-
103-
let data_hash = blake3_hash(&object);
104-
if data_hash != hash {
105-
error!(
106-
?data_hash,
107-
data_size = %object.len(),
108-
?hash,
109-
"Retrieved data doesn't match requested mapping hash"
110-
);
111-
trace!(data = %hex::encode(object), "Retrieved data");
112-
return HttpResponse::ServiceUnavailable().finish();
113-
}
114-
115-
object
96+
let objects = match object_fetcher_result {
97+
Ok(objects) => {
98+
trace!(
99+
?hashes,
100+
count = %objects.len(),
101+
sizes = ?objects.iter().map(|object| object.len()),
102+
"Objects fetched successfully"
103+
);
104+
objects
116105
}
117106
Err(err) => {
118-
error!(?hash, ?err, "Failed to fetch object");
107+
error!(?hashes, ?err, "Failed to fetch objects");
119108
return HttpResponse::ServiceUnavailable().finish();
120109
}
121110
};
122111

112+
// TODO: return a multi-part response, with one part per object
123113
HttpResponse::Ok()
124114
.content_type("application/octet-stream")
125-
.body(object)
115+
.body(objects.concat())
126116
}
127117

128118
/// Starts the DSN object HTTP server.
@@ -135,7 +125,7 @@ where
135125
HttpServer::new(move || {
136126
App::new()
137127
.app_data(web::Data::new(server_params.clone()))
138-
.route("/data/{hash}", web::get().to(serve_object::<PG>))
128+
.route("/data/{hashes}", web::get().to(serve_object::<PG>))
139129
})
140130
.bind(http_endpoint)?
141131
.run()

crates/subspace-gateway/src/main.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
//! Subspace gateway implementation.
22
3+
#![feature(iterator_try_collect)]
4+
35
mod commands;
46
mod node_client;
57
mod piece_getter;

shared/subspace-data-retrieval/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ include = [
1515
anyhow = "1.0.89"
1616
async-trait = "0.1.83"
1717
futures = "0.3.31"
18+
hex = "0.4.3"
1819
parity-scale-codec = { version = "3.6.12", features = ["derive"] }
1920
subspace-archiving = { version = "0.1.0", path = "../../crates/subspace-archiving" }
2021
subspace-core-primitives = { version = "0.1.0", path = "../../crates/subspace-core-primitives" }

0 commit comments

Comments
 (0)