Skip to content
Closed
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
368 changes: 344 additions & 24 deletions Cargo.lock

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,7 @@ rstest = "0.22.0"
test-log = "0.2.16"

# SP1 dependencies
sindri = { version = "0.2.2", features = ["sp1-v4"] }
sp1-core-machine = "=4.1.2"
sp1-sdk = "=4.1.2"
sp1-prover = "=4.1.2"
Expand Down
28 changes: 28 additions & 0 deletions Makefile.elf.toml
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,34 @@ args = [
"-vv",
]

[tasks.install-sindri-cli]
description = "Install Sindri CLI"
command = "cargo"
args = [
"install",
"sindri-cli@0.2.2"
"--force",
"--locked"
]

[tasks.ap-elf-sindri-upload]
description = "Upload ELF file to Sindri"
dependencies = [
"ap-elf",
"install-sindri-cli"
]
cwd = "crates/aggchain-proof-program"
command = "cargo"
args = [
"sindri",
"deploy",
".",
"--api-key",
"${SINDRI_API_KEY}",
#"--tags",
#"${SINDRI_PROJECT_TAG}",
]

[tasks.sp1-toolchain-shell]
description = "Drop into an interactive shell in sp1 build environment image"
dependencies = ["sp1-toolchain-image"]
Expand Down
8 changes: 8 additions & 0 deletions crates/aggchain-proof-program/sindri.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"$schema": "https://sindri.app/api/v1/sindri-manifest-schema.json",
"name": "pessimistic-proof",
"circuitType": "sp1",
"provingScheme": "plonk",
"sp1Version": "4.0.0",
Comment on lines +4 to +6
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These three fields should be fixed while you pin to Sp1 v4.0.0, but the other fields (name and elfPath) are flexible. As mentioned in the PR description name is fully up to your team. The elfPath provides the relative path from this Sindri manifest to the compiled ELF.

"elfPath": "elf/riscv32im-succinct-zkvm-elf"
}
48 changes: 48 additions & 0 deletions crates/prover-config/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ pub enum ProverType {
CpuProver(CpuProverConfig),
GpuProver(GpuProverConfig),
MockProver(MockProverConfig),
SindriProver(SindriProverConfig),
}

impl Default for ProverType {
Expand Down Expand Up @@ -156,6 +157,45 @@ impl Default for MockProverConfig {
}
}

#[serde_as]
#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Clone)]
#[serde(rename_all = "kebab-case")]
pub struct SindriProverConfig {
#[serde_as(as = "Option<crate::with::HumanDuration>")]
pub proving_request_timeout: Option<Duration>,

#[serde(default = "default_network_proving_timeout")]
#[serde(with = "crate::with::HumanDuration")]
pub proving_timeout: Duration,

#[serde(default = "default_sindri_project_name")]
pub project_name: String,

#[serde(default = "default_sindri_project_tag")]
pub project_tag: String,
}

impl SindriProverConfig {
// This constant represents the number of second added to the proving_timeout
pub const DEFAULT_PROVING_TIMEOUT_PADDING: Duration = Duration::from_secs(1);

pub fn get_proving_request_timeout(&self) -> Duration {
self.proving_request_timeout
.unwrap_or_else(|| self.proving_timeout + Self::DEFAULT_PROVING_TIMEOUT_PADDING)
}
}

impl Default for SindriProverConfig {
fn default() -> Self {
Self {
proving_request_timeout: None,
proving_timeout: default_network_proving_timeout(),
project_name: default_sindri_project_name(),
project_tag: default_sindri_project_tag(),
}
}
}

pub const fn default_max_concurrency_limit() -> usize {
100
}
Expand All @@ -167,3 +207,11 @@ const fn default_local_proving_timeout() -> Duration {
const fn default_network_proving_timeout() -> Duration {
Duration::from_secs(60 * 5)
}

fn default_sindri_project_name() -> String {
"pessimistic-proof".to_string()
}

fn default_sindri_project_tag() -> String {
"latest".to_string()
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@

[primary-prover.sindri-prover]
proving-request-timeout = "5m"
proving-timeout = "10m"
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As you are running tests, you may notice that the proving-timeout you supply in your config does not match the exact time the Sindri client times out (especially if you are aggressive and put 1 minute or less). This is expected. For efficiency reasons, the Sindri client will hold a connection for some time while awaiting a proof job to complete. (The timeout delay will not go above a few minutes.)

project-name = "pessimistic-proof"
project-tag = "latest"
20 changes: 19 additions & 1 deletion crates/prover-config/tests/validate_deserialize.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
use pretty_assertions::assert_eq;
use prover_config::{CpuProverConfig, MockProverConfig, NetworkProverConfig, ProverType};
use prover_config::{
CpuProverConfig, MockProverConfig, NetworkProverConfig, ProverType, SindriProverConfig,
};
use serde::{Deserialize, Serialize};

#[derive(Serialize, Deserialize, Debug, PartialEq, Eq)]
Expand Down Expand Up @@ -75,3 +77,19 @@ fn mock_prover() {
})
);
}

