diff --git a/.env.docker b/.env.docker index fbe17bf..46a3f82 100644 --- a/.env.docker +++ b/.env.docker @@ -8,4 +8,5 @@ APP_PROVING_RESP_SUB_ID=askeladd.proving.response APP_USER_BECH32_SK=nsec1tsaxyqcxp8atqup4facwp0as52f2c0evj4cxpw6yaqetusu7sg8qqzkr3k # Prover agent's secret key # DO NOT USE THIS KEY IN PRODUCTION -APP_PROVER_AGENT_SK=nsec18s6wcqlkglhjmfz3tnjkh0qscf6cqen96ecp5k5k73ktew3l97tstuvy2x \ No newline at end of file +APP_PROVER_AGENT_SK=nsec18s6wcqlkglhjmfz3tnjkh0qscf6cqen96ecp5k5k73ktew3l97tstuvy2x +APP_PROVER_AGENT_PK=npub1efldztlt3z3xs2f8fwpmscvus90g6tvv9g6f9c8hduuh0rfhzeaqvxpnkq \ No newline at end of file diff --git a/README.md b/README.md index 4107508..af1ec12 100644 --- a/README.md +++ b/README.md @@ -39,6 +39,7 @@ Typical flow: - [ ] Use [NIP-90 - Data Vending Machine](https://nips.nostr.com/90) to define interaction between Service Providers (prover agents) and customers (users needing to generate proofs). - Check mechanism to see how it works. + - Check for more information. - [ ] Use [NIP-89 -Recommended Application Handlers](https://nips.nostr.com/89) for prover agents to advertise their support for certain types of proving requests, their pricing model, etc. - [ ] Use [NIP-57 - Lightning Zaps](https://nips.nostr.com/57) to handle the payment for the proofs. - [ ] Use [NIP-13 - Proof of Work](https://nips.nostr.com/13) for spam protection. diff --git a/crates/cli/src/prover_agent.rs b/crates/cli/src/prover_agent.rs index 290e2b6..e6eb7ad 100644 --- a/crates/cli/src/prover_agent.rs +++ b/crates/cli/src/prover_agent.rs @@ -1,137 +1,28 @@ use askeladd::config::Settings; -use askeladd::db::{Database, RequestStatus}; -use askeladd::prover_service::ProverService; -use askeladd::types::{FibonnacciProvingRequest, FibonnacciProvingResponse}; +use askeladd::dvm::service_provider::ServiceProvider; use dotenv::dotenv; -use nostr_sdk::prelude::*; - -#[macro_use] -extern crate log; #[tokio::main] -async fn main() -> Result<()> { - // Initialize logger and set default level to info +async fn main() -> Result<(), Box> { + // ****************************************************** + // ****************** SETUP ***************************** + // ****************************************************** pretty_env_logger::formatted_builder() .filter_level(log::LevelFilter::Info) .init(); - - // Load configuration from .env filed dotenv().ok(); let settings = Settings::new().expect("Failed to load settings"); - let user_secret_key = SecretKey::from_bech32(&settings.user_bech32_sk)?; - let user_keys = Keys::new(user_secret_key); - let user_public_key = user_keys.public_key(); - - let prover_agent_keys = Keys::new(SecretKey::from_bech32(&settings.prover_agent_sk).unwrap()); - - let opts = Options::new().wait_for_send(false); - let client = Client::with_opts(&prover_agent_keys, opts); - - for relay in settings.subscribed_relays { - client.add_relay(&relay).await?; - } - - client.connect().await; - debug!("Nostr client connected to relays."); - - let proving_req_sub_id = SubscriptionId::new(settings.proving_req_sub_id); - let filter = Filter::new().kind(Kind::TextNote).author(user_public_key); - - client - .subscribe_with_id(proving_req_sub_id.clone(), vec![filter], None) - .await - .expect("Failed to subscribe to proving requests"); - - let db = - Database::new(settings.db_path.to_str().unwrap()).expect("Failed to initialize database"); - - let proving_service: ProverService = Default::default(); - - info!("Subscribed to proving requests, waiting for requests..."); - client - .handle_notifications(|notification| async { - if let RelayPoolNotification::Event { - subscription_id, - event, - .. - } = notification - { - if subscription_id == proving_req_sub_id { - info!("Proving request received [{}]", event.id.to_string()); - - // Deserialize the request - if let Ok(request) = - serde_json::from_str::(&event.content) - { - // Check if request has already been processed - if let Ok(Some(status)) = db.get_request_status(&request.request_id) { - match status { - RequestStatus::Completed => { - info!( - "Request {} already processed, skipping", - request.request_id - ); - return Ok(false); - } - RequestStatus::Failed => { - info!("Request {} failed before, retrying", request.request_id); - } - RequestStatus::Pending => { - info!( - "Request {} is already pending, skipping", - request.request_id - ); - return Ok(false); - } - } - } else { - // New request, insert into database - if let Err(e) = db.insert_request(&request) { - error!("Failed to insert request into database: {}", e); - return Ok(false); - } - } - - // Generate the proof - match proving_service.generate_proof(request.clone()) { - Ok(response) => { - // Serialize the response to JSON - let response_json = serde_json::to_string(&response)?; - - // Publish the proving response - let tags = vec![]; - let event_id = - client.publish_text_note(response_json, tags).await?; - info!("Proving response published [{}]", event_id.to_string()); + // ****************************************************** + // ****************** INIT SERVICE PROVIDER ************* + // ****************************************************** + let mut service_provider = ServiceProvider::new(settings)?; + service_provider.init().await?; - // Update database - if let Err(e) = db.update_request( - &response.request_id, - &response, - RequestStatus::Completed, - ) { - error!("Failed to update request in database: {}", e); - } - } - Err(e) => { - error!("Proof generation failed: {}", e); - // Update database with failed status - if let Err(db_err) = db.update_request( - &request.request_id, - &FibonnacciProvingResponse::default(), - RequestStatus::Failed, - ) { - error!("Failed to update request in database: {}", db_err); - } - } - } - } - } - } - Ok(false) - }) - .await?; + // ****************************************************** + // ****************** RUN SERVICE PROVIDER ************** + // ****************************************************** + service_provider.run().await?; Ok(()) } diff --git a/crates/cli/src/user_cli.rs b/crates/cli/src/user_cli.rs index 573f3d2..176d9c9 100644 --- a/crates/cli/src/user_cli.rs +++ b/crates/cli/src/user_cli.rs @@ -1,106 +1,60 @@ use askeladd::config::Settings; -use askeladd::types::{FibonnacciProvingRequest, FibonnacciProvingResponse}; -use askeladd::verifier_service::VerifierService; +use askeladd::dvm::customer::Customer; +use askeladd::dvm::types::GenerateZKPJobRequest; +use askeladd::types::FibonnacciProvingRequest; use dotenv::dotenv; -use nostr_sdk::prelude::*; -use tokio::time::{sleep, Duration}; -use uuid::Uuid; -#[macro_use] -extern crate log; +use log::info; #[tokio::main] -async fn main() -> Result<()> { - // Initialize logger and set default level to info +async fn main() -> Result<(), Box> { + // ****************************************************** + // ****************** SETUP ***************************** + // ****************************************************** pretty_env_logger::formatted_builder() .filter_level(log::LevelFilter::Info) .init(); - - info!("User agent starting..."); - info!("Waiting 5 seconds before submitting proving request..."); - // Add a delay before connecting - sleep(Duration::from_secs(5)).await; - - // Load configuration from .env file dotenv().ok(); let settings = Settings::new().expect("Failed to load settings"); - let prover_agent_secret_key = SecretKey::from_bech32(&settings.prover_agent_sk)?; - let prover_agent_keys = Keys::new(prover_agent_secret_key); - let prover_agent_public_key = prover_agent_keys.public_key(); - let user_keys = Keys::new(SecretKey::from_bech32(&settings.user_bech32_sk).unwrap()); - - let opts = Options::new().wait_for_send(false); - let client = Client::with_opts(&user_keys, opts); - - for relay in settings.subscribed_relays { - client.add_relay(&relay).await?; - } - - client.connect().await; - debug!("Nostr client connected to relays."); - - // Generate a unique request ID - let request_id = Uuid::new_v4().to_string(); - - // Create a proving request - let proving_request = FibonnacciProvingRequest { - request_id: request_id.clone(), - log_size: 5, - claim: 443693538, + // ****************************************************** + // ****************** INIT CUSTOMER ********************* + // ****************************************************** + let mut customer = Customer::new(settings)?; + customer.init().await?; + + // ****************************************************** + // ****************** PREPARE JOB *********************** + // ****************************************************** + let job_id = GenerateZKPJobRequest::new_job_id(); + + let job_request = GenerateZKPJobRequest { + job_id, + request: FibonnacciProvingRequest { + log_size: 5, + claim: 443693538, + }, }; - // Serialize the request to JSON - let request_json = serde_json::to_string(&proving_request)?; - - // Publish the proving request - debug!("Publishing proving request..."); - let event_id = client.publish_text_note(request_json, []).await?; - - info!("Proving request published [{}]", event_id.to_string()); - - // Subscribe to proving responses - let proving_resp_sub_id = SubscriptionId::new(settings.proving_resp_sub_id); - let filter = Filter::new() - .kind(Kind::TextNote) - .author(prover_agent_public_key) - .since(Timestamp::now()); - - client - .subscribe_with_id(proving_resp_sub_id.clone(), vec![filter], None) - .await - .expect("Failed to subscribe to proving responses"); - - // Handle subscription notifications - client - .handle_notifications(|notification| async { - if let RelayPoolNotification::Event { - subscription_id, - event, - .. - } = notification - { - if subscription_id == proving_resp_sub_id { - info!("Proving response received [{}]", event.id.to_string()); - - // Deserialize the response - if let Ok(response) = - serde_json::from_str::(&event.content) - { - // Verify the proof - let verifier_service: VerifierService = Default::default(); - info!("Verifying proof..."); - match verifier_service.verify_proof(response) { - Ok(_) => info!("Proof successfully verified"), - Err(e) => error!("Proof verification failed: {}", e), - } - // Stop listening after receiving and verifying the response - return Ok(true); - } - } - } - Ok(false) - }) + // ****************************************************** + // ****************** SUBMIT JOB ************************ + // ****************************************************** + info!("Submitting job with id: {}", job_request.job_id); + customer.submit_job(job_request.clone()).await?; + + // ****************************************************** + // ****************** WAIT FOR JOB RESULT *************** + // ****************************************************** + info!("Waiting for job result with id: {}", job_request.job_id); + let job_result = customer + .wait_for_job_result(&job_request.job_id, 60) .await?; + // ****************************************************** + // ****************** VERIFY PROOF ********************** + // ****************************************************** + info!("Verifying proof with id: {}", job_request.job_id); + let is_valid = customer.verify_proof(&job_result)?; + info!("Proof is valid: {}", is_valid); + Ok(()) } diff --git a/crates/core/Cargo.toml b/crates/core/Cargo.toml index 5b541be..16d69db 100644 --- a/crates/core/Cargo.toml +++ b/crates/core/Cargo.toml @@ -11,3 +11,7 @@ thiserror = "1.0.62" config = "0.14.0" dotenv = "0.15" rusqlite = { version = "0.32.0", features = ["bundled"] } +nostr-sdk = "0.33.0" +log = "0.4.22" +tokio = { version = "1", default-features = false } +uuid = { version = "1.3", features = ["v4"] } diff --git a/crates/core/src/config.rs b/crates/core/src/config.rs index 5184cc7..90ea605 100644 --- a/crates/core/src/config.rs +++ b/crates/core/src/config.rs @@ -19,6 +19,7 @@ pub struct Settings { pub proving_resp_sub_id: String, pub user_bech32_sk: String, pub prover_agent_sk: String, + pub prover_agent_pk: String, #[serde(default = "default_db_path")] pub db_path: PathBuf, } @@ -64,3 +65,9 @@ impl Settings { Ok(settings) } } + +impl Default for Settings { + fn default() -> Self { + Self::new().expect("Failed to load settings") + } +} diff --git a/crates/core/src/db.rs b/crates/core/src/db.rs index 744595c..f898889 100644 --- a/crates/core/src/db.rs +++ b/crates/core/src/db.rs @@ -36,15 +36,11 @@ impl Database { Ok(()) } - pub fn insert_request(&self, request: &FibonnacciProvingRequest) -> Result<()> { + pub fn insert_request(&self, job_id: &str, request: &FibonnacciProvingRequest) -> Result<()> { let request_json = serde_json::to_string(request).unwrap(); self.conn.execute( "INSERT INTO requests (id, request_json, status) VALUES (?1, ?2, ?3)", - params![ - request.request_id, - request_json, - RequestStatus::Pending.to_string() - ], + params![job_id, request_json, RequestStatus::Pending.to_string()], )?; Ok(()) } @@ -52,10 +48,13 @@ impl Database { pub fn update_request( &self, request_id: &str, - response: &FibonnacciProvingResponse, + response: Option<&FibonnacciProvingResponse>, status: RequestStatus, ) -> Result<()> { - let response_json = serde_json::to_string(response).unwrap(); + let response_json = match response { + Some(response) => serde_json::to_string(response).unwrap(), + None => "".to_string(), + }; self.conn.execute( "UPDATE requests SET response_json = ?1, status = ?2, updated_at = CURRENT_TIMESTAMP WHERE id = ?3", params![response_json, status.to_string(), request_id], diff --git a/crates/core/src/dvm/customer.rs b/crates/core/src/dvm/customer.rs new file mode 100644 index 0000000..daddd5d --- /dev/null +++ b/crates/core/src/dvm/customer.rs @@ -0,0 +1,167 @@ +use std::time::Duration; + +use log::{debug, error, info}; +use nostr_sdk::prelude::*; +use thiserror::Error; +use tokio::time::timeout; + +use crate::config::Settings; +use crate::dvm::types::{GenerateZKPJobRequest, GenerateZKPJobResult}; +use crate::verifier_service::VerifierService; + +pub struct Customer { + settings: Settings, + nostr_client: Client, + verifier_service: VerifierService, +} + +#[derive(Error, Debug)] +pub enum CustomerError { + #[error("Failed to connect to Nostr relay: {0}")] + NostrConnectionError(String), + #[error("Failed to subscribe to Nostr events: {0}")] + NostrSubscriptionError(String), + #[error("Nostr client error: {0}")] + NostrClientError(#[from] nostr_sdk::client::Error), + #[error("JSON serialization error: {0}")] + SerializationError(#[from] serde_json::Error), + #[error("Proof verification error: {0}")] + VerificationError(String), + #[error("Job timed out: {0}")] + JobTimeout(String), + #[error("Unknown error: {0}")] + Unknown(String), +} + +impl Customer { + pub fn new(settings: Settings) -> Result { + let user_keys = Keys::new(SecretKey::from_bech32(&settings.user_bech32_sk).unwrap()); + let opts = Options::new().wait_for_send(false); + let client = Client::with_opts(&user_keys, opts); + + Ok(Self { + settings, + nostr_client: client, + verifier_service: Default::default(), + }) + } + + pub async fn init(&mut self) -> Result<(), CustomerError> { + for relay in &self.settings.subscribed_relays { + self.nostr_client + .add_relay(relay) + .await + .map_err(|e| CustomerError::NostrConnectionError(e.to_string()))?; + } + self.nostr_client.connect().await; + debug!("Nostr client connected to relays."); + Ok(()) + } + + pub async fn submit_job(&self, job: GenerateZKPJobRequest) -> Result<(), CustomerError> { + let request_json = serde_json::to_string(&job)?; + debug!("Publishing proving request..."); + let event_id = self + .nostr_client + .publish_text_note(request_json, []) + .await?; + info!("Proving request published [{}]", event_id.to_string()); + Ok(()) + } + + pub async fn wait_for_job_result( + &self, + job_id: &str, + timeout_secs: u64, + ) -> Result { + let proving_resp_sub_id = SubscriptionId::new(&self.settings.proving_resp_sub_id); + let prover_agent_public_key = PublicKey::from_bech32(&self.settings.prover_agent_pk) + .map_err(|e| CustomerError::Unknown(format!("Failed to parse public key: {}", e)))?; + let filter = Filter::new() + .kind(Kind::TextNote) + .author(prover_agent_public_key) + .since(Timestamp::now() - Duration::from_secs(10)); + + self.nostr_client + .subscribe_with_id(proving_resp_sub_id.clone(), vec![filter], None) + .await + .map_err(|e| CustomerError::NostrSubscriptionError(e.to_string()))?; + + let timeout_duration = Duration::from_secs(timeout_secs); + timeout( + timeout_duration, + self.listen_for_job_result(job_id, proving_resp_sub_id), + ) + .await + .map_err(|_| CustomerError::JobTimeout(job_id.to_string()))? + } + + async fn listen_for_job_result( + &self, + job_id: &str, + subscription_id: SubscriptionId, + ) -> Result { + let job_id = job_id.to_string(); + let subscription_id = subscription_id.clone(); + + self.nostr_client + .handle_notifications(|notification| { + let job_id = job_id.clone(); + let subscription_id = subscription_id.clone(); + async move { + if let RelayPoolNotification::Event { + subscription_id: sub_id, + event, + .. + } = notification + { + if sub_id == subscription_id { + if let Ok(result) = + serde_json::from_str::(&event.content) + { + if result.job_id == job_id { + info!("Job result found for job_id: {}", job_id); + return Ok(true); // Signal that we've found the result + } + } + } + } + Ok(false) // Continue listening + } + }) + .await + .map_err(CustomerError::NostrClientError)?; + + // If we've found the result, parse it again + // This is not ideal, but it works around the limitations of handle_notifications + let events = self + .nostr_client + .get_events_of( + vec![Filter::new() + .kind(Kind::TextNote) + .author(PublicKey::from_bech32(&self.settings.prover_agent_pk).unwrap()) + .since(Timestamp::now() - Duration::from_secs(60))], + None, + ) + .await + .map_err(CustomerError::NostrClientError)?; + + for event in events { + if let Ok(job_result) = serde_json::from_str::(&event.content) { + if job_result.job_id == job_id { + return Ok(job_result); + } + } + } + + Err(CustomerError::Unknown("Job result not found".to_string())) + } + + pub fn verify_proof(&self, job_result: &GenerateZKPJobResult) -> Result { + info!("Verifying proof..."); + self.verifier_service + .verify_proof(job_result.response.clone()) + .map(|_| true) + .map_err(|e| CustomerError::VerificationError(e.to_string())) + } +} diff --git a/crates/core/src/dvm/mod.rs b/crates/core/src/dvm/mod.rs new file mode 100644 index 0000000..541581d --- /dev/null +++ b/crates/core/src/dvm/mod.rs @@ -0,0 +1,51 @@ +pub mod customer; +pub mod service_provider; + +pub mod constants { + + pub const DVM_NAME: &str = "askeladd"; + pub const DVM_DESCRIPTION: &str = "Censorship-resistant global proving network."; + pub const SERVICE_NAME: &str = "generate-zk-proof"; + pub const VERSION: &str = "0.1.0"; + pub const JOB_REQUEST_KIND: u64 = 5600; + pub const JOB_RESULT_KIND: u64 = 6600; +} + +pub mod types { + use serde::{Deserialize, Serialize}; + use uuid::Uuid; + + use crate::types::{FibonnacciProvingRequest, FibonnacciProvingResponse}; + + #[derive(Debug, Serialize, Deserialize, Clone)] + pub struct GenerateZKPJobRequest { + pub job_id: String, + pub request: FibonnacciProvingRequest, + } + + impl GenerateZKPJobRequest { + pub fn new(request: FibonnacciProvingRequest) -> Self { + Self { + job_id: Self::new_job_id(), + request, + } + } + + pub fn new_job_id() -> String { + Uuid::new_v4().to_string() + } + } + + #[derive(Debug, Serialize, Deserialize)] + pub enum JobStatus { + Pending, + Completed, + Failed, + } + + #[derive(Debug, Serialize, Deserialize)] + pub struct GenerateZKPJobResult { + pub job_id: String, + pub response: FibonnacciProvingResponse, + } +} diff --git a/crates/core/src/dvm/service_provider.rs b/crates/core/src/dvm/service_provider.rs new file mode 100644 index 0000000..03dc444 --- /dev/null +++ b/crates/core/src/dvm/service_provider.rs @@ -0,0 +1,193 @@ +use std::error::Error; + +use log::{debug, error, info}; +use nostr_sdk::prelude::*; +use thiserror::Error; + +use crate::config::Settings; +use crate::db::{Database, RequestStatus}; +use crate::dvm::types::{GenerateZKPJobRequest, GenerateZKPJobResult}; +use crate::prover_service::ProverService; + +/// ServiceProvider is the main component of the Askeladd prover agent. +/// It manages the lifecycle of proving requests, from receiving them via Nostr, +/// to generating proofs and publishing the results. +/// +/// The ServiceProvider integrates with a Nostr client for communication, +/// a database for persistence, and a proving service for generating proofs. +pub struct ServiceProvider { + /// Application settings + settings: Settings, + /// Service for generating proofs + proving_service: ProverService, + /// Nostr client for communication + nostr_client: Client, + /// Database for persisting request states + db: Database, +} + +/// Errors that can occur during ServiceProvider operations +#[derive(Error, Debug)] +pub enum ServiceProviderError { + #[error("Failed to connect to Nostr relay: {0}")] + NostrConnectionError(String), + #[error("Failed to subscribe to Nostr events: {0}")] + NostrSubscriptionError(String), + #[error("Database error: {0}")] + DatabaseError(#[from] rusqlite::Error), + #[error("Nostr client error: {0}")] + NostrClientError(#[from] nostr_sdk::client::Error), + #[error("JSON serialization error: {0}")] + SerializationError(#[from] serde_json::Error), + #[error("Unknown error")] + Unknown, +} + +impl ServiceProvider { + /// Creates a new ServiceProvider instance + /// + /// # Arguments + /// + /// * `settings` - Application settings + /// + /// # Returns + /// + /// A Result containing the new ServiceProvider or an error + pub fn new(settings: Settings) -> Result { + // Initialize Nostr keys and client + let prover_agent_keys = + Keys::new(SecretKey::from_bech32(&settings.prover_agent_sk).unwrap()); + let opts = Options::new().wait_for_send(false); + let client = Client::with_opts(&prover_agent_keys, opts); + + // Initialize database + let db = Database::new(settings.db_path.to_str().unwrap())?; + + Ok(Self { + settings, + proving_service: Default::default(), + nostr_client: client, + db, + }) + } + + /// Initializes the ServiceProvider by connecting to Nostr relays + pub async fn init(&mut self) -> Result<(), ServiceProviderError> { + // Connect to all configured relays + for relay in &self.settings.subscribed_relays { + self.nostr_client + .add_relay(relay) + .await + .map_err(|e| ServiceProviderError::NostrConnectionError(e.to_string()))?; + } + self.nostr_client.connect().await; + debug!("Nostr client connected to relays."); + Ok(()) + } + + /// Runs the main loop of the ServiceProvider + /// + /// This method subscribes to Nostr events and handles incoming proving requests + pub async fn run(&self) -> Result<(), ServiceProviderError> { + let proving_req_sub_id = SubscriptionId::new(&self.settings.proving_req_sub_id); + let filter = Filter::new().kind(Kind::TextNote).since(Timestamp::now()); + + // Subscribe to Nostr events + self.nostr_client + .subscribe_with_id(proving_req_sub_id.clone(), vec![filter], None) + .await + .map_err(|e| ServiceProviderError::NostrSubscriptionError(e.to_string()))?; + + info!("Subscribed to proving requests, waiting for requests..."); + + // Start handling Nostr notifications + self.nostr_client + .handle_notifications(|notification| async { + match self.handle_notification(notification).await { + Ok(result) => Ok(result), + Err(e) => Err(Box::new(e) as Box), + } + }) + .await?; + + Ok(()) + } + + /// Handles incoming Nostr notifications + async fn handle_notification( + &self, + notification: RelayPoolNotification, + ) -> Result { + if let RelayPoolNotification::Event { + subscription_id, + event, + .. + } = notification + { + if subscription_id == SubscriptionId::new(&self.settings.proving_req_sub_id) { + self.handle_event(event).await?; + } + } + Ok(false) + } + + /// Handles a single proving request event + async fn handle_event(&self, event: Box) -> Result<(), ServiceProviderError> { + debug!("Event received [{}]", event.id); + if let Ok(job_request) = serde_json::from_str::(&event.content) { + info!("Proving request received [{}]", event.id); + let request = job_request.request; + + if let Some(status) = self.db.get_request_status(&job_request.job_id)? { + match status { + RequestStatus::Completed => { + info!("Request {} already processed, skipping", job_request.job_id); + return Ok(()); + } + RequestStatus::Failed => { + info!("Request {} failed before, retrying", job_request.job_id); + } + RequestStatus::Pending => { + info!( + "Request {} is already pending, skipping", + job_request.job_id + ); + return Ok(()); + } + } + } else { + self.db.insert_request(&job_request.job_id, &request)?; + } + + match self.proving_service.generate_proof(request) { + Ok(response) => { + let job_result = GenerateZKPJobResult { + job_id: job_request.job_id.clone(), + response, + }; + let response_json = serde_json::to_string(&job_result)?; + let event_id = self + .nostr_client + .publish_text_note(response_json, vec![]) + .await?; + info!("Proving response published [{}]", event_id.to_string()); + + self.db.update_request( + &job_request.job_id, + Some(&job_result.response), + RequestStatus::Completed, + )?; + } + Err(e) => { + error!("Proof generation failed: {}", e); + self.db + .update_request(&job_request.job_id, None, RequestStatus::Failed)?; + } + } + } else { + debug!("Received non-request event, ignoring"); + } + + Ok(()) + } +} diff --git a/crates/core/src/lib.rs b/crates/core/src/lib.rs index eca37d0..4e91b3b 100644 --- a/crates/core/src/lib.rs +++ b/crates/core/src/lib.rs @@ -1,5 +1,6 @@ pub mod config; pub mod db; +pub mod dvm; pub mod prover_service; pub mod types; pub mod verifier_service; diff --git a/crates/core/src/prover_service.rs b/crates/core/src/prover_service.rs index 650c5a3..6da6e2a 100644 --- a/crates/core/src/prover_service.rs +++ b/crates/core/src/prover_service.rs @@ -15,7 +15,6 @@ impl ProverService { let fib = Fibonacci::new(request.log_size, BaseField::from(request.claim)); match fib.prove() { Ok(proof) => Ok(FibonnacciProvingResponse::new( - request.request_id, request.log_size, request.claim, proof, diff --git a/crates/core/src/types.rs b/crates/core/src/types.rs index e9997ed..e056d05 100644 --- a/crates/core/src/types.rs +++ b/crates/core/src/types.rs @@ -3,26 +3,38 @@ use stwo_prover::core::prover::StarkProof; #[derive(Debug, Serialize, Deserialize, Clone)] pub struct FibonnacciProvingRequest { - pub request_id: String, pub log_size: u32, pub claim: u32, } -#[derive(Debug, Serialize, Deserialize, Default)] +#[derive(Debug, Serialize, Deserialize)] pub struct FibonnacciProvingResponse { - pub request_id: String, pub log_size: u32, pub claim: u32, - pub proof: Option, + pub proof: StarkProof, } impl FibonnacciProvingResponse { - pub fn new(request_id: String, log_size: u32, claim: u32, proof: StarkProof) -> Self { + pub fn new(log_size: u32, claim: u32, proof: StarkProof) -> Self { Self { - request_id, log_size, claim, - proof: Some(proof), + proof, + } + } +} + +impl Clone for FibonnacciProvingResponse { + fn clone(&self) -> Self { + // Temporarily use serde for a dirty clone + // TODO: Implement a proper clone or find a better design that does not require cloning the + // proof + let proof_json = serde_json::to_string(&self.proof).unwrap(); + let proof = serde_json::from_str(&proof_json).unwrap(); + Self { + log_size: self.log_size, + claim: self.claim, + proof, } } } diff --git a/crates/core/src/verifier_service.rs b/crates/core/src/verifier_service.rs index b9dde95..7b4e007 100644 --- a/crates/core/src/verifier_service.rs +++ b/crates/core/src/verifier_service.rs @@ -13,6 +13,6 @@ impl VerifierService { response: FibonnacciProvingResponse, ) -> Result<(), VerificationError> { let fib = Fibonacci::new(response.log_size, BaseField::from(response.claim)); - fib.verify(response.proof.unwrap()) + fib.verify(response.proof) } }