oasis_core_runtime/consensus/state/
registry.rs

1//! Registry state in the consensus layer.
2use anyhow::anyhow;
3
4use crate::{
5    common::{
6        crypto::{
7            hash::Hash,
8            signature::{MultiSigned, PublicKey},
9        },
10        key_format::{KeyFormat, KeyFormatAtom},
11        namespace::Namespace,
12    },
13    consensus::{
14        registry::{Node, Runtime},
15        state::StateError,
16    },
17    key_format,
18    storage::mkvs::ImmutableMKVS,
19};
20
21/// Consensus registry state wrapper.
22pub struct ImmutableState<'a, T: ImmutableMKVS> {
23    mkvs: &'a T,
24}
25
26impl<'a, T: ImmutableMKVS> ImmutableState<'a, T> {
27    /// Constructs a new ImmutableMKVS.
28    pub fn new(mkvs: &'a T) -> ImmutableState<'a, T> {
29        ImmutableState { mkvs }
30    }
31}
32
33key_format!(SignedNodeKeyFmt, 0x11, Hash);
34key_format!(RuntimeKeyFmt, 0x13, Hash);
35key_format!(SuspendedRuntimeKeyFmt, 0x18, Hash);
36
37impl<'a, T: ImmutableMKVS> ImmutableState<'a, T> {
38    fn decode_node(&self, data: &[u8]) -> Result<Node, StateError> {
39        let signed: MultiSigned =
40            cbor::from_slice(data).map_err(|err| StateError::Unavailable(anyhow!(err)))?;
41        // The signed blob is transported as-is so we need to use non-strict decoding.
42        cbor::from_slice_non_strict(&signed.blob)
43            .map_err(|err| StateError::Unavailable(anyhow!(err)))
44    }
45
46    /// Looks up a specific node by its identifier.
47    pub fn node(&self, id: &PublicKey) -> Result<Option<Node>, StateError> {
48        let h = Hash::digest_bytes(id.as_ref());
49        match self.mkvs.get(&SignedNodeKeyFmt(h).encode()) {
50            Ok(Some(b)) => Ok(Some(self.decode_node(&b)?)),
51            Ok(None) => Ok(None),
52            Err(err) => Err(StateError::Unavailable(anyhow!(err))),
53        }
54    }
55
56    /// Returns the list of all registered nodes.
57    pub fn nodes(&self) -> Result<Vec<Node>, StateError> {
58        let mut it = self.mkvs.iter();
59        it.seek(&SignedNodeKeyFmt::default().encode_partial(0));
60
61        let mut result: Vec<Node> = Vec::new();
62
63        while let Some(value) = it
64            .next()
65            .and_then(|(key, value)| SignedNodeKeyFmt::decode(&key).map(|_| value))
66        {
67            result.push(self.decode_node(&value)?)
68        }
69
70        Ok(result)
71    }
72
73    fn decode_runtime(&self, data: &[u8]) -> Result<Runtime, StateError> {
74        cbor::from_slice(data).map_err(|err| StateError::Unavailable(anyhow!(err)))
75    }
76
77    /// Looks up a specific runtime by its identifier.
78    ///
79    /// # Note
80    ///
81    /// This includes both non-suspended and suspended runtimes.
82    pub fn runtime(&self, id: &Namespace) -> Result<Option<Runtime>, StateError> {
83        let h = Hash::digest_bytes(id.as_ref());
84
85        // Try non-suspended first.
86        match self.mkvs.get(&RuntimeKeyFmt(h).encode()) {
87            Ok(Some(b)) => Ok(Some(self.decode_runtime(&b)?)),
88            Ok(None) => {
89                // Also try suspended.
90                match self.mkvs.get(&SuspendedRuntimeKeyFmt(h).encode()) {
91                    Ok(Some(b)) => Ok(Some(self.decode_runtime(&b)?)),
92                    Ok(None) => Ok(None),
93                    Err(err) => Err(StateError::Unavailable(anyhow!(err))),
94                }
95            }
96            Err(err) => Err(StateError::Unavailable(anyhow!(err))),
97        }
98    }
99}
100
101#[cfg(test)]
102mod test {
103    use std::collections::BTreeMap;
104
105    use crate::{
106        common::crypto::signature,
107        consensus::registry::{
108            AnyNodeRuntimeAdmissionPolicy, Capabilities, CapabilityTEE, ConsensusInfo,
109            EntityWhitelistRoleAdmissionPolicy, NodeRuntime, P2PInfo, PerRoleAdmissionPolicy,
110            RolesMask, RuntimeAdmissionPolicy, RuntimeKind, TEEHardware, TLSInfo, VRFInfo,
111            VersionInfo,
112        },
113        storage::mkvs::{
114            interop::{Fixture, ProtocolServer},
115            Root, RootType, Tree,
116        },
117        Version,
118    };
119
120    use super::*;
121
122    #[test]
123    fn test_registry_state_interop() {
124        // Keep in sync with go/consensus/cometbft/apps/registry/state/interop/interop.go.
125        // If mock consensus state changes, update the root hash bellow.
126        // See protocol server stdout for hash.
127        // To make the hash show up during tests, run "cargo test" as
128        // "cargo test -- --nocapture".
129
130        // Setup protocol server with initialized mock consensus state.
131        let server = ProtocolServer::new(Fixture::ConsensusMock.into());
132        let mock_consensus_root = Root {
133            version: 1,
134            root_type: RootType::State,
135            hash: Hash::from("8e39bf193f8a954ab8f8d7cb6388c591fd0785ea060bbd8e3752e266b54499d3"),
136            ..Default::default()
137        };
138        let mkvs = Tree::builder()
139            .with_capacity(100_000, 10_000_000)
140            .with_root(mock_consensus_root)
141            .build(server.read_sync());
142        let registry_state = ImmutableState::new(&mkvs);
143
144        // Test get nodes.
145        let nodes = registry_state.nodes().expect("nodes query should work");
146        assert_eq!(
147            nodes.len(),
148            2,
149            "expected number of nodes should be returned"
150        );
151
152        let expected_nodes = vec![
153                Node{
154                    v: 3,
155                    id: signature::PublicKey::from("43e5aaee54c768867718837ef4f6a6161e0615da0fcf8da394e5c8b7a0d54c18"),
156                    entity_id: signature::PublicKey::from("761950dfe65936f6e9d06a0124bc930f7d5b1812ceefdfb2cae0ef5841291531"),
157                    expiration: 32,
158                    ..Default::default()
159                },
160                Node{
161                    v: 3,
162                    id: signature::PublicKey::from("f43c3559658f76b85d0630f56dc75d603807ac60be0ca3aada66799289066758"),
163                    entity_id: signature::PublicKey::from("761950dfe65936f6e9d06a0124bc930f7d5b1812ceefdfb2cae0ef5841291531"),
164                    expiration: 32,
165                    tls: TLSInfo{
166                        pub_key: signature::PublicKey::from("fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0"),
167                        ..Default::default()
168                    },
169                    p2p: P2PInfo{
170                        id: signature::PublicKey::from("fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff3"),
171                        addresses: Some(Vec::new()),
172                    },
173                    consensus: ConsensusInfo{
174                        id: signature::PublicKey::from("fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff4"),
175                        addresses: Some(Vec::new()),
176                    },
177                    vrf: VRFInfo{
178                        id: PublicKey::from("fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff5"),
179                    },
180                    runtimes: Some(vec![
181                        NodeRuntime{
182                            id: Namespace::from("8000000000000000000000000000000000000000000000000000000000000010"),
183                            version: Version::from(321),
184                            ..Default::default()
185                        },
186                        NodeRuntime{
187                            id: Namespace::from("8000000000000000000000000000000000000000000000000000000000000011"),
188                            version: Version::from(123),
189                            capabilities: Capabilities{
190                               tee: Some(CapabilityTEE{
191                                   hardware: TEEHardware::TEEHardwareIntelSGX,
192                                    rak: signature::PublicKey::from("fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8"),
193                                    attestation: vec![0, 1,2,3,4,5],
194                                    ..Default::default()
195                               }),
196                            },
197                            extra_info: Some(vec![5,3,2,1]),
198                        },
199                    ]),
200                    ..Default::default()
201                },
202            ];
203        assert_eq!(nodes, expected_nodes,);
204
205        let node = registry_state
206            .node(&expected_nodes.get(1).unwrap().id)
207            .expect("node query should work");
208        assert_eq!(node, Some(expected_nodes.get(1).unwrap().clone()));
209
210        let node = registry_state
211            .node(&signature::PublicKey::from(
212                "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff",
213            ))
214            .expect("node query should work");
215        assert_eq!(node, None);
216
217        let expected_runtimes = vec![
218            Runtime {
219                v: 3,
220                id: Namespace::from(
221                    "8000000000000000000000000000000000000000000000000000000000000010",
222                ),
223                entity_id: signature::PublicKey::from(
224                    "761950dfe65936f6e9d06a0124bc930f7d5b1812ceefdfb2cae0ef5841291531",
225                ),
226                kind: RuntimeKind::KindCompute,
227                tee_hardware: TEEHardware::TEEHardwareInvalid,
228                admission_policy: RuntimeAdmissionPolicy {
229                    any_node: Some(AnyNodeRuntimeAdmissionPolicy {}),
230                    ..Default::default()
231                },
232                deployments: vec![
233                    VersionInfo {
234                        version: Version::from(321),
235                        valid_from: 42,
236                        ..Default::default()
237                    },
238                    VersionInfo {
239                        version: Version::from(320),
240                        valid_from: 10,
241                        ..Default::default()
242                    },
243                ],
244                ..Default::default()
245            },
246            Runtime {
247                v: 3,
248                id: Namespace::from(
249                    "8000000000000000000000000000000000000000000000000000000000000011",
250                ),
251                entity_id: signature::PublicKey::from(
252                    "761950dfe65936f6e9d06a0124bc930f7d5b1812ceefdfb2cae0ef5841291531",
253                ),
254                kind: RuntimeKind::KindCompute,
255                tee_hardware: TEEHardware::TEEHardwareIntelSGX,
256                admission_policy: RuntimeAdmissionPolicy {
257                    any_node: Some(AnyNodeRuntimeAdmissionPolicy {}),
258                    ..Default::default()
259                },
260                deployments: vec![
261                    VersionInfo {
262                        version: Version::from(123),
263                        valid_from: 42,
264                        tee: vec![1, 2, 3, 4, 5],
265                        bundle_checksum: vec![0x5; 32],
266                    },
267                    VersionInfo {
268                        version: Version::from(120),
269                        valid_from: 10,
270                        tee: vec![5, 4, 3, 2, 1],
271                        ..Default::default()
272                    },
273                ],
274                ..Default::default()
275            },
276            Runtime {
277                v: 3,
278                id: Namespace::from(
279                    "8000000000000000000000000000000000000000000000000000000000000012",
280                ),
281                entity_id: signature::PublicKey::from(
282                    "761950dfe65936f6e9d06a0124bc930f7d5b1812ceefdfb2cae0ef5841291531",
283                ),
284                kind: RuntimeKind::KindCompute,
285                tee_hardware: TEEHardware::TEEHardwareIntelSGX,
286                admission_policy: RuntimeAdmissionPolicy {
287                    per_role: BTreeMap::from([(
288                        RolesMask::ROLE_OBSERVER,
289                        PerRoleAdmissionPolicy {
290                            entity_whitelist: Some(EntityWhitelistRoleAdmissionPolicy {
291                                entities: BTreeMap::new(),
292                            }),
293                        },
294                    )]),
295                    ..Default::default()
296                },
297                deployments: vec![VersionInfo {
298                    version: Version::from(123),
299                    valid_from: 42,
300                    tee: vec![1, 2, 3, 4, 5],
301                    bundle_checksum: vec![0x5; 32],
302                }],
303                ..Default::default()
304            },
305        ];
306
307        for rt in expected_runtimes {
308            let ext_rt = registry_state
309                .runtime(&rt.id)
310                .expect("runtime query should work");
311            assert_eq!(ext_rt, Some(rt));
312        }
313    }
314}