Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions src/contract.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use crate::{
stable_json::stable_stringify,
types::{HtxPhala, Result, WatchdogError},
types::{HtxIntelTdx, Result, WatchdogError},
};
use ethers::{
prelude::*,
Expand Down Expand Up @@ -61,7 +61,7 @@ impl ContractClient {
Ok(count.as_u64())
}

pub async fn submit_htx(&self, htx: &HtxPhala) -> Result<(String, String)> {
pub async fn submit_htx(&self, htx: &HtxIntelTdx) -> Result<(String, String)> {
// Serialize HTX to stable JSON
let json_string = stable_stringify(htx)?;
tracing::debug!("HTX JSON: {}", json_string);
Expand Down
58 changes: 32 additions & 26 deletions src/main.rs
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
mod contract;
mod db;
mod phala;
mod secret;
mod stable_json;
mod types;

use crate::{
contract::ContractClient,
db::SupabaseClient,
phala::extract_phala_htx,
secret::extract_secret_htx,
types::{Result, Workload},
};
use std::time::Duration;
Expand Down Expand Up @@ -113,40 +115,44 @@ async fn process_single_workload(
db_client: &SupabaseClient,
contract_client: &ContractClient,
) -> Result<()> {
match workload.provider.as_str() {
"phala" => {
let htx = match workload.provider.as_str() {
"phala" | "secret" => {
let cvm_url = workload.get_cvm_url().ok_or_else(|| {
types::WatchdogError::Other("Missing cvmUrl in config".to_string())
})?;

info!("🔄 Processing Phala workload: {}", workload.name);

// Extract HTX
let htx = extract_phala_htx(&cvm_url).await?;

// Check node count
let node_count = contract_client.node_count().await?;
if node_count == 0 {
warn!("⚠️ No nodes registered, skipping HTX submission");
return Ok(());
}

// Submit HTX
let (tx_hash, htx_id) = contract_client.submit_htx(&htx).await?;

info!(
"📤 HTX submitted - txHash: {}, htxId: {}, nodeCount: {}",
tx_hash, htx_id, node_count
"🔄 Processing workload: {} (provider: {})",
workload.name, workload.provider
);

// Update last heartbeat
db_client.update_heartbeat(&workload.id).await?;

Ok(())
match workload.provider.as_str() {
"phala" => extract_phala_htx(&cvm_url).await?,
"secret" => extract_secret_htx(&cvm_url).await?,
_ => unreachable!(),
}
}
other => {
warn!("⚠️ Provider '{}' not yet supported", other);
Ok(())
return Ok(());
}
};

// Check node count
let node_count = contract_client.node_count().await?;
if node_count == 0 {
warn!("⚠️ No nodes registered, skipping HTX submission");
return Ok(());
}

// Submit HTX
let (tx_hash, htx_id) = contract_client.submit_htx(&htx).await?;

info!(
"📤 HTX submitted - txHash: {}, htxId: {}, nodeCount: {}",
tx_hash, htx_id, node_count
);

// Update last heartbeat
db_client.update_heartbeat(&workload.id).await?;

Ok(())
}
6 changes: 3 additions & 3 deletions src/phala.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use crate::types::{AttestData, HtxPhala, PhalaAttestationResponse, PhalaInfoResponse, Result};
use crate::types::{AttestData, HtxIntelTdx, PhalaAttestationResponse, PhalaInfoResponse, Result};
use reqwest::Client;

pub async fn extract_phala_htx(cvm_url: &str) -> Result<HtxPhala> {
pub async fn extract_phala_htx(cvm_url: &str) -> Result<HtxIntelTdx> {
let client = Client::new();
let base_url = cvm_url.trim_end_matches('/');

Expand All @@ -14,7 +14,7 @@ pub async fn extract_phala_htx(cvm_url: &str) -> Result<HtxPhala> {
let attestation: PhalaAttestationResponse =
client.get(&attestation_url).send().await?.json().await?;

Ok(HtxPhala {
Ok(HtxIntelTdx {
provider: "phala".to_string(),
app_compose: info.tcb_info.app_compose,
attest_data: AttestData {
Expand Down
27 changes: 27 additions & 0 deletions src/secret.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
use crate::types::{
AttestData, HtxIntelTdx, Result, SecretAttestationResponse, SecretSelfResponse,
};
use reqwest::Client;

pub async fn extract_secret_htx(cvm_url: &str) -> Result<HtxIntelTdx> {
let client = Client::new();
let base_url = cvm_url.trim_end_matches('/');

// Fetch /info endpoint
let info_url = format!("{}/self", base_url);
let info: SecretSelfResponse = client.get(&info_url).send().await?.json().await?;

// Fetch /attestation endpoint
let attestation_url = format!("{}/cpu", base_url);
let attestation: SecretAttestationResponse =
client.get(&attestation_url).send().await?.json().await?;

Ok(HtxIntelTdx {
provider: "secret".to_string(),
app_compose: info.tcb_info.app_compose,
attest_data: AttestData {
quote: attestation.quote,
event_log: attestation.event_log,
},
})
}
15 changes: 14 additions & 1 deletion src/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ impl Workload {
}

#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct HtxPhala {
pub struct HtxIntelTdx {
pub provider: String,
pub app_compose: String,
pub attest_data: AttestData,
Expand All @@ -45,6 +45,11 @@ pub struct PhalaInfoResponse {
pub tcb_info: TcbInfo,
}

#[derive(Debug, Deserialize)]
pub struct SecretSelfResponse {
pub tcb_info: TcbInfo,
}

#[derive(Debug, Deserialize)]
pub struct TcbInfo {
pub app_compose: String,
Expand All @@ -58,6 +63,14 @@ pub struct PhalaAttestationResponse {
pub vm_config: String, // Ignored - not included in HTX
}

#[derive(Debug, Deserialize)]
pub struct SecretAttestationResponse {
pub quote: String,
pub event_log: String,
#[allow(dead_code)]
pub vm_config: String, // Ignored - not included in HTX
}

#[derive(thiserror::Error, Debug)]
pub enum WatchdogError {
#[error("HTTP error: {0}")]
Expand Down