1use std::{
13 path::{Path, PathBuf},
14 sync::{Arc, Mutex},
15};
16
17use miden_core::{
18 mast::MastForest,
19 program::StackInputs,
20 serde::{ByteReader, ByteWriter, Deserializable, DeserializationError, Serializable},
21};
22use miden_mast_package::{Package, debug_info::PackageDebugInfo};
23use miden_processor::{
24 ExecutionOptions, LoadedMastForest,
25 advice::{AdviceInputs, AdviceMutation},
26};
27
28use super::advice::{read_event_log, write_event_log};
29
30#[derive(Clone, Default)]
37pub struct MastForestRecorder {
38 forests: Arc<Mutex<Vec<LoadedMastForest>>>,
39}
40
41impl MastForestRecorder {
42 pub fn new() -> Self {
44 Self::default()
45 }
46
47 pub fn snapshot(&self) -> Vec<LoadedMastForest> {
49 self.forests.lock().expect("mast forest log poisoned").clone()
50 }
51
52 pub(crate) fn record(&self, forest: LoadedMastForest) {
54 let mut guard = self.forests.lock().expect("mast forest log poisoned");
55 if !guard
56 .iter()
57 .any(|existing| Arc::ptr_eq(existing.mast_forest(), forest.mast_forest()))
58 {
59 guard.push(forest);
60 }
61 }
62
63 pub(crate) fn clear(&self) {
65 self.forests.lock().expect("mast forest log poisoned").clear();
66 }
67}
68
69#[derive(Clone, Debug, PartialEq, Eq)]
71pub struct ReplaySnapshotWrite {
72 pub path: PathBuf,
73 pub event_count: usize,
74 pub forest_count: usize,
75}
76
77#[derive(Clone, Debug, PartialEq, Eq, thiserror::Error)]
79#[error("failed to write replay snapshot to {}: {}", path.display(), message)]
80pub struct ReplaySnapshotWriteError {
81 pub path: PathBuf,
82 pub message: String,
83}
84
85#[derive(Clone, Debug, Default)]
87pub struct ReplaySnapshotRecorder {
88 status: Arc<Mutex<Option<Result<ReplaySnapshotWrite, ReplaySnapshotWriteError>>>>,
89}
90
91impl ReplaySnapshotRecorder {
92 pub fn new() -> Self {
93 Self::default()
94 }
95
96 pub fn take(&self) -> Option<Result<ReplaySnapshotWrite, ReplaySnapshotWriteError>> {
98 self.status.lock().expect("replay snapshot status poisoned").take()
99 }
100
101 pub(crate) fn record_success(&self, write: ReplaySnapshotWrite) {
102 *self.status.lock().expect("replay snapshot status poisoned") = Some(Ok(write));
103 }
104
105 pub(crate) fn record_error(&self, path: PathBuf, err: impl ToString) {
106 *self.status.lock().expect("replay snapshot status poisoned") =
107 Some(Err(ReplaySnapshotWriteError {
108 path,
109 message: err.to_string(),
110 }));
111 }
112}
113
114const SNAPSHOT_MAGIC: [u8; 6] = *b"MDNSNP";
117const SNAPSHOT_VERSION: u8 = 3;
118
119pub struct ReplaySnapshot {
121 pub package: Arc<Package>,
123 pub stack_inputs: StackInputs,
125 pub advice_inputs: AdviceInputs,
127 pub options: ExecutionOptions,
129 pub mast_forests: Vec<LoadedMastForest>,
132 pub event_log: Vec<Vec<AdviceMutation>>,
135}
136
137impl ReplaySnapshot {
138 pub fn write_to_file(&self, path: impl AsRef<Path>) -> std::io::Result<()> {
140 std::fs::write(path, self.to_bytes())
141 }
142
143 pub fn read_from_file(path: impl AsRef<Path>) -> Result<Self, ReplaySnapshotError> {
145 let bytes = std::fs::read(path).map_err(ReplaySnapshotError::Io)?;
146 Self::read_from_bytes(&bytes).map_err(ReplaySnapshotError::Deserialization)
147 }
148
149 pub fn to_bytes(&self) -> Vec<u8> {
151 let mut bytes = Vec::new();
152 self.write_into(&mut bytes);
153 bytes
154 }
155
156 pub fn read_from_bytes(bytes: &[u8]) -> Result<Self, DeserializationError> {
158 let mut reader = miden_core::serde::SliceReader::new(bytes);
159 Self::read_from(&mut reader)
160 }
161}
162
163impl Serializable for ReplaySnapshot {
164 fn write_into<W: ByteWriter>(&self, target: &mut W) {
165 target.write_bytes(&SNAPSHOT_MAGIC);
166 target.write_u8(SNAPSHOT_VERSION);
167 self.package.write_into(target);
168 self.stack_inputs.write_into(target);
169 self.advice_inputs.write_into(target);
170 write_execution_options(&self.options, target);
171 target.write_usize(self.mast_forests.len());
172 for forest in &self.mast_forests {
173 forest.mast_forest().as_ref().write_into(target);
174 match forest.package_debug_info().ok().flatten() {
175 Some(debug_info) => {
176 target.write_bool(true);
177 debug_info.as_ref().write_into(target);
178 }
179 None => {
180 target.write_bool(false);
181 }
182 }
183 }
184 write_event_log(&self.event_log, target);
185 }
186}
187
188impl Deserializable for ReplaySnapshot {
189 fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
190 let magic: [u8; 6] = source.read_array()?;
191 if magic != SNAPSHOT_MAGIC {
192 return Err(DeserializationError::InvalidValue(
193 "not a Miden debugger replay snapshot (bad magic)".to_string(),
194 ));
195 }
196 let version = source.read_u8()?;
197 if version != SNAPSHOT_VERSION {
198 return Err(DeserializationError::InvalidValue(format!(
199 "unsupported replay snapshot version {version} (expected {SNAPSHOT_VERSION})"
200 )));
201 }
202 let package = Arc::new(Package::read_from_trusted(source)?);
203 let stack_inputs = StackInputs::read_from(source)?;
204 let advice_inputs = AdviceInputs::read_from(source)?;
205 let options = read_execution_options(source)?;
206 let forest_count = source.read_usize()?;
207 let mut mast_forests = Vec::with_capacity(forest_count);
208 for _ in 0..forest_count {
209 let mast_forest = Arc::new(MastForest::read_from(source)?);
210 mast_forests.push(if source.read_bool()? {
211 let debug_info = Some(PackageDebugInfo::read_from(source)?);
212 LoadedMastForest::with_package_debug_info(mast_forest, Ok(debug_info))
213 } else {
214 LoadedMastForest::new(mast_forest)
215 });
216 }
217 let event_log = read_event_log(source)?;
218 Ok(Self {
219 package,
220 stack_inputs,
221 advice_inputs,
222 options,
223 mast_forests,
224 event_log,
225 })
226 }
227}
228
229fn write_execution_options<W: ByteWriter>(options: &ExecutionOptions, target: &mut W) {
230 target.write_u32(options.max_cycles());
231 target.write_u32(options.expected_cycles());
232 target.write_usize(options.core_trace_fragment_size());
233 target.write_usize(options.max_advice_size_bytes());
234 target.write_usize(options.max_hash_len_bytes());
235 target.write_bool(options.overlapped_trace_build());
236 target.write_usize(options.max_num_continuations());
237 target.write_usize(options.max_stack_depth());
238 target.write_usize(options.max_memory_elements());
239}
240
241fn read_execution_options<R: ByteReader>(
242 source: &mut R,
243) -> Result<ExecutionOptions, DeserializationError> {
244 let max_cycles = source.read_u32()?;
245 let expected_cycles = source.read_u32()?;
246 let core_trace_fragment_size = source.read_usize()?;
247 let max_advice_size_bytes = source.read_usize()?;
248 let max_hash_len_bytes = source.read_usize()?;
249 let overlapped_trace_build = source.read_bool()?;
250 let max_num_continuations = source.read_usize()?;
251 let max_stack_depth = source.read_usize()?;
252 let max_memory_elements = source.read_usize()?;
253
254 ExecutionOptions::new(Some(max_cycles), expected_cycles, core_trace_fragment_size)
255 .map_err(|err| {
256 DeserializationError::InvalidValue(format!("invalid execution options: {err}"))
257 })
258 .and_then(|options| {
259 options
260 .with_max_advice_size_bytes(max_advice_size_bytes)
261 .with_max_hash_len_bytes(max_hash_len_bytes)
262 .with_overlapped_trace_build(overlapped_trace_build)
263 .with_max_num_continuations(max_num_continuations)
264 .with_max_memory_elements(max_memory_elements)
265 .with_max_stack_depth(max_stack_depth)
266 .map_err(|err| {
267 DeserializationError::InvalidValue(format!("invalid execution options: {err}"))
268 })
269 })
270}
271
272#[derive(Debug, thiserror::Error)]
274pub enum ReplaySnapshotError {
275 #[error("failed to read replay snapshot file: {0}")]
276 Io(std::io::Error),
277 #[error("failed to deserialize replay snapshot: {0}")]
278 Deserialization(DeserializationError),
279}
280
281#[cfg(test)]
282mod tests {
283 use miden_assembly::{Assembler, DefaultSourceManager};
284 use miden_core::{Felt, Word, crypto::merkle::InnerNodeInfo};
285
286 use super::*;
287
288 fn word(values: [u32; 4]) -> Word {
289 Word::from(values.map(Felt::from))
290 }
291
292 #[test]
295 fn replay_snapshot_round_trips() {
296 let source_manager = Arc::new(DefaultSourceManager::default());
297 let program = Assembler::new(source_manager)
298 .assemble_program("program", "begin push.1 push.2 add drop end")
299 .map(Arc::<Package>::from)
300 .expect("failed to assemble test program");
301 let forest = LoadedMastForest::with_package_debug_info(
302 program.mast_forest().clone(),
303 program.debug_info(),
304 );
305
306 let event_log = vec![
307 vec![AdviceMutation::extend_advice_stack(
308 [Felt::from(7u32), Felt::from(8u32)].into_iter().collect(),
309 )],
310 vec![],
311 vec![AdviceMutation::extend_merkle_store([InnerNodeInfo {
312 value: word([1, 2, 3, 4]),
313 left: word([5, 6, 7, 8]),
314 right: word([9, 10, 11, 12]),
315 }])],
316 ];
317
318 let snapshot = ReplaySnapshot {
319 package: program.clone(),
320 stack_inputs: StackInputs::new(&[Felt::from(42u32), Felt::from(43u32)]).unwrap(),
321 advice_inputs: AdviceInputs::default()
322 .with_stack([Felt::from(99u32)].into_iter().collect()),
323 options: ExecutionOptions::new(Some(100_000), 32, 1024)
324 .unwrap()
325 .with_max_advice_size_bytes(256)
326 .with_max_hash_len_bytes(512)
327 .with_overlapped_trace_build(false)
328 .with_max_num_continuations(128)
329 .with_max_memory_elements(1024)
330 .with_max_stack_depth(128)
331 .unwrap(),
332 mast_forests: vec![forest],
333 event_log,
334 };
335
336 let restored = ReplaySnapshot::read_from_bytes(&snapshot.to_bytes())
337 .expect("snapshot failed to deserialize");
338
339 assert_eq!(restored.package.commitment(), snapshot.package.commitment());
340 assert_eq!(restored.stack_inputs, snapshot.stack_inputs);
341 assert_eq!(restored.advice_inputs, snapshot.advice_inputs);
342 assert_eq!(restored.options, snapshot.options);
343 assert_eq!(restored.mast_forests.len(), 1);
344 assert_eq!(restored.event_log.len(), 3);
345 assert_eq!(restored.event_log[1].len(), 0, "empty event batch must survive");
346 match restored.event_log[0].as_slice() {
347 [AdviceMutation::ExtendStack { stack }] => {
348 assert_eq!(
349 stack.iter().copied().collect::<Vec<_>>(),
350 [Felt::from(7u32), Felt::from(8u32)],
351 );
352 }
353 _ => panic!("unexpected first event batch"),
354 }
355 match restored.event_log[2].as_slice() {
356 [AdviceMutation::ExtendMerkleStore { inner_nodes }] => {
357 assert_eq!(inner_nodes.len(), 1);
358 assert_eq!(inner_nodes[0].value, word([1, 2, 3, 4]));
359 assert_eq!(inner_nodes[0].right, word([9, 10, 11, 12]));
360 }
361 _ => panic!("unexpected merkle-store event batch"),
362 }
363 }
364
365 #[test]
367 fn replay_snapshot_rejects_bad_magic() {
368 let err = ReplaySnapshot::read_from_bytes(b"not a snapshot at all really");
369 assert!(err.is_err(), "expected deserialization to fail on bad magic");
370 }
371
372 #[test]
375 fn replay_snapshot_rejects_previous_version() {
376 let mut bytes = SNAPSHOT_MAGIC.to_vec();
377 bytes.push(SNAPSHOT_VERSION - 1);
378
379 let Err(err) = ReplaySnapshot::read_from_bytes(&bytes) else {
380 panic!("expected deserialization to reject a snapshot from a previous version");
381 };
382 assert!(err.to_string().contains("unsupported replay snapshot version"));
383 }
384}