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}