Skip to main content

miden_debug_engine/exec/
recording_host.rs

1use alloc::{sync::Arc, vec::Vec};
2
3use miden_core::Word;
4use miden_debug_types::{Location, SourceFile, SourceSpan};
5use miden_processor::{
6    BaseHost, FutureMaybeSend, Host, LoadedMastForest, ProcessorState,
7    advice::AdviceMutation,
8    event::{EventError, EventId, EventName, TraceError},
9};
10
11use super::{EventMutationRecorder, MastForestRecorder, advice::clone_advice_mutations};
12
13/// A host wrapper that records advice mutations and resolved MAST forests, so the execution can
14/// be replayed later from a [`ReplaySnapshot`](super::ReplaySnapshot).
15///
16/// A recorder left unset is not recorded into.
17pub struct RecordingHost<'a, H> {
18    inner: &'a mut H,
19    /// When set, the advice mutations produced by each `on_event` invocation of the inner host
20    /// are recorded here, in execution order, so they can be replayed later (e.g. transaction
21    /// debugging with event replay).
22    event_recorder: Option<EventMutationRecorder>,
23    /// When set, the MAST forests the inner host resolves are recorded here, so a replay session
24    /// can load the same code for `call`/`dyncall` targets.
25    forest_recorder: Option<MastForestRecorder>,
26}
27
28impl<'a, H> RecordingHost<'a, H> {
29    pub fn new(
30        inner: &'a mut H,
31        event_recorder: Option<EventMutationRecorder>,
32        forest_recorder: Option<MastForestRecorder>,
33    ) -> Self {
34        Self {
35            inner,
36            event_recorder,
37            forest_recorder,
38        }
39    }
40}
41
42impl<H: Host> BaseHost for RecordingHost<'_, H> {
43    fn get_label_and_source_file(
44        &self,
45        location: &Location,
46    ) -> (SourceSpan, Option<Arc<SourceFile>>) {
47        self.inner.get_label_and_source_file(location)
48    }
49
50    fn resolve_event(&self, event_id: EventId) -> Option<&EventName> {
51        self.inner.resolve_event(event_id)
52    }
53
54    fn resolve_trace(&self, trace_id: EventId) -> Option<&EventName> {
55        self.inner.resolve_trace(trace_id)
56    }
57}
58
59impl<H: Host> Host for RecordingHost<'_, H> {
60    fn get_mast_forest(
61        &self,
62        node_digest: &Word,
63    ) -> impl FutureMaybeSend<Option<LoadedMastForest>> {
64        // Record every forest the inner host resolves, so a replay session can load the same
65        // code for `call`/`dyncall` targets. The recorder deduplicates.
66        let forest_recorder = self.forest_recorder.clone();
67        let fut = self.inner.get_mast_forest(node_digest);
68        async move {
69            let forest = fut.await;
70            if let (Some(recorder), Some(forest)) = (&forest_recorder, &forest) {
71                recorder.record(forest.clone());
72            }
73            forest
74        }
75    }
76
77    fn on_event(
78        &mut self,
79        process: &ProcessorState<'_>,
80    ) -> impl FutureMaybeSend<Result<Vec<AdviceMutation>, EventError>> {
81        // Every invocation is recorded, including empty mutation sets: event replay pops one
82        // entry per event, so the log must stay aligned with the event stream.
83        let recorder = self.event_recorder.clone();
84        let fut = self.inner.on_event(process);
85        async move {
86            let result = fut.await;
87            if let (Some(recorder), Ok(mutations)) = (&recorder, &result) {
88                recorder.record(clone_advice_mutations(mutations));
89            }
90            result
91        }
92    }
93
94    fn on_trace(
95        &mut self,
96        process: &ProcessorState<'_>,
97    ) -> impl FutureMaybeSend<Result<(), TraceError>> {
98        self.inner.on_trace(process)
99    }
100}
101
102#[cfg(test)]
103mod tests;