1use std::collections::BTreeMap;
8
9use anyhow::{anyhow, bail};
10use num_traits::Zero;
11use tiny_keccak::{Hasher, TupleHash};
12
13use crate::{
14 common::{
15 crypto::{
16 hash::Hash,
17 signature::{self, Signature},
18 x25519,
19 },
20 namespace::Namespace,
21 quantity, sgx,
22 version::Version,
23 },
24 consensus::{beacon::EpochTime, scheduler, staking},
25 identity::Identity,
26};
27
28pub const MODULE_NAME: &str = "registry";
30
31pub const METHOD_PROVE_FRESHNESS: &str = "registry.ProveFreshness";
33
34pub const ATTESTATION_SIGNATURE_CONTEXT: &[u8] = b"oasis-core/node: TEE attestation signature";
36
37pub const ENDORSE_CAPABILITY_TEE_SIGNATURE_CONTEXT: &[u8] =
39 b"oasis-core/node: endorse TEE capability";
40
41#[derive(Clone, Debug, Default, PartialEq, Eq, Hash, cbor::Encode, cbor::Decode)]
43pub struct TCPAddress {
44 #[cbor(rename = "IP")]
45 pub ip: Vec<u8>,
46 #[cbor(rename = "Port")]
47 pub port: i64,
48 #[cbor(rename = "Zone")]
49 pub zone: String,
50}
51
52#[derive(Clone, Debug, Default, PartialEq, Eq, Hash, cbor::Encode, cbor::Decode)]
57pub struct TLSAddress {
58 pub pub_key: signature::PublicKey,
60
61 pub address: TCPAddress,
63}
64
65#[derive(Clone, Debug, Default, PartialEq, Eq, Hash, cbor::Encode, cbor::Decode)]
67pub struct TLSInfo {
68 pub pub_key: signature::PublicKey,
70
71 #[cbor(rename = "next_pub_key", optional)]
72 pub _deprecated_next_pub_key: Option<signature::PublicKey>,
73
74 #[cbor(rename = "addresses", optional)]
75 pub _deprecated_addresses: Option<Vec<TLSAddress>>,
76}
77
78#[derive(Clone, Debug, Default, PartialEq, Eq, Hash, cbor::Encode, cbor::Decode)]
80pub struct P2PInfo {
81 pub id: signature::PublicKey,
83
84 pub addresses: Option<Vec<TCPAddress>>,
86}
87
88#[derive(Clone, Debug, Default, PartialEq, Eq, Hash, cbor::Encode, cbor::Decode)]
93pub struct ConsensusAddress {
94 pub id: signature::PublicKey,
96
97 pub address: TCPAddress,
99}
100
101#[derive(Clone, Debug, Default, PartialEq, Eq, Hash, cbor::Encode, cbor::Decode)]
103pub struct ConsensusInfo {
104 pub id: signature::PublicKey,
106
107 pub addresses: Option<Vec<ConsensusAddress>>,
109}
110
111#[derive(Clone, Debug, Default, PartialEq, Eq, Hash, cbor::Encode, cbor::Decode)]
113pub struct VRFInfo {
114 pub id: signature::PublicKey,
116}
117
118#[derive(Clone, Debug, Default, PartialEq, Eq, Hash, cbor::Encode, cbor::Decode)]
120pub struct CapabilityTEE {
121 pub hardware: TEEHardware,
123
124 pub rak: signature::PublicKey,
126
127 #[cbor(optional)]
129 pub rek: Option<x25519::PublicKey>,
130
131 pub attestation: Vec<u8>,
133}
134
135impl CapabilityTEE {
136 pub fn try_decode_attestation<T>(&self) -> Result<T, cbor::DecodeError>
138 where
139 T: cbor::Decode,
140 {
141 cbor::from_slice_non_strict(&self.attestation)
142 }
143
144 pub fn matches(&self, identity: &Identity) -> bool {
146 match self.hardware {
147 TEEHardware::TEEHardwareInvalid => false,
148 TEEHardware::TEEHardwareIntelSGX => {
149 let attestation: SGXAttestation = match self.try_decode_attestation() {
151 Ok(a) => a,
152 _ => return false,
153 };
154 identity.rak_matches(&self.rak, &attestation.quote())
155 }
156 }
157 }
158
159 pub fn verify(
161 &self,
162 policy: &sgx::QuotePolicy,
163 node_id: &signature::PublicKey,
164 ) -> anyhow::Result<VerifiedAttestation> {
165 match self.hardware {
166 TEEHardware::TEEHardwareInvalid => bail!("invalid TEE hardware"),
167 TEEHardware::TEEHardwareIntelSGX => {
168 let attestation: SGXAttestation = self.try_decode_attestation()?;
170 attestation.verify(
171 policy,
172 node_id,
173 &self.rak,
174 self.rek.as_ref().ok_or(anyhow!("missing REK"))?,
175 )
176 }
177 }
178 }
179}
180
181#[derive(Clone, Debug, Default, PartialEq, Eq, Hash, cbor::Encode, cbor::Decode)]
188pub struct EndorsedCapabilityTEE {
189 pub capability_tee: CapabilityTEE,
191
192 pub node_endorsement: signature::SignatureBundle,
194}
195
196impl EndorsedCapabilityTEE {
197 pub fn verify_endorsement(&self) -> anyhow::Result<()> {
201 if !self.node_endorsement.verify(
202 ENDORSE_CAPABILITY_TEE_SIGNATURE_CONTEXT,
203 &cbor::to_vec(self.capability_tee.clone()),
204 ) {
205 bail!("invalid node endorsement signature");
206 }
207 Ok(())
208 }
209
210 pub fn verify(
212 &self,
213 policy: &sgx::QuotePolicy,
214 ) -> anyhow::Result<VerifiedEndorsedCapabilityTEE> {
215 self.verify_endorsement()?;
217
218 let verified_attestation = self
220 .capability_tee
221 .verify(policy, &self.node_endorsement.public_key)?;
222
223 Ok(VerifiedEndorsedCapabilityTEE {
224 verified_attestation,
225 node_id: Some(self.node_endorsement.public_key),
226 })
227 }
228}
229
230#[derive(Clone, Debug, Default)]
232pub struct VerifiedEndorsedCapabilityTEE {
233 pub verified_attestation: VerifiedAttestation,
235 pub node_id: Option<signature::PublicKey>,
237}
238
239impl From<VerifiedAttestation> for VerifiedEndorsedCapabilityTEE {
240 fn from(verified_attestation: VerifiedAttestation) -> Self {
241 Self {
242 verified_attestation,
243 node_id: None,
244 }
245 }
246}
247
248impl From<sgx::VerifiedQuote> for VerifiedEndorsedCapabilityTEE {
249 fn from(verified_quote: sgx::VerifiedQuote) -> Self {
250 Self {
251 verified_attestation: verified_quote.into(),
252 node_id: None,
253 }
254 }
255}
256
257#[derive(Clone, Debug, Default, PartialEq, Eq, Hash, cbor::Encode, cbor::Decode)]
259pub struct Capabilities {
260 #[cbor(optional)]
262 pub tee: Option<CapabilityTEE>,
263}
264
265#[derive(Clone, Debug, Default, PartialEq, Eq, Hash, cbor::Encode, cbor::Decode)]
267pub struct NodeRuntime {
268 pub id: Namespace,
270
271 pub version: Version,
273
274 pub capabilities: Capabilities,
276
277 pub extra_info: Option<Vec<u8>>,
279}
280
281#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, cbor::Encode, cbor::Decode)]
283#[cbor(transparent)]
284pub struct RolesMask(pub u32);
285
286impl RolesMask {
290 pub const ROLE_EMPTY: RolesMask = RolesMask(0);
292 pub const ROLE_COMPUTE_WORKER: RolesMask = RolesMask(1 << 0);
294 pub const ROLE_OBSERVER: RolesMask = RolesMask(1 << 1);
296 pub const ROLE_KEY_MANAGER: RolesMask = RolesMask(1 << 2);
298 pub const ROLE_VALIDATOR: RolesMask = RolesMask(1 << 3);
300 pub const ROLE_RESERVED_3: RolesMask = RolesMask(1 << 4);
302 pub const ROLE_STORAGE_RPC: RolesMask = RolesMask(1 << 5);
304
305 pub const ROLES_RESERVED: RolesMask =
307 RolesMask(!((Self::ROLE_STORAGE_RPC.0 << 1) - 1) | Self::ROLE_RESERVED_3.0);
308
309 pub fn contains(&self, role: RolesMask) -> bool {
311 (self.0 & role.0) != 0
312 }
313
314 pub fn is_single_role(&self) -> bool {
316 self.0 != 0 && self.0 & (self.0 - 1) == 0 && self.0 & Self::ROLES_RESERVED.0 == 0
318 }
319}
320
321impl std::ops::BitAnd for RolesMask {
322 type Output = Self;
323
324 fn bitand(self, rhs: Self) -> Self::Output {
325 Self(self.0.bitand(rhs.0))
326 }
327}
328
329impl std::ops::BitOr for RolesMask {
330 type Output = Self;
331
332 fn bitor(self, rhs: Self) -> Self::Output {
333 Self(self.0.bitor(rhs.0))
334 }
335}
336
337impl PartialOrd for RolesMask {
338 fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
339 Some(self.cmp(other))
340 }
341}
342
343impl Ord for RolesMask {
344 fn cmp(&self, other: &Self) -> std::cmp::Ordering {
345 self.0.cmp(&other.0)
346 }
347}
348
349impl Default for RolesMask {
350 fn default() -> Self {
351 Self::ROLE_EMPTY
352 }
353}
354
355#[derive(Clone, Debug, Default, PartialEq, Eq, cbor::Encode, cbor::Decode)]
357pub struct Node {
358 pub v: u16,
360
361 pub id: signature::PublicKey,
363
364 pub entity_id: signature::PublicKey,
366
367 pub expiration: u64,
369
370 pub tls: TLSInfo,
372
373 pub p2p: P2PInfo,
375
376 pub consensus: ConsensusInfo,
378
379 pub vrf: VRFInfo,
381
382 pub runtimes: Option<Vec<NodeRuntime>>,
384
385 pub roles: RolesMask,
387
388 #[cbor(optional)]
390 pub software_version: Option<String>,
391}
392
393impl Node {
394 pub fn has_roles(&self, roles: RolesMask) -> bool {
396 self.roles.contains(roles)
397 }
398
399 pub fn has_tee(&self, identity: &Identity, runtime_id: &Namespace, version: &Version) -> bool {
401 if let Some(rts) = &self.runtimes {
402 for rt in rts {
403 if runtime_id != &rt.id {
404 continue;
405 }
406 if version != &rt.version {
407 continue;
408 }
409 if let Some(tee) = &rt.capabilities.tee {
410 if tee.matches(identity) {
411 return true;
412 }
413 }
414 }
415 }
416 false
417 }
418
419 pub fn get_runtime(&self, runtime_id: &Namespace, version: &Version) -> Option<NodeRuntime> {
422 if let Some(rts) = &self.runtimes {
423 for rt in rts {
424 if runtime_id != &rt.id {
425 continue;
426 }
427 if version != &rt.version {
428 continue;
429 }
430 return Some(rt.clone());
431 }
432 }
433 None
434 }
435}
436
437#[derive(Clone, Debug, Default, PartialEq, Eq, Hash, cbor::Encode, cbor::Decode)]
439#[repr(u32)]
440pub enum RuntimeKind {
441 #[default]
443 KindInvalid = 0,
444 KindCompute = 1,
446 KindKeyManager = 2,
448}
449
450#[derive(Clone, Debug, Default, PartialEq, Eq, Hash, cbor::Encode, cbor::Decode)]
452pub struct ExecutorParameters {
453 pub group_size: u16,
455 pub group_backup_size: u16,
457 pub allowed_stragglers: u16,
459 pub round_timeout: i64,
461 pub max_messages: u32,
464 #[cbor(optional)]
467 pub min_live_rounds_percent: u8,
468 #[cbor(optional)]
472 pub max_missed_proposals_percent: u8,
473 #[cbor(optional)]
476 pub min_live_rounds_eval: u64,
477 #[cbor(optional)]
480 pub max_liveness_fails: u8,
481}
482
483#[derive(Clone, Debug, Default, PartialEq, Eq, Hash, cbor::Encode, cbor::Decode)]
485pub struct TxnSchedulerParameters {
486 #[cbor(optional)]
489 pub batch_flush_timeout: i64,
490 #[cbor(optional)]
492 pub max_batch_size: u64,
493 #[cbor(optional)]
495 pub max_batch_size_bytes: u64,
496 #[cbor(optional)]
498 pub max_in_messages: u32,
499 #[cbor(optional)]
501 pub propose_batch_timeout: i64,
502}
503
504#[derive(Clone, Debug, Default, PartialEq, Eq, Hash, cbor::Encode, cbor::Decode)]
506pub struct StorageParameters {
507 pub checkpoint_interval: u64,
509 pub checkpoint_num_kept: u64,
511 pub checkpoint_chunk_size: u64,
513}
514
515#[derive(Clone, Debug, Default, PartialEq, Eq, Hash, cbor::Encode, cbor::Decode)]
519pub struct SchedulingConstraints {
520 #[cbor(optional)]
521 pub validator_set: Option<ValidatorSetConstraint>,
522
523 #[cbor(optional)]
524 pub max_nodes: Option<MaxNodesConstraint>,
525
526 #[cbor(optional)]
527 pub min_pool_size: Option<MinPoolSizeConstraint>,
528}
529
530#[derive(Clone, Debug, Default, PartialEq, Eq, Hash, cbor::Encode, cbor::Decode)]
533pub struct ValidatorSetConstraint {}
534
535#[derive(Clone, Debug, Default, PartialEq, Eq, Hash, cbor::Encode, cbor::Decode)]
537pub struct MaxNodesConstraint {
538 pub limit: u16,
539}
540
541#[derive(Clone, Debug, Default, PartialEq, Eq, Hash, cbor::Encode, cbor::Decode)]
543pub struct MinPoolSizeConstraint {
544 pub limit: u16,
545}
546
547#[derive(Clone, Debug, Default, PartialEq, Eq, Hash, cbor::Encode, cbor::Decode)]
549pub struct RuntimeStakingParameters {
550 #[cbor(optional)]
556 pub thresholds: BTreeMap<staking::ThresholdKind, quantity::Quantity>,
557
558 #[cbor(optional)]
560 pub slashing: BTreeMap<staking::SlashReason, staking::Slash>,
561
562 #[cbor(optional)]
565 pub reward_equivocation: u8,
566
567 #[cbor(optional)]
570 pub reward_bad_results: u8,
571
572 #[cbor(optional)]
575 pub min_in_message_fee: quantity::Quantity,
576}
577
578#[derive(Clone, Debug, Default, PartialEq, Eq, Hash, cbor::Encode, cbor::Decode)]
580pub struct EntityWhitelistRuntimeAdmissionPolicy {
581 #[cbor(optional)]
583 pub entities: BTreeMap<signature::PublicKey, EntityWhitelistConfig>,
584}
585
586#[derive(Clone, Debug, Default, PartialEq, Eq, Hash, cbor::Encode, cbor::Decode)]
588pub struct EntityWhitelistConfig {
589 #[cbor(optional)]
595 pub max_nodes: BTreeMap<RolesMask, u16>,
596}
597
598#[derive(Clone, Debug, Default, PartialEq, Eq, Hash, cbor::Encode, cbor::Decode)]
600pub struct EntityWhitelistRoleConfig {
601 #[cbor(optional)]
602 pub max_nodes: u16,
603}
604
605#[derive(Clone, Debug, Default, PartialEq, Eq, Hash, cbor::Encode, cbor::Decode)]
607pub struct EntityWhitelistRoleAdmissionPolicy {
608 pub entities: BTreeMap<signature::PublicKey, EntityWhitelistRoleConfig>,
609}
610
611#[derive(Clone, Debug, Default, PartialEq, Eq, Hash, cbor::Encode, cbor::Decode)]
613pub struct PerRoleAdmissionPolicy {
614 #[cbor(optional)]
615 pub entity_whitelist: Option<EntityWhitelistRoleAdmissionPolicy>,
616}
617
618#[derive(Clone, Debug, Default, PartialEq, Eq, Hash, cbor::Encode, cbor::Decode)]
620pub struct AnyNodeRuntimeAdmissionPolicy {}
621
622#[derive(Clone, Debug, Default, PartialEq, Eq, Hash, cbor::Encode, cbor::Decode)]
624pub struct RuntimeAdmissionPolicy {
625 #[cbor(optional)]
627 pub any_node: Option<AnyNodeRuntimeAdmissionPolicy>,
628
629 #[cbor(optional)]
631 pub entity_whitelist: Option<EntityWhitelistRuntimeAdmissionPolicy>,
632
633 #[cbor(optional)]
636 pub per_role: BTreeMap<RolesMask, PerRoleAdmissionPolicy>,
637}
638
639#[derive(Clone, Debug, Default, PartialEq, Eq, Hash, cbor::Encode, cbor::Decode)]
641#[repr(u8)]
642pub enum RuntimeGovernanceModel {
643 #[default]
645 GovernanceInvalid = 0,
646 GovernanceEntity = 1,
648 GovernanceRuntime = 2,
650 GovernanceConsensus = 3,
652}
653
654#[derive(Clone, Debug, Default, PartialEq, Eq, Hash, cbor::Encode, cbor::Decode)]
656pub struct VersionInfo {
657 pub version: Version,
659 pub valid_from: EpochTime,
661 #[cbor(optional)]
663 pub tee: Vec<u8>,
664 #[cbor(optional)]
666 pub bundle_checksum: Vec<u8>,
667}
668
669impl VersionInfo {
670 pub fn try_decode_tee<T>(&self) -> Result<T, cbor::DecodeError>
672 where
673 T: cbor::Decode,
674 {
675 cbor::from_slice_non_strict(&self.tee)
676 }
677}
678
679#[derive(Clone, Debug, cbor::Encode, cbor::Decode)]
681#[cbor(tag = "v")]
682pub enum SGXConstraints {
683 #[cbor(rename = 0, missing)]
685 V0 {
686 #[cbor(optional)]
688 enclaves: Vec<sgx::EnclaveIdentity>,
689
690 #[cbor(optional)]
692 allowed_quote_statuses: Vec<i64>,
693 },
694
695 #[cbor(rename = 1)]
697 V1 {
698 #[cbor(optional)]
700 enclaves: Vec<sgx::EnclaveIdentity>,
701
702 #[cbor(optional)]
704 policy: sgx::QuotePolicy,
705
706 #[cbor(optional)]
708 max_attestation_age: u64,
709 },
710}
711
712impl SGXConstraints {
713 pub fn enclaves(&self) -> &Vec<sgx::EnclaveIdentity> {
715 match self {
716 Self::V0 { ref enclaves, .. } => enclaves,
717 Self::V1 { ref enclaves, .. } => enclaves,
718 }
719 }
720
721 pub fn contains_enclave(&self, eid: &sgx::EnclaveIdentity) -> bool {
723 self.enclaves().contains(eid)
724 }
725
726 pub fn policy(&self) -> sgx::QuotePolicy {
728 match self {
729 Self::V0 {
730 ref allowed_quote_statuses,
731 ..
732 } => sgx::QuotePolicy {
733 ias: Some(sgx::ias::QuotePolicy {
734 disabled: false,
735 allowed_quote_statuses: allowed_quote_statuses.clone(),
736 gid_blacklist: Vec::new(),
737 min_tcb_evaluation_data_number: 0,
738 }),
739 ..Default::default()
740 },
741 Self::V1 { ref policy, .. } => policy.clone(),
742 }
743 }
744}
745
746#[derive(Clone, Debug, Default)]
748pub struct VerifiedAttestation {
749 pub quote: sgx::VerifiedQuote,
751 pub height: Option<u64>,
753}
754
755impl From<sgx::VerifiedQuote> for VerifiedAttestation {
756 fn from(quote: sgx::VerifiedQuote) -> Self {
757 Self {
758 quote,
759 height: None,
760 }
761 }
762}
763
764#[derive(Clone, Debug, cbor::Encode, cbor::Decode)]
766#[cbor(tag = "v")]
767pub enum SGXAttestation {
768 #[cbor(rename = 0, missing)]
770 V0(sgx::ias::AVR),
771
772 #[cbor(rename = 1)]
774 V1 {
775 quote: sgx::Quote,
777 height: u64,
779 signature: Signature,
781 },
782}
783
784impl SGXAttestation {
785 pub fn quote(&self) -> sgx::Quote {
787 match self {
788 Self::V0(avr) => sgx::Quote::Ias(avr.clone()),
789 Self::V1 { quote, .. } => quote.clone(),
790 }
791 }
792
793 pub fn hash(
795 report_data: &[u8],
796 node_id: &signature::PublicKey,
797 height: u64,
798 rek: &x25519::PublicKey,
799 ) -> [u8; 32] {
800 let mut h = TupleHash::v256(ATTESTATION_SIGNATURE_CONTEXT);
801 h.update(report_data);
802 h.update(node_id.as_ref());
803 h.update(&height.to_le_bytes());
804 h.update(rek.0.as_bytes());
805 let mut result = [0u8; 32];
806 h.finalize(&mut result);
807 result
808 }
809
810 pub fn verify(
812 &self,
813 policy: &sgx::QuotePolicy,
814 node_id: &signature::PublicKey,
815 rak: &signature::PublicKey,
816 rek: &x25519::PublicKey,
817 ) -> anyhow::Result<VerifiedAttestation> {
818 let verified_quote = self.quote().verify(policy)?;
820
821 Identity::verify_binding(&verified_quote, rak)?;
823
824 match self {
826 Self::V1 {
827 height, signature, ..
828 } => {
829 let h = Self::hash(&verified_quote.report_data, node_id, *height, rek);
830 signature.verify(rak, ATTESTATION_SIGNATURE_CONTEXT, &h)?;
831
832 Ok(VerifiedAttestation {
833 quote: verified_quote,
834 height: Some(*height),
835 })
836 }
837 _ => bail!("V0 attestation not supported"),
838 }
839 }
840}
841
842#[derive(Clone, Debug, Default, PartialEq, Eq, Hash, cbor::Encode, cbor::Decode)]
844#[repr(u8)]
845pub enum TEEHardware {
846 #[default]
848 TEEHardwareInvalid = 0,
849 TEEHardwareIntelSGX = 1,
851}
852
853pub const LATEST_RUNTIME_DESCRIPTOR_VERSION: u16 = 3;
856
857#[derive(Clone, Debug, Default, PartialEq, Eq, Hash, cbor::Encode, cbor::Decode)]
859pub struct Runtime {
860 pub v: u16,
862 pub id: Namespace,
864 pub entity_id: signature::PublicKey,
866 pub genesis: RuntimeGenesis,
868 pub kind: RuntimeKind,
870 pub tee_hardware: TEEHardware,
872 #[cbor(optional)]
874 pub deployments: Vec<VersionInfo>,
875 #[cbor(optional)]
877 pub key_manager: Option<Namespace>,
878 #[cbor(optional)]
880 pub executor: ExecutorParameters,
881 #[cbor(optional)]
883 pub txn_scheduler: TxnSchedulerParameters,
884 #[cbor(optional)]
886 pub storage: StorageParameters,
887 pub admission_policy: RuntimeAdmissionPolicy,
889 #[cbor(optional)]
891 pub constraints:
892 BTreeMap<scheduler::CommitteeKind, BTreeMap<scheduler::Role, SchedulingConstraints>>,
893 #[cbor(optional, skip_serializing_if = "staking_params_are_empty")]
895 pub staking: RuntimeStakingParameters,
896 pub governance_model: RuntimeGovernanceModel,
898}
899
900fn staking_params_are_empty(p: &RuntimeStakingParameters) -> bool {
901 p.thresholds.is_empty()
902 && p.slashing.is_empty()
903 && p.reward_equivocation == 0
904 && p.reward_bad_results == 0
905 && p.min_in_message_fee.is_zero()
906}
907
908impl Runtime {
909 pub fn active_deployment(&self, now: EpochTime) -> Option<VersionInfo> {
911 self.deployments
912 .iter()
913 .filter(|vi| vi.valid_from <= now) .fold(None, |acc, vi| match acc {
915 None => Some(vi.clone()),
916 Some(ad) if ad.valid_from < vi.valid_from => Some(vi.clone()),
917 _ => acc,
918 })
919 }
920
921 pub fn deployment_for_version(&self, version: Version) -> Option<VersionInfo> {
923 self.deployments
924 .iter()
925 .find(|vi| vi.version == version)
926 .cloned()
927 }
928}
929
930impl cbor::Encode for Box<Runtime> {
931 fn into_cbor_value(self) -> cbor::Value {
932 (*self).into_cbor_value()
933 }
934}
935
936impl cbor::Decode for Box<Runtime> {
937 fn try_from_cbor_value(value: cbor::Value) -> Result<Self, cbor::DecodeError> {
938 Runtime::try_from_cbor_value(value).map(Box::new)
939 }
940}
941
942#[derive(Clone, Debug, Default, PartialEq, Eq, Hash, cbor::Encode, cbor::Decode)]
944pub struct RuntimeGenesis {
945 pub state_root: Hash,
948
949 pub round: u64,
951}
952
953#[cfg(test)]
954mod tests {
955 use std::{convert::TryInto, net::Ipv4Addr};
956
957 use base64::prelude::*;
958 use rustc_hex::{FromHex, ToHex};
959
960 use crate::common::quantity::Quantity;
961
962 use super::*;
963
964 macro_rules! btreemap {
966 ( $($key:expr => $value:expr,)+ ) => (btreemap!($($key => $value),+));
968 ( $($key:expr => $value:expr),* ) => {
969 {
970 let mut m = BTreeMap::new();
971 $( m.insert($key.into(), $value); )*
972 m
973 }
974 };
975}
976
977 #[test]
978 fn test_consistent_runtime() {
979 let tcs = vec![
981 ("q2F2AGJpZFggAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABka2luZABnZ2VuZXNpc6Jlcm91bmQAanN0YXRlX3Jvb3RYIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAZ3N0b3JhZ2Wjc2NoZWNrcG9pbnRfaW50ZXJ2YWwAc2NoZWNrcG9pbnRfbnVtX2tlcHQAdWNoZWNrcG9pbnRfY2h1bmtfc2l6ZQBoZXhlY3V0b3Klamdyb3VwX3NpemUAbG1heF9tZXNzYWdlcwBtcm91bmRfdGltZW91dABxZ3JvdXBfYmFja3VwX3NpemUAcmFsbG93ZWRfc3RyYWdnbGVycwBpZW50aXR5X2lkWCAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGx0ZWVfaGFyZHdhcmUAbXR4bl9zY2hlZHVsZXKgcGFkbWlzc2lvbl9wb2xpY3mhaGFueV9ub2RloHBnb3Zlcm5hbmNlX21vZGVsAA==", Runtime {
983 admission_policy: RuntimeAdmissionPolicy {
984 any_node: Some(AnyNodeRuntimeAdmissionPolicy {}),
985 ..Default::default()
986 },
987 ..Default::default()
988 }),
989 (
991 "q2F2AGJpZFggAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABka2luZABnZ2VuZXNpc6Jlcm91bmQAanN0YXRlX3Jvb3RYIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAZ3N0b3JhZ2Wjc2NoZWNrcG9pbnRfaW50ZXJ2YWwAc2NoZWNrcG9pbnRfbnVtX2tlcHQAdWNoZWNrcG9pbnRfY2h1bmtfc2l6ZQBoZXhlY3V0b3Klamdyb3VwX3NpemUAbG1heF9tZXNzYWdlcwBtcm91bmRfdGltZW91dABxZ3JvdXBfYmFja3VwX3NpemUAcmFsbG93ZWRfc3RyYWdnbGVycwBpZW50aXR5X2lkWCAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGx0ZWVfaGFyZHdhcmUAbXR4bl9zY2hlZHVsZXKgcGFkbWlzc2lvbl9wb2xpY3mhaGFueV9ub2RloHBnb3Zlcm5hbmNlX21vZGVsAA==",
992 Runtime {
993 admission_policy: RuntimeAdmissionPolicy {
994 any_node: Some(AnyNodeRuntimeAdmissionPolicy {}),
995 ..Default::default()
996 },
997 staking: RuntimeStakingParameters {
998 thresholds: BTreeMap::new(),
999 slashing: BTreeMap::new(),
1000 reward_equivocation: 0,
1001 reward_bad_results: 0,
1002 min_in_message_fee: Quantity::from(0u32),
1003 },
1004 ..Default::default()
1005 },
1006 ),
1007 (
1009 "rGF2AGJpZFggAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABka2luZABnZ2VuZXNpc6Jlcm91bmQAanN0YXRlX3Jvb3RYIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAZ3N0YWtpbmehcnJld2FyZF9iYWRfcmVzdWx0cwpnc3RvcmFnZaNzY2hlY2twb2ludF9pbnRlcnZhbABzY2hlY2twb2ludF9udW1fa2VwdAB1Y2hlY2twb2ludF9jaHVua19zaXplAGhleGVjdXRvcqVqZ3JvdXBfc2l6ZQBsbWF4X21lc3NhZ2VzAG1yb3VuZF90aW1lb3V0AHFncm91cF9iYWNrdXBfc2l6ZQByYWxsb3dlZF9zdHJhZ2dsZXJzAGllbnRpdHlfaWRYIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAbHRlZV9oYXJkd2FyZQBtdHhuX3NjaGVkdWxlcqBwYWRtaXNzaW9uX3BvbGljeaFoYW55X25vZGWgcGdvdmVybmFuY2VfbW9kZWwA",
1010 Runtime {
1011 admission_policy: RuntimeAdmissionPolicy {
1012 any_node: Some(AnyNodeRuntimeAdmissionPolicy {}),
1013 ..Default::default()
1014 },
1015 staking: RuntimeStakingParameters {
1016 thresholds: BTreeMap::new(),
1017 slashing: BTreeMap::new(),
1018 reward_equivocation: 0,
1019 reward_bad_results: 10,
1020 min_in_message_fee: Quantity::from(0u32),
1021 },
1022 ..Default::default()
1023 },
1024 ),
1025 (
1026 "r2F2GCpiaWRYIIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAZGtpbmQCZ2dlbmVzaXOiZXJvdW5kGCtqc3RhdGVfcm9vdFggseUhAZ+3vd413IH+55BlYQy937jvXCXihJg2aBkqbQ1nc3Rha2luZ6FycmV3YXJkX2JhZF9yZXN1bHRzCmdzdG9yYWdlo3NjaGVja3BvaW50X2ludGVydmFsGCFzY2hlY2twb2ludF9udW1fa2VwdAZ1Y2hlY2twb2ludF9jaHVua19zaXplGGVoZXhlY3V0b3Kpamdyb3VwX3NpemUJbG1heF9tZXNzYWdlcwVtcm91bmRfdGltZW91dAZxZ3JvdXBfYmFja3VwX3NpemUIcmFsbG93ZWRfc3RyYWdnbGVycwdybWF4X2xpdmVuZXNzX2ZhaWxzAXRtaW5fbGl2ZV9yb3VuZHNfZXZhbAJ3bWluX2xpdmVfcm91bmRzX3BlcmNlbnQEeBxtYXhfbWlzc2VkX3Byb3Bvc2Fsc19wZXJjZW50A2llbnRpdHlfaWRYIBI0VniQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAa2NvbnN0cmFpbnRzoQGhAaNpbWF4X25vZGVzoWVsaW1pdAptbWluX3Bvb2xfc2l6ZaFlbGltaXQFbXZhbGlkYXRvcl9zZXSga2RlcGxveW1lbnRzgaRjdGVlS3ZlcnNpb24gdGVlZ3ZlcnNpb26iZW1ham9yGCxlcGF0Y2gBanZhbGlkX2Zyb20Ab2J1bmRsZV9jaGVja3N1bVggAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQFra2V5X21hbmFnZXJYIIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABbHRlZV9oYXJkd2FyZQFtdHhuX3NjaGVkdWxlcqVubWF4X2JhdGNoX3NpemUZJxBvbWF4X2luX21lc3NhZ2VzGCBzYmF0Y2hfZmx1c2hfdGltZW91dBo7msoAdG1heF9iYXRjaF9zaXplX2J5dGVzGgCYloB1cHJvcG9zZV9iYXRjaF90aW1lb3V0Gnc1lABwYWRtaXNzaW9uX3BvbGljeaFwZW50aXR5X3doaXRlbGlzdKFoZW50aXRpZXOhWCASNFZ4kAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKFpbWF4X25vZGVzogEDBAFwZ292ZXJuYW5jZV9tb2RlbAM=",
1027 Runtime {
1028 v: 42,
1029 id: Namespace::from(
1030 "8000000000000000000000000000000000000000000000000000000000000000",
1031 ),
1032 entity_id: signature::PublicKey::from(
1033 "1234567890000000000000000000000000000000000000000000000000000000",
1034 ),
1035 genesis: RuntimeGenesis {
1036 round: 43,
1037 state_root: Hash::digest_bytes(b"stateroot hash"),
1038 },
1039 kind: RuntimeKind::KindKeyManager,
1040 tee_hardware: TEEHardware::TEEHardwareIntelSGX,
1041 deployments: vec![VersionInfo {
1042 version: Version {
1043 major: 44,
1044 minor: 0,
1045 patch: 1,
1046 },
1047 valid_from: 0,
1048 tee: b"version tee".to_vec(),
1049 bundle_checksum: vec![0x1; 32],
1050 }],
1051 key_manager: Some(Namespace::from(
1052 "8000000000000000000000000000000000000000000000000000000000000001",
1053 )),
1054 executor: ExecutorParameters {
1055 group_size: 9,
1056 group_backup_size: 8,
1057 allowed_stragglers: 7,
1058 round_timeout: 6,
1059 max_messages: 5,
1060 min_live_rounds_percent: 4,
1061 max_missed_proposals_percent: 3,
1062 min_live_rounds_eval: 2,
1063 max_liveness_fails: 1,
1064 },
1065 txn_scheduler: TxnSchedulerParameters {
1066 batch_flush_timeout: 1_000_000_000, max_batch_size: 10_000,
1068 max_batch_size_bytes: 10_000_000,
1069 max_in_messages: 32,
1070 propose_batch_timeout: 2_000_000_000, },
1072 storage: StorageParameters {
1073 checkpoint_interval: 33,
1074 checkpoint_num_kept: 6,
1075 checkpoint_chunk_size: 101,
1076 },
1077 admission_policy: RuntimeAdmissionPolicy {
1078 entity_whitelist: Some(EntityWhitelistRuntimeAdmissionPolicy {
1079 entities: btreemap! {
1080 signature::PublicKey::from("1234567890000000000000000000000000000000000000000000000000000000") => EntityWhitelistConfig {
1081 max_nodes: btreemap! {
1082 RolesMask::ROLE_COMPUTE_WORKER => 3,
1083 RolesMask::ROLE_KEY_MANAGER => 1,
1084 }
1085 }
1086 },
1087 }),
1088 ..Default::default()
1089 },
1090 constraints: btreemap! {
1091 scheduler::CommitteeKind::ComputeExecutor => btreemap! {
1092 scheduler::Role::Worker => SchedulingConstraints{
1093 max_nodes: Some(
1094 MaxNodesConstraint{
1095 limit: 10,
1096 }
1097 ),
1098 min_pool_size: Some(
1099 MinPoolSizeConstraint {
1100 limit: 5,
1101 }
1102 ),
1103 validator_set: Some(ValidatorSetConstraint{}),
1104 },
1105 }
1106 },
1107 staking: RuntimeStakingParameters {
1108 thresholds: BTreeMap::new(),
1109 slashing: BTreeMap::new(),
1110 reward_equivocation: 0,
1111 reward_bad_results: 10,
1112 min_in_message_fee: Quantity::from(0u32),
1113 },
1114 governance_model: RuntimeGovernanceModel::GovernanceConsensus,
1115 },
1116 ),
1117 ];
1118 for (encoded_base64, rr) in tcs {
1119 let dec: Runtime = cbor::from_slice(&BASE64_STANDARD.decode(encoded_base64).unwrap())
1120 .expect("runtime should deserialize correctly");
1121 assert_eq!(dec, rr, "decoded runtime should match the expected value");
1122 let ser = BASE64_STANDARD.encode(cbor::to_vec(dec));
1123 assert_eq!(ser, encoded_base64, "runtime should serialize correctly");
1124 }
1125 }
1126
1127 #[test]
1128 fn test_consistent_node() {
1129 let tcs = vec![
1131 (
1132 "qmF2A2JpZFggAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABjcDJwomJpZFggAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABpYWRkcmVzc2Vz9mN0bHOhZ3B1Yl9rZXlYIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAY3ZyZqFiaWRYIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAZXJvbGVzAGhydW50aW1lc/ZpY29uc2Vuc3VzomJpZFggAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABpYWRkcmVzc2Vz9mllbnRpdHlfaWRYIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAamV4cGlyYXRpb24A",
1133 Node{v: 3, ..Default::default()},
1134 true,
1135 ),
1136 (
1137 "qmF2A2JpZFgg//////////////////////////////////////////BjcDJwomJpZFgg//////////////////////////////////////////VpYWRkcmVzc2Vz9mN0bHOhZ3B1Yl9rZXlYIP/////////////////////////////////////////yY3ZyZqFiaWRYIP/////////////////////////////////////////3ZXJvbGVzAGhydW50aW1lc4KkYmlkWCCAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEGd2ZXJzaW9uoWVwYXRjaBkBQWpleHRyYV9pbmZv9mxjYXBhYmlsaXRpZXOgpGJpZFgggAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABFndmVyc2lvbqFlcGF0Y2gYe2pleHRyYV9pbmZvRAUDAgFsY2FwYWJpbGl0aWVzoWN0ZWWjY3Jha1gg//////////////////////////////////////////hoaGFyZHdhcmUBa2F0dGVzdGF0aW9uRgABAgMEBWljb25zZW5zdXOiYmlkWCD/////////////////////////////////////////9mlhZGRyZXNzZXOAaWVudGl0eV9pZFgg//////////////////////////////////////////FqZXhwaXJhdGlvbhgg",
1138 Node{
1139 v: 3,
1140 id: signature::PublicKey::from("fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0"),
1141 entity_id: signature::PublicKey::from("fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff1"),
1142 expiration: 32,
1143 tls: TLSInfo{
1144 pub_key: signature::PublicKey::from("fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff2"),
1145 ..Default::default()
1146 },
1147 p2p: P2PInfo{
1148 id: signature::PublicKey::from("fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff5"),
1149 ..Default::default()
1150 },
1151 consensus: ConsensusInfo{
1152 id: signature::PublicKey::from("fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff6"),
1153 addresses: Some(Vec::new()),
1154 },
1155 vrf: VRFInfo{
1156 id: signature::PublicKey::from("fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7"),
1157 },
1158 runtimes: Some(vec![
1159 NodeRuntime{
1160 id: Namespace::from("8000000000000000000000000000000000000000000000000000000000000010"),
1161 version: Version::from(321u64),
1162 ..Default::default()
1163 },
1164 NodeRuntime{
1165 id: Namespace::from("8000000000000000000000000000000000000000000000000000000000000011"),
1166 version: Version::from(123),
1167 capabilities: Capabilities{
1168 tee: Some(CapabilityTEE{
1169 hardware: TEEHardware::TEEHardwareIntelSGX,
1170 rak: signature::PublicKey::from("fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8"),
1171 attestation: vec![0, 1,2,3,4,5],
1172 ..Default::default()
1173 }),
1174 },
1175 extra_info: Some(vec![5,3,2,1]),
1176 },
1177 ]),
1178 ..Default::default()
1179 },
1180 true,
1181 ),
1182 (
1183 "qWF2A2JpZFggAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABjcDJwomJpZFggAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABpYWRkcmVzc2Vz9mN0bHOhZ3B1Yl9rZXlYIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAZXJvbGVzAGhydW50aW1lc4GkYmlkWCCAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEGd2ZXJzaW9uoGpleHRyYV9pbmZv9mxjYXBhYmlsaXRpZXOgaWNvbnNlbnN1c6JiaWRYIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAaWFkZHJlc3Nlc/ZpZW50aXR5X2lkWCAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGpleHBpcmF0aW9uAA==",
1184 Node{
1185 v: 3,
1186 runtimes: Some(vec![
1187 NodeRuntime{
1188 id: Namespace::from("8000000000000000000000000000000000000000000000000000000000000010"),
1189 version: Version::from(0u64),
1190 ..Default::default()
1191 },
1192 ]),
1193 ..Default::default()
1194 },
1195 false,
1196 ),
1197 ];
1198 for (encoded_base64, node, round_trip) in tcs {
1199 println!("{:?}", node);
1200 let dec: Node = cbor::from_slice(&BASE64_STANDARD.decode(encoded_base64).unwrap())
1201 .expect("node should deserialize correctly");
1202 assert_eq!(dec, node, "decoded node should match the expected value");
1203
1204 if round_trip {
1205 let ser = BASE64_STANDARD.encode(cbor::to_vec(dec));
1206 assert_eq!(ser, encoded_base64, "node should serialize correctly");
1207 }
1208 }
1209 }
1210
1211 #[test]
1212 fn test_deserialize_node_v2() {
1213 let tcs = vec![
1215 (
1216 "qWF2AmJpZFggAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABjcDJwomJpZFggAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABpYWRkcmVzc2Vz9mN0bHOiZ3B1Yl9rZXlYIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAaWFkZHJlc3Nlc/Zlcm9sZXMAaHJ1bnRpbWVz9mljb25zZW5zdXOiYmlkWCAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGlhZGRyZXNzZXP2aWVudGl0eV9pZFggAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABqZXhwaXJhdGlvbgA=",
1217 Node{v: 2, ..Default::default()},
1218 ),
1219 (
1220 "qmF2AmJpZFggAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABjcDJwomJpZFggAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABpYWRkcmVzc2Vz9mN0bHOjZ3B1Yl9rZXlYIP/////////////////////////////////////////yaWFkZHJlc3Nlc4BsbmV4dF9wdWJfa2V5WCD/////////////////////////////////////////82N2cmahYmlkWCAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGVyb2xlcwBocnVudGltZXP2aWNvbnNlbnN1c6JiaWRYIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAaWFkZHJlc3Nlc/ZpZW50aXR5X2lkWCAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGpleHBpcmF0aW9uAA==",
1221 Node{
1222 v: 2,
1223 tls: TLSInfo{
1224 pub_key: signature::PublicKey::from("fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff2"),
1225 _deprecated_next_pub_key: Some(signature::PublicKey::from("fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff3")),
1226 _deprecated_addresses: Some(vec![]),
1227 },
1228 ..Default::default()
1229 },
1230 ),
1231 (
1232 "qmF2AmJpZFggAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABjcDJwomJpZFggAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABpYWRkcmVzc2Vz9mN0bHOjZ3B1Yl9rZXlYIP/////////////////////////////////////////yaWFkZHJlc3Nlc4OiZ2FkZHJlc3OjYklQUAAAAAAAAAAAAAD//38AAAFkUG9ydBh7ZFpvbmVgZ3B1Yl9rZXlYIP/////////////////////////////////////////0omdhZGRyZXNzo2JJUFAAAAAAAAAAAAAA///AqAEBZFBvcnQZD6BkWm9uZWBncHViX2tleVgg/////////////////////////////////////////8SiZ2FkZHJlc3OjYklQUAAAAAAAAAAAAAD//+pkY1hkUG9ydBkfQGRab25lYGdwdWJfa2V5WCD/////////////////////////////////////////1GxuZXh0X3B1Yl9rZXlYIP/////////////////////////////////////////zY3ZyZqFiaWRYIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAZXJvbGVzAGhydW50aW1lc/ZpY29uc2Vuc3VzomJpZFggAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABpYWRkcmVzc2Vz9mllbnRpdHlfaWRYIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAamV4cGlyYXRpb24A",
1233 Node{
1234 v: 2,
1235 tls: TLSInfo{
1236 pub_key: signature::PublicKey::from("fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff2"),
1237 _deprecated_next_pub_key: Some(signature::PublicKey::from("fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff3")),
1238 _deprecated_addresses: Some(vec![
1239 TLSAddress{
1240 pub_key: signature::PublicKey::from("fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff4"),
1241 address: TCPAddress { ip: Ipv4Addr::new(127, 0, 0, 1).to_ipv6_mapped().octets().to_vec(), port: 123, ..Default::default() }
1242 },
1243 TLSAddress{
1244 pub_key: signature::PublicKey::from("ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc4"),
1245 address: TCPAddress { ip: Ipv4Addr::new(192, 168, 1, 1).to_ipv6_mapped().octets().to_vec(), port: 4000, ..Default::default() }
1246 },
1247 TLSAddress{
1248 pub_key: signature::PublicKey::from("ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffd4"),
1249 address: TCPAddress { ip: Ipv4Addr::new(234, 100, 99, 88).to_ipv6_mapped().octets().to_vec(), port: 8000, ..Default::default() }
1250 },
1251
1252 ])
1253 },
1254 ..Default::default()
1255 },
1256 ),
1257 (
1258 "qmF2AmJpZFgg//////////////////////////////////////////BjcDJwomJpZFgg//////////////////////////////////////////VpYWRkcmVzc2Vz9mN0bHOjZ3B1Yl9rZXlYIP/////////////////////////////////////////yaWFkZHJlc3Nlc4GiZ2FkZHJlc3OjYklQUAAAAAAAAAAAAAD//38AAAFkUG9ydBh7ZFpvbmVgZ3B1Yl9rZXlYIP/////////////////////////////////////////0bG5leHRfcHViX2tleVgg//////////////////////////////////////////NjdnJmoWJpZFgg//////////////////////////////////////////dlcm9sZXMAaHJ1bnRpbWVzgqRiaWRYIIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQZ3ZlcnNpb26hZXBhdGNoGQFBamV4dHJhX2luZm/2bGNhcGFiaWxpdGllc6CkYmlkWCCAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEWd2ZXJzaW9uoWVwYXRjaBh7amV4dHJhX2luZm9EBQMCAWxjYXBhYmlsaXRpZXOhY3RlZaNjcmFrWCD/////////////////////////////////////////+GhoYXJkd2FyZQFrYXR0ZXN0YXRpb25GAAECAwQFaWNvbnNlbnN1c6JiaWRYIP/////////////////////////////////////////2aWFkZHJlc3Nlc4BpZW50aXR5X2lkWCD/////////////////////////////////////////8WpleHBpcmF0aW9uGCA=",
1259 Node{
1260 v: 2,
1261 id: signature::PublicKey::from("fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0"),
1262 entity_id: signature::PublicKey::from("fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff1"),
1263 expiration: 32,
1264 tls: TLSInfo{
1265 pub_key: signature::PublicKey::from("fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff2"),
1266 _deprecated_next_pub_key: Some(signature::PublicKey::from("fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff3")),
1267 _deprecated_addresses: Some(vec![TLSAddress{
1268 pub_key: signature::PublicKey::from("fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff4"),
1269 address: TCPAddress { ip: Ipv4Addr::new(127, 0, 0, 1).to_ipv6_mapped().octets().to_vec(), port: 123, ..Default::default() }
1270 }])
1271 },
1272 p2p: P2PInfo{
1273 id: signature::PublicKey::from("fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff5"),
1274 ..Default::default()
1275 },
1276 consensus: ConsensusInfo{
1277 id: signature::PublicKey::from("fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff6"),
1278 addresses: Some(Vec::new()),
1279 },
1280 vrf: VRFInfo{
1281 id: signature::PublicKey::from("fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7"),
1282 },
1283 runtimes: Some(vec![
1284 NodeRuntime{
1285 id: Namespace::from("8000000000000000000000000000000000000000000000000000000000000010"),
1286 version: Version::from(321u64),
1287 ..Default::default()
1288 },
1289 NodeRuntime{
1290 id: Namespace::from("8000000000000000000000000000000000000000000000000000000000000011"),
1291 version: Version::from(123),
1292 capabilities: Capabilities{
1293 tee: Some(CapabilityTEE{
1294 hardware: TEEHardware::TEEHardwareIntelSGX,
1295 rak: signature::PublicKey::from("fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8"),
1296 attestation: vec![0, 1,2,3,4,5],
1297 ..Default::default()
1298 }),
1299 },
1300 extra_info: Some(vec![5,3,2,1]),
1301 },
1302 ]),
1303 ..Default::default()
1304 },
1305 ),
1306 (
1307 "qmF2AmJpZFgg//////////////////////////////////////////BjcDJwomJpZFgg//////////////////////////////////////////VpYWRkcmVzc2Vz9mN0bHOjZ3B1Yl9rZXlYIP/////////////////////////////////////////yaWFkZHJlc3Nlc4GiZ2FkZHJlc3OjYklQUAAAAAAAAAAAAAD//38AAAFkUG9ydBh7ZFpvbmVgZ3B1Yl9rZXlYIP/////////////////////////////////////////0bG5leHRfcHViX2tleVgg//////////////////////////////////////////NjdnJmoWJpZFgg//////////////////////////////////////////dlcm9sZXMAaHJ1bnRpbWVzgqRiaWRYIIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQZ3ZlcnNpb26hZXBhdGNoGQFBamV4dHJhX2luZm/2bGNhcGFiaWxpdGllc6CkYmlkWCCAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEWd2ZXJzaW9uoWVwYXRjaBh7amV4dHJhX2luZm9EBQMCAWxjYXBhYmlsaXRpZXOhY3RlZaRjcmFrWCD/////////////////////////////////////////+GNyZWtYIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAaGhhcmR3YXJlAWthdHRlc3RhdGlvbkYAAQIDBAVpY29uc2Vuc3VzomJpZFgg//////////////////////////////////////////ZpYWRkcmVzc2VzgGllbnRpdHlfaWRYIP/////////////////////////////////////////xamV4cGlyYXRpb24YIA==",
1308 Node{
1309 v: 2,
1310 id: signature::PublicKey::from("fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0"),
1311 entity_id: signature::PublicKey::from("fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff1"),
1312 expiration: 32,
1313 tls: TLSInfo{
1314 pub_key: signature::PublicKey::from("fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff2"),
1315 _deprecated_next_pub_key: Some(signature::PublicKey::from("fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff3")),
1316 _deprecated_addresses: Some(vec![TLSAddress{
1317 pub_key: signature::PublicKey::from("fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff4"),
1318 address: TCPAddress { ip: Ipv4Addr::new(127, 0, 0, 1).to_ipv6_mapped().octets().to_vec(), port: 123, ..Default::default() }
1319 }])
1320 },
1321 p2p: P2PInfo{
1322 id: signature::PublicKey::from("fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff5"),
1323 ..Default::default()
1324 },
1325 consensus: ConsensusInfo{
1326 id: signature::PublicKey::from("fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff6"),
1327 addresses: Some(Vec::new()),
1328 },
1329 vrf: VRFInfo{
1330 id: signature::PublicKey::from("fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7"),
1331 },
1332 runtimes: Some(vec![
1333 NodeRuntime{
1334 id: Namespace::from("8000000000000000000000000000000000000000000000000000000000000010"),
1335 version: Version::from(321u64),
1336 ..Default::default()
1337 },
1338 NodeRuntime{
1339 id: Namespace::from("8000000000000000000000000000000000000000000000000000000000000011"),
1340 version: Version::from(123),
1341 capabilities: Capabilities{
1342 tee: Some(CapabilityTEE{
1343 hardware: TEEHardware::TEEHardwareIntelSGX,
1344 rak: signature::PublicKey::from("fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8"),
1345 rek: Some(x25519::PublicKey::from([0;32])),
1346 attestation: vec![0, 1,2,3,4,5],
1347 }),
1348 },
1349 extra_info: Some(vec![5,3,2,1]),
1350 },
1351 ]),
1352 ..Default::default()
1353 },
1354 ),
1355 ];
1356 for (encoded_base64, node) in tcs {
1357 println!("{:?}", node);
1358 let dec: Node = cbor::from_slice(&BASE64_STANDARD.decode(encoded_base64).unwrap())
1359 .expect("node should deserialize correctly");
1360 assert_eq!(dec, node, "decoded node should match the expected value");
1361 }
1362 }
1363
1364 #[test]
1365 fn test_runtime_deployments() {
1366 let rt = Runtime::default();
1367 assert_eq!(rt.active_deployment(0), None);
1368
1369 let rt = Runtime {
1370 deployments: vec![
1371 VersionInfo {
1372 version: Version {
1373 major: 0,
1374 minor: 1,
1375 patch: 0,
1376 },
1377 valid_from: 0,
1378 ..Default::default()
1379 },
1380 VersionInfo {
1381 version: Version {
1382 major: 0,
1383 minor: 2,
1384 patch: 0,
1385 },
1386 valid_from: 10,
1387 ..Default::default()
1388 },
1389 VersionInfo {
1390 version: Version {
1391 major: 0,
1392 minor: 3,
1393 patch: 0,
1394 },
1395 valid_from: 20,
1396 ..Default::default()
1397 },
1398 ],
1399 ..Default::default()
1400 };
1401
1402 let ad = rt.active_deployment(0).unwrap();
1403 assert_eq!(ad.version.minor, 1);
1404 let ad = rt.active_deployment(1).unwrap();
1405 assert_eq!(ad.version.minor, 1);
1406 let ad = rt.active_deployment(9).unwrap();
1407 assert_eq!(ad.version.minor, 1);
1408 let ad = rt.active_deployment(10).unwrap();
1409 assert_eq!(ad.version.minor, 2);
1410 let ad = rt.active_deployment(20).unwrap();
1411 assert_eq!(ad.version.minor, 3);
1412 let ad = rt.active_deployment(50).unwrap();
1413 assert_eq!(ad.version.minor, 3);
1414 let ad = rt.active_deployment(100).unwrap();
1415 assert_eq!(ad.version.minor, 3);
1416 let ad = rt.active_deployment(1000).unwrap();
1417 assert_eq!(ad.version.minor, 3);
1418
1419 let ad = rt
1420 .deployment_for_version(Version {
1421 major: 0,
1422 minor: 1,
1423 patch: 0,
1424 })
1425 .unwrap();
1426 assert_eq!(ad.valid_from, 0);
1427 let ad = rt
1428 .deployment_for_version(Version {
1429 major: 0,
1430 minor: 2,
1431 patch: 0,
1432 })
1433 .unwrap();
1434 assert_eq!(ad.valid_from, 10);
1435 let ad = rt
1436 .deployment_for_version(Version {
1437 major: 0,
1438 minor: 3,
1439 patch: 0,
1440 })
1441 .unwrap();
1442 assert_eq!(ad.valid_from, 20);
1443 let ad = rt.deployment_for_version(Version {
1444 major: 0,
1445 minor: 99,
1446 patch: 0,
1447 });
1448 assert_eq!(ad, None);
1449 }
1450
1451 #[test]
1452 fn test_hash_attestation() {
1453 let report_data = b"foo bar";
1454 let node_id = signature::PublicKey::from(
1455 "47aadd91516ac548decdb436fde957992610facc09ba2f850da0fe1b2be96119",
1456 );
1457 let height = 42;
1458 let rek: [u8; x25519::PUBLIC_KEY_LENGTH] =
1459 "7992610facc09ba2f850da0fe1b2be9611947aadd91516ac548decdb436fde95"
1460 .from_hex::<Vec<u8>>()
1461 .unwrap()
1462 .try_into()
1463 .unwrap();
1464 let rek = x25519::PublicKey::from(rek);
1465
1466 let h = SGXAttestation::hash(report_data, &node_id, height, &rek);
1467 assert_eq!(
1468 h.to_hex::<String>(),
1469 "9a288bd33ba7a4c2eefdee68e4c08c1a34c369302ef8176a3bfdb4fedcec333e"
1470 );
1471 }
1472
1473 #[test]
1474 fn test_roles_mask() {
1475 let mask = RolesMask::ROLE_OBSERVER | RolesMask::ROLE_COMPUTE_WORKER;
1476 assert!(mask.contains(RolesMask::ROLE_OBSERVER));
1477 assert!(mask.contains(RolesMask::ROLE_COMPUTE_WORKER));
1478 assert!(!mask.contains(RolesMask::ROLE_KEY_MANAGER));
1479 assert!(!mask.contains(RolesMask::ROLE_VALIDATOR));
1480 assert!(!mask.contains(RolesMask::ROLE_STORAGE_RPC));
1481 assert!(!mask.is_single_role());
1482
1483 let mask = RolesMask::ROLE_OBSERVER;
1484 assert!(mask.is_single_role());
1485
1486 let node = Node {
1487 roles: RolesMask::ROLE_COMPUTE_WORKER | RolesMask::ROLE_VALIDATOR,
1488 ..Default::default()
1489 };
1490 assert!(node.has_roles(RolesMask::ROLE_COMPUTE_WORKER));
1491 assert!(node.has_roles(RolesMask::ROLE_VALIDATOR));
1492 assert!(node.has_roles(RolesMask::ROLE_COMPUTE_WORKER | RolesMask::ROLE_VALIDATOR));
1493 assert!(!node.has_roles(RolesMask::ROLE_KEY_MANAGER));
1494 assert!(!node.has_roles(RolesMask::ROLE_STORAGE_RPC));
1495 }
1496}