dusk_cdf/
zkdb.rs

1mod breakpoint;
2mod state;
3
4use std::io;
5use std::ops::{Deref, DerefMut};
6
7use crate::{CircuitDescription, Config, Constraint, Preamble, Witness};
8
9use breakpoint::Breakpoints;
10
11pub use breakpoint::Breakpoint;
12pub use state::State;
13
14/// ZKP Debugger with CDF backend
15#[derive(Debug, Clone)]
16pub struct ZkDebugger<S> {
17    breakpoints: Breakpoints,
18    cdf: CircuitDescription<S>,
19    constraint: usize,
20}
21
22impl<S> Deref for ZkDebugger<S> {
23    type Target = CircuitDescription<S>;
24
25    fn deref(&self) -> &Self::Target {
26        &self.cdf
27    }
28}
29
30impl<S> DerefMut for ZkDebugger<S> {
31    fn deref_mut(&mut self) -> &mut Self::Target {
32        &mut self.cdf
33    }
34}
35
36impl<S> From<CircuitDescription<S>> for ZkDebugger<S> {
37    fn from(cdf: CircuitDescription<S>) -> Self {
38        Self {
39            breakpoints: Breakpoints::default(),
40            cdf,
41            constraint: 0,
42        }
43    }
44}
45
46impl<S> ZkDebugger<S> {
47    /// Configuration of the CDF file
48    pub const fn config(&self) -> &Config {
49        &self.cdf.preamble().config
50    }
51
52    /// Preamble for the CDF file
53    pub const fn preamble(&self) -> &Preamble {
54        self.cdf.preamble()
55    }
56
57    /// Add a breakpoint to the provided source/line
58    ///
59    /// If `line` is `None`, the breakpoint will be triggered in any incidence of `source`
60    pub fn add_breakpoint(&mut self, source: String, line: Option<u64>) -> usize {
61        self.breakpoints.add(source, line)
62    }
63
64    /// Remove a breakpoint with the provided id.
65    ///
66    /// If the id is not in the set, will return `None`.
67    pub fn remove_breakpoint(&mut self, id: usize) -> Option<Breakpoint> {
68        self.breakpoints.remove(id)
69    }
70
71    /// Fetch a breakpoint from an id returned from `add_breakpoint`.
72    pub fn fetch_breakpoint(&mut self, id: usize) -> Option<&Breakpoint> {
73        self.breakpoints.find_breakpoint_from_id(id)
74    }
75}
76
77impl<S> ZkDebugger<S>
78where
79    S: io::Read + io::Seek,
80{
81    /// Create a CDF with the provided source and use it as backend for the debugger
82    pub fn from_reader(source: S) -> io::Result<Self> {
83        CircuitDescription::from_reader(source).map(Self::from)
84    }
85
86    /// Attempt to fetch the current constraint from the source
87    pub fn fetch_current_constraint(&mut self) -> io::Result<Constraint> {
88        self.cdf.fetch_constraint(self.constraint)
89    }
90
91    /// Attempt to read an indexed constraint from the source
92    pub fn fetch_constraint(&mut self, idx: usize) -> io::Result<Constraint> {
93        self.cdf.fetch_constraint(idx)
94    }
95
96    /// Attempt to read an indexed witness from the source
97    pub fn fetch_witness(&mut self, idx: usize) -> io::Result<Witness> {
98        self.cdf.fetch_witness(idx)
99    }
100
101    /// Move to previous source/line.
102    ///
103    /// May jump more than one constraint in case we have multiple constraints defined in a single
104    /// source/file tuple.
105    pub fn afore(&mut self) -> io::Result<State> {
106        let Self {
107            breakpoints,
108            cdf,
109            constraint,
110        } = self;
111
112        let mut idx = *constraint;
113        if idx == 0 {
114            return Ok(State::Beginning);
115        }
116
117        let current = cdf.fetch_constraint(idx)?;
118        let source = current.name().to_string();
119        let line = current.line();
120
121        loop {
122            idx -= 1;
123
124            if idx == 0 {
125                *constraint = 0;
126                return Ok(State::Beginning);
127            }
128
129            let current = cdf.fetch_constraint(idx)?;
130            let is_invalid = !current.polynomial().evaluation;
131            let different_line = source != current.name() || line != current.line();
132
133            if different_line && is_invalid {
134                *constraint = idx;
135                return Ok(State::InvalidConstraint { id: idx });
136            }
137
138            if different_line {
139                if let Some(id) = breakpoints.find_breakpoint(&current) {
140                    *constraint = idx;
141                    return Ok(State::Breakpoint { id });
142                }
143            }
144
145            if different_line {
146                break;
147            }
148        }
149
150        *constraint = idx;
151        Ok(State::Constraint { id: idx })
152    }
153
154    /// Continue the execution until EOF, breakpoint, or invalid constraint.
155    pub fn cont(&mut self) -> io::Result<State> {
156        let Self {
157            breakpoints,
158            cdf,
159            constraint,
160        } = self;
161
162        let mut idx = *constraint;
163        let eof = cdf.preamble().constraints.saturating_sub(1);
164
165        if idx == eof {
166            return Ok(State::End { id: idx });
167        }
168
169        let current = cdf.fetch_constraint(idx)?;
170        let source = current.name().to_string();
171        let line = current.line();
172
173        loop {
174            idx += 1;
175
176            let current = cdf.fetch_constraint(idx)?;
177            let is_invalid = !current.polynomial().evaluation;
178            let different_line = source != current.name() || line != current.line();
179
180            if different_line && is_invalid {
181                *constraint = idx;
182                return Ok(State::InvalidConstraint { id: idx });
183            }
184
185            if idx == eof {
186                *constraint = idx;
187                return Ok(State::End { id: idx });
188            }
189
190            if different_line {
191                if let Some(id) = breakpoints.find_breakpoint(&current) {
192                    *constraint = idx;
193                    return Ok(State::Breakpoint { id });
194                }
195            }
196        }
197    }
198
199    /// Attempt to jump to a given constraint
200    pub fn goto(&mut self, idx: usize) -> io::Result<State> {
201        let Self {
202            cdf, constraint, ..
203        } = self;
204
205        if idx == 0 {
206            *constraint = 0;
207            return Ok(State::Beginning);
208        }
209
210        let current = cdf.fetch_constraint(idx)?;
211        let is_invalid = !current.polynomial().evaluation;
212
213        *constraint = idx;
214
215        if is_invalid {
216            return Ok(State::InvalidConstraint { id: idx });
217        }
218
219        if idx == cdf.preamble().constraints.saturating_sub(1) {
220            return Ok(State::End { id: idx });
221        }
222
223        Ok(State::Constraint { id: idx })
224    }
225
226    /// Move to next source/line.
227    ///
228    /// May jump more than one constraint in case we have multiple constraints defined in a single
229    /// source/file tuple.
230    pub fn step(&mut self) -> io::Result<State> {
231        let Self {
232            breakpoints,
233            cdf,
234            constraint,
235        } = self;
236
237        let mut idx = *constraint;
238        let eof = cdf.preamble().constraints.saturating_sub(1);
239
240        if idx == eof {
241            return Ok(State::End { id: idx });
242        }
243
244        let current = cdf.fetch_constraint(idx)?;
245        let source = current.name().to_string();
246        let line = current.line();
247
248        loop {
249            idx += 1;
250
251            let current = cdf.fetch_constraint(idx)?;
252            let is_invalid = !current.polynomial().evaluation;
253            let different_line = source != current.name() || line != current.line();
254
255            if different_line && is_invalid {
256                *constraint = idx;
257                return Ok(State::InvalidConstraint { id: idx });
258            }
259
260            if idx == eof {
261                *constraint = idx;
262                return Ok(State::End { id: idx });
263            }
264
265            if different_line {
266                if let Some(id) = breakpoints.find_breakpoint(&current) {
267                    *constraint = idx;
268                    return Ok(State::Breakpoint { id });
269                }
270            }
271
272            if different_line {
273                break;
274            }
275        }
276
277        *constraint = idx;
278        Ok(State::Constraint { id: idx })
279    }
280
281    /// Reverse the execution until BOF, breakpoint, or invalid constraint.
282    pub fn turn(&mut self) -> io::Result<State> {
283        let Self {
284            breakpoints,
285            cdf,
286            constraint,
287        } = self;
288
289        let mut idx = *constraint;
290        if idx == 0 {
291            return Ok(State::Beginning);
292        }
293
294        let current = cdf.fetch_constraint(idx)?;
295        let source = current.name().to_string();
296        let line = current.line();
297
298        loop {
299            idx -= 1;
300
301            if idx == 0 {
302                *constraint = 0;
303                return Ok(State::Beginning);
304            }
305
306            let current = cdf.fetch_constraint(idx)?;
307            let is_invalid = !current.polynomial().evaluation;
308            let different_line = source != current.name() || line != current.line();
309
310            if different_line && is_invalid {
311                *constraint = idx;
312                return Ok(State::InvalidConstraint { id: idx });
313            }
314
315            if different_line {
316                if let Some(id) = breakpoints.find_breakpoint(&current) {
317                    *constraint = idx;
318                    return Ok(State::Breakpoint { id });
319                }
320            }
321        }
322    }
323}