use std::sync::Arc;
use thiserror::Error;
use crate::{
block::Block,
ironwood, orchard, sapling, sprout,
subtree::{NoteCommitmentSubtree, NoteCommitmentSubtreeIndex},
};
#[derive(Clone, Debug, Default, Eq, PartialEq)]
pub struct NoteCommitmentTrees {
pub sprout: Arc<sprout::tree::NoteCommitmentTree>,
pub sapling: Arc<sapling::tree::NoteCommitmentTree>,
pub sapling_subtree: Option<NoteCommitmentSubtree<sapling_crypto::Node>>,
pub orchard: Arc<orchard::tree::NoteCommitmentTree>,
pub orchard_subtree: Option<NoteCommitmentSubtree<orchard::tree::Node>>,
pub ironwood: Arc<ironwood::tree::NoteCommitmentTree>,
pub ironwood_subtree: Option<NoteCommitmentSubtree<ironwood::tree::Node>>,
}
#[derive(Error, Copy, Clone, Debug, Eq, PartialEq, Hash)]
pub enum NoteCommitmentTreeError {
#[error("sprout error: {0}")]
Sprout(#[from] sprout::tree::NoteCommitmentTreeError),
#[error("sapling error: {0}")]
Sapling(#[from] sapling::tree::NoteCommitmentTreeError),
#[error("orchard error: {0}")]
Orchard(#[from] orchard::tree::NoteCommitmentTreeError),
#[error("ironwood error: {0}")]
Ironwood(ironwood::tree::NoteCommitmentTreeError),
}
impl NoteCommitmentTrees {
#[allow(clippy::unwrap_in_result)]
pub fn update_trees_parallel(
&mut self,
block: &Arc<Block>,
) -> Result<(), NoteCommitmentTreeError> {
let block = block.clone();
let height = block
.coinbase_height()
.expect("height was already validated");
let NoteCommitmentTrees {
sprout,
sapling,
orchard,
ironwood,
..
} = self.clone();
let sprout_note_commitments: Vec<_> = block.sprout_note_commitments().cloned().collect();
let sapling_note_commitments: Vec<_> = block.sapling_note_commitments().cloned().collect();
let orchard_note_commitments: Vec<_> = block.orchard_note_commitments().cloned().collect();
let ironwood_note_commitments: Vec<_> =
block.ironwood_note_commitments().cloned().collect();
let mut sprout_result = None;
let mut sapling_result = None;
let mut orchard_result = None;
let mut ironwood_result = None;
rayon::in_place_scope_fifo(|scope| {
if !sprout_note_commitments.is_empty() {
scope.spawn_fifo(|_scope| {
sprout_result = Some(Self::update_sprout_note_commitment_tree(
sprout,
sprout_note_commitments,
));
});
}
if !sapling_note_commitments.is_empty() {
scope.spawn_fifo(|_scope| {
sapling_result = Some(Self::update_sapling_note_commitment_tree(
sapling,
sapling_note_commitments,
));
});
}
if !orchard_note_commitments.is_empty() {
scope.spawn_fifo(|_scope| {
orchard_result = Some(Self::update_orchard_note_commitment_tree(
orchard,
orchard_note_commitments,
));
});
}
if !ironwood_note_commitments.is_empty() {
scope.spawn_fifo(|_scope| {
ironwood_result = Some(Self::update_ironwood_note_commitment_tree(
ironwood,
ironwood_note_commitments,
));
});
}
});
if let Some(sprout_result) = sprout_result {
self.sprout = sprout_result?;
}
if let Some(sapling_result) = sapling_result {
let (sapling, subtree_root) = sapling_result?;
self.sapling = sapling;
self.sapling_subtree =
subtree_root.map(|(idx, node)| NoteCommitmentSubtree::new(idx, height, node));
};
if let Some(orchard_result) = orchard_result {
let (orchard, subtree_root) = orchard_result?;
self.orchard = orchard;
self.orchard_subtree =
subtree_root.map(|(idx, node)| NoteCommitmentSubtree::new(idx, height, node));
};
if let Some(ironwood_result) = ironwood_result {
let (ironwood, subtree_root) = ironwood_result?;
self.ironwood = ironwood;
self.ironwood_subtree =
subtree_root.map(|(idx, node)| NoteCommitmentSubtree::new(idx, height, node));
};
Ok(())
}
fn update_sprout_note_commitment_tree(
mut sprout: Arc<sprout::tree::NoteCommitmentTree>,
sprout_note_commitments: Vec<sprout::NoteCommitment>,
) -> Result<Arc<sprout::tree::NoteCommitmentTree>, NoteCommitmentTreeError> {
let sprout_nct = Arc::make_mut(&mut sprout);
for sprout_note_commitment in sprout_note_commitments {
sprout_nct.append(sprout_note_commitment)?;
}
let _ = sprout_nct.root();
Ok(sprout)
}
#[allow(clippy::unwrap_in_result)]
pub fn update_sapling_note_commitment_tree(
mut sapling: Arc<sapling::tree::NoteCommitmentTree>,
sapling_note_commitments: Vec<sapling::tree::NoteCommitmentUpdate>,
) -> Result<
(
Arc<sapling::tree::NoteCommitmentTree>,
Option<(NoteCommitmentSubtreeIndex, sapling_crypto::Node)>,
),
NoteCommitmentTreeError,
> {
let sapling_nct = Arc::make_mut(&mut sapling);
let subtree_root = sapling_nct.append_batch(&sapling_note_commitments)?;
let _ = sapling_nct.root();
Ok((sapling, subtree_root))
}
#[allow(clippy::unwrap_in_result)]
pub fn update_orchard_note_commitment_tree(
mut orchard: Arc<orchard::tree::NoteCommitmentTree>,
orchard_note_commitments: Vec<orchard::tree::NoteCommitmentUpdate>,
) -> Result<
(
Arc<orchard::tree::NoteCommitmentTree>,
Option<(NoteCommitmentSubtreeIndex, orchard::tree::Node)>,
),
NoteCommitmentTreeError,
> {
let orchard_nct = Arc::make_mut(&mut orchard);
let subtree_root = orchard_nct.append_batch(&orchard_note_commitments)?;
let _ = orchard_nct.root();
Ok((orchard, subtree_root))
}
#[allow(clippy::unwrap_in_result)]
pub fn update_ironwood_note_commitment_tree(
mut ironwood: Arc<ironwood::tree::NoteCommitmentTree>,
ironwood_note_commitments: Vec<ironwood::tree::NoteCommitmentUpdate>,
) -> Result<
(
Arc<ironwood::tree::NoteCommitmentTree>,
Option<(NoteCommitmentSubtreeIndex, ironwood::tree::Node)>,
),
NoteCommitmentTreeError,
> {
let ironwood_nct = Arc::make_mut(&mut ironwood);
let subtree_root = ironwood_nct
.append_batch(&ironwood_note_commitments)
.map_err(NoteCommitmentTreeError::Ironwood)?;
let _ = ironwood_nct.root();
Ok((ironwood, subtree_root))
}
}
#[cfg(test)]
mod tests {
use halo2::pasta::pallas;
use crate::{
block::{Header, Height},
parameters::{Network, NetworkUpgrade::Nu6_3},
serialization::ZcashDeserializeInto,
transaction::{arbitrary::v5_transactions, LockTime, Transaction},
transparent,
};
use super::*;
#[test]
fn parallel_block_update_keeps_orchard_and_ironwood_order_and_assignment() {
let _init_guard = zakura_test::init();
let mut orchard_data = Network::iter()
.flat_map(|network| v5_transactions(network.block_iter()))
.find_map(|transaction| transaction.orchard_shielded_data().cloned())
.expect("test vectors include an Orchard transaction");
let mut ironwood_data = orchard_data.clone();
let mut orchard_actions = orchard_data.actions.as_slice().to_vec();
orchard_actions.truncate(1);
orchard_actions[0].action.cm_x = pallas::Base::from(1);
orchard_data.actions = orchard_actions
.try_into()
.expect("the test bundle has at least one action");
let mut ironwood_actions = ironwood_data.actions.as_slice().to_vec();
ironwood_actions.truncate(1);
ironwood_actions[0].action.cm_x = pallas::Base::from(2);
ironwood_data.actions = ironwood_actions
.try_into()
.expect("the test bundle has at least one action");
let make_transaction =
|orchard_shielded_data: orchard::ShieldedData,
ironwood_shielded_data: ironwood::ShieldedData| {
Arc::new(Transaction::V6 {
network_upgrade: Nu6_3,
lock_time: LockTime::unlocked(),
expiry_height: Height(1),
inputs: Vec::new(),
outputs: Vec::new(),
sapling_shielded_data: None,
orchard_shielded_data: Some(orchard_shielded_data),
ironwood_shielded_data: Some(ironwood_shielded_data),
})
};
let height = Height(123);
let coinbase = Arc::new(Transaction::V6 {
network_upgrade: Nu6_3,
lock_time: LockTime::unlocked(),
expiry_height: height,
inputs: vec![transparent::Input::Coinbase {
height,
data: Vec::new(),
sequence: u32::MAX,
}],
outputs: Vec::new(),
sapling_shielded_data: None,
orchard_shielded_data: None,
ironwood_shielded_data: None,
});
let header: Header = zakura_test::vectors::DUMMY_HEADER
.zcash_deserialize_into()
.expect("dummy header should deserialize");
let block = Arc::new(Block {
header: Arc::new(header),
transactions: vec![
coinbase,
make_transaction(orchard_data.clone(), ironwood_data.clone()),
make_transaction(ironwood_data, orchard_data),
],
});
let orchard_commitments: Vec<_> = block.orchard_note_commitments().copied().collect();
let ironwood_commitments: Vec<_> = block.ironwood_note_commitments().copied().collect();
assert_eq!(
orchard_commitments,
[pallas::Base::from(1), pallas::Base::from(2)]
);
assert_eq!(
ironwood_commitments,
[pallas::Base::from(2), pallas::Base::from(1)]
);
let mut expected_orchard = orchard::tree::NoteCommitmentTree::default();
for commitment in &orchard_commitments {
expected_orchard
.append(*commitment)
.expect("two Orchard commitments fit in an empty tree");
}
let mut expected_ironwood = ironwood::tree::NoteCommitmentTree::default();
for commitment in &ironwood_commitments {
expected_ironwood
.append(*commitment)
.expect("two Ironwood commitments fit in an empty tree");
}
assert_ne!(expected_orchard.root(), expected_ironwood.root());
let mut trees = NoteCommitmentTrees::default();
let unchanged_sprout = trees.sprout.clone();
let unchanged_sapling = trees.sapling.clone();
trees
.update_trees_parallel(&block)
.expect("the mixed-pool block fits in empty trees");
assert_eq!(trees.sprout, unchanged_sprout);
assert_eq!(trees.sapling, unchanged_sapling);
assert_eq!(trees.orchard.root(), expected_orchard.root());
assert_eq!(trees.ironwood.root(), expected_ironwood.root());
assert_eq!(trees.orchard.count(), 2);
assert_eq!(trees.ironwood.count(), 2);
assert_eq!(trees.orchard_subtree, None);
assert_eq!(trees.ironwood_subtree, None);
assert!(!Arc::ptr_eq(&trees.orchard, &trees.ironwood));
}
#[test]
fn orchard_and_ironwood_trees_remain_independent() {
let mut trees = NoteCommitmentTrees::default();
let empty_orchard_root = trees.orchard.root();
let empty_ironwood_root = trees.ironwood.root();
assert_eq!(empty_orchard_root, empty_ironwood_root);
assert!(!Arc::ptr_eq(&trees.orchard, &trees.ironwood));
let (ironwood, ironwood_subtree) =
NoteCommitmentTrees::update_ironwood_note_commitment_tree(
trees.ironwood.clone(),
vec![pallas::Base::from(1)],
)
.expect("one Ironwood commitment fits in an empty tree");
trees.ironwood = ironwood;
assert_eq!(ironwood_subtree, None);
assert_eq!(trees.orchard.root(), empty_orchard_root);
assert_ne!(trees.ironwood.root(), empty_ironwood_root);
let ironwood_root = trees.ironwood.root();
let (orchard, orchard_subtree) = NoteCommitmentTrees::update_orchard_note_commitment_tree(
trees.orchard.clone(),
vec![pallas::Base::from(2)],
)
.expect("one Orchard commitment fits in an empty tree");
trees.orchard = orchard;
assert_eq!(orchard_subtree, None);
assert_eq!(trees.ironwood.root(), ironwood_root);
assert_ne!(trees.orchard.root(), trees.ironwood.root());
}
#[test]
fn orchard_and_ironwood_tree_errors_keep_their_pool_identity() {
let orchard_error =
NoteCommitmentTreeError::from(orchard::tree::NoteCommitmentTreeError::FullTree);
let ironwood_error =
NoteCommitmentTreeError::Ironwood(ironwood::tree::NoteCommitmentTreeError::FullTree);
assert!(matches!(orchard_error, NoteCommitmentTreeError::Orchard(_)));
assert!(matches!(
ironwood_error,
NoteCommitmentTreeError::Ironwood(_)
));
assert_eq!(
ironwood_error.to_string(),
"ironwood error: The note commitment tree is full"
);
}
}