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