oasis_core_runtime/consensus/state/keymanager/
churp.rs

1//! Key manager state in the consensus layer.
2use anyhow::anyhow;
3
4use crate::{
5    common::{
6        crypto::hash::Hash,
7        key_format::{KeyFormat, KeyFormatAtom},
8        namespace::Namespace,
9    },
10    consensus::{keymanager::churp::Status, state::StateError},
11    key_format,
12    storage::mkvs::ImmutableMKVS,
13};
14
15key_format!(StatusKeyFmt, 0x75, (Hash, u8));
16
17/// Consensus CHURP state wrapper.
18pub struct ImmutableState<'a, T: ImmutableMKVS> {
19    mkvs: &'a T,
20}
21
22impl<'a, T: ImmutableMKVS> ImmutableState<'a, T> {
23    /// Constructs a new ImmutableMKVS.
24    pub fn new(mkvs: &'a T) -> ImmutableState<'a, T> {
25        ImmutableState { mkvs }
26    }
27}
28
29impl<'a, T: ImmutableMKVS> ImmutableState<'a, T> {
30    /// Looks up a specific key manager status by its namespace identifier.
31    pub fn status(
32        &self,
33        runtime_id: Namespace,
34        churp_id: u8,
35    ) -> Result<Option<Status>, StateError> {
36        let h = Hash::digest_bytes(runtime_id.as_ref());
37        match self.mkvs.get(&StatusKeyFmt((h, churp_id)).encode()) {
38            Ok(Some(b)) => Ok(Some(self.decode_status(&b)?)),
39            Ok(None) => Ok(None),
40            Err(err) => Err(StateError::Unavailable(anyhow!(err))),
41        }
42    }
43
44    fn decode_status(&self, data: &[u8]) -> Result<Status, StateError> {
45        cbor::from_slice(data).map_err(|err| StateError::Unavailable(anyhow!(err)))
46    }
47}
48
49#[cfg(test)]
50mod test {
51    use std::collections::HashMap;
52
53    use super::*;
54    use crate::{
55        common::{
56            crypto::signature::{PublicKey, Signature, SignatureBundle},
57            sgx::{EnclaveIdentity, MrEnclave, MrSigner},
58        },
59        consensus::keymanager::churp::{Application, PolicySGX, SignedPolicySGX, SuiteId},
60        storage::mkvs::{
61            interop::{Fixture, ProtocolServer},
62            Root, RootType, Tree,
63        },
64    };
65
66    #[test]
67    fn test_keymanager_secrets_state_interop() {
68        // Keep in sync with go/consensus/cometbft/apps/keymanager/churp/state/interop/interop.go.
69        // If mock consensus state changes, update the root hash bellow.
70        // See protocol server stdout for hash.
71        // To make the hash show up during tests, run "cargo test" as
72        // "cargo test -- --nocapture".
73
74        // Setup protocol server with initialized mock consensus state.
75        let server = ProtocolServer::new(Fixture::ConsensusMock.into());
76        let mock_consensus_root = Root {
77            version: 1,
78            root_type: RootType::State,
79            hash: Hash::from("8e39bf193f8a954ab8f8d7cb6388c591fd0785ea060bbd8e3752e266b54499d3"),
80            ..Default::default()
81        };
82        let mkvs = Tree::builder()
83            .with_capacity(100_000, 10_000_000)
84            .with_root(mock_consensus_root)
85            .build(server.read_sync());
86        let state = ImmutableState::new(&mkvs);
87
88        // Prepare expected results.
89        let runtime_id =
90            Namespace::from("8000000000000000000000000000000000000000000000000000000000000000");
91        let enclave1 = EnclaveIdentity {
92            mr_enclave: MrEnclave::from(
93                "c9a589851b1f35627177fd70378ed778170f737611e4dfbf0b6d25bdff55b474",
94            ),
95            mr_signer: MrSigner::from(
96                "7d310664780931ae103ab30a90171c201af385a72757bb4683578fdebde9adf5",
97            ),
98        };
99        let enclave2 = EnclaveIdentity {
100            mr_enclave: MrEnclave::from(
101                "756eaf76f5482c5345808b1eaccdd5c60f864bb2aa2d2b870df00ce435af4e23",
102            ),
103            mr_signer: MrSigner::from(
104                "3597a2ff0743016f28e5d7e129304ee1c43dbdae3dba94e19cee3549038a5a32",
105            ),
106        };
107        let signer1 =
108            PublicKey::from("96533c123a6f4d33c68357109c2eb7c6e6a0f947be3ae1e320d153f561523ff2");
109        let signer2 =
110            PublicKey::from("4b97bfd95e829d5838131492b5c133e66ac6ef0db414c0be6207ec78c12d2b17");
111        let sig1 = Signature::from("eda666cff6e4030200737e0c7707ad4a378aab4cc0455306992c13da2155b97c91b0fde0325a7a6818f2cbf92813cc587723c8c205a7cb5389ca7b21a038b60a");
112        let sig2 = Signature::from("db90d354272e025aa9a5856f32ea4f5d6becb0ff6340f3cb7f9104ac04ef29ed4f9b5c21b7ea82924800b30f94724b40c376414f80780ff8b7b60a34edea9f02");
113        let checksum =
114            Hash::from("1bff211fae98c88ba82388ae954b88a71d3bbe327e162e9fa711fe7a1b759c3e");
115        let committee = vec![signer1, signer2];
116        let mut applications = HashMap::new();
117        applications.insert(
118            signer1,
119            Application {
120                checksum: checksum.clone(),
121                reconstructed: false,
122            },
123        );
124        applications.insert(
125            signer2,
126            Application {
127                checksum: checksum.clone(),
128                reconstructed: true,
129            },
130        );
131        let checksum = Some(checksum);
132        let next_checksum = checksum;
133
134        // Test empty status.
135        let status = Status {
136            ..Default::default()
137        };
138
139        let status = state
140            .status(status.runtime_id, status.id)
141            .expect("status query should work")
142            .expect("status query should return a result");
143        assert_eq!(status, status, "invalid status");
144
145        // Test non-empty status.
146        let status = Status {
147            id: 1,
148            runtime_id,
149            suite_id: SuiteId::NistP384Sha3_384,
150            threshold: 2,
151            extra_shares: 1,
152            handoff_interval: 3,
153            policy: SignedPolicySGX {
154                policy: PolicySGX {
155                    id: 1,
156                    runtime_id,
157                    serial: 6,
158                    may_share: vec![enclave1],
159                    may_join: vec![enclave2],
160                    may_query: HashMap::new(),
161                },
162                signatures: vec![
163                    SignatureBundle {
164                        public_key: signer1,
165                        signature: sig1,
166                    },
167                    SignatureBundle {
168                        public_key: signer2,
169                        signature: sig2,
170                    },
171                ],
172            },
173            handoff: 4,
174            checksum,
175            committee,
176            next_handoff: 5,
177            next_checksum,
178            applications,
179        };
180
181        let status = state
182            .status(status.runtime_id, status.id)
183            .expect("status query should work")
184            .expect("status query should return a result");
185        assert_eq!(status, status, "invalid status");
186    }
187}