use std::{
collections::{BTreeMap, BTreeSet},
sync::LazyLock,
};
use anyhow::Context;
use lexe_api::def::AppNodeProvisionApi;
use lexe_common::{
api::{provision::NodeProvisionRequest, version::NodeEnclave},
constants,
releases::Release,
};
use lexe_enclave::enclave;
use lexe_node_client::client::NodeClient;
use lexe_tokio::task::LxTask;
use serde::Deserialize;
use tracing::{info, info_span, warn};
use crate::{config::WalletEnv, types::auth::RootSeed};
pub static RELEASES_JSON: &str = include_str!("../../data/releases.json");
pub static LATEST_TRUSTED_MEASUREMENTS: LazyLock<
BTreeSet<enclave::Measurement>,
> = LazyLock::new(|| {
trusted_node_releases()
.values()
.rev()
.take(constants::RELEASE_WINDOW_SIZE)
.map(|release| release.measurement)
.collect()
});
#[derive(Deserialize)]
pub struct ReleasesJson(
pub BTreeMap<String, BTreeMap<semver::Version, Release>>,
);
pub fn trusted_node_releases() -> BTreeMap<semver::Version, Release> {
releases_json().0.remove("node").unwrap_or_default()
}
pub fn releases_json() -> ReleasesJson {
serde_json::from_str(RELEASES_JSON).expect("Invalid releases.json")
}
pub(crate) async fn provision_all(
node_client: NodeClient,
mut enclaves_to_provision: BTreeSet<NodeEnclave>,
root_seed: RootSeed,
wallet_env: WalletEnv,
google_auth_code: Option<String>,
allow_gvfs_access: bool,
encrypted_seed: Option<Vec<u8>>,
) -> anyhow::Result<()> {
info!("Starting provisioning: {enclaves_to_provision:?}");
let latest = match enclaves_to_provision.pop_last() {
Some(enclave) => enclave,
None => {
info!("No enclaves to provision");
return Ok(());
}
};
let root_seed_clone = clone_root_seed(&root_seed);
provision_one(
&node_client,
latest,
root_seed_clone,
wallet_env,
google_auth_code.clone(),
allow_gvfs_access,
encrypted_seed.clone(),
)
.await?;
if enclaves_to_provision.is_empty() {
return Ok(());
}
const SPAN_NAME: &str = "(secondary-provision)";
let task =
LxTask::spawn_with_span(SPAN_NAME, info_span!(SPAN_NAME), async move {
for node_enclave in enclaves_to_provision {
let root_seed_clone = clone_root_seed(&root_seed);
let provision_result = provision_one(
&node_client,
node_enclave.clone(),
root_seed_clone,
wallet_env,
google_auth_code.clone(),
allow_gvfs_access,
encrypted_seed.clone(),
)
.await;
if let Err(e) = provision_result {
warn!(
version = %node_enclave.version,
measurement = %node_enclave.measurement,
machine_id = %node_enclave.machine_id,
"Secondary provision failed: {e:#}"
);
}
}
info!("Secondary provisioning complete");
});
task.detach();
Ok(())
}
async fn provision_one(
node_client: &NodeClient,
enclave: NodeEnclave,
root_seed: RootSeed,
wallet_env: WalletEnv,
google_auth_code: Option<String>,
allow_gvfs_access: bool,
encrypted_seed: Option<Vec<u8>>,
) -> anyhow::Result<()> {
let provision_req = NodeProvisionRequest {
root_seed: root_seed.into_unstable(),
deploy_env: wallet_env.deploy_env,
network: wallet_env.network,
google_auth_code,
allow_gvfs_access,
encrypted_seed,
};
node_client
.provision(enclave.measurement, provision_req)
.await
.context("Failed to provision node")?;
info!(
version = %enclave.version,
measurement = %enclave.measurement,
machine_id = %enclave.machine_id,
"Provision success:"
);
Ok(())
}
pub fn clone_root_seed(root_seed_ref: &RootSeed) -> RootSeed {
RootSeed::from_bytes(root_seed_ref.as_bytes())
.expect("RootSeed always contains 32 bytes")
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn test_trusted_measurements() {
assert!(!LATEST_TRUSTED_MEASUREMENTS.is_empty());
}
}