1- use anyhow:: { Result , bail} ;
1+ use anyhow:: { Context , Result , bail} ;
22use backoff:: ExponentialBackoffBuilder ;
33use backoff:: backoff:: Backoff ;
4+ use base64:: Engine ;
45use futures_util:: stream:: { SplitSink , SplitStream } ;
56use futures_util:: { SinkExt , StreamExt } ;
67use http:: HeaderValue ;
@@ -9,32 +10,194 @@ use pyth_lazer_publisher_sdk::transaction::SignedLazerTransaction;
910use std:: sync:: Arc ;
1011use std:: sync:: atomic:: { AtomicBool , Ordering } ;
1112use std:: time:: { Duration , Instant } ;
13+ use tokio:: io:: { AsyncReadExt , AsyncWriteExt } ;
1214use tokio:: net:: TcpStream ;
1315use tokio:: select;
1416use tokio:: sync:: broadcast;
1517use tokio_tungstenite:: tungstenite:: client:: IntoClientRequest ;
1618use tokio_tungstenite:: {
17- MaybeTlsStream , WebSocketStream , connect_async_with_config,
19+ MaybeTlsStream , WebSocketStream , client_async , connect_async_with_config,
1820 tungstenite:: Message as TungsteniteMessage ,
1921} ;
2022use url:: Url ;
2123
2224type RelayerWsSender = SplitSink < WebSocketStream < MaybeTlsStream < TcpStream > > , TungsteniteMessage > ;
2325type RelayerWsReceiver = SplitStream < WebSocketStream < MaybeTlsStream < TcpStream > > > ;
2426
25- async fn connect_to_relayer ( url : Url , token : & str ) -> Result < ( RelayerWsSender , RelayerWsReceiver ) > {
26- tracing:: info!( "connecting to the relayer at {}" , url) ;
27- let mut req = url. clone ( ) . into_client_request ( ) ?;
27+ async fn connect_through_proxy (
28+ proxy_url : & Url ,
29+ target_url : & Url ,
30+ token : & str ,
31+ ) -> Result < ( RelayerWsSender , RelayerWsReceiver ) > {
32+ tracing:: info!(
33+ "connecting to the relayer at {} via proxy {}" ,
34+ target_url,
35+ proxy_url
36+ ) ;
37+
38+ let proxy_host = proxy_url. host_str ( ) . context ( "Proxy URL must have a host" ) ?;
39+ let proxy_port = proxy_url
40+ . port ( )
41+ . unwrap_or ( if proxy_url. scheme ( ) == "https" {
42+ 443
43+ } else {
44+ 80
45+ } ) ;
46+
47+ let proxy_addr = format ! ( "{proxy_host}:{proxy_port}" ) ;
48+ let mut stream = TcpStream :: connect ( & proxy_addr)
49+ . await
50+ . context ( format ! ( "Failed to connect to proxy at {proxy_addr}" ) ) ?;
51+
52+ let target_host = target_url
53+ . host_str ( )
54+ . context ( "Target URL must have a host" ) ?;
55+ let target_port = target_url
56+ . port ( )
57+ . unwrap_or ( if target_url. scheme ( ) == "wss" {
58+ 443
59+ } else {
60+ 80
61+ } ) ;
62+
63+ let target_authority = format ! ( "{target_host}:{target_port}" ) ;
64+ let mut request_parts = vec ! [ format!( "CONNECT {target_authority} HTTP/1.1" ) ] ;
65+ request_parts. push ( format ! ( "Host: {target_authority}" ) ) ;
66+
67+ let username = proxy_url. username ( ) ;
68+ if !username. is_empty ( ) {
69+ let password = proxy_url. password ( ) . unwrap_or ( "" ) ;
70+ let credentials = format ! ( "{username}:{password}" ) ;
71+ let encoded = base64:: engine:: general_purpose:: STANDARD . encode ( credentials. as_bytes ( ) ) ;
72+ request_parts. push ( format ! ( "Proxy-Authorization: Basic {encoded}" ) ) ;
73+ }
74+
75+ request_parts. push ( "Proxy-Connection: Keep-Alive" . to_string ( ) ) ;
76+ request_parts. push ( String :: new ( ) ) ; // Empty line to end headers
77+ request_parts. push ( String :: new ( ) ) ; // CRLF to end request
78+
79+ let connect_request = request_parts. join ( "\r \n " ) ;
80+
81+ stream
82+ . write_all ( connect_request. as_bytes ( ) )
83+ . await
84+ . context ( format ! (
85+ "Failed to send CONNECT request to proxy at {proxy_url}"
86+ ) ) ?;
87+
88+ let mut response_buffer = Vec :: new ( ) ;
89+ let mut temp_buf = [ 0u8 ; 1024 ] ;
90+ let mut headers_complete = false ;
91+
92+ while !headers_complete {
93+ let n = stream. read ( & mut temp_buf) . await . context ( format ! (
94+ "Failed to read CONNECT response from proxy at {proxy_url}"
95+ ) ) ?;
96+
97+ if n == 0 {
98+ bail ! ( "Proxy closed connection before sending complete response" ) ;
99+ }
100+
101+ response_buffer. extend_from_slice ( temp_buf. get ( ..n) . context ( "Invalid buffer slice" ) ?) ;
102+
103+ if response_buffer. windows ( 4 ) . any ( |w| w == b"\r \n \r \n " ) {
104+ headers_complete = true ;
105+ }
106+ }
107+
108+ let response_str = String :: from_utf8_lossy ( & response_buffer) ;
109+
110+ let status_line = response_str
111+ . lines ( )
112+ . next ( )
113+ . context ( "Empty response from proxy" ) ?;
114+
115+ let parts: Vec < & str > = status_line. split_whitespace ( ) . collect ( ) ;
116+ if parts. len ( ) < 2 {
117+ bail ! (
118+ "Invalid HTTP response from proxy at {}: {}" ,
119+ proxy_url,
120+ status_line
121+ ) ;
122+ }
123+
124+ let status_code = parts
125+ . get ( 1 )
126+ . context ( "Missing status code in proxy response" ) ?
127+ . parse :: < u16 > ( )
128+ . context ( "Invalid status code in proxy response" ) ?;
129+
130+ if status_code != 200 {
131+ let status_text = parts
132+ . get ( 2 ..)
133+ . map ( |s| s. join ( " " ) )
134+ . unwrap_or_else ( || "Unknown" . to_string ( ) ) ;
135+ bail ! (
136+ "Proxy CONNECT failed with status {} {}: {}" ,
137+ status_code,
138+ status_text,
139+ status_line
140+ ) ;
141+ }
142+
143+ tracing:: info!( "Successfully connected through proxy at {}" , proxy_url) ;
144+
145+ let mut req = target_url. clone ( ) . into_client_request ( ) ?;
28146 let headers = req. headers_mut ( ) ;
29147 headers. insert (
30148 "Authorization" ,
31149 HeaderValue :: from_str ( & format ! ( "Bearer {token}" ) ) ?,
32150 ) ;
33- let ( ws_stream, _) = connect_async_with_config ( req, None , true ) . await ?;
34- tracing:: info!( "connected to the relayer at {}" , url) ;
151+
152+ let maybe_tls_stream = if target_url. scheme ( ) == "wss" {
153+ let tls_connector = tokio_native_tls:: native_tls:: TlsConnector :: builder ( )
154+ . build ( )
155+ . context ( "Failed to build TLS connector" ) ?;
156+ let tokio_connector = tokio_native_tls:: TlsConnector :: from ( tls_connector) ;
157+ let domain = target_host;
158+ let tls_stream = tokio_connector
159+ . connect ( domain, stream)
160+ . await
161+ . context ( "Failed to establish TLS connection" ) ?;
162+
163+ MaybeTlsStream :: NativeTls ( tls_stream)
164+ } else {
165+ MaybeTlsStream :: Plain ( stream)
166+ } ;
167+
168+ let ( ws_stream, _) = client_async ( req, maybe_tls_stream)
169+ . await
170+ . context ( "Failed to complete WebSocket handshake" ) ?;
171+
172+ tracing:: info!(
173+ "WebSocket connection established to relayer at {} via proxy {}" ,
174+ target_url,
175+ proxy_url
176+ ) ;
35177 Ok ( ws_stream. split ( ) )
36178}
37179
180+ async fn connect_to_relayer (
181+ url : Url ,
182+ token : & str ,
183+ proxy_url : Option < & Url > ,
184+ ) -> Result < ( RelayerWsSender , RelayerWsReceiver ) > {
185+ if let Some ( proxy) = proxy_url {
186+ connect_through_proxy ( proxy, & url, token) . await
187+ } else {
188+ tracing:: info!( "connecting to the relayer at {}" , url) ;
189+ let mut req = url. clone ( ) . into_client_request ( ) ?;
190+ let headers = req. headers_mut ( ) ;
191+ headers. insert (
192+ "Authorization" ,
193+ HeaderValue :: from_str ( & format ! ( "Bearer {token}" ) ) ?,
194+ ) ;
195+ let ( ws_stream, _) = connect_async_with_config ( req, None , true ) . await ?;
196+ tracing:: info!( "connected to the relayer at {}" , url) ;
197+ Ok ( ws_stream. split ( ) )
198+ }
199+ }
200+
38201struct RelayerWsSession {
39202 ws_sender : RelayerWsSender ,
40203}
@@ -58,11 +221,11 @@ impl RelayerWsSession {
58221}
59222
60223pub struct RelayerSessionTask {
61- // connection state
62224 pub url : Url ,
63225 pub token : String ,
64226 pub receiver : broadcast:: Receiver < SignedLazerTransaction > ,
65227 pub is_ready : Arc < AtomicBool > ,
228+ pub proxy_url : Option < Url > ,
66229}
67230
68231impl RelayerSessionTask {
@@ -108,10 +271,8 @@ impl RelayerSessionTask {
108271 }
109272
110273 pub async fn run_relayer_connection ( & mut self ) -> Result < ( ) > {
111- // Establish relayer connection
112- // Relayer will drop the connection if no data received in 5s
113274 let ( relayer_ws_sender, mut relayer_ws_receiver) =
114- connect_to_relayer ( self . url . clone ( ) , & self . token ) . await ?;
275+ connect_to_relayer ( self . url . clone ( ) , & self . token , self . proxy_url . as_ref ( ) ) . await ?;
115276 let mut relayer_ws_session = RelayerWsSession {
116277 ws_sender : relayer_ws_sender,
117278 } ;
@@ -236,11 +397,11 @@ mod tests {
236397 let ( relayer_sender, relayer_receiver) = broadcast:: channel ( RELAYER_CHANNEL_CAPACITY ) ;
237398
238399 let mut relayer_session_task = RelayerSessionTask {
239- // connection state
240400 url : Url :: parse ( "ws://127.0.0.1:12346" ) . unwrap ( ) ,
241401 token : "token1" . to_string ( ) ,
242402 receiver : relayer_receiver,
243403 is_ready : Arc :: new ( AtomicBool :: new ( false ) ) ,
404+ proxy_url : None ,
244405 } ;
245406 tokio:: spawn ( async move { relayer_session_task. run ( ) . await } ) ;
246407 tokio:: time:: sleep ( std:: time:: Duration :: from_millis ( 1000 ) ) . await ;
0 commit comments