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