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 .constant_str("timestamp")
101 .fixed_size_bytes(×tamp)
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 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 #[cfg(feature = "node")]
150 pub fn connect(self, urls: Vec<Url>) -> anyhow::Result<L1Client> {
151 let t = SwitchingTransport::new(self, urls)
153 .with_context(|| "failed to create switching transport")?;
154 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 let failures = metrics.counter_family("failed_requests".into(), vec!["provider".into()]);
169
170 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 pub fn new(opt: L1ClientOptions, urls: Vec<Url>) -> anyhow::Result<Self> {
192 let Some(first_url) = urls.first().cloned() else {
194 return Err(anyhow::anyhow!("No valid URLs provided"));
195 };
196
197 let metrics = L1ClientMetrics::new(&**opt.metrics, urls.len());
199
200 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 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 fn log_success(&mut self) {
230 self.consecutive_failures = 0;
231 }
232
233 fn log_failure(&mut self, opt: &L1ClientOptions) -> bool {
235 self.consecutive_failures += 1;
237
238 let should_switch = self.should_switch(opt);
240
241 self.last_failure = Some(Instant::now());
243
244 should_switch
246 }
247
248 fn should_switch(&mut self, opt: &L1ClientOptions) -> bool {
250 if self.shutting_down {
252 return false;
253 }
254
255 if self.consecutive_failures >= opt.l1_consecutive_failure_tolerance {
257 self.shutting_down = true;
258 return true;
259 }
260
261 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 fn should_revert(&mut self, revert_at: Option<Instant>) -> bool {
275 if self.shutting_down {
276 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 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#[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 self.current_transport.read().clone().client.poll_ready(cx)
322 }
323
324 fn call(&mut self, req: RequestPacket) -> Self::Future {
325 let self_clone = self.clone();
327
328 Box::pin(async move {
330 let mut current_transport = self_clone.current_transport.read().clone();
332
333 let should_revert = current_transport
335 .status
336 .write()
337 .should_revert(current_transport.revert_at);
338 if should_revert {
339 let n = self_clone.urls.len();
341 let prev_primary_gen = (current_transport.generation / n) * n;
343 let next_gen = prev_primary_gen + n;
345 current_transport = self_clone.switch_to(next_gen, current_transport);
346 }
347
348 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 Err(RpcError::Transport(TransportErrorKind::Custom(
354 "Rate limit exceeded".into(),
355 )));
356 } else {
357 current_transport.status.write().rate_limited_until = None;
359 }
360 }
361
362 match current_transport.client.call(req).await {
364 Ok(res) => {
365 current_transport.status.write().log_success();
367 Ok(res)
368 },
369 Err(err) => {
370 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 if let RpcError::ErrorResp(e) = &err {
383 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 tracing::warn!(?err, "L1 client error");
393
394 if current_transport
397 .status
398 .write()
399 .log_failure(&self_clone.opt)
400 {
401 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 None
423 } else if current_transport.generation.is_multiple_of(self.urls.len()) {
424 Some(Instant::now() + self.opt.l1_failover_revert)
427 } else {
428 current_transport.revert_at
430 };
431
432 let new_transport = SingleTransport::new(&url, next_gen, revert_at);
434
435 *self.current_transport.write() = new_transport.clone();
437
438 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 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 pub fn new(url: Vec<Url>) -> anyhow::Result<Self> {
470 L1ClientOptions::default().connect(url)
471 }
472
473 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 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 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 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 let mut block_stream = {
537 let res = match &ws_urls {
538 Some(urls) => {
539 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 let stream = poller_builder.with_poll_interval(polling_interval).into_stream();
560
561 let rpc = rpc.clone();
562
563 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 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(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 let block_timeout = tokio::time::timeout(subscription_timeout, block_stream.next()).await;
605 match block_timeout {
606 Ok(Some(head)) => {
608 let head = head.number;
609 tracing::debug!(head, "Received L1 block");
610
611 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 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 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 Ok(None) => {
655 tracing::error!("L1 block stream ended unexpectedly, trying to re-establish block stream");
656 break;
657 }
658 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 pub async fn snapshot(&self) -> L1Snapshot {
673 self.state.lock().await.snapshot
674 }
675
676 pub async fn wait_for_block(&self, number: u64) {
682 loop {
683 let mut events = self.receiver.activate_cloned();
686
687 {
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 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 tracing::warn!(number, "L1 event stream ended unexpectedly; retry");
710 self.retry_delay().await;
711 }
712 }
713
714 pub async fn wait_for_finalized_block(&self, number: u64) -> L1BlockInfo {
719 loop {
720 let mut events = self.receiver.activate_cloned();
723
724 {
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 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 tracing::warn!(number, "L1 event stream ended unexpectedly; retry",);
755 self.retry_delay().await;
756 }
757 }
758
759 pub async fn wait_for_finalized_block_with_timestamp(&self, timestamp: U256) -> L1BlockInfo {
762 let (mut state, mut block) = 'outer: loop {
764 let mut events = self.receiver.activate_cloned();
767
768 {
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 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 tracing::warn!(%timestamp, "L1 event stream ended unexpectedly; retry",);
797 self.retry_delay().await;
798 };
799
800 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 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 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 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 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 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 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 if prev_finalized >= Some(new_finalized) {
969 return vec![];
970 }
971
972 let opt = self.options();
973
974 let prev = prev_finalized.map(|prev| prev + 1).unwrap_or(0);
977
978 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 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 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 pub async fn is_proxy_contract(&self, proxy_address: Address) -> anyhow::Result<bool> {
1029 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 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 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 let provider = AnvilProvider::new(inner_provider, Arc::new(anvil));
1191 let mut contracts = Contracts::new();
1193
1194 let l1_client = new_l1_client(provider.anvil(), false).await;
1196
1197 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 for n in 1..=num_deposits {
1204 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 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 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 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 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 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 l1_client.wait_for_finalized_block(2).await;
1322
1323 let block = l1_client.wait_for_finalized_block(0).await;
1325
1326 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 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 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 let true_block = l1_client.wait_for_finalized_block(0).await;
1398 let timestamp = true_block.timestamp;
1399
1400 l1_client.wait_for_finalized_block(10).await;
1402
1403 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 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 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 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 drop(anvil);
1475
1476 tracing::info!("sleep 5");
1481 sleep(Duration::from_secs(5)).await;
1482
1483 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 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 let client = L1ClientOptions {
1562 l1_polling_interval: Duration::from_secs(1),
1563 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 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 for _ in 0..2 {
1633 provider.get_block_number().await.unwrap_err();
1634 assert!(get_failover_index(&provider) == 0);
1635 }
1636
1637 provider.get_block_number().await.unwrap_err();
1639 assert!(get_failover_index(&provider) == 1);
1640
1641 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 provider.get_block_number().await.unwrap_err();
1661 sleep(Duration::from_secs(1)).await;
1662 provider.get_block_number().await.unwrap_err();
1663
1664 assert!(get_failover_index(&provider) == 0);
1666
1667 sleep(Duration::from_secs(1)).await;
1669
1670 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 provider.get_block_number().await.unwrap_err();
1694 assert_eq!(get_failover_index(&provider), 1);
1695
1696 provider.get_block_number().await.unwrap();
1698
1699 sleep(Duration::from_millis(2100)).await;
1701 provider.get_block_number().await.unwrap_err();
1702 }
1703
1704 #[test_log::test(tokio::test(flavor = "multi_thread"))]
1708 async fn test_update_loop_initializes_l1_state() {
1709 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}