@@ -9,11 +9,50 @@ use alloy_rpc_types_beacon::{
99use axum:: {
1010 Json ,
1111 extract:: { Path , Query , State } ,
12+ http:: HeaderMap ,
1213 response:: { IntoResponse , Response } ,
1314} ;
14- use hyper :: StatusCode ;
15+ use ssz :: Encode ;
1516use std:: { collections:: HashMap , str:: FromStr as _} ;
1617
18+ /// Helper function to determine if the Accept header indicates a preference for SSZ (octet-stream)
19+ /// over JSON.
20+ pub fn must_be_ssz ( headers : & HeaderMap ) -> bool {
21+ headers
22+ . get ( axum:: http:: header:: ACCEPT )
23+ . and_then ( |v| v. to_str ( ) . ok ( ) )
24+ . map ( |accept_str| {
25+ let mut octet_stream_q = 0.0 ;
26+ let mut json_q = 0.0 ;
27+
28+ // Parse each media type in the Accept header
29+ for media_type in accept_str. split ( ',' ) {
30+ let media_type = media_type. trim ( ) ;
31+ let quality = media_type
32+ . split ( ';' )
33+ . find_map ( |param| {
34+ let param = param. trim ( ) ;
35+ if let Some ( q) = param. strip_prefix ( "q=" ) {
36+ q. parse :: < f32 > ( ) . ok ( )
37+ } else {
38+ None
39+ }
40+ } )
41+ . unwrap_or ( 1.0 ) ; // Default quality factor is 1.0
42+
43+ if media_type. starts_with ( "application/octet-stream" ) {
44+ octet_stream_q = quality;
45+ } else if media_type. starts_with ( "application/json" ) {
46+ json_q = quality;
47+ }
48+ }
49+
50+ // Prefer octet-stream if it has higher quality factor
51+ octet_stream_q > json_q
52+ } )
53+ . unwrap_or ( false )
54+ }
55+
1756/// Handles incoming Beacon API requests for blob sidecars
1857///
1958/// This endpoint is deprecated. Use `GET /eth/v1/beacon/blobs/{block_id}` instead.
@@ -32,6 +71,7 @@ pub async fn handle_get_blob_sidecars(
3271///
3372/// GET /eth/v1/beacon/blobs/{block_id}
3473pub async fn handle_get_blobs (
74+ headers : HeaderMap ,
3575 State ( api) : State < EthApi > ,
3676 Path ( block_id) : Path < String > ,
3777 Query ( versioned_hashes) : Query < HashMap < String , String > > ,
@@ -50,11 +90,18 @@ pub async fn handle_get_blobs(
5090
5191 // Get the blob sidecars using existing EthApi logic
5292 match api. anvil_get_blobs_by_block_id ( block_id, versioned_hashes) {
53- Ok ( Some ( blobs) ) => (
54- StatusCode :: OK ,
55- Json ( GetBlobsResponse { execution_optimistic : false , finalized : false , data : blobs } ) ,
56- )
57- . into_response ( ) ,
93+ Ok ( Some ( blobs) ) => {
94+ if must_be_ssz ( & headers) {
95+ blobs. as_ssz_bytes ( ) . into_response ( )
96+ } else {
97+ Json ( GetBlobsResponse {
98+ execution_optimistic : false ,
99+ finalized : false ,
100+ data : blobs,
101+ } )
102+ . into_response ( )
103+ }
104+ }
58105 Ok ( None ) => BeaconError :: block_not_found ( ) . into_response ( ) ,
59106 Err ( _) => BeaconError :: internal_error ( ) . into_response ( ) ,
60107 }
@@ -67,17 +114,67 @@ pub async fn handle_get_blobs(
67114/// GET /eth/v1/beacon/genesis
68115pub async fn handle_get_genesis ( State ( api) : State < EthApi > ) -> Response {
69116 match api. anvil_get_genesis_time ( ) {
70- Ok ( genesis_time) => (
71- StatusCode :: OK ,
72- Json ( GenesisResponse {
73- data : GenesisData {
74- genesis_time,
75- genesis_validators_root : B256 :: ZERO ,
76- genesis_fork_version : B32 :: ZERO ,
77- } ,
78- } ) ,
79- )
80- . into_response ( ) ,
117+ Ok ( genesis_time) => Json ( GenesisResponse {
118+ data : GenesisData {
119+ genesis_time,
120+ genesis_validators_root : B256 :: ZERO ,
121+ genesis_fork_version : B32 :: ZERO ,
122+ } ,
123+ } )
124+ . into_response ( ) ,
81125 Err ( _) => BeaconError :: internal_error ( ) . into_response ( ) ,
82126 }
83127}
128+ #[ cfg( test) ]
129+ mod tests {
130+ use super :: * ;
131+ use axum:: http:: HeaderValue ;
132+
133+ fn header_map_with_accept ( accept : & str ) -> HeaderMap {
134+ let mut headers = HeaderMap :: new ( ) ;
135+ headers. insert ( axum:: http:: header:: ACCEPT , HeaderValue :: from_str ( accept) . unwrap ( ) ) ;
136+ headers
137+ }
138+
139+ #[ test]
140+ fn test_must_be_ssz ( ) {
141+ let test_cases = vec ! [
142+ ( None , false , "no Accept header" ) ,
143+ ( Some ( "application/json" ) , false , "JSON only" ) ,
144+ ( Some ( "application/octet-stream" ) , true , "octet-stream only" ) ,
145+ ( Some ( "application/octet-stream;q=1.0,application/json;q=0.9" ) , true , "SSZ preferred" ) ,
146+ (
147+ Some ( "application/json;q=1.0,application/octet-stream;q=0.9" ) ,
148+ false ,
149+ "JSON preferred" ,
150+ ) ,
151+ ( Some ( "application/octet-stream;q=0.5,application/json;q=0.5" ) , false , "equal quality" ) ,
152+ (
153+ Some ( "text/html;q=0.9, application/octet-stream;q=1.0, application/json;q=0.8" ) ,
154+ true ,
155+ "multiple types" ,
156+ ) ,
157+ (
158+ Some ( "application/octet-stream ; q=1.0 , application/json ; q=0.9" ) ,
159+ true ,
160+ "whitespace handling" ,
161+ ) ,
162+ ( Some ( "application/octet-stream, application/json;q=0.9" ) , true , "default quality" ) ,
163+ ] ;
164+
165+ for ( accept_header, expected, description) in test_cases {
166+ let headers = match accept_header {
167+ None => HeaderMap :: new ( ) ,
168+ Some ( header) => header_map_with_accept ( header) ,
169+ } ;
170+ assert_eq ! (
171+ must_be_ssz( & headers) ,
172+ expected,
173+ "Test case '{}' failed: expected {}, got {}" ,
174+ description,
175+ expected,
176+ !expected
177+ ) ;
178+ }
179+ }
180+ }
0 commit comments