Skip to main content

miden_core/deferred/
state.rs

1use alloc::{collections::BTreeMap, sync::Arc, vec::Vec};
2
3use super::{
4    DeferredError, Digest, IntegrityError, MAX_DEFERRED_ELEMENTS, Node, NodeType, PrecompileError,
5    PrecompileRegistry, PrecompileWitness, TRUE_DIGEST, Tag,
6};
7
8/// Deferred graph and eager evaluation state.
9///
10/// Registered original nodes, canonical/helper nodes, evaluation memos, the current root, and the
11/// element budget live here. [`Self::into_witness`] exports only the original root-reachable graph
12/// and releases the state.
13#[derive(Debug, Clone)]
14pub struct DeferredState {
15    registry: Arc<PrecompileRegistry>,
16    nodes: BTreeMap<Digest, Node>,
17    pub(super) root: Digest,
18    evals: BTreeMap<Digest, Digest>,
19    remaining_elements: usize,
20}
21
22impl Default for DeferredState {
23    fn default() -> Self {
24        Self::new(Arc::new(PrecompileRegistry::new()))
25            .expect("empty registry initialization cannot fail")
26    }
27}
28
29impl DeferredState {
30    pub fn new(registry: Arc<PrecompileRegistry>) -> Result<Self, PrecompileError> {
31        let mut state = Self::empty(registry);
32        state.initialize_precompile_nodes()?;
33        Ok(state)
34    }
35
36    /// Creates a state seeded only with framework basics.
37    fn empty(registry: Arc<PrecompileRegistry>) -> Self {
38        let mut nodes = BTreeMap::new();
39        nodes.insert(TRUE_DIGEST, Node::TRUE);
40
41        let mut evals = BTreeMap::new();
42        evals.insert(TRUE_DIGEST, TRUE_DIGEST);
43
44        Self {
45            registry,
46            nodes,
47            root: TRUE_DIGEST,
48            evals,
49            remaining_elements: MAX_DEFERRED_ELEMENTS,
50        }
51    }
52
53    /// Loads all precompile initialization nodes, then evaluates each to ensure the bootstrap set
54    /// resolves under this registry.
55    fn initialize_precompile_nodes(&mut self) -> Result<(), PrecompileError> {
56        let init_nodes = self.registry.init_nodes();
57        let init_digests: Vec<Digest> = init_nodes.iter().map(Node::digest).collect();
58
59        // Load the complete set before enforcing child closure. This lets init nodes depend on
60        // TRUE or on any other node in the complete init set, independent of registry order.
61        for node in init_nodes {
62            self.registry.validate_node(&node)?;
63            self.insert_node(node)?;
64        }
65
66        for digest in init_digests {
67            self.evaluate_digest(digest)?;
68        }
69
70        Ok(())
71    }
72
73    /// Adds precompiles to this state without discarding existing nodes, evaluation memos, root, or
74    /// budget accounting.
75    ///
76    /// Registration is additive only: duplicate precompile ids panic via
77    /// [`PrecompileRegistry::merge`], matching setup-time registry construction behavior. The
78    /// state is cloned before mutation so failed precompile initialization leaves `self`
79    /// unchanged.
80    pub fn extend_precompiles(
81        &mut self,
82        precompiles: PrecompileRegistry,
83    ) -> Result<(), PrecompileError> {
84        let mut next = self.clone();
85        Arc::make_mut(&mut next.registry).merge(precompiles);
86        next.initialize_precompile_nodes()?;
87
88        *self = next;
89        Ok(())
90    }
91
92    pub fn registry(&self) -> &PrecompileRegistry {
93        &self.registry
94    }
95
96    /// Returns the current deferred root; [`super::TRUE_DIGEST`] means no statements are logged.
97    pub fn root(&self) -> Digest {
98        self.root
99    }
100
101    pub fn get_node(&self, digest: &Digest) -> Option<&Node> {
102        self.nodes.get(digest)
103    }
104
105    /// Returns the already-memoized canonical digest for `digest`, if present.
106    ///
107    /// This is strictly read-only: it does not evaluate `digest`, validate deferred nodes, insert
108    /// canonical results, or mutate the memo table. Missing memos and dangling memos whose
109    /// canonical node is absent from this state both return `None`.
110    pub fn get_canonical_digest(&self, digest: Digest) -> Option<Digest> {
111        let canonical_digest = self.evals.get(&digest).copied()?;
112        self.nodes.contains_key(&canonical_digest).then_some(canonical_digest)
113    }
114
115    /// Returns the already-memoized canonical node for `digest`, if present.
116    ///
117    /// This is strictly read-only and returns only canonical results that are already memoized and
118    /// stored in this state.
119    pub fn get_canonical_node(&self, digest: Digest) -> Option<(Digest, &Node)> {
120        let canonical_digest = self.get_canonical_digest(digest)?;
121        self.nodes.get(&canonical_digest).map(|node| (canonical_digest, node))
122    }
123
124    /// Returns the already-memoized canonical node for `digest` or
125    /// [`PrecompileError::MissingNode`].
126    ///
127    /// This is strictly read-only and never evaluates or mutates deferred state.
128    pub fn require_canonical_node(
129        &self,
130        digest: Digest,
131    ) -> Result<(Digest, &Node), PrecompileError> {
132        self.get_canonical_node(digest).ok_or(PrecompileError::MissingNode)
133    }
134
135    /// Returns the approximate number of field elements occupied by registered deferred nodes.
136    pub fn num_elements(&self) -> usize {
137        self.nodes
138            .iter()
139            .filter_map(|(digest, node)| {
140                (*digest != TRUE_DIGEST).then_some(node.storage_felt_len())
141            })
142            .sum()
143    }
144
145    pub fn remaining_elements(&self) -> usize {
146        self.remaining_elements
147    }
148
149    /// Recognizes `tag` under the installed registry and returns its declared outer payload shape.
150    ///
151    /// This does not inspect a payload, validate structural child references, or evaluate
152    /// precompile semantics. [`Self::register`] performs those checks for a complete node.
153    pub fn decode(&self, tag: Tag) -> Result<NodeType, PrecompileError> {
154        self.registry.decode_node_type(tag)
155    }
156
157    /// Registers a `PrecompileRegistry`-valid node in the DAG and evaluates it immediately.
158    ///
159    /// Registration validates the node shape and child references, stores the original node under
160    /// its own digest, evaluates it under the current registry, stores the canonical result node,
161    /// preserves helper nodes registered during evaluation, and records the evaluation memo from
162    /// original digest to canonical digest. The returned digest is always the original node digest.
163    /// If evaluation fails, registration returns that error immediately. Re-registering an
164    /// identical successfully registered node is idempotent and budget-free.
165    pub fn register(&mut self, node: Node) -> Result<Digest, PrecompileError> {
166        self.validate_node_for_insertion(&node)?;
167        let digest = self.insert_node(node)?;
168        self.evaluate_digest(digest)?;
169        Ok(digest)
170    }
171
172    /// Logs a statement commitment after proving the current root and statement evaluate to TRUE.
173    ///
174    /// The statement digest must already be registered (present in `nodes`), unless it is the
175    /// implicit [`TRUE_DIGEST`]. On success, this inserts the framework AND node, advances the
176    /// deferred root, memoizes the new root as TRUE, and returns the new root.
177    pub fn log_statement(&mut self, statement_digest: Digest) -> Result<Digest, PrecompileError> {
178        let prev_root = self.root;
179
180        self.require_true_eval(prev_root)?;
181        self.require_true_eval(statement_digest)?;
182
183        let and_node = Node::and(prev_root, statement_digest);
184        let new_root = and_node.digest();
185        self.insert_node(and_node)?;
186        self.root = new_root;
187        self.record_eval(new_root, Node::TRUE)?;
188        Ok(new_root)
189    }
190
191    /// Logs a statement only if its constrained transition matches `expected_new_root`.
192    ///
193    /// The VM constrains `log_deferred` as a Poseidon2 fold over the previous deferred root and
194    /// the statement digest. This helper binds the in-memory deferred DAG to that constrained
195    /// transition: it validates the expected root before mutating `self`, then applies the same
196    /// semantic checks as [`Self::log_statement`].
197    pub fn log_verified_statement(
198        &mut self,
199        statement_digest: Digest,
200        expected_new_root: Digest,
201    ) -> Result<Digest, PrecompileError> {
202        let actual_new_root = Node::and(self.root, statement_digest).digest();
203        if actual_new_root != expected_new_root {
204            return Err(DeferredError::InvalidDeferredRootTransition {
205                expected: expected_new_root,
206                actual: actual_new_root,
207            }
208            .into());
209        }
210
211        self.log_statement(statement_digest)
212    }
213
214    /// Evaluates a registered node addressed by digest and returns the canonical node digest.
215    ///
216    /// Evaluation memoization is an implementation detail: callers receive the canonical digest
217    /// whether the result was already known or computed by this call. Use [`Self::get_node`] with
218    /// the returned digest to inspect the canonical node contents.
219    pub fn evaluate_digest(&mut self, digest: Digest) -> Result<Digest, PrecompileError> {
220        let node = self.nodes.get(&digest).ok_or(PrecompileError::MissingNode)?.clone();
221        if let Some(canonical_digest) = self.evals.get(&digest) {
222            if self.nodes.contains_key(canonical_digest) {
223                return Ok(*canonical_digest);
224            }
225            return Err(PrecompileError::MissingNode);
226        }
227
228        self.validate_node_for_insertion(&node)?;
229        let canonical = if node.tag() == Tag::TRUE {
230            Node::TRUE
231        } else if node.tag() == Tag::AND {
232            let (lhs, rhs) = node.payload().as_join()?;
233            for child in [lhs, rhs] {
234                self.require_true_eval(child)?;
235            }
236            Node::TRUE
237        } else if node.tag() == Tag::CHUNKS {
238            node
239        } else {
240            let registry = Arc::clone(&self.registry);
241            let mut context = DeferredContext::new(self);
242            registry.evaluate(&node, &mut context)?
243        };
244
245        self.record_eval(digest, canonical)?;
246        self.evals.get(&digest).copied().ok_or(PrecompileError::MissingNode)
247    }
248
249    /// Consumes completed execution state and exports its root-reachable portable graph.
250    ///
251    /// Executions without logged work return `None`. Export preserves original node commitments
252    /// and omits unreachable nodes and evaluation caches; it does not serialize through bytes.
253    pub fn into_witness(self) -> Result<Option<PrecompileWitness>, IntegrityError> {
254        if self.root == TRUE_DIGEST {
255            return Ok(None);
256        }
257        PrecompileWitness::from_state(&self).map(Some)
258    }
259
260    fn validate_node_for_insertion(&self, node: &Node) -> Result<NodeType, PrecompileError> {
261        let node_type = self.registry.validate_node(node)?;
262        for child in node.children() {
263            if child != TRUE_DIGEST && !self.nodes.contains_key(&child) {
264                return Err(PrecompileError::MissingNode);
265            }
266        }
267        Ok(node_type)
268    }
269
270    fn insert_node(&mut self, node: Node) -> Result<Digest, PrecompileError> {
271        let digest = node.digest();
272        match self.nodes.get(&digest) {
273            Some(existing) if existing == &node => Ok(digest),
274            Some(_) => Err(DeferredError::ConflictingNode.into()),
275            None => {
276                let required = node.storage_felt_len();
277                self.remaining_elements = self.remaining_elements.checked_sub(required).ok_or(
278                    DeferredError::DeferredStateTooLarge {
279                        num_elements: required,
280                        max: self.remaining_elements,
281                    },
282                )?;
283                self.nodes.insert(digest, node);
284                Ok(digest)
285            },
286        }
287    }
288
289    /// Records an evaluation memo and stores its canonical node in `nodes` for downstream
290    /// references.
291    fn record_eval(
292        &mut self,
293        input_digest: Digest,
294        canonical: Node,
295    ) -> Result<(), PrecompileError> {
296        if !self.nodes.contains_key(&input_digest) {
297            return Err(PrecompileError::MissingNode);
298        }
299        self.validate_node_for_insertion(&canonical)?;
300        let canonical_digest = self.insert_node(canonical)?;
301        match self.evals.get(&input_digest) {
302            Some(existing) if *existing == canonical_digest => Ok(()),
303            Some(_) => Err(DeferredError::ConflictingNode.into()),
304            None => {
305                self.evals.insert(input_digest, canonical_digest);
306                Ok(())
307            },
308        }
309    }
310
311    fn require_true_eval(&mut self, digest: Digest) -> Result<(), PrecompileError> {
312        if self.evaluate_digest(digest)? != TRUE_DIGEST {
313            return Err(PrecompileError::AssertionFailed);
314        }
315        Ok(())
316    }
317}
318
319// DEFERRED CONTEXT
320// ================================================================================================
321
322/// Capability object passed to precompiles during recursive evaluation.
323///
324/// Precompiles do not own the DAG; they receive this handle to evaluate registered children and to
325/// register helper nodes referenced by compound canonicals during execution.
326pub struct DeferredContext<'a> {
327    state: &'a mut DeferredState,
328}
329
330impl<'a> DeferredContext<'a> {
331    /// Binds state for one framework-driven evaluation.
332    pub(crate) fn new(state: &'a mut DeferredState) -> Self {
333        Self { state }
334    }
335
336    /// Returns the registered node addressed by `digest`, if present.
337    ///
338    /// This is a syntactic DAG lookup: it does not evaluate the node or canonicalize it.
339    pub fn get_node(&self, digest: &Digest) -> Option<&Node> {
340        self.state.get_node(digest)
341    }
342
343    /// Evaluates a registered child digest and returns the canonical node digest.
344    ///
345    /// The `nodes` membership check preserves the registered child closure; memoization is
346    /// transparent to precompile implementations. Use
347    /// [`Self::get_node`] with the returned digest to inspect the canonical node contents.
348    pub fn evaluate_digest(&mut self, digest: Digest) -> Result<Digest, PrecompileError> {
349        self.state.evaluate_digest(digest)
350    }
351
352    /// Evaluates two registered child digests to their canonical node digests.
353    pub fn evaluate_digest_pair(
354        &mut self,
355        lhs: Digest,
356        rhs: Digest,
357    ) -> Result<(Digest, Digest), PrecompileError> {
358        Ok((self.evaluate_digest(lhs)?, self.evaluate_digest(rhs)?))
359    }
360
361    /// Evaluates two child digests and requires their canonical nodes to be equal.
362    pub fn ensure_equal(&mut self, lhs: Digest, rhs: Digest) -> Result<(), PrecompileError> {
363        let (lhs, rhs) = self.evaluate_digest_pair(lhs, rhs)?;
364        if lhs != rhs {
365            return Err(PrecompileError::AssertionFailed);
366        }
367        Ok(())
368    }
369
370    /// Registers a freshly minted helper node and returns its original digest.
371    ///
372    /// Use this when a compound canonical needs stable child commitments that were created during
373    /// evaluation. Helper registration follows the same eager semantics as ordinary registration.
374    pub fn register(&mut self, node: Node) -> Result<Digest, PrecompileError> {
375        self.state.register(node)
376    }
377}
378
379#[cfg(test)]
380mod tests {
381    use super::*;
382    use crate::{
383        Felt, ZERO,
384        deferred::{Payload, Precompile, precompile_id},
385    };
386
387    #[derive(Debug, Clone, Copy)]
388    struct RejectingPrecompile;
389
390    impl Precompile for RejectingPrecompile {
391        fn name(&self) -> &'static str {
392            "rejecting-registration-fixture"
393        }
394
395        fn id(&self) -> Felt {
396            precompile_id(self.name())
397        }
398
399        fn decode(&self, args: [Felt; 3]) -> Option<NodeType> {
400            (args == [ZERO; 3]).then_some(NodeType::Data)
401        }
402
403        fn evaluate(
404            &self,
405            _args: [Felt; 3],
406            _payload: &Payload,
407            _context: &mut DeferredContext<'_>,
408        ) -> Result<Node, PrecompileError> {
409            Err(PrecompileError::AssertionFailed)
410        }
411    }
412
413    #[test]
414    fn construction_uses_the_fixed_deferred_element_limit() {
415        let state = DeferredState::new(Arc::new(PrecompileRegistry::new())).unwrap();
416        let default_state = DeferredState::default();
417
418        assert_eq!(state.num_elements(), 0);
419        assert_eq!(state.remaining_elements(), MAX_DEFERRED_ELEMENTS);
420        assert_eq!(default_state.remaining_elements(), MAX_DEFERRED_ELEMENTS);
421    }
422
423    #[test]
424    fn register_eagerly_propagates_precompile_evaluation_errors() {
425        let precompile = RejectingPrecompile;
426        let tag =
427            Tag::precompile(precompile.id(), [ZERO; 3]).expect("fixture id is precompile-owned");
428        let registry = Arc::new(PrecompileRegistry::new().with_precompile(precompile));
429        let mut state = DeferredState::new(registry).unwrap();
430        let node = Node::value(tag, [ZERO; 8]).unwrap();
431        let digest = node.digest();
432
433        let error = state.register(node).unwrap_err();
434
435        assert!(matches!(error.root(), PrecompileError::AssertionFailed));
436        assert_eq!(state.get_canonical_digest(digest), None);
437    }
438}