Skip to main content

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    Certificate2, FeeAccount, FeeAccountProof, FeeMerkleTree, Leaf2, NodeState, PubKey,
9    Transaction,
10    config::PublicNetworkConfig,
11    v0::traits::{PersistenceOptions, SequencerPersistence},
12    v0_3::{
13        AuthenticatedValidator, ChainConfig, RegisteredValidator, RewardAccountProofV1,
14        RewardAccountQueryDataV1, RewardAccountV1, RewardAmount, RewardMerkleTreeV1,
15        StakeTableEvent,
16    },
17    v0_4::{RewardAccountProofV2, RewardAccountQueryDataV2, RewardAccountV2, RewardMerkleTreeV2},
18};
19use futures::future::{BoxFuture, Future};
20use hotshot::types::BLSPubKey;
21use hotshot_query_service::{
22    availability::{AvailabilityDataSource, BlockQueryData, LeafQueryData, VidCommonQueryData},
23    data_source::{UpdateDataSource, VersionedDataSource},
24    fetching::provider::AnyProvider,
25    node::NodeDataSource,
26    status::StatusDataSource,
27};
28use hotshot_types::{
29    PeerConfig,
30    data::{EpochNumber, VidShare, ViewNumber},
31    light_client::{LCV3StateSignatureRequestBody, StateVerKey},
32    simple_certificate::LightClientStateUpdateCertificateV2,
33    traits::{network::ConnectedNetwork, node_implementation::NodeType},
34    x25519,
35};
36use indexmap::IndexMap;
37use light_client::{state::LightClientOptions, storage::LightClientSqliteOptions};
38use serde::{Deserialize, Serialize};
39use url::Url;
40
41use super::{
42    AccountQueryData, BlocksFrontier, fs,
43    options::{Options, Query},
44    sql,
45};
46use crate::{
47    SeqTypes, U256,
48    api::{ApiState, LightClientProvider},
49    persistence,
50    state_cert::StateCertFetchError,
51};
52
53pub trait DataSourceOptions: PersistenceOptions {
54    type DataSource: SequencerDataSource<Options = Self>;
55
56    fn enable_query_module(&self, opt: Options, query: Query) -> Options;
57}
58
59impl DataSourceOptions for persistence::sql::Options {
60    type DataSource = sql::DataSource;
61
62    fn enable_query_module(&self, opt: Options, query: Query) -> Options {
63        opt.query_sql(query, self.clone())
64    }
65}
66
67impl DataSourceOptions for persistence::fs::Options {
68    type DataSource = fs::DataSource;
69
70    fn enable_query_module(&self, opt: Options, query: Query) -> Options {
71        opt.query_fs(query, self.clone())
72    }
73}
74
75/// A data source with sequencer-specific functionality.
76///
77/// This trait extends the generic [`AvailabilityDataSource`] with some additional data needed to
78/// provided sequencer-specific endpoints.
79#[async_trait]
80pub trait SequencerDataSource:
81    AvailabilityDataSource<SeqTypes>
82    + NodeDataSource<SeqTypes>
83    + StatusDataSource
84    + UpdateDataSource<SeqTypes>
85    + VersionedDataSource
86    + Sized
87{
88    type Options: DataSourceOptions<DataSource = Self>;
89
90    /// Instantiate a data source from command line options.
91    async fn create(opt: Self::Options, provider: Provider, reset: bool) -> anyhow::Result<Self>;
92}
93
94/// Provider for fetching missing data for the query service.
95pub type Provider = AnyProvider<SeqTypes>;
96
97/// Create a provider for fetching missing data from a list of peer query services.
98pub(super) async fn provider<N, P>(
99    peers: impl IntoIterator<Item = Url>,
100    state: &ApiState<N, P>,
101    opt: LightClientOptions,
102    db_opt: LightClientSqliteOptions,
103) -> anyhow::Result<Provider>
104where
105    N: ConnectedNetwork<PubKey>,
106    P: SequencerPersistence,
107{
108    Ok(Provider::default()
109        .with_provider(LightClientProvider::new(peers, state.clone(), opt, db_opt).await?))
110}
111
112pub(crate) trait SubmitDataSource<N: ConnectedNetwork<PubKey>, P: SequencerPersistence> {
113    fn submit(&self, tx: Transaction) -> impl Send + Future<Output = anyhow::Result<()>>;
114}
115
116pub(crate) trait HotShotConfigDataSource {
117    fn get_config(&self) -> impl Send + Future<Output = PublicNetworkConfig>;
118}
119
120#[async_trait]
121pub(crate) trait StateSignatureDataSource<N: ConnectedNetwork<PubKey>> {
122    async fn get_state_signature(&self, height: u64) -> Option<LCV3StateSignatureRequestBody>;
123}
124
125pub(crate) trait NodeStateDataSource {
126    fn node_state(&self) -> impl Send + Future<Output = NodeState>;
127}
128
129#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
130pub struct NodePublicKeys {
131    pub eth_account: Option<Address>,
132    pub consensus_key: BLSPubKey,
133    pub state_ver_key: StateVerKey,
134    #[serde(with = "x25519_tagged")]
135    pub x25519_key: Option<x25519::PublicKey>,
136}
137
138mod x25519_tagged {
139    use serde::{Deserialize, Deserializer, Serialize, Serializer, de::Error as _};
140
141    use super::x25519;
142
143    pub fn serialize<S: Serializer>(
144        key: &Option<x25519::PublicKey>,
145        s: S,
146    ) -> Result<S::Ok, S::Error> {
147        key.as_ref().map(ToString::to_string).serialize(s)
148    }
149
150    pub fn deserialize<'de, D: Deserializer<'de>>(
151        d: D,
152    ) -> Result<Option<x25519::PublicKey>, D::Error> {
153        Option::<String>::deserialize(d)?
154            .map(|s| s.parse().map_err(D::Error::custom))
155            .transpose()
156    }
157}
158
159pub(crate) trait NodeKeysDataSource {
160    fn node_public_keys(&self) -> impl Send + Future<Output = NodePublicKeys>;
161}
162
163pub(crate) trait TokenDataSource<T: NodeType> {
164    fn get_initial_supply_l1(&self) -> impl Send + Future<Output = anyhow::Result<U256>>;
165    fn get_total_supply_l1(&self) -> impl Send + Future<Output = anyhow::Result<U256>>;
166    fn get_decided_header(&self) -> impl Send + Future<Output = espresso_types::Header>;
167}
168
169#[derive(Serialize, Deserialize)]
170#[serde(bound = "T: NodeType")]
171pub struct StakeTableWithEpochNumber<T: NodeType> {
172    pub epoch: Option<EpochNumber>,
173    pub stake_table: Vec<PeerConfig<T>>,
174}
175
176pub(crate) trait StakeTableDataSource<T: NodeType> {
177    /// Get the stake table for a given epoch
178    fn get_stake_table(
179        &self,
180        epoch: Option<EpochNumber>,
181    ) -> impl Send + Future<Output = anyhow::Result<Vec<PeerConfig<T>>>>;
182
183    /// Get the stake table for the current epoch if not provided
184    fn get_stake_table_current(
185        &self,
186    ) -> impl Send + Future<Output = anyhow::Result<StakeTableWithEpochNumber<T>>>;
187
188    /// Get the DA stake table for a given epoch
189    fn get_da_stake_table(
190        &self,
191        epoch: Option<EpochNumber>,
192    ) -> impl Send + Future<Output = anyhow::Result<Vec<PeerConfig<T>>>>;
193
194    /// Get the DA stake table for the current epoch if not provided
195    fn get_da_stake_table_current(
196        &self,
197    ) -> impl Send + Future<Output = anyhow::Result<StakeTableWithEpochNumber<T>>>;
198
199    /// Get all the validators
200    fn get_validators(
201        &self,
202        epoch: EpochNumber,
203    ) -> impl Send + Future<Output = anyhow::Result<IndexMap<Address, AuthenticatedValidator<BLSPubKey>>>>;
204
205    fn get_block_reward(
206        &self,
207        epoch: Option<EpochNumber>,
208    ) -> impl Send + Future<Output = anyhow::Result<Option<RewardAmount>>>;
209    /// Get the current proposal participation.
210    fn current_proposal_participation(
211        &self,
212    ) -> impl Send + Future<Output = HashMap<BLSPubKey, f64>>;
213
214    /// Get the proposal participation for a given epoch.
215    fn proposal_participation(
216        &self,
217        epoch: EpochNumber,
218    ) -> impl Send + Future<Output = HashMap<BLSPubKey, f64>>;
219    /// Get the current vote participation.
220    fn current_vote_participation(&self) -> impl Send + Future<Output = HashMap<BLSPubKey, f64>>;
221
222    /// Get the vote participation for a given epoch.
223    fn vote_participation(
224        &self,
225        epoch: EpochNumber,
226    ) -> impl Send + Future<Output = HashMap<BLSPubKey, f64>>;
227
228    fn get_all_validators(
229        &self,
230        epoch: EpochNumber,
231        offset: u64,
232        limit: u64,
233    ) -> impl Send + Future<Output = anyhow::Result<Vec<RegisteredValidator<PubKey>>>>;
234
235    /// Get stake table events from L1 blocks `from_l1_block..=to_l1_block`.
236    fn stake_table_events(
237        &self,
238        from_l1_block: u64,
239        to_l1_block: u64,
240    ) -> impl Send + Future<Output = anyhow::Result<Vec<StakeTableEvent>>>;
241}
242
243// Thin wrapper trait to access persistence methods from API handlers
244#[async_trait]
245pub(crate) trait StateCertDataSource {
246    async fn get_state_cert_by_epoch(
247        &self,
248        epoch: u64,
249    ) -> anyhow::Result<Option<LightClientStateUpdateCertificateV2<SeqTypes>>>;
250
251    async fn insert_state_cert(
252        &self,
253        epoch: u64,
254        cert: LightClientStateUpdateCertificateV2<SeqTypes>,
255    ) -> anyhow::Result<()>;
256}
257
258pub(crate) trait CatchupDataSource: Sync {
259    /// Get the state of the requested `account`.
260    ///
261    /// The state is fetched from a snapshot at the given height and view, which _must_ correspond!
262    /// `height` is provided to simplify lookups for backends where data is not indexed by view.
263    /// This function is intended to be used for catchup, so `view` should be no older than the last
264    /// decided view.
265    fn get_account(
266        &self,
267        instance: &NodeState,
268        height: u64,
269        view: ViewNumber,
270        account: FeeAccount,
271    ) -> impl Send + Future<Output = anyhow::Result<AccountQueryData>> {
272        async move {
273            let tree = self
274                .get_accounts(instance, height, view, &[account])
275                .await?;
276            let (proof, balance) = FeeAccountProof::prove(&tree, account.into()).context(
277                format!("account {account} not available for height {height}, view {view}"),
278            )?;
279            Ok(AccountQueryData { balance, proof })
280        }
281    }
282
283    /// Get the state of the requested `accounts`.
284    ///
285    /// The state is fetched from a snapshot at the given height and view, which _must_ correspond!
286    /// `height` is provided to simplify lookups for backends where data is not indexed by view.
287    /// This function is intended to be used for catchup, so `view` should be no older than the last
288    /// decided view.
289    fn get_accounts(
290        &self,
291        instance: &NodeState,
292        height: u64,
293        view: ViewNumber,
294        accounts: &[FeeAccount],
295    ) -> impl Send + Future<Output = anyhow::Result<FeeMerkleTree>>;
296
297    /// Get the blocks Merkle tree frontier.
298    ///
299    /// The state is fetched from a snapshot at the given height and view, which _must_ correspond!
300    /// `height` is provided to simplify lookups for backends where data is not indexed by view.
301    /// This function is intended to be used for catchup, so `view` should be no older than the last
302    /// decided view.
303    fn get_frontier(
304        &self,
305        instance: &NodeState,
306        height: u64,
307        view: ViewNumber,
308    ) -> impl Send + Future<Output = anyhow::Result<BlocksFrontier>>;
309
310    fn get_chain_config(
311        &self,
312        commitment: Commitment<ChainConfig>,
313    ) -> impl Send + Future<Output = anyhow::Result<ChainConfig>>;
314
315    fn get_leaf_chain(
316        &self,
317        height: u64,
318    ) -> impl Send + Future<Output = anyhow::Result<Vec<Leaf2>>>;
319
320    /// Load the cert2 stored at exactly `height`, if one exists.
321    ///
322    /// Returns `None` when no cert2 is stored at exactly `height`.
323    fn get_cert2(
324        &self,
325        _height: u64,
326    ) -> impl Send + Future<Output = anyhow::Result<Option<Certificate2<SeqTypes>>>> {
327        async { Ok(None) }
328    }
329
330    /// Get the state of the requested `account`.
331    ///
332    /// The state is fetched from a snapshot at the given height and view, which _must_ correspond!
333    /// `height` is provided to simplify lookups for backends where data is not indexed by view.
334    /// This function is intended to be used for catchup, so `view` should be no older than the last
335    /// decided view.
336    fn get_reward_account_v2(
337        &self,
338        instance: &NodeState,
339        height: u64,
340        view: ViewNumber,
341        account: RewardAccountV2,
342    ) -> impl Send + Future<Output = anyhow::Result<RewardAccountQueryDataV2>> {
343        async move {
344            let tree = self
345                .get_reward_accounts_v2(instance, height, view, &[account])
346                .await?;
347            let (proof, balance) = RewardAccountProofV2::prove(&tree, account.into()).context(
348                format!("reward account {account} not available for height {height}, view {view}"),
349            )?;
350            Ok(RewardAccountQueryDataV2 { balance, proof })
351        }
352    }
353
354    fn get_reward_accounts_v2(
355        &self,
356        instance: &NodeState,
357        height: u64,
358        view: ViewNumber,
359        accounts: &[RewardAccountV2],
360    ) -> impl Send + Future<Output = anyhow::Result<RewardMerkleTreeV2>>;
361
362    fn get_reward_account_v1(
363        &self,
364        instance: &NodeState,
365        height: u64,
366        view: ViewNumber,
367        account: RewardAccountV1,
368    ) -> impl Send + Future<Output = anyhow::Result<RewardAccountQueryDataV1>> {
369        async move {
370            let tree = self
371                .get_reward_accounts_v1(instance, height, view, &[account])
372                .await?;
373            let (proof, balance) = RewardAccountProofV1::prove(&tree, account.into()).context(
374                format!("reward account {account} not available for height {height}, view {view}"),
375            )?;
376            Ok(RewardAccountQueryDataV1 { balance, proof })
377        }
378    }
379
380    fn get_reward_accounts_v1(
381        &self,
382        instance: &NodeState,
383        height: u64,
384        view: ViewNumber,
385        accounts: &[RewardAccountV1],
386    ) -> impl Send + Future<Output = anyhow::Result<RewardMerkleTreeV1>>;
387
388    fn get_reward_merkle_tree_v2(
389        &self,
390        height: u64,
391        view: ViewNumber,
392    ) -> impl Send + Future<Output = anyhow::Result<Vec<u8>>>;
393
394    fn get_state_cert(
395        &self,
396        epoch: u64,
397    ) -> impl Send + Future<Output = anyhow::Result<LightClientStateUpdateCertificateV2<SeqTypes>>>;
398}
399
400pub trait RequestResponseDataSource<Types: NodeType> {
401    fn request_vid_shares(
402        &self,
403        block_number: u64,
404        vid_common_data: VidCommonQueryData<Types>,
405        duration: Duration,
406    ) -> impl Future<Output = BoxFuture<'static, anyhow::Result<Vec<VidShare>>>> + Send;
407}
408
409#[async_trait]
410pub trait StateCertFetchingDataSource<Types: NodeType> {
411    async fn request_state_cert(
412        &self,
413        epoch: u64,
414        timeout: Duration,
415    ) -> Result<LightClientStateUpdateCertificateV2<Types>, StateCertFetchError>;
416}
417
418/// Database table size information.
419#[derive(Serialize, Deserialize, Clone, Debug)]
420pub struct TableSize {
421    pub table_name: String,
422    pub row_count: i64,
423    pub total_size_bytes: Option<i64>,
424}
425
426/// Status of a single deferred background migration.
427#[derive(Serialize, Deserialize, Clone, Debug)]
428pub struct MigrationStatus {
429    pub name: String,
430    pub started_at: chrono::DateTime<chrono::Utc>,
431    pub completed_at: Option<chrono::DateTime<chrono::Utc>>,
432    pub last_offset: Option<i64>,
433}
434
435/// Data source for database metadata and statistics.
436///
437/// This trait is only implemented by SQL-based storage backends (PostgreSQL and SQLite).
438pub(crate) trait DatabaseMetadataSource {
439    /// Get the sizes of all tables in the database.
440    fn get_table_sizes(&self) -> impl Send + Future<Output = anyhow::Result<Vec<TableSize>>>;
441
442    /// Get the status of all deferred background migrations.
443    fn get_migration_status(
444        &self,
445    ) -> impl Send + Future<Output = anyhow::Result<Vec<MigrationStatus>>>;
446}
447
448// ============================================================================
449// Arc delegation implementations
450// ============================================================================
451// These implementations allow Arc<T> to implement the data source traits
452// when T implements them, which is necessary for NodeApiStateImpl to work
453// with Arc-wrapped data sources.
454
455use std::sync::Arc;
456
457#[async_trait]
458impl<D> StateCertDataSource for Arc<D>
459where
460    D: StateCertDataSource + Sync + Send,
461{
462    async fn get_state_cert_by_epoch(
463        &self,
464        epoch: u64,
465    ) -> anyhow::Result<Option<LightClientStateUpdateCertificateV2<SeqTypes>>> {
466        (*self).get_state_cert_by_epoch(epoch).await
467    }
468
469    async fn insert_state_cert(
470        &self,
471        epoch: u64,
472        cert: LightClientStateUpdateCertificateV2<SeqTypes>,
473    ) -> anyhow::Result<()> {
474        (*self).insert_state_cert(epoch, cert).await
475    }
476}
477
478impl<Types, D> RequestResponseDataSource<Types> for Arc<D>
479where
480    Types: NodeType,
481    D: RequestResponseDataSource<Types> + Send + Sync,
482{
483    async fn request_vid_shares(
484        &self,
485        block_number: u64,
486        vid_common_data: VidCommonQueryData<Types>,
487        timeout_duration: Duration,
488    ) -> BoxFuture<'static, anyhow::Result<Vec<VidShare>>> {
489        self.as_ref()
490            .request_vid_shares(block_number, vid_common_data, timeout_duration)
491            .await
492    }
493}
494
495#[async_trait]
496impl<Types, D> StateCertFetchingDataSource<Types> for Arc<D>
497where
498    Types: NodeType,
499    D: StateCertFetchingDataSource<Types> + Sync + Send,
500{
501    async fn request_state_cert(
502        &self,
503        epoch: u64,
504        timeout: Duration,
505    ) -> Result<LightClientStateUpdateCertificateV2<Types>, StateCertFetchError> {
506        (*self).request_state_cert(epoch, timeout).await
507    }
508}
509
510#[async_trait]
511impl<T, D> StakeTableDataSource<T> for Arc<D>
512where
513    T: NodeType,
514    D: StakeTableDataSource<T> + Sync + Send,
515{
516    fn get_stake_table(
517        &self,
518        epoch: Option<EpochNumber>,
519    ) -> impl Send + Future<Output = anyhow::Result<Vec<PeerConfig<T>>>> {
520        let this = self.clone();
521        async move { (*this).get_stake_table(epoch).await }
522    }
523
524    fn get_stake_table_current(
525        &self,
526    ) -> impl Send + Future<Output = anyhow::Result<StakeTableWithEpochNumber<T>>> {
527        let this = self.clone();
528        async move { (*this).get_stake_table_current().await }
529    }
530
531    fn get_da_stake_table(
532        &self,
533        epoch: Option<EpochNumber>,
534    ) -> impl Send + Future<Output = anyhow::Result<Vec<PeerConfig<T>>>> {
535        let this = self.clone();
536        async move { (*this).get_da_stake_table(epoch).await }
537    }
538
539    fn get_da_stake_table_current(
540        &self,
541    ) -> impl Send + Future<Output = anyhow::Result<StakeTableWithEpochNumber<T>>> {
542        let this = self.clone();
543        async move { (*this).get_da_stake_table_current().await }
544    }
545
546    fn get_validators(
547        &self,
548        epoch: EpochNumber,
549    ) -> impl Send + Future<Output = anyhow::Result<IndexMap<Address, AuthenticatedValidator<BLSPubKey>>>>
550    {
551        let this = self.clone();
552        async move { (*this).get_validators(epoch).await }
553    }
554
555    fn get_block_reward(
556        &self,
557        epoch: Option<EpochNumber>,
558    ) -> impl Send + Future<Output = anyhow::Result<Option<RewardAmount>>> {
559        let this = self.clone();
560        async move { (*this).get_block_reward(epoch).await }
561    }
562
563    fn current_proposal_participation(
564        &self,
565    ) -> impl Send + Future<Output = HashMap<BLSPubKey, f64>> {
566        let this = self.clone();
567        async move { (*this).current_proposal_participation().await }
568    }
569
570    fn proposal_participation(
571        &self,
572        epoch: EpochNumber,
573    ) -> impl Send + Future<Output = HashMap<BLSPubKey, f64>> {
574        let this = self.clone();
575        async move { (*this).proposal_participation(epoch).await }
576    }
577
578    fn current_vote_participation(&self) -> impl Send + Future<Output = HashMap<BLSPubKey, f64>> {
579        let this = self.clone();
580        async move { (*this).current_vote_participation().await }
581    }
582
583    fn vote_participation(
584        &self,
585        epoch: EpochNumber,
586    ) -> impl Send + Future<Output = HashMap<BLSPubKey, f64>> {
587        let this = self.clone();
588        async move { (*this).vote_participation(epoch).await }
589    }
590
591    fn get_all_validators(
592        &self,
593        epoch: EpochNumber,
594        offset: u64,
595        limit: u64,
596    ) -> impl Send + Future<Output = anyhow::Result<Vec<RegisteredValidator<PubKey>>>> {
597        let this = self.clone();
598        async move { (*this).get_all_validators(epoch, offset, limit).await }
599    }
600
601    fn stake_table_events(
602        &self,
603        from_l1_block: u64,
604        to_l1_block: u64,
605    ) -> impl Send + Future<Output = anyhow::Result<Vec<StakeTableEvent>>> {
606        let this = self.clone();
607        async move { (*this).stake_table_events(from_l1_block, to_l1_block).await }
608    }
609}
610
611/// Data source for pruning state: the oldest retained block and leaf.
612///
613/// SQL backends return the actual oldest entry; the filesystem backend always returns `None`
614/// since it does not prune.
615pub(crate) trait PruningDataSource {
616    /// Get the oldest block in storage, or `None` if empty or unsupported.
617    fn get_oldest_block(
618        &self,
619    ) -> impl Send + Future<Output = anyhow::Result<Option<BlockQueryData<SeqTypes>>>>;
620
621    /// Get the oldest leaf in storage, or `None` if empty or unsupported.
622    fn get_oldest_leaf(
623        &self,
624    ) -> impl Send + Future<Output = anyhow::Result<Option<LeafQueryData<SeqTypes>>>>;
625}
626
627#[cfg(test)]
628mod test {
629    use hotshot_types::{light_client::StateKeyPair, traits::signature_key::SignatureKey as _};
630
631    use super::*;
632
633    #[test]
634    fn test_node_public_keys_serialize_like_stake_table() {
635        let account = Address::random();
636        let consensus_key = BLSPubKey::generated_from_seed_indexed([1; 32], 0).0;
637        let state_ver_key = StateKeyPair::generate_from_seed_indexed([2; 32], 0).ver_key();
638        let x25519_key = x25519::Keypair::generated_from_seed_indexed([3; 32], 0)
639            .unwrap()
640            .public_key();
641
642        let validator = serde_json::to_value(RegisteredValidator::<BLSPubKey> {
643            account,
644            stake_table_key: Some(consensus_key),
645            state_ver_key: Some(state_ver_key.clone()),
646            stake: U256::from(1u64),
647            commission: 0,
648            delegators: HashMap::new(),
649            authenticated: true,
650            x25519_key: Some(x25519_key),
651            p2p_addr: None,
652        })
653        .unwrap();
654
655        let keys = serde_json::to_value(NodePublicKeys {
656            eth_account: Some(account),
657            consensus_key,
658            state_ver_key,
659            x25519_key: Some(x25519_key),
660        })
661        .unwrap();
662
663        assert_eq!(keys["eth_account"], validator["account"]);
664        assert_eq!(keys["consensus_key"], validator["stake_table_key"]);
665        assert_eq!(keys["state_ver_key"], validator["state_ver_key"]);
666        assert_eq!(keys["x25519_key"], x25519_key.to_string());
667    }
668}
669
670#[cfg(any(test, feature = "testing"))]
671pub mod testing {
672    use super::{super::Options, *};
673
674    #[async_trait]
675    pub trait TestableSequencerDataSource: SequencerDataSource {
676        type Storage: Sync;
677
678        async fn create_storage() -> Self::Storage;
679        fn persistence_options(storage: &Self::Storage) -> Self::Options;
680        fn leaf_only_ds_options(
681            _storage: &Self::Storage,
682            _opt: Options,
683        ) -> anyhow::Result<Options> {
684            anyhow::bail!("not supported")
685        }
686        fn options(storage: &Self::Storage, opt: Options) -> Options;
687    }
688}