@@ -8,10 +8,12 @@ use std::{
88use alloy:: {
99 primitives:: { address, Address , U256 } ,
1010 providers:: ProviderBuilder ,
11- rpc:: types:: beacon:: BlsPublicKey ,
11+ rpc:: { client :: RpcClient , types:: beacon:: BlsPublicKey } ,
1212 sol,
13+ transports:: http:: Http ,
1314} ;
1415use eyre:: { bail, ensure, Context } ;
16+ use reqwest:: Client ;
1517use serde:: { Deserialize , Serialize } ;
1618use tracing:: { debug, info, warn} ;
1719use url:: Url ;
@@ -193,8 +195,8 @@ impl MuxKeysLoader {
193195 }
194196 let client = reqwest:: ClientBuilder :: new ( ) . timeout ( http_timeout) . build ( ) ?;
195197 let response = client. get ( url) . send ( ) . await ?;
196- let pubkeys = safe_read_http_response ( response) . await ?;
197- serde_json:: from_str ( & pubkeys )
198+ let pubkey_bytes = safe_read_http_response ( response) . await ?;
199+ serde_json:: from_slice ( & pubkey_bytes )
198200 . wrap_err ( "failed to fetch mux keys from HTTP endpoint" )
199201 }
200202
@@ -204,7 +206,13 @@ impl MuxKeysLoader {
204206 bail ! ( "Lido registry requires RPC URL to be set in the PBS config" ) ;
205207 } ;
206208
207- fetch_lido_registry_keys ( rpc_url, chain, U256 :: from ( * node_operator_id) ) . await
209+ fetch_lido_registry_keys (
210+ rpc_url,
211+ chain,
212+ U256 :: from ( * node_operator_id) ,
213+ http_timeout,
214+ )
215+ . await
208216 }
209217 NORegistry :: SSV => {
210218 fetch_ssv_pubkeys ( chain, U256 :: from ( * node_operator_id) , http_timeout) . await
@@ -254,10 +262,17 @@ async fn fetch_lido_registry_keys(
254262 rpc_url : Url ,
255263 chain : Chain ,
256264 node_operator_id : U256 ,
265+ http_timeout : Duration ,
257266) -> eyre:: Result < Vec < BlsPublicKey > > {
258267 debug ! ( ?chain, %node_operator_id, "loading operator keys from Lido registry" ) ;
259268
260- let provider = ProviderBuilder :: new ( ) . on_http ( rpc_url) ;
269+ // Create an RPC provider with HTTP timeout support
270+ let client = Client :: builder ( ) . timeout ( http_timeout) . build ( ) ?;
271+ let http = Http :: with_client ( client, rpc_url) ;
272+ let is_local = http. guess_local ( ) ;
273+ let rpc_client = RpcClient :: new ( http, is_local) ;
274+ let provider = ProviderBuilder :: new ( ) . on_client ( rpc_client) ;
275+
261276 let registry_address = lido_registry_address ( chain) ?;
262277 let registry = LidoRegistry :: new ( registry_address, provider) ;
263278
@@ -362,9 +377,8 @@ async fn fetch_ssv_pubkeys_from_url(
362377 } ) ?;
363378
364379 // Parse the response as JSON
365- let body_string = safe_read_http_response ( response) . await ?;
366- serde_json:: from_slice :: < SSVResponse > ( body_string. as_bytes ( ) )
367- . wrap_err ( "failed to parse SSV response" )
380+ let body_bytes = safe_read_http_response ( response) . await ?;
381+ serde_json:: from_slice :: < SSVResponse > ( & body_bytes) . wrap_err ( "failed to parse SSV response" )
368382}
369383
370384#[ derive( Deserialize ) ]
@@ -386,19 +400,17 @@ struct SSVPagination {
386400
387401#[ cfg( test) ]
388402mod tests {
389- use std:: { env , net:: SocketAddr } ;
403+ use std:: net:: SocketAddr ;
390404
391405 use alloy:: { hex:: FromHex , primitives:: U256 , providers:: ProviderBuilder } ;
392406 use axum:: { response:: Response , routing:: get} ;
393- use scopeguard:: defer;
394- use serial_test:: serial;
395407 use tokio:: { net:: TcpListener , task:: JoinHandle } ;
396408 use url:: Url ;
397409
398410 use super :: * ;
399- use crate :: config :: {
400- CB_TEST_HTTP_DISABLE_CONTENT_LENGTH_ENV , HTTP_TIMEOUT_SECONDS_DEFAULT ,
401- MUXER_HTTP_MAX_LENGTH ,
411+ use crate :: {
412+ config :: { HTTP_TIMEOUT_SECONDS_DEFAULT , MUXER_HTTP_MAX_LENGTH } ,
413+ utils :: { set_ignore_content_length , ResponseReadError } ,
402414 } ;
403415
404416 const TEST_HTTP_TIMEOUT : u64 = 2 ;
@@ -471,25 +483,28 @@ mod tests {
471483 }
472484
473485 #[ tokio:: test]
474- #[ serial]
475486 /// Tests that the SSV network fetch is handled properly when the response's
476487 /// body is too large
477488 async fn test_ssv_network_fetch_big_data ( ) -> eyre:: Result < ( ) > {
478489 // Start the mock server
479490 let port = 30101 ;
480- env:: remove_var ( CB_TEST_HTTP_DISABLE_CONTENT_LENGTH_ENV ) ;
481491 let _server_handle = create_mock_server ( port) . await ?;
482492 let url = format ! ( "http://localhost:{port}/big_data" ) ;
483493 let response = fetch_ssv_pubkeys_from_url ( & url, Duration :: from_secs ( 120 ) ) . await ;
484494
485495 // The response should fail due to content length being too big
486- assert ! ( response. is_err( ) , "Expected error due to big content length, but got success" ) ;
487- if let Err ( e) = response {
488- assert ! (
489- e. to_string( ) . contains( "content length" ) &&
490- e. to_string( ) . contains( "exceeds the maximum allowed length" ) ,
491- "Expected content length error, got: {e}" ,
492- ) ;
496+ match response {
497+ Ok ( _) => {
498+ panic ! ( "Expected an error due to big content length, but got a successful response" )
499+ }
500+ Err ( e) => match e. downcast_ref :: < ResponseReadError > ( ) {
501+ Some ( ResponseReadError :: PayloadTooLarge { max, content_length, raw } ) => {
502+ assert_eq ! ( * max, MUXER_HTTP_MAX_LENGTH ) ;
503+ assert ! ( * content_length > MUXER_HTTP_MAX_LENGTH ) ;
504+ assert ! ( raw. is_empty( ) ) ;
505+ }
506+ _ => panic ! ( "Expected PayloadTooLarge error, got: {}" , e) ,
507+ } ,
493508 }
494509
495510 // Clean up the server handle
@@ -522,25 +537,29 @@ mod tests {
522537 }
523538
524539 #[ tokio:: test]
525- #[ serial]
526540 /// Tests that the SSV network fetch is handled properly when the response's
527541 /// content-length header is missing
528542 async fn test_ssv_network_fetch_big_data_without_content_length ( ) -> eyre:: Result < ( ) > {
529543 // Start the mock server
530544 let port = 30103 ;
531- env:: set_var ( CB_TEST_HTTP_DISABLE_CONTENT_LENGTH_ENV , "1" ) ;
532- defer ! { env:: remove_var( CB_TEST_HTTP_DISABLE_CONTENT_LENGTH_ENV ) ; }
545+ set_ignore_content_length ( true ) ;
533546 let _server_handle = create_mock_server ( port) . await ?;
534547 let url = format ! ( "http://localhost:{port}/big_data" ) ;
535548 let response = fetch_ssv_pubkeys_from_url ( & url, Duration :: from_secs ( 120 ) ) . await ;
536549
537- // The response should fail due to timeout
538- assert ! ( response. is_err( ) , "Expected error due to body size, but got success" ) ;
539- if let Err ( e) = response {
540- assert ! (
541- e. to_string( ) . contains( "Response body exceeds the maximum allowed length " ) ,
542- "Expected content length error, got: {e}" ,
543- ) ;
550+ // The response should fail due to the body being too big
551+ match response {
552+ Ok ( _) => {
553+ panic ! ( "Expected an error due to excessive data, but got a successful response" )
554+ }
555+ Err ( e) => match e. downcast_ref :: < ResponseReadError > ( ) {
556+ Some ( ResponseReadError :: PayloadTooLarge { max, content_length, raw } ) => {
557+ assert_eq ! ( * max, MUXER_HTTP_MAX_LENGTH ) ;
558+ assert_eq ! ( * content_length, 0 ) ;
559+ assert ! ( !raw. is_empty( ) ) ;
560+ }
561+ _ => panic ! ( "Expected PayloadTooLarge error, got: {}" , e) ,
562+ } ,
544563 }
545564
546565 // Clean up the server handle
@@ -585,10 +604,12 @@ mod tests {
585604 . unwrap ( )
586605 }
587606
588- /// Sends a response with a large body but no content length
607+ /// Sends a response with a large body - larger than the maximum allowed.
608+ /// Note that hyper overwrites the content-length header automatically, so
609+ /// setting it here wouldn't actually change the value that ultimately
610+ /// gets sent to the server.
589611 async fn handle_big_data ( ) -> Response {
590- // Create a response with a large body but no content length
591- let body = "f" . repeat ( 2 * MUXER_HTTP_MAX_LENGTH as usize ) ;
612+ let body = "f" . repeat ( 2 * MUXER_HTTP_MAX_LENGTH ) ;
592613 Response :: builder ( )
593614 . status ( 200 )
594615 . header ( "Content-Type" , "application/text" )
0 commit comments