proof_smoke.rs (23370B)
1 #![forbid(unsafe_code)] 2 #![cfg_attr(coverage_nightly, coverage(off))] 3 4 use crate::cli::Command; 5 use radroots_sp1_guest_trade::{ 6 RADROOTS_SP1_TRADE_KIND_LISTING, RADROOTS_SP1_TRADE_KIND_ORDER_DECISION, 7 RADROOTS_SP1_TRADE_KIND_ORDER_REQUEST, RADROOTS_SP1_TRADE_ORDER_ACCEPTANCE_PROOF_TARGET, 8 RADROOTS_SP1_TRADE_PROTOCOL_VERSION, RADROOTS_SP1_TRADE_REDUCER_PROGRAM_HASH, 9 RADROOTS_SP1_TRADE_WITNESS_VERSION, RadrootsSp1TradeCanonicalEventEvidence, 10 RadrootsSp1TradeEventEvidenceRole, RadrootsSp1TradeEventWorkflowPosition, 11 RadrootsSp1TradeInventoryBinWitness, RadrootsSp1TradeInventoryCommitmentWitness, 12 RadrootsSp1TradeOrderAcceptanceWitness, RadrootsSp1TradeOrderDecisionEventWitness, 13 RadrootsSp1TradeOrderDecisionWitness, RadrootsSp1TradeOrderItemWitness, 14 RadrootsSp1TradeOrderRequestWitness, 15 }; 16 use radroots_sp1_host_trade::{ 17 RadrootsSp1TradeProofMode, generate_order_acceptance_proof, 18 verify_order_acceptance_proof_artifact_structure, 19 }; 20 use serde::{Deserialize, Serialize}; 21 use std::path::Path; 22 use std::time::Instant; 23 use thiserror::Error; 24 25 const PROTOCOL_VERSION: &str = "radroots.rhi.proof_smoke.v0"; 26 const WORKER_NAME: &str = "rhi"; 27 const SP1_VERSION: &str = "6.2.1"; 28 const ORDER_ACCEPTANCE_TINY_FIXTURE: &str = "order_acceptance_tiny_v1"; 29 30 #[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] 31 #[serde(deny_unknown_fields)] 32 pub struct RhiProofSmokeRequest { 33 pub protocol_version: String, 34 pub operation: RhiProofSmokeOperation, 35 pub backend: RhiProofSmokeBackend, 36 #[serde(default)] 37 pub fixture: Option<String>, 38 } 39 40 #[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)] 41 #[serde(rename_all = "snake_case")] 42 pub enum RhiProofSmokeOperation { 43 Health, 44 ProofSmoke, 45 } 46 47 #[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)] 48 #[serde(rename_all = "snake_case")] 49 pub enum RhiProofSmokeBackend { 50 DeterministicNone, 51 LocalExecute, 52 } 53 54 #[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] 55 pub struct RhiProofSmokeResponse { 56 pub ok: bool, 57 pub protocol_version: String, 58 pub operation: RhiProofSmokeOperation, 59 pub worker_name: String, 60 pub worker_version: String, 61 pub git_rev: String, 62 pub sp1_version: String, 63 pub backend: RhiProofSmokeBackend, 64 pub capabilities: Vec<String>, 65 pub proof_generated: bool, 66 pub public_values_hash: Option<String>, 67 pub sp1_program_hash: Option<String>, 68 pub sp1_verifying_key_hash: Option<String>, 69 pub event_set_root: Option<String>, 70 pub reducer_output_root: Option<String>, 71 pub elapsed_ms: u128, 72 pub warnings: Vec<String>, 73 pub error: Option<String>, 74 } 75 76 #[derive(Debug, Error, PartialEq, Eq)] 77 pub enum RhiProofSmokeError { 78 #[error("invalid protocol version")] 79 InvalidProtocolVersion, 80 #[error("proof_smoke requires fixture order_acceptance_tiny_v1")] 81 InvalidFixture, 82 #[error("local_execute backend is unavailable in this build")] 83 LocalExecuteUnavailable, 84 #[error("deterministic proof smoke failed: {0}")] 85 Deterministic(String), 86 #[error("SP1 execute proof smoke failed: {0}")] 87 Sp1Execute(String), 88 } 89 90 pub async fn run_cli_command(command: Command) -> anyhow::Result<()> { 91 let Command::ProofSmoke { input, output } = command else { 92 return Err(anyhow::anyhow!("proof-smoke command expected")); 93 }; 94 let request_bytes = read_input(input.as_deref())?; 95 let response = handle_request_bytes(&request_bytes).await; 96 let response_bytes = serde_json::to_vec_pretty(&response)?; 97 write_output(output.as_deref(), &response_bytes)?; 98 if response.ok { 99 Ok(()) 100 } else { 101 Err(anyhow::anyhow!( 102 "{}", 103 response 104 .error 105 .as_deref() 106 .unwrap_or("proof smoke request failed") 107 )) 108 } 109 } 110 111 pub async fn handle_request_bytes(bytes: &[u8]) -> RhiProofSmokeResponse { 112 let started = Instant::now(); 113 match serde_json::from_slice::<RhiProofSmokeRequest>(bytes) { 114 Ok(request) => response_for_request(request, started).await, 115 Err(error) => response_for_error( 116 RhiProofSmokeOperation::Health, 117 RhiProofSmokeBackend::DeterministicNone, 118 started, 119 error.to_string(), 120 ), 121 } 122 } 123 124 async fn response_for_request( 125 request: RhiProofSmokeRequest, 126 started: Instant, 127 ) -> RhiProofSmokeResponse { 128 if request.protocol_version != PROTOCOL_VERSION { 129 return response_for_error( 130 request.operation, 131 request.backend, 132 started, 133 RhiProofSmokeError::InvalidProtocolVersion.to_string(), 134 ); 135 } 136 137 match request.operation { 138 RhiProofSmokeOperation::Health => response_for_success( 139 request.operation, 140 request.backend, 141 started, 142 None, 143 None, 144 None, 145 None, 146 None, 147 Vec::new(), 148 ), 149 RhiProofSmokeOperation::ProofSmoke => { 150 match run_proof_smoke(request.backend, request.fixture).await { 151 Ok(output) => response_for_success( 152 request.operation, 153 request.backend, 154 started, 155 Some(output.public_values_hash), 156 output.sp1_program_hash, 157 output.sp1_verifying_key_hash, 158 Some(output.event_set_root), 159 Some(output.reducer_output_root), 160 output.warnings, 161 ), 162 Err(error) => response_for_error( 163 request.operation, 164 request.backend, 165 started, 166 error.to_string(), 167 ), 168 } 169 } 170 } 171 } 172 173 struct RhiProofSmokeOutput { 174 public_values_hash: String, 175 sp1_program_hash: Option<String>, 176 sp1_verifying_key_hash: Option<String>, 177 event_set_root: String, 178 reducer_output_root: String, 179 warnings: Vec<String>, 180 } 181 182 async fn run_proof_smoke( 183 backend: RhiProofSmokeBackend, 184 fixture: Option<String>, 185 ) -> Result<RhiProofSmokeOutput, RhiProofSmokeError> { 186 if fixture.as_deref() != Some(ORDER_ACCEPTANCE_TINY_FIXTURE) { 187 return Err(RhiProofSmokeError::InvalidFixture); 188 } 189 190 let witness = order_acceptance_tiny_witness(); 191 match backend { 192 RhiProofSmokeBackend::DeterministicNone => deterministic_smoke(&witness), 193 RhiProofSmokeBackend::LocalExecute => local_execute_smoke(&witness).await, 194 } 195 } 196 197 fn deterministic_smoke( 198 witness: &RadrootsSp1TradeOrderAcceptanceWitness, 199 ) -> Result<RhiProofSmokeOutput, RhiProofSmokeError> { 200 let bundle = generate_order_acceptance_proof(witness, RadrootsSp1TradeProofMode::None) 201 .map_err(|error| RhiProofSmokeError::Deterministic(error.to_string()))?; 202 verify_order_acceptance_proof_artifact_structure(&bundle.execution, &bundle.proof) 203 .map_err(|error| RhiProofSmokeError::Deterministic(error.to_string()))?; 204 Ok(RhiProofSmokeOutput { 205 public_values_hash: canonical_hex_64(&bundle.execution.public_values_hash)?, 206 sp1_program_hash: None, 207 sp1_verifying_key_hash: None, 208 event_set_root: canonical_hex_64(&bundle.execution.public_values.event_set_root)?, 209 reducer_output_root: canonical_hex_64(&bundle.execution.public_values.new_state_root)?, 210 warnings: Vec::new(), 211 }) 212 } 213 214 #[cfg(feature = "sp1_proving")] 215 async fn local_execute_smoke( 216 witness: &RadrootsSp1TradeOrderAcceptanceWitness, 217 ) -> Result<RhiProofSmokeOutput, RhiProofSmokeError> { 218 let execution = radroots_sp1_host_trade::execute_order_acceptance_sp1_public_values(witness) 219 .await 220 .map_err(|error| RhiProofSmokeError::Sp1Execute(error.to_string()))? 221 .execution; 222 Ok(RhiProofSmokeOutput { 223 public_values_hash: canonical_hex_64(&execution.public_values_hash)?, 224 sp1_program_hash: execution 225 .public_values 226 .sp1_program_hash 227 .as_deref() 228 .map(canonical_hex_64) 229 .transpose()?, 230 sp1_verifying_key_hash: execution 231 .public_values 232 .sp1_verifying_key_hash 233 .as_deref() 234 .map(canonical_hex_64) 235 .transpose()?, 236 event_set_root: canonical_hex_64(&execution.public_values.event_set_root)?, 237 reducer_output_root: canonical_hex_64(&execution.public_values.new_state_root)?, 238 warnings: Vec::new(), 239 }) 240 } 241 242 #[cfg(not(feature = "sp1_proving"))] 243 async fn local_execute_smoke( 244 _witness: &RadrootsSp1TradeOrderAcceptanceWitness, 245 ) -> Result<RhiProofSmokeOutput, RhiProofSmokeError> { 246 Err(RhiProofSmokeError::LocalExecuteUnavailable) 247 } 248 249 fn response_for_success( 250 operation: RhiProofSmokeOperation, 251 backend: RhiProofSmokeBackend, 252 started: Instant, 253 public_values_hash: Option<String>, 254 sp1_program_hash: Option<String>, 255 sp1_verifying_key_hash: Option<String>, 256 event_set_root: Option<String>, 257 reducer_output_root: Option<String>, 258 warnings: Vec<String>, 259 ) -> RhiProofSmokeResponse { 260 RhiProofSmokeResponse { 261 ok: true, 262 protocol_version: PROTOCOL_VERSION.to_string(), 263 operation, 264 worker_name: WORKER_NAME.to_string(), 265 worker_version: env!("CARGO_PKG_VERSION").to_string(), 266 git_rev: option_env!("RADROOTS_GIT_REV") 267 .unwrap_or("unknown") 268 .to_string(), 269 sp1_version: SP1_VERSION.to_string(), 270 backend, 271 capabilities: capabilities(), 272 proof_generated: false, 273 public_values_hash, 274 sp1_program_hash, 275 sp1_verifying_key_hash, 276 event_set_root, 277 reducer_output_root, 278 elapsed_ms: started.elapsed().as_millis(), 279 warnings, 280 error: None, 281 } 282 } 283 284 fn canonical_hex_64(value: &str) -> Result<String, RhiProofSmokeError> { 285 let candidate = value.strip_prefix("0x").unwrap_or(value); 286 if candidate.len() == 64 && candidate.bytes().all(|byte| byte.is_ascii_hexdigit()) { 287 return Ok(candidate.to_ascii_lowercase()); 288 } 289 Err(RhiProofSmokeError::Deterministic( 290 "public value is not canonical 32-byte hex".to_string(), 291 )) 292 } 293 294 fn response_for_error( 295 operation: RhiProofSmokeOperation, 296 backend: RhiProofSmokeBackend, 297 started: Instant, 298 error: String, 299 ) -> RhiProofSmokeResponse { 300 let mut response = response_for_success( 301 operation, 302 backend, 303 started, 304 None, 305 None, 306 None, 307 None, 308 None, 309 Vec::new(), 310 ); 311 response.ok = false; 312 response.error = Some(error); 313 response 314 } 315 316 fn capabilities() -> Vec<String> { 317 let mut values = vec![ 318 "health".to_string(), 319 "proof_smoke".to_string(), 320 "deterministic_none".to_string(), 321 ]; 322 if cfg!(feature = "sp1_proving") { 323 values.push("local_execute".to_string()); 324 } 325 values 326 } 327 328 pub(crate) fn order_acceptance_tiny_witness() -> RadrootsSp1TradeOrderAcceptanceWitness { 329 RadrootsSp1TradeOrderAcceptanceWitness { 330 witness_version: RADROOTS_SP1_TRADE_WITNESS_VERSION, 331 proof_target: RADROOTS_SP1_TRADE_ORDER_ACCEPTANCE_PROOF_TARGET.to_string(), 332 listing_event_id: "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" 333 .to_string(), 334 request_event_id: "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb" 335 .to_string(), 336 decision_event_id: "cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc" 337 .to_string(), 338 event_evidence: order_acceptance_tiny_event_evidence(), 339 request: RadrootsSp1TradeOrderRequestWitness { 340 order_id: "order-1".to_string(), 341 listing_addr: 342 "30402:1111111111111111111111111111111111111111111111111111111111111111:listing-1" 343 .to_string(), 344 buyer_pubkey: "2222222222222222222222222222222222222222222222222222222222222222" 345 .to_string(), 346 seller_pubkey: "1111111111111111111111111111111111111111111111111111111111111111" 347 .to_string(), 348 items: vec![RadrootsSp1TradeOrderItemWitness { 349 bin_id: "bin-1".to_string(), 350 bin_count: 2, 351 }], 352 }, 353 decision: RadrootsSp1TradeOrderDecisionEventWitness { 354 order_id: "order-1".to_string(), 355 listing_addr: 356 "30402:1111111111111111111111111111111111111111111111111111111111111111:listing-1" 357 .to_string(), 358 buyer_pubkey: "2222222222222222222222222222222222222222222222222222222222222222" 359 .to_string(), 360 seller_pubkey: "1111111111111111111111111111111111111111111111111111111111111111" 361 .to_string(), 362 decision: RadrootsSp1TradeOrderDecisionWitness::Accepted { 363 inventory_commitments: vec![RadrootsSp1TradeInventoryCommitmentWitness { 364 bin_id: "bin-1".to_string(), 365 bin_count: 2, 366 }], 367 }, 368 }, 369 inventory_bins: vec![RadrootsSp1TradeInventoryBinWitness { 370 bin_id: "bin-1".to_string(), 371 listing_capacity: 5, 372 previous_reserved: 1, 373 }], 374 inventory_sequence: 7, 375 previous_state_root: None, 376 reducer_program_hash: RADROOTS_SP1_TRADE_REDUCER_PROGRAM_HASH.to_string(), 377 radroots_protocol_version: RADROOTS_SP1_TRADE_PROTOCOL_VERSION.to_string(), 378 sp1_program_hash: None, 379 sp1_verifying_key_hash: None, 380 } 381 } 382 383 fn order_acceptance_tiny_event_evidence() -> Vec<RadrootsSp1TradeCanonicalEventEvidence> { 384 vec![ 385 RadrootsSp1TradeCanonicalEventEvidence { 386 event_id: "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" 387 .to_string(), 388 signer_pubkey: "1111111111111111111111111111111111111111111111111111111111111111" 389 .to_string(), 390 kind: RADROOTS_SP1_TRADE_KIND_LISTING, 391 canonical_event_hash: 392 "0x1010101010101010101010101010101010101010101010101010101010101010".to_string(), 393 signature_hash: "0x1111111111111111111111111111111111111111111111111111111111111111" 394 .to_string(), 395 preverified_signature: true, 396 role: RadrootsSp1TradeEventEvidenceRole::Seller, 397 workflow_position: RadrootsSp1TradeEventWorkflowPosition::Listing, 398 content_hash: "0x1212121212121212121212121212121212121212121212121212121212121212" 399 .to_string(), 400 tags_hash: "0x1313131313131313131313131313131313131313131313131313131313131313" 401 .to_string(), 402 ordering_key: "001:listing".to_string(), 403 }, 404 RadrootsSp1TradeCanonicalEventEvidence { 405 event_id: "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb" 406 .to_string(), 407 signer_pubkey: "2222222222222222222222222222222222222222222222222222222222222222" 408 .to_string(), 409 kind: RADROOTS_SP1_TRADE_KIND_ORDER_REQUEST, 410 canonical_event_hash: 411 "0x2020202020202020202020202020202020202020202020202020202020202020".to_string(), 412 signature_hash: "0x2121212121212121212121212121212121212121212121212121212121212121" 413 .to_string(), 414 preverified_signature: true, 415 role: RadrootsSp1TradeEventEvidenceRole::Buyer, 416 workflow_position: RadrootsSp1TradeEventWorkflowPosition::OrderRequest, 417 content_hash: "0x2222222222222222222222222222222222222222222222222222222222222222" 418 .to_string(), 419 tags_hash: "0x2323232323232323232323232323232323232323232323232323232323232323" 420 .to_string(), 421 ordering_key: "002:order_request".to_string(), 422 }, 423 RadrootsSp1TradeCanonicalEventEvidence { 424 event_id: "cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc" 425 .to_string(), 426 signer_pubkey: "1111111111111111111111111111111111111111111111111111111111111111" 427 .to_string(), 428 kind: RADROOTS_SP1_TRADE_KIND_ORDER_DECISION, 429 canonical_event_hash: 430 "0x3030303030303030303030303030303030303030303030303030303030303030".to_string(), 431 signature_hash: "0x3131313131313131313131313131313131313131313131313131313131313131" 432 .to_string(), 433 preverified_signature: true, 434 role: RadrootsSp1TradeEventEvidenceRole::Seller, 435 workflow_position: RadrootsSp1TradeEventWorkflowPosition::OrderDecision, 436 content_hash: "0x3232323232323232323232323232323232323232323232323232323232323232" 437 .to_string(), 438 tags_hash: "0x3333333333333333333333333333333333333333333333333333333333333333" 439 .to_string(), 440 ordering_key: "003:order_decision".to_string(), 441 }, 442 ] 443 } 444 445 fn read_input(input: Option<&Path>) -> anyhow::Result<Vec<u8>> { 446 match input { 447 Some(path) => Ok(std::fs::read(path)?), 448 None => { 449 use std::io::Read; 450 let mut bytes = Vec::new(); 451 std::io::stdin().read_to_end(&mut bytes)?; 452 Ok(bytes) 453 } 454 } 455 } 456 457 fn write_output(output: Option<&Path>, bytes: &[u8]) -> anyhow::Result<()> { 458 match output { 459 Some(path) => { 460 std::fs::write(path, bytes)?; 461 Ok(()) 462 } 463 None => { 464 println!("{}", String::from_utf8_lossy(bytes)); 465 Ok(()) 466 } 467 } 468 } 469 470 #[cfg(test)] 471 mod tests { 472 use super::{ 473 PROTOCOL_VERSION, RhiProofSmokeBackend, RhiProofSmokeOperation, RhiProofSmokeRequest, 474 RhiProofSmokeResponse, handle_request_bytes, 475 }; 476 477 fn request(operation: RhiProofSmokeOperation, backend: RhiProofSmokeBackend) -> Vec<u8> { 478 serde_json::to_vec(&RhiProofSmokeRequest { 479 protocol_version: PROTOCOL_VERSION.to_string(), 480 operation, 481 backend, 482 fixture: Some("order_acceptance_tiny_v1".to_string()), 483 }) 484 .expect("request json") 485 } 486 487 #[tokio::test] 488 async fn health_returns_worker_capabilities() { 489 let bytes = serde_json::to_vec(&RhiProofSmokeRequest { 490 protocol_version: PROTOCOL_VERSION.to_string(), 491 operation: RhiProofSmokeOperation::Health, 492 backend: RhiProofSmokeBackend::DeterministicNone, 493 fixture: None, 494 }) 495 .expect("request json"); 496 let response: RhiProofSmokeResponse = handle_request_bytes(&bytes).await; 497 assert!(response.ok); 498 assert_eq!(response.worker_name, "rhi"); 499 assert!(response.capabilities.contains(&"health".to_string())); 500 assert!(!response.proof_generated); 501 } 502 503 #[tokio::test] 504 async fn deterministic_proof_smoke_returns_public_values() { 505 let response = handle_request_bytes(&request( 506 RhiProofSmokeOperation::ProofSmoke, 507 RhiProofSmokeBackend::DeterministicNone, 508 )) 509 .await; 510 assert!(response.ok); 511 assert_eq!(response.operation, RhiProofSmokeOperation::ProofSmoke); 512 assert!(response.public_values_hash.is_some()); 513 assert!(response.sp1_program_hash.is_none()); 514 assert!(response.sp1_verifying_key_hash.is_none()); 515 assert!(response.event_set_root.is_some()); 516 assert!(response.reducer_output_root.is_some()); 517 for value in [ 518 response.public_values_hash.as_deref(), 519 response.event_set_root.as_deref(), 520 response.reducer_output_root.as_deref(), 521 ] { 522 let value = value.expect("public value"); 523 assert_eq!(value.len(), 64); 524 assert!(!value.starts_with("0x")); 525 assert!(value.bytes().all(|byte| byte.is_ascii_hexdigit())); 526 } 527 assert!(!response.proof_generated); 528 } 529 530 #[tokio::test] 531 async fn proof_smoke_rejects_unknown_fixture() { 532 let response = handle_request_bytes( 533 &serde_json::to_vec(&RhiProofSmokeRequest { 534 protocol_version: PROTOCOL_VERSION.to_string(), 535 operation: RhiProofSmokeOperation::ProofSmoke, 536 backend: RhiProofSmokeBackend::DeterministicNone, 537 fixture: Some("other".to_string()), 538 }) 539 .expect("request json"), 540 ) 541 .await; 542 assert!(!response.ok); 543 assert_eq!( 544 response.error.as_deref(), 545 Some("proof_smoke requires fixture order_acceptance_tiny_v1") 546 ); 547 } 548 549 #[tokio::test] 550 async fn proof_smoke_rejects_full_proof_request_fields() { 551 let response = handle_request_bytes( 552 br#"{"protocol_version":"radroots.rhi.proof_smoke.v0","operation":"proof_smoke","backend":"deterministic_none","fixture":"order_acceptance_tiny_v1","proof_mode":"core"}"#, 553 ) 554 .await; 555 assert!(!response.ok); 556 assert!( 557 response 558 .error 559 .as_deref() 560 .is_some_and(|error| error.contains("unknown field")) 561 ); 562 } 563 564 #[cfg(not(feature = "sp1_proving"))] 565 #[tokio::test] 566 async fn local_execute_reports_unavailable_without_feature() { 567 let response = handle_request_bytes(&request( 568 RhiProofSmokeOperation::ProofSmoke, 569 RhiProofSmokeBackend::LocalExecute, 570 )) 571 .await; 572 assert!(!response.ok); 573 assert_eq!( 574 response.error.as_deref(), 575 Some("local_execute backend is unavailable in this build") 576 ); 577 } 578 579 #[cfg(feature = "sp1_proving")] 580 #[tokio::test] 581 async fn local_execute_returns_sp1_public_values_without_proof_generation() { 582 let deterministic = handle_request_bytes(&request( 583 RhiProofSmokeOperation::ProofSmoke, 584 RhiProofSmokeBackend::DeterministicNone, 585 )) 586 .await; 587 let response = handle_request_bytes(&request( 588 RhiProofSmokeOperation::ProofSmoke, 589 RhiProofSmokeBackend::LocalExecute, 590 )) 591 .await; 592 assert!(response.ok); 593 assert_eq!(response.operation, RhiProofSmokeOperation::ProofSmoke); 594 assert_eq!(response.backend, RhiProofSmokeBackend::LocalExecute); 595 assert!(response.capabilities.contains(&"local_execute".to_string())); 596 assert!(!response.proof_generated); 597 assert_ne!( 598 response.public_values_hash, 599 deterministic.public_values_hash 600 ); 601 assert!(response.sp1_program_hash.is_some()); 602 assert!(response.sp1_verifying_key_hash.is_some()); 603 for value in [ 604 response.sp1_program_hash.as_deref(), 605 response.sp1_verifying_key_hash.as_deref(), 606 ] { 607 let value = value.expect("SP1 identity"); 608 assert_eq!(value.len(), 64); 609 assert!(!value.starts_with("0x")); 610 assert!(value.bytes().all(|byte| byte.is_ascii_hexdigit())); 611 } 612 assert_eq!(response.event_set_root, deterministic.event_set_root); 613 assert_eq!( 614 response.reducer_output_root, 615 deterministic.reducer_output_root 616 ); 617 assert!(response.error.is_none()); 618 } 619 }