use std::collections::BTreeMap;
use anyhow::{anyhow, bail};
use num_traits::Zero;
use tiny_keccak::{Hasher, TupleHash};
use crate::{
common::{
crypto::{
hash::Hash,
signature::{self, Signature},
x25519,
},
namespace::Namespace,
quantity, sgx,
version::Version,
},
consensus::{beacon::EpochTime, scheduler, staking},
identity::Identity,
};
pub const MODULE_NAME: &str = "registry";
pub const METHOD_PROVE_FRESHNESS: &str = "registry.ProveFreshness";
pub const ATTESTATION_SIGNATURE_CONTEXT: &[u8] = b"oasis-core/node: TEE attestation signature";
pub const ENDORSE_CAPABILITY_TEE_SIGNATURE_CONTEXT: &[u8] =
b"oasis-core/node: endorse TEE capability";
#[derive(Clone, Debug, Default, PartialEq, Eq, Hash, cbor::Encode, cbor::Decode)]
pub struct TCPAddress {
#[cbor(rename = "IP")]
pub ip: Vec<u8>,
#[cbor(rename = "Port")]
pub port: i64,
#[cbor(rename = "Zone")]
pub zone: String,
}
#[derive(Clone, Debug, Default, PartialEq, Eq, Hash, cbor::Encode, cbor::Decode)]
pub struct TLSAddress {
pub pub_key: signature::PublicKey,
pub address: TCPAddress,
}
#[derive(Clone, Debug, Default, PartialEq, Eq, Hash, cbor::Encode, cbor::Decode)]
pub struct TLSInfo {
pub pub_key: signature::PublicKey,
#[cbor(rename = "next_pub_key", optional)]
pub _deprecated_next_pub_key: Option<signature::PublicKey>,
#[cbor(rename = "addresses", optional)]
pub _deprecated_addresses: Option<Vec<TLSAddress>>,
}
#[derive(Clone, Debug, Default, PartialEq, Eq, Hash, cbor::Encode, cbor::Decode)]
pub struct P2PInfo {
pub id: signature::PublicKey,
pub addresses: Option<Vec<TCPAddress>>,
}
#[derive(Clone, Debug, Default, PartialEq, Eq, Hash, cbor::Encode, cbor::Decode)]
pub struct ConsensusAddress {
pub id: signature::PublicKey,
pub address: TCPAddress,
}
#[derive(Clone, Debug, Default, PartialEq, Eq, Hash, cbor::Encode, cbor::Decode)]
pub struct ConsensusInfo {
pub id: signature::PublicKey,
pub addresses: Option<Vec<ConsensusAddress>>,
}
#[derive(Clone, Debug, Default, PartialEq, Eq, Hash, cbor::Encode, cbor::Decode)]
pub struct VRFInfo {
pub id: signature::PublicKey,
}
#[derive(Clone, Debug, Default, PartialEq, Eq, Hash, cbor::Encode, cbor::Decode)]
pub struct CapabilityTEE {
pub hardware: TEEHardware,
pub rak: signature::PublicKey,
#[cbor(optional)]
pub rek: Option<x25519::PublicKey>,
pub attestation: Vec<u8>,
}
impl CapabilityTEE {
pub fn try_decode_attestation<T>(&self) -> Result<T, cbor::DecodeError>
where
T: cbor::Decode,
{
cbor::from_slice_non_strict(&self.attestation)
}
pub fn matches(&self, identity: &Identity) -> bool {
match self.hardware {
TEEHardware::TEEHardwareInvalid => false,
TEEHardware::TEEHardwareIntelSGX => {
let attestation: SGXAttestation = match self.try_decode_attestation() {
Ok(a) => a,
_ => return false,
};
identity.rak_matches(&self.rak, &attestation.quote())
}
}
}
pub fn verify(
&self,
policy: &sgx::QuotePolicy,
node_id: &signature::PublicKey,
) -> anyhow::Result<VerifiedAttestation> {
match self.hardware {
TEEHardware::TEEHardwareInvalid => bail!("invalid TEE hardware"),
TEEHardware::TEEHardwareIntelSGX => {
let attestation: SGXAttestation = self.try_decode_attestation()?;
attestation.verify(
policy,
node_id,
&self.rak,
self.rek.as_ref().ok_or(anyhow!("missing REK"))?,
)
}
}
}
}
#[derive(Clone, Debug, Default, PartialEq, Eq, Hash, cbor::Encode, cbor::Decode)]
pub struct EndorsedCapabilityTEE {
pub capability_tee: CapabilityTEE,
pub node_endorsement: signature::SignatureBundle,
}
impl EndorsedCapabilityTEE {
pub fn verify_endorsement(&self) -> anyhow::Result<()> {
if !self.node_endorsement.verify(
ENDORSE_CAPABILITY_TEE_SIGNATURE_CONTEXT,
&cbor::to_vec(self.capability_tee.clone()),
) {
bail!("invalid node endorsement signature");
}
Ok(())
}
pub fn verify(
&self,
policy: &sgx::QuotePolicy,
) -> anyhow::Result<VerifiedEndorsedCapabilityTEE> {
self.verify_endorsement()?;
let verified_attestation = self
.capability_tee
.verify(policy, &self.node_endorsement.public_key)?;
Ok(VerifiedEndorsedCapabilityTEE {
verified_attestation,
node_id: Some(self.node_endorsement.public_key),
})
}
}
#[derive(Clone, Debug, Default)]
pub struct VerifiedEndorsedCapabilityTEE {
pub verified_attestation: VerifiedAttestation,
pub node_id: Option<signature::PublicKey>,
}
impl From<VerifiedAttestation> for VerifiedEndorsedCapabilityTEE {
fn from(verified_attestation: VerifiedAttestation) -> Self {
Self {
verified_attestation,
node_id: None,
}
}
}
impl From<sgx::VerifiedQuote> for VerifiedEndorsedCapabilityTEE {
fn from(verified_quote: sgx::VerifiedQuote) -> Self {
Self {
verified_attestation: verified_quote.into(),
node_id: None,
}
}
}
#[derive(Clone, Debug, Default, PartialEq, Eq, Hash, cbor::Encode, cbor::Decode)]
pub struct Capabilities {
#[cbor(optional)]
pub tee: Option<CapabilityTEE>,
}
#[derive(Clone, Debug, Default, PartialEq, Eq, Hash, cbor::Encode, cbor::Decode)]
pub struct NodeRuntime {
pub id: Namespace,
pub version: Version,
pub capabilities: Capabilities,
pub extra_info: Option<Vec<u8>>,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, cbor::Encode, cbor::Decode)]
#[cbor(transparent)]
pub struct RolesMask(pub u32);
impl RolesMask {
pub const ROLE_EMPTY: RolesMask = RolesMask(0);
pub const ROLE_COMPUTE_WORKER: RolesMask = RolesMask(1 << 0);
pub const ROLE_OBSERVER: RolesMask = RolesMask(1 << 1);
pub const ROLE_KEY_MANAGER: RolesMask = RolesMask(1 << 2);
pub const ROLE_VALIDATOR: RolesMask = RolesMask(1 << 3);
pub const ROLE_RESERVED_3: RolesMask = RolesMask(1 << 4);
pub const ROLE_STORAGE_RPC: RolesMask = RolesMask(1 << 5);
pub const ROLES_RESERVED: RolesMask =
RolesMask(u32::MAX & !((Self::ROLE_STORAGE_RPC.0 << 1) - 1) | Self::ROLE_RESERVED_3.0);
pub fn contains(&self, role: RolesMask) -> bool {
(self.0 & role.0) != 0
}
pub fn is_single_role(&self) -> bool {
self.0 != 0 && self.0 & (self.0 - 1) == 0 && self.0 & Self::ROLES_RESERVED.0 == 0
}
}
impl std::ops::BitAnd for RolesMask {
type Output = Self;
fn bitand(self, rhs: Self) -> Self::Output {
Self(self.0.bitand(rhs.0))
}
}
impl std::ops::BitOr for RolesMask {
type Output = Self;
fn bitor(self, rhs: Self) -> Self::Output {
Self(self.0.bitor(rhs.0))
}
}
impl PartialOrd for RolesMask {
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
Some(self.cmp(other))
}
}
impl Ord for RolesMask {
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
self.0.cmp(&other.0)
}
}
impl Default for RolesMask {
fn default() -> Self {
Self::ROLE_EMPTY
}
}
#[derive(Clone, Debug, Default, PartialEq, Eq, cbor::Encode, cbor::Decode)]
pub struct Node {
pub v: u16,
pub id: signature::PublicKey,
pub entity_id: signature::PublicKey,
pub expiration: u64,
pub tls: TLSInfo,
pub p2p: P2PInfo,
pub consensus: ConsensusInfo,
pub vrf: VRFInfo,
pub runtimes: Option<Vec<NodeRuntime>>,
pub roles: RolesMask,
#[cbor(optional)]
pub software_version: Option<String>,
}
impl Node {
pub fn has_roles(&self, roles: RolesMask) -> bool {
self.roles.contains(roles)
}
pub fn has_tee(&self, identity: &Identity, runtime_id: &Namespace, version: &Version) -> bool {
if let Some(rts) = &self.runtimes {
for rt in rts {
if runtime_id != &rt.id {
continue;
}
if version != &rt.version {
continue;
}
if let Some(tee) = &rt.capabilities.tee {
if tee.matches(identity) {
return true;
}
}
}
}
false
}
pub fn get_runtime(&self, runtime_id: &Namespace, version: &Version) -> Option<NodeRuntime> {
if let Some(rts) = &self.runtimes {
for rt in rts {
if runtime_id != &rt.id {
continue;
}
if version != &rt.version {
continue;
}
return Some(rt.clone());
}
}
None
}
}
#[derive(Clone, Debug, Default, PartialEq, Eq, Hash, cbor::Encode, cbor::Decode)]
#[repr(u32)]
pub enum RuntimeKind {
#[default]
KindInvalid = 0,
KindCompute = 1,
KindKeyManager = 2,
}
#[derive(Clone, Debug, Default, PartialEq, Eq, Hash, cbor::Encode, cbor::Decode)]
pub struct ExecutorParameters {
pub group_size: u16,
pub group_backup_size: u16,
pub allowed_stragglers: u16,
pub round_timeout: i64,
pub max_messages: u32,
#[cbor(optional)]
pub min_live_rounds_percent: u8,
#[cbor(optional)]
pub max_missed_proposals_percent: u8,
#[cbor(optional)]
pub min_live_rounds_eval: u64,
#[cbor(optional)]
pub max_liveness_fails: u8,
}
#[derive(Clone, Debug, Default, PartialEq, Eq, Hash, cbor::Encode, cbor::Decode)]
pub struct TxnSchedulerParameters {
#[cbor(optional)]
pub batch_flush_timeout: i64,
#[cbor(optional)]
pub max_batch_size: u64,
#[cbor(optional)]
pub max_batch_size_bytes: u64,
#[cbor(optional)]
pub max_in_messages: u32,
#[cbor(optional)]
pub propose_batch_timeout: i64,
}
#[derive(Clone, Debug, Default, PartialEq, Eq, Hash, cbor::Encode, cbor::Decode)]
pub struct StorageParameters {
pub checkpoint_interval: u64,
pub checkpoint_num_kept: u64,
pub checkpoint_chunk_size: u64,
}
#[derive(Clone, Debug, Default, PartialEq, Eq, Hash, cbor::Encode, cbor::Decode)]
pub struct SchedulingConstraints {
#[cbor(optional)]
pub validator_set: Option<ValidatorSetConstraint>,
#[cbor(optional)]
pub max_nodes: Option<MaxNodesConstraint>,
#[cbor(optional)]
pub min_pool_size: Option<MinPoolSizeConstraint>,
}
#[derive(Clone, Debug, Default, PartialEq, Eq, Hash, cbor::Encode, cbor::Decode)]
pub struct ValidatorSetConstraint {}
#[derive(Clone, Debug, Default, PartialEq, Eq, Hash, cbor::Encode, cbor::Decode)]
pub struct MaxNodesConstraint {
pub limit: u16,
}
#[derive(Clone, Debug, Default, PartialEq, Eq, Hash, cbor::Encode, cbor::Decode)]
pub struct MinPoolSizeConstraint {
pub limit: u16,
}
#[derive(Clone, Debug, Default, PartialEq, Eq, Hash, cbor::Encode, cbor::Decode)]
pub struct RuntimeStakingParameters {
#[cbor(optional)]
pub thresholds: BTreeMap<staking::ThresholdKind, quantity::Quantity>,
#[cbor(optional)]
pub slashing: BTreeMap<staking::SlashReason, staking::Slash>,
#[cbor(optional)]
pub reward_equivocation: u8,
#[cbor(optional)]
pub reward_bad_results: u8,
#[cbor(optional)]
pub min_in_message_fee: quantity::Quantity,
}
#[derive(Clone, Debug, Default, PartialEq, Eq, Hash, cbor::Encode, cbor::Decode)]
pub struct EntityWhitelistRuntimeAdmissionPolicy {
#[cbor(optional)]
pub entities: BTreeMap<signature::PublicKey, EntityWhitelistConfig>,
}
#[derive(Clone, Debug, Default, PartialEq, Eq, Hash, cbor::Encode, cbor::Decode)]
pub struct EntityWhitelistConfig {
#[cbor(optional)]
pub max_nodes: BTreeMap<RolesMask, u16>,
}
#[derive(Clone, Debug, Default, PartialEq, Eq, Hash, cbor::Encode, cbor::Decode)]
pub struct EntityWhitelistRoleConfig {
#[cbor(optional)]
pub max_nodes: u16,
}
#[derive(Clone, Debug, Default, PartialEq, Eq, Hash, cbor::Encode, cbor::Decode)]
pub struct EntityWhitelistRoleAdmissionPolicy {
pub entities: BTreeMap<signature::PublicKey, EntityWhitelistRoleConfig>,
}
#[derive(Clone, Debug, Default, PartialEq, Eq, Hash, cbor::Encode, cbor::Decode)]
pub struct PerRoleAdmissionPolicy {
#[cbor(optional)]
pub entity_whitelist: Option<EntityWhitelistRoleAdmissionPolicy>,
}
#[derive(Clone, Debug, Default, PartialEq, Eq, Hash, cbor::Encode, cbor::Decode)]
pub struct AnyNodeRuntimeAdmissionPolicy {}
#[derive(Clone, Debug, Default, PartialEq, Eq, Hash, cbor::Encode, cbor::Decode)]
pub struct RuntimeAdmissionPolicy {
#[cbor(optional)]
pub any_node: Option<AnyNodeRuntimeAdmissionPolicy>,
#[cbor(optional)]
pub entity_whitelist: Option<EntityWhitelistRuntimeAdmissionPolicy>,
#[cbor(optional)]
pub per_role: BTreeMap<RolesMask, PerRoleAdmissionPolicy>,
}
#[derive(Clone, Debug, Default, PartialEq, Eq, Hash, cbor::Encode, cbor::Decode)]
#[repr(u8)]
pub enum RuntimeGovernanceModel {
#[default]
GovernanceInvalid = 0,
GovernanceEntity = 1,
GovernanceRuntime = 2,
GovernanceConsensus = 3,
}
#[derive(Clone, Debug, Default, PartialEq, Eq, Hash, cbor::Encode, cbor::Decode)]
pub struct VersionInfo {
pub version: Version,
pub valid_from: EpochTime,
#[cbor(optional)]
pub tee: Vec<u8>,
#[cbor(optional)]
pub bundle_checksum: Vec<u8>,
}
impl VersionInfo {
pub fn try_decode_tee<T>(&self) -> Result<T, cbor::DecodeError>
where
T: cbor::Decode,
{
cbor::from_slice_non_strict(&self.tee)
}
}
#[derive(Clone, Debug, cbor::Encode, cbor::Decode)]
#[cbor(tag = "v")]
pub enum SGXConstraints {
#[cbor(rename = 0, missing)]
V0 {
#[cbor(optional)]
enclaves: Vec<sgx::EnclaveIdentity>,
#[cbor(optional)]
allowed_quote_statuses: Vec<i64>,
},
#[cbor(rename = 1)]
V1 {
#[cbor(optional)]
enclaves: Vec<sgx::EnclaveIdentity>,
#[cbor(optional)]
policy: sgx::QuotePolicy,
#[cbor(optional)]
max_attestation_age: u64,
},
}
impl SGXConstraints {
pub fn enclaves(&self) -> &Vec<sgx::EnclaveIdentity> {
match self {
Self::V0 { ref enclaves, .. } => enclaves,
Self::V1 { ref enclaves, .. } => enclaves,
}
}
pub fn contains_enclave(&self, eid: &sgx::EnclaveIdentity) -> bool {
self.enclaves().contains(eid)
}
pub fn policy(&self) -> sgx::QuotePolicy {
match self {
Self::V0 {
ref allowed_quote_statuses,
..
} => sgx::QuotePolicy {
ias: Some(sgx::ias::QuotePolicy {
disabled: false,
allowed_quote_statuses: allowed_quote_statuses.clone(),
gid_blacklist: Vec::new(),
min_tcb_evaluation_data_number: 0,
}),
..Default::default()
},
Self::V1 { ref policy, .. } => policy.clone(),
}
}
}
#[derive(Clone, Debug, Default)]
pub struct VerifiedAttestation {
pub quote: sgx::VerifiedQuote,
pub height: Option<u64>,
}
impl From<sgx::VerifiedQuote> for VerifiedAttestation {
fn from(quote: sgx::VerifiedQuote) -> Self {
Self {
quote,
height: None,
}
}
}
#[derive(Clone, Debug, cbor::Encode, cbor::Decode)]
#[cbor(tag = "v")]
pub enum SGXAttestation {
#[cbor(rename = 0, missing)]
V0(sgx::ias::AVR),
#[cbor(rename = 1)]
V1 {
quote: sgx::Quote,
height: u64,
signature: Signature,
},
}
impl SGXAttestation {
pub fn quote(&self) -> sgx::Quote {
match self {
Self::V0(avr) => sgx::Quote::Ias(avr.clone()),
Self::V1 { quote, .. } => quote.clone(),
}
}
pub fn hash(
report_data: &[u8],
node_id: &signature::PublicKey,
height: u64,
rek: &x25519::PublicKey,
) -> [u8; 32] {
let mut h = TupleHash::v256(ATTESTATION_SIGNATURE_CONTEXT);
h.update(report_data);
h.update(node_id.as_ref());
h.update(&height.to_le_bytes());
h.update(rek.0.as_bytes());
let mut result = [0u8; 32];
h.finalize(&mut result);
result
}
pub fn verify(
&self,
policy: &sgx::QuotePolicy,
node_id: &signature::PublicKey,
rak: &signature::PublicKey,
rek: &x25519::PublicKey,
) -> anyhow::Result<VerifiedAttestation> {
let verified_quote = self.quote().verify(policy)?;
Identity::verify_binding(&verified_quote, rak)?;
match self {
Self::V1 {
height, signature, ..
} => {
let h = Self::hash(&verified_quote.report_data, node_id, *height, rek);
signature.verify(rak, ATTESTATION_SIGNATURE_CONTEXT, &h)?;
Ok(VerifiedAttestation {
quote: verified_quote,
height: Some(*height),
})
}
_ => bail!("V0 attestation not supported"),
}
}
}
#[derive(Clone, Debug, Default, PartialEq, Eq, Hash, cbor::Encode, cbor::Decode)]
#[repr(u8)]
pub enum TEEHardware {
#[default]
TEEHardwareInvalid = 0,
TEEHardwareIntelSGX = 1,
}
pub const LATEST_RUNTIME_DESCRIPTOR_VERSION: u16 = 3;
#[derive(Clone, Debug, Default, PartialEq, Eq, Hash, cbor::Encode, cbor::Decode)]
pub struct Runtime {
pub v: u16,
pub id: Namespace,
pub entity_id: signature::PublicKey,
pub genesis: RuntimeGenesis,
pub kind: RuntimeKind,
pub tee_hardware: TEEHardware,
#[cbor(optional)]
pub deployments: Vec<VersionInfo>,
#[cbor(optional)]
pub key_manager: Option<Namespace>,
#[cbor(optional)]
pub executor: ExecutorParameters,
#[cbor(optional)]
pub txn_scheduler: TxnSchedulerParameters,
#[cbor(optional)]
pub storage: StorageParameters,
pub admission_policy: RuntimeAdmissionPolicy,
#[cbor(optional)]
pub constraints:
BTreeMap<scheduler::CommitteeKind, BTreeMap<scheduler::Role, SchedulingConstraints>>,
#[cbor(optional, skip_serializing_if = "staking_params_are_empty")]
pub staking: RuntimeStakingParameters,
pub governance_model: RuntimeGovernanceModel,
}
fn staking_params_are_empty(p: &RuntimeStakingParameters) -> bool {
p.thresholds.is_empty()
&& p.slashing.is_empty()
&& p.reward_equivocation == 0
&& p.reward_bad_results == 0
&& p.min_in_message_fee.is_zero()
}
impl Runtime {
pub fn active_deployment(&self, now: EpochTime) -> Option<VersionInfo> {
self.deployments
.iter()
.filter(|vi| vi.valid_from <= now) .fold(None, |acc, vi| match acc {
None => Some(vi.clone()),
Some(ad) if ad.valid_from < vi.valid_from => Some(vi.clone()),
_ => acc,
})
}
pub fn deployment_for_version(&self, version: Version) -> Option<VersionInfo> {
self.deployments
.iter()
.find(|vi| vi.version == version)
.cloned()
}
}
#[derive(Clone, Debug, Default, PartialEq, Eq, Hash, cbor::Encode, cbor::Decode)]
pub struct RuntimeGenesis {
pub state_root: Hash,
pub round: u64,
}
#[cfg(test)]
mod tests {
use std::{convert::TryInto, net::Ipv4Addr};
use base64::prelude::*;
use rustc_hex::{FromHex, ToHex};
use crate::common::quantity::Quantity;
use super::*;
macro_rules! btreemap {
( $($key:expr => $value:expr,)+ ) => (btreemap!($($key => $value),+));
( $($key:expr => $value:expr),* ) => {
{
let mut m = BTreeMap::new();
$( m.insert($key.into(), $value); )*
m
}
};
}
#[test]
fn test_consistent_runtime() {
let tcs = vec![
("q2F2AGJpZFggAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABka2luZABnZ2VuZXNpc6Jlcm91bmQAanN0YXRlX3Jvb3RYIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAZ3N0b3JhZ2Wjc2NoZWNrcG9pbnRfaW50ZXJ2YWwAc2NoZWNrcG9pbnRfbnVtX2tlcHQAdWNoZWNrcG9pbnRfY2h1bmtfc2l6ZQBoZXhlY3V0b3Klamdyb3VwX3NpemUAbG1heF9tZXNzYWdlcwBtcm91bmRfdGltZW91dABxZ3JvdXBfYmFja3VwX3NpemUAcmFsbG93ZWRfc3RyYWdnbGVycwBpZW50aXR5X2lkWCAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGx0ZWVfaGFyZHdhcmUAbXR4bl9zY2hlZHVsZXKgcGFkbWlzc2lvbl9wb2xpY3mhaGFueV9ub2RloHBnb3Zlcm5hbmNlX21vZGVsAA==", Runtime {
admission_policy: RuntimeAdmissionPolicy {
any_node: Some(AnyNodeRuntimeAdmissionPolicy {}),
..Default::default()
},
..Default::default()
}),
(
"q2F2AGJpZFggAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABka2luZABnZ2VuZXNpc6Jlcm91bmQAanN0YXRlX3Jvb3RYIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAZ3N0b3JhZ2Wjc2NoZWNrcG9pbnRfaW50ZXJ2YWwAc2NoZWNrcG9pbnRfbnVtX2tlcHQAdWNoZWNrcG9pbnRfY2h1bmtfc2l6ZQBoZXhlY3V0b3Klamdyb3VwX3NpemUAbG1heF9tZXNzYWdlcwBtcm91bmRfdGltZW91dABxZ3JvdXBfYmFja3VwX3NpemUAcmFsbG93ZWRfc3RyYWdnbGVycwBpZW50aXR5X2lkWCAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGx0ZWVfaGFyZHdhcmUAbXR4bl9zY2hlZHVsZXKgcGFkbWlzc2lvbl9wb2xpY3mhaGFueV9ub2RloHBnb3Zlcm5hbmNlX21vZGVsAA==",
Runtime {
admission_policy: RuntimeAdmissionPolicy {
any_node: Some(AnyNodeRuntimeAdmissionPolicy {}),
..Default::default()
},
staking: RuntimeStakingParameters {
thresholds: BTreeMap::new(),
slashing: BTreeMap::new(),
reward_equivocation: 0,
reward_bad_results: 0,
min_in_message_fee: Quantity::from(0u32),
},
..Default::default()
},
),
(
"rGF2AGJpZFggAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABka2luZABnZ2VuZXNpc6Jlcm91bmQAanN0YXRlX3Jvb3RYIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAZ3N0YWtpbmehcnJld2FyZF9iYWRfcmVzdWx0cwpnc3RvcmFnZaNzY2hlY2twb2ludF9pbnRlcnZhbABzY2hlY2twb2ludF9udW1fa2VwdAB1Y2hlY2twb2ludF9jaHVua19zaXplAGhleGVjdXRvcqVqZ3JvdXBfc2l6ZQBsbWF4X21lc3NhZ2VzAG1yb3VuZF90aW1lb3V0AHFncm91cF9iYWNrdXBfc2l6ZQByYWxsb3dlZF9zdHJhZ2dsZXJzAGllbnRpdHlfaWRYIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAbHRlZV9oYXJkd2FyZQBtdHhuX3NjaGVkdWxlcqBwYWRtaXNzaW9uX3BvbGljeaFoYW55X25vZGWgcGdvdmVybmFuY2VfbW9kZWwA",
Runtime {
admission_policy: RuntimeAdmissionPolicy {
any_node: Some(AnyNodeRuntimeAdmissionPolicy {}),
..Default::default()
},
staking: RuntimeStakingParameters {
thresholds: BTreeMap::new(),
slashing: BTreeMap::new(),
reward_equivocation: 0,
reward_bad_results: 10,
min_in_message_fee: Quantity::from(0u32),
},
..Default::default()
},
),
(
"r2F2GCpiaWRYIIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAZGtpbmQCZ2dlbmVzaXOiZXJvdW5kGCtqc3RhdGVfcm9vdFggseUhAZ+3vd413IH+55BlYQy937jvXCXihJg2aBkqbQ1nc3Rha2luZ6FycmV3YXJkX2JhZF9yZXN1bHRzCmdzdG9yYWdlo3NjaGVja3BvaW50X2ludGVydmFsGCFzY2hlY2twb2ludF9udW1fa2VwdAZ1Y2hlY2twb2ludF9jaHVua19zaXplGGVoZXhlY3V0b3Kpamdyb3VwX3NpemUJbG1heF9tZXNzYWdlcwVtcm91bmRfdGltZW91dAZxZ3JvdXBfYmFja3VwX3NpemUIcmFsbG93ZWRfc3RyYWdnbGVycwdybWF4X2xpdmVuZXNzX2ZhaWxzAXRtaW5fbGl2ZV9yb3VuZHNfZXZhbAJ3bWluX2xpdmVfcm91bmRzX3BlcmNlbnQEeBxtYXhfbWlzc2VkX3Byb3Bvc2Fsc19wZXJjZW50A2llbnRpdHlfaWRYIBI0VniQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAa2NvbnN0cmFpbnRzoQGhAaNpbWF4X25vZGVzoWVsaW1pdAptbWluX3Bvb2xfc2l6ZaFlbGltaXQFbXZhbGlkYXRvcl9zZXSga2RlcGxveW1lbnRzgaRjdGVlS3ZlcnNpb24gdGVlZ3ZlcnNpb26iZW1ham9yGCxlcGF0Y2gBanZhbGlkX2Zyb20Ab2J1bmRsZV9jaGVja3N1bVggAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQFra2V5X21hbmFnZXJYIIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABbHRlZV9oYXJkd2FyZQFtdHhuX3NjaGVkdWxlcqVubWF4X2JhdGNoX3NpemUZJxBvbWF4X2luX21lc3NhZ2VzGCBzYmF0Y2hfZmx1c2hfdGltZW91dBo7msoAdG1heF9iYXRjaF9zaXplX2J5dGVzGgCYloB1cHJvcG9zZV9iYXRjaF90aW1lb3V0Gnc1lABwYWRtaXNzaW9uX3BvbGljeaFwZW50aXR5X3doaXRlbGlzdKFoZW50aXRpZXOhWCASNFZ4kAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKFpbWF4X25vZGVzogEDBAFwZ292ZXJuYW5jZV9tb2RlbAM=",
Runtime {
v: 42,
id: Namespace::from(
"8000000000000000000000000000000000000000000000000000000000000000",
),
entity_id: signature::PublicKey::from(
"1234567890000000000000000000000000000000000000000000000000000000",
),
genesis: RuntimeGenesis {
round: 43,
state_root: Hash::digest_bytes(b"stateroot hash"),
},
kind: RuntimeKind::KindKeyManager,
tee_hardware: TEEHardware::TEEHardwareIntelSGX,
deployments: vec![VersionInfo {
version: Version {
major: 44,
minor: 0,
patch: 1,
},
valid_from: 0,
tee: b"version tee".to_vec(),
bundle_checksum: vec![0x1; 32],
}],
key_manager: Some(Namespace::from(
"8000000000000000000000000000000000000000000000000000000000000001",
)),
executor: ExecutorParameters {
group_size: 9,
group_backup_size: 8,
allowed_stragglers: 7,
round_timeout: 6,
max_messages: 5,
min_live_rounds_percent: 4,
max_missed_proposals_percent: 3,
min_live_rounds_eval: 2,
max_liveness_fails: 1,
},
txn_scheduler: TxnSchedulerParameters {
batch_flush_timeout: 1_000_000_000, max_batch_size: 10_000,
max_batch_size_bytes: 10_000_000,
max_in_messages: 32,
propose_batch_timeout: 2_000_000_000, },
storage: StorageParameters {
checkpoint_interval: 33,
checkpoint_num_kept: 6,
checkpoint_chunk_size: 101,
},
admission_policy: RuntimeAdmissionPolicy {
entity_whitelist: Some(EntityWhitelistRuntimeAdmissionPolicy {
entities: btreemap! {
signature::PublicKey::from("1234567890000000000000000000000000000000000000000000000000000000") => EntityWhitelistConfig {
max_nodes: btreemap! {
RolesMask::ROLE_COMPUTE_WORKER => 3,
RolesMask::ROLE_KEY_MANAGER => 1,
}
}
},
}),
..Default::default()
},
constraints: btreemap! {
scheduler::CommitteeKind::ComputeExecutor => btreemap! {
scheduler::Role::Worker => SchedulingConstraints{
max_nodes: Some(
MaxNodesConstraint{
limit: 10,
}
),
min_pool_size: Some(
MinPoolSizeConstraint {
limit: 5,
}
),
validator_set: Some(ValidatorSetConstraint{}),
},
}
},
staking: RuntimeStakingParameters {
thresholds: BTreeMap::new(),
slashing: BTreeMap::new(),
reward_equivocation: 0,
reward_bad_results: 10,
min_in_message_fee: Quantity::from(0u32),
},
governance_model: RuntimeGovernanceModel::GovernanceConsensus,
},
),
];
for (encoded_base64, rr) in tcs {
let dec: Runtime = cbor::from_slice(&BASE64_STANDARD.decode(encoded_base64).unwrap())
.expect("runtime should deserialize correctly");
assert_eq!(dec, rr, "decoded runtime should match the expected value");
let ser = BASE64_STANDARD.encode(cbor::to_vec(dec));
assert_eq!(ser, encoded_base64, "runtime should serialize correctly");
}
}
#[test]
fn test_consistent_node() {
let tcs = vec![
(
"qmF2A2JpZFggAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABjcDJwomJpZFggAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABpYWRkcmVzc2Vz9mN0bHOhZ3B1Yl9rZXlYIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAY3ZyZqFiaWRYIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAZXJvbGVzAGhydW50aW1lc/ZpY29uc2Vuc3VzomJpZFggAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABpYWRkcmVzc2Vz9mllbnRpdHlfaWRYIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAamV4cGlyYXRpb24A",
Node{v: 3, ..Default::default()},
true,
),
(
"qmF2A2JpZFgg//////////////////////////////////////////BjcDJwomJpZFgg//////////////////////////////////////////VpYWRkcmVzc2Vz9mN0bHOhZ3B1Yl9rZXlYIP/////////////////////////////////////////yY3ZyZqFiaWRYIP/////////////////////////////////////////3ZXJvbGVzAGhydW50aW1lc4KkYmlkWCCAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEGd2ZXJzaW9uoWVwYXRjaBkBQWpleHRyYV9pbmZv9mxjYXBhYmlsaXRpZXOgpGJpZFgggAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABFndmVyc2lvbqFlcGF0Y2gYe2pleHRyYV9pbmZvRAUDAgFsY2FwYWJpbGl0aWVzoWN0ZWWjY3Jha1gg//////////////////////////////////////////hoaGFyZHdhcmUBa2F0dGVzdGF0aW9uRgABAgMEBWljb25zZW5zdXOiYmlkWCD/////////////////////////////////////////9mlhZGRyZXNzZXOAaWVudGl0eV9pZFgg//////////////////////////////////////////FqZXhwaXJhdGlvbhgg",
Node{
v: 3,
id: signature::PublicKey::from("fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0"),
entity_id: signature::PublicKey::from("fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff1"),
expiration: 32,
tls: TLSInfo{
pub_key: signature::PublicKey::from("fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff2"),
..Default::default()
},
p2p: P2PInfo{
id: signature::PublicKey::from("fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff5"),
..Default::default()
},
consensus: ConsensusInfo{
id: signature::PublicKey::from("fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff6"),
addresses: Some(Vec::new()),
},
vrf: VRFInfo{
id: signature::PublicKey::from("fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7"),
},
runtimes: Some(vec![
NodeRuntime{
id: Namespace::from("8000000000000000000000000000000000000000000000000000000000000010"),
version: Version::from(321u64),
..Default::default()
},
NodeRuntime{
id: Namespace::from("8000000000000000000000000000000000000000000000000000000000000011"),
version: Version::from(123),
capabilities: Capabilities{
tee: Some(CapabilityTEE{
hardware: TEEHardware::TEEHardwareIntelSGX,
rak: signature::PublicKey::from("fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8"),
attestation: vec![0, 1,2,3,4,5],
..Default::default()
}),
},
extra_info: Some(vec![5,3,2,1]),
},
]),
..Default::default()
},
true,
),
(
"qWF2A2JpZFggAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABjcDJwomJpZFggAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABpYWRkcmVzc2Vz9mN0bHOhZ3B1Yl9rZXlYIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAZXJvbGVzAGhydW50aW1lc4GkYmlkWCCAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEGd2ZXJzaW9uoGpleHRyYV9pbmZv9mxjYXBhYmlsaXRpZXOgaWNvbnNlbnN1c6JiaWRYIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAaWFkZHJlc3Nlc/ZpZW50aXR5X2lkWCAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGpleHBpcmF0aW9uAA==",
Node{
v: 3,
runtimes: Some(vec![
NodeRuntime{
id: Namespace::from("8000000000000000000000000000000000000000000000000000000000000010"),
version: Version::from(0u64),
..Default::default()
},
]),
..Default::default()
},
false,
),
];
for (encoded_base64, node, round_trip) in tcs {
println!("{:?}", node);
let dec: Node = cbor::from_slice(&BASE64_STANDARD.decode(encoded_base64).unwrap())
.expect("node should deserialize correctly");
assert_eq!(dec, node, "decoded node should match the expected value");
if round_trip {
let ser = BASE64_STANDARD.encode(cbor::to_vec(dec));
assert_eq!(ser, encoded_base64, "node should serialize correctly");
}
}
}
#[test]
fn test_deserialize_node_v2() {
let tcs = vec![
(
"qWF2AmJpZFggAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABjcDJwomJpZFggAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABpYWRkcmVzc2Vz9mN0bHOiZ3B1Yl9rZXlYIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAaWFkZHJlc3Nlc/Zlcm9sZXMAaHJ1bnRpbWVz9mljb25zZW5zdXOiYmlkWCAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGlhZGRyZXNzZXP2aWVudGl0eV9pZFggAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABqZXhwaXJhdGlvbgA=",
Node{v: 2, ..Default::default()},
),
(
"qmF2AmJpZFggAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABjcDJwomJpZFggAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABpYWRkcmVzc2Vz9mN0bHOjZ3B1Yl9rZXlYIP/////////////////////////////////////////yaWFkZHJlc3Nlc4BsbmV4dF9wdWJfa2V5WCD/////////////////////////////////////////82N2cmahYmlkWCAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGVyb2xlcwBocnVudGltZXP2aWNvbnNlbnN1c6JiaWRYIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAaWFkZHJlc3Nlc/ZpZW50aXR5X2lkWCAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGpleHBpcmF0aW9uAA==",
Node{
v: 2,
tls: TLSInfo{
pub_key: signature::PublicKey::from("fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff2"),
_deprecated_next_pub_key: Some(signature::PublicKey::from("fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff3")),
_deprecated_addresses: Some(vec![]),
},
..Default::default()
},
),
(
"qmF2AmJpZFggAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABjcDJwomJpZFggAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABpYWRkcmVzc2Vz9mN0bHOjZ3B1Yl9rZXlYIP/////////////////////////////////////////yaWFkZHJlc3Nlc4OiZ2FkZHJlc3OjYklQUAAAAAAAAAAAAAD//38AAAFkUG9ydBh7ZFpvbmVgZ3B1Yl9rZXlYIP/////////////////////////////////////////0omdhZGRyZXNzo2JJUFAAAAAAAAAAAAAA///AqAEBZFBvcnQZD6BkWm9uZWBncHViX2tleVgg/////////////////////////////////////////8SiZ2FkZHJlc3OjYklQUAAAAAAAAAAAAAD//+pkY1hkUG9ydBkfQGRab25lYGdwdWJfa2V5WCD/////////////////////////////////////////1GxuZXh0X3B1Yl9rZXlYIP/////////////////////////////////////////zY3ZyZqFiaWRYIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAZXJvbGVzAGhydW50aW1lc/ZpY29uc2Vuc3VzomJpZFggAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABpYWRkcmVzc2Vz9mllbnRpdHlfaWRYIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAamV4cGlyYXRpb24A",
Node{
v: 2,
tls: TLSInfo{
pub_key: signature::PublicKey::from("fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff2"),
_deprecated_next_pub_key: Some(signature::PublicKey::from("fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff3")),
_deprecated_addresses: Some(vec![
TLSAddress{
pub_key: signature::PublicKey::from("fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff4"),
address: TCPAddress { ip: Ipv4Addr::new(127, 0, 0, 1).to_ipv6_mapped().octets().to_vec(), port: 123, ..Default::default() }
},
TLSAddress{
pub_key: signature::PublicKey::from("ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc4"),
address: TCPAddress { ip: Ipv4Addr::new(192, 168, 1, 1).to_ipv6_mapped().octets().to_vec(), port: 4000, ..Default::default() }
},
TLSAddress{
pub_key: signature::PublicKey::from("ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffd4"),
address: TCPAddress { ip: Ipv4Addr::new(234, 100, 99, 88).to_ipv6_mapped().octets().to_vec(), port: 8000, ..Default::default() }
},
])
},
..Default::default()
},
),
(
"qmF2AmJpZFgg//////////////////////////////////////////BjcDJwomJpZFgg//////////////////////////////////////////VpYWRkcmVzc2Vz9mN0bHOjZ3B1Yl9rZXlYIP/////////////////////////////////////////yaWFkZHJlc3Nlc4GiZ2FkZHJlc3OjYklQUAAAAAAAAAAAAAD//38AAAFkUG9ydBh7ZFpvbmVgZ3B1Yl9rZXlYIP/////////////////////////////////////////0bG5leHRfcHViX2tleVgg//////////////////////////////////////////NjdnJmoWJpZFgg//////////////////////////////////////////dlcm9sZXMAaHJ1bnRpbWVzgqRiaWRYIIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQZ3ZlcnNpb26hZXBhdGNoGQFBamV4dHJhX2luZm/2bGNhcGFiaWxpdGllc6CkYmlkWCCAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEWd2ZXJzaW9uoWVwYXRjaBh7amV4dHJhX2luZm9EBQMCAWxjYXBhYmlsaXRpZXOhY3RlZaNjcmFrWCD/////////////////////////////////////////+GhoYXJkd2FyZQFrYXR0ZXN0YXRpb25GAAECAwQFaWNvbnNlbnN1c6JiaWRYIP/////////////////////////////////////////2aWFkZHJlc3Nlc4BpZW50aXR5X2lkWCD/////////////////////////////////////////8WpleHBpcmF0aW9uGCA=",
Node{
v: 2,
id: signature::PublicKey::from("fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0"),
entity_id: signature::PublicKey::from("fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff1"),
expiration: 32,
tls: TLSInfo{
pub_key: signature::PublicKey::from("fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff2"),
_deprecated_next_pub_key: Some(signature::PublicKey::from("fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff3")),
_deprecated_addresses: Some(vec![TLSAddress{
pub_key: signature::PublicKey::from("fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff4"),
address: TCPAddress { ip: Ipv4Addr::new(127, 0, 0, 1).to_ipv6_mapped().octets().to_vec(), port: 123, ..Default::default() }
}])
},
p2p: P2PInfo{
id: signature::PublicKey::from("fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff5"),
..Default::default()
},
consensus: ConsensusInfo{
id: signature::PublicKey::from("fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff6"),
addresses: Some(Vec::new()),
},
vrf: VRFInfo{
id: signature::PublicKey::from("fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7"),
},
runtimes: Some(vec![
NodeRuntime{
id: Namespace::from("8000000000000000000000000000000000000000000000000000000000000010"),
version: Version::from(321u64),
..Default::default()
},
NodeRuntime{
id: Namespace::from("8000000000000000000000000000000000000000000000000000000000000011"),
version: Version::from(123),
capabilities: Capabilities{
tee: Some(CapabilityTEE{
hardware: TEEHardware::TEEHardwareIntelSGX,
rak: signature::PublicKey::from("fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8"),
attestation: vec![0, 1,2,3,4,5],
..Default::default()
}),
},
extra_info: Some(vec![5,3,2,1]),
},
]),
..Default::default()
},
),
(
"qmF2AmJpZFgg//////////////////////////////////////////BjcDJwomJpZFgg//////////////////////////////////////////VpYWRkcmVzc2Vz9mN0bHOjZ3B1Yl9rZXlYIP/////////////////////////////////////////yaWFkZHJlc3Nlc4GiZ2FkZHJlc3OjYklQUAAAAAAAAAAAAAD//38AAAFkUG9ydBh7ZFpvbmVgZ3B1Yl9rZXlYIP/////////////////////////////////////////0bG5leHRfcHViX2tleVgg//////////////////////////////////////////NjdnJmoWJpZFgg//////////////////////////////////////////dlcm9sZXMAaHJ1bnRpbWVzgqRiaWRYIIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQZ3ZlcnNpb26hZXBhdGNoGQFBamV4dHJhX2luZm/2bGNhcGFiaWxpdGllc6CkYmlkWCCAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEWd2ZXJzaW9uoWVwYXRjaBh7amV4dHJhX2luZm9EBQMCAWxjYXBhYmlsaXRpZXOhY3RlZaRjcmFrWCD/////////////////////////////////////////+GNyZWtYIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAaGhhcmR3YXJlAWthdHRlc3RhdGlvbkYAAQIDBAVpY29uc2Vuc3VzomJpZFgg//////////////////////////////////////////ZpYWRkcmVzc2VzgGllbnRpdHlfaWRYIP/////////////////////////////////////////xamV4cGlyYXRpb24YIA==",
Node{
v: 2,
id: signature::PublicKey::from("fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0"),
entity_id: signature::PublicKey::from("fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff1"),
expiration: 32,
tls: TLSInfo{
pub_key: signature::PublicKey::from("fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff2"),
_deprecated_next_pub_key: Some(signature::PublicKey::from("fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff3")),
_deprecated_addresses: Some(vec![TLSAddress{
pub_key: signature::PublicKey::from("fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff4"),
address: TCPAddress { ip: Ipv4Addr::new(127, 0, 0, 1).to_ipv6_mapped().octets().to_vec(), port: 123, ..Default::default() }
}])
},
p2p: P2PInfo{
id: signature::PublicKey::from("fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff5"),
..Default::default()
},
consensus: ConsensusInfo{
id: signature::PublicKey::from("fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff6"),
addresses: Some(Vec::new()),
},
vrf: VRFInfo{
id: signature::PublicKey::from("fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7"),
},
runtimes: Some(vec![
NodeRuntime{
id: Namespace::from("8000000000000000000000000000000000000000000000000000000000000010"),
version: Version::from(321u64),
..Default::default()
},
NodeRuntime{
id: Namespace::from("8000000000000000000000000000000000000000000000000000000000000011"),
version: Version::from(123),
capabilities: Capabilities{
tee: Some(CapabilityTEE{
hardware: TEEHardware::TEEHardwareIntelSGX,
rak: signature::PublicKey::from("fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8"),
rek: Some(x25519::PublicKey::from([0;32])),
attestation: vec![0, 1,2,3,4,5],
}),
},
extra_info: Some(vec![5,3,2,1]),
},
]),
..Default::default()
},
),
];
for (encoded_base64, node) in tcs {
println!("{:?}", node);
let dec: Node = cbor::from_slice(&BASE64_STANDARD.decode(encoded_base64).unwrap())
.expect("node should deserialize correctly");
assert_eq!(dec, node, "decoded node should match the expected value");
}
}
#[test]
fn test_runtime_deployments() {
let rt = Runtime::default();
assert_eq!(rt.active_deployment(0), None);
let rt = Runtime {
deployments: vec![
VersionInfo {
version: Version {
major: 0,
minor: 1,
patch: 0,
},
valid_from: 0,
..Default::default()
},
VersionInfo {
version: Version {
major: 0,
minor: 2,
patch: 0,
},
valid_from: 10,
..Default::default()
},
VersionInfo {
version: Version {
major: 0,
minor: 3,
patch: 0,
},
valid_from: 20,
..Default::default()
},
],
..Default::default()
};
let ad = rt.active_deployment(0).unwrap();
assert_eq!(ad.version.minor, 1);
let ad = rt.active_deployment(1).unwrap();
assert_eq!(ad.version.minor, 1);
let ad = rt.active_deployment(9).unwrap();
assert_eq!(ad.version.minor, 1);
let ad = rt.active_deployment(10).unwrap();
assert_eq!(ad.version.minor, 2);
let ad = rt.active_deployment(20).unwrap();
assert_eq!(ad.version.minor, 3);
let ad = rt.active_deployment(50).unwrap();
assert_eq!(ad.version.minor, 3);
let ad = rt.active_deployment(100).unwrap();
assert_eq!(ad.version.minor, 3);
let ad = rt.active_deployment(1000).unwrap();
assert_eq!(ad.version.minor, 3);
let ad = rt
.deployment_for_version(Version {
major: 0,
minor: 1,
patch: 0,
})
.unwrap();
assert_eq!(ad.valid_from, 0);
let ad = rt
.deployment_for_version(Version {
major: 0,
minor: 2,
patch: 0,
})
.unwrap();
assert_eq!(ad.valid_from, 10);
let ad = rt
.deployment_for_version(Version {
major: 0,
minor: 3,
patch: 0,
})
.unwrap();
assert_eq!(ad.valid_from, 20);
let ad = rt.deployment_for_version(Version {
major: 0,
minor: 99,
patch: 0,
});
assert_eq!(ad, None);
}
#[test]
fn test_hash_attestation() {
let report_data = b"foo bar";
let node_id = signature::PublicKey::from(
"47aadd91516ac548decdb436fde957992610facc09ba2f850da0fe1b2be96119",
);
let height = 42;
let rek: [u8; x25519::PUBLIC_KEY_LENGTH] =
"7992610facc09ba2f850da0fe1b2be9611947aadd91516ac548decdb436fde95"
.from_hex::<Vec<u8>>()
.unwrap()
.try_into()
.unwrap();
let rek = x25519::PublicKey::from(rek);
let h = SGXAttestation::hash(report_data, &node_id, height, &rek);
assert_eq!(
h.to_hex::<String>(),
"9a288bd33ba7a4c2eefdee68e4c08c1a34c369302ef8176a3bfdb4fedcec333e"
);
}
#[test]
fn test_roles_mask() {
let mask = RolesMask::ROLE_OBSERVER | RolesMask::ROLE_COMPUTE_WORKER;
assert!(mask.contains(RolesMask::ROLE_OBSERVER));
assert!(mask.contains(RolesMask::ROLE_COMPUTE_WORKER));
assert!(!mask.contains(RolesMask::ROLE_KEY_MANAGER));
assert!(!mask.contains(RolesMask::ROLE_VALIDATOR));
assert!(!mask.contains(RolesMask::ROLE_STORAGE_RPC));
assert!(!mask.is_single_role());
let mask = RolesMask::ROLE_OBSERVER;
assert!(mask.is_single_role());
let node = Node {
roles: RolesMask::ROLE_COMPUTE_WORKER | RolesMask::ROLE_VALIDATOR,
..Default::default()
};
assert!(node.has_roles(RolesMask::ROLE_COMPUTE_WORKER));
assert!(node.has_roles(RolesMask::ROLE_VALIDATOR));
assert!(node.has_roles(RolesMask::ROLE_COMPUTE_WORKER | RolesMask::ROLE_VALIDATOR));
assert!(!node.has_roles(RolesMask::ROLE_KEY_MANAGER));
assert!(!node.has_roles(RolesMask::ROLE_STORAGE_RPC));
}
}