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}