risc0_zkvm/host/server/
session.rs

1// Copyright 2025 RISC Zero, Inc.
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15//! This module defines [Session] and [Segment] which provides a way to share
16//! execution traces between the execution phase and the proving phase.
17
18use std::{collections::BTreeSet, fs, path::PathBuf};
19
20use anyhow::{ensure, Result};
21use enum_map::EnumMap;
22use risc0_binfmt::SystemState;
23use risc0_circuit_rv32im::execute::EcallMetric;
24use serde::{Deserialize, Serialize};
25
26use crate::{
27    host::{
28        client::env::{ProveKeccakRequest, ProveZkrRequest, SegmentPath},
29        prove_info::SessionStats,
30    },
31    sha::Digest,
32    Assumption, AssumptionReceipt, Assumptions, ExitCode, Journal, MaybePruned, Output,
33    ReceiptClaim,
34};
35
36use super::exec::syscall::{SyscallKind, SyscallMetric};
37
38#[derive(Clone, Default, Serialize, Deserialize, Debug)]
39pub struct PageFaults {
40    pub(crate) reads: BTreeSet<u32>,
41    pub(crate) writes: BTreeSet<u32>,
42}
43
44/// The execution trace of a program.
45///
46/// The record of memory transactions of an execution that starts from an
47/// initial memory image (which includes the starting PC) and proceeds until
48/// either a sys_halt or a sys_pause syscall is encountered. This record is
49/// stored as a vector of [Segment]s.
50#[non_exhaustive]
51pub struct Session {
52    /// The constituent [Segment]s of the Session. The final [Segment] will have
53    /// an [ExitCode] of [Halted](ExitCode::Halted), [Paused](ExitCode::Paused),
54    /// or [SessionLimit](ExitCode::SessionLimit), and all other [Segment]s (if
55    /// any) will have [ExitCode::SystemSplit].
56    pub segments: Vec<Box<dyn SegmentRef>>,
57
58    /// The input digest.
59    pub input: Digest,
60
61    /// The data publicly committed by the guest program.
62    pub journal: Option<Journal>,
63
64    /// The [ExitCode] of the session.
65    pub exit_code: ExitCode,
66
67    /// The list of assumptions made by the guest and resolved by the host.
68    pub assumptions: Vec<(Assumption, AssumptionReceipt)>,
69
70    /// The list of assumptions made by the guest and resolved by the host in an mmr.
71    pub mmr_assumptions: Vec<AssumptionReceipt>,
72
73    /// The hooks to be called during the proving phase.
74    pub hooks: Vec<Box<dyn SessionEvents>>,
75
76    /// The number of user cycles without any overhead for continuations or po2
77    /// padding.
78    pub user_cycles: u64,
79
80    /// The number of cycles needed for paging operations.
81    pub paging_cycles: u64,
82
83    /// The number of cycles needed for the proof system which includes padding
84    /// up to the nearest power of 2.
85    pub reserved_cycles: u64,
86
87    /// Total number of cycles that a prover experiences. This includes overhead
88    /// associated with continuations and padding up to the nearest power of 2.
89    pub total_cycles: u64,
90
91    /// The system state of the initial MemoryImage.
92    pub pre_state: SystemState,
93
94    /// The system state of the final MemoryImage at the end of execution.
95    pub post_state: SystemState,
96
97    /// A list of pending ZKR proof requests.
98    // TODO: make this scalable so we don't OOM
99    pub(crate) pending_zkrs: Vec<ProveZkrRequest>,
100
101    /// A list of pending keccak proof requests.
102    // TODO: make this scalable so we don't OOM
103    pub(crate) pending_keccaks: Vec<ProveKeccakRequest>,
104
105    /// ecall metrics grouped by name.
106    pub(crate) ecall_metrics: Vec<(String, EcallMetric)>,
107
108    /// syscall metrics grouped by kind.
109    pub(crate) syscall_metrics: EnumMap<SyscallKind, SyscallMetric>,
110}
111
112/// The execution trace of a portion of a program.
113///
114/// The record of memory transactions of an execution that starts from an
115/// initial memory image, and proceeds until terminated by the system or user.
116/// This represents a chunk of execution work that will be proven in a single
117/// call to the ZKP system. It does not necessarily represent an entire program;
118/// see [Session] for tracking memory transactions until a user-requested
119/// termination.
120#[derive(Clone, Serialize, Deserialize)]
121pub struct Segment {
122    /// The index of this [Segment] within the [Session]
123    pub index: u32,
124
125    pub(crate) inner: risc0_circuit_rv32im::execute::Segment,
126
127    pub(crate) output: Option<Output>,
128}
129
130impl Segment {
131    /// Give the power of two length of this [Segment]
132    ///
133    /// If the [Segment]'s execution trace had 2^20 rows, this would return 20.
134    pub fn po2(&self) -> usize {
135        self.inner.po2 as usize
136    }
137
138    pub(crate) fn user_cycles(&self) -> u32 {
139        self.inner.suspend_cycle
140    }
141}
142
143/// A reference to a [Segment].
144///
145/// This allows implementers to determine the best way to represent this in an
146/// pluggable manner. See the [SimpleSegmentRef] for a very basic
147/// implementation.
148pub trait SegmentRef: Send {
149    /// Resolve this reference into an actual [Segment].
150    fn resolve(&self) -> Result<Segment>;
151}
152
153/// The Events of [Session]
154pub trait SessionEvents {
155    /// Fired before the proving of a segment starts.
156    #[allow(unused)]
157    fn on_pre_prove_segment(&self, segment: &Segment) {}
158
159    /// Fired after the proving of a segment ends.
160    #[allow(unused)]
161    fn on_post_prove_segment(&self, segment: &Segment) {}
162}
163
164impl Session {
165    /// Add a hook to be called during the proving phase.
166    pub fn add_hook<E: SessionEvents + 'static>(&mut self, hook: E) {
167        self.hooks.push(Box::new(hook));
168    }
169
170    /// Calculate for the [ReceiptClaim] associated with this [Session]. The
171    /// [ReceiptClaim] is the claim that will be proven if this [Session]
172    /// is passed to the [crate::Prover].
173    pub fn claim(&self) -> Result<ReceiptClaim> {
174        // Construct the Output struct for the session, checking internal consistency.
175        // NOTE: The Session output is distinct from the final Segment output because in the
176        // Session output any proven assumptions are not included.
177        self.claim_with_assumptions(self.assumptions.iter().map(|(_, x)| x))
178    }
179
180    pub(crate) fn claim_with_assumptions<'a>(
181        &self,
182        assumptions: impl Iterator<Item = &'a AssumptionReceipt>,
183    ) -> Result<ReceiptClaim> {
184        let output = if self.exit_code.expects_output() {
185            self.journal
186                .as_ref()
187                .map(|journal| -> Result<_> {
188                    Ok(Output {
189                        journal: journal.bytes.clone().into(),
190                        assumptions: Assumptions(
191                            assumptions
192                                .filter_map(|x| match x {
193                                    AssumptionReceipt::Proven(_) => None,
194                                    AssumptionReceipt::Unresolved(a) => Some(a.clone().into()),
195                                })
196                                .collect::<Vec<_>>(),
197                        )
198                        .into(),
199                    })
200                })
201                .transpose()?
202        } else {
203            ensure!(
204                self.journal.is_none(),
205                "Session with exit code {:?} has a journal",
206                self.exit_code
207            );
208            ensure!(
209                self.assumptions.is_empty(),
210                "Session with exit code {:?} has encoded assumptions",
211                self.exit_code
212            );
213            None
214        };
215
216        Ok(ReceiptClaim {
217            pre: self.pre_state.clone().into(),
218            post: self.post_state.clone().into(),
219            exit_code: self.exit_code,
220            input: MaybePruned::Pruned(self.input),
221            output: output.into(),
222        })
223    }
224
225    /// Log cycle information for this [Session].
226    ///
227    /// This logs the total and user cycles for this [Session] at the INFO level.
228    pub fn log(&self) {
229        if std::env::var_os("RISC0_INFO").is_none() {
230            return;
231        }
232
233        let pct = |cycles: u64| cycles as f64 / self.total_cycles as f64 * 100.0;
234
235        tracing::info!("number of segments: {}", self.segments.len());
236        tracing::info!("{} total cycles", self.total_cycles);
237        tracing::info!(
238            "{} user cycles ({:.2}%)",
239            self.user_cycles,
240            pct(self.user_cycles)
241        );
242        tracing::info!(
243            "{} paging cycles ({:.2}%)",
244            self.paging_cycles,
245            pct(self.paging_cycles)
246        );
247        tracing::info!(
248            "{} reserved cycles ({:.2}%)",
249            self.reserved_cycles,
250            pct(self.reserved_cycles)
251        );
252
253        tracing::info!("ecalls");
254        let mut ecall_metrics = self.ecall_metrics.clone();
255        ecall_metrics.sort_by(|a, b| a.1.cycles.cmp(&b.1.cycles));
256        for (name, metric) in ecall_metrics.iter().rev() {
257            tracing::info!(
258                "\t{} {name} calls, {} cycles, ({:.2}%)",
259                metric.count,
260                metric.cycles,
261                pct(metric.cycles)
262            );
263        }
264
265        tracing::info!("syscalls");
266        let mut syscall_metrics: Vec<_> = self.syscall_metrics.iter().collect();
267        syscall_metrics.sort_by(|a, b| a.1.count.cmp(&b.1.count));
268        for (name, metric) in syscall_metrics.iter().rev() {
269            tracing::info!("\t{} {name:?} calls", metric.count);
270        }
271    }
272
273    /// Returns stats for the session
274    ///
275    /// This contains cycle and segment information about the session useful for debugging and measuring performance.
276    pub fn stats(&self) -> SessionStats {
277        SessionStats {
278            segments: self.segments.len(),
279            total_cycles: self.total_cycles,
280            user_cycles: self.user_cycles,
281            paging_cycles: self.paging_cycles,
282            reserved_cycles: self.reserved_cycles,
283        }
284    }
285}
286
287/// Implementation of a [SegmentRef] that does not save the segment.
288///
289/// This is useful for DevMode where the segments aren't needed.
290#[derive(Serialize, Deserialize)]
291pub struct NullSegmentRef;
292
293impl SegmentRef for NullSegmentRef {
294    fn resolve(&self) -> anyhow::Result<Segment> {
295        unimplemented!()
296    }
297}
298
299pub fn null_callback(_: Segment) -> Result<Box<dyn SegmentRef>> {
300    Ok(Box::new(NullSegmentRef))
301}
302
303/// A very basic implementation of a [SegmentRef].
304///
305/// The [Segment] itself is stored in this implementation.
306#[derive(Clone, Serialize, Deserialize)]
307pub struct SimpleSegmentRef {
308    segment: Segment,
309}
310
311impl SegmentRef for SimpleSegmentRef {
312    fn resolve(&self) -> Result<Segment> {
313        Ok(self.segment.clone())
314    }
315}
316
317impl SimpleSegmentRef {
318    /// Construct a [SimpleSegmentRef] with the specified [Segment].
319    pub fn new(segment: Segment) -> Self {
320        Self { segment }
321    }
322}
323
324/// A basic implementation of a [SegmentRef] that saves the segment to a file
325///
326/// The [Segment] is stored in a user-specified file in this implementation,
327/// and the SegmentRef holds the filename.
328///
329/// There is an example of using [FileSegmentRef] in our [EVM example][1]
330///
331/// [1]: https://github.com/risc0/risc0/blob/main/examples/zkevm-demo/src/main.rs
332pub struct FileSegmentRef {
333    path: PathBuf,
334    _dir: SegmentPath,
335}
336
337impl SegmentRef for FileSegmentRef {
338    fn resolve(&self) -> Result<Segment> {
339        let contents = fs::read(&self.path)?;
340        let segment = bincode::deserialize(&contents)?;
341        Ok(segment)
342    }
343}
344
345impl FileSegmentRef {
346    /// Construct a [FileSegmentRef]
347    ///
348    /// This builds a FileSegmentRef that stores `segment` in a file at `path`.
349    pub fn new(segment: &Segment, dir: &SegmentPath) -> Result<Self> {
350        let path = dir.path().join(format!("{}.bincode", segment.index));
351        fs::write(&path, bincode::serialize(&segment)?)?;
352        Ok(Self {
353            path,
354            _dir: dir.clone(),
355        })
356    }
357}