oasis_core_runtime/consensus/roothash/
message.rs

1use anyhow::Result;
2
3use crate::{
4    common::{crypto::hash::Hash, quantity::Quantity, versioned::Versioned},
5    consensus::{address::Address, governance, registry, staking},
6};
7
8/// A message that can be emitted by the runtime to be processed by the consensus layer.
9#[derive(Clone, Debug, PartialEq, Eq, cbor::Encode, cbor::Decode)]
10pub enum Message {
11    #[cbor(rename = "staking")]
12    Staking(Versioned<StakingMessage>),
13
14    #[cbor(rename = "registry")]
15    Registry(Versioned<RegistryMessage>),
16
17    #[cbor(rename = "governance")]
18    Governance(Versioned<GovernanceMessage>),
19}
20
21impl Message {
22    /// Returns a hash of provided runtime messages.
23    pub fn messages_hash(msgs: &[Message]) -> Hash {
24        if msgs.is_empty() {
25            // Special case if there are no messages.
26            return Hash::empty_hash();
27        }
28        Hash::digest_bytes(&cbor::to_vec(msgs.to_vec()))
29    }
30
31    /// Returns a hash of provided incoming runtime messages.
32    pub fn in_messages_hash(msgs: &[IncomingMessage]) -> Hash {
33        if msgs.is_empty() {
34            // Special case if there are no messages.
35            return Hash::empty_hash();
36        }
37        Hash::digest_bytes(&cbor::to_vec(msgs.to_vec()))
38    }
39
40    /// Performs basic validation of the runtime message.
41    pub fn validate_basic(&self) -> Result<()> {
42        match self {
43            Message::Staking(msg) => msg.inner.validate_basic(),
44            Message::Registry(msg) => msg.inner.validate_basic(),
45            Message::Governance(msg) => msg.inner.validate_basic(),
46        }
47    }
48}
49
50#[derive(Clone, Debug, PartialEq, Eq, Hash, cbor::Encode, cbor::Decode)]
51pub enum StakingMessage {
52    #[cbor(rename = "transfer")]
53    Transfer(staking::Transfer),
54
55    #[cbor(rename = "withdraw")]
56    Withdraw(staking::Withdraw),
57
58    #[cbor(rename = "add_escrow")]
59    AddEscrow(staking::Escrow),
60
61    #[cbor(rename = "reclaim_escrow")]
62    ReclaimEscrow(staking::ReclaimEscrow),
63}
64
65impl StakingMessage {
66    /// Performs basic validation of the staking message.
67    pub fn validate_basic(&self) -> Result<()> {
68        match self {
69            StakingMessage::Transfer(_) => {
70                // No validation at this time.
71                Ok(())
72            }
73            StakingMessage::Withdraw(_) => {
74                // No validation at this time.
75                Ok(())
76            }
77            StakingMessage::AddEscrow(_) => {
78                // No validation at this time.
79                Ok(())
80            }
81            StakingMessage::ReclaimEscrow(_) => {
82                // No validation at this time.
83                Ok(())
84            }
85        }
86    }
87}
88
89#[derive(Clone, Debug, PartialEq, Eq, Hash, cbor::Encode, cbor::Decode)]
90pub enum RegistryMessage {
91    #[cbor(rename = "update_runtime")]
92    UpdateRuntime(registry::Runtime),
93}
94
95impl RegistryMessage {
96    /// Performs basic validation of the registry message.
97    pub fn validate_basic(&self) -> Result<()> {
98        match self {
99            RegistryMessage::UpdateRuntime(_) => {
100                // The runtime descriptor will already be validated in registerRuntime
101                // in the registry app when it processes the message, so we don't have
102                // to do any validation here.
103                Ok(())
104            }
105        }
106    }
107}
108
109#[derive(Clone, Debug, PartialEq, Eq, cbor::Encode, cbor::Decode)]
110pub enum GovernanceMessage {
111    #[cbor(rename = "cast_vote")]
112    CastVote(governance::ProposalVote),
113    #[cbor(rename = "submit_proposal")]
114    SubmitProposal(governance::ProposalContent),
115}
116
117impl GovernanceMessage {
118    /// Performs basic validation of the governance message.
119    pub fn validate_basic(&self) -> Result<()> {
120        match self {
121            GovernanceMessage::CastVote(_) => {
122                // No validation at this time.
123                Ok(())
124            }
125            GovernanceMessage::SubmitProposal(_) => {
126                // No validation at this time.
127                Ok(())
128            }
129        }
130    }
131}
132
133/// An incoming message emitted by the consensus layer to be processed by the runtime.
134#[derive(Clone, Debug, Default, PartialEq, Eq, Hash, cbor::Encode, cbor::Decode)]
135pub struct IncomingMessage {
136    /// Unique identifier of the message.
137    pub id: u64,
138    /// Address of the caller authenticated by the consensus layer.
139    pub caller: Address,
140    /// An optional tag provided by the caller which is ignored and can be used to match processed
141    /// incoming message events later.
142    #[cbor(optional)]
143    pub tag: u64,
144    /// Fee sent into the runtime as part of the message being sent. The fee is transferred before
145    /// the message is processed by the runtime.
146    #[cbor(optional)]
147    pub fee: Quantity,
148    /// Tokens sent into the runtime as part of the message being sent. The tokens are transferred
149    /// before the message is processed by the runtime.
150    #[cbor(optional)]
151    pub tokens: Quantity,
152    /// Arbitrary runtime-dependent data.
153    #[cbor(optional)]
154    pub data: Vec<u8>,
155}
156
157impl IncomingMessage {
158    /// Returns a hash of provided runtime messages.
159    pub fn in_messages_hash(msgs: &[IncomingMessage]) -> Hash {
160        if msgs.is_empty() {
161            // Special case if there are no messages.
162            return Hash::empty_hash();
163        }
164        Hash::digest_bytes(&cbor::to_vec(msgs.to_vec()))
165    }
166}
167
168#[cfg(test)]
169mod tests {
170    use std::collections::BTreeMap;
171
172    use crate::{
173        common::{crypto::signature::PublicKey, namespace::Namespace, quantity},
174        consensus::scheduler,
175    };
176
177    use super::*;
178
179    #[test]
180    fn test_consistent_messages_hash() {
181        // NOTE: This runtime structure must be synced with go/roothash/api/messages_test.go.
182        let test_ent_id =
183            PublicKey::from("4ea5328f943ef6f66daaed74cb0e99c3b1c45f76307b425003dbc7cb3638ed35");
184
185        let q = quantity::Quantity::from(1000u32);
186
187        let mut st = BTreeMap::new();
188        st.insert(staking::ThresholdKind::KindNodeCompute, q.clone());
189
190        let mut wlc = BTreeMap::new();
191        wlc.insert(registry::RolesMask::ROLE_COMPUTE_WORKER, 2);
192
193        let mut wl = BTreeMap::new();
194        wl.insert(
195            test_ent_id,
196            registry::EntityWhitelistConfig { max_nodes: wlc },
197        );
198
199        let rt = registry::Runtime {
200            v: registry::LATEST_RUNTIME_DESCRIPTOR_VERSION,
201            id: Namespace::default(),
202            entity_id: test_ent_id,
203            genesis: registry::RuntimeGenesis {
204                state_root: Hash::empty_hash(),
205                round: 0,
206            },
207            kind: registry::RuntimeKind::KindCompute,
208            tee_hardware: registry::TEEHardware::TEEHardwareInvalid,
209            deployments: vec![registry::VersionInfo::default()],
210            key_manager: None,
211            executor: registry::ExecutorParameters {
212                group_size: 3,
213                group_backup_size: 5,
214                allowed_stragglers: 1,
215                round_timeout: 10,
216                max_messages: 32,
217                ..Default::default()
218            },
219            txn_scheduler: registry::TxnSchedulerParameters {
220                batch_flush_timeout: 1_000_000_000, // 1 second.
221                max_batch_size: 1,
222                max_batch_size_bytes: 1024,
223                max_in_messages: 0,
224                propose_batch_timeout: 2_000_000_000, // 2 seconds.
225            },
226            storage: registry::StorageParameters {
227                checkpoint_interval: 0,
228                checkpoint_num_kept: 0,
229                checkpoint_chunk_size: 0,
230            },
231            admission_policy: registry::RuntimeAdmissionPolicy {
232                entity_whitelist: Some(registry::EntityWhitelistRuntimeAdmissionPolicy {
233                    entities: wl,
234                }),
235                ..Default::default()
236            },
237            constraints: {
238                let mut cs = BTreeMap::new();
239                cs.insert(scheduler::CommitteeKind::ComputeExecutor, {
240                    let mut ce = BTreeMap::new();
241                    ce.insert(
242                        scheduler::Role::Worker,
243                        registry::SchedulingConstraints {
244                            min_pool_size: Some(registry::MinPoolSizeConstraint { limit: 1 }),
245                            validator_set: Some(registry::ValidatorSetConstraint {}),
246                            ..Default::default()
247                        },
248                    );
249                    ce.insert(
250                        scheduler::Role::BackupWorker,
251                        registry::SchedulingConstraints {
252                            min_pool_size: Some(registry::MinPoolSizeConstraint { limit: 2 }),
253                            ..Default::default()
254                        },
255                    );
256                    ce
257                });
258
259                cs
260            },
261            staking: registry::RuntimeStakingParameters {
262                thresholds: st,
263                ..Default::default()
264            },
265            governance_model: registry::RuntimeGovernanceModel::GovernanceEntity,
266        };
267
268        // NOTE: These hashes MUST be synced with go/roothash/api/message/message_test.go.
269        let tcs = vec![
270            (
271                vec![],
272                "c672b8d1ef56ed28ab87c3622c5114069bdd3ad7b8f9737498d0c01ecef0967a",
273            ),
274            (
275                vec![Message::Staking(Versioned::new(
276                    0,
277                    StakingMessage::Transfer(staking::Transfer::default()),
278                ))],
279                "a6b91f974b34a9192efd12025659a768520d2f04e1dae9839677456412cdb2be",
280            ),
281            (
282                vec![Message::Staking(Versioned::new(
283                    0,
284                    StakingMessage::Withdraw(staking::Withdraw::default()),
285                ))],
286                "069b0fda76d804e3fd65d4bbd875c646f15798fb573ac613100df67f5ba4c3fd",
287            ),
288            (
289                vec![Message::Staking(Versioned::new(
290                    0,
291                    StakingMessage::AddEscrow(staking::Escrow::default()),
292                ))],
293                "65049870b9dae657390e44065df0c78176816876e67b96dac7791ee6a1aa42e2",
294            ),
295            (
296                vec![Message::Staking(Versioned::new(
297                    0,
298                    StakingMessage::ReclaimEscrow(staking::ReclaimEscrow::default()),
299                ))],
300                "c78547eae2f104268e49827cbe624cf2b350ee59e8d693dec0673a70a4664a2e",
301            ),
302            (
303                vec![Message::Registry(Versioned::new(
304                    0,
305                    RegistryMessage::UpdateRuntime(registry::Runtime {
306                        admission_policy: registry::RuntimeAdmissionPolicy {
307                            any_node: Some(registry::AnyNodeRuntimeAdmissionPolicy {}),
308                            ..Default::default()
309                        },
310                        ..Default::default()
311                    }),
312                ))],
313                // FIXME: Change to e6e170fb771583147255e0c96dc88615d4fd2fd28488ae489df01da201affe72 once cbor is fixed.
314                "baf9eeaa4860e363a9c27d99555839afc535f0cd32d23dc640f0f020677460e0",
315            ),
316            (
317                vec![Message::Registry(Versioned::new(
318                    0,
319                    RegistryMessage::UpdateRuntime(rt),
320                ))],
321                "03e77fbeda1a2291c87c06c59335a49fe18852266d58608c1ddec8ef64209458",
322            ),
323            (
324                vec![Message::Governance(Versioned::new(
325                    0,
326                    GovernanceMessage::CastVote(governance::ProposalVote {
327                        id: 32,
328                        vote: governance::Vote::Yes,
329                    }),
330                ))],
331                "f45e26eb8ace807ad5bd02966cde1f012d1d978d4cbddd59e9bfd742dcf39b90",
332            ),
333            (
334                vec![Message::Governance(Versioned::new(
335                    0,
336                    GovernanceMessage::SubmitProposal(governance::ProposalContent {
337                        cancel_upgrade: Some(governance::CancelUpgradeProposal { proposal_id: 32 }),
338                        ..Default::default()
339                    }),
340                ))],
341                "03312ddb5c41a30fbd29fb91cf6bf26d58073996f89657ca4f3b3a43a98bfd0b",
342            ),
343        ];
344        for (msgs, expected_hash) in tcs {
345            println!("{:?}", cbor::to_vec(msgs.clone()));
346            assert_eq!(Message::messages_hash(&msgs), Hash::from(expected_hash));
347        }
348    }
349}