execute_verifier/
execute-verifier.rs

1//! OpenVM verifier execution
2//!
3//! First, generate the fixtures by running:
4//! ```bash
5//! cargo r -r --bin generate-fixtures --features generate-fixtures
6//! ```
7//!
8//! To profile this binary, build it with the profiling profile:
9//! ```bash
10//! cargo b --profile profiling --bin execute-verifier
11//! ```
12//!
13//! Then run it with samply for profiling:
14//! ```bash
15//! samply record --rate 10000 target/profiling/execute-verifier --mode preflight --verifier internal kitchen-sink
16//! ```
17
18use std::fs;
19
20use clap::{arg, Parser, ValueEnum};
21use eyre::Result;
22use openvm_benchmarks_utils::get_fixtures_dir;
23use openvm_circuit::arch::{
24    instructions::exe::VmExe, ContinuationVmProof, Streams, VirtualMachine,
25};
26#[cfg(feature = "evm-prove")]
27use openvm_continuations::verifier::root::types::RootVmVerifierInput;
28use openvm_continuations::{
29    verifier::{internal::types::InternalVmVerifierInput, leaf::types::LeafVmVerifierInput},
30    SC,
31};
32use openvm_native_circuit::{NativeCpuBuilder, NATIVE_MAX_TRACE_HEIGHTS};
33use openvm_native_recursion::hints::Hintable;
34use openvm_sdk::{
35    commit::VmCommittedExe,
36    config::{AggregationConfig, DEFAULT_NUM_CHILDREN_INTERNAL, DEFAULT_NUM_CHILDREN_LEAF},
37};
38use openvm_stark_sdk::{
39    config::baby_bear_poseidon2::BabyBearPoseidon2Engine,
40    engine::{StarkEngine, StarkFriEngine},
41    openvm_stark_backend::{
42        config::StarkGenericConfig, proof::Proof, prover::hal::DeviceDataTransporter,
43    },
44    p3_baby_bear::BabyBear,
45};
46use tracing_subscriber::{fmt, EnvFilter};
47
48#[derive(Clone, Debug, ValueEnum)]
49enum ExecutionMode {
50    Pure,
51    Metered,
52    Preflight,
53}
54
55#[derive(Clone, Debug, ValueEnum)]
56enum VerifierType {
57    Leaf,
58    Internal,
59    #[cfg(feature = "evm-prove")]
60    Root,
61}
62
63#[derive(Clone, Debug, ValueEnum)]
64enum ProofType {
65    Leaf,
66    Internal,
67}
68
69#[derive(Parser)]
70#[command(author, version, about = "OpenVM verifier execution")]
71struct Cli {
72    /// Program name to use for fixtures
73    #[arg(value_name = "PROGRAM", default_value = "kitchen-sink")]
74    program: String,
75
76    #[arg(short, long, value_enum, default_value = "leaf")]
77    verifier: VerifierType,
78
79    #[arg(short, long, value_enum, default_value = "preflight")]
80    mode: ExecutionMode,
81
82    #[arg(long, help = "Verifier index (for leaf and internal verifiers)")]
83    index: Option<usize>,
84
85    #[arg(short, long)]
86    verbose: bool,
87}
88
89fn load_proof(
90    fixtures_dir: &std::path::Path,
91    program_name: &str,
92    proof_type: ProofType,
93    index: usize,
94) -> Result<Proof<SC>> {
95    let proof_filename = match proof_type {
96        ProofType::Leaf => format!("{}.leaf.{}.proof", program_name, index),
97        ProofType::Internal => format!("{}.internal.{}.proof", program_name, index),
98    };
99
100    let proof_bytes = fs::read(fixtures_dir.join(proof_filename))
101        .unwrap_or_else(|_| panic!("No {:?} proof available at index {}", proof_type, index));
102    let proof: Proof<SC> = bitcode::deserialize(&proof_bytes).unwrap();
103    Ok(proof)
104}
105
106/// Determines which proofs an internal verifier at given index should aggregate
107/// Returns (proof_type, indices) where indices are the proof indices to load
108fn get_internal_verifier_proof_indices(
109    internal_verifier_index: usize,
110    leaf_proof_count: usize,
111    num_children: usize,
112) -> (ProofType, Vec<usize>) {
113    // Calculate how internal proofs are generated in layers
114    let mut current_layer_proofs = leaf_proof_count;
115    let mut current_layer_start_idx = 0;
116    let mut is_leaf_layer = true;
117    let mut internal_node_idx = 0;
118
119    // First, traverse through regular aggregation layers
120    loop {
121        // Calculate number of internal proofs in current layer
122        let num_internal_proofs_in_layer = current_layer_proofs.div_ceil(num_children);
123
124        if internal_node_idx + num_internal_proofs_in_layer > internal_verifier_index {
125            // Found the layer containing our internal verifier
126            let layer_local_idx = internal_verifier_index - internal_node_idx;
127            let start_proof_idx = layer_local_idx * num_children;
128            let end_proof_idx = ((layer_local_idx + 1) * num_children).min(current_layer_proofs);
129
130            let proof_indices: Vec<usize> = (start_proof_idx..end_proof_idx)
131                .map(|i| {
132                    if is_leaf_layer {
133                        i
134                    } else {
135                        current_layer_start_idx + i
136                    }
137                })
138                .collect();
139
140            let proof_type = if is_leaf_layer {
141                ProofType::Leaf
142            } else {
143                ProofType::Internal
144            };
145
146            return (proof_type, proof_indices);
147        }
148
149        // Move to next layer
150        internal_node_idx += num_internal_proofs_in_layer;
151        if !is_leaf_layer {
152            current_layer_start_idx += current_layer_proofs;
153        }
154        current_layer_proofs = num_internal_proofs_in_layer;
155        is_leaf_layer = false;
156
157        // If we're down to 1 proof, we've reached the final internal proof
158        if current_layer_proofs == 1 {
159            break;
160        }
161    }
162
163    // If we get here, the index might be for a wrapper internal proof
164    // Wrapper proofs aggregate exactly one proof (the previous internal proof)
165    let last_regular_internal_idx = internal_node_idx;
166    if internal_verifier_index >= last_regular_internal_idx {
167        // This is a wrapper proof - it aggregates exactly one internal proof
168        // The wrapper at index `last_regular_internal_idx` wraps the final internal proof (at index
169        // last_regular_internal_idx - 1) The wrapper at index `last_regular_internal_idx +
170        // 1` wraps the wrapper at index last_regular_internal_idx
171        let wrapper_layer = internal_verifier_index - last_regular_internal_idx;
172        let proof_to_wrap_idx = if wrapper_layer == 0 {
173            last_regular_internal_idx - 1 // Wrap the final internal proof
174        } else {
175            internal_verifier_index - 1 // Wrap the previous wrapper
176        };
177
178        return (ProofType::Internal, vec![proof_to_wrap_idx]);
179    }
180
181    panic!(
182        "Invalid internal verifier index {}",
183        internal_verifier_index
184    );
185}
186
187fn main() -> Result<()> {
188    let cli = Cli::parse();
189
190    // Set up logging
191    let filter = if cli.verbose {
192        EnvFilter::from_default_env()
193    } else {
194        EnvFilter::new("info")
195    };
196    fmt::fmt().with_env_filter(filter).init();
197
198    let fixtures_dir = get_fixtures_dir();
199    let app_proof_bytes =
200        fs::read(fixtures_dir.join(format!("{}.app.proof", cli.program))).unwrap();
201    let app_proof: ContinuationVmProof<SC> = bitcode::deserialize(&app_proof_bytes).unwrap();
202
203    match cli.verifier {
204        VerifierType::Leaf => {
205            let leaf_exe_bytes =
206                fs::read(fixtures_dir.join(format!("{}.leaf.exe", cli.program))).unwrap();
207            let leaf_exe: VmExe<BabyBear> = bitcode::deserialize(&leaf_exe_bytes).unwrap();
208
209            let leaf_pk_bytes =
210                fs::read(fixtures_dir.join(format!("{}.leaf.pk", cli.program))).unwrap();
211            let leaf_pk = bitcode::deserialize(&leaf_pk_bytes).unwrap();
212
213            let leaf_inputs = LeafVmVerifierInput::chunk_continuation_vm_proof(
214                &app_proof,
215                DEFAULT_NUM_CHILDREN_LEAF,
216            );
217            let index = cli.index.unwrap_or(0);
218            let leaf_input = leaf_inputs
219                .get(index)
220                .unwrap_or_else(|| panic!("No leaf input available at index {}", index));
221
222            let agg_config = AggregationConfig::default();
223            let config = agg_config.leaf_vm_config();
224            let engine = BabyBearPoseidon2Engine::new(agg_config.leaf_fri_params);
225            let d_pk = engine.device().transport_pk_to_device(&leaf_pk);
226            let vm = VirtualMachine::new(engine, NativeCpuBuilder, config, d_pk)?;
227            let input_stream = leaf_input.write_to_stream();
228
229            execute_verifier(cli.mode, vm, &leaf_exe, input_stream)?;
230        }
231        VerifierType::Internal => {
232            let internal_exe_bytes =
233                fs::read(fixtures_dir.join(format!("{}.internal.exe", cli.program))).unwrap();
234            let internal_exe: VmExe<BabyBear> = bitcode::deserialize(&internal_exe_bytes).unwrap();
235
236            let internal_pk_bytes =
237                fs::read(fixtures_dir.join(format!("{}.internal.pk", cli.program))).unwrap();
238            let internal_pk = bitcode::deserialize(&internal_pk_bytes).unwrap();
239
240            let index = cli.index.unwrap_or(0);
241
242            // Count available leaf proofs
243            let leaf_proof_count = {
244                let mut count = 0;
245                while fixtures_dir
246                    .join(format!("{}.leaf.{}.proof", cli.program, count))
247                    .exists()
248                {
249                    count += 1;
250                }
251                count
252            };
253
254            // Determine which proofs this internal verifier should aggregate
255            let (proof_type, proof_indices) = get_internal_verifier_proof_indices(
256                index,
257                leaf_proof_count,
258                DEFAULT_NUM_CHILDREN_INTERNAL,
259            );
260
261            // Load the determined proofs
262            let proofs: Vec<_> = proof_indices
263                .into_iter()
264                .map(|idx| load_proof(&fixtures_dir, &cli.program, proof_type.clone(), idx))
265                .collect::<Result<Vec<_>, _>>()?;
266
267            tracing::info!(
268                "Internal verifier {} will aggregate {} {:?} proofs",
269                index,
270                proofs.len(),
271                proof_type
272            );
273
274            let agg_config = AggregationConfig::default();
275            let config = agg_config.internal_vm_config();
276            let engine = BabyBearPoseidon2Engine::new(agg_config.internal_fri_params);
277
278            let internal_committed_exe =
279                VmCommittedExe::<SC>::commit(internal_exe, engine.config().pcs());
280            let internal_inputs = InternalVmVerifierInput::chunk_leaf_or_internal_proofs(
281                internal_committed_exe.get_program_commit().into(),
282                &proofs,
283                DEFAULT_NUM_CHILDREN_INTERNAL,
284            );
285            let d_pk = engine.device().transport_pk_to_device(&internal_pk);
286            let vm = VirtualMachine::new(engine, NativeCpuBuilder, config, d_pk)?;
287            let input_stream = internal_inputs.first().unwrap().write();
288
289            execute_verifier(cli.mode, vm, &internal_committed_exe.exe, input_stream)?;
290        }
291        #[cfg(feature = "evm-prove")]
292        VerifierType::Root => {
293            let root_exe_bytes =
294                fs::read(fixtures_dir.join(format!("{}.root.exe", cli.program))).unwrap();
295            let root_exe: VmExe<BabyBear> = bitcode::deserialize(&root_exe_bytes).unwrap();
296
297            let root_pk_bytes =
298                fs::read(fixtures_dir.join(format!("{}.root.pk", cli.program))).unwrap();
299            let root_pk = bitcode::deserialize(&root_pk_bytes).unwrap();
300
301            // Load root verifier input
302            let root_input_bytes =
303                fs::read(fixtures_dir.join(format!("{}.root.input", cli.program))).unwrap();
304            let root_input: RootVmVerifierInput<SC> =
305                bitcode::deserialize(&root_input_bytes).unwrap();
306
307            let agg_config = AggregationConfig::default();
308            let config = agg_config.root_verifier_vm_config();
309            let engine = BabyBearPoseidon2Engine::new(agg_config.root_fri_params);
310            let d_pk = engine.device().transport_pk_to_device(&root_pk);
311            let vm = VirtualMachine::new(engine, NativeCpuBuilder, config, d_pk)?;
312            let input_stream = root_input.write();
313
314            execute_verifier(cli.mode, vm, &root_exe, input_stream)?;
315        }
316    }
317
318    Ok(())
319}
320
321fn execute_verifier(
322    mode: ExecutionMode,
323    vm: VirtualMachine<BabyBearPoseidon2Engine, NativeCpuBuilder>,
324    exe: &VmExe<BabyBear>,
325    input_stream: impl Into<Streams<BabyBear>>,
326) -> Result<()> {
327    match mode {
328        ExecutionMode::Pure => {
329            tracing::info!("Running pure execute...");
330            let interpreter = vm.executor().instance(exe)?;
331            interpreter.execute(input_stream, None)?;
332        }
333        ExecutionMode::Metered => {
334            tracing::info!("Running metered execute...");
335            let ctx = vm.build_metered_ctx();
336            let interpreter = vm.metered_interpreter(exe)?;
337            interpreter.execute_metered(input_stream, ctx)?;
338        }
339        ExecutionMode::Preflight => {
340            tracing::info!("Running preflight execute...");
341            let state = vm.create_initial_state(exe, input_stream);
342            let mut interpreter = vm.preflight_interpreter(exe)?;
343            vm.execute_preflight(&mut interpreter, state, None, NATIVE_MAX_TRACE_HEIGHTS)?;
344        }
345    }
346    Ok(())
347}