miniscript_debug/descriptor/
tr.rs

1// Tapscript
2use core::cmp::{self, max};
3use core::str::FromStr;
4use core::{fmt, hash};
5
6use bitcoin::blockdata::opcodes;
7use bitcoin::util::taproot::{
8    LeafVersion, TaprootBuilder, TaprootSpendInfo, TAPROOT_CONTROL_BASE_SIZE,
9    TAPROOT_CONTROL_MAX_NODE_COUNT, TAPROOT_CONTROL_NODE_SIZE,
10};
11use bitcoin::{secp256k1, Address, Network, Script};
12use sync::Arc;
13
14use super::checksum::{self, verify_checksum};
15use crate::expression::{self, FromTree};
16use crate::miniscript::Miniscript;
17use crate::policy::semantic::Policy;
18use crate::policy::Liftable;
19use crate::prelude::*;
20use crate::util::{varint_len, witness_size};
21use crate::{
22    errstr, Error, ForEachKey, MiniscriptKey, Satisfier, Tap, ToPublicKey, TranslatePk, Translator,
23};
24
25/// A Taproot Tree representation.
26// Hidden leaves are not yet supported in descriptor spec. Conceptually, it should
27// be simple to integrate those here, but it is best to wait on core for the exact syntax.
28#[derive(Clone, Ord, PartialOrd, Eq, PartialEq, Hash)]
29pub enum TapTree<Pk: MiniscriptKey> {
30    /// A taproot tree structure
31    Tree(Arc<TapTree<Pk>>, Arc<TapTree<Pk>>),
32    /// A taproot leaf denoting a spending condition
33    // A new leaf version would require a new Context, therefore there is no point
34    // in adding a LeafVersion with Leaf type here. All Miniscripts right now
35    // are of Leafversion::default
36    Leaf(Arc<Miniscript<Pk, Tap>>),
37}
38
39/// A taproot descriptor
40pub struct Tr<Pk: MiniscriptKey> {
41    /// A taproot internal key
42    internal_key: Pk,
43    /// Optional Taproot Tree with spending conditions
44    tree: Option<TapTree<Pk>>,
45    /// Optional spending information associated with the descriptor
46    /// This will be [`None`] when the descriptor is not derived.
47    /// This information will be cached automatically when it is required
48    //
49    // The inner `Arc` here is because Rust does not allow us to return a reference
50    // to the contents of the `Option` from inside a `MutexGuard`. There is no outer
51    // `Arc` because when this structure is cloned, we create a whole new mutex.
52    spend_info: Mutex<Option<Arc<TaprootSpendInfo>>>,
53}
54
55impl<Pk: MiniscriptKey> Clone for Tr<Pk> {
56    fn clone(&self) -> Self {
57        // When cloning, construct a new Mutex so that distinct clones don't
58        // cause blocking between each other. We clone only the internal `Arc`,
59        // so the clone is always cheap (in both time and space)
60        Self {
61            internal_key: self.internal_key.clone(),
62            tree: self.tree.clone(),
63            spend_info: Mutex::new(
64                self.spend_info
65                    .lock()
66                    .expect("Lock poisoned")
67                    .as_ref()
68                    .map(Arc::clone),
69            ),
70        }
71    }
72}
73
74impl<Pk: MiniscriptKey> PartialEq for Tr<Pk> {
75    fn eq(&self, other: &Self) -> bool {
76        self.internal_key == other.internal_key && self.tree == other.tree
77    }
78}
79
80impl<Pk: MiniscriptKey> Eq for Tr<Pk> {}
81
82impl<Pk: MiniscriptKey> PartialOrd for Tr<Pk> {
83    fn partial_cmp(&self, other: &Self) -> Option<cmp::Ordering> {
84        match self.internal_key.partial_cmp(&other.internal_key) {
85            Some(cmp::Ordering::Equal) => {}
86            ord => return ord,
87        }
88        self.tree.partial_cmp(&other.tree)
89    }
90}
91
92impl<Pk: MiniscriptKey> Ord for Tr<Pk> {
93    fn cmp(&self, other: &Self) -> cmp::Ordering {
94        match self.internal_key.cmp(&other.internal_key) {
95            cmp::Ordering::Equal => {}
96            ord => return ord,
97        }
98        self.tree.cmp(&other.tree)
99    }
100}
101
102impl<Pk: MiniscriptKey> hash::Hash for Tr<Pk> {
103    fn hash<H: hash::Hasher>(&self, state: &mut H) {
104        self.internal_key.hash(state);
105        self.tree.hash(state);
106    }
107}
108
109impl<Pk: MiniscriptKey> TapTree<Pk> {
110    // Helper function to compute height
111    // TODO: Instead of computing this every time we add a new leaf, we should
112    // add height as a separate field in taptree
113    fn taptree_height(&self) -> usize {
114        match *self {
115            TapTree::Tree(ref left_tree, ref right_tree) => {
116                1 + max(left_tree.taptree_height(), right_tree.taptree_height())
117            }
118            TapTree::Leaf(..) => 0,
119        }
120    }
121
122    /// Iterate over all miniscripts
123    pub fn iter(&self) -> TapTreeIter<Pk> {
124        TapTreeIter {
125            stack: vec![(0, self)],
126        }
127    }
128
129    // Helper function to translate keys
130    fn translate_helper<T, Q, Error>(&self, t: &mut T) -> Result<TapTree<Q>, Error>
131    where
132        T: Translator<Pk, Q, Error>,
133        Q: MiniscriptKey,
134    {
135        let frag = match self {
136            TapTree::Tree(l, r) => TapTree::Tree(
137                Arc::new(l.translate_helper(t)?),
138                Arc::new(r.translate_helper(t)?),
139            ),
140            TapTree::Leaf(ms) => TapTree::Leaf(Arc::new(ms.translate_pk(t)?)),
141        };
142        Ok(frag)
143    }
144}
145
146impl<Pk: MiniscriptKey> fmt::Display for TapTree<Pk> {
147    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
148        match self {
149            TapTree::Tree(ref left, ref right) => write!(f, "{{{},{}}}", *left, *right),
150            TapTree::Leaf(ref script) => write!(f, "{}", *script),
151        }
152    }
153}
154
155impl<Pk: MiniscriptKey> fmt::Debug for TapTree<Pk> {
156    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
157        match self {
158            TapTree::Tree(ref left, ref right) => write!(f, "{{{:?},{:?}}}", *left, *right),
159            TapTree::Leaf(ref script) => write!(f, "{:?}", *script),
160        }
161    }
162}
163
164impl<Pk: MiniscriptKey> Tr<Pk> {
165    /// Create a new [`Tr`] descriptor from internal key and [`TapTree`]
166    pub fn new(internal_key: Pk, tree: Option<TapTree<Pk>>) -> Result<Self, Error> {
167        let nodes = tree.as_ref().map(|t| t.taptree_height()).unwrap_or(0);
168
169        if nodes <= TAPROOT_CONTROL_MAX_NODE_COUNT {
170            Ok(Self {
171                internal_key,
172                tree,
173                spend_info: Mutex::new(None),
174            })
175        } else {
176            Err(Error::MaxRecursiveDepthExceeded)
177        }
178    }
179
180    /// Obtain the internal key of [`Tr`] descriptor
181    pub fn internal_key(&self) -> &Pk {
182        &self.internal_key
183    }
184
185    /// Obtain the [`TapTree`] of the [`Tr`] descriptor
186    pub fn taptree(&self) -> &Option<TapTree<Pk>> {
187        &self.tree
188    }
189
190    /// Iterate over all scripts in merkle tree. If there is no script path, the iterator
191    /// yields [`None`]
192    pub fn iter_scripts(&self) -> TapTreeIter<Pk> {
193        match self.tree {
194            Some(ref t) => t.iter(),
195            None => TapTreeIter { stack: vec![] },
196        }
197    }
198
199    /// Compute the [`TaprootSpendInfo`] associated with this descriptor if spend data is `None`.
200    ///
201    /// If spend data is already computed (i.e it is not `None`), this does not recompute it.
202    ///
203    /// [`TaprootSpendInfo`] is only required for spending via the script paths.
204    pub fn spend_info(&self) -> Arc<TaprootSpendInfo>
205    where
206        Pk: ToPublicKey,
207    {
208        // If the value is already cache, read it
209        // read only panics if the lock is poisoned (meaning other thread having a lock panicked)
210        let read_lock = self.spend_info.lock().expect("Lock poisoned");
211        if let Some(ref spend_info) = *read_lock {
212            return Arc::clone(spend_info);
213        }
214        drop(read_lock);
215
216        // Get a new secp context
217        // This would be cheap operation after static context support from upstream
218        let secp = secp256k1::Secp256k1::verification_only();
219        // Key spend path with no merkle root
220        let data = if self.tree.is_none() {
221            TaprootSpendInfo::new_key_spend(&secp, self.internal_key.to_x_only_pubkey(), None)
222        } else {
223            let mut builder = TaprootBuilder::new();
224            for (depth, ms) in self.iter_scripts() {
225                let script = ms.encode();
226                builder = builder
227                    .add_leaf(depth, script)
228                    .expect("Computing spend data on a valid Tree should always succeed");
229            }
230            // Assert builder cannot error here because we have a well formed descriptor
231            match builder.finalize(&secp, self.internal_key.to_x_only_pubkey()) {
232                Ok(data) => data,
233                Err(_) => unreachable!("We know the builder can be finalized"),
234            }
235        };
236        let spend_info = Arc::new(data);
237        *self.spend_info.lock().expect("Lock poisoned") = Some(Arc::clone(&spend_info));
238        spend_info
239    }
240
241    /// Checks whether the descriptor is safe.
242    pub fn sanity_check(&self) -> Result<(), Error> {
243        for (_depth, ms) in self.iter_scripts() {
244            ms.sanity_check()?;
245        }
246        Ok(())
247    }
248
249    /// Computes an upper bound on the weight of a satisfying witness to the
250    /// transaction.
251    ///
252    /// Assumes all ec-signatures are 73 bytes, including push opcode and
253    /// sighash suffix. Includes the weight of the VarInts encoding the
254    /// scriptSig and witness stack length.
255    ///
256    /// # Errors
257    /// When the descriptor is impossible to safisfy (ex: sh(OP_FALSE)).
258    pub fn max_satisfaction_weight(&self) -> Result<usize, Error> {
259        let tree = match self.taptree() {
260            // key spend path:
261            // scriptSigLen(4) + stackLen(1) + stack[Sig]Len(1) + stack[Sig](65)
262            None => return Ok(4 + 1 + 1 + 65),
263            // script path spend..
264            Some(tree) => tree,
265        };
266
267        tree.iter()
268            .filter_map(|(depth, ms)| {
269                let script_size = ms.script_size();
270                let max_sat_elems = ms.max_satisfaction_witness_elements().ok()?;
271                let max_sat_size = ms.max_satisfaction_size().ok()?;
272                let control_block_size = control_block_len(depth);
273                Some(
274                    // scriptSig len byte
275                    4 +
276                    // witness field stack len (+2 for control block & script)
277                    varint_len(max_sat_elems + 2) +
278                    // size of elements to satisfy script
279                    max_sat_size +
280                    // second to last element: script
281                    varint_len(script_size) +
282                    script_size +
283                    // last element: control block
284                    varint_len(control_block_size) +
285                    control_block_size,
286                )
287            })
288            .max()
289            .ok_or(Error::ImpossibleSatisfaction)
290    }
291}
292
293impl<Pk: MiniscriptKey + ToPublicKey> Tr<Pk> {
294    /// Obtains the corresponding script pubkey for this descriptor.
295    pub fn script_pubkey(&self) -> Script {
296        let output_key = self.spend_info().output_key();
297        let builder = bitcoin::blockdata::script::Builder::new();
298        builder
299            .push_opcode(opcodes::all::OP_PUSHNUM_1)
300            .push_slice(&output_key.serialize())
301            .into_script()
302    }
303
304    /// Obtains the corresponding address for this descriptor.
305    pub fn address(&self, network: Network) -> Address {
306        let spend_info = self.spend_info();
307        Address::p2tr_tweaked(spend_info.output_key(), network)
308    }
309
310    /// Returns satisfying non-malleable witness and scriptSig with minimum
311    /// weight to spend an output controlled by the given descriptor if it is
312    /// possible to construct one using the `satisfier`.
313    pub fn get_satisfaction<S>(&self, satisfier: S) -> Result<(Vec<Vec<u8>>, Script), Error>
314    where
315        S: Satisfier<Pk>,
316    {
317        best_tap_spend(self, satisfier, false /* allow_mall */)
318    }
319
320    /// Returns satisfying, possibly malleable, witness and scriptSig with
321    /// minimum weight to spend an output controlled by the given descriptor if
322    /// it is possible to construct one using the `satisfier`.
323    pub fn get_satisfaction_mall<S>(&self, satisfier: S) -> Result<(Vec<Vec<u8>>, Script), Error>
324    where
325        S: Satisfier<Pk>,
326    {
327        best_tap_spend(self, satisfier, true /* allow_mall */)
328    }
329}
330
331/// Iterator for Taproot structures
332/// Yields a pair of (depth, miniscript) in a depth first walk
333/// For example, this tree:
334///                                     - N0 -
335///                                    /     \\
336///                                   N1      N2
337///                                  /  \    /  \\
338///                                 A    B  C   N3
339///                                            /  \\
340///                                           D    E
341/// would yield (2, A), (2, B), (2,C), (3, D), (3, E).
342///
343#[derive(Debug, Clone)]
344pub struct TapTreeIter<'a, Pk: MiniscriptKey> {
345    stack: Vec<(u8, &'a TapTree<Pk>)>,
346}
347
348impl<'a, Pk> Iterator for TapTreeIter<'a, Pk>
349where
350    Pk: MiniscriptKey + 'a,
351{
352    type Item = (u8, &'a Miniscript<Pk, Tap>);
353
354    fn next(&mut self) -> Option<Self::Item> {
355        while !self.stack.is_empty() {
356            let (depth, last) = self.stack.pop().expect("Size checked above");
357            match &*last {
358                TapTree::Tree(l, r) => {
359                    self.stack.push((depth + 1, r));
360                    self.stack.push((depth + 1, l));
361                }
362                TapTree::Leaf(ref ms) => return Some((depth, ms)),
363            }
364        }
365        None
366    }
367}
368
369#[rustfmt::skip]
370impl_block_str!(
371    Tr<Pk>,
372    // Helper function to parse taproot script path
373    fn parse_tr_script_spend(tree: &expression::Tree,) -> Result<TapTree<Pk>, Error> {
374        match tree {
375            expression::Tree { name, args } if !name.is_empty() && args.is_empty() => {
376                let script = Miniscript::<Pk, Tap>::from_str(name)?;
377                Ok(TapTree::Leaf(Arc::new(script)))
378            }
379            expression::Tree { name, args } if name.is_empty() && args.len() == 2 => {
380                let left = Self::parse_tr_script_spend(&args[0])?;
381                let right = Self::parse_tr_script_spend(&args[1])?;
382                Ok(TapTree::Tree(Arc::new(left), Arc::new(right)))
383            }
384            _ => Err(Error::Unexpected(
385                "unknown format for script spending paths while parsing taproot descriptor"
386                    .to_string(),
387            )),
388        }
389    }
390);
391
392impl_from_tree!(
393    Tr<Pk>,
394    fn from_tree(top: &expression::Tree) -> Result<Self, Error> {
395        if top.name == "tr" {
396            match top.args.len() {
397                1 => {
398                    let key = &top.args[0];
399                    if !key.args.is_empty() {
400                        return Err(Error::Unexpected(format!(
401                            "#{} script associated with `key-path` while parsing taproot descriptor",
402                            key.args.len()
403                        )));
404                    }
405                    Tr::new(expression::terminal(key, Pk::from_str)?, None)
406                }
407                2 => {
408                    let key = &top.args[0];
409                    if !key.args.is_empty() {
410                        return Err(Error::Unexpected(format!(
411                            "#{} script associated with `key-path` while parsing taproot descriptor",
412                            key.args.len()
413                        )));
414                    }
415                    let tree = &top.args[1];
416                    let ret = Self::parse_tr_script_spend(tree)?;
417                    Tr::new(expression::terminal(key, Pk::from_str)?, Some(ret))
418                }
419                _ => {
420                    return Err(Error::Unexpected(format!(
421                        "{}[#{} args] while parsing taproot descriptor",
422                        top.name,
423                        top.args.len()
424                    )));
425                }
426            }
427        } else {
428            return Err(Error::Unexpected(format!(
429                "{}[#{} args] while parsing taproot descriptor",
430                top.name,
431                top.args.len()
432            )));
433        }
434    }
435);
436
437impl_from_str!(
438    Tr<Pk>,
439    type Err = Error;,
440    fn from_str(s: &str) -> Result<Self, Self::Err> {
441        let desc_str = verify_checksum(s)?;
442        let top = parse_tr_tree(desc_str)?;
443        Self::from_tree(&top)
444    }
445);
446
447impl<Pk: MiniscriptKey> fmt::Debug for Tr<Pk> {
448    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
449        match self.tree {
450            Some(ref s) => write!(f, "tr({:?},{:?})", self.internal_key, s),
451            None => write!(f, "tr({:?})", self.internal_key),
452        }
453    }
454}
455
456impl<Pk: MiniscriptKey> fmt::Display for Tr<Pk> {
457    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
458        use fmt::Write;
459        let mut wrapped_f = checksum::Formatter::new(f);
460        let key = &self.internal_key;
461        match self.tree {
462            Some(ref s) => write!(wrapped_f, "tr({},{})", key, s)?,
463            None => write!(wrapped_f, "tr({})", key)?,
464        }
465        wrapped_f.write_checksum_if_not_alt()
466    }
467}
468
469// Helper function to parse string into miniscript tree form
470fn parse_tr_tree(s: &str) -> Result<expression::Tree, Error> {
471    for ch in s.bytes() {
472        if !ch.is_ascii() {
473            return Err(Error::Unprintable(ch));
474        }
475    }
476
477    if s.len() > 3 && &s[..3] == "tr(" && s.as_bytes()[s.len() - 1] == b')' {
478        let rest = &s[3..s.len() - 1];
479        if !rest.contains(',') {
480            let internal_key = expression::Tree {
481                name: rest,
482                args: vec![],
483            };
484            return Ok(expression::Tree {
485                name: "tr",
486                args: vec![internal_key],
487            });
488        }
489        // use str::split_once() method to refactor this when compiler version bumps up
490        let (key, script) = split_once(rest, ',')
491            .ok_or_else(|| Error::BadDescriptor("invalid taproot descriptor".to_string()))?;
492
493        let internal_key = expression::Tree {
494            name: key,
495            args: vec![],
496        };
497        if script.is_empty() {
498            return Ok(expression::Tree {
499                name: "tr",
500                args: vec![internal_key],
501            });
502        }
503        let (tree, rest) = expression::Tree::from_slice_delim(script, 1, '{')?;
504        if rest.is_empty() {
505            Ok(expression::Tree {
506                name: "tr",
507                args: vec![internal_key, tree],
508            })
509        } else {
510            Err(errstr(rest))
511        }
512    } else {
513        Err(Error::Unexpected("invalid taproot descriptor".to_string()))
514    }
515}
516
517fn split_once(inp: &str, delim: char) -> Option<(&str, &str)> {
518    if inp.is_empty() {
519        None
520    } else {
521        let mut found = inp.len();
522        for (idx, ch) in inp.chars().enumerate() {
523            if ch == delim {
524                found = idx;
525                break;
526            }
527        }
528        // No comma or trailing comma found
529        if found >= inp.len() - 1 {
530            Some((inp, ""))
531        } else {
532            Some((&inp[..found], &inp[found + 1..]))
533        }
534    }
535}
536
537impl<Pk: MiniscriptKey> Liftable<Pk> for TapTree<Pk> {
538    fn lift(&self) -> Result<Policy<Pk>, Error> {
539        fn lift_helper<Pk: MiniscriptKey>(s: &TapTree<Pk>) -> Result<Policy<Pk>, Error> {
540            match s {
541                TapTree::Tree(ref l, ref r) => {
542                    Ok(Policy::Threshold(1, vec![lift_helper(l)?, lift_helper(r)?]))
543                }
544                TapTree::Leaf(ref leaf) => leaf.lift(),
545            }
546        }
547
548        let pol = lift_helper(self)?;
549        Ok(pol.normalized())
550    }
551}
552
553impl<Pk: MiniscriptKey> Liftable<Pk> for Tr<Pk> {
554    fn lift(&self) -> Result<Policy<Pk>, Error> {
555        match &self.tree {
556            Some(root) => Ok(Policy::Threshold(
557                1,
558                vec![Policy::Key(self.internal_key.clone()), root.lift()?],
559            )),
560            None => Ok(Policy::Key(self.internal_key.clone())),
561        }
562    }
563}
564
565impl<Pk: MiniscriptKey> ForEachKey<Pk> for Tr<Pk> {
566    fn for_each_key<'a, F: FnMut(&'a Pk) -> bool>(&'a self, mut pred: F) -> bool
567    where
568        Pk: 'a,
569    {
570        let script_keys_res = self
571            .iter_scripts()
572            .all(|(_d, ms)| ms.for_each_key(&mut pred));
573        script_keys_res && pred(&self.internal_key)
574    }
575}
576
577impl<P, Q> TranslatePk<P, Q> for Tr<P>
578where
579    P: MiniscriptKey,
580    Q: MiniscriptKey,
581{
582    type Output = Tr<Q>;
583
584    fn translate_pk<T, E>(&self, translate: &mut T) -> Result<Self::Output, E>
585    where
586        T: Translator<P, Q, E>,
587    {
588        let translate_desc = Tr {
589            internal_key: translate.pk(&self.internal_key)?,
590            tree: match &self.tree {
591                Some(tree) => Some(tree.translate_helper(translate)?),
592                None => None,
593            },
594            spend_info: Mutex::new(None),
595        };
596        Ok(translate_desc)
597    }
598}
599
600// Helper function to compute the len of control block at a given depth
601fn control_block_len(depth: u8) -> usize {
602    TAPROOT_CONTROL_BASE_SIZE + (depth as usize) * TAPROOT_CONTROL_NODE_SIZE
603}
604
605// Helper function to get a script spend satisfaction
606// try script spend
607fn best_tap_spend<Pk, S>(
608    desc: &Tr<Pk>,
609    satisfier: S,
610    allow_mall: bool,
611) -> Result<(Vec<Vec<u8>>, Script), Error>
612where
613    Pk: ToPublicKey,
614    S: Satisfier<Pk>,
615{
616    let spend_info = desc.spend_info();
617    // First try the key spend path
618    if let Some(sig) = satisfier.lookup_tap_key_spend_sig() {
619        Ok((vec![sig.to_vec()], Script::new()))
620    } else {
621        // Since we have the complete descriptor we can ignore the satisfier. We don't use the control block
622        // map (lookup_control_block) from the satisfier here.
623        let (mut min_wit, mut min_wit_len) = (None, None);
624        for (depth, ms) in desc.iter_scripts() {
625            let mut wit = if allow_mall {
626                match ms.satisfy_malleable(&satisfier) {
627                    Ok(wit) => wit,
628                    Err(..) => continue, // No witness for this script in tr descriptor, look for next one
629                }
630            } else {
631                match ms.satisfy(&satisfier) {
632                    Ok(wit) => wit,
633                    Err(..) => continue, // No witness for this script in tr descriptor, look for next one
634                }
635            };
636            // Compute the final witness size
637            // Control block len + script len + witnesssize + varint(wit.len + 2)
638            // The extra +2 elements are control block and script itself
639            let wit_size = witness_size(&wit)
640                + control_block_len(depth)
641                + ms.script_size()
642                + varint_len(ms.script_size());
643            if min_wit_len.is_some() && Some(wit_size) > min_wit_len {
644                continue;
645            } else {
646                let leaf_script = (ms.encode(), LeafVersion::TapScript);
647                let control_block = spend_info
648                    .control_block(&leaf_script)
649                    .expect("Control block must exist in script map for every known leaf");
650                wit.push(leaf_script.0.into_bytes()); // Push the leaf script
651                                                      // There can be multiple control blocks for a (script, ver) pair
652                                                      // Find the smallest one amongst those
653                wit.push(control_block.serialize());
654                // Finally, save the minimum
655                min_wit = Some(wit);
656                min_wit_len = Some(wit_size);
657            }
658        }
659        match min_wit {
660            Some(wit) => Ok((wit, Script::new())),
661            None => Err(Error::CouldNotSatisfy), // Could not satisfy all miniscripts inside Tr
662        }
663    }
664}
665
666#[cfg(test)]
667mod tests {
668    use super::*;
669    use crate::ForEachKey;
670
671    #[test]
672    fn test_for_each() {
673        let desc = "tr(acc0, {
674            multi_a(3, acc10, acc11, acc12), {
675              and_v(
676                v:multi_a(2, acc10, acc11, acc12),
677                after(10)
678              ),
679              and_v(
680                v:multi_a(1, acc10, acc11, ac12),
681                after(100)
682              )
683            }
684         })";
685        let desc = desc.replace(&[' ', '\n'][..], "");
686        let tr = Tr::<String>::from_str(&desc).unwrap();
687        // Note the last ac12 only has ac and fails the predicate
688        assert!(!tr.for_each_key(|k| k.starts_with("acc")));
689    }
690}