From 0d0f9049928cf0a9015ff5355c686b167e9b6b94 Mon Sep 17 00:00:00 2001 From: Arya Date: Fri, 8 Nov 2024 01:47:14 -0500 Subject: [PATCH] Tracks issued_assets map in non-finalized state --- .../tests/snapshot/get_block_template_rpcs.rs | 4 +- zebra-rpc/src/methods/tests/vectors.rs | 8 ++- zebra-state/src/service/check.rs | 1 + zebra-state/src/service/check/issuance.rs | 41 +++++++++++ .../finalized_state/disk_format/shielded.rs | 23 ++++++ .../finalized_state/zebra_db/shielded.rs | 30 +++----- .../src/service/non_finalized_state/chain.rs | 71 +++++++++++-------- zebra-state/src/service/write.rs | 1 + 8 files changed, 126 insertions(+), 53 deletions(-) create mode 100644 zebra-state/src/service/check/issuance.rs diff --git a/zebra-rpc/src/methods/tests/snapshot/get_block_template_rpcs.rs b/zebra-rpc/src/methods/tests/snapshot/get_block_template_rpcs.rs index 8afb7dd312d..c76a97cf3c9 100644 --- a/zebra-rpc/src/methods/tests/snapshot/get_block_template_rpcs.rs +++ b/zebra-rpc/src/methods/tests/snapshot/get_block_template_rpcs.rs @@ -100,8 +100,10 @@ pub async fn test_responses( )), extra_coinbase_data: None, debug_like_zcashd: true, + #[cfg(feature = "internal-miner")] + internal_miner: false, // TODO: Use default field values when optional features are enabled in tests #8183 - ..Default::default() + // ..Default::default() }; // nu5 block height diff --git a/zebra-rpc/src/methods/tests/vectors.rs b/zebra-rpc/src/methods/tests/vectors.rs index 5b5a21e23d0..655c4347c70 100644 --- a/zebra-rpc/src/methods/tests/vectors.rs +++ b/zebra-rpc/src/methods/tests/vectors.rs @@ -1286,8 +1286,10 @@ async fn rpc_getblocktemplate_mining_address(use_p2pkh: bool) { miner_address, extra_coinbase_data: None, debug_like_zcashd: true, + #[cfg(feature = "internal-miner")] + internal_miner: false, // TODO: Use default field values when optional features are enabled in tests #8183 - ..Default::default() + // ..Default::default() }; // nu5 block height @@ -1735,8 +1737,10 @@ async fn rpc_getdifficulty() { miner_address: None, extra_coinbase_data: None, debug_like_zcashd: true, + #[cfg(feature = "internal-miner")] + internal_miner: false, // TODO: Use default field values when optional features are enabled in tests #8183 - ..Default::default() + // ..Default::default() }; // nu5 block height diff --git a/zebra-state/src/service/check.rs b/zebra-state/src/service/check.rs index ced63bfea16..d2eaeff4e5a 100644 --- a/zebra-state/src/service/check.rs +++ b/zebra-state/src/service/check.rs @@ -28,6 +28,7 @@ use crate::service::non_finalized_state::Chain; pub(crate) mod anchors; pub(crate) mod difficulty; +pub(crate) mod issuance; pub(crate) mod nullifier; pub(crate) mod utxo; diff --git a/zebra-state/src/service/check/issuance.rs b/zebra-state/src/service/check/issuance.rs new file mode 100644 index 00000000000..d94b16896aa --- /dev/null +++ b/zebra-state/src/service/check/issuance.rs @@ -0,0 +1,41 @@ +use std::{collections::HashMap, sync::Arc}; + +use zebra_chain::{orchard::AssetBase, transaction::Transaction}; + +use crate::{service::finalized_state::AssetState, ValidateContextError}; + +#[tracing::instrument(skip(issued_assets, transaction, modifier))] +fn modify_non_finalized_chain AssetState>( + issued_assets: &mut HashMap, + transaction: &Arc, + modifier: F, +) { + for (asset_base, change) in AssetState::from_transaction(transaction) { + let new_state = issued_assets + .get(&asset_base) + .map_or(change, |&prev| modifier(prev, change)); + issued_assets.insert(asset_base, new_state); + } +} + +#[tracing::instrument(skip(issued_assets, transaction))] +pub(crate) fn add_to_non_finalized_chain( + issued_assets: &mut HashMap, + transaction: &Arc, +) -> Result<(), ValidateContextError> { + modify_non_finalized_chain(issued_assets, transaction, |prev, change| { + prev.with_change(change) + }); + + Ok(()) +} + +#[tracing::instrument(skip(issued_assets, transaction))] +pub(crate) fn remove_from_non_finalized_chain( + issued_assets: &mut HashMap, + transaction: &Arc, +) { + modify_non_finalized_chain(issued_assets, transaction, |prev, change| { + prev.with_revert(change) + }); +} diff --git a/zebra-state/src/service/finalized_state/disk_format/shielded.rs b/zebra-state/src/service/finalized_state/disk_format/shielded.rs index 074509da3a4..ef9acd16537 100644 --- a/zebra-state/src/service/finalized_state/disk_format/shielded.rs +++ b/zebra-state/src/service/finalized_state/disk_format/shielded.rs @@ -5,6 +5,8 @@ //! [`crate::constants::state_database_format_version_in_code()`] must be incremented //! each time the database format (column, serialization, etc) changes. +use std::sync::Arc; + use bincode::Options; use zebra_chain::{ @@ -13,6 +15,7 @@ use zebra_chain::{ orchard_zsa::{self, BurnItem}, sapling, sprout, subtree::{NoteCommitmentSubtreeData, NoteCommitmentSubtreeIndex}, + transaction::Transaction, }; use crate::service::finalized_state::disk_format::{FromDisk, IntoDisk}; @@ -40,6 +43,17 @@ impl AssetState { } } + /// Adds partial asset states + pub fn with_revert(&self, change: Self) -> Self { + Self { + is_finalized: self.is_finalized && !change.is_finalized, + total_supply: self + .total_supply + .checked_sub(change.total_supply) + .expect("change should be less than total supply"), + } + } + pub fn from_note(is_finalized: bool, note: orchard_zsa::Note) -> (AssetBase, Self) { ( note.asset(), @@ -72,6 +86,15 @@ impl AssetState { pub fn from_burns(burns: Vec) -> impl Iterator { burns.into_iter().map(Self::from_burn) } + + pub fn from_transaction( + transaction: &Arc, + ) -> impl Iterator + '_ { + transaction + .issue_actions() + .flat_map(|action| AssetState::from_notes(action.is_finalized(), action.notes())) + .chain(AssetState::from_burns(transaction.burns())) + } } impl IntoDisk for AssetState { 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 c652b13bd93..2de873498f6 100644 --- a/zebra-state/src/service/finalized_state/zebra_db/shielded.rs +++ b/zebra-state/src/service/finalized_state/zebra_db/shielded.rs @@ -518,28 +518,18 @@ impl DiskWriteBatch { let updated_issued_assets = transaction .iter() - .map(|tx| (tx.issue_actions(), tx.burns())) + .flat_map(AssetState::from_transaction) .fold( HashMap::new(), - |mut issued_assets: HashMap, (actions, burns)| { - for (asset_base, asset_state_change) in actions - .flat_map(|action| { - AssetState::from_notes(action.is_finalized(), action.notes()) - }) - .chain(AssetState::from_burns(burns)) - { - if let Some(asset_state) = issued_assets.get_mut(&asset_base) { - assert!(!asset_state.is_finalized); - *asset_state = asset_state.with_change(asset_state_change); - } else { - let prev_state = issued_assets_cf.zs_get(&asset_base); - let new_state = prev_state.map_or(asset_state_change, |prev| { - prev.with_change(asset_state_change) - }); - issued_assets.insert(asset_base, new_state); - }; - } - + |mut issued_assets: HashMap, + (asset_base, asset_state_change)| { + let new_state = issued_assets + .get(&asset_base) + .copied() + .or_else(|| issued_assets_cf.zs_get(&asset_base)) + .map(|prev| prev.with_change(asset_state_change)) + .unwrap_or(asset_state_change); + issued_assets.insert(asset_base, new_state); issued_assets }, ); diff --git a/zebra-state/src/service/non_finalized_state/chain.rs b/zebra-state/src/service/non_finalized_state/chain.rs index d0ce3eee904..ccd857508f7 100644 --- a/zebra-state/src/service/non_finalized_state/chain.rs +++ b/zebra-state/src/service/non_finalized_state/chain.rs @@ -15,22 +15,26 @@ use zebra_chain::{ amount::{Amount, NegativeAllowed, NonNegative}, block::{self, Height}, history_tree::HistoryTree, - orchard, + orchard::{self, AssetBase}, parallel::tree::NoteCommitmentTrees, parameters::Network, primitives::Groth16Proof, sapling, sprout, subtree::{NoteCommitmentSubtree, NoteCommitmentSubtreeData, NoteCommitmentSubtreeIndex}, - transaction::Transaction::*, - transaction::{self, Transaction}, + transaction::{ + self, + Transaction::{self, *}, + }, transparent, value_balance::ValueBalance, work::difficulty::PartialCumulativeWork, }; use crate::{ - request::Treestate, service::check, ContextuallyVerifiedBlock, HashOrHeight, OutputLocation, - TransactionLocation, ValidateContextError, + request::Treestate, + service::{check, finalized_state::AssetState}, + ContextuallyVerifiedBlock, HashOrHeight, OutputLocation, TransactionLocation, + ValidateContextError, }; use self::index::TransparentTransfers; @@ -174,6 +178,10 @@ pub struct ChainInner { pub(crate) orchard_subtrees: BTreeMap>, + /// A map of `issued_assets`, + // TODO: Add reference to ZIP + pub(crate) orchard_issued_assets: HashMap, + // Nullifiers // /// The Sprout nullifiers revealed by `blocks`. @@ -220,30 +228,8 @@ impl Chain { finalized_tip_chain_value_pools: ValueBalance, ) -> Self { let inner = ChainInner { - blocks: Default::default(), - height_by_hash: Default::default(), - tx_loc_by_hash: Default::default(), - created_utxos: Default::default(), - spent_utxos: Default::default(), - sprout_anchors: MultiSet::new(), - sprout_anchors_by_height: Default::default(), - sprout_trees_by_anchor: Default::default(), - sprout_trees_by_height: Default::default(), - sapling_anchors: MultiSet::new(), - sapling_anchors_by_height: Default::default(), - sapling_trees_by_height: Default::default(), - sapling_subtrees: Default::default(), - orchard_anchors: MultiSet::new(), - orchard_anchors_by_height: Default::default(), - orchard_trees_by_height: Default::default(), - orchard_subtrees: Default::default(), - sprout_nullifiers: Default::default(), - sapling_nullifiers: Default::default(), - orchard_nullifiers: Default::default(), - partial_transparent_transfers: Default::default(), - partial_cumulative_work: Default::default(), - history_trees_by_height: Default::default(), chain_value_pools: finalized_tip_chain_value_pools, + ..ChainInner::default() }; let mut chain = Self { @@ -1507,7 +1493,7 @@ impl Chain { &None, &None, #[cfg(feature ="tx-v6")] - &None + &None, ), V5 { inputs, @@ -1588,7 +1574,10 @@ impl Chain { self.update_chain_tip_with(sapling_shielded_data_shared_anchor)?; self.update_chain_tip_with(orchard_shielded_data_vanilla)?; #[cfg(feature = "tx-v6")] - self.update_chain_tip_with(orchard_shielded_data_zsa)?; + { + self.update_chain_tip_with(orchard_shielded_data_zsa)?; + self.update_chain_tip_with(transaction)?; + } } // update the chain value pool balances @@ -1763,6 +1752,9 @@ impl UpdateWith for Chain { 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); + + #[cfg(feature = "tx-v6")] + self.revert_chain_with(transaction, position); } // TODO: move these to the shielded UpdateWith.revert...()? @@ -2117,6 +2109,25 @@ impl UpdateWith>> } } +#[cfg(feature = "tx-v6")] +impl UpdateWith> for Chain { + #[instrument(skip(self, transaction))] + fn update_chain_tip_with( + &mut self, + transaction: &Arc, + ) -> Result<(), ValidateContextError> { + check::issuance::add_to_non_finalized_chain(&mut self.orchard_issued_assets, transaction) + } + + #[instrument(skip(self, transaction))] + fn revert_chain_with(&mut self, transaction: &Arc, _position: RevertPosition) { + check::issuance::remove_from_non_finalized_chain( + &mut self.orchard_issued_assets, + transaction, + ); + } +} + impl UpdateWith> for Chain { fn update_chain_tip_with( &mut self, diff --git a/zebra-state/src/service/write.rs b/zebra-state/src/service/write.rs index acbc5c14ce1..87d3c6e2e6b 100644 --- a/zebra-state/src/service/write.rs +++ b/zebra-state/src/service/write.rs @@ -141,6 +141,7 @@ pub fn write_blocks_from_channels( ) { let mut last_zebra_mined_log_height = None; let mut prev_finalized_note_commitment_trees = None; + // TODO: Add a `prev_issued_assets` here. // Write all the finalized blocks sent by the state, // until the state closes the finalized block channel's sender.