espresso_types/v0/v0_1/
l1.rs

1use alloy::{
2    network::Ethereum,
3    primitives::{B256, U256},
4    providers::{
5        fillers::{FillProvider, JoinFill, RecommendedFillers},
6        Identity, RootProvider,
7    },
8    transports::http::{Client, Http},
9};
10use alloy_compat::ethers_serde;
11use async_broadcast::{InactiveReceiver, Sender};
12use clap::Parser;
13use derive_more::Deref;
14use hotshot_types::traits::metrics::{Counter, Gauge, Metrics, NoMetrics};
15use lru::LruCache;
16use parking_lot::RwLock;
17use serde::{Deserialize, Serialize};
18use std::{
19    num::NonZeroUsize,
20    sync::Arc,
21    time::{Duration, Instant},
22};
23use tokio::{
24    sync::{Mutex, Notify},
25    task::JoinHandle,
26};
27use url::Url;
28
29use crate::v0::utils::parse_duration;
30
31#[derive(Clone, Copy, Debug, Default, Deserialize, Serialize, Hash, PartialEq, Eq)]
32pub struct L1BlockInfo {
33    pub number: u64,
34    #[serde(with = "ethers_serde::u256")]
35    pub timestamp: U256,
36    #[serde(with = "ethers_serde::b256")]
37    pub hash: B256,
38}
39
40#[derive(Clone, Copy, Debug, PartialOrd, Ord, Hash, PartialEq, Eq)]
41pub(crate) struct L1BlockInfoWithParent {
42    pub(crate) info: L1BlockInfo,
43    pub(crate) parent_hash: B256,
44}
45
46#[derive(Clone, Copy, Debug, Default, Deserialize, Serialize, Hash, PartialEq, Eq)]
47pub struct L1Snapshot {
48    /// The relevant snapshot of the L1 includes a reference to the current head of the L1 chain.
49    ///
50    /// Note that the L1 head is subject to changing due to a reorg. However, no reorg will change
51    /// the _number_ of this block in the chain: L1 block numbers will always be sequentially
52    /// increasing. Therefore, the sequencer does not have to worry about reorgs invalidating this
53    /// snapshot.
54    pub head: u64,
55
56    /// The snapshot also includes information about the latest finalized L1 block.
57    ///
58    /// Since this block is finalized (ie cannot be reorged) we can include specific information
59    /// about the particular block, such as its hash and timestamp.
60    ///
61    /// This block may be `None` in the rare case where Espresso has started shortly after the
62    /// genesis of the L1, and the L1 has yet to finalize a block. In all other cases it will be
63    /// `Some`.
64    pub finalized: Option<L1BlockInfo>,
65}
66
67/// Configuration for an L1 client.
68#[derive(Clone, Debug, Parser)]
69pub struct L1ClientOptions {
70    /// Delay when retrying failed L1 queries.
71    #[clap(
72        long,
73        env = "ESPRESSO_SEQUENCER_L1_RETRY_DELAY",
74        default_value = "1s",
75        value_parser = parse_duration,
76    )]
77    pub l1_retry_delay: Duration,
78
79    /// Request rate when polling L1.
80    #[clap(
81        long,
82        env = "ESPRESSO_SEQUENCER_L1_POLLING_INTERVAL",
83        default_value = "7s",
84        value_parser = parse_duration,
85    )]
86    pub l1_polling_interval: Duration,
87
88    /// Maximum number of L1 blocks to keep in cache at once.
89    #[clap(
90        long,
91        env = "ESPRESSO_SEQUENCER_L1_BLOCKS_CACHE_SIZE",
92        default_value = "100"
93    )]
94    pub l1_blocks_cache_size: NonZeroUsize,
95
96    /// Number of L1 events to buffer before discarding.
97    #[clap(
98        long,
99        env = "ESPRESSO_SEQUENCER_L1_EVENTS_CHANNEL_CAPACITY",
100        default_value = "100"
101    )]
102    pub l1_events_channel_capacity: usize,
103
104    /// Maximum number of L1 blocks that can be scanned for events in a single query.
105    #[clap(
106        long,
107        env = "ESPRESSO_SEQUENCER_L1_EVENTS_MAX_BLOCK_RANGE",
108        default_value = "10000"
109    )]
110    pub l1_events_max_block_range: u64,
111
112    /// Maximum time to wait for new heads before considering a stream invalid and reconnecting.
113    #[clap(
114        long,
115        env = "ESPRESSO_SEQUENCER_L1_SUBSCRIPTION_TIMEOUT",
116        default_value = "1m",
117        value_parser = parse_duration,
118    )]
119    pub subscription_timeout: Duration,
120
121    /// Fail over to another provider if the current provider fails twice within this window.
122    #[clap(
123        long,
124        env = "ESPRESSO_SEQUENCER_L1_FREQUENT_FAILURE_TOLERANCE",
125        default_value = "1m",
126        value_parser = parse_duration,
127    )]
128    pub l1_frequent_failure_tolerance: Duration,
129
130    /// Fail over to another provider if the current provider fails many times in a row, within any
131    /// time window.
132    #[clap(
133        long,
134        env = "ESPRESSO_SEQUENCER_L1_CONSECUTIVE_FAILURE_TOLERANCE",
135        default_value = "10"
136    )]
137    pub l1_consecutive_failure_tolerance: usize,
138
139    /// Revert back to the first provider this duration after failing over.
140    #[clap(
141        long,
142        env = "ESPRESSO_SEQUENCER_L1_FAILOVER_REVERT",
143        default_value = "30m",
144        value_parser = parse_duration,
145    )]
146    pub l1_failover_revert: Duration,
147
148    /// Amount of time to wait after receiving a 429 response before making more L1 RPC requests.
149    ///
150    /// If not set, the general l1-retry-delay will be used.
151    #[clap(
152        long,
153        env = "ESPRESSO_SEQUENCER_L1_RATE_LIMIT_DELAY",
154        value_parser = parse_duration,
155    )]
156    pub l1_rate_limit_delay: Option<Duration>,
157
158    /// Separate provider to use for subscription feeds.
159    ///
160    /// Typically this would be a WebSockets endpoint while the main provider uses HTTP.
161    #[clap(long, env = "ESPRESSO_SEQUENCER_L1_WS_PROVIDER", value_delimiter = ',')]
162    pub l1_ws_provider: Option<Vec<Url>>,
163
164    /// Interval at which the background update loop polls the L1 stake table contract for new events
165    /// and updates local persistence.
166    ///
167    #[clap(
168        long,
169        env = "ESPRESSO_SEQUENCER_L1_STAKE_TABLE_UPDATE_INTERVAL",
170        default_value = "60m",
171        value_parser = parse_duration,
172    )]
173    pub stake_table_update_interval: Duration,
174
175    /// Maximum duration to retry fetching L1 events before panicking.
176    ///
177    /// This prevents infinite retries by panicking if the total number of retries exceed the maximum duration.
178    /// This is helpful in cases where the RPC block range limit or the event return limit is hit,
179    /// or if there is an outage. In such cases, panicking ensures that the node operator can take
180    /// action instead of the node getting stuck indefinitely. This is necessary because the stake table is constructed
181    /// from the fetched events, and is required for node to participate in consensus.
182    #[clap(
183        long,
184        env = "ESPRESSO_SEQUENCER_L1_EVENTS_MAX_RETRY_DURATION",
185        default_value = "20m",
186        value_parser = parse_duration,
187    )]
188    pub l1_events_max_retry_duration: Duration,
189
190    /// A block range which is expected to contain the finalized heads of all L1 provider chains.
191    ///
192    /// If specified, it is assumed that if a block `n` is known to be finalized according to a
193    /// certain provider, then any block less than `n - L1_FINALIZED_SAFETY_MARGIN` is finalized
194    /// _according to any provider_. In other words, if we fail over from one provider to another,
195    /// the second provider will never be lagging the first by more than this margin.
196    ///
197    /// This allows us to quickly query for very old finalized blocks by number. Without this
198    /// assumption, we always need to verify that a block is finalized by fetching all blocks in a
199    /// hash chain between the known finalized block and the desired block, recomputing and checking
200    /// the hashes. This is fine and good for blocks very near the finalized head, but for
201    /// extremely old blocks it is prohibitively expensive, and these old blocks are extremely
202    /// unlikely to be unfinalized anyways.
203    #[clap(long, env = "ESPRESSO_SEQUENCER_L1_FINALIZED_SAFETY_MARGIN")]
204    pub l1_finalized_safety_margin: Option<u64>,
205
206    #[clap(skip = Arc::<Box<dyn Metrics>>::new(Box::new(NoMetrics)))]
207    pub metrics: Arc<Box<dyn Metrics>>,
208}
209
210/// Type alias for alloy provider
211pub type L1Provider = FillProvider<
212    JoinFill<Identity, <Ethereum as RecommendedFillers>::RecommendedFillers>,
213    RootProvider,
214>;
215
216#[derive(Clone, Debug, Deref)]
217/// An Ethereum provider and configuration to interact with the L1.
218///
219/// This client runs asynchronously, updating an in-memory snapshot of the relevant L1 information
220/// each time a new L1 block is published. The main advantage of this is that we can update the L1
221/// state at the pace of the L1, instead of the much faster pace of HotShot consensus.This makes it
222/// easy to use a subscription instead of polling for new blocks, vastly reducing the number of L1
223/// RPC calls we make.
224pub struct L1Client {
225    /// The alloy provider used for L1 communication with wallet and default fillers
226    #[deref]
227    pub provider: L1Provider,
228    /// Actual transport used in `self.provider`
229    /// i.e. the `t` variable in `ProviderBuilder::new().on_client(RpcClient::new(t, is_local))`
230    pub transport: SwitchingTransport,
231    /// Shared state updated by an asynchronous task which polls the L1.
232    pub(crate) state: Arc<Mutex<L1State>>,
233    /// Channel used by the async update task to send events to clients.
234    pub(crate) sender: Sender<L1Event>,
235    /// Receiver for events from the async update task.
236    pub(crate) receiver: InactiveReceiver<L1Event>,
237    /// Async task which updates the shared state.
238    pub(crate) update_task: Arc<L1UpdateTask>,
239}
240
241/// In-memory view of the L1 state, updated asynchronously.
242#[derive(Debug)]
243pub(crate) struct L1State {
244    pub(crate) snapshot: L1Snapshot,
245    pub(crate) finalized: LruCache<u64, L1BlockInfoWithParent>,
246    pub(crate) last_finalized: Option<u64>,
247}
248
249#[derive(Clone, Debug)]
250pub(crate) enum L1Event {
251    NewHead { head: u64 },
252    NewFinalized { finalized: L1BlockInfoWithParent },
253}
254
255#[derive(Debug, Default)]
256pub(crate) struct L1UpdateTask(pub(crate) Mutex<Option<JoinHandle<()>>>);
257
258#[derive(Clone, Debug)]
259pub(crate) struct L1ClientMetrics {
260    pub(crate) head: Arc<dyn Gauge>,
261    pub(crate) finalized: Arc<dyn Gauge>,
262    pub(crate) reconnects: Arc<dyn Counter>,
263    pub(crate) failovers: Arc<dyn Counter>,
264    pub(crate) failures: Arc<Vec<Box<dyn Counter>>>,
265}
266
267/// An RPC client with multiple remote (HTTP) providers.
268///
269/// This client utilizes one RPC provider at a time, but if it detects that the provider is in a
270/// failing state, it will automatically switch to the next provider in its list.
271#[derive(Clone, Debug)]
272pub struct SwitchingTransport {
273    /// The transport currently being used by the client
274    pub(crate) current_transport: Arc<RwLock<SingleTransport>>,
275    /// The list of configured HTTP URLs to use for RPC requests
276    pub(crate) urls: Arc<Vec<Url>>,
277    pub(crate) opt: Arc<L1ClientOptions>,
278    pub(crate) metrics: L1ClientMetrics,
279    pub(crate) switch_notify: Arc<Notify>,
280}
281
282/// The state of the current provider being used by a [`SwitchingTransport`].
283/// This is cloneable and returns a reference to the same underlying data.
284#[derive(Debug, Clone)]
285pub(crate) struct SingleTransport {
286    pub(crate) generation: usize,
287    pub(crate) client: Http<Client>,
288    pub(crate) status: Arc<RwLock<SingleTransportStatus>>,
289    /// Time at which to revert back to the primary provider after a failover.
290    pub(crate) revert_at: Option<Instant>,
291}
292
293/// The status of a single transport
294#[derive(Debug, Default)]
295pub(crate) struct SingleTransportStatus {
296    pub(crate) last_failure: Option<Instant>,
297    pub(crate) consecutive_failures: usize,
298    pub(crate) rate_limited_until: Option<Instant>,
299    /// Whether or not this current transport is being shut down (switching to the next transport)
300    pub(crate) shutting_down: bool,
301}