1use std::sync::Arc;
2
3mod init;
4
5pub use init::SP1LocalNodeBuilder;
6
7use either::Either;
8use mti::prelude::{MagicTypeIdExt, V7};
9use sp1_core_executor::{ExecutionReport, SP1Context};
10use sp1_core_machine::io::SP1Stdin;
11use sp1_hypercube::{SP1PcsProofOuter, SP1VerifyingKey, SP1WrapProof};
12use sp1_primitives::{io::SP1PublicValues, SP1OuterGlobalContext};
13use sp1_prover_types::{
14 network_base_types::ProofMode, Artifact, ArtifactClient, ArtifactType, InMemoryArtifactClient,
15 TaskStatus, TaskType,
16};
17pub use sp1_verifier::{ProofFromNetwork, SP1Proof};
18use tokio::task::JoinSet;
19use tracing::{instrument, Instrument};
20
21use crate::{
22 shapes::DEFAULT_ARITY,
23 worker::{
24 LocalWorkerClient, ProofId, RawTaskRequest, RequesterId, SP1NodeCore, TaskContext,
25 VkeyMapControllerInput, VkeyMapControllerOutput, WorkerClient,
26 },
27};
28
29pub(crate) struct SP1NodeInner {
30 artifact_client: InMemoryArtifactClient,
31 worker_client: LocalWorkerClient,
32 core: SP1NodeCore,
33 _tasks: JoinSet<()>,
34}
35
36pub struct SP1LocalNode {
37 inner: Arc<SP1NodeInner>,
38}
39
40impl Clone for SP1LocalNode {
41 fn clone(&self) -> Self {
42 Self { inner: self.inner.clone() }
43 }
44}
45
46impl SP1LocalNode {
47 pub fn core(&self) -> &SP1NodeCore {
48 &self.inner.core
49 }
50
51 #[instrument(name = "execute_program", skip_all)]
52 pub async fn execute(
53 &self,
54 elf: &[u8],
55 stdin: SP1Stdin,
56 context: SP1Context<'static>,
57 ) -> anyhow::Result<(SP1PublicValues, [u8; 32], ExecutionReport)> {
58 self.inner.core.execute(elf, stdin, context).await
59 }
60
61 pub async fn setup(&self, elf: &[u8]) -> anyhow::Result<SP1VerifyingKey> {
62 let elf_artifact = self.inner.artifact_client.create_artifact()?;
63 self.inner.artifact_client.upload_program(&elf_artifact, elf.to_vec()).await?;
64
65 let vk_artifact = self.inner.artifact_client.create_artifact()?;
67 let context = TaskContext {
68 proof_id: ProofId::new("core_proof"),
69 parent_id: None,
70 parent_context: None,
71 requester_id: RequesterId::new("local node"),
72 };
73 let setup_request = RawTaskRequest {
74 inputs: vec![elf_artifact.clone()],
75 outputs: vec![vk_artifact.clone()],
76 context: context.clone(),
77 };
78 tracing::trace!("submitting setup task");
79 let setup_id =
80 self.inner.worker_client.submit_task(TaskType::SetupVkey, setup_request).await?;
81 let subscriber =
83 self.inner.worker_client.subscriber(context.proof_id.clone()).await?.per_task();
84 let status =
85 subscriber.wait_task(setup_id).instrument(tracing::debug_span!("setup task")).await?;
86 if status != TaskStatus::Succeeded {
87 return Err(anyhow::anyhow!("setup task failed"));
88 }
89 tracing::trace!("setup task succeeded");
90 let vk = self.inner.artifact_client.download::<SP1VerifyingKey>(&vk_artifact).await?;
92
93 self.inner.artifact_client.try_delete(&elf_artifact, ArtifactType::Program).await?;
95 self.inner
96 .artifact_client
97 .try_delete(&vk_artifact, ArtifactType::UnspecifiedArtifactType)
98 .await?;
99
100 Ok(vk)
101 }
102
103 pub async fn prove(
104 &self,
105 elf: &[u8],
106 stdin: SP1Stdin,
107 context: SP1Context<'static>,
108 ) -> anyhow::Result<ProofFromNetwork> {
109 self.prove_with_mode(elf, stdin, context, ProofMode::Compressed).await
110 }
111
112 pub async fn build_vks(
113 &self,
114 range_or_limit: Option<Either<Vec<usize>, usize>>,
115 chunk_size: usize,
116 ) -> anyhow::Result<VkeyMapControllerOutput> {
117 let vk_controller_artifact = self.inner.artifact_client.create_artifact()?;
118 let input =
119 VkeyMapControllerInput { range_or_limit, chunk_size, reduce_batch_size: DEFAULT_ARITY };
120 self.inner.artifact_client.upload(&vk_controller_artifact, input).await?;
121
122 let output_artifact = self.inner.artifact_client.create_artifact()?;
123
124 let proof_id = ProofId::new("proof".create_type_id::<V7>().to_string());
125
126 let request = RawTaskRequest {
127 inputs: vec![vk_controller_artifact.clone()],
128 outputs: vec![output_artifact.clone()],
129 context: TaskContext {
130 proof_id: proof_id.clone(),
131 parent_id: None,
132 parent_context: None,
133 requester_id: RequesterId::new(format!("local-node-{}", std::process::id())),
134 },
135 };
136
137 let task_id =
138 self.inner.worker_client.submit_task(TaskType::UtilVkeyMapController, request).await?;
139 let subscriber = self.inner.worker_client.subscriber(proof_id).await?.per_task();
140 let status = subscriber.wait_task(task_id).await?;
141 if status != TaskStatus::Succeeded {
142 return Err(anyhow::anyhow!("controller task failed"));
143 }
144
145 self.inner
147 .artifact_client
148 .try_delete(&vk_controller_artifact, ArtifactType::Program)
149 .await?;
150
151 let output = self
153 .inner
154 .artifact_client
155 .download::<VkeyMapControllerOutput>(&output_artifact)
156 .await?;
157
158 self.inner
160 .artifact_client
161 .try_delete(&output_artifact, ArtifactType::UnspecifiedArtifactType)
162 .await?;
163
164 Ok(output)
165 }
166
167 #[instrument(name = "prove", skip_all, fields(mode = ?mode))]
168 pub async fn prove_with_mode(
169 &self,
170 elf: &[u8],
171 stdin: SP1Stdin,
172 context: SP1Context<'static>,
173 mode: ProofMode,
174 ) -> anyhow::Result<ProofFromNetwork> {
175 let elf_artifact = self.inner.artifact_client.create_artifact()?;
176 self.inner.artifact_client.upload_program(&elf_artifact, elf.to_vec()).await?;
177
178 let proof_nonce_artifact = self.inner.artifact_client.create_artifact()?;
179 self.inner
180 .artifact_client
181 .upload::<[u32; 4]>(&proof_nonce_artifact, context.proof_nonce)
182 .await?;
183
184 let stdin_artifact = self.inner.artifact_client.create_artifact()?;
185 self.inner
186 .artifact_client
187 .upload_with_type(&stdin_artifact, ArtifactType::Stdin, stdin)
188 .await?;
189
190 let mode_artifact = Artifact((mode as i32).to_string());
191
192 let output_artifact = self.inner.artifact_client.create_artifact()?;
194
195 let proof_id = ProofId::new("proof".create_type_id::<V7>().to_string());
196 let request = RawTaskRequest {
197 inputs: vec![
198 elf_artifact.clone(),
199 stdin_artifact.clone(),
200 mode_artifact.clone(),
201 proof_nonce_artifact.clone(),
202 ],
203 outputs: vec![output_artifact.clone()],
204 context: TaskContext {
205 proof_id: proof_id.clone(),
206 parent_id: None,
207 parent_context: None,
208 requester_id: RequesterId::new(format!("local-node-{}", std::process::id())),
209 },
210 };
211
212 let task_id = self.inner.worker_client.submit_task(TaskType::Controller, request).await?;
213 let subscriber = self.inner.worker_client.subscriber(proof_id).await?.per_task();
214 let status = subscriber.wait_task(task_id).await?;
215 if status != TaskStatus::Succeeded {
216 return Err(anyhow::anyhow!("controller task failed"));
217 }
218
219 self.inner.artifact_client.try_delete(&elf_artifact, ArtifactType::Program).await?;
221 self.inner.artifact_client.try_delete(&stdin_artifact, ArtifactType::Stdin).await?;
222
223 let proof =
225 self.inner.artifact_client.download::<ProofFromNetwork>(&output_artifact).await?;
226 self.inner
228 .artifact_client
229 .try_delete(&output_artifact, ArtifactType::UnspecifiedArtifactType)
230 .await?;
231
232 self.inner
233 .artifact_client
234 .try_delete(&proof_nonce_artifact, ArtifactType::UnspecifiedArtifactType)
235 .await?;
236
237 Ok(proof)
238 }
239
240 pub fn verify(&self, vk: &SP1VerifyingKey, proof: &SP1Proof) -> anyhow::Result<()> {
241 self.inner.core.verify(vk, proof)
242 }
243
244 #[cfg(test)]
245 #[allow(dead_code)]
246 pub(crate) fn wrap_vk(&self) -> &sp1_hypercube::MachineVerifyingKey<SP1OuterGlobalContext> {
247 self.inner.core.wrap_vk()
248 }
249
250 pub async fn shrink_wrap(
252 &self,
253 compressed_proof: &SP1Proof,
254 ) -> anyhow::Result<SP1WrapProof<SP1OuterGlobalContext, SP1PcsProofOuter>> {
255 let compressed_proof = match compressed_proof {
256 SP1Proof::Compressed(proof) => *proof.clone(),
257 _ => return Err(anyhow::anyhow!("given proof is not a compressed proof")),
258 };
259 let compressed_proof_artifact = self.inner.artifact_client.create_artifact()?;
261 self.inner.artifact_client.upload(&compressed_proof_artifact, compressed_proof).await?;
262
263 let output_artifact = self.inner.artifact_client.create_artifact()?;
265
266 let proof_id = ProofId::new("shrink wrap".create_type_id::<V7>().to_string());
268 let request = RawTaskRequest {
269 inputs: vec![compressed_proof_artifact.clone()],
270 outputs: vec![output_artifact.clone()],
271 context: TaskContext {
272 proof_id: proof_id.clone(),
273 parent_id: None,
274 parent_context: None,
275 requester_id: RequesterId::new(format!("local-node-{}", std::process::id())),
276 },
277 };
278
279 let task_id = self.inner.worker_client.submit_task(TaskType::ShrinkWrap, request).await?;
280 let subscriber = self.inner.worker_client.subscriber(proof_id).await?.per_task();
282 let status = subscriber.wait_task(task_id).await?;
283 if status != TaskStatus::Succeeded {
284 return Err(anyhow::anyhow!("shrink wrap task failed"));
285 }
286
287 let proof = self
289 .inner
290 .artifact_client
291 .download::<SP1WrapProof<SP1OuterGlobalContext, SP1PcsProofOuter>>(&output_artifact)
292 .await?;
293 tokio::try_join!(
295 self.inner
296 .artifact_client
297 .try_delete(&compressed_proof_artifact, ArtifactType::UnspecifiedArtifactType),
298 self.inner
299 .artifact_client
300 .try_delete(&output_artifact, ArtifactType::UnspecifiedArtifactType)
301 )?;
302
303 Ok(proof)
304 }
305}
306
307#[cfg(test)]
308mod tests {
309 use serial_test::serial;
310 use sp1_core_machine::utils::setup_logger;
311
312 use crate::CpuSP1ProverComponents;
313 use sp1_hypercube::HashableKey;
314
315 use crate::worker::{cpu_worker_builder, SP1LocalNodeBuilder, SP1WorkerBuilder};
316
317 use super::*;
318
319 async fn run_e2e_node_test(
320 builder: SP1WorkerBuilder<CpuSP1ProverComponents>,
321 ) -> anyhow::Result<()> {
322 let elf = test_artifacts::FIBONACCI_ELF;
323 let stdin = SP1Stdin::default();
324 let mode = ProofMode::Compressed;
325
326 let client =
327 SP1LocalNodeBuilder::from_worker_client_builder(builder).build().await.unwrap();
328
329 let proof_nonce = [0x6284, 0xC0DE, 0x4242, 0xCAFE];
330
331 let time = tokio::time::Instant::now();
332 let context = SP1Context { proof_nonce, ..Default::default() };
333
334 let (_, _, report) = client.execute(&elf, stdin.clone(), context.clone()).await.unwrap();
335
336 let execute_time = time.elapsed();
337 let cycles = report.total_instruction_count() as usize;
338 tracing::info!(
339 "execute time: {:?}, cycles: {}, gas: {:?}",
340 execute_time,
341 cycles,
342 report.gas()
343 );
344
345 let time = tokio::time::Instant::now();
346 let vk = client.setup(&elf).await.unwrap();
347 let setup_time = time.elapsed();
348 tracing::info!("setup time: {:?}", setup_time);
349
350 let time = tokio::time::Instant::now();
351
352 tracing::info!("proving with mode: {mode:?}");
353 let proof = client
354 .prove_with_mode(&elf, stdin.clone(), context.clone(), mode)
355 .await
356 .expect("proof failed");
357 let proof_time = time.elapsed();
358 tracing::info!("proof time: {:?}", proof_time);
359
360 tokio::task::spawn_blocking(move || client.verify(&vk, &proof.proof).unwrap())
362 .await
363 .unwrap();
364
365 Ok(())
366 }
367
368 #[tokio::test]
369 #[serial]
370 async fn test_e2e_node() -> anyhow::Result<()> {
371 setup_logger();
372 run_e2e_node_test(cpu_worker_builder()).await
373 }
374
375 #[tokio::test]
376 #[cfg(feature = "experimental")]
377 #[serial]
378 async fn test_e2e_node_experimental() -> anyhow::Result<()> {
379 setup_logger();
380 run_e2e_node_test(cpu_worker_builder().without_vk_verification()).await
381 }
382
383 #[tokio::test]
384 #[serial]
385 #[ignore = "only run to write the vk root and num keys to a file"]
386 async fn make_verifier_vks() -> anyhow::Result<()> {
387 setup_logger();
388
389 let client = SP1LocalNodeBuilder::from_worker_client_builder(cpu_worker_builder())
390 .build()
391 .await
392 .unwrap();
393
394 let recursion_vks = client.core().recursion_vks();
395
396 let mut file = std::fs::File::create("../verifier/vk-artifacts/verifier_vks.bin")?;
397
398 bincode::serialize_into(&mut file, &recursion_vks)?;
399 Ok(())
400 }
401
402 #[tokio::test]
403 #[serial]
404 async fn test_e2e_groth16_node() -> anyhow::Result<()> {
405 setup_logger();
406
407 let elf = test_artifacts::FIBONACCI_ELF;
408 let stdin = SP1Stdin::default();
409 let mode = ProofMode::Groth16;
410
411 let client = SP1LocalNodeBuilder::from_worker_client_builder(cpu_worker_builder())
412 .build()
413 .await
414 .unwrap();
415
416 let time = tokio::time::Instant::now();
417 let context = SP1Context::default();
418 let (_, _, report) = client.execute(&elf, stdin.clone(), context.clone()).await.unwrap();
419 let execute_time = time.elapsed();
420 let cycles = report.total_instruction_count() as usize;
421 tracing::info!(
422 "execute time: {:?}, cycles: {}, gas: {:?}",
423 execute_time,
424 cycles,
425 report.gas()
426 );
427
428 let time = tokio::time::Instant::now();
429 let vk = client.setup(&elf).await.unwrap();
430 let setup_time = time.elapsed();
431 tracing::info!("setup time: {:?}", setup_time);
432
433 let time = tokio::time::Instant::now();
434
435 tracing::info!("proving with mode: {mode:?}");
436 let proof = client.prove_with_mode(&elf, stdin, context, mode).await.unwrap();
437 let proof_time = time.elapsed();
438 tracing::info!("proof time: {:?}", proof_time);
439
440 tokio::task::spawn_blocking(move || client.verify(&vk, &proof.proof).unwrap())
442 .await
443 .unwrap();
444
445 Ok(())
446 }
447
448 #[tokio::test]
455 #[serial]
456 #[cfg(feature = "experimental")]
457 #[ignore = "sanity check test; see doc-comment for proper usage"]
458 async fn test_e2e_groth16_node_modified_vk_root() -> anyhow::Result<()> {
459 use slop_algebra::AbstractField;
460 use sp1_primitives::SP1Field;
461 use sp1_recursion_executor::DIGEST_SIZE;
462 use std::collections::BTreeMap;
463 use std::io::Write;
464
465 setup_logger();
466
467 let original_map: BTreeMap<[SP1Field; DIGEST_SIZE], usize> =
469 bincode::deserialize(include_bytes!("../../../vk_map.bin"))
470 .expect("failed to deserialize vk_map.bin");
471 tracing::info!("original vk_map has {} entries", original_map.len());
472
473 let mut modified_map = original_map.clone();
474 let first_key = *modified_map.keys().next().unwrap();
475 let first_val = modified_map.remove(&first_key).unwrap();
476 let mut new_key = first_key;
477 new_key[0] += SP1Field::one();
478 modified_map.insert(new_key, first_val);
479 assert_eq!(modified_map.len(), original_map.len(), "map size should not change");
480
481 let temp_dir = tempfile::tempdir()?;
483 let vk_map_path = temp_dir.path().join("modified_vk_map.bin");
484 {
485 let mut file = std::fs::File::create(&vk_map_path)?;
486 bincode::serialize_into(&mut file, &modified_map)?;
487 file.flush()?;
488 }
489 tracing::info!("wrote modified vk_map to {:?}", vk_map_path);
490
491 let builder =
493 cpu_worker_builder().with_vk_map_path(vk_map_path.to_str().unwrap().to_string());
494
495 let elf = test_artifacts::FIBONACCI_ELF;
496 let stdin = SP1Stdin::default();
497 let mode = ProofMode::Groth16;
498
499 let client =
500 SP1LocalNodeBuilder::from_worker_client_builder(builder).build().await.unwrap();
501
502 let time = tokio::time::Instant::now();
506 let vk = client.setup(&elf).await.unwrap();
507 tracing::info!("setup time: {:?}", time.elapsed());
508
509 let time = tokio::time::Instant::now();
510 let context = SP1Context::default();
511 tracing::info!("proving with mode: {mode:?} (modified vk_root)");
512 let proof = client.prove_with_mode(&elf, stdin, context, mode).await.unwrap();
513 tracing::info!("proof time: {:?}", time.elapsed());
514
515 tokio::task::spawn_blocking(move || {
519 client.verify(&vk, &proof.proof).unwrap();
520 tracing::info!("verification with modified vk_root PASSED");
521 })
522 .await
523 .unwrap();
524
525 Ok(())
526 }
527
528 #[tokio::test]
529 #[serial]
530 async fn test_node_deferred_compress() -> anyhow::Result<()> {
531 setup_logger();
532
533 let client = SP1LocalNodeBuilder::from_worker_client_builder(cpu_worker_builder())
534 .build()
535 .await
536 .unwrap();
537
538 let keccak_elf = test_artifacts::KECCAK256_ELF;
540
541 let verify_elf = test_artifacts::VERIFY_PROOF_ELF;
543
544 tracing::info!("setup keccak elf");
545 let keccak_vk = client.setup(&keccak_elf).await?;
546
547 tracing::info!("setup verify elf");
548 let verify_vk = client.setup(&verify_elf).await?;
549
550 tracing::info!("prove subproof 1");
551 let mut stdin = SP1Stdin::new();
552 stdin.write(&1usize);
553 stdin.write(&vec![0u8, 0, 0]);
554 let context = SP1Context::default();
555 let deferred_proof_1 = client
556 .prove_with_mode(&keccak_elf, stdin, context.clone(), ProofMode::Compressed)
557 .await?;
558 let pv_1 = deferred_proof_1.public_values.as_slice().to_vec().clone();
559
560 tracing::info!("prove subproof 2");
562 let mut stdin = SP1Stdin::new();
563 stdin.write(&3usize);
564 stdin.write(&vec![0u8, 1, 2]);
565 stdin.write(&vec![2, 3, 4]);
566 stdin.write(&vec![5, 6, 7]);
567 let deferred_proof_2 = client
568 .prove_with_mode(&keccak_elf, stdin, context.clone(), ProofMode::Compressed)
569 .await?;
570 let pv_2 = deferred_proof_2.public_values.as_slice().to_vec().clone();
571
572 let deferred_reduce_1 = match deferred_proof_1.proof {
573 SP1Proof::Compressed(proof) => *proof,
574 _ => return Err(anyhow::anyhow!("deferred proof 1 is not a compressed proof")),
575 };
576 let deferred_reduce_2 = match deferred_proof_2.proof {
577 SP1Proof::Compressed(proof) => *proof,
578 _ => return Err(anyhow::anyhow!("deferred proof 2 is not a compressed proof")),
579 };
580
581 let mut invalid_proof = deferred_reduce_1.clone();
583 invalid_proof.proof.public_values.clear();
584 let mut execute_stdin = SP1Stdin::new();
585 let vkey_digest = keccak_vk.hash_u32();
586 execute_stdin.write(&vkey_digest);
587 execute_stdin.write(&vec![pv_1.clone(), pv_2.clone(), pv_2.clone()]);
588 execute_stdin.write_proof(invalid_proof, keccak_vk.vk.clone());
589 execute_stdin.write_proof(deferred_reduce_2.clone(), keccak_vk.vk.clone());
590 execute_stdin.write_proof(deferred_reduce_2.clone(), keccak_vk.vk.clone());
591
592 let execute_result = client.execute(&verify_elf, execute_stdin, context.clone()).await;
593 let err = execute_result.expect_err("expected deferred proof verification to fail");
594 assert!(
595 err.to_string().contains("deferred proof 0 failed verification"),
596 "unexpected error: {err}"
597 );
598
599 let mut execute_stdin = SP1Stdin::new();
601 let vkey_digest = keccak_vk.hash_u32();
602 execute_stdin.write(&vkey_digest);
603 execute_stdin.write(&vec![pv_1.clone(), pv_2.clone(), pv_2.clone()]);
604 execute_stdin.write_proof(deferred_reduce_1.clone(), keccak_vk.vk.clone());
605 execute_stdin.write_proof(deferred_reduce_2.clone(), keccak_vk.vk.clone());
606 execute_stdin.write_proof(deferred_reduce_2.clone(), keccak_vk.vk.clone());
607
608 let (_execute_pv, _execute_digest, execute_report) =
609 client.execute(&verify_elf, execute_stdin, context.clone()).await?;
610 assert_eq!(execute_report.exit_code, 0);
611
612 let mut stdin = SP1Stdin::new();
614 let vkey_digest = keccak_vk.hash_u32();
615 stdin.write(&vkey_digest);
616 stdin.write(&vec![pv_1.clone(), pv_2.clone(), pv_2.clone()]);
617 stdin.write_proof(deferred_reduce_1.clone(), keccak_vk.vk.clone());
618 stdin.write_proof(deferred_reduce_2.clone(), keccak_vk.vk.clone());
619 stdin.write_proof(deferred_reduce_2.clone(), keccak_vk.vk.clone());
620
621 tracing::info!("proving verify program (core)");
622 let verify_proof =
623 client.prove_with_mode(&verify_elf, stdin, context, ProofMode::Compressed).await?;
624
625 tracing::info!("verifying verify proof");
626 tokio::task::spawn_blocking(move || {
627 client.verify(&verify_vk, &verify_proof.proof).unwrap()
628 })
629 .await
630 .unwrap();
631
632 Ok(())
633 }
634}