@@ -101,53 +101,109 @@ fn next<T>(list: &[T]) -> &T {
101101 & list[ next_idx ( ) % list. len ( ) ]
102102}
103103
104- /// Returns the next _mainnet_ rpc endpoint in inline
104+ /// Returns the next _mainnet_ rpc URL in inline
105105///
106106/// This will rotate all available rpc endpoints
107107pub fn next_http_rpc_endpoint ( ) -> String {
108108 next_rpc_endpoint ( NamedChain :: Mainnet )
109109}
110110
111- /// Returns the next _mainnet_ rpc endpoint in inline
111+ /// Returns the next _mainnet_ rpc URL in inline
112112///
113113/// This will rotate all available rpc endpoints
114114pub fn next_ws_rpc_endpoint ( ) -> String {
115115 next_ws_endpoint ( NamedChain :: Mainnet )
116116}
117117
118- /// Returns the next HTTP RPC endpoint .
118+ /// Returns the next HTTP RPC URL .
119119pub fn next_rpc_endpoint ( chain : NamedChain ) -> String {
120120 next_url ( false , chain)
121121}
122122
123- /// Returns the next WS RPC endpoint .
123+ /// Returns the next WS RPC URL .
124124pub fn next_ws_endpoint ( chain : NamedChain ) -> String {
125125 next_url ( true , chain)
126126}
127127
128- /// Returns endpoint that has access to archive state
129- pub fn next_http_archive_rpc_endpoint ( ) -> String {
130- next_archive_endpoint ( false )
128+ /// Returns a websocket URL that has access to archive state
129+ pub fn next_http_archive_rpc_url ( ) -> String {
130+ next_archive_url ( false )
131131}
132132
133- /// Returns endpoint that has access to archive state
134- pub fn next_ws_archive_rpc_endpoint ( ) -> String {
135- next_archive_endpoint ( true )
133+ /// Returns an HTTP URL that has access to archive state
134+ pub fn next_ws_archive_rpc_url ( ) -> String {
135+ next_archive_url ( true )
136136}
137137
138- /// Returns endpoint that has access to archive state, http or ws.
139- /// Use env vars (comma separated urls) or default inline keys (Alchemy for ws, Infura for http).
140- fn next_archive_endpoint ( is_ws : bool ) -> String {
141- let env_urls = if is_ws { ENV_WS_ARCHIVE_ENDPOINTS } else { ENV_HTTP_ARCHIVE_ENDPOINTS } ;
142-
143- let rpc_env_vars = env:: var ( env_urls) . unwrap_or_default ( ) ;
144- if !rpc_env_vars. is_empty ( ) {
145- let urls = rpc_env_vars. split ( ',' ) . collect :: < Vec < & str > > ( ) ;
146- next ( & urls) . to_string ( )
147- } else if is_ws {
148- format ! ( "wss://eth-mainnet.g.alchemy.com/v2/{}" , next( & ALCHEMY_KEYS ) )
138+ /// Returns a URL that has access to archive state.
139+ ///
140+ /// Uses either environment variables (comma separated urls) or default keys.
141+ fn next_archive_url ( is_ws : bool ) -> String {
142+ let urls = archive_urls ( is_ws) ;
143+ let url = if env_archive_urls ( is_ws) . is_empty ( ) {
144+ next ( urls)
149145 } else {
150- format ! ( "https://eth-mainnet.g.alchemy.com/v2/{}" , next( & ALCHEMY_KEYS ) )
146+ urls. choose_weighted ( & mut rand:: thread_rng ( ) , |url| {
147+ if url. contains ( "reth" ) {
148+ 2usize
149+ } else {
150+ 1usize
151+ }
152+ } )
153+ . unwrap ( )
154+ } ;
155+ eprintln ! ( "--- next_archive_url(is_ws={is_ws}) = {url} ---" ) ;
156+ url. clone ( )
157+ }
158+
159+ fn archive_urls ( is_ws : bool ) -> & ' static [ String ] {
160+ static WS : LazyLock < Vec < String > > = LazyLock :: new ( || get ( true ) ) ;
161+ static HTTP : LazyLock < Vec < String > > = LazyLock :: new ( || get ( false ) ) ;
162+
163+ fn get ( is_ws : bool ) -> Vec < String > {
164+ let env_urls = env_archive_urls ( is_ws) ;
165+ if !env_urls. is_empty ( ) {
166+ let mut urls = env_urls. to_vec ( ) ;
167+ urls. shuffle ( & mut rand:: thread_rng ( ) ) ;
168+ return urls;
169+ }
170+
171+ let mut urls = Vec :: new ( ) ;
172+ for & key in ALCHEMY_KEYS . iter ( ) {
173+ if is_ws {
174+ urls. push ( format ! ( "wss://eth-mainnet.g.alchemy.com/v2/{key}" ) ) ;
175+ } else {
176+ urls. push ( format ! ( "https://eth-mainnet.g.alchemy.com/v2/{key}" ) ) ;
177+ }
178+ }
179+ urls
180+ }
181+
182+ if is_ws {
183+ & WS
184+ } else {
185+ & HTTP
186+ }
187+ }
188+
189+ fn env_archive_urls ( is_ws : bool ) -> & ' static [ String ] {
190+ static WS : LazyLock < Vec < String > > = LazyLock :: new ( || get ( true ) ) ;
191+ static HTTP : LazyLock < Vec < String > > = LazyLock :: new ( || get ( false ) ) ;
192+
193+ fn get ( is_ws : bool ) -> Vec < String > {
194+ let env = if is_ws { ENV_WS_ARCHIVE_ENDPOINTS } else { ENV_HTTP_ARCHIVE_ENDPOINTS } ;
195+ let env = env:: var ( env) . unwrap_or_default ( ) ;
196+ let env = env. trim ( ) ;
197+ if env. is_empty ( ) {
198+ return vec ! [ ] ;
199+ }
200+ env. split ( ',' ) . map ( str:: trim) . filter ( |s| !s. is_empty ( ) ) . map ( ToString :: to_string) . collect ( )
201+ }
202+
203+ if is_ws {
204+ & WS
205+ } else {
206+ & HTTP
151207 }
152208}
153209
@@ -162,7 +218,9 @@ pub fn next_etherscan_api_key(chain: NamedChain) -> String {
162218 Optimism => & ETHERSCAN_OPTIMISM_KEYS ,
163219 _ => & ETHERSCAN_MAINNET_KEYS ,
164220 } ;
165- next ( keys) . to_string ( )
221+ let key = next ( keys) . to_string ( ) ;
222+ eprintln ! ( "--- next_etherscan_api_key(chain={chain:?}) = {key} ---" ) ;
223+ key
166224}
167225
168226fn next_url ( is_ws : bool , chain : NamedChain ) -> String {
@@ -206,12 +264,14 @@ fn next_url(is_ws: bool, chain: NamedChain) -> String {
206264 } ;
207265 let full = if prefix. is_empty ( ) { network. to_string ( ) } else { format ! ( "{prefix}-{network}" ) } ;
208266
209- match ( is_ws, is_infura) {
267+ let url = match ( is_ws, is_infura) {
210268 ( false , true ) => format ! ( "https://{full}.infura.io/v3/{key}" ) ,
211269 ( true , true ) => format ! ( "wss://{full}.infura.io/ws/v3/{key}" ) ,
212270 ( false , false ) => format ! ( "https://{full}.g.alchemy.com/v2/{key}" ) ,
213271 ( true , false ) => format ! ( "wss://{full}.g.alchemy.com/v2/{key}" ) ,
214- }
272+ } ;
273+ eprintln ! ( "--- next_url(is_ws={is_ws}, chain={chain:?}) = {url} ---" ) ;
274+ url
215275}
216276
217277#[ cfg( test) ]
0 commit comments