Skip to content

Commit f4575b4

Browse files
committed
feat: add binary to run zkVM prover for the mock chain
1 parent fc625d4 commit f4575b4

4 files changed

Lines changed: 141 additions & 11 deletions

File tree

‎Cargo.lock‎

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎crates/prover/sp1/Cargo.toml‎

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -8,19 +8,20 @@ version = "0.1.0"
88
edition = "2024"
99
publish = false
1010

11+
[[bin]]
12+
name = "prove"
13+
path = "src/bin/prove.rs"
14+
1115
[dependencies]
1216
sp1-sdk = "6.3.1"
1317
ethlambda-prover-core = { path = "../core" }
1418
ethlambda-types = { path = "../../common/types" }
1519
# Serializes the whole SP1ProofWithPublicValues into `Proof` so verify can
1620
# recover the committed public values.
1721
bincode = "1.3"
22+
# `prove` binary: CLI flags and the async runtime the prover client needs.
23+
clap = { version = "4", features = ["derive"] }
24+
tokio = { version = "1", features = ["macros", "rt-multi-thread"] }
1825

1926
[build-dependencies]
2027
sp1-build = "6.3.1"
21-
22-
[dev-dependencies]
23-
# Build valid `(state, block)` inputs for tests and cross-check the guest's
24-
# committed roots against a host-side run of the same transition.
25-
ethlambda-state-transition = { path = "../../blockchain/state_transition" }
26-
tokio = { version = "1", features = ["macros", "rt-multi-thread"] }

‎crates/prover/sp1/src/bin/prove.rs‎

Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
//! Mock-chain proving driver for the SP1 state-transition backend.
2+
//!
3+
//! Generates a mock chain and, depending on `--mode`, runs the guest over each
4+
//! transition either via `execute` (no proof) or via `prove` + `verify`,
5+
//! Each block carries attestations that advance the justification/finalization,
6+
//! so the runs exercise the full state transition.
7+
8+
use clap::{Parser, ValueEnum};
9+
use ethlambda_prover_core::{
10+
StfProver,
11+
mock_chain::{MockTransition, gen_mock_chain},
12+
};
13+
use ethlambda_prover_sp1::Sp1Prover;
14+
use ethlambda_types::ShortRoot;
15+
16+
/// Run STF transitions over a mock chain.
17+
#[derive(Parser)]
18+
#[command(name = "prove")]
19+
struct Args {
20+
/// Number of blocks (transitions) in the mock chain.
21+
#[arg(short, long, default_value_t = 4)]
22+
blocks: u64,
23+
/// Number of validators in the genesis set.
24+
#[arg(short = 'n', long, default_value_t = 4)]
25+
validators: u64,
26+
/// Which guest path to run over the chain.
27+
#[arg(long, value_enum, default_value_t = Mode::Prove)]
28+
mode: Mode,
29+
}
30+
31+
#[derive(Clone, Copy, ValueEnum)]
32+
enum Mode {
33+
/// Run the guest via `execute` only (no proof generated).
34+
Execute,
35+
/// Generate and verify a proof for each transition.
36+
Prove,
37+
}
38+
39+
#[tokio::main]
40+
async fn main() -> Result<(), Box<dyn std::error::Error>> {
41+
let args = Args::parse();
42+
43+
println!(
44+
"Generating mock chain: {} blocks, {} validators",
45+
args.blocks, args.validators
46+
);
47+
let chain = gen_mock_chain(args.blocks, args.validators);
48+
49+
println!("Setting up the SP1 prover");
50+
let prover = Sp1Prover::new().await;
51+
52+
match args.mode {
53+
Mode::Execute => run_execute(&prover, &chain).await,
54+
Mode::Prove => run_prove(&prover, &chain).await,
55+
}
56+
}
57+
58+
/// Execute path: run the guest without proving, per transition.
59+
async fn run_execute(
60+
prover: &Sp1Prover,
61+
chain: &[MockTransition],
62+
) -> Result<(), Box<dyn std::error::Error>> {
63+
let mut prev_post = None;
64+
for (i, t) in chain.iter().enumerate() {
65+
print_transition_header(i + 1, chain.len(), t);
66+
check_chaining(prev_post, t, i + 1);
67+
68+
print!("Execution started");
69+
let ev = prover.execute(&t.input).await?;
70+
assert_eq!(ev.pre_state_root, t.pre_state_root);
71+
assert_eq!(ev.block_root, t.block_root);
72+
assert_eq!(ev.post_state_root, t.post_state_root);
73+
println!("Execution completed");
74+
75+
prev_post = Some(t.post_state_root);
76+
}
77+
println!("\nExecuted {} transitions over the mock chain", chain.len());
78+
Ok(())
79+
}
80+
81+
/// Proving path: generate and verify a proof, per transition.
82+
async fn run_prove(
83+
prover: &Sp1Prover,
84+
chain: &[MockTransition],
85+
) -> Result<(), Box<dyn std::error::Error>> {
86+
let mut prev_post = None;
87+
for (i, t) in chain.iter().enumerate() {
88+
print_transition_header(i + 1, chain.len(), t);
89+
check_chaining(prev_post, t, i + 1);
90+
91+
92+
let proof = prover.prove(&t.input).await?;
93+
println!("Generated Proof ({} bytes)", proof.as_bytes().len());
94+
95+
let pv = prover.verify(&proof).await?;
96+
assert_eq!(pv.pre_state_root, t.pre_state_root);
97+
assert_eq!(pv.block_root, t.block_root);
98+
assert_eq!(pv.post_state_root, t.post_state_root);
99+
println!("Proof verified and the post_state_root is {}", ShortRoot(&pv.post_state_root.0));
100+
101+
prev_post = Some(pv.post_state_root);
102+
}
103+
println!(
104+
"\nProved + verified {} transitions over the mock chain",
105+
chain.len()
106+
);
107+
Ok(())
108+
}
109+
110+
fn print_transition_header(n: usize, total: usize, t: &MockTransition) {
111+
println!("\n-- transition {n}/{total} --");
112+
println!(" pre_state_root : {}", ShortRoot(&t.pre_state_root.0));
113+
println!(" block_root : {}", ShortRoot(&t.block_root.0));
114+
println!(
115+
"justified slot ={} finalized slot ={}",
116+
t.justified_slot, t.finalized_slot
117+
);
118+
}
119+
120+
/// The previous transition's post-state root must equal this one's pre-state root.
121+
fn check_chaining(
122+
prev_post: Option<ethlambda_types::primitives::H256>,
123+
t: &MockTransition,
124+
n: usize,
125+
) {
126+
if let Some(prev) = prev_post {
127+
assert_eq!(prev, t.pre_state_root, "chain broken before transition {n}");
128+
}
129+
}

