espresso_node/api/
data_source.rs

1use std::{collections::HashMap, time::Duration};
2
3use alloy::primitives::Address;
4use anyhow::Context;
5use async_trait::async_trait;
6use committable::Commitment;
7use espresso_types::{
8    FeeAccount, FeeAccountProof, FeeMerkleTree, Leaf2, NodeState, PubKey, Transaction,
9    config::PublicNetworkConfig,
10    v0::traits::{PersistenceOptions, SequencerPersistence},
11    v0_3::{
12        AuthenticatedValidator, ChainConfig, RegisteredValidator, RewardAccountProofV1,
13        RewardAccountQueryDataV1, RewardAccountV1, RewardAmount, RewardMerkleTreeV1,
14        StakeTableEvent,
15    },
16    v0_4::{RewardAccountProofV2, RewardAccountQueryDataV2, RewardAccountV2, RewardMerkleTreeV2},
17};
18use futures::future::{BoxFuture, Future};
19use hotshot::types::BLSPubKey;
20use hotshot_query_service::{
21    availability::{AvailabilityDataSource, VidCommonQueryData},
22    data_source::{UpdateDataSource, VersionedDataSource},
23    fetching::provider::{AnyProvider, QueryServiceProvider},
24    node::NodeDataSource,
25    status::StatusDataSource,
26};
27use hotshot_types::{
28    PeerConfig,
29    data::{EpochNumber, VidShare, ViewNumber},
30    light_client::LCV3StateSignatureRequestBody,
31    simple_certificate::LightClientStateUpdateCertificateV2,
32    traits::{network::ConnectedNetwork, node_implementation::NodeType},
33};
34use indexmap::IndexMap;
35use serde::{Deserialize, Serialize};
36use tide_disco::Url;
37
38use super::{
39    AccountQueryData, BlocksFrontier, fs,
40    options::{Options, Query},
41    sql,
42};
43use crate::{SeqTypes, SequencerApiVersion, U256, persistence, state_cert::StateCertFetchError};
44
45pub trait DataSourceOptions: PersistenceOptions {
46    type DataSource: SequencerDataSource<Options = Self>;
47
48    fn enable_query_module(&self, opt: Options, query: Query) -> Options;
49}
50
51impl DataSourceOptions for persistence::sql::Options {
52    type DataSource = sql::DataSource;
53
54    fn enable_query_module(&self, opt: Options, query: Query) -> Options {
55        opt.query_sql(query, self.clone())
56    }
57}
58
59impl DataSourceOptions for persistence::fs::Options {
60    type DataSource = fs::DataSource;
61
62    fn enable_query_module(&self, opt: Options, query: Query) -> Options {
63        opt.query_fs(query, self.clone())
64    }
65}
66
67/// A data source with sequencer-specific functionality.
68///
69/// This trait extends the generic [`AvailabilityDataSource`] with some additional data needed to
70/// provided sequencer-specific endpoints.
71#[async_trait]
72pub trait SequencerDataSource:
73    AvailabilityDataSource<SeqTypes>
74    + NodeDataSource<SeqTypes>
75    + StatusDataSource
76    + UpdateDataSource<SeqTypes>
77    + VersionedDataSource
78    + Sized
79{
80    type Options: DataSourceOptions<DataSource = Self>;
81
82    /// Instantiate a data source from command line options.
83    async fn create(opt: Self::Options, provider: Provider, reset: bool) -> anyhow::Result<Self>;
84}
85
86/// Provider for fetching missing data for the query service.
87pub type Provider = AnyProvider<SeqTypes>;
88
89/// Create a provider for fetching missing data from a list of peer query services.
90pub fn provider(
91    peers: impl IntoIterator<Item = Url>,
92    bind_version: SequencerApiVersion,
93) -> Provider {
94    let mut provider = Provider::default();
95    for peer in peers {
96        tracing::info!("will fetch missing data from {peer}");
97        provider = provider.with_provider(QueryServiceProvider::new(peer, bind_version));
98    }
99    provider
100}
101
102pub(crate) trait SubmitDataSource<N: ConnectedNetwork<PubKey>, P: SequencerPersistence> {
103    fn submit(&self, tx: Transaction) -> impl Send + Future<Output = anyhow::Result<()>>;
104}
105
106pub(crate) trait HotShotConfigDataSource {
107    fn get_config(&self) -> impl Send + Future<Output = PublicNetworkConfig>;
108}
109
110#[async_trait]
111pub(crate) trait StateSignatureDataSource<N: ConnectedNetwork<PubKey>> {
112    async fn get_state_signature(&self, height: u64) -> Option<LCV3StateSignatureRequestBody>;
113}
114
115pub(crate) trait NodeStateDataSource {
116    fn node_state(&self) -> impl Send + Future<Output = NodeState>;
117}
118
119pub(crate) trait TokenDataSource<T: NodeType> {
120    fn get_initial_supply_l1(&self) -> impl Send + Future<Output = anyhow::Result<U256>>;
121    fn get_total_supply_l1(&self) -> impl Send + Future<Output = anyhow::Result<U256>>;
122    fn get_decided_header(&self) -> impl Send + Future<Output = espresso_types::Header>;
123}
124
125#[derive(Serialize, Deserialize)]
126#[serde(bound = "T: NodeType")]
127pub struct StakeTableWithEpochNumber<T: NodeType> {
128    pub epoch: Option<EpochNumber>,
129    pub stake_table: Vec<PeerConfig<T>>,
130}
131
132pub(crate) trait StakeTableDataSource<T: NodeType> {
133    /// Get the stake table for a given epoch
134    fn get_stake_table(
135        &self,
136        epoch: Option<EpochNumber>,
137    ) -> impl Send + Future<Output = anyhow::Result<Vec<PeerConfig<T>>>>;
138
139    /// Get the stake table for the current epoch if not provided
140    fn get_stake_table_current(
141        &self,
142    ) -> impl Send + Future<Output = anyhow::Result<StakeTableWithEpochNumber<T>>>;
143
144    /// Get the DA stake table for a given epoch
145    fn get_da_stake_table(
146        &self,
147        epoch: Option<EpochNumber>,
148    ) -> impl Send + Future<Output = anyhow::Result<Vec<PeerConfig<T>>>>;
149
150    /// Get the DA stake table for the current epoch if not provided
151    fn get_da_stake_table_current(
152        &self,
153    ) -> impl Send + Future<Output = anyhow::Result<StakeTableWithEpochNumber<T>>>;
154
155    /// Get all the validators
156    fn get_validators(
157        &self,
158        epoch: EpochNumber,
159    ) -> impl Send + Future<Output = anyhow::Result<IndexMap<Address, AuthenticatedValidator<BLSPubKey>>>>;
160
161    fn get_block_reward(
162        &self,
163        epoch: Option<EpochNumber>,
164    ) -> impl Send + Future<Output = anyhow::Result<Option<RewardAmount>>>;
165    /// Get the current proposal participation.
166    fn current_proposal_participation(
167        &self,
168    ) -> impl Send + Future<Output = HashMap<BLSPubKey, f64>>;
169
170    /// Get the proposal participation for a given epoch.
171    fn proposal_participation(
172        &self,
173        epoch: EpochNumber,
174    ) -> impl Send + Future<Output = HashMap<BLSPubKey, f64>>;
175    /// Get the current vote participation.
176    fn current_vote_participation(&self) -> impl Send + Future<Output = HashMap<BLSPubKey, f64>>;
177
178    /// Get the vote participation for a given epoch.
179    fn vote_participation(
180        &self,
181        epoch: EpochNumber,
182    ) -> impl Send + Future<Output = HashMap<BLSPubKey, f64>>;
183
184    fn get_all_validators(
185        &self,
186        epoch: EpochNumber,
187        offset: u64,
188        limit: u64,
189    ) -> impl Send + Future<Output = anyhow::Result<Vec<RegisteredValidator<PubKey>>>>;
190
191    /// Get stake table events from L1 blocks `from_l1_block..=to_l1_block`.
192    fn stake_table_events(
193        &self,
194        from_l1_block: u64,
195        to_l1_block: u64,
196    ) -> impl Send + Future<Output = anyhow::Result<Vec<StakeTableEvent>>>;
197}
198
199// Thin wrapper trait to access persistence methods from API handlers
200#[async_trait]
201pub(crate) trait StateCertDataSource {
202    async fn get_state_cert_by_epoch(
203        &self,
204        epoch: u64,
205    ) -> anyhow::Result<Option<LightClientStateUpdateCertificateV2<SeqTypes>>>;
206
207    async fn insert_state_cert(
208        &self,
209        epoch: u64,
210        cert: LightClientStateUpdateCertificateV2<SeqTypes>,
211    ) -> anyhow::Result<()>;
212}
213
214pub(crate) trait CatchupDataSource: Sync {
215    /// Get the state of the requested `account`.
216    ///
217    /// The state is fetched from a snapshot at the given height and view, which _must_ correspond!
218    /// `height` is provided to simplify lookups for backends where data is not indexed by view.
219    /// This function is intended to be used for catchup, so `view` should be no older than the last
220    /// decided view.
221    fn get_account(
222        &self,
223        instance: &NodeState,
224        height: u64,
225        view: ViewNumber,
226        account: FeeAccount,
227    ) -> impl Send + Future<Output = anyhow::Result<AccountQueryData>> {
228        async move {
229            let tree = self
230                .get_accounts(instance, height, view, &[account])
231                .await?;
232            let (proof, balance) = FeeAccountProof::prove(&tree, account.into()).context(
233                format!("account {account} not available for height {height}, view {view}"),
234            )?;
235            Ok(AccountQueryData { balance, proof })
236        }
237    }
238
239    /// Get the state of the requested `accounts`.
240    ///
241    /// The state is fetched from a snapshot at the given height and view, which _must_ correspond!
242    /// `height` is provided to simplify lookups for backends where data is not indexed by view.
243    /// This function is intended to be used for catchup, so `view` should be no older than the last
244    /// decided view.
245    fn get_accounts(
246        &self,
247        instance: &NodeState,
248        height: u64,
249        view: ViewNumber,
250        accounts: &[FeeAccount],
251    ) -> impl Send + Future<Output = anyhow::Result<FeeMerkleTree>>;
252
253    /// Get the blocks Merkle tree frontier.
254    ///
255    /// The state is fetched from a snapshot at the given height and view, which _must_ correspond!
256    /// `height` is provided to simplify lookups for backends where data is not indexed by view.
257    /// This function is intended to be used for catchup, so `view` should be no older than the last
258    /// decided view.
259    fn get_frontier(
260        &self,
261        instance: &NodeState,
262        height: u64,
263        view: ViewNumber,
264    ) -> impl Send + Future<Output = anyhow::Result<BlocksFrontier>>;
265
266    fn get_chain_config(
267        &self,
268        commitment: Commitment<ChainConfig>,
269    ) -> impl Send + Future<Output = anyhow::Result<ChainConfig>>;
270
271    fn get_leaf_chain(
272        &self,
273        height: u64,
274    ) -> impl Send + Future<Output = anyhow::Result<Vec<Leaf2>>>;
275
276    /// Get the state of the requested `account`.
277    ///
278    /// The state is fetched from a snapshot at the given height and view, which _must_ correspond!
279    /// `height` is provided to simplify lookups for backends where data is not indexed by view.
280    /// This function is intended to be used for catchup, so `view` should be no older than the last
281    /// decided view.
282    fn get_reward_account_v2(
283        &self,
284        instance: &NodeState,
285        height: u64,
286        view: ViewNumber,
287        account: RewardAccountV2,
288    ) -> impl Send + Future<Output = anyhow::Result<RewardAccountQueryDataV2>> {
289        async move {
290            let tree = self
291                .get_reward_accounts_v2(instance, height, view, &[account])
292                .await?;
293            let (proof, balance) = RewardAccountProofV2::prove(&tree, account.into()).context(
294                format!("reward account {account} not available for height {height}, view {view}"),
295            )?;
296            Ok(RewardAccountQueryDataV2 { balance, proof })
297        }
298    }
299
300    fn get_reward_accounts_v2(
301        &self,
302        instance: &NodeState,
303        height: u64,
304        view: ViewNumber,
305        accounts: &[RewardAccountV2],
306    ) -> impl Send + Future<Output = anyhow::Result<RewardMerkleTreeV2>>;
307
308    fn get_reward_account_v1(
309        &self,
310        instance: &NodeState,
311        height: u64,
312        view: ViewNumber,
313        account: RewardAccountV1,
314    ) -> impl Send + Future<Output = anyhow::Result<RewardAccountQueryDataV1>> {
315        async move {
316            let tree = self
317                .get_reward_accounts_v1(instance, height, view, &[account])
318                .await?;
319            let (proof, balance) = RewardAccountProofV1::prove(&tree, account.into()).context(
320                format!("reward account {account} not available for height {height}, view {view}"),
321            )?;
322            Ok(RewardAccountQueryDataV1 { balance, proof })
323        }
324    }
325
326    fn get_reward_accounts_v1(
327        &self,
328        instance: &NodeState,
329        height: u64,
330        view: ViewNumber,
331        accounts: &[RewardAccountV1],
332    ) -> impl Send + Future<Output = anyhow::Result<RewardMerkleTreeV1>>;
333
334    fn get_reward_merkle_tree_v2(
335        &self,
336        height: u64,
337        view: ViewNumber,
338    ) -> impl Send + Future<Output = anyhow::Result<Vec<u8>>>;
339
340    fn get_state_cert(
341        &self,
342        epoch: u64,
343    ) -> impl Send + Future<Output = anyhow::Result<LightClientStateUpdateCertificateV2<SeqTypes>>>;
344}
345
346pub trait RequestResponseDataSource<Types: NodeType> {
347    fn request_vid_shares(
348        &self,
349        block_number: u64,
350        vid_common_data: VidCommonQueryData<Types>,
351        duration: Duration,
352    ) -> impl Future<Output = BoxFuture<'static, anyhow::Result<Vec<VidShare>>>> + Send;
353}
354
355#[async_trait]
356pub trait StateCertFetchingDataSource<Types: NodeType> {
357    async fn request_state_cert(
358        &self,
359        epoch: u64,
360        timeout: Duration,
361    ) -> Result<LightClientStateUpdateCertificateV2<Types>, StateCertFetchError>;
362}
363
364/// Database table size information.
365#[derive(Serialize, Deserialize, Clone, Debug)]
366pub struct TableSize {
367    pub table_name: String,
368    pub row_count: i64,
369    pub total_size_bytes: Option<i64>,
370}
371
372/// Data source for database metadata and statistics.
373///
374/// This trait is only implemented by SQL-based storage backends (PostgreSQL and SQLite).
375pub(crate) trait DatabaseMetadataSource {
376    /// Get the sizes of all tables in the database.
377    fn get_table_sizes(&self) -> impl Send + Future<Output = anyhow::Result<Vec<TableSize>>>;
378}
379
380#[cfg(any(test, feature = "testing"))]
381pub mod testing {
382    use super::{super::Options, *};
383
384    #[async_trait]
385    pub trait TestableSequencerDataSource: SequencerDataSource {
386        type Storage: Sync;
387
388        async fn create_storage() -> Self::Storage;
389        fn persistence_options(storage: &Self::Storage) -> Self::Options;
390        fn leaf_only_ds_options(
391            _storage: &Self::Storage,
392            _opt: Options,
393        ) -> anyhow::Result<Options> {
394            anyhow::bail!("not supported")
395        }
396        fn options(storage: &Self::Storage, opt: Options) -> Options;
397    }
398}