1#[cfg(feature = "node")]
2use std::time::Instant;
3use std::{
4 num::{NonZeroU64, NonZeroUsize},
5 sync::Arc,
6 time::Duration,
7};
8
9use alloy::primitives::{B256, U256};
10#[cfg(feature = "node")]
11use alloy::{
12 network::Ethereum,
13 providers::{
14 Identity, Provider, RootProvider,
15 fillers::{FillProvider, JoinFill, RecommendedFillers},
16 },
17 transports::http::{Client, Http},
18};
19use alloy_compat::ethers_serde;
20#[cfg(feature = "node")]
21use async_broadcast::{InactiveReceiver, Sender};
22use clap::Parser;
23#[cfg(feature = "node")]
24use derive_more::Deref;
25use espresso_utils::redact::redact_urls;
26#[cfg(feature = "node")]
27use hotshot_types::traits::metrics::{Counter, Gauge};
28use hotshot_types::traits::metrics::{Metrics, NoMetrics};
29#[cfg(feature = "node")]
30use lru::LruCache;
31#[cfg(feature = "node")]
32use parking_lot::RwLock;
33use serde::{Deserialize, Serialize};
34use thiserror::Error;
35#[cfg(feature = "node")]
36use tokio::{
37 sync::{Mutex, Notify},
38 task::JoinHandle,
39};
40use url::Url;
41
42use crate::v0::utils::parse_duration;
43
44#[derive(Clone, Copy, Debug, Default, Deserialize, Serialize, Hash, PartialEq, Eq)]
45pub struct L1BlockInfo {
46 pub number: u64,
47 #[serde(with = "ethers_serde::u256")]
48 pub timestamp: U256,
49 #[serde(with = "ethers_serde::b256")]
50 pub hash: B256,
51}
52
53#[cfg_attr(not(feature = "node"), allow(dead_code))]
54#[derive(Clone, Copy, Debug, PartialOrd, Ord, Hash, PartialEq, Eq)]
55pub(crate) struct L1BlockInfoWithParent {
56 pub(crate) info: L1BlockInfo,
57 pub(crate) parent_hash: B256,
58}
59
60#[derive(Clone, Copy, Debug, Default, Deserialize, Serialize, Hash, PartialEq, Eq)]
61pub struct L1Snapshot {
62 pub head: u64,
69
70 pub finalized: Option<L1BlockInfo>,
79}
80
81#[derive(Clone, Copy, Debug, PartialEq, Eq)]
84pub struct L1SafetyMargin(pub(crate) Option<NonZeroU64>);
85
86#[derive(Debug, Error)]
87#[error("invalid safety margin {input:?}: expected a nonzero block count or `unlimited`")]
88pub struct ParseL1SafetyMarginError {
89 pub(crate) input: String,
90}
91
92#[derive(Clone, derive_more::Debug, Parser)]
94pub struct L1ClientOptions {
95 #[clap(
97 long,
98 env = "ESPRESSO_L1_RETRY_DELAY",
99 default_value = "1s",
100 value_parser = parse_duration,
101 )]
102 pub l1_retry_delay: Duration,
103
104 #[clap(
106 long,
107 env = "ESPRESSO_L1_POLLING_INTERVAL",
108 default_value = "7s",
109 value_parser = parse_duration,
110 )]
111 pub l1_polling_interval: Duration,
112
113 #[clap(
117 long,
118 env = "ESPRESSO_L1_WAIT_REFRESH_INTERVAL",
119 default_value = "500ms",
120 value_parser = parse_duration,
121 )]
122 pub l1_wait_refresh_interval: Duration,
123
124 #[clap(
129 long,
130 env = "ESPRESSO_L1_WAIT_REFRESH_TIMEOUT",
131 default_value = "1500ms",
132 value_parser = parse_duration,
133 )]
134 pub l1_wait_refresh_timeout: Duration,
135
136 #[clap(
138 long,
139 env = "ESPRESSO_L1_REQUEST_TIMEOUT",
140 default_value = "10s",
141 value_parser = parse_duration,
142 )]
143 pub l1_request_timeout: Duration,
144
145 #[clap(long, env = "ESPRESSO_L1_BLOCKS_CACHE_SIZE", default_value = "100")]
147 pub l1_blocks_cache_size: NonZeroUsize,
148
149 #[clap(
151 long,
152 env = "ESPRESSO_L1_EVENTS_CHANNEL_CAPACITY",
153 default_value = "100"
154 )]
155 pub l1_events_channel_capacity: usize,
156
157 #[clap(
159 long,
160 env = "ESPRESSO_L1_EVENTS_MAX_BLOCK_RANGE",
161 default_value = "10000"
162 )]
163 pub l1_events_max_block_range: u64,
164
165 #[clap(
167 long,
168 env = "ESPRESSO_L1_SUBSCRIPTION_TIMEOUT",
169 default_value = "1m",
170 value_parser = parse_duration,
171 )]
172 pub subscription_timeout: Duration,
173
174 #[clap(
176 long,
177 env = "ESPRESSO_L1_FREQUENT_FAILURE_TOLERANCE",
178 default_value = "1m",
179 value_parser = parse_duration,
180 )]
181 pub l1_frequent_failure_tolerance: Duration,
182
183 #[clap(
186 long,
187 env = "ESPRESSO_L1_CONSECUTIVE_FAILURE_TOLERANCE",
188 default_value = "10"
189 )]
190 pub l1_consecutive_failure_tolerance: usize,
191
192 #[clap(
194 long,
195 env = "ESPRESSO_L1_FAILOVER_REVERT",
196 default_value = "30m",
197 value_parser = parse_duration,
198 )]
199 pub l1_failover_revert: Duration,
200
201 #[clap(
205 long,
206 env = "ESPRESSO_L1_RATE_LIMIT_DELAY",
207 value_parser = parse_duration,
208 )]
209 pub l1_rate_limit_delay: Option<Duration>,
210
211 #[clap(long, env = "ESPRESSO_L1_WS_PROVIDER", value_delimiter = ',')]
215 #[debug("{:?}", l1_ws_provider.as_ref().map(redact_urls))]
216 pub l1_ws_provider: Option<Vec<Url>>,
217
218 #[clap(
222 long,
223 env = "ESPRESSO_NODE_L1_STAKE_TABLE_UPDATE_INTERVAL",
224 default_value = "60m",
225 value_parser = parse_duration,
226 )]
227 pub stake_table_update_interval: Duration,
228
229 #[clap(
237 long,
238 env = "ESPRESSO_L1_EVENTS_MAX_RETRY_DURATION",
239 default_value = "20m",
240 value_parser = parse_duration,
241 )]
242 pub l1_events_max_retry_duration: Duration,
243
244 #[clap(
260 long,
261 env = "ESPRESSO_L1_FINALIZED_SAFETY_MARGIN",
262 default_value = "100"
263 )]
264 pub l1_finalized_safety_margin: L1SafetyMargin,
265
266 #[clap(skip = Arc::<Box<dyn Metrics>>::new(Box::new(NoMetrics)))]
267 pub metrics: Arc<Box<dyn Metrics>>,
268}
269
270#[cfg(feature = "node")]
272pub type L1Provider = FillProvider<
273 JoinFill<Identity, <Ethereum as RecommendedFillers>::RecommendedFillers>,
274 RootProvider,
275>;
276
277#[cfg(feature = "node")]
278#[derive(Clone, Debug, Deref)]
279pub struct L1Client {
287 #[deref]
289 pub provider: L1Provider,
290 pub transport: SwitchingTransport,
293 pub(crate) state: Arc<Mutex<L1State>>,
295 pub(crate) sender: Sender<L1Event>,
297 pub(crate) receiver: InactiveReceiver<L1Event>,
299 pub(crate) update_task: Arc<L1UpdateTask>,
301}
302
303#[cfg(feature = "node")]
304impl Provider for L1Client {
305 fn root(&self) -> &RootProvider {
306 self.provider.root()
307 }
308}
309
310#[cfg(feature = "node")]
312#[derive(Debug)]
313pub(crate) struct L1State {
314 pub(crate) snapshot: L1Snapshot,
315 pub(crate) finalized: LruCache<u64, L1BlockInfoWithParent>,
316 pub(crate) last_finalized: Option<u64>,
317 pub(crate) last_head_refresh: Option<Instant>,
320 pub(crate) last_finalized_refresh: Option<Instant>,
325}
326
327#[cfg(feature = "node")]
328#[derive(Clone, Debug)]
329pub(crate) enum L1Event {
330 NewHead { head: u64 },
331 NewFinalized { finalized: L1BlockInfoWithParent },
332}
333
334#[cfg(feature = "node")]
335#[derive(Debug, Default)]
336pub(crate) struct L1UpdateTask(pub(crate) Mutex<Option<JoinHandle<()>>>);
337
338#[cfg(feature = "node")]
339#[derive(Clone, Debug)]
340pub(crate) struct L1ClientMetrics {
341 pub(crate) head: Arc<dyn Gauge>,
342 pub(crate) finalized: Arc<dyn Gauge>,
343 pub(crate) reconnects: Arc<dyn Counter>,
344 pub(crate) failovers: Arc<dyn Counter>,
345 pub(crate) failures: Arc<Vec<Box<dyn Counter>>>,
346}
347
348#[cfg(feature = "node")]
353#[derive(Clone, derive_more::Debug)]
354pub struct SwitchingTransport {
355 pub(crate) current_transport: Arc<RwLock<SingleTransport>>,
357 #[debug("{:?}", redact_urls(urls.iter()))]
359 pub(crate) urls: Arc<Vec<Url>>,
360 pub(crate) opt: Arc<L1ClientOptions>,
361 pub(crate) metrics: L1ClientMetrics,
362 pub(crate) switch_notify: Arc<Notify>,
363}
364
365#[cfg(feature = "node")]
368#[derive(derive_more::Debug, Clone)]
369pub(crate) struct SingleTransport {
370 pub(crate) generation: usize,
371 pub(crate) redacted_url: String,
372 #[debug(skip)]
373 pub(crate) client: Http<Client>,
374 pub(crate) status: Arc<RwLock<SingleTransportStatus>>,
375 pub(crate) revert_at: Option<Instant>,
377}
378
379#[cfg(feature = "node")]
381#[derive(Debug, Default)]
382pub(crate) struct SingleTransportStatus {
383 pub(crate) last_failure: Option<Instant>,
384 pub(crate) consecutive_failures: usize,
385 pub(crate) consecutive_rate_limits: usize,
386 pub(crate) rate_limited_until: Option<Instant>,
387 pub(crate) shutting_down: bool,
389}