oasis_core_runtime/consensus/state/
roothash.rs1use std::{collections::BTreeMap, convert::TryInto};
3
4use anyhow::anyhow;
5
6use crate::{
7 common::{
8 crypto::hash::Hash,
9 key_format::{KeyFormat, KeyFormatAtom},
10 namespace::Namespace,
11 },
12 consensus::{
13 roothash::{Error, RoundResults, RoundRoots, RuntimeState},
14 state::StateError,
15 },
16 key_format,
17 storage::mkvs::ImmutableMKVS,
18};
19
20pub struct ImmutableState<'a, T: ImmutableMKVS> {
22 mkvs: &'a T,
23}
24
25impl<'a, T: ImmutableMKVS> ImmutableState<'a, T> {
26 pub fn new(mkvs: &'a T) -> ImmutableState<'a, T> {
28 ImmutableState { mkvs }
29 }
30}
31
32key_format!(RuntimeKeyFmt, 0x20, Hash);
33key_format!(StateRootKeyFmt, 0x25, Hash);
34key_format!(LastRoundResultsKeyFmt, 0x27, Hash);
35key_format!(PastRootsKeyFmt, 0x2a, (Hash, u64));
36
37impl<'a, T: ImmutableMKVS> ImmutableState<'a, T> {
38 pub fn runtime_state(&self, id: Namespace) -> Result<RuntimeState, Error> {
40 match self
41 .mkvs
42 .get(&RuntimeKeyFmt(Hash::digest_bytes(id.as_ref())).encode())
43 {
44 Ok(Some(b)) => cbor::from_slice_non_strict(&b)
45 .map_err(|err| StateError::Unavailable(anyhow!(err)).into()),
46 Ok(None) => Err(Error::InvalidRuntime(id)),
47 Err(err) => Err(StateError::Unavailable(anyhow!(err)).into()),
48 }
49 }
50
51 pub fn state_root(&self, id: Namespace) -> Result<Hash, Error> {
53 match self
54 .mkvs
55 .get(&StateRootKeyFmt(Hash::digest_bytes(id.as_ref())).encode())
56 {
57 Ok(Some(b)) => Ok(Hash(b.try_into().map_err(|_| -> Error {
58 StateError::Unavailable(anyhow!("corrupted hash value")).into()
59 })?)),
60 Ok(None) => Err(Error::InvalidRuntime(id)),
61 Err(err) => Err(StateError::Unavailable(anyhow!(err)).into()),
62 }
63 }
64
65 pub fn last_round_results(&self, id: Namespace) -> Result<RoundResults, Error> {
67 match self
68 .mkvs
69 .get(&LastRoundResultsKeyFmt(Hash::digest_bytes(id.as_ref())).encode())
70 {
71 Ok(Some(b)) => {
72 cbor::from_slice(&b).map_err(|err| StateError::Unavailable(anyhow!(err)).into())
73 }
74 Ok(None) => Err(Error::InvalidRuntime(id)),
75 Err(err) => Err(StateError::Unavailable(anyhow!(err)).into()),
76 }
77 }
78
79 pub fn round_roots(&self, id: Namespace, round: u64) -> Result<Option<RoundRoots>, StateError> {
81 match self
82 .mkvs
83 .get(&PastRootsKeyFmt((Hash::digest_bytes(id.as_ref()), round)).encode())
84 {
85 Ok(Some(b)) => {
86 cbor::from_slice(&b).map_err(|err| StateError::Unavailable(anyhow!(err)))
87 }
88 Ok(None) => Ok(None),
89 Err(err) => Err(StateError::Unavailable(anyhow!(err))),
90 }
91 }
92
93 pub fn past_round_roots(&self, id: Namespace) -> Result<BTreeMap<u64, RoundRoots>, StateError> {
95 let h = Hash::digest_bytes(id.as_ref());
96 let mut it = self.mkvs.iter();
97 it.seek(&PastRootsKeyFmt((h, Default::default())).encode_partial(1));
98
99 let mut result: BTreeMap<u64, RoundRoots> = BTreeMap::new();
100
101 for (round, value) in it.map_while(|(key, value)| {
102 PastRootsKeyFmt::decode(&key)
103 .filter(|PastRootsKeyFmt((ns, _))| ns == &h)
104 .map(|PastRootsKeyFmt((_, round))| (round, value))
105 }) {
106 result.insert(
107 round,
108 cbor::from_slice(&value).map_err(|err| StateError::Unavailable(anyhow!(err)))?,
109 );
110 }
111
112 Ok(result)
113 }
114}
115
116#[cfg(test)]
117mod test {
118 use crate::storage::mkvs::{
119 interop::{Fixture, ProtocolServer},
120 Root, RootType, Tree,
121 };
122
123 use super::*;
124 #[test]
125 fn test_roothash_state_interop() {
126 let server = ProtocolServer::new(Fixture::ConsensusMock.into());
134 let mock_consensus_root = Root {
135 version: 1,
136 root_type: RootType::State,
137 hash: Hash::from("8e39bf193f8a954ab8f8d7cb6388c591fd0785ea060bbd8e3752e266b54499d3"),
138 ..Default::default()
139 };
140 let mkvs = Tree::builder()
141 .with_capacity(100_000, 10_000_000)
142 .with_root(mock_consensus_root)
143 .build(server.read_sync());
144 let state = ImmutableState::new(&mkvs);
145
146 let runtime_id =
147 Namespace::from("8000000000000000000000000000000000000000000000000000000000000010");
148
149 let runtime_state = state
151 .runtime_state(runtime_id)
152 .expect("runtime state query should work");
153 println!("{:?}", runtime_state);
154 assert_eq!(runtime_state.runtime.id, runtime_id);
155 assert_eq!(runtime_state.suspended, false);
156 assert_eq!(runtime_state.genesis_block.header.round, 1);
157 assert_eq!(
158 runtime_state.genesis_block.header.io_root,
159 Hash::digest_bytes(format!("genesis").as_bytes())
160 );
161 assert_eq!(
162 runtime_state.genesis_block.header.state_root,
163 Hash::digest_bytes(format!("genesis").as_bytes())
164 );
165 assert_eq!(runtime_state.last_block.header.round, 10);
166 assert_eq!(
167 runtime_state.last_block.header.io_root,
168 Hash::digest_bytes(format!("io 10").as_bytes())
169 );
170 assert_eq!(
171 runtime_state.last_block.header.state_root,
172 Hash::digest_bytes(format!("state 10").as_bytes())
173 );
174 assert_eq!(runtime_state.last_block_height, 90);
175 assert_eq!(runtime_state.last_normal_round, 10);
176 assert_eq!(runtime_state.last_normal_height, 90);
177
178 let past_round_roots = state
180 .past_round_roots(runtime_id)
181 .expect("past round roots query should work");
182 assert_eq!(
183 10,
184 past_round_roots.len(),
185 "expected number of roots should match"
186 );
187 past_round_roots.iter().for_each(|(round, roots)| {
188 assert_eq!(
189 RoundRoots {
190 state_root: Hash::digest_bytes(format!("state {}", round).as_bytes()),
191 io_root: Hash::digest_bytes(format!("io {}", round).as_bytes())
192 },
193 *roots,
194 "expected roots should match"
195 );
196 });
197
198 let round_roots = state
200 .round_roots(runtime_id, 100)
201 .expect("round roots query should work");
202 assert_eq!(None, round_roots, "round root should be missing");
203
204 let round_roots = state
205 .round_roots(runtime_id, 10)
206 .expect("round roots query should work");
207 assert_eq!(
208 Some(RoundRoots {
209 state_root: Hash::digest_bytes(format!("state {}", 10).as_bytes()),
210 io_root: Hash::digest_bytes(format!("io {}", 10).as_bytes())
211 }),
212 round_roots,
213 "round root should be missing"
214 );
215
216 let runtime_id =
218 Namespace::from("8000000000000000000000000000000000000000000000000000000000000000");
219 let past_round_roots = state
220 .past_round_roots(runtime_id)
221 .expect("past round roots query should work");
222 assert_eq!(
223 0,
224 past_round_roots.len(),
225 "there should be no roots for non-existing runtime"
226 );
227 let round_roots = state
228 .round_roots(runtime_id, 10)
229 .expect("round roots query should work");
230 assert_eq!(
231 None, round_roots,
232 "round root should be missing for non-existing runtime"
233 )
234 }
235}