11//! HTTP server which fetches objects from the DSN based on a hash, using a mapping indexer service.
22
33use actix_web:: { web, App , HttpResponse , HttpServer , Responder } ;
4- use serde:: { Deserialize , Deserializer , Serialize } ;
5- use std:: default:: Default ;
64use 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 ;
107use subspace_data_retrieval:: object_fetcher:: ObjectFetcher ;
118use subspace_data_retrieval:: piece_getter:: PieceGetter ;
129use 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 `+`.
7053async 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
7457where
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 ( )
0 commit comments