use std::{collections::HashMap, sync::Arc};
use tracing::trace;
use zebra_chain::transaction::Transaction;
use crate::{
error::DuplicateNullifierError,
service::{
finalized_state::ZebraDb,
non_finalized_state::{Chain, SpendingTransactionId},
},
SemanticallyVerifiedBlock, ValidateContextError,
};
#[allow(unused_imports)]
use crate::service;
#[tracing::instrument(skip(semantically_verified, finalized_state))]
pub(crate) fn no_duplicates_in_finalized_chain(
semantically_verified: &SemanticallyVerifiedBlock,
finalized_state: &ZebraDb,
) -> Result<(), ValidateContextError> {
for nullifier in semantically_verified.block.sprout_nullifiers() {
if finalized_state.contains_sprout_nullifier(nullifier) {
Err(nullifier.duplicate_nullifier_error(true))?;
}
}
for nullifier in semantically_verified.block.sapling_nullifiers() {
if finalized_state.contains_sapling_nullifier(nullifier) {
Err(nullifier.duplicate_nullifier_error(true))?;
}
}
for nullifier in semantically_verified.block.orchard_nullifiers() {
if finalized_state.contains_orchard_nullifier(nullifier) {
Err(nullifier.duplicate_nullifier_error(true))?;
}
}
Ok(())
}
fn find_duplicate_nullifier<'a, NullifierT, FinalizedStateContainsFn, NonFinalizedStateContainsFn>(
revealed_nullifiers: impl IntoIterator<Item = &'a NullifierT>,
finalized_chain_contains: FinalizedStateContainsFn,
non_finalized_chain_contains: Option<NonFinalizedStateContainsFn>,
) -> Result<(), ValidateContextError>
where
NullifierT: DuplicateNullifierError + 'a,
FinalizedStateContainsFn: Fn(&'a NullifierT) -> bool,
NonFinalizedStateContainsFn: Fn(&'a NullifierT) -> bool,
{
for nullifier in revealed_nullifiers {
if let Some(true) = non_finalized_chain_contains.as_ref().map(|f| f(nullifier)) {
Err(nullifier.duplicate_nullifier_error(false))?
} else if finalized_chain_contains(nullifier) {
Err(nullifier.duplicate_nullifier_error(true))?
}
}
Ok(())
}
#[tracing::instrument(skip_all)]
pub(crate) fn tx_no_duplicates_in_chain(
finalized_chain: &ZebraDb,
non_finalized_chain: Option<&Arc<Chain>>,
transaction: &Arc<Transaction>,
) -> Result<(), ValidateContextError> {
find_duplicate_nullifier(
transaction.sprout_nullifiers(),
|nullifier| finalized_chain.contains_sprout_nullifier(nullifier),
non_finalized_chain
.map(|chain| |nullifier| chain.sprout_nullifiers.contains_key(nullifier)),
)?;
find_duplicate_nullifier(
transaction.sapling_nullifiers(),
|nullifier| finalized_chain.contains_sapling_nullifier(nullifier),
non_finalized_chain
.map(|chain| |nullifier| chain.sapling_nullifiers.contains_key(nullifier)),
)?;
find_duplicate_nullifier(
transaction.orchard_nullifiers(),
|nullifier| finalized_chain.contains_orchard_nullifier(nullifier),
non_finalized_chain
.map(|chain| |nullifier| chain.orchard_nullifiers.contains_key(nullifier)),
)?;
Ok(())
}
#[tracing::instrument(skip(chain_nullifiers, shielded_data_nullifiers))]
pub(crate) fn add_to_non_finalized_chain_unique<'block, NullifierT>(
chain_nullifiers: &mut HashMap<NullifierT, SpendingTransactionId>,
shielded_data_nullifiers: impl IntoIterator<Item = &'block NullifierT>,
revealing_tx_id: SpendingTransactionId,
) -> Result<(), ValidateContextError>
where
NullifierT: DuplicateNullifierError + Copy + std::fmt::Debug + Eq + std::hash::Hash + 'block,
{
for nullifier in shielded_data_nullifiers.into_iter() {
trace!(?nullifier, "adding nullifier");
if chain_nullifiers
.insert(*nullifier, revealing_tx_id)
.is_some()
{
Err(nullifier.duplicate_nullifier_error(false))?;
}
}
Ok(())
}
#[tracing::instrument(skip(chain_nullifiers, shielded_data_nullifiers))]
pub(crate) fn remove_from_non_finalized_chain<'block, NullifierT>(
chain_nullifiers: &mut HashMap<NullifierT, SpendingTransactionId>,
shielded_data_nullifiers: impl IntoIterator<Item = &'block NullifierT>,
) where
NullifierT: std::fmt::Debug + Eq + std::hash::Hash + 'block,
{
for nullifier in shielded_data_nullifiers.into_iter() {
trace!(?nullifier, "removing nullifier");
assert!(
chain_nullifiers.remove(nullifier).is_some(),
"nullifier must be present if block was added to chain"
);
}
}