adds revealing tx ids for nullifiers in finalized and non-finalized states

This commit is contained in:
Arya 2024-09-30 21:46:31 -04:00
parent 2c6bac8f13
commit 69035327e2
4 changed files with 129 additions and 40 deletions

View File

@ -1,9 +1,9 @@
//! Checks for nullifier uniqueness.
use std::{collections::HashSet, sync::Arc};
use std::{collections::HashMap, sync::Arc};
use tracing::trace;
use zebra_chain::transaction::Transaction;
use zebra_chain::transaction::{self, Transaction};
use crate::{
error::DuplicateNullifierError,
@ -105,19 +105,22 @@ pub(crate) fn tx_no_duplicates_in_chain(
find_duplicate_nullifier(
transaction.sprout_nullifiers(),
|nullifier| finalized_chain.contains_sprout_nullifier(nullifier),
non_finalized_chain.map(|chain| |nullifier| chain.sprout_nullifiers.contains(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(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(nullifier)),
non_finalized_chain
.map(|chain| |nullifier| chain.orchard_nullifiers.contains_key(nullifier)),
)?;
Ok(())
@ -156,8 +159,9 @@ pub(crate) fn tx_no_duplicates_in_chain(
/// [5]: service::non_finalized_state::Chain
#[tracing::instrument(skip(chain_nullifiers, shielded_data_nullifiers))]
pub(crate) fn add_to_non_finalized_chain_unique<'block, NullifierT>(
chain_nullifiers: &mut HashSet<NullifierT>,
chain_nullifiers: &mut HashMap<NullifierT, transaction::Hash>,
shielded_data_nullifiers: impl IntoIterator<Item = &'block NullifierT>,
revealing_tx_id: transaction::Hash,
) -> Result<(), ValidateContextError>
where
NullifierT: DuplicateNullifierError + Copy + std::fmt::Debug + Eq + std::hash::Hash + 'block,
@ -166,7 +170,10 @@ where
trace!(?nullifier, "adding nullifier");
// reject the nullifier if it is already present in this non-finalized chain
if !chain_nullifiers.insert(*nullifier) {
if chain_nullifiers
.insert(*nullifier, revealing_tx_id)
.is_some()
{
Err(nullifier.duplicate_nullifier_error(false))?;
}
}
@ -200,7 +207,7 @@ where
/// [1]: service::non_finalized_state::Chain
#[tracing::instrument(skip(chain_nullifiers, shielded_data_nullifiers))]
pub(crate) fn remove_from_non_finalized_chain<'block, NullifierT>(
chain_nullifiers: &mut HashSet<NullifierT>,
chain_nullifiers: &mut HashMap<NullifierT, transaction::Hash>,
shielded_data_nullifiers: impl IntoIterator<Item = &'block NullifierT>,
) where
NullifierT: std::fmt::Debug + Eq + std::hash::Hash + 'block,
@ -209,7 +216,7 @@ pub(crate) fn remove_from_non_finalized_chain<'block, NullifierT>(
trace!(?nullifier, "removing nullifier");
assert!(
chain_nullifiers.remove(nullifier),
chain_nullifiers.remove(nullifier).is_some(),
"nullifier must be present if block was added to chain"
);
}

View File

@ -23,7 +23,7 @@ use zebra_chain::{
parallel::tree::NoteCommitmentTrees,
sapling, sprout,
subtree::{NoteCommitmentSubtreeData, NoteCommitmentSubtreeIndex},
transaction::Transaction,
transaction::{self, Transaction},
};
use crate::{
@ -33,7 +33,7 @@ use crate::{
disk_format::RawBytes,
zebra_db::ZebraDb,
},
BoxError,
BoxError, TransactionLocation,
};
// Doc-only items
@ -61,6 +61,42 @@ impl ZebraDb {
self.db.zs_contains(&orchard_nullifiers, &orchard_nullifier)
}
/// Returns the [`transaction::Hash`] of the transaction that revealed
/// the given [`sprout::Nullifier`], if it is revealed in the finalized state.
#[allow(clippy::unwrap_in_result)]
pub fn sprout_revealing_tx_id(
&self,
sprout_nullifier: &sprout::Nullifier,
) -> Option<transaction::Hash> {
let sprout_nullifiers = self.db.cf_handle("sprout_nullifiers").unwrap();
let revealing_tx_location = self.db.zs_get(&sprout_nullifiers, &sprout_nullifier)?;
self.transaction_hash(revealing_tx_location)
}
/// Returns the [`transaction::Hash`] of the transaction that revealed
/// the given [`sapling::Nullifier`], if it is revealed in the finalized state.
#[allow(clippy::unwrap_in_result)]
pub fn sapling_revealing_tx_id(
&self,
sapling_nullifier: &sapling::Nullifier,
) -> Option<transaction::Hash> {
let sapling_nullifiers = self.db.cf_handle("sapling_nullifiers").unwrap();
let revealing_tx_location = self.db.zs_get(&sapling_nullifiers, &sapling_nullifier)?;
self.transaction_hash(revealing_tx_location)
}
/// Returns the [`transaction::Hash`] of the transaction that revealed
/// the given [`orchard::Nullifier`], if it is revealed in the finalized state.
#[allow(clippy::unwrap_in_result)]
pub fn orchard_revealing_tx_id(
&self,
orchard_nullifier: &orchard::Nullifier,
) -> Option<transaction::Hash> {
let orchard_nullifiers = self.db.cf_handle("orchard_nullifiers").unwrap();
let revealing_tx_location = self.db.zs_get(&orchard_nullifiers, &orchard_nullifier)?;
self.transaction_hash(revealing_tx_location)
}
/// Returns `true` if the finalized state contains `sprout_anchor`.
#[allow(dead_code)]
pub fn contains_sprout_anchor(&self, sprout_anchor: &sprout::tree::Root) -> bool {
@ -440,11 +476,12 @@ impl DiskWriteBatch {
db: &DiskDb,
finalized: &FinalizedBlock,
) -> Result<(), BoxError> {
let FinalizedBlock { block, .. } = finalized;
let FinalizedBlock { block, height, .. } = finalized;
// Index each transaction's shielded data
for transaction in &block.transactions {
self.prepare_nullifier_batch(db, transaction)?;
for (tx_index, transaction) in block.transactions.iter().enumerate() {
let tx_loc = TransactionLocation::from_usize(*height, tx_index);
self.prepare_nullifier_batch(db, transaction, tx_loc)?;
}
Ok(())
@ -461,6 +498,7 @@ impl DiskWriteBatch {
&mut self,
db: &DiskDb,
transaction: &Transaction,
transaction_location: TransactionLocation,
) -> Result<(), BoxError> {
let sprout_nullifiers = db.cf_handle("sprout_nullifiers").unwrap();
let sapling_nullifiers = db.cf_handle("sapling_nullifiers").unwrap();
@ -468,13 +506,13 @@ impl DiskWriteBatch {
// Mark sprout, sapling and orchard nullifiers as spent
for sprout_nullifier in transaction.sprout_nullifiers() {
self.zs_insert(&sprout_nullifiers, sprout_nullifier, ());
self.zs_insert(&sprout_nullifiers, sprout_nullifier, transaction_location);
}
for sapling_nullifier in transaction.sapling_nullifiers() {
self.zs_insert(&sapling_nullifiers, sapling_nullifier, ());
self.zs_insert(&sapling_nullifiers, sapling_nullifier, transaction_location);
}
for orchard_nullifier in transaction.orchard_nullifiers() {
self.zs_insert(&orchard_nullifiers, orchard_nullifier, ());
self.zs_insert(&orchard_nullifiers, orchard_nullifier, transaction_location);
}
Ok(())

View File

@ -540,7 +540,7 @@ impl NonFinalizedState {
#[allow(dead_code)]
pub fn best_contains_sprout_nullifier(&self, sprout_nullifier: &sprout::Nullifier) -> bool {
self.best_chain()
.map(|best_chain| best_chain.sprout_nullifiers.contains(sprout_nullifier))
.map(|best_chain| best_chain.sprout_nullifiers.contains_key(sprout_nullifier))
.unwrap_or(false)
}
@ -552,7 +552,11 @@ impl NonFinalizedState {
sapling_nullifier: &zebra_chain::sapling::Nullifier,
) -> bool {
self.best_chain()
.map(|best_chain| best_chain.sapling_nullifiers.contains(sapling_nullifier))
.map(|best_chain| {
best_chain
.sapling_nullifiers
.contains_key(sapling_nullifier)
})
.unwrap_or(false)
}
@ -564,7 +568,11 @@ impl NonFinalizedState {
orchard_nullifier: &zebra_chain::orchard::Nullifier,
) -> bool {
self.best_chain()
.map(|best_chain| best_chain.orchard_nullifiers.contains(orchard_nullifier))
.map(|best_chain| {
best_chain
.orchard_nullifiers
.contains_key(orchard_nullifier)
})
.unwrap_or(false)
}

View File

@ -177,11 +177,11 @@ pub struct ChainInner {
// Nullifiers
//
/// The Sprout nullifiers revealed by `blocks`.
pub(crate) sprout_nullifiers: HashSet<sprout::Nullifier>,
pub(crate) sprout_nullifiers: HashMap<sprout::Nullifier, transaction::Hash>,
/// The Sapling nullifiers revealed by `blocks`.
pub(crate) sapling_nullifiers: HashSet<sapling::Nullifier>,
pub(crate) sapling_nullifiers: HashMap<sapling::Nullifier, transaction::Hash>,
/// The Orchard nullifiers revealed by `blocks`.
pub(crate) orchard_nullifiers: HashSet<orchard::Nullifier>,
pub(crate) orchard_nullifiers: HashMap<orchard::Nullifier, transaction::Hash>,
// Transparent Transfers
// TODO: move to the transparent section
@ -1546,10 +1546,13 @@ impl Chain {
self.update_chain_tip_with(&(inputs, &transaction_hash, spent_outputs))?;
// add the shielded data
self.update_chain_tip_with(joinsplit_data)?;
self.update_chain_tip_with(sapling_shielded_data_per_spend_anchor)?;
self.update_chain_tip_with(sapling_shielded_data_shared_anchor)?;
self.update_chain_tip_with(orchard_shielded_data)?;
self.update_chain_tip_with(&(joinsplit_data, &transaction_hash))?;
self.update_chain_tip_with(&(
sapling_shielded_data_per_spend_anchor,
&transaction_hash,
))?;
self.update_chain_tip_with(&(sapling_shielded_data_shared_anchor, &transaction_hash))?;
self.update_chain_tip_with(&(orchard_shielded_data, &transaction_hash))?;
}
// update the chain value pool balances
@ -1704,10 +1707,17 @@ impl UpdateWith<ContextuallyVerifiedBlock> for Chain {
);
// remove the shielded data
self.revert_chain_with(joinsplit_data, position);
self.revert_chain_with(sapling_shielded_data_per_spend_anchor, position);
self.revert_chain_with(sapling_shielded_data_shared_anchor, position);
self.revert_chain_with(orchard_shielded_data, position);
self.revert_chain_with(&(joinsplit_data, transaction_hash), position);
self.revert_chain_with(
&(sapling_shielded_data_per_spend_anchor, transaction_hash),
position,
);
self.revert_chain_with(
&(sapling_shielded_data_shared_anchor, transaction_hash),
position,
);
self.revert_chain_with(&(orchard_shielded_data, transaction_hash), position);
}
// TODO: move these to the shielded UpdateWith.revert...()?
@ -1939,11 +1949,19 @@ impl
}
}
impl UpdateWith<Option<transaction::JoinSplitData<Groth16Proof>>> for Chain {
impl
UpdateWith<(
&Option<transaction::JoinSplitData<Groth16Proof>>,
&transaction::Hash,
)> for Chain
{
#[instrument(skip(self, joinsplit_data))]
fn update_chain_tip_with(
&mut self,
joinsplit_data: &Option<transaction::JoinSplitData<Groth16Proof>>,
&(joinsplit_data, revealing_tx_id): &(
&Option<transaction::JoinSplitData<Groth16Proof>>,
&transaction::Hash,
),
) -> Result<(), ValidateContextError> {
if let Some(joinsplit_data) = joinsplit_data {
// We do note commitment tree updates in parallel rayon threads.
@ -1951,6 +1969,7 @@ impl UpdateWith<Option<transaction::JoinSplitData<Groth16Proof>>> for Chain {
check::nullifier::add_to_non_finalized_chain_unique(
&mut self.sprout_nullifiers,
joinsplit_data.nullifiers(),
*revealing_tx_id,
)?;
}
Ok(())
@ -1964,7 +1983,10 @@ impl UpdateWith<Option<transaction::JoinSplitData<Groth16Proof>>> for Chain {
#[instrument(skip(self, joinsplit_data))]
fn revert_chain_with(
&mut self,
joinsplit_data: &Option<transaction::JoinSplitData<Groth16Proof>>,
&(joinsplit_data, _revealing_tx_id): &(
&Option<transaction::JoinSplitData<Groth16Proof>>,
&transaction::Hash,
),
_position: RevertPosition,
) {
if let Some(joinsplit_data) = joinsplit_data {
@ -1980,14 +2002,17 @@ impl UpdateWith<Option<transaction::JoinSplitData<Groth16Proof>>> for Chain {
}
}
impl<AnchorV> UpdateWith<Option<sapling::ShieldedData<AnchorV>>> for Chain
impl<AnchorV> UpdateWith<(&Option<sapling::ShieldedData<AnchorV>>, &transaction::Hash)> for Chain
where
AnchorV: sapling::AnchorVariant + Clone,
{
#[instrument(skip(self, sapling_shielded_data))]
fn update_chain_tip_with(
&mut self,
sapling_shielded_data: &Option<sapling::ShieldedData<AnchorV>>,
&(sapling_shielded_data, revealing_tx_id): &(
&Option<sapling::ShieldedData<AnchorV>>,
&transaction::Hash,
),
) -> Result<(), ValidateContextError> {
if let Some(sapling_shielded_data) = sapling_shielded_data {
// We do note commitment tree updates in parallel rayon threads.
@ -1995,6 +2020,7 @@ where
check::nullifier::add_to_non_finalized_chain_unique(
&mut self.sapling_nullifiers,
sapling_shielded_data.nullifiers(),
*revealing_tx_id,
)?;
}
Ok(())
@ -2008,7 +2034,10 @@ where
#[instrument(skip(self, sapling_shielded_data))]
fn revert_chain_with(
&mut self,
sapling_shielded_data: &Option<sapling::ShieldedData<AnchorV>>,
&(sapling_shielded_data, _revealing_tx_id): &(
&Option<sapling::ShieldedData<AnchorV>>,
&transaction::Hash,
),
_position: RevertPosition,
) {
if let Some(sapling_shielded_data) = sapling_shielded_data {
@ -2024,11 +2053,14 @@ where
}
}
impl UpdateWith<Option<orchard::ShieldedData>> for Chain {
impl UpdateWith<(&Option<orchard::ShieldedData>, &transaction::Hash)> for Chain {
#[instrument(skip(self, orchard_shielded_data))]
fn update_chain_tip_with(
&mut self,
orchard_shielded_data: &Option<orchard::ShieldedData>,
&(orchard_shielded_data, revealing_tx_id): &(
&Option<orchard::ShieldedData>,
&transaction::Hash,
),
) -> Result<(), ValidateContextError> {
if let Some(orchard_shielded_data) = orchard_shielded_data {
// We do note commitment tree updates in parallel rayon threads.
@ -2036,6 +2068,7 @@ impl UpdateWith<Option<orchard::ShieldedData>> for Chain {
check::nullifier::add_to_non_finalized_chain_unique(
&mut self.orchard_nullifiers,
orchard_shielded_data.nullifiers(),
*revealing_tx_id,
)?;
}
Ok(())
@ -2049,7 +2082,10 @@ impl UpdateWith<Option<orchard::ShieldedData>> for Chain {
#[instrument(skip(self, orchard_shielded_data))]
fn revert_chain_with(
&mut self,
orchard_shielded_data: &Option<orchard::ShieldedData>,
(orchard_shielded_data, _revealing_tx_id): &(
&Option<orchard::ShieldedData>,
&transaction::Hash,
),
_position: RevertPosition,
) {
if let Some(orchard_shielded_data) = orchard_shielded_data {