commit e73a0bdab6c462d7774b2a83586ccedd59021bbb
parent 674e63f60ce02866ab337d4906f47bd6d53c09fd
Author: triesap <tyson@radroots.org>
Date: Thu, 21 May 2026 07:24:15 +0000
trade_validation_receipt: add remote http prover
- add explicit remote_http policy config and fail-closed validation
- implement provider-neutral request, polling, response checks, and local verification
- publish receipts only after verified remote proof artifacts succeed
- cover mocked remote success, polling, and terminal failure paths
Diffstat:
4 files changed, 986 insertions(+), 13 deletions(-)
diff --git a/Cargo.lock b/Cargo.lock
@@ -3646,6 +3646,7 @@ dependencies = [
"radroots_sp1_guest_trade",
"radroots_sp1_host_trade",
"radroots_trade",
+ "reqwest",
"serde",
"serde_json",
"sha2",
diff --git a/Cargo.toml b/Cargo.toml
@@ -45,6 +45,7 @@ anyhow = { version = "1" }
clap = { version = "4", features = ["derive"] }
serde = { version = "1", default-features = false }
serde_json = { version = "1", default-features = false }
+reqwest = { version = "0.12", default-features = false, features = ["json", "rustls-tls"] }
sha2 = { version = "0.10" }
tokio = { version = "1", features = ["full"] }
thiserror = { version = "2" }
diff --git a/src/features/trade_validation_receipt.rs b/src/features/trade_validation_receipt.rs
@@ -29,7 +29,7 @@ use radroots_sp1_guest_trade::{
use radroots_sp1_host_trade::{
RadrootsSp1TradeHostError, RadrootsSp1TradeProofBundle, RadrootsSp1TradeProofMode,
generate_order_acceptance_proof, validation_receipt_for_order_acceptance_proof,
- verify_order_acceptance_proof_artifact,
+ verify_order_acceptance_proof_artifact_structure,
};
use radroots_trade::validation_receipt::{
RadrootsValidationReceiptError, RadrootsValidationReceiptExpectedBinding,
@@ -37,8 +37,17 @@ use radroots_trade::validation_receipt::{
};
use serde::{Deserialize, Serialize};
use sha2::{Digest, Sha256};
+#[cfg(feature = "sp1_proving")]
+use std::time::Duration;
use thiserror::Error;
+#[cfg(feature = "sp1_proving")]
+use radroots_sp1_host_trade::{
+ RADROOTS_SP1_TRADE_REMOTE_PROVER_SCHEMA_VERSION, RADROOTS_SP1_TRADE_SP1_VERSION_LINE,
+ RadrootsSp1TradeRemoteProverRequest, RadrootsSp1TradeRemoteProverResponse,
+ RadrootsSp1TradeRemoteProverStatus, RadrootsSp1TradeResolvedProofArtifact,
+};
+
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct TradeValidationReceiptJobRequest {
@@ -90,6 +99,8 @@ pub struct TradeValidationReceiptProverPolicy {
pub expected_sp1_program_hash: Option<String>,
#[serde(default)]
pub expected_sp1_verifying_key_hash: Option<String>,
+ #[serde(default)]
+ pub remote_http: Option<TradeValidationReceiptRemoteHttpProverConfig>,
}
impl Default for TradeValidationReceiptProverPolicy {
@@ -105,6 +116,7 @@ impl TradeValidationReceiptProverPolicy {
proof_mode: RadrootsSp1TradeProofMode::None,
expected_sp1_program_hash: None,
expected_sp1_verifying_key_hash: None,
+ remote_http: None,
}
}
@@ -114,6 +126,7 @@ impl TradeValidationReceiptProverPolicy {
proof_mode: RadrootsSp1TradeProofMode::None,
expected_sp1_program_hash: None,
expected_sp1_verifying_key_hash: None,
+ remote_http: None,
}
}
@@ -174,10 +187,31 @@ impl TradeValidationReceiptProverPolicy {
}
Ok(())
}
- TradeValidationReceiptProverBackend::LocalCudaProve
- | TradeValidationReceiptProverBackend::RemoteHttpProve => Err(
+ TradeValidationReceiptProverBackend::LocalCudaProve => Err(
TradeValidationReceiptJobError::ProverBackendUnavailable(self.backend.as_str()),
),
+ TradeValidationReceiptProverBackend::RemoteHttpProve => {
+ if self.proof_mode == RadrootsSp1TradeProofMode::None {
+ return Err(TradeValidationReceiptJobError::ProverBackendRequiresSp1Proof);
+ }
+ if self.expected_sp1_program_hash.is_none()
+ || self.expected_sp1_verifying_key_hash.is_none()
+ {
+ return Err(TradeValidationReceiptJobError::Sp1IdentityPolicyRequired);
+ }
+ let remote_http = self
+ .remote_http
+ .as_ref()
+ .ok_or(TradeValidationReceiptJobError::RemoteHttpConfigRequired)?;
+ remote_http.validate()?;
+ remote_http_auth_token(remote_http)?;
+ if !cfg!(feature = "sp1_proving") {
+ return Err(TradeValidationReceiptJobError::ProverBackendUnavailable(
+ self.backend.as_str(),
+ ));
+ }
+ Ok(())
+ }
}
}
@@ -208,6 +242,93 @@ impl TradeValidationReceiptProverPolicy {
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
+pub struct TradeValidationReceiptRemoteHttpProverConfig {
+ pub endpoint_url: String,
+ pub auth: TradeValidationReceiptRemoteHttpAuth,
+ pub request_timeout_ms: u64,
+ pub poll_interval_ms: u64,
+ pub max_poll_attempts: u32,
+ pub max_response_bytes: usize,
+}
+
+impl TradeValidationReceiptRemoteHttpProverConfig {
+ pub fn validate(&self) -> Result<(), TradeValidationReceiptJobError> {
+ if self.endpoint_url.trim().is_empty() {
+ return Err(TradeValidationReceiptJobError::RemoteHttpInvalidConfig(
+ "endpoint_url",
+ ));
+ }
+ let url = reqwest::Url::parse(self.endpoint_url.as_str())
+ .map_err(|_| TradeValidationReceiptJobError::RemoteHttpInvalidConfig("endpoint_url"))?;
+ if url.scheme() != "http" && url.scheme() != "https" {
+ return Err(TradeValidationReceiptJobError::RemoteHttpInvalidConfig(
+ "endpoint_url",
+ ));
+ }
+ if self.request_timeout_ms == 0 {
+ return Err(TradeValidationReceiptJobError::RemoteHttpInvalidConfig(
+ "request_timeout_ms",
+ ));
+ }
+ if self.poll_interval_ms == 0 {
+ return Err(TradeValidationReceiptJobError::RemoteHttpInvalidConfig(
+ "poll_interval_ms",
+ ));
+ }
+ if self.max_response_bytes == 0 {
+ return Err(TradeValidationReceiptJobError::RemoteHttpInvalidConfig(
+ "max_response_bytes",
+ ));
+ }
+ self.auth.validate()
+ }
+}
+
+#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
+#[serde(tag = "mode", rename_all = "snake_case")]
+pub enum TradeValidationReceiptRemoteHttpAuth {
+ NoAuth,
+ BearerTokenEnv { env_var: String },
+}
+
+impl TradeValidationReceiptRemoteHttpAuth {
+ fn validate(&self) -> Result<(), TradeValidationReceiptJobError> {
+ match self {
+ Self::NoAuth => Ok(()),
+ Self::BearerTokenEnv { env_var } => {
+ if env_var.trim().is_empty() {
+ return Err(TradeValidationReceiptJobError::RemoteHttpInvalidConfig(
+ "auth.env_var",
+ ));
+ }
+ Ok(())
+ }
+ }
+ }
+}
+
+fn remote_http_auth_token(
+ config: &TradeValidationReceiptRemoteHttpProverConfig,
+) -> Result<Option<String>, TradeValidationReceiptJobError> {
+ match &config.auth {
+ TradeValidationReceiptRemoteHttpAuth::NoAuth => Ok(None),
+ TradeValidationReceiptRemoteHttpAuth::BearerTokenEnv { env_var } => {
+ let value = std::env::var(env_var).map_err(|_| {
+ TradeValidationReceiptJobError::RemoteHttpAuthTokenMissing(env_var.clone())
+ })?;
+ let token = value.trim();
+ if token.is_empty() {
+ return Err(TradeValidationReceiptJobError::RemoteHttpAuthTokenMissing(
+ env_var.clone(),
+ ));
+ }
+ Ok(Some(token.to_owned()))
+ }
+ }
+}
+
+#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
+#[serde(deny_unknown_fields)]
pub struct TradeValidationReceiptJobResult {
pub cryptographic_proof_verified: bool,
pub decision_event_id: String,
@@ -281,6 +402,28 @@ pub enum TradeValidationReceiptJobError {
ProverBackendUnavailable(&'static str),
#[error("configured SP1 identity policy is required for this prover backend")]
Sp1IdentityPolicyRequired,
+ #[error("remote_http prover config is required")]
+ RemoteHttpConfigRequired,
+ #[error("remote_http prover config field {0} is invalid")]
+ RemoteHttpInvalidConfig(&'static str),
+ #[error("remote_http bearer token environment variable {0} is missing or empty")]
+ RemoteHttpAuthTokenMissing(String),
+ #[error("remote_http transport error: {0}")]
+ RemoteHttpTransport(String),
+ #[error("remote_http response exceeded configured byte limit")]
+ RemoteHttpResponseTooLarge,
+ #[error("remote_http response field {0} is invalid")]
+ RemoteHttpInvalidResponse(&'static str),
+ #[error("remote_http terminal {status}: {reason_code}: {message}")]
+ RemoteHttpTerminal {
+ status: &'static str,
+ reason_code: String,
+ message: String,
+ },
+ #[error("remote_http polling timed out")]
+ RemoteHttpTimeout,
+ #[error("remote_http response identity field {0} did not match")]
+ RemoteHttpIdentityMismatch(&'static str),
#[error("expected SP1 program hash does not match configured policy")]
ExpectedSp1ProgramHashMismatch,
#[error("expected SP1 verifying key hash does not match configured policy")]
@@ -386,7 +529,7 @@ pub async fn handle_trade_validation_receipt_job_request(
sp1_verifying_key_hash: request.sp1_verifying_key_hash.clone(),
};
let proof_outcome = proof_bundle_for_policy(&witness, prover_policy).await?;
- verify_order_acceptance_proof_artifact(
+ verify_order_acceptance_proof_artifact_structure(
&proof_outcome.bundle.execution,
&proof_outcome.bundle.proof,
)?;
@@ -623,9 +766,9 @@ async fn proof_bundle_for_policy(
TradeValidationReceiptProverBackend::LocalCudaProve => Err(
TradeValidationReceiptJobError::ProverBackendUnavailable(policy.backend.as_str()),
),
- TradeValidationReceiptProverBackend::RemoteHttpProve => Err(
- TradeValidationReceiptJobError::ProverBackendUnavailable(policy.backend.as_str()),
- ),
+ TradeValidationReceiptProverBackend::RemoteHttpProve => {
+ run_remote_http_prove_backend(witness, policy).await
+ }
}
}
@@ -663,9 +806,9 @@ async fn run_local_cpu_prove_backend(
) -> Result<TradeValidationReceiptProofOutcome, TradeValidationReceiptJobError> {
let bundle =
radroots_sp1_host_trade::generate_order_acceptance_sp1_proof(witness, proof_mode).await?;
- radroots_sp1_host_trade::verify_order_acceptance_sp1_proof_artifact(
+ radroots_sp1_host_trade::verify_order_acceptance_resolved_sp1_proof_artifact(
&bundle.execution,
- &bundle.proof,
+ &RadrootsSp1TradeResolvedProofArtifact::inline(bundle.proof.clone()),
)
.await?;
Ok(TradeValidationReceiptProofOutcome {
@@ -677,6 +820,302 @@ async fn run_local_cpu_prove_backend(
})
}
+#[cfg(feature = "sp1_proving")]
+async fn run_remote_http_prove_backend(
+ witness: &RadrootsSp1TradeOrderAcceptanceWitness,
+ policy: &TradeValidationReceiptProverPolicy,
+) -> Result<TradeValidationReceiptProofOutcome, TradeValidationReceiptJobError> {
+ let remote_http = policy
+ .remote_http
+ .as_ref()
+ .ok_or(TradeValidationReceiptJobError::RemoteHttpConfigRequired)?;
+ let expected_sp1_program_hash = policy
+ .expected_sp1_program_hash
+ .as_deref()
+ .ok_or(TradeValidationReceiptJobError::Sp1IdentityPolicyRequired)?;
+ let expected_sp1_verifying_key_hash = policy
+ .expected_sp1_verifying_key_hash
+ .as_deref()
+ .ok_or(TradeValidationReceiptJobError::Sp1IdentityPolicyRequired)?;
+ let execution = radroots_sp1_host_trade::execute_order_acceptance_public_values(witness)?;
+ let request = RadrootsSp1TradeRemoteProverRequest {
+ schema_version: RADROOTS_SP1_TRADE_REMOTE_PROVER_SCHEMA_VERSION,
+ request_id: remote_http_request_id(witness)?,
+ proof_target: RADROOTS_SP1_TRADE_ORDER_ACCEPTANCE_PROOF_TARGET.to_string(),
+ proof_mode: policy.proof_mode,
+ sp1_version_line: RADROOTS_SP1_TRADE_SP1_VERSION_LINE.to_string(),
+ witness: witness.clone(),
+ expected_sp1_program_hash: expected_sp1_program_hash.to_owned(),
+ expected_sp1_verifying_key_hash: expected_sp1_verifying_key_hash.to_owned(),
+ expected_reducer_program_hash: RADROOTS_SP1_TRADE_REDUCER_PROGRAM_HASH.to_string(),
+ expected_protocol_version: RADROOTS_SP1_TRADE_PROTOCOL_VERSION.to_string(),
+ expected_witness_version: RADROOTS_SP1_TRADE_WITNESS_VERSION,
+ };
+ let response = remote_http_completed_response(remote_http, &request).await?;
+ let artifact = remote_http_verified_artifact(
+ &execution,
+ policy,
+ expected_sp1_program_hash,
+ expected_sp1_verifying_key_hash,
+ &request,
+ response,
+ )
+ .await?;
+ Ok(TradeValidationReceiptProofOutcome {
+ sp1_execute_public_values_hash: Some(execution.public_values_hash.clone()),
+ bundle: RadrootsSp1TradeProofBundle {
+ execution,
+ proof: artifact,
+ },
+ proof_generated: true,
+ sp1_execute_checked: true,
+ cryptographic_proof_verified: true,
+ })
+}
+
+#[cfg(not(feature = "sp1_proving"))]
+async fn run_remote_http_prove_backend(
+ _witness: &RadrootsSp1TradeOrderAcceptanceWitness,
+ _policy: &TradeValidationReceiptProverPolicy,
+) -> Result<TradeValidationReceiptProofOutcome, TradeValidationReceiptJobError> {
+ Err(TradeValidationReceiptJobError::ProverBackendUnavailable(
+ TradeValidationReceiptProverBackend::RemoteHttpProve.as_str(),
+ ))
+}
+
+#[cfg(feature = "sp1_proving")]
+fn remote_http_request_id(
+ witness: &RadrootsSp1TradeOrderAcceptanceWitness,
+) -> Result<String, TradeValidationReceiptJobError> {
+ let bytes = serde_json::to_vec(witness)?;
+ Ok(hash_bytes("radroots:rhi-remote-proof-request:v1", &bytes))
+}
+
+#[cfg(feature = "sp1_proving")]
+async fn remote_http_completed_response(
+ config: &TradeValidationReceiptRemoteHttpProverConfig,
+ request: &RadrootsSp1TradeRemoteProverRequest,
+) -> Result<RadrootsSp1TradeRemoteProverResponse, TradeValidationReceiptJobError> {
+ let mut response =
+ remote_http_post_json_io(config, config.endpoint_url.as_str(), request).await?;
+ match response.status {
+ RadrootsSp1TradeRemoteProverStatus::Completed => return Ok(response),
+ RadrootsSp1TradeRemoteProverStatus::Failed => {
+ return Err(remote_http_terminal_error("failed", response));
+ }
+ RadrootsSp1TradeRemoteProverStatus::Rejected => {
+ return Err(remote_http_terminal_error("rejected", response));
+ }
+ RadrootsSp1TradeRemoteProverStatus::Accepted
+ | RadrootsSp1TradeRemoteProverStatus::Running => {}
+ }
+ for _ in 0..config.max_poll_attempts {
+ let status_url = remote_http_status_url(config, &response)?;
+ tokio::time::sleep(Duration::from_millis(config.poll_interval_ms)).await;
+ response = remote_http_get_json_io(config, status_url.as_str(), request).await?;
+ match response.status {
+ RadrootsSp1TradeRemoteProverStatus::Completed => return Ok(response),
+ RadrootsSp1TradeRemoteProverStatus::Failed => {
+ return Err(remote_http_terminal_error("failed", response));
+ }
+ RadrootsSp1TradeRemoteProverStatus::Rejected => {
+ return Err(remote_http_terminal_error("rejected", response));
+ }
+ RadrootsSp1TradeRemoteProverStatus::Accepted
+ | RadrootsSp1TradeRemoteProverStatus::Running => {}
+ }
+ }
+ Err(TradeValidationReceiptJobError::RemoteHttpTimeout)
+}
+
+#[cfg(feature = "sp1_proving")]
+fn remote_http_terminal_error(
+ status: &'static str,
+ response: RadrootsSp1TradeRemoteProverResponse,
+) -> TradeValidationReceiptJobError {
+ TradeValidationReceiptJobError::RemoteHttpTerminal {
+ status,
+ reason_code: response
+ .reason_code
+ .unwrap_or_else(|| "remote_prover_terminal".to_string()),
+ message: response
+ .message
+ .unwrap_or_else(|| "remote prover reached a terminal non-success state".to_string()),
+ }
+}
+
+#[cfg(feature = "sp1_proving")]
+async fn remote_http_verified_artifact(
+ execution: &radroots_sp1_guest_trade::RadrootsSp1TradePublicValuesExecution,
+ policy: &TradeValidationReceiptProverPolicy,
+ expected_sp1_program_hash: &str,
+ expected_sp1_verifying_key_hash: &str,
+ request: &RadrootsSp1TradeRemoteProverRequest,
+ response: RadrootsSp1TradeRemoteProverResponse,
+) -> Result<radroots_sp1_host_trade::RadrootsSp1TradeProofArtifact, TradeValidationReceiptJobError>
+{
+ if response.schema_version != RADROOTS_SP1_TRADE_REMOTE_PROVER_SCHEMA_VERSION {
+ return Err(TradeValidationReceiptJobError::RemoteHttpInvalidResponse(
+ "schema_version",
+ ));
+ }
+ if response.request_id != request.request_id {
+ return Err(TradeValidationReceiptJobError::RemoteHttpIdentityMismatch(
+ "request_id",
+ ));
+ }
+ if response.proof_mode != Some(policy.proof_mode) {
+ return Err(TradeValidationReceiptJobError::RemoteHttpIdentityMismatch(
+ "proof_mode",
+ ));
+ }
+ if response.proof_system != Some(policy.proof_mode.proof_system()) {
+ return Err(TradeValidationReceiptJobError::RemoteHttpIdentityMismatch(
+ "proof_system",
+ ));
+ }
+ if response.public_values_hash.as_deref() != Some(execution.public_values_hash.as_str()) {
+ return Err(TradeValidationReceiptJobError::RemoteHttpIdentityMismatch(
+ "public_values_hash",
+ ));
+ }
+ if response.sp1_program_hash.as_deref() != Some(expected_sp1_program_hash) {
+ return Err(TradeValidationReceiptJobError::RemoteHttpIdentityMismatch(
+ "sp1_program_hash",
+ ));
+ }
+ if response.sp1_verifying_key_hash.as_deref() != Some(expected_sp1_verifying_key_hash) {
+ return Err(TradeValidationReceiptJobError::RemoteHttpIdentityMismatch(
+ "sp1_verifying_key_hash",
+ ));
+ }
+ let artifact = response.proof_artifact.ok_or(
+ TradeValidationReceiptJobError::RemoteHttpInvalidResponse("proof_artifact"),
+ )?;
+ let resolved = RadrootsSp1TradeResolvedProofArtifact {
+ artifact,
+ resolved_proof_envelope_base64: response.resolved_proof_envelope_base64,
+ };
+ verify_remote_proof_artifact_io(execution, &resolved).await?;
+ Ok(resolved.artifact)
+}
+
+#[cfg(feature = "sp1_proving")]
+async fn verify_remote_proof_artifact_io(
+ execution: &radroots_sp1_guest_trade::RadrootsSp1TradePublicValuesExecution,
+ resolved: &RadrootsSp1TradeResolvedProofArtifact,
+) -> Result<(), TradeValidationReceiptJobError> {
+ #[cfg(test)]
+ if let Some(result) = pop_remote_proof_verification_hook() {
+ return result;
+ }
+
+ radroots_sp1_host_trade::verify_order_acceptance_resolved_sp1_proof_artifact(
+ execution, resolved,
+ )
+ .await?;
+ Ok(())
+}
+
+#[cfg(feature = "sp1_proving")]
+async fn remote_http_post_json_io(
+ config: &TradeValidationReceiptRemoteHttpProverConfig,
+ url: &str,
+ request: &RadrootsSp1TradeRemoteProverRequest,
+) -> Result<RadrootsSp1TradeRemoteProverResponse, TradeValidationReceiptJobError> {
+ #[cfg(test)]
+ if let Some(result) = pop_remote_http_response_hook(request) {
+ return result;
+ }
+
+ let client = remote_http_client(config)?;
+ let mut builder = client.post(url).json(request);
+ if let Some(token) = remote_http_auth_token(config)? {
+ builder = builder.bearer_auth(token);
+ }
+ remote_http_response_json(config, builder.send().await).await
+}
+
+#[cfg(feature = "sp1_proving")]
+async fn remote_http_get_json_io(
+ config: &TradeValidationReceiptRemoteHttpProverConfig,
+ url: &str,
+ _request: &RadrootsSp1TradeRemoteProverRequest,
+) -> Result<RadrootsSp1TradeRemoteProverResponse, TradeValidationReceiptJobError> {
+ #[cfg(test)]
+ if let Some(result) = pop_remote_http_response_hook(_request) {
+ return result;
+ }
+
+ let client = remote_http_client(config)?;
+ let mut builder = client.get(url);
+ if let Some(token) = remote_http_auth_token(config)? {
+ builder = builder.bearer_auth(token);
+ }
+ remote_http_response_json(config, builder.send().await).await
+}
+
+#[cfg(feature = "sp1_proving")]
+fn remote_http_client(
+ config: &TradeValidationReceiptRemoteHttpProverConfig,
+) -> Result<reqwest::Client, TradeValidationReceiptJobError> {
+ reqwest::Client::builder()
+ .timeout(Duration::from_millis(config.request_timeout_ms))
+ .build()
+ .map_err(|error| TradeValidationReceiptJobError::RemoteHttpTransport(error.to_string()))
+}
+
+#[cfg(feature = "sp1_proving")]
+async fn remote_http_response_json(
+ config: &TradeValidationReceiptRemoteHttpProverConfig,
+ response: Result<reqwest::Response, reqwest::Error>,
+) -> Result<RadrootsSp1TradeRemoteProverResponse, TradeValidationReceiptJobError> {
+ let response = response
+ .map_err(|error| TradeValidationReceiptJobError::RemoteHttpTransport(error.to_string()))?;
+ if !response.status().is_success() {
+ return Err(TradeValidationReceiptJobError::RemoteHttpTransport(
+ format!("http status {}", response.status().as_u16()),
+ ));
+ }
+ let bytes = response
+ .bytes()
+ .await
+ .map_err(|error| TradeValidationReceiptJobError::RemoteHttpTransport(error.to_string()))?;
+ if bytes.len() > config.max_response_bytes {
+ return Err(TradeValidationReceiptJobError::RemoteHttpResponseTooLarge);
+ }
+ serde_json::from_slice::<RadrootsSp1TradeRemoteProverResponse>(&bytes)
+ .map_err(TradeValidationReceiptJobError::Serde)
+}
+
+#[cfg(feature = "sp1_proving")]
+fn remote_http_status_url(
+ config: &TradeValidationReceiptRemoteHttpProverConfig,
+ response: &RadrootsSp1TradeRemoteProverResponse,
+) -> Result<String, TradeValidationReceiptJobError> {
+ if let Some(url) = response.status_url.as_deref() {
+ let parsed = reqwest::Url::parse(url)
+ .map_err(|_| TradeValidationReceiptJobError::RemoteHttpInvalidResponse("status_url"))?;
+ if parsed.scheme() != "http" && parsed.scheme() != "https" {
+ return Err(TradeValidationReceiptJobError::RemoteHttpInvalidResponse(
+ "status_url",
+ ));
+ }
+ return Ok(parsed.to_string());
+ }
+ if let Some(path) = response.status_path.as_deref() {
+ let base = reqwest::Url::parse(config.endpoint_url.as_str())
+ .map_err(|_| TradeValidationReceiptJobError::RemoteHttpInvalidConfig("endpoint_url"))?;
+ return base
+ .join(path)
+ .map(|url| url.to_string())
+ .map_err(|_| TradeValidationReceiptJobError::RemoteHttpInvalidResponse("status_path"));
+ }
+ Err(TradeValidationReceiptJobError::RemoteHttpInvalidResponse(
+ "status_url",
+ ))
+}
+
#[cfg(not(feature = "sp1_proving"))]
async fn run_local_cpu_prove_backend(
_witness: &RadrootsSp1TradeOrderAcceptanceWitness,
@@ -855,6 +1294,13 @@ struct TradeValidationReceiptTestHooks {
std::collections::VecDeque<Result<RadrootsNostrEvent, TradeValidationReceiptJobError>>,
publish_event_results:
std::collections::VecDeque<Result<String, TradeValidationReceiptJobError>>,
+ #[cfg(feature = "sp1_proving")]
+ remote_http_results: std::collections::VecDeque<
+ Result<RadrootsSp1TradeRemoteProverResponse, TradeValidationReceiptJobError>,
+ >,
+ #[cfg(feature = "sp1_proving")]
+ remote_proof_verification_results:
+ std::collections::VecDeque<Result<(), TradeValidationReceiptJobError>>,
published_events: Vec<PublishedEventParts>,
}
@@ -897,13 +1343,83 @@ fn pop_publish_event_hook(
hooks.publish_event_results.pop_front()
}
+#[cfg(all(test, feature = "sp1_proving"))]
+fn pop_remote_http_response_hook(
+ request: &RadrootsSp1TradeRemoteProverRequest,
+) -> Option<Result<RadrootsSp1TradeRemoteProverResponse, TradeValidationReceiptJobError>> {
+ pop_remote_http_response_hook_without_request().map(|result| {
+ result.and_then(|response| remote_http_test_response_for_request(request, response))
+ })
+}
+
+#[cfg(all(test, feature = "sp1_proving"))]
+fn pop_remote_http_response_hook_without_request()
+-> Option<Result<RadrootsSp1TradeRemoteProverResponse, TradeValidationReceiptJobError>> {
+ trade_validation_receipt_test_hooks()
+ .lock()
+ .unwrap_or_else(std::sync::PoisonError::into_inner)
+ .remote_http_results
+ .pop_front()
+}
+
+#[cfg(all(test, feature = "sp1_proving"))]
+fn remote_http_test_response_for_request(
+ request: &RadrootsSp1TradeRemoteProverRequest,
+ mut response: RadrootsSp1TradeRemoteProverResponse,
+) -> Result<RadrootsSp1TradeRemoteProverResponse, TradeValidationReceiptJobError> {
+ if response.request_id == "__request_id__" {
+ response.request_id = request.request_id.clone();
+ }
+ if response.status == RadrootsSp1TradeRemoteProverStatus::Completed
+ && response.proof_artifact.is_none()
+ {
+ let execution =
+ radroots_sp1_host_trade::execute_order_acceptance_public_values(&request.witness)?;
+ let artifact =
+ radroots_sp1_host_trade::referenced_order_acceptance_proof_artifact_for_execution(
+ &execution,
+ request.proof_mode,
+ format!("radroots-proof://sha256/{}", "1".repeat(64)),
+ )?;
+ if response.proof_system.is_none() {
+ response.proof_system = Some(request.proof_mode.proof_system());
+ }
+ if response.proof_mode.is_none() {
+ response.proof_mode = Some(request.proof_mode);
+ }
+ if response.public_values_hash.is_none() {
+ response.public_values_hash = Some(execution.public_values_hash);
+ }
+ if response.sp1_program_hash.is_none() {
+ response.sp1_program_hash = Some(request.expected_sp1_program_hash.clone());
+ }
+ if response.sp1_verifying_key_hash.is_none() {
+ response.sp1_verifying_key_hash = Some(request.expected_sp1_verifying_key_hash.clone());
+ }
+ if response.proof_artifact.is_none() {
+ response.proof_artifact = Some(artifact);
+ }
+ }
+ Ok(response)
+}
+
+#[cfg(all(test, feature = "sp1_proving"))]
+fn pop_remote_proof_verification_hook() -> Option<Result<(), TradeValidationReceiptJobError>> {
+ trade_validation_receipt_test_hooks()
+ .lock()
+ .unwrap_or_else(std::sync::PoisonError::into_inner)
+ .remote_proof_verification_results
+ .pop_front()
+}
+
#[cfg(test)]
#[cfg_attr(coverage_nightly, coverage(off))]
mod tests {
use super::{
TradeValidationReceiptJobError, TradeValidationReceiptJobRequest,
TradeValidationReceiptJobResult, TradeValidationReceiptProverBackend,
- TradeValidationReceiptProverPolicy, TradeValidationReceiptTestHooks,
+ TradeValidationReceiptProverPolicy, TradeValidationReceiptRemoteHttpAuth,
+ TradeValidationReceiptRemoteHttpProverConfig, TradeValidationReceiptTestHooks,
handle_trade_validation_receipt_job_request, trade_validation_receipt_test_hooks,
};
use radroots_core::{
@@ -932,7 +1448,14 @@ mod tests {
RADROOTS_SP1_TRADE_PROTOCOL_VERSION, RADROOTS_SP1_TRADE_REDUCER_PROGRAM_HASH,
RadrootsSp1TradeInventoryBinWitness,
};
+ #[cfg(feature = "sp1_proving")]
+ use radroots_sp1_host_trade::RadrootsSp1TradeHostError;
use radroots_sp1_host_trade::RadrootsSp1TradeProofMode;
+ #[cfg(feature = "sp1_proving")]
+ use radroots_sp1_host_trade::{
+ RADROOTS_SP1_TRADE_REMOTE_PROVER_SCHEMA_VERSION, RadrootsSp1TradeRemoteProverResponse,
+ RadrootsSp1TradeRemoteProverStatus,
+ };
use radroots_trade::validation_receipt::{
RadrootsValidationReceiptExpectedBinding, RadrootsValidationReceiptProofSystem,
verify_validation_receipt_event,
@@ -1093,6 +1616,7 @@ mod tests {
request_event: &RadrootsNostrEvent,
decision_event: &RadrootsNostrEvent,
proof_mode: RadrootsSp1TradeProofMode,
+ sp1_program_hash: Option<String>,
sp1_verifying_key_hash: Option<String>,
) -> RadrootsNostrEvent {
let request = TradeValidationReceiptJobRequest {
@@ -1113,7 +1637,7 @@ mod tests {
proof_mode,
reducer_program_hash: RADROOTS_SP1_TRADE_REDUCER_PROGRAM_HASH.to_string(),
radroots_protocol_version: RADROOTS_SP1_TRADE_PROTOCOL_VERSION.to_string(),
- sp1_program_hash: None,
+ sp1_program_hash,
sp1_verifying_key_hash,
};
signed_event(
@@ -1136,6 +1660,120 @@ mod tests {
format!("0x{}", ch.to_string().repeat(64))
}
+ fn remote_http_config() -> TradeValidationReceiptRemoteHttpProverConfig {
+ TradeValidationReceiptRemoteHttpProverConfig {
+ endpoint_url: "http://127.0.0.1:65535/prove".to_string(),
+ auth: TradeValidationReceiptRemoteHttpAuth::NoAuth,
+ request_timeout_ms: 1000,
+ poll_interval_ms: 1,
+ max_poll_attempts: 1,
+ max_response_bytes: 65_536,
+ }
+ }
+
+ fn remote_http_policy() -> TradeValidationReceiptProverPolicy {
+ TradeValidationReceiptProverPolicy {
+ backend: TradeValidationReceiptProverBackend::RemoteHttpProve,
+ proof_mode: RadrootsSp1TradeProofMode::Core,
+ expected_sp1_program_hash: Some(hash32('a')),
+ expected_sp1_verifying_key_hash: Some(hash32('b')),
+ remote_http: Some(remote_http_config()),
+ }
+ }
+
+ #[cfg(feature = "sp1_proving")]
+ fn remote_response(
+ status: RadrootsSp1TradeRemoteProverStatus,
+ ) -> RadrootsSp1TradeRemoteProverResponse {
+ RadrootsSp1TradeRemoteProverResponse {
+ schema_version: RADROOTS_SP1_TRADE_REMOTE_PROVER_SCHEMA_VERSION,
+ request_id: "__request_id__".to_string(),
+ status,
+ status_url: None,
+ status_path: None,
+ proof_system: None,
+ proof_mode: None,
+ public_values_hash: None,
+ sp1_program_hash: None,
+ sp1_verifying_key_hash: None,
+ proof_artifact: None,
+ resolved_proof_envelope_base64: None,
+ reason_code: None,
+ message: None,
+ detail: None,
+ }
+ }
+
+ #[cfg(feature = "sp1_proving")]
+ async fn run_remote_http_job(
+ remote_http_results: Vec<
+ Result<RadrootsSp1TradeRemoteProverResponse, TradeValidationReceiptJobError>,
+ >,
+ remote_proof_verification_results: Vec<Result<(), TradeValidationReceiptJobError>>,
+ publish_results: Vec<Result<String, TradeValidationReceiptJobError>>,
+ ) -> Result<Vec<super::PublishedEventParts>, TradeValidationReceiptJobError> {
+ run_remote_http_job_with_policy(
+ remote_http_policy(),
+ remote_http_results,
+ remote_proof_verification_results,
+ publish_results,
+ )
+ .await
+ }
+
+ #[cfg(feature = "sp1_proving")]
+ async fn run_remote_http_job_with_policy(
+ policy: TradeValidationReceiptProverPolicy,
+ remote_http_results: Vec<
+ Result<RadrootsSp1TradeRemoteProverResponse, TradeValidationReceiptJobError>,
+ >,
+ remote_proof_verification_results: Vec<Result<(), TradeValidationReceiptJobError>>,
+ publish_results: Vec<Result<String, TradeValidationReceiptJobError>>,
+ ) -> Result<Vec<super::PublishedEventParts>, TradeValidationReceiptJobError> {
+ let _guard = test_guard();
+ let worker = RadrootsNostrKeys::generate();
+ let requester = RadrootsNostrKeys::generate();
+ let buyer = RadrootsNostrKeys::generate();
+ let seller = RadrootsNostrKeys::generate();
+ let listing_event = listing_event(&seller);
+ let (request_event, decision_event) = signed_order_events(&buyer, &seller, &listing_event);
+ let job = job_request(
+ &requester,
+ &worker,
+ &listing_event,
+ &request_event,
+ &decision_event,
+ RadrootsSp1TradeProofMode::Core,
+ Some(hash32('a')),
+ Some(hash32('b')),
+ );
+
+ {
+ let mut hooks = trade_validation_receipt_test_hooks()
+ .lock()
+ .unwrap_or_else(std::sync::PoisonError::into_inner);
+ hooks.fetch_event_by_id_results.push_back(Ok(listing_event));
+ hooks.fetch_event_by_id_results.push_back(Ok(request_event));
+ hooks
+ .fetch_event_by_id_results
+ .push_back(Ok(decision_event));
+ hooks.remote_http_results.extend(remote_http_results);
+ hooks
+ .remote_proof_verification_results
+ .extend(remote_proof_verification_results);
+ hooks.publish_event_results.extend(publish_results);
+ }
+
+ handle_trade_validation_receipt_job_request(&job, &worker, &client_for(&worker), &policy)
+ .await?;
+
+ Ok(trade_validation_receipt_test_hooks()
+ .lock()
+ .unwrap_or_else(std::sync::PoisonError::into_inner)
+ .published_events
+ .clone())
+ }
+
#[test]
fn prover_policy_requires_configured_sp1_identity_for_local_cpu() {
let missing_identity = TradeValidationReceiptProverPolicy {
@@ -1143,6 +1781,7 @@ mod tests {
proof_mode: RadrootsSp1TradeProofMode::Core,
expected_sp1_program_hash: None,
expected_sp1_verifying_key_hash: Some(hash32('b')),
+ remote_http: None,
};
assert!(matches!(
missing_identity.validate(),
@@ -1154,6 +1793,7 @@ mod tests {
proof_mode: RadrootsSp1TradeProofMode::Core,
expected_sp1_program_hash: Some(hash32('a')),
expected_sp1_verifying_key_hash: Some(hash32('b')),
+ remote_http: None,
};
let request = TradeValidationReceiptJobRequest {
witness_version: radroots_sp1_guest_trade::RADROOTS_SP1_TRADE_WITNESS_VERSION,
@@ -1190,6 +1830,329 @@ mod tests {
));
}
+ #[test]
+ fn remote_http_policy_requires_explicit_config_and_identity_before_relay_fetch() {
+ let missing_config = TradeValidationReceiptProverPolicy {
+ backend: TradeValidationReceiptProverBackend::RemoteHttpProve,
+ proof_mode: RadrootsSp1TradeProofMode::Core,
+ expected_sp1_program_hash: Some(hash32('a')),
+ expected_sp1_verifying_key_hash: Some(hash32('b')),
+ remote_http: None,
+ };
+ assert!(matches!(
+ missing_config.validate(),
+ Err(TradeValidationReceiptJobError::RemoteHttpConfigRequired)
+ ));
+
+ let missing_identity = TradeValidationReceiptProverPolicy {
+ backend: TradeValidationReceiptProverBackend::RemoteHttpProve,
+ proof_mode: RadrootsSp1TradeProofMode::Core,
+ expected_sp1_program_hash: None,
+ expected_sp1_verifying_key_hash: Some(hash32('b')),
+ remote_http: Some(remote_http_config()),
+ };
+ assert!(matches!(
+ missing_identity.validate(),
+ Err(TradeValidationReceiptJobError::Sp1IdentityPolicyRequired)
+ ));
+
+ let mut invalid_config = remote_http_policy();
+ invalid_config
+ .remote_http
+ .as_mut()
+ .expect("remote config")
+ .endpoint_url = "file:///tmp/prove".to_string();
+ assert!(matches!(
+ invalid_config.validate(),
+ Err(TradeValidationReceiptJobError::RemoteHttpInvalidConfig(
+ "endpoint_url"
+ ))
+ ));
+ }
+
+ #[cfg(feature = "sp1_proving")]
+ #[tokio::test]
+ async fn remote_http_prove_publishes_only_after_remote_artifact_verification() {
+ let published = run_remote_http_job(
+ vec![Ok(remote_response(
+ RadrootsSp1TradeRemoteProverStatus::Completed,
+ ))],
+ vec![Ok(())],
+ vec![Ok(publish_result_id(1)), Ok(publish_result_id(2))],
+ )
+ .await
+ .expect("remote proof job");
+
+ assert_eq!(published.len(), 2);
+ assert_eq!(published[0].kind, KIND_TRADE_VALIDATION_RECEIPT);
+ assert_eq!(published[1].kind, KIND_WORKER_TRADE_TRANSITION_PROOF_RES);
+ let result: TradeValidationReceiptJobResult =
+ serde_json::from_str(&published[1].content).expect("result json");
+ assert_eq!(
+ result.prover_backend,
+ TradeValidationReceiptProverBackend::RemoteHttpProve
+ );
+ assert!(result.proof_generated);
+ assert_eq!(result.proof_mode, RadrootsSp1TradeProofMode::Core);
+ assert_eq!(result.proof_system, "sp1_core");
+ assert!(result.sp1_execute_checked);
+ assert_eq!(
+ result.sp1_execute_public_values_hash.as_deref(),
+ Some(result.public_values_hash.as_str())
+ );
+ assert!(result.cryptographic_proof_verified);
+ }
+
+ #[cfg(feature = "sp1_proving")]
+ #[tokio::test]
+ async fn remote_http_prove_polls_running_until_completed() {
+ let mut policy = remote_http_policy();
+ policy
+ .remote_http
+ .as_mut()
+ .expect("remote config")
+ .max_poll_attempts = 2;
+ let mut accepted = remote_response(RadrootsSp1TradeRemoteProverStatus::Accepted);
+ accepted.status_path = Some("/prove/status/request-1".to_string());
+ let mut running = remote_response(RadrootsSp1TradeRemoteProverStatus::Running);
+ running.status_path = Some("/prove/status/request-1".to_string());
+ let published = run_remote_http_job_with_policy(
+ policy,
+ vec![
+ Ok(accepted),
+ Ok(running),
+ Ok(remote_response(
+ RadrootsSp1TradeRemoteProverStatus::Completed,
+ )),
+ ],
+ vec![Ok(())],
+ vec![Ok(publish_result_id(1)), Ok(publish_result_id(2))],
+ )
+ .await
+ .expect("polled remote proof job");
+
+ assert_eq!(published.len(), 2);
+ let result: TradeValidationReceiptJobResult =
+ serde_json::from_str(&published[1].content).expect("result json");
+ assert_eq!(
+ result.prover_backend,
+ TradeValidationReceiptProverBackend::RemoteHttpProve
+ );
+ assert!(result.cryptographic_proof_verified);
+ }
+
+ #[cfg(feature = "sp1_proving")]
+ #[tokio::test]
+ async fn remote_http_prove_does_not_publish_when_verification_fails() {
+ let error = run_remote_http_job(
+ vec![Ok(remote_response(
+ RadrootsSp1TradeRemoteProverStatus::Completed,
+ ))],
+ vec![Err(TradeValidationReceiptJobError::Proof(
+ RadrootsSp1TradeHostError::Sp1ProofVerificationFailed("test".to_string()),
+ ))],
+ vec![Err(TradeValidationReceiptJobError::InvalidJobRequest)],
+ )
+ .await
+ .expect_err("remote verification failure");
+
+ assert!(matches!(
+ error,
+ TradeValidationReceiptJobError::Proof(
+ RadrootsSp1TradeHostError::Sp1ProofVerificationFailed(_)
+ )
+ ));
+ assert!(
+ trade_validation_receipt_test_hooks()
+ .lock()
+ .unwrap_or_else(std::sync::PoisonError::into_inner)
+ .published_events
+ .is_empty()
+ );
+ }
+
+ #[cfg(feature = "sp1_proving")]
+ #[tokio::test]
+ async fn remote_http_prove_does_not_publish_when_reference_digest_mismatches() {
+ let mut response = remote_response(RadrootsSp1TradeRemoteProverStatus::Completed);
+ response.resolved_proof_envelope_base64 = Some("cHJvb2Y=".to_string());
+ let error = run_remote_http_job(
+ vec![Ok(response)],
+ Vec::new(),
+ vec![Err(TradeValidationReceiptJobError::InvalidJobRequest)],
+ )
+ .await
+ .expect_err("remote proof reference mismatch");
+
+ assert!(matches!(
+ error,
+ TradeValidationReceiptJobError::Proof(
+ RadrootsSp1TradeHostError::Sp1ProofReferenceDigestMismatch
+ )
+ ));
+ assert!(
+ trade_validation_receipt_test_hooks()
+ .lock()
+ .unwrap_or_else(std::sync::PoisonError::into_inner)
+ .published_events
+ .is_empty()
+ );
+ }
+
+ #[cfg(feature = "sp1_proving")]
+ #[tokio::test]
+ async fn remote_http_prove_does_not_publish_when_sp1_identity_mismatches() {
+ let mut response = remote_response(RadrootsSp1TradeRemoteProverStatus::Completed);
+ response.sp1_program_hash = Some(hash32('c'));
+ let error = run_remote_http_job(
+ vec![Ok(response)],
+ vec![Ok(())],
+ vec![Err(TradeValidationReceiptJobError::InvalidJobRequest)],
+ )
+ .await
+ .expect_err("remote sp1 identity mismatch");
+
+ assert!(matches!(
+ error,
+ TradeValidationReceiptJobError::RemoteHttpIdentityMismatch("sp1_program_hash")
+ ));
+ assert!(
+ trade_validation_receipt_test_hooks()
+ .lock()
+ .unwrap_or_else(std::sync::PoisonError::into_inner)
+ .published_events
+ .is_empty()
+ );
+ }
+
+ #[cfg(feature = "sp1_proving")]
+ #[tokio::test]
+ async fn remote_http_prove_does_not_publish_when_public_values_mismatch() {
+ let mut response = remote_response(RadrootsSp1TradeRemoteProverStatus::Completed);
+ response.public_values_hash = Some(hash32('d'));
+ let error = run_remote_http_job(
+ vec![Ok(response)],
+ vec![Ok(())],
+ vec![Err(TradeValidationReceiptJobError::InvalidJobRequest)],
+ )
+ .await
+ .expect_err("remote public values mismatch");
+
+ assert!(matches!(
+ error,
+ TradeValidationReceiptJobError::RemoteHttpIdentityMismatch("public_values_hash")
+ ));
+ assert!(
+ trade_validation_receipt_test_hooks()
+ .lock()
+ .unwrap_or_else(std::sync::PoisonError::into_inner)
+ .published_events
+ .is_empty()
+ );
+ }
+
+ #[cfg(feature = "sp1_proving")]
+ #[tokio::test]
+ async fn remote_http_prove_does_not_publish_terminal_failed_or_rejected() {
+ let mut failed = remote_response(RadrootsSp1TradeRemoteProverStatus::Failed);
+ failed.reason_code = Some("remote_failed".to_string());
+ failed.message = Some("remote prover failed".to_string());
+ let error = run_remote_http_job(
+ vec![Ok(failed)],
+ Vec::new(),
+ vec![Err(TradeValidationReceiptJobError::InvalidJobRequest)],
+ )
+ .await
+ .expect_err("remote failed");
+ assert!(matches!(
+ error,
+ TradeValidationReceiptJobError::RemoteHttpTerminal {
+ status: "failed",
+ ..
+ }
+ ));
+ assert!(
+ trade_validation_receipt_test_hooks()
+ .lock()
+ .unwrap_or_else(std::sync::PoisonError::into_inner)
+ .published_events
+ .is_empty()
+ );
+
+ let mut rejected = remote_response(RadrootsSp1TradeRemoteProverStatus::Rejected);
+ rejected.reason_code = Some("remote_rejected".to_string());
+ rejected.message = Some("remote prover rejected request".to_string());
+ let error = run_remote_http_job(
+ vec![Ok(rejected)],
+ Vec::new(),
+ vec![Err(TradeValidationReceiptJobError::InvalidJobRequest)],
+ )
+ .await
+ .expect_err("remote rejected");
+ assert!(matches!(
+ error,
+ TradeValidationReceiptJobError::RemoteHttpTerminal {
+ status: "rejected",
+ ..
+ }
+ ));
+ assert!(
+ trade_validation_receipt_test_hooks()
+ .lock()
+ .unwrap_or_else(std::sync::PoisonError::into_inner)
+ .published_events
+ .is_empty()
+ );
+ }
+
+ #[cfg(feature = "sp1_proving")]
+ #[tokio::test]
+ async fn remote_http_prove_does_not_publish_timeout_or_oversized_response() {
+ let mut accepted = remote_response(RadrootsSp1TradeRemoteProverStatus::Accepted);
+ accepted.status_path = Some("/prove/status/request-1".to_string());
+ let mut running = remote_response(RadrootsSp1TradeRemoteProverStatus::Running);
+ running.status_path = Some("/prove/status/request-1".to_string());
+ let error = run_remote_http_job(
+ vec![Ok(accepted), Ok(running)],
+ Vec::new(),
+ vec![Err(TradeValidationReceiptJobError::InvalidJobRequest)],
+ )
+ .await
+ .expect_err("remote timeout");
+ assert!(matches!(
+ error,
+ TradeValidationReceiptJobError::RemoteHttpTimeout
+ ));
+ assert!(
+ trade_validation_receipt_test_hooks()
+ .lock()
+ .unwrap_or_else(std::sync::PoisonError::into_inner)
+ .published_events
+ .is_empty()
+ );
+
+ let error = run_remote_http_job(
+ vec![Err(
+ TradeValidationReceiptJobError::RemoteHttpResponseTooLarge,
+ )],
+ Vec::new(),
+ vec![Err(TradeValidationReceiptJobError::InvalidJobRequest)],
+ )
+ .await
+ .expect_err("remote oversized response");
+ assert!(matches!(
+ error,
+ TradeValidationReceiptJobError::RemoteHttpResponseTooLarge
+ ));
+ assert!(
+ trade_validation_receipt_test_hooks()
+ .lock()
+ .unwrap_or_else(std::sync::PoisonError::into_inner)
+ .published_events
+ .is_empty()
+ );
+ }
+
#[tokio::test]
async fn proof_job_publishes_verified_receipt_and_result_after_proof_verification() {
let _guard = test_guard();
@@ -1207,6 +2170,7 @@ mod tests {
&decision_event,
RadrootsSp1TradeProofMode::None,
None,
+ None,
);
{
@@ -1315,6 +2279,7 @@ mod tests {
&decision_event,
RadrootsSp1TradeProofMode::Compressed,
None,
+ None,
);
{
@@ -1374,6 +2339,7 @@ mod tests {
&decision_event,
RadrootsSp1TradeProofMode::None,
None,
+ None,
);
let mut request_json: serde_json::Value =
serde_json::from_str(&job.content).expect("request json");
@@ -1429,6 +2395,7 @@ mod tests {
&decision_event,
RadrootsSp1TradeProofMode::None,
None,
+ None,
);
{
@@ -1479,6 +2446,7 @@ mod tests {
&decision_event,
RadrootsSp1TradeProofMode::None,
None,
+ None,
);
request_event.content.push(' ');
@@ -1531,6 +2499,7 @@ mod tests {
&decision_event,
RadrootsSp1TradeProofMode::None,
None,
+ None,
);
let mut request: TradeValidationReceiptJobRequest =
serde_json::from_str(&job.content).expect("job request json");
@@ -1591,6 +2560,7 @@ mod tests {
&decision_event,
RadrootsSp1TradeProofMode::None,
None,
+ None,
);
{
@@ -1609,6 +2579,7 @@ mod tests {
proof_mode: RadrootsSp1TradeProofMode::None,
expected_sp1_program_hash: None,
expected_sp1_verifying_key_hash: None,
+ remote_http: None,
};
let error = handle_trade_validation_receipt_job_request(
&job,
diff --git a/src/proof_smoke.rs b/src/proof_smoke.rs
@@ -15,7 +15,7 @@ use radroots_sp1_guest_trade::{
};
use radroots_sp1_host_trade::{
RadrootsSp1TradeProofMode, generate_order_acceptance_proof,
- verify_order_acceptance_proof_artifact,
+ verify_order_acceptance_proof_artifact_structure,
};
use serde::{Deserialize, Serialize};
use std::path::Path;
@@ -189,7 +189,7 @@ fn deterministic_smoke(
) -> Result<RhiProofSmokeOutput, RhiProofSmokeError> {
let bundle = generate_order_acceptance_proof(witness, RadrootsSp1TradeProofMode::None)
.map_err(|error| RhiProofSmokeError::Deterministic(error.to_string()))?;
- verify_order_acceptance_proof_artifact(&bundle.execution, &bundle.proof)
+ verify_order_acceptance_proof_artifact_structure(&bundle.execution, &bundle.proof)
.map_err(|error| RhiProofSmokeError::Deterministic(error.to_string()))?;
Ok(RhiProofSmokeOutput {
public_values_hash: canonical_hex_64(&bundle.execution.public_values_hash)?,