espresso_types/v0/impls/
instance_state.rs

1use std::{collections::BTreeMap, sync::Arc};
2
3use alloy::primitives::Address;
4use anyhow::{bail, Context};
5#[cfg(any(test, feature = "testing"))]
6use async_lock::RwLock;
7use async_trait::async_trait;
8use hotshot_types::{
9    data::EpochNumber, epoch_membership::EpochMembershipCoordinator, traits::states::InstanceState,
10    HotShotConfig,
11};
12#[cfg(any(test, feature = "testing"))]
13use vbs::version::StaticVersionType;
14use vbs::version::Version;
15
16use super::{
17    state::ValidatedState,
18    traits::{EventsPersistenceRead, MembershipPersistence},
19    v0_1::NoStorage,
20    v0_3::{EventKey, IndexedStake, StakeTableEvent},
21    SeqTypes, UpgradeType, ViewBasedUpgrade,
22};
23use crate::{
24    v0::{
25        impls::StakeTableHash, traits::StateCatchup, v0_3::ChainConfig, GenesisHeader, L1BlockInfo,
26        L1Client, Timestamp, Upgrade, UpgradeMode,
27    },
28    v0_3::{RewardAmount, Validator},
29    EpochCommittees, PubKey, ValidatorMap,
30};
31
32/// Represents the immutable state of a node.
33///
34/// For mutable state, use `ValidatedState`.
35#[derive(derive_more::Debug, Clone)]
36pub struct NodeState {
37    pub node_id: u64,
38    pub chain_config: ChainConfig,
39    pub l1_client: L1Client,
40    #[debug("{}", state_catchup.name())]
41    pub state_catchup: Arc<dyn StateCatchup>,
42    pub genesis_header: GenesisHeader,
43    pub genesis_state: ValidatedState,
44    pub genesis_chain_config: ChainConfig,
45    pub l1_genesis: Option<L1BlockInfo>,
46    #[debug(skip)]
47    pub coordinator: EpochMembershipCoordinator<SeqTypes>,
48    pub epoch_height: Option<u64>,
49    pub genesis_version: Version,
50    pub epoch_start_block: u64,
51
52    /// Map containing all planned and executed upgrades.
53    ///
54    /// Currently, only one upgrade can be executed at a time.
55    /// For multiple upgrades, the node needs to be restarted after each upgrade.
56    ///
57    /// This field serves as a record for planned and past upgrades,
58    /// listed in the genesis TOML file. It will be very useful if multiple upgrades
59    /// are supported in the future.
60    pub upgrades: BTreeMap<Version, Upgrade>,
61    /// Current version of the sequencer.
62    ///
63    /// This version is checked to determine if an upgrade is planned,
64    /// and which version variant for versioned types  
65    /// to use in functions such as genesis.
66    /// (example: genesis returns V2 Header if version is 0.2)
67    pub current_version: Version,
68}
69
70impl NodeState {
71    pub async fn block_reward(&self, epoch: EpochNumber) -> anyhow::Result<RewardAmount> {
72        EpochCommittees::fetch_and_calculate_block_reward(epoch, self.coordinator.clone()).await
73    }
74
75    pub async fn fixed_block_reward(&self) -> anyhow::Result<RewardAmount> {
76        let coordinator = self.coordinator.clone();
77        let membership = coordinator.membership().read().await;
78        membership
79            .fixed_block_reward()
80            .context("fixed block reward not found")
81    }
82}
83
84#[async_trait]
85impl MembershipPersistence for NoStorage {
86    async fn load_stake(
87        &self,
88        _epoch: EpochNumber,
89    ) -> anyhow::Result<Option<(ValidatorMap, Option<RewardAmount>, Option<StakeTableHash>)>> {
90        Ok(None)
91    }
92
93    async fn load_latest_stake(&self, _limit: u64) -> anyhow::Result<Option<Vec<IndexedStake>>> {
94        Ok(None)
95    }
96
97    async fn store_stake(
98        &self,
99        _epoch: EpochNumber,
100        _stake: ValidatorMap,
101        _block_reward: Option<RewardAmount>,
102        _stake_table_hash: Option<StakeTableHash>,
103    ) -> anyhow::Result<()> {
104        Ok(())
105    }
106
107    async fn store_events(
108        &self,
109        _l1_finalized: u64,
110        _events: Vec<(EventKey, StakeTableEvent)>,
111    ) -> anyhow::Result<()> {
112        Ok(())
113    }
114
115    async fn load_events(
116        &self,
117        _from_l1_block: u64,
118        _l1_block: u64,
119    ) -> anyhow::Result<(
120        Option<EventsPersistenceRead>,
121        Vec<(EventKey, StakeTableEvent)>,
122    )> {
123        bail!("unimplemented")
124    }
125
126    async fn store_all_validators(
127        &self,
128        _epoch: EpochNumber,
129        _all_validators: ValidatorMap,
130    ) -> anyhow::Result<()> {
131        Ok(())
132    }
133
134    async fn load_all_validators(
135        &self,
136        _epoch: EpochNumber,
137        _offset: u64,
138        _limit: u64,
139    ) -> anyhow::Result<Vec<Validator<PubKey>>> {
140        bail!("unimplemented")
141    }
142}
143
144impl NodeState {
145    pub fn new(
146        node_id: u64,
147        chain_config: ChainConfig,
148        l1_client: L1Client,
149        catchup: impl StateCatchup + 'static,
150        current_version: Version,
151        coordinator: EpochMembershipCoordinator<SeqTypes>,
152        genesis_version: Version,
153    ) -> Self {
154        Self {
155            node_id,
156            chain_config,
157            genesis_chain_config: chain_config,
158            l1_client,
159            state_catchup: Arc::new(catchup),
160            genesis_header: GenesisHeader {
161                timestamp: Default::default(),
162                chain_config,
163            },
164            genesis_state: ValidatedState {
165                chain_config: chain_config.into(),
166                ..Default::default()
167            },
168            l1_genesis: None,
169            upgrades: Default::default(),
170            current_version,
171            epoch_height: None,
172            coordinator,
173            genesis_version,
174            epoch_start_block: 0,
175        }
176    }
177
178    #[cfg(any(test, feature = "testing"))]
179    pub fn mock() -> Self {
180        use hotshot_example_types::storage_types::TestStorage;
181        use vbs::version::StaticVersion;
182
183        use crate::v0_3::Fetcher;
184
185        let chain_config = ChainConfig::default();
186        let l1 = L1Client::new(vec!["http://localhost:3331".parse().unwrap()])
187            .expect("Failed to create L1 client");
188
189        let membership = Arc::new(RwLock::new(EpochCommittees::new_stake(
190            vec![],
191            Default::default(),
192            None,
193            Fetcher::mock(),
194            0,
195        )));
196
197        let storage = TestStorage::default();
198        let coordinator = EpochMembershipCoordinator::new(membership, 100, &storage);
199        Self::new(
200            0,
201            chain_config,
202            l1,
203            Arc::new(mock::MockStateCatchup::default()),
204            StaticVersion::<0, 1>::version(),
205            coordinator,
206            Version { major: 0, minor: 1 },
207        )
208    }
209
210    #[cfg(any(test, feature = "testing"))]
211    pub fn mock_v2() -> Self {
212        use hotshot_example_types::storage_types::TestStorage;
213        use vbs::version::StaticVersion;
214
215        use crate::v0_3::Fetcher;
216
217        let chain_config = ChainConfig::default();
218        let l1 = L1Client::new(vec!["http://localhost:3331".parse().unwrap()])
219            .expect("Failed to create L1 client");
220
221        let membership = Arc::new(RwLock::new(EpochCommittees::new_stake(
222            vec![],
223            Default::default(),
224            None,
225            Fetcher::mock(),
226            0,
227        )));
228        let storage = TestStorage::default();
229        let coordinator = EpochMembershipCoordinator::new(membership, 100, &storage);
230
231        Self::new(
232            0,
233            chain_config,
234            l1,
235            Arc::new(mock::MockStateCatchup::default()),
236            StaticVersion::<0, 2>::version(),
237            coordinator,
238            Version { major: 0, minor: 2 },
239        )
240    }
241
242    #[cfg(any(test, feature = "testing"))]
243    pub fn mock_v3() -> Self {
244        use hotshot_example_types::storage_types::TestStorage;
245        use vbs::version::StaticVersion;
246
247        use crate::v0_3::Fetcher;
248        let l1 = L1Client::new(vec!["http://localhost:3331".parse().unwrap()])
249            .expect("Failed to create L1 client");
250
251        let membership = Arc::new(RwLock::new(EpochCommittees::new_stake(
252            vec![],
253            Default::default(),
254            None,
255            Fetcher::mock(),
256            0,
257        )));
258
259        let storage = TestStorage::default();
260        let coordinator = EpochMembershipCoordinator::new(membership, 100, &storage);
261        Self::new(
262            0,
263            ChainConfig::default(),
264            l1,
265            mock::MockStateCatchup::default(),
266            StaticVersion::<0, 3>::version(),
267            coordinator,
268            Version { major: 0, minor: 3 },
269        )
270    }
271
272    pub fn with_l1(mut self, l1_client: L1Client) -> Self {
273        self.l1_client = l1_client;
274        self
275    }
276
277    pub fn with_genesis(mut self, state: ValidatedState) -> Self {
278        self.genesis_state = state;
279        self
280    }
281
282    pub fn with_chain_config(mut self, cfg: ChainConfig) -> Self {
283        self.chain_config = cfg;
284        self
285    }
286
287    pub fn with_upgrades(mut self, upgrades: BTreeMap<Version, Upgrade>) -> Self {
288        self.upgrades = upgrades;
289        self
290    }
291
292    pub fn with_current_version(mut self, version: Version) -> Self {
293        self.current_version = version;
294        self
295    }
296
297    pub fn with_genesis_version(mut self, version: Version) -> Self {
298        self.genesis_version = version;
299        self
300    }
301
302    pub fn with_epoch_height(mut self, epoch_height: u64) -> Self {
303        self.epoch_height = Some(epoch_height);
304        self
305    }
306
307    pub fn with_epoch_start_block(mut self, epoch_start_block: u64) -> Self {
308        self.epoch_start_block = epoch_start_block;
309        self
310    }
311}
312
313/// NewType to hold upgrades and some convenience behavior.
314pub struct UpgradeMap(pub BTreeMap<Version, Upgrade>);
315impl UpgradeMap {
316    pub fn chain_config(&self, version: Version) -> ChainConfig {
317        self.0
318            .get(&version)
319            .unwrap()
320            .upgrade_type
321            .chain_config()
322            .unwrap()
323    }
324}
325
326impl From<BTreeMap<Version, Upgrade>> for UpgradeMap {
327    fn from(inner: BTreeMap<Version, Upgrade>) -> Self {
328        Self(inner)
329    }
330}
331
332// This allows us to turn on `Default` on InstanceState trait
333// which is used in `HotShot` by `TestBuilderImplementation`.
334#[cfg(any(test, feature = "testing"))]
335impl Default for NodeState {
336    fn default() -> Self {
337        use hotshot_example_types::storage_types::TestStorage;
338        use vbs::version::StaticVersion;
339
340        use crate::v0_3::Fetcher;
341
342        let chain_config = ChainConfig::default();
343        let l1 = L1Client::new(vec!["http://localhost:3331".parse().unwrap()])
344            .expect("Failed to create L1 client");
345
346        let membership = Arc::new(RwLock::new(EpochCommittees::new_stake(
347            vec![],
348            Default::default(),
349            None,
350            Fetcher::mock(),
351            0,
352        )));
353        let storage = TestStorage::default();
354        let coordinator = EpochMembershipCoordinator::new(membership, 100, &storage);
355
356        Self::new(
357            1u64,
358            chain_config,
359            l1,
360            Arc::new(mock::MockStateCatchup::default()),
361            StaticVersion::<0, 1>::version(),
362            coordinator,
363            Version { major: 0, minor: 1 },
364        )
365    }
366}
367
368impl InstanceState for NodeState {}
369
370impl Upgrade {
371    pub fn set_hotshot_config_parameters(&self, config: &mut HotShotConfig<SeqTypes>) {
372        match &self.mode {
373            UpgradeMode::View(v) => {
374                config.start_proposing_view = v.start_proposing_view;
375                config.stop_proposing_view = v.stop_proposing_view;
376                config.start_voting_view = v.start_voting_view.unwrap_or(0);
377                config.stop_voting_view = v.stop_voting_view.unwrap_or(u64::MAX);
378                config.start_proposing_time = 0;
379                config.stop_proposing_time = u64::MAX;
380                config.start_voting_time = 0;
381                config.stop_voting_time = u64::MAX;
382            },
383            UpgradeMode::Time(t) => {
384                config.start_proposing_time = t.start_proposing_time.unix_timestamp();
385                config.stop_proposing_time = t.stop_proposing_time.unix_timestamp();
386                config.start_voting_time = t.start_voting_time.unwrap_or_default().unix_timestamp();
387                config.stop_voting_time = t
388                    .stop_voting_time
389                    .unwrap_or(Timestamp::max())
390                    .unix_timestamp();
391                config.start_proposing_view = 0;
392                config.stop_proposing_view = u64::MAX;
393                config.start_voting_view = 0;
394                config.stop_voting_view = u64::MAX;
395            },
396        }
397    }
398    pub fn pos_view_based(address: Address) -> Upgrade {
399        let chain_config = ChainConfig {
400            base_fee: 0.into(),
401            stake_table_contract: Some(address),
402            ..Default::default()
403        };
404
405        let mode = UpgradeMode::View(ViewBasedUpgrade {
406            start_voting_view: None,
407            stop_voting_view: None,
408            start_proposing_view: 200,
409            stop_proposing_view: 1000,
410        });
411
412        let upgrade_type = UpgradeType::Epoch { chain_config };
413        Upgrade { mode, upgrade_type }
414    }
415}
416
417#[cfg(any(test, feature = "testing"))]
418pub mod mock {
419    use std::collections::HashMap;
420
421    use alloy::primitives::U256;
422    use anyhow::Context;
423    use async_trait::async_trait;
424    use committable::Commitment;
425    use hotshot_types::{
426        data::ViewNumber, simple_certificate::LightClientStateUpdateCertificateV2,
427        stake_table::HSStakeTable,
428    };
429    use jf_merkle_tree_compat::{ForgetableMerkleTreeScheme, MerkleTreeScheme};
430
431    use super::*;
432    use crate::{
433        retain_accounts,
434        v0_3::{RewardAccountProofV1, RewardAccountV1, RewardMerkleCommitmentV1},
435        v0_4::{RewardAccountProofV2, RewardAccountV2, RewardMerkleCommitmentV2},
436        BackoffParams, BlockMerkleTree, FeeAccount, FeeAccountProof, FeeMerkleCommitment, Leaf2,
437    };
438
439    #[derive(Debug, Clone)]
440    pub struct MockStateCatchup {
441        backoff: BackoffParams,
442        state: HashMap<ViewNumber, Arc<ValidatedState>>,
443        delay: std::time::Duration,
444    }
445
446    impl Default for MockStateCatchup {
447        fn default() -> Self {
448            Self {
449                backoff: Default::default(),
450                state: Default::default(),
451                delay: std::time::Duration::ZERO,
452            }
453        }
454    }
455
456    impl FromIterator<(ViewNumber, Arc<ValidatedState>)> for MockStateCatchup {
457        fn from_iter<I: IntoIterator<Item = (ViewNumber, Arc<ValidatedState>)>>(iter: I) -> Self {
458            Self {
459                backoff: Default::default(),
460                state: iter.into_iter().collect(),
461                delay: std::time::Duration::ZERO,
462            }
463        }
464    }
465
466    impl MockStateCatchup {
467        pub fn with_delay(mut self, delay: std::time::Duration) -> Self {
468            self.delay = delay;
469            self
470        }
471    }
472
473    #[async_trait]
474    impl StateCatchup for MockStateCatchup {
475        async fn try_fetch_leaf(
476            &self,
477            _retry: usize,
478            _height: u64,
479            _stake_table: HSStakeTable<SeqTypes>,
480            _success_threshold: U256,
481        ) -> anyhow::Result<Leaf2> {
482            Err(anyhow::anyhow!("todo"))
483        }
484
485        async fn try_fetch_accounts(
486            &self,
487            _retry: usize,
488            _instance: &NodeState,
489            _height: u64,
490            view: ViewNumber,
491            fee_merkle_tree_root: FeeMerkleCommitment,
492            accounts: &[FeeAccount],
493        ) -> anyhow::Result<Vec<FeeAccountProof>> {
494            tokio::time::sleep(self.delay).await;
495
496            let src = &self.state[&view].fee_merkle_tree;
497            assert_eq!(src.commitment(), fee_merkle_tree_root);
498
499            tracing::info!("catchup: fetching accounts {accounts:?} for view {view}");
500            let tree = retain_accounts(src, accounts.iter().copied())
501                .with_context(|| "failed to retain accounts")?;
502
503            // Verify the proofs
504            let mut proofs = Vec::new();
505            for account in accounts {
506                let (proof, _) = FeeAccountProof::prove(&tree, (*account).into())
507                    .context(format!("response missing fee account {account}"))?;
508                proof.verify(&fee_merkle_tree_root).context(format!(
509                    "invalid proof for fee account {account}, root: {fee_merkle_tree_root}"
510                ))?;
511                proofs.push(proof);
512            }
513
514            Ok(proofs)
515        }
516
517        async fn try_remember_blocks_merkle_tree(
518            &self,
519            _retry: usize,
520            _instance: &NodeState,
521            _height: u64,
522            view: ViewNumber,
523            mt: &mut BlockMerkleTree,
524        ) -> anyhow::Result<()> {
525            tokio::time::sleep(self.delay).await;
526
527            tracing::info!("catchup: fetching frontier for view {view}");
528            let src = &self.state[&view].block_merkle_tree;
529
530            assert_eq!(src.commitment(), mt.commitment());
531            assert!(
532                src.num_leaves() > 0,
533                "catchup should not be triggered when blocks tree is empty"
534            );
535
536            let index = src.num_leaves() - 1;
537            let (elem, proof) = src.lookup(index).expect_ok().unwrap();
538            mt.remember(index, elem, proof.clone())
539                .expect("Proof verifies");
540
541            Ok(())
542        }
543
544        async fn try_fetch_chain_config(
545            &self,
546            _retry: usize,
547            _commitment: Commitment<ChainConfig>,
548        ) -> anyhow::Result<ChainConfig> {
549            tokio::time::sleep(self.delay).await;
550
551            Ok(ChainConfig::default())
552        }
553
554        async fn try_fetch_reward_accounts_v2(
555            &self,
556            _retry: usize,
557            _instance: &NodeState,
558            _height: u64,
559            _view: ViewNumber,
560            _reward_merkle_tree_root: RewardMerkleCommitmentV2,
561            _accounts: &[RewardAccountV2],
562        ) -> anyhow::Result<Vec<RewardAccountProofV2>> {
563            anyhow::bail!("unimplemented")
564        }
565
566        async fn try_fetch_reward_accounts_v1(
567            &self,
568            _retry: usize,
569            _instance: &NodeState,
570            _height: u64,
571            _view: ViewNumber,
572            _reward_merkle_tree_root: RewardMerkleCommitmentV1,
573            _accounts: &[RewardAccountV1],
574        ) -> anyhow::Result<Vec<RewardAccountProofV1>> {
575            anyhow::bail!("unimplemented")
576        }
577
578        async fn try_fetch_state_cert(
579            &self,
580            _retry: usize,
581            _epoch: u64,
582        ) -> anyhow::Result<LightClientStateUpdateCertificateV2<SeqTypes>> {
583            anyhow::bail!("unimplemented")
584        }
585
586        fn backoff(&self) -> &BackoffParams {
587            &self.backoff
588        }
589
590        fn name(&self) -> String {
591            "MockStateCatchup".into()
592        }
593
594        fn is_local(&self) -> bool {
595            true
596        }
597    }
598}