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 = 2;
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_unchecked(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_adv_map_value_size());
234 target.write_usize(options.max_adv_map_elements());
235 target.write_usize(options.max_hash_len_bytes());
236 target.write_usize(options.max_deferred_elements());
237 target.write_bool(options.overlapped_trace_build());
238 target.write_usize(options.max_num_continuations());
239 target.write_usize(options.max_merkle_store_nodes());
240 target.write_usize(options.max_stack_depth());
241 target.write_usize(options.max_memory_elements());
242}
243
244fn read_execution_options<R: ByteReader>(
245 source: &mut R,
246) -> Result<ExecutionOptions, DeserializationError> {
247 let max_cycles = source.read_u32()?;
248 let expected_cycles = source.read_u32()?;
249 let core_trace_fragment_size = source.read_usize()?;
250 let max_adv_map_value_size = source.read_usize()?;
251 let max_adv_map_elements = source.read_usize()?;
252 let max_hash_len_bytes = source.read_usize()?;
253 let max_deferred_elements = source.read_usize()?;
254 let overlapped_trace_build = source.read_bool()?;
255 let max_num_continuations = source.read_usize()?;
256 let max_merkle_store_nodes = source.read_usize()?;
257 let max_stack_depth = source.read_usize()?;
258 let max_memory_elements = source.read_usize()?;
259
260 ExecutionOptions::new(Some(max_cycles), expected_cycles, core_trace_fragment_size)
261 .map_err(|err| {
262 DeserializationError::InvalidValue(format!("invalid execution options: {err}"))
263 })
264 .and_then(|options| {
265 options
266 .with_max_adv_map_value_size(max_adv_map_value_size)
267 .with_max_adv_map_elements(max_adv_map_elements)
268 .with_max_hash_len_bytes(max_hash_len_bytes)
269 .with_max_deferred_elements(max_deferred_elements)
270 .with_overlapped_trace_build(overlapped_trace_build)
271 .with_max_num_continuations(max_num_continuations)
272 .with_max_merkle_store_nodes(max_merkle_store_nodes)
273 .with_max_memory_elements(max_memory_elements)
274 .with_max_stack_depth(max_stack_depth)
275 .map_err(|err| {
276 DeserializationError::InvalidValue(format!("invalid execution options: {err}"))
277 })
278 })
279}
280
281#[derive(Debug, thiserror::Error)]
283pub enum ReplaySnapshotError {
284 #[error("failed to read replay snapshot file: {0}")]
285 Io(std::io::Error),
286 #[error("failed to deserialize replay snapshot: {0}")]
287 Deserialization(DeserializationError),
288}
289
290#[cfg(test)]
291mod tests {
292 use miden_assembly::{Assembler, DefaultSourceManager};
293 use miden_core::{Felt, Word, crypto::merkle::InnerNodeInfo};
294
295 use super::*;
296
297 fn word(values: [u32; 4]) -> Word {
298 Word::from(values.map(Felt::from))
299 }
300
301 #[test]
304 fn replay_snapshot_round_trips() {
305 let source_manager = Arc::new(DefaultSourceManager::default());
306 let program = Assembler::new(source_manager)
307 .assemble_program("program", "begin push.1 push.2 add drop end")
308 .map(Arc::<Package>::from)
309 .expect("failed to assemble test program");
310 let forest = LoadedMastForest::with_package_debug_info(
311 program.mast_forest().clone(),
312 program.debug_info(),
313 );
314
315 let event_log = vec![
316 vec![AdviceMutation::extend_advice_stack(
317 [Felt::from(7u32), Felt::from(8u32)].into_iter().collect(),
318 )],
319 vec![],
320 vec![AdviceMutation::extend_merkle_store([InnerNodeInfo {
321 value: word([1, 2, 3, 4]),
322 left: word([5, 6, 7, 8]),
323 right: word([9, 10, 11, 12]),
324 }])],
325 ];
326
327 let snapshot = ReplaySnapshot {
328 package: program.clone(),
329 stack_inputs: StackInputs::new(&[Felt::from(42u32), Felt::from(43u32)]).unwrap(),
330 advice_inputs: AdviceInputs::default()
331 .with_advice_stack([Felt::from(99u32)].into_iter().collect()),
332 options: ExecutionOptions::new(Some(100_000), 32, 1024)
333 .unwrap()
334 .with_max_adv_map_value_size(64)
335 .with_max_adv_map_elements(256)
336 .with_max_hash_len_bytes(512)
337 .with_max_deferred_elements(768)
338 .with_overlapped_trace_build(false)
339 .with_max_num_continuations(128)
340 .with_max_merkle_store_nodes(384)
341 .with_max_memory_elements(1024)
342 .with_max_stack_depth(128)
343 .unwrap(),
344 mast_forests: vec![forest],
345 event_log,
346 };
347
348 let restored = ReplaySnapshot::read_from_bytes(&snapshot.to_bytes())
349 .expect("snapshot failed to deserialize");
350
351 assert_eq!(restored.package.digest(), snapshot.package.digest());
352 assert_eq!(restored.stack_inputs, snapshot.stack_inputs);
353 assert_eq!(restored.advice_inputs, snapshot.advice_inputs);
354 assert_eq!(restored.options, snapshot.options);
355 assert_eq!(restored.mast_forests.len(), 1);
356 assert_eq!(restored.event_log.len(), 3);
357 assert_eq!(restored.event_log[1].len(), 0, "empty event batch must survive");
358 match restored.event_log[0].as_slice() {
359 [AdviceMutation::ExtendStack { stack }] => {
360 assert_eq!(
361 stack.iter().copied().collect::<Vec<_>>(),
362 [Felt::from(7u32), Felt::from(8u32)],
363 );
364 }
365 _ => panic!("unexpected first event batch"),
366 }
367 match restored.event_log[2].as_slice() {
368 [AdviceMutation::ExtendMerkleStore { infos }] => {
369 assert_eq!(infos.len(), 1);
370 assert_eq!(infos[0].value, word([1, 2, 3, 4]));
371 assert_eq!(infos[0].right, word([9, 10, 11, 12]));
372 }
373 _ => panic!("unexpected merkle-store event batch"),
374 }
375 }
376
377 #[test]
379 fn replay_snapshot_rejects_bad_magic() {
380 let err = ReplaySnapshot::read_from_bytes(b"not a snapshot at all really");
381 assert!(err.is_err(), "expected deserialization to fail on bad magic");
382 }
383
384 #[test]
387 fn replay_snapshot_rejects_previous_version() {
388 let mut bytes = SNAPSHOT_MAGIC.to_vec();
389 bytes.push(SNAPSHOT_VERSION - 1);
390
391 let Err(err) = ReplaySnapshot::read_from_bytes(&bytes) else {
392 panic!("expected deserialization to reject a version 1 snapshot");
393 };
394 assert!(err.to_string().contains("unsupported replay snapshot version 1"));
395 }
396}