11use eth2:: types:: builder_bid:: SignedBuilderBid ;
2+ use eth2:: types:: fork_versioned_response:: EmptyMetadata ;
23use eth2:: types:: {
3- EthSpec , ExecutionBlockHash , ForkVersionedResponse , PublicKeyBytes ,
4- SignedValidatorRegistrationData , Slot ,
4+ ContentType , EthSpec , ExecutionBlockHash , ForkName , ForkVersionDecode , ForkVersionDeserialize ,
5+ ForkVersionedResponse , PublicKeyBytes , SignedValidatorRegistrationData , Slot ,
56} ;
67use eth2:: types:: { FullPayloadContents , SignedBlindedBeaconBlock } ;
78pub use eth2:: Error ;
8- use eth2:: { ok_or_error, StatusCode , CONSENSUS_VERSION_HEADER } ;
9- use reqwest:: header:: { HeaderMap , HeaderValue } ;
9+ use eth2:: {
10+ ok_or_error, StatusCode , CONSENSUS_VERSION_HEADER , CONTENT_TYPE_HEADER ,
11+ JSON_CONTENT_TYPE_HEADER , SSZ_CONTENT_TYPE_HEADER ,
12+ } ;
13+ use reqwest:: header:: { HeaderMap , HeaderValue , ACCEPT } ;
1014use reqwest:: { IntoUrl , Response } ;
1115use sensitive_url:: SensitiveUrl ;
1216use serde:: de:: DeserializeOwned ;
1317use serde:: Serialize ;
18+ use ssz:: Encode ;
19+ use std:: str:: FromStr ;
20+ use std:: sync:: atomic:: { AtomicBool , Ordering } ;
21+ use std:: sync:: Arc ;
1422use std:: time:: Duration ;
1523
1624pub const DEFAULT_TIMEOUT_MILLIS : u64 = 15000 ;
@@ -49,6 +57,7 @@ pub struct BuilderHttpClient {
4957 server : SensitiveUrl ,
5058 timeouts : Timeouts ,
5159 user_agent : String ,
60+ ssz_enabled : Arc < AtomicBool > ,
5261}
5362
5463impl BuilderHttpClient {
@@ -64,13 +73,86 @@ impl BuilderHttpClient {
6473 server,
6574 timeouts : Timeouts :: new ( builder_header_timeout) ,
6675 user_agent,
76+ ssz_enabled : Arc :: new ( false . into ( ) ) ,
6777 } )
6878 }
6979
7080 pub fn get_user_agent ( & self ) -> & str {
7181 & self . user_agent
7282 }
7383
84+ fn fork_name_from_header ( & self , headers : & HeaderMap ) -> Result < Option < ForkName > , String > {
85+ headers
86+ . get ( CONSENSUS_VERSION_HEADER )
87+ . map ( |fork_name| {
88+ fork_name
89+ . to_str ( )
90+ . map_err ( |e| e. to_string ( ) )
91+ . and_then ( ForkName :: from_str)
92+ } )
93+ . transpose ( )
94+ }
95+
96+ fn content_type_from_header ( & self , headers : & HeaderMap ) -> ContentType {
97+ let Some ( content_type) = headers. get ( CONTENT_TYPE_HEADER ) . map ( |content_type| {
98+ let content_type = content_type. to_str ( ) ;
99+ match content_type {
100+ Ok ( SSZ_CONTENT_TYPE_HEADER ) => ContentType :: Ssz ,
101+ _ => ContentType :: Json ,
102+ }
103+ } ) else {
104+ return ContentType :: Json ;
105+ } ;
106+ content_type
107+ }
108+
109+ async fn get_with_header <
110+ T : DeserializeOwned + ForkVersionDecode + ForkVersionDeserialize ,
111+ U : IntoUrl ,
112+ > (
113+ & self ,
114+ url : U ,
115+ timeout : Duration ,
116+ headers : HeaderMap ,
117+ ) -> Result < ForkVersionedResponse < T > , Error > {
118+ let response = self
119+ . get_response_with_header ( url, Some ( timeout) , headers)
120+ . await ?;
121+
122+ let headers = response. headers ( ) . clone ( ) ;
123+ let response_bytes = response. bytes ( ) . await ?;
124+
125+ let Ok ( Some ( fork_name) ) = self . fork_name_from_header ( & headers) else {
126+ // if no fork version specified, attempt to fallback to JSON
127+ self . ssz_enabled . store ( false , Ordering :: SeqCst ) ;
128+ return serde_json:: from_slice ( & response_bytes) . map_err ( Error :: InvalidJson ) ;
129+ } ;
130+
131+ let content_type = self . content_type_from_header ( & headers) ;
132+
133+ match content_type {
134+ ContentType :: Ssz => {
135+ self . ssz_enabled . store ( true , Ordering :: SeqCst ) ;
136+ T :: from_ssz_bytes_by_fork ( & response_bytes, fork_name)
137+ . map ( |data| ForkVersionedResponse {
138+ version : Some ( fork_name) ,
139+ metadata : EmptyMetadata { } ,
140+ data,
141+ } )
142+ . map_err ( Error :: InvalidSsz )
143+ }
144+ ContentType :: Json => {
145+ self . ssz_enabled . store ( false , Ordering :: SeqCst ) ;
146+ serde_json:: from_slice ( & response_bytes) . map_err ( Error :: InvalidJson )
147+ }
148+ }
149+ }
150+
151+ /// Return `true` if the most recently received response from the builder had SSZ Content-Type.
152+ pub fn is_ssz_enabled ( & self ) -> bool {
153+ self . ssz_enabled . load ( Ordering :: SeqCst )
154+ }
155+
74156 async fn get_with_timeout < T : DeserializeOwned , U : IntoUrl > (
75157 & self ,
76158 url : U ,
@@ -83,6 +165,21 @@ impl BuilderHttpClient {
83165 . map_err ( Into :: into)
84166 }
85167
168+ /// Perform a HTTP GET request, returning the `Response` for further processing.
169+ async fn get_response_with_header < U : IntoUrl > (
170+ & self ,
171+ url : U ,
172+ timeout : Option < Duration > ,
173+ headers : HeaderMap ,
174+ ) -> Result < Response , Error > {
175+ let mut builder = self . client . get ( url) ;
176+ if let Some ( timeout) = timeout {
177+ builder = builder. timeout ( timeout) ;
178+ }
179+ let response = builder. headers ( headers) . send ( ) . await . map_err ( Error :: from) ?;
180+ ok_or_error ( response) . await
181+ }
182+
86183 /// Perform a HTTP GET request, returning the `Response` for further processing.
87184 async fn get_response_with_timeout < U : IntoUrl > (
88185 & self ,
@@ -112,6 +209,32 @@ impl BuilderHttpClient {
112209 ok_or_error ( response) . await
113210 }
114211
212+ async fn post_ssz_with_raw_response < U : IntoUrl > (
213+ & self ,
214+ url : U ,
215+ ssz_body : Vec < u8 > ,
216+ mut headers : HeaderMap ,
217+ timeout : Option < Duration > ,
218+ ) -> Result < Response , Error > {
219+ let mut builder = self . client . post ( url) ;
220+ if let Some ( timeout) = timeout {
221+ builder = builder. timeout ( timeout) ;
222+ }
223+
224+ headers. insert (
225+ CONTENT_TYPE_HEADER ,
226+ HeaderValue :: from_static ( SSZ_CONTENT_TYPE_HEADER ) ,
227+ ) ;
228+
229+ let response = builder
230+ . headers ( headers)
231+ . body ( ssz_body)
232+ . send ( )
233+ . await
234+ . map_err ( Error :: from) ?;
235+ ok_or_error ( response) . await
236+ }
237+
115238 async fn post_with_raw_response < T : Serialize , U : IntoUrl > (
116239 & self ,
117240 url : U ,
@@ -152,6 +275,42 @@ impl BuilderHttpClient {
152275 Ok ( ( ) )
153276 }
154277
278+ /// `POST /eth/v1/builder/blinded_blocks` with SSZ serialized request body
279+ pub async fn post_builder_blinded_blocks_ssz < E : EthSpec > (
280+ & self ,
281+ blinded_block : & SignedBlindedBeaconBlock < E > ,
282+ ) -> Result < FullPayloadContents < E > , Error > {
283+ let mut path = self . server . full . clone ( ) ;
284+
285+ let body = blinded_block. as_ssz_bytes ( ) ;
286+
287+ path. path_segments_mut ( )
288+ . map_err ( |( ) | Error :: InvalidUrl ( self . server . clone ( ) ) ) ?
289+ . push ( "eth" )
290+ . push ( "v1" )
291+ . push ( "builder" )
292+ . push ( "blinded_blocks" ) ;
293+
294+ let mut headers = HeaderMap :: new ( ) ;
295+ if let Ok ( value) = HeaderValue :: from_str ( & blinded_block. fork_name_unchecked ( ) . to_string ( ) ) {
296+ headers. insert ( CONSENSUS_VERSION_HEADER , value) ;
297+ }
298+
299+ let result = self
300+ . post_ssz_with_raw_response (
301+ path,
302+ body,
303+ headers,
304+ Some ( self . timeouts . post_blinded_blocks ) ,
305+ )
306+ . await ?
307+ . bytes ( )
308+ . await ?;
309+
310+ FullPayloadContents :: from_ssz_bytes_by_fork ( & result, blinded_block. fork_name_unchecked ( ) )
311+ . map_err ( Error :: InvalidSsz )
312+ }
313+
155314 /// `POST /eth/v1/builder/blinded_blocks`
156315 pub async fn post_builder_blinded_blocks < E : EthSpec > (
157316 & self ,
@@ -202,7 +361,17 @@ impl BuilderHttpClient {
202361 . push ( format ! ( "{parent_hash:?}" ) . as_str ( ) )
203362 . push ( pubkey. as_hex_string ( ) . as_str ( ) ) ;
204363
205- let resp = self . get_with_timeout ( path, self . timeouts . get_header ) . await ;
364+ let mut headers = HeaderMap :: new ( ) ;
365+ if let Ok ( ssz_content_type_header) = HeaderValue :: from_str ( & format ! (
366+ "{}; q=1.0,{}; q=0.9" ,
367+ SSZ_CONTENT_TYPE_HEADER , JSON_CONTENT_TYPE_HEADER
368+ ) ) {
369+ headers. insert ( ACCEPT , ssz_content_type_header) ;
370+ } ;
371+
372+ let resp = self
373+ . get_with_header ( path, self . timeouts . get_header , headers)
374+ . await ;
206375
207376 if matches ! ( resp, Err ( Error :: StatusCode ( StatusCode :: NO_CONTENT ) ) ) {
208377 Ok ( None )
0 commit comments