Skip to content

Commit

Permalink
♻️ move core logic into service_provider and customer instead of …
Browse files Browse the repository at this point in the history
…in the main
  • Loading branch information
AbdelStark committed Jul 26, 2024
1 parent f73a51a commit fade137
Show file tree
Hide file tree
Showing 14 changed files with 511 additions and 231 deletions.
3 changes: 2 additions & 1 deletion .env.docker
Original file line number Diff line number Diff line change
Expand Up @@ -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
APP_PROVER_AGENT_SK=nsec18s6wcqlkglhjmfz3tnjkh0qscf6cqen96ecp5k5k73ktew3l97tstuvy2x
APP_PROVER_AGENT_PK=npub1efldztlt3z3xs2f8fwpmscvus90g6tvv9g6f9c8hduuh0rfhzeaqvxpnkq
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 <https://vendata.io/dvms> mechanism to see how it works.
- Check <https://www.data-vending-machines.org/> 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.
Expand Down
137 changes: 14 additions & 123 deletions crates/cli/src/prover_agent.rs
Original file line number Diff line number Diff line change
@@ -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<dyn std::error::Error>> {
// ******************************************************
// ****************** 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::<FibonnacciProvingRequest>(&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(())
}
134 changes: 44 additions & 90 deletions crates/cli/src/user_cli.rs
Original file line number Diff line number Diff line change
@@ -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<dyn std::error::Error>> {
// ******************************************************
// ****************** 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::<FibonnacciProvingResponse>(&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(())
}
4 changes: 4 additions & 0 deletions crates/core/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"] }
7 changes: 7 additions & 0 deletions crates/core/src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
}
Expand Down Expand Up @@ -64,3 +65,9 @@ impl Settings {
Ok(settings)
}
}

impl Default for Settings {
fn default() -> Self {
Self::new().expect("Failed to load settings")
}
}
15 changes: 7 additions & 8 deletions crates/core/src/db.rs
Original file line number Diff line number Diff line change
Expand Up @@ -36,26 +36,25 @@ 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(())
}

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],
Expand Down
Loading

0 comments on commit fade137

Please sign in to comment.