Skip to main content

pczt/roles/combiner/
mod.rs

1//! The Combiner role (anyone can execute).
2//!
3//! - Combines several PCZTs that represent the same transaction into a single PCZT.
4
5use alloc::collections::BTreeMap;
6use alloc::vec::Vec;
7
8use crate::Pczt;
9
10pub struct Combiner {
11    pczts: Vec<Pczt>,
12}
13
14impl Combiner {
15    /// Instantiates the Combiner role with the given PCZTs.
16    pub fn new(pczts: Vec<Pczt>) -> Self {
17        Self { pczts }
18    }
19
20    /// Combines the PCZTs.
21    pub fn combine(self) -> Result<Pczt, Error> {
22        self.pczts
23            .into_iter()
24            .try_fold(None, |acc, pczt| match acc {
25                None => Ok(Some(pczt)),
26                Some(acc) => merge(acc, pczt).map(Some),
27            })
28            .transpose()
29            .unwrap_or(Err(Error::NoPczts))
30    }
31}
32
33fn merge(lhs: Pczt, rhs: Pczt) -> Result<Pczt, Error> {
34    // Per-protocol bundles are merged first, because each is only interpretable in the
35    // context of its own global.
36    let transparent = lhs
37        .transparent
38        .merge(rhs.transparent, &lhs.global, &rhs.global)
39        .ok_or(Error::DataMismatch)?;
40    let sapling = lhs
41        .sapling
42        .merge(rhs.sapling, &lhs.global, &rhs.global)
43        .ok_or(Error::DataMismatch)?;
44    let orchard = lhs
45        .orchard
46        .merge(rhs.orchard, &lhs.global, &rhs.global)
47        .ok_or(Error::DataMismatch)?;
48    let ironwood = lhs
49        .ironwood
50        .merge(rhs.ironwood, &lhs.global, &rhs.global)
51        .ok_or(Error::DataMismatch)?;
52
53    // Now that the per-protocol bundles are merged, merge the globals.
54    let global = lhs.global.merge(rhs.global).ok_or(Error::DataMismatch)?;
55
56    Ok(Pczt {
57        global,
58        transparent,
59        sapling,
60        orchard,
61        ironwood,
62    })
63}
64
65/// Merges two values for an optional field together.
66///
67/// Returns `false` if the values cannot be merged.
68pub(crate) fn merge_optional<T: PartialEq>(lhs: &mut Option<T>, rhs: Option<T>) -> bool {
69    match (&lhs, rhs) {
70        // If the RHS is not present, keep the LHS.
71        (_, None) => (),
72        // If the LHS is not present, set it to the RHS.
73        (None, Some(rhs)) => *lhs = Some(rhs),
74        // If both are present and are equal, nothing to do.
75        (Some(lhs), Some(rhs)) if lhs == &rhs => (),
76        // If both are present and are not equal, fail. Here we differ from BIP 174.
77        (Some(_), Some(_)) => return false,
78    }
79
80    // Success!
81    true
82}
83
84/// Merges two maps together.
85///
86/// Returns `false` if the values cannot be merged.
87pub(crate) fn merge_map<K: Ord, V: PartialEq>(
88    lhs: &mut BTreeMap<K, V>,
89    rhs: BTreeMap<K, V>,
90) -> bool {
91    for (key, rhs_value) in rhs.into_iter() {
92        if let Some(lhs_value) = lhs.get_mut(&key) {
93            // If the key is present in both maps, and their values are not equal, fail.
94            // Here we differ from BIP 174.
95            if lhs_value != &rhs_value {
96                return false;
97            }
98        } else {
99            lhs.insert(key, rhs_value);
100        }
101    }
102
103    // Success!
104    true
105}
106
107/// Errors that can occur while combining PCZTs.
108#[derive(Debug)]
109pub enum Error {
110    NoPczts,
111    DataMismatch,
112}