use std::{collections::HashMap, sync::Arc};
use tracing::trace;
use zakura_chain::{ironwood, transaction::Transaction};
use crate::{
error::DuplicateNullifierError,
service::{
finalized_state::ZakuraDb,
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: &ZakuraDb,
) -> 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))?;
}
}
for nullifier in semantically_verified.block.ironwood_nullifiers() {
if finalized_state.contains_ironwood_nullifier(nullifier) {
Err(duplicate_ironwood_nullifier_error(nullifier, true))?;
}
}
Ok(())
}
fn find_duplicate_nullifier_with<
'a,
NullifierT,
FinalizedStateContainsFn,
NonFinalizedStateContainsFn,
DuplicateNullifierErrorFn,
>(
revealed_nullifiers: impl IntoIterator<Item = &'a NullifierT>,
finalized_chain_contains: FinalizedStateContainsFn,
non_finalized_chain_contains: Option<NonFinalizedStateContainsFn>,
duplicate_nullifier_error: DuplicateNullifierErrorFn,
) -> Result<(), ValidateContextError>
where
NullifierT: 'a,
FinalizedStateContainsFn: Fn(&'a NullifierT) -> bool,
NonFinalizedStateContainsFn: Fn(&'a NullifierT) -> bool,
DuplicateNullifierErrorFn: Fn(&NullifierT, bool) -> ValidateContextError,
{
for nullifier in revealed_nullifiers {
if let Some(true) = non_finalized_chain_contains.as_ref().map(|f| f(nullifier)) {
Err(duplicate_nullifier_error(nullifier, false))?
} else if finalized_chain_contains(nullifier) {
Err(duplicate_nullifier_error(nullifier, 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,
{
find_duplicate_nullifier_with(
revealed_nullifiers,
finalized_chain_contains,
non_finalized_chain_contains,
|nullifier, in_finalized_state| nullifier.duplicate_nullifier_error(in_finalized_state),
)
}
fn duplicate_ironwood_nullifier_error(
nullifier: &ironwood::Nullifier,
in_finalized_state: bool,
) -> ValidateContextError {
ValidateContextError::DuplicateIronwoodNullifier {
nullifier: *nullifier,
in_finalized_state,
}
}
#[tracing::instrument(skip_all)]
pub(crate) fn tx_no_duplicates_in_chain(
finalized_chain: &ZakuraDb,
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)),
)?;
find_duplicate_nullifier_with(
transaction.ironwood_nullifiers(),
|nullifier| finalized_chain.contains_ironwood_nullifier(nullifier),
non_finalized_chain
.map(|chain| |nullifier| chain.ironwood_nullifiers.contains_key(nullifier)),
duplicate_ironwood_nullifier_error,
)?;
Ok(())
}
fn add_to_non_finalized_chain_unique_with<'block, NullifierT, DuplicateNullifierErrorFn>(
chain_nullifiers: &mut HashMap<NullifierT, SpendingTransactionId>,
shielded_data_nullifiers: impl IntoIterator<Item = &'block NullifierT>,
revealing_tx_id: SpendingTransactionId,
duplicate_nullifier_error: DuplicateNullifierErrorFn,
) -> Result<(), ValidateContextError>
where
NullifierT: Copy + std::fmt::Debug + Eq + std::hash::Hash + 'block,
DuplicateNullifierErrorFn: Fn(&NullifierT, bool) -> ValidateContextError,
{
for nullifier in shielded_data_nullifiers.into_iter() {
trace!(?nullifier, "adding nullifier");
if chain_nullifiers
.insert(*nullifier, revealing_tx_id)
.is_some()
{
Err(duplicate_nullifier_error(nullifier, false))?;
}
}
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,
{
add_to_non_finalized_chain_unique_with(
chain_nullifiers,
shielded_data_nullifiers,
revealing_tx_id,
|nullifier, in_finalized_state| nullifier.duplicate_nullifier_error(in_finalized_state),
)
}
#[tracing::instrument(skip(chain_nullifiers, shielded_data_nullifiers))]
pub(crate) fn add_ironwood_to_non_finalized_chain_unique<'block>(
chain_nullifiers: &mut HashMap<ironwood::Nullifier, SpendingTransactionId>,
shielded_data_nullifiers: impl IntoIterator<Item = &'block ironwood::Nullifier>,
revealing_tx_id: SpendingTransactionId,
) -> Result<(), ValidateContextError> {
add_to_non_finalized_chain_unique_with(
chain_nullifiers,
shielded_data_nullifiers,
revealing_tx_id,
duplicate_ironwood_nullifier_error,
)
}
#[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"
);
}
}