Skip to main content

sp1_sdk/network/
prover.rs

1//! # Network Prover
2//!
3//! This module provides an implementation of the [`crate::Prover`] trait that can generate proofs
4//! on a remote RPC server.
5
6use std::time::{Duration, Instant};
7
8use super::prove::NetworkProveBuilder;
9use crate::{
10    network::{
11        client::NetworkClient,
12        proto::{
13            types::{
14                ExecutionStatus, FulfillmentStatus, FulfillmentStrategy, ProofMode, ProofRequest,
15            },
16            GetProofRequestStatusResponse,
17        },
18        signer::NetworkSigner,
19        tee::{client::Client as TeeClient, verify_tee_proof},
20        Error, NetworkMode, DEFAULT_AUCTION_TIMEOUT_DURATION, DEFAULT_GAS_LIMIT,
21        MAINNET_EXPLORER_URL, MAINNET_RPC_URL, PRIVATE_EXPLORER_URL, PRIVATE_NETWORK_RPC_URL,
22        RESERVED_EXPLORER_URL, RESERVED_RPC_URL, TEE_NETWORK_RPC_URL,
23    },
24    prover::{verify_proof, BaseProveRequest, SendFutureResult},
25    ProofFromNetwork, Prover, SP1ProofMode, SP1ProofWithPublicValues, SP1ProvingKey,
26    SP1VerifyingKey,
27};
28
29use crate::network::proto::GetProofRequestParamsResponse;
30
31use alloy_primitives::{Address, B256, U256};
32use anyhow::{Context, Result};
33use sp1_build::Elf;
34use sp1_core_executor::{SP1Context, StatusCode};
35use sp1_core_machine::io::SP1Stdin;
36use sp1_core_machine::riscv::RiscvAir;
37use sp1_hypercube::Machine;
38use sp1_primitives::SP1Field;
39use sp1_prover::worker::{SP1LightNode, SP1NodeCore};
40use sp1_prover::SP1_CIRCUIT_VERSION;
41
42use tokio::time::sleep;
43
44/// An implementation of [`crate::ProverClient`] that can generate proofs on a remote RPC server.
45#[derive(Clone)]
46pub struct NetworkProver {
47    pub(crate) client: NetworkClient,
48    pub(crate) node: SP1LightNode,
49    pub(crate) tee_signers: Vec<Address>,
50    pub(crate) network_mode: NetworkMode,
51    /// Whether to use hosted defaults for proof requests.
52    ///
53    /// When set, [`NetworkProver::prove`] skips local simulation and sets the cycle and gas limits
54    /// to their maximum, so that `prove(&pk, stdin).await` works without any network-specific
55    /// toggles. This is the behavior wanted by self-hosted clusters talking to the
56    /// network-gateway. It is independent of [`NetworkMode`]; a hosted prover runs in
57    /// [`NetworkMode::Reserved`].
58    pub(crate) hosted: bool,
59}
60
61impl Prover for NetworkProver {
62    // todo!(n): Remove usage of anyhow.
63    type ProvingKey = SP1ProvingKey;
64    type Error = anyhow::Error;
65    type ProveRequest<'a> = NetworkProveBuilder<'a>;
66
67    fn inner(&self) -> &SP1NodeCore {
68        self.node.inner()
69    }
70
71    fn setup(&self, elf: Elf) -> impl SendFutureResult<Self::ProvingKey, Self::Error> {
72        async move {
73            let vk = self.node.setup(&elf).await?;
74            let pk = SP1ProvingKey { vk, elf };
75            Ok(pk)
76        }
77    }
78
79    fn prove<'a>(&'a self, pk: &'a Self::ProvingKey, stdin: SP1Stdin) -> Self::ProveRequest<'a> {
80        let strategy = self.default_fulfillment_strategy();
81
82        // A hosted prover skips simulation and proves up to the maximum limits by default, so that
83        // `prove(&pk, stdin).await` works with no network-specific toggles. These remain overridable
84        // per request via the builder methods.
85        let (skip_simulation, cycle_limit, gas_limit) =
86            if self.hosted { (true, Some(u64::MAX), Some(u64::MAX)) } else { (false, None, None) };
87
88        NetworkProveBuilder {
89            base: BaseProveRequest::new(self, pk, stdin),
90            timeout: None,
91            strategy,
92            skip_simulation,
93            cycle_limit,
94            gas_limit,
95            tee_2fa: false,
96            min_auction_period: 0,
97            whitelist: None,
98            auctioneer: None,
99            executor: None,
100            verifier: None,
101            treasury: None,
102            max_price_per_pgu: None,
103            auction_timeout: None,
104            private_stdin: false,
105        }
106    }
107
108    fn verify(
109        &self,
110        proof: &SP1ProofWithPublicValues,
111        vkey: &SP1VerifyingKey,
112        status_code: Option<StatusCode>,
113    ) -> Result<(), crate::SP1VerificationError> {
114        if let Some(tee_proof) = &proof.tee_proof {
115            verify_tee_proof(&self.tee_signers, tee_proof, vkey, proof.public_values.as_ref())?;
116        }
117
118        verify_proof(self.inner(), self.version(), proof, vkey, status_code)
119    }
120}
121
122impl NetworkProver {
123    /// Creates a new [`NetworkProver`] with the given signer and network mode.
124    ///
125    /// # Details
126    /// * `signer`: The network signer to use for signing requests. Can be a `NetworkSigner`,
127    ///   private key string, or anything that implements `Into<NetworkSigner>`.
128    /// * `rpc_url`: The rpc url to use for the prover network.
129    /// * `network_mode`: The network mode determining which proving strategy to use.
130    ///
131    /// # Examples
132    /// Using a private key string:
133    /// ```rust,no_run
134    /// use sp1_sdk::{network::NetworkMode, NetworkProver};
135    ///
136    /// let prover = NetworkProver::new("0x...", "...", NetworkMode::Mainnet);
137    /// ```
138    ///
139    /// Using a `NetworkSigner`:
140    /// ```rust,no_run
141    /// use sp1_sdk::{
142    ///     network::{signer::NetworkSigner, NetworkMode},
143    ///     NetworkProver,
144    /// };
145    ///
146    /// let signer = NetworkSigner::local("0x...").unwrap();
147    /// let prover = NetworkProver::new(signer, "...", NetworkMode::Reserved);
148    /// ```
149    #[must_use]
150    pub async fn new(
151        signer: impl Into<NetworkSigner>,
152        rpc_url: &str,
153        network_mode: NetworkMode,
154    ) -> Self {
155        Self::new_with_machine(signer, rpc_url, network_mode, RiscvAir::machine()).await
156    }
157
158    #[must_use]
159    /// Same as `new` but with a custom machine
160    pub async fn new_with_machine(
161        signer: impl Into<NetworkSigner>,
162        rpc_url: &str,
163        network_mode: NetworkMode,
164        machine: Machine<SP1Field, RiscvAir<SP1Field>>,
165    ) -> Self {
166        // Install default CryptoProvider if not already installed.
167        let _ = rustls::crypto::ring::default_provider().install_default();
168
169        let signer = signer.into();
170        let node = SP1LightNode::new_with_machine(machine).await;
171        let client = NetworkClient::new(signer, rpc_url, network_mode);
172        Self { client, node, tee_signers: vec![], network_mode, hosted: false }
173    }
174
175    /// Sets the list of TEE signers, used for verifying TEE proofs.
176    #[must_use]
177    pub fn with_tee_signers(mut self, tee_signers: Vec<Address>) -> Self {
178        self.tee_signers = tee_signers;
179        self
180    }
181
182    /// Sets whether this prover uses hosted defaults (skip simulation, max cycle and gas limits).
183    ///
184    /// See [`NetworkProver::hosted`] for details.
185    #[must_use]
186    pub(crate) fn with_hosted(mut self, hosted: bool) -> Self {
187        self.hosted = hosted;
188        self
189    }
190
191    /// Gets the network mode of this prover.
192    #[must_use]
193    pub fn network_mode(&self) -> NetworkMode {
194        self.network_mode
195    }
196
197    /// Gets the default fulfillment strategy for this prover's network mode.
198    #[must_use]
199    pub fn default_fulfillment_strategy(&self) -> FulfillmentStrategy {
200        match self.network_mode {
201            NetworkMode::Mainnet => FulfillmentStrategy::Auction,
202            NetworkMode::Reserved => FulfillmentStrategy::Hosted,
203        }
204    }
205
206    /// Get the credit balance of your account on the prover network.
207    ///
208    /// # Example
209    /// ```rust,no_run
210    /// use sp1_sdk::{ProverClient, SP1Stdin};
211    ///
212    /// tokio_test::block_on(async {
213    ///     let client = ProverClient::builder().network().build().await;
214    ///     let balance = client.get_balance().await.unwrap();
215    /// })
216    /// ```
217    pub async fn get_balance(&self) -> Result<U256> {
218        self.client.get_balance().await
219    }
220
221    /// Registers a program if it is not already registered.
222    ///
223    /// # Details
224    /// * `vk`: The verifying key to use for the program.
225    /// * `elf`: The elf to use for the program.
226    ///
227    /// Note that this method requires that the user honestly registers the program (i.e., the elf
228    /// matches the vk).
229    ///
230    /// # Example
231    /// ```rust,no_run
232    /// use sp1_sdk::{Elf, Prover, ProverClient, ProvingKey, SP1Stdin};
233    ///
234    /// tokio_test::block_on(async {
235    ///     let elf = Elf::Static(&[1, 2, 3]);
236    ///     let client = ProverClient::builder().network().build().await;
237    ///     let pk = client.setup(elf).await.unwrap();
238    ///     let vk_hash = client.register_program(&pk.verifying_key(), pk.elf()).await.unwrap();
239    /// });
240    /// ```
241    pub async fn register_program(&self, vk: &SP1VerifyingKey, elf: &[u8]) -> Result<B256> {
242        self.client.register_program(vk, elf).await
243    }
244
245    /// Gets the proof request parameters from the network.
246    ///
247    /// # Details
248    /// * `mode`: The proof mode to get the parameters for.
249    ///
250    /// # Example
251    /// ```rust,no_run
252    /// use sp1_sdk::{ProverClient, SP1ProofMode};
253    /// tokio_test::block_on(async {
254    ///     let client = ProverClient::builder().network().build().await;
255    ///     let params = client.get_proof_request_params(SP1ProofMode::Compressed).await.unwrap();
256    /// })
257    /// ```
258    pub async fn get_proof_request_params(
259        &self,
260        mode: SP1ProofMode,
261    ) -> Result<GetProofRequestParamsResponse> {
262        match self.network_mode {
263            NetworkMode::Mainnet => {
264                let response = self.client.get_proof_request_params(mode.into()).await?;
265                Ok(response)
266            }
267            NetworkMode::Reserved => {
268                Err(anyhow::anyhow!(
269                    "get_proof_request_params is only available in Mainnet mode (auction-based proving). This feature is not supported in Reserved mode."
270                ))
271            }
272        }
273    }
274
275    /// Gets the status of a proof request. Re-exposes the status response from the client.
276    ///
277    /// # Details
278    /// * `request_id`: The request ID to get the status of.
279    ///
280    /// # Example
281    /// ```rust,no_run
282    /// use sp1_sdk::{network::B256, ProverClient};
283    ///
284    /// tokio_test::block_on(async {
285    ///     let request_id = B256::from_slice(&vec![1u8; 32]);
286    ///     let client = ProverClient::builder().network().build().await;
287    ///     let (status, maybe_proof) = client.get_proof_status(request_id).await.unwrap();
288    /// })
289    /// ```
290    pub async fn get_proof_status(
291        &self,
292        request_id: B256,
293    ) -> Result<(GetProofRequestStatusResponse, Option<SP1ProofWithPublicValues>)> {
294        let (status, maybe_proof): (GetProofRequestStatusResponse, Option<ProofFromNetwork>) =
295            self.client.get_proof_request_status(request_id, None).await?;
296        let maybe_proof = maybe_proof.map(Into::into);
297        Ok((status, maybe_proof))
298    }
299
300    /// Gets the proof request details, if available.
301    ///
302    /// The [`ProofRequest`] type contains useful information about the request, like the cycle
303    /// count, or the gas used.
304    ///
305    /// # Details
306    /// * `request_id`: The request ID to get the status of.
307    ///
308    /// # Example
309    /// ```rust,no_run
310    /// use sp1_sdk::{network::B256, ProverClient};
311    ///
312    /// tokio_test::block_on(async {
313    ///     let request_id = B256::from_slice(&vec![1u8; 32]);
314    ///     let client = ProverClient::builder().network().build().await;
315    ///     let request = client.get_proof_request(request_id).await.unwrap();
316    /// })
317    /// ```
318    pub async fn get_proof_request(&self, request_id: B256) -> Result<Option<ProofRequest>> {
319        let res = self.client.get_proof_request_details(request_id, None).await?;
320        Ok(res.request)
321    }
322
323    /// Gets the status of a proof request with handling for timeouts and unfulfillable requests.
324    ///
325    /// Returns the proof if it is fulfilled and the fulfillment status. Handles statuses indicating
326    /// that the proof is unfulfillable or unexecutable with errors.
327    ///
328    /// # Details
329    /// * `request_id`: The request ID to get the status of.
330    /// * `remaining_timeout`: The remaining timeout for the proof request.
331    ///
332    /// # Example
333    /// ```rust,no_run
334    /// use sp1_sdk::{network::B256, ProverClient};
335    ///
336    /// tokio_test::block_on(async {
337    ///     let request_id = B256::from_slice(&vec![1u8; 32]);
338    ///     let client = ProverClient::builder().network().build().await;
339    ///     let (maybe_proof, fulfillment_status) =
340    ///         client.process_proof_status(request_id, None).await.unwrap();
341    /// })
342    /// ```
343    pub async fn process_proof_status(
344        &self,
345        request_id: B256,
346        remaining_timeout: Option<Duration>,
347    ) -> Result<(Option<SP1ProofWithPublicValues>, FulfillmentStatus)> {
348        // Get the status.
349        let (status, maybe_proof): (GetProofRequestStatusResponse, Option<ProofFromNetwork>) =
350            self.client.get_proof_request_status(request_id, remaining_timeout).await?;
351
352        let maybe_proof = maybe_proof.map(Into::into);
353
354        let execution_status = ExecutionStatus::try_from(status.execution_status()).unwrap();
355        let fulfillment_status = FulfillmentStatus::try_from(status.fulfillment_status()).unwrap();
356
357        // Check fulfillment before the deadline — a fulfilled proof should be
358        // returned even if polled after the deadline has passed.
359        if fulfillment_status == FulfillmentStatus::Fulfilled {
360            return Ok((maybe_proof, fulfillment_status));
361        }
362        if execution_status == ExecutionStatus::Unexecutable {
363            return Err(Error::RequestUnexecutable { request_id: request_id.to_vec() }.into());
364        }
365        if fulfillment_status == FulfillmentStatus::Unfulfillable {
366            return Err(Error::RequestUnfulfillable { request_id: request_id.to_vec() }.into());
367        }
368
369        // Only check the deadline for requests that are still in progress.
370        let current_time =
371            std::time::SystemTime::now().duration_since(std::time::UNIX_EPOCH).unwrap().as_secs();
372        if current_time > status.deadline() {
373            return Err(Error::RequestTimedOut { request_id: request_id.to_vec() }.into());
374        }
375
376        Ok((None, fulfillment_status))
377    }
378
379    /// Requests a proof from the prover network, returning the request ID.
380    ///
381    /// # Details
382    /// * `vk_hash`: The hash of the verifying key to use for the proof.
383    /// * `stdin`: The input to use for the proof.
384    /// * `mode`: The proof mode to use for the proof.
385    /// * `strategy`: The fulfillment strategy to use for the proof.
386    /// * `cycle_limit`: The cycle limit to use for the proof.
387    /// * `gas_limit`: The gas limit to use for the proof.
388    /// * `timeout`: The timeout for the proof request.
389    /// * `min_auction_period`: The minimum auction period for the proof request in seconds.
390    /// * `whitelist`: The auction whitelist for the proof request.
391    /// * `auctioneer`: The auctioneer address for the proof request.
392    /// * `executor`: The executor address for the proof request.
393    /// * `verifier`: The verifier address for the proof request.
394    /// * `treasury`: The treasury address for the proof request.
395    /// * `public_values_hash`: The hash of the public values to use for the proof.
396    /// * `base_fee`: The base fee to use for the proof request.
397    /// * `max_price_per_pgu`: The maximum price per PGU to use for the proof request.
398    /// * `domain`: The domain bytes to use for the proof request.
399    #[allow(clippy::too_many_arguments)]
400    pub(crate) async fn request_proof(
401        &self,
402        vk_hash: B256,
403        stdin: &SP1Stdin,
404        mode: ProofMode,
405        strategy: FulfillmentStrategy,
406        cycle_limit: u64,
407        gas_limit: u64,
408        timeout: Option<Duration>,
409        min_auction_period: u64,
410        whitelist: Option<Vec<Address>>,
411        auctioneer: Address,
412        executor: Address,
413        verifier: Address,
414        treasury: Address,
415        public_values_hash: Option<Vec<u8>>,
416        base_fee: u64,
417        max_price_per_pgu: u64,
418        domain: Vec<u8>,
419        private_stdin: bool,
420    ) -> Result<B256> {
421        if self.client.rpc_url == TEE_NETWORK_RPC_URL && strategy != FulfillmentStrategy::Reserved {
422            return Err(anyhow::anyhow!(
423                "Private proving is available with reserved fulfillment strategy only. Use FulfillmentStrategy::Reserved."
424            ));
425        }
426
427        // Get the timeout. If no timeout is specified, auto-calculate based on gas limit for
428        // Mainnet, use default timeout for Reserved.
429        let timeout_secs = timeout.map_or_else(
430            || match self.network_mode {
431                NetworkMode::Mainnet => super::utils::calculate_timeout_from_gas_limit(gas_limit),
432                NetworkMode::Reserved => super::DEFAULT_TIMEOUT_SECS,
433            },
434            |dur| dur.as_secs(),
435        );
436
437        let max_price_per_bpgu = max_price_per_pgu * 1_000_000_000;
438
439        // Log the request.
440        tracing::info!("Requesting proof:");
441        tracing::info!("├─ Strategy: {:?}", strategy);
442        tracing::info!("├─ Proof mode: {:?}", mode);
443        tracing::info!("├─ Circuit version: {}", SP1_CIRCUIT_VERSION);
444        tracing::info!("├─ Timeout: {} seconds", timeout_secs);
445        if let Some(ref hash) = public_values_hash {
446            tracing::info!("├─ Public values hash: 0x{}", hex::encode(hash));
447        }
448        if strategy == FulfillmentStrategy::Auction {
449            tracing::info!(
450                "├─ Base fee: {} ({} $PROVE)",
451                base_fee,
452                Self::format_prove_amount(base_fee)
453            );
454            tracing::info!(
455                "├─ Max price per bPGU: {} ({} $PROVE)",
456                max_price_per_bpgu,
457                Self::format_prove_amount(max_price_per_bpgu)
458            );
459            tracing::info!("├─ Minimum auction period: {:?} seconds", min_auction_period);
460            tracing::info!("├─ Prover Whitelist: {:?}", whitelist);
461        }
462        tracing::info!("├─ Cycle limit: {} cycles", cycle_limit);
463        tracing::info!("└─ Gas limit: {} PGUs", gas_limit);
464
465        // Request the proof.
466        let response = self
467            .client
468            .request_proof(
469                vk_hash,
470                stdin,
471                mode,
472                SP1_CIRCUIT_VERSION,
473                strategy,
474                timeout_secs,
475                cycle_limit,
476                gas_limit,
477                min_auction_period,
478                whitelist,
479                auctioneer,
480                executor,
481                verifier,
482                treasury,
483                public_values_hash,
484                base_fee,
485                max_price_per_pgu,
486                domain,
487                private_stdin,
488            )
489            .await?;
490
491        // Log the request ID and transaction hash.
492        let tx_hash = B256::from_slice(response.tx_hash());
493        let request_id = B256::from_slice(response.request_id());
494        tracing::info!("Created request {} in transaction {:?}", request_id, tx_hash);
495
496        let explorer = match self.client.rpc_url.trim_end_matches('/') {
497            MAINNET_RPC_URL => Some(MAINNET_EXPLORER_URL),
498            RESERVED_RPC_URL => Some(RESERVED_EXPLORER_URL),
499            PRIVATE_NETWORK_RPC_URL => Some(PRIVATE_EXPLORER_URL),
500            _ => None,
501        };
502
503        if let Some(base_url) = explorer {
504            tracing::info!("View request status at: {}/request/{}", base_url, request_id);
505        }
506
507        Ok(request_id)
508    }
509
510    /// Cancels a proof request by updating the deadline to the current time.
511    /// Only available in Mainnet mode (auction-based proving).
512    pub async fn cancel_request(&self, request_id: B256) -> Result<()> {
513        match self.network_mode {
514            NetworkMode::Mainnet => {
515                self.client.cancel_request(request_id).await?;
516                Ok(())
517            }
518            NetworkMode::Reserved => {
519                Err(anyhow::anyhow!(
520                    "cancel_request is only available in Mainnet mode (auction-based proving). This feature is not supported in Reserved mode."
521                ))
522            }
523        }
524    }
525
526    /// Waits for a proof to be generated and returns the proof. If a timeout is supplied, the
527    /// function will return an error if the proof is not generated within the timeout.
528    /// If `auction_timeout` is supplied, the function will return an error if the proof request
529    /// remains in "requested" status for longer than the auction timeout.
530    pub async fn wait_proof(
531        &self,
532        request_id: B256,
533        timeout: Option<Duration>,
534        auction_timeout: Option<Duration>,
535    ) -> Result<SP1ProofWithPublicValues> {
536        let mut is_assigned = false;
537        let start_time = Instant::now();
538        let mut requested_start_time: Option<Instant> = None;
539        #[allow(unused)]
540        let auction_timeout_duration = auction_timeout.unwrap_or(DEFAULT_AUCTION_TIMEOUT_DURATION);
541
542        loop {
543            // Calculate the remaining timeout.
544            if let Some(timeout) = timeout {
545                if start_time.elapsed() > timeout {
546                    return Err(Error::RequestTimedOut { request_id: request_id.to_vec() }.into());
547                }
548            }
549            let remaining_timeout = timeout.map(|t| {
550                let elapsed = start_time.elapsed();
551                t.checked_sub(elapsed).unwrap_or(Duration::from_secs(0))
552            });
553
554            let (maybe_proof, fulfillment_status) =
555                self.process_proof_status(request_id, remaining_timeout).await?;
556
557            if fulfillment_status == FulfillmentStatus::Fulfilled {
558                return Ok(maybe_proof.unwrap());
559            } else if fulfillment_status == FulfillmentStatus::Assigned && !is_assigned {
560                tracing::info!("Proof request assigned, proving...");
561                is_assigned = true;
562            } else if fulfillment_status == FulfillmentStatus::Requested {
563                // Track when we first entered requested status.
564                if requested_start_time.is_none() {
565                    requested_start_time = Some(Instant::now());
566                }
567
568                // Check if we've exceeded the auction timeout (only for Mainnet mode).
569                if self.network_mode == NetworkMode::Mainnet {
570                    if let Some(req_start) = requested_start_time {
571                        if req_start.elapsed() > auction_timeout_duration {
572                            tracing::info!("Auction period exceeded, cancelling request...");
573                            self.client.cancel_request(request_id).await?;
574                            return Err(Error::RequestAuctionTimedOut {
575                                request_id: request_id.to_vec(),
576                            }
577                            .into());
578                        }
579                    }
580                }
581            }
582
583            sleep(Duration::from_secs(2)).await;
584        }
585    }
586
587    #[allow(clippy::too_many_arguments)]
588    pub(crate) async fn request_proof_impl(
589        &self,
590        pk: &SP1ProvingKey,
591        stdin: &SP1Stdin,
592        mode: SP1ProofMode,
593        strategy: FulfillmentStrategy,
594        timeout: Option<Duration>,
595        skip_simulation: bool,
596        cycle_limit: Option<u64>,
597        gas_limit: Option<u64>,
598        min_auction_period: u64,
599        whitelist: Option<Vec<Address>>,
600        auctioneer: Option<Address>,
601        executor: Option<Address>,
602        verifier: Option<Address>,
603        treasury: Option<Address>,
604        max_price_per_pgu: Option<u64>,
605        private_stdin: bool,
606    ) -> Result<B256> {
607        let vk_hash = self.register_program(&pk.vk, &pk.elf).await?;
608        let (cycle_limit, gas_limit, public_values_hash) = self
609            .get_execution_limits(cycle_limit, gas_limit, &pk.elf, stdin, skip_simulation)
610            .await?;
611        let (auctioneer, executor, verifier, treasury, max_price_per_pgu, base_fee, domain) = self
612            .get_auction_request_params(
613                mode,
614                auctioneer,
615                executor,
616                verifier,
617                treasury,
618                max_price_per_pgu,
619            )
620            .await?;
621
622        self.request_proof(
623            vk_hash,
624            stdin,
625            mode.into(),
626            strategy,
627            cycle_limit,
628            gas_limit,
629            timeout,
630            min_auction_period,
631            whitelist,
632            auctioneer,
633            executor,
634            verifier,
635            treasury,
636            public_values_hash,
637            base_fee,
638            max_price_per_pgu,
639            domain,
640            private_stdin,
641        )
642        .await
643    }
644
645    #[allow(clippy::too_many_arguments)]
646    pub(crate) async fn prove_impl(
647        &self,
648        pk: &SP1ProvingKey,
649        stdin: &SP1Stdin,
650        mode: SP1ProofMode,
651        strategy: FulfillmentStrategy,
652        timeout: Option<Duration>,
653        skip_simulation: bool,
654        cycle_limit: Option<u64>,
655        gas_limit: Option<u64>,
656        tee_2fa: bool,
657        min_auction_period: u64,
658        whitelist: Option<Vec<Address>>,
659        auctioneer: Option<Address>,
660        executor: Option<Address>,
661        verifier: Option<Address>,
662        treasury: Option<Address>,
663        max_price_per_pgu: Option<u64>,
664        auction_timeout: Option<Duration>,
665        private_stdin: bool,
666    ) -> Result<SP1ProofWithPublicValues> {
667        #[allow(unused_mut)]
668        let mut whitelist = whitelist.clone();
669
670        // Attempt to get proof, with retry logic for failed auction requests.
671        #[allow(clippy::never_loop)]
672        loop {
673            let request_id = self
674                .request_proof_impl(
675                    pk,
676                    stdin,
677                    mode,
678                    strategy,
679                    timeout,
680                    skip_simulation,
681                    cycle_limit,
682                    gas_limit,
683                    min_auction_period,
684                    whitelist.clone(),
685                    auctioneer,
686                    executor,
687                    verifier,
688                    treasury,
689                    max_price_per_pgu,
690                    private_stdin,
691                )
692                .await?;
693
694            // If 2FA is enabled, spawn a task to get the tee proof.
695            // Note: We only support one type of TEE proof for now.
696            let handle = if tee_2fa {
697                let elf_vec = pk.elf.to_vec();
698                let request = super::tee::api::TEERequest::new(
699                    &self.client.signer,
700                    *request_id,
701                    elf_vec,
702                    stdin.clone(),
703                    cycle_limit.unwrap_or_else(|| {
704                        super::utils::get_default_cycle_limit_for_mode(self.network_mode)
705                    }),
706                )
707                .await?;
708
709                Some(tokio::spawn(async move {
710                    let tee_client = TeeClient::default();
711
712                    tee_client.execute(request).await
713                }))
714            } else {
715                None
716            };
717
718            // Wait for the proof to be generated.
719            let mut proof = match self.wait_proof(request_id, timeout, auction_timeout).await {
720                Ok(proof) => proof,
721                Err(e) => {
722                    // Check if this is a Mainnet auction request that we can retry.
723                    if self.network_mode == NetworkMode::Mainnet {
724                        if let Some(network_error) = e.downcast_ref::<Error>() {
725                            if matches!(
726                                network_error,
727                                Error::RequestUnfulfillable { .. }
728                                    | Error::RequestTimedOut { .. }
729                                    | Error::RequestAuctionTimedOut { .. }
730                            ) && strategy == FulfillmentStrategy::Auction
731                                && whitelist.is_none()
732                            {
733                                tracing::warn!(
734                                    "Retrying auction request with fallback whitelist..."
735                                );
736
737                                // Get fallback high availability provers and retry.
738                                let mut rpc = self.client.auction_prover_network_client().await?;
739                                let fallback_whitelist = rpc
740                                    .get_provers_by_uptime(
741                                        crate::network::proto::auction_types::GetProversByUptimeRequest {
742                                            high_availability_only: true,
743                                        },
744                                    )
745                                    .await?
746                                    .into_inner()
747                                    .provers
748                                    .into_iter()
749                                    .map(|p| Address::from_slice(&p))
750                                    .collect::<Vec<_>>();
751                                if fallback_whitelist.is_empty() {
752                                    tracing::warn!("No fallback high availability provers found.");
753                                    return Err(e);
754                                }
755                                whitelist = Some(fallback_whitelist);
756                                continue;
757                            }
758                        }
759                    }
760
761                    // If we can't retry, return the error.
762                    return Err(e);
763                }
764            };
765
766            // If 2FA is enabled, wait for the tee proof to be generated and add it to the proof.
767            if let Some(handle) = handle {
768                let tee_proof = handle
769                    .await
770                    .context("Spawning a new task to get the tee proof failed")?
771                    .context("Error response from TEE server")?;
772
773                proof.tee_proof = Some(tee_proof.as_prefix_bytes());
774            }
775
776            return Ok(proof);
777        }
778    }
779
780    /// The cycle limit and gas limit are determined according to the following priority:
781    ///
782    /// 1. If either of the limits are explicitly set by the requester, use the specified value.
783    /// 2. If simulation is enabled, calculate the limits by simulating the execution of the
784    ///    program. This is the default behavior.
785    /// 3. Otherwise, use the default limits ([`MAINNET_DEFAULT_CYCLE_LIMIT`] or
786    ///    [`RESERVED_DEFAULT_CYCLE_LIMIT`] and [`DEFAULT_GAS_LIMIT`]).
787    async fn get_execution_limits(
788        &self,
789        cycle_limit: Option<u64>,
790        gas_limit: Option<u64>,
791        elf: &[u8],
792        stdin: &SP1Stdin,
793        skip_simulation: bool,
794    ) -> Result<(u64, u64, Option<Vec<u8>>)> {
795        let cycle_limit_value = if let Some(cycles) = cycle_limit {
796            cycles
797        } else if skip_simulation {
798            super::utils::get_default_cycle_limit_for_mode(self.network_mode)
799        } else {
800            // Will be calculated through simulation.
801            0
802        };
803
804        let gas_limit_value = if let Some(gas) = gas_limit {
805            gas
806        } else if skip_simulation {
807            DEFAULT_GAS_LIMIT
808        } else {
809            // Will be calculated through simulation.
810            0
811        };
812
813        // If both limits were explicitly provided or skip_simulation is true, return immediately.
814        if (cycle_limit.is_some() && gas_limit.is_some()) || skip_simulation {
815            return Ok((cycle_limit_value, gas_limit_value, None));
816        }
817
818        // One of the limits were not provided and simulation is not skipped, so simulate to get
819        // one. or both limits.
820        let execute_result = self
821            .node
822            .execute(elf, stdin.clone(), SP1Context::builder().calculate_gas(true).build())
823            .await
824            .map_err(|_| Error::SimulationFailed)?;
825
826        let (_, committed_value_digest, report) = execute_result;
827
828        // Use simulated values for the ones that are not explicitly provided.
829        let final_cycle_limit = if cycle_limit.is_none() {
830            report.total_instruction_count()
831        } else {
832            cycle_limit_value
833        };
834        let final_gas_limit = if gas_limit.is_none() {
835            report.gas().unwrap_or(DEFAULT_GAS_LIMIT)
836        } else {
837            gas_limit_value
838        };
839
840        let public_values_hash = Some(committed_value_digest.to_vec());
841
842        Ok((final_cycle_limit, final_gas_limit, public_values_hash))
843    }
844
845    /// The proof request parameters for the auction strategy are determined according to the
846    /// following priority:
847    ///
848    /// 1. If the parameter is explicitly set by the requester, use the specified value.
849    /// 2. Otherwise, use the default values fetched from the network RPC.
850    #[allow(unused_variables)]
851    #[allow(clippy::unused_async)]
852    async fn get_auction_request_params(
853        &self,
854        mode: SP1ProofMode,
855        auctioneer: Option<Address>,
856        executor: Option<Address>,
857        verifier: Option<Address>,
858        treasury: Option<Address>,
859        max_price_per_pgu: Option<u64>,
860    ) -> Result<(Address, Address, Address, Address, u64, u64, Vec<u8>)> {
861        match self.network_mode {
862            NetworkMode::Mainnet => {
863                let params = self.get_proof_request_params(mode).await?;
864                match params {
865                    GetProofRequestParamsResponse::Auction(auction_params) => {
866                        let auctioneer_value = if let Some(auctioneer) = auctioneer {
867                            auctioneer
868                        } else {
869                            Address::from_slice(&auction_params.auctioneer)
870                        };
871                        let executor_value = if let Some(executor) = executor {
872                            executor
873                        } else {
874                            Address::from_slice(&auction_params.executor)
875                        };
876                        let verifier_value = if let Some(verifier) = verifier {
877                            verifier
878                        } else {
879                            Address::from_slice(&auction_params.verifier)
880                        };
881                        let treasury_value = if let Some(treasury) = treasury {
882                            treasury
883                        } else {
884                            Address::from_slice(&auction_params.treasury)
885                        };
886                        let max_price_per_pgu_value = if let Some(max_price_per_pgu) = max_price_per_pgu {
887                            max_price_per_pgu
888                        } else {
889                            auction_params
890                                .max_price_per_pgu
891                                .parse::<u64>()
892                                .expect("invalid max_price_per_pgu")
893                        };
894                        let base_fee = auction_params
895                            .base_fee
896                            .parse::<u64>()
897                            .expect("invalid base_fee");
898                        Ok((auctioneer_value, executor_value, verifier_value, treasury_value, max_price_per_pgu_value, base_fee, auction_params.domain))
899                    }
900                    GetProofRequestParamsResponse::Unsupported => {
901                        Err(anyhow::anyhow!(
902                            "get_proof_request_params is not supported in {:?} mode. This operation is only available for Mainnet (auction-based proving).",
903                            self.network_mode
904                        ))
905                    }
906                }
907            }
908            NetworkMode::Reserved => {
909                // Reserved mode doesn't use auction parameters.
910                Ok((Address::ZERO, Address::ZERO, Address::ZERO, Address::ZERO, 0, 0, vec![]))
911            }
912        }
913    }
914
915    /// Formats a PROVE amount (with 18 decimals) as a string with 4 decimal places.
916    fn format_prove_amount(amount: u64) -> String {
917        let whole = amount / 1_000_000_000_000_000_000;
918        let remainder = amount % 1_000_000_000_000_000_000;
919        let frac = remainder / 100_000_000_000_000;
920        format!("{whole}.{frac:04}")
921    }
922}
923
924impl From<SP1ProofMode> for ProofMode {
925    fn from(value: SP1ProofMode) -> Self {
926        match value {
927            SP1ProofMode::Core => Self::Core,
928            SP1ProofMode::Compressed => Self::Compressed,
929            SP1ProofMode::Plonk => Self::Plonk,
930            SP1ProofMode::Groth16 => Self::Groth16,
931        }
932    }
933}