From 69035327e21e5b89f80a265fff55b89cdbed587f Mon Sep 17 00:00:00 2001 From: Arya Date: Mon, 30 Sep 2024 21:46:31 -0400 Subject: [PATCH] adds revealing tx ids for nullifiers in finalized and non-finalized states --- zebra-state/src/service/check/nullifier.rs | 25 +++--- .../finalized_state/zebra_db/shielded.rs | 54 +++++++++++-- .../src/service/non_finalized_state.rs | 14 +++- .../src/service/non_finalized_state/chain.rs | 76 ++++++++++++++----- 4 files changed, 129 insertions(+), 40 deletions(-) diff --git a/zebra-state/src/service/check/nullifier.rs b/zebra-state/src/service/check/nullifier.rs index 809e78383..d595d1167 100644 --- a/zebra-state/src/service/check/nullifier.rs +++ b/zebra-state/src/service/check/nullifier.rs @@ -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, + chain_nullifiers: &mut HashMap, shielded_data_nullifiers: impl IntoIterator, + 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, + chain_nullifiers: &mut HashMap, shielded_data_nullifiers: impl IntoIterator, ) 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" ); } diff --git a/zebra-state/src/service/finalized_state/zebra_db/shielded.rs b/zebra-state/src/service/finalized_state/zebra_db/shielded.rs index 4bba75b18..db976bf48 100644 --- a/zebra-state/src/service/finalized_state/zebra_db/shielded.rs +++ b/zebra-state/src/service/finalized_state/zebra_db/shielded.rs @@ -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 { + 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 { + 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 { + 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(()) diff --git a/zebra-state/src/service/non_finalized_state.rs b/zebra-state/src/service/non_finalized_state.rs index 08d644550..699933c6a 100644 --- a/zebra-state/src/service/non_finalized_state.rs +++ b/zebra-state/src/service/non_finalized_state.rs @@ -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) } diff --git a/zebra-state/src/service/non_finalized_state/chain.rs b/zebra-state/src/service/non_finalized_state/chain.rs index 6119748c0..0320751ca 100644 --- a/zebra-state/src/service/non_finalized_state/chain.rs +++ b/zebra-state/src/service/non_finalized_state/chain.rs @@ -177,11 +177,11 @@ pub struct ChainInner { // Nullifiers // /// The Sprout nullifiers revealed by `blocks`. - pub(crate) sprout_nullifiers: HashSet, + pub(crate) sprout_nullifiers: HashMap, /// The Sapling nullifiers revealed by `blocks`. - pub(crate) sapling_nullifiers: HashSet, + pub(crate) sapling_nullifiers: HashMap, /// The Orchard nullifiers revealed by `blocks`. - pub(crate) orchard_nullifiers: HashSet, + pub(crate) orchard_nullifiers: HashMap, // 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 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>> for Chain { +impl + UpdateWith<( + &Option>, + &transaction::Hash, + )> for Chain +{ #[instrument(skip(self, joinsplit_data))] fn update_chain_tip_with( &mut self, - joinsplit_data: &Option>, + &(joinsplit_data, revealing_tx_id): &( + &Option>, + &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>> 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>> for Chain { #[instrument(skip(self, joinsplit_data))] fn revert_chain_with( &mut self, - joinsplit_data: &Option>, + &(joinsplit_data, _revealing_tx_id): &( + &Option>, + &transaction::Hash, + ), _position: RevertPosition, ) { if let Some(joinsplit_data) = joinsplit_data { @@ -1980,14 +2002,17 @@ impl UpdateWith>> for Chain { } } -impl UpdateWith>> for Chain +impl UpdateWith<(&Option>, &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_shielded_data, revealing_tx_id): &( + &Option>, + &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_shielded_data, _revealing_tx_id): &( + &Option>, + &transaction::Hash, + ), _position: RevertPosition, ) { if let Some(sapling_shielded_data) = sapling_shielded_data { @@ -2024,11 +2053,14 @@ where } } -impl UpdateWith> for Chain { +impl UpdateWith<(&Option, &transaction::Hash)> for Chain { #[instrument(skip(self, orchard_shielded_data))] fn update_chain_tip_with( &mut self, - orchard_shielded_data: &Option, + &(orchard_shielded_data, revealing_tx_id): &( + &Option, + &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> 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> for Chain { #[instrument(skip(self, orchard_shielded_data))] fn revert_chain_with( &mut self, - orchard_shielded_data: &Option, + (orchard_shielded_data, _revealing_tx_id): &( + &Option, + &transaction::Hash, + ), _position: RevertPosition, ) { if let Some(orchard_shielded_data) = orchard_shielded_data {