oasis_core_runtime/consensus/roothash/
mod.rs1use thiserror::Error;
8
9use crate::{
10 common::{
11 crypto::{hash::Hash, signature::PublicKey},
12 namespace::Namespace,
13 },
14 consensus::{registry::Runtime, scheduler::Committee, state::StateError},
15};
16
17mod block;
19mod commitment;
20mod message;
21
22pub use block::*;
24pub use commitment::*;
25pub use message::*;
26
27#[derive(Debug, Error)]
29pub enum Error {
30 #[error("roothash: invalid runtime {0}")]
31 InvalidRuntime(Namespace),
32
33 #[error(transparent)]
34 State(#[from] StateError),
35
36 #[error("roothash/commitment: no runtime configured")]
37 NoRuntime,
38
39 #[error("roothash/commitment: no committee configured")]
40 NoCommittee,
41
42 #[error("roothash/commitment: invalid committee kind")]
43 InvalidCommitteeKind,
44
45 #[error("roothash/commitment: batch RAK signature invalid")]
46 RakSigInvalid,
47
48 #[error("roothash/commitment: node not part of committee")]
49 NotInCommittee,
50
51 #[error("roothash/commitment: node already sent commitment")]
52 AlreadyCommitted,
53
54 #[error("roothash/commitment: submitted commitment is not based on correct block")]
55 NotBasedOnCorrectBlock,
56
57 #[error("roothash/commitment: discrepancy detected")]
58 DiscrepancyDetected,
59
60 #[error("roothash/commitment: still waiting for commits")]
61 StillWaiting,
62
63 #[error("roothash/commitment: insufficient votes to finalize discrepancy resolution round")]
64 InsufficientVotes,
65
66 #[error("roothash/commitment: bad executor commitment")]
67 BadExecutorCommitment,
68
69 #[error("roothash/commitment: invalid messages")]
70 InvalidMessages,
71
72 #[error("roothash/commitment: invalid round")]
73 InvalidRound,
74
75 #[error("roothash/commitment: no proposer commitment")]
76 NoProposerCommitment,
77
78 #[error("roothash/commitment: bad proposer commitment")]
79 BadProposerCommitment,
80}
81
82#[derive(Clone, Debug, Default, PartialEq, Eq, Hash, cbor::Encode, cbor::Decode)]
84pub struct AnnotatedBlock {
85 pub consensus_height: i64,
87 pub block: Block,
89}
90
91#[derive(Clone, Debug, Default, PartialEq, Eq, cbor::Encode, cbor::Decode)]
93pub struct MessageEvent {
94 #[cbor(optional)]
95 pub module: String,
96
97 #[cbor(optional)]
98 pub code: u32,
99
100 #[cbor(optional)]
101 pub index: u32,
102
103 #[cbor(optional)]
104 pub result: Option<cbor::Value>,
105}
106
107impl MessageEvent {
108 pub fn is_success(&self) -> bool {
110 self.code == 0
111 }
112}
113
114#[derive(Clone, Debug, Default, PartialEq, Eq, cbor::Encode, cbor::Decode)]
116#[cbor(allow_unknown)]
117pub struct RuntimeState {
118 pub runtime: Runtime,
120 #[cbor(optional)]
122 pub suspended: bool,
123
124 pub genesis_block: Block,
126
127 pub last_block: Block,
129 pub last_block_height: i64,
131
132 pub last_normal_round: u64,
135 pub last_normal_height: i64,
137
138 #[cbor(optional)]
140 pub commitee: Option<Committee>,
141 #[cbor(optional)]
144 pub next_timeout: i64,
145
146 pub liveness_stats: Option<LivenessStatistics>,
148}
149
150#[derive(Clone, Debug, Default, PartialEq, Eq, cbor::Encode, cbor::Decode)]
152pub struct LivenessStatistics {
153 pub total_rounds: u64,
156
157 pub good_rounds: Vec<u64>,
160
161 pub finalized_proposals: Vec<u64>,
167
168 pub missed_proposals: Vec<u64>,
174}
175
176#[derive(Clone, Debug, Default, PartialEq, Eq, cbor::Encode, cbor::Decode)]
178pub struct RoundResults {
179 #[cbor(optional)]
181 pub messages: Vec<MessageEvent>,
182
183 #[cbor(optional)]
186 pub good_compute_entities: Vec<PublicKey>,
187 #[cbor(optional)]
190 pub bad_compute_entities: Vec<PublicKey>,
191}
192
193#[derive(Clone, Debug, Default, Eq, PartialEq, Hash, cbor::Encode, cbor::Decode)]
195#[cbor(as_array)]
196pub struct RoundRoots {
197 pub state_root: Hash,
198 pub io_root: Hash,
199}
200
201#[cfg(test)]
202mod tests {
203 use base64::prelude::*;
204
205 use super::*;
206
207 #[test]
208 fn test_consistent_round_results() {
209 let tcs = vec![
210 ("oA==", RoundResults::default()),
211 ("oWhtZXNzYWdlc4GiZGNvZGUBZm1vZHVsZWR0ZXN0", RoundResults {
212 messages: vec![MessageEvent{module: "test".to_owned(), code: 1, index: 0, result: None}],
213 ..Default::default()
214 }),
215 ("omhtZXNzYWdlc4GkZGNvZGUYKmVpbmRleAFmbW9kdWxlZHRlc3RmcmVzdWx0a3Rlc3QtcmVzdWx0dWdvb2RfY29tcHV0ZV9lbnRpdGllc4NYIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAWCAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAVggAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAI=",
216 RoundResults {
217 messages: vec![MessageEvent{module: "test".to_owned(), code: 42, index: 1, result: Some(cbor::Value::TextString("test-result".to_string()))}],
218 good_compute_entities: vec![
219 "0000000000000000000000000000000000000000000000000000000000000000".into(),
220 "0000000000000000000000000000000000000000000000000000000000000001".into(),
221 "0000000000000000000000000000000000000000000000000000000000000002".into(),
222 ],
223 ..Default::default()
224 }),
225 ("o2htZXNzYWdlc4GkZGNvZGUYKmVpbmRleAFmbW9kdWxlZHRlc3RmcmVzdWx0a3Rlc3QtcmVzdWx0dGJhZF9jb21wdXRlX2VudGl0aWVzgVggAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAF1Z29vZF9jb21wdXRlX2VudGl0aWVzglggAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABYIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAC",
226 RoundResults {
227 messages: vec![MessageEvent{module: "test".to_owned(), code: 42, index: 1, result: Some(cbor::Value::TextString("test-result".to_string()))}],
228 good_compute_entities: vec![
229 "0000000000000000000000000000000000000000000000000000000000000000".into(),
230 "0000000000000000000000000000000000000000000000000000000000000002".into(),
231 ],
232 bad_compute_entities: vec![
233 "0000000000000000000000000000000000000000000000000000000000000001".into(),
234 ],
235 }),
236 ];
237 for (encoded_base64, rr) in tcs {
238 let dec: RoundResults =
239 cbor::from_slice(&BASE64_STANDARD.decode(encoded_base64).unwrap())
240 .expect("round results should deserialize correctly");
241 assert_eq!(dec, rr, "decoded results should match the expected value");
242 }
243 }
244
245 #[test]
246 fn test_consistent_round_roots() {
247 let tcs = vec![
248 ("glggAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABYIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", RoundRoots::default()),
249 ("glggPTf+WENeDYcyPe5KLBsznvlU3mNxbuefV0f5TZdPkT9YIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", RoundRoots {
250 state_root: Hash::digest_bytes(b"test"),
251 ..Default::default()
252 }),
253 ("glggAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABYID03/lhDXg2HMj3uSiwbM575VN5jcW7nn1dH+U2XT5E/", RoundRoots {
254 io_root: Hash::digest_bytes(b"test"),
255 ..Default::default()
256 }),
257 ("glggPTf+WENeDYcyPe5KLBsznvlU3mNxbuefV0f5TZdPkT9YID03/lhDXg2HMj3uSiwbM575VN5jcW7nn1dH+U2XT5E/",
258 RoundRoots {
259 state_root: Hash::digest_bytes(b"test"),
260 io_root: Hash::digest_bytes(b"test"),
261 }),
262 ("glggC4+lzfqNgLxCHLxwDp+Bf5PLLb0DILrUZWwF+lp6Z/NYIJ3seczGUDFDvmAEdVCeep6Xsn8XRosTKWpu9wZ3mQRq",
263 RoundRoots {
264 state_root: Hash::digest_bytes(b"test1"),
265 io_root: Hash::digest_bytes(b"test2"),
266 }),
267 ("glggnex5zMZQMUO+YAR1UJ56npeyfxdGixMpam73BneZBGpYIAuPpc36jYC8Qhy8cA6fgX+Tyy29AyC61GVsBfpaemfz",
268 RoundRoots {
269 state_root: Hash::digest_bytes(b"test2"),
270 io_root: Hash::digest_bytes(b"test1"),
271 }),
272 ];
273
274 for (encoded_base64, rr) in tcs {
275 let dec: RoundRoots =
276 cbor::from_slice(&BASE64_STANDARD.decode(encoded_base64).unwrap())
277 .expect("round roots should deserialize correctly");
278 assert_eq!(
279 dec, rr,
280 "decoded round roots should match the expected value"
281 );
282 }
283 }
284}