use std::{
result::Result::Ok as StdOk,
str::FromStr,
sync::Arc,
time::{Duration, SystemTime, UNIX_EPOCH},
};
use alloy_primitives::{Address, B256, U256};
use anyhow::{Context, Ok, Result};
use async_trait::async_trait;
use reqwest_middleware::ClientWithMiddleware as HttpClientWithMiddleware;
use serde::{de::DeserializeOwned, Serialize};
use sp1_core_machine::io::SP1Stdin;
use sp1_prover::{HashableKey, SP1VerifyingKey};
use tokio::sync::OnceCell;
use tonic::{transport::Channel, Code};
use super::{
grpc,
retry::{self, RetryableRpc, DEFAULT_RETRY_TIMEOUT},
signer::NetworkSigner,
utils::{sign_message, Signable},
NetworkMode, MAINNET_EXPLORER_URL, RESERVED_EXPLORER_URL,
};
use crate::network::proto::{
artifact::{artifact_store_client::ArtifactStoreClient, ArtifactType, CreateArtifactRequest},
auction_network::prover_network_client::ProverNetworkClient as AuctionProverNetworkClient,
auction_types::{
CancelRequestRequest as AuctionCancelRequestRequest,
CancelRequestRequestBody as AuctionCancelRequestRequestBody,
GetBalanceRequest as AuctionGetBalanceRequest,
GetFilteredProofRequestsRequest as AuctionGetFilteredProofRequestsRequest,
GetMarketPricePerPguRequest as AuctionGetMarketPricePerPguRequest,
GetNonceRequest as AuctionGetNonceRequest, GetProgramRequest as AuctionGetProgramRequest,
GetProofRequestParamsRequest as AuctionGetProofRequestParamsRequest,
GetProofRequestStatusRequest as AuctionGetProofRequestStatusRequest,
GetProversByUptimeRequest as AuctionGetProversByUptimeRequest,
MessageFormat as AuctionMessageFormat, RequestProofRequest as AuctionRequestProofRequest,
RequestProofRequestBody as AuctionRequestProofRequestBody,
TransactionVariant as AuctionTransactionVariant,
},
base_network::prover_network_client::ProverNetworkClient as BaseProverNetworkClient,
base_types::{
GetBalanceRequest as BaseGetBalanceRequest,
GetFilteredProofRequestsRequest as BaseGetFilteredProofRequestsRequest,
GetNonceRequest as BaseGetNonceRequest, GetProgramRequest as BaseGetProgramRequest,
GetProofRequestDetailsRequest as BaseGetProofRequestDetailsRequest,
GetProofRequestDetailsResponse as BaseGetProofRequestDetailsResponse,
GetProofRequestStatusRequest as BaseGetProofRequestStatusRequest,
MessageFormat as BaseMessageFormat, ProofRequest as BaseProofRequest,
RequestProofRequest as BaseRequestProofRequest,
RequestProofRequestBody as BaseRequestProofRequestBody,
},
types::{
CreateProgramRequest, CreateProgramRequestBody, CreateProgramResponse, FulfillmentStatus,
FulfillmentStrategy, GetProofRequestDetailsRequest, GetProofRequestDetailsResponse,
MessageFormat, ProofMode,
},
CancelRequestResponse,
GetBalanceResponse,
GetFilteredProofRequestsResponse,
GetNonceResponse,
GetProgramResponse,
GetProofRequestParamsResponse,
GetProofRequestStatusResponse,
RequestProofResponse,
};
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct MarketPrice {
pub wei: u128,
pub as_of: u64,
}
pub(super) fn parse_fulfillment_status(
raw_status: i32,
request_id: B256,
) -> Result<FulfillmentStatus> {
FulfillmentStatus::try_from(raw_status).with_context(|| {
format!(
"unsupported fulfillment status {raw_status} while getting proof request status for request 0x{}",
hex::encode(request_id)
)
})
}
#[derive(Clone)]
pub struct NetworkClient {
pub(crate) signer: NetworkSigner,
pub(crate) http: HttpClientWithMiddleware,
pub(crate) rpc_url: String,
pub(crate) network_mode: NetworkMode,
pub(crate) channel: Arc<OnceCell<Channel>>,
}
#[async_trait]
impl RetryableRpc for NetworkClient {
async fn with_retry<'a, T, F, Fut>(&'a self, operation: F, operation_name: &str) -> Result<T>
where
F: Fn() -> Fut + Send + Sync + 'a,
Fut: std::future::Future<Output = Result<T>> + Send,
T: Send,
{
self.with_retry_timeout(operation, DEFAULT_RETRY_TIMEOUT, operation_name).await
}
async fn with_retry_timeout<'a, T, F, Fut>(
&'a self,
operation: F,
timeout: Duration,
operation_name: &str,
) -> Result<T>
where
F: Fn() -> Fut + Send + Sync + 'a,
Fut: std::future::Future<Output = Result<T>> + Send,
T: Send,
{
retry::retry_operation(operation, Some(timeout), operation_name).await
}
}
impl NetworkClient {
pub fn new(
signer: NetworkSigner,
rpc_url: impl Into<String>,
network_mode: NetworkMode,
) -> Self {
let client = reqwest::Client::builder()
.pool_max_idle_per_host(0)
.pool_idle_timeout(Duration::from_secs(240))
.build()
.unwrap();
Self {
signer,
http: client.into(),
rpc_url: rpc_url.into(),
network_mode,
channel: Arc::new(OnceCell::new()),
}
}
#[must_use]
pub fn get_explorer_url(&self) -> &'static str {
match self.network_mode {
NetworkMode::Mainnet => MAINNET_EXPLORER_URL,
NetworkMode::Reserved => RESERVED_EXPLORER_URL,
}
}
pub async fn get_nonce(&self) -> Result<u64> {
let response = self.get_nonce_response().await?;
Ok(response.nonce())
}
async fn get_nonce_response(&self) -> Result<GetNonceResponse> {
match self.network_mode {
NetworkMode::Mainnet => {
self.with_retry(
|| async {
let mut rpc = self.auction_prover_network_client().await?;
let res = rpc
.get_nonce(AuctionGetNonceRequest {
address: self.signer.address().to_vec(),
})
.await?;
Ok(GetNonceResponse::from(res.into_inner()))
},
"getting nonce",
)
.await
}
NetworkMode::Reserved => {
self.with_retry(
|| async {
let mut rpc = self.base_prover_network_client().await?;
let res = rpc
.get_nonce(BaseGetNonceRequest {
address: self.signer.address().to_vec(),
})
.await?;
Ok(GetNonceResponse::from(res.into_inner()))
},
"getting nonce",
)
.await
}
}
}
pub async fn get_balance(&self) -> Result<U256> {
let response = self.get_balance_response().await?;
Ok(U256::from_str(response.balance()).unwrap())
}
async fn get_balance_response(&self) -> Result<GetBalanceResponse> {
match self.network_mode {
NetworkMode::Mainnet => {
self.with_retry(
|| async {
let mut rpc = self.auction_prover_network_client().await?;
let res = rpc
.get_balance(AuctionGetBalanceRequest {
address: self.signer.address().to_vec(),
})
.await?;
Ok(GetBalanceResponse::from(res.into_inner()))
},
"getting balance",
)
.await
}
NetworkMode::Reserved => {
self.with_retry(
|| async {
let mut rpc = self.base_prover_network_client().await?;
let res = rpc
.get_balance(BaseGetBalanceRequest {
address: self.signer.address().to_vec(),
})
.await?;
Ok(GetBalanceResponse::from(res.into_inner()))
},
"getting balance",
)
.await
}
}
}
pub fn get_vk_hash(vk: &SP1VerifyingKey) -> Result<B256> {
let vk_hash = vk.hash_bytes();
Ok(B256::from_slice(&vk_hash))
}
pub async fn register_program(&self, vk: &SP1VerifyingKey, elf: &[u8]) -> Result<B256> {
let vk_hash = Self::get_vk_hash(vk)?;
if (self.get_program(vk_hash).await?).is_some() {
Ok(vk_hash)
} else {
self.create_program(vk_hash, vk, elf).await?;
tracing::info!("Registered program {:?}", vk_hash);
Ok(vk_hash)
}
}
pub async fn get_program(&self, vk_hash: B256) -> Result<Option<GetProgramResponse>> {
match self.network_mode {
NetworkMode::Mainnet => {
self.with_retry(
|| async {
let mut rpc = self.auction_prover_network_client().await?;
match rpc
.get_program(AuctionGetProgramRequest { vk_hash: vk_hash.to_vec() })
.await
{
StdOk(response) => {
Ok(Some(GetProgramResponse::from(response.into_inner())))
}
Err(status) if status.code() == Code::NotFound => Ok(None),
Err(e) => Err(e.into()),
}
},
"getting program",
)
.await
}
NetworkMode::Reserved => {
self.with_retry(
|| async {
let mut rpc = self.base_prover_network_client().await?;
match rpc
.get_program(BaseGetProgramRequest { vk_hash: vk_hash.to_vec() })
.await
{
StdOk(response) => {
Ok(Some(GetProgramResponse::from(response.into_inner())))
}
Err(status) if status.code() == Code::NotFound => Ok(None),
Err(e) => Err(e.into()),
}
},
"getting program",
)
.await
}
}
}
pub async fn create_program(
&self,
vk_hash: B256,
vk: &SP1VerifyingKey,
elf: &[u8],
) -> Result<CreateProgramResponse> {
let program_uri = self.create_artifact_with_content(ArtifactType::Program, &elf).await?;
let vk_encoded = bincode::serialize(&vk)?;
self.with_retry(
|| async {
let mut rpc = self.prover_network_client().await?;
let nonce = self.get_nonce().await?;
let request_body = CreateProgramRequestBody {
nonce,
vk_hash: vk_hash.to_vec(),
vk: vk_encoded.clone(),
program_uri: program_uri.clone(),
};
Ok(rpc
.create_program(CreateProgramRequest {
format: MessageFormat::Binary.into(),
signature: request_body.sign(&self.signer).await?,
body: Some(request_body),
})
.await?
.into_inner())
},
"creating program",
)
.await
}
pub async fn get_proof_request_params(
&self,
mode: ProofMode,
) -> Result<GetProofRequestParamsResponse> {
match self.network_mode {
NetworkMode::Mainnet => {
self.with_retry(
|| async {
let mut rpc = self.auction_prover_network_client().await?;
let response = rpc
.get_proof_request_params(AuctionGetProofRequestParamsRequest {
mode: mode.into(),
})
.await?
.into_inner();
Ok(GetProofRequestParamsResponse::from(response))
},
"getting proof request parameters",
)
.await
}
NetworkMode::Reserved => Ok(GetProofRequestParamsResponse::Unsupported),
}
}
pub async fn get_market_price_per_pgu(&self) -> Result<MarketPrice> {
match self.network_mode {
NetworkMode::Mainnet => {
self.with_retry(
|| async {
let mut rpc = self.auction_prover_network_client().await?;
let response = rpc
.get_market_price_per_pgu(AuctionGetMarketPricePerPguRequest {})
.await?
.into_inner();
let wei = response.price.parse::<u128>().with_context(|| {
format!("invalid market_price_per_pgu wei: {:?}", response.price)
})?;
let as_of = u64::try_from(response.as_of).with_context(|| {
format!("invalid market_price as_of: {}", response.as_of)
})?;
Ok(MarketPrice { wei, as_of })
},
"getting market price per PGU",
)
.await
}
NetworkMode::Reserved => {
Err(anyhow::anyhow!("get_market_price_per_pgu is not supported in Reserved mode"))
}
}
}
#[allow(clippy::too_many_arguments)]
pub async fn get_filtered_proof_requests(
&self,
version: Option<String>,
fulfillment_status: Option<i32>,
execution_status: Option<i32>,
minimum_deadline: Option<u64>,
vk_hash: Option<Vec<u8>>,
requester: Option<Vec<u8>>,
fulfiller: Option<Vec<u8>>,
from: Option<u64>,
to: Option<u64>,
limit: Option<u32>,
page: Option<u32>,
mode: Option<i32>,
not_bid_by: Option<Vec<u8>>,
execute_fail_cause: Option<i32>,
settlement_status: Option<i32>,
error: Option<i32>,
) -> Result<GetFilteredProofRequestsResponse> {
match self.network_mode {
NetworkMode::Mainnet => {
self.with_retry(
|| {
let version = version.clone();
let vk_hash = vk_hash.clone();
let requester = requester.clone();
let fulfiller = fulfiller.clone();
let not_bid_by = not_bid_by.clone();
async move {
let mut rpc = self.auction_prover_network_client().await?;
let response = rpc
.get_filtered_proof_requests(
AuctionGetFilteredProofRequestsRequest {
version,
fulfillment_status,
execution_status,
minimum_deadline,
vk_hash,
requester,
fulfiller,
from,
to,
limit,
page,
mode,
not_bid_by,
execute_fail_cause,
settlement_status,
error,
..Default::default()
},
)
.await?
.into_inner();
Ok(GetFilteredProofRequestsResponse::from(response))
}
},
"getting filtered proof requests",
)
.await
}
NetworkMode::Reserved => {
self.with_retry(
|| {
let version = version.clone();
let vk_hash = vk_hash.clone();
let requester = requester.clone();
let fulfiller = fulfiller.clone();
let not_bid_by = not_bid_by.clone();
async move {
let mut rpc = self.base_prover_network_client().await?;
let response = rpc
.get_filtered_proof_requests(BaseGetFilteredProofRequestsRequest {
version,
fulfillment_status,
execution_status,
minimum_deadline,
vk_hash,
requester,
fulfiller,
from,
to,
limit,
page,
mode,
not_bid_by,
execute_fail_cause,
settlement_status,
error,
..Default::default()
})
.await?
.into_inner();
Ok(GetFilteredProofRequestsResponse::from(response))
}
},
"getting filtered proof requests",
)
.await
}
}
}
pub async fn get_proof_request_status<P: DeserializeOwned>(
&self,
request_id: B256,
timeout: Option<Duration>,
) -> Result<(GetProofRequestStatusResponse, Option<P>)> {
let res = match self.network_mode {
NetworkMode::Mainnet => {
let auction_response = self
.with_retry_timeout(
|| async {
let mut rpc = self.auction_prover_network_client().await?;
Ok(rpc
.get_proof_request_status(AuctionGetProofRequestStatusRequest {
request_id: request_id.to_vec(),
})
.await?
.into_inner())
},
timeout.unwrap_or(DEFAULT_RETRY_TIMEOUT),
"getting proof request status",
)
.await?;
GetProofRequestStatusResponse::from(auction_response)
}
NetworkMode::Reserved => {
let base_response = self
.with_retry_timeout(
|| async {
let mut rpc = self.base_prover_network_client().await?;
Ok(rpc
.get_proof_request_status(BaseGetProofRequestStatusRequest {
request_id: request_id.to_vec(),
})
.await?
.into_inner())
},
timeout.unwrap_or(DEFAULT_RETRY_TIMEOUT),
"getting proof request status",
)
.await?;
GetProofRequestStatusResponse::from(base_response)
}
};
let status = parse_fulfillment_status(res.fulfillment_status(), request_id)?;
let proof = match status {
FulfillmentStatus::Fulfilled => {
let proof_uri =
res.proof_uri().ok_or_else(|| anyhow::anyhow!("No proof URI provided"))?;
let proof_bytes = self.download_artifact(proof_uri).await?;
Some(bincode::deserialize(&proof_bytes).context("Failed to deserialize proof")?)
}
_ => None,
};
Ok((res, proof))
}
pub async fn get_proof_request_details(
&self,
request_id: B256,
timeout: Option<Duration>,
) -> Result<GetProofRequestDetailsResponse> {
let res = match self.network_mode {
NetworkMode::Mainnet => {
self.with_retry_timeout(
|| async {
let mut rpc = self.auction_prover_network_client().await?;
Ok(rpc
.get_proof_request_details(GetProofRequestDetailsRequest {
request_id: request_id.to_vec(),
})
.await?
.into_inner())
},
timeout.unwrap_or(DEFAULT_RETRY_TIMEOUT),
"getting proof request details",
)
.await?
}
NetworkMode::Reserved => {
let response = self
.with_retry_timeout(
|| async {
let mut rpc = self.base_prover_network_client().await?;
Ok(rpc
.get_proof_request_details(BaseGetProofRequestDetailsRequest {
request_id: request_id.to_vec(),
})
.await?
.into_inner())
},
timeout.unwrap_or(DEFAULT_RETRY_TIMEOUT),
"getting proof request details",
)
.await?;
Self::convert_base_proof_request_details(response)
}
};
Ok(res)
}
fn convert_base_proof_request_details(
response: BaseGetProofRequestDetailsResponse,
) -> GetProofRequestDetailsResponse {
GetProofRequestDetailsResponse {
request: response.request.map(|request| {
let BaseProofRequest {
request_id,
vk_hash,
version,
mode,
strategy,
program_uri,
stdin_uri,
deadline,
cycle_limit,
gas_price,
fulfillment_status,
execution_status,
requester,
fulfiller,
program_name,
requester_name,
fulfiller_name,
created_at,
updated_at,
fulfilled_at,
tx_hash,
cycles,
public_values_hash,
deduction_amount,
refund_amount,
gas_limit,
gas_used,
execute_fail_cause,
settlement_status,
program_public_uri,
stdin_public_uri,
min_auction_period,
whitelist,
error,
stdin_private,
} = request;
crate::network::proto::auction_types::ProofRequest {
request_id,
vk_hash,
version,
mode,
strategy,
program_uri,
stdin_uri,
deadline,
cycle_limit,
gas_price,
fulfillment_status,
execution_status,
requester,
fulfiller,
program_name,
requester_name,
fulfiller_name,
created_at,
updated_at,
fulfilled_at,
tx_hash,
cycles,
public_values_hash,
deduction_amount,
refund_amount,
gas_limit,
gas_used,
execute_fail_cause,
settlement_status,
program_public_uri,
stdin_public_uri,
min_auction_period,
whitelist,
base_fee: None,
max_price_per_pgu: None,
error,
is_canceled: false,
stdin_private,
}
}),
}
}
#[allow(clippy::too_many_arguments)]
#[allow(unused_variables)]
pub async fn request_proof(
&self,
vk_hash: B256,
stdin: &SP1Stdin,
mode: ProofMode,
version: &str,
strategy: FulfillmentStrategy,
timeout_secs: u64,
cycle_limit: u64,
gas_limit: u64,
min_auction_period: u64,
whitelist: Option<Vec<Address>>,
auctioneer: Address,
executor: Address,
verifier: Address,
treasury: Address,
public_values_hash: Option<Vec<u8>>,
base_fee: u64,
max_price_per_pgu: u64,
domain: Vec<u8>,
private_stdin: bool,
) -> Result<RequestProofResponse> {
let start = SystemTime::now();
let since_the_epoch = start.duration_since(UNIX_EPOCH).expect("Invalid start time");
let deadline = since_the_epoch.as_secs() + timeout_secs;
let stdin_uri = self
.create_artifact_with_content(
if private_stdin { ArtifactType::PrivateStdin } else { ArtifactType::Stdin },
&stdin,
)
.await?;
match self.network_mode {
NetworkMode::Mainnet => {
self.with_retry(
|| async {
let mut rpc = self.auction_prover_network_client().await?;
let nonce = self.get_nonce().await?;
let whitelist = if let Some(whitelist) = &whitelist {
whitelist.iter().map(|addr| addr.to_vec()).collect()
} else {
let result = rpc
.get_provers_by_uptime(AuctionGetProversByUptimeRequest {
high_availability_only: false,
})
.await?;
result.into_inner().provers
};
let request_body = AuctionRequestProofRequestBody {
nonce,
version: format!("sp1-{version}"),
vk_hash: vk_hash.to_vec(),
mode: mode.into(),
strategy: strategy.into(),
stdin_uri: stdin_uri.clone(),
deadline,
cycle_limit,
gas_limit,
min_auction_period,
whitelist,
domain: domain.clone(),
auctioneer: auctioneer.to_vec(),
executor: executor.to_vec(),
verifier: verifier.to_vec(),
treasury: treasury.to_vec(),
public_values_hash: public_values_hash.clone(),
base_fee: base_fee.to_string(),
max_price_per_pgu: max_price_per_pgu.to_string(),
variant: AuctionTransactionVariant::RequestVariant.into(),
stdin_private: private_stdin,
};
let request_response = rpc
.request_proof(AuctionRequestProofRequest {
format: AuctionMessageFormat::Binary.into(),
signature: request_body.sign(&self.signer).await?,
body: Some(request_body),
})
.await?
.into_inner();
Ok(RequestProofResponse::from(request_response))
},
"requesting proof",
)
.await
}
NetworkMode::Reserved => {
self.with_retry(
|| async {
let mut rpc = self.base_prover_network_client().await?;
let nonce = self.get_nonce().await?;
let request_body = BaseRequestProofRequestBody {
nonce,
version: format!("sp1-{version}"),
vk_hash: vk_hash.to_vec(),
mode: mode.into(),
strategy: strategy.into(),
stdin_uri: stdin_uri.clone(),
deadline,
cycle_limit,
gas_limit,
min_auction_period,
whitelist: whitelist
.clone()
.map(|list| list.into_iter().map(|addr| addr.to_vec()).collect())
.unwrap_or_default(),
stdin_private: private_stdin,
};
let request_response = rpc
.request_proof(BaseRequestProofRequest {
format: BaseMessageFormat::Binary.into(),
signature: request_body.sign(&self.signer).await?,
body: Some(request_body),
})
.await?
.into_inner();
Ok(RequestProofResponse::from(request_response))
},
"requesting proof",
)
.await
}
}
}
pub(crate) async fn prover_network_client(
&self,
) -> Result<AuctionProverNetworkClient<Channel>> {
self.auction_prover_network_client().await
}
async fn channel(&self) -> Result<Channel> {
self.channel
.get_or_try_init(|| async {
tracing::debug!(rpc_url = %self.rpc_url, "establishing gRPC channel");
Ok(grpc::configure_endpoint(&self.rpc_url)?.connect_lazy())
})
.await
.cloned()
}
pub(crate) async fn auction_prover_network_client(
&self,
) -> Result<AuctionProverNetworkClient<Channel>> {
Ok(AuctionProverNetworkClient::new(self.channel().await?))
}
pub(crate) async fn base_prover_network_client(
&self,
) -> Result<BaseProverNetworkClient<Channel>> {
Ok(BaseProverNetworkClient::new(self.channel().await?))
}
pub(crate) async fn artifact_store_client(&self) -> Result<ArtifactStoreClient<Channel>> {
Ok(ArtifactStoreClient::new(self.channel().await?))
}
pub(crate) async fn create_artifact_with_content<T: Serialize + Send + Sync>(
&self,
artifact_type: ArtifactType,
item: &T,
) -> Result<String> {
let response = self
.with_retry(
|| async {
let mut store = self.artifact_store_client().await?;
let signature =
sign_message("create_artifact".as_bytes(), &self.signer).await?;
let request =
CreateArtifactRequest { artifact_type: artifact_type.into(), signature };
Ok(store.create_artifact(request).await?.into_inner())
},
"creating artifact",
)
.await?;
let presigned_url = response.artifact_presigned_url;
let uri = response.artifact_uri;
let serialized = bincode::serialize::<T>(item)?;
let compressed = zstd::encode_all(&serialized[..], 3)
.map_err(|e| anyhow::anyhow!("Failed to compress artifact: {e}"))?;
self.with_retry(
|| async {
let response =
self.http.put(&presigned_url).body(compressed.clone()).send().await?;
if !response.status().is_success() {
return Err(anyhow::anyhow!(
"Failed to upload artifact: HTTP {}",
response.status()
));
}
Ok(())
},
"uploading artifact content",
)
.await?;
Ok(uri)
}
pub(crate) async fn download_artifact(&self, uri: &str) -> Result<Vec<u8>> {
self.with_retry(
|| async {
let response =
self.http.get(uri).send().await.context("Failed to download from URI")?;
if !response.status().is_success() {
return Err(anyhow::anyhow!(
"Failed to download artifact: HTTP {}",
response.status()
));
}
Ok(response.bytes().await.context("Failed to read response body")?.to_vec())
},
"downloading artifact",
)
.await
}
pub async fn cancel_request(&self, request_id: B256) -> Result<CancelRequestResponse> {
match self.network_mode {
NetworkMode::Mainnet => {
self.with_retry(
|| async {
let mut rpc = self.auction_prover_network_client().await?;
let nonce = self.get_nonce().await?;
let request_body = AuctionCancelRequestRequestBody {
nonce,
request_id: request_id.to_vec(),
};
let response = rpc
.cancel_request(AuctionCancelRequestRequest {
format: AuctionMessageFormat::Binary.into(),
signature: request_body.sign(&self.signer).await?,
body: Some(request_body),
})
.await?
.into_inner();
Ok(CancelRequestResponse::from(response))
},
"cancelling request",
)
.await
}
NetworkMode::Reserved => Ok(CancelRequestResponse::Unsupported),
}
}
}
#[cfg(test)]
mod test {
use std::{
convert::Infallible,
task::{Context, Poll},
time::Duration,
};
use alloy_primitives::B256;
use tonic::{
codegen::{http, Body, BoxFuture, Service, StdError},
server::{Grpc, NamedService, UnaryService},
transport::Server,
Request, Response, Status,
};
use crate::network::{proto::base_types, signer::NetworkSigner, NetworkMode, RESERVED_RPC_URL};
use super::parse_fulfillment_status;
#[derive(Clone)]
struct ReservedProofDetailsFixture(base_types::GetProofRequestDetailsResponse);
impl NamedService for ReservedProofDetailsFixture {
const NAME: &'static str = "network.ProverNetwork";
}
impl UnaryService<base_types::GetProofRequestDetailsRequest> for ReservedProofDetailsFixture {
type Response = base_types::GetProofRequestDetailsResponse;
type Future = BoxFuture<Response<Self::Response>, Status>;
fn call(
&mut self,
request: Request<base_types::GetProofRequestDetailsRequest>,
) -> Self::Future {
assert_eq!(
request.into_inner().request_id,
self.0.request.as_ref().unwrap().request_id
);
let response = self.0.clone();
Box::pin(async move { Ok(Response::new(response)) })
}
}
impl<B> Service<http::Request<B>> for ReservedProofDetailsFixture
where
B: Body + Send + 'static,
B::Error: Into<StdError> + Send + 'static,
{
type Response = http::Response<tonic::body::BoxBody>;
type Error = Infallible;
type Future = BoxFuture<Self::Response, Self::Error>;
fn poll_ready(&mut self, _cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
Poll::Ready(Ok(()))
}
fn call(&mut self, request: http::Request<B>) -> Self::Future {
assert_eq!(request.uri().path(), "/network.ProverNetwork/GetProofRequestDetails");
let method = self.clone();
Box::pin(async move {
Ok(Grpc::new(tonic::codec::ProstCodec::default()).unary(method, request).await)
})
}
}
#[test]
fn test_can_create_network_client_with_0x_bytes() {
let private_key = hex::encode(alloy_signer_local::PrivateKeySigner::random().to_bytes());
let signer = NetworkSigner::local(&private_key).unwrap();
let _ = super::NetworkClient::new(signer, RESERVED_RPC_URL, NetworkMode::Reserved);
}
#[test]
fn fulfillment_status_parser_handles_known_and_unknown_values() {
let request_id = B256::from([0xab; 32]);
for raw_status in 0..=6 {
let status = parse_fulfillment_status(raw_status, request_id).unwrap();
assert_eq!(status as i32, raw_status);
}
let error = parse_fulfillment_status(7, request_id).unwrap_err().to_string();
assert!(error.contains("unsupported fulfillment status 7"));
assert!(error.contains(&format!("0x{}", hex::encode(request_id))));
}
#[tokio::test]
async fn reserved_proof_request_details_uses_reserved_schema() {
let request_id = B256::repeat_byte(0x11);
let request = base_types::ProofRequest {
request_id: request_id.to_vec(),
vk_hash: vec![0x22; 32],
version: "sp1-v6.3.1".to_string(),
mode: base_types::ProofMode::Plonk.into(),
strategy: base_types::FulfillmentStrategy::Reserved.into(),
program_uri: "programs/test".to_string(),
stdin_uri: "stdins/test".to_string(),
deadline: 1_800_000_000,
cycle_limit: 1_000_000,
error: base_types::ProofRequestError::ExecutionFailure.into(),
stdin_private: true,
..Default::default()
};
let listener = tokio::net::TcpListener::bind("127.0.0.1:0").await.unwrap();
let address = listener.local_addr().unwrap();
let incoming = futures::stream::unfold(listener, |listener| async move {
let connection = listener.accept().await.map(|(stream, _)| stream);
Some((connection, listener))
});
let server = tokio::spawn(
Server::builder()
.add_service(ReservedProofDetailsFixture(
base_types::GetProofRequestDetailsResponse { request: Some(request) },
))
.serve_with_incoming(incoming),
);
let private_key = hex::encode(alloy_signer_local::PrivateKeySigner::random().to_bytes());
let signer = NetworkSigner::local(&private_key).unwrap();
let client =
super::NetworkClient::new(signer, format!("http://{address}"), NetworkMode::Reserved);
let response = client
.get_proof_request_details(request_id, Some(Duration::from_secs(1)))
.await
.unwrap();
server.abort();
let request = response.request.unwrap();
assert_eq!(request.request_id, request_id.as_slice());
assert_eq!(request.vk_hash, vec![0x22; 32]);
assert_eq!(request.version, "sp1-v6.3.1");
assert_eq!(request.mode, base_types::ProofMode::Plonk as i32);
assert_eq!(request.strategy, base_types::FulfillmentStrategy::Reserved as i32);
assert_eq!(request.program_uri, "programs/test");
assert_eq!(request.stdin_uri, "stdins/test");
assert_eq!(request.deadline, 1_800_000_000);
assert_eq!(request.cycle_limit, 1_000_000);
assert_eq!(request.error, base_types::ProofRequestError::ExecutionFailure as i32);
assert!(request.stdin_private);
assert_eq!(request.base_fee, None);
assert_eq!(request.max_price_per_pgu, None);
assert!(!request.is_canceled);
}
}