‎crates/prover/sp1/src/lib.rs‎

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ const CYCLE_LIMIT: u64 = 10_000_000;
1111
///
1212
/// The proving/verifying keys are derived once via [`Sp1Prover::new`] because
1313
/// `setup` is expensive and must not run per proof.
14-
/// [TODO!]: check if using Lazylock might be better
14+
/// [TODO!]: check if using Lazylock might be better
1515
pub struct Sp1Prover {
1616
client: MockProver,
1717
pk: SP1ProvingKey,
@@ -20,7 +20,7 @@ pub struct Sp1Prover {
2020

2121
impl Sp1Prover {
2222
/// Build the prover once, caching the proving/verifying keys and
23-
/// currently uses the MockProver.
23+
/// currently uses the MockProver.
2424
pub async fn new() -> Self {
2525
let client = MockProver::new().await;
2626
// let client = ProverClient::builder().cpu().await;
@@ -37,7 +37,6 @@ impl Sp1Prover {
3737

3838
impl StfProver for Sp1Prover {
3939
async fn prove(&self, input: &StfInput) -> Result<Proof, ProverError> {
40-
4140
let mut stdin = SP1Stdin::new();
4241
stdin.write(input);
4342

@@ -52,8 +51,8 @@ impl StfProver for Sp1Prover {
5251

5352
// Store the whole proof (including public values) so `verify` can
5453
// recover the committed `StfPublicValues`.
55-
let bytes =
56-
bincode::serialize(&proof).map_err(|err| ProverError::Serialization(err.to_string()))?;
54+
let bytes = bincode::serialize(&proof)
55+
.map_err(|err| ProverError::Serialization(err.to_string()))?;
5756
Ok(Proof(bytes))
5857
}
5958

0 commit comments

Comments
 (0)