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(u32::MAX & !((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
930#[derive(Clone, Debug, Default, PartialEq, Eq, Hash, cbor::Encode, cbor::Decode)]
932pub struct RuntimeGenesis {
933 pub state_root: Hash,
936
937 pub round: u64,
939}
940
941#[cfg(test)]
942mod tests {
943 use std::{convert::TryInto, net::Ipv4Addr};
944
945 use base64::prelude::*;
946 use rustc_hex::{FromHex, ToHex};
947
948 use crate::common::quantity::Quantity;
949
950 use super::*;
951
952 macro_rules! btreemap {
954 ( $($key:expr => $value:expr,)+ ) => (btreemap!($($key => $value),+));
956 ( $($key:expr => $value:expr),* ) => {
957 {
958 let mut m = BTreeMap::new();
959 $( m.insert($key.into(), $value); )*
960 m
961 }
962 };
963}
964
965 #[test]
966 fn test_consistent_runtime() {
967 let tcs = vec![
969 ("q2F2AGJpZFggAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABka2luZABnZ2VuZXNpc6Jlcm91bmQAanN0YXRlX3Jvb3RYIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAZ3N0b3JhZ2Wjc2NoZWNrcG9pbnRfaW50ZXJ2YWwAc2NoZWNrcG9pbnRfbnVtX2tlcHQAdWNoZWNrcG9pbnRfY2h1bmtfc2l6ZQBoZXhlY3V0b3Klamdyb3VwX3NpemUAbG1heF9tZXNzYWdlcwBtcm91bmRfdGltZW91dABxZ3JvdXBfYmFja3VwX3NpemUAcmFsbG93ZWRfc3RyYWdnbGVycwBpZW50aXR5X2lkWCAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGx0ZWVfaGFyZHdhcmUAbXR4bl9zY2hlZHVsZXKgcGFkbWlzc2lvbl9wb2xpY3mhaGFueV9ub2RloHBnb3Zlcm5hbmNlX21vZGVsAA==", Runtime {
971 admission_policy: RuntimeAdmissionPolicy {
972 any_node: Some(AnyNodeRuntimeAdmissionPolicy {}),
973 ..Default::default()
974 },
975 ..Default::default()
976 }),
977 (
979 "q2F2AGJpZFggAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABka2luZABnZ2VuZXNpc6Jlcm91bmQAanN0YXRlX3Jvb3RYIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAZ3N0b3JhZ2Wjc2NoZWNrcG9pbnRfaW50ZXJ2YWwAc2NoZWNrcG9pbnRfbnVtX2tlcHQAdWNoZWNrcG9pbnRfY2h1bmtfc2l6ZQBoZXhlY3V0b3Klamdyb3VwX3NpemUAbG1heF9tZXNzYWdlcwBtcm91bmRfdGltZW91dABxZ3JvdXBfYmFja3VwX3NpemUAcmFsbG93ZWRfc3RyYWdnbGVycwBpZW50aXR5X2lkWCAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGx0ZWVfaGFyZHdhcmUAbXR4bl9zY2hlZHVsZXKgcGFkbWlzc2lvbl9wb2xpY3mhaGFueV9ub2RloHBnb3Zlcm5hbmNlX21vZGVsAA==",
980 Runtime {
981 admission_policy: RuntimeAdmissionPolicy {
982 any_node: Some(AnyNodeRuntimeAdmissionPolicy {}),
983 ..Default::default()
984 },
985 staking: RuntimeStakingParameters {
986 thresholds: BTreeMap::new(),
987 slashing: BTreeMap::new(),
988 reward_equivocation: 0,
989 reward_bad_results: 0,
990 min_in_message_fee: Quantity::from(0u32),
991 },
992 ..Default::default()
993 },
994 ),
995 (
997 "rGF2AGJpZFggAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABka2luZABnZ2VuZXNpc6Jlcm91bmQAanN0YXRlX3Jvb3RYIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAZ3N0YWtpbmehcnJld2FyZF9iYWRfcmVzdWx0cwpnc3RvcmFnZaNzY2hlY2twb2ludF9pbnRlcnZhbABzY2hlY2twb2ludF9udW1fa2VwdAB1Y2hlY2twb2ludF9jaHVua19zaXplAGhleGVjdXRvcqVqZ3JvdXBfc2l6ZQBsbWF4X21lc3NhZ2VzAG1yb3VuZF90aW1lb3V0AHFncm91cF9iYWNrdXBfc2l6ZQByYWxsb3dlZF9zdHJhZ2dsZXJzAGllbnRpdHlfaWRYIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAbHRlZV9oYXJkd2FyZQBtdHhuX3NjaGVkdWxlcqBwYWRtaXNzaW9uX3BvbGljeaFoYW55X25vZGWgcGdvdmVybmFuY2VfbW9kZWwA",
998 Runtime {
999 admission_policy: RuntimeAdmissionPolicy {
1000 any_node: Some(AnyNodeRuntimeAdmissionPolicy {}),
1001 ..Default::default()
1002 },
1003 staking: RuntimeStakingParameters {
1004 thresholds: BTreeMap::new(),
1005 slashing: BTreeMap::new(),
1006 reward_equivocation: 0,
1007 reward_bad_results: 10,
1008 min_in_message_fee: Quantity::from(0u32),
1009 },
1010 ..Default::default()
1011 },
1012 ),
1013 (
1014 "r2F2GCpiaWRYIIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAZGtpbmQCZ2dlbmVzaXOiZXJvdW5kGCtqc3RhdGVfcm9vdFggseUhAZ+3vd413IH+55BlYQy937jvXCXihJg2aBkqbQ1nc3Rha2luZ6FycmV3YXJkX2JhZF9yZXN1bHRzCmdzdG9yYWdlo3NjaGVja3BvaW50X2ludGVydmFsGCFzY2hlY2twb2ludF9udW1fa2VwdAZ1Y2hlY2twb2ludF9jaHVua19zaXplGGVoZXhlY3V0b3Kpamdyb3VwX3NpemUJbG1heF9tZXNzYWdlcwVtcm91bmRfdGltZW91dAZxZ3JvdXBfYmFja3VwX3NpemUIcmFsbG93ZWRfc3RyYWdnbGVycwdybWF4X2xpdmVuZXNzX2ZhaWxzAXRtaW5fbGl2ZV9yb3VuZHNfZXZhbAJ3bWluX2xpdmVfcm91bmRzX3BlcmNlbnQEeBxtYXhfbWlzc2VkX3Byb3Bvc2Fsc19wZXJjZW50A2llbnRpdHlfaWRYIBI0VniQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAa2NvbnN0cmFpbnRzoQGhAaNpbWF4X25vZGVzoWVsaW1pdAptbWluX3Bvb2xfc2l6ZaFlbGltaXQFbXZhbGlkYXRvcl9zZXSga2RlcGxveW1lbnRzgaRjdGVlS3ZlcnNpb24gdGVlZ3ZlcnNpb26iZW1ham9yGCxlcGF0Y2gBanZhbGlkX2Zyb20Ab2J1bmRsZV9jaGVja3N1bVggAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQFra2V5X21hbmFnZXJYIIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABbHRlZV9oYXJkd2FyZQFtdHhuX3NjaGVkdWxlcqVubWF4X2JhdGNoX3NpemUZJxBvbWF4X2luX21lc3NhZ2VzGCBzYmF0Y2hfZmx1c2hfdGltZW91dBo7msoAdG1heF9iYXRjaF9zaXplX2J5dGVzGgCYloB1cHJvcG9zZV9iYXRjaF90aW1lb3V0Gnc1lABwYWRtaXNzaW9uX3BvbGljeaFwZW50aXR5X3doaXRlbGlzdKFoZW50aXRpZXOhWCASNFZ4kAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKFpbWF4X25vZGVzogEDBAFwZ292ZXJuYW5jZV9tb2RlbAM=",
1015 Runtime {
1016 v: 42,
1017 id: Namespace::from(
1018 "8000000000000000000000000000000000000000000000000000000000000000",
1019 ),
1020 entity_id: signature::PublicKey::from(
1021 "1234567890000000000000000000000000000000000000000000000000000000",
1022 ),
1023 genesis: RuntimeGenesis {
1024 round: 43,
1025 state_root: Hash::digest_bytes(b"stateroot hash"),
1026 },
1027 kind: RuntimeKind::KindKeyManager,
1028 tee_hardware: TEEHardware::TEEHardwareIntelSGX,
1029 deployments: vec![VersionInfo {
1030 version: Version {
1031 major: 44,
1032 minor: 0,
1033 patch: 1,
1034 },
1035 valid_from: 0,
1036 tee: b"version tee".to_vec(),
1037 bundle_checksum: vec![0x1; 32],
1038 }],
1039 key_manager: Some(Namespace::from(
1040 "8000000000000000000000000000000000000000000000000000000000000001",
1041 )),
1042 executor: ExecutorParameters {
1043 group_size: 9,
1044 group_backup_size: 8,
1045 allowed_stragglers: 7,
1046 round_timeout: 6,
1047 max_messages: 5,
1048 min_live_rounds_percent: 4,
1049 max_missed_proposals_percent: 3,
1050 min_live_rounds_eval: 2,
1051 max_liveness_fails: 1,
1052 },
1053 txn_scheduler: TxnSchedulerParameters {
1054 batch_flush_timeout: 1_000_000_000, max_batch_size: 10_000,
1056 max_batch_size_bytes: 10_000_000,
1057 max_in_messages: 32,
1058 propose_batch_timeout: 2_000_000_000, },
1060 storage: StorageParameters {
1061 checkpoint_interval: 33,
1062 checkpoint_num_kept: 6,
1063 checkpoint_chunk_size: 101,
1064 },
1065 admission_policy: RuntimeAdmissionPolicy {
1066 entity_whitelist: Some(EntityWhitelistRuntimeAdmissionPolicy {
1067 entities: btreemap! {
1068 signature::PublicKey::from("1234567890000000000000000000000000000000000000000000000000000000") => EntityWhitelistConfig {
1069 max_nodes: btreemap! {
1070 RolesMask::ROLE_COMPUTE_WORKER => 3,
1071 RolesMask::ROLE_KEY_MANAGER => 1,
1072 }
1073 }
1074 },
1075 }),
1076 ..Default::default()
1077 },
1078 constraints: btreemap! {
1079 scheduler::CommitteeKind::ComputeExecutor => btreemap! {
1080 scheduler::Role::Worker => SchedulingConstraints{
1081 max_nodes: Some(
1082 MaxNodesConstraint{
1083 limit: 10,
1084 }
1085 ),
1086 min_pool_size: Some(
1087 MinPoolSizeConstraint {
1088 limit: 5,
1089 }
1090 ),
1091 validator_set: Some(ValidatorSetConstraint{}),
1092 },
1093 }
1094 },
1095 staking: RuntimeStakingParameters {
1096 thresholds: BTreeMap::new(),
1097 slashing: BTreeMap::new(),
1098 reward_equivocation: 0,
1099 reward_bad_results: 10,
1100 min_in_message_fee: Quantity::from(0u32),
1101 },
1102 governance_model: RuntimeGovernanceModel::GovernanceConsensus,
1103 },
1104 ),
1105 ];
1106 for (encoded_base64, rr) in tcs {
1107 let dec: Runtime = cbor::from_slice(&BASE64_STANDARD.decode(encoded_base64).unwrap())
1108 .expect("runtime should deserialize correctly");
1109 assert_eq!(dec, rr, "decoded runtime should match the expected value");
1110 let ser = BASE64_STANDARD.encode(cbor::to_vec(dec));
1111 assert_eq!(ser, encoded_base64, "runtime should serialize correctly");
1112 }
1113 }
1114
1115 #[test]
1116 fn test_consistent_node() {
1117 let tcs = vec![
1119 (
1120 "qmF2A2JpZFggAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABjcDJwomJpZFggAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABpYWRkcmVzc2Vz9mN0bHOhZ3B1Yl9rZXlYIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAY3ZyZqFiaWRYIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAZXJvbGVzAGhydW50aW1lc/ZpY29uc2Vuc3VzomJpZFggAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABpYWRkcmVzc2Vz9mllbnRpdHlfaWRYIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAamV4cGlyYXRpb24A",
1121 Node{v: 3, ..Default::default()},
1122 true,
1123 ),
1124 (
1125 "qmF2A2JpZFgg//////////////////////////////////////////BjcDJwomJpZFgg//////////////////////////////////////////VpYWRkcmVzc2Vz9mN0bHOhZ3B1Yl9rZXlYIP/////////////////////////////////////////yY3ZyZqFiaWRYIP/////////////////////////////////////////3ZXJvbGVzAGhydW50aW1lc4KkYmlkWCCAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEGd2ZXJzaW9uoWVwYXRjaBkBQWpleHRyYV9pbmZv9mxjYXBhYmlsaXRpZXOgpGJpZFgggAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABFndmVyc2lvbqFlcGF0Y2gYe2pleHRyYV9pbmZvRAUDAgFsY2FwYWJpbGl0aWVzoWN0ZWWjY3Jha1gg//////////////////////////////////////////hoaGFyZHdhcmUBa2F0dGVzdGF0aW9uRgABAgMEBWljb25zZW5zdXOiYmlkWCD/////////////////////////////////////////9mlhZGRyZXNzZXOAaWVudGl0eV9pZFgg//////////////////////////////////////////FqZXhwaXJhdGlvbhgg",
1126 Node{
1127 v: 3,
1128 id: signature::PublicKey::from("fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0"),
1129 entity_id: signature::PublicKey::from("fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff1"),
1130 expiration: 32,
1131 tls: TLSInfo{
1132 pub_key: signature::PublicKey::from("fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff2"),
1133 ..Default::default()
1134 },
1135 p2p: P2PInfo{
1136 id: signature::PublicKey::from("fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff5"),
1137 ..Default::default()
1138 },
1139 consensus: ConsensusInfo{
1140 id: signature::PublicKey::from("fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff6"),
1141 addresses: Some(Vec::new()),
1142 },
1143 vrf: VRFInfo{
1144 id: signature::PublicKey::from("fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7"),
1145 },
1146 runtimes: Some(vec![
1147 NodeRuntime{
1148 id: Namespace::from("8000000000000000000000000000000000000000000000000000000000000010"),
1149 version: Version::from(321u64),
1150 ..Default::default()
1151 },
1152 NodeRuntime{
1153 id: Namespace::from("8000000000000000000000000000000000000000000000000000000000000011"),
1154 version: Version::from(123),
1155 capabilities: Capabilities{
1156 tee: Some(CapabilityTEE{
1157 hardware: TEEHardware::TEEHardwareIntelSGX,
1158 rak: signature::PublicKey::from("fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8"),
1159 attestation: vec![0, 1,2,3,4,5],
1160 ..Default::default()
1161 }),
1162 },
1163 extra_info: Some(vec![5,3,2,1]),
1164 },
1165 ]),
1166 ..Default::default()
1167 },
1168 true,
1169 ),
1170 (
1171 "qWF2A2JpZFggAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABjcDJwomJpZFggAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABpYWRkcmVzc2Vz9mN0bHOhZ3B1Yl9rZXlYIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAZXJvbGVzAGhydW50aW1lc4GkYmlkWCCAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEGd2ZXJzaW9uoGpleHRyYV9pbmZv9mxjYXBhYmlsaXRpZXOgaWNvbnNlbnN1c6JiaWRYIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAaWFkZHJlc3Nlc/ZpZW50aXR5X2lkWCAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGpleHBpcmF0aW9uAA==",
1172 Node{
1173 v: 3,
1174 runtimes: Some(vec![
1175 NodeRuntime{
1176 id: Namespace::from("8000000000000000000000000000000000000000000000000000000000000010"),
1177 version: Version::from(0u64),
1178 ..Default::default()
1179 },
1180 ]),
1181 ..Default::default()
1182 },
1183 false,
1184 ),
1185 ];
1186 for (encoded_base64, node, round_trip) in tcs {
1187 println!("{:?}", node);
1188 let dec: Node = cbor::from_slice(&BASE64_STANDARD.decode(encoded_base64).unwrap())
1189 .expect("node should deserialize correctly");
1190 assert_eq!(dec, node, "decoded node should match the expected value");
1191
1192 if round_trip {
1193 let ser = BASE64_STANDARD.encode(cbor::to_vec(dec));
1194 assert_eq!(ser, encoded_base64, "node should serialize correctly");
1195 }
1196 }
1197 }
1198
1199 #[test]
1200 fn test_deserialize_node_v2() {
1201 let tcs = vec![
1203 (
1204 "qWF2AmJpZFggAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABjcDJwomJpZFggAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABpYWRkcmVzc2Vz9mN0bHOiZ3B1Yl9rZXlYIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAaWFkZHJlc3Nlc/Zlcm9sZXMAaHJ1bnRpbWVz9mljb25zZW5zdXOiYmlkWCAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGlhZGRyZXNzZXP2aWVudGl0eV9pZFggAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABqZXhwaXJhdGlvbgA=",
1205 Node{v: 2, ..Default::default()},
1206 ),
1207 (
1208 "qmF2AmJpZFggAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABjcDJwomJpZFggAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABpYWRkcmVzc2Vz9mN0bHOjZ3B1Yl9rZXlYIP/////////////////////////////////////////yaWFkZHJlc3Nlc4BsbmV4dF9wdWJfa2V5WCD/////////////////////////////////////////82N2cmahYmlkWCAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGVyb2xlcwBocnVudGltZXP2aWNvbnNlbnN1c6JiaWRYIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAaWFkZHJlc3Nlc/ZpZW50aXR5X2lkWCAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGpleHBpcmF0aW9uAA==",
1209 Node{
1210 v: 2,
1211 tls: TLSInfo{
1212 pub_key: signature::PublicKey::from("fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff2"),
1213 _deprecated_next_pub_key: Some(signature::PublicKey::from("fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff3")),
1214 _deprecated_addresses: Some(vec![]),
1215 },
1216 ..Default::default()
1217 },
1218 ),
1219 (
1220 "qmF2AmJpZFggAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABjcDJwomJpZFggAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABpYWRkcmVzc2Vz9mN0bHOjZ3B1Yl9rZXlYIP/////////////////////////////////////////yaWFkZHJlc3Nlc4OiZ2FkZHJlc3OjYklQUAAAAAAAAAAAAAD//38AAAFkUG9ydBh7ZFpvbmVgZ3B1Yl9rZXlYIP/////////////////////////////////////////0omdhZGRyZXNzo2JJUFAAAAAAAAAAAAAA///AqAEBZFBvcnQZD6BkWm9uZWBncHViX2tleVgg/////////////////////////////////////////8SiZ2FkZHJlc3OjYklQUAAAAAAAAAAAAAD//+pkY1hkUG9ydBkfQGRab25lYGdwdWJfa2V5WCD/////////////////////////////////////////1GxuZXh0X3B1Yl9rZXlYIP/////////////////////////////////////////zY3ZyZqFiaWRYIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAZXJvbGVzAGhydW50aW1lc/ZpY29uc2Vuc3VzomJpZFggAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABpYWRkcmVzc2Vz9mllbnRpdHlfaWRYIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAamV4cGlyYXRpb24A",
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 TLSAddress{
1228 pub_key: signature::PublicKey::from("fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff4"),
1229 address: TCPAddress { ip: Ipv4Addr::new(127, 0, 0, 1).to_ipv6_mapped().octets().to_vec(), port: 123, ..Default::default() }
1230 },
1231 TLSAddress{
1232 pub_key: signature::PublicKey::from("ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc4"),
1233 address: TCPAddress { ip: Ipv4Addr::new(192, 168, 1, 1).to_ipv6_mapped().octets().to_vec(), port: 4000, ..Default::default() }
1234 },
1235 TLSAddress{
1236 pub_key: signature::PublicKey::from("ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffd4"),
1237 address: TCPAddress { ip: Ipv4Addr::new(234, 100, 99, 88).to_ipv6_mapped().octets().to_vec(), port: 8000, ..Default::default() }
1238 },
1239
1240 ])
1241 },
1242 ..Default::default()
1243 },
1244 ),
1245 (
1246 "qmF2AmJpZFgg//////////////////////////////////////////BjcDJwomJpZFgg//////////////////////////////////////////VpYWRkcmVzc2Vz9mN0bHOjZ3B1Yl9rZXlYIP/////////////////////////////////////////yaWFkZHJlc3Nlc4GiZ2FkZHJlc3OjYklQUAAAAAAAAAAAAAD//38AAAFkUG9ydBh7ZFpvbmVgZ3B1Yl9rZXlYIP/////////////////////////////////////////0bG5leHRfcHViX2tleVgg//////////////////////////////////////////NjdnJmoWJpZFgg//////////////////////////////////////////dlcm9sZXMAaHJ1bnRpbWVzgqRiaWRYIIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQZ3ZlcnNpb26hZXBhdGNoGQFBamV4dHJhX2luZm/2bGNhcGFiaWxpdGllc6CkYmlkWCCAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEWd2ZXJzaW9uoWVwYXRjaBh7amV4dHJhX2luZm9EBQMCAWxjYXBhYmlsaXRpZXOhY3RlZaNjcmFrWCD/////////////////////////////////////////+GhoYXJkd2FyZQFrYXR0ZXN0YXRpb25GAAECAwQFaWNvbnNlbnN1c6JiaWRYIP/////////////////////////////////////////2aWFkZHJlc3Nlc4BpZW50aXR5X2lkWCD/////////////////////////////////////////8WpleHBpcmF0aW9uGCA=",
1247 Node{
1248 v: 2,
1249 id: signature::PublicKey::from("fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0"),
1250 entity_id: signature::PublicKey::from("fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff1"),
1251 expiration: 32,
1252 tls: TLSInfo{
1253 pub_key: signature::PublicKey::from("fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff2"),
1254 _deprecated_next_pub_key: Some(signature::PublicKey::from("fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff3")),
1255 _deprecated_addresses: Some(vec![TLSAddress{
1256 pub_key: signature::PublicKey::from("fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff4"),
1257 address: TCPAddress { ip: Ipv4Addr::new(127, 0, 0, 1).to_ipv6_mapped().octets().to_vec(), port: 123, ..Default::default() }
1258 }])
1259 },
1260 p2p: P2PInfo{
1261 id: signature::PublicKey::from("fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff5"),
1262 ..Default::default()
1263 },
1264 consensus: ConsensusInfo{
1265 id: signature::PublicKey::from("fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff6"),
1266 addresses: Some(Vec::new()),
1267 },
1268 vrf: VRFInfo{
1269 id: signature::PublicKey::from("fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7"),
1270 },
1271 runtimes: Some(vec![
1272 NodeRuntime{
1273 id: Namespace::from("8000000000000000000000000000000000000000000000000000000000000010"),
1274 version: Version::from(321u64),
1275 ..Default::default()
1276 },
1277 NodeRuntime{
1278 id: Namespace::from("8000000000000000000000000000000000000000000000000000000000000011"),
1279 version: Version::from(123),
1280 capabilities: Capabilities{
1281 tee: Some(CapabilityTEE{
1282 hardware: TEEHardware::TEEHardwareIntelSGX,
1283 rak: signature::PublicKey::from("fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8"),
1284 attestation: vec![0, 1,2,3,4,5],
1285 ..Default::default()
1286 }),
1287 },
1288 extra_info: Some(vec![5,3,2,1]),
1289 },
1290 ]),
1291 ..Default::default()
1292 },
1293 ),
1294 (
1295 "qmF2AmJpZFgg//////////////////////////////////////////BjcDJwomJpZFgg//////////////////////////////////////////VpYWRkcmVzc2Vz9mN0bHOjZ3B1Yl9rZXlYIP/////////////////////////////////////////yaWFkZHJlc3Nlc4GiZ2FkZHJlc3OjYklQUAAAAAAAAAAAAAD//38AAAFkUG9ydBh7ZFpvbmVgZ3B1Yl9rZXlYIP/////////////////////////////////////////0bG5leHRfcHViX2tleVgg//////////////////////////////////////////NjdnJmoWJpZFgg//////////////////////////////////////////dlcm9sZXMAaHJ1bnRpbWVzgqRiaWRYIIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQZ3ZlcnNpb26hZXBhdGNoGQFBamV4dHJhX2luZm/2bGNhcGFiaWxpdGllc6CkYmlkWCCAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEWd2ZXJzaW9uoWVwYXRjaBh7amV4dHJhX2luZm9EBQMCAWxjYXBhYmlsaXRpZXOhY3RlZaRjcmFrWCD/////////////////////////////////////////+GNyZWtYIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAaGhhcmR3YXJlAWthdHRlc3RhdGlvbkYAAQIDBAVpY29uc2Vuc3VzomJpZFgg//////////////////////////////////////////ZpYWRkcmVzc2VzgGllbnRpdHlfaWRYIP/////////////////////////////////////////xamV4cGlyYXRpb24YIA==",
1296 Node{
1297 v: 2,
1298 id: signature::PublicKey::from("fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0"),
1299 entity_id: signature::PublicKey::from("fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff1"),
1300 expiration: 32,
1301 tls: TLSInfo{
1302 pub_key: signature::PublicKey::from("fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff2"),
1303 _deprecated_next_pub_key: Some(signature::PublicKey::from("fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff3")),
1304 _deprecated_addresses: Some(vec![TLSAddress{
1305 pub_key: signature::PublicKey::from("fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff4"),
1306 address: TCPAddress { ip: Ipv4Addr::new(127, 0, 0, 1).to_ipv6_mapped().octets().to_vec(), port: 123, ..Default::default() }
1307 }])
1308 },
1309 p2p: P2PInfo{
1310 id: signature::PublicKey::from("fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff5"),
1311 ..Default::default()
1312 },
1313 consensus: ConsensusInfo{
1314 id: signature::PublicKey::from("fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff6"),
1315 addresses: Some(Vec::new()),
1316 },
1317 vrf: VRFInfo{
1318 id: signature::PublicKey::from("fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7"),
1319 },
1320 runtimes: Some(vec![
1321 NodeRuntime{
1322 id: Namespace::from("8000000000000000000000000000000000000000000000000000000000000010"),
1323 version: Version::from(321u64),
1324 ..Default::default()
1325 },
1326 NodeRuntime{
1327 id: Namespace::from("8000000000000000000000000000000000000000000000000000000000000011"),
1328 version: Version::from(123),
1329 capabilities: Capabilities{
1330 tee: Some(CapabilityTEE{
1331 hardware: TEEHardware::TEEHardwareIntelSGX,
1332 rak: signature::PublicKey::from("fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8"),
1333 rek: Some(x25519::PublicKey::from([0;32])),
1334 attestation: vec![0, 1,2,3,4,5],
1335 }),
1336 },
1337 extra_info: Some(vec![5,3,2,1]),
1338 },
1339 ]),
1340 ..Default::default()
1341 },
1342 ),
1343 ];
1344 for (encoded_base64, node) in tcs {
1345 println!("{:?}", node);
1346 let dec: Node = cbor::from_slice(&BASE64_STANDARD.decode(encoded_base64).unwrap())
1347 .expect("node should deserialize correctly");
1348 assert_eq!(dec, node, "decoded node should match the expected value");
1349 }
1350 }
1351
1352 #[test]
1353 fn test_runtime_deployments() {
1354 let rt = Runtime::default();
1355 assert_eq!(rt.active_deployment(0), None);
1356
1357 let rt = Runtime {
1358 deployments: vec![
1359 VersionInfo {
1360 version: Version {
1361 major: 0,
1362 minor: 1,
1363 patch: 0,
1364 },
1365 valid_from: 0,
1366 ..Default::default()
1367 },
1368 VersionInfo {
1369 version: Version {
1370 major: 0,
1371 minor: 2,
1372 patch: 0,
1373 },
1374 valid_from: 10,
1375 ..Default::default()
1376 },
1377 VersionInfo {
1378 version: Version {
1379 major: 0,
1380 minor: 3,
1381 patch: 0,
1382 },
1383 valid_from: 20,
1384 ..Default::default()
1385 },
1386 ],
1387 ..Default::default()
1388 };
1389
1390 let ad = rt.active_deployment(0).unwrap();
1391 assert_eq!(ad.version.minor, 1);
1392 let ad = rt.active_deployment(1).unwrap();
1393 assert_eq!(ad.version.minor, 1);
1394 let ad = rt.active_deployment(9).unwrap();
1395 assert_eq!(ad.version.minor, 1);
1396 let ad = rt.active_deployment(10).unwrap();
1397 assert_eq!(ad.version.minor, 2);
1398 let ad = rt.active_deployment(20).unwrap();
1399 assert_eq!(ad.version.minor, 3);
1400 let ad = rt.active_deployment(50).unwrap();
1401 assert_eq!(ad.version.minor, 3);
1402 let ad = rt.active_deployment(100).unwrap();
1403 assert_eq!(ad.version.minor, 3);
1404 let ad = rt.active_deployment(1000).unwrap();
1405 assert_eq!(ad.version.minor, 3);
1406
1407 let ad = rt
1408 .deployment_for_version(Version {
1409 major: 0,
1410 minor: 1,
1411 patch: 0,
1412 })
1413 .unwrap();
1414 assert_eq!(ad.valid_from, 0);
1415 let ad = rt
1416 .deployment_for_version(Version {
1417 major: 0,
1418 minor: 2,
1419 patch: 0,
1420 })
1421 .unwrap();
1422 assert_eq!(ad.valid_from, 10);
1423 let ad = rt
1424 .deployment_for_version(Version {
1425 major: 0,
1426 minor: 3,
1427 patch: 0,
1428 })
1429 .unwrap();
1430 assert_eq!(ad.valid_from, 20);
1431 let ad = rt.deployment_for_version(Version {
1432 major: 0,
1433 minor: 99,
1434 patch: 0,
1435 });
1436 assert_eq!(ad, None);
1437 }
1438
1439 #[test]
1440 fn test_hash_attestation() {
1441 let report_data = b"foo bar";
1442 let node_id = signature::PublicKey::from(
1443 "47aadd91516ac548decdb436fde957992610facc09ba2f850da0fe1b2be96119",
1444 );
1445 let height = 42;
1446 let rek: [u8; x25519::PUBLIC_KEY_LENGTH] =
1447 "7992610facc09ba2f850da0fe1b2be9611947aadd91516ac548decdb436fde95"
1448 .from_hex::<Vec<u8>>()
1449 .unwrap()
1450 .try_into()
1451 .unwrap();
1452 let rek = x25519::PublicKey::from(rek);
1453
1454 let h = SGXAttestation::hash(report_data, &node_id, height, &rek);
1455 assert_eq!(
1456 h.to_hex::<String>(),
1457 "9a288bd33ba7a4c2eefdee68e4c08c1a34c369302ef8176a3bfdb4fedcec333e"
1458 );
1459 }
1460
1461 #[test]
1462 fn test_roles_mask() {
1463 let mask = RolesMask::ROLE_OBSERVER | RolesMask::ROLE_COMPUTE_WORKER;
1464 assert!(mask.contains(RolesMask::ROLE_OBSERVER));
1465 assert!(mask.contains(RolesMask::ROLE_COMPUTE_WORKER));
1466 assert!(!mask.contains(RolesMask::ROLE_KEY_MANAGER));
1467 assert!(!mask.contains(RolesMask::ROLE_VALIDATOR));
1468 assert!(!mask.contains(RolesMask::ROLE_STORAGE_RPC));
1469 assert!(!mask.is_single_role());
1470
1471 let mask = RolesMask::ROLE_OBSERVER;
1472 assert!(mask.is_single_role());
1473
1474 let node = Node {
1475 roles: RolesMask::ROLE_COMPUTE_WORKER | RolesMask::ROLE_VALIDATOR,
1476 ..Default::default()
1477 };
1478 assert!(node.has_roles(RolesMask::ROLE_COMPUTE_WORKER));
1479 assert!(node.has_roles(RolesMask::ROLE_VALIDATOR));
1480 assert!(node.has_roles(RolesMask::ROLE_COMPUTE_WORKER | RolesMask::ROLE_VALIDATOR));
1481 assert!(!node.has_roles(RolesMask::ROLE_KEY_MANAGER));
1482 assert!(!node.has_roles(RolesMask::ROLE_STORAGE_RPC));
1483 }
1484}