diff --git a/crates/core/executor/src/context.rs b/crates/core/executor/src/context.rs index 0d6c05b170..32503861a8 100644 --- a/crates/core/executor/src/context.rs +++ b/crates/core/executor/src/context.rs @@ -126,6 +126,11 @@ impl<'a> SP1ContextBuilder<'a> { self.skip_deferred_proof_verification = skip; self } + + /// Get the maximum number of cpu cycles to use for execution. + pub fn get_max_cycles(&self) -> Option { + self.max_cycles + } } #[cfg(test)] diff --git a/crates/sdk/src/action.rs b/crates/sdk/src/action.rs index 58b38da010..b767457ee9 100644 --- a/crates/sdk/src/action.rs +++ b/crates/sdk/src/action.rs @@ -7,6 +7,9 @@ use anyhow::{Ok, Result}; use sp1_stark::{SP1CoreOpts, SP1ProverOpts}; use std::time::Duration; +#[cfg(feature = "network-v2")] +use crate::network_v2::FulfillmentStrategy; + use crate::{provers::ProofOpts, Prover, SP1ProofKind, SP1ProofWithPublicValues}; /// Builder to prepare and configure execution of a program on an input. @@ -88,6 +91,10 @@ pub struct Prove<'a> { core_opts: SP1CoreOpts, recursion_opts: SP1CoreOpts, timeout: Option, + #[cfg(feature = "network-v2")] + fulfillment_strategy: Option, + #[cfg(feature = "network-v2")] + skip_simulation: bool, } impl<'a> Prove<'a> { @@ -109,6 +116,10 @@ impl<'a> Prove<'a> { core_opts: SP1CoreOpts::default(), recursion_opts: SP1CoreOpts::recursion(), timeout: None, + #[cfg(feature = "network-v2")] + fulfillment_strategy: None, + #[cfg(feature = "network-v2")] + skip_simulation: false, } } @@ -123,9 +134,22 @@ impl<'a> Prove<'a> { core_opts, recursion_opts, timeout, + #[cfg(feature = "network-v2")] + fulfillment_strategy, + #[cfg(feature = "network-v2")] + skip_simulation, } = self; let opts = SP1ProverOpts { core_opts, recursion_opts }; - let proof_opts = ProofOpts { sp1_prover_opts: opts, timeout }; + let proof_opts = ProofOpts { + sp1_prover_opts: opts, + timeout, + #[cfg(feature = "network-v2")] + fulfillment_strategy, + #[cfg(feature = "network-v2")] + cycle_limit: context_builder.get_max_cycles(), + #[cfg(feature = "network-v2")] + skip_simulation, + }; let context = context_builder.build(); // Dump the program and stdin to files for debugging if `SP1_DUMP` is set. @@ -207,26 +231,60 @@ impl<'a> Prove<'a> { self } - /// Set the maximum number of cpu cycles to use for execution. + /// Set the skip deferred proof verification flag. + pub fn set_skip_deferred_proof_verification(mut self, value: bool) -> Self { + self.context_builder.set_skip_deferred_proof_verification(value); + self + } + + /// Set the maximum number of cpu cycles to use for execution. The request fails if the cycles + /// used exceed this limit. /// - /// If the cycle limit is exceeded, execution will return - /// [`sp1_core_executor::ExecutionError::ExceededCycleLimit`]. + /// When set, this value will always be used as the cycle limit, regardless of the + /// `skip_simulation` setting. + /// + /// If this is not set: + /// - The cycle limit will be calculated by simulating the program (if simulation is enabled) + /// - The default cycle limit will be used (if simulation is disabled via `skip_simulation()`) + /// + /// In the case that cycle limit is greater than the cycles used, a refund will be issued. + /// + /// In the case of running locally, if the cycle limit is exceeded, execution will return + /// [`sp1_core_executor::ExecutionError::ExceededCycleLimit`] pub fn cycle_limit(mut self, cycle_limit: u64) -> Self { self.context_builder.max_cycles(cycle_limit); self } - - /// Set the timeout for the proof's generation. + /// Sets the timeout for the proof's generation. The network will ignore any requests that take longer + /// than this timeout. /// - /// This parameter is only used when the prover is run in network mode. + /// Additionally, the prover will stop polling for the proof request status when this + /// timeout is reached. pub fn timeout(mut self, timeout: Duration) -> Self { self.timeout = Some(timeout); self } - /// Set the skip deferred proof verification flag. - pub fn set_skip_deferred_proof_verification(mut self, value: bool) -> Self { - self.context_builder.set_skip_deferred_proof_verification(value); + /// Sets the fulfillment strategy for the proof's generation. + /// + /// See `FulfillmentStrategy` for more details about each strategy. + #[cfg(feature = "network-v2")] + pub fn strategy(mut self, strategy: FulfillmentStrategy) -> Self { + self.fulfillment_strategy = Some(strategy); + self + } + + /// Disables simulation for cycle limit calculation. + /// + /// This is useful if program execution requires significant computation, and you already have + /// an expected cycle count you can use with `cycle_limit()`. + /// + /// When simulation is disabled: + /// - If a cycle limit was set via `cycle_limit()`, that value will be used + /// - Otherwise, the default cycle limit will be used + #[cfg(feature = "network-v2")] + pub fn skip_simulation(mut self) -> Self { + self.skip_simulation = true; self } } diff --git a/crates/sdk/src/network-v2/client.rs b/crates/sdk/src/network-v2/client.rs index a6658c732d..5bcd965a08 100644 --- a/crates/sdk/src/network-v2/client.rs +++ b/crates/sdk/src/network-v2/client.rs @@ -30,7 +30,6 @@ use crate::network_v2::{Error, Signable}; /// The default RPC endpoint for the Succinct prover network. pub const DEFAULT_PROVER_NETWORK_RPC: &str = "https://rpc.production.succinct.tools/"; - pub struct NetworkClient { signer: PrivateKeySigner, http: HttpClientWithMiddleware, diff --git a/crates/sdk/src/network-v2/prover.rs b/crates/sdk/src/network-v2/prover.rs index 52698f9aa7..14da8c4044 100644 --- a/crates/sdk/src/network-v2/prover.rs +++ b/crates/sdk/src/network-v2/prover.rs @@ -23,19 +23,16 @@ use crate::{ SP1ProvingKey, SP1VerifyingKey, }; -/// The default proof mode to use for proof requests. -pub const DEFAULT_PROOF_MODE: ProofMode = ProofMode::Groth16; - /// The default fulfillment strategy to use for proof requests. pub const DEFAULT_FULFILLMENT_STRATEGY: FulfillmentStrategy = FulfillmentStrategy::Hosted; /// The default timeout for a proof request to be fulfilled (4 hours). pub const DEFAULT_TIMEOUT_SECS: u64 = 14400; -/// Minimum allowed timeout in seconds (10 seconds) +/// The minimum allowed timeout for a proof request to be fulfilled (10 seconds). pub const MIN_TIMEOUT_SECS: u64 = 10; -/// Maximum allowed timeout in seconds (24 hours) +/// The maximum allowed timeout for a proof request to be fulfilled (24 hours). pub const MAX_TIMEOUT_SECS: u64 = 86400; /// The default cycle limit for a proof request if simulation and the cycle limit is not explicitly @@ -46,11 +43,6 @@ pub const DEFAULT_CYCLE_LIMIT: u64 = 100_000_000; pub struct NetworkProver { client: NetworkClient, local_prover: CpuProver, - mode: ProofMode, - strategy: FulfillmentStrategy, - timeout_secs: u64, - cycle_limit: Option, - skip_simulation: bool, } impl NetworkProver { @@ -59,15 +51,7 @@ impl NetworkProver { let version = SP1_CIRCUIT_VERSION; log::info!("Client circuit version: {}", version); - Self { - client: NetworkClient::new(private_key), - local_prover: CpuProver::new(), - mode: DEFAULT_PROOF_MODE, - strategy: DEFAULT_FULFILLMENT_STRATEGY, - timeout_secs: DEFAULT_TIMEOUT_SECS, - cycle_limit: None, - skip_simulation: false, - } + Self { client: NetworkClient::new(private_key), local_prover: CpuProver::new() } } /// Sets up the proving key and verifying key for the given ELF. @@ -84,82 +68,16 @@ impl NetworkProver { self } - /// Sets the mode to use for proof requests. - /// - /// See `ProofMode` for more details about each mode. - pub fn with_mode(mut self, mode: ProofMode) -> Self { - self.mode = mode; - self - } - - /// Sets the fulfillment strategy for proof requests. - /// - /// See `FulfillmentStrategy` for more details about each strategy. - pub fn with_strategy(mut self, strategy: FulfillmentStrategy) -> Self { - self.strategy = strategy; - self - } - - /// Sets the timeout for proof requests. The network will ignore any requests that take longer - /// than this timeout. - /// - /// Additionally, the `NetworkProver` will stop polling for the proof request status when this - /// timeout is reached. - /// - /// The timeout will be clamped between MIN_TIMEOUT_SECS and MAX_TIMEOUT_SECS. - /// If not set, the default timeout (DEFAULT_TIMEOUT_SECS) will be used. - pub fn with_timeout_secs(mut self, timeout_secs: u64) -> Self { - self.timeout_secs = timeout_secs.clamp(MIN_TIMEOUT_SECS, MAX_TIMEOUT_SECS); - self - } - - /// Sets a fixed cycle limit for proof requests. The request fails if the cycles used exceed - /// this limit. - /// - /// When set, this value will always be used as the cycle limit, regardless of the - /// `skip_simulation` setting. - /// - /// If this is not set: - /// - The cycle limit will be calculated by simulating the program (if simulation is enabled) - /// - The default cycle limit will be used (if simulation is disabled via `skip_simulation()`) - /// - /// In the case that cycle limit is greater than the cycles used, a refund will be issued. - pub fn with_cycle_limit(mut self, cycle_limit: u64) -> Self { - self.cycle_limit = Some(cycle_limit); - self - } - - /// Disables simulation for cycle limit calculation. - /// - /// This is useful if program execution requires significant computation, and you already have - /// an expected cycle count you can use with `with_cycle_limit()`. - /// - /// When simulation is disabled: - /// - If a cycle limit was set via `with_cycle_limit()`, that value will be used - /// - Otherwise, the default cycle limit will be used - pub fn skip_simulation(mut self) -> Self { - self.skip_simulation = true; - self - } - /// Creates a new network prover builder. See [`NetworkProverBuilder`] for more details. pub fn builder() -> NetworkProverBuilder { NetworkProverBuilder::default() } - /// Gets the mode to use for a proof request. - fn get_mode(&self) -> ProofMode { - self.mode - } - - /// Gets the fulfillment strategy to use for a proof request. - fn get_strategy(&self) -> FulfillmentStrategy { - self.strategy - } - - /// Gets the configured timeout in seconds to use for a proof request. - fn get_timeout_secs(&self) -> u64 { - self.timeout_secs + /// Gets the clamped timeout in seconds from the provided options. + fn get_timeout_secs(timeout: Option) -> u64 { + timeout + .map(|d| d.as_secs().clamp(MIN_TIMEOUT_SECS, MAX_TIMEOUT_SECS)) + .unwrap_or(DEFAULT_TIMEOUT_SECS) } /// Get the cycle limit to used for a proof request. @@ -168,14 +86,20 @@ impl NetworkProver { /// - If a cycle limit was explicitly set, use that /// - If simulation is enabled (default), calculate limit by simulating /// - Otherwise use the default cycle limit - fn get_cycle_limit(&self, elf: &[u8], stdin: &SP1Stdin) -> Result { + fn get_cycle_limit( + &self, + elf: &[u8], + stdin: &SP1Stdin, + cycle_limit: Option, + skip_simulation: bool, + ) -> Result { // If cycle_limit was explicitly set via with_cycle_limit(), always use that - if let Some(limit) = self.cycle_limit { + if let Some(limit) = cycle_limit { return Ok(limit); } // If simulation is enabled (default), simulate to get the limit. - if !self.skip_simulation { + if !skip_simulation { let (_, report) = self .local_prover .sp1_prover() @@ -297,16 +221,19 @@ impl NetworkProver { &self, pk: &SP1ProvingKey, stdin: SP1Stdin, + opts: ProofOpts, + kind: SP1ProofKind, ) -> Result { // Ensure the program is registered. let vk_hash = self.register_program(&pk.vk, &pk.elf).await?; // Get the configured settings. let version = SP1_CIRCUIT_VERSION; - let mode = self.get_mode(); - let strategy = self.get_strategy(); - let timeout_secs = self.get_timeout_secs(); - let cycle_limit = self.get_cycle_limit(&pk.elf, &stdin)?; + let mode = kind.into(); + let strategy = opts.fulfillment_strategy.unwrap_or(DEFAULT_FULFILLMENT_STRATEGY); + let timeout_secs = Self::get_timeout_secs(opts.timeout); + let cycle_limit = + self.get_cycle_limit(&pk.elf, &stdin, opts.cycle_limit, opts.skip_simulation)?; // Request the proof. let request_id = self @@ -316,19 +243,6 @@ impl NetworkProver { // Wait for the proof to be generated. self.wait_proof(&request_id, timeout_secs).await } - - /// Note: It is recommended to use NetworkProver::prove() with builder methods instead. - fn prove_with_config<'a>( - &'a self, - pk: &SP1ProvingKey, - stdin: SP1Stdin, - opts: ProofOpts, - context: SP1Context<'a>, - _kind: SP1ProofKind, - ) -> Result { - warn_if_not_default(&opts.sp1_prover_opts, &context); - block_on(self.prove(pk, stdin)).map_err(Into::into) - } } impl Prover for NetworkProver { @@ -352,7 +266,8 @@ impl Prover for NetworkProver { context: SP1Context<'a>, kind: SP1ProofKind, ) -> Result { - self.prove_with_config(pk, stdin, opts, context, kind) + warn_if_not_default(&opts.sp1_prover_opts, &context); + block_on(self.prove(pk, stdin, opts, kind)).map_err(Into::into) } } @@ -469,71 +384,75 @@ where #[cfg(test)] mod tests { use super::*; + use sp1_core_machine::io::SP1Stdin; const TEST_PRIVATE_KEY: &str = "0000000000000000000000000000000000000000000000000000000000000001"; #[test] - fn test_builder_pattern() { - let prover = NetworkProver::new(TEST_PRIVATE_KEY) - .with_timeout_secs(100) - .with_cycle_limit(1000) - .with_mode(ProofMode::Core) - .with_strategy(FulfillmentStrategy::Hosted) - .skip_simulation(); - - assert_eq!(prover.timeout_secs, 100); - assert_eq!(prover.cycle_limit, Some(1000)); - assert_eq!(prover.mode, ProofMode::Core); - assert_eq!(prover.strategy, FulfillmentStrategy::Hosted); - assert!(prover.skip_simulation); + fn test_proof_opts_configuration() { + let opts = ProofOpts { + timeout: Some(Duration::from_secs(100)), + cycle_limit: Some(1000), + fulfillment_strategy: Some(FulfillmentStrategy::Hosted), + skip_simulation: true, + ..Default::default() + }; + + assert_eq!(opts.timeout.unwrap().as_secs(), 100); + assert_eq!(opts.cycle_limit.unwrap(), 1000); + assert_eq!(opts.fulfillment_strategy.unwrap(), FulfillmentStrategy::Hosted); + assert!(opts.skip_simulation); } #[test] - fn test_timeout_bounds() { - // Test minimum bound - let prover = NetworkProver::new(TEST_PRIVATE_KEY).with_timeout_secs(1); - assert_eq!(prover.timeout_secs, MIN_TIMEOUT_SECS); - - // Test maximum bound - let prover = - NetworkProver::new(TEST_PRIVATE_KEY).with_timeout_secs(MAX_TIMEOUT_SECS + 1000); - assert_eq!(prover.timeout_secs, MAX_TIMEOUT_SECS); + fn test_proof_opts_defaults() { + let opts = ProofOpts::default(); - // Test value within bounds - let valid_timeout = 3600; // 1 hour - let prover = NetworkProver::new(TEST_PRIVATE_KEY).with_timeout_secs(valid_timeout); - assert_eq!(prover.timeout_secs, valid_timeout); + assert_eq!(opts.timeout, None); + assert_eq!(opts.cycle_limit, None); + assert_eq!(opts.fulfillment_strategy, None); + assert!(!opts.skip_simulation); } #[test] - fn test_default_values() { + fn test_cycle_limit_handling() { let prover = NetworkProver::new(TEST_PRIVATE_KEY); + let dummy_stdin = SP1Stdin::default(); + let dummy_elf = test_artifacts::FIBONACCI_ELF; + + // Test with explicit cycle limit + let result = prover.get_cycle_limit(dummy_elf, &dummy_stdin, Some(1000), false); + assert_eq!(result.unwrap(), 1000); + + // Test with simulation disabled, no explicit limit + let result = prover.get_cycle_limit(dummy_elf, &dummy_stdin, None, true); + assert_eq!(result.unwrap(), DEFAULT_CYCLE_LIMIT); - assert_eq!(prover.timeout_secs, DEFAULT_TIMEOUT_SECS); - assert_eq!(prover.mode, ProofMode::Core); - assert_eq!(prover.strategy, FulfillmentStrategy::Hosted); - assert_eq!(prover.cycle_limit, None); - assert!(!prover.skip_simulation); + // Test with simulation enabled + let result = prover.get_cycle_limit(dummy_elf, &dummy_stdin, None, false); + assert!(result.is_ok()); } #[test] - fn test_cycle_limit_handling() { - let prover = NetworkProver::new(TEST_PRIVATE_KEY); - let dummy_stdin = SP1Stdin::new(); - let elf = test_artifacts::FIBONACCI_ELF; - - // Test with simulation enabled (default) - let limit = prover.get_cycle_limit(elf, &dummy_stdin).unwrap(); - assert!(limit > 0); + fn test_timeout_clamping() { + // Test minimum bound + let timeout_secs = NetworkProver::get_timeout_secs(Some(Duration::from_secs(1))); + assert_eq!(timeout_secs, MIN_TIMEOUT_SECS); - // Test with simulation disabled - let prover = prover.skip_simulation(); - assert_eq!(prover.get_cycle_limit(elf, &dummy_stdin).unwrap(), DEFAULT_CYCLE_LIMIT); + // Test maximum bound + let timeout_secs = + NetworkProver::get_timeout_secs(Some(Duration::from_secs(MAX_TIMEOUT_SECS + 1000))); + assert_eq!(timeout_secs, MAX_TIMEOUT_SECS); - // Test with explicit cycle limit - let explicit_limit = 1000; - let prover = prover.with_cycle_limit(explicit_limit); - assert_eq!(prover.get_cycle_limit(elf, &dummy_stdin).unwrap(), explicit_limit); + // Test value within bounds + let valid_timeout = 3600; + let timeout_secs = + NetworkProver::get_timeout_secs(Some(Duration::from_secs(valid_timeout))); + assert_eq!(timeout_secs, valid_timeout); + + // Test default when None + let timeout_secs = NetworkProver::get_timeout_secs(None); + assert_eq!(timeout_secs, DEFAULT_TIMEOUT_SECS); } } diff --git a/crates/sdk/src/provers/mod.rs b/crates/sdk/src/provers/mod.rs index 1db5309fac..985fca23ea 100644 --- a/crates/sdk/src/provers/mod.rs +++ b/crates/sdk/src/provers/mod.rs @@ -24,6 +24,9 @@ use sp1_stark::{air::PublicValues, MachineVerificationError, SP1ProverOpts, Word use strum_macros::EnumString; use thiserror::Error; +#[cfg(feature = "network-v2")] +use crate::network_v2::FulfillmentStrategy; + use crate::install::try_install_circuit_artifacts; use crate::{SP1Proof, SP1ProofKind, SP1ProofWithPublicValues}; @@ -43,6 +46,15 @@ pub struct ProofOpts { pub sp1_prover_opts: SP1ProverOpts, /// Optional timeout duration for proof generation. pub timeout: Option, + /// Optional fulfillment strategy for proof generation. Only used for network proofs. + #[cfg(feature = "network-v2")] + pub fulfillment_strategy: Option, + /// Optional cycle limit for proof generation. Only used for network proofs. + #[cfg(feature = "network-v2")] + pub cycle_limit: Option, + /// Whether to skip simulation for proof generation. Only used for network proofs. + #[cfg(feature = "network-v2")] + pub skip_simulation: bool, } #[derive(Error, Debug)] diff --git a/examples/fibonacci/script/bin/network.rs b/examples/fibonacci/script/bin/network.rs index d8d271cc4d..950f39563d 100644 --- a/examples/fibonacci/script/bin/network.rs +++ b/examples/fibonacci/script/bin/network.rs @@ -1,58 +1,35 @@ -use sp1_sdk::{ - include_elf, - network_v2::{Error, FulfillmentStrategy, NetworkProver, DEFAULT_PROVER_NETWORK_RPC}, - utils, Prover, SP1Stdin, -}; -use std::env; +use sp1_sdk::network_v2::FulfillmentStrategy; +use sp1_sdk::{include_elf, utils, ProverClient, SP1ProofWithPublicValues, SP1Stdin}; +use std::time::Duration; /// The ELF we want to execute inside the zkVM. const ELF: &[u8] = include_elf!("fibonacci-program"); -#[tokio::main] -async fn main() { +fn main() { // Setup logging. utils::setup_logger(); - // Read environment variables. - let private_key = - env::var("SP1_PRIVATE_KEY").expect("SP1_PRIVATE_KEY must be set for remote proving"); - let rpc_url = - env::var("PROVER_NETWORK_RPC").unwrap_or_else(|_| DEFAULT_PROVER_NETWORK_RPC.to_string()); + // Create an input stream and write '500' to it. + let n = 1000u32; - // Create the network prover client. - let prover = NetworkProver::new(&private_key) - .with_rpc_url(rpc_url) - .with_cycle_limit(20_000) // Set a manual cycle limit - .with_timeout_secs(3600) // Set a 1 hour timeout - .with_strategy(FulfillmentStrategy::Hosted) // Use the hosted strategy - .skip_simulation(); // Skip simulation since we know our cycle requirements - - // Setup proving key and verifying key. - let (pk, vk) = prover.setup(ELF); - - // Write the input to the stdin. + // The input stream that the program will read from using `sp1_zkvm::io::read`. Note that the + // types of the elements in the input stream must match the types being read in the program. let mut stdin = SP1Stdin::new(); - stdin.write(&1000u32); - - // Send the proof request to the prover network, with examples of how to handle errors. - let proof_result = prover.prove(&pk, stdin).await; - let mut proof = match proof_result { - Ok(proof) => proof, - Err(e) => match e { - Error::RequestUnexecutable => { - eprintln!("Error executing: {}", e); - std::process::exit(1); - } - Error::RequestUnfulfillable => { - eprintln!("Error proving: {}", e); - std::process::exit(1); - } - _ => { - eprintln!("Unexpected error: {}", e); - std::process::exit(1); - } - }, - }; + stdin.write(&n); + + // Create a `ProverClient` method. + let client = ProverClient::new(); + + // Generate the proof for the given program and input, using the our network configuration. + let (pk, vk) = client.setup(ELF); + let mut proof = client + .prove(&pk, stdin) + .strategy(FulfillmentStrategy::Hosted) + .cycle_limit(20_000) + .timeout(Duration::from_secs(3600)) + .skip_simulation() + .run() + .unwrap(); println!("generated proof"); @@ -68,7 +45,15 @@ async fn main() { println!("b: {}", b); // Verify proof and public values - prover.verify(&proof, &vk).expect("verification failed"); + client.verify(&proof, &vk).expect("verification failed"); + + // Test a round trip of proof serialization and deserialization. + proof.save("proof-with-pis.bin").expect("saving proof failed"); + let deserialized_proof = + SP1ProofWithPublicValues::load("proof-with-pis.bin").expect("loading proof failed"); + + // Verify the deserialized proof. + client.verify(&deserialized_proof, &vk).expect("verification failed"); - println!("successfully generated and verified proof for the program!"); + println!("successfully generated and verified proof for the program!") }