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}