Skip to content

Commit

Permalink
Wip sharp prover service impl
Browse files Browse the repository at this point in the history
  • Loading branch information
unstark committed May 14, 2024
1 parent b31cf6e commit 9ef6e02
Show file tree
Hide file tree
Showing 17 changed files with 3,180 additions and 895 deletions.
3,786 changes: 2,936 additions & 850 deletions Cargo.lock

Large diffs are not rendered by default.

15 changes: 9 additions & 6 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,14 @@ authors = ["Apoorv Sadana <@apoorvsadana>"]
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
alloy = { git = "https://github.com/alloy-rs/alloy", rev = "86027c9bb984f3a12a30ffd2a3c5f2f06595f1d6", features = [
alloy = { git = "https://github.com/alloy-rs/alloy", rev = "7373f6db761d5a19888e3a0c527e8a3ca31e7a1e", features = [
"sol-types",
"json",
"contract",
"providers",
"rpc-client",
"transport-http",
"reqwest",
], optional = true }
async-trait = "0.1.77"
axum = { version = "0.7.4", features = ["macros"] }
Expand All @@ -33,7 +37,7 @@ thiserror = "1.0.57"
tokio = { version = "1.37.0", features = ["sync", "macros", "rt-multi-thread"] }
tracing = "0.1.40"
tracing-subscriber = { version = "0.3.18", features = ["env-filter"] }
url = "2.5.0"
url = { version = "2.5.0", features = ["serde"] }
uuid = { version = "1.7.0", features = ["v4", "serde"] }
stark_evm_adapter = "0.1.1"
hex = "0.4"
Expand All @@ -42,15 +46,14 @@ hex = "0.4"
cairo-vm = { git = "https://github.com/lambdaclass/cairo-vm" }

# Sharp (Starkware)
# snos = { git = "https://github.com/keep-starknet-strange/snos", rev = "042c3abe7c139d801d66abd4b35226ab09755544" }
snos = { git = "https://github.com/unstark/snos", branch = "bump-cairo-lang" }

# Sharp P2P
cairo-proof-parser = { git = "https://github.com/cartridge-gg/cairo-proof-parser", rev = "1cd7af307609d0f6a602a59d124d5044e56cc7b4" }
# cairo-proof-parser = { git = "https://github.com/cartridge-gg/cairo-proof-parser", rev = "1cd7af307609d0f6a602a59d124d5044e56cc7b4" }

# Madara prover API
madara-prover-common = { git = "https://github.com/Moonsong-Labs/madara-prover-api", branch = "od/use-latest-cairo-vm" }
# FIXME: build errors
# madara-prover-rpc-client = { git = "https://github.com/Moonsong-Labs/madara-prover-api", branch = "od/use-latest-cairo-vm" }
madara-prover-rpc-client = { git = "https://github.com/Moonsong-Labs/madara-prover-api", branch = "od/use-latest-cairo-vm" }

[features]
default = ["ethereum", "with_mongdb", "with_sqs"]
Expand Down
1 change: 1 addition & 0 deletions src/contracts/artifacts/FactRegistry.json

Large diffs are not rendered by default.

8 changes: 8 additions & 0 deletions src/contracts/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
use alloy::sol;

sol!(
#[allow(missing_docs)]
#[sol(rpc)]
FactRegistry,
"src/contracts/artifacts/FactRegistry.json"
);
5 changes: 2 additions & 3 deletions src/da_clients/ethereum/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,9 @@
use std::str::FromStr;

use alloy::rpc::client::RpcClient;
use alloy::transports::http::Http;
use alloy::transports::http::{Client, Http};
use async_trait::async_trait;
use color_eyre::Result;
use reqwest::Client;
use starknet::core::types::FieldElement;
use url::Url;

Expand Down Expand Up @@ -34,7 +33,7 @@ impl DaClient for EthereumDaClient {
impl From<EthereumDaConfig> for EthereumDaClient {
fn from(config: EthereumDaConfig) -> Self {
let provider = RpcClient::builder()
.reqwest_http(Url::from_str(config.rpc_url.as_str()).expect("Failed to parse ETHEREUM_RPC_URL"));
.http(Url::from_str(config.rpc_url.as_str()).expect("Failed to parse ETHEREUM_RPC_URL"));
EthereumDaClient { provider }
}
}
2 changes: 2 additions & 0 deletions src/main.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
/// Config of the service. Contains configurations for DB, Queues and other services.
mod config;
/// Rust bindings for Starknet solidity contracts
mod contracts;
/// Controllers for the routes
mod controllers;
/// Contains the trait that all DA clients must implement
Expand Down
4 changes: 3 additions & 1 deletion src/provers/error.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
use crate::settings::SettingsProviderError;

#[derive(Debug, thiserror::Error)]
pub enum ProverError {
pub enum ProverServiceError {
#[error("Internal prover error: {0}")]
Internal(#[source] Box<dyn std::error::Error + Send + Sync + 'static>),
#[error("Stone prover failed: {0}")]
SettingsProvider(#[from] SettingsProviderError),
#[error("Task is invalid: {0}")]
TaskInvalid(String),
}
9 changes: 4 additions & 5 deletions src/provers/iosis/mod.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
use async_trait::async_trait;
use cairo_vm::vm::runners::cairo_pie::CairoPie;

use super::error::ProverError;
use super::{ProverService, TaskId, TaskStatus};
use super::error::ProverServiceError;
use super::{ProverService, Task, TaskId, TaskStatus};
use crate::settings::SettingsProvider;

/// Sharp P2P is a shared peer-to-peer network of zkSTARK provers.
Expand All @@ -23,11 +22,11 @@ pub struct IosisProverService {

#[async_trait]
impl ProverService for IosisProverService {
async fn submit_task(&self, cairo_pie: CairoPie) -> Result<TaskId, ProverError> {
async fn submit_task(&self, task: Task) -> Result<TaskId, ProverServiceError> {
todo!()
}

async fn get_task_status(&self, task_id: &TaskId) -> Result<TaskStatus, ProverError> {
async fn get_task_status(&self, task_id: &TaskId) -> Result<TaskStatus, ProverServiceError> {
todo!()
}
}
Expand Down
24 changes: 11 additions & 13 deletions src/provers/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ pub mod stone;

use async_trait::async_trait;
use cairo_vm::vm::runners::cairo_pie::CairoPie;
use error::ProverError;
use error::ProverServiceError;

/// Prover service provides an abstraction over different proving services that do the following:
/// - Accept a task containing Cairo intermediate execution artifacts (in PIE format)
Expand All @@ -14,24 +14,22 @@ use error::ProverError;
/// - Register the proof onchain (individiual proof facts available for each task)
///
/// A common Madara workflow would be single task per block (SNOS execution result).
///
/// TODO: investigate if there are cases when aggregation has to be done (or disabled) on the
/// orchestrator side.
#[async_trait]
pub trait ProverService: Send + Sync {
async fn submit_task(&self, cairo_pie: CairoPie) -> Result<TaskId, ProverError>;
async fn get_task_status(&self, task_id: &TaskId) -> Result<TaskStatus, ProverError>;
async fn submit_task(&self, task: Task) -> Result<TaskId, ProverServiceError>;
async fn get_task_status(&self, task_id: &TaskId) -> Result<TaskStatus, ProverServiceError>;
}

pub enum Task {
EncodedPie(String),
PieObject(Box<CairoPie>),
}

// TODO: maybe this should be UUID or another type enforcing uniqueness.
// Should SHARP job IDs be transparently treated as task IDs or mapped internally?
pub type TaskId = String;

#[derive(Debug, Clone, PartialEq, Eq)]
pub enum TaskStatus {
// TODO: find a common denominator
// - https://github.com/keep-starknet-strange/snos/blob/042c3abe7c139d801d66abd4b35226ab09755544/src/sharp/mod.rs#L19
// - https://github.com/iosis-tech/sharp-p2p/blob/e5f160c6926c32ddfea8fd165c773641a38844f1/crates/common/src/topic.rs#L14
// - Madara prover API (jobs are not yet implemented)
// Note that not all the intermediate states might be available
Processing,
Succeeded,
Failed(String),
}
20 changes: 20 additions & 0 deletions src/provers/sharp/config.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
use alloy::primitives::Address;
use serde::{Deserialize, Serialize};
use url::Url;

/// SHARP proving service configuration
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SharpConfig {
/// SHARP service url
pub service_url: Url,
/// EVM RPC node url
pub rpc_node_url: Url,
/// GPS verifier contract address (implements FactRegistry)
pub verifier_address: Address,
}

impl Default for SharpConfig {
fn default() -> Self {
unimplemented!("No defaults for SHARP config")
}
}
28 changes: 28 additions & 0 deletions src/provers/sharp/error.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
use cairo_vm::program_hash::ProgramHashError;
use reqwest::StatusCode;

use crate::provers::error::ProverServiceError;

#[derive(Debug, thiserror::Error)]
pub enum SharpError {
#[error("Failed to to add SHARP job: {0}")]
AddJobFailure(#[source] reqwest::Error),
#[error("Failed to to get status of a SHARP job: {0}")]
GetJobStatusFailure(#[source] reqwest::Error),
#[error("SHARP service returned an error {0}")]
SharpService(StatusCode),
#[error("Fact registry call failed: {0}")]
FactRegistry(#[source] alloy::contract::Error),
#[error("Failed to parse task ID: {0}")]
TaskIdParse(uuid::Error),
#[error("Failed to encode PIE")]
PieEncode(#[source] snos::error::SnOsError),
#[error("Failed to compute program hash: {0}")]
ProgramHashCompute(#[source] ProgramHashError),
}

impl From<SharpError> for ProverServiceError {
fn from(value: SharpError) -> Self {
Self::Internal(Box::new(value))
}
}
29 changes: 29 additions & 0 deletions src/provers/sharp/fact_checker.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
use alloy::primitives::{Address, B256};
use alloy::providers::{ProviderBuilder, RootProvider};
use alloy::transports::http::{Client, Http};
use url::Url;

use crate::contracts::FactRegistry::{self, FactRegistryInstance};

use super::error::SharpError;

pub struct FactChecker {
fact_registry: FactRegistryInstance<TransportT, ProviderT>,
}

type TransportT = Http<Client>;
type ProviderT = RootProvider<TransportT>;

impl FactChecker {
pub fn new(rpc_node_url: Url, verifier_address: Address) -> Self {
let provider = ProviderBuilder::new().on_http(rpc_node_url);
let fact_registry = FactRegistry::new(verifier_address, provider);
Self { fact_registry }
}

pub async fn is_valid(&self, fact: B256) -> Result<bool, SharpError> {
let FactRegistry::isValidReturn { _0 } =
self.fact_registry.isValid(fact.clone()).call().await.map_err(SharpError::FactRegistry)?;
Ok(_0)
}
}
82 changes: 72 additions & 10 deletions src/provers/sharp/mod.rs
Original file line number Diff line number Diff line change
@@ -1,30 +1,92 @@
pub mod config;
pub mod error;
pub mod fact_checker;
pub mod sharp_client;

use std::str::FromStr;

use alloy::primitives::B256;
use async_trait::async_trait;
use cairo_vm::program_hash::compute_program_hash_chain;
use cairo_vm::vm::runners::cairo_pie::CairoPie;
use snos::sharp::CairoJobStatus;
use uuid::Uuid;

use super::error::ProverError;
use super::{ProverService, TaskId, TaskStatus};
use self::config::SharpConfig;
use self::error::SharpError;
use self::fact_checker::FactChecker;
use self::sharp_client::SharpClient;

use super::error::ProverServiceError;
use super::{ProverService, Task, TaskId, TaskStatus};
use crate::settings::SettingsProvider;

pub const SHARP_SETTINGS_NAME: &str = "sharp";

/// SHARP (aka GPS) is a shared proving service hosted by Starkware.
pub struct SharpProverService {
// TODO: integrate Sharp client (there are dependency conflicts, should we copy the code instead?)
// https://github.com/keep-starknet-strange/snos/blob/main/src/sharp/mod.rs
// ProverService interface is basically modeled after SHARP so the integration should be seamless.
sharp_client: SharpClient,
fact_checker: FactChecker,
}

#[async_trait]
impl ProverService for SharpProverService {
async fn submit_task(&self, cairo_pie: CairoPie) -> Result<TaskId, ProverError> {
todo!()
async fn submit_task(&self, task: Task) -> Result<TaskId, ProverServiceError> {
let encoded_pie = match task {
Task::EncodedPie(enc) => enc,
Task::PieObject(pie) => {
todo!("Need to resolve cairo-vm versions comflict");
// pie::encode_pie_mem(*pie).map_err(SharpError::PieEncode)?
}
};
let res = self.sharp_client.add_job(&encoded_pie).await?;
if let Some(job_key) = res.cairo_job_key {
Ok(job_key.to_string())
} else {
Err(ProverServiceError::TaskInvalid(res.error_message.unwrap_or_default()).into())
}
}

async fn get_task_status(&self, task_id: &TaskId) -> Result<TaskStatus, ProverError> {
todo!()
async fn get_task_status(&self, task_id: &TaskId) -> Result<TaskStatus, ProverServiceError> {
let job_key = Uuid::from_str(task_id).map_err(SharpError::TaskIdParse)?;
let res = self.sharp_client.get_job_status(&job_key).await?;
match res.status {
CairoJobStatus::FAILED => Ok(TaskStatus::Failed(res.error_log.unwrap_or_default())),
CairoJobStatus::INVALID => {
Ok(TaskStatus::Failed(format!("Task is invalid: {:?}", res.invalid_reason.unwrap_or_default())))
}
CairoJobStatus::UNKNOWN => Ok(TaskStatus::Failed(format!("Task not found: {}", task_id))),
CairoJobStatus::IN_PROGRESS | CairoJobStatus::NOT_CREATED | CairoJobStatus::PROCESSED => {
Ok(TaskStatus::Processing)
}
CairoJobStatus::ONCHAIN => {
let fact = self.get_fact(task_id);
if self.fact_checker.is_valid(fact).await? {
Ok(TaskStatus::Succeeded)
} else {
Ok(TaskStatus::Failed(format!("Fact {} is not valid or not registed", hex::encode(&fact))))
}
}
}
}
}

impl SharpProverService {
pub fn with_settings(settings: &impl SettingsProvider) -> Self {
todo!()
let sharp_cfg: SharpConfig = settings.get_settings(SHARP_SETTINGS_NAME).unwrap();
let sharp_client = SharpClient::new(sharp_cfg.service_url);
let fact_checker = FactChecker::new(sharp_cfg.rpc_node_url, sharp_cfg.verifier_address);
Self { sharp_client, fact_checker }
}

pub fn set_fact(&mut self, task_id: &TaskId, pie: &CairoPie) -> Result<(), SharpError> {
let program_hash =
compute_program_hash_chain(&pie.metadata.program, 1).map_err(SharpError::ProgramHashCompute)?;
// TODO: https://github.com/starkware-libs/cairo-lang/blob/efa9648f57568aad8f8a13fbf027d2de7c63c2c0/src/starkware/cairo/bootloaders/generate_fact.py#L32
Ok(())
}

pub fn get_fact(&self, task_id: &TaskId) -> B256 {
B256::ZERO
}
}
47 changes: 47 additions & 0 deletions src/provers/sharp/sharp_client.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
use serde_json::json;
use snos::sharp::{CairoJobResponse, CairoStatusResponse};
use url::Url;
use uuid::Uuid;

use super::error::SharpError;

pub const DEFAULT_SHARP_URL: &str = "https://testnet.provingservice.io";

/// SHARP API async wrapper
pub struct SharpClient {
url: Url,
client: reqwest::Client,
}

impl SharpClient {
pub fn new(url: Url) -> Self {
Self { url, client: reqwest::Client::new() }
}

pub async fn add_job(&self, encoded_pie: &str) -> Result<CairoJobResponse, SharpError> {
let data = json!({ "action": "add_job", "request": { "cairo_pie": encoded_pie } });
let res = self.client.post(self.url.clone()).json(&data).send().await.map_err(SharpError::AddJobFailure)?;

match res.status() {
reqwest::StatusCode::OK => res.json().await.map_err(SharpError::AddJobFailure),
code => Err(SharpError::SharpService(code)),
}
}

pub async fn get_job_status(&self, job_key: &Uuid) -> Result<CairoStatusResponse, SharpError> {
let data = json!({ "action": "get_status", "request": { "cairo_job_key": job_key } });
let res =
self.client.post(self.url.clone()).json(&data).send().await.map_err(SharpError::GetJobStatusFailure)?;

match res.status() {
reqwest::StatusCode::OK => res.json().await.map_err(SharpError::GetJobStatusFailure),
code => Err(SharpError::SharpService(code)),
}
}
}

impl Default for SharpClient {
fn default() -> Self {
Self::new(DEFAULT_SHARP_URL.parse().unwrap())
}
}
Loading

0 comments on commit 9ef6e02

Please sign in to comment.