adds revealing tx ids for nullifiers in finalized and non-finalized states
This commit is contained in:
parent
2c6bac8f13
commit
69035327e2
|
@ -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"
|
||||
);
|
||||
}
|
||||
|
|
|
@ -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(())
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
||||
|
|
|
@ -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 {
|
||||
|
|
Loading…
Reference in New Issue