oasis_core_runtime/consensus/state/
beacon.rs

1//! Beacon state in the consensus layer.
2use anyhow::anyhow;
3
4use crate::{
5    common::key_format::{KeyFormat, KeyFormatAtom},
6    consensus::{
7        beacon::{EpochTime, EpochTimeState},
8        state::StateError,
9    },
10    key_format,
11    storage::mkvs::{FallibleMKVS, ImmutableMKVS},
12};
13
14/// Consensus beacon state wrapper.
15pub struct ImmutableState<'a, T: ImmutableMKVS> {
16    mkvs: &'a T,
17}
18
19impl<'a, T: ImmutableMKVS> ImmutableState<'a, T> {
20    /// Constructs a new ImmutableMKVS.
21    pub fn new(mkvs: &'a T) -> ImmutableState<'a, T> {
22        ImmutableState { mkvs }
23    }
24}
25
26key_format!(CurrentEpochKeyFmt, 0x40, ());
27key_format!(FutureEpochKeyFmt, 0x41, ());
28
29impl<'a, T: ImmutableMKVS> ImmutableState<'a, T> {
30    /// Returns the current epoch number.
31    pub fn epoch(&self) -> Result<EpochTime, StateError> {
32        self.epoch_state().map(|es| es.epoch)
33    }
34
35    /// Returns the current epoch state.
36    pub fn epoch_state(&self) -> Result<EpochTimeState, StateError> {
37        match self.mkvs.get(&CurrentEpochKeyFmt(()).encode()) {
38            Ok(Some(b)) => {
39                let state: EpochTimeState =
40                    cbor::from_slice(&b).map_err(|err| StateError::Unavailable(anyhow!(err)))?;
41                Ok(state)
42            }
43            Ok(None) => Ok(EpochTimeState::default()),
44            Err(err) => Err(StateError::Unavailable(anyhow!(err))),
45        }
46    }
47
48    /// Returns the future epoch number.
49    pub fn future_epoch(&self) -> Result<EpochTime, StateError> {
50        self.future_epoch_state().map(|es| es.epoch)
51    }
52
53    /// Returns the future epoch state.
54    pub fn future_epoch_state(&self) -> Result<EpochTimeState, StateError> {
55        match self.mkvs.get(&FutureEpochKeyFmt(()).encode()) {
56            Ok(Some(b)) => {
57                let state: EpochTimeState =
58                    cbor::from_slice(&b).map_err(|err| StateError::Unavailable(anyhow!(err)))?;
59                Ok(state)
60            }
61            Ok(None) => Ok(EpochTimeState::default()),
62            Err(err) => Err(StateError::Unavailable(anyhow!(err))),
63        }
64    }
65}
66
67/// Mutable consensus beacon state wrapper.
68pub struct MutableState;
69
70impl MutableState {
71    /// Set current epoch state.
72    pub fn set_epoch_state<S: FallibleMKVS>(
73        mkvs: &mut S,
74        epoch_state: EpochTimeState,
75    ) -> Result<(), StateError> {
76        mkvs.insert(&CurrentEpochKeyFmt(()).encode(), &cbor::to_vec(epoch_state))?;
77        Ok(())
78    }
79
80    /// Set future epoch state.
81    pub fn set_future_epoch_state<S: FallibleMKVS>(
82        mkvs: &mut S,
83        epoch_state: EpochTimeState,
84    ) -> Result<(), StateError> {
85        mkvs.insert(&FutureEpochKeyFmt(()).encode(), &cbor::to_vec(epoch_state))?;
86        Ok(())
87    }
88}
89
90#[cfg(test)]
91mod test {
92    use crate::{
93        common::crypto::hash::Hash,
94        storage::mkvs::{
95            interop::{Fixture, ProtocolServer},
96            sync::NoopReadSyncer,
97            Root, RootType, Tree,
98        },
99    };
100
101    use super::*;
102
103    #[test]
104    fn test_mutable_state() {
105        let mut mkvs = Tree::builder()
106            .with_root_type(RootType::State)
107            .build(Box::new(NoopReadSyncer));
108
109        MutableState::set_epoch_state(
110            &mut mkvs,
111            EpochTimeState {
112                epoch: 10,
113                height: 100,
114            },
115        )
116        .unwrap();
117
118        MutableState::set_future_epoch_state(
119            &mut mkvs,
120            EpochTimeState {
121                epoch: 11,
122                height: 110,
123            },
124        )
125        .unwrap();
126
127        let beacon_state = ImmutableState::new(&mkvs);
128
129        // Test current epoch state.
130        let epoch_state = beacon_state
131            .epoch_state()
132            .expect("epoch state query should work");
133        assert_eq!(10u64, epoch_state.epoch, "expected epoch should match");
134        assert_eq!(100i64, epoch_state.height, "expected height should match");
135
136        // Test future epoch state.
137        let epoch_state = beacon_state
138            .future_epoch_state()
139            .expect("future epoch state query should work");
140        assert_eq!(11u64, epoch_state.epoch, "expected epoch should match");
141        assert_eq!(110i64, epoch_state.height, "expected height should match");
142    }
143
144    #[test]
145    fn test_beacon_state_interop() {
146        // Keep in sync with go/consensus/cometbft/apps/beacon/state/interop/interop.go.
147        // If mock consensus state changes, update the root hash bellow.
148        // See protocol server stdout for hash.
149        // To make the hash show up during tests, run "cargo test" as
150        // "cargo test -- --nocapture".
151
152        // Setup protocol server with initialized mock consensus state.
153        let server = ProtocolServer::new(Fixture::ConsensusMock.into());
154        let mock_consensus_root = Root {
155            version: 1,
156            root_type: RootType::State,
157            hash: Hash::from("8e39bf193f8a954ab8f8d7cb6388c591fd0785ea060bbd8e3752e266b54499d3"),
158            ..Default::default()
159        };
160        let mkvs = Tree::builder()
161            .with_capacity(100_000, 10_000_000)
162            .with_root(mock_consensus_root)
163            .build(server.read_sync());
164        let beacon_state = ImmutableState::new(&mkvs);
165
166        // Test current epoch number.
167        let epoch = beacon_state.epoch().expect("epoch query should work");
168        assert_eq!(42u64, epoch, "expected epoch should match");
169
170        // Test current epoch state.
171        let epoch_state = beacon_state
172            .epoch_state()
173            .expect("epoch state query should work");
174        assert_eq!(42u64, epoch_state.epoch, "expected epoch should match");
175        assert_eq!(13i64, epoch_state.height, "expected height should match");
176
177        // Test future epoch number.
178        let epoch = beacon_state
179            .future_epoch()
180            .expect("future epoch query should work");
181        assert_eq!(43u64, epoch, "expected future epoch should match");
182
183        // Test future epoch state.
184        let epoch_state = beacon_state
185            .future_epoch_state()
186            .expect("future epoch state query should work");
187        assert_eq!(43u64, epoch_state.epoch, "expected epoch should match");
188        assert_eq!(15i64, epoch_state.height, "expected height should match");
189    }
190}