Skip to main content

espresso_types/v0/impls/
l1.rs

1use std::{cmp::Ordering, sync::Arc};
2#[cfg(feature = "node")]
3use std::{cmp::min, num::NonZeroUsize, pin::Pin, result::Result as StdResult, time::Instant};
4
5#[cfg(feature = "node")]
6use alloy::{
7    eips::BlockId,
8    hex,
9    primitives::Address,
10    providers::{Provider, ProviderBuilder, WsConnect},
11    rpc::{
12        client::RpcClient,
13        json_rpc::{RequestPacket, ResponsePacket},
14    },
15    transports::{RpcError, TransportErrorKind, http::Http},
16};
17use alloy::{
18    primitives::{B256, U256},
19    rpc::types::Block,
20};
21#[cfg(feature = "node")]
22use anyhow::Context;
23#[cfg(feature = "node")]
24use async_trait::async_trait;
25use clap::Parser;
26use committable::{Commitment, Committable, RawCommitmentBuilder};
27#[cfg(feature = "node")]
28use futures::{
29    future::{Future, TryFuture, TryFutureExt},
30    stream::{self, StreamExt},
31};
32#[cfg(feature = "node")]
33use hotshot_contract_adapter::sol_types::FeeContract;
34use hotshot_types::traits::metrics::Metrics;
35#[cfg(feature = "node")]
36use lru::LruCache;
37#[cfg(feature = "node")]
38use parking_lot::RwLock;
39#[cfg(feature = "node")]
40use tokio::{
41    spawn,
42    sync::{Mutex, MutexGuard, Notify},
43    time::{Duration, sleep},
44};
45#[cfg(feature = "node")]
46use tower_service::Service;
47#[cfg(feature = "node")]
48use tracing::Instrument;
49#[cfg(feature = "node")]
50use url::Url;
51
52use super::{L1BlockInfo, v0_1::L1BlockInfoWithParent};
53#[cfg(feature = "node")]
54use super::{
55    L1ClientMetrics, L1State, L1UpdateTask,
56    v0_1::{SingleTransport, SingleTransportStatus, SwitchingTransport},
57};
58use crate::L1ClientOptions;
59#[cfg(feature = "node")]
60use crate::{FeeInfo, L1Client, L1Event, L1Snapshot};
61
62impl PartialOrd for L1BlockInfo {
63    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
64        Some(self.cmp(other))
65    }
66}
67
68impl Ord for L1BlockInfo {
69    fn cmp(&self, other: &Self) -> Ordering {
70        self.number.cmp(&other.number)
71    }
72}
73
74impl From<&Block> for L1BlockInfo {
75    fn from(block: &Block) -> Self {
76        Self {
77            number: block.header.number,
78            timestamp: U256::from(block.header.timestamp),
79            hash: block.header.hash,
80        }
81    }
82}
83
84impl From<&Block> for L1BlockInfoWithParent {
85    fn from(block: &Block) -> Self {
86        Self {
87            info: block.into(),
88            parent_hash: block.header.parent_hash,
89        }
90    }
91}
92
93impl Committable for L1BlockInfo {
94    fn commit(&self) -> Commitment<Self> {
95        let timestamp: [u8; 32] = self.timestamp.to_le_bytes();
96
97        RawCommitmentBuilder::new(&Self::tag())
98            .u64_field("number", self.number)
99            // `RawCommitmentBuilder` doesn't have a `u256_field` method, so we simulate it:
100            .constant_str("timestamp")
101            .fixed_size_bytes(&timestamp)
102            .constant_str("hash")
103            .fixed_size_bytes(&self.hash.0)
104            .finalize()
105    }
106
107    fn tag() -> String {
108        "L1BLOCK".into()
109    }
110}
111
112impl L1BlockInfo {
113    pub fn number(&self) -> u64 {
114        self.number
115    }
116
117    pub fn timestamp(&self) -> U256 {
118        self.timestamp
119    }
120
121    pub fn hash(&self) -> B256 {
122        self.hash
123    }
124}
125
126#[cfg(feature = "node")]
127impl Drop for L1UpdateTask {
128    fn drop(&mut self) {
129        if let Some(task) = self.0.get_mut().take() {
130            task.abort();
131        }
132    }
133}
134
135impl Default for L1ClientOptions {
136    fn default() -> Self {
137        Self::parse_from(std::iter::empty::<String>())
138    }
139}
140
141impl L1ClientOptions {
142    /// Use the given metrics collector to publish metrics related to the L1 client.
143    pub fn with_metrics(mut self, metrics: &(impl Metrics + ?Sized)) -> Self {
144        self.metrics = Arc::new(metrics.subgroup("l1".into()));
145        self
146    }
147
148    /// Instantiate an `L1Client` for a given list of provider `Url`s.
149    #[cfg(feature = "node")]
150    pub fn connect(self, urls: Vec<Url>) -> anyhow::Result<L1Client> {
151        // create custom transport
152        let t = SwitchingTransport::new(self, urls)
153            .with_context(|| "failed to create switching transport")?;
154        // Create a new L1 client with the transport
155        Ok(L1Client::with_transport(t))
156    }
157
158    #[cfg(feature = "node")]
159    fn rate_limit_delay(&self) -> Duration {
160        self.l1_rate_limit_delay.unwrap_or(self.l1_retry_delay)
161    }
162}
163
164#[cfg(feature = "node")]
165impl L1ClientMetrics {
166    fn new(metrics: &(impl Metrics + ?Sized), num_urls: usize) -> Self {
167        // Create a counter family for the failures per URL
168        let failures = metrics.counter_family("failed_requests".into(), vec!["provider".into()]);
169
170        // Create a counter for each URL
171        let mut failure_metrics = Vec::with_capacity(num_urls);
172        for url_index in 0..num_urls {
173            failure_metrics.push(failures.create(vec![url_index.to_string()]));
174        }
175
176        Self {
177            head: metrics.create_gauge("head".into(), None).into(),
178            finalized: metrics.create_gauge("finalized".into(), None).into(),
179            reconnects: metrics
180                .create_counter("stream_reconnects".into(), None)
181                .into(),
182            failovers: metrics.create_counter("failovers".into(), None).into(),
183            failures: Arc::new(failure_metrics),
184        }
185    }
186}
187
188#[cfg(feature = "node")]
189impl SwitchingTransport {
190    /// Create a new `SwitchingTransport` with the given options and URLs
191    pub fn new(opt: L1ClientOptions, urls: Vec<Url>) -> anyhow::Result<Self> {
192        // Return early if there were no URLs provided
193        let Some(first_url) = urls.first().cloned() else {
194            return Err(anyhow::anyhow!("No valid URLs provided"));
195        };
196
197        // Create the metrics
198        let metrics = L1ClientMetrics::new(&**opt.metrics, urls.len());
199
200        // Create a new `SingleTransport` for the first URL
201        let first_transport = Arc::new(RwLock::new(SingleTransport::new(&first_url, 0, None)));
202
203        Ok(Self {
204            urls: Arc::new(urls),
205            current_transport: first_transport,
206            opt: Arc::new(opt),
207            metrics,
208            switch_notify: Arc::new(Notify::new()),
209        })
210    }
211
212    /// Returns when the transport has been switched
213    async fn wait_switch(&self) {
214        self.switch_notify.notified().await;
215    }
216
217    fn options(&self) -> &L1ClientOptions {
218        &self.opt
219    }
220
221    fn metrics(&self) -> &L1ClientMetrics {
222        &self.metrics
223    }
224}
225
226#[cfg(feature = "node")]
227impl SingleTransportStatus {
228    /// Log a successful call to the inner transport
229    fn log_success(&mut self) {
230        self.consecutive_failures = 0;
231    }
232
233    /// Log a failure to call the inner transport. Returns whether or not the transport should be switched to the next URL
234    fn log_failure(&mut self, opt: &L1ClientOptions) -> bool {
235        // Increment the consecutive failures
236        self.consecutive_failures += 1;
237
238        // Check if we should switch to the next URL
239        let should_switch = self.should_switch(opt);
240
241        // Update the last failure time
242        self.last_failure = Some(Instant::now());
243
244        // Return whether or not we should switch
245        should_switch
246    }
247
248    /// Whether or not the transport should be switched to the next URL
249    fn should_switch(&mut self, opt: &L1ClientOptions) -> bool {
250        // If someone else already beat us to switching, return false
251        if self.shutting_down {
252            return false;
253        }
254
255        // If we've reached the max number of consecutive failures, switch to the next URL
256        if self.consecutive_failures >= opt.l1_consecutive_failure_tolerance {
257            self.shutting_down = true;
258            return true;
259        }
260
261        // If we've failed recently, switch to the next URL
262        let now = Instant::now();
263        if let Some(prev) = self.last_failure
264            && now.saturating_duration_since(prev) < opt.l1_frequent_failure_tolerance
265        {
266            self.shutting_down = true;
267            return true;
268        }
269
270        false
271    }
272
273    /// Whether or not the transport should be switched back to the primary URL.
274    fn should_revert(&mut self, revert_at: Option<Instant>) -> bool {
275        if self.shutting_down {
276            // We have already switched away from this transport in another thread.
277            return false;
278        }
279        let Some(revert_at) = revert_at else {
280            return false;
281        };
282        if Instant::now() >= revert_at {
283            self.shutting_down = true;
284            return true;
285        }
286
287        false
288    }
289}
290
291#[cfg(feature = "node")]
292impl SingleTransport {
293    /// Create a new `SingleTransport` with the given URL
294    fn new(url: &Url, generation: usize, revert_at: Option<Instant>) -> Self {
295        Self {
296            generation,
297            client: Http::new(url.clone()),
298            status: Default::default(),
299            revert_at,
300        }
301    }
302}
303
304/// `SwitchingTransport` is an alternative [`Client`](https://docs.rs/alloy/0.12.5/alloy/transports/http/struct.Client.html)
305/// which by implementing `tower_service::Service`, traits like [`Transport`](https://docs.rs/alloy/0.12.5/alloy/transports/trait.Transport.html)
306/// are auto-derived, thus can be used as an alt [`RpcClient`](https://docs.rs/alloy/0.12.5/alloy/rpc/client/struct.RpcClient.html#method.new)
307/// that can be further hooked with `Provider` via `Provider::on_client()`.
308#[cfg(feature = "node")]
309#[async_trait]
310impl Service<RequestPacket> for SwitchingTransport {
311    type Error = RpcError<TransportErrorKind>;
312    type Response = ResponsePacket;
313    type Future =
314        Pin<Box<dyn Future<Output = Result<ResponsePacket, RpcError<TransportErrorKind>>> + Send>>;
315
316    fn poll_ready(
317        &mut self,
318        cx: &mut std::task::Context<'_>,
319    ) -> std::task::Poll<StdResult<(), Self::Error>> {
320        // Just poll the (current) inner client
321        self.current_transport.read().clone().client.poll_ready(cx)
322    }
323
324    fn call(&mut self, req: RequestPacket) -> Self::Future {
325        // Clone ourselves
326        let self_clone = self.clone();
327
328        // Pin and box, which turns this into a future
329        Box::pin(async move {
330            // Clone the current transport
331            let mut current_transport = self_clone.current_transport.read().clone();
332
333            // Revert back to the primary transport if it's time.
334            let should_revert = current_transport
335                .status
336                .write()
337                .should_revert(current_transport.revert_at);
338            if should_revert {
339                // Switch to the next generation which maps to index 0.
340                let n = self_clone.urls.len();
341                // Rounding down to a multiple of n gives us the last generation of the primary transport.
342                let prev_primary_gen = (current_transport.generation / n) * n;
343                // Adding n jumps to the next generation.
344                let next_gen = prev_primary_gen + n;
345                current_transport = self_clone.switch_to(next_gen, current_transport);
346            }
347
348            // If we've been rate limited, back off until the limit (hopefully) expires.
349            let rate_limit_until = current_transport.status.read().rate_limited_until;
350            if let Some(t) = rate_limit_until {
351                if t > Instant::now() {
352                    // Return an error with a non-standard code to indicate client-side rate limit.
353                    return Err(RpcError::Transport(TransportErrorKind::Custom(
354                        "Rate limit exceeded".into(),
355                    )));
356                } else {
357                    // Reset the rate limit if we are passed it so we don't check every time
358                    current_transport.status.write().rate_limited_until = None;
359                }
360            }
361
362            // Call the inner client, match on the result
363            match current_transport.client.call(req).await {
364                Ok(res) => {
365                    // If it's okay, log the success to the status
366                    current_transport.status.write().log_success();
367                    Ok(res)
368                },
369                Err(err) => {
370                    // Increment the failure metric
371                    if let Some(f) = self_clone
372                        .metrics
373                        .failures
374                        .get(current_transport.generation % self_clone.urls.len())
375                    {
376                        f.add(1);
377                    }
378
379                    // Treat rate limited errors specially; these should not cause failover, but instead
380                    // should only cause us to temporarily back off on making requests to the RPC
381                    // server.
382                    if let RpcError::ErrorResp(e) = &err {
383                        // 429 == Too Many Requests
384                        if e.code == 429 {
385                            current_transport.status.write().rate_limited_until =
386                                Some(Instant::now() + self_clone.opt.rate_limit_delay());
387                            return Err(err);
388                        }
389                    }
390
391                    // Log the error and indicate a failure
392                    tracing::warn!(?err, "L1 client error");
393
394                    // If the transport should switch, do so. We don't need to worry about
395                    // race conditions here, since it will only return true once.
396                    if current_transport
397                        .status
398                        .write()
399                        .log_failure(&self_clone.opt)
400                    {
401                        // Increment the failovers metric
402                        self_clone.metrics.failovers.add(1);
403                        self_clone.switch_to(current_transport.generation + 1, current_transport);
404                    }
405
406                    Err(err)
407                },
408            }
409        })
410    }
411}
412
413#[cfg(feature = "node")]
414impl SwitchingTransport {
415    fn switch_to(&self, next_gen: usize, current_transport: SingleTransport) -> SingleTransport {
416        let next_index = next_gen % self.urls.len();
417        let url = self.urls[next_index].clone();
418        tracing::info!(%url, next_gen, "switch L1 transport");
419
420        let revert_at = if next_gen.is_multiple_of(self.urls.len()) {
421            // If we are reverting to the primary transport, clear our scheduled revert time.
422            None
423        } else if current_transport.generation.is_multiple_of(self.urls.len()) {
424            // If we are failing over from the primary transport, schedule a time to automatically
425            // revert back.
426            Some(Instant::now() + self.opt.l1_failover_revert)
427        } else {
428            // Otherwise keep the currently scheduled revert time.
429            current_transport.revert_at
430        };
431
432        // Create a new transport from the next URL and index
433        let new_transport = SingleTransport::new(&url, next_gen, revert_at);
434
435        // Switch to the next URL
436        *self.current_transport.write() = new_transport.clone();
437
438        // Notify the transport that it has been switched
439        self.switch_notify.notify_waiters();
440
441        new_transport
442    }
443}
444
445#[cfg(feature = "node")]
446impl L1Client {
447    fn with_transport(transport: SwitchingTransport) -> Self {
448        // Create a new provider with that RPC client using the custom transport
449        let rpc_client = RpcClient::new(transport.clone(), false);
450        let provider = ProviderBuilder::new().connect_client(rpc_client);
451
452        let opt = transport.options().clone();
453
454        let (sender, mut receiver) = async_broadcast::broadcast(opt.l1_events_channel_capacity);
455        receiver.set_await_active(false);
456        receiver.set_overflow(true);
457
458        Self {
459            provider,
460            transport,
461            state: Arc::new(Mutex::new(L1State::new(opt.l1_blocks_cache_size))),
462            sender,
463            receiver: receiver.deactivate(),
464            update_task: Default::default(),
465        }
466    }
467
468    /// Construct a new L1 client with the default options.
469    pub fn new(url: Vec<Url>) -> anyhow::Result<Self> {
470        L1ClientOptions::default().connect(url)
471    }
472
473    /// test only
474    pub fn anvil(anvil: &alloy::node_bindings::AnvilInstance) -> anyhow::Result<Self> {
475        L1ClientOptions {
476            l1_ws_provider: Some(vec![anvil.ws_endpoint().parse()?]),
477            ..Default::default()
478        }
479        .connect(vec![anvil.endpoint().parse()?])
480    }
481
482    /// Start the background tasks which keep the L1 client up to date.
483    pub async fn spawn_tasks(&self) {
484        let mut update_task = self.update_task.0.lock().await;
485        if update_task.is_none() {
486            *update_task = Some(spawn(self.update_loop()));
487        }
488    }
489
490    /// Shut down background tasks associated with this L1 client.
491    ///
492    /// The L1 client will still be usable, but will stop updating until [`start`](Self::start) is
493    /// called again.
494    pub async fn shut_down_tasks(&self) {
495        let update_task = self.update_task.0.lock().await.take();
496        if let Some(update_task) = update_task {
497            update_task.abort();
498        }
499    }
500
501    fn update_loop(&self) -> impl Future<Output = ()> + use<> {
502        let opt = self.options().clone();
503        let rpc = self.provider.clone();
504        let ws_urls = opt.l1_ws_provider.clone();
505        let retry_delay = opt.l1_retry_delay;
506        let subscription_timeout = opt.subscription_timeout;
507        let state = self.state.clone();
508        let sender = self.sender.clone();
509        let metrics = self.metrics().clone();
510        let polling_interval = opt.l1_polling_interval;
511        let transport = self.transport.clone();
512
513        let span = tracing::warn_span!("L1 client update");
514
515        async move {
516
517            for i in 0.. {
518                let ws;
519
520                // Fetch current L1 head block for the first value of the stream to avoid having
521                // to wait for new L1 blocks until the update loop starts processing blocks.
522                let l1_head = loop {
523                    match rpc.get_block(BlockId::latest()).await {
524                        Ok(Some(block)) => break block.header,
525                        Ok(None) => {
526                            tracing::info!("Failed to fetch L1 head block, will retry");
527                        },
528                        Err(err) => {
529                            tracing::info!("Failed to fetch L1 head block, will retry: err {err}");
530                        }
531                    }
532                    sleep(retry_delay).await;
533                };
534
535                // Subscribe to new blocks.
536                let mut block_stream = {
537                    let res = match &ws_urls {
538                        Some(urls) => {
539                            // Use a new WebSockets host each time we retry in case there is a
540                            // problem with one of the hosts specifically.
541                            let provider = i % urls.len();
542                            let url = &urls[provider];
543                            ws = match ProviderBuilder::new().connect_ws(WsConnect::new(url.clone())).await {
544                                Ok(ws) => ws,
545                                Err(err) => {
546                                    tracing::warn!(provider, "Failed to connect WebSockets provider: {err:#}");
547                                    sleep(retry_delay).await;
548                                    continue;
549                                }
550                            };
551                            ws.subscribe_blocks().await.map(|stream| {stream::once(async { l1_head.clone() }).chain(stream.into_stream()).boxed()})
552                        },
553                        None => {
554                           rpc
555                            .watch_blocks()
556                            .await
557                            .map(|poller_builder| {
558                                // Configure it and get the stream
559                                let stream = poller_builder.with_poll_interval(polling_interval).into_stream();
560
561                                let rpc = rpc.clone();
562
563                                // For HTTP, we simulate a subscription by polling. The polling
564                                // stream provided by ethers only yields block hashes, so for each
565                                // one, we have to go fetch the block itself.
566                                stream::once(async { l1_head.clone() })
567                                 .chain(
568                                    stream.map(stream::iter).flatten().filter_map(move |hash| {
569                                        let rpc = rpc.clone();
570                                        async move {
571                                            match rpc.get_block(BlockId::hash(hash)).await {
572                                                Ok(Some(block)) => Some(block.header),
573                                                // If we can't fetch the block for some reason, we can
574                                                // just skip it.
575                                                Ok(None) => {
576                                                    tracing::warn!(%hash, "HTTP stream yielded a block hash that was not available");
577                                                    None
578                                                }
579                                                Err(err) => {
580                                                    tracing::warn!(%hash, "Error fetching block from HTTP stream: {err:#}");
581                                                    None
582                                                }
583                                            }
584                                        }
585                                    }))
586                                // Take until the transport is switched, so we will call `watch_blocks` instantly on it
587                            }.take_until(transport.wait_switch())
588                            .boxed())
589                        }
590                    };
591                    match res {
592                        Ok(stream) => stream,
593                        Err(err) => {
594                            tracing::error!("Error subscribing to L1 blocks: {err:#}");
595                            sleep(retry_delay).await;
596                            continue;
597                        }
598                    }
599                };
600
601                tracing::info!("Established L1 block stream");
602                loop {
603                    // Wait for a block, timing out if we don't get one soon enough
604                    let block_timeout = tokio::time::timeout(subscription_timeout, block_stream.next()).await;
605                    match block_timeout {
606                        // We got a block
607                        Ok(Some(head)) => {
608                            let head = head.number;
609                            tracing::debug!(head, "Received L1 block");
610
611                            // A new block has been produced. This happens fairly rarely, so it is now ok to
612                            // poll to see if a new block has been finalized.
613                            let finalized = loop {
614                                match fetch_finalized_block_from_rpc(&rpc).await {
615                                    Ok(finalized) => break finalized,
616                                    Err(err) => {
617                                        tracing::warn!("Error getting finalized block: {err:#}");
618                                        sleep(retry_delay).await;
619                                    }
620                                }
621                            };
622
623                            // Update the state snapshot;
624                            let mut state = state.lock().await;
625                            if head > state.snapshot.head {
626                                tracing::debug!(head, old_head = state.snapshot.head, "L1 head updated");
627                                metrics.head.set(head as usize);
628                                state.snapshot.head = head;
629                                // Emit an event about the new L1 head. Ignore send errors; it just means no
630                                // one is listening to events right now.
631                                sender
632                                    .broadcast_direct(L1Event::NewHead { head })
633                                    .await
634                                    .ok();
635                            }
636                            if let Some(finalized) = finalized
637                                && Some(finalized.info) > state.snapshot.finalized {
638                                    tracing::info!(
639                                        ?finalized,
640                                        old_finalized = ?state.snapshot.finalized,
641                                        "L1 finalized updated",
642                                    );
643                                    metrics.finalized.set(finalized.info.number as usize);
644                                    state.snapshot.finalized = Some(finalized.info);
645                                    state.put_finalized(finalized);
646                                    sender
647                                        .broadcast_direct(L1Event::NewFinalized { finalized })
648                                        .await
649                                        .ok();
650                                }
651                            tracing::debug!("Updated L1 snapshot to {:?}", state.snapshot);
652                        }
653                        // The stream ended
654                        Ok(None) => {
655                            tracing::error!("L1 block stream ended unexpectedly, trying to re-establish block stream");
656                            break;
657                        }
658                        // We timed out waiting for a block
659                        Err(_) => {
660                            tracing::error!("No block received for {} seconds, trying to re-establish block stream", subscription_timeout.as_secs());
661                            break;
662                        }
663                    }
664                }
665
666                metrics.reconnects.add(1);
667            }
668        }.instrument(span)
669    }
670
671    /// Get a snapshot from the l1.
672    pub async fn snapshot(&self) -> L1Snapshot {
673        self.state.lock().await.snapshot
674    }
675
676    /// Wait until the highest L1 block number reaches at least `number`.
677    ///
678    /// This function does not return any information about the block, since the block is not
679    /// necessarily finalized when it returns. It is only used to guarantee that some block at
680    /// height `number` exists, possibly in the unsafe part of the L1 chain.
681    pub async fn wait_for_block(&self, number: u64) {
682        loop {
683            // Subscribe to events before checking the current state, to ensure we don't miss a
684            // relevant event.
685            let mut events = self.receiver.activate_cloned();
686
687            // Check if the block we are waiting for already exists.
688            {
689                let state = self.state.lock().await;
690                if state.snapshot.head >= number {
691                    return;
692                }
693                tracing::info!(number, head = state.snapshot.head, "Waiting for l1 block");
694            }
695
696            // Wait for the block.
697            while let Some(event) = events.next().await {
698                let L1Event::NewHead { head } = event else {
699                    continue;
700                };
701                if head >= number {
702                    tracing::info!(number, head, "Got L1 block");
703                    return;
704                }
705                tracing::debug!(number, head, "Waiting for L1 block");
706            }
707
708            // This should not happen: the event stream ended. All we can do is try again.
709            tracing::warn!(number, "L1 event stream ended unexpectedly; retry");
710            self.retry_delay().await;
711        }
712    }
713
714    /// Get information about the given block.
715    ///
716    /// If the desired block number is not finalized yet, this function will block until it becomes
717    /// finalized.
718    pub async fn wait_for_finalized_block(&self, number: u64) -> L1BlockInfo {
719        loop {
720            // Subscribe to events before checking the current state, to ensure we don't miss a relevant
721            // event.
722            let mut events = self.receiver.activate_cloned();
723
724            // Check if the block we are waiting for already exists.
725            {
726                let state = self.state.lock().await;
727                if let Some(finalized) = state.snapshot.finalized {
728                    if finalized.number >= number {
729                        return self.fetch_finalized_block_by_number(state, number).await.1;
730                    }
731                    tracing::info!(
732                        number,
733                        finalized = ?state.snapshot.finalized,
734                        "waiting for l1 finalized block",
735                    );
736                };
737            }
738
739            // Wait for the block.
740            while let Some(event) = events.next().await {
741                let L1Event::NewFinalized { finalized } = event else {
742                    continue;
743                };
744                let mut state = self.state.lock().await;
745                state.put_finalized(finalized);
746                if finalized.info.number >= number {
747                    tracing::info!(number, ?finalized, "got finalized L1 block");
748                    return self.fetch_finalized_block_by_number(state, number).await.1;
749                }
750                tracing::debug!(number, ?finalized, "waiting for finalized L1 block");
751            }
752
753            // This should not happen: the event stream ended. All we can do is try again.
754            tracing::warn!(number, "L1 event stream ended unexpectedly; retry",);
755            self.retry_delay().await;
756        }
757    }
758
759    /// Get information about the first finalized block with timestamp greater than or equal
760    /// `timestamp`.
761    pub async fn wait_for_finalized_block_with_timestamp(&self, timestamp: U256) -> L1BlockInfo {
762        // Wait until the finalized block has timestamp >= `timestamp`.
763        let (mut state, mut block) = 'outer: loop {
764            // Subscribe to events before checking the current state, to ensure we don't miss a
765            // relevant event.
766            let mut events = self.receiver.activate_cloned();
767
768            // Check if the block we are waiting for already exists.
769            {
770                let state = self.state.lock().await;
771                if let Some(finalized) = state.snapshot.finalized
772                    && finalized.timestamp >= timestamp
773                {
774                    break 'outer (state, finalized);
775                }
776                tracing::info!(
777                    %timestamp,
778                    finalized = ?state.snapshot.finalized,
779                    "waiting for L1 finalized block",
780                );
781            }
782
783            // Wait for the block.
784            while let Some(event) = events.next().await {
785                let L1Event::NewFinalized { finalized } = event else {
786                    continue;
787                };
788                if finalized.info.timestamp >= timestamp {
789                    tracing::info!(%timestamp, ?finalized, "got finalized block");
790                    break 'outer (self.state.lock().await, finalized.info);
791                }
792                tracing::debug!(%timestamp, ?finalized, "waiting for L1 finalized block");
793            }
794
795            // This should not happen: the event stream ended. All we can do is try again.
796            tracing::warn!(%timestamp, "L1 event stream ended unexpectedly; retry",);
797            self.retry_delay().await;
798        };
799
800        // It is possible there is some earlier block that also has the proper timestamp. Binary
801        // search until we find the true earliest block with timestamp >= `timestamp`.
802        //
803        // Invariants:
804        // * `upper_bound <= lower_bound`
805        // * `upper_bound = block.number`
806        // * Block number `lower_bound - 1` has timestamp < `timestamp` (strictly)
807        // * `block` has timestamp >= `timestamp`
808        let mut upper_bound = block.number;
809        let mut lower_bound = 0;
810        while lower_bound < upper_bound {
811            let midpoint = (upper_bound + lower_bound) / 2;
812            tracing::debug!(
813                lower_bound,
814                midpoint,
815                upper_bound,
816                %timestamp,
817                ?block,
818                "searching for earliest block with sufficient timestamp"
819            );
820
821            let (state_lock, midpoint_block) =
822                self.fetch_finalized_block_by_number(state, midpoint).await;
823            state = state_lock;
824
825            tracing::debug!(?midpoint_block, %timestamp, "pivot on midpoint block");
826            if midpoint_block.timestamp < timestamp {
827                lower_bound = midpoint + 1;
828            } else {
829                upper_bound = midpoint;
830                block = midpoint_block;
831            }
832        }
833
834        block
835    }
836
837    async fn fetch_finalized_block_by_number<'a>(
838        &'a self,
839        mut state: MutexGuard<'a, L1State>,
840        number: u64,
841    ) -> (MutexGuard<'a, L1State>, L1BlockInfo) {
842        let latest_finalized = state
843            .snapshot
844            .finalized
845            .expect("get_finalized_block called before any blocks are finalized");
846        assert!(
847            number <= latest_finalized.number,
848            "requesting a finalized block {number} that isn't finalized; snapshot: {:?}",
849            state.snapshot,
850        );
851
852        if let Some(safety_margin) = self.options().l1_finalized_safety_margin
853            && number < latest_finalized.number.saturating_sub(safety_margin)
854        {
855            // If the requested block height is so old that we can assume all L1 providers have
856            // finalized it, we don't need to worry about failing over to a lagging L1 provider
857            // which has yet to finalize the block, so we don't need to bother with the
858            // expensive hash chaining logic below. Just look up the block by number and assume
859            // the response is finalized.
860            tracing::debug!(
861                number,
862                ?latest_finalized,
863                "skipping hash check for old finalized block"
864            );
865            let (state, block) = self
866                .load_and_cache_finalized_block(state, number.into())
867                .await;
868            return (state, block.info);
869        }
870
871        // To get this block and be sure we are getting the correct finalized block, we first need
872        // to find an equal or later block so we can find the expected hash of this block. If we
873        // were to just look up the block by number, there could be problems if we failed over to a
874        // different (lagging) L1 provider, which has yet to finalize this block and reports a
875        // different block with the same number.
876        let mut successor_number = number;
877        let mut successor = loop {
878            if let Some(block) = state.finalized.get(&successor_number) {
879                break *block;
880            }
881            successor_number += 1;
882            if successor_number > latest_finalized.number {
883                // We don't have any cached finalized block after the requested one; fetch the
884                // current finalized block from the network.
885                // Don't hold state lock while fetching from network.
886                drop(state);
887                let block = loop {
888                    match fetch_finalized_block_from_rpc(&self.provider).await {
889                        Ok(Some(block)) => {
890                            break block;
891                        },
892                        Ok(None) => {
893                            tracing::warn!(
894                                "no finalized block even though finalized snapshot is Some; this \
895                                 can be caused by an L1 client failover"
896                            );
897                            self.retry_delay().await;
898                        },
899                        Err(err) => {
900                            tracing::warn!("Error getting finalized block: {err:#}");
901                            self.retry_delay().await;
902                        },
903                    }
904                };
905                state = self.state.lock().await;
906                state.put_finalized(block);
907                break block;
908            }
909        };
910
911        // Work backwards from the known finalized successor, fetching blocks by parent hash so we
912        // know we are getting the correct block.
913        while successor.info.number > number {
914            tracing::debug!(
915                number,
916                ?successor,
917                "checking hash chaining for finalized block"
918            );
919            (state, successor) = self
920                .load_and_cache_finalized_block(state, successor.parent_hash.into())
921                .await;
922        }
923
924        (state, successor.info)
925    }
926
927    async fn load_and_cache_finalized_block<'a>(
928        &'a self,
929        mut state: MutexGuard<'a, L1State>,
930        id: BlockId,
931    ) -> (MutexGuard<'a, L1State>, L1BlockInfoWithParent) {
932        // Don't hold state lock while fetching from network.
933        drop(state);
934        let block = loop {
935            let block = match self.provider.get_block(id).await {
936                Ok(Some(block)) => block,
937                Ok(None) => {
938                    tracing::warn!(
939                        %id,
940                        "provider error: finalized L1 block should always be available"
941                    );
942                    self.retry_delay().await;
943                    continue;
944                },
945                Err(err) => {
946                    tracing::warn!(%id, "failed to get finalized L1 block: {err:#}");
947                    self.retry_delay().await;
948                    continue;
949                },
950            };
951            break (&block).into();
952        };
953        state = self.state.lock().await;
954        state.put_finalized(block);
955        (state, block)
956    }
957
958    /// Get fee info for each `Deposit` occurring between `prev`
959    /// and `new`. Returns `Vec<FeeInfo>`
960    pub async fn get_finalized_deposits(
961        &self,
962        fee_contract_address: Address,
963        prev_finalized: Option<u64>,
964        new_finalized: u64,
965    ) -> Vec<FeeInfo> {
966        // No new blocks have been finalized, therefore there are no
967        // new deposits.
968        if prev_finalized >= Some(new_finalized) {
969            return vec![];
970        }
971
972        let opt = self.options();
973
974        // `prev` should have already been processed unless we
975        // haven't processed *any* blocks yet.
976        let prev = prev_finalized.map(|prev| prev + 1).unwrap_or(0);
977
978        // Divide the range `prev_finalized..=new_finalized` into chunks of size
979        // `events_max_block_range`.
980        let mut start = prev;
981        let end = new_finalized;
982        let chunk_size = opt.l1_events_max_block_range;
983        let chunks = std::iter::from_fn(move || {
984            let chunk_end = min(start + chunk_size - 1, end);
985            if chunk_end < start {
986                return None;
987            }
988
989            let chunk = (start, chunk_end);
990            start = chunk_end + 1;
991            Some(chunk)
992        });
993
994        // Fetch events for each chunk.
995        let events = stream::iter(chunks).then(|(from, to)| {
996            let retry_delay = opt.l1_retry_delay;
997            let fee_contract = FeeContract::new(fee_contract_address, self.provider.clone());
998            async move {
999                tracing::debug!(from, to, "fetch events in range");
1000
1001                // query for deposit events, loop until successful.
1002                loop {
1003                    match fee_contract
1004                        .Deposit_filter()
1005                        .address(*fee_contract.address())
1006                        .from_block(from)
1007                        .to_block(to)
1008                        .query()
1009                        .await
1010                    {
1011                        Ok(events) => break stream::iter(events),
1012                        Err(err) => {
1013                            tracing::warn!(from, to, %err, "Fee L1Event Error");
1014                            sleep(retry_delay).await;
1015                        },
1016                    }
1017                }
1018            }
1019        });
1020        events
1021            .flatten()
1022            .map(|(deposit, _)| FeeInfo::from(deposit))
1023            .collect()
1024            .await
1025    }
1026
1027    /// Check if the given address is a proxy contract.
1028    pub async fn is_proxy_contract(&self, proxy_address: Address) -> anyhow::Result<bool> {
1029        // confirm that the proxy_address is a proxy
1030        // using the implementation slot, 0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc, which is the keccak-256 hash of "eip1967.proxy.implementation" subtracted by 1
1031        let hex_bytes =
1032            hex::decode("360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc")
1033                .expect("Failed to decode hex string");
1034        let implementation_slot = B256::from_slice(&hex_bytes);
1035        let storage = self
1036            .provider
1037            .get_storage_at(proxy_address, implementation_slot.into())
1038            .await?;
1039
1040        let implementation_address = Address::from_slice(&storage.to_be_bytes::<32>()[12..]);
1041
1042        // when the implementation address is not equal to zero, it's a proxy
1043        Ok(implementation_address != Address::ZERO)
1044    }
1045
1046    pub async fn retry_on_all_providers<Fut>(
1047        &self,
1048        op: impl Fn() -> Fut,
1049    ) -> Result<Fut::Ok, Fut::Error>
1050    where
1051        Fut: TryFuture,
1052    {
1053        let transport = &self.transport;
1054        let start = transport.current_transport.read().generation % transport.urls.len();
1055        let end = start + transport.urls.len();
1056        loop {
1057            match TryFutureExt::into_future(op()).await {
1058                Ok(res) => return Ok(res),
1059                Err(err) => {
1060                    if transport.current_transport.read().generation >= end {
1061                        return Err(err);
1062                    } else {
1063                        self.retry_delay().await;
1064                    }
1065                },
1066            }
1067        }
1068    }
1069
1070    pub(crate) fn options(&self) -> &L1ClientOptions {
1071        self.transport.options()
1072    }
1073
1074    fn metrics(&self) -> &L1ClientMetrics {
1075        self.transport.metrics()
1076    }
1077
1078    async fn retry_delay(&self) {
1079        sleep(self.options().l1_retry_delay).await;
1080    }
1081}
1082
1083#[cfg(feature = "node")]
1084impl L1State {
1085    fn new(cache_size: NonZeroUsize) -> Self {
1086        Self {
1087            snapshot: Default::default(),
1088            finalized: LruCache::new(cache_size),
1089            last_finalized: None,
1090        }
1091    }
1092
1093    fn put_finalized(&mut self, block: L1BlockInfoWithParent) {
1094        assert!(
1095            self.snapshot.finalized.is_some()
1096                && block.info.number <= self.snapshot.finalized.unwrap().number,
1097            "inserting a finalized block {block:?} that isn't finalized; snapshot: {:?}",
1098            self.snapshot,
1099        );
1100
1101        if Some(block.info.number()) > self.last_finalized {
1102            self.last_finalized = Some(block.info.number());
1103        }
1104
1105        if let Some((old_number, old_block)) = self.finalized.push(block.info.number, block)
1106            && old_number == block.info.number
1107            && block != old_block
1108        {
1109            tracing::error!(
1110                ?old_block,
1111                ?block,
1112                "got different info for the same finalized height; something has gone very wrong \
1113                 with the L1",
1114            );
1115        }
1116    }
1117}
1118
1119#[cfg(feature = "node")]
1120async fn fetch_finalized_block_from_rpc(
1121    rpc: &impl Provider,
1122) -> anyhow::Result<Option<L1BlockInfoWithParent>> {
1123    let Some(block) = rpc.get_block(BlockId::finalized()).await? else {
1124        // This can happen in rare cases where the L1 chain is very young and has not finalized a
1125        // block yet. This is more common in testing and demo environments. In any case, we proceed
1126        // with a null L1 block rather than wait for the L1 to finalize a block, which can take a
1127        // long time.
1128        tracing::warn!("no finalized block yet");
1129        return Ok(None);
1130    };
1131
1132    Ok(Some((&block).into()))
1133}
1134
1135#[cfg(test)]
1136mod test {
1137    use std::{ops::Add, time::Duration};
1138
1139    use alloy::{
1140        eips::BlockNumberOrTag,
1141        node_bindings::{Anvil, AnvilInstance},
1142        primitives::utils::parse_ether,
1143        providers::layers::AnvilProvider,
1144    };
1145    use espresso_contract_deployer::{Contracts, deploy_fee_contract_proxy};
1146    use time::OffsetDateTime;
1147
1148    use super::*;
1149
1150    async fn new_l1_client_opt(
1151        anvil: &Arc<AnvilInstance>,
1152        f: impl FnOnce(&mut L1ClientOptions),
1153    ) -> L1Client {
1154        let mut opt = L1ClientOptions {
1155            l1_events_max_block_range: 1,
1156            l1_polling_interval: Duration::from_secs(1),
1157            subscription_timeout: Duration::from_secs(5),
1158            ..Default::default()
1159        };
1160        f(&mut opt);
1161
1162        let l1_client = opt
1163            .connect(vec![anvil.endpoint_url()])
1164            .expect("Failed to create L1 client");
1165
1166        l1_client.spawn_tasks().await;
1167        l1_client
1168    }
1169
1170    async fn new_l1_client(anvil: &Arc<AnvilInstance>, include_ws: bool) -> L1Client {
1171        new_l1_client_opt(anvil, |opt| {
1172            if include_ws {
1173                opt.l1_ws_provider = Some(vec![anvil.ws_endpoint_url()]);
1174            }
1175        })
1176        .await
1177    }
1178
1179    #[test_log::test(tokio::test(flavor = "multi_thread"))]
1180    async fn test_get_finalized_deposits() -> anyhow::Result<()> {
1181        let num_deposits = 5;
1182
1183        let anvil = Anvil::new().spawn();
1184        let wallet = anvil.wallet().unwrap();
1185        let deployer = wallet.default_signer().address();
1186        let inner_provider = ProviderBuilder::new()
1187            .wallet(wallet)
1188            .connect_http(anvil.endpoint_url());
1189        // a provider that holds both anvil (to avoid accidental drop) and wallet-enabled L1 provider
1190        let provider = AnvilProvider::new(inner_provider, Arc::new(anvil));
1191        // cache store for deployed contracts
1192        let mut contracts = Contracts::new();
1193
1194        // init and kick off the L1Client which wraps around standard L1 provider with more app-specific state management
1195        let l1_client = new_l1_client(provider.anvil(), false).await;
1196
1197        // Initialize a contract with some deposits
1198        let fee_proxy_addr = deploy_fee_contract_proxy(&provider, &mut contracts, deployer).await?;
1199        let fee_proxy = FeeContract::new(fee_proxy_addr, &provider);
1200        let num_tx_for_deploy = provider.get_block_number().await?;
1201
1202        // make some deposits.
1203        for n in 1..=num_deposits {
1204            // Varied amounts are less boring.
1205            let amount = n as f32 / 10.0;
1206            let receipt = fee_proxy
1207                .deposit(deployer)
1208                .value(parse_ether(&amount.to_string())?)
1209                .send()
1210                .await?
1211                .get_receipt()
1212                .await?;
1213            assert!(receipt.inner.is_success());
1214        }
1215
1216        let cur_height = provider.get_block_number().await?;
1217        assert_eq!(num_deposits + num_tx_for_deploy, cur_height);
1218
1219        // Set prev deposits to `None` so `Filter` will start at block
1220        // 0. The test would also succeed if we pass `0` (b/c first
1221        // block did not deposit).
1222        let pending = l1_client
1223            .get_finalized_deposits(fee_proxy_addr, None, cur_height)
1224            .await;
1225
1226        assert_eq!(num_deposits as usize, pending.len(), "{pending:?}");
1227        assert_eq!(deployer, pending[0].account().0);
1228        assert_eq!(
1229            U256::from(1500000000000000000u64),
1230            pending
1231                .iter()
1232                .fold(U256::from(0), |total, info| total.add(info.amount().0))
1233        );
1234
1235        // check a few more cases
1236        let pending = l1_client
1237            .get_finalized_deposits(fee_proxy_addr, Some(0), cur_height)
1238            .await;
1239        assert_eq!(num_deposits as usize, pending.len());
1240
1241        let pending = l1_client
1242            .get_finalized_deposits(fee_proxy_addr, Some(0), 0)
1243            .await;
1244        assert_eq!(0, pending.len());
1245
1246        let pending = l1_client
1247            .get_finalized_deposits(fee_proxy_addr, Some(0), 1)
1248            .await;
1249        assert_eq!(0, pending.len());
1250
1251        let pending = l1_client
1252            .get_finalized_deposits(fee_proxy_addr, Some(num_tx_for_deploy), num_tx_for_deploy)
1253            .await;
1254        assert_eq!(0, pending.len());
1255
1256        let pending = l1_client
1257            .get_finalized_deposits(
1258                fee_proxy_addr,
1259                Some(num_tx_for_deploy),
1260                num_tx_for_deploy + 1,
1261            )
1262            .await;
1263        assert_eq!(1, pending.len());
1264
1265        // what happens if `new_finalized` is `0`?
1266        let pending = l1_client
1267            .get_finalized_deposits(fee_proxy_addr, Some(num_tx_for_deploy), 0)
1268            .await;
1269        assert_eq!(0, pending.len());
1270
1271        Ok(())
1272    }
1273
1274    async fn test_wait_for_finalized_block_helper(ws: bool) {
1275        let anvil = Arc::new(Anvil::new().block_time_f64(0.1).spawn());
1276        let l1_client = new_l1_client(&anvil, ws).await;
1277        let provider = &l1_client.provider;
1278
1279        // Wait for a block 10 blocks in the future.
1280        let block_height = provider.get_block_number().await.unwrap();
1281        let block = l1_client.wait_for_finalized_block(block_height + 10).await;
1282        assert_eq!(block.number, block_height + 10);
1283
1284        // Compare against underlying provider.
1285        let true_block = provider
1286            .get_block(BlockId::Number(BlockNumberOrTag::Number(block_height + 10)))
1287            .full()
1288            .await
1289            .unwrap()
1290            .unwrap();
1291
1292        assert_eq!(
1293            block.timestamp.to::<u64>(),
1294            true_block.header.inner.timestamp
1295        );
1296        assert_eq!(block.hash, true_block.header.hash);
1297    }
1298
1299    #[test_log::test(tokio::test(flavor = "multi_thread"))]
1300    async fn test_wait_for_finalized_block_ws() {
1301        test_wait_for_finalized_block_helper(true).await
1302    }
1303
1304    #[test_log::test(tokio::test(flavor = "multi_thread"))]
1305    async fn test_wait_for_finalized_block_http() {
1306        test_wait_for_finalized_block_helper(false).await
1307    }
1308
1309    async fn test_wait_for_old_finalized_block_helper(ws: bool) {
1310        let anvil = Arc::new(Anvil::new().block_time_f64(0.2).spawn());
1311        let l1_client = new_l1_client_opt(&anvil, |opt| {
1312            if ws {
1313                opt.l1_ws_provider = Some(vec![anvil.ws_endpoint_url()]);
1314            }
1315            opt.l1_finalized_safety_margin = Some(1);
1316        })
1317        .await;
1318        let provider = &l1_client.provider;
1319
1320        // Wait for anvil to finalize a few blocks.
1321        l1_client.wait_for_finalized_block(2).await;
1322
1323        // Get an old finalized block.
1324        let block = l1_client.wait_for_finalized_block(0).await;
1325
1326        // Compare against underlying provider.
1327        let true_block = provider.get_block(0.into()).await.unwrap().unwrap();
1328        assert_eq!(block.hash, true_block.header.hash);
1329    }
1330
1331    #[test_log::test(tokio::test(flavor = "multi_thread"))]
1332    async fn test_wait_for_old_finalized_block_ws() {
1333        test_wait_for_old_finalized_block_helper(true).await
1334    }
1335
1336    #[test_log::test(tokio::test(flavor = "multi_thread"))]
1337    async fn test_wait_for_old_finalized_block_http() {
1338        test_wait_for_old_finalized_block_helper(false).await
1339    }
1340
1341    async fn test_wait_for_finalized_block_by_timestamp_helper(ws: bool) {
1342        let anvil = Arc::new(Anvil::new().block_time_f64(0.2).spawn());
1343        let l1_client = new_l1_client(&anvil, ws).await;
1344        let provider = &l1_client.provider;
1345
1346        // Wait for a block 5 blocks in the future.
1347        let timestamp = U256::from(OffsetDateTime::now_utc().unix_timestamp() as u64 + 5);
1348        let block = l1_client
1349            .wait_for_finalized_block_with_timestamp(timestamp)
1350            .await;
1351        assert!(
1352            block.timestamp >= timestamp,
1353            "wait_for_finalized_block_with_timestamp({timestamp}) returned too early a block: \
1354             {block:?}",
1355        );
1356        let parent = provider
1357            .get_block(BlockId::Number(BlockNumberOrTag::Number(block.number - 1)))
1358            .full()
1359            .await
1360            .unwrap()
1361            .unwrap();
1362        assert!(
1363            parent.header.inner.timestamp < timestamp.to::<u64>(),
1364            "wait_for_finalized_block_with_timestamp({timestamp}) did not return the earliest \
1365             possible block: returned {block:?}, but earlier block {parent:?} has an acceptable \
1366             timestamp too",
1367        );
1368
1369        // Compare against underlying provider.
1370        let true_block = provider
1371            .get_block(BlockId::Number(BlockNumberOrTag::Number(block.number)))
1372            .await
1373            .unwrap()
1374            .unwrap();
1375        assert_eq!(
1376            block.timestamp.to::<u64>(),
1377            true_block.header.inner.timestamp
1378        );
1379        assert_eq!(block.hash, true_block.header.hash);
1380    }
1381
1382    #[test_log::test(tokio::test(flavor = "multi_thread"))]
1383    async fn test_wait_for_finalized_block_by_timestamp_ws() {
1384        test_wait_for_finalized_block_by_timestamp_helper(true).await
1385    }
1386
1387    #[test_log::test(tokio::test(flavor = "multi_thread"))]
1388    async fn test_wait_for_finalized_block_by_timestamp_http() {
1389        test_wait_for_finalized_block_by_timestamp_helper(false).await
1390    }
1391
1392    async fn test_wait_for_old_finalized_block_by_timestamp_helper(ws: bool) {
1393        let anvil = Arc::new(Anvil::new().block_time_f64(0.2).spawn());
1394        let l1_client = new_l1_client(&anvil, ws).await;
1395
1396        // Get the timestamp of the first block.
1397        let true_block = l1_client.wait_for_finalized_block(0).await;
1398        let timestamp = true_block.timestamp;
1399
1400        // Wait for some more blocks to be produced.
1401        l1_client.wait_for_finalized_block(10).await;
1402
1403        // Get the old block by timestamp.
1404        let block = l1_client
1405            .wait_for_finalized_block_with_timestamp(U256::from(timestamp))
1406            .await;
1407        assert_eq!(block, true_block);
1408    }
1409
1410    #[test_log::test(tokio::test(flavor = "multi_thread"))]
1411    async fn test_wait_for_old_finalized_block_by_timestamp_ws() {
1412        test_wait_for_old_finalized_block_by_timestamp_helper(true).await
1413    }
1414
1415    #[test_log::test(tokio::test(flavor = "multi_thread"))]
1416    async fn test_wait_for_old_finalized_block_by_timestamp_http() {
1417        test_wait_for_old_finalized_block_by_timestamp_helper(false).await
1418    }
1419
1420    async fn test_wait_for_block_helper(ws: bool) {
1421        let anvil = Arc::new(Anvil::new().block_time_f64(0.1).spawn());
1422        let l1_client = new_l1_client(&anvil, ws).await;
1423        let provider = &l1_client.provider;
1424
1425        // Wait for a block 10 blocks in the future.
1426        let block_height = provider.get_block_number().await.unwrap();
1427        l1_client.wait_for_block(block_height + 10).await;
1428
1429        let new_block_height = provider.get_block_number().await.unwrap();
1430        assert!(
1431            new_block_height >= block_height + 10,
1432            "wait_for_block returned too early; initial height = {block_height}, new height = \
1433             {new_block_height}",
1434        );
1435    }
1436
1437    #[test_log::test(tokio::test(flavor = "multi_thread"))]
1438    async fn test_wait_for_block_ws() {
1439        test_wait_for_block_helper(true).await
1440    }
1441
1442    #[test_log::test(tokio::test(flavor = "multi_thread"))]
1443    async fn test_wait_for_block_http() {
1444        test_wait_for_block_helper(false).await
1445    }
1446
1447    async fn test_reconnect_update_task_helper(ws: bool) {
1448        // Use port 0 to let OS assign a port, avoiding race conditions
1449        let anvil = Arc::new(Anvil::new().block_time(1).port(0u16).spawn());
1450        let port = anvil.port();
1451        let client = new_l1_client(&anvil, ws).await;
1452
1453        let initial_state = client.snapshot().await;
1454        tracing::info!(?initial_state, "initial state");
1455
1456        // Check the state is updating.
1457        let mut retry = 0;
1458        let updated_state = loop {
1459            assert!(retry < 10, "state did not update in time");
1460
1461            let updated_state = client.snapshot().await;
1462            if updated_state.head > initial_state.head {
1463                break updated_state;
1464            }
1465            tracing::info!(retry, "waiting for state update");
1466            sleep(Duration::from_secs(1)).await;
1467            retry += 1;
1468        };
1469        tracing::info!(?updated_state, "state updated");
1470
1471        // Disconnect the WebSocket and reconnect it. Technically this spawns a whole new Anvil
1472        // chain, but for the purposes of this test it should look to the client like an L1 server
1473        // closing a WebSocket connection.
1474        drop(anvil);
1475
1476        // Let the connection stay down for a little while: Ethers internally tries to reconnect,
1477        // and starting up to fast again might hit that and cause a false positive. The problem is,
1478        // Ethers doesn't try very hard, and if we wait a bit, we will test the worst possible case
1479        // where the internal retry logic gives up and just kills the whole provider.
1480        tracing::info!("sleep 5");
1481        sleep(Duration::from_secs(5)).await;
1482
1483        // Once a connection is reestablished, the state will eventually start to update again.
1484        tracing::info!("restarting L1");
1485        let _anvil = Anvil::new().block_time(1).port(port).spawn();
1486
1487        let mut retry = 0;
1488        let final_state = loop {
1489            assert!(retry < 5, "state did not update in time");
1490
1491            let final_state = client.snapshot().await;
1492            if final_state.head > updated_state.head {
1493                break final_state;
1494            }
1495            tracing::info!(retry, "waiting for state update");
1496            sleep(Duration::from_secs(1)).await;
1497            retry += 1;
1498        };
1499        tracing::info!(?final_state, "state updated");
1500    }
1501
1502    #[test_log::test(tokio::test(flavor = "multi_thread"))]
1503    async fn test_reconnect_update_task_ws() {
1504        test_reconnect_update_task_helper(true).await
1505    }
1506
1507    #[test_log::test(tokio::test(flavor = "multi_thread"))]
1508    async fn test_reconnect_update_task_http() {
1509        test_reconnect_update_task_helper(false).await
1510    }
1511
1512    // #[test_log::test(tokio::test(flavor = "multi_thread"))]
1513    // async fn test_fetch_stake_table() -> anyhow::Result<()> {
1514
1515    //     let anvil = Anvil::new().spawn();
1516    //     let wallet = anvil.wallet().unwrap();
1517    //     let inner_provider = ProviderBuilder::new()
1518    //         .wallet(wallet)
1519    //         .on_http(anvil.endpoint_url());
1520    //     let provider = AnvilProvider::new(inner_provider, Arc::new(anvil));
1521
1522    //     let l1_client = new_l1_client(provider.anvil(), false).await;
1523    //     let mut contracts = Contracts::new();
1524
1525    //     let stake_table_addr =
1526    //         deploy_permissioned_stake_table(&provider, &mut contracts, vec![]).await?;
1527    //     let stake_table_contract = PermissionedStakeTable::new(stake_table_addr, &provider);
1528
1529    //     let mut rng = rand::thread_rng();
1530    //     let node = NodeInfoSol::rand(&mut rng);
1531
1532    //     let new_nodes: Vec<NodeInfoSol> = vec![node];
1533    //     stake_table_contract
1534    //         .update(vec![], new_nodes)
1535    //         .send()
1536    //         .await?
1537    //         .watch()
1538    //         .await?;
1539
1540    //     let block = l1_client.get_block(BlockId::latest()).await?.unwrap();
1541    //     let nodes = l1_client
1542    //         .get_stake_table(stake_table_addr, block.header.inner.number)
1543    //         .await?;
1544
1545    //     let result = nodes.stake_table.0[0].clone();
1546    //     assert_eq!(result.stake_table_entry.stake_amount.to::<u64>(), 1);
1547    //     Ok(())
1548    // }
1549
1550    /// A helper function to get the index of the current provider in the failover list.
1551    fn get_failover_index(provider: &L1Client) -> usize {
1552        let transport = &provider.transport;
1553        provider.transport.current_transport.read().generation % transport.urls.len()
1554    }
1555
1556    async fn test_failover_update_task_helper(ws: bool) {
1557        let anvil = Anvil::new().block_time(1).spawn();
1558
1559        // Create an L1 client with fake providers, and check that the state is still updated after
1560        // it correctly fails over to the real providers.
1561        let client = L1ClientOptions {
1562            l1_polling_interval: Duration::from_secs(1),
1563            // Use a very long subscription timeout, so that we only succeed by triggering a
1564            // failover.
1565            subscription_timeout: Duration::from_secs(1000),
1566            l1_ws_provider: if ws {
1567                Some(vec![
1568                    "ws://notarealurl:1234".parse().unwrap(),
1569                    anvil.ws_endpoint_url(),
1570                ])
1571            } else {
1572                None
1573            },
1574            ..Default::default()
1575        }
1576        .connect(vec![
1577            "http://notarealurl:1234".parse().unwrap(),
1578            anvil.endpoint_url(),
1579        ])
1580        .expect("Failed to create L1 client");
1581
1582        client.spawn_tasks().await;
1583
1584        let initial_state = client.snapshot().await;
1585        tracing::info!(?initial_state, "initial state");
1586
1587        // Check the state is updating.
1588        let mut retry = 0;
1589        let updated_state = loop {
1590            assert!(retry < 10, "state did not update in time");
1591
1592            let updated_state = client.snapshot().await;
1593            if updated_state.head > initial_state.head {
1594                break updated_state;
1595            }
1596            tracing::info!(retry, "waiting for state update");
1597            sleep(Duration::from_secs(1)).await;
1598            retry += 1;
1599        };
1600        tracing::info!(?updated_state, "state updated");
1601    }
1602
1603    #[test_log::test(tokio::test(flavor = "multi_thread"))]
1604    async fn test_failover_update_task_ws() {
1605        test_failover_update_task_helper(true).await;
1606    }
1607
1608    #[test_log::test(tokio::test(flavor = "multi_thread"))]
1609    async fn test_failover_update_task_http() {
1610        test_failover_update_task_helper(false).await;
1611    }
1612
1613    #[test_log::test(tokio::test(flavor = "multi_thread"))]
1614    async fn test_failover_consecutive_failures() {
1615        let anvil = Anvil::new().block_time(1).spawn();
1616
1617        let l1_options = L1ClientOptions {
1618            l1_polling_interval: Duration::from_secs(1),
1619            l1_frequent_failure_tolerance: Duration::from_millis(0),
1620            l1_consecutive_failure_tolerance: 3,
1621            ..Default::default()
1622        };
1623
1624        let provider = l1_options
1625            .connect(vec![
1626                "http://notarealurl:1234".parse().unwrap(),
1627                anvil.endpoint_url(),
1628            ])
1629            .expect("Failed to create L1 client");
1630
1631        // Make just enough failed requests not to trigger a failover.
1632        for _ in 0..2 {
1633            provider.get_block_number().await.unwrap_err();
1634            assert!(get_failover_index(&provider) == 0);
1635        }
1636
1637        // The final request triggers failover.
1638        provider.get_block_number().await.unwrap_err();
1639        assert!(get_failover_index(&provider) == 1);
1640
1641        // Now requests succeed.
1642        provider.get_block_number().await.unwrap();
1643    }
1644
1645    #[test_log::test(tokio::test(flavor = "multi_thread"))]
1646    async fn test_failover_frequent_failures() {
1647        let anvil = Anvil::new().block_time(1).spawn();
1648        let provider = L1ClientOptions {
1649            l1_polling_interval: Duration::from_secs(1),
1650            l1_frequent_failure_tolerance: Duration::from_millis(100),
1651            ..Default::default()
1652        }
1653        .connect(vec![
1654            "http://notarealurl:1234".parse().unwrap(),
1655            anvil.endpoint_url(),
1656        ])
1657        .expect("Failed to create L1 client");
1658
1659        // Two failed requests that are not within the tolerance window do not trigger a failover.
1660        provider.get_block_number().await.unwrap_err();
1661        sleep(Duration::from_secs(1)).await;
1662        provider.get_block_number().await.unwrap_err();
1663
1664        // Check that we didn't fail over.
1665        assert!(get_failover_index(&provider) == 0);
1666
1667        // Reset the window.
1668        sleep(Duration::from_secs(1)).await;
1669
1670        // Two failed requests in a row trigger failover.
1671        provider.get_block_number().await.unwrap_err();
1672        provider.get_block_number().await.unwrap_err();
1673        provider.get_block_number().await.unwrap();
1674        assert!(get_failover_index(&provider) == 1);
1675    }
1676
1677    #[test_log::test(tokio::test(flavor = "multi_thread"))]
1678    async fn test_failover_revert() {
1679        let anvil = Anvil::new().block_time(1).spawn();
1680        let provider = L1ClientOptions {
1681            l1_polling_interval: Duration::from_secs(1),
1682            l1_consecutive_failure_tolerance: 1,
1683            l1_failover_revert: Duration::from_secs(2),
1684            ..Default::default()
1685        }
1686        .connect(vec![
1687            "http://notarealurl:1234".parse().unwrap(),
1688            anvil.endpoint_url(),
1689        ])
1690        .expect("Failed to create L1 client");
1691
1692        // The first request fails and triggers a failover.
1693        provider.get_block_number().await.unwrap_err();
1694        assert_eq!(get_failover_index(&provider), 1);
1695
1696        // The next request succeeds from the other provider.
1697        provider.get_block_number().await.unwrap();
1698
1699        // Eventually we revert back to the primary and requests fail again.
1700        sleep(Duration::from_millis(2100)).await;
1701        provider.get_block_number().await.unwrap_err();
1702    }
1703
1704    // Checks that the L1 client initialized the state on startup even
1705    // if the L1 is not currently mining blocks. It's useful for testing that we
1706    // don't require an L1 that is continuously mining blocks.
1707    #[test_log::test(tokio::test(flavor = "multi_thread"))]
1708    async fn test_update_loop_initializes_l1_state() {
1709        // Use port 0 to let OS assign a port, avoiding race conditions
1710        let anvil = Arc::new(Anvil::new().port(0u16).spawn());
1711        let l1_client = new_l1_client(&anvil, true).await;
1712
1713        for _try in 0..10 {
1714            let mut state = l1_client.state.lock().await;
1715            let has_snapshot = state.snapshot.finalized.is_some();
1716            let has_cache = state.finalized.get(&0).is_some();
1717            drop(state);
1718            if has_snapshot && has_cache {
1719                return;
1720            }
1721            sleep(Duration::from_millis(200)).await;
1722        }
1723        panic!("L1 state of L1Client not initialized");
1724    }
1725}