1
1
#[ cfg( test) ]
2
2
use std:: cell:: Cell ;
3
3
use std:: {
4
+ fmt,
4
5
net:: Ipv4Addr ,
6
+ str:: FromStr ,
5
7
time:: { SystemTime , UNIX_EPOCH } ,
6
8
} ;
7
9
8
10
use alloy:: { hex, primitives:: U256 } ;
9
- use axum:: http:: HeaderValue ;
11
+ use axum:: {
12
+ extract:: { FromRequest , Request } ,
13
+ http:: HeaderValue ,
14
+ response:: { IntoResponse , Response as AxumResponse } ,
15
+ } ;
16
+ use bytes:: Bytes ;
10
17
use futures:: StreamExt ;
11
18
use lh_types:: test_utils:: { SeedableRng , TestRandom , XorShiftRng } ;
19
+ use mediatype:: { MediaType , MediaTypeList , names} ;
12
20
use rand:: { Rng , distr:: Alphanumeric } ;
13
- use reqwest:: { Response , header:: HeaderMap } ;
21
+ use reqwest:: {
22
+ Response , StatusCode ,
23
+ header:: { ACCEPT , CONTENT_TYPE , HeaderMap } ,
24
+ } ;
14
25
use serde:: { Serialize , de:: DeserializeOwned } ;
15
26
use serde_json:: Value ;
16
27
use ssz:: { Decode , Encode } ;
@@ -31,6 +42,7 @@ use crate::{
31
42
} ;
32
43
33
44
const MILLIS_PER_SECOND : u64 = 1_000 ;
45
+ pub const CONSENSUS_VERSION_HEADER : & str = "Eth-Consensus-Version" ;
34
46
35
47
#[ derive( Debug , Error ) ]
36
48
pub enum ResponseReadError {
@@ -408,6 +420,189 @@ pub fn get_user_agent_with_version(req_headers: &HeaderMap) -> eyre::Result<Head
408
420
Ok ( HeaderValue :: from_str ( & format ! ( "commit-boost/{HEADER_VERSION_VALUE} {ua}" ) ) ?)
409
421
}
410
422
423
+ /// Parse ACCEPT header, default to JSON if missing or mal-formatted
424
+ pub fn get_accept_header ( req_headers : & HeaderMap ) -> Accept {
425
+ Accept :: from_str (
426
+ req_headers. get ( ACCEPT ) . and_then ( |value| value. to_str ( ) . ok ( ) ) . unwrap_or ( "application/json" ) ,
427
+ )
428
+ . unwrap_or ( Accept :: Json )
429
+ }
430
+
431
+ /// Parse CONTENT TYPE header, default to JSON if missing or mal-formatted
432
+ pub fn get_content_type_header ( req_headers : & HeaderMap ) -> ContentType {
433
+ ContentType :: from_str (
434
+ req_headers
435
+ . get ( CONTENT_TYPE )
436
+ . and_then ( |value| value. to_str ( ) . ok ( ) )
437
+ . unwrap_or ( "application/json" ) ,
438
+ )
439
+ . unwrap_or ( ContentType :: Json )
440
+ }
441
+
442
+ /// Parse CONSENSUS_VERSION header
443
+ pub fn get_consensus_version_header ( req_headers : & HeaderMap ) -> Option < ForkName > {
444
+ ForkName :: from_str (
445
+ req_headers
446
+ . get ( CONSENSUS_VERSION_HEADER )
447
+ . and_then ( |value| value. to_str ( ) . ok ( ) )
448
+ . unwrap_or ( "" ) ,
449
+ )
450
+ . ok ( )
451
+ }
452
+
453
+ #[ derive( Debug , Clone , Copy , PartialEq ) ]
454
+ pub enum ForkName {
455
+ Electra ,
456
+ }
457
+
458
+ impl std:: fmt:: Display for ForkName {
459
+ fn fmt ( & self , f : & mut std:: fmt:: Formatter < ' _ > ) -> std:: fmt:: Result {
460
+ match self {
461
+ ForkName :: Electra => write ! ( f, "electra" ) ,
462
+ }
463
+ }
464
+ }
465
+
466
+ impl FromStr for ForkName {
467
+ type Err = String ;
468
+ fn from_str ( value : & str ) -> Result < Self , Self :: Err > {
469
+ match value {
470
+ "electra" => Ok ( ForkName :: Electra ) ,
471
+ _ => Err ( format ! ( "Invalid fork name {}" , value) ) ,
472
+ }
473
+ }
474
+ }
475
+
476
+ #[ derive( Debug , Clone , Copy , PartialEq ) ]
477
+ pub enum ContentType {
478
+ Json ,
479
+ Ssz ,
480
+ }
481
+
482
+ impl std:: fmt:: Display for ContentType {
483
+ fn fmt ( & self , f : & mut std:: fmt:: Formatter < ' _ > ) -> std:: fmt:: Result {
484
+ match self {
485
+ ContentType :: Json => write ! ( f, "application/json" ) ,
486
+ ContentType :: Ssz => write ! ( f, "application/octet-stream" ) ,
487
+ }
488
+ }
489
+ }
490
+
491
+ impl FromStr for ContentType {
492
+ type Err = String ;
493
+ fn from_str ( value : & str ) -> Result < Self , Self :: Err > {
494
+ match value {
495
+ "application/json" => Ok ( ContentType :: Json ) ,
496
+ "application/octet-stream" => Ok ( ContentType :: Ssz ) ,
497
+ _ => Ok ( ContentType :: Json ) ,
498
+ }
499
+ }
500
+ }
501
+
502
+ #[ derive( Debug , Clone , Copy , PartialEq ) ]
503
+ pub enum Accept {
504
+ Json ,
505
+ Ssz ,
506
+ Any ,
507
+ }
508
+
509
+ impl fmt:: Display for Accept {
510
+ fn fmt ( & self , f : & mut fmt:: Formatter < ' _ > ) -> fmt:: Result {
511
+ match self {
512
+ Accept :: Ssz => write ! ( f, "application/octet-stream" ) ,
513
+ Accept :: Json => write ! ( f, "application/json" ) ,
514
+ Accept :: Any => write ! ( f, "*/*" ) ,
515
+ }
516
+ }
517
+ }
518
+
519
+ impl FromStr for Accept {
520
+ type Err = String ;
521
+
522
+ fn from_str ( s : & str ) -> Result < Self , Self :: Err > {
523
+ let media_type_list = MediaTypeList :: new ( s) ;
524
+
525
+ // [q-factor weighting]: https://datatracker.ietf.org/doc/html/rfc7231#section-5.3.2
526
+ // find the highest q-factor supported accept type
527
+ let mut highest_q = 0_u16 ;
528
+ let mut accept_type = None ;
529
+
530
+ const APPLICATION : & str = names:: APPLICATION . as_str ( ) ;
531
+ const OCTET_STREAM : & str = names:: OCTET_STREAM . as_str ( ) ;
532
+ const JSON : & str = names:: JSON . as_str ( ) ;
533
+ const STAR : & str = names:: _STAR. as_str ( ) ;
534
+ const Q : & str = names:: Q . as_str ( ) ;
535
+
536
+ media_type_list. into_iter ( ) . for_each ( |item| {
537
+ if let Ok ( MediaType { ty, subty, suffix : _, params } ) = item {
538
+ let q_accept = match ( ty. as_str ( ) , subty. as_str ( ) ) {
539
+ ( APPLICATION , OCTET_STREAM ) => Some ( Accept :: Ssz ) ,
540
+ ( APPLICATION , JSON ) => Some ( Accept :: Json ) ,
541
+ ( STAR , STAR ) => Some ( Accept :: Any ) ,
542
+ _ => None ,
543
+ }
544
+ . map ( |item_accept_type| {
545
+ let q_val = params
546
+ . iter ( )
547
+ . find_map ( |( n, v) | match n. as_str ( ) {
548
+ Q => {
549
+ Some ( ( v. as_str ( ) . parse :: < f32 > ( ) . unwrap_or ( 0_f32 ) * 1000_f32 ) as u16 )
550
+ }
551
+ _ => None ,
552
+ } )
553
+ . or ( Some ( 1000_u16 ) ) ;
554
+
555
+ ( q_val. unwrap ( ) , item_accept_type)
556
+ } ) ;
557
+
558
+ match q_accept {
559
+ Some ( ( q, accept) ) if q > highest_q => {
560
+ highest_q = q;
561
+ accept_type = Some ( accept) ;
562
+ }
563
+ _ => ( ) ,
564
+ }
565
+ }
566
+ } ) ;
567
+ accept_type. ok_or_else ( || "accept header is not supported" . to_string ( ) )
568
+ }
569
+ }
570
+
571
+ #[ must_use]
572
+ #[ derive( Debug , Clone , Copy , Default ) ]
573
+ pub struct JsonOrSsz < T > ( pub T ) ;
574
+
575
+ impl < T , S > FromRequest < S > for JsonOrSsz < T >
576
+ where
577
+ T : serde:: de:: DeserializeOwned + ssz:: Decode + ' static ,
578
+ S : Send + Sync ,
579
+ {
580
+ type Rejection = AxumResponse ;
581
+
582
+ async fn from_request ( req : Request , _state : & S ) -> Result < Self , Self :: Rejection > {
583
+ let headers = req. headers ( ) . clone ( ) ;
584
+ let content_type = headers. get ( CONTENT_TYPE ) . and_then ( |value| value. to_str ( ) . ok ( ) ) ;
585
+
586
+ let bytes = Bytes :: from_request ( req, _state) . await . map_err ( IntoResponse :: into_response) ?;
587
+
588
+ if let Some ( content_type) = content_type {
589
+ if content_type. starts_with ( & ContentType :: Json . to_string ( ) ) {
590
+ let payload: T = serde_json:: from_slice ( & bytes)
591
+ . map_err ( |_| StatusCode :: BAD_REQUEST . into_response ( ) ) ?;
592
+ return Ok ( Self ( payload) ) ;
593
+ }
594
+
595
+ if content_type. starts_with ( & ContentType :: Ssz . to_string ( ) ) {
596
+ let payload = T :: from_ssz_bytes ( & bytes)
597
+ . map_err ( |_| StatusCode :: BAD_REQUEST . into_response ( ) ) ?;
598
+ return Ok ( Self ( payload) ) ;
599
+ }
600
+ }
601
+
602
+ Err ( StatusCode :: UNSUPPORTED_MEDIA_TYPE . into_response ( ) )
603
+ }
604
+ }
605
+
411
606
#[ cfg( unix) ]
412
607
pub async fn wait_for_signal ( ) -> eyre:: Result < ( ) > {
413
608
use tokio:: signal:: unix:: { SignalKind , signal} ;
0 commit comments