penumbra_sdk_sct/component/
clock.rs

1use crate::{epoch::Epoch, state_key};
2use anyhow::{anyhow, Context, Result};
3use async_trait::async_trait;
4use cnidarium::{StateRead, StateWrite};
5use penumbra_sdk_proto::{StateReadProto, StateWriteProto};
6use std::str::FromStr;
7
8#[async_trait]
9/// Provides read access to epoch indices, block heights, timestamps, and other related data.
10pub trait EpochRead: StateRead {
11    /// Get the current block height.
12    ///
13    /// # Errors
14    /// Returns an error if the block height is missing.
15    async fn get_block_height(&self) -> Result<u64> {
16        self.get_proto(state_key::block_manager::block_height())
17            .await?
18            .ok_or_else(|| anyhow!("Missing block_height"))
19    }
20
21    /// Gets the current block timestamp from the JMT
22    ///
23    /// # Errors
24    /// Returns an error if the block timestamp is missing.
25    ///
26    /// # Panic
27    /// Panics if the block timestamp is not a valid RFC3339 time string.
28    async fn get_current_block_timestamp(&self) -> Result<tendermint::Time> {
29        let timestamp_string: String = self
30            .get_proto(state_key::block_manager::current_block_timestamp())
31            .await?
32            .ok_or_else(|| anyhow!("Missing current_block_timestamp"))?;
33
34        Ok(tendermint::Time::from_str(&timestamp_string)
35            .context("current_block_timestamp was an invalid RFC3339 time string")?)
36    }
37
38    /// Gets a historic block timestamp from nonverifiable storage.
39    ///
40    /// # Errors
41    /// Returns an error if the block timestamp is missing.
42    ///
43    /// # Panic
44    /// Panics if the block timestamp is not a valid RFC3339 time string.
45    async fn get_block_timestamp(&self, height: u64) -> Result<tendermint::Time> {
46        let timestamp_string: String = self
47            .nonverifiable_get_proto(&state_key::block_manager::block_timestamp(height).as_bytes())
48            .await?
49            .ok_or_else(|| anyhow!("Missing block_timestamp for height {}", height))?;
50
51        Ok(
52            tendermint::Time::from_str(&timestamp_string).context(format!(
53                "block_timestamp for height {} was an invalid RFC3339 time string",
54                height
55            ))?,
56        )
57    }
58
59    /// Get the current application epoch.
60    ///
61    /// # Errors
62    /// Returns an error if the epoch is missing.
63    async fn get_current_epoch(&self) -> Result<Epoch> {
64        // Get the height
65        let height = self.get_block_height().await?;
66
67        self.get(&state_key::epoch_manager::epoch_by_height(height))
68            .await?
69            .ok_or_else(|| anyhow!("missing epoch for current height: {height}"))
70    }
71
72    /// Get the epoch corresponding to the supplied height.
73    ///
74    /// # Errors
75    /// Returns an error if the epoch is missing.
76    async fn get_epoch_by_height(&self, height: u64) -> Result<Epoch> {
77        self.get(&state_key::epoch_manager::epoch_by_height(height))
78            .await?
79            .ok_or_else(|| anyhow!("missing epoch for height"))
80    }
81
82    /// Returns true if we are triggering an early epoch end.
83    async fn is_epoch_ending_early(&self) -> bool {
84        self.object_get(state_key::epoch_manager::end_epoch_early())
85            .unwrap_or(false)
86    }
87}
88
89impl<T: StateRead + ?Sized> EpochRead for T {}
90
91/// Provides write access to the chain's epoch manager.
92/// The epoch manager is responsible for tracking block and epoch heights
93/// as well as related data like reported timestamps and epoch duration.
94#[async_trait]
95pub trait EpochManager: StateWrite {
96    /// Writes the current block's timestamp as an RFC3339 string to verifiable storage.
97    ///
98    /// Also writes the current block's timestamp to the appropriate key in nonverifiable storage.
99    fn put_block_timestamp(&mut self, height: u64, timestamp: tendermint::Time) {
100        self.put_proto(
101            state_key::block_manager::current_block_timestamp().into(),
102            timestamp.to_rfc3339(),
103        );
104
105        self.nonverifiable_put_proto(
106            state_key::block_manager::block_timestamp(height).into(),
107            timestamp.to_rfc3339(),
108        );
109    }
110
111    /// Write a value in the end epoch flag in object-storage.
112    /// This is used to trigger an early epoch end at the end of the block.
113    fn set_end_epoch_flag(&mut self) {
114        self.object_put(state_key::epoch_manager::end_epoch_early(), true)
115    }
116
117    /// Writes the block height to verifiable storage.
118    fn put_block_height(&mut self, height: u64) {
119        self.put_proto(state_key::block_manager::block_height().to_string(), height)
120    }
121
122    /// Index the current epoch by height.
123    fn put_epoch_by_height(&mut self, height: u64, epoch: Epoch) {
124        self.put(state_key::epoch_manager::epoch_by_height(height), epoch)
125    }
126}
127
128impl<T: StateWrite + ?Sized> EpochManager for T {}