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 = 1;
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_num_continuations());
237 target.write_usize(options.max_stack_depth());
238}
239
240fn read_execution_options<R: ByteReader>(
241 source: &mut R,
242) -> Result<ExecutionOptions, DeserializationError> {
243 let max_cycles = source.read_u32()?;
244 let expected_cycles = source.read_u32()?;
245 let core_trace_fragment_size = source.read_usize()?;
246 let max_adv_map_value_size = source.read_usize()?;
247 let max_adv_map_elements = source.read_usize()?;
248 let max_hash_len_bytes = source.read_usize()?;
249 let max_num_continuations = source.read_usize()?;
250 let max_stack_depth = source.read_usize()?;
251
252 ExecutionOptions::new(Some(max_cycles), expected_cycles, core_trace_fragment_size)
253 .map_err(|err| {
254 DeserializationError::InvalidValue(format!("invalid execution options: {err}"))
255 })
256 .and_then(|options| {
257 options
258 .with_max_adv_map_value_size(max_adv_map_value_size)
259 .with_max_adv_map_elements(max_adv_map_elements)
260 .with_max_hash_len_bytes(max_hash_len_bytes)
261 .with_max_num_continuations(max_num_continuations)
262 .with_max_stack_depth(max_stack_depth)
263 .map_err(|err| {
264 DeserializationError::InvalidValue(format!("invalid execution options: {err}"))
265 })
266 })
267}
268
269#[derive(Debug, thiserror::Error)]
271pub enum ReplaySnapshotError {
272 #[error("failed to read replay snapshot file: {0}")]
273 Io(std::io::Error),
274 #[error("failed to deserialize replay snapshot: {0}")]
275 Deserialization(DeserializationError),
276}
277
278#[cfg(test)]
279mod tests {
280 use miden_assembly::{Assembler, DefaultSourceManager};
281 use miden_core::{Felt, Word, crypto::merkle::InnerNodeInfo};
282
283 use super::*;
284
285 fn word(values: [u32; 4]) -> Word {
286 Word::from(values.map(Felt::from))
287 }
288
289 #[test]
292 fn replay_snapshot_round_trips() {
293 let source_manager = Arc::new(DefaultSourceManager::default());
294 let program = Assembler::new(source_manager)
295 .assemble_program("program", "begin push.1 push.2 add drop end")
296 .map(Arc::<Package>::from)
297 .expect("failed to assemble test program");
298 let forest = LoadedMastForest::with_package_debug_info(
299 program.mast_forest().clone(),
300 program.debug_info(),
301 );
302
303 let event_log = vec![
304 vec![AdviceMutation::extend_stack([Felt::from(7u32), Felt::from(8u32)])],
305 vec![],
306 vec![AdviceMutation::extend_merkle_store([InnerNodeInfo {
307 value: word([1, 2, 3, 4]),
308 left: word([5, 6, 7, 8]),
309 right: word([9, 10, 11, 12]),
310 }])],
311 ];
312
313 let snapshot = ReplaySnapshot {
314 package: program.clone(),
315 stack_inputs: StackInputs::new(&[Felt::from(42u32), Felt::from(43u32)]).unwrap(),
316 advice_inputs: AdviceInputs::default().with_stack([Felt::from(99u32)]),
317 options: ExecutionOptions::new(Some(100_000), 32, 1024)
318 .unwrap()
319 .with_max_adv_map_value_size(64)
320 .with_max_adv_map_elements(256)
321 .with_max_hash_len_bytes(512)
322 .with_max_num_continuations(128)
323 .with_max_stack_depth(128)
324 .unwrap(),
325 mast_forests: vec![forest],
326 event_log,
327 };
328
329 let restored = ReplaySnapshot::read_from_bytes(&snapshot.to_bytes())
330 .expect("snapshot failed to deserialize");
331
332 assert_eq!(restored.package.digest(), snapshot.package.digest());
333 assert_eq!(restored.stack_inputs, snapshot.stack_inputs);
334 assert_eq!(restored.advice_inputs, snapshot.advice_inputs);
335 assert_eq!(restored.options, snapshot.options);
336 assert_eq!(restored.mast_forests.len(), 1);
337 assert_eq!(restored.event_log.len(), 3);
338 assert_eq!(restored.event_log[1].len(), 0, "empty event batch must survive");
339 match restored.event_log[0].as_slice() {
340 [AdviceMutation::ExtendStack { values }] => {
341 assert_eq!(values.as_slice(), &[Felt::from(7u32), Felt::from(8u32)]);
342 }
343 _ => panic!("unexpected first event batch"),
344 }
345 match restored.event_log[2].as_slice() {
346 [AdviceMutation::ExtendMerkleStore { infos }] => {
347 assert_eq!(infos.len(), 1);
348 assert_eq!(infos[0].value, word([1, 2, 3, 4]));
349 assert_eq!(infos[0].right, word([9, 10, 11, 12]));
350 }
351 _ => panic!("unexpected merkle-store event batch"),
352 }
353 }
354
355 #[test]
357 fn replay_snapshot_rejects_bad_magic() {
358 let err = ReplaySnapshot::read_from_bytes(b"not a snapshot at all really");
359 assert!(err.is_err(), "expected deserialization to fail on bad magic");
360 }
361}