diff --git a/zebra-state/src/service/finalized_state.rs b/zebra-state/src/service/finalized_state.rs index f8c9bade5..57d22493c 100644 --- a/zebra-state/src/service/finalized_state.rs +++ b/zebra-state/src/service/finalized_state.rs @@ -20,6 +20,7 @@ use std::{ }; use zebra_chain::{block, parallel::tree::NoteCommitmentTrees, parameters::Network}; +use zebra_db::transparent::TX_LOC_BY_SPENT_OUT_LOC; use crate::{ constants::{state_database_format_version_in_code, STATE_DATABASE_KIND}, @@ -77,6 +78,7 @@ pub const STATE_COLUMN_FAMILIES_IN_CODE: &[&str] = &[ "tx_loc_by_transparent_addr_loc", "utxo_by_out_loc", "utxo_loc_by_transparent_addr_loc", + TX_LOC_BY_SPENT_OUT_LOC, // Sprout "sprout_nullifiers", "sprout_anchors", diff --git a/zebra-state/src/service/finalized_state/zebra_db/block.rs b/zebra-state/src/service/finalized_state/zebra_db/block.rs index 4dc3a801e..469d83b4f 100644 --- a/zebra-state/src/service/finalized_state/zebra_db/block.rs +++ b/zebra-state/src/service/finalized_state/zebra_db/block.rs @@ -355,6 +355,10 @@ impl ZebraDb { .iter() .map(|(outpoint, _output_loc, utxo)| (*outpoint, utxo.clone())) .collect(); + let out_loc_by_outpoint: HashMap = spent_utxos + .iter() + .map(|(outpoint, out_loc, _utxo)| (*outpoint, *out_loc)) + .collect(); let spent_utxos_by_out_loc: BTreeMap = spent_utxos .into_iter() .map(|(_outpoint, out_loc, utxo)| (out_loc, utxo)) @@ -392,6 +396,7 @@ impl ZebraDb { new_outputs_by_out_loc, spent_utxos_by_outpoint, spent_utxos_by_out_loc, + out_loc_by_outpoint, address_balances, self.finalized_value_pool(), prev_note_commitment_trees, @@ -448,6 +453,7 @@ impl DiskWriteBatch { new_outputs_by_out_loc: BTreeMap, spent_utxos_by_outpoint: HashMap, spent_utxos_by_out_loc: BTreeMap, + out_loc_by_outpoint: HashMap, address_balances: HashMap, value_pool: ValueBalance, prev_note_commitment_trees: Option, @@ -479,12 +485,13 @@ impl DiskWriteBatch { if !finalized.height.is_min() { // Commit transaction indexes self.prepare_transparent_transaction_batch( - db, + zebra_db, network, finalized, &new_outputs_by_out_loc, &spent_utxos_by_outpoint, &spent_utxos_by_out_loc, + &out_loc_by_outpoint, address_balances, )?; diff --git a/zebra-state/src/service/finalized_state/zebra_db/transparent.rs b/zebra-state/src/service/finalized_state/zebra_db/transparent.rs index edfcf509b..e9957c1aa 100644 --- a/zebra-state/src/service/finalized_state/zebra_db/transparent.rs +++ b/zebra-state/src/service/finalized_state/zebra_db/transparent.rs @@ -1,5 +1,6 @@ //! Provides high-level access to database: -//! - unspent [`transparent::Output`]s (UTXOs), and +//! - unspent [`transparent::Output`]s (UTXOs), +//! - spent [`transparent::Output`]s, and //! - transparent address indexes. //! //! This module makes sure that: @@ -37,12 +38,44 @@ use crate::{ }, zebra_db::ZebraDb, }, - BoxError, + BoxError, TypedColumnFamily, }; +/// The name of the transaction hash by spent outpoints column family. +/// +/// This constant should be used so the compiler can detect typos. +pub const TX_LOC_BY_SPENT_OUT_LOC: &str = "tx_loc_by_spent_out_loc"; + +/// The type for reading value pools from the database. +/// +/// This constant should be used so the compiler can detect incorrectly typed accesses to the +/// column family. +pub type TransactionLocationBySpentOutputLocationCf<'cf> = + TypedColumnFamily<'cf, OutputLocation, TransactionLocation>; + impl ZebraDb { + // Column family convenience methods + + /// Returns a typed handle to the transaction location by spent output location column family. + pub(crate) fn tx_loc_by_spent_output_loc_cf( + &self, + ) -> TransactionLocationBySpentOutputLocationCf { + TransactionLocationBySpentOutputLocationCf::new(&self.db, TX_LOC_BY_SPENT_OUT_LOC) + .expect("column family was created when database was created") + } + // Read transparent methods + /// Returns the [`TransactionLocation`] for a transaction that spent the output + /// at the provided [`OutputLocation`], if it is in the finalized state. + #[allow(clippy::unwrap_in_result)] + pub fn tx_loc_by_spent_output_loc( + &self, + output_location: &OutputLocation, + ) -> Option { + self.tx_loc_by_spent_output_loc_cf().zs_get(output_location) + } + /// Returns the [`AddressBalanceLocation`] for a [`transparent::Address`], /// if it is in the finalized state. #[allow(clippy::unwrap_in_result)] @@ -342,14 +375,16 @@ impl DiskWriteBatch { #[allow(clippy::too_many_arguments)] pub fn prepare_transparent_transaction_batch( &mut self, - db: &DiskDb, + zebra_db: &ZebraDb, network: &Network, finalized: &FinalizedBlock, new_outputs_by_out_loc: &BTreeMap, spent_utxos_by_outpoint: &HashMap, spent_utxos_by_out_loc: &BTreeMap, + out_loc_by_outpoint: &HashMap, mut address_balances: HashMap, ) -> Result<(), BoxError> { + let db = &zebra_db.db; let FinalizedBlock { block, height, .. } = finalized; // Update created and spent transparent outputs @@ -371,11 +406,12 @@ impl DiskWriteBatch { let spending_tx_location = TransactionLocation::from_usize(*height, tx_index); self.prepare_spending_transparent_tx_ids_batch( - db, + zebra_db, network, spending_tx_location, transaction, spent_utxos_by_outpoint, + out_loc_by_outpoint, &address_balances, )?; } @@ -531,16 +567,18 @@ impl DiskWriteBatch { /// # Errors /// /// - This method doesn't currently return any errors, but it might in future - #[allow(clippy::unwrap_in_result)] + #[allow(clippy::unwrap_in_result, clippy::too_many_arguments)] pub fn prepare_spending_transparent_tx_ids_batch( &mut self, - db: &DiskDb, + zebra_db: &ZebraDb, network: &Network, spending_tx_location: TransactionLocation, transaction: &Transaction, spent_utxos_by_outpoint: &HashMap, + out_loc_by_outpoint: &HashMap, address_balances: &HashMap, ) -> Result<(), BoxError> { + let db = &zebra_db.db; let tx_loc_by_transparent_addr_loc = db.cf_handle("tx_loc_by_transparent_addr_loc").unwrap(); @@ -569,6 +607,15 @@ impl DiskWriteBatch { AddressTransaction::new(sending_address_location, spending_tx_location); self.zs_insert(&tx_loc_by_transparent_addr_loc, address_transaction, ()); } + + let spent_output_location = out_loc_by_outpoint + .get(&spent_outpoint) + .expect("spent outpoints must already have output locations"); + + let _ = zebra_db + .tx_loc_by_spent_output_loc_cf() + .with_batch_for_writing(self) + .zs_insert(spent_output_location, &spending_tx_location); } Ok(()) diff --git a/zebra-state/src/service/non_finalized_state/chain.rs b/zebra-state/src/service/non_finalized_state/chain.rs index 12ee05287..a390d6c98 100644 --- a/zebra-state/src/service/non_finalized_state/chain.rs +++ b/zebra-state/src/service/non_finalized_state/chain.rs @@ -94,6 +94,15 @@ pub struct ChainInner { /// including those created by earlier transactions or blocks in the chain. pub(crate) spent_utxos: HashSet, + // TODO: + // - Add a field for tracking spending tx ids by spent outpoint + // - Update the field when committing blocks to non-finalized chain + // - Add a read fn for querying tx ids by spent outpoint + // - Add a db format upgrade for indexing spending tx ids (transaction locations) by + // spent outpoints (output locations) in the finalized state + // - Add ReadRequest & ReadResponse variants for querying spending tx ids by + // spent outpoints and handle them in the ReadStateService + // Note commitment trees // /// The Sprout note commitment tree for each anchor.