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