Skip to content
Merged
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
6 changes: 6 additions & 0 deletions crates/cli/src/program.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,9 @@ pub enum ProgramCommands {
/// Get a shell into the docker environment that the program runs in
Shell,

/// Upload the compiled program to Pinata IPFS
Upload,

/// Commands to manage the program compilation cache
Cache {
#[command(subcommand)]
Expand All @@ -50,6 +53,9 @@ pub async fn execute(command: ProgramCommands, config: &AppConfig) -> Result<()>
e3_support_scripts::program_compile(config.program().clone(), dev).await?
}
ProgramCommands::Shell => e3_support_scripts::program_shell().await?,
ProgramCommands::Upload => {
e3_support_scripts::program_upload(config.program().clone(), None).await?
}
ProgramCommands::Cache { command } => match command {
ProgramCacheCommands::Purge => e3_support_scripts::program_cache_purge().await?,
},
Expand Down
37 changes: 27 additions & 10 deletions crates/config/src/app_config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -91,21 +91,41 @@ impl Default for NodeDefinition {
}

#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
pub struct Risc0Config {
pub struct BoundlessConfig {
/// RPC URL for blockchain (e.g., Sepolia)
pub rpc_url: String,
/// Private key for submitting requests
pub private_key: String,
/// Pinata JWT for uploading programs/inputs
#[serde(default)]
pub bonsai_api_key: Option<String>,
pub pinata_jwt: Option<String>,
/// Pre-uploaded program URL (if program is already on IPFS)
#[serde(default)]
pub bonsai_api_url: Option<String>,
pub program_url: Option<String>,
/// Submit requests onchain (true) or offchain (false)
#[serde(default = "default_true")]
pub onchain: bool,
}

fn default_true() -> bool {
true
}

#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
pub struct Risc0Config {
/// Dev mode: 0 = production, 1 = dev mode (fake proofs)
#[serde(default)]
pub risc0_dev_mode: u8,
/// Boundless configuration
#[serde(default)]
pub boundless: Option<BoundlessConfig>,
}

impl Default for Risc0Config {
fn default() -> Self {
Risc0Config {
bonsai_api_key: None,
bonsai_api_url: None,
risc0_dev_mode: 0,
risc0_dev_mode: 1, // Default to dev mode for safety
boundless: None,
}
}
}
Comment thread
hmzakhalid marked this conversation as resolved.
Expand Down Expand Up @@ -491,8 +511,6 @@ node:

program:
risc0:
bonsai_api_key: "12345678"
bonsai_api_url: "http://my.api.com"
risc0_dev_mode: 0

nodes:
Expand Down Expand Up @@ -530,9 +548,8 @@ nodes:
assert_eq!(
config.program().risc0(),
Some(&Risc0Config {
bonsai_api_key: Some("12345678".to_string()),
bonsai_api_url: Some("http://my.api.com".to_string()),
risc0_dev_mode: 0,
boundless: None,
})
);
assert!(config.peers().is_empty());
Expand Down
90 changes: 62 additions & 28 deletions crates/program-server/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -135,40 +135,54 @@ pub struct AppConfig {
pub localhost_rewrite: Option<String>,
}

async fn call_webhook(
callback_url: &str,
e3_id: u64,
proof: Vec<u8>,
ciphertext: Vec<u8>,
) -> Result<()> {
println!("call_webhook()");
let payload = WebhookPayload {
e3_id,
ciphertext,
proof,
async fn call_webhook(callback_url: &str, payload: WebhookPayload) -> Result<()> {
let e3_id = match &payload {
WebhookPayload::Completed { e3_id, .. } => *e3_id,
WebhookPayload::Failed { e3_id, .. } => *e3_id,
};

match &payload {
WebhookPayload::Completed {
ciphertext, proof, ..
} => {
println!(
"call_webhook() - status: Completed, ciphertext len: {}, proof len: {}",
ciphertext.len(),
proof.len()
);
}
WebhookPayload::Failed { error, .. } => {
println!("call_webhook() - status: Failed, error: {}", error);
}
}

println!("callback_url: {}", callback_url);

reqwest::Client::new()
let response = reqwest::Client::new()
.post(callback_url)
.json(&payload)
.send()
.await?
.error_for_status()?;
.await?;

println!("Webhook response status: {}", response.status());
if !response.status().is_success() {
let error_body = response.text().await?;
println!("Webhook error response: {}", error_body);
return Err(anyhow::anyhow!(
"Webhook failed with status and body: {}",
error_body
));
}

response.error_for_status()?;
println!("✓ Webhook called successfully for E3 {}", e3_id);
Ok(())
}

async fn handle_webhook_delivery(
e3_id: u64,
callback_url: &str,
proof: Vec<u8>,
ciphertext: Vec<u8>,
) -> Result<()> {
async fn handle_webhook_delivery(callback_url: &str, payload: WebhookPayload) -> Result<()> {
println!("handle_webhook_delivery()");
call_webhook(callback_url, e3_id, proof, ciphertext).await?;
println!("✓ Webhook sent successfully for E3 {}", e3_id);
call_webhook(callback_url, payload).await?;
println!("✓ Webhook sent successfully");
Ok(())
}

Expand All @@ -178,12 +192,32 @@ async fn process_computation_background(
callback_url: &str,
fhe_inputs: FHEInputs,
) -> Result<()> {
let (proof, ciphertext) = runner(fhe_inputs).await?;
println!("computation finished!");
println!("handling webhook delivery...");
handle_webhook_delivery(e3_id, callback_url, proof, ciphertext).await?;
println!("✓ Computation completed for E3 {}", e3_id);
Ok(())
match runner(fhe_inputs).await {
Ok((proof, ciphertext)) => {
println!("computation finished!");
println!("handling webhook delivery...");
let payload = WebhookPayload::Completed {
e3_id,
ciphertext,
proof,
};
handle_webhook_delivery(callback_url, payload).await?;
println!("✓ Computation completed for E3 {}", e3_id);
Ok(())
}
Err(e) => {
let error_msg = e.to_string();
eprintln!("Computation failed for E3 {}: {}", e3_id, error_msg);

let payload = WebhookPayload::Failed {
e3_id,
error: format!("Compute failed: {}", error_msg),
};
handle_webhook_delivery(callback_url, payload).await?;

Err(e)
}
}
}

async fn handle_compute(
Expand Down
46 changes: 34 additions & 12 deletions crates/program-server/src/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,16 +24,25 @@ pub struct ComputeRequest {
pub callback_url: Option<String>,
}

#[derive(Derivative, Serialize)]
#[derive(Derivative, Serialize, Deserialize)]
#[derivative(Debug)]
pub struct WebhookPayload {
pub e3_id: u64,
#[serde(serialize_with = "serialize_as_hex")]
#[derivative(Debug = "ignore")]
pub ciphertext: Vec<u8>,
#[serde(serialize_with = "serialize_as_hex")]
#[derivative(Debug = "ignore")]
pub proof: Vec<u8>,
#[serde(tag = "status", rename_all = "lowercase")]
pub enum WebhookPayload {
Completed {
e3_id: u64,
#[serde(serialize_with = "serialize_as_hex")]
#[serde(deserialize_with = "deserialize_hex_string")]
#[derivative(Debug = "ignore")]
ciphertext: Vec<u8>,
#[serde(serialize_with = "serialize_as_hex")]
#[serde(deserialize_with = "deserialize_hex_string")]
#[derivative(Debug = "ignore")]
proof: Vec<u8>,
},
Failed {
e3_id: u64,
error: String,
},
}

fn serialize_as_hex<S>(bytes: &Vec<u8>, serializer: S) -> Result<S::Ok, S::Error>
Expand Down Expand Up @@ -140,15 +149,28 @@ mod tests {
}

#[test]
fn test_webhook_payload_serialization() {
let payload = WebhookPayload {
fn test_webhook_payload_serialization_completed() {
let payload = WebhookPayload::Completed {
e3_id: 12345,
ciphertext: vec![0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef],
proof: vec![0xde, 0xad, 0xbe, 0xef],
};

let json = serde_json::to_string(&payload).expect("Failed to serialize");
let expected = r#"{"e3_id":12345,"ciphertext":"0x0123456789abcdef","proof":"0xdeadbeef"}"#;
let expected = r#"{"status":"completed","e3_id":12345,"ciphertext":"0x0123456789abcdef","proof":"0xdeadbeef"}"#;

assert_eq!(json, expected);
}

#[test]
fn test_webhook_payload_serialization_failed() {
let payload = WebhookPayload::Failed {
e3_id: 12345,
error: "Computation failed".to_string(),
};

let json = serde_json::to_string(&payload).expect("Failed to serialize");
let expected = r#"{"status":"failed","e3_id":12345,"error":"Computation failed"}"#;

assert_eq!(json, expected);
}
Expand Down
51 changes: 37 additions & 14 deletions crates/support-scripts/ctl/start
Original file line number Diff line number Diff line change
@@ -1,21 +1,31 @@
#!/usr/bin/env bash

# Clear any existing environment variables
unset API_KEY API_URL RISC0_DEV_MODE
unset RISC0_DEV_MODE RPC_URL PRIVATE_KEY PINATA_JWT PROGRAM_URL BOUNDLESS_ONCHAIN

# Parse command line arguments
while [[ $# -gt 0 ]]; do
case $1 in
--api-key)
API_KEY="$2"
--risc0-dev-mode)
RISC0_DEV_MODE="$2"
shift 2
;;
--api-url)
API_URL="$2"
--rpc-url)
RPC_URL="$2"
shift 2
;;
--risc0-dev-mode)
RISC0_DEV_MODE="$2"
--private-key)
PRIVATE_KEY="$2"
shift 2
;;
--pinata-jwt)
PINATA_JWT="$2"
shift 2
;;
--program-url)
PROGRAM_URL="$2"
shift 2
;;
--boundless-onchain)
BOUNDLESS_ONCHAIN="$2"
shift 2
;;
*)
Expand All @@ -32,11 +42,24 @@ if [[ -n "$RISC0_DEV_MODE" ]]; then
CONTAINER_ARGS+=("--risc0-dev-mode" "$RISC0_DEV_MODE")
fi

if [[ -n "$API_KEY" && -n "$API_URL" ]]; then
CONTAINER_ARGS+=("--api-key" "$API_KEY" "--api-url" "$API_URL")
elif [[ -n "$API_KEY" || -n "$API_URL" ]]; then
echo "Error: Both --api-key and --api-url must be provided together, or neither"
if [[ -n "$RPC_URL" && -n "$PRIVATE_KEY" ]]; then
CONTAINER_ARGS+=("--rpc-url" "$RPC_URL")
CONTAINER_ARGS+=("--private-key" "$PRIVATE_KEY")
elif [[ -n "$RPC_URL" || -n "$PRIVATE_KEY" ]]; then
echo "Error: Both --rpc-url and --private-key must be provided together, or neither"
exit 1
fi

exec "$SCRIPT_DIR/container" "${CONTAINER_ARGS[@]}"
if [[ -n "$PINATA_JWT" ]]; then
CONTAINER_ARGS+=("--pinata-jwt" "$PINATA_JWT")
fi

if [[ -n "$PROGRAM_URL" ]]; then
CONTAINER_ARGS+=("--program-url" "$PROGRAM_URL")
fi

if [[ -n "$BOUNDLESS_ONCHAIN" ]]; then
CONTAINER_ARGS+=("--boundless-onchain" "$BOUNDLESS_ONCHAIN")
fi

exec "$SCRIPT_DIR/container" "${CONTAINER_ARGS[@]}"
24 changes: 24 additions & 0 deletions crates/support-scripts/ctl/upload
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
#!/usr/bin/env bash

PINATA_JWT=""
while [[ $# -gt 0 ]]; do
case $1 in
--pinata-jwt)
PINATA_JWT="$2"
shift 2
;;
*)
echo "Unknown argument: $1"
exit 1
;;
esac
done

SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
CONTAINER_ARGS=("./scripts/container/upload.sh")

if [[ -n "$PINATA_JWT" ]]; then
CONTAINER_ARGS+=("--pinata-jwt" "$PINATA_JWT")
fi

exec "$SCRIPT_DIR/container" "${CONTAINER_ARGS[@]}"
5 changes: 5 additions & 0 deletions crates/support-scripts/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,11 @@ pub async fn program_start(program_config: ProgramConfig, is_dev: Option<bool>)
ProgramSupport::new(program_config, is_dev).start().await
}

/// Upload the compiled program to Pinata IPFS
Comment thread
hmzakhalid marked this conversation as resolved.
pub async fn program_upload(program_config: ProgramConfig, is_dev: Option<bool>) -> Result<()> {
ProgramSupport::new(program_config, is_dev).upload().await
}

/// Open up a shell in the docker container
pub async fn program_shell() -> Result<()> {
let cwd = env::current_dir()?;
Expand Down
7 changes: 7 additions & 0 deletions crates/support-scripts/src/program.rs
Original file line number Diff line number Diff line change
Expand Up @@ -48,4 +48,11 @@ impl ProgramSupportApi for ProgramSupport {
ProgramSupport::Risc0(s) => s.start().await,
}
}

async fn upload(&self) -> Result<()> {
match self {
ProgramSupport::Dev(s) => s.upload().await,
ProgramSupport::Risc0(s) => s.upload().await,
}
}
}
Loading
Loading