Skip to main content

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