simplicity/node/
convert.rs

1// SPDX-License-Identifier: CC0-1.0
2
3//! Node Conversion
4//!
5//! This module defines a trait [`Conversion`] which is called by the
6//! [`Node::convert`] method. The `Convert` trait is used to convert between
7//! one node type and another, controlling several nuanced aspects of the
8//! conversion. Specifically:
9//!
10//! 1. Cached data can be translated from the old type to the new one.
11//! 2. Witness data can be provided (or translated from the old witness type,
12//!    if it was nontrivial) to attach to witness nodes.
13//! 3. For `case` nodes, the decision can be made to hide one of the children.
14//!    In this case the `case` node is converted to an `AssertL` or `AssertR`
15//!    node, depending which child was hidden.
16//!
17
18use crate::dag::PostOrderIterItem;
19use crate::jet::Jet;
20use crate::Value;
21
22use super::{
23    Commit, CommitNode, Inner, Marker, NoDisconnect, NoWitness, Node, Redeem, RedeemData,
24    RedeemNode,
25};
26
27use std::sync::Arc;
28
29/// A decision about which, if any, child branches of a `case` combinator to hide
30/// during a [`Node::convert`] conversion
31#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Debug, Hash)]
32pub enum Hide {
33    Neither,
34    Left,
35    Right,
36}
37
38/// The primary trait controlling how a node conversion is done.
39///
40/// When a node is converted using [`Node::convert`], the original DAG rooted at
41/// that node is traversed in post order. For each node, the following steps are
42/// taken:
43///
44/// 1. First, [`Self::visit_node`] is called, before any other checks are
45///    done. This happens regardless of the node's type or whether it is going
46///    to be pruned.
47///
48///    This method is provided for convenience and does not affect the course
49///    of the algorithm. It has a default implementation which does nothing.
50///
51/// 2. Then, if the node is a witness node, `Self::convert_witness` is called
52///    to obtain witness data.
53///
54/// 3. If the node is a case node, [`Self::prune_case`] is called to decide
55///    whether to prune either child of the node (turning the `case` into an
56///    `assertl` or `assertr`). The default implementation hides neither.
57///
58/// 4. Finally, the node's data is passed to [`Self::convert_data`], whose job
59///    it is to compute the cached data for the new node. For `case` combinators
60///    where one child was pruned, `convert_data` will receive an `assertl` or
61///    `assertl`, as appropriate, rather than a `case`.
62///
63/// If any method returns an error, then iteration is aborted immediately and
64/// the error returned to the caller. If the converter would like to recover
65/// from errors and/or accumulate multiple errors, it needs to do this by
66/// tracking errors internally.
67///
68/// The finalization method will not return any errors except those returned by
69/// methods on [`Converter`].
70pub trait Converter<N: Marker, M: Marker> {
71    /// The error type returned by the methods on this trait.
72    type Error;
73
74    /// This method is called on every node, to inform the `Converter` about the
75    /// state of the iterator.
76    ///
77    /// No action needs to be taken. The default implementation does nothing.
78    fn visit_node(&mut self, _data: &PostOrderIterItem<&Node<N>>) {}
79
80    /// For witness nodes, this method is called first to attach witness data to
81    /// the node.
82    ///
83    /// It takes the iteration data of the current node, as well as the current
84    /// witness (which in a typical scenario will be an empty structure, but
85    /// with custom node types may be a placeholder or other useful information)
86    ///
87    /// No typechecking or other sanity-checking is done on the returned value.
88    /// It is the caller's responsibility to make sure that the provided witness
89    /// actually matches the type of the combinator that it is being attached to.
90    fn convert_witness(
91        &mut self,
92        data: &PostOrderIterItem<&Node<N>>,
93        witness: &N::Witness,
94    ) -> Result<M::Witness, Self::Error>;
95
96    /// For disconnect nodes, this method is called first to attach a disconnected
97    /// expression to the node.
98    ///
99    /// It takes the iteration data of the current node, as well as the current
100    /// disconnected expression (which in a typical scenario will be an empty
101    /// structure, but with custom node types may be a placeholder or other
102    /// useful information)
103    ///
104    /// No typechecking or other sanity-checking is done on the returned expression.
105    /// It is the caller's responsibility to make sure that the provided expression
106    /// actually matches the type of the combinator that it is being attached to.
107    fn convert_disconnect(
108        &mut self,
109        data: &PostOrderIterItem<&Node<N>>,
110        maybe_converted: Option<&Arc<Node<M>>>,
111        disconnect: &N::Disconnect,
112    ) -> Result<M::Disconnect, Self::Error>;
113
114    /// For case nodes, this method is called first to decide which, if any, children
115    /// to prune.
116    ///
117    /// It takes the iteration data of the current node, as well as both of its already
118    /// converted children. This method returns a hiding decision.
119    ///
120    /// The default implementation doesn't do any hiding.
121    fn prune_case(
122        &mut self,
123        _data: &PostOrderIterItem<&Node<N>>,
124        _left: &Arc<Node<M>>,
125        _right: &Arc<Node<M>>,
126    ) -> Result<Hide, Self::Error> {
127        Ok(Hide::Neither)
128    }
129
130    /// This method is called for every node, after [`Self::convert_witness`] or
131    /// [`Self::prune_case`], if either is applicable.
132    ///
133    /// For case nodes for which [`Self::prune_case`] returned [`Hide::Left`] or
134    /// [`Hide::Right`], `inner` will be an [`Inner::AssertR`] or [`Inner::AssertL`]
135    /// respectively; the pruned child will then appear only as a CMR.
136    ///
137    /// It accepts the iteration data of the current node, from which the existing
138    /// cached data can be obtained by calling `data.node.cached_data()`, as well
139    /// as an `Inner` structure containing its already-converted children.
140    ///
141    /// Returns new cached data which will be attached to the newly-converted node.
142    fn convert_data(
143        &mut self,
144        data: &PostOrderIterItem<&Node<N>>,
145        inner: Inner<&Arc<Node<M>>, M::Jet, &M::Disconnect, &M::Witness>,
146    ) -> Result<M::CachedData, Self::Error>;
147}
148
149/// Basic finalizer which converts a [`super::CommitNode`] to a [`super::RedeemNode`]
150/// by attaching witness data from an iterator.
151///
152/// Does not do any type-checking and may attach an invalid witness to a combinator.
153///
154/// If it encounters a disconnect node, it simply returns an error.
155///
156/// **This finalizer should not be used in production.
157/// It introduces default values ([`Value::zero`]) to handle missing witness data,
158/// which might trigger unexpected spending paths.**
159// FIXME we should do type checking, but this would require a method to check
160// type compatibility between a Value and a type::Final.
161pub struct SimpleFinalizer<W: Iterator<Item = Value>> {
162    iter: W,
163}
164
165impl<W: Iterator<Item = Value>> SimpleFinalizer<W> {
166    pub fn new(iter: W) -> Self {
167        SimpleFinalizer { iter }
168    }
169}
170
171impl<W: Iterator<Item = Value>, J: Jet> Converter<Commit<J>, Redeem<J>> for SimpleFinalizer<W> {
172    type Error = crate::FinalizeError;
173
174    fn convert_witness(
175        &mut self,
176        data: &PostOrderIterItem<&CommitNode<J>>,
177        _: &NoWitness,
178    ) -> Result<Value, Self::Error> {
179        Ok(self
180            .iter
181            .next()
182            .unwrap_or_else(|| Value::zero(&data.node.arrow().target)))
183    }
184
185    fn convert_disconnect(
186        &mut self,
187        _: &PostOrderIterItem<&CommitNode<J>>,
188        _: Option<&Arc<RedeemNode<J>>>,
189        _: &NoDisconnect,
190    ) -> Result<Arc<RedeemNode<J>>, Self::Error> {
191        Err(crate::FinalizeError::DisconnectRedeemTime)
192    }
193
194    fn convert_data(
195        &mut self,
196        data: &PostOrderIterItem<&CommitNode<J>>,
197        inner: Inner<&Arc<RedeemNode<J>>, J, &Arc<RedeemNode<J>>, &Value>,
198    ) -> Result<Arc<RedeemData<J>>, Self::Error> {
199        let converted_data = inner
200            .map(|node| node.cached_data())
201            .map_disconnect(|node| node.cached_data())
202            .map_witness(Value::shallow_clone);
203        Ok(Arc::new(RedeemData::new(
204            data.node.arrow().shallow_clone(),
205            converted_data,
206        )))
207    }
208}