#[test]
fn sindri_prover() {
let input = "./tests/fixtures/validate_config/prover_config_sindri_prover.toml";
let config: TestConfig = toml::from_str(&std::fs::read_to_string(input).unwrap()).unwrap();

assert_eq!(
config.primary_prover,
ProverType::SindriProver(SindriProverConfig {
proving_request_timeout: Some(std::time::Duration::from_secs(300)),
proving_timeout: std::time::Duration::from_secs(600),
project_name: "pessimistic-proof".to_string(),
project_tag: "latest".to_string(),
})
);
}
1 change: 1 addition & 0 deletions crates/prover-executor/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ prover-engine.workspace = true
prover-logger.workspace = true
prover-config.workspace = true

sindri = { workspace = true, features = ["sp1-v4"] }
sp1-sdk = { workspace = true, features = ["native-gnark"] }
sp1-prover = { workspace = true, features = ["native-gnark"] }

Expand Down
98 changes: 98 additions & 0 deletions crates/prover-executor/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,10 @@ use std::{
};

pub use error::Error;
use error::ProofVerificationError;
use futures::{Future, TryFutureExt};
use prover_config::ProverType;
use sindri::{client::SindriClient, integrations::sp1_v4::SP1ProofInfo, JobStatus, ProofInput};
use sp1_sdk::{
network::{prover::NetworkProver, FulfillmentStrategy},
CpuProver, Prover, ProverClient, SP1ProofWithPublicValues, SP1ProvingKey, SP1Stdin,
Expand Down Expand Up @@ -137,6 +139,22 @@ impl Executor {
)
}
ProverType::GpuProver(_) => todo!(),
ProverType::SindriProver(sindri_prover_config) => {
debug!("Creating Sindri prover executor...");
let mut sindri_client = SindriClient::default();
sindri_client.polling_options.timeout = Some(sindri_prover_config.proving_timeout);
let verification_key = Self::get_vkey(program);
Self::build_network_service(
sindri_prover_config.get_proving_request_timeout(),
SindriExecutor {
prover: Arc::new(sindri_client),
verification_key,
timeout: sindri_prover_config.proving_timeout,
project_name: sindri_prover_config.project_name.clone(),
project_tag: sindri_prover_config.project_tag.clone(),
},
)
}
}
}

Expand Down Expand Up @@ -293,3 +311,83 @@ impl Service<Request> for NetworkExecutor {
Box::pin(fut)
}
}

#[derive(Clone)]
struct SindriExecutor {
prover: Arc<SindriClient>,
verification_key: SP1VerifyingKey,
timeout: Duration,
project_name: String,
project_tag: String,
}

impl Service<Request> for SindriExecutor {
type Response = Response;

type Error = Error;

type Future = Pin<Box<dyn Future<Output = Result<Self::Response, Self::Error>> + Send>>;

fn poll_ready(&mut self, _cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
Poll::Ready(Ok(()))
}

fn call(&mut self, req: Request) -> Self::Future {
let prover = self.prover.clone();
let stdin = req.stdin;
let verification_key = self.verification_key.clone();
let timeout = self.timeout;
let project_name = self.project_name.clone();
let project_tag = self.project_tag.clone();

debug!("Proving with network prover with timeout: {:?}", timeout);
let fut = async move {
debug!("Starting the proving of the requested MultiBatchHeader");

// Convert Sp1Stdin type to the Sindri client's ProofInput type
let proof_input = ProofInput::try_from(stdin)
.map_err(|error| Error::ProverFailed(error.to_string()))?;

// Submit the proof request, and poll until the job is completed or a
// timeout occurs. The Sindri client was passed the timeout parameter
// upon creation
let proof_response = prover
.prove_circuit(
&format!("{}:{}", project_name, project_tag),
proof_input,
None,
None,
None,
)
.await
.map_err(|error| Error::ProverFailed(error.to_string()))?;

// If the Sindri job completed with an error, retrieve the error message
if proof_response.status == JobStatus::Failed {
return Err(Error::ProverFailed(
proof_response.error.flatten().unwrap_or(
"Sindri job was marked as failed. No error message was provided."
.to_string(),
),
));
}
Comment on lines +366 to +373
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

While errors related to authorization or some other configuration issue are caught by the map_err above, there may be cases where the Sindri proof request is made and a job is run successfully, but the result is not a valid Sp1 proof. (For instance if you map an input to the wrong ELF)


// Convert the proof response to a SP1 proof with public values
let proof = proof_response
.to_sp1_proof_with_public()
.map_err(|error| Error::ProverFailed(error.to_string()))?;

debug!("Proving completed. Verifying the proof...");
proof_response
.verify_sp1_proof_locally(&verification_key)
.map_err(|error| {
Error::ProofVerificationFailed(ProofVerificationError::Plonk(error.to_string()))
})?;

debug!("Proof verification completed successfully");
Ok(Response { proof })
};

Box::pin(fut)
}
}
Loading