oasis_core_runtime/consensus/
registry.rs

1//! Registry structures.
2//!
3//! # Note
4//!
5//! This **MUST** be kept in sync with go/registry/api.
6//!
7use 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
28/// A unique module name for the registry module.
29pub const MODULE_NAME: &str = "registry";
30
31/// The method name for freshness proofs.
32pub const METHOD_PROVE_FRESHNESS: &str = "registry.ProveFreshness";
33
34/// Attestation signature context.
35pub const ATTESTATION_SIGNATURE_CONTEXT: &[u8] = b"oasis-core/node: TEE attestation signature";
36
37/// TEE capability endorsement signature context.
38pub const ENDORSE_CAPABILITY_TEE_SIGNATURE_CONTEXT: &[u8] =
39    b"oasis-core/node: endorse TEE capability";
40
41/// Represents the address of a TCP endpoint.
42#[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/// Represents an Oasis committee address that includes a TLS public key and a TCP address.
53///
54/// NOTE: The address TLS public key can be different from the actual node TLS public key to allow
55/// using a sentry node's addresses.
56#[derive(Clone, Debug, Default, PartialEq, Eq, Hash, cbor::Encode, cbor::Decode)]
57pub struct TLSAddress {
58    /// Public key used for establishing TLS connections.
59    pub pub_key: signature::PublicKey,
60
61    /// Address at which the node can be reached.
62    pub address: TCPAddress,
63}
64
65/// Node's TLS information.
66#[derive(Clone, Debug, Default, PartialEq, Eq, Hash, cbor::Encode, cbor::Decode)]
67pub struct TLSInfo {
68    /// Public key used for establishing TLS connections.
69    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/// Node's P2P information.
79#[derive(Clone, Debug, Default, PartialEq, Eq, Hash, cbor::Encode, cbor::Decode)]
80pub struct P2PInfo {
81    /// Unique identifier of the node on the P2P transport.
82    pub id: signature::PublicKey,
83
84    /// List of addresses at which the node can be reached.
85    pub addresses: Option<Vec<TCPAddress>>,
86}
87
88/// Represents a consensus address that includes an ID and a TCP address.
89///
90/// NOTE: The consensus address ID could be different from the consensus ID
91/// to allow using a sentry node's ID and address instead of the validator's.
92#[derive(Clone, Debug, Default, PartialEq, Eq, Hash, cbor::Encode, cbor::Decode)]
93pub struct ConsensusAddress {
94    /// Public key identifying the node.
95    pub id: signature::PublicKey,
96
97    /// Address at which the node can be reached.
98    pub address: TCPAddress,
99}
100
101/// Node's consensus member information.
102#[derive(Clone, Debug, Default, PartialEq, Eq, Hash, cbor::Encode, cbor::Decode)]
103pub struct ConsensusInfo {
104    /// Unique identifier of the node as a consensus member.
105    pub id: signature::PublicKey,
106
107    /// List of addresses at which the node can be reached.
108    pub addresses: Option<Vec<ConsensusAddress>>,
109}
110
111/// Contains information for this node's participation in VRF based elections.
112#[derive(Clone, Debug, Default, PartialEq, Eq, Hash, cbor::Encode, cbor::Decode)]
113pub struct VRFInfo {
114    /// Unique identifier of the node used to generate VRF proofs.
115    pub id: signature::PublicKey,
116}
117
118/// Represents the node's TEE capability.
119#[derive(Clone, Debug, Default, PartialEq, Eq, Hash, cbor::Encode, cbor::Decode)]
120pub struct CapabilityTEE {
121    /// Hardware type.
122    pub hardware: TEEHardware,
123
124    /// Runtime attestation key.
125    pub rak: signature::PublicKey,
126
127    /// Runtime encryption key.
128    #[cbor(optional)]
129    pub rek: Option<x25519::PublicKey>,
130
131    /// Attestation.
132    pub attestation: Vec<u8>,
133}
134
135impl CapabilityTEE {
136    /// Tries to decode the TEE-specific attestation.
137    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    /// Checks whether the TEE capability matches the given TEE identity.
145    pub fn matches(&self, identity: &Identity) -> bool {
146        match self.hardware {
147            TEEHardware::TEEHardwareInvalid => false,
148            TEEHardware::TEEHardwareIntelSGX => {
149                // Decode SGX attestation and check quote equality.
150                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    /// Verifies the TEE capability.
160    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                // Decode SGX attestation and verify it.
169                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/// An endorsed CapabilityTEE structure.
182///
183///
184/// Endorsement is needed for off-chain runtime components where their RAK is not published in the
185/// consensus layer and verification is part of the runtime itself. Via endorsement one can enforce
186/// policies like "only components executed by the current compute committee are authorized".
187#[derive(Clone, Debug, Default, PartialEq, Eq, Hash, cbor::Encode, cbor::Decode)]
188pub struct EndorsedCapabilityTEE {
189    /// TEE capability structure to be endorsed.
190    pub capability_tee: CapabilityTEE,
191
192    /// Node endorsement signature.
193    pub node_endorsement: signature::SignatureBundle,
194}
195
196impl EndorsedCapabilityTEE {
197    /// Verify the endorsement signature is valid.
198    ///
199    /// **This does not verify the TEE capability itself, use `verify` for that.**
200    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    /// Verify endorsed TEE capability is valid.
211    pub fn verify(
212        &self,
213        policy: &sgx::QuotePolicy,
214    ) -> anyhow::Result<VerifiedEndorsedCapabilityTEE> {
215        // Verify node endorsement.
216        self.verify_endorsement()?;
217
218        // Verify TEE capability.
219        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/// A verified endorsed CapabilityTEE structure.
231#[derive(Clone, Debug, Default)]
232pub struct VerifiedEndorsedCapabilityTEE {
233    /// Verified TEE remote attestation.
234    pub verified_attestation: VerifiedAttestation,
235    /// Optional identifier of the node that endorsed the TEE capability.
236    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/// Represents a node's capabilities.
258#[derive(Clone, Debug, Default, PartialEq, Eq, Hash, cbor::Encode, cbor::Decode)]
259pub struct Capabilities {
260    /// Is the capability of a node executing batches in a TEE.
261    #[cbor(optional)]
262    pub tee: Option<CapabilityTEE>,
263}
264
265/// Represents the runtimes supported by a given Oasis node.
266#[derive(Clone, Debug, Default, PartialEq, Eq, Hash, cbor::Encode, cbor::Decode)]
267pub struct NodeRuntime {
268    /// Public key identifying the runtime.
269    pub id: Namespace,
270
271    /// Version of the runtime.
272    pub version: Version,
273
274    /// Node's capabilities for a given runtime.
275    pub capabilities: Capabilities,
276
277    /// Extra per node + per runtime opaque data associated with the current instance.
278    pub extra_info: Option<Vec<u8>>,
279}
280
281/// Oasis node roles bitmask.
282#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, cbor::Encode, cbor::Decode)]
283#[cbor(transparent)]
284pub struct RolesMask(pub u32);
285
286// XXX: Would be nicer to use bitflags crate for this, but there is no way to add
287// custom derives to the enum at the moment.
288// Should be possible in v2.0 (https://github.com/bitflags/bitflags/issues/262).
289impl RolesMask {
290    /// Empty roles mask.
291    pub const ROLE_EMPTY: RolesMask = RolesMask(0);
292    /// Compute worker role.
293    pub const ROLE_COMPUTE_WORKER: RolesMask = RolesMask(1 << 0);
294    /// Observer role.
295    pub const ROLE_OBSERVER: RolesMask = RolesMask(1 << 1);
296    /// Key manager role.
297    pub const ROLE_KEY_MANAGER: RolesMask = RolesMask(1 << 2);
298    /// Validator role.
299    pub const ROLE_VALIDATOR: RolesMask = RolesMask(1 << 3);
300    /// Public consensus RPC services worker role.
301    pub const ROLE_RESERVED_3: RolesMask = RolesMask(1 << 4);
302    /// Public storage RPC services worker role.
303    pub const ROLE_STORAGE_RPC: RolesMask = RolesMask(1 << 5);
304
305    // Bits of the Oasis node roles bitmask that are reserved and must not be used.
306    pub const ROLES_RESERVED: RolesMask =
307        RolesMask(u32::MAX & !((Self::ROLE_STORAGE_RPC.0 << 1) - 1) | Self::ROLE_RESERVED_3.0);
308
309    /// Whether the roles mask contains any of the specified roles.
310    pub fn contains(&self, role: RolesMask) -> bool {
311        (self.0 & role.0) != 0
312    }
313
314    /// Whether the roles mask encodes a single valid role.
315    pub fn is_single_role(&self) -> bool {
316        // Ensures exactly one bit is set, and the set bit is a valid role.
317        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/// Node registry descriptor.
356#[derive(Clone, Debug, Default, PartialEq, Eq, cbor::Encode, cbor::Decode)]
357pub struct Node {
358    /// Structure version.
359    pub v: u16,
360
361    /// Public key identifying the node.
362    pub id: signature::PublicKey,
363
364    /// Public key identifying the Entity controlling the node.
365    pub entity_id: signature::PublicKey,
366
367    /// Epoch in which the node's commitment expires.
368    pub expiration: u64,
369
370    /// Information for connecting to this node via TLS.
371    pub tls: TLSInfo,
372
373    /// Information for connecting to this node via P2P.
374    pub p2p: P2PInfo,
375
376    /// Information for connecting to this node as a consensus member.
377    pub consensus: ConsensusInfo,
378
379    /// Information for this node's participation in VRF based elections.
380    pub vrf: VRFInfo,
381
382    /// Node's runtimes.
383    pub runtimes: Option<Vec<NodeRuntime>>,
384
385    /// Bitmask representing the node roles.
386    pub roles: RolesMask,
387
388    /// Node's oasis-node software version.
389    #[cbor(optional)]
390    pub software_version: Option<String>,
391}
392
393impl Node {
394    /// Checks whether the node has any of the specified roles.
395    pub fn has_roles(&self, roles: RolesMask) -> bool {
396        self.roles.contains(roles)
397    }
398
399    /// Checks whether the node has the provided TEE identity configured.
400    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    /// Searches for an existing supported runtime descriptor
420    /// in runtimes with the specified version and returns it.
421    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/// Runtime kind.
438#[derive(Clone, Debug, Default, PartialEq, Eq, Hash, cbor::Encode, cbor::Decode)]
439#[repr(u32)]
440pub enum RuntimeKind {
441    /// Invalid runtime that should never be explicitly set.
442    #[default]
443    KindInvalid = 0,
444    /// Generic compute runtime.
445    KindCompute = 1,
446    /// Key manager runtime.
447    KindKeyManager = 2,
448}
449
450/// Parameters for the executor committee.
451#[derive(Clone, Debug, Default, PartialEq, Eq, Hash, cbor::Encode, cbor::Decode)]
452pub struct ExecutorParameters {
453    /// Size of the committee.
454    pub group_size: u16,
455    /// Size of the discrepancy resolution group.
456    pub group_backup_size: u16,
457    /// Number of allowed stragglers.
458    pub allowed_stragglers: u16,
459    /// Round timeout in consensus blocks.
460    pub round_timeout: i64,
461    /// Maximum number of messages that can be emitted by the runtime
462    /// in a single round.
463    pub max_messages: u32,
464    /// Minimum percentage of rounds in an epoch that a node must participate in positively in order
465    /// to be considered live. Nodes not satisfying this may be penalized.
466    #[cbor(optional)]
467    pub min_live_rounds_percent: u8,
468    /// Maximum percentage of proposed rounds in an epoch that can fail for a node
469    /// to be considered live. Nodes not satisfying this may be penalized. Zero means
470    /// that all proposed rounds can fail.
471    #[cbor(optional)]
472    pub max_missed_proposals_percent: u8,
473    /// Minimum number of live rounds in an epoch for the liveness calculations to be considered for
474    /// evaluation.
475    #[cbor(optional)]
476    pub min_live_rounds_eval: u64,
477    /// Maximum number of liveness failures that are tolerated before suspending and/or slashing the
478    /// node. Zero means unlimited.
479    #[cbor(optional)]
480    pub max_liveness_fails: u8,
481}
482
483/// Parameters for the runtime transaction scheduler.
484#[derive(Clone, Debug, Default, PartialEq, Eq, Hash, cbor::Encode, cbor::Decode)]
485pub struct TxnSchedulerParameters {
486    /// How long to wait for a scheduled batch in nanoseconds (when using the
487    /// "simple" scheduling algorithm).
488    #[cbor(optional)]
489    pub batch_flush_timeout: i64,
490    /// Maximum size of a scheduled batch.
491    #[cbor(optional)]
492    pub max_batch_size: u64,
493    /// Maximum size of a scheduled batch in bytes.
494    #[cbor(optional)]
495    pub max_batch_size_bytes: u64,
496    /// Maximum size of the incoming message queue.
497    #[cbor(optional)]
498    pub max_in_messages: u32,
499    /// How long to wait before accepting proposal from the next backup scheduler in nanoseconds.
500    #[cbor(optional)]
501    pub propose_batch_timeout: i64,
502}
503
504/// Storage parameters.
505#[derive(Clone, Debug, Default, PartialEq, Eq, Hash, cbor::Encode, cbor::Decode)]
506pub struct StorageParameters {
507    /// Expected runtime state checkpoint interval (in rounds).
508    pub checkpoint_interval: u64,
509    /// Expected minimum number of checkpoints to keep.
510    pub checkpoint_num_kept: u64,
511    /// Chunk size parameter for checkpoint creation.
512    pub checkpoint_chunk_size: u64,
513}
514
515/// The node scheduling constraints.
516///
517/// Multiple fields may be set in which case the ALL the constraints must be satisfied.
518#[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/// A constraint which specifies that the entity must have a node that is part of the validator set.
531/// No other options can currently be specified.
532#[derive(Clone, Debug, Default, PartialEq, Eq, Hash, cbor::Encode, cbor::Decode)]
533pub struct ValidatorSetConstraint {}
534
535/// A constraint which specifies that only the given number of nodes may be eligible per entity.
536#[derive(Clone, Debug, Default, PartialEq, Eq, Hash, cbor::Encode, cbor::Decode)]
537pub struct MaxNodesConstraint {
538    pub limit: u16,
539}
540
541/// A constraint which specifies the minimum required candidate pool size.
542#[derive(Clone, Debug, Default, PartialEq, Eq, Hash, cbor::Encode, cbor::Decode)]
543pub struct MinPoolSizeConstraint {
544    pub limit: u16,
545}
546
547/// Stake-related parameters for a runtime.
548#[derive(Clone, Debug, Default, PartialEq, Eq, Hash, cbor::Encode, cbor::Decode)]
549pub struct RuntimeStakingParameters {
550    /// Minimum stake thresholds for a runtime. These per-runtime thresholds are
551    /// in addition to the global thresholds. May be left unspecified.
552    ///
553    /// In case a node is registered for multiple runtimes, it will need to
554    /// satisfy the maximum threshold of all the runtimes.
555    #[cbor(optional)]
556    pub thresholds: BTreeMap<staking::ThresholdKind, quantity::Quantity>,
557
558    /// Per-runtime misbehavior slashing parameters.
559    #[cbor(optional)]
560    pub slashing: BTreeMap<staking::SlashReason, staking::Slash>,
561
562    /// The percentage of the reward obtained when slashing for equivocation that is transferred to
563    /// the runtime's account.
564    #[cbor(optional)]
565    pub reward_equivocation: u8,
566
567    /// The percentage of the reward obtained when slashing for incorrect results that is
568    /// transferred to the runtime's account.
569    #[cbor(optional)]
570    pub reward_bad_results: u8,
571
572    /// Specifies the minimum fee that the incoming message must include for the
573    /// message to be queued.
574    #[cbor(optional)]
575    pub min_in_message_fee: quantity::Quantity,
576}
577
578/// Policy that allows only whitelisted entities' nodes to register.
579#[derive(Clone, Debug, Default, PartialEq, Eq, Hash, cbor::Encode, cbor::Decode)]
580pub struct EntityWhitelistRuntimeAdmissionPolicy {
581    /// Entity whitelist configuration for each whitelisted entity.
582    #[cbor(optional)]
583    pub entities: BTreeMap<signature::PublicKey, EntityWhitelistConfig>,
584}
585
586/// Entity whitelist configuration.
587#[derive(Clone, Debug, Default, PartialEq, Eq, Hash, cbor::Encode, cbor::Decode)]
588pub struct EntityWhitelistConfig {
589    /// Maximum number of nodes that an entity can register under the given
590    /// runtime for a specific role. If the map is empty or absent, the number
591    /// of nodes is unlimited. If the map is present and non-empty, the number
592    /// of nodes is restricted to the specified maximum (where zero
593    /// means no nodes allowed), any missing roles imply zero nodes.
594    #[cbor(optional)]
595    pub max_nodes: BTreeMap<RolesMask, u16>,
596}
597
598/// A per-entity whitelist configuration for a given role.
599#[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/// A per-role entity whitelist policy.
606#[derive(Clone, Debug, Default, PartialEq, Eq, Hash, cbor::Encode, cbor::Decode)]
607pub struct EntityWhitelistRoleAdmissionPolicy {
608    pub entities: BTreeMap<signature::PublicKey, EntityWhitelistRoleConfig>,
609}
610
611/// A per-role admission policy.
612#[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/// Admission policy that allows any node to register.
619#[derive(Clone, Debug, Default, PartialEq, Eq, Hash, cbor::Encode, cbor::Decode)]
620pub struct AnyNodeRuntimeAdmissionPolicy {}
621
622/// Specification of which nodes are allowed to register for a runtime.
623#[derive(Clone, Debug, Default, PartialEq, Eq, Hash, cbor::Encode, cbor::Decode)]
624pub struct RuntimeAdmissionPolicy {
625    /// Allow any node to register.
626    #[cbor(optional)]
627    pub any_node: Option<AnyNodeRuntimeAdmissionPolicy>,
628
629    /// Allow only the whitelisted entities' nodes to register.
630    #[cbor(optional)]
631    pub entity_whitelist: Option<EntityWhitelistRuntimeAdmissionPolicy>,
632
633    /// A per-role admission policy that must be satisfied in addition to the global admission
634    /// policy for a specific role.
635    #[cbor(optional)]
636    pub per_role: BTreeMap<RolesMask, PerRoleAdmissionPolicy>,
637}
638
639/// Runtime governance model.
640#[derive(Clone, Debug, Default, PartialEq, Eq, Hash, cbor::Encode, cbor::Decode)]
641#[repr(u8)]
642pub enum RuntimeGovernanceModel {
643    /// Invalid model that should never be explicitly set.
644    #[default]
645    GovernanceInvalid = 0,
646    /// Entity governance model.
647    GovernanceEntity = 1,
648    /// Runtime governance model.
649    GovernanceRuntime = 2,
650    /// Consensus governance model.
651    GovernanceConsensus = 3,
652}
653
654/// Per-runtime version information.
655#[derive(Clone, Debug, Default, PartialEq, Eq, Hash, cbor::Encode, cbor::Decode)]
656pub struct VersionInfo {
657    /// Version of the runtime.
658    pub version: Version,
659    /// The epoch at which this version is valid.
660    pub valid_from: EpochTime,
661    /// Enclave version information, in an enclave provided specific format (if any).
662    #[cbor(optional)]
663    pub tee: Vec<u8>,
664    /// The SHA256 hash of the runtime bundle (optional).
665    #[cbor(optional)]
666    pub bundle_checksum: Vec<u8>,
667}
668
669impl VersionInfo {
670    /// Tries to decode the TEE-specific version information.
671    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/// Intel SGX TEE constraints.
680#[derive(Clone, Debug, cbor::Encode, cbor::Decode)]
681#[cbor(tag = "v")]
682pub enum SGXConstraints {
683    /// Old V0 format that only supported IAS policies.
684    #[cbor(rename = 0, missing)]
685    V0 {
686        /// The allowed MRENCLAVE/MRSIGNER pairs.
687        #[cbor(optional)]
688        enclaves: Vec<sgx::EnclaveIdentity>,
689
690        /// A set of allowed quote statuses.
691        #[cbor(optional)]
692        allowed_quote_statuses: Vec<i64>,
693    },
694
695    /// New V1 format that supports both IAS and PCS policies.
696    #[cbor(rename = 1)]
697    V1 {
698        /// The allowed MRENCLAVE/MRSIGNER pairs.
699        #[cbor(optional)]
700        enclaves: Vec<sgx::EnclaveIdentity>,
701
702        /// The quote policy.
703        #[cbor(optional)]
704        policy: sgx::QuotePolicy,
705
706        /// The maximum attestation age (in blocks).
707        #[cbor(optional)]
708        max_attestation_age: u64,
709    },
710}
711
712impl SGXConstraints {
713    /// Identities of allowed enclaves.
714    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    /// Checks whether the given enclave identity is whitelisted.
722    pub fn contains_enclave(&self, eid: &sgx::EnclaveIdentity) -> bool {
723        self.enclaves().contains(eid)
724    }
725
726    /// SGX quote policy.
727    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/// Verified remote attestation.
747#[derive(Clone, Debug, Default)]
748pub struct VerifiedAttestation {
749    /// Verified enclave quote.
750    pub quote: sgx::VerifiedQuote,
751    /// Enclave's view of the consensus layer height at the time of attestation.
752    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/// Intel SGX remote attestation.
765#[derive(Clone, Debug, cbor::Encode, cbor::Decode)]
766#[cbor(tag = "v")]
767pub enum SGXAttestation {
768    /// Old V0 format that only supported IAS quotes.
769    #[cbor(rename = 0, missing)]
770    V0(sgx::ias::AVR),
771
772    /// New V1 format that supports both IAS and PCS policies.
773    #[cbor(rename = 1)]
774    V1 {
775        /// An Intel SGX quote.
776        quote: sgx::Quote,
777        /// The runtime's view of the consensus layer height at the time of attestation.
778        height: u64,
779        /// The signature of the attestation by the enclave (RAK).
780        signature: Signature,
781    },
782}
783
784impl SGXAttestation {
785    /// SGX attestation quote.
786    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    /// Hashes the required data that needs to be signed by RAK producing the attestation signature.
794    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    /// Verifies the SGX attestation.
811    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        // Verify the quote.
819        let verified_quote = self.quote().verify(policy)?;
820
821        // Ensure that the report data includes the hash of the node's RAK.
822        Identity::verify_binding(&verified_quote, rak)?;
823
824        // Verify the attestation signature.
825        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/// TEE hardware implementation.
843#[derive(Clone, Debug, Default, PartialEq, Eq, Hash, cbor::Encode, cbor::Decode)]
844#[repr(u8)]
845pub enum TEEHardware {
846    /// Non-TEE implementation.
847    #[default]
848    TEEHardwareInvalid = 0,
849    /// Intel SGX TEE implementation.
850    TEEHardwareIntelSGX = 1,
851}
852
853/// The latest entity descriptor version that should be used for all new descriptors. Using earlier
854/// versions may be rejected.
855pub const LATEST_RUNTIME_DESCRIPTOR_VERSION: u16 = 3;
856
857/// Runtime.
858#[derive(Clone, Debug, Default, PartialEq, Eq, Hash, cbor::Encode, cbor::Decode)]
859pub struct Runtime {
860    /// Structure version.
861    pub v: u16,
862    /// Globally unique long term identifier of the runtime.
863    pub id: Namespace,
864    /// Public key identifying the Entity controlling the runtime.
865    pub entity_id: signature::PublicKey,
866    /// Runtime genesis information.
867    pub genesis: RuntimeGenesis,
868    /// Type of runtime.
869    pub kind: RuntimeKind,
870    /// Runtime's TEE hardware requirements.
871    pub tee_hardware: TEEHardware,
872    /// Runtime deployment information.
873    #[cbor(optional)]
874    pub deployments: Vec<VersionInfo>,
875    /// Key manager runtime ID for this runtime.
876    #[cbor(optional)]
877    pub key_manager: Option<Namespace>,
878    /// Parameters of the executor committee.
879    #[cbor(optional)]
880    pub executor: ExecutorParameters,
881    /// Transaction scheduling parameters of the executor committee.
882    #[cbor(optional)]
883    pub txn_scheduler: TxnSchedulerParameters,
884    /// Parameters of the storage committee.
885    #[cbor(optional)]
886    pub storage: StorageParameters,
887    /// Which nodes are allowed to register for this runtime.
888    pub admission_policy: RuntimeAdmissionPolicy,
889    /// Node scheduling constraints.
890    #[cbor(optional)]
891    pub constraints:
892        BTreeMap<scheduler::CommitteeKind, BTreeMap<scheduler::Role, SchedulingConstraints>>,
893    /// Runtime's staking-related parameters.
894    #[cbor(optional, skip_serializing_if = "staking_params_are_empty")]
895    pub staking: RuntimeStakingParameters,
896    /// Runtime governance model.
897    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    /// The currently active deployment for the specified epoch if it exists.
910    pub fn active_deployment(&self, now: EpochTime) -> Option<VersionInfo> {
911        self.deployments
912            .iter()
913            .filter(|vi| vi.valid_from <= now) // Ignore versions that are not valid yet.
914            .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    /// Deployment corresponding to the specified version if it exists.
922    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/// Runtime genesis information that is used to initialize runtime state in the first block.
931#[derive(Clone, Debug, Default, PartialEq, Eq, Hash, cbor::Encode, cbor::Decode)]
932pub struct RuntimeGenesis {
933    /// State root that should be used at genesis time. If the runtime should start with empty state,
934    /// this must be set to the empty hash.
935    pub state_root: Hash,
936
937    /// Runtime round in the genesis.
938    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    /// Constructs a BTreeMap using a `btreemap! { key => value, ... }` syntax.
953    macro_rules! btreemap {
954    // allow trailing comma
955    ( $($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        // NOTE: These tests MUST be synced with go/registry/api/runtime.go.
968        let tcs = vec![
969            // FIXME: Change to "qmF2AGJpZFggAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABka2luZABnZ2VuZXNpc6Jlcm91bmQAanN0YXRlX3Jvb3RYIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAZ3N0b3JhZ2Wjc2NoZWNrcG9pbnRfaW50ZXJ2YWwAc2NoZWNrcG9pbnRfbnVtX2tlcHQAdWNoZWNrcG9pbnRfY2h1bmtfc2l6ZQBoZXhlY3V0b3Klamdyb3VwX3NpemUAbG1heF9tZXNzYWdlcwBtcm91bmRfdGltZW91dABxZ3JvdXBfYmFja3VwX3NpemUAcmFsbG93ZWRfc3RyYWdnbGVycwBpZW50aXR5X2lkWCAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGx0ZWVfaGFyZHdhcmUAcGFkbWlzc2lvbl9wb2xpY3mhaGFueV9ub2RloHBnb3Zlcm5hbmNlX21vZGVsAA==" once cbor is fixed.
970            ("q2F2AGJpZFggAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABka2luZABnZ2VuZXNpc6Jlcm91bmQAanN0YXRlX3Jvb3RYIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAZ3N0b3JhZ2Wjc2NoZWNrcG9pbnRfaW50ZXJ2YWwAc2NoZWNrcG9pbnRfbnVtX2tlcHQAdWNoZWNrcG9pbnRfY2h1bmtfc2l6ZQBoZXhlY3V0b3Klamdyb3VwX3NpemUAbG1heF9tZXNzYWdlcwBtcm91bmRfdGltZW91dABxZ3JvdXBfYmFja3VwX3NpemUAcmFsbG93ZWRfc3RyYWdnbGVycwBpZW50aXR5X2lkWCAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGx0ZWVfaGFyZHdhcmUAbXR4bl9zY2hlZHVsZXKgcGFkbWlzc2lvbl9wb2xpY3mhaGFueV9ub2RloHBnb3Zlcm5hbmNlX21vZGVsAA==", Runtime {
971                admission_policy: RuntimeAdmissionPolicy {
972                    any_node: Some(AnyNodeRuntimeAdmissionPolicy {}),
973                    ..Default::default()
974                },
975                ..Default::default()
976            }),
977            // FIXME: Change to "qmF2AGJpZFggAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABka2luZABnZ2VuZXNpc6Jlcm91bmQAanN0YXRlX3Jvb3RYIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAZ3N0b3JhZ2Wjc2NoZWNrcG9pbnRfaW50ZXJ2YWwAc2NoZWNrcG9pbnRfbnVtX2tlcHQAdWNoZWNrcG9pbnRfY2h1bmtfc2l6ZQBoZXhlY3V0b3Klamdyb3VwX3NpemUAbG1heF9tZXNzYWdlcwBtcm91bmRfdGltZW91dABxZ3JvdXBfYmFja3VwX3NpemUAcmFsbG93ZWRfc3RyYWdnbGVycwBpZW50aXR5X2lkWCAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGx0ZWVfaGFyZHdhcmUAcGFkbWlzc2lvbl9wb2xpY3mhaGFueV9ub2RloHBnb3Zlcm5hbmNlX21vZGVsAA==" once cbor is fixed.
978            (
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            // FIXME: Change to "q2F2AGJpZFggAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABka2luZABnZ2VuZXNpc6Jlcm91bmQAanN0YXRlX3Jvb3RYIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAZ3N0YWtpbmehcnJld2FyZF9iYWRfcmVzdWx0cwpnc3RvcmFnZaNzY2hlY2twb2ludF9pbnRlcnZhbABzY2hlY2twb2ludF9udW1fa2VwdAB1Y2hlY2twb2ludF9jaHVua19zaXplAGhleGVjdXRvcqVqZ3JvdXBfc2l6ZQBsbWF4X21lc3NhZ2VzAG1yb3VuZF90aW1lb3V0AHFncm91cF9iYWNrdXBfc2l6ZQByYWxsb3dlZF9zdHJhZ2dsZXJzAGllbnRpdHlfaWRYIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAbHRlZV9oYXJkd2FyZQBwYWRtaXNzaW9uX3BvbGljeaFoYW55X25vZGWgcGdvdmVybmFuY2VfbW9kZWwA" once cbor is fixed.
996            (
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, // 1 second.
1055                        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, // 2 seconds.
1059                    },
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        // NOTE: These tests MUST be synced with go/common/node/node_test.go.
1118        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        // NOTE: These tests MUST be synced with go/common/node/node_test.go.
1202        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}