use std::{collections::HashMap, sync::Arc};
use rayon::prelude::*;
use zebra_chain::{
block::{Block, Height},
sprout,
transaction::{Hash as TransactionHash, Transaction, UnminedTx},
};
use crate::{
service::{finalized_state::ZebraDb, non_finalized_state::Chain},
SemanticallyVerifiedBlock, ValidateContextError,
};
#[tracing::instrument(skip(finalized_state, parent_chain, transaction))]
fn sapling_orchard_anchors_refer_to_final_treestates(
finalized_state: &ZebraDb,
parent_chain: Option<&Arc<Chain>>,
transaction: &Arc<Transaction>,
transaction_hash: TransactionHash,
tx_index_in_block: Option<usize>,
height: Option<Height>,
) -> Result<(), ValidateContextError> {
for (anchor_index_in_tx, anchor) in transaction.sapling_anchors().enumerate() {
tracing::debug!(
?anchor,
?anchor_index_in_tx,
?tx_index_in_block,
?height,
"observed sapling anchor",
);
if !parent_chain
.map(|chain| chain.sapling_anchors.contains(&anchor))
.unwrap_or(false)
&& !finalized_state.contains_sapling_anchor(&anchor)
{
return Err(ValidateContextError::UnknownSaplingAnchor {
anchor,
height,
tx_index_in_block,
transaction_hash,
});
}
tracing::debug!(
?anchor,
?anchor_index_in_tx,
?tx_index_in_block,
?height,
"validated sapling anchor",
);
}
if let Some(orchard_shielded_data) = transaction.orchard_shielded_data() {
tracing::debug!(
?orchard_shielded_data.shared_anchor,
?tx_index_in_block,
?height,
"observed orchard anchor",
);
if !parent_chain
.map(|chain| {
chain
.orchard_anchors
.contains(&orchard_shielded_data.shared_anchor)
})
.unwrap_or(false)
&& !finalized_state.contains_orchard_anchor(&orchard_shielded_data.shared_anchor)
{
return Err(ValidateContextError::UnknownOrchardAnchor {
anchor: orchard_shielded_data.shared_anchor,
height,
tx_index_in_block,
transaction_hash,
});
}
tracing::debug!(
?orchard_shielded_data.shared_anchor,
?tx_index_in_block,
?height,
"validated orchard anchor",
);
}
Ok(())
}
#[tracing::instrument(skip(sprout_final_treestates, finalized_state, parent_chain, transaction))]
fn fetch_sprout_final_treestates(
sprout_final_treestates: &mut HashMap<
sprout::tree::Root,
Arc<sprout::tree::NoteCommitmentTree>,
>,
finalized_state: &ZebraDb,
parent_chain: Option<&Arc<Chain>>,
transaction: &Arc<Transaction>,
tx_index_in_block: Option<usize>,
height: Option<Height>,
) {
for (joinsplit_index_in_tx, joinsplit) in transaction.sprout_groth16_joinsplits().enumerate() {
if sprout_final_treestates.contains_key(&joinsplit.anchor) {
continue;
}
let input_tree = parent_chain
.and_then(|chain| chain.sprout_trees_by_anchor.get(&joinsplit.anchor).cloned())
.or_else(|| finalized_state.sprout_tree_by_anchor(&joinsplit.anchor));
if let Some(input_tree) = input_tree {
sprout_final_treestates.insert(joinsplit.anchor, input_tree);
tracing::debug!(
sprout_final_treestate_count = ?sprout_final_treestates.len(),
?joinsplit.anchor,
?joinsplit_index_in_tx,
?tx_index_in_block,
?height,
"observed sprout final treestate anchor",
);
}
}
tracing::trace!(
sprout_final_treestate_count = ?sprout_final_treestates.len(),
?sprout_final_treestates,
?height,
"returning sprout final treestate anchors",
);
}
#[tracing::instrument(skip(sprout_final_treestates, transaction))]
fn sprout_anchors_refer_to_treestates(
sprout_final_treestates: &HashMap<sprout::tree::Root, Arc<sprout::tree::NoteCommitmentTree>>,
transaction: &Arc<Transaction>,
transaction_hash: TransactionHash,
tx_index_in_block: Option<usize>,
height: Option<Height>,
) -> Result<(), ValidateContextError> {
let mut interstitial_trees: HashMap<sprout::tree::Root, Arc<sprout::tree::NoteCommitmentTree>> =
HashMap::new();
let joinsplit_count = transaction.sprout_groth16_joinsplits().count();
for (joinsplit_index_in_tx, joinsplit) in transaction.sprout_groth16_joinsplits().enumerate() {
let input_tree = interstitial_trees
.get(&joinsplit.anchor)
.cloned()
.or_else(|| sprout_final_treestates.get(&joinsplit.anchor).cloned());
tracing::trace!(
?input_tree,
final_lookup = ?sprout_final_treestates.get(&joinsplit.anchor),
interstitial_lookup = ?interstitial_trees.get(&joinsplit.anchor),
interstitial_tree_count = ?interstitial_trees.len(),
?interstitial_trees,
?height,
"looked up sprout treestate anchor",
);
let mut input_tree = match input_tree {
Some(tree) => tree,
None => {
tracing::debug!(
?joinsplit.anchor,
?joinsplit_index_in_tx,
?tx_index_in_block,
?height,
"failed to find sprout anchor",
);
return Err(ValidateContextError::UnknownSproutAnchor {
anchor: joinsplit.anchor,
height,
tx_index_in_block,
transaction_hash,
});
}
};
tracing::debug!(
?joinsplit.anchor,
?joinsplit_index_in_tx,
?tx_index_in_block,
?height,
"validated sprout anchor",
);
if joinsplit_index_in_tx == joinsplit_count - 1 {
continue;
}
let input_tree_inner = Arc::make_mut(&mut input_tree);
for cm in joinsplit.commitments {
input_tree_inner.append(cm)?;
}
interstitial_trees.insert(input_tree.root(), input_tree);
tracing::debug!(
?joinsplit.anchor,
?joinsplit_index_in_tx,
?tx_index_in_block,
?height,
"observed sprout interstitial anchor",
);
}
Ok(())
}
#[tracing::instrument(skip_all)]
pub(crate) fn block_sapling_orchard_anchors_refer_to_final_treestates(
finalized_state: &ZebraDb,
parent_chain: &Arc<Chain>,
semantically_verified: &SemanticallyVerifiedBlock,
) -> Result<(), ValidateContextError> {
semantically_verified
.block
.transactions
.iter()
.enumerate()
.try_for_each(|(tx_index_in_block, transaction)| {
sapling_orchard_anchors_refer_to_final_treestates(
finalized_state,
Some(parent_chain),
transaction,
semantically_verified.transaction_hashes[tx_index_in_block],
Some(tx_index_in_block),
Some(semantically_verified.height),
)
})
}
#[tracing::instrument(skip_all)]
pub(crate) fn block_fetch_sprout_final_treestates(
finalized_state: &ZebraDb,
parent_chain: &Arc<Chain>,
semantically_verified: &SemanticallyVerifiedBlock,
) -> HashMap<sprout::tree::Root, Arc<sprout::tree::NoteCommitmentTree>> {
let mut sprout_final_treestates = HashMap::new();
for (tx_index_in_block, transaction) in
semantically_verified.block.transactions.iter().enumerate()
{
fetch_sprout_final_treestates(
&mut sprout_final_treestates,
finalized_state,
Some(parent_chain),
transaction,
Some(tx_index_in_block),
Some(semantically_verified.height),
);
}
sprout_final_treestates
}
#[tracing::instrument(skip(sprout_final_treestates, block, transaction_hashes))]
pub(crate) fn block_sprout_anchors_refer_to_treestates(
sprout_final_treestates: HashMap<sprout::tree::Root, Arc<sprout::tree::NoteCommitmentTree>>,
block: Arc<Block>,
transaction_hashes: Arc<[TransactionHash]>,
height: Height,
) -> Result<(), ValidateContextError> {
tracing::trace!(
sprout_final_treestate_count = ?sprout_final_treestates.len(),
?sprout_final_treestates,
?height,
"received sprout final treestate anchors",
);
let check_tx_sprout_anchors = |(tx_index_in_block, transaction)| {
sprout_anchors_refer_to_treestates(
&sprout_final_treestates,
transaction,
transaction_hashes[tx_index_in_block],
Some(tx_index_in_block),
Some(height),
)?;
Ok(())
};
if sprout_final_treestates.is_empty() {
block
.transactions
.iter()
.enumerate()
.try_for_each(check_tx_sprout_anchors)
} else {
block
.transactions
.par_iter()
.enumerate()
.try_for_each(check_tx_sprout_anchors)
}
}
#[tracing::instrument(skip_all)]
pub(crate) fn tx_anchors_refer_to_final_treestates(
finalized_state: &ZebraDb,
parent_chain: Option<&Arc<Chain>>,
unmined_tx: &UnminedTx,
) -> Result<(), ValidateContextError> {
sapling_orchard_anchors_refer_to_final_treestates(
finalized_state,
parent_chain,
&unmined_tx.transaction,
unmined_tx.id.mined_id(),
None,
None,
)?;
if unmined_tx.transaction.has_sprout_joinsplit_data() {
let mut sprout_final_treestates = HashMap::new();
fetch_sprout_final_treestates(
&mut sprout_final_treestates,
finalized_state,
parent_chain,
&unmined_tx.transaction,
None,
None,
);
let mut sprout_anchors_result = None;
rayon::in_place_scope_fifo(|s| {
s.spawn_fifo(|_s| {
tracing::trace!(
sprout_final_treestate_count = ?sprout_final_treestates.len(),
?sprout_final_treestates,
"received sprout final treestate anchors",
);
sprout_anchors_result = Some(sprout_anchors_refer_to_treestates(
&sprout_final_treestates,
&unmined_tx.transaction,
unmined_tx.id.mined_id(),
None,
None,
));
});
});
sprout_anchors_result.expect("scope has finished")?;
}
Ok(())